Step to UEFI (291)Cpp UEFI 007 类

C++ 的类可以看作是一种特别的数据类型。基本的结构如下:

Class 类名 {
Private:
   XXXX //公有成员
Public:
  YYYY://私有成员
Protected:
 ZZZZ://保护成员
}

Public 成员相当于 Struct 结构体定义的,可以用 a.x 这种形式访问(public访问权限是全局的);

Private 成员不能直接被类的实体访问,也不能被子类的实体访问,但是可以被类的成员函数访问(private访问权限就是对内不设防,对外设防的);

Protected 成员不能被类的实体访问,但是可以被子类访问,也可以被类的成员函数访问(protected访问权限就是对内不受保护,对外受保护的)

这样设计的目的是:

1、访问权限作用,保护内部资源

(1)private的成员是class内部使用,外部没必要直接访问(读取或赋值),所以干脆在语法上让你看不见

(2)访问权限的保护是一种语法层面的保护,而非硬件上限制访问,硬件做不了这么细致

(3)最终目的也是为了整个程序更安全

2、访问权限作用, 隐藏外部无需关心的细节

(1)将class内部使用而外部绝不会用到的成员变量隐藏起来,以免干扰外部或者不小心被外部修改了

(2)隐藏细节可以降低使用类库的人的难度,调用时只看到对我有用的东西

(3)这个隐藏其实是把class的作者和使用者专业分工,是很有必要的

3、这就是面向对象的封装特性

(1)封装特性的体现之一就是抽象,抽象的一层意思就是隐藏掉不必要的细节

(2)封装特性的体现之一就是组织和保护,便于整个整体和外部更合理的交流

参考:

  1. https://c.biancheng.net/view/2217.html C++类成员的访问权限以及类的封装
  2. https://blog.csdn.net/weixin_39270987/article/details/108669956 C++类详解(public、private、protected)
  3. https://blog.csdn.net/qq_45544223/article/details/107043760 【C++的面向对象】——- 类成员的访问权限

Rapoo V820 VS2019 控制 LED 的代码

Rapoo(雷柏) V820是一款游戏机械键盘,采用雷柏自主机械轴,双色注塑键帽,5个独立游戏G键,全尺寸一体式掌托,USB口109键无冲突,109键可编程,12种背光模式,加厚金属上盖。

它自带了一个颜色控制的程序,经过研究总结出了它的控制方法。使用 USBLyzer 抓取它设置的动作发现应用程序会对设备发送几个 Package。

经过多次实验,确定了2个 Pacakge 是必须的,发送之后键盘会改变LED颜色。第一个是下面代码中的cmdBuffer定义的,第二个是LedBuffer中定义的,所有颜色信息都是通过这个提供给键盘的。LedBuffer[8] 开始是按键的Blue;LedBuffer[140]开始是按键的 Red取值;LedBuffer[272] 开始为 Green 取值。

按键偏移总结如下:

#include <stdio.h>
#include <wchar.h>
#include <string.h>
#include <stdlib.h>

#include "hidapi.h"


int Asc2KeyLEDOffset(char asc) {
	int KeyOffset;
	switch (asc) {
	case '~':
	case '`':
		KeyOffset = 30;
		break;
	case '!':
	case '1':
		KeyOffset = 31;
		break;
	case '@':
	case '2':
		KeyOffset = 32;
		break;
	case '#':
	case '3':
		KeyOffset = 33;
		break;
	case '$':
	case '4':
		KeyOffset = 34;
		break;
	case '%':
	case '5':
		KeyOffset = 35;
		break;
	case '^':
	case '6':
		KeyOffset = 36;
		break;
	case '&':
	case '7':
		KeyOffset = 37;
		break;
	case '*':
	case '8':
		KeyOffset = 38;
		break;
	case '(':
	case '9':
		KeyOffset = 39;
		break;
	case ')':
	case '0':
		KeyOffset = 40;
		break;
	case '_':
	case '-':
		KeyOffset = 41;
		break;
	case '+':
	case '=':
		KeyOffset = 42;
		break;
	case 'Q':
	case 'q':
		KeyOffset = 53;
		break;
	case 'W':
	case 'w':
		KeyOffset = 54;
		break;
	case 'E':
	case 'e':
		KeyOffset = 55;
		break;
	case 'R':
	case 'r':
		KeyOffset = 56;
		break;
	case 'T':
	case 't':
		KeyOffset = 57;
		break;
	case 'Y':
	case 'y':
		KeyOffset = 58;
		break;
	case 'U':
	case 'u':
		KeyOffset = 59;
		break;
	case 'I':
	case 'i':
		KeyOffset = 60;
		break;
	case 'O':
	case 'o':
		KeyOffset = 61;
		break;
	case 'P':
	case 'p':
		KeyOffset = 62;
		break;
	case '{':
	case '[':
		KeyOffset = 63;
		break;
	case '}':
	case ']':
		KeyOffset = 64;
		break;
	case '|':
	case '\\':
		KeyOffset = 65;
		break;
	case 'A':
	case 'a':
		KeyOffset = 75;
		break;
	case 'S':
	case 's':
		KeyOffset = 76;
		break;
	case 'D':
	case 'd':
		KeyOffset = 77;
		break;
	case 'F':
	case 'f':
		KeyOffset = 78;
		break;
	case 'G':
	case 'g':
		KeyOffset = 79;
		break;
	case 'H':
	case 'h':
		KeyOffset = 80;
		break;
	case 'J':
	case 'j':
		KeyOffset = 81;
		break;
	case 'K':
	case 'k':
		KeyOffset = 82;
		break;
	case 'L':
	case 'l':
		KeyOffset = 83;
		break;
	case ':':
	case ';':
		KeyOffset = 84;
		break;
	case '"':
	case '\'':
		KeyOffset = 85;
		break;
	case 'Z':
	case 'z':
		KeyOffset = 98;
		break;
	case 'X':
	case 'x':
		KeyOffset = 99;
		break;
	case 'C':
	case 'c':
		KeyOffset = 100;
		break;
	case 'V':
	case 'v':
		KeyOffset = 101;
		break;
	case 'B':
	case 'b':
		KeyOffset = 102;
		break;
	case 'N':
	case 'n':
		KeyOffset = 103;
		break;
	case 'M':
	case 'm':
		KeyOffset = 104;
		break;
	case '<':
	case ',':
		KeyOffset = 105;
		break;
	case '>':
	case '.':
		KeyOffset = 106;
		break;
	case '?':
	case '/':
		KeyOffset = 107;
		break;
	case ' ':
		KeyOffset = 123;
		break;

	default:
		KeyOffset = 0;
		break;
	}
	return KeyOffset;
}

