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;
}
嗯,其實整體還是比較簡單的,只是代碼比較多,最好還是直接讀上面我的代碼倉庫里面的。
-
嵌入式
+關注
關注
5083文章
19129瀏覽量
305434 -
鍵盤
+關注
關注
4文章
859瀏覽量
39689 -
RTT
+關注
關注
0文章
65瀏覽量
17145 -
RT-Thread
+關注
關注
31文章
1290瀏覽量
40161 -
ch32
+關注
關注
0文章
73瀏覽量
656
發布評論請先 登錄
相關推薦
評論