ADC 与传感器驱动
本文性质声明:本机无开发板,全部内容未实测,为手册级框架。站内对照:dbg-data-layout(数据布局与缓存的本机实测)、emb-bus(DMA 环形缓冲)、emb-gpio-timer(定时器与 DMA 联动)。
一、ADC 的三个约束:采样率、分辨率、参考电压
ADC 的本质是”用 Vref 量输入”,三个参数互相锁死:
- 分辨率(8/10/12/16 位):数字量的满量程 = Vref。“12 位 ADC”的输出
4095只意味着”输入 ≈ Vref”,不是”4.095V”——Vref 用 3.3V 还是内部 1.2V 基准,同一物理量读出的数差一倍。内部基准省事但精度差(温漂),精密测量用外部基准。 - 采样率:每秒多少次转换。约束来自源阻抗和采样保持电容(源阻抗大,电容充电慢,读数偏低——手册会给”最大源阻抗”)和每次转换的时钟周期。
- 噪声与有效分辨率:标称 12 位不等于 12 位可用——电源纹波、参考漂移、数字噪声让最低几位乱跳。判断标准是”多次采样同一稳定输入的抖动幅度”,不是数据手册的位数。
工程换算(最常用的一条):物理值 = 读数 × Vref / 2^N,再减去零点误差、乘增益系数——这两个系数来自校准(短接时的读数、标准源时的读数),出厂校一次写进 Flash。不校准的 ADC 通道之间能差 5%。
二、DMA 采集与滑动平均
软件轮询采样在高采样率下把 CPU 拖死;中断 + DMA 是标准解:
uint16_t adc_buf[256];
HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc_buf, 256); // DMA 循环填满 256 个半字
// 半传输/全传输中断里置标志,主循环按标志取"新的一半"处理
双缓冲(半中断)模式:DMA 填前一半时主循环处理后一半,反之亦然——零丢失 + 处理时间与采集重叠。这是 emb-bus 的 DMA 环形在 ADC 上的应用。
滤波四件套按复杂度排序:
- 算术平均:N 次求和除 N——简单,但毛刺被平均进去而不是剔除。
- 滑动平均(FIR):保留最近 N 个值滚动平均——延迟 = N/2 个采样周期,实现是环形缓冲 + 累加减。
- 中值滤波:取窗口内中位数——剔除脉冲毛刺的首选(ADC 偶发尖刺、传感器偶发野值),代价是排序开销。
- 一阶低通(IIR):
y = y + α(x - y),α = 采样周期/(时间常数+周期)——一个乘法一个加法, smoothest 的信噪比/复杂度比,嵌入式滤波的默认选择。
别在中断里做重滤波:滤波放主循环/任务,中断只负责”把数据 DMA 走”——本站 dbg-log 量过”级别短路放在格式化前后差 900 倍”,同族思想:中断里越轻越好。
三、传感器驱动:上电-初始化-量测状态机
数字传感器(I2C/SPI 接口)的驱动不是”读个数”,是三段状态机:
上电 → [等稳定期 t_startup] → 软复位/自检 → 读 WHO_AM_I/ID 校验
→ 写配置(量程/速率/滤波器)→ 回读校验 → [等首次转换 t_conv]
→ 稳态量测循环(读-换算-滤波-发布)
四段各自的坑:
- 上电稳定期:手册给的是最小值,实际板子上电和传感器上电有时序差——没等够就通信,表现是 ID 偶发读错。
- ID 校验:读芯片标识寄存器比对——不校验的驱动在传感器型号变更/通信故障时静默返回垃圾。校验失败要能上报(状态机进 fault,不是继续读数)。
- 配置回读:写进去的配置寄存器读回来比对——SPI/I2C 的位错、拉伸期的时钟毛刺在这一步现形。
- 转换等待:连续转换模式读结果寄存器的节奏要和
t_conv匹配——读太快要嘛拿到旧值要嘛触发时钟拉伸(I2C)。
量测循环的节拍用定时器驱动(emb-gpio-timer 的时基),任务式结构:定时器置”该采样了”标志 → 任务里读 + 滤波 + 发布。发布 = 更新一个带时间戳的结构体 + 置版本号,消费者无锁读快照(conc-patterns 的 COW 快照思想)。
四、驱动分层:HAL 与寄存器的取舍
| 层 | 做法 | 优点 | 代价 |
|---|---|---|---|
| 寄存器直写 | GPIOA->ODR = ... | 无隐藏、最快、最好调 | 换芯片 = 重写 |
| 厂商 HAL(STM32 HAL 等) | HAL_ADC_Start_DMA(...) | 换芯片改动小、外设初始化省事 | 黑盒、开销、版本坑(HAL 的 bug 和 API 变迁是日常) |
| 自建薄 HAL | 自己包一层 adc_read_channel() 底下 HAL 或寄存器 | 业务代码不碰硬件 | 要维护 |
实践的混合:启动/时钟/中断向量这些”一锤子”代码用厂商库省事;数据通路(ADC 批量读、SPI 高频传输)用寄存器+DMA 自己写——热点路径的每一微秒都值钱,冷路径的可移植性值钱。业务代码只依赖自己那层:换 MCU 时重写的是薄 HAL 的底部,不是全部。
五、排障索引
| 症状 | 入口 |
|---|---|
| ADC 读数偏低且随源阻抗变 | 采样时间不够(第一节);加大采样周期或加运放缓冲 |
| 最低几位乱跳 | 有效分辨率问题(第一节);参考源和地线的处理 |
| DMA 采集丢数据 | 半中断处理太慢;缓冲区太小;DMA 与 CPU 访存冲突 |
| 传感器 ID 偶发读错 | 上电稳定期不够;I2C 拉伸没处理(emb-bus) |
| 读数有规律毛刺 | 和 PWM/继电器同步 → 采样时机避开开关动作;中值滤波 |
| 换芯片移植痛苦 | 业务代码漏到了 HAL 层;检查依赖方向(engineering-arch 的依赖方向) |
六、未实测清单
- 本文所有 ADC/传感器行为——无开发板,未测。
- 有效分辨率的实测方法——未测(原理给出,数值以你的板子为准)。
- 各 HAL 库的版本差异——未对比。
- 用户 Linux 开发机上的项目传感器——该机不可达,未涉及。