跳转到内容
新建笔记

STM32 USB DAQ:采样、缓冲、固件架构与验证

本篇讨论如何把 STM32 的采样、缓冲与 USB 传输组织成可验证的数据采集系统。适用对象是准备实现自定义 USB DAQ 的开发者。它是一份设计与移植指南:板级时钟、ADC 前端、引脚、USB PHY 和固件中间件必须按实际硬件完成,不能把示例拼接成“已经在所有 STM32H7 上验证的完整工程”。

通用 USB 概念见 USB 基础,命令和上位机实现见 USB DAQ 协议与上位机。

至少先定义以下参数:

参数要明确的问题
通道单端或差分、同步或扫描、有效通道数
采样率每通道采样率,还是所有通道合计?
数值格式12/16/24 bit ADC,实际传输占几个字节,是否有符号
量程ADC 引脚范围、前端增益/偏置、校准参数
连续性允许停止采样、丢样,还是必须在指定时间内完整保存?
时序采样时钟、触发条件、最大允许时延和主机积压
控制功能仅 AI,还是还要 AO、DI、DO、计数器?

以 4 通道、每通道 50 kS/s、每样本传输 2 字节为例:

R=4×50,000×2=400,000 B/s.R=4\times50{,}000\times2=400{,}000\ \mathrm{B/s}.

若主机最多连续 T=0.1T=0.1 s 没有取走数据,仅样本就积累:

M≥RT=40,000 B.M\geq RT=40{,}000\ \mathrm{B}.

再计帧头、调度余量和队列管理开销,才能确定实际缓冲大小。如果平均输出吞吐低于输入吞吐,增加缓冲只能延迟溢出,不能解决问题。

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 峰值速率。

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 描述符、收发缓冲、流控和业务分帧
厂商自定义接口 + WinUSBWindows 用户态应用可直接访问端点驱动绑定、接口 GUID、端点查询、应用协议
自定义接口 + libusb/PyUSB可组织跨平台用户态访问各系统驱动/权限、libusb 后端和资源释放
标准 Audio 等设备类可接入对应系统软件生态类规范中的格式、时钟、描述符和同步要求

CDC 的数据端点也可以采用 Bulk,因此“先用 CDC,再切换成 Bulk”不是严格的分类说法。真正改变的可能是设备类、接口描述符和主机 API。

WinUSB 自动绑定需要设备提供相应 Microsoft OS 描述符与 WINUSB 兼容 ID。厂商类号 0xFF 本身不足以触发自动安装。Microsoft:WinUSB 自动绑定

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完成块和硬件错误
缓冲管理谁拥有哪块内存、何时可覆盖、队列满时如何处理
协议层固定宽度字段、长度与字节序、命令响应和数据帧
业务状态机配置合法性、启停顺序、能力声明和故障状态

半满回调表示前半缓冲可供消费;完成回调表示后半缓冲可供消费。循环 DMA 接着会覆盖旧区域,消费者必须在下一次覆盖之前复制或完成处理。

如果每半块有 NN 个样本、总样本率为 ftotalf_{\mathrm{total}},可用时间约为:

Thalf=Nftotal.T_{\mathrm{half}}=\frac{N}{f_{\mathrm{total}}}.

例如半块 512 点、总采样率 200 kS/s,约每 2.56 ms 产生一块;“有双缓冲”并不意味着可以无限等待 USB。

回调中应记录块索引、样本序号和错误,并通知主循环或任务。是否直接使用 DMA 区域、复制到发送池或使用更多缓冲,取决于上述时间预算。不要在中断里等待 USB 完成,也不要忽略队列满或发送忙状态。

很多 STM32 USB 中间件的设置发送缓冲接口保存的是指针,传输在之后完成。ST CDC 实现可以看到 TxBuffer、TxState 与低层发送调用;返回成功表示提交被接受,不等于硬件已经发送完毕。ST USB CDC 源码

因此应遵循:

  1. 编码到一块由发送池持有的缓冲。
  2. 提交成功后标记为 USB 正在使用。
  3. 在对应发送完成事件后归还。
  4. 发送忙时保留待发数据或显式报告丢弃,不能悄悄覆盖。

不能把函数局部 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 字节应用缓冲,最多装:

⌊512−42⌋=254\left\lfloor\frac{512-4}{2}\right\rfloor=254

个 16 bit 样本。采用新协议后应按其新帧头重新计算,不沿用这里的 254。最可靠的方法是集中定义容量并在编码入口检查。

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 通道或前端量程。主机看到“配置成功”之前,这些动作必须完成并检查错误。

在定时器向上计数、更新事件作为触发、每个更新触发一次序列的条件下:

ftrigger=ftimer(PSC+1)(ARR+1).f_{\mathrm{trigger}}=\frac{f_{\mathrm{timer}}}{(PSC+1)(ARR+1)}.

例如实际定时器输入为 240 MHz,PSC=0、ARR=2399,触发频率才是 100 kHz。必须使用实际定时器时钟,而不是直接拿 CPU 主频;中心对齐、重复计数器和不同触发事件也会改变关系。

多通道扫描还要检查每次序列的转换时间是否小于触发间隔。请求采样率可表示为整数,不代表硬件一定能精确实现它;响应可以报告实际采样率。

6.2 ADC 码值如何换成电压

跳转到“6.2 ADC 码值如何换成电压”

转换关系由 ADC 编码和模拟前端决定。若使用经过端点标定的无符号码值模型:

V=Vmin⁡+code2N−1(Vmax⁡−Vmin⁡).V=V_{\min}+\frac{code}{2^N-1}(V_{\max}-V_{\min}).

这是一种指定了端点映射的模型。理想量化器的 LSB 常按满量程跨度除以 2N2^N 定义;二者用途不同。双极性二补码、偏移二进制、前端增益和偏置需要各自的公式。不能把 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

阶段验证内容应留下的证据
枚举速度、配置、接口、端点、驱动绑定描述符导出、系统版本、板卡/固件版本
固定数据传输已知字节模式双向传输长度、内容校验、超时和断开记录
协议分片/合并输入、非法长度、错误命令、序号自动测试结果
ADC 接入已知信号、每通道样本率、通道顺序原始样本与测量条件
压力与故障主机慢读、USB断开、重连、队列满丢样计数、状态转换、恢复结果
模拟性能噪声、增益/偏置、频响、抗混叠、同步校准与实验报告

可以先做 CDC 回环或 ST 官方板卡示例验证链路,再逐步接入自定义协议和真实采样。开发时间应根据这些验收结果安排,不能只承诺固定几周完成。

原笔记的 USB 分层、商业产品比较、固件模块、命令表、ADC/DMA 数据流、Python/C++ 上位机与安装步骤均已按主题重新组织。已知有越界、缓冲生命周期、虚报功能或不完整描述符的问题,没有继续以可运行代码呈现。协议与上位机页提供可离线验证的实现;实际 STM32 板级工程仍需依据选定硬件完成并验证。

原始来源:USB 的理解与 STM32 DAQ 草案。历史全文由笔记迁移记录关联的 Git 提交保留。