//#pragma comment (lib,"setupapi.lib")//hidapi所需的lib环境,没有此文件会导致链接错误

// Headers needed for sleeping.
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

// Fallback/example
#ifndef HID_API_MAKE_VERSION
#define HID_API_MAKE_VERSION(mj, mn, p) (((mj) << 24) | ((mn) << 8) | (p))
#endif
#ifndef HID_API_VERSION
#define HID_API_VERSION HID_API_MAKE_VERSION(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH)
#endif

//
// Sample using platform-specific headers
#if defined(__APPLE__) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
#include <hidapi_darwin.h>
#endif

#if defined(_WIN32) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
#include "hidapi_winapi.h"
#endif

#if defined(USING_HIDAPI_LIBUSB) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
#include <hidapi_libusb.h>
#endif
//

int res;

unsigned char cmdBuffer[5] = { 0x05, 0x83, 0x00,  0x00, 0x00 };

unsigned char LedBuffer[1032] = {
0x06, 0x09, 0xac, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

const char* hid_bus_name(hid_bus_type bus_type) {
	static const char* const HidBusTypeName[] = {
		"Unknown",
		"USB",
		"Bluetooth",
		"I2C",
		"SPI",
	};

	if ((int)bus_type < 0)
		bus_type = HID_API_BUS_UNKNOWN;
	if ((int)bus_type >= (int)(sizeof(HidBusTypeName) / sizeof(HidBusTypeName[0])))
		bus_type = HID_API_BUS_UNKNOWN;

	return HidBusTypeName[bus_type];
}

void print_device(struct hid_device_info* cur_dev) {
	printf("Device Found\n  type: %04hx %04hx\n  path: %s\n  serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
	printf("\n");
	printf("  Manufacturer: %ls\n", cur_dev->manufacturer_string);
	printf("  Product:      %ls\n", cur_dev->product_string);
	printf("  Release:      %hx\n", cur_dev->release_number);
	printf("  Interface:    %d\n", cur_dev->interface_number);
	printf("  Usage (page): 0x%hx (0x%hx)\n", cur_dev->usage, cur_dev->usage_page);
	printf("  Bus type: %d (%s)\n", cur_dev->bus_type, hid_bus_name(cur_dev->bus_type));
	printf("\n");
}

void print_hid_report_descriptor_from_device(hid_device* device) {
	unsigned char descriptor[HID_API_MAX_REPORT_DESCRIPTOR_SIZE];
	int res = 0;

	printf("  Report Descriptor: ");
	res = hid_get_report_descriptor(device, descriptor, sizeof(descriptor));
	if (res < 0) {
		printf("error getting: %ls", hid_error(device));
	}
	else {
		printf("(%d bytes)", res);
	}
	for (int i = 0; i < res; i++) {
		if (i % 10 == 0) {
			printf("\n");
		}
		printf("0x%02x, ", descriptor[i]);
	}
	printf("\n");
}

void print_hid_report_descriptor_from_path(const char* path) {
	hid_device* device = hid_open_path(path);
	if (device) {
		print_hid_report_descriptor_from_device(device);
		hid_close(device);
	}
	else {
		printf("  Report Descriptor: Unable to open device by path\n");
	}
}

void print_devices(struct hid_device_info* cur_dev) {
	for (; cur_dev; cur_dev = cur_dev->next) {
		print_device(cur_dev);
	}
}

hid_device* Handle05 = NULL;
hid_device* Handle06 = NULL;

void print_devices_with_descriptor(struct hid_device_info* cur_dev) {
	for (; cur_dev; cur_dev = cur_dev->next) {
		if (cur_dev->vendor_id == 0x24ae) {
			if ((strstr(cur_dev->path, "Col05") != NULL) || (strstr(cur_dev->path, "Col06") != NULL)) {
				print_device(cur_dev);
				print_hid_report_descriptor_from_path(cur_dev->path);

				if (strstr(cur_dev->path, "Col05") != NULL) {
					Handle05 = hid_open_path(cur_dev->path);
					if (!Handle05) {
						printf("unable to open device\n");
						system("pause");
						hid_exit();
						return;
					}
				}
				if (strstr(cur_dev->path, "Col06") != NULL) {
					Handle06 = hid_open_path(cur_dev->path);
					if (!Handle06) {
						printf("unable to open device\n");
						system("pause");
						hid_exit();
						return;
					}
				}



			}

		}
	}
}

void LightAKey(hid_device* Handle1, hid_device* Handle2, char c, UINT8 value)
{
	res = hid_send_feature_report(Handle1, cmdBuffer, 5);
	if (res < 0) {
		printf("Unable to write() Handle05: %ls\n", hid_error(Handle1));
	}
	LedBuffer[Asc2KeyLEDOffset(c)] = value;
	res = hid_send_feature_report(Handle2, LedBuffer, sizeof(LedBuffer));
	if (res < 0) {
		printf("Unable to write(): %ls Handle06\n", hid_error(Handle2));
	}

}
int main(int argc, char* argv[])
{
	(void)argc;
	(void)argv;

	struct hid_device_info* devs;

	printf("hidapi test/example tool. Compiled with hidapi version %s, runtime version %s.\n", HID_API_VERSION_STR, hid_version_str());
	if (HID_API_VERSION == HID_API_MAKE_VERSION(hid_version()->major, hid_version()->minor, hid_version()->patch)) {
		printf("Compile-time version matches runtime version of hidapi.\n\n");
	}
	else {
		printf("Compile-time version is different than runtime version of hidapi.\n]n");
	}

	if (hid_init())
		return -1;

#if defined(__APPLE__) && HID_API_VERSION >= HID_API_MAKE_VERSION(0, 12, 0)
	// To work properly needs to be called before hid_open/hid_open_path after hid_init.
	// Best/recommended option - call it right after hid_init.
	hid_darwin_set_open_exclusive(0);
#endif

	devs = hid_enumerate(0x0, 0x0);

	print_devices_with_descriptor(devs);
	hid_free_enumeration(devs);

	/*
	res = hid_send_feature_report(Handle05, cmdBuffer, 5);
	if (res < 0) {
		printf("Unable to write() Handle05: %ls\n", hid_error(Handle05));
	}
	LedBuffer[Asc2KeyLEDOffset('a')] = 200;
	res = hid_send_feature_report(Handle06, LedBuffer, sizeof(LedBuffer));
	if (res < 0) {
		printf("Unable to write(): %ls Handle06\n", hid_error(Handle06));
	}
	system("pause");
	*/

	LightAKey(Handle05, Handle06, 'l', 200);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'a', 200);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'b', 200);
	Sleep(800);
	LightAKey(Handle05, Handle06, '-', 200);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'z', 200);
	Sleep(800);
	LightAKey(Handle05, Handle06, '.', 200);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'c', 200);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'o', 200);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'm', 200);
	Sleep(800);

	LightAKey(Handle05, Handle06, 'm', 0);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'o', 0);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'c', 0);
	Sleep(800);
	LightAKey(Handle05, Handle06, '.', 0);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'z', 0);
	Sleep(800);
	LightAKey(Handle05, Handle06, '-', 0);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'b', 0);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'a', 0);
	Sleep(800);
	LightAKey(Handle05, Handle06, 'l', 0);

	hid_close(Handle05);
	hid_close(Handle06);





	/* Free static HIDAPI objects. */
	hid_exit();
	 
