4. 批量端点配置实战:批量端点的典型配置参数、FIFO深度与端点大小的关系、双缓冲与乒乓缓冲机制
批量端点,说白了就是USB世界里那个「只管送,不管快慢」的老实人。它不保证延迟,但保证数据一定到。我刚开始接触USB时,总觉得批量传输最简单——扔数据就行了呗。结果第一次调STM32的批量端点,发现速度死活上不去,才意识到这里面的门道比想象中深得多。
今天咱们就把批量端点的配置掰开揉碎。从寄存器参数到FIFO深度,再到双缓冲和乒乓缓冲,一个一个说清楚。
4.1 批量端点的典型配置参数
先看一张表,把批量端点的核心配置参数列出来。这些参数在USB设备端(比如STM32、CY7C68013、i.MX RT系列)的寄存器里都能找到对应位域。
| 参数名称 | 典型值/范围 | 说明 |
|---|---|---|
| 端点类型 | BULK (0x02) | USB 2.0规范定义,描述符中bEndpointAddress的bits[1:0] |
| 端点方向 | IN / OUT | IN为主机到设备,OUT为设备到主机(注意方向定义) |
| 最大包大小 (MaxPacketSize) | 8, 16, 32, 64 (FS) 512 (HS) |
全速最大64字节,高速固定512字节 |
| FIFO深度 | 取决于芯片,通常为512B~4KB | 每个端点独立或共享,直接影响吞吐 |
| 双缓冲使能 | 0 / 1 | 使能后硬件自动切换缓冲区 |
| 乒乓缓冲使能 | 0 / 1 | 类似双缓冲,但通过端点序号交替实现 |
| NAK间隔 | 0~255 (帧数) | 设备忙时返回NAK的速率控制 |
嗯,这里要注意:最大包大小这个参数,很多人以为设得越大越好。其实不然。高速模式下512字节是硬性规定,你改不了。全速模式下虽然可以设64字节,但如果你只传几十个字节,设64反而浪费带宽——因为主机每次事务都要给你512微帧的时间片。
4.2 FIFO深度与端点大小的关系
FIFO深度,这是批量端点性能的命门。我见过太多工程师,配置时随便填个FIFO大小,结果批量传输速度只有理论值的十分之一。
先理清一个概念:FIFO深度 ≠ 端点缓冲区大小。端点缓冲区是软件看到的逻辑空间,FIFO是硬件层面的物理存储。两者之间有个映射关系。
以STM32的USB OTG_HS为例,它的FIFO分配逻辑是这样的:
/* STM32 USB OTG_HS FIFO配置示例 */
/* 全局FIFO大小:4KB (1024 * 32位) */
/* 1. 配置接收FIFO (RXFIFO) */
USB_OTG_HS->GRXFSIZ = 0x80; /* 512字节,用于所有OUT端点 */
/* 2. 配置非周期性发送FIFO (NPTXFIFO) */
USB_OTG_HS->GNPTXFSIZ = (0x80 << 16) | 0x80;
/* 起始地址0x80,大小512字节,用于批量IN端点 */
/* 3. 配置周期性发送FIFO (PTXFIFO) — 批量端点不用 */
/* 批量端点只使用NPTXFIFO */
这里有个关键点:FIFO深度必须至少能容纳2个最大包。为什么?因为USB主机在发送一个事务后,不会等设备处理完再发下一个。如果FIFO深度小于2个包,主机连续发送时,第二个包就会丢失。
我曾在项目中遇到过一个问题:用CY7C68013做批量传输,速度始终卡在20MB/s左右。查了半天,发现FIFO深度只设了512字节(刚好1个包)。改成1024字节后,速度直接翻倍到40MB/s。
FIFO深度和端点大小的关系,可以用下面这张图来理解:
4.3 双缓冲机制
双缓冲,说白了就是硬件帮你准备了两块缓冲区。一块给USB DMA用,一块给CPU用。两者交替,互不干扰。
没有双缓冲时,流程是这样的:
- USB主机发数据到FIFO
- CPU从FIFO读数据到内存
- CPU处理数据期间,FIFO空闲
- 处理完,等待下一个数据
这个流程的问题很明显:CPU处理数据时,USB总线是空闲的。对于批量传输这种追求吞吐的场景,这是致命的。
双缓冲的流程就漂亮多了:
- USB主机发数据到缓冲区A
- 硬件自动切换到缓冲区B,继续接收下一包
- CPU同时处理缓冲区A的数据
- 处理完A,缓冲区B已经满了,直接切换回来
这样一来,USB总线和CPU处理完全流水线化。理论上吞吐量可以翻倍。
4.4 乒乓缓冲机制
乒乓缓冲,你可以把它理解成双缓冲的「软件实现版」。它不依赖硬件自动切换,而是通过两个端点序号交替使用来达到类似效果。
举个例子,在CY7C68013上,你可以这样配置:
/* CY7C68013 乒乓缓冲配置示例 */
/* 使用端点2和端点4作为一对乒乓缓冲区 */
EP2CFG = 0xE2; /* 端点2: 批量IN, 512字节, 4倍缓冲 */
EP4CFG = 0xE0; /* 端点4: 批量OUT, 512字节, 4倍缓冲 */
/* 实际使用时,主机交替访问端点2和端点4 */
/* 设备端在固件中切换当前使用的端点 */
乒乓缓冲和双缓冲的区别,我整理了一张对比表:
| 特性 | 双缓冲 (Double Buffering) | 乒乓缓冲 (Ping-Pong Buffering) |
|---|---|---|
| 实现方式 | 硬件自动切换 | 软件/固件控制切换 |
| 缓冲区数量 | 2个(同一端点内) | 2个(不同端点) |
| 端点占用 | 1个端点 | 2个端点 |
| 切换延迟 | 纳秒级(硬件) | 微秒级(固件响应) |
| CPU负担 | 低 | 中(需要处理切换逻辑) |
| 典型芯片 | STM32 USB OTG_HS | CY7C68013, FT232H |
| 适用场景 | 高速批量传输 | 全速/低速,或硬件不支持双缓冲时 |
4.5 实战配置建议
说了这么多理论,最后给几个实际配置的参考方案。这些是我在多个项目中验证过的,可以直接拿来用。
方案一:STM32F4 + USB OTG_HS (高速批量)
/* 端点配置 */
/* 批量IN端点: 端点1 IN */
USB_OTG_HS->DIEPCTL1 = (1 << 0) /* 使能 */
| (3 << 18) /* 端点类型: 批量 */
| (512 << 0) /* 最大包大小 */
| (1 << 28); /* 使能双缓冲 */
/* FIFO配置 */
USB_OTG_HS->GRXFSIZ = 0x100; /* 接收FIFO: 1024字节 */
USB_OTG_HS->GNPTXFSIZ = (0x100 << 16) | 0x100; /* 发送FIFO: 1024字节 */
方案二:CY7C68013 (高速批量,乒乓缓冲)
/* 端点2: 批量IN, 512字节, 4倍缓冲 */
EP2CFG = 0xE2;
/* 端点4: 批量IN, 512字节, 4倍缓冲 (乒乓伙伴) */
EP4CFG = 0xE2;
/* 端点6: 批量OUT, 512字节, 4倍缓冲 */
EP6CFG = 0xE6;
/* 端点8: 批量OUT, 512字节, 4倍缓冲 (乒乓伙伴) */
EP8CFG = 0xE6;
方案三:全速设备 (比如STM32F103)
/* 全速批量端点,最大包64字节 */
/* FIFO深度设128字节就够了 */
/* 双缓冲不是必须的,但开了也没坏处 */
- 高速批量:FIFO深度 ≥ 1024字节,开双缓冲
- 全速批量:FIFO深度 ≥ 128字节,双缓冲可选
- 如果芯片不支持双缓冲,用乒乓缓冲替代
- FIFO深度宁大勿小,但别超过芯片总FIFO容量
批量端点的配置,说白了就是平衡FIFO资源、缓冲区大小和切换机制这三者。你想想看,USB总线的带宽是固定的,但你的FIFO深度和缓冲机制决定了你能用上多少。调好了,速度飞起;调不好,数据都堵在门口。
嗯,这一章的内容就到这儿。记住我说的那几个关键参数,下次调批量端点的时候,心里就有底了。