admin 管理员组

文章数量: 1087139


2024年4月23日发(作者:css hover active)

STM32 USB HID详解

1、USB简介

2、USB描述符

USB只是一个总线,只提供一个数据通路而已。USB总线驱动程序并不知道

一个设备具体如何操作,有哪些行为。具体的一个设备实现什么功能,要由设备

自己来决定。那么,USB主机是如何知道一个设备的功能以及行为呢?这就要通

过描述符来实现了。描述符中记录了设备的类型、厂商ID和产品ID(通常依靠

它们来加载对应的驱动程序)、端点情况、版本号等众多信息。

标准的USB设备有5种USB描述符:设备描述符,配置描述符,接口描述符,

端点描述符,字符串描述符。下面详解:

2.1、设备描述符

一个USB设备只有一个设备描述符。设备描述符主要记录的信息有:设备所

使用的USB协议版本号、设备类型、端点0的最大包大小、厂商ID(VID)和产

品ID(PID)、设备版本号、厂商字符串索引、产品字符串索引、设备序列号索

引、可能的配置数等。

偏移量 域 大小/字说明

0 bLength 1 该描述符的长度(0x12=18字节)

1 bDescriptorType 1 描述符类型(0x01设备描述符)

2 bcdUSB 2 本设备使用的USB协议版本

4 bDeviceClass 1 类代码

5 bDeviceSubClass 1 子类代码

6 bDeviceProtocol 1 协议码

7 bMaxPacketSize 1 端点0最大包长

8 idVendor 2 厂商ID

10 idProduct 2 产品ID

12 bcdDevice 2 设备版本号

14 iManufacturer 1 描述厂商的字符串索引

15 iProduct 1 描述产品的字符串索引

16 iSerialNumber 1 产品序列号的字符串索引

17 bNumConfigurations 1 可能的配置数

2.2、配置描述符

设备描述符里决定了该设备有多少种配置,每种配置都有一个配置描述符。

配置描述符主要记录的信息有:配置所包含的接口数、配置的编号、供电方式、

是否支持远程唤醒、电流需求量等。

偏移量 域 大小/字说明

0 bLength 1 该描述符的长度(0x09字节)

1 bDescriptorType 1 描述符类型(0x02配置描述符)

2 wTotalLength 2 配置、接口、端点和类描述符字节

总和

4 bNumInterfaces 1 支持接口数

5 bConfigurationValue 1 本配置描述符标识

6 iConfiguration 1 配置描述符说明字符串索引

7 bmAttributes 1 电源及唤醒

8 MaxPower 1 设备耗电电流

2.3、接口描述符

在每个配置描述符中又定义了该配置有多少个接口,每个接口都有一个接口

描述符。接口描述符主要记录的信息有:接口的编号、接口的端点数、接口所使

用的类、子类、协议等。

偏移量 域 大小/字说明

0 bLength 1 该描述符的长度(0x09字节)

1 bDescriptorType 1 描述符类型(0x04接口描述符)

2 bInterfaceNumber 1 本接口描述符标识

3 bAlternateSetting 1

4 bNumEndpoints 1 接口端点数

5 bInterfaceClass 1 接口类代码

6 bInterfaceSubClass 1 启动类型1=BOOT, 0=No BOOT

7 bInterfaceProtocol 1 0=None, 1=Keyboard, 2=Mouse

8 iInterface 1 接口描述符说明字符串索引

2.4、[类描述符]

该描述符不是必须的,如果配置的USB类型有类特殊描述符(如HID类),

它跟在相应的接口描述符之后。

2.5、端点描述符

在接口描述符里又定义了该接口有多少个端点,每个端点都有一个端点描述

符。端点描述符主要记录的信息有:端点号及方向、端点的传输类型、最大包长

度、查询时间间隔等。

偏移量 域 大小/字说明

0 bLength 1 该描述符的长度(0x07字节)

1 bDescriptorType 1 描述符类型(0x5端点描述符)

2 bEndpointAddress 1 端点地址

3 bmAttributes 1 端点类型

4 wMaxPacketSize 2 端点发送接收最大包长

6 bInterval 1 中断端点轮训时间间隔

2.6、[字符串描述符]

字符串描述符主要是提供一些方便人们阅读的信息,它不是必需的。

偏移量 域

0

1

2

bLength

bDescriptorType

wLANGID

大小/字

1

1

2/XX

说明

该描述符的长度(0x04/0xXX字

节),第一个字符串描述符0x04

描述符类型(0x3字符串描述符)

第一个字符串描述符时2字节,表

示语言编码,其他自定义

3、USB HID

为了把一个设备识别为HID类别,设备在定义描述符的时候必须遵守HID规

范。除了USB标准定义的一些描述符外,HID设备还必须定义HID描述符。另外