#ifdef _WIN32
	system("pause");
#endif

	return 0;
}

DCode/DMU

DMU/DCode

前面提到了 PCode/Punit,这次介绍另外的 DCode/ DMU(Die Management Unit)。 这个 IP 是负责 CPU Die 的功耗的(包括大核和小核)。它会控制休眠和工作时的功耗,温度管理,以及 IccMax。同时会参与 Reset 动作。

和前面的类似,PUnit 上面跑的 Firmware 叫做 PCode。

PCode/PUnit

现代处理器变得越来越复杂,唯一不变的是:性能越强需要的功耗越大。为此,Intel 处理器专门引入了一个控制CPU电力消耗的部件:P-Unit。

P-Unit 是 “P’ower Management ‘Unit’ for the SOC-N(North)”的缩写。主要功能是负责 SoC-N 上面的 IP 供电/温度。这里的 SoC-N 可以理解为之前的 North Bridge , 包括 Memory Controller ,但是不包括 Graphic(目前 Intel 平台这部分独立成一个 Die)。

P-Uint 不会负责 SoC-S(South,相当于 之前的 South Bridge)上面的设备,这个是 PMC 的工作。此外 PUinit 还负责各种重启,MCA和Crashlog流程,能够帮助解决 HW 的Bug。

Intel 处理器上的P-Unit 核心是一个 Xtensa 处理器,负责运行 PCode (Power Managerment Firmware)。 PCode 是通过 mFIT 集成在 IFWI 中的固件。

