STM32进行USB HID 改 BULK小记
STM32进行USB HID 改 BULK小记
2019-07-18 12:31:20
2753
分类专栏: 文章标签:
版权
本博客参考了以下同仁的精彩文章,在此表示感谢。
本文纯工程实践,无原理协议分析(我对USB协议一点都不懂,分析个啥呀),跟着做就能搭建自己的USB传输架构。
实验背景
由于项目需求,要实现由下位机采集数据,通过USB接口传输给上位机进行数据后期处理;手头正好有一块STM32的开发板,直接拿来就用。
使用工具
STM32CubeMX
前期查阅了一些USB资料,发现使用 STM32CubeMX 是一个非常好的捷径。此软件以图形化的界面形式来让你实现自己的单片机底层及各个模块初始化设置,感觉好多单片机制造厂商都开发了自己的一套这样的设计工具,大大提高的开发效率。之前使用 Silab 公司单片机的时候,也接触过此类开发工具,使开发者摆脱了查阅硬件相关寄存器的烦恼,使我们更加专注顶层的设计。
在文章中我将介绍如何根据自己手中的单片机型号及PCB原理图,使用STM32CubeMX进行外设的配置及初始化。
我使用的版本为 5.2.1,下载链接如下:
使用教程:
Bus Hound
很好用的一个抓包工具,用来显示USB数据通信的方向,端口,数据等信息,通过USB传输的数据都可以在上面显示。可作为USB数据通信的检验工具。
使用版本: 6.0.1
使用方法自行查阅。
TI_VISA
此工具用来生成USB的驱动文件,很好用。
使用版本:ni-visa_19.0_online;此版本支持WIN10系统。
实验步骤
首先由STM32CubeMX生成对应的工程,参考
生成功能代码后,只需要更改 usbd_hid.h 、usbd_hid.c 、usbd_desc.c 三个USB相关文件。
总结起来,需要更改 设备描述符 (USBD_FS_DeviceDesc),配置描述符(USBD_HID_CfgDesc)。其中配置描述符中包含了对接口(Interface)与端点(两个endpoint,端点2的输入与输出(0x82,0x02))的描述。
usbd_hid.h更改:
/* Includes ------------------------------------------------------------------*/
#include "usbd_ioreq.h"
#define HID_EPIN_ADDR 0x82
#define HID_EPIN_SIZE 64
#define HID_EPOUT_ADDR 0x02
#define HID_EPOUT_SIZE 64
#define USB_HID_CONFIG_DESC_SIZ 32
其中定义了两个端点(endpoint)0x82与0x02,USB数据传输都是通过端点。其中 “2” 是代表使用端口“2”,当然你也可以使用 0x81与0x01,代表使用端口“1”。
bit7代表数据传输方向,bit7=1时,表示数据读入,数据由USB设备发送给USB主机;
bit7=0时,表示数据输出,数据由USB主机发送给USB设备。
具体的描述符细节请参考
HID_EPIN_SIZE 与 HID_EPOUT_SIZE 为 64,表示单次传输的最大字节为64,根据USB协议,此处最大的值就为64。
USB_HID_CONFIG_DESC_SIZ 为 32,这个值是 配置描述符(USBD_HID_CfgDesc)数组的大小,根据实际包含的字节数量来更改。
usbd_hid.c更改:
此文件包含 配置描述符(USBD_HID_CfgDesc)
更改 bNumEndpoints 为2,因为我们使用了端口2的两个endpoint,一个用来输入,另一个用来输出。
bInterfaceClass 及以下配置如下所示;
0x09, /*bLength: Interface Descriptor size*/
USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
0x00, /*bInterfaceNumber: Number of Interface*/
0x00, /*bAlternateSetting: Alternate setting*/
0x02, /*bNumEndpoints*/
0x00, /*bInterfaceClass: HID*/
0x00, /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
0x00, /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
0, /*iInterface: Index of string descriptor*/
删除报告描述符的内容,在bulk中不需要报告描述符。
// 0x09, /*bLength: HID Descriptor size*/
// HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
// 0x11, /*bcdHID: HID Class Spec release number*/
// 0x01,
// 0x00, /*bCountryCode: Hardware target country*/
// 0x01, /*bNumDescriptors: Number of HID class descriptors to follow*/
// 0x22, /*bDescriptorType*/
// HID_MOUSE_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
// 0,
接下来就是配置两个 endpoint;每个endpoint的配置数量为7个字节,HID_XXX_ADDR,HID_XXX_SIZE 在 usbd_hid.h 中定义。
0x02 代表此 endpoint 为Bulk模式。也可更改为控制模式与中断模式。
0x07, /*bLength: Endpoint Descriptor size*/
USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
HID_EPIN_ADDR, /*bEndpointAddress: Endpoint Address (IN)*/
0x02, /*bmAttributes: 1-Ctrl;2-Bulk;3-Interrupt endpoint*/
HID_EPIN_SIZE, /*wMaxPacketSize: 64 Byte max */
0x00,
HID_FS_BINTERVAL, /*bInterval: Polling Interval (10 ms)*/
/* 34 */
0x07, /*bLength: Endpoint Descriptor size*/
USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/
HID_EPOUT_ADDR, /*bEndpointAddress: Endpoint Address (OUT)*/
0x02, /*bmAttributes: 1-Ctrl;2-Bulk;3-Interrupt endpoint/
HID_EPOUT_SIZE, /*wMaxPacketSize: 64 Byte max */
0x00,
HID_FS_BINTERVAL, /*bInterval: Polling Interval (10 ms)*/
usbd_desc.c更改:
在此文件中,需要更改设备描述符,以及一些厂家信息与VID,PID等信息。
#define USBD_VID 1189
#define USBD_LANGID_STRING 1033
#define USBD_MANUFACTURER_STRING "www sia cn"
#define USBD_PID_FS 2313
#define USBD_PRODUCT_STRING_FS "CHENHAO USB"
#define USBD_CONFIGURATION_STRING_FS "HID Config"
#define USBD_INTERFACE_STRING_FS "HID Interface"
我更改了VID,PID,厂家名称,产品名称 4 个参数。在厂家与产品的字符串中不要写下划线,会在使用NI_VISA时识别不了。改成“空格”就可以识别了。亲身经历。
更改usbd_desc.c文件中的设备描述符:
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
0x12, /*bLength */
USB_DESC_TYPE_DEVICE, /*bDescriptorType*/
0x00, /*bcdUSB */
0x02,
0xff, /*bDeviceClass*/
0x00, /*bDeviceSubClass*/
0x00, /*bDeviceProtocol*/
USB_MAX_EP0_SIZE, /*bMaxPacketSize*/
LOBYTE(USBD_VID), /*idVendor*/
HIBYTE(USBD_VID), /*idVendor*/
LOBYTE(USBD_PID_FS), /*idProduct*/
HIBYTE(USBD_PID_FS), /*idProduct*/
0x00, /*bcdDevice rel. 2.00*/
0x02,
USBD_IDX_MFC_STR, /*Index of manufacturer string*/
USBD_IDX_PRODUCT_STR, /*Index of product string*/
USBD_IDX_SERIAL_STR, /*Index of serial number string*/
USBD_MAX_NUM_CONFIGURATION /*bNumConfigurations*/
};
此描述符只需更改 bDeviceClass,如上所示。
至此,相关描述符已完成更改。
在 usbd_hid.c 文件中添加声名数据接收函数(自动生成的功能中只有MCU通过USB发送数据的函数)USBD_HID_DataOut;
声名USB接收数据缓存 USB_Rx_Buffer[HID_EPOUT_SIZE];
声名USB接收数据个数变量 USB_Received_Count;
将 USBD_HID_DataOut 函数加入到 USBD_HID 数据结构中;
static uint8_t USBD_HID_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum);
uint8_t USB_Rx_Buffer[HID_EPOUT_SIZE];
uint8_t USB_Received_Count;
USBD_ClassTypeDef USBD_HID =
{
USBD_HID_Init,
USBD_HID_DeInit,
USBD_HID_Setup,
NULL, /*EP0_TxSent*/
NULL, /*EP0_RxReady*/
USBD_HID_DataIn, /*DataIn*/
USBD_HID_DataOut, /*DataOut*/ //此处之前为NULL
NULL, /*SOF */
NULL,
NULL,
USBD_HID_GetCfgDesc,
USBD_HID_GetCfgDesc,
USBD_HID_GetCfgDesc,
USBD_HID_GetDeviceQualifierDesc,
};
修改USB初始化函数,添加对端口2输入与输出的初始化函数;并且将使能输出端口,时刻准备接收数据。
static uint8_t USBD_HID_Init (USBD_HandleTypeDef *pdev,
uint8_t cfgidx)
{
uint8_t ret = 0;
/* Open EP IN */
USBD_LL_OpenEP(pdev,
HID_EPIN_ADDR,
USBD_EP_TYPE_BULK,
HID_EPIN_SIZE);
/* Open EP OUT */
USBD_LL_OpenEP(pdev,
HID_EPOUT_ADDR,
USBD_EP_TYPE_BULK,
HID_EPOUT_SIZE);
USBD_LL_PrepareReceive(pdev,
HID_EPOUT_ADDR,
(uint8_t*)(USB_Rx_Buffer),
HID_EPOUT_SIZE);
pdev->pClassData = USBD_malloc(sizeof (USBD_HID_HandleTypeDef));
if(pdev->pClassData == NULL)
{
ret = 1;
}
else
{
((USBD_HID_HandleTypeDef *)pdev->pClassData)->state = HID_IDLE;
}
return ret;
}
同理,添加USB端口复位函数内容:
static uint8_t USBD_HID_DeInit (USBD_HandleTypeDef *pdev,
uint8_t cfgidx)
{
/* Close HID EPs */
USBD_LL_CloseEP(pdev,
HID_EPIN_ADDR);
/* Close HID EPs */
USBD_LL_CloseEP(pdev,
HID_EPOUT_ADDR);
/* FRee allocated memory */
if(pdev->pClassData != NULL)
{
USBD_free(pdev->pClassData);
pdev->pClassData = NULL;
}
return USBD_OK;
}
编写 USBD_HID_DataOut;此函数作用是获取PC机发来的数据个数;并重新使能接收功能。
static uint8_t USBD_HID_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum)
{
USB_Received_Count = USBD_GetRxCount( pdev,epnum );
USBD_LL_PrepareReceive(pdev,
HID_EPOUT_ADDR,
(uint8_t*)(USB_Rx_Buffer),
HID_EPOUT_SIZE);
return USBD_OK;
}
在main.c函数中添加如下代码:增加头文件与声名相关变量;
#include "usbd_hid.h"
extern USBD_HandleTypeDef hUsbDeviceFS;
extern uint8_t USB_Rx_Buffer[HID_EPOUT_SIZE];
extern uint8_t USB_Received_Count;
在while(1)结构中,添加如下代码:
功能:只有当MCU接受到USB信息后,才会将接收到的信息再发回给PC机,并且每次接收到数据时,都会更改一次LED的状态,用来观察通讯是否正常。
while (1)
{
if (USB_Received_Count > 0) {
USBD_HID_SendReport(&hUsbDeviceFS, USB_Rx_Buffer, USB_Received_Count);
if (i%2 == 0){
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
}
else HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
USB_Received_Count = 0;
i++;
}
}
代码更改部分已经结束。编译后下载。
板子上电后,连接USB至电脑。在设备管理器中会出现一个带感叹号设备,说明此设备没有驱动。
现在使用 NI_VISA 生成驱动。
1. 打开NI_VISA Driver Wizard
2. 选择USB,next
3.根据你编写的VID与PID,找到你的设备;next
4.然后就是一路next,一直到结束。在最后会在一个路径下生成一个 prefix.inf 的文件,这个就是你板子USB 的驱动。
5.然后就可以手动安装此驱动就可以了。设备管理器上就可以正常显示你的设备。
使用bus hound进行数据抓取;
上图中可以看到我们使用了endpoint 2 OUT/IN,类型是 BULK,最大packet为64。和我们配置的一模一样。
设置要发送的数据,选择OUT一行,点击 RUN向MCU发送数据,会发现板子上的LED发生变化,说明已经接收到了数据。然后选择IN一行,点击RUN,从MCU读取数据,发现输入和输出的数据完全一样。表示USB数据传输功能正常。
至此,USB传输功能已完成。
有说的不对的地方,还请大家多多指正。