描述符相关源码分析
本章分析百问网提供的全场景工业互联设备管理系统解决方案中有关 USB 设备编程,应用层中关于 USB 设备描述符相关源码进行分析。
ux_device_descriptors.h 源文件分析
重点分析头文件中,关于 USB 中各种设备描述符的定义。
USB 复合设备类型枚举
typedef enum
{
CLASS_TYPE_NONE = 0,
CLASS_TYPE_HID = 1,
CLASS_TYPE_CDC_ACM = 2,
CLASS_TYPE_MSC = 3,
CLASS_TYPE_CDC_ECM = 4,
CLASS_TYPE_DFU = 5,
CLASS_TYPE_PIMA_MTP = 6,
CLASS_TYPE_RNDIS = 7,
} USBD_CompositeClassTypeDef;
该枚举定义了一个 USB 复合设备中可能包含的各个 USB 类的类型,例如:
- CLASS_TYPE_NONE:未定义或无类;
- CLASS_TYPE_HID:人机接口设备(如键盘、鼠标);
- CLASS_TYPE_CDC_ACM:通信设备类 - 抽象控制模型(CDC ACM,常用于虚拟串口);
- CLASS_TYPE_MSC:大容量存储类(Mass Storage Class);
- 其它类型包括网络、固件升级(DFU)等。
这种枚举有助于在代码中根据USB复合设备所属的不同类的类型进行不同的处理和配置。
Note:
回顾一下
USB 复合设备(Composite Device)与设备类型的概念。复合设备是指一个物理上的 USB 设备,同时实现了多个逻辑功能或者服务,这些功能分别对应不同的 USB 接口。也就是说,一个复合设备在同一个 USB 连接上提供了多个逻辑上独立的功能模块。例如,一个 USB 设备既可以作为大容量存储设备(MSC),同时具备人机接口(HID)功能。
虽然主机只看到一个 USB 设备(单一的物理连接),但是设备内部包含了多个接口,每个接口负责不同的功能,按照类型进行划分。
在复合设备的 USB 配置描述符中,会包含多个接口描述符。为了使主机能够正确地识别和分组这些接口,USB 标准引入了接口关联描述符(Interface Association Descriptor, IAD),用于将属于同一功能模块的多个接口归类在一起。后面会在源码中看到。
USB 设备的类型
USB 设备的类型通常是指设备所实现的功能类别,也称为设备类(Class)。USB 标准为常见的功能定义了若干标准类,例如:
- HID(Human Interface Device): 人机接口设备,如键盘、鼠标、游戏控制器等。
- CDC(Communication Device Class): 通信设备类,如虚拟串口、调制解调器等。
- MSC(Mass Storage Class): 大容量存储设备,如 U 盘、移动硬盘等。
- Audio、Video、Printer、Hub 等: 分别对应音频设备、视频设备、打印机和集线器等。
USB 设备类型的信息主要通过描述符传递。对于非复合设备,bDeviceClass 字段直接标识设备的类型;而对于复合设备,各接口描述符中的 bInterfaceClass 字段会标识每个接口的类型。在后面的源码中会看到。
USB 标准定义了一系列常用设备类,但设备制造商也可以使用“厂商自定义(Vendor-specific)”的类代码,以实现标准类未覆盖的特殊功能。
不同的设备类型不仅定义了基本功能,还规定了相应的数据传输协议和控制命令,确保主机与设备之间能按照预期的方式通信。
USB 端点句柄结构体
typedef struct
{
uint32_t status; // 端点当前的状态(例如:空闲、传输中、错误等状态标识)
uint32_t total_length; // 本次传输数据的总长度
uint32_t rem_length; // 传输过程中剩余未传输的数据长度
uint32_t maxpacket; // 端点一次传输所允许的最大数据包大小
uint16_t is_used; // 标志此端点是否已经被分配或使用(可能用作布尔值)
uint16_t bInterval; // 主要用于中断型或周期性传输的端点,定义传输间隔
} USBD_EndpointTypeDef;
typedef struct
{
uint8_t add; // 端点地址(含方向信息,USB端点地址范围通常是0~15,加上方向位)
uint8_t type; // 端点传输类型(控制、批量、等时、或中断)
uint16_t size; // 端点的最大包大小
uint8_t is_used; // 标识端点是否已被使用
} USBD_EPTypeDef;
这两种结构体在程序中分别用于不同的管理场景,一个用于详细传输控制,较为详细的定义端点的句柄信息,一个用于描述复合设备中各类端点的基本信息,简化端点的定义。
Note:
USB 端点(Endpoint)作为为设备与主机之间进行数据传输的逻辑连接点(主机-设备管道终点)。参考USB 通信流 5.3.1 设备端点
逻辑传输通道
- 端点不是一个物理接口,而是在 USB 设备内部逻辑上定义的数据传输管道(pipe)的终点。每个端点负责数据的发送或接收,并且其方向性是单一的(除控制端点0之外,其他端点都是单向的)。端点地址和方向
- 每个端点都有一个唯一的端点地址,该地址由端点号和方向(IN 或 OUT)组成。
- IN 端点:数据从设备传输到主机。
- OUT 端点:数据从主机传输到设备。
- 例如,控制端点(Endpoint 0)通常既能发送也能接收数据,但在其他数据传输中,每个端点只能单方向传输。传输类型
- USB 端点支持四种主要传输类型,每种传输类型适用于不同的数据传输需求:
- 控制传输(Control Transfer):主要用于设备的初始化、配置和管理。所有 USB 设备都必须实现端点0作为控制端点。
- 批量传输(Bulk Transfer):用于传输大数据量、对传输速度要求较高但对延时不敏感的数据,如文件传输。
- 中断传输(Interrupt Transfer):用于低延迟、周期性的数据传输,例如键盘和鼠标数据。
- 等时传输(Isochronous Transfer):适用于对实时性要求较高的数据流传输,如音视频数据,但不提供数据重传机制。设备描述符中的角色
- 在 USB 设备的配置描述符中,每个接口都会定义一个或多个端点(除了控制端点0之外)。这些端点通过端点描述符详细说明其属性,包括:
- 端点地址
- 传输类型
- 最大数据包大小
- 传输间隔(主要用于中断和等时传输)数据传输的基本单位
- 端点是 USB 数据传输的基本单位。主机与设备之间所有的数据交换都是通过这些端点来完成的。
- 不同端点之间的独立性使得一个 USB 设备可以同时进行多种不同类型的数据传输,例如在一个复合设备中,一个端点可以用于虚拟串口数据传输(CDC ACM),而另一个端点则可能用于大容量存储数据传输(MSC)。
复合设备元素结构体
typedef struct
{
USBD_CompositeClassTypeDef ClassType; // 指明该复合设备元素所属的USB类(使用上面定义的枚举)
uint32_t ClassId; // 用于唯一标识某个类实例,可在多实例或动态配置时使用
uint8_t InterfaceType; // 指示接口的类型或用途(例如:控制、数据传输等)
uint32_t Active; // 标志该类是否处于激活状态
uint32_t NumEps; // 该类使用的端点数量
uint32_t NumIf; // 该类使用的接口数量
USBD_EPTypeDef Eps[USBD_MAX_CLASS_ENDPOINTS]; // 保存该类所有端点的描述信息,数组大小由宏USBD_MAX_CLASS_ENDPOINTS决定
uint8_t Ifs[USBD_MAX_CLASS_INTERFACES]; // 保存该类所涉及的接口号,数组大小由宏USBD_MAX_CLASS_INTERFACES决定
} USBD_CompositeElementTypeDef;
用于管理复合设备中的每个功能模块,不同功能模块实现不同的 USB 类型。结构体记录了每个模块所使用的接口和端点信息,便于后续描述符的构建和配置管理。
设备类句柄结构体
typedef struct _USBD_DevClassHandleTypeDef
{
uint8_t Speed; // USB设备的工作速度(如全速、高速、超级速度等)
uint32_t classId; // 当前设备类的标识号
uint32_t NumClasses; // 设备中包含的类的数量(不超过宏USBD_MAX_SUPPORTED_CLASS)
USBD_CompositeElementTypeDef tclasslist[USBD_MAX_SUPPORTED_CLASS]; // 数组形式存储设备中各个复合类的描述,每个元素为前面定义的USBD_CompositeElementTypeDef
uint32_t CurrDevDescSz; // 当前设备描述符的大小,用于设备描述符数据的管理
uint32_t CurrConfDescSz; // 当前配置描述符的大小,用于描述符的构建和传输
} USBD_DevClassHandleTypeDef;
该结构体是整个 USB 设备描述符管理的核心句柄,包含了设备运行时需要知道的所有类、端点、接口等信息,便于后续描述符数据的生成和 USB 通信的管理。
USB 端点方向枚举
typedef enum
{
OUT = 0x00, // 表示数据从主机发送到设备,对应端点地址中方向位为0
IN = 0x80, // 表示数据从设备发送到主机,对应端点地址中方向位为1(通常是0x80,即最高位为1)
} USBD_EPDirectionTypeDef;
USB 相关描述符结构体
设备描述符
typedef struct
{
uint8_t bLength; // 描述符的长度(单位字节)
uint8_t bDescriptorType; // 描述符类型(设备描述符的类型值为1)
uint16_t bcdUSB; // USB规范版本号,例如0x0200表示USB2.0
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize; // 端点0(控制传输)的最大包大小
uint16_t idVendor; // 厂商ID
uint16_t idProduct; // 产品ID
uint16_t bcdDevice; // 设备版本号
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber; // 字符串描述符索引,分别对应厂商、产品、序列号字符串
uint8_t bNumConfigurations; // 设备支持的配置数量
} __PACKED USBD_DeviceDescTypedef;
__PACKED:宏通常用于告诉编译器对该结构体进行紧凑打包,不添加额外的内存填充,以确保描述符数据与USB规范一致。
接口关联描述符(IAD)
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bFirstInterface; // 归组中第一个接口的编号
uint8_t bInterfaceCount; // 该功能中包含的接口数量
uint8_t bFunctionClass;
uint8_t bFunctionSubClass;
uint8_t bFunctionProtocol;
uint8_t iFunction; // 其它字段定义该功能的类别、子类别和协议,以及字符串描述符索引iFunction。
} __PACKED USBD_IadDescTypedef;
接口描述符
typedef struct
{
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber; // 接口编号
uint8_t bAlternateSetting; // 备用设置编号(用于同一接口的不同配置)
uint8_t bNumEndpoints; // 该接口使用的非控制端点数
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} __PACKED USBD_IfDescTypedef;
端点描述符
typedef struct
{
uint8_t bLength; // 描述符长度:该字段指明了描述符的总字节数。对于端点描述符,通常是固定值(例如 7)。
uint8_t bDescriptorType; // 描述符类型:这里表示这是一个端点描述符,标准 USB 定义中端点描述符的类型值为 0x05。
uint8_t bEndpointAddress; // 端点地址:低 4 位表示端点号,高 4 位用于指示数据传输方向(IN 或 OUT)。
uint8_t bmAttributes; // 属性:标识端点支持的传输类型,如控制传输、批量传输、中断传输或等时传输。
uint16_t wMaxPacketSize; // 最大数据包大小:指定该端点一次能传输的最大字节数。
uint8_t bInterval; // 轮询间隔:用于中断传输和等时传输,表示主机对该端点的轮询时间间隔(通常以毫秒为单位)。
} __PACKED USBD_EpDescTypedef;
此结构体定义了一个 USB 端点的描述符信息,主机通过该描述符得知设备端点的基本属性。
使用 __PACKED 关键字可以防止编译器进行内存对齐填充,确保结构体与 USB 标准要求的格式一致。
配置描述符
typedef struct
{
uint8_t bLength; // 描述符长度:表示配置描述符的字节数,对于标准配置描述符通常是 9 字节。
uint8_t bDescriptorType; // 描述符类型:配置描述符的类型值为 0x02。
uint16_t wDescriptorLength; // 总长度:整个配置描述符及其包含的所有接口和端点描述符的总长度。
uint8_t bNumInterfaces; // 接口数量:该配置中包含的接口数目。
uint8_t bConfigurationValue; // 配置值:主机选择此配置时会使用这个值。
uint8_t iConfiguration; // 配置描述符字符串索引:用于提供该配置的可读描述信息。
uint8_t bmAttributes; // 配置属性:指示设备是否支持自供电、远程唤醒等特性。
uint8_t bMaxPower; // 最大功率:设备在该配置下的最大功耗,以 2mA 为单位(例如 50 表示 100mA)。
} __PACKED USBD_ConfigDescTypedef;
配置描述符描述了设备在特定配置下的整体属性,包括包含哪些接口、设备功耗和其他配置信息。
主机通过读取该描述符来选择设备的工作模式。
设备限定符描述符
typedef struct
{
uint8_t bLength; // 描述符长度:通常为 10 字节。
uint8_t bDescriptorType; // 描述符类型:设备限定符描述符的类型值为 0x06。
uint16_t bcdDevice; // 设备版本号:以 BCD(Binary Coded Decimal)形式表示,如 0x0110 表示 1.10 版本。
uint8_t Class; // 设备类代码:指定设备的类代码(若接口中另有定义,则这里可能为 0)。
uint8_t SubClass; // 设备子类代码。
uint8_t Protocol; // 设备协议代码。
uint8_t bMaxPacketSize; // 控制端点的最大数据包大小:用于默认控制传输。
uint8_t bNumConfigurations;// 配置数量:设备支持的配置数量。
uint8_t bReserved; // 保留字段:必须设置为 0。
} __PACKED USBD_DevQualiDescTypedef;
设备限定符描述符主要用于描述设备在非当前工作速率(例如高速设备在全速下)的能力信息。
当设备支持多种速率时,主机可以通过该描述符获知在另一速率下的相关参数。
CDC 类(通信设备类)相关的功能描述符
CDC 头部功能描述符
typedef struct
{
/* Header Functional Descriptor*/
uint8_t bLength; // 描述符长度:通常为 5 字节。
uint8_t bDescriptorType; // 描述符类型:对于 CDC 功能描述符,类型一般为 0x24。
uint8_t bDescriptorSubtype; // 子类型:指明这是一个头部功能描述符(子类型 0x00)。
uint16_t bcdCDC; // CDC 规范版本:用 BCD 格式表示,如 0x0110 表示 CDC 1.10 版。
} __PACKED USBD_CDCHeaderFuncDescTypedef;
此描述符标志着 CDC 功能描述符的开始,并提供了 CDC 规范的版本信息,确保主机了解设备遵循的 CDC 标准。
CDC 呼叫管理功能描述符
typedef struct
{
/* Call Management Functional Descriptor*/
uint8_t bLength; // 描述符长度:通常为 5 字节。
uint8_t bDescriptorType; // 描述符类型:仍然为 0x24,表示功能描述符。
uint8_t bDescriptorSubtype; // 子类型:指明这是呼叫管理功能描述符(通常为 0x01)。
uint8_t bmCapabilities; // 能力标志:指示设备是否支持处理呼叫管理请求(例如是否支持数据接口处理呼叫)。
uint8_t bDataInterface; // 数据接口编号:如果设备支持呼叫管理,指明用于数据传输的接口编号。
} __PACKED USBD_CDCCallMgmFuncDescTypedef;
描述了设备在呼叫管理方面的能力,通常用于区分控制通道与数据通道的职责,使主机能够正确地将呼叫管理命令发送到指定接口。
CDC ACM 功能描述符
typedef struct
{
/* ACM Functional Descriptor*/
uint8_t bLength; // 描述符长度:通常为 4 字节。
uint8_t bDescriptorType; // 描述符类型:0x24,表示功能描述符。
uint8_t bDescriptorSubtype; // 子类型:表明这是抽象控制管理(ACM)描述符(通常为 0x02)。
uint8_t bmCapabilities; // 能力标志:指出设备支持哪些 ACM 功能,例如是否支持发送通知、设定换行符等。
} __PACKED USBD_CDCACMFuncDescTypedef;
用于提供 CDC ACM 特定的控制能力信息,对于实现虚拟串口功能来说,此描述符使主机能够获知设备在控制功能方面的支持情况。
CDC 联合功能描述符
typedef struct
{
/* Union Functional Descriptor*/
uint8_t bLength; // 描述符长度:通常为 5 字节(对于简单情况,若从接口有多个,长度可能更长)。
uint8_t bDescriptorType; // 描述符类型:0x24。
uint8_t bDescriptorSubtype; // 子类型:指明这是联合功能描述符(通常为 0x06)。
uint8_t bMasterInterface; // 主接口号:通常为控制接口的编号。
uint8_t bSlaveInterface; // 从接口号:通常为数据接口的编号。
} __PACKED USBD_CDCUnionFuncDescTypedef;
此描述符用于关联控制接口(主接口)和数据接口(从接口),使主机能清晰地知道哪个数据接口与哪个控制接口对应,从而正确地配置 CDC 设备。
ux_device_descriptors.c 源文件分析
USBD_Get_Device_Framework_Speed
该函数根据传入的设备速度参数(Speed
)生成对应的 USB 设备描述符框架(Device Framework),并返回该描述符数组的指针,同时通过 Length
参数返回描述符的总长度。USB 设备描述符框架通常包含设备描述符和配置描述符,用于设备枚举和配置时向主机提供设备相关信息。
/**
* @brief USBD_Get_Device_Framework_Speed
* 根据设备速度(高速或全速)返回设备描述符框架
* @param Speed : HIGH 或 FULL SPEED 标志,指示当前设备的工作速度
* @param Length : 指向一个 ULONG 类型变量,用于返回对应描述符数组的总长度
* @retval 返回指向生成的设备描述符框架的缓冲区指针
*/
uint8_t *USBD_Get_Device_Framework_Speed(uint8_t Speed, ULONG *Length)
{
// 定义用于保存返回的描述符框架缓冲区地址
uint8_t *pFrameWork = NULL;
// 根据传入的 Speed 判断设备工作速度
if (USBD_FULL_SPEED == Speed)
{
/* 如果设备为全速(FULL SPEED),则调用构建全速设备描述符框架的函数 */
USBD_Device_Framework_Builder(&USBD_Device_FS, // 全速设备描述符构建结构体
pDevFrameWorkDesc_FS, // 存放全速描述符的缓冲区
UserClassInstance, // 用户定义的类实例(用于添加类特定描述符)
Speed); // 当前设备速度
/* 计算全速描述符框架的总长度:设备描述符大小 + 配置描述符大小 */
*Length = (ULONG)(USBD_Device_FS.CurrDevDescSz + USBD_Device_FS.CurrConfDescSz);
/* 将描述符框架缓冲区的指针赋给 pFrameWork */
pFrameWork = pDevFrameWorkDesc_FS;
}
else
{
/* 如果设备不是全速,则认为为高速(HIGH SPEED) */
USBD_Device_Framework_Builder(&USBD_Device_HS, // 高速设备描述符构建结构体
pDevFrameWorkDesc_HS, // 存放高速描述符的缓冲区
UserClassInstance, // 用户定义的类实例
Speed); // 当前设备速度
/* 计算高速描述符框架的总长度 */
*Length = (ULONG)(USBD_Device_HS.CurrDevDescSz + USBD_Device_HS.CurrConfDescSz);
/* 将高速描述符框架的缓冲区指针赋给 pFrameWork */
pFrameWork = pDevFrameWorkDesc_HS;
}
// 返回构建好的描述符框架的指针
return pFrameWork;
}
USBD_Device_Framework_Builder
该函数主要用于构建 USB 设备的描述符框架(Device Framework)。它根据传入的参数:
- pdev:设备实例(包含设备描述符大小、配置描述符大小以及类信息等)
- pDevFrameWorkDesc:描述符框架缓冲区的起始地址
- UserClassInstance:一个数组,指示需要添加到设备框架中的各个 USB 类(例如 CDC ACM、MSC 等)
- Speed:设备工作速度(高速或全速)
函数首先构建通用的设备描述符(包括设备描述符和在高速模式下的设备限定描述符),然后遍历 UserClassInstance
数组,调用类描述符构建函数 USBD_FrameWork_AddClass
将各个类的描述符追加到设备框架中。最后,根据添加的类数量和类型更新设备描述符中的类别信息(对于复合设备或特定的 CDC ACM 设备)。
/**
* @brief USBD_Device_Framework_Builder
* 构建设备描述符框架
* @param pdev: 指向设备实例的指针,保存描述符构建过程中的相关信息
* @param pDevFrameWorkDesc: 指向设备描述符框架缓冲区的指针
* @param UserClassInstance: 数组,包含要添加的各个 USB 类的类型
* @param Speed: 设备速度参数(USBD_HIGH_SPEED 或 USBD_FULL_SPEED)
* @retval 返回指向构建完成的设备描述符框架的指针
*/
static uint8_t *USBD_Device_Framework_Builder(USBD_DevClassHandleTypeDef *pdev,
uint8_t *pDevFrameWorkDesc,
uint8_t *UserClassInstance,
uint8_t Speed)
{
// 静态定义设备描述符和设备限定描述符的指针(静态变量在多次调用中保持值)
static USBD_DeviceDescTypedef *pDevDesc;
static USBD_DevQualiDescTypedef *pDevQualDesc;
uint8_t Idx_Instance = 0U; // 用于遍历 UserClassInstance 数组的索引
/* 将当前配置描述符和设备描述符的累计长度清零 */
pdev->CurrConfDescSz = 0U;
pdev->CurrDevDescSz = 0U;
/* 设置设备描述符指针,指向描述符框架缓冲区的起始位置 */
pDevDesc = (USBD_DeviceDescTypedef *)pDevFrameWorkDesc;
/* ---------------- 构建设备通用描述符部分 ---------------- */
// 设置设备描述符的各个字段
pDevDesc->bLength = (uint8_t)sizeof(USBD_DeviceDescTypedef); // 描述符长度
pDevDesc->bDescriptorType = UX_DEVICE_DESCRIPTOR_ITEM; // 描述符类型(设备描述符)
pDevDesc->bcdUSB = USB_BCDUSB; // USB 协议版本 USB 2.0
pDevDesc->bDeviceClass = 0x00; // 初始设备类别(0表示由接口决定)
pDevDesc->bDeviceSubClass = 0x00; // 子类
pDevDesc->bDeviceProtocol = 0x00; // 协议
pDevDesc->bMaxPacketSize = USBD_MAX_EP0_SIZE; // 控制端点 0 的最大包大小
pDevDesc->idVendor = USBD_VID; // 厂商 ID
pDevDesc->idProduct = USBD_PID; // 产品 ID
pDevDesc->bcdDevice = 0x0200; // 设备版本
pDevDesc->iManufacturer = USBD_IDX_MFC_STR; // 厂商字符串索引
pDevDesc->iProduct = USBD_IDX_PRODUCT_STR; // 产品字符串索引
pDevDesc->iSerialNumber = USBD_IDX_SERIAL_STR; // 序列号字符串索引
pDevDesc->bNumConfigurations = USBD_MAX_NUM_CONFIGURATION; // 支持的配置数
/* 更新设备描述符的累计长度 */
pdev->CurrDevDescSz += (uint32_t)sizeof(USBD_DeviceDescTypedef);
/* --------- 如果设备为高速模式,则添加设备限定描述符 --------- */
if (Speed == USBD_HIGH_SPEED)
{
// 设备限定描述符紧跟在设备描述符后面
pDevQualDesc = (USBD_DevQualiDescTypedef *)(pDevFrameWorkDesc + pdev->CurrDevDescSz);
pDevQualDesc->bLength = (uint8_t)sizeof(USBD_DevQualiDescTypedef); // 描述符长度
pDevQualDesc->bDescriptorType = UX_DEVICE_QUALIFIER_DESCRIPTOR_ITEM; // 描述符类型(设限定描述符)
pDevQualDesc->bcdDevice = 0x0200; // 设备版本
pDevQualDesc->Class = 0x00; // 类别(由接口决定)
pDevQualDesc->SubClass = 0x00; // 子类
pDevQualDesc->Protocol = 0x00; // 协议
pDevQualDesc->bMaxPacketSize = 0x40; // 控制端点最大包大小(一般高速为 64 字节)
pDevQualDesc->bNumConfigurations = 0x01; // 配置数(一般为1)
pDevQualDesc->bReserved = 0x00; // 保留字段
/* 更新设备描述符累计长度,包含设备限定描述符 */
pdev->CurrDevDescSz += (uint32_t)sizeof(USBD_DevQualiDescTypedef);
}
/* -------------------- 添加各个类的描述符 -------------------- */
// 遍历 UserClassInstance 数组,依次添加每个 USB 类的描述符
while (Idx_Instance < USBD_MAX_SUPPORTED_CLASS)
{
// 判断当前类是否有效:
// 1. pdev->classId 和 pdev->NumClasses 均未超出最大支持类数量
// 2. UserClassInstance 数组中该位置不为 CLASS_TYPE_NONE
if ((pdev->classId < USBD_MAX_SUPPORTED_CLASS) &&
(pdev->NumClasses < USBD_MAX_SUPPORTED_CLASS) &&
(UserClassInstance[Idx_Instance] != CLASS_TYPE_NONE))
{
/* 调用复合类描述符构建函数,将当前类的描述符添加到设备框架中
参数说明:
- pdev: 设备实例
- (USBD_CompositeClassTypeDef)UserClassInstance[Idx_Instance]: 当前 USB 类(转换为复合类类型)
- 0: 通常用于接口号或其他偏移量(此处传0)
- Speed: 设备速度
- (pDevFrameWorkDesc + pdev->CurrDevDescSz): 新增描述符在缓冲区中的起始位置 */
(void)USBD_FrameWork_AddClass(pdev,
(USBD_CompositeClassTypeDef)UserClassInstance[Idx_Instance],
0, Speed,
(pDevFrameWorkDesc + pdev->CurrDevDescSz));
/* 每添加一个类,就更新设备实例中的 classId 和已添加的类数量 */
pdev->classId++;
pdev->NumClasses++;
}
// 移动到下一个类实例
Idx_Instance++;
}
/* ------------- 根据添加的类数量更新设备描述符中的类别字段 ------------- */
if (pdev->NumClasses > 1)
{
/* 如果添加了多个类,则设备为复合设备(Composite Device)
此时设备类别、子类、协议按照 USB 标准规定进行设置:
bDeviceClass = 0xEF 表示 Miscellaneous Device Class
bDeviceSubClass = 0x02
bDeviceProtocol = 0x01 */
pDevDesc->bDeviceClass = 0xEF;
pDevDesc->bDeviceSubClass = 0x02;
pDevDesc->bDeviceProtocol = 0x01;
}
else
{
/* 如果只有一个类,并且该类为 CDC ACM,则按照 CDC ACM 的标准更新设备类别字段:
bDeviceClass = 0x02 (Communication Device Class)
bDeviceSubClass = 0x02
bDeviceProtocol = 0x00 */
if (UserClassInstance[0] == CLASS_TYPE_CDC_ACM)
{
pDevDesc->bDeviceClass = 0x02;
pDevDesc->bDeviceSubClass = 0x02;
pDevDesc->bDeviceProtocol = 0x00;
}
}
/* 返回构建完成的设备描述符框架缓冲区指针 */
return pDevFrameWorkDesc;
}
USBD_FrameWork_AddClass
该函数用于在 USB 设备描述符框架中注册一个 USB 类(例如 CDC、MSC 等),并将该类的相关参数存储到设备实例中的全局类列表中。同时,它会调用配置描述符构建函数(USBD_FrameWork_AddToConfDesc
),为该类构建相应的配置描述符(包括接口描述符和端点描述符)。
/**
* @brief USBD_FrameWork_AddClass
* 注册一个 USB 类到类构建器中
* @param pdev: 设备实例指针,包含设备描述符和类信息
* @param class: 要添加的 USB 类类型(取自 USBD_CompositeClassTypeDef 枚举)
* @param cfgidx: 配置索引(目前未使用)
* @param Speed: 设备工作速度(例如高速或全速)
* @param pCmpstConfDesc: 指向复合设备配置描述符的缓冲区
* @retval 返回状态,成功返回 UX_SUCCESS,失败返回 UX_ERROR
*/
uint8_t USBD_FrameWork_AddClass(USBD_DevClassHandleTypeDef *pdev,
USBD_CompositeClassTypeDef class,
uint8_t cfgidx, uint8_t Speed,
uint8_t *pCmpstConfDesc)
{
/* 检查当前 classId 是否在支持的范围内,并且对应位置的类尚未被激活 */
if ((pdev->classId < USBD_MAX_SUPPORTED_CLASS) &&
(pdev->tclasslist[pdev->classId].Active == 0U))
{
/* 将当前 USB 类的参数存储到全局类列表中:
- 设置 ClassId 为当前的 classId 值
- 标记该类为激活状态(Active = 1)
- 保存 USB 类类型(class)
*/
pdev->tclasslist[pdev->classId].ClassId = pdev->classId;
pdev->tclasslist[pdev->classId].Active = 1U;
pdev->tclasslist[pdev->classId].ClassType = class;
/* 调用配置描述符构建函数,为该 USB 类构建并追加配置描述符和端点描述符
参数说明:
- pdev: 设备实例
- Speed: 设备工作速度
- pCmpstConfDesc: 指向复合设备配置描述符缓冲区
如果返回结果不为 UX_SUCCESS,则说明构建过程中出现错误,
这时返回 UX_ERROR 表示添加类失败。
*/
if (USBD_FrameWork_AddToConfDesc(pdev, Speed, pCmpstConfDesc) != UX_SUCCESS)
{
return UX_ERROR;
}
}
/* cfgidx 参数目前未被使用,使用 UNUSED 宏避免编译器警告 */
UNUSED(cfgidx);
/* 如果一切正常,返回成功状态 */
return UX_SUCCESS;
}
其他函数
函数体中只是对传入的参数进行简单的判断,并赋值。
/**
* @brief USBD_FrameWork_AddConfDesc
* Add a new class to the configuration descriptor
* @param Conf: configuration descriptor
* @param pSze: pointer to the configuration descriptor size
* @retval none
*/
static void USBD_FrameWork_AddConfDesc(uint32_t Conf, uint32_t *pSze)
/**
* @brief USBD_FrameWork_AddConfDesc
* Add a new class to the configuration descriptor
* @param Conf: configuration descriptor
* @param pSze: pointer to the configuration descriptor size
* @retval none
*/
static void USBD_FrameWork_AddConfDesc(uint32_t Conf, uint32_t *pSze)
/**
* @brief USBD_FrameWork_AssignEp
* Assign and endpoint
* @param pdev: device instance
* @param Add: Endpoint address
* @param Type: Endpoint type
* @param Sze: Endpoint max packet size
* @retval none
*/
static void USBD_FrameWork_AssignEp(
USBD_DevClassHandleTypeDef *pdev,
uint8_t Add,
uint8_t Type,
uint32_t Sze)
#if USBD_CDC_ACM_CLASS_ACTIVATED == 1
/**
* @brief USBD_FrameWork_CDCDesc
* Configure and Append the CDC Descriptor
* @param pdev: device instance
* @param pConf: Configuration descriptor pointer
* @param Sze: pointer to the current configuration descriptor size
* @retval None
*/
static void USBD_FrameWork_CDCDesc(
USBD_DevClassHandleTypeDef *pdev,
uint32_t pConf,
uint32_t *Sze)