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

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 0x80const 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, // 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 oncetypedef 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<= 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 0) return true; return false;}  
嗯,其实整体还是比较简单的,只是代码比较多,最好还是直接读上面我的代码仓库里面的。

美或对中兴华为启动第二轮调查
未见其人先闻其声 iPhone 8未发布已压制S8销售
详解Linux系统文件页表目录和Linux系统页表结构
本月8日电信版小米手机正式发布
薄膜表面缺陷检测系统可检测出表面的质量问题
【RTT大赛作品连载】CH32V103 USBHID键盘鼠标
朋友圈要炸了!苹果刚发布iOS11系统,就怼上微信,微信或许也将退出iOS
富士X-T100数码相机搭载了2420万像素APS-C传感器拍摄性能十分强大
采用ARM Cortex-M01处理器的DMX512调光设备的软硬件系统设计
【飞凌嵌入式】国产平台RK3568核心板内存大升级!
介绍几个关于ISP算法架构的项目
太阳能微型逆变器的解决方案介绍
走不出品牌误区,难抓得住粉丝流量
大唐移动:SPAN ADT自动路测系统介绍
“元件强基”与“电机提效”两大产业政策为万物互联与智能制造铺就创新发展道路
基于C8051F020单片机实现多波段光谱辐射计采集系统的设计
SMT焊盘氧化的原因,避免SMT焊盘氧化的方法
人工智能医疗加速发展 AI技术监听细胞声音
窄脉冲LIV测试系统的典型应用有哪些?
浅谈Zynq Qspi控制器的三种模式