USB 键盘整蛊专家

这是一个能够让你整蛊别人的设备,将它串联到对方的USB 键盘和主机之间后,你可以用过手机上的 Blinker蓝牙连接到这个设备,然后在 Blinker中输出的信息就会出现在对方的电脑上。

硬件设计如下:

  1. 左上角是预留的调试烧录接口,通过这个接口可以进行烧录,同时  Debug信息也可以通过这个接口发送到 PC端;
  2. 左下角是这个的设计核心,它是一个 ESP32-S3 芯片,通过它实现USB Host 和蓝牙通讯的功能;
  3. ESP32-S3工作电压是 3.3V,这里使用 TLV1117 来实现,这个芯片外围只需要2个 1uf电容
  4. 右下角是 Ch9329 芯片,它是一个 HID 转串口芯片,在这个设计中用于实现USB键盘的功能。

CH9326是一款HID转串口免驱芯片。CH9326支持双向数据传输,用于接收串口数据,并按照HID类设备规范,将数据打包通过USB口上传给计算机,或者从计算机接收符合HID类设备的USB数据包,并从串口进行发送。通过提供的上位机软件,用户也可自行配置芯片的VID、PID,以及各种字符串描述符。芯片是 SOP16 封装,容易焊接。

设计的基本思路是:ESP32-S3 负责解析USB键盘数据,用这种方法来获得按键信息。之后,将获得的信息通过串口发送给CH9326, 然后 Ch9326会实现PC端的模拟按键。可以看到,这个设备对于PC端来说是透明的。之后,可以使用  Blinker 的蓝牙功能连接手机和这个设备,之后就可以从手机端发送字符给PC。

PCB 设计如下:

成品如下(彩色丝印,镀金工艺,背面是设计的一个二维码):

编写 Arduino 代码如下:

#include &lt;elapsedMillis.h>
#include &lt;usb/usb_host.h>
#include "show_desc.hpp"
#include "usbhhelp.hpp"

#define BLINKER_PRINT Serial
#define BLINKER_BLE
#include &lt;Blinker.h>

//键盘数据
char keypress[]  = {0x57, 0xAB, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10};

bool isKeyboard = false;
bool isKeyboardReady = false;
uint8_t KeyboardInterval;
bool isKeyboardPolling = false;
elapsedMillis KeyboardTimer;

const size_t KEYBOARD_IN_BUFFER_SIZE = 8;
usb_transfer_t *KeyboardIn = NULL;

// 将 Buffer 指向的内容,size 长度,计算 checksum 之后发送到Serial2
void SendData(byte *Buffer, byte size) {
  byte sum = 0;
  for (int i = 0; i &lt; size - 1; i++) {
    Serial2.write(*Buffer);
    sum = sum + *Buffer;
    Buffer++;
  }
  *Buffer = sum;
  Serial2.write(sum);
}
// 将ASCII 字符转化为 HID Scancode值
byte Asc2Scancode(byte Asc, boolean *shift) {
  if ((Asc >= 'a') &amp;&amp; (Asc &lt;= 'z')) {
    *shift = false;
    return (Asc - 'a' + 0x04);
  }
  if ((Asc >= 'A') &amp;&amp; (Asc &lt;= 'Z')) {
    *shift = true;
    return (Asc - 'A' + 0x04);
  }
  if ((Asc >= '1') &amp;&amp; (Asc &lt;= '0')) {
    *shift = false;
    return (Asc - '0' + 0x1E);
  }
  if (Asc == '>') {
    *shift = true;
    return (0x37);
  }
  if (Asc == '.') {
    *shift = false;
    return (0x37);
  }
  if (Asc == '_') {
    *shift = true;
    return (0x2D);
  }
  if (Asc == '-') {
    *shift = false;
    return (0x2D);
  }
  return 0;
}

// 如果未绑定的组件被触发,则会执行其中内容
// 输入框输入都会在这里处理
void dataRead(const String &amp; data)
{
  BLINKER_LOG("Blinker readString: ", data);
  boolean shift;
  byte scanCode;
  for (int i = 0; i &lt; data.length(); i++) {
    BLINKER_LOG("Key In", data.charAt(1));
    // 将收到的 ASCII 转为 ScanCode
    scanCode = Asc2Scancode(data.charAt(i), &amp;shift);
    // 一些按键当有 Shift 按下时会发生转义
    if (scanCode != 0) {
      if (shift == true) {
        keypress[5] = 0x02;
      }
      BLINKER_LOG("Scancode", scanCode);
      // 填写要发送的 ScanCode
      keypress[7] = scanCode;
      SendData((byte*)keypress, sizeof(keypress));
      delay(10);
      keypress[5] = 0x00; keypress[7] = 0;
      SendData((byte*)keypress, sizeof(keypress));
      delay(10);
    }
  }
}