设备和主机的通信是通过报告的形式来实现的,所以还必须定义报告描述符;而

物理描述符不是必需的。还有就是HID描述符是关联于接口(而不是端点)的,

所以设备不需要为每个端点都提供一个HID描述符。详情参看《USB HID协议中

文版_USB HID 设备》

3.1、HID描述符

偏移量 域 大小/字

说明

HID描述符是HID类特有的描述符,保证设备正确识别,遵循规定的格式。

0

1

2

4

5

6

7

bLength

bDescriptorType

bcdHID

bCountryCode

bNumDescriptors

bDescriptorType

wItemLength

1

1

2

1

1

1

2

该描述符的长度(0x09字节)

描述符类型(0x21 HID描述符)

HID规范版本

国家代码

支持的描述符个数

支持的描述符类别0x22报表

支持的描述符长度

3.2、报告描述符

报告描述符比较复杂,它是以item形式排列组合而成,无固定长度。为了

准确描述来自一个控制管道的数据,一个报告描述符必须包括以下内容:

偏移量 域

0

2

4

6

8

10

13

15

17

XX

3.3、[物理描述符]

该描述符不是必须的。

USAGE_PAGE

USAGE

COLLECTION

USAGE ID

LOGICAL_MINIMUM

LOGICAL_MAXIMUM

REPORT_SIZE

REPORT_COUNT

INPUT/OUTPUT

…上述重复

END_COLLECTION

大小/字

2

2

2

2

2

3

2

2

2

说明

4、STM32 USB HID

在STM32上实现USB HID的功能,首先芯片选择要选则带有USB接口的系列。

正确搭建硬件环境,添加使用官方USB库。

4.1、USB库简介

详细正确内容请参看《深入解析STM32_USB-FS-Device_Lib库V0.2》以下仅

个人整理。

包含官方库文件,及自

己封装的一些函数。

1、hw_config

其他工程摘的USB配置(IO、中断等)函数,也可自行实现在其他文件中。

2、usb_core.c

这个c文件是个庞大的文件,主要是定义了usb2.0的标注协议处理函数。

3、usb_desc.c

描述符相关

4、usb_endp.c

这个文件很简单,就是定义了结果几个端点输入输出函数。

5、usb_init.c

这个文件是主要是初始化。

6、usb_int.c

一看就知道跟中断相关。在该文件中定义了两个函数,分别为低优先级的端

点正确传输中断服务程序CTR_LP()和高优先级端点正确传输的中断服务程序

CTR_HP()。

7、usb_io.c

自己封装USB操作函数,初始化、发送数据等。

8、usb_istr.c

这个c文件,主要是注册一些端点响应函数,如上面的端点输入输出回电函

数,还有就是ISTR中断状态状态寄存器的中断处理。

9、usb_mem.c

从文件名就能知道跟内存有关,这个文件主要定义了两个函数,一个读双缓

冲区PMA的数据PMAToUserBufferCopy(),另一个是写数据到双缓冲区

PMA,UserToPMABufferCopy。如果,当你的usb设备接收到了数据,当然数据存

放在PMA中了,我们要读出数据就要用到PMAToUserBufferCopy()函数了,如果

我们想要发送数据给usb主机,就要将你要发送的数据拷贝到PMA缓冲区中了,

这样才能发送出去,原理跟串口类似。

10、usb_prop.c

usb_prop.c文件可以说是一个蛮重要的文件,因为USB的许多处理函数都在

这里定义。在无论是在USB的建立阶段、数据阶段还是状态阶段的一些处理都在

这个文件,USB标准函数请求的函数也在这个文件里。

11、usb_pwr.c

这个文件看文件名就知道跟功耗有关了,有很多的状态:上电、掉电、挂起、

恢复。

12、usb_regs.c

13、usb_sil.c

这个文件主要是简单接口层的初始化,和端点的读写操作函数。总共有3个

函数:USB_SIL_Init();USB_SIL_Write();USB_SIL_Read()。

14、platform_config.h

其他工程摘的USB上拉IO定义。

4.2、USB HID设备自定义

在各种STM32学习板自带的演示例程中,都有几个USB的例程。如果我们想

实现一个USB功能,可以拿里面的例子来改。那么具体要改哪些地方呢?首先要

改各种描述符,然后是具体的数据处理。

1、更改设备描述符

描述符在文件usb_desc.c中。设备描述符的结构都标准的,长度也是固定的。

更改如下:

const uint8_t DeviceDescriptor[SIZ_DEVICE_DESC] =

