打印
[STM32F4]

授人以渔 第三节:基于stm32f407实现usb键盘功能(2e...

[复制链接]
7602|20
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
z755924843|  楼主 | 2017-3-15 11:07 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
前面的基础知识讲的也差不多了,直接上教程:
1. 利用stm32cubemx创建1个stm32f407的工程,工程需要配置的有:RCC 、SYS、USB_OTG_FS,USB_DEVICE,PID,VID…如果需要详细的步骤就看第一节的教程,这里给大家推荐一个usb的调试工具叫device-monitoring-studio我看网上大部分人都在用bus-hound两者相比,我认为前者的用于usb调试更加方便,不过这种就是仁者见仁智者见智了。
2.我用的编译器IAR,我看网上有许多人都在用Keil,两者的步骤也差不多,打开工程.
3. 我在程序中main.c增加了1个按键和1LED,用来模拟键盘按键和键盘灯
/* USER CODE BEGIN 4 */
static void UserGpioInit(void)
{
   
  /* KEY PE2,PE3,PE4*/
  /* LED PF9,PF10*/
  
  /* GPIO Ports Clock Enable */
   GPIO_InitTypeDef GPIO_InitStruct;
  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOF_CLK_ENABLE();
  __HAL_RCC_GPIOE_CLK_ENABLE();
  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9|GPIO_PIN_10, GPIO_PIN_RESET);
  /*Configure GPIO pins : PF9 PF10 */
  GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOE,GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4, GPIO_PIN_RESET);
  /*Configure GPIO pins : PF9 PF10 */
  GPIO_InitStruct.Pin =GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
}
uint32_t ReturnKeyData(void)
{
  uint32_t i;
  if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2)==GPIO_PIN_RESET){
    for(i=0;i<0xff;i++);
    if(HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2)==GPIO_PIN_RESET){
      return 1;
    }
  }
else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3)==GPIO_PIN_RESET){
   for(i=0;i<0xff;i++);
   if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3)==GPIO_PIN_RESET){
     return 2;
    }
  }
else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4)==GPIO_PIN_RESET){
   for(i=0;i<0xff;i++);
   if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4)==GPIO_PIN_RESET){
     return 3;
    }
  }
return 0;
}
4.因为生成的例程是鼠标例程,并且例程中只使用了一个端点(因为鼠标只需要向上发送数据),但键盘需要向上位机发送数据(in端点0x81),并且同时也接收上位机的数据(out端点0x01),并且鼠标和键盘的数据格式也不同,我们还需要发送键盘的数据格式。
下面我们整理一下需要修改的地方:
1)首先让上位机认识咱们的设备是键盘
2)增加1个端点,完成接收上位机下发下来的数据
3)讲下位机发送的数据格式改成符合键盘的格式。
完成以上操作之后,我们就可以将我们的开发板变成一个USB键盘.
5.我们的例程由于是usb-hid设备,而键盘设备也是hid设备,所以如果让上位机把我们的设备认识键盘的话,只需要改一下接口描述符就可以了:
/************** Descriptor of JoystickMouse interface ****************/
  /*09 */
0x09,         /*bLength: InterfaceDescriptor size*/
USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
0x00,         /*bInterfaceNumber:Number of Interface*/
0x00,         /*bAlternateSetting:Alternate setting*/
0x02,        /*bNumEndpoints*/
0x03,         /*bInterfaceClass:HID*/
0x01,         /*bInterfaceSubClass: 1=BOOT, 0=no boot*/
  0x02,        /*nInterfaceProtocol :0=none, 1=keyboard, 2=mouse*/
0,            /*iInterface: Indexof string descriptor*/

nInterfaceProtocol改成2就可以了
6.增加端点
需要修改增加配置描述符
①  将端点接口描述符中的端点改成2
②  增加配置描述符数组最后增加一个端点描述符
/******************** Descriptor of Keyboard outputendpoint ********************/
  /* 34 */
  0x07,         /*bLength: Endpoint Descriptor size*/
  USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
  HID_EPOUT_ADDR,     /*bEndpointAddress: Endpoint Address(IN)*/
  0x03,         /*bmAttributes: Interrupt endpoint*/
  HID_EPOUT_SIZE, /*wMaxPacketSize: 4 Byte max*/
  0x00,
  HID_FS_BINTERVAL,          /*bInterval: Polli ng Interval (10ms)*/
  /*41*/