void keyboard_transfer_cb(usb_transfer_t *transfer)
{
  if (Device_Handle == transfer->device_handle) {
    isKeyboardPolling = false;
    if (transfer->status == 0) {
      if (transfer->actual_num_bytes == 8) {
        uint8_t *const p = transfer->data_buffer;
        ESP_LOGI("", "HID report: %02x %02x %02x %02x %02x %02x %02x %02x",
                 p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
        // USB Host 解析得到的数据,传输给PC
        //
        memcpy(&amp;keypress[5],p,transfer->actual_num_bytes);
        SendData((byte*)keypress, sizeof(keypress));
      }
      else {
        ESP_LOGI("", "Keyboard boot hid transfer too short or long");
      }
    }
    else {
      ESP_LOGI("", "transfer->status %d", transfer->status);
    }
  }
}

void check_interface_desc_boot_keyboard(const void *p)
{
  const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p;

  if ((intf->bInterfaceClass == USB_CLASS_HID) &amp;&amp;
      (intf->bInterfaceSubClass == 1) &amp;&amp;
      (intf->bInterfaceProtocol == 1)) {
    isKeyboard = true;
    ESP_LOGI("", "Claiming a boot keyboard!");
    esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle,
                    intf->bInterfaceNumber, intf->bAlternateSetting);
    if (err != ESP_OK) ESP_LOGI("", "usb_host_interface_claim failed: %x", err);
  }
}

void prepare_endpoint(const void *p)
{
  const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p;
  esp_err_t err;

  // must be interrupt for HID
  if ((endpoint->bmAttributes &amp; USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_INT) {
    ESP_LOGI("", "Not interrupt endpoint: 0x%02x", endpoint->bmAttributes);
    return;
  }
  if (endpoint->bEndpointAddress &amp; USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) {
    err = usb_host_transfer_alloc(KEYBOARD_IN_BUFFER_SIZE, 0, &amp;KeyboardIn);
    if (err != ESP_OK) {
      KeyboardIn = NULL;
      ESP_LOGI("", "usb_host_transfer_alloc In fail: %x", err);
      return;
    }
    KeyboardIn->device_handle = Device_Handle;
    KeyboardIn->bEndpointAddress = endpoint->bEndpointAddress;
    KeyboardIn->callback = keyboard_transfer_cb;
    KeyboardIn->context = NULL;
    isKeyboardReady = true;
    KeyboardInterval = endpoint->bInterval;
    ESP_LOGI("", "USB boot keyboard ready");
  }
  else {
    ESP_LOGI("", "Ignoring interrupt Out endpoint");
  }
}

void show_config_desc_full(const usb_config_desc_t *config_desc)
{
  // Full decode of config desc.
  const uint8_t *p = &amp;config_desc->val[0];
  static uint8_t USB_Class = 0;
  uint8_t bLength;
  for (int i = 0; i &lt; config_desc->wTotalLength; i += bLength, p += bLength) {
    bLength = *p;
    if ((i + bLength) &lt;= config_desc->wTotalLength) {
      const uint8_t bDescriptorType = *(p + 1);
      switch (bDescriptorType) {
        case USB_B_DESCRIPTOR_TYPE_DEVICE:
          ESP_LOGI("", "USB Device Descriptor should not appear in config");
          break;
        case USB_B_DESCRIPTOR_TYPE_CONFIGURATION:
          show_config_desc(p);
          break;
        case USB_B_DESCRIPTOR_TYPE_STRING:
          ESP_LOGI("", "USB string desc TBD");
          break;
        case USB_B_DESCRIPTOR_TYPE_INTERFACE:
          USB_Class = show_interface_desc(p);
          check_interface_desc_boot_keyboard(p);
          break;
        case USB_B_DESCRIPTOR_TYPE_ENDPOINT:
          show_endpoint_desc(p);
          if (isKeyboard &amp;&amp; KeyboardIn == NULL) prepare_endpoint(p);
          break;
        case USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER:
          // Should not be config config?
          ESP_LOGI("", "USB device qual desc TBD");
          break;
        case USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION:
          // Should not be config config?
          ESP_LOGI("", "USB Other Speed TBD");
          break;
        case USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER:
          // Should not be config config?
          ESP_LOGI("", "USB Interface Power TBD");
          break;
        case 0x21:
          if (USB_Class == USB_CLASS_HID) {
            show_hid_desc(p);
          }
          break;
        default:
          ESP_LOGI("", "Unknown USB Descriptor Type: 0x%x", bDescriptorType);
          break;
      }
    }
    else {
      ESP_LOGI("", "USB Descriptor invalid");
      return;
    }
  }
}

void setup()
{
  // 初始化调试串口
  Serial.begin(115200);
  // 初始 CH9329 串口
  Serial2.begin(9600, SERIAL_8N1, 14, 13, false, 1000, 112);

  //Serial2.begin(9600);
#if defined(BLINKER_PRINT)
  BLINKER_DEBUG.stream(BLINKER_PRINT);
#endif

  // 初始化blinker
  Blinker.begin();
  Blinker.attachData(dataRead);
  usbh_setup(show_config_desc_full);
}

void loop()
{
  usbh_task();
  Blinker.run();

  if (isKeyboardReady &amp;&amp; !isKeyboardPolling &amp;&amp; (KeyboardTimer > KeyboardInterval)) {
    KeyboardIn->num_bytes = 8;
    esp_err_t err = usb_host_transfer_submit(KeyboardIn);
    if (err != ESP_OK) {
      ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err);
    }
    isKeyboardPolling = true;
    KeyboardTimer = 0;
  }

  while (Serial.available()) {
    char c = Serial.read();
    if (c == 'q') {
      boolean shift = false;
      // 填写要发送的 ScanCode
      keypress[5] = 0x08;
      SendData((byte*)keypress, sizeof(keypress));
      delay(20);
      keypress[5] = 0;
      SendData((byte*)keypress, sizeof(keypress));
    }
    Serial.print(c);
  }
}

