本篇讨论如何把 STM32 的采样、缓冲与 USB 传输组织成可验证的数据采集系统。适用对象是准备实现自定义 USB DAQ 的开发者。它是一份设计与移植指南:板级时钟、ADC 前端、引脚、USB PHY 和固件中间件必须按实际硬件完成,不能把示例拼接成“已经在所有 STM32H7 上验证的完整工程”。
通用 USB 概念见 USB 基础,命令和上位机实现见 USB DAQ 协议与上位机。
1. 从采集需求开始
跳转到“1. 从采集需求开始”至少先定义以下参数:
| 参数 | 要明确的问题 |
|---|---|
| 通道 | 单端或差分、同步或扫描、有效通道数 |
| 采样率 | 每通道采样率,还是所有通道合计? |
| 数值格式 | 12/16/24 bit ADC,实际传输占几个字节,是否有符号 |
| 量程 | ADC 引脚范围、前端增益/偏置、校准参数 |
| 连续性 | 允许停止采样、丢样,还是必须在指定时间内完整保存? |
| 时序 | 采样时钟、触发条件、最大允许时延和主机积压 |
| 控制功能 | 仅 AI,还是还要 AO、DI、DO、计数器? |
以 4 通道、每通道 50 kS/s、每样本传输 2 字节为例:
若主机最多连续 s 没有取走数据,仅样本就积累:
再计帧头、调度余量和队列管理开销,才能确定实际缓冲大小。如果平均输出吞吐低于输入吞吐,增加缓冲只能延迟溢出,不能解决问题。
1.1 商业采集模块可以作为需求参照
跳转到“1.1 商业采集模块可以作为需求参照”原笔记以研华 USB-4716 为参照。其旧版资料说明 16 路单端/8 路差分、16 bit ADC 和最高 200 kS/s 总采样吞吐率;4 通道同时使用时,每通道不能直接按 200 kS/s 计算。旧 AE 资料标注 USB 2.0,当前 BE 产品资料标注 USB 3.0,应记录完整型号和文档版本。Advantech 旧版 USB-4716 数据表、当前产品说明
这些指标不能证明其内部 MCU、USB 设备类或私有协议与某个 STM32 方案相同。自研方案还必须验证模拟前端、抗混叠、同步、噪声、校准和长期稳定性,不能只比较 USB 峰值速率。
2. 控制器、PHY 与速度
跳转到“2. 控制器、PHY 与速度”STM32H7 是系列名。选型时必须查完整料号、封装、芯片修订及板卡原理图。以 STM32H743 为例,产品资料列出两个 USB OTG 接口,其中一个支持 HS/FS;实现高速连接还要匹配控制器和 PHY。ST:STM32H743 产品资料
flowchart LR ADC[ADC / 采样前端] --> MCU[STM32 内存与应用] MCU --> Core[USB 控制器] Core -->|FS 路径| FS[片内 Full-Speed PHY] Core -->|支持的 ULPI 路径| HS[外部 High-Speed PHY] FS --> Connector[USB 连接器] HS --> Connector Connector --> PC[主机]ST 的 H7 USB 培训材料展示了片内 FS PHY 与外部 ULPI HS PHY 的不同连接路径。不能因为外设名称含 OTG_HS,就认为直接接片内 D+/D− 已经能工作在 480 Mbit/s。ST:H7 USB FS/HS 架构
上板后读取实际枚举速度,并检查对应配置描述符。USB 版本字段、物理连接速度和端点最大包长需要分别确认。
3. 选择设备类与驱动路径
跳转到“3. 选择设备类与驱动路径”| 方案 | 优点 | 必须完成的工作 |
|---|---|---|
| CDC ACM 虚拟串口 | 可复用串口调试和应用接口 | 正确 CDC 描述符、收发缓冲、流控和业务分帧 |
| 厂商自定义接口 + WinUSB | Windows 用户态应用可直接访问端点 | 驱动绑定、接口 GUID、端点查询、应用协议 |
| 自定义接口 + libusb/PyUSB | 可组织跨平台用户态访问 | 各系统驱动/权限、libusb 后端和资源释放 |
| 标准 Audio 等设备类 | 可接入对应系统软件生态 | 类规范中的格式、时钟、描述符和同步要求 |
CDC 的数据端点也可以采用 Bulk,因此“先用 CDC,再切换成 Bulk”不是严格的分类说法。真正改变的可能是设备类、接口描述符和主机 API。
WinUSB 自动绑定需要设备提供相应 Microsoft OS 描述符与 WINUSB 兼容 ID。厂商类号 0xFF 本身不足以触发自动安装。Microsoft:WinUSB 自动绑定
4. 固件模块与数据流
跳转到“4. 固件模块与数据流”flowchart TD Timer[定时器采样触发] --> ADC[ADC 转换序列] ADC --> DMA[DMA 写入采样缓冲] DMA --> Ready[半满 / 满回调发布完成块] Ready --> Queue[有界待发送队列] Queue --> Packet[帧编码与状态统计] Packet --> USB[USB IN 传输] USB --> Complete[发送完成归还缓冲] Complete --> Queue OUT[USB OUT 命令] --> Parser[边界检查与命令解析] Parser --> State[采集状态机] State --> Timer State --> ADC建议按职责拆开,而不是把描述符、ADC 和所有业务处理都写进 USB 回调:
| 模块 | 职责 |
|---|---|
| 板级初始化 | 时钟、电源、GPIO、ADC/DMA、触发定时器、USB控制器/PHY |
| USB适配层 | 类注册、端点与描述符、收发完成、OUT重新准备接收 |
| 采集驱动 | 采样配置、启动/停止、DMA完成块和硬件错误 |
| 缓冲管理 | 谁拥有哪块内存、何时可覆盖、队列满时如何处理 |
| 协议层 | 固定宽度字段、长度与字节序、命令响应和数据帧 |
| 业务状态机 | 配置合法性、启停顺序、能力声明和故障状态 |
4.1 DMA 回调只发布事件
跳转到“4.1 DMA 回调只发布事件”半满回调表示前半缓冲可供消费;完成回调表示后半缓冲可供消费。循环 DMA 接着会覆盖旧区域,消费者必须在下一次覆盖之前复制或完成处理。
如果每半块有 个样本、总样本率为 ,可用时间约为:
例如半块 512 点、总采样率 200 kS/s,约每 2.56 ms 产生一块;“有双缓冲”并不意味着可以无限等待 USB。
回调中应记录块索引、样本序号和错误,并通知主循环或任务。是否直接使用 DMA 区域、复制到发送池或使用更多缓冲,取决于上述时间预算。不要在中断里等待 USB 完成,也不要忽略队列满或发送忙状态。
4.2 发送缓冲的所有权
跳转到“4.2 发送缓冲的所有权”很多 STM32 USB 中间件的设置发送缓冲接口保存的是指针,传输在之后完成。ST CDC 实现可以看到 TxBuffer、TxState 与低层发送调用;返回成功表示提交被接受,不等于硬件已经发送完毕。ST USB CDC 源码
因此应遵循:
- 编码到一块由发送池持有的缓冲。
- 提交成功后标记为 USB 正在使用。
- 在对应发送完成事件后归还。
- 发送忙时保留待发数据或显式报告丢弃,不能悄悄覆盖。
不能把函数局部 uint8_t response[64] 的地址交给异步发送后立即返回;也不能在上一次传输尚未完成时改写同一静态数组。
4.3 应用帧与端点最大包长
跳转到“4.3 应用帧与端点最大包长”FS Bulk 最大包长为 64 字节,HS Bulk 最大包长为 512 字节。这是 USB 事务中的包长限制,不等于整个应用传输必须只有这么多字节;控制器和驱动可以把较大的传输拆成多个包。Microsoft:Bulk 端点
原示例有一个必须修复的容量错误:
/* 错误结构的容量分析,不能作为发送实现复制。 *//* 缓冲:512 字节;帧头:4 字节;样本:512 个 uint16_t。 *//* 所需容量:4 + 512 * 2 = 1028 字节,已经超过 512 字节。 */对于 4 字节帧头、512 字节应用缓冲,最多装:
个 16 bit 样本。采用新协议后应按其新帧头重新计算,不沿用这里的 254。最可靠的方法是集中定义容量并在编码入口检查。
5. H7 的 DMA 与缓存
跳转到“5. H7 的 DMA 与缓存”STM32H743 的 L1 数据缓存和指令缓存分别为 16 KB。缓存行大小、DMA可访问地址和 USB缓冲位置是不同问题;不能把“缓存容量”和“对齐字节数”混为一谈。
CPU 与 DMA 共享可缓存内存时可能看到不同数据。可以为共享缓冲安排合适的不可缓存区域,或按传输方向执行缓存维护。CPU写、DMA读与DMA写、CPU读需要分别处理;缓存维护覆盖的区间还应遵守缓存行边界,避免影响相邻对象。ST AN4839:缓存与数据一致性
aligned(32) 表示 32 字节对齐。它只解决对齐,不会自动让内存不可缓存,也不会自动执行 clean/invalidate。USB无法枚举时还要排查时钟、供电、PHY、描述符、低层初始化和接收流程;不能只凭“开启 DMA 后失败”就确定唯一原因。
6. 配置、启停与能力声明
跳转到“6. 配置、启停与能力声明”建议使用以下状态:
stateDiagram-v2 [*] --> Idle Idle --> Configured: 有效配置成功应用 Configured --> Running: 启动成功 Running --> Configured: 停止并完成清理 Running --> Fault: 采集故障或不可恢复溢出 Fault --> Idle: 明确复位或重新初始化| 操作 | 应明确的行为 |
|---|---|
| GET_INFO | 报告真实硬件能力、固件/协议版本、已实现功能 |
| AI_CONFIG | 检查通道/量程/采样率,成功后才改变当前配置 |
| AI_START | 分配采集会话号,复位样本计数,按约定顺序回应与产生数据 |
| AI_STOP | 停采样,决定排空还是丢弃待发数据,再报告状态 |
| AI_READ | 只有实现了请求式读取才支持;连续推送模式不要假装已实现此命令 |
| AO_WRITE | 只有真实 DAC/模拟前端和驱动已经实现时才支持 |
| DIO_CONFIG/READ/WRITE | 校验方向、有效位和硬件能力;无此功能就明确拒绝 |
原例中的 ADC_Configure() 只有注释和结构体复制,这不会改变定时器频率、ADC 通道或前端量程。主机看到“配置成功”之前,这些动作必须完成并检查错误。
6.1 定时器采样率推导
跳转到“6.1 定时器采样率推导”在定时器向上计数、更新事件作为触发、每个更新触发一次序列的条件下:
例如实际定时器输入为 240 MHz,PSC=0、ARR=2399,触发频率才是 100 kHz。必须使用实际定时器时钟,而不是直接拿 CPU 主频;中心对齐、重复计数器和不同触发事件也会改变关系。
多通道扫描还要检查每次序列的转换时间是否小于触发间隔。请求采样率可表示为整数,不代表硬件一定能精确实现它;响应可以报告实际采样率。
6.2 ADC 码值如何换成电压
跳转到“6.2 ADC 码值如何换成电压”转换关系由 ADC 编码和模拟前端决定。若使用经过端点标定的无符号码值模型:
这是一种指定了端点映射的模型。理想量化器的 LSB 常按满量程跨度除以 定义;二者用途不同。双极性二补码、偏移二进制、前端增益和偏置需要各自的公式。不能把 STM32 ADC 的任意输入直接解释为 ±10 V,也不能把“16 bit 输出”当成16 bit有效精度。
7. 描述符与 Windows 接入
跳转到“7. 描述符与 Windows 接入”描述符应和实际固件一致:接口数、端点地址/类型、FS/HS 最大包长、字符串、设备能力都应逐项核对。若支持不同运行速度,不能让 HS 配置函数继续返回只含 FS 包长的配置。
对于 WinUSB:
- 开发调试可在确认设备/接口后使用合适的驱动安装工具;替换的是选定接口的驱动绑定。
- 产品化可实现 Microsoft OS 描述符自动绑定,或按目标 Windows 版本提供自定义 INF 与签名目录。
USBDevice的系统安装类 GUID 与应用查找设备使用的接口 GUID 不同;应为应用接口生成自己的 GUID。- VID/PID 必须来自设备制造方合法分配的标识,不应把 ST 示例中的 VID/PID 直接用于自有成品。
只有一个短 BOS 字节数组并不构成完整的 Microsoft OS 描述符实现;还需要对应能力、请求响应和兼容 ID/接口信息。Microsoft:WinUSB 安装与 INF
8. 分阶段验证
跳转到“8. 分阶段验证”| 阶段 | 验证内容 | 应留下的证据 |
|---|---|---|
| 枚举 | 速度、配置、接口、端点、驱动绑定 | 描述符导出、系统版本、板卡/固件版本 |
| 固定数据传输 | 已知字节模式双向传输 | 长度、内容校验、超时和断开记录 |
| 协议 | 分片/合并输入、非法长度、错误命令、序号 | 自动测试结果 |
| ADC 接入 | 已知信号、每通道样本率、通道顺序 | 原始样本与测量条件 |
| 压力与故障 | 主机慢读、USB断开、重连、队列满 | 丢样计数、状态转换、恢复结果 |
| 模拟性能 | 噪声、增益/偏置、频响、抗混叠、同步 | 校准与实验报告 |
可以先做 CDC 回环或 ST 官方板卡示例验证链路,再逐步接入自定义协议和真实采样。开发时间应根据这些验收结果安排,不能只承诺固定几周完成。
9. 原长文的修订范围
跳转到“9. 原长文的修订范围”原笔记的 USB 分层、商业产品比较、固件模块、命令表、ADC/DMA 数据流、Python/C++ 上位机与安装步骤均已按主题重新组织。已知有越界、缓冲生命周期、虚报功能或不完整描述符的问题,没有继续以可运行代码呈现。协议与上位机页提供可离线验证的实现;实际 STM32 板级工程仍需依据选定硬件完成并验证。
原始来源:USB 的理解与 STM32 DAQ 草案。历史全文由笔记迁移记录关联的 Git 提交保留。