{

0x12, /*bLength */

USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType*/

0x00, /*bcdUSB */

0x02,

0x00, /*bDeviceClass*/

0x00, /*bDeviceSubClass*/

0x00, /*bDeviceProtocol*/

0x40, /*bMaxPacketSize40*/

0x83, /*idVendor (0x0483)*/

0x04,

0x50, /*idProduct = 0x5750*/

0x57,

0x00, /*bcdDevice rel. 2.00*/

0x02,

0x01, /*Index of string descriptor describing manufacturer */

0x02, /*Index of string descriptor describing product*/

0x03, /*Index of string descriptor describing the device serial

number */

0x01 /*bNumConfigurations*/

};

2、更改配置描述符集合

配置描述符集合包括配置描述符、接口描述符、类特殊描述符(这里是HID

描述符)、以及端点描述符。

const uint8_t ConfigDescriptor[SIZ_CONFIG_DESC] =

{

/*配置描述符*/

0x09, /* bLength: Configuation Descriptor size */

USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bConfigurationType */

CUSTOMHID_SIZ_CONFIG_DESC, /* wTotalLength: Bytes returned */

0x00,

0x01, /* bNumInterfaces: 1 interface */

0x01, /* bConfigurationValue: Configuration value */

0x00, /* iConfiguration */

0xC0, /* bmAttributes: Bus powered */

0x96, /* MaxPower 0x96*2=300 mA */

/*接口描述符*/

0x09, /* bLength: Interface Descriptor size */

USB_INTERFACE_DESCRIPTOR_TYPE, /* bInterfaceType */

0x00, /* bInterfaceNumber: Number of Interface */

0x00, /* bAlternateSetting: Alternate setting */

0x02, /* bNumEndpoints */

0x03, /* bInterfaceClass: HID */

0x00, /* bInterfaceSubClass : 1=BOOT, 0=no boot */

0x00, /* nInterfaceProtocol : 0=none */

0, /* iInterface: Index of string descriptor */

/*HID描述符*/

0x09, /* bLength: HID Descriptor size */

HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */

0x10, /* bcdHID: HID Class Spec release number */

0x01,

0x00, /* bCountryCode: Hardware target country */

0x01, /* bNumDescriptors: Number of HID class

descriptors to follow */

0x22, /* bDescriptorType */

CUSTOMHID_SIZ_REPORT_DESC,/* wItemLength: Total length of Report

descriptor */

0x00,

/*端点描述符*/

0x07, /* bLength: Endpoint Descriptor size */

USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */

0x82, /* bEndpointAddress: Endpoint Address (IN) */

// 0 : the endpoint number

// 4 : reserved

// bit 7 : 0(OUT), 1(IN)

0x03, /* bmAttributes: Interrupt endpoint */

0x40, /* wMaxPacketSize: 64 Bytes max */

0x00,

0x02, /* bInterval: Polling Interval (2 ms) */

0x07, /* bLength: Endpoint Descriptor size */

USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: */

0x01, /* bEndpointAddress: */

/* Endpoint Address (OUT) */

0x03, /* bmAttributes: Interrupt endpoint */

0x40, /* wMaxPacketSize: 64 Bytes max */

0x00,

0x02, /* bInterval: Polling Interval (2 ms) */

};

3、更改字符串描述符

字符串描述符主要是设备的显示名称等。

const uint8_t StringLangID[SIZ_STRING_LANGID] =

{

CUSTOMHID_SIZ_STRING_LANGID,

USB_STRING_DESCRIPTOR_TYPE,

0x09,

0x04

}; /* LangID = 0x0409: U.S. English */

const uint8_t StringVendor[SIZ_STRING_VENDOR] =

{

CUSTOMHID_SIZ_STRING_VENDOR, /* Size of Vendor string */

USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType*/

'M', 0, 'y', 0, 'U', 0,'S', 0,'B', 0, '_', 0, 'H', 0,'I',0,'D',0

};

const uint8_t StringProduct[SIZ_STRING_PRODUCT] =

{

CUSTOMHID_SIZ_STRING_PRODUCT, /* bLength */

USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */

'C', 0, 'S', 0, 'C', 0, ' ', 0, 'R', 0,

0,'a',0,'d',0,'e',0,'r',0,' ',0

};

uint8_t StringSerial[CUSTOMHID_SIZ_STRING_SERIAL] =

{

CUSTOMHID_SIZ_STRING_SERIAL, /* bLength */

USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */

'x', 0, 'x', 0, 'x', 0,'x', 0,'x', 0, 'x', 0, 'x', 0

};

4、更改报告描述符

报告描述符比较复杂,这里就不详述了

const uint8_t ReportDescriptor[SIZ_REPORT_DESC] =