将板卡装入外壳后的照片:

完整的代码:

电路图和PCB 下载:

Step to UEFI (290)Cpp UEFI 006 抄一个 Print

前面编写测试代码的过程中,总感觉没有 Print 直接输出来的顺手,于是研究了一下 Print 的实现。基本原理是,对变量格式化后输出到一个 字符串Buffer 中,然后直接输出Buffer。

首先,编写一个测试的 CPP:

#include <UEFI/UEFI.h>
#include <type_traits>
#include "print.h"

EFI_SYSTEM_TABLE* gST;

EFI_STATUS
efi_main(EFI_HANDLE /*image*/, EFI_SYSTEM_TABLE* systemTable)
{
	gST=systemTable;
	Print(u"%d\n",2024);
	return EFI_SUCCESS;
}

其中使用了 Print.h 头文件,定义如下:

UINTN
EFIAPI
Print (
  IN const CHAR16 *Format,
  ...
);

接下来编写Print.cpp,关键代码来自\MdePkg\Library\UefiLib\UefiLibPrint.c

UINTN
EFIAPI
Print (
  IN CONST CHAR16  *Format,
  ...
  )
{
  VA_LIST  Marker;
  UINTN    Return;

  VA_START (Marker, Format);

  Return = InternalPrint (Format, gST->ConOut, Marker);

  VA_END (Marker);

  return Return;
}

其中的InternalPrint() 函数有较大改动,直接在函数中开了一个内存用于当作 Buffer (CharBuffer[]),不需要AllocatePool()动态分配。

UINTN
InternalPrint (
  IN  CONST CHAR16                     *Format,
  IN  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *Console,
  IN  VA_LIST                          Marker
  )
{
  EFI_STATUS  Status;
  UINTN       Return;
  CHAR16      *Buffer;
  UINTN       BufferSize;
  CHAR16      CharBuffer[320];

  ASSERT (Format != NULL);
  ASSERT (((UINTN)Format & BIT0) == 0);
  ASSERT (Console != NULL);

  BufferSize = 320;

  Buffer = &CharBuffer[0];
  ASSERT (Buffer != NULL);

  Return = UnicodeVSPrint (Buffer, BufferSize, Format, Marker);

  if ((Console != NULL) && (Return > 0)) {
    //
    // To be extra safe make sure Console has been initialized
    //
    Status = Console->OutputString (Console, Buffer);
  }

  return Return;
}

接下来编写编译的批处理,可以看到最主要是编译生成 test8.obj 和 print.obj ,最后将二者Link 在一起即可:

set Target=test8
cl /c /I"C:\\BuildBs\\CppStudy\\Cpp\\UEFI-CPP-headers" /Zc:wchar_t- /Zi /W4 /WX- /diagnostics:column /Od /D _UNICODE /D UNICODE /D HAVE_USE_MS_ABI /D GNU_EFI_USE_EXTERNAL_STDARG /D _UNICODE /D UNICODE /Gm- /MDd /GS- /fp:precise /permissive- /Zc:wchar_t /Zc:forScope /Zc:inline /std:c++17 /Fo"C:\\BuildBs\\CppStudy\\Cpp\\" /FAsc /Fd"C:\\BuildBs\\CppStudy\\Cpp\\vc142.pdb" /external:W4 /Gd /TP /wd4229 /FC /errorReport:prompt /Oi- %Target%.cpp
cl /c /I"C:\\BuildBs\\CppStudy\\Cpp\\UEFI-CPP-headers" /Zc:wchar_t- /Zi /W4 /WX- /diagnostics:column /Od /D _UNICODE /D UNICODE /D HAVE_USE_MS_ABI /D GNU_EFI_USE_EXTERNAL_STDARG /D _UNICODE /D UNICODE /Gm- /MDd /GS- /fp:precise /permissive- /Zc:wchar_t /Zc:forScope /Zc:inline /std:c++17 /Fo"C:\\BuildBs\\CppStudy\\Cpp\\" /FAsc /Fd"C:\\BuildBs\\CppStudy\\Cpp\\vc142.pdb" /external:W4 /Gd /TP /wd4229 /FC /errorReport:prompt /Oi- print.cpp

if %errorlevel% NEQ 0 goto EndError

