一、USB 基础与 STM32 硬件架构
1.1 USB 核心概念
USB (Universal Serial Bus) 是一种主从架构的串行总线标准:
- 主机 (Host):控制总线,发起所有传输
- 设备 (Device):响应主机请求
- OTG(On-The-Go):设备可在主机和设备模式间切换
- 传输速度:STM32 支持 USB 2.0 全速 (FS, 12Mbps) 和高速 (HS, 480Mbps)
- 端点 (Endpoint) :USB 通讯的基本单元,每个设备最多 16 个 IN 端点和 16 个 OUT 端点
- 端点 0:控制传输专用,双向,所有 USB 设备必须具备
- 端点 1~15:批量、中断、同步传输,单向
1.2 STM32 USB 外设架构
STM32 的 USB OTG 外设主要由以下部分组成:
- OTG 核心:处理 USB 协议底层逻辑
- 设备控制器 (PCD):设备模式下的控制逻辑
- 主机控制器 (HCD):主机模式下的控制逻辑
- 端点 FIFO:每个端点有独立的 FIFO 缓冲区
- DMA 控制器:用于高速数据传输
- 中断控制器:处理 USB 相关中断
二、USB 传输类型详解
| 传输类型 | 用途 | 特点 | 典型应用 |
|---|---|---|---|
| 控制传输 | 设备枚举、配置、命令控制 | 双向,可靠,低带宽 | 设备识别、驱动安装 |
| 批量传输 | 大量非实时数据 | 单向,可靠,带宽可变 | U 盘、打印机 |
| 中断传输 | 少量实时数据 | 单向,低延迟,固定周期 | 键盘、鼠标 |
| 同步传输 | 实时数据流 | 单向,不可靠,固定带宽 | 音频、视频 |
三、STM32 USB 关键寄存器详解
3.1 全局控制与状态寄存器
- USB_OTG_GOTGCTL:OTG 控制寄存器,控制主从模式切换
- USB_OTG_GINTSTS:全局中断状态寄存器,指示所有中断源
- USB_OTG_GINTMSK:全局中断屏蔽寄存器,使能 / 禁用特定中断
- USB_OTG_GRXSTSP:接收状态弹出寄存器,读取 OUT 端点接收状态
3.2 设备模式专用寄存器
- USB_OTG_DCFG:设备配置寄存器,设置设备速度、端点 0 最大包长
- USB_OTG_DCTL:设备控制寄存器,控制设备复位、远程唤醒
- USB_OTG_DSTS:设备状态寄存器,指示设备当前状态
- USB_OTG_DAINT:设备所有端点中断寄存器,指示哪个端点产生中断
3.3 端点控制寄存器
- USB_OTG_DIEPCTLx:设备 IN 端点 x 控制寄存器,配置端点类型、最大包长
- USB_OTG_DOEPCTLx:设备 OUT 端点 x 控制寄存器
- USB_OTG_DIEPTSIZx:设备 IN 端点 x 传输大小寄存器,设置要发送的字节数
- USB_OTG_DOEPTSIZx:设备 OUT 端点 x 传输大小寄存器,设置要接收的字节数
四、STM32CubeMX USB 配置步骤
4.1 基础时钟配置 (关键!)
USB 时钟必须精确为48MHz(FS)或480MHz(HS):
- 启用外部高速晶振 (HSE)
- 配置 PLL:
- 对于 HSE=8MHz:PLLM=8, PLLN=336, PLLP=2, PLLQ=7 → USB 时钟 = 48MHz
- 对于 HSE=25MHz:PLLM=25, PLLN=336, PLLP=2, PLLQ=7 → USB 时钟 = 48MHz
- 在 "Clock Configuration" 中确认 "USB Clock" 为 48MHz
4.2 USB 外设配置
- 在 "Pinout & Configuration" 中选择 USB_OTG_FS 或 USB_OTG_HS
- 选择模式:
- Device_Only:仅设备模式 (最常用)
- Host_Only:仅主机模式
- OTG:支持主从切换
- 配置 GPIO:
- USB_DM 和 USB_DP 自动配置为复用功能
- 如需 VBUS 检测,配置 VBUS 引脚为输入模式
4.3 USB 中间件配置
- 切换到 "Middleware" 选项卡
- 选择 "USB_DEVICE"
- 选择设备类:
- Communication Device Class (CDC):虚拟串口
- Mass Storage Class (MSC):U 盘
- Human Interface Device (HID):人机接口设备
- Audio Class:音频设备
- 配置设备描述符参数 (VID、PID、产品名称等)
五、USB 设备状态机
STM32 USB 设备在运行过程中会经历以下状态:
- 连接状态 (Attached):设备物理连接到主机
- 上电状态 (Powered):设备从 USB 总线获得电源
- 默认状态 (Default):设备收到复位信号后
- 地址状态 (Address):主机为设备分配唯一地址
- 配置状态 (Configured):主机完成设备配置,设备可正常工作
- 挂起状态 (Suspended):总线空闲超过 3ms,设备进入低功耗模式
六、常用 USB 模式 HAL 库应用示例
6.1 虚拟串口 (CDC) 模式
应用场景:PC 与单片机之间的串口通讯,替代传统 RS232 接口
CubeMX 配置:
- USB_DEVICE → Class for FS IP → Communication Device Class(CDC)
- 生成代码后,在
usbd_cdc_if.c文件中实现数据收发
发送数据示例:
cpp
#include "usbd_cdc_if.h"
// 发送字符串
uint8_t tx_buffer[] = "Hello USB CDC!\r\n";
CDC_Transmit_FS(tx_buffer, sizeof(tx_buffer)-1);
// 发送二进制数据
uint16_t adc_value = 1234;
uint8_t data[2];
data[0] = (adc_value >> 8) & 0xFF;
data[1] = adc_value & 0xFF;
CDC_Transmit_FS(data, 2);
接收数据示例 :在usbd_cdc_if.c的CDC_Receive_FS函数中添加处理代码:
cpp
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
// Buf: 接收到的数据缓冲区
// Len: 接收到的数据长度
// 回显接收到的数据
CDC_Transmit_FS(Buf, *Len);
// 准备下一次接收
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
}
6.2 大容量存储 (MSC) 模式
应用场景:实现 U 盘功能,可访问内部 Flash 或外部 SD 卡
CubeMX 配置:
- USB_DEVICE → Class for FS IP → Mass Storage Class(MSC)
- 生成代码后,在
usbd_storage_if.c文件中实现存储介质操作
内部 Flash 模拟 U 盘示例 :修改usbd_storage_if.c中的以下函数:
cpp
#define STORAGE_LUN_NBR 1
#define STORAGE_BLK_NBR 64 // 64个块
#define STORAGE_BLK_SIZ 512 // 每个块512字节
// 内部Flash地址(根据实际芯片调整)
#define FLASH_START_ADDR 0x08020000
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
uint32_t i;
uint32_t flash_addr = FLASH_START_ADDR + blk_addr * STORAGE_BLK_SIZ;
for(i=0; i<blk_len*STORAGE_BLK_SIZ; i++)
{
buf[i] = *(__IO uint8_t*)(flash_addr + i);
}
return (USBD_OK);
}
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
uint32_t i;
uint32_t flash_addr = FLASH_START_ADDR + blk_addr * STORAGE_BLK_SIZ;
HAL_StatusTypeDef status;
// 解锁Flash
HAL_FLASH_Unlock();
// 擦除扇区
FLASH_EraseInitTypeDef erase_init;
erase_init.TypeErase = FLASH_TYPEERASE_SECTORS;
erase_init.Sector = FLASH_SECTOR_5; // 根据实际地址调整
erase_init.NbSectors = 1;
erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3;
uint32_t sector_error;
HAL_FLASHEx_Erase(&erase_init, §or_error);
// 写入数据
for(i=0; i<blk_len*STORAGE_BLK_SIZ; i+=4)
{
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD,
flash_addr + i,
*(uint32_t*)(buf + i));
if(status != HAL_OK) break;
}
// 锁定Flash
HAL_FLASH_Lock();
return (USBD_OK);
}
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
*block_num = STORAGE_BLK_NBR;
*block_size = STORAGE_BLK_SIZ;
return (USBD_OK);
}
6.3 人机接口 (HID) 模式
应用场景:无需安装驱动的设备,如键盘、鼠标、游戏手柄
CubeMX 配置:
- USB_DEVICE → Class for FS IP → Human Interface Device(HID)
- 生成代码后,在
usbd_hid.c文件中修改报告描述符
USB 鼠标示例:
- 修改
usbd_hid.c中的报告描述符:
cpp
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END =
{
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Buttons)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x03, // Usage Maximum (0x03)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x03, // Report Count (3)
0x81, 0x02, // Input (Data,Var,Abs)
0x75, 0x05, // Report Size (5)
0x95, 0x01, // Report Count (1)
0x81, 0x03, // Input (Const,Var,Abs)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x38, // Usage (Wheel)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x03, // Report Count (3)
0x81, 0x06, // Input (Data,Var,Rel)
0xC0, // End Collection
0xC0 // End Collection
};
- 发送鼠标报告:
cpp
#include "usbd_hid.h"
// 鼠标报告结构
typedef struct {
uint8_t buttons; // 位0:左键, 位1:右键, 位2:中键
int8_t x; // X轴移动(-127~127)
int8_t y; // Y轴移动(-127~127)
int8_t wheel; // 滚轮移动(-127~127)
} MouseReport;
// 移动鼠标
MouseReport report = {0};
report.x = 10; // 向右移动10个单位
report.y = -5; // 向上移动5个单位
USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&report, sizeof(report));
// 点击左键
report.buttons = 0x01;
USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&report, sizeof(report));
HAL_Delay(100);
report.buttons = 0x00;
USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&report, sizeof(report));
6.4 复合设备模式
应用场景:同时实现多个 USB 功能,如虚拟串口 + U 盘
CubeMX 配置:
- USB_DEVICE → Class for FS IP → Composite Device
- 添加需要的设备类 (如 CDC 和 MSC)
- 生成代码后,在
usbd_composite.c文件中配置接口
七、HAL 库 USB 核心函数详解
7.1 设备控制函数
HAL_PCD_Init(PCD_HandleTypeDef *hpcd):初始化 USB 设备控制器HAL_PCD_DeInit(PCD_HandleTypeDef *hpcd):反初始化 USB 设备控制器HAL_PCD_Start(PCD_HandleTypeDef *hpcd):启动 USB 设备HAL_PCD_Stop(PCD_HandleTypeDef *hpcd):停止 USB 设备HAL_PCD_SetAddress(PCD_HandleTypeDef *hpcd, uint8_t address):设置设备地址
7.2 端点操作函数
HAL_PCD_EP_Open(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint16_t ep_mps, uint8_t ep_type):打开端点HAL_PCD_EP_Close(PCD_HandleTypeDef *hpcd, uint8_t ep_addr):关闭端点HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pbuf, uint32_t len):发送数据HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t *pbuf, uint32_t len):接收数据HAL_PCD_EP_SetStall(PCD_HandleTypeDef *hpcd, uint8_t ep_addr):设置端点暂停HAL_PCD_EP_ClearStall(PCD_HandleTypeDef *hpcd, uint8_t ep_addr):清除端点暂停
7.3 中断回调函数
HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd):USB 复位回调HAL_PCD_SuspendCallback(PCD_HandleTypeDef *hpcd):USB 挂起回调HAL_PCD_ResumeCallback(PCD_HandleTypeDef *hpcd):USB 恢复回调HAL_PCD_ConnectCallback(PCD_HandleTypeDef *hpcd):USB 连接回调HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd):USB 断开回调
八、常见问题与解决方案
-
USB 枚举失败
- 检查 USB 时钟是否精确为 48MHz
- 检查 USB_DM 和 USB_DP 引脚是否正确连接
- 检查设备描述符和配置描述符是否正确
- 确保 VBUS 检测功能正常 (如果启用)
-
数据传输不稳定
- 增加 USB 线的屏蔽性
- 缩短 USB 线长度
- 检查电源稳定性
- 调整端点 FIFO 大小
-
驱动安装问题
- CDC 设备:Windows 10 及以上系统自带驱动
- HID 设备:所有系统都自带驱动
- 自定义设备:需要使用 WinUSB 或 libusb 驱动
-
中断冲突
- 调整 USB 中断优先级,确保高于其他低优先级中断
- 避免在中断服务程序中执行耗时操作