最近在做一个基于STM32F103的USB设备开发项目,需要实现高速数据传输。在评估了几种USB协议栈后,最终选择了TinyUSB这个轻量级的开源解决方案。但在实际使用过程中发现,其性能表现与官方宣称的有些差距,于是决定对TinyUSB在STM32F103平台上的性能进行全面测试和优化。
TinyUSB作为一个嵌入式USB协议栈,以其小巧的体积和良好的可移植性著称。但在资源有限的STM32F103C8T6(俗称"蓝莓派")上运行时,我们发现当传输数据量增大时,会出现明显的性能瓶颈。这篇文章将分享我们的测试方法、发现的问题以及最终的优化方案。
测试使用的是STM32F103C8T6最小系统板,核心参数如下:
我们设计了一个简单的测试固件,实现了一个虚拟串口(CDC)和一个大容量存储设备(MSC)。测试主要关注CDC的批量传输性能,因为这是我们实际项目中最常用的传输模式。
c复制// USB设备描述符配置
tusb_desc_device_t const desc_device = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0xCafe,
.idProduct = 0x4000,
.bcdDevice = 0x0100,
// ...其他标准描述符
};
我们主要关注以下几个性能指标:
PC端使用Python编写测试脚本,通过pyUSB库与设备通信。测试脚本实现了以下功能:
python复制import usb.core
import time
# 查找设备
dev = usb.core.find(idVendor=0xCafe, idProduct=0x4000)
# 配置测试参数
packet_size = 512
total_packets = 1000
# 开始测试
start = time.time()
for i in range(total_packets):
dev.write(0x81, b'a'*packet_size, 1000)
elapsed = time.time() - start
throughput = (packet_size * total_packets) / elapsed / 1024
print(f"Throughput: {throughput:.2f} KB/s")
初始测试结果如下(传输512字节数据包):
| 测试项 | 数值 |
|---|---|
| 平均吞吐量 | 320 KB/s |
| 最大延迟 | 15 ms |
| CPU利用率 | 78% |
| 丢包率(24h) | 0.5% |
这个结果明显低于USB全速(12Mbps)的理论最大值(~1MB/s),说明存在优化空间。
为了定位瓶颈,我们在固件中添加了时间测量代码:
c复制#define DWT_CYCCNT *(volatile uint32_t *)0xE0001004
#define DWT_CONTROL *(volatile uint32_t *)0xE0001000
#define SCB_DEMCR *(volatile uint32_t *)0xE000EDFC
void dwt_init(void) {
SCB_DEMCR |= 1 << 24; // Enable DWT
DWT_CYCCNT = 0;
DWT_CONTROL |= 1; // Enable cycle counter
}
uint32_t dwt_get_ticks() {
return DWT_CYCCNT;
}
通过测量发现以下几个主要性能瓶颈:
具体测量数据:
| 操作 | 耗时(cycles) | 占比 |
|---|---|---|
| 数据拷贝 | 1200 | 35% |
| 中断处理 | 800 | 23% |
| 协议处理 | 600 | 18% |
| 其他 | 800 | 24% |
原始代码中,数据从USB FIFO拷贝到应用缓冲区,再从应用缓冲区拷贝到发送FIFO。我们修改为直接使用DMA传输:
c复制// 修改后的端点配置
#define CFG_TUD_CDC_RX_BUFSIZE 512
#define CFG_TUD_CDC_TX_BUFSIZE 512
// 启用DMA
USBD_LL_Init(0);
USBD_LL_OpenEP(0, 0x81, USBD_EP_TYPE_BULK, CFG_TUD_CDC_RX_BUFSIZE);
USBD_LL_PrepareReceive(0, 0x81, buffer, CFG_TUD_CDC_RX_BUFSIZE);
简化中断处理流程,将非关键操作移到主循环:
c复制void OTG_FS_IRQHandler(void) {
uint32_t int_status = USB_OTG_FS->GINTSTS;
// 仅处理必须立即响应的事件
if(int_status & USB_OTG_GINTSTS_RXFLVL) {
// 快速处理接收中断
handle_rx_flvl();
USB_OTG_FS->GINTSTS = USB_OTG_GINTSTS_RXFLVL;
}
// 其他中断类型...
}
根据实际测试调整缓冲区大小和数量:
c复制// 修改tusb_config.h中的配置
#define CFG_TUD_CDC_EP_BUFSIZE 1024 // 增大端点缓冲区
#define CFG_TUD_TASK_QUEUE_SZ 16 // 增大任务队列
#define CFG_TUD_CDC_RX_BUFSIZE (4*1024) // 应用层接收缓冲区
#define CFG_TUD_CDC_TX_BUFSIZE (4*1024) // 应用层发送缓冲区
将部分轮询任务改为事件驱动:
c复制void tud_task_ext(uint32_t timeout_ms) {
static uint32_t last_check = 0;
uint32_t now = HAL_GetTick();
// 减少调用频率
if((now - last_check) > 10) {
tud_task();
last_check = now;
}
}
经过上述优化后,重新测试得到以下结果:
| 测试项 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 平均吞吐量 | 320 KB/s | 780 KB/s | 143% |
| 最大延迟 | 15 ms | 5 ms | 66% |
| CPU利用率 | 78% | 45% | 42% |
| 丢包率(24h) | 0.5% | 0.05% | 90% |
现象:偶尔出现数据丢失或错误
解决方案:
现象:设备偶尔无法被主机识别
解决方案:
现象:运行一段时间后吞吐量下降
解决方案:
在实际项目中,我们通过上述优化使TinyUSB在STM32F103上的性能接近理论最大值。虽然这款MCU的资源有限,但通过合理的优化仍然可以实现相当不错的USB性能。