STM32 keyboard USB键盘功能的实现

发布者:Tapir最新更新时间:2016-12-20 来源: eefocus关键字: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了


关键字:STM32  keyboard  USB键盘 引用地址:STM32 keyboard USB键盘功能的实现

上一篇:STM32 JoystickMouse USB游戏杆鼠标的实现
下一篇:STM32 USB 大容量存储器Mass Storage工程的讲解

推荐阅读最新更新时间:2024-03-16 15:26

STM32看门狗与复位IC同时存在的注意事项
1写在前面 我们都知道在复杂环境,比如一些工厂,特别是在有大型机电设备的环境下,我们的电源信号、通信信号都有可能受到干扰。 那么,在这种情况下,我们软件和硬件都有必要做一定预防处理。 除了需要外接复位IC,同时,为了系统能稳定长期的工作,我们可能还有必要添加看门狗。 本文就围绕复位IC、看门狗展开相关内容的讲解。 2 MCU电路为什么要使用复位芯片? STM32都有一个最低工作电压(比如1.8V),当电源电压跌落到低于MCU所要求的最低值时,MCU工作可能发生混乱,造成程序跑飞,引起整机死机、误动作等现象。 使用复位IC的原理是通过确定的电压值(阈值)启动复位操作,同时排除瞬间干扰的影响,又有防止MCU在电源启动和关闭期间的误
[单片机]
<font color='red'>STM32</font>看门狗与复位IC同时存在的注意事项
stm32 fsmc 功能讲解
LCD有如下控制线: CS:Chip Select 片选,低电平有效 RS:Register Select 寄存器选择 WR:Write 写信号,低电平有效 RD:Read 读信号,低电平有效 RESET:重启信号,低电平有效 DB0-DB15:数据线 假如这些线,全部用普通IO口控制。根据LCD控制芯片手册(大部分控制芯片时序差不多): 如果情况如下: DB0-DB15的IO全部为1(表示数据0xff),也可以为其他任意值,这里以0xff为例。 CS为0(表示选上芯片,CS拉低时,芯片对传入的数据才会有效) RS为1(表示DB0-15上传递的是要被写到寄存器的值),如果为0,表示传递的是数据。 WR为0,RD为1(表示是写动
[单片机]
stm32 上电初始化串口输出一个字节FF问题
最近玩stm32,使用串口发送数据在PC端使用串口工具检测接收到的数据,发现每次上电串口工具都会蹦出一个FF,这让我郁闷好久。在网上查了好多解决问题的办法,有的说先初始化UART 在初始化UART对应的GPIO脚,有的说把中断关闭等等 。我试了都不行,串口还是会发送FF,简直郁闷。。。。 后来我单步调试,发现在初始化的时候函数GPIO_PinAFConfig();初始化导致串口上电在TX脚上输出一个高电平。所以我就尝试在函数GPIO_Iinit();初始化之前首先初始化GPIO_PinAFConfig();这样就不会出现FF了。。。 总之解决办法如下就不会出现问题: 1.开启IO和外设USART时钟
[单片机]
stm32中断向量表初探
cortex-M3的异常向量表中的内容并不是指令,0x00000000处(当然也可能映射到别的范围)是主堆栈指针的数值,0x00000004的内容是复位后需要跳转到的地址,是一个地址而不是一条指令。 0x08000000数据如下(memory 窗口查看--STM32小端): 10 02 00 20 05 19 00 08 AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD __initial_sp //0x20000210; Top of Stack DCD Reset_Handler
[单片机]
基于STM32的半导体制冷片控制系统设计
一些医疗检测仪器在检测时需要模拟人体温度环境以确保检测的精确性,本文以STM32为主控制器,电机驱动芯片DRV8834 为驱动器,驱动半导体致冷器(帕尔贴)给散热片加热或者制冷。但由于常规的温度控制存在惯性温度误差的问题,无法兼顾高精度和高速性的严格要求,所以采用模糊自适应PID控制方法在线实时调整PID参数,计算PID参数Kp、Ki、Kd调整控制脉冲来控制驱动器的使能。从simulink仿真的和实验结果来看模糊PID控制系统精度高、响应速度快,能达到预期效果。 温度参数是工业生产中常用的被控对象之一,在化工生产、冶金工业、电力工程和食品加工等领域广泛应用,在医疗检测设备中时常需要模拟人体温度进行成分检测。采用直流电机驱动芯片DRV
[单片机]
基于<font color='red'>STM32</font>的半导体制冷片控制系统设计
stm32的串口调试卡死问题
且看这次出现的bug是一直等待, while(USART_GetFlagStatus(USE_Usart,USART_FLAG_TC)==RESET); 这个有很多说法 不过本人处理的方法是加入了一个一行代码就好了 NVIC_SetVectorTable(NVIC_VectTab_FLASH,0);
[单片机]
STM32之位带操作
  Cortex-M3 支持了位操作后,可以使用普通的加载/存储指令来对单一的比特进行读写。   在 CM3 支持的位带中,有两个区中实现了位带。   其中一个是 SRAM 区的最低 1MB 范围, 0x20000000 ‐ 0x200FFFFF(SRAM 区中的最低 1MB);   第二个则是片内外设区的最低 1MB范围, 0x40000000 ‐ 0x400FFFFF(片上外设区中的最低 1MB)。   这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的 位带别名区 ,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。   CM3 使
[单片机]
16. 定时器中断实验
STM32的定时器的分类: 1. 高级定时器 TIME1,TIME8 2. 通用定时器 TIME2~TIME5 3. 基本定时器 TIME6,TIME7 本章讲解通用定时器,参考《开发指南》第13,14,15章以及《中文参考手册》第14章。 一。 计数器的计数模式 1. 向上计数模式 2. 向下计数模式 在向下模式中,计数器从自动装入的值(TIMx_ARR计数器的值)开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出事件。 3. 中央对齐模式(向上/向下) 本实验中使用向下计数模式 二。 STM32通用定时器 STM32 的通用 TIMx (TIM2、TIM3、TIM4
[单片机]
16. 定时器中断实验
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved