STM32 keyboard USB键盘功能的实现

下面编写下USB键盘的程序,依然在CustomHID工程上修改。
依旧最先修改的是usb_desc.c文件。我们从设备描述符开始讲述。
设备描述符需要修改下bMaxPacketSize(最大包长度)域为0x08,因为被本次的工程最大通讯长度就是8字节,正好符合USB规范,所以这里改成0x08,还要注意在usb_prop.c的DEVICE_PROP Device_Property结构体里注册的最大长度也要是0x08,与设备描述符的要相同(我们在下文说到)。这里最好还要修改下PID和VID的域的值,以防该PID和VID对应的设备已经在电脑里有了驱动而导致功能不正常。

/* USB标准设备描述符*/ const uint8_t Keyboard_DeviceDescriptor[KEYBOARD_SIZ_DEVICE_DESC] = { 0x12, /*bLength:长度,设备描述符的长度为18字节*/ USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType:类型,设备描述符的编号是0x01*/ 0x00, /*bcdUSB:所使用的USB版本为2.0*/ 0x02, 0x00, /*bDeviceClass:设备所使用的类代码*/ 0x00, /*bDeviceSubClass:设备所使用的子类代码*/ 0x00, /*bDeviceProtocol:设备所使用的协议*/ 0x08, /*bMaxPacketSize:最大包长度为8字节*/ 0x78, /*idVendor:厂商ID为0x7788*/ 0x67, 0x12, /*idProduct:产品ID为0x1122*/ 0x01, 0x00, /*bcdDevice:设备的版本号为2.00*/ 0x02, 1, /*iManufacturer:厂商字符串的索引*/ 2, /*iProduct:产品字符串的索引*/ 3, /*iSerialNumber:设备的序列号字符串索引*/ 0x01 /*bNumConfiguration:设备有1种配置*/ }; /* keyboard设备描述符 */

接下去修改下配置描述符。 找到接口的描述符的bNumEndpoints(该接口所使用的端点数)域,不用修改,但需要提下,还是0x02,表示使用2个端点。修改下接口描述符的 nInterfaceProtocol (该接口使用的协议)域为0x01,表示是键盘。在输入端点描述符中端点设置 端点1 为为中断传输的输入端点,设置 wMaxPacketSize:(该端点支持的最大包长度)域的值为0x08,因为本次键盘的工程需要向USB主机发送8字节。在输出端点描述符设置端点1为中断传输的输出端点, 设置为中断传输 设置  wMaxPacketSize:(该端点支持的最大包长度)域的值为0x01,因为USB主机只会向USB从设备发送1个字节。

