USB-HID是Universal Serial Bus-Human Interface Device的缩写,由其名称可以了解HID设备是直接与人交互的设备,例如键盘、鼠标与游戏杆等,也是PC上为数不多的不需要额外驱动的协议栈,所以diy一些小设备非常方便。
之前一直在用STM32芯片,ST已经提供了一套完备的USB协议栈,只要定义好设备配置符就可以用了,看了下CH32V10x系列的资料,里面只有通过USB模拟CH372设备的例程。于是借着比赛的机会,正好把USB的HID协议学习一下。
对USB感兴趣的小伙伴,推荐看下《圈圈教你玩USB》,讲的还是很细致的,虽然是多年前的书了,但毕竟USB也还是原来的那套,本文就不详细介绍USB协议栈了。
移植过程是比较长的,如果你也是用了CH32V103,并且想要实现键盘鼠标功能的话,可以直接参考我共享的代码:CH32V103-USB-HID-KeyboradMouse: CH32V103 usb HID 的键盘鼠标库 (gitee.com)
这里已经写好了针对键盘/鼠标/键盘鼠标混合的功能实现,并参考了arduino的API,实现键盘的按下按键、释放按键、释放所有按键、输入字符等功能,实现鼠标的点击、移动等功能,可以参考arduino的Keyboard和Mouse的使用方法,当然这里是C实现的,所以函数接口名是做了修改了哈。
使用方法很简单,只要进行键盘/鼠标/键盘鼠标混合的初始化就可以使用了,具体的方法看代码中的描述。
/*三选一初始化即可*/
USB_HID_Init(HID_KEYBOARD);
USB_HID_Init(HID_MOUSE);
USB_HID_Init(HID_KEYBOARD_AND_MOUSE);
接下来是移植过程:
CH32V103的标准库中有USB的相关底层驱动,见ch32v10x_usb.c和ch32v10x_usb.h,其中定义了USB协议栈需要的各种配置符结构,我们只要按照需要定义就好了。但定义好了配置,该如何与主机端通讯呢?可以参考USB模拟CH372设备的例程,USB通讯都是通过USB中断函数中的状态机实现的。所以只要定义好配置描述符,并按需要修改中断函数,理论上就完成了移植工作。
1、USB设备描述符,这里的最大包长度就按照CH32V103的EndPoint0的缓冲区大小来设置了,厂家ID和产品ID都自定义,后面的字符串索引号是和字符串描述符相关联的,所以要对应上。然后配置只有一种即可。
static const USB_DEV_DESCR USBDevDescr = {
.bLength = 18, /*bLength:长度,设备描述符的长度为18字节*/
.bDescriptorType = 0x01, /*bDescriptorType:类型,设备描述符的编号是0x01*/
.bcdUSB = 0x0200, /*bcdUSB:所使用的USB版本为2.0*/
.bDeviceClass = 0x00, /*bDeviceClass:设备所使用的类代码*/
.bDeviceSubClass = 0x00, /*bDeviceSubClass:设备所使用的子类代码*/
.bDeviceProtocol = 0x00, /*bDeviceSubClass:设备所使用的子类代码*/
.bMaxPacketSize0 = DevEP0SIZE, /*bMaxPacketSize:最大包长度为64字节*/
.idVendor = USBD_VID, /*idVendor:厂商ID*/
.idProduct = USBD_PID_FS, /*idProduct:产品ID*/
.bcdDevice = 0x0200, /*bcdDevice:设备的版本号为2.00*/
.iManufacturer = 0x01, /*iManufacturer:厂商字符串的索引*/
.iProduct = 0x02, /*iProduct:产品字符串的索引*/
.iSerialNumber = 0x03, /*iSerialNumber:设备的序列号字符串索引*/
.bNumConfigurations = 0x01 /*bNumConfiguration:设备有1种配置*/
};
2、USB配置描述符,下面分别是键盘/鼠标/键盘鼠标混合的配置符代码,不同的差别就在配置描述符的总长度和所支持的接口数量。
static const USB_CFG_DESCR USBCfgDescr_KB = {
.bLength = 0x09, /*bLength:长度,设备字符串的长度为9字节*/
.bDescriptorType = 0x02, /*bDescriptorType:类型,配置描述符的类型编号为0x2*/
.wTotalLength = USB_CFG_DESCR_LEN_KB, /*wTotalLength:配置描述符的总长度为50字节*/
.bNumInterfaces = 0x01, /*bNumInterfaces:配置所支持的接口数量1个*/
.bConfigurationValue = 0x01, /*bConfigurationValue:该配置的值*/
.iConfiguration = 0x00, /*iConfiguration:该配置的字符串的索引值,该值为0表示没有字符串*/
.bmAttributes = 0xA0, /* bmAttributes:设备的一些特性,0xA0表示不自供电,支持远程唤醒
D7:保留必须为1
D6:是否自供电
D5:是否支持远程唤醒
D4~D0:保留设置为0
*/
.MaxPower = 0x32 /*从总线上获得的最大电流为100mA */
};
static const USB_CFG_DESCR USBCfgDescr_M = {
.bLength = 0x09, /*bLength:长度,设备字符串的长度为9字节*/
.bDescriptorType = 0x02, /*bDescriptorType:类型,配置描述符的类型编号为0x2*/
.wTotalLength = USB_CFG_DESCR_LEN_M, /*wTotalLength:配置描述符的总长度为41字节*/
.bNumInterfaces = 0x01, /*bNumInterfaces:配置所支持的接口数量1个*/
.bConfigurationValue = 0x01, /*bConfigurationValue:该配置的值*/
.iConfiguration = 0x00, /*iConfiguration:该配置的字符串的索引值,该值为0表示没有字符串*/
.bmAttributes = 0xA0, /* bmAttributes:设备的一些特性,0xA0表示不自供电,支持远程唤醒
D7:保留必须为1
D6:是否自供电
D5:是否支持远程唤醒
D4~D0:保留设置为0
*/
.MaxPower = 0x32 /*从总线上获得的最大电流为100mA */
};
static const USB_CFG_DESCR USBCfgDescr_KBM = {
.bLength = 0x09, /*bLength:长度,设备字符串的长度为9字节*/
.bDescriptorType = 0x02, /*bDescriptorType:类型,配置描述符的类型编号为0x2*/
.wTotalLength = USB_CFG_DESCR_LEN_KBM, /*wTotalLength:配置描述符的总长度为66字节*/
.bNumInterfaces = 0x02, /*bNumInterfaces:配置所支持的接口数量2个*/
.bConfigurationValue = 0x01, /*bConfigurationValue:该配置的值*/
.iConfiguration = 0x00, /*iConfiguration:该配置的字符串的索引值,该值为0表示没有字符串*/
.bmAttributes = 0xA0, /* bmAttributes:设备的一些特性,0xA0表示不自供电,支持远程唤醒
D7:保留必须为1
D6:是否自供电
D5:是否支持远程唤醒
D4~D0:保留设置为0
*/
.MaxPower = 0x32 /*从总线上获得的最大电流为100mA */
};
3、设备描述符,设备描述符中包括了接口描述符/HID描述符/端点描述符,因此这里构建了个结构体,方便一起发送给主机端,也是分为三种:键盘/鼠标/键盘和鼠标,其实就是不同的接口描述符/HID描述符/端点描述符的组合方式。
typedef struct __PACKED {
USB_CFG_DESCR cfg_descr;
USB_ITF_DESCR itf_descr;
USB_HID_DESCR hid_descr;
USB_ENDP_DESCR inendp_descr;
USB_ENDP_DESCR outendp_descr;
} USB_CFG_DESCR_KEYBOARD;
typedef struct __PACKED {
USB_CFG_DESCR cfg_descr;
USB_ITF_DESCR itf_descr;
USB_HID_DESCR hid_descr;
USB_ENDP_DESCR inendp_descr;
} USB_CFG_DESCR_MOUSE;
typedef struct __PACKED {
USB_CFG_DESCR cfg_descr;
USB_ITF_DESCR itf1_descr;
USB_HID_DESCR hid1_descr;
USB_ENDP_DESCR inendp1_descr;
USB_ENDP_DESCR outendp1_descr;
USB_ITF_DESCR itf2_descr;
USB_HID_DESCR hid2_descr;
USB_ENDP_DESCR inendp2_descr;
} USB_CFG_DESCR_KEYBOARD_AND_MOUSE;
static const USB_CFG_DESCR_KEYBOARD CfgDescr_keyboard = {
.cfg_descr = USBCfgDescr_KB,
.itf_descr = KeyBoardItfDescr,
.hid_descr = KeyBoardHIDDescr,
.inendp_descr = KeyBoardINEndpDescr,
.outendp_descr = KeyBoardOUTEndpDescr,
};
static const USB_CFG_DESCR_MOUSE CfgDescr_Mouse = {
.cfg_descr = USBCfgDescr_M,
.itf_descr = MouseItfDescr,
.hid_descr = MouseHIDDescr,
.inendp_descr = MouseEndpDescr,
};
static const USB_CFG_DESCR_KEYBOARD_AND_MOUSE CfgDescr_keyboardAndMouse = {
.cfg_descr = USBCfgDescr_KBM,
.itf1_descr = KeyBoardItfDescr,
.hid1_descr = KeyBoardHIDDescr,
.inendp1_descr = KeyBoardINEndpDescr,
.outendp1_descr = KeyBoardOUTEndpDescr,
.itf2_descr = MouseItfDescr,
.hid2_descr = MouseHIDDescr,
.inendp2_descr = MouseEndpDescr,
};
4、键盘的接口描述符,HID描述符,和两个端点的描述符,这里我创建了两个端点描述符,一个用于发送按键值,另一个用于接收主机端的状态值,比如大写锁定灯,小键盘灯之类的
static const USB_ITF_DESCR KeyBoardItfDescr = {
.bLength = 0x09, /*bLength:长度,设备字符串的长度为9字节*/
.bDescriptorType = 0x04, /*bDescriptorType:接口描述符的类型为0x4 */
.bInterfaceNumber = 0x00, /*bInterfaceNumber:该接口的编号*/
.bAlternateSetting = 0x00, /*bAlternateSetting:该接口的备用编号 */
.bNumEndpoints = 0x02, /*bInterfaceNumber:该接口的编号*/
.bInterfaceClass = 0x03, /*bInterfaceClass该接口所使用的类为HID*/
.bInterfaceSubClass = 0x01, /*bInterfaceSubClass:该接口所用的子类 1=BOOT, 0=no boot */
.bInterfaceProtocol = 0x01, /*nInterfaceProtocol :该接口使用的协议0=none, 1=keyboard, 2=mouse */
.iInterface = 0x00 /*iInterface: 该接口字符串的索引 */
};
static const USB_HID_DESCR KeyBoardHIDDescr = {
.bLength = 0x09, /*bLength: HID描述符的长度为9字节 */
.bDescriptorType = 0x21, /*bDescriptorType: HID的描述符类型为0x21 */
.bcdHID = 0x0111, /*bcdHID: HID协议的版本为1.1 */
.bCountryCode = 0x00, /*bCountryCode: 国家代号 */
.bNumDescriptors = 0x01, /*bNumDescriptors: 下级描述符的数量*/
.bDescriptorTypeX = 0x22, /*bDescriptorType:下级描述符的类型*/
.wDescriptorLengthL = sizeof(KeyboardRepDescr)&0xFF, /*wItemLength: 下级描述符的长度*/
.wDescriptorLengthH = 0x00,
};
static const USB_ENDP_DESCR KeyBoardINEndpDescr = {
.bLength = 0x07,
.bDescriptorType = 0x05,
.bEndpointAddress = 0x81, /* bEndpointAddress: 该端点(输入)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端点号*/
.bmAttributes = 0x03, /* bmAttributes: 端点的属性为为中断端点.
D0~D1表示传输类型:0(控制传输),1(等时传输),2(批量传输),3(中断传输)
非等时传输端点:D2~D7:保留为0
等时传输端点:
D2~D3表示同步的类型:0(无同步),1(异步),2(适配),3(同步)
D4~D5表示用途:0(数据端点),1(反馈端点),2(暗含反馈的数据端点),3(保留)
D6~D7:保留,
*/
.wMaxPacketSize = DevEP0SIZE, /* wMaxPacketSize: 该端点支持的最大包长度为DevEP0SIZE字节*/
.bInterval = 0x0A, /* bInterval: 轮询间隔(10ms) */
};
static const USB_ENDP_DESCR KeyBoardOUTEndpDescr = {
.bLength = 0x07,
.bDescriptorType = 0x05,
.bEndpointAddress = 0x01, /* bEndpointAddress: 该端点(输入)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端点号*/
.bmAttributes = 0x03, /* bmAttributes: 端点的属性为为中断端点.
D0~D1表示传输类型:0(控制传输),1(等时传输),2(批量传输),3(中断传输)
非等时传输端点:D2~D7:保留为0
等时传输端点:
D2~D3表示同步的类型:0(无同步),1(异步),2(适配),3(同步)
D4~D5表示用途:0(数据端点),1(反馈端点),2(暗含反馈的数据端点),3(保留)
D6~D7:保留,
*/
.wMaxPacketSize = DevEP0SIZE, /* wMaxPacketSize: 该端点支持的最大包长度为DevEP0SIZE字节*/
.bInterval = 0x0A, /* bInterval: 轮询间隔(10ms) */
};
5、鼠标的接口描述符,HID描述符,和端点描述符
static const USB_ITF_DESCR MouseItfDescr = {
.bLength = 0x09, /*bLength:长度,设备字符串的长度为9字节*/
.bDescriptorType = 0x04, /*bDescriptorType:接口描述符的类型为0x4 */
.bInterfaceNumber = 0x01, /*bInterfaceNumber:该接口的编号*/
.bAlternateSetting = 0x00, /*bAlternateSetting:该接口的备用编号 */
.bNumEndpoints = 0x01, /*bInterfaceNumber:该接口的编号*/
.bInterfaceClass = 0x03, /*bInterfaceClass该接口所使用的类为HID*/
.bInterfaceSubClass = 0x01, /*bInterfaceSubClass:该接口所用的子类 1=BOOT, 0=no boot */
.bInterfaceProtocol = 0x02, /*nInterfaceProtocol :该接口使用的协议0=none, 1=keyboard, 2=mouse */
.iInterface = 0x00 /*iInterface: 该接口字符串的索引 */
};
static const USB_HID_DESCR MouseHIDDescr = {
.bLength = 0x09, /*bLength: HID描述符的长度为9字节 */
.bDescriptorType = 0x21, /*bDescriptorType: HID的描述符类型为0x21 */
.bcdHID = 0x0111, /*bcdHID: HID协议的版本为1.1 */
.bCountryCode = 0x00, /*bCountryCode: 国家代号 */
.bNumDescriptors = 0x01, /*bNumDescriptors: 下级描述符的数量*/
.bDescriptorTypeX = 0x22, /*bDescriptorType:下级描述符的类型*/
.wDescriptorLengthL = sizeof(MouseRepDescr)&0xFF, /*wItemLength: 下级描述符的长度*/
.wDescriptorLengthH = 0x00,
};
static const USB_ENDP_DESCR MouseEndpDescr = {
.bLength = 0x07,
.bDescriptorType = 0x05,
.bEndpointAddress = 0x82, /* bEndpointAddress: 该端点(输入)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端点号*/
.bmAttributes = 0x03, /* bmAttributes: 端点的属性为为中断端点.
D0~D1表示传输类型:0(控制传输),1(等时传输),2(批量传输),3(中断传输)
非等时传输端点:D2~D7:保留为0
等时传输端点:
D2~D3表示同步的类型:0(无同步),1(异步),2(适配),3(同步)
D4~D5表示用途:0(数据端点),1(反馈端点),2(暗含反馈的数据端点),3(保留)
D6~D7:保留,
*/
.wMaxPacketSize = DevEP0SIZE, /* wMaxPacketSize: 该端点支持的最大包长度为DevEP0SIZE字节*/
.bInterval = 0x0A, /* bInterval: 轮询间隔(10ms) */
};
6、上述代码中的宏定义值
/* Global define */
#define DevEP0SIZE 0x40 // Max 64 bypes
#define USBD_VID 0x026D
#define USBD_PID_FS 0x24CD
#define USB_CFG_DESCR_LEN_KB 50 //ONLY KEYBOARD
#define USB_CFG_DESCR_LEN_M 41 //ONLY MOUSE
#define USB_CFG_DESCR_LEN_KBM 66 //ONLY KEYBOARD AND MOUSE
7、在USB设备描述符中所提到的字符串描述符,这部分可以通过USB字符串描述符生成器来得到
/* Language Descriptor */
static const UINT8 MyLangDescr[] = {
0x04, /*bLength:本描述符的长度为4字节*/
0x03, /*bDescriptorType:字符串描述符的类型为0x03*/
0x09, /*bString:语言ID为0x0409,表示美式英语*/
0x04
};
/* Manufactor Descriptor */
static const UINT8 MyManuInfo[] = {
0x1E, /*bLength:厂商字符串描述符的长度*/
0x03, /*bDescriptorType:字符串描述符的类型为0x03*/
/*ZealerluStudio*/
0x5A,0x00,0x65,0x00,0x61,0x00,0x6C,0x00,0x65,0x00,0x72,0x00,0x6C,0x00,
0x75,0x00,0x53,0x00,0x74,0x00,0x75,0x00,0x64,0x00,0x69,0x00,0x6F,0x00
};
/* Product Information */
static const UINT8 MyProdInfo[] = {
0x1A, /* bLength:产品的字符串描述符*/
0x03, /* bDescriptorType:字符串描述符的类型为0x03*/
/*Mult-PushRod*/
0x4D,0x00,0x75,0x00,0x6C,0x00,0x74,0x00,0x2D,0x00,0x50,0x00,0x75,0x00,
0x73,0x00,0x68,0x00,0x52,0x00,0x6F,0x00,0x64,0x00
};
/* Product ID Information */
static const UINT8 MyProdIDInfo[] = {
0x26, /* bLength:产品的字符串描述符*/
0x03, /* bDescriptorType:字符串描述符的类型为0x03*/
/*RT-Thread & Risc-V*/
0x52,0x00,0x54,0x00,0x2D,0x00,0x54,0x00,0x68,0x00,0x72,0x00,0x65,0x00,
0x61,0x00,0x64,0x00,0x20,0x00,0x26,0x00,0x20,0x00,0x52,0x00,0x69,0x00,0x73,0x00,
0x63,0x00,0x2D,0x00,0x56,0x00
};
9、键盘除了上述配置类的描述符,还需要一个报告描述符来描述通讯数据的格式
/* HID的报告描述符*/
/* 定义了8字节发送:
** 第一字节表示特殊件是否按下:D0:Ctrl D1:Shift D2:Alt
** 第二字节保留,值为0
** 第三~第八字节:普通键键值的数组,最多能同时按下6个键
** 定义了1字节接收:对应键盘上的LED灯,这里只用了两个位。
** D0:Num Lock D1:Cap Lock D2:Scroll Lock D3:Compose D4:Kana
** */
static const uint8_t KeyboardRepDescr[] =
{
/*short Item D7~D4:bTag;D3~D2:bType;D1~D0:bSize
**bTag ---主条目 1000:输入(Input) 1001:输出(Output) 1011:特性(Feature) 1010:集合(Collection) 1100:关集合(End Collection)
** 全局条目 0000:用途页(Usage Page) 0001:逻辑最小值(Logical Minimum) 0010:逻辑最大值(Logical Maximum) 0011:物理最小值(Physical Minimum)
** 0100:物理最大值(Physical Maximum) 0101:单元指数(Unit Exponet) 0110:单元(Unit) 0111:数据域大小(Report Size)
** 1000:报告ID(Report ID) 1001:数据域数量(Report Count) 1010:压栈(Push) 1011:出栈(Pop) 1100~1111:保留(Reserved)
** 局部条目 0000:用途(Usage) 0001:用途最小值(Usage Minimum) 0010:用途最大值(Usage Maximum) 0011:标识符索引(Designator Index)
** 0100:标识符最小值(Designator Minimum) 0101:标识符最大值(Designator Maximum) 0111:字符串索引(String Index) 1000:字符串最小值(String Minimum)
** 1001:字符串最大值(String Maximum) 1010:分隔符(Delimiter) 其他:保留(Reserved)
**bType --- 00:主条目(main) 01:全局条目(globle) 10:局部条目(local) 11:保留(reserved)
**bSize --- 00:0字节 01:1字节 10:2字节 11:4字节*/
//0x05:0000 01 01 这是个全局条目,用途页选择为普通桌面页
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//0x09:0000 10 01 这是个全局条目,用途选择为键盘
0x09, 0x06, // USAGE (Keyboard)
//0xa1:1010 00 01 这是个主条目,选择为应用集合,
0xa1, 0x01, // COLLECTION (Application)
//0x05:0000 01 11 这是个全局条目,用途页选择为键盘/按键
0x05, 0x07, // USAGE_PAGE (Keyboard/Keypad)
//0x19:0001 10 01 这是个局部条目,用途的最小值为0xe0,对应键盘上的左ctrl键
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
//0x29:0010 10 01 这是个局部条目,用途的最大值为0xe7,对应键盘上的有GUI(WIN)键
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
//0x15:0001 01 01 这是个全局条目,说明数据的逻辑值最小值为0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//0x25:0010 01 01 这是个全局条目,说明数据的逻辑值最大值为1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//0x95:1001 01 01 这是个全局条目,数据域的数量为8个
0x95, 0x08, // REPORT_COUNT (8)
//0x75:0111 01 01 这是个全局条目,每个数据域的长度为1位
0x75, 0x01, // REPORT_SIZE (1)
//0x81:1000 00 01 这是个主条目,有8*1bit数据域作为输入,属性为:Data,Var,Abs
0x81, 0x02, // INPUT (Data,Var,Abs)
//0x95:1001 01 01 这是个全局条目,数据域的数量为1个
0x95, 0x01, // REPORT_COUNT (1)
//0x75:0111 01 01 这是个全局条目,每个数据域的长度为8位
0x75, 0x08, // REPORT_SIZE (8)
//0x81:1000 00 01 这是个主条目,有1*8bit数据域作为输入,属性为:Cnst,Var,Abs
0x81, 0x03, // INPUT (Cnst,Var,Abs)
//0x95:1001 01 01 这是个全局条目,数据域的数量为6个
0x95, 0x06, // REPORT_COUNT (6)
//0x75:0111 01 01 这是个全局条目,每个数据域的长度为8位
0x75, 0x08, // REPORT_SIZE (8)
//0x25:0010 01 01 这是个全局条目,逻辑最大值为255
0x25, 0xFF, // LOGICAL_MAXIMUM (255)
//0x19:0001 10 01 这是个局部条目,用途的最小值为0
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
//0x29:0010 10 01 这是个局部条目,用途的最大值为0x65
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
//0x81:1000 00 01 这是个主条目,有6*8bit的数据域作为输入,属相为属性为:Data,Var,Abs
0x81, 0x00, // INPUT (Data,Ary,Abs)
//0x25:0010 01 01 这是个全局条目,逻辑的最大值为1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//0x95:1001 01 01 这是个全局条目,数据域的数量为2
0x95, 0x07, // REPORT_COUNT (2)
//0x75:0111 01 01 这是个全局条目,每个数据域的长度为1位
0x75, 0x01, // REPORT_SIZE (1)
//0x05:0000 01 01 这是个全局条目,用途页选择为LED页
0x05, 0x08, // USAGE_PAGE (LEDs)
//0x19:0001 10 01 这是个局部条目,用途的最小值为0x01,对应键盘上的Num Lock
0x19, 0x01, // USAGE_MINIMUM (Num Lock)
//0x29:0010 10 01 这是个局部条目,用途的最大值为0x02,对应键盘上的Caps Lock
0x29, 0x07, // USAGE_MAXIMUM (Caps Lock)
//0x91:1001 00 01 这是个主条目,有2*1bit的数据域作为输出,属性为:Data,Var,Abs
0x91, 0x02, // OUTPUT (Data,Var,Abs)
//0x95:1001 01 01 这是个全局条目,数据域的数量为1个
0x95, 0x01, // REPORT_COUNT (1)
//0x75:0111 01 01 这是个全局条目,每个数据域的长度为6bit,正好与前面的2bit组成1字节
0x75, 0x01, // REPORT_SIZE (6)
//0x91:1001 00 01 这是个主条目,有1*6bit数据域最为输出,属性为:Cnst,Var,Abs
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
0xc0 // END_COLLECTION
};
10、鼠标也是一样。这个报告描述符可通过HID官网的生成工具来配置得到
static const uint8_t MouseRepDescr[] =
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x03, // REPORT_COUNT (3)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x05, // REPORT_SIZE (5)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x38, // USAGE (Wheel)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (3)
0x81, 0x06, // INPUT (Data,Var,Rel)
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};
11、按照USB协议来修改中断函数中的状态机,这里分层去修改,还是比较好理解的。
void USB_DevTransProcess( void )
{
UINT8 len, chtype;
UINT8 intflag, errflag = 0;
intflag = R8_USB_INT_FG;
if( intflag & RB_UIF_TRANSFER )
{
switch ( R8_USB_INT_ST & MASK_UIS_TOKEN)
{
case UIS_TOKEN_SETUP:
R8_UEP0_CTRL = RB_UEP_R_TOG | RB_UEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_NAK;
len = R8_USB_RX_LEN;
if ( len == sizeof( USB_SETUP_REQ ) )
{
SetupReqLen = pSetupReqPak->wLength;
SetupReqCode = pSetupReqPak->bRequest;
chtype = pSetupReqPak->bRequestType;
len = 0;
errflag = 0;
if ( ( pSetupReqPak->bRequestType & USB_REQ_TYP_MASK ) != USB_REQ_TYP_STANDARD )
{
switch(SetupReqCode)
{
case HID_GET_REPORT: //GetReport
len = 1;
pEP0_DataBuf[0] = 0xaa;
break;
case HID_SET_IDLE:
R8_UEP0_T_LEN = 0;
break; //这个一定要有
case HID_SET_REPORT:
HIDInitFlag = 1;
break;
default:
errflag = 0xFF;
}
}
else
{
switch( SetupReqCode )
{
case USB_GET_DESCRIPTOR:
{
switch( ((pSetupReqPak->wValue)>>8) )
{
case USB_DESCR_TYP_DEVICE:
pDescr = (const UINT8*)&USBDevDescr;
len = sizeof(USB_DEV_DESCR);
break;
case USB_DESCR_TYP_CONFIG:
switch(HIDMode)
{
case HID_KEYBOARD:
pDescr = (const UINT8*)&CfgDescr_keyboard;
len = sizeof(USB_CFG_DESCR_KEYBOARD);
break;
case HID_MOUSE:
pDescr = (const UINT8*)&CfgDescr_Mouse;
len = sizeof(USB_CFG_DESCR_MOUSE);
break;
case HID_KEYBOARD_AND_MOUSE:
pDescr = (const UINT8*)&CfgDescr_keyboardAndMouse;
len = sizeof(USB_CFG_DESCR_KEYBOARD_AND_MOUSE);
break;
}
break;
case USB_DESCR_TYP_STRING:
switch( (pSetupReqPak->wValue)&0xff )
{
case 0:
pDescr = MyLangDescr;
len = MyLangDescr[0];
break;
case 1:
pDescr = MyManuInfo;
len = MyManuInfo[0];
break;
case 2:
pDescr = MyProdInfo;
len = MyProdInfo[0];
break;
case 3:
pDescr = MyProdIDInfo;
len = MyProdIDInfo[0];
break;
default:
errflag = 0xFF;
break;
}
break;
case USB_DESCR_TYP_REPORT:
if(((pSetupReqPak->wIndex)&0xff) == 0) //接口0报表描述符
{
if(HIDMode == HID_KEYBOARD)
{
pDescr = KeyboardRepDescr;
len = sizeof(KeyboardRepDescr);
HIDInitFlag = 1;
}
else if(HIDMode == HID_MOUSE)
{
pDescr = MouseRepDescr;
len = sizeof(MouseRepDescr);
HIDInitFlag = 1;
}
else
{
pDescr = KeyboardRepDescr;
len = sizeof(KeyboardRepDescr);
}
}
else if(((pSetupReqPak->wIndex)&0xff) == 1) //接口1报表描述符
{
pDescr = MouseRepDescr; //数据准备上传
len = sizeof(MouseRepDescr);
HIDInitFlag = 1;
}
else len = 0xff; //本程序只有2个接口,这句话正常不可能执行
break;
default :
errflag = 0xff;
break;
}
if( SetupReqLen>len ) SetupReqLen = len;
len = (SetupReqLen >= DevEP0SIZE) ? DevEP0SIZE : SetupReqLen;
memcpy( pEP0_DataBuf, pDescr, len );
pDescr += len;
}
break;
case USB_SET_ADDRESS:
SetupReqLen = (pSetupReqPak->wValue)&0xff;
break;
case USB_GET_CONFIGURATION:
pEP0_DataBuf[0] = DevConfig;
if ( SetupReqLen > 1 ) SetupReqLen = 1;
break;
case USB_SET_CONFIGURATION:
DevConfig = (pSetupReqPak->wValue)&0xff;
break;
case USB_CLEAR_FEATURE:
if ( ( pSetupReqPak->bRequestType & USB_REQ_RECIP_MASK ) == USB_REQ_RECIP_ENDP )
{
switch( (pSetupReqPak->wIndex)&0xff )
{
case 0x82:
R8_UEP2_CTRL = (R8_UEP2_CTRL & ~( RB_UEP_T_TOG|MASK_UEP_T_RES )) | UEP_T_RES_NAK;
break;
case 0x02:
R8_UEP2_CTRL = (R8_UEP2_CTRL & ~( RB_UEP_R_TOG|MASK_UEP_R_RES )) | UEP_R_RES_ACK;
break;
case 0x81:
R8_UEP1_CTRL = (R8_UEP1_CTRL & ~( RB_UEP_T_TOG|MASK_UEP_T_RES )) | UEP_T_RES_NAK;
break;
case 0x01:
R8_UEP1_CTRL = (R8_UEP1_CTRL & ~( RB_UEP_R_TOG|MASK_UEP_R_RES )) | UEP_R_RES_ACK;
break;
default:
errflag = 0xFF;
break;
}
}
else errflag = 0xFF;
break;
case USB_GET_INTERFACE:
pEP0_DataBuf[0] = 0x00;
if ( SetupReqLen > 1 ) SetupReqLen = 1;
break;
case USB_GET_STATUS:
pEP0_DataBuf[0] = 0x00;
pEP0_DataBuf[1] = 0x00;
if ( SetupReqLen > 2 ) SetupReqLen = 2;
break;
default:
errflag = 0xff;
break;
}
}
}
else errflag = 0xff;
if( errflag == 0xff)
{
R8_UEP0_CTRL = RB_UEP_R_TOG | RB_UEP_T_TOG | UEP_R_RES_STALL | UEP_T_RES_STALL;
}
else
{
if( chtype & 0x80 )
{
len = (SetupReqLen>DevEP0SIZE) ? DevEP0SIZE : SetupReqLen;
SetupReqLen -= len;
}
else len = 0;
R8_UEP0_T_LEN = len;
R8_UEP0_CTRL = RB_UEP_R_TOG | RB_UEP_T_TOG | UEP_R_RES_ACK | UEP_T_RES_ACK;
}
break;
case UIS_TOKEN_IN:
switch ( R8_USB_INT_ST & ( MASK_UIS_TOKEN | MASK_UIS_ENDP ) )
{
case UIS_TOKEN_IN:
switch( SetupReqCode )
{
case USB_GET_DESCRIPTOR:
len = SetupReqLen >= DevEP0SIZE ? DevEP0SIZE : SetupReqLen;
memcpy( pEP0_DataBuf, pDescr, len );
SetupReqLen -= len;
pDescr += len;
R8_UEP0_T_LEN = len;
R8_UEP0_CTRL ^= RB_UEP_T_TOG;
break;
case USB_SET_ADDRESS:
R8_USB_DEV_AD = (R8_USB_DEV_AD&RB_UDA_GP_BIT) | SetupReqLen;
R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
break;
default:
R8_UEP0_T_LEN = 0;
R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
break;
}
break;
case UIS_TOKEN_IN | 1:
R8_UEP1_T_LEN = 0;
KeyBoardHIDEndpBusy = 0;
R8_UEP1_CTRL ^= RB_UEP_T_TOG;
R8_UEP1_CTRL = (R8_UEP1_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_NAK;
break;
case UIS_TOKEN_IN | 2:
R8_UEP2_T_LEN = 0;
MouseHIDEndpBusy = 0;
R8_UEP2_CTRL ^= RB_UEP_T_TOG;
R8_UEP2_CTRL = (R8_UEP2_CTRL & ~MASK_UEP_T_RES) | UEP_T_RES_NAK;
break;
}
break;
case UIS_TOKEN_OUT:
switch ( R8_USB_INT_ST & ( MASK_UIS_TOKEN | MASK_UIS_ENDP ) )
{
case UIS_TOKEN_OUT:
len = R8_USB_RX_LEN;
break;
case UIS_TOKEN_OUT | 1:
if ( R8_USB_INT_ST & RB_UIS_TOG_OK )
{
R8_UEP1_CTRL ^= RB_UEP_R_TOG;
len = R8_USB_RX_LEN;
DevEP1_OUT_Deal( len );
}
break;
}
break;
case UIS_TOKEN_SOF:
break;
default :
break;
}
R8_USB_INT_FG = RB_UIF_TRANSFER;
}
else if( intflag & RB_UIF_BUS_RST )
{
R8_USB_DEV_AD = 0;
R8_UEP0_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
R8_UEP1_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
R8_UEP2_CTRL = UEP_R_RES_ACK | UEP_T_RES_NAK;
R8_USB_INT_FG |= RB_UIF_BUS_RST;
HIDInitFlag = 0;
}
else if( intflag & RB_UIF_SUSPEND )
{
if ( R8_USB_MIS_ST & RB_UMS_SUSPEND ) {;}
else{;}
R8_USB_INT_FG = RB_UIF_SUSPEND;
HIDInitFlag = 0;
}
else
{
R8_USB_INT_FG = intflag;
}
}
12、键盘发送按键和接收指示灯状态
static void KB_Send(UINT8 *kb)
{
if(HIDInitFlag == 0)
return;
while( KeyBoardHIDEndpBusy )
{
; //如果忙(上一包数据没有传上去),则等待。
}
KeyBoardHIDEndpBusy = 1; //设置为忙状态
memcpy(pEP1_IN_DataBuf, kb, 8);
DevEP1_IN_Deal(8);
}
/*******************************************************************************
* Function Name : DevEP1_OUT_Deal
* Description : Deal device Endpoint 1 OUT.
* Input : l: Data length.
* Return : None
*******************************************************************************/
void DevEP1_OUT_Deal( UINT8 l )
{
KeyLedStatus = pEP1_OUT_DataBuf[0];
}
13、鼠标的发送接口
static void MS_Send(UINT8 *ms)
{
if(HIDInitFlag == 0)
return;
while( MouseHIDEndpBusy )
{
; //如果忙(上一包数据没有传上去),则等待。
}
MouseHIDEndpBusy = 1; //设置为忙状态
memcpy(pEP1_IN_DataBuf, ms, 3);
DevEP1_IN_Deal(3);
}
14、移植arduinio的键盘功能
#define SHIFT 0x80
const UINT8 asciimap[128] =
{
0x00, // NUL
0x00, // SOH
0x00, // STX
0x00, // ETX
0x00, // EOT
0x00, // ENQ
0x00, // ACK
0x00, // BEL
0x2a, // BS Backspace
0x2b, // TAB Tab
0x28, // LF Enter
0x00, // VT
0x00, // FF
0x00, // CR
0x00, // SO
0x00, // SI
0x00, // DEL
0x00, // DC1
0x00, // DC2
0x00, // DC3
0x00, // DC4
0x00, // NAK
0x00, // SYN
0x00, // ETB
0x00, // CAN
0x00, // EM
0x00, // SUB
0x00, // ESC
0x00, // FS
0x00, // GS
0x00, // RS
0x00, // US
0x2c, // ' '
0x1e|SHIFT, // !
0x34|SHIFT, // "
0x20|SHIFT, // #
0x21|SHIFT, // $
0x22|SHIFT, // %
0x24|SHIFT, // &
0x34, // '
0x26|SHIFT, // (
0x27|SHIFT, // )
0x25|SHIFT, // *
0x2e|SHIFT, // +
0x36, // ,
0x2d, // -
0x37, // .
0x38, // /
0x27, // 0
0x1e, // 1
0x1f, // 2
0x20, // 3
0x21, // 4
0x22, // 5
0x23, // 6
0x24, // 7
0x25, // 8
0x26, // 9
0x33|SHIFT, // :
0x33, // ;
0x36|SHIFT, // <
0x2e, // =
0x37|SHIFT, // >
0x38|SHIFT, // ?
0x1f|SHIFT, // @
0x04|SHIFT, // A
0x05|SHIFT, // B
0x06|SHIFT, // C
0x07|SHIFT, // D
0x08|SHIFT, // E
0x09|SHIFT, // F
0x0a|SHIFT, // G
0x0b|SHIFT, // H
0x0c|SHIFT, // I
0x0d|SHIFT, // J
0x0e|SHIFT, // K
0x0f|SHIFT, // L
0x10|SHIFT, // M
0x11|SHIFT, // N
0x12|SHIFT, // O
0x13|SHIFT, // P
0x14|SHIFT, // Q
0x15|SHIFT, // R
0x16|SHIFT, // S
0x17|SHIFT, // T
0x18|SHIFT, // U
0x19|SHIFT, // V
0x1a|SHIFT, // W
0x1b|SHIFT, // X
0x1c|SHIFT, // Y
0x1d|SHIFT, // Z
0x2f, // [
0x31, // bslash
0x30, // ]
0x23|SHIFT, // ^
0x2d|SHIFT, // _
0x35, // `
0x04, // a
0x05, // b
0x06, // c
0x07, // d
0x08, // e
0x09, // f
0x0a, // g
0x0b, // h
0x0c, // i
0x0d, // j
0x0e, // k
0x0f, // l
0x10, // m
0x11, // n
0x12, // o
0x13, // p
0x14, // q
0x15, // r
0x16, // s
0x17, // t
0x18, // u
0x19, // v
0x1a, // w
0x1b, // x
0x1c, // y
0x1d, // z
0x2f|SHIFT, // {
0x31|SHIFT, // |
0x30|SHIFT, // }
0x35|SHIFT, // ~
0 // DEL
};
// Low level key report: up to 6 keys and shift, ctrl etc at once
typedef struct
{
UINT8 modifiers;
UINT8 reserved;
UINT8 keys[6];
} KeyReport;
/*KeyBoard Function*/
// press() adds the specified key (printing, non-printing, or modifier)
// to the persistent key report and sends the report. Because of the way
// USB HID works, the host acts like the key remains pressed until we
// call release(), releaseAll(), or otherwise clear the report and resend.
UINT8 KB_Press(UINT8 k)
{
UINT8 i;
if (k >= 136) { // it's a non-printing key (not a modifier)
k = k - 136;
} else if (k >= 128) { // it's a modifier key
_keyReport.modifiers |= (1<<(k-128));
k = 0;
} else { // it's a printing key
k = asciimap[k];
if (k & 0x80) { // it's a capital letter or other character reached with shift
_keyReport.modifiers |= 0x02; // the left shift modifier
k &= 0x7F;
}
}
// Add k to the key report only if it's not already present
// and if there is an empty slot.
if (_keyReport.keys[0] != k && _keyReport.keys[1] != k &&
_keyReport.keys[2] != k && _keyReport.keys[3] != k &&
_keyReport.keys[4] != k && _keyReport.keys[5] != k) {
for (i=0; i<6; i++) {
if (_keyReport.keys[i] == 0x00) {
_keyReport.keys[i] = k;
break;
}
}
if (i == 6) {
// rt_kprintf("press error.\r\n");
return 0;
}
}
KB_Send((UINT8 *)&_keyReport);
return 1;
}
// release() takes the specified key out of the persistent key report and
// sends the report. This tells the OS the key is no longer pressed and that
// it shouldn't be repeated any more.
UINT8 KB_Release(UINT8 k)
{
uint8_t i;
if (k >= 136) { // it's a non-printing key (not a modifier)
k = k - 136;
} else if (k >= 128) { // it's a modifier key
_keyReport.modifiers &= ~(1<<(k-128));
k = 0;
} else { // it's a printing key
k = asciimap[k];
if (!k) {
return 0;
}
if (k & 0x80) { // it's a capital letter or other character reached with shift
_keyReport.modifiers &= ~(0x02); // the left shift modifier
k &= 0x7F;
}
}
// Test the key report to see if k is present. Clear it if it exists.
// Check all positions in case the key is present more than once (which it shouldn't be)
for (i=0; i<6; i++) {
if (0 != k && _keyReport.keys[i] == k) {
_keyReport.keys[i] = 0x00;
}
}
KB_Send((UINT8 *)&_keyReport);
return 1;
}
void KB_ReleaseAll(void)
{
_keyReport.keys[0] = 0;
_keyReport.keys[1] = 0;
_keyReport.keys[2] = 0;
_keyReport.keys[3] = 0;
_keyReport.keys[4] = 0;
_keyReport.keys[5] = 0;
_keyReport.modifiers = 0;
KB_Send((UINT8 *)&_keyReport);
}
UINT8 KB_Write(UINT8 c)
{
uint8_t p = KB_Press(c); // Keydown
KB_Release(c); // Keyup
return p; // just return the result of press() since release() almost always returns 1
}
UINT8 KB_Write_str(const UINT8 *buffer, size_t size) {
size_t n = 0;
while (size--) {
if (*buffer != '\r') {
if (KB_Write(*buffer)) {
n++;
} else {
break;
}
}
buffer++;
}
return n;
}
bool KB_Read_num_lock(void)
{
return KeyLedStatus & 0x01;
}
bool KB_Read_caps_lock(void)
{
return KeyLedStatus & 0x02;
}
15、移植arduino的鼠标功能
typedef struct
{
UINT8 button;
UINT8 X;
UINT8 Y;
UINT8 Z;
} MouseReport;
/*Mouse Function*/
void MS_Move(INT8 x, INT8 y, INT8 wheel)
{
_mouseReport.X = x;
_mouseReport.Y = y;
_mouseReport.Z = wheel;
MS_Send((UINT8 *)&_mouseReport);
}
void MS_Click(UINT8 b)
{
_mouseReport.button = b;
MS_Move(0,0,0);
_mouseReport.button = 0;
MS_Move(0,0,0);
}
void MS_Buttons(UINT8 b)
{
if (b != _mouseReport.button)
{
_mouseReport.button = b;
MS_Move(0,0,0);
}
}
void MS_Press(UINT8 b)
{
MS_Buttons(_mouseReport.button | b);
}
void MS_Release(UINT8 b)
{
MS_Buttons(_mouseReport.button & ~b);
}
bool MS_isPressed(uint8_t b)
{
if ((b & _mouseReport.button) > 0)
return true;
return false;
}
嗯,其实整体还是比较简单的,只是代码比较多,最好还是直接读上面我的代码仓库里面的。
全部0条评论
快来发表一下你的评论吧 !