1. 项目背景与需求分析
最近在帮朋友改造一款简易手持温湿度采集仪时,遇到了串口通信的瓶颈问题。原设计采用CP2102 USB转串口芯片,当数据传输速率达到800KBps左右时,丢包率急剧上升,导致温湿度曲线波动剧烈,严重影响数据可靠性。这促使我寻找更稳定高效的通信方案。
STM32F103系列微控制器内置USB全速设备接口,理论上可以提供比串口更稳定、更快速的数据传输。但官方提供的标准库示例(STSW-STM32121)主要面向HID鼠标等标准设备,需要经过深度改造才能满足自定义数据通信需求。
2. USB HID通信方案设计
2.1 方案选型考量
在选择通信方案时,我主要考虑了以下几个因素:
- 通信稳定性:USB协议本身具有完善的错误检测和重传机制,比串口通信更可靠
- 传输速率:USB全速设备理论速率可达12Mbps,远高于串口的115200bps
- 硬件简化:直接使用STM32内置USB接口,可省去外部转换芯片,简化PCB设计
- 开发难度:HID类设备驱动在主流操作系统中都已内置,无需额外开发驱动
2.2 HID协议特点
HID(Human Interface Device)协议最初是为键盘、鼠标等人机交互设备设计的,但它具有以下特点使其适合用作通用数据通信:
- 即插即用:操作系统自带驱动,无需安装额外软件
- 低延迟:中断传输方式保证快速响应
- 灵活配置:通过报告描述符可以自定义数据格式
3. 关键实现步骤
3.1 自定义报告描述符
报告描述符是HID通信的核心,它定义了设备与主机之间的数据交换格式。我基于官方鼠标示例进行了如下改造:
c复制const uint8_t HID_ReportDescriptor[HID_IN_PACKET + HID_OUT_PACKET + 20] = {
0x05, 0x8C, // Usage Page (Generic Desktop)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
// 自定义64字节输入报告(设备→主机)
0x05, 0x01, // Usage Page (Generic Desktop)
0x19, 0x00, // Usage Minimum (0)
0x29, 0x3F, // Usage Maximum (63)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF,0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8 bits)
0x95, 0x40, // Report Count (64 elements)
0x81, 0x02, // Input (Data,Var,Abs)
// 自定义64字节输出报告(主机→设备)
0x05, 0x01, // Usage Page (Generic Desktop)
0x19, 0x00, // Usage Minimum (0)
0x29, 0x3F, // Usage Maximum (63)
0x15, 0x00, // Logical Minimum (0)
0x26, 0xFF,0x00, // Logical Maximum (255)
0x75, 0x08, // Report Size (8 bits)
0x95, 0x40, // Report Count (64 elements)
0x91, 0x02, // Output (Data,Var,Abs)
0xC0, 0xC0 // End Collection, End Collection
};
关键参数说明:
- Report Size:8位,表示每个数据单元占8位
- Report Count:64,表示每次传输64个数据单元
- Input/Output:分别定义输入和输出报告
3.2 通信接口封装
为了便于使用,我将USB HID通信功能封装为独立的模块,提供简洁的API接口:
c复制// my_usb_hid.h
#ifndef __MY_USB_HID_H
#define __MY_USB_HID_H
#include "stm32f10x.h"
#include "usb_lib.h"
#define HID_SEND_MAX_LEN 64
#define HID_RECV_MAX_LEN 64
uint8_t USB_HID_Send(uint8_t *buf, uint16_t len);
void USB_HID_Receive_Callback(uint8_t *buf, uint8_t len);
#endif
发送函数实现要点:
- 检查USB枚举状态
- 验证数据长度
- 使用DMA方式传输数据
c复制uint8_t USB_HID_Send(uint8_t *buf, uint16_t len)
{
if (bDeviceState != CONFIGURED) return 1;
if (len > HID_SEND_MAX_LEN) len = HID_SEND_MAX_LEN;
UserToPMABufferCopy(buf, ENDP1_TXADDR, len);
SetEPTxCount(ENDP1, len);
SetEPTxValid(ENDP1);
return 0;
}
接收处理采用回调机制:
c复制void EP2_OUT_Callback(void)
{
uint8_t recv_len = GetEPRxCount(ENDP2);
uint8_t recv_buf[HID_RECV_MAX_LEN];
PMAToUserBufferCopy(recv_buf, ENDP2_RXADDR, recv_len);
USB_HID_Receive_Callback(recv_buf, recv_len);
SetEPRxValid(ENDP2);
}
__weak void USB_HID_Receive_Callback(uint8_t *buf, uint8_t len)
{
// 用户自定义处理逻辑
}
4. 性能测试与优化
4.1 测试方案设计
为验证通信性能,我设计了以下测试场景:
- STM32循环发送64字节数据包
- PC端使用Python编写的测试程序接收并统计速率
- 对比相同硬件条件下的串口通信性能
测试代码示例:
c复制// main.c测试循环
while(1) {
USB_HID_Send(test_data, 64);
// 不加延时以测试极限速率
}
Python接收端关键代码:
python复制def hid_receive_handler(data):
global total_bytes
total_bytes += len(data) - 1 # 减去报告ID
# 主循环
while True:
if time.time() - start_time >= 1:
speed = total_bytes / 1024 # KB/s
print(f"速率: {speed:.2f} KB/s")
total_bytes = 0
start_time = time.time()
time.sleep(0.1)
4.2 测试结果对比
| 通信方式 | 理论速率 | 实测速率 | 稳定性 |
|---|---|---|---|
| USB HID | 12Mbps | 1.0-1.2Mbps | 无丢包 |
| 串口(115200) | 115.2Kbps | 约11.2KB/s | 高波特率易丢包 |
实测表明,USB HID通信速率可达串口的10倍以上,且稳定性显著提升。
5. 应用扩展与二次开发
基于此通信框架,可以进行多种功能扩展:
5.1 协议增强
- 添加帧头帧尾校验
- 实现数据分包重组
- 加入重传机制
示例协议格式:
code复制[0xAA][0x55][长度][数据...][校验和]
5.2 功能扩展
- 复合设备:在报告描述符中添加键盘、鼠标等标准HID功能
- 动态配置:通过输出报告实现设备参数配置
- 状态反馈:利用输入报告上传设备状态信息
5.3 跨平台适配
- Windows端可使用pywinusb或HIDAPI
- Linux端可直接使用hidraw接口
- 嵌入式主机端同样适用此通信方案
6. 开发经验与注意事项
在实际开发过程中,我总结了以下重要经验:
6.1 枚举失败排查
- 检查报告描述符格式是否正确
- 验证设备描述符中的各类长度参数
- 使用USB分析仪捕获枚举过程
6.2 性能优化要点
- 减少数据拷贝次数
- 合理设置端点缓冲区大小
- 避免在中断处理中进行复杂运算
6.3 稳定性保障
- 添加超时处理机制
- 实现心跳包检测连接状态
- 重要数据需添加校验重传
7. 完整工程结构建议
一个规范的USB HID通信工程应包含以下模块:
code复制Project/
├── CMSIS/ # 内核支持文件
├── STM32F10x_StdPeriph_Driver/ # 标准外设库
├── USB_Device/ # USB设备库
│ ├── usb_core.c
│ ├── usb_init.c
│ └── ...
├── User/
│ ├── main.c # 主程序
│ ├── my_usb_hid.c # HID通信模块
│ ├── my_usb_hid.h
│ ├── usb_desc.c # 描述符定义
│ ├── usb_desc.h
│ └── ...
└── ...
这种模块化设计便于功能扩展和跨平台移植。