③  修改HID描述符中的报告描述符长度索引
/******************** Descriptor ofJoystick Keyboard HID ********************/
  /*18 */
0x09,         /*bLength: HIDDescriptor size*/
HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
0x11,         /*bcdHID: HID ClassSpec release number*/
0x01,
0x00,         /*bCountryCode:Hardware target country*/
0x01,         /*bNumDescriptors:Number of HID class descriptors to follow*/
0x22,         /*bDescriptorType*/
  HID_KEYBOARD_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
0x00,
④  修改整个配置描述符的大小,这个是在hid.h中定义的
0x09, /* bLength: ConfigurationDescriptor size */
USB_DESC_TYPE_CONFIGURATION, /*bDescriptorType: Configuration */
USB_HID_CONFIG_DESC_SIZ,
/* wTotalLength: Bytes returned */
0x00,
0x01,         /*bNumInterfaces: 1interface*/
0x01,        /*bConfigurationValue: Configuration value*/
0x00,         /*iConfiguration:Index of string descriptor describing
the configuration*/
0xE0,         /*bmAttributes: buspowered and Support Remote Wake-up */
      0x32,         /*MaxPower 100 mA: this current isused for detecting Vbus*/
修改后的配置描述符为:
/**
* \GET_DESCRIPTOR 请求
* \描述符类型及编号
*  |        描述符类型             | 编号
* |:-------------------------|:----------
*  | 设备描述符(DEVICE)       | 1
*  | 配置描述符(CONFIGURATION)|  2
*  | 字符串描述符(STRING)     |  3
*  | 接口描述符(INTERFACE)    |  4
*  | 端点描述符(ENDPOINT)     |  5
*/
/* USB HID device ConfigurationDescriptor */
__ALIGN_BEGIN static uint8_tUSBD_HID_CfgDesc[USB_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
0x09, /* bLength: Configuration Descriptor size */
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
USB_HID_CONFIG_DESC_SIZ,
/* wTotalLength: Bytes returned */
0x00,
0x01,         /*bNumInterfaces: 1interface*/
0x01,        /*bConfigurationValue: Configuration value*/
0x00,         /*iConfiguration:Index of string descriptor describing
the configuration*/
0xE0,         /*bmAttributes: buspowered and Support Remote Wake-up */
0x32,         /*MaxPower 100 mA:this current is used for detecting Vbus*/
/************** Descriptor of Joystick Keyboard interface****************/
/* 09 */
0x09,         /*bLength: InterfaceDescriptor size*/
USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
0x00,         /*bInterfaceNumber:Number of Interface*/
0x00,         /*bAlternateSetting:Alternate setting*/
0x02,         /*bNumEndpoints*/
0x03,         /*bInterfaceClass:HID*/
0x01,         /*bInterfaceSubClass: 1=BOOT, 0=no boot*/
0x01,         /*nInterfaceProtocol: 0=none, 1=keyboard, 2=mouse*/
0,            /*iInterface: Indexof string descriptor*/
/******************** Descriptor of Joystick Keyboard HID********************/
/* 18 */
0x09,         /*bLength: HIDDescriptor size*/
HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
0x11,         /*bcdHID: HID ClassSpec release number*/
0x01,
0x00,         /*bCountryCode:Hardware target country*/
0x01,         /*bNumDescriptors:Number of HID class descriptors to follow*/
0x22,         /*bDescriptorType*/
HID_KEYBOARDMOUSE_REPORT_DESC_SIZE,//HID_KEYBOARD_REPORT_DESC_SIZE,//HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength:Total length of Report descriptor*/
0x00,
/******************** Descriptor of Keyboardinput endpoint ********************/
/* 27 */
0x07,          /*bLength: EndpointDescriptor size*/
USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
HID_EPIN_ADDR,    /*bEndpointAddress: Endpoint Address (IN)*/
0x03,          /*bmAttributes:Interrupt endpoint*/
HID_EPIN_SIZE, /*wMaxPacketSize: 4 Byte max */
0x00,
HID_FS_BINTERVAL,         /*bInterval: Polli ng Interval (10 ms)*/
/******************** Descriptor ofKeyboard output endpoint********************/
/* 34 */
0x07,          /*bLength: EndpointDescriptor size*/
USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
HID_EPOUT_ADDR,    /*bEndpointAddress: Endpoint Address (IN)*/
0x03,          /*bmAttributes:Interrupt endpoint*/
HID_EPOUT_SIZE, /*wMaxPacketSize: 4 Byte max */
0x00,
HID_FS_BINTERVAL,         /*bInterval: Polli ng Interval (10 ms)*/
/*41*/
} ;
修改完成配置描述符之后,我们还需要修改报告描述符,将报告描述符改成键盘。
__ALIGN_BEGIN static uint8_tHID_KEYBOARD_ReportDesc[HID_KEYBOARD_REPORT_DESC_SIZE]  __ALIGN_END =
{
0x05,0x01,
0x09,0x06,
0xa1,0x01,
0x05,0x07,
0x19,0xe0,
0x29,0xe7,
0x15,0x00,
0x25,0x01,
0x95,0x08,
0x75,0x01,
0x81,0x02,
0x95,0x01,
0x75,0x08,
0x81,0x03,
0x95,0x06,
0x75,0x08,
0x15,0x00,
0x25,0xff,
0x05,0x07,
0x19,0x00,
0x29,0x65,
0x81,0x00,
0x25,0x01,
0x95,0x05,
0x75,0x01,
0x05,0x08,
0x19,0x01,
0x29,0x05,
0x91,0x02,
0x95,0x01,
0x75,0x03,
0x91,0x03,
0xc0
};
修改完成之后,还需要将报告描述符和配置描述符关联上,我们需要修改
static uint8_t  USBD_HID_Setup (USBD_HandleTypeDef *pdev,
                               USBD_SetupReqTypedef *req)函数中的