link "/OUT:C:\\BuildBs\\CppStudy\\Cpp\\%Target%.efi" /VERBOSE /INCREMENTAL:NO "/LIBPATH:C:\\BuildBs\\CppStudy\\Cpp\\" libcmtd.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /NODEFAULTLIB /MANIFEST:NO /DEBUG:FULL "/PDB:C:\\BuildBs\\CppStudy\\Cpp\\bootx64.pdb" /SUBSYSTEM:EFI_APPLICATION /OPT:REF /TLBID:1 "/ENTRY:efi_main" /NXCOMPAT:NO "/IMPLIB:C:\\BuildBs\\CppStudy\\Cpp\\bootx64.lib" /MACHINE:X64 "C:\\BuildBs\\CppStudy\\Cpp\\%Target%.obj" "C:\\BuildBs\\CppStudy\\Cpp\\print.obj"
copy /y %Target%.efi Emulator\
:EndError

最后将Print.cpp、Test8.CPP和g8.bat 放在一起,即可编译。

模拟器运行结果如下:

完整代码如下,需要注意的是编译批处理内部使用了绝对路径,如果想实验,最好按照之前的文章架设同样名称的目录测试。

基于Ch554 制作的USB喇叭

很早之前使用 Arduino Pro Micro 实现过USB耳机转接器,这次尝试使用 WCH 的 Ch554 来实现(实际上可以使用 更见偏移的 Ch552 来实现,但是因为 Ch552 有烧写次数限制,所以最终是在 Ch554上进行开发)。

无需过多了解 USB Audio的相关知识,所作的工作基本上只有:通过描述符报告自己是一个USB Audio 设备。之后 Windows 就会发送 48Khz 16位双声道的采样数据给设备(如果想了解更多,推荐去USB中文网阅读相关内容)。我们在设备响应的 OUTPUT 端点上即可收到数据。

需要特别注意的是,代码中有一个向HOST 汇报当前支持采样率的描述符。这里申明了2个采样率:22,050Hz和48000Hz。

  0x0E,  	//Size of the descriptor, in bytes  
  0x24,  	//CS_INTERFACE Descriptor Type  
  0x02,  	//FORMAT_TYPE descriptor subtype  
  0x01,  	//FORMAT_TYPE_I  
  0x02,  	//Indicates the number of physical channels in the audio data stream.  
  0x02,  	//The number of bytes occupied by one audio subframe. Can be 1, 2, 3 or 4.  
  0x10,  	//The number of effectively used bits from the available bits in an audio subframe.  
  0x02,  	//Indicates how the sampling frequency can be programmed:  
  0x22,0x56,0x00, //	  Sampling frequency 1 in Hz for this isochronous data endpoint.  
  0x80,0xBB,0x00, //	  Sampling frequency 2 in Hz for this isochronous data endpoint.  

在工作过程中,Windows会通知当前使用的采样率。

需要注意的是:

  1. 如果你只报告支持 48000的采样率,那么Windows就不会发送 SET_CUR
  2. Windows不支持所有的采样率,我这边实验只支持48000Hz的采样率,换句话,你申明支持 24000或者22050,实际上并不会使用。

具体的数据是下面这样的,可以看到这种同步传输/等时传输的数据和通常的最大区别在于不会有 ACK 信号,相当于HOST 直接丢出来不管对错。

从上面可以看到每个数据之间间隔是1ms,每笔数据 192字节。

对应在代码中会在USBAudioSpeaker.c文件中的Mass_Storage_Out函数进行处理:

void Mass_Storage_Out (void) {
    PWM_CTRL |= bPWM2_OUT_EN;
	for (uint8_t i = 0; i < BOT_EP_Rx_Length; i=i+4){		
		PWM_DATA2 = BOT_Rx_Buf[i+1];
		// Delay for 20833ns
		for (uint16_t j=0;j<51;j++) {
		   __asm__ ("nop\n");
		}
    }
	PWM_CTRL &= (~bPWM2_OUT_EN);
    //Serial0_println("Ending");
	BOT_EP_Tx_ISO_Valid();
}

经过前面的工作,现在能够拿到PC输出的音频数据,接下来的问题就是如何将收到的数据通过喇叭播放出去。这个过程相当于一个 DAC (数字到模拟)的过程。这次选择的方法是:通过 PWM 进行模拟。这是使用的是CH554 芯片,它支持PWM:2 组 PWM 输出,PWM1/PWM2 为 2 路 8 位 。在下图可以看到 P1.5/P3.1/P3.0/P3.4都是可以选择的引脚。代码使用了 P3.4这个引脚。

PWM初始化代码如下,特别注意使用了1分频产生 PWM 信号,我们使用的主频为 24Mhz 5V,因此频率是 24000000/256=93750Hz

  // 打开 PWM2 功能
  PIN_FUNC &= ~(bPWM2_PIN_X);
  // PWM 分频设置
  // 1 分频,这样 PWM 频率为 Fsys/256
  PWM_CK_SE=1;

上述设置之后,直接在 PWM_DATA2 寄存器中填写你要生成的高电平比例即可产生对应的 PWM 信号。对应的代码就在前面提到的void Mass_Storage_Out (void) {} 函数中。此外,使用NOP 指令制作了一个简单的延时,延时 1/48000=20833ns:

// Delay for 20833ns
		for (uint16_t j=0;j<51;j++) {
		   __asm__ ("nop\n");
		}

