0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

【RTT大赛作品连载】CH32V103 USBHID键盘鼠标

煲仔卤煮的炼钢 来源:煲仔卤煮的炼钢 作者:煲仔卤煮的炼钢 2021-11-29 08:49 次阅读

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;
}

嗯,其实整体还是比较简单的,只是代码比较多,最好还是直接读上面我的代码仓库里面的。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    4981

    文章

    18272

    浏览量

    288328
  • 键盘
    +关注

    关注

    4

    文章

    843

    浏览量

    38997
  • RTT
    RTT
    +关注

    关注

    0

    文章

    63

    浏览量

    16881
  • RT-Thread
    +关注

    关注

    31

    文章

    1148

    浏览量

    38865
  • ch32
    +关注

    关注

    0

    文章

    73

    浏览量

    508
收藏 人收藏

    评论

    相关推荐

    RTT大赛作品连载】AB32VG1评估板到货控制彩灯测试

    RTT大赛作品连载】AB32VG1评估板到货控制彩灯测试篇;接下来看看在如何AB32VG1评估板控制彩灯! 在RT-ThreadStudio新建项目到对应开发配置及下载及验证测试!
    的头像 发表于 11-07 19:39 4847次阅读
    【<b class='flag-5'>RTT</b><b class='flag-5'>大赛</b><b class='flag-5'>作品</b><b class='flag-5'>连载</b>】AB32VG1评估板到货控制彩灯测试

    RTT大赛作品连载CH32V103开发板资料及上电首测

    简介   参加论坛举办的rtt大赛也有几天了,我选用的是CH32V103的板子,第一步就是收集板卡的相关资料了。 CH32V103是 沁恒微电子设计的一款RSIC-V的单片机,使用了R
    的头像 发表于 11-15 08:45 7611次阅读
    【<b class='flag-5'>RTT</b><b class='flag-5'>大赛</b><b class='flag-5'>作品</b><b class='flag-5'>连载</b>】<b class='flag-5'>CH32V103</b>开发板资料及上电首测

    RTT大赛作品连载】AB32VG1评估板 音乐播放器

    RTT大赛作品连载】AB32VG1评估板 音乐播放器
    的头像 发表于 11-12 21:11 5880次阅读
    【<b class='flag-5'>RTT</b><b class='flag-5'>大赛</b><b class='flag-5'>作品</b><b class='flag-5'>连载</b>】AB32VG1评估板 音乐播放器

    RTT大赛作品连载】中科蓝讯AB32VG1开发板开箱篇

    介绍电路原理图分析接口说明,AB32VG1开发板是以中科蓝讯(Bluetrum)公司推出的基于RISC-V架构的高配置芯片AB5301A为核心所组成的。【RTT大赛作品连载】中科蓝讯A
    的头像 发表于 11-13 10:01 9697次阅读
    【<b class='flag-5'>RTT</b><b class='flag-5'>大赛</b><b class='flag-5'>作品</b><b class='flag-5'>连载</b>】中科蓝讯AB32VG1开发板开箱篇

    CH32V103数据手册

    教程基于沁恒32位通用增强型RISC-V架构MCU CH32V103,力争全面分析CH32V103的每个外设功能及使用方法,手把手教大家玩转RISC-V MCU应用开发。教程侧重于外设
    发表于 07-19 07:55

    【文章连载】RT-Thread创新应用大赛文章汇总

    连载】中科蓝讯AB32VG1开发板开箱篇专栏作者:煲仔卤煮的炼钢【RTT大赛作品连载CH32V103
    发表于 10-11 15:13

    【二等奖】RT-Thread创新应用设计大赛作品1

    鼠标键盘的HID库,写在了【RTT大赛作品连载CH32V103
    发表于 01-25 11:49

    如何入门CH32V103

    如何入门CH32V103
    发表于 02-16 07:12

    CH32V103复位如何保持变量?

    CH32V103复位如何保持变量,只有上电才清零. 原来用stm32f103可以设置 __attribute__((unused, section(".noinit")))改用 CH32V103 后, 每次复位都会得到一固定的值
    发表于 06-15 06:13

    CH32V103基础教程3-USART

    本章教程通过CH32V103的串口与上位机之间进行数据的发送和接收,具体执行步骤如下:· 上位机向CH32V103发送数据;· CH32V103接收数据并发送给上位机;· 上位机接收CH32V
    发表于 04-13 15:56

    CH32V103基础教程83-USB模拟鼠标键盘设备

    本章教学主要使用CH32V103 USB模拟鼠标键盘设备。 1、USB简介及相关函数介绍关于USB工具介绍,可参考前面章节。 2、硬件设计本章教程主要进行USB模拟鼠标键盘实验,只需
    发表于 05-09 16:56

    RISC-V MCU应用开发教程之CH32V103

    教程基于沁恒32位通用增强型RISC-V架构MCU CH32V103,力争全面分析CH32V103的每个外设功能及使用方法,手把手教大家玩转RISC-V MCU应用开发。教程侧重于外设功能分析及代码
    发表于 11-18 16:21 18次下载
    RISC-V MCU应用开发教程之<b class='flag-5'>CH32V103</b>

    ch32v103应用教程 ch32v103c8t6兼容 ch32v103c8t6烧录器

    ch32v103应用教程 ch32v103c8t6兼容 ch32v103c8t6烧录器 ch32v103c8t6编程器 CH32V103应用
    的头像 发表于 08-22 15:19 991次阅读

    ch32v103和stm32的区别

    ch32v103和stm32的区别  在现代工业和科技领域中,集成电路是不可或缺的一个组成部分。集成电路向来是在研发过程中不断进步和发展,而其中最为著名和广泛使用的两种芯片,就是ch32v103
    的头像 发表于 08-22 15:48 822次阅读

    ch32v103与stm32f103的区别

    ch32v103与stm32f103的区别  Ch32v103与STM32f103是两种不同的芯片,虽然它们都是基于ARM Cortex-M3内核的32位微控制器,但它们在硬件配置、功
    的头像 发表于 08-22 15:49 1376次阅读