case USB_REQ_GET_DESCRIPTOR:选项:
关联上之后,还需要初始化新添加的这个out端点
先宏定义一个这个端点的地址和大小
#define HID_EPOUT_ADDR               0x01
#define HID_EPOUT_SIZE                 0x10  
然后在static uint8_t  USBD_HID_Init(USBD_HandleTypeDef *pdev,
                              uint8_t cfgidx)函数中增加
/* Open EP OUT */
USBD_LL_OpenEP(pdev,
                 HID_EPOUT_ADDR,
                 USBD_EP_TYPE_INTR,
                     HID_EPOUT_SIZE);

初始化完成后,我们还需要重新定义一个OUT函数
我增加了USBD_HID_DataOut函数
至此,我们的设备就成为了一个usb键盘设备了。
修改uint8_t USBD_HID_SendReport  (USBD_HandleTypeDef  *pdev,
                                 uint8_t *report,
                                    uint16_t len)
函数用于向上位机发送数据
uint8_t USBD_HID_SendReport     (USBD_HandleTypeDef  *pdev,
                                 uint8_t*report,
                                 uint16_t len)
{
USBD_HID_HandleTypeDef     *hhid =(USBD_HID_HandleTypeDef*)pdev->pClassData;
if (pdev->dev_state == USBD_STATE_CONFIGURED )
{
   if(hhid->state == HID_IDLE)
   {
     hhid->state = HID_BUSY;
     USBD_LL_Transmit (pdev,
                        HID_EPIN_ADDR,                                      
                        report,
                        len);
   }
}
return USBD_OK;
}
在主函数中调用:
txbuffer[2] = 0x4;
USBD_HID_SendReport (&hUsbDeviceFS,txbuffer,8);
调用上面两句代码之后,pc机上就会收到一连a,那么为什么0x4代码a那?这都是报告描述符的功劳,想了解的就去看报告描述符吧。
如果想让设备停止发送的话,只需要将4改成0 就可以了。

注:当我们在键盘上按下“capsLock”“numlock”等能够开启键盘灯的按键时,我们的usb接收数组usbrxbuffer都能接收到相应的数值,我们只要根据数值点亮对应的LED,就实现了键盘灯的操作了这个就不贴代码了,看了教程的人就自己操作吧。

沙发
Xy201207| | 2017-3-15 11:23 | 只看该作者
看了个把月的USB 报告描述符还是没弄懂

使用特权

评论回复
板凳
z755924843|  楼主 | 2017-3-15 11:29 | 只看该作者
Xy201207 发表于 2017-3-15 11:23
看了个把月的USB 报告描述符还是没弄懂

可以看看圈圈玩转usb 那上面有一节专门介绍的,或者是usb开发大全 也可以上面也有介绍,但是最好还是看看usb原版的协议。

使用特权

评论回复
地板
fengm| | 2017-3-15 11:46 | 只看该作者

使用特权