{

//

0x05, 0x8c, /* USAGE_PAGE (ST Page) */

0x09, 0x01, /* USAGE (Demo Kit) */

0xa1, 0x01, /* COLLECTION (Application) */

// The Input report

0x09,0x03, // USAGE ID - Vendor defined

0x15,0x00, // LOGICAL_MINIMUM (0)

0x26,0x00, 0xFF, // LOGICAL_MAXIMUM (255)

0x75,0x08, // REPORT_SIZE (8bit)

0x95,0x40, // REPORT_COUNT (64Byte)

0x81,0x02, // INPUT (Data,Var,Abs)

// The Output report

0x09,0x04, // USAGE ID - Vendor defined

0x15,0x00, // LOGICAL_MINIMUM (0)

0x26,0x00,0xFF, // LOGICAL_MAXIMUM (255)

0x75,0x08, // REPORT_SIZE (8bit)

0x95,0x40, // REPORT_COUNT (64Byte)

0x91,0x02, // OUTPUT (Data,Var,Abs)

'e',

0xc0 /* END_COLLECTION */

};

5、更改端口初始化

对应描述符中的端口设置对端口初始化。在usb_prop.c文件中,找到void

CustomHID_Reset (void)函数,该函数是负责初始化端点的。

/* Initialize Endpoint 1 */

SetEPType(ENDP1, EP_INTERRUPT);

SetEPRxAddr(ENDP1, ENDP1_RXADDR);

SetEPRxCount(ENDP1, REPORT_COUNT);

SetEPRxStatus(ENDP1, EP_RX_VALID);

/* Initialize Endpoint 2 */

SetEPType(ENDP2, EP_INTERRUPT);

SetEPTxAddr(ENDP2, ENDP2_TXADDR);

SetEPTxCount(ENDP2, REPORT_COUNT);

SetEPTxStatus(ENDP2, EP_TX_NAK);

设置端点1接收,端点2发送。

6、更改数据接收函数

在usb_conf.h中找到#define EP1_OUT_Callback NOP_Process 一行,将它

屏蔽。

在usb_endp.c中增加void EP1_OUT_Callback(void)回调函数

void EP1_OUT_Callback(void)

{

u16 count_tmp;

count_tmp = GetEPRxCount(ENDP1); //获取接收到数据长度

PMAToUserBufferCopy(USB_Receive_Buffer+USB_Receive_DataLen,

ENDP1_RXADDR,count_tmp);//拷贝出数据

SetEPRxValid(ENDP1);//

完成拷贝后置有效状态,从而EP1发送ACK主

机可以进行下一个数据包的发送

…//自行处理

}

7、实现数据发送函数

发送过程就是,UserToPMABufferCopy拷贝要发送的数据至缓冲区,

SetEPTxCount设置发送大小,SetEPTxValid启动发送, GetEPTxStatus等待发

送完成。封装发送数据的函数,方便使用。

void USB_IO_SendData()

{

USB_Send_Frame = 0;

if(USB_Send_DataLen>64)

{

while(USB_Send_DataLen>=64)

{

USB_Send_DataLen = USB_Send_DataLen - 64;

UserToPMABufferCopy((USB_Send_Buffer+(USB_Send_Frame<<6

)), ENDP2_TXADDR, 64);

USB_Send_Frame ++;

SetEPTxCount(ENDP2, 64);

SetEPTxValid(ENDP2);

while(GetEPTxStatus(ENDP2)!=EP_TX_NAK);//

}

if(USB_Send_DataLen>0)

{

memset(sendtemp,0,64);

memcpy(sendtemp,(USB_Send_Buffer+(USB_Send_Frame<<6)),U

SB_Send_DataLen);

UserToPMABufferCopy(sendtemp, ENDP2_TXADDR, 64);

USB_Send_Frame ++;

SetEPTxCount(ENDP2, 64);

SetEPTxValid(ENDP2);

USB_Send_DataLen=0;

}

}

else

{

if(USB_Send_DataLen==64)

{

UserToPMABufferCopy(USB_Send_Buffer, ENDP2_TXADDR, 64);

SetEPTxCount(ENDP2, 64);

USB_Send_DataLen = 0;

USB_Send_Flag = 1;

SetEPTxValid(ENDP2);

}

else if(USB_Send_DataLen>0)

{

memset(sendtemp,0,64);

memcpy(sendtemp,(USB_Send_Buffer+(USB_Send_Frame<<6)),U

SB_Send_DataLen);

UserToPMABufferCopy(sendtemp, ENDP2_TXADDR, 64);

SetEPTxCount(ENDP2, 64);

}

}

}

USB_Send_DataLen = 0;

SetEPTxValid(ENDP2);

4.3、通信测试

更改platform_config.h中USB上拉引脚的控制口,it.c实现USB中断,调用

usb_io.c中USB_IO_Init函数完成USB初始化,自行实现接收、发送数据即可。

信息与设备描述符、字符串描述符内容一致。

发送接收数据如下


本文标签: 描述符 设备 端点 配置 接口