这篇文章使用ESP32S3实现了一个力反馈设备驱动,目前在尘埃拉力赛2.0和欧卡上测试还比较正常,但是依然存在一些小的问题,此文仅以用来记录学习和探索的过程。
材料选型
主控选用esp32s3;电机驱动选用的是Odriver,使用CAN通信和电机通信。
参考资料
一、ESP32实现USB-HID设备
ESP32自带tinyUSB协议栈
,结合示例程序能非常容易的实现USB设备,打开esp-idf
的example
选择tusd-hid
示例程序
这是接收数据的回调函数:
// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
(void) instance;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
return 0;
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
}
第一个回调函数是接收请求报告的时候进入的回调函数,
instance
是实例编号,也就是usb的接口号,在我的实现中我只有一个USB接口所以基本为0,
report_id
为报告ID,报告ID和你的报告描述符的设置有关系;
report_type
为报告类型,有三种,特性报告,输入报告,输出报告,不过这个中断函数几乎都是特性报告,因为这个中断函数是请求输入报告,即主机请求从机想主机发送特性的报告,例如查询刚刚加载的数据是否成功?查询内存是否充足?等等;
buffer
是数据缓冲,这个函数将请求的数据直接复制到缓冲器,然后将最后一个参数设置为返回的数据长度,并且最后函数要return 这个值;
bufsize
为返回数据长度;
第二个回调函数的参数与上述一样
注意:这个参数仅仅在报告类型为Feature报告的时候才有效,输出报告的时候该参数一直为零,根据协议,原始数据的第零位是报告ID
USB-HID协议简述
USB-HID协议非常复杂,我也是因此项目初次接触,只能简单阐述一下这里面需要的知识,有错误之处,请多包涵;
同时我主要参考了《圈圈带你玩USB》一书以及B站up hoop0 的视频,推荐大家看一看原视频,深入浅出,讲的很好。
枚举过程
枚举过程由USB芯片自动完成,在制作过程中不必过分在意,但是了解枚举过程还是有助于了解其工作原理。在这里贴一篇博主的文章供参考
设备描述符
设备描述符(Device Descriptor)说明了USB设备的通用信息,包含应用到全部设备和所有设备配置的信息。USB设备只有一个设备描述符(Device Descriptor)。设备描述符是在设备连接时主机读取的第一个描述符。设备描述符所含的信息,被主机用来取得设备的额外内容。设备描述符(Device Descriptor)提供了关于设备、设备的配置以及任何设备所归属的类的信息。
字段名 | 长度(字节) | 说明 |
---|---|---|
bLength | 1 | 描述符总长度(固定为18字节) |
bDescriptorType | 1 | 描述符类型(设备描述符固定为0x01 ) |
bcdUSB | 2 | USB协议版本(BCD格式,如0x0200 表示USB 2.0) |
bDeviceClass | 1 | 设备类代码(例如:0x00 表示接口定义类,0x03 为HID类) |
bDeviceSubClass | 1 | 设备子类代码(依赖主类,例如HID子类0x01 为启动设备) |
bDeviceProtocol | 1 | 设备协议(依赖类和子类,例如HID协议0x02 表示鼠标) |
bMaxPacketSize0 | 1 | 端点0的最大数据包大小(有效值:8, 16, 32, 64) |
idVendor | 2 | 厂商ID(由USB-IF官方分配,例如0x1234 ) |
idProduct | 2 | 产品ID(厂商自定义,例如0xABCD ) |
bcdDevice | 2 | 设备版本号(BCD格式,例如0x0100 表示版本1.0) |
iManufacturer | 1 | 厂商名称字符串的索引(0表示无字符串) |
iProduct | 1 | 产品名称字符串的索引(0表示无字符串) |
iSerialNumber | 1 | 序列号字符串的索引(0表示无字符串) |
bNumConfigurations | 1 | 设备支持的配置数量(至少为1) |
一般 bDeviceClass
bDeviceSubClass
bDeviceProtocol
不写,即填充为0x00
因为这会限制后面的协议,而bInterfaceClass
中指定实现的功能
在ESP32的tinyUSB示例中
const tinyusb_config_t tusb_cfg = {
.device_descriptor = NULL,
.string_descriptor = hid_string_descriptor,
.string_descriptor_count = sizeof(hid_string_descriptor) / sizeof(hid_string_descriptor[0]),
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = hid_configuration_descriptor, // HID configuration descriptor for full-speed and high-speed are the same
.hs_configuration_descriptor = hid_configuration_descriptor,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = hid_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
tinyusb配置结构体中device_descriptor
指针默认为NULL,这代表使用ESP32默认的设备描述符,如果要改变其ProduceID和VendorID可以在配置项(menuconfig)中修改
配置描述符集合
配置描述符涉及多个层次的描述符,包括配置描述符、接口描述符、HID描述符和端点描述符。
配置描述符
字段 | 长度 | 说明 | 示例值 |
---|---|---|---|
bLength |
1 | 描述符长度,固定为 0x09 |
0x09 |
bDescriptorType |
1 | 描述符类型,固定为 0x02 (配置描述符) |
0x02 |
wTotalLength |
2 | 整个配置的总长度(包含所有接口、端点和 HID 描述符) | 0x0022 |
bNumInterfaces |
1 | 配置包含的接口数量 | 0x01 |
bConfigurationValue |
1 | 配置标识符,用于主机选择配置 | 0x01 |
iConfiguration |
1 | 描述此配置的字符串索引(0x00 表示无) |
0x00 |
bmAttributes |
1 | 配置属性: | |
Bit7=1(必须) | |||
Bit6=自供电(1=是) | |||
Bit5=远程唤醒(1=支持) | 0xA0 |
||
bMaxPower |
1 | 最大功耗(单位:2 mA),例如 0x32 = 100 mA |
0x32 |
接口描述符
字段 | 长度 | 说明 | 示例值 |
---|---|---|---|
bLength |
1 | 描述符长度,固定为 0x09 |
0x09 |
bDescriptorType |
1 | 描述符类型,固定为 0x04 (接口描述符) |
0x04 |
bInterfaceNumber |
1 | 接口编号(从 0 开始) | 0x00 |
bAlternateSetting |
1 | 备用接口编号(通常为 0x00 ) |
0x00 |
bNumEndpoints |
1 | 接口使用的端点数(不含控制端点 0) | 0x01 |
bInterfaceClass |
1 | 接口类:HID 设备为 0x03 |
0x03 |
bInterfaceSubClass |
1 | 子类:0x01 =支持启动协议,0x00 =无 |
0x00 |
bInterfaceProtocol |
1 | 协议:0x01 =键盘,0x02 =鼠标,0x00 =无 |
0x02 |
iInterface |
1 | 接口字符串索引(0x00 表示无) |
0x00 |
HID描述符
字段 | 长度 | 说明 | 示例值 |
---|---|---|---|
bLength |
1 | 描述符长度,固定为 0x09 |
0x09 |
bDescriptorType |
1 | 描述符类型,固定为 0x21 (HID 描述符) |
0x21 |
bcdHID |
2 | HID 规范版本(如 0x0110 表示 HID 1.10) |
0x0110 |
bCountryCode |
1 | 国家代码(0x00 =不支持本地化) |
0x00 |
bNumDescriptors |
1 | 下级描述符数量(至少 1 个报告描述符) | 0x01 |
下级描述符信息 | 3 | 每个下级描述符: | |
bDescriptorType (如 0x22 =报告描述符) |
|||
wDescriptorLength |
0x22, 0x0032 |
端点描述符
字段 | 长度 | 说明 | 示例值 |
---|---|---|---|
bLength |
1 | 描述符长度,固定为 0x07 |
0x07 |
bDescriptorType |
1 | 描述符类型,固定为 0x05 (端点描述符) |
0x05 |
bEndpointAddress |
1 | 端点地址: | |
Bit7=方向(0=OUT,1=IN) | |||
Bits0-3=端点号 | 0x81 (IN 端点1) |
||
bmAttributes |
1 | 传输类型: | |
Bits0-1=0x03 =中断传输 |
0x03 |
||
wMaxPacketSize |
2 | 最大数据包大小(如 0x0008 =8 字节) |
0x0008 |
bInterval |
1 | 轮询间隔(单位 ms,低速=10 |
0x0A (10 ms) |
在ESP32IDF示例中,并不是一一填写这些描述符,他已经提供了固定的描述符配置,仅需要填写关键的信息,在文usbd.h
中提供了大量的宏定义来提供这些描述符,在使用实例中,描述符被定义为
static const uint8_t hid_configuration_descriptor[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, boot protocol, report descriptor len, EP OUT address,EP In address, size & polling interval
TUD_HID_INOUT_DESCRIPTOR(0, 4, false, FFB_DESC_LENGTH, 0x02,0x81, 64, 5),
};
TUD_CONFIG_DESCRIPTOR
是tinyusb
写好的宏函数,扩展为配置描述符,只需要填入对应的参数即可TUD_HID_INOUT_DESCRIPTOR
则是一个大杂烩包含了剩下描述符(不包含报告描述符和字符串描述符)同样只需要填入参数即可
最后配置描述符集合hid_configuration_descriptor[]
这个东西比较复杂,我基本参考网上的描述符,但是都大同小异,无非是数据长度的差异,但是我是用的描述符有一些问题(我也不知道是不是文件描述符的问题),使用此描述符的设备无法在我的笔记本电脑上被识别为力反馈设备。但是我提供了对应的补丁(一些注册表文件)
该描述符可通过文件我的开源代码下载...
力反馈设备相关协议
简单说明了我们需要填写的USB描述符以后,现在开始介绍正式的力反馈设备相关的协议,参考资料是hid《usage-table》的Physic Input Device Page(0x0F)页
力反馈设备的力反馈是由11种预设效果合成的,参数块的数量等于关节数量乘以轴数,或等于 2,以较大值为准,最小值 2 可支持一个包络参数块和一个额外的特定类型参数块。具体种类参见下表(usage table )
效果类型主要分为恒定效果,斜坡效果,方波效果,正弦波,三角波效果,锯齿波(向上),锯齿波(向下),弹簧效果,阻尼效果,惯性效果,摩擦效果以及自定义。自定义可以忽略,我查阅基本所有的都能没有实现自定义效果
而参数块基本可以分为,恒定参数,包络参数,包络参数主要实现的是淡入淡出的效果,周期参数块和条件参数快,条件参数主要实现的是弹簧,摩擦,阻尼,惯性等效果
力反馈设备的通信主要是下载效果,设置效果的参数,打开效果,关闭效果等等,下面开始详细解释一下各个包的含义