/* USB配置描述符集合(配置、接口、端点、类、厂商)(Configuration, Interface, Endpoint, Class, Vendor */ const uint8_t Keyboard_ConfigDescriptor[KEYBOARD_SIZ_CONFIG_DESC] = { 0x09, /*bLength:长度,设备字符串的长度为9字节*/ USB_CONFIGURATION_DESCRIPTOR_TYPE, /*bDescriptorType:类型,配置描述符的类型编号为0x2*/ KEYBOARD_SIZ_CONFIG_DESC, /*wTotalLength:配置描述符的总长度为41字节*/ 0x00, 0x01, /*bNumInterfaces:配置所支持的接口数量1个*/ 0x01, /*bConfigurationValue:该配置的值*/ 0x00, /*iConfiguration:该配置的字符串的索引值,该值为0表示没有字符串*/ 0xC0, /* bmAttributes:设备的一些特性,0xc0表示自供电,不支持远程唤醒 D7:保留必须为1,D6:是否自供电,D5:是否支持远程唤醒,D4~D0:保留设置为0*/ 0x32, /*从总线上获得的最大电流为100mA */ // 0x96, /*MaxPower:设备需要从总线上获取多少电流,单位为2mA,0x96表示300mA*/ /************** 接口描述符****************/ /* 09 */ 0x09, /*bLength:长度,接口描述符的长度为9字节 */ USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType:接口描述符的类型为0x4 */ 0x00, /*bInterfaceNumber:该接口的编号*/ 0x00, /*bAlternateSetting:该接口的备用编号 */ 0x02, /*bNumEndpoints:该接口所使用的端点数*/ 0x03, /*bInterfaceClass该接口所使用的类为HID*/ 0x01, /*bInterfaceSubClass:该接口所用的子类 1=BOOT, 0=no boot */ 0x01, /*nInterfaceProtocol :该接口使用的协议0=none, 1=keyboard, 2=mouse */ 0, /*iInterface: 该接口字符串的索引 */ /*****************HID描述符 ********************/ /* 18 */ 0x09, /*bLength: HID描述符的长度为9字节 */ HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID的描述符类型为0x21 */ 0x10, /*bcdHID: HID协议的版本为1.1 */ 0x01, 0x21, /*bCountryCode: 国家代号 */ 0x01, /*bNumDescriptors: 下级描述符的数量*/ 0x22, /*bDescriptorType:下级描述符的类型*/ KEYBOARD_SIZ_REPORT_DESC,/* wItemLength: 下一集描述符的长度*/ 0x00, /********************输入端点描述符******************/ /* 27 */ 0x07, /* bLength: 端点描述符的长度为7字节*/ USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: 端点描述符的类型为0x05*/ 0x81, /* bEndpointAddress: 该端点(输入)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端点号*/ 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:保留,*/ 0x08, /* wMaxPacketSize: 该端点支持的最大包长度为8字节*/ 0x00, 0x0A, /* bInterval: 轮询间隔(32ms) */ /********************输出端点描述符*******************/ /* 34 */ 0x07, /* bLength: 端点描述符的长度为7字节*/ USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: 端点描述符的类型为0x05*/ 0x01, /* bEndpointAddress: 该端点(输入)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端点号*/ 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:保留,*/ 0x01, /* wMaxPacketSize: 该端点支持的最大包长度为字节*/ 0x00, 0x0A, /* bInterval: 轮询间隔(32ms) */ /* 41 */ };

讲到报告描述符,得将报告描述符的整个替换掉。该报告描述符定义了8字节的输入域,第一个字节表示特殊件是否按下,键盘的特殊键包括:ctrl,shift,alt键等,该字节D0表示Ctrl键,D1表示Shift键,D2表示Alt键,其他位保留。第二个字节保留,固定值为0。第三个字节到第八个字节用来保存按键值,如果只有一个按键按下,则按键值保存在第三个字节,如果有两个或两个以上的按键按下,则依次保存在从第三字节开始的字节中,当然最多只能6个按键同时按下,超过6个键值,则不响应。报告描述符还定义了一个字节的输出域,该字节是是用来控制LED灯的,我们都知道键盘的上有几个LED灯,比如说大小写键的LED灯等,该字节的定义:D0:Num Lock   D1:Cap Lock   D2:Scroll Lock   D3:Compose   D4:Kana,由于我一直用笔记本电脑的键盘,所以我也只认识前三个键。在这个工程,我只做了Ctrl键、Shift键、Num Lock键,以及A键。

/* 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*/ const uint8_t Keyboard_ReportDescriptor[KEYBOARD_SIZ_REPORT_DESC] = { /*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, 0x02, // 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, 0x02, // 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, 0x06, // REPORT_SIZE (6) //0x91:1001 00 01 这是个主条目,有1*6bit数据域最为输出,属性为:Cnst,Var,Abs 0x91, 0x03, // OUTPUT (Cnst,Var,Abs) 0xc0 // END_COLLECTION };

说到按键的键值的定义,那就要参考HID用途表(从54页开始),截个开始的功能表吧,凑够途中可以看到A键的键值是4。
STM32 keyboard USB键盘功能的实现 - ziye334 - ziye334的博客
 
其他的一些描述符,这里也贴出来:

/* 语言ID描述符 */ const uint8_t Keyboard_StringLangID[KEYBOARD_SIZ_STRING_LANGID] = { KEYBOARD_SIZ_STRING_LANGID, /*bLength:本描述符的长度为4字节*/ USB_STRING_DESCRIPTOR_TYPE, /*bDescriptorType:字符串描述符的类型为0x03*/ 0x09, /*bString:语言ID为0x0409,表示美式英语*/ 0x04 }; /* LangID = 0x0409: U.S. English*/ /*厂商字符串描述符*/ const uint8_t Keyboard_StringVendor[KEYBOARD_SIZ_STRING_VENDOR] = { KEYBOARD_SIZ_STRING_VENDOR, /*bLength:厂商字符串描述符的长度*/ USB_STRING_DESCRIPTOR_TYPE, /*bDescriptorType:字符串描述符的类型为0x03*/ 'B' , 0, 'y', 0, ':' , 0, 'z' , 0, 'i', 0, 'y', 0,'e', 0,'3', 0, '3', 0, '4', 0 /*自定义*/ }; /*产品的字符串描述符*/ const uint8_t Keyboard_StringProduct[KEYBOARD_SIZ_STRING_PRODUCT] = { KEYBOARD_SIZ_STRING_PRODUCT, /* bLength:产品的字符串描述符*/ USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType:字符串描述符的类型为0x03*/ 'z', 0, 'i', 0, 'y', 0, 'e', 0, '3', 0, '3', 0 ,'4',0/*自定义*/ }; /*产品序列号的字符串描述符*/ uint8_t Keyboard_StringSerial[KEYBOARD_SIZ_STRING_SERIAL] = { KEYBOARD_SIZ_STRING_SERIAL, /* bLength:产品序列号*/ USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType:字符串描述符的类型为0x03*/ '1', 0, '2', 0, '3', 0,'4', 0,'5', 0, '6', 0, '7', 0 /*自定义*/ };

这里还要贴出usb_desc.h中的一些宏定义,这些宏定义在上面的描述符中用到:

#define USB_DEVICE_DESCRIPTOR_TYPE 0x01 //设备描述符类型 #define USB_CONFIGURATION_DESCRIPTOR_TYPE 0x02 //配置描述符类型 #define USB_STRING_DESCRIPTOR_TYPE 0x03 //字符串描述符类型 #define USB_INTERFACE_DESCRIPTOR_TYPE 0x04 //接口描述符类型 #define USB_ENDPOINT_DESCRIPTOR_TYPE 0x05 //端点描述符类型 #define HID_DESCRIPTOR_TYPE 0x21 //HID描述符类型 #define KEYBOARD_SIZ_HID_DESC 0x09 //HID描述符的长度 #define KEYBOARD_OFF_HID_DESC 0x12 //HID描述符在配置描述符集合数组中的偏移 #define KEYBOARD_SIZ_DEVICE_DESC 18 //设备描述符的长度 #define KEYBOARD_SIZ_CONFIG_DESC 41 //配置描述符的长度 #define KEYBOARD_SIZ_REPORT_DESC 61 //报告描述符的的长度 #define KEYBOARD_SIZ_STRING_LANGID 4 //语言ID字符串描述符的长度 #define KEYBOARD_SIZ_STRING_VENDOR 22 //厂商字符串描述符的长度 #define KEYBOARD_SIZ_STRING_PRODUCT 16 //产品字符串描述符的长度 #define KEYBOARD_SIZ_STRING_SERIAL 26 //序列号字符串描述符的长度 #define STANDARD_ENDPOINT_DESC_SIZE 0x09 // #define REPORT_COUNT 8 //报告返回长度



讲完了usb_desc.c的文件,下面要修改的是usb_prop.c文件。首先要修改下这个文件只需要修改下DEVICE_PROP Device_Property里面的内容,把 MaxPacketSize域的大小改为0x08,与设备描述符对应。如下:

DEVICE_PROP Device_Property = //注册一些CustomHID函数 { Keyboard_init, //Keyboard的初始化函数 Keyboard_Reset, //Keyboard的复位函数 Keyboard_Status_In, //Keyboard状态输入函数 Keyboard_Status_Out, //Keyboard状态输出函数 Keyboard_Data_Setup, //Keyboard的处理有数据阶段的特殊类请求函数 Keyboard_NoData_Setup, //Keyboard的处理没有数据阶段的特殊类请求函数 Keyboard_Get_Interface_Setting, //Keyboard获取接口及备用接口设置(是否可用) Keyboard_GetDeviceDescriptor, //Keyboard获取设备描述符 Keyboard_GetConfigDescriptor, //Keyboard获取配置描述符 Keyboard_GetStringDescriptor, //Keyboard获取字符串描述符 0, //当前库未使用 0x08 /*MAX PACKET SIZE*/ //最大的包长度为64字节 };

接下去要修改KeyBoard_Reset()函数。这个函数只要改下端点的配置,配置端点1输出有次,及端点1输入有效:

/******************************************************************************* * Function Name : Keyboard_Reset. * Description : Keyboard reset routine.复位 * Input : None. * Output : None. * Return : None. *******************************************************************************/ void Keyboard_Reset(void) { /* Set KEYBOARD_DEVICE as not configured */ pInformation->Current_Configuration = 0; //设置当前的配置为0,表示没有配置过 pInformation->Current_Interface = 0; //默认的接口 /* Current Feature initialization */ pInformation->Current_Feature = Keyboard_ConfigDescriptor[7];//当前的属性,bmAttributes:设备的一些特性,0xc0表示自供电,不支持远程唤醒 #ifdef STM32F10X_CL /* EP0 is already configured in DFU_Init() by USB_SIL_Init() function */ /* Init EP1 IN snd EP1 OUT as Interrupt endpoint */ OTG_DEV_EP_Init(EP1_IN, OTG_DEV_EP_TYPE_INT, EP1_SIZE); OTG_DEV_EP_Init(EP1_OUT, OTG_DEV_EP_TYPE_INT, EP1_SIZE); #else SetBTABLE(BTABLE_ADDRESS); /* Initialize Endpoint 0 */ SetEPType(ENDP0, EP_CONTROL); //设置端点1为控制端点 SetEPTxStatus(ENDP0, EP_TX_STALL); //设置端点0发送延时 SetEPRxAddr(ENDP0, ENDP0_RXADDR); //设置端点0的接收缓冲区地址 SetEPTxAddr(ENDP0, ENDP0_TXADDR); //设置端点0的发送缓冲区地址 Clear_Status_Out(ENDP0); //清除端点0的状态 SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);//设置端点0的接收的计数 SetEPRxValid(ENDP0); //使能接收状态 /* Initialize Endpoint 1 */ SetEPType(ENDP1, EP_INTERRUPT); //设置端点1为中断控制端点 SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置端点1的发送缓冲地址 SetEPTxCount(ENDP1, 8); //设置端点1的接收计数 // SetEPRxStatus(ENDP1, EP_RX_DIS); //设置端点1接收无效 SetEPTxStatus(ENDP1, EP_TX_NAK); SetEPRxAddr(ENDP1, ENDP1_RXADDR); //设置接收数据的地址 SetEPRxCount(ENDP1, 1); //设置接收长度 SetEPRxStatus(ENDP1, EP_RX_VALID);//设置端点有效,可以接收数据 // bDeviceState = ATTACHED; //设置设备状态为 ATTACHED状态 /* Set this device to response on default address */ SetDeviceAddress(0); //设置设备为默认地址 #endif /* STM32F10X_CL */ bDeviceState = ATTACHED; }

这样的话,端点就配置好了,我们还要编写模拟鼠标的功能程序,这里我使用四按键分别模拟键盘的。我们首先在hw_config.c中编写按键引脚的配置函数USB_GPIO_Configuration()如下:

/******************************************************************************* * Function Name : USB_GPIO_Confguration * Description : USB相关引脚配置 * Input : None. * Output : None. * Return : None. *******************************************************************************/ void USB_GPIO_Confguration(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_DISCONNECT, ENABLE); /*上拉电阻引脚*/ GPIO_InitStructure.GPIO_Pin = USB_DISCONNECT_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(USB_DISCONNECT, &GPIO_InitStructure); /*KEY按键引脚配置*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_8; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 ; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOC, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOD, &GPIO_InitStructure); }

该函数在BSP.c的BSP_Init()中调用。
接下去我们要 编写按键扫描函数Keyboard_Scan(),这个函数不像鼠标工程里的按键扫描函数一样以检测到按键就返回,这个函数需要检测按键同时按下的情况,所以需要将所有的按键检测一遍在返回,代码如下:

/******************************************************************************* * Function Name : Keyboard_Scan. * Description : 键盘按键扫描 * Input : None. * Output : None. * Return value : 返回方向的值 *******************************************************************************/ uint8_t Keyboard_Scan(void) { uint8_t temp=0; if(KEY_CTRL) //Ctrl键按下 { Delay_ms(50); if(KEY_CTRL) temp|=CTRL_B; } if(KEY_SHIFT) //Shift键按下 { Delay_ms(50); if(KEY_SHIFT) temp|=SHIFT_B; } if(KEY_CAPSLOCK) //大小写键按下 { Delay_ms(50); if(KEY_CAPSLOCK) temp|=CAPSLOCK_B; } if(KEY_CHARA) //字符'a'键按下 { Delay_ms(50); if(KEY_CHARA) temp|=KEYA_B; } return temp; }

之后还要编写键值发送函数:

/******************************************************************************* * Function Name : KeyBoard_Send. * Description : 发送keyboard的信息 * Input : Keys: 检测到被按下的按键值 * Output : None. * Return value : None. *******************************************************************************/ void Keyboard_Send(uint8_t key) { uint8_t Keyboad_Buf[8]={0,0,0,0,0,0,0,0}; uint8_t i=2; if(key&CTRL_B) //Ctrl是特殊键,在第一个字节的D0 { Keyboad_Buf[0]|=0x01; printf("CTRL键\r\n"); } if(key&SHIFT_B) //Shift是特殊键,在第一个字节的D1 { Keyboad_Buf[0]|=0x02; printf("SHIFT键\r\n"); } if(key&CAPSLOCK_B) //Caps Lock键 { Keyboad_Buf[i++]=0x39;//在HID用途表中代号是0x39 printf("CAPS LOCK键\r\n"); } if(key&KEYA_B) //A键 { Keyboad_Buf[i++]=0x04;//在HID用途表中代号为0x04 printf("A键\r\n"); } USB_SIL_Write(EP1_IN, Keyboad_Buf, 8); SetEPTxValid(ENDP1); }

这个函数有个参数key,我们通过Keyboard_Send(Keyboard_Scan())把扫描到的按键值传递到Keyboard_Send函数中,在该函数中根据键值来填充数组的值,如果检测到ctrl键则置Keyboad_Buf[0]的D0位为1,如果检测到时shift键,则置Keyboad_Buf[0]的D1为1。如果是检测到是Caps lock键或其他键,置Keyboad_Buf[2]开始的字节。
最后要编写main函数了,main()函数很简答就是在while(1)中扫描按键,如果有按键按下,则调用 Keyboard_Send()发送函数,发送按键值:

/******************************************************** 函数:main() 描述:程序入口地址 参数:无 返回:无 ********************************************************/ int main(void) { BSP_Init(); printf(" |===============================================|\r\n"); printf(" USB Keyboard 程序开始 \r\n"); printf("|===============================================|\r\n"); while (bDeviceState != CONFIGURED); while(1) { if (bDeviceState == CONFIGURED) //如果USB已将配置好了 { /* 检测按键状态,并发送鼠标位置数据 */ if (Keyboard_Scan()!= 0) //检查是否有按键按下 { Keyboard_Send(Keyboard_Scan()); //如果有,发送键值 } else //没有按键按下 { Keyboard_Send(0); //发送空数据,如果不发,USB主机会认为你一直在键入上次的键值 } } } }

这里需要注意的是,每次发送键值后,都要发送8字节的空数据到USB主机中,否则,我们举个例子,我按下A键,结果USB主机收到后就会在屏幕上输入'a',但是会一直不停得输入’a‘,这个问题当时也困扰我许久,最后才发现,原来USB主机会一直保持上次收到的数据进行操作,我们上次按下'a',则电脑就会一直打印'a'不停,所以在发送完按键值后,需要在发送空数据给USB主机。

最后,我还是贴出我的readme.txt让大家更详细了解下程序软硬件:
===================================================================================
下载方式
===================================================================================
SWD JTAG


===================================================================================
程序功能
===================================================================================
模拟键盘的功能,这里模拟了4个按键的功能,分别为:ctrl,shift,caps lock,A这四个键。


===================================================================================
硬件连接
===================================================================================
神州III号开发板:
ctrl键:开发板上的WAKEUP键,连接PA0。
shift键:开发板上的TEMPER键,连接PC13。
caps lock键:开发板上的USER1键,连接PA8。
A键:开发板上的USER2键,连接PD3
LED1:开发板上的标号为OS1的LED,大小写锁键对应的LED

===================================================================================
软件设置
===================================================================================
连接串口2,打开串口调试助手,选择好相应的COM口
  按如下进行设置:
  ----------------
  波特率 | 115200 |
  ----------------
  数据位 |   8    |
  ----------------
  停止位 |   1    |
  ----------------
  校验位 | None   |
  ----------------
  流控制 | None   |
  ----------------

===================================================================================
实验现象
===================================================================================
按下开发板模拟的ctrl键,配合电脑键盘上的按键,比如ctrl+c,ctrl+v等,实现一些功能。
按下开发板模拟的shift键,配合模拟的A键,就可以输出大写A了
当然,同时按下ctrl+shift键,实现输入法的切换
按下开发板模拟的caps Lock键,就能进行大小写切换,大写时,开发板上的LED1亮,小写时灭
按下开发板模拟的A键,就能输入a了

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值