在编译时,还需要对项目进行如下设置:

  1. 选择 Ch552 Board,前面提到了Ch552和 Ch554 在代码方面是完全兼容的;
  2. 设置USB 为“U148B USB Ram”
  3. Clock Source 为 “24Mhz(internal),5V”

硬件方面非常简单,Ch554最小系统,喇叭接到对应引脚即可:

这是我设计的用于测试 Ch554 和 Ch559 最小开发板。Ch554和Ch559 最小系统外围只需要2个电容即可,两颗芯片相互独立:

完整代码:

电路图:

从上面可以看到,Ch55xduino提供的 USB 框架扩展性不错。Ch554 可以方便的通过 Ch55xduino 实现一个USB Speaker 的功能。目前美中不足的只是音频质量较差(所有看到的人都怀疑这个是一个收音机),后续会持续进行改进。

CH559 Arduino 使用 PWM2 的例子

代码非常简单,根据官方例子移植到 Arduino 完成。使用 P2.5 引脚,Ch55xduino 编译:

#define SetPWMClk(CK_SE) (PWM_CK_SE = CK_SE)                                  //分频,默认时钟Fsys            
#define SetPWMCycle(Cycle) (PWM_CYCLE = Cycle)                                //设置循环周期
#define SetPWM1Dat(dat) (PWM_DATA = dat)                                      //设置PWM输出占空比
#define SetPWM2Dat(dat) (PWM_DATA2 = dat)

/*******************************************************************************
* Function Name  : InitPWM2(UINT8 polar)
* Description    : PWM初始化函数
* Input          : polar=0选择默认低电平,高电平输出有效;
                   polar=1选择默认高电平,低电平输出有效;
* Output         : None
* Return         : None
*******************************************************************************/
void  InitPWM2(uint8_t polar)
{
    PWM_CTRL &= ~bPWM_CLR_ALL;                                                //清空FIFO和计数                                                      
    PWM_CTRL &= ~bPWM_MOD_MFM;
    PWM_CTRL |=  bPWM_IE_END;                                                  //使能PWM计数周期完成中断
    PWM_CTRL |= bPWM2_OUT_EN;                                                 //PWM2输出使能  
    PWM_CTRL  |= bPWM_IF_END;                                                 //清除所有的中断标志
    if(polar){
        PWM_CTRL |= bPWM2_POLAR;                                              //低电平有效
    }
    else{
        PWM_CTRL &= ~bPWM2_POLAR;                                             //高电平有效  
    }     
}

void setup() {
  // put your setup code here, to run once:
    SetPWMClk(12);          //设置PWM1&2的时钟分频系数为12
    InitPWM2(0);            //PWM2初始化,高电平有效
    SetPWMCycle(1000);      //设置循环周期100
    SetPWM2Dat(50);         //PWM1占空比设置50/100
}

void loop() {
  // put your main code here, to run repeatedly:
}

Step to UEFI (289)Cpp UEFI 005 C++函数默认参数

C++ 定义函数时可以直接给形参指定默认值,如果调用函数没有给形参赋值,那就直接使用默认值。这个功能非常容易理解。编写如下代码进行验证:

#include <UEFI/UEFI.h>
#include <type_traits>

EFI_SYSTEM_TABLE* gSystemTable;

void printInt(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* conOut, int value) {
	CHAR16 out[32];
	CHAR16* ptr = out;
	static_assert(std::is_unsigned_v<char16_t>);
	if (value == 0)
	{
		conOut->OutputString(conOut, u"0");
		return;
	}

	ptr += 31;
	*--ptr = 0;
	int tmp = value;// >= 0 ? value : -value; 

	while (tmp)
	{
		*--ptr = '0' + tmp % 10;
		tmp /= 10;
	}

	if (value < 0) *--ptr = '-';
	conOut->OutputString(conOut, ptr);
}

void func(int a, int b=2, int c=3){
    printInt(gSystemTable->ConOut,a);
	gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"\r\n");
	printInt(gSystemTable->ConOut,b);
	gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"\r\n");
	printInt(gSystemTable->ConOut,c);
	gSystemTable->ConOut->OutputString(gSystemTable->ConOut, u"\r\n");
}

EFI_STATUS
efi_main(EFI_HANDLE /*image*/, EFI_SYSTEM_TABLE* systemTable)
{
	gSystemTable=systemTable;
	func(30);
	return EFI_SUCCESS;
}

上面定义了 void func(int a, int b=2, int c=3) 这个函数,当通过func(30)调用时,相当于只给 a 赋值 30,其余的直接使用了默认值。

需要注意的是,在使用时有一些限制。比如:C++规定,默认参数只能放在形参列表的最后,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值。

参考:

1. https://c.biancheng.net/view/2204.html C++函数的默认参数详解

UEFI TIPS: Warning C4305

当我们在代码中直接定义浮点数如下所示时,会遇到 Warning C4305: ‘initializing’: truncation from ‘double’ to ‘float’

  float f1=0.12,f2=0.34;

这个警告的意思是:你定义的是一个 double 而非 float 类型。

解决方法有如下2种:

1.更换类型为 double

2.在数值后面加上 “f” 例如:

  float f1=0.12f,f2=0.34f;