STM32 的 USB 驱动代码通常基于 STM32 HAL 库(硬件抽象层)或 LL 库(低层库)实现,其核心是对 USB 外设的寄存器操作、USB 协议解析和设备类功能的封装。从架构上可分为硬件抽象层、USB 核心层和设备类驱动层三个主要部分,下面结合典型代码结构进行分析:
一、整体架构与核心文件
STM32 的 USB 驱动代码通常包含以下关键文件(以 STM32F4 系列 USB FS 为例):
stm32f4xx_hal_usb.h/c:HAL 层核心文件,封装 USB 外设的初始化、中断处理、数据收发等硬件操作。
usb_core.h/c:USB 协议核心层,处理 USB 枚举、端点管理、 Setup 包解析等协议相关逻辑。
usb_xxx.c/h:设备类驱动(如usb_cdc.c for CDC 串口、usb_hid.c for HID 设备),实现特定设备类的功能(如数据透传、报告传输)。
usb_desc.h/c:定义 USB 设备描述符(设备描述符、配置描述符、接口描述符等),用于主机枚举识别设备。
二、核心模块解析
1. 硬件抽象层(HAL 层)
HAL 层直接操作 USB 外设寄存器,提供最基础的硬件控制接口,核心功能包括:
USB 外设初始化:
通过HAL_USB_Init(USB_HandleTypeDef *husb)函数完成,主要配置:
时钟:USB 需要 48MHz 时钟(通常由 PLL 分频或 HSI48 提供);
GPIO:配置 USB_DP/USB_DM 引脚为复用功能;
中断:使能 USB 全局中断,配置中断优先级;
外设参数:设置 USB 模式(设备模式 / 主机模式)、端点数量等。
示例代码片段:
c
运行
USB_HandleTypeDef husb;
void USB_Init(void) {
husb.Instance = USB_OTG_FS; // 选择USB外设(FS/HS)
husb.Init.Mode = USB_OTG_DEVICE_MODE; // 设备模式
husb.Init.low_power_enable = DISABLE;
// 其他配置(如中断优先级、PHY类型等)
if (HAL_USB_Init(&husb) != HAL_OK) {
Error_Handler();
}
}
中断处理:
USB 外设产生的所有事件(如总线复位、Setup 包接收、数据传输完成)都会触发中断,由HAL_USB_IRQHandler(USB_HandleTypeDef *husb)统一处理。该函数会根据中断标志位(如USB_OTG_GINTSTS寄存器)识别事件类型,并调用对应的回调函数(如HAL_USB_ResetCallback、HAL_USB_SetupStageCallback)。
示例中断处理流程:
void OTG_FS_IRQHandler(void) {
HAL_USB_IRQHandler(&husb); // 调用HAL层中断处理函数
}
// 总线复位回调(枚举第一步)
void HAL_USB_ResetCallback(USB_HandleTypeDef *husb) {
// 复位后初始化端点0(控制端点)
USB_Endpoint0_Init(husb);
}
2. USB 核心层(协议处理)
核心层基于 HAL 层提供的硬件接口,实现 USB 协议的核心逻辑,重点是枚举过程和端点管理:
枚举过程:
枚举是 USB 设备插入主机后,主机识别设备的过程,核心是通过控制端点(端点 0) 交换描述符。核心层需处理主机发送的 Setup 包(包含请求类型、请求码等),并返回对应的数据(如设备描述符、配置描述符)。
Setup 包解析示例:
void USB_ProcessSetupPacket(USB_HandleTypeDef *husb, uint8_t *setup_buf) {
USB_SetupTypeDef *setup = (USB_SetupTypeDef*)setup_buf;
switch (setup->bmRequestType & USB_REQ_TYPE_MASK) {
case USB_REQ_TYPE_STANDARD: // 标准请求(如获取描述符)
USB_HandleStandardRequest(husb, setup);
break;
case USB_REQ_TYPE_CLASS: // 类请求(如CDC的设置波特率)
USB_HandleClassRequest(husb, setup);
break;
// 其他请求类型...
}
}
端点管理:
STM32 USB 外设通常包含多个端点(如 USB FS 有 4 个双向端点),每个端点对应特定的传输类型(控制、批量、中断、同步)。核心层需管理端点的缓冲区(通过USB_OTG_EP寄存器配置)、监控传输完成状态,并提供数据收发接口(如USB_EP_Transmit、USB_EP_Receive)。
端点配置示例(批量输出端点):
void USB_Endpoint1_Init(USB_HandleTypeDef *husb) {
USB_OTG_EPTypeDef ep;
ep.Num = 1; // 端点号1
ep.IsIN = 0; // 输出端点(主机到设备)
ep.Type = USB_OTG_EP_BULK; // 批量传输
ep.MaxPacketSize = 64; // 最大包大小64字节
HAL_USB_EP_Init(husb, &ep); // 初始化端点
}
3. 设备类驱动层
USB 协议定义了多种设备类(如 CDC、HID、MSC 等),类驱动层基于核心层实现特定类的功能,以CDC(虚拟串口) 为例:
类描述符:除标准描述符外,CDC 还需定义类特定描述符(如CDC_DescriptorType、CDC_FunctionalDescriptor),用于主机识别串口功能。
类请求处理:处理主机发送的类特定请求(如设置波特率、流量控制),例如:
void USB_CDC_HandleClassRequest(USB_HandleTypeDef *husb, USB_SetupTypeDef *setup) {
switch (setup->bRequest) {
case CDC_SET_LINE_CODING: // 设置波特率
USB_CDC_ReceiveLineCoding(husb, setup->wLength); // 接收主机发送的波特率参数
break;
case CDC_SEND_BREAK: // 发送中断信号
USB_CDC_SendBreak(husb);
break;
// 其他类请求...
}
}
数据收发:通过批量端点实现数据透传,例如:
// 发送数据(设备到主机)
uint8_t USB_CDC_Transmit(USB_HandleTypeDef *husb, uint8_t *data, uint16_t len) {
return HAL_USB_EP_Transmit(husb, CDC_EP_IN, data, len); // 通过IN端点发送
}
// 接收数据(主机到设备)
void USB_CDC_ReceiveCallback(USB_HandleTypeDef *husb) {
uint16_t len = HAL_USB_EP_GetRxCount(husb, CDC_EP_OUT); // 获取接收长度
HAL_USB_EP_Receive(husb, CDC_EP_OUT, rx_buf, len); // 读取数据到缓冲区
}
三、关键注意事项
时钟配置:USB 需要严格的 48MHz 时钟(误差≤0.25%),否则会导致枚举失败,需确保时钟源(HSI48 或 PLL)配置正确。
端点缓冲区:每个端点的缓冲区大小需匹配描述符中定义的wMaxPacketSize,过大或过小会导致数据传输异常。
中断优先级:USB 中断优先级需高于应用层中断,避免中断延迟导致的数据包丢失。
枚举失败排查:常见原因包括描述符格式错误、端点配置不匹配、时钟不稳定等,可通过 USB 分析仪抓取枚举过程的数据包定位问题。
总结
STM32 的 USB 驱动代码是硬件操作、协议解析和设备类功能的分层封装:HAL 层负责硬件控制,核心层处理 USB 协议(枚举、端点管理),类驱动层实现特定设备的功能。理解各层的交互逻辑(如中断触发→核心层解析→类驱动处理)是掌握 USB 驱动的关键。
详细介绍枚举和数据通信过程
在 USB 协议中,枚举过程是设备插入主机后建立通信的基础,而数据通信则是枚举完成后设备与主机通过端点传输数据的过程。下面结合 STM32 USB 驱动代码,详细解析这两个核心过程。
一、USB 枚举过程(Device Enumeration)
枚举是主机识别设备、分配地址并配置设备的过程,核心是通过控制端点(Endpoint 0) 交换信息。整个过程由主机主导,设备被动响应,分为以下 8 个关键步骤:
步骤 1:设备插入与总线复位(Bus Reset)
触发条件:设备插入 USB 接口后,主机检测到 DP/DM 线上的电平变化(全速设备会将 DP 线拉高,低速设备拉低),识别到有新设备接入。
主机动作:主机向设备发送总线复位信号(持续至少 10ms 的低电平),强制设备回到默认状态。
设备响应(STM32 代码):
设备 USB 外设检测到复位信号后触发中断,HAL 层中断处理函数HAL_USB_IRQHandler调用复位回调HAL_USB_ResetCallback。此时设备会:
复位所有端点(仅保留端点 0 有效);
回到默认地址(0x00,所有新设备的初始地址);
初始化端点 0 的控制传输缓冲区(通常 64 字节)。
代码示例:
void HAL_USB_ResetCallback(USB_HandleTypeDef *husb) {
// 复位端点配置,仅保留端点0
USB_ResetEndpoints(husb);
// 初始化端点0(控制端点,双向,最大包长64字节)
USB_Endpoint0_Init(husb, 64);
// 重置设备地址为默认地址0x00
husb->dev_addr = 0;
}
步骤 2:主机获取设备描述符(Get Device Descriptor)
主机动作:主机通过默认地址(0x00)向设备端点 0 发送Setup 包,请求获取设备描述符(bRequest = USB_REQ_GET_DESCRIPTOR,wValue = 0x0100表示设备描述符)。
Setup 包结构(8 字节):
设备响应(STM32 代码):
设备通过HAL_USB_SetupStageCallback回调接收 Setup 包,核心层解析后调用USB_HandleStandardRequest处理标准请求,从usb_desc.c中读取设备描述符并通过端点 0 返回给主机。
设备描述符示例(usb_desc.c中定义):
const uint8_t Device_Descriptor[18] = {
0x12, // bLength:描述符长度18字节
0x01, // bDescriptorType:设备描述符类型(0x01)
0x0200, // bcdUSB:支持USB 2.0
0x00, // bDeviceClass:设备类(0表示由接口描述符定义)
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
0x40, // bMaxPacketSize0:端点0最大包长64字节
0x1234, // idVendor:厂商ID
0x5678, // idProduct:产品ID
0x0100, // bcdDevice:设备版本
0x01, // iManufacturer:厂商字符串索引
0x02, // iProduct:产品字符串索引
0x03, // iSerialNumber:序列号索引
0x01 // bNumConfigurations:支持的配置数
};
步骤 3:主机分配设备地址(Set Address)
主机动作:主机根据设备描述符确认设备合法性后,发送设置地址请求(bRequest = USB_REQ_SET_ADDRESS),为设备分配唯一地址(如 0x05)。
Setup 包关键字段:wValue = 0x0005(新地址)。
设备响应(STM32 代码):
设备解析请求后,在USB_HandleStandardRequest中保存新地址,并通过状态阶段返回确认。注意:新地址在本次控制传输完成后生效(即状态阶段结束后)。
代码示例:
case USB_REQ_SET_ADDRESS:
// 保存新地址(wValue低8位)
husb->dev_addr = (uint8_t)(setup->wValue & 0xFF);
// 发送状态包确认(控制传输的状态阶段)
USB_CtlSendStatus(husb);
break;
步骤 4:主机再次获取完整设备描述符
主机动作:使用新分配的地址,再次请求完整的设备描述符(确认地址生效,并获取更详细信息)。
设备响应:设备以新地址响应,返回完整的设备描述符。
步骤 5:主机获取配置描述符(Get Configuration Descriptor)
主机动作:发送请求获取配置描述符(wValue = 0x0200),包含配置描述符、接口描述符、端点描述符等组合信息。
设备响应:设备返回配置描述符集合,其中包含:
配置描述符:总长度、接口数量、功耗等;
接口描述符:接口类(如 CDC 类为 0x02)、端点数量等;
端点描述符:端点号、传输类型(批量 / 中断等)、最大包长等。
例如,CDC 设备的端点描述符(usb_desc.c):
// 批量IN端点(设备→主机,端点1)
const uint8_t EP1_IN_Descriptor[7] = {
0x07, // bLength:7字节
0x05, // bDescriptorType:端点描述符
0x81, // bEndpointAddress:端点1,IN方向(0x80表示IN)
0x02, // bmAttributes:批量传输(0x02)
0x4000, // wMaxPacketSize:64字节(低字节有效)
0x00 // bInterval:批量传输无需轮询间隔
};
步骤 6:主机获取字符串描述符(可选)
主机动作:若设备描述符中字符串索引非 0,主机请求字符串描述符(如厂商名称、产品名称),wValue指定字符串索引和语言 ID(如 0x0409 表示英文)。
设备响应:设备返回 UTF-16 编码的字符串(如 "STM32 USB CDC")。
步骤 7:主机设置配置(Set Configuration)
主机动作:发送设置配置请求(bRequest = USB_REQ_SET_CONFIGURATION),指定要激活的配置值(如wValue = 0x01表示第一个配置)。
设备响应:设备激活配置,初始化配置中定义的所有端点(如 CDC 的批量 IN/OUT 端点、中断端点),并返回确认。
STM32 代码中,配置激活后会调用类驱动的初始化函数:
void USB_HandleSetConfiguration(USB_HandleTypeDef *husb, uint8_t config) {
if (config == 1) { // 激活配置1
// 初始化CDC类的端点(批量IN/OUT、中断IN)
USB_CDC_InitEndpoints(husb);
// 标记配置完成
husb->configured = 1;
}
}
步骤 8:枚举完成
设备进入配置状态,所有端点按配置描述符生效,可开始数据通信。
二、数据通信过程(Data Transfer)
枚举完成后,设备与主机通过端点(Endpoint) 进行数据传输,USB 定义了 4 种传输类型,对应不同的应用场景:
1. 控制传输(Control Transfer)
用途:用于配置设备、获取状态等(如枚举过程中的请求),仅通过端点 0 实现。
传输阶段:Setup 阶段(8 字节请求)→ Data 阶段(可选,传输数据)→ Status 阶段(确认完成)。
STM32 处理:通过HAL_USB_DataInStageCallback(设备→主机)和HAL_USB_DataOutStageCallback(主机→设备)回调处理数据阶段,状态阶段由USB_CtlSendStatus或USB_CtlReceiveStatus完成。
2. 批量传输(Bulk Transfer)
用途:传输大量数据(如文件、串口数据),无固定时序但保证数据正确性(出错重传),典型应用:U 盘、CDC 虚拟串口。
STM32 实现:
发送(设备→主机):通过HAL_USB_EP_Transmit函数向 IN 端点写入数据,传输完成后触发HAL_USB_EP_TxCallback。
接收(主机→设备):通过HAL_USB_EP_Receive函数从 OUT 端点读取数据,接收完成后触发HAL_USB_EP_RxCallback。
代码示例(CDC 设备发送数据):
// 发送数据到主机(通过批量IN端点1)
uint8_t CDC_Transmit(uint8_t *data, uint16_t len) {
// 检查端点状态,空闲时发送
if (husb->ep[1].is_in_busy == 0) {
return HAL_USB_EP_Transmit(&husb, 1, data, len);
}
return HAL_BUSY;
}
// 发送完成回调
void HAL_USB_EP_TxCallback(USB_HandleTypeDef *husb, uint8_t epnum) {
if (epnum == 1) { // 端点1发送完成
// 标记发送完成,可准备下一次发送
cdc_tx_complete = 1;
}
}
3. 中断传输(Interrupt Transfer)
用途:传输小批量、低延迟数据(如鼠标 / 键盘事件、设备状态通知),主机按固定间隔(bInterval)轮询设备。
STM32 实现:
设备通过中断 IN 端点主动上报数据,主机按bInterval(如 10ms)查询。例如,CDC 设备通过中断端点通知主机串口状态(如是否有数据待发):
// 向主机发送CDC状态通知(通过中断IN端点2)
void CDC_SendStatus(uint8_t status) {
uint8_t buf[1] = {status};
HAL_USB_EP_Transmit(&husb, 2, buf, 1); // 端点2为中断IN端点
}
4. 同步传输(Isochronous Transfer)
用途:传输实时数据(如音频、视频),保证时序但不保证正确性(出错不重传),STM32 较少使用(需特定外设支持)。
三、关键交互逻辑(STM32 驱动视角)
中断驱动:所有 USB 事件(复位、Setup 包、数据传输完成)均通过中断触发,核心处理函数为HAL_USB_IRQHandler,再分发到各阶段回调(如SetupStageCallback、DataInStageCallback)。
端点状态管理:STM32 通过USB_OTG_EP寄存器组管理端点状态(空闲 / 忙碌),每次传输前需检查端点是否空闲,避免数据覆盖。
类驱动适配:不同设备类(CDC/HID/MSC)的数据通信逻辑封装在类驱动中,例如:
HID 设备通过中断端点发送报告(HID_SendReport);
MSC 设备通过批量端点传输 SCSI 命令和数据。
总结
枚举过程是设备与主机建立 “身份认知” 的过程,核心是通过端点 0 交换描述符和配置信息,最终设备获得唯一地址并激活配置。
数据通信是枚举后的实际数据传输,依赖枚举过程中配置的端点,按传输类型(控制 / 批量 / 中断 / 同步)通过不同端点完成,由 STM32 的中断回调和端点操作函数驱动。
理解这两个过程的交互逻辑,是调试 USB 设备枚举失败、数据传输异常等问题的关键。
————————————————
版权声明:本文为CSDN博主「飞吧~皮卡丘」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shanstellar/article/details/150775719
|
|