一、STM32F1 的 USB 特性
STM32F1 的 USB 外设实现了 USB2.0 的接口和 APB1 总线间的接口。它有以下特性:
- 符合 USB2.0 全速设备的技术规范
- 可配置 1 到 8 个 USB 端点
- CRC(循环冗余校验)生成/校验,反向不归零(NRZI)编码/解码和位填充
- 支持同步传输
- 支持批量/同步端点的双缓冲区机制
- 支持 USB 挂起/恢复操作
- 帧锁定时钟脉冲生成
STM32F1的 USB 外设使用标准的 48Mhz 时钟,允许每个端点有独立的缓冲区,每个端点最大为 512 字节缓冲,最大 16 个单向或 8 个双向端点。USB 的传输格式由硬件完成,状态可以由寄存器标记,可以很大程度上简化我们的程序设计。USB模块启动时间tSTARTUP最大为1us, 这个需要在编程时注意。
ST 官方 Cube 库中提供的 官方 USB 协议栈,主要是包含了 USBD 内核 与 USB 各种类。USBD内核 一般是固定的,用户一般不需要修改,但 USBD类,如果用户需要修改或者扩展,比如复合设备或者用户自定义设备,则需要用户自行修改。
USB 协议栈将所有 USB 类都抽象成一个数据结构:USBD_ClassTypeDef,USBD 内核与USBD 类之间的纽带就是 USBD_ClassType 这个结构体。这个结构体是一个抽象类,定义了一些虚拟函数,比如初始化,反初始化,类请求指令处理函数,端点 0 发送完成,端点 0 接收处理,数据发送完成,数据接收处理,SOF 中断处理,同步传输发送未完成,同步传输接收未完成处理等等;用户在实现自己具体的 USB 类的时候需要将它实例化,USBD_ClassTypeDef 结构体是 USBD 内核提供给外部定义一个 USB 设备类的窗口,而 USB 类文件实际就是实现这个结构体具体实例化的过程。最后将这个具体实例化的对象注册到 USBD 内核的同时,USBD 内核 与 USBD 类也进行了关联。
二、USB 设备库
USB 设备库:
• 支持多包传输特性:不需按最大包尺寸划分,即可发送大量数据。
• 支持控制端点上最多 3 个双向传输 (兼容 OHCI 控制器)。
• 无需更改库代码 (只读),使用配置文件更改内核和库配置。
• 包括 32 位对齐数据结构体以处理高速模式中基于 DMA 的传输。
• 支持用户级别的多 USB OTG 内核实例 (配置文件)。
注 : USB 设备库可与 RTOS 共用或单独使用;CMSIS RTOS 封装的作用是对 OS 内核抽象。
USB 设备样例不显示消息。
三、USB 设备库架构
USB 设备库主要分为三层,应用在这三层之上。
第一层主要包含两部分:内核 和 类驱动 。
• 内核包含四个主要模块:
- USB 内核模块:提供本级 API、管理内部 USB 设备库状态机、处理 USB 中断的回调
- USB 请求模块:处理 USB 标准协议中第 9 章所规定的请求
- USB I/O 请求模块:处理底层 I/O 请求
- USB 日志和调试模块:遵循调试级别 USB_DEBUG_LEVEL,输出用户、日志、错误和调试消息。
• USB 设备库包括一组预定义的类驱动,可通过 USBD_RegisterClass () 程序链接至 USB内核。
USB 设备库为兼容 USB 2.0 的通用 USB 设备栈,与所有 STM32 USB 内核兼容,由于配置封装文件解除了 USB 库与底层驱动的依赖关系,因此它可轻松链接至任何 USB HAL 驱动。
四、USB OTG 硬件抽象层
可使用底层驱动将 USB OTG 内核与高层栈连接起来。
- 底层(底层 USB 驱动)为设备和 OTG 模式提供了通用 API:每种模式中的内核初始化及对传输流的控制
- 外设控制器驱动 (PCD)层提供了访问设备模式及此模式中处理中断程序的 API。
- OTG 控制器驱动 (OTG)层提供了访问 OTG 模式及此模式中处理中断程序的 API。
五、USB 驱动编程手册
5.1 设备初始化
使用 stm32fxxx_hal_pcd.c 文件中的下述函数初始化设备:
HAL_StatusTypeDef HAL_PCD_Init(PCD_HandleTypeDef *hpcd)
5.2 端点配置
USB 内核初始化之后,上层可能调用底层驱动,打开或关闭激活端点,开始传输数据。使用下列两个 API:
//ep_addr、 ep_mps 和 ep_type 分别为端点地址、最大数据传输和传输类型。
HAL_StatusTypeDef HAL_PCD_EP_Open(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint16_t ep_mps, uint8_t ep_type)
HAL_StatusTypeDef HAL_PCD_EP_Close(PCD_HandleTypeDef *hpcd, uint8_t ep_addr)
5.3 设备内核结构体
设备库中使用的主要结构体为设备句柄,其类型为 “USBD_HandleTypedef” ,在 usbd_def.h 中。
USB 全局设备结构体包含所有变量和结构体,用以实时保存与设备、控制传输状态机以及端点的状态相关的所有信息。
/* USB Device handle structure */
typedef struct _USBD_HandleTypeDef
{
uint8_t id;
uint32_t dev_config; // dev_config 保存着当前 USB 设备配置
uint32_t dev_default_config;
uint32_t dev_config_status;
USBD_SpeedTypeDef dev_speed;
USBD_EndpointTypeDef ep_in[16];
USBD_EndpointTypeDef ep_out[16];
uint32_t ep0_state; // ep0_state 控制着状态机
uint32_t ep0_data_len;
uint8_t dev_state; // dev_state 定义了连接、配置和上电状态
uint8_t dev_old_state;
uint8_t dev_address;
uint8_t dev_connection_status;
uint8_t dev_test_mode;
uint32_t dev_remote_wakeup;
USBD_SetupReqTypedef request;
USBD_DescriptorsTypeDef *pDesc;
USBD_ClassTypeDef *pClass;
void *pClassData;
void *pUserData;
void *pData;
} USBD_HandleTypeDef;
//在此结构体中, dev_state 定义了连接、配置和上电状态:
/* Device Status */
#define USBD_STATE_DEFAULT 0x01U
#define USBD_STATE_ADDRESSED 0x02U
#define USBD_STATE_CONFIGURED 0x03U
#define USBD_STATE_SUSPENDED 0x04U
/* EP0 State */
#define USBD_EP0_IDLE 0x00U
#define USBD_EP0_SETUP 0x01U
#define USBD_EP0_DATA_IN 0x02U
#define USBD_EP0_DATA_OUT 0x03U
#define USBD_EP0_STATUS_IN 0x04U
#define USBD_EP0_STATUS_OUT 0x05U
#define USBD_EP0_STALL 0x06U
#define USBD_EP_TYPE_CTRL 0x00U
#define USBD_EP_TYPE_ISOC 0x01U
#define USBD_EP_TYPE_BULK 0x02U
#define USBD_EP_TYPE_INTR 0x03U
5.4 USB 数据传输流程
PCD 层提供所需的所有 API,可启动及控制传输流,见下列函数:
HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pBuf, uint32_t len)
HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pBuf, uint32_t len)
HAL_StatusTypeDef HAL_PCD_EP_SetStall(PCD_HandleTypeDef *hpcd, uint8_t ep_addr)
HAL_StatusTypeDef HAL_PCD_EP_ClrStall(PCD_HandleTypeDef *hpcd, uint8_t ep_addr)
HAL_StatusTypeDef HAL_PCD_EP_Flush(PCD_HandleTypeDef *hpcd, uint8_t ep_addr)
PCD 层有一个函数必须被 USB 中断调用:
void HAL_PCD_IRQHandler(PCD_HandleTypeDef *hpcd)
stm32fxxx_hal_pcd.h 文件包含了库内核层处理 USB 事件时调用的函数原型。
六、USB 设备库概述
USB 设备库基于通用 USB 底层驱动开发,可在全速和高速模式中工作。
它实现了USB 2.0版本定义的USB设备库状态机。此模块功能由USB设备库固件包中"Core" 目录下的文件提供 。USB 类模块为类层,与协议规范兼容。
6.1 USB 设备库描述
6.1.1 USB 设备库流程
处理控制端点 0
USB 规范定义了四种传输类型:控制传输、中断传输、批量传输和同步传输。USB 主机通过控制端点向设备发送请求(在这种情况下,控制端点为端点 0)。 请求作为 SETUP 包发送到设备。这些请求可分为三类:标准、特定类、特定厂商。因为标准请求对所有 USB 设备都是通用的,所以库可收到和处理控制端点 0上的所有标准请求。
特定类和特定厂商请求的格式和意义并不对所有 USB 设备通用。
所有 SETUP 请求都由处于中断模式的状态机处理。 USB 正确传输的末尾会产生一个中断。库代码收到该中断。在中断处理程序中,触发端点被识别。如果事件为端点 0 上的设置,则保存所收到设置的参数且启动状态机。
非控制端点上的事务
特定类内核使用非控制端点,方法是通过数据 IN 和 OUT 阶段回调,调用一组函数发送或接收数据。
SETUP 包的数据结构
当一个新的 SETUP 包到达时, SETUP 包的所有八个字节都被复制到一个内部结构体USB_SETUP_REQ req,因此在处理期间,下一个 SETUP 包不会覆盖前一个包。此内部结 构体定义为:
typedef struct usb_setup_req
{
uint8_t bmRequest;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} USBD_SetupReqTypedef;
标准请求
下面 USB 规范表中的大多数请求都作为库中的标准请求处理。该表列出了所有标准请求及其库中的有效参数。此表中没有的请求则认为非标准请求。
注 : 在列状态中:D = 默认状态; A = 地址状态; C = 配置状态; All = 全部状态。EP: D0-D3 = 端点地址; D4-D6 = 保留为零; D7= 0: OUT 端点, 1:IN 端点。
非标准请求
所有非标准请求都通过回调函数传至特定类的代码。
SETUP 阶段
库传递所有非标准请求到特定类的代码,回调为pdev->pClass->Setup (pdev, req)函数。 非标准请求包括用户解释请求和无效请求。用户解释请求为特定类请求、特定厂商请求,或库认为无效但应用欲解释为有效的请求。
无效请求为非标准请求、非用户解释请求。因为 pdev->pClass->Setup (pdev, req) 在 SETUP 阶段后、数据阶段前调用,所以用户代码应负责在 pdev->pClass->Setup (pdev, req) 中解析 SETUP 包的内容(req)。若请求无效,则用户代码必须调用 USBD_CtlError(pdev , req),并返回 pdev->pClass->Setup (pdev, req) 的调用者对于用户解释请求,如果请求有数据阶段,用户代码应为后续数据阶段准备数据缓冲;否则用户代码执行请求,返回 pdev->pClass->Setup (pdev, req) 的调用者。
DATA 阶段
类层使用标准 USBD_CtlSendData 和 USBD_CtlPrepareRx 发送或接收数据,数据传输流由库内部处理,用户不需要将数据切分为 ep_size 大小的包。
状态阶段
当从 pdev->pClass->Setup (pdev, req) 回调返回之后,状态阶段由库处理。
如图7: USB设备库处理流程图中所示,USB编程只需要三个模块:USB库、USB类和主应用。
主应用执行用户定义程序,main.c、stm32fxx_it.c、usbd_conf.c 和 usbd_desc.c 及其头文件为用户开发自己的应用时的主要文件(应用强制),用户可根据应用对其修改 (类驱动) 。
若需初始化 USB HAL 驱动、 USB 设备库、和板级支持包 (BSP)并启动库,用户需调用这 三个 API ( USB 设备库初始化):
- USBD_Init ():此函数初始化设备栈,加载类驱动与描述符地址。设备描述符存储于 usbd_desc.c 和 usbd_desc.h (用于配置描述符类型)文件中。
- USBD_RegisterClass():此函数将类驱动链接到设备内核。
- USBD_Start():此函数允许用户开启 USB 设备内核。
6.1.2 USB 设备数据流程
因为 USB OTG 内核支持多包特性,所以在需要封装来管理控制端点上的多包特性时, USB 库 (USB 内核和 USB 类层) 通过 IO 请求层处理端点0 (EP0)上的数据处理,当其它端点被使用时,直接从 stm32fxxx_hal_pcd 层处理。下图显示了该数据流方案。
6.1.3 具有底层驱动的内核接口
如前面所述,底层接口层为 STM32Cube HAL 的链接层, USB 设备库用其与STM32Cube HAL 底层驱动接口。
底层接口实现了底层 API 函数,在 USB 事件后调用库内核回调函数。
在 STM32Cube 解决方案中,因为底层接口的一些部分依赖于板子和系统,所以底层接口的实现作为 USB 设备样例的一部分提供。
下表列出了底层 API 函数:
注 : 这些 API 由 USB 设备配置文件 (usbd_conf.c)提供。用户应在用户文件中实现,适配到USB 设备控制器驱动。
API | 说明 |
---|---|
USBD_LL_Init | 底层初始化 |
USBD_LL_DeInit | 底层取消初始化 |
USBD_LL_Start | 底层开始 |
USBH_LL_Stop | 底层停止 |
USBD_LL_OpenEP | 初始端点 |
USBD_LL_CloseEP | 关闭并对端点状态取消初始化 |
USBD_LL_FlushEP | 除掉底层驱动的一个端点。 |
USBD_LL_StallEP | 在底层驱动的端点上设置暂停状态。 |
USBD_LL_ClearStallEP | 在底层驱动的端点上清除暂停状态。 |
USBD_LL_IsStallEP | 返回暂停状态。 |
USBD_LL_SetUSBAddress | 为设备指定 USB 地址 |
USBD_LL_Transmit | 通过端点传输数据 |
USBD_LL_PrepareReceive | 为接收准备端点 |
USBD_LL_GetRxDataSize | 返回最后传输的包大小。 |
6.1.4 USB 设备库接口模型
USB 设备库是由通用可移植的 USB 设备内核和类模块构建而成。
下面是在 USB 事件后,底层接口调用的设备库回调函数。
回调函数 | 说明 |
---|---|
HAL_PCD_ConnectCallback | 设备连接回调 |
HAL_PCD_DataInStageCallback | 数据 IN 阶段回调 |
HAL_PCD_DataOutStageCallback | 数据 OUT 阶段回调 |
HAL_PCD_DisconnectCallback | 中断连接回调 |
HAL_PCD_ISOINIncompleteCallback | ISO IN 事务回调 |
HAL_PCD_ISOOUTIncompleteCallback | ISO OUT 事务回调 |
HAL_PCD_ResetCallback | USB 复位回调 |
HAL_PCD_ResumeCallback | USB 重新开始回调 |
HAL_PCD_SetupStageCallback | 设置阶段回调 |
HAL_PCD_SOFCallback | 帧回调起始 |
HAL_PCD_SuspendCallback | 挂起回调 |
6.1.5 配置 USB 设备固件库
可使用 usbd_conf.h 文件配置 USB 设备库。
usbd_conf.h 为特定的配置文件,用于定义一些全局参数及特定配置。usbd_conf.c 文件为接口文件,用于将上层库与 HAL 驱动和 BSP 驱动链接起来。
注 : 用户可从 STM32Cube 提供的usbd_conf.h 文件开始。此文件也可复制到应用目录,根据应用需求修改。
注 : 默认情况下,对于 USB 设备样例,库与用户消息并不会显示在 LCD上。
但是用户可实现自己的消息 (若要将库消息重定向到 LCD 屏幕上,需将lcd_log.c 驱动添加到应用源中)并可选择是否显示,这可通过符合应用要求的方式修改配置文件“usbd_conf.h” 中的 USBD_DEBUG_LEVEL 实现,该文件位于项目的包含目录下,修改方式为:
0:无日志 / 调试消息
1:使能日志消息
2:使能日志和调试消息
6.1.6 USB 控制功能
用户应用可受益于 USB 设备中包含的一些 USB 功能,例如:
设备复位 当设备从 USB 收到复位信号时,库会复位,并初始化软硬件上的应用。此函数为中断程序的一部分。
设备挂起 当设备检测到 USB 上的挂起条件,库会停止所有操作,并将系统置于挂起状态(前提条件为 usbd_conf.c 文件中使能了低功耗模式管理)。
设备重新开始 当设备检测到 USB 上的重新开始信号时,库会恢复 USB 内核时钟并将系统置于空闲状态 (前提条件为 usbd_conf.c 文件中使能了低功耗模式管理)。
6.2 USB 设备库功能
6.2.1 USB 设备 - 内核文件
Core 目录包含了 USB 设备库状态机,它由通用串行总线规范版本 2.0 定义。
文件 | 说明 |
---|---|
usbd_core (.c, .h) | 此文件包含了处理所有 USB 通信和状态机的函数。 |
usbd_req(.c,.h) | 此文件包含了 USB 规范的第 9 章列出的请求实现。 |
usbd_ctlreq(.c,.h) | 此文件处理 USB 事务结果。 |
usbd_conf_template(.c,.h) | 为底层接口文件的模板文件,用户应对其修改并包括在应用文件中 |
usbd_def(.c, .h) | 通用的库定义 |
6.2.2 USB 设备 - 类驱动文件
Class 目录包含与类实现有关的所有文件,满足了这些类中协议构建规范的要求。
6.2.3 usbd_core (.c,.h) 文件功能
6.2.4 usbd_ioreq (.c,.h) 文件功能
6.2.5 usbd_ctrlq (.c,.h) 文件功能
6.3 USB 设备类接口
在USB设备库初始化期间选择USB类,方法是选择响应的类回调结构体。类结构体如下定义:
6.3.1 USB 类回调结构体 (usbd_def.h)
typedef struct _Device_cb
{
uint8_t (*Init)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx);
uint8_t (*DeInit)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx);
/* Control Endpoints*/
uint8_t (*Setup)(struct _USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req);
uint8_t (*EP0_TxSent)(struct _USBD_HandleTypeDef *pdev);
uint8_t (*EP0_RxReady)(struct _USBD_HandleTypeDef *pdev);
/* Class Specific Endpoints*/
uint8_t (*DataIn)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
uint8_t (*DataOut)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
uint8_t (*SOF)(struct _USBD_HandleTypeDef *pdev);
uint8_t (*IsoINIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
uint8_t (*IsoOUTIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum);
uint8_t *(*GetHSConfigDescriptor)(uint16_t *length);
uint8_t *(*GetFSConfigDescriptor)(uint16_t *length);
uint8_t *(*GetOtherSpeedConfigDescriptor)(uint16_t *length);
uint8_t *(*GetDeviceQualifierDescriptor)(uint16_t *length);
#if (USBD_SUPPORT_USER_STRING_DESC == 1U)
uint8_t *(*GetUsrStrDescriptor)(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length);
#endif
} USBD_ClassTypeDef;
Init:当设备收到设置配置请求时,会调用此回调;在此函数中类接口使用的端点打开。
DeInit:当收到清除配置请求时,会调用此回调;此函数会关闭类接口使用的端点。
/* Control Endpoints*/
Setup:调用此回调可处理特定类设置请求。
EP0_TxSent:当发送状态完成时,会调用此回调。
EP0_RxSent:当接收状态完成时,会调用此回调。
/* Class Specific Endpoints*/
DataIn:调用此回调可执行非控制端点相关数据输入阶段的数据。
DataOut:调用此回调可执行非控制端点相关数据输出阶段的数据。
SOF:当收到 SOF 中断时调用此回调;可使用此回调将一些过程与帧开始同步。
IsoINIncomplete:当最后一个同步 IN 传输未完成时,调用此回调。
IsoOUTIncomplete:当最后一个同步 OUT 传输未完成时,调用此回调。
GetHSConfigDescriptor:此回调返回 HS USB 配置描述符。
GetFSConfigDescriptor:此回调返回 FS USB 配置描述符。
GetOtherSpeedConfigDescriptor:此回调返回高速模式中所用类的其它配置描述符。
GetDeviceQualifierDescriptor:此回调返回设备合格描述符
库还提供了描述符回调结构体,以允许用户在应用运行时管理设备和字符串描述符。描述符结构体如下定义:
6.3.1.1 USB 设备描述符结构体 (usbd_def.h)
/* USB Device descriptors structure */
typedef struct
{
uint8_t *(*GetDeviceDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *(*GetLangIDStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *(*GetManufacturerStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *(*GetProductStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *(*GetSerialStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *(*GetConfigurationStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t *(*GetInterfaceStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
#if (USBD_LPM_ENABLED == 1U)
uint8_t *(*GetBOSDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
#endif
} USBD_DescriptorsTypeDef;
GetDeviceDescriptor:此回调返回设备描述符。
GetLangIDStrDescriptor:此回调返回语言 ID 字符串描述符。
GetManufacturerStrDescriptor:此回调返回制造商字符串描述符。
GetProductStrDescriptor:此回调返回产品字符串描述符。
GetSerialStrDescriptor:此回调返回序列号字符串描述符。
GetConfigurationStrDescriptor:此回调返回配置字符串描述符。
GetInterfaceStrDescriptor:此回调返回接口字符串描述符。
注 : USB 设备样例内提供的 usbd_desc.c 文件实现了这些回调实体。
七、USB 设备库类模块
类模块包含了关于类实现的所有文件。它与这些类中构建协议的规范兼容。下表展示了MSC、 HID、 DFU、音频、 CDC 类的 USB 设备类文件。
7.1 USB 设备类文件表
7.1.1 HID 类
HID 类实现
此驱动实现了规范的下列方面:
- 启动接口子类
- 鼠标协议
- 使用页:通用桌面
- 使用:摇杆
- 收集:应用
HID 用户接口
输入报告仅通过中断进入管道发送 (HID 鼠标样例)。
必须通过控制管道或中断输出管道,由主机启动特性和输出报告 (自定义HID 样例)
USBD_HID_SendReport可被HID鼠标应用使用,来发送HID报告,在这个版本中,HID驱动仅处理 IN 传输。此函数的用法举例如下所示:
static __IO uint32_t counter=0;
HAL_IncTick();
/* check Joystick state every 10ms */
if (counter++ == 10)
{
GetPointerData(HID_Buffer);
/* send data though IN endpoint*/
if((HID_Buffer[1] != 0) || (HID_Buffer[2] != 0))
{
USBD_HID_SendReport(&USBD_Device, HID_Buffer, 4);
}
counter =0;
}
Toggle_Leds();
}
HID 类驱动 API
所有 HID 类驱动 API 都定义于 usbd_hid.c 中,并总结于下表中
函数 | 说明 |
---|---|
static uint8_t USBD_HID_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx) | 初始化 HID 接口,打开所用的端点。 |
static uint8_t USBD_HID_DeInit(USBD_HandleTypeDef *pdev,uint8_t cfgidx) | 解除初始化 HID 层,关闭所用的端点。 |
static uint8_t USBD_HID_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) | 处理 HID 特定请求。 |
uint8_t USBD_HID_SendReport(USBD_HandleTypeDef *pdev,uint8_t *report, uint16_t len) | 发送 HID 报告。 |
HID栈由调用USBD_HID_Init()来初始化,之后应用必须调用USBD_HID_SendReport()函数以发送 HID 报告。
下列 HID 特定请求通过端点 0 (控制)实现:
#define HID_REQ_SET_PROTOCOL 0x0B
#define HID_REQ_GET_PROTOCOL 0x03
#define HID_REQ_SET_IDLE 0x0A
#define HID_REQ_GET_IDLE 0x02
#define HID_REQ_SET_REPORT 0x09
#define HID_REQ_GET_REPORT 0x01
IN 端点地址和可发送的最大字节数由这些定义给出:
#define HID_EPIN_ADDR 0x81
#define HID_EPIN_SIZE 0x04
7.1.2 大容量存储类 (略)
7.1.3 设备固件升级(DFU)类 (略)
7.1.4 音频类
此驱动管理音频类,符合 “ 音频设备 USB 设备类定义 V1.0 1998-3-18”。
此驱动实现了规范的下列方面:
- 设备描述符管理
- 配置描述符管理
- 标准 AC 接口描述符管理
- 1 个音频流接口 (单通道、 PCM、立体声模式)
- 1 个音频流端点
- 1 个音频终端输入 (1 个通道)
- 音频特定类 AC 接口
- 音频特定类 AS 接口
- 音频控制请求:仅支持 SET_CUR 和 GET_CUR 请求 (静音)
- 音频特性单元 (限为静音控制)
- 音频同步类型:异步
- 单固定音频采样率 (可在 usbd_conf.h 文件中配置)
注 : 音频类基于 USB 规范 1.0,因此仅支持低速模式和全速模式,不支持高速传输。请参考 “ 音频设备 USB 设备类定义 V1.0 1998-3-18” 以获取更详细信息。
可针对特定用户应用增加或修改这些方面。
此驱动没有实现规范的下述方面 (但有可能修改驱动以管理这些特性):
- 音频控制端点管理
- SET_CUR 和 GET_CUR 以外的音频控制请求
- 音频控制请求的抽象层 (仅管理静音功能)
- 音频同步类型:自适应
- 音频压缩模块和接口
- MIDI 接口和模块
- 混合 / 选择 / 处理 / 扩展单元 (所列单元限为静音控制)
- 其它任何特定应用模块
- 复合及可变的音频采样率
- 音频输出流端点 / 接口 (麦克风)
音频类实现
音频传输基于同步端点事务。 音频控制请求还通过控制端点 (端点 0)管理。
在每一帧传输的音频数据包必须在此帧时间之内 (下一帧之前)处理掉。音频质量取决于数据传输和数据处理之间的同步情况。此驱动依赖所交付 I2S时钟的精度,实现简单的同步机制。在每帧开始时,驱动会检查是否正确执行了前一帧的处理,若仍在进行则将其停止。 为防止任何数据覆盖,主要使用了两种保护方式:
- 在 USB 缓冲和输出设备寄存器 (I2S)之间使用 DMA 进行数据传输。
- 使用多缓冲存储从 USB 接收的数据。
基于此机制,如果时钟精度或处理速率不够高,则会导致较差的音频质量。
此机制可通过实现更灵活的音频流控制来加强,如 USB 反馈模式、动态音频时钟纠正,或使用 SOF 事件生成 / 控制音频时钟。
驱动还支持基本音频控制请求。为简化驱动,仅实现了两个请求。然而,仅需稍微修改音频内核驱动即可支持其他请求。
音频内核文件 usbd_audio (.c, .h)
usbd_audio (.c, .h)此驱动为音频内核。它管理音频数据传输并控制请求。它不直接处理音频硬件 (由底层驱动管理)。
函数 | 说明 |
---|---|
static uint8_t USBD_AUDIO_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx); | 初始化音频接口。 |
static uint8_t USBD_AUDIO_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx); | 解除初始化音频接口。 |
static uint8_t USBD_AUDIO_Setup(USBD_HandleTypeDef *pdev,USBD_SetupReqTypedef *req); | 处理音频控制请求解析。 |
static uint8_t *USBD_AUDIO_GetCfgDesc(uint16_t *length); | 获取配置描述符 |
static uint8_t *USBD_AUDIO_GetDeviceQualifierDesc(uint16_t *length); | 获取设备限定符描述符 |
static uint8_t USBD_AUDIO_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum); | 处理音频输入数据阶段。 |
static uint8_t USBD_AUDIO_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum); | 处理音频输出数据阶段。 |
static uint8_t USBD_AUDIO_EP0_RxReady(USBD_HandleTypeDef *pdev); | 处理 EP0 Rx 事件 |
static uint8_t USBD_AUDIO_EP0_TxReady(USBD_HandleTypeDef *pdev); | 处理 EP0 Tx 事件 |
static uint8_t USBD_AUDIO_SOF(USBD_HandleTypeDef *pdev); | 处理 SOF 事件 (数据缓冲更新和同步)。 |
static uint8_t USBD_AUDIO_IsoINIncomplete(USBD_HandleTypeDef *pdev, uint8_t epnum); | 处理 IsoINIncomplete 数据输入情况。 |
static uint8_t USBD_AUDIO_IsoOutIncomplete(USBD_HandleTypeDef *pdev, uint8_t epnum); | 处理 IsoINIncomplete 数据输出情况。 |
static void AUDIO_REQ_GetCurrent(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req); | 处理 GET_CUR 音频控制请求。 |
static void AUDIO_REQ_SetCurrent(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req); | 处理 SET_CUR 音频控制请求。 |
底层硬件接口通过它们相应的驱动结构体管理:
typedef struct
{
int8_t (*Init)(uint32_t AudioFreq, uint32_t Volume, uint32_t options);
int8_t (*DeInit)(uint32_t options);
int8_t (*AudioCmd)(uint8_t *pbuf, uint32_t size, uint8_t cmd);
int8_t (*VolumeCtl)(uint8_t vol);
int8_t (*MuteCtl)(uint8_t cmd);
int8_t (*PeriodicTC)(uint8_t cmd);
int8_t (*GetState)(void);
} USBD_AUDIO_ItfTypeDef;
每个音频硬件接口驱动都应该提供一个类型为USBD_AUDIO_ItfTypeDef的结构体指针。(下边的章节会写该结构体所指向的函数和变量)。如果给定的存储器接口不支持某功能,则相应的字段置为 NULL 值。
usbd_audio_if (.c, .h)
usbd_audio_if (.c, .h)此驱动管理底层音频硬件。 usbd_audio_if.c/.h 驱动管理音频输出接口 (从 USB 到音频扬声器 / 耳机)。用户可调用底层编解码器驱动(即 stm324xg_eval_audio.c/.h)以进行基本的音频操作 (播放 / 暂停 / 音量控制…)。
此驱动提供了结构体指针:
/** AUDIO_IF Interface callback. */
extern USBD_AUDIO_ItfTypeDef USBD_AUDIO_fops_FS;
usbd_audio_if (.c,.h)文件中函数说明。
函数 | 说明 |
---|---|
static int8_t AUDIO_Init_FS(uint32_t AudioFreq, uint32_t Volume, uint32_t options); | 初始化音频接口。 |
static int8_t AUDIO_DeInit_FS(uint32_t options); | 解除初始化音频接口,释放所用的资源。 |
static int8_t AUDIO_AudioCmd_FS(uint8_t* pbuf, uint32_t size, uint8_t cmd); | 处理音频播放器指令 (播放、暂停 …) |
static int8_t AUDIO_VolumeCtl_FS(uint8_t vol); | 处理音频播放器音量控制。 |
static int8_t AUDIO_MuteCtl_FS(uint8_t cmd); | 处理音频播放器静音状态。 |
static int8_t AUDIO_PeriodicTC_FS(uint8_t cmd); | 处理当前数据包传输的结束(当前版本的驱动程序不需要)。 |
static int8_t AUDIO_GetState_FS(void); | 返回驱动音频播放器的当前状态 (正在播放 / 已暂停 / 错误 …)。 |
通过下列状态列表来获得当前音频播放器的状态:
怎样使用此驱动:
此驱动使用了硬件驱动的抽象层(即 HW 编解码器、I2S 接口、I2C 控制接口 …)。此抽象通过底层 (即 usbd_audio_if.c)执行,您可根据您的应用以及相应的硬件对其修改。
若要使用此驱动:
通过文件 usbd_conf.h,您可配置:
- 音频采样率 (定义 USBD_AUDIO_FREQ)
在启动时调用函数 USBD_AUDIO_Init(),配置所有必要的固件和硬件部件(特定应用的硬件配置函数也由此函数调用)硬件部件由底层接口 (即usbd_audio_if.c)管理,可由用户根据 应用需要修改。
整个传输由下述函数管理 (用户不需为输出传输调用任何函数):
- usbd_audio_DataIn()和usbd_audio_DataOut()使用收到的或发送的数据更新音频缓冲。对于输出传输,当收到数据时,它们会被直接复制到音频缓冲中,写缓冲 (wr_ptr)增加。
音频控制请求由函数 USBD_AUDIO_Setup() 和USBD_AUDIO_EP0_RxReady() 管理。这些函数会将音频控制请求路由至底层 (即 usbd_audio_if.c) 。在当前版本中,仅管理了SET_CUR 和 GET_CUR 请求,仅用于静音控制。
音频的已知限制
如果配置了低音频采样率(将USBD_AUDIO_FREQ定义为24 kHz以下),则在暂停/重新开始 / 停止操作时可能导致噪声问题。这是由于在停止 I2S 时钟和发送静音指令到外部编解码器之间的软件时序调节。
支持的音频采样率为:96 kHz到24 kHz(此驱动不支持非整数kHz值,例如11.025 kHz、22.05 kHz 或 44.1 kHz)。对于 1000 Hz 的整数倍数频率,主机会在每帧 (1 ms)发送 整数个字节。当频率不是 1000Hz 的整数倍时,主机会在每帧发送非整数个字节。实际上,这是通过发送不同大小的帧来管理的 (即对于 22.05 kHz,主机将发送 19 帧的 22 字节和一帧的 23 字节)。音频内核不会管理此大小差别,多余的字节会一直被忽略。建议设置高采样率和标准采样率,以得到最好的音频质量 (即 96 kHz 或 48 kHz)。请注意,最大允许的音频频率为 96 kHz(此限制的原因是评估板上使用的编解码器。STM32 I2S 单元可达到 192 kHz)。
7.1.5 通信设备类 (CDC) (略)
7.1.6 添加自定义类 (略)
7.1.7 库大小优化
在本节中,我们回顾一些基本技巧,涉及怎样优化 USB 设备库之上开发的应用大小。
缩小 USB 样例是一个重要的目标,尤其对于具有较少 Flash/RAM 内存的 STM32 产品 (如STM32 L0 和 F0)来说尤其重要。
降低堆和栈大小设置 (在连接 (linker)文件中)
栈为程序存储的内存区,例如:
- 本地变量
- 返回地址
- 函数参数
- 编译器临时量
- 中断上下文
如果您的连接器配置保留了大量的堆和栈,而您的应用并不需要,您可以决定其合适的大小。
尽可能使用局部变量,而不用全局变量
如果一个变量仅在一个函数中使用,那么应在函数内将其声明为局部变量。
常量应在闪存中分配
建议将永不变化的所有常量全局变量分配至只读区。例如,使用 C 关键字 “const” 将 USB 描述符声明为常量。
例如:
使用静态内存分配,而不是 malloc
USB 设备库为类处理结构体使用动态内存分配以支持多实例 (在双核工作情况下),这意味着我们可以将同一 USB 类用于 USB 的两个实例 (HS 和FS)。
使用动态分配的第二个原因是当 USB 不再使用时,可释放内存。然而,动态内存分配会增加一些空间上的开销,主要是 ROM 内存。因此,对于低内存 STM32设备,当不需要多实例支持时,建议使用静态分配。在这种情况下,需要声明静态缓冲,大小为类处理结构体的大小。
下面是一个实现样例:
- 在 usbd_conf.h 文件中,定义内存静态分配和程序;
USBD_static_malloc() 和 USBD_static_free()
#define MAX_STATIC_ALLOC_SIZE 4 /* HID */ 类类类类类类
#define USBD_malloc (uint32_t *)USBD_static_malloc
#define USBD_free USBD_static_free
- 在 usbd_conf.c 文件中如下实现:
/**
* @brief Static single allocation.
* @param size: Size of allocated memory
* @retval None
*/
void *USBD_static_malloc(uint32_t size)
{
/* static uint8_t mem[sizeof(USBD_AUDIO_HandleTypeDef)]; */
/* USER CODE BEGIN 4 */
/**
* To compute the request size you must use the formula:
AUDIO_OUT_PACKET = (USBD_AUDIO_FREQ * 2 * 2) /1000)
AUDIO_TOTAL_BUF_SIZE = AUDIO_OUT_PACKET * AUDIO_OUT_PACKET_NUM with
Number of sub-packets in the audio transfer buffer. You can modify this value but always make sure
that it is an even number and higher than 3
AUDIO_OUT_PACKET_NUM = 80
*/
static uint8_t mem[512];
/* USER CODE END 4 */
return mem;
}
/**
* @brief Dummy memory free
* @param p: Pointer to allocated memory address
* @retval None
*/
void USBD_static_free(void *p)
{
}
八、常见问题
1. 怎样在运行时修改设备和字符串描述符 ?
在 usbd_desc.c 文件中,可使用 Get Descriptor 回调修改设备和字符串相关的描述符。 应用可使用 switch case 语句,返回应用索引相关的正确描述符缓冲。
2. 大容量存储类怎样支持超过一个逻辑单元 (LUN) ? (略)
3. 端点地址在哪里定义 ?
端点地址在类驱动的头文件中定义。例如,对于 MSC 演示,IN/OUT 端点地址如下定义在 usbd_msc.h 文件中:
#define MSC_EPIN_ADDR 0x81 For Endpoint 1 IN
#define MSC_EPOUT_ADDR 0x01 For Endpoint 1 OUT
4. USB 设备库可配置为在高速或全速模式运行吗?
是的,库可处理 USB OTG HS 和 USB OTG FS 内核,如果 USB OTG FS 内核仅能工 作于全速模式, USB OTG HS 可工作于高速或全速模式。若要选择合适的 USB 内核,用户必须在编译预处理器内增加下列宏定义 (在样例提供的预配置项目中已经完成):
- "USE_USB_HS" 当使用 USB 高速 (HS)内核时
- "USE_USB_FS" 当使用 USB 全速 (FS)内核时
- "USE_USB_HS" 和 "USE_USB_HS_IN_FS" 当在 FS 模式使用 USB 高速(HS)内核时
5. 怎样在 USB 设备类驱动中更改所用的端点?
若要更改端点或增加一个新端点,请:
a) 使用 USBD_LL_OpenEP() 执行端点初始化。
b) 对于在 USBD_LL_Init() 函数中使用这些 API 的 usb_conf.c 文件,配置新定义端点的 TX 或 Rx FIFO 大小。
- 对于 STM32F2 和 STM32F4 系列 (FS 和 HS 内核):
HAL_PCD_SetRxFiFo();
HAL_PCD_SetTxFiFo();
Rx 和 Tx FIFO 的总大小应该小于所用内核的总 FIFO 大小(对于 USB OTG FS 内核,为 320 x 32 比特;对于 USB OTG HS 内核,为 1024 x 32 比特)。
- 对于 STM32F0、 STM32L0、 STM32F1 和 STM32F3 系列 (仅 FS 内核):
HAL_PCD_PMA_Config();
6. USB 设备库与实时操作系统 (RTOS)兼容吗 ?
是的,USB 设备库可与 RTOS 共用使用,CMSIS RTOS 封装的作用是对 OS 内核抽象。