目录
开发准备
硬件型号:ESP32S3
开发环境:ESP-IDF v4.4
开发平台:VS Code
如果在使用官方TinyUSB库时出现问题,可能是作者有使用个人TinyUSB库的原因,资源见文章底部
第一步
确保硬件支持TinyUSB库并已开启配置
如上图所示,开启TinyUSB、HID功能
如果未找到HID相关开启选项,可自行检查硬件是否支持,或者配置文件是否配对
第二步
初始化USB设备
ESP_LOGI(TAG, "USB initialization");
const tinyusb_config_t tusb_cfg = {};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB initialization DONE");
调用上述代码,初始化ESP32为USB设备,其中tinyusb_config_t 结构体可以为空,tinyusb_driver_install将使用默认参数进行USB配置,如需手动配置,见以下步骤,否则跳过即可
tinyusb_config_t结构体如下所示:
typedef struct {
tusb_desc_device_t *descriptor; // 设备描述符结构体
const char **string_descriptor; // 字符串描述符
const uint8_t *config_descriptor; // 配置描述符数组
bool external_phy; // 外部PHY,一般为false
} tinyusb_config_t;
设备描述符tusb_desc_device_t
typedef struct TU_ATTR_PACKED
{
uint8_t bLength ; // 设备描述符的字节数大小
uint8_t bDescriptorType ; // 描述符类型,设备描述符为0x01
uint16_t bcdUSB ; // USB版本号
uint8_t bDeviceClass ; // USB分配的设备类代码,0x01~0xfe为标准设备类,0xff为厂商自定义类型
uint8_t bDeviceSubClass ; // USB分配的子类代码
uint8_t bDeviceProtocol ; // USB分配的设备协议代码
uint8_t bMaxPacketSize0 ; // 端点0的最大信息包大小
uint16_t idVendor ; // 制造商ID
uint16_t idProduct ; // 产品ID
uint16_t bcdDevice ; // 设备出厂编号
uint8_t iManufacturer ; // 制造商的字符串描述符索引
uint8_t iProduct ; // 产品的字符串描述符索引
uint8_t iSerialNumber ; // 设备序列号的字符串描述符索引
uint8_t bNumConfigurations ; // 可能的配置数量
} tusb_desc_device_t;
TinyUSB官方默认配置如下所示:
tusb_desc_device_t descriptor_kconfig = {
.bLength = sizeof(descriptor_kconfig),
.bDescriptorType = TUSB_DESC_DEVICE, // 0x01
.bcdUSB = 0x0200, // USB2.0
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, // 64
.idVendor = 0xCafe,
.idProduct = USB_TUSB_PID, // 0x4010
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
配置描述符config_descriptor数组
默认配置如下所示:
uint8_t const desc_configuration[] = {
// 配置描述符
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, 0, 100),
// 接口描述符、HID描述符、端点描述符
TUD_HID_INOUT_DESCRIPTOR(ITF_NUM_HID, STRID_HID_INTERFACE, HID_PROTOCOL_NONE, sizeof(desc_hid_report), 0x01, 0x81, 64, 10)
};
TUD_CONFIG_DESCRIPTOR为宏定义,跳转之后见以下解析:
其中配置描述符解析说明如下:
固定长度9 -- 配置描述符
配置描述符 -- TUSB_DESC_CONFIGURATION -- 0x02
配置描述符信息总的大小,包括接口描述符、端点描述符等等 -- U16_TO_U8S_LE(TUSB_DESC_TOTAL_LEN) --> 9 + 9 + 9 + 7 + 7(双向通信)
接口数量 -- ITF_NUM_TOTAL --> 1
Set_Configuration命令需要的参数值 -- 1
配置字符串索引 -- 0
bit7=1 bit6:1--自供电 0--总线供电 bit5:1--远程唤起 0--不支持 bit[4:0]=0 -- TU_BIT(7) | 0
供电 -- (100)/2 == 50*2 = 100mA
-------------------------------------------------------------------------------------------------------------------------
9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(TUSB_DESC_TOTAL_LEN), ITF_NUM_TOTAL, 1, 0, TU_BIT(7) | 0, (100)/2,
如果是双向通信则按上述设置,如果仅是单向输入或输出的话,配置描述符信息总的大小需要变成9 + 9 + 9 + 7
TUD_HID_INOUT_DESCRIPTOR为宏定义,跳转之后见以下解析:
其中接口描述符解析说明如下:
固定长度9 -- 接口描述符
接口描述符 -- TUSB_DESC_INTERFACE --> 0x04
接口0 (接口从0开始) -- 0
接口索引值 -- 0
端点个数(端点0不可用)-- 2
3 = HID -- TUSB_CLASS_HID --> 0x03
接口子类型:01为Boot Device,键鼠在BIOS下就启动 -- 0
接口协议:00--None 01--Keyboard 02--Mouse -- 0
描述该接口的字符串索引 -- STRID_HID_INTERFACE
-------------------------------------------------------------------------------------------------------------------------
9, TUSB_DESC_INTERFACE, 0, 0, 2, TUSB_CLASS_HID, (uint8_t)((0) ? HID_SUBCLASS_BOOT : 0), 0, STRID_HID_INTERFACE,
其中HID描述符解析说明如下:
固定长度9 -- HID描述符
HID描述符 -- HID_DESC_TYPE_HID --> 0x21
HID专属版本号 -- 0x0111
国家代码 -- 0
附属类描述字的数目1个 -- 1
描述字类型:报告 -- HID_DESC_TYPE_REPORT --> 0x22
HID报告描述字总字节数 -- sizeof(desc_hid_report)
-------------------------------------------------------------------------------------------------------------------------
9, HID_DESC_TYPE_HID, U16_TO_U8S_LE(0x0110), 0, 1, HID_DESC_TYPE_REPORT, U16_TO_U8S_LE(sizeof(desc_hid_report)),
其中端点描述符解析说明如下:
固定长度7 -- 端点描述符 -- 输入端点
端点描述符 -- TUSB_DESC_ENDPOINT
bit[7]:1--IN--设备到主机 0--OUT--主机到设备 -- 0x81
传输类型 -- TUSB_XFER_INTERRUPT(中断)
端点最大信息包尺寸 -- 最大64 -- 0x40
轮询间隔 -- 10
-------------------------------------------------------------------------------------------------------------------------
7, TUSB_DESC_ENDPOINT, 0x81, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(0x40), 10,
如果需要将HID设备设置为双向通信,则需要加上输出端点,如下所示:
固定长度7 -- 端点描述符 -- 输出端点
端点描述符 -- TUSB_DESC_ENDPOINT
bit[7]:1--IN--设备到主机 0--OUT--主机到设备 -- 0x01
传输类型 -- TUSB_XFER_INTERRUPT(中断)
端点最大信息包尺寸 -- 最大64 -- 0x40
轮询间隔 -- 10
-------------------------------------------------------------------------------------------------------------------------
7, TUSB_DESC_ENDPOINT, 0x01, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(0x40), 10
到这里,描述符config_descriptor数组基本配置完成了!
配置报告描述符desc_hid_report(重点!!!)
这里作者使用的是TinyUSB官方的HID通用输入输出设备模板,如需自定义报告描述符,可适用于HID Descriptor Tool官方生成工具,资源在文章底部
uint8_t const desc_hid_report[] = {
TUD_HID_REPORT_DESC_GENERIC_INOUT(63, HID_REPORT_ID(3))
};
上述代码中,63表示一次最大发送报告数量,HID_REPORT_ID(3)表示HID通用设备的报告ID为3。
每个HID设备只能有一个设备描述符,但可以有多个报告描述符,每个描述符表示一种类型,比如说我可以同时在desc_hid_report数组中添加上键盘、鼠标的报告描述符,这样我在插上电脑后,电脑会设备出三种不同类型的设备,需要注意的是,不同类型的设备报告ID必要不同!,因为在发送和接收报告数据时,需要通过报告ID来辨别是哪个类型的设备,如下所示:
uint8_t const desc_hid_report[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID_KEYBOARD)), // 1
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID_MOUSE)), // 2
TUD_HID_REPORT_DESC_GENERIC_INOUT(63, HID_REPORT_ID(3))
};
关于一次最大发送报告数量的设置,必要比端点描述符中定义的最大信息包尺寸小一个字节,这是因为默认第0字节会存放报告ID,占位一个字节,例如:端点最大信息包尺寸为64,那么一次最大发送报告数量应该为63;端点最大信息包尺寸为32,那么一次最大发送报告数量应该为31。
配置字符串描述符string_descriptor
tusb_desc_strarray_device_t string_descriptor= {
// array of pointer to string descriptors
(char[]){0x09, 0x04}, // 0: 支持语言:英语 (0x0409)
"TinyUSB", // 1: 制造商
"TinyUSB Device", // 2: 产品
"123456", // 3: 串行、芯片ID
"TinyUSB HID" // 4: HID
};
这些都可以自定义,不一定要按照上面的配置
关于语言的配置,一般都用英语,如需配置其他语言,可下载文档:Universal Serial Bus (USB),自行参阅
到这里,USB HID设备基本配置初始完成!
第三步
发送报告数据
调用TinyUSB库中tud_hid_report函数发送报告
// 检查接口是否可用
if (tud_hid_ready())
{
uint8_t report_data[63] = {1, 2, 3, 4};
// 3 -- 报告ID
// 发送的报告数组
// 发送的长度
tud_hid_report(3, report_data, 63);
}
其中发送的长度为报告描述符中一次最大发送报告数量,即63,必须设置正确,否则使用调试工具无法在HID设备上抓取报告数据,只能在USB总线上抓取!!
下图为在HID设备上抓取报告数据(注意在抓取数据时,必须先打开HID设备(例如PortHelper),才能抓取到数据):
上图中,03表示报告ID,后面数据为用户发送报告数据,调试工具为Bus Hound,资源见文章底部
接收报告数据
需要重定向接收报告回调函数,在HID接收到报告数据时,则触发该回调函数
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize)
{
/*
(void) itf; // 一般都为0
(void) report_id; // 报告ID
(void) report_type; // 报告类型
(void) buffer; // 接收报告数组
(void) bufsize; // 接收报告长度
*/
// 处理代码
}
总结
以上为作者自己在开发过程中一些见解,如有错误还请各位大佬多多指点,我是陈师傅,我们下章再见!
资源下载https://pan.baidu.com/s/1x2iweOhID6AAAPnns8qLzA?pwd=2b39