评论回复
5
fengm| | 2017-3-15 11:48 | 只看该作者
USB的开发有小i版主开发过教程。

使用特权

评论回复
6
snowflyin| | 2017-3-16 10:48 | 只看该作者
太棒了, 期待第四节

使用特权

评论回复
7
gonghanling| | 2017-3-23 17:00 | 只看该作者
楼主,代码能发我一份吗?有些地方看了好几遍还是不懂,也不知道怎么改代码。。。

使用特权

评论回复
8
z755924843|  楼主 | 2017-3-24 09:16 | 只看该作者
gonghanling 发表于 2017-3-23 17:00
楼主,代码能发我一份吗?有些地方看了好几遍还是不懂,也不知道怎么改代码。。。 ...

已经将代码发布到论坛上了,请自行下载。

使用特权

评论回复
9
selongli| | 2017-3-24 16:56 | 只看该作者

使用特权

评论回复
10
selongli| | 2017-3-24 16:58 | 只看该作者
STM32CubeMX这个工具,也是近年来开发STM32比较流行的一个工具。

使用特权

评论回复
11
841937225| | 2017-5-4 09:53 | 只看该作者
你好lz,我想实现个按键翻页的功能,但是按照您描述的发送txbuf[2]=0x4E后再发送一次txbuf[2]=0x00,电脑还是会不停的翻页还会时不时冒出e字符

使用特权

评论回复
12
z755924843|  楼主 | 2017-5-15 10:36 | 只看该作者
841937225 发表于 2017-5-4 09:53
你好lz,我想实现个按键翻页的功能,但是按照您描述的发送txbuf[2]=0x4E后再发送一次txbuf[2]=0x00,电脑还 ...

发送完键值之后,在发送一组0x00 数据,表示按键抬起。

使用特权

评论回复
13
joelcao| | 2017-7-25 17:09 | 只看该作者
将键盘的EPIN和EPOUT的地址改为0x82和0x02后,键盘就无法工作,发送数据就出错,这个是为什么?

使用特权

评论回复
14
wenunit| | 2017-9-12 16:46 | 只看该作者
好人啊,过程很清淅,一下搞定,,,,,,,,,,,{:handshake:},{:handshake:},{:handshake:},{:handshake:},{:handshake:},{:handshake:},{:handshake:},{:handshake:},{:handshake:},{:handshake:}

使用特权

评论回复
15
打发12| | 2017-10-20 20:10 | 只看该作者
本帖最后由 打发12 于 2017-10-20 20:20 编辑

谢谢楼主,按照你的步骤我已经成功了,这是我的程序分享给大家(分卷了把z01-z04后缀zip去掉)
我是直接在mouse函数里面改描述符的,没有新建keyboard的函数
(我这是stm32f103c8的stm32cubeMX_usb工程)

usb_hid_F1.z01.zip

2 MB

usb_hid_F1.z02.zip

2 MB

usb_hid_F1.z03.zip

2 MB

usb_hid_F1.z04.zip

2 MB

usb_hid_F1.zip

1.98 MB

使用特权

评论回复
评论
z755924843 2017-10-24 11:33 回复TA
666 
16
13014366077| | 2017-11-10 15:33 | 只看该作者
楼主,看了您的教程,移植了一遍键盘代码,但是为什么我的USB被电脑识别成了 STM Device in DFU Mode,
而不是键盘呢?请求楼主指点一下。

使用特权

评论回复
17
doniexun| | 2018-1-23 22:16 | 只看该作者
感谢楼主分享,找时间实践一下

使用特权

评论回复
18
caoenq| | 2018-3-19 21:56 | 只看该作者
z755924843 发表于 2017-3-24 09:16
已经将代码发布到论坛上了,请自行下载。

您好,楼主,可否给个您上传代码的位置的链接,我表示没找到。。。

使用特权

评论回复
19
z755924843|  楼主 | 2018-3-21 23:33 | 只看该作者
caoenq 发表于 2018-3-19 21:56
您好,楼主,可否给个您上传代码的位置的链接,我表示没找到。。。

搜索我的帖子,在另外的帖子,或者是 15楼也上传了 你可以下载他的,

使用特权

评论回复
20
fyc8604| | 2018-3-23 08:25 | 只看该作者
感谢楼主的无私分享!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:嵌入式相关网站喜欢的朋友了解一下http://www.micropython.group

31

主题

260

帖子

39

粉丝