1. 项目概述:基于STM32F103的TinyUSB性能调优实战
在嵌入式开发中,USB通信性能往往是影响整体系统表现的关键因素。最近我在一个物联网网关项目中遇到了USB CDC(通信设备类)传输速率不达标的问题,目标设备使用的是大家熟悉的"小蓝板"STM32F103C8T6开发板。经过系统性的测试和优化,最终将传输速率从最初的64KB/s提升到了接近理论极限的1MB/s。这个案例非常具有代表性,因为STM32F103系列作为经典的低成本MCU,在各类嵌入式产品中应用广泛,而TinyUSB又是目前最受欢迎的轻量级USB协议栈之一。
2. TinyUSB协议栈与测试方案设计
2.1 TinyUSB协议栈特点解析
TinyUSB是一个MIT协议开源的嵌入式USB协议栈,其设计理念非常契合资源受限的MCU环境。我在多个项目中使用过这个协议栈,发现它有以下几个显著优势:
- 跨平台支持:覆盖了常见的USB IP核,包括DWC2、MUSB、Chipidea以及STM32的全速设备控制器等
- 完善的Class实现:支持CDC、MSC、HID、MIDI等多种USB设备类
- 丰富的示例代码:自带大量测试用例和开发板移植,大大降低了上手难度
特别值得一提的是,TinyUSB默认配置追求极致的资源节约,这在资源紧张的MCU上是优点,但在性能需求较高的场景就需要针对性调整了。
2.2 测试方案设计思路
为了准确评估USB性能,我选择了CDC回环测试(Loopback)方案,具体考虑如下:
- 测试原理:设备将接收到的数据原样返回,通过测量大数据块的传输时间计算吞吐量
- 方案优势:
- 无需编写专用上位机驱动(使用系统自带CDC驱动)
- Bulk传输模式可以最大化利用USB带宽
- 测试结果直接反映MCU的USB核心性能
在实际操作中,我基于TinyUSB自带的cdc_dual_ports示例进行修改,这样既保证了测试的可靠性,又节省了开发时间。
3. 测试环境搭建与初始性能评估
3.1 硬件准备与固件编译
我使用的是市面上常见的STM32F103C8T6最小系统板(俗称"小蓝板"),成本不到20元却具备完整的USB设备功能。TinyUSB已经为这块板子提供了现成的BSP支持,位于代码库的hw/bsp/stm32f1/boards/stm32f103_bluepill目录。
编译过程非常简单:
bash复制cd examples/device/cdc_dual_ports
make BOARD=stm32f103_bluepill
生成的固件位于_build/stm32f103_bluepill/cdc_dual_ports.bin,可以使用OpenOCD通过ST-Link烧录:
bash复制openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "program _build/stm32f103_bluepill/cdc_dual_ports.bin verify reset exit 0x8000000"
3.2 测试代码实现
为了实现回环测试,我修改了示例中的cdc_task()函数:
c复制static void cdc_task(void) {
for (uint8_t itf = 0; itf < CFG_TUD_CDC; itf++) {
if (tud_cdc_n_available(itf)) {
uint8_t buf[64];
uint32_t count = tud_cdc_n_read(itf, buf, sizeof(buf));
tud_cdc_n_write(itf, buf, count);
}
}
}
这个修改去掉了原来的DTR连接检查(注释掉的代码),直接进行数据回传。注意这里使用了64字节的缓冲区,这是TinyUSB的默认设置。
3.3 上位机测试程序
为了简化测试流程,我编写了一个Python脚本用于性能测试:
python复制#!/usr/bin/python3
import sys
import serial
from time import perf_counter
def format_bytes(size):
# 字节单位转换函数
...
def main():
if len(sys.argv) != 2:
print("No port name specified")
exit()
ser = serial.Serial(sys.argv[1], baudrate=115200)
# 生成1MB测试数据(256×4096)
data = bytes()
for i in range(4096):
data = data + bytes(range(256))
print("Testing port", sys.argv[1])
t_start = perf_counter()
ser.write(data)
t_stop = perf_counter()
print("Throughput:", format_bytes(4096 * 256 / (t_stop - t_start)) + "/s")
if __name__ == "__main__":
main()
3.4 初始性能测试结果
首次测试的结果令人失望 - 仅有约64KB/s的传输速率。考虑到STM32F103的USB是全速设备(12Mbps),理论吞吐量应该在1MB/s左右,这说明有很大的优化空间。
4. 性能优化实战
4.1 优化方向分析
通过分析TinyUSB的默认配置和STM32F103的特性,我确定了以下几个优化方向:
- 资源分配优化:原示例使用了双CDC端口,但实际上我们只需要一个
- 编译器优化等级:默认的-Os(优化体积)改为-O2(优化速度)
- 缓冲区大小调整:增大CDC端点的收发缓冲区
4.2 具体优化措施
4.2.1 减少CDC端口数量
修改tusb_config.h:
c复制#define CFG_TUD_CDC 1 // 原值为2
这样可以节省出一个CDC接口占用的资源,包括端点描述符、缓冲区等。
4.2.2 调整编译器优化选项
修改gcc_common.mk文件:
makefile复制# 原设置
# CFLAGS_OPTIMIZED ?= -Os
# 修改后
CFLAGS_OPTIMIZED ?= -O2
-O2优化级别会在不显著增加代码体积的情况下提高运行速度,这对USB这种高频率中断的服务非常有利。
4.2.3 增大缓冲区尺寸
继续修改tusb_config.h:
c复制#define CFG_TUD_CDC_RX_BUFSIZE 4096 // 原值为64
#define CFG_TUD_CDC_TX_BUFSIZE 4096 // 原值为64
#define CFG_TUD_CDC_EP_BUFSIZE 4096 // 原值为64
这里将各种缓冲区大小从64字节提升到4096字节,主要考虑:
- 减少中断频率:大缓冲区意味着每个USB传输可以携带更多数据
- 匹配USB全速设备的特性:USB全速的最大包长度是64字节,但可以通过多次传输组成更大的逻辑包
- STM32F103有足够的RAM(20KB)来支持这些缓冲区
4.3 优化后性能测试
实施上述优化后重新测试,结果令人振奋 - 传输速率达到了约1MB/s,接近理论最大值。这意味着我们的优化措施是有效的。
5. 关键问题分析与调优经验
5.1 性能瓶颈分析
在最初的测试中,性能低下的主要原因包括:
- 频繁中断:小缓冲区导致每个USB事务都需要CPU干预
- 编译器优化不足:-Os选项生成的代码执行效率不高
- 资源竞争:不必要的双CDC端口占用了宝贵的USB资源
5.2 调优经验总结
通过这个项目,我总结了以下几点嵌入式USB性能调优的经验:
-
缓冲区不是越大越好:
- 需要平衡性能和内存占用
- 对于STM32F103,4KB缓冲区是个不错的折中
- 在实际项目中,应该根据可用内存调整
-
编译器选项的影响:
- -O2相比-Os能带来显著的性能提升
- 但会略微增加代码体积(约5-10%)
- 在Flash空间紧张时需要权衡
-
USB配置的黄金法则:
c复制// 在tusb_config.h中应该关注的配置项 #define CFG_TUD_CDC_RX_BUFSIZE (根据需求调整) #define CFG_TUD_CDC_TX_BUFSIZE (与RX相同或更大) #define CFG_TUD_CDC_EP_BUFSIZE (建议≥1024) #define CFG_TUD_TASK_INTERVAL 1000 // 任务运行间隔(μs) -
实测中的注意事项:
- 测试数据量要足够大(至少几十KB)
- 避免在测试期间插入打印语句影响计时
- 多次测试取平均值
6. 扩展思考与进阶优化
6.1 其他可能的优化方向
除了已经实施的措施,还可以尝试以下优化:
-
调整USB中断优先级:
c复制// 在stm32f1xx_hal_conf.h中 #define USB_IRQn_PRIO 1 // 提高USB中断优先级 -
使用DMA传输:
- STM32F103的USB支持DMA
- 可以进一步降低CPU负载
-
优化时钟配置:
- 确保USB时钟是准确的48MHz
- 检查时钟树配置
6.2 不同场景下的配置建议
根据项目需求,TinyUSB的配置应该灵活调整:
-
资源受限设备:
- 保持小缓冲区
- 使用-Os优化
- 禁用不必要的USB类
-
高性能需求设备:
- 增大缓冲区
- 使用-O2或-O3优化
- 合理分配USB带宽
-
多功能复合设备:
- 精心规划端点使用
- 可能需要调整USB描述符
- 考虑实现USB挂起/恢复功能
7. 项目总结与个人体会
这次性能调优实践让我对嵌入式USB开发有了更深的理解。有几点特别值得分享的经验:
-
不要忽视默认配置:TinyUSB为了通用性采用了保守配置,实际项目中需要根据需求调整
-
性能分析要系统化:从编译器选项到缓冲区大小,每个环节都可能成为瓶颈
-
测试方法很重要:好的测试方案能准确反映真实性能,CDC回环测试就是个不错的选择
在实际操作中,我发现STM32F103的USB性能完全可以满足大多数全速应用的需求,关键是要合理配置。对于需要更高速率的场景,可以考虑升级到STM32F4/F7系列(支持USB高速),但成本会相应增加。
最后分享一个调试小技巧:当USB通信出现问题时,可以先检查dmesg输出(Linux)或设备管理器(Windows)的识别情况,然后再深入协议栈层面分析,这样能事半功倍。