1. 从项目标题看本质:为什么非要用“UART口ADC + Cortex-M4”的组合
拿到“ADS122U04IPWR 与 STM32F411RE 做工业和 HVAC 温度测量”这个标题,我先拆解一下:ADS122U04IPWR 是一颗 24 位 Δ-Σ ADC,UART 接口;STM32F411RE 是一颗 Cortex-M4F 内核 MCU,主频 100MHz,512KB Flash、128KB SRAM。这两个芯片放在一起,解决的不是“能不能测温度”的问题,而是“在工业/HVAC 现场环境下,怎样把毫伏级温度信号稳定、准确、低延迟地变成远程上位机可读的数据”的问题。
这类项目在暖通空调控制器、冷水机组、锅炉房温度采集、楼宇自控系统里太常见了。现场走线往往几十米甚至上百米,探头分布在风管、水管、室外机、机房各个角落,电磁干扰源又多:变频器、水泵电机、接触器吸合瞬间的浪涌,随便一个都能让未经验证的温度采集方案产生几度的跳动。选 ADS122U04 的最大理由,就是它的 UART 接口。相比 SPI、I²C 这类同步串行接口,UART 是异步差分传输,线缆要求低,抗干扰能力强,传输距离长,而且可以和 RS-485 收发器直接配合,随便一对双绞线就能把多块 ADC 挂在总线上。这就是把 ADC 芯片做成“传感器节点”的最佳形态。
至于 STM32F411RE,它在整个系统的位置是“主控制器”。F411 算 STM32F4 家族里的均衡选手:性能比 F1 强不少,但又不至于像 H7 那样贵。内部的硬件 USART 可以开 DMA,接收 ADC 上传的数据帧时几乎不占 CPU;512KB Flash 装下完整的温度标定查找表、滤波算法和 MODBUS 协议栈也够用;128KB SRAM 跑实时操作系统或者裸机状态机都绰绰有余。工业产品不是做毕设,核心诉求是“长时间稳定跑不重启”,F411 的成熟度、供货量、Flash/SRAM 容量,都正好卡在性价比甜点上。
如果你也是第一次接触这类“串口型 ADC”的方案,我建议你把这个项目理解成:传感器侧用一块带 UART 接口的高精度 ADC 做模拟前端,MCU 侧用 DMA 接收数据帧,温度换算、校准、越限报警等逻辑全部交给嵌入式软件处理。这套架构最大的好处是传感器和主机之间解耦,探头坏了换探头,ADC 节点挂了不影响总线上的其他节点。下文我会把硬件设计、UART 协议、寄存器配置、温度算法和实际调试中踩过的坑全部分享出来,目标是让有单片机基础的工程师照着做,一周内能跑通两路 PT100 的实时测温。
2. 传感器选型与硬件设计:一张图看懂模拟前端
2.1 工业测温里,RTD、热电偶、NTC 到底选谁
标题只写了“工业和 HVAC 应用中的温度”,并没有限定传感器类型,所以这正好是第一个要决策的点。我在实际项目里通常按测温范围和成本拆成三个阵营:
- RTD(PT100/PT1000):-200℃ 到 600℃ 范围,线性度最好,长期稳定性最好,HVAC 的水温、风温、回风温度检测基本都选它。缺点是价格偏高,且需要激励电流源,三线制/四线制接线才能抵消导线电阻。
- 热电偶(K 型、T 型):测温上限高,成本低,但输出是微伏到毫伏级信号,冷端补偿麻烦,HVAC 场景一般很少用,工业窑炉、排烟温度才会优先考虑。
- NTC 热敏电阻:便宜、灵敏度高、响应快,25℃ 时阻值几 kΩ 到几十 kΩ,适合 HVAC 室内温控器这种近距离、精度要求不苛刻的场景。缺点是 B 值离散性大、温度上限通常 150℃ 以下。
ADS122U04 这四路输入、两个可编程激励电流源(IDAC)、内置 PGA,说穿了就是为了兼容以上三类信号设计的。但做产品要克制,不要妄图一个板上啥都能接。我建议把 RTD 作为这套系统的主力,因为 HVAC 场景下精度要求通常就是 ±0.5℃ 以内,PT100 配合 24 位 ADC 很容易达到;同时把 ADC 的第二路输入留给 NTC,作为风管湿度补偿用的辅助温度;热电偶采集则留给另一块从板,避免模拟前端互相干扰。这个思路在工程上最稳妥。
2.2 激励电流、PGA 增益和比率测量:把“为什么这么配”讲清楚
ADS122U04 内部有两个 IDAC,可以输出 50µA 到 1500µA 的激励电流。测 PT100 标准做法是:IDAC1 电流流过 RTD,再流到基准电阻 RREF,最后回到 IDAC2。RTD 两端的电压进入 ADC 差分输入,RREF 两端的电压作为外部参考电压 REFP/REFN。这种接法叫“比率测量”,核心好处是激励电流的大小波动、温漂对测量结果的影响会被同时抵消掉。你不需要一个高精度恒流源,只需要一只温漂系数低于 10ppm/℃ 的精密电阻 RREF,成本能降一大截。
具体参数怎么定?我举一个实际配置:PT100,测温范围 -40℃ 到 150℃,选 IDAC = 500µA,PGA 增益 = 8,外部基准 RREF = 2.49kΩ(1kΩ 会不够,见后面的计算)。按照 PT100 的 Callendar-Van Dusen 公式,150℃ 时阻值约 158.84Ω,压降 = 0.5mA × 158.84Ω = 79.42mV;-40℃ 时阻值约 84.27Ω,压降 = 42.14mV。ADS122U04 内部参考默认 2.048V,但用了外部基准后,满量程差分输入电压 = Vref / Gain = (0.5mA × 2.49kΩ) / 8 = 155.6mV。也就是说,-40℃ 到 150℃ 的信号摆幅只占 ADC 量程的 27%~51%,余量很充裕,就算接线引入 2Ω 接触电阻也不会顶到饱和。激励电流不是越大越好,500µA 是自热和信噪比之间的平衡点:电流每增加 1 倍,RTD 自身发热功率增加 4 倍,在 0℃ 冰点校准的时候,自热造成的温升可能直接吃掉 0.1℃ 的精度。
再看 ADC 的分辨率能不能覆盖 0.01℃ 级别:24 位数据,LSB = Vref / (Gain × 2^23)。按 Vref = 1.245V(0.5mA × 2.49kΩ)、Gain = 8 计算,LSB ≈ 18.5nV;对应到 PT100 的电阻灵敏度约 0.385Ω/℃,换算成温度分辨率大约 1.48e-5℃/LSB。这个数值远远超过实际需要,真正的瓶颈是噪声、参考电阻温漂和传感器自身精度,而不是 ADC 位数。那些“24 位 ADC 测温度一定准”的说法,恰恰是新手最容易踩的误区。
2.3 PCB 布线与电源处理:比写代码更早决定成败
ADS122U04 是 Δ-Σ ADC,对模拟电源和数字电源的噪声都很敏感。我的板子上做了三个关键处理:一是 AVDD 和 DVDD 分别供电,中间串磁珠,模拟地和数字地单点连接,ADC 下方不要铺数字地平面;二是 REFP/REFN 差分走线,周围包地,远离 USART 的 TX/RX 走线,因为 UART 信号跳变沿的电流冲击会通过寄生电容耦合到高阻抗参考输入上,造成转换值周期性跳动;三是 RTD 激励电流的走线尽量短且宽,减少铜箔电阻的温漂误差。
还有一个细节:ADS122U04 是 TSSOP-16 封装,芯片底下没有散热 pad,热耦合到 RTD 探头的量其实很小,但 PCB 上不要把它放在发热元件(比如 DC-DC 电感、LDO)旁边。HVAC 控制器机箱里经常有变压器和继电器,这类强干扰源离 ADC 尽量远,必要时用屏蔽罩把模拟区罩起来。硬件上这些小习惯,会让后面的调试省掉一半时间。
3. UART 通信与驱动设计:不靠 SPI,关键帧格式要搞透
3.1 ADS122U04 的 UART 帧:地址、命令、数据、CRC 一个都不能少
ADS122U04 与 MCU 的通信没有标准寄存器堆那种“拉低片选、写寄存器”的直观操作,而是走命令帧/响应帧。一般帧由四部分组成:器件地址字节、命令/寄存器标识字节、数据字节(写寄存器时有),最后是 CRC8 校验字节。地址字节的低 4 位是器件地址,通过 ADDR1/ADDR0 引脚配置;高 4 位用于寄存器选择或者命令类型。某一条典型命令是“写 CONFIG0 寄存器”:主机连续发送“器件地址 + 寄存器号 + 配置值 + CRC”,ADC 收到后校验通过,回一帧确认,下一次转换周期就应用新配置。
我强烈建议拿到芯片后第一步不要写业务代码,先用一个 USB 转 TTL 的调试工具,接上 ADC 的 TX/RX,手动敲十六进制命令,确认芯片能正常回数据。这个习惯帮我避免了很多“程序没问题但硬件不通”的盲区。比如我最开始把地址引脚接反了,导致命令总是发给“不存在的设备”,ADC 完全没有任何响应。一旦确认数据链路通了,再往下写 STM32 驱动就很快。
CRC 多项式是 0x07,和常见的 CRC-8/ITU(0x31)不一样,不能用现成的 CRC8 表格直接套。我在这块栽过一次:程序里复用了 I²C 校验的查表法,结果每次读回来的数据都报 CRC 错误,后来对照数据手册逐位推了一遍才发现多项式弄错了。驱动里记得单独写一个“ADS122U04 专用 CRC8”,初始值、输出异或值都按手册定义来。
3.2 STM32F411RE 的接收状态机与 DMA 配置
MCU 侧我用 USART2 接 ADS122U04,115200bps 或 19200bps 都可以,取决于 ADC 内部的波特率配置。要注意这个芯片的波特率来源于内部振荡器,标称精度一般,长帧在高波特率下偶尔会出错,所以工业现场我反而选低波特率,19200bps 更稳,一个 5 字节的响应帧也就 2.6ms,完全够用。
接收侧的核心思路是 DMA + IDLE 中断。配置 USART2 的 RX DMA 到一块 32 字节的环形缓冲区,开启总线空闲(IDLE)中断。ADC 每次转换完会主动推一帧,MCU 收到一帧后总线空闲,进入中断,此时 DMA 剩余的传输计数就是帧长度。这种方式比逐字节进中断高效得多,CPU 占用几乎为零。代码如下:
// 假设 DMA1_Stream5 是 USART2_RX,缓冲区 recv_buf[32] void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_IDLE) != RESET) { // 读 SR 再读 DR,清 IDLE 标志 (void)USART2->SR; (void)USART2->DR; // 计算帧长:32 - DMA_GetCurrDataCounter(...) frame_len = 32 - DMA_GetCurrDataCounter(DMA1_Stream5); if (frame_len >= 3 && frame_len <= FRAME_MAX) { pending_frame = 1; } DMA_Cmd(DMA1_Stream5, DISABLE); // 重置 DMA 传输计数到缓冲区起始位置 DMA_SetCurrDataCounter(DMA1_Stream5, 32); DMA_Cmd(DMA1_Stream5, ENABLE); } }业务层则是一个有限状态机:收到完整帧后先解析地址,再校验 CRC,通过后把 24 位原始数据拆出来,置“数据就绪”标志,供上层温度换算任务轮询。如果连续 3 帧 CRC 错误,我会上报一次通信故障事件,并重新初始化 UART。千万不要在主循环里做阻塞等待,否则 ADC 的连续转换模式会把 CPU 时间全部吃掉。
3.3 寄存器配置的“实战参数表”与注意事项
ADS122U04 有几个关键寄存器需要初始化:配置输入多路开关(选哪两个引脚作为差分输入)、PGA 增益、数据速率、IDAC 电流档位、转换模式(单次还是连续)、基准源选择。我基于 PT100 + 三线制 + 连续转换模式,贴一份参考配置值:
| 配置项 | 设置值 | 说明 |
|---|---|---|
| 输入通道 | AIN0 = +IN, AIN1 = -IN | RTD 差分输入 |
| PGA 增益 | 8 | 满足 -40℃ 到 150℃ 量程 |
| 基准源 | 外部 REFP/REFN | 接 RREF,实现比率测量 |
| IDAC1 电流 | 500µA | 激励电流,流向 RTD |
| IDAC2 电流 | 500µA | 回流电流,流向 RREF |
| 数据速率 | 20SPS(或按滤波需求) | 20SPS 时工频抑制较好 |
| 转换模式 | 连续转换 | 适合实时采集 |
具体的位域赋值请一定以你拿到芯片的最新版数据手册为准,不同批次、不同后缀(IPWR 代表封装和温区)的配置字可能有更新。我的做法是:在工程中建一个ads122u04_config.h,把所有配置值用宏定义封装,方便后续移植。
还有一个容易忽略的坑:如果用了外部基准,CONFIG 寄存器里必须把基准源明确选为外部,否则芯片缺省用内部 2.048V 参考,你测量的电压范围会变,数据就会整体偏差。这个错误发生得很隐蔽,因为芯片不会报错,只是转换结果莫名其妙地“不对”。我当时排查了很久,最后是逐位读回寄存器才发现基准选择位没写对。
4. 温度计算与校准:从原始码到 0.1℃ 的最后一公里
4.1 原始码换算成电压和电阻
假设你从 ADC 读回一个 24 位补码数据 raw,首先要判断符号:最高位是 1 表示负电压,要手动扩展符号位。然后把 raw 乘上 LSB 电压,得到差分输入电压。LSB 的计算式是:
LSB = Vref / (Gain × 2^23)
例如 Vref = 1.245V、Gain = 8,则 LSB ≈ 18.5nV。代码里为了效率,我不用浮点数乘除,而是直接用 64 位整型算出“电压×10^9”的纳伏值,再做除法。RTD 的电阻值 = 差分电压 / 激励电流。由于是比率测量,如果 RREF 和 RTD 在同一个电流回路里,其实可以直接用码值比例算电阻,避免浮点:
R_rtd = RREF × raw_rtd / raw_ref
其中 raw_ref 是 RREF 两端电压数字化后的码值。前提是 RREF 的温漂足够小,我选的是 10ppm/℃ 的金属膜电阻,零下 20℃ 到 60℃ 的环境变化对结果影响小于 0.04%。
4.2 RTD 非线性修正:CVD 公式与查表法
PT100 的电阻-温度曲线不是绝对线性,0℃ 以上通常用二项式就能做到不错的效果,更严谨的是 Callendar-Van Dusen 方程:
- 当 t ≥ 0℃ 时:R(t) = R0 × (1 + A·t + B·t²)
- 当 t < 0℃ 时:R(t) = R0 × (1 + A·t + B·t² + C·(t-100)·t³)
标准系数 A = 3.9083×10⁻³,B = -5.775×10⁻⁷,C = -4.183×10⁻¹²。工程里两种实现路线:查表法(IEC 60751 标准分度表 + 线性插值)适合 MCU,因为数组可以预先生成,代码里直接二分查找;直接公式法适合量程不大、对 FLASH 占用敏感的场景。我在 F411 里用的是查表,-40℃ 到 150℃ 每 1℃ 一个点,共 191 个点,占用不到 400 字节,插值后的误差小于 0.01℃。
有个细节值得提醒:查表基准必须和传感器等级一致。工业 PT100 有 AA 级、A 级、B 级之分,A 级允差是 ±(0.15 + 0.002·|t|)℃,B 级是 ±(0.30 + 0.005·|t|)℃。如果你的传感器本身是 B 级,ADC 算得再准也没有意义,整体误差会被探头吃掉。楼主位项目描述里没提传感器精度,但做 HVAC 温控器,我一般直接定 A 级 PT100,价格差异很小,带来的客户投诉差异却很大。
4.3 现场校准:零点和斜率漂移的修正方法
硬件焊好之后不要急着标定。我的标准流程是:先用高精度电阻箱替代 RTD 接入板子,分别给 100Ω(0℃)、138.51Ω(100℃)、158.84℃(150℃)三个值,记录 ADC 原始码,建立一条直线“码值→电阻”,得到实际增益和偏移,写进 Flash 的校淮区。这一步能修正 RREF 的初始误差、PCB 导线电阻、差分放大器的输入偏置电压。
然后做冰点槽和沸水槽验证:把 PT100 探头插入 0℃ 冰水混合物和 100℃ 沸水,读数应该在 0.0℃ ± 0.1℃ 和 99.9℃ ± 0.2℃ 内(受气压影响沸点略低,我这个只做定性检查)。校准数据要设计成可在线更新,我用的是 F411 内部 Flash 末尾的一个扇区,上电时读取,配合一个简单的校验和。产线校准时通过串口命令下发三点校准值,避免每台设备都要改固件。
5. 实际调试中踩过的坑:噪声、自热、通信和地线相关的血泪史
5.1 转换值漂移和显示跳动,先怀疑自热噪声而不是算法
一次在现场调试时,ADC 读数总是周期性上下跳 0.3℃,起初我怀疑滤波算法不够。后来把探头捏在手里,跳变周期和手接触的节奏对上了,才发现问题出在 IDAC 电流设置太大,探头自身被加热了。当时为了追求“分辨率更高”把激励电流设成了 1mA,PT100 在 0℃ 时功耗 100µW,由于探头热阻约 300℃/W,内部温升直接来到 0.03~0.04℃,再加上空气流动波动,显示自然就抖了。解决办法是回到 500µA,同时软件里做一阶低通滤波,时间常数 0.5s 左右。HVAC 的风管温度本来就变化慢,不需要毫秒级响应。
5.2 UART 通信偶尔丢帧或 CRC 报错
这个现象在实验室基本复现不出来,一到现场就出现,原因往往是电磁干扰灌进了 UART 线。UART 是异步通信,空闲时 TX 为高电平,如果现场有变频器频繁启停,线缆等效天线会把差模噪声耦合进信号。解决办法有两层:硬件上,MCU 与 ADC 之间不要直接飞线,用带屏蔽的双绞线,屏蔽层单端接地;软件上,代码要支持自动重同步,而不是报错后死等。我写的状态机在连续收到错误帧时会主动清空接收缓冲、复位 DMA、重新发送一次“读取转换结果”命令,让通信恢复。实测在接触器动作的瞬间,偶尔丢一帧,但系统能在几十毫秒内自愈。
5.3 参考电阻的温漂成为精度瓶颈:一个容易被忽视的“隐形误差”
比率测量消除了激励电流的温漂,但 RREF 本身的温漂不会自动消失。曾经有个项目在北方冬天室外机柜里跑,白天误差正常,晚上温度降到 -20℃ 时,整机示值偏了 0.5℃。排查到最后,发现 RREF 用的是普通 1% 贴片电阻,温漂系数大约 100ppm/℃,在 -20℃ 到 25℃ 之间变化 45℃,电阻值变化 0.45%,换算成 PT100 电阻就是 0.45Ω,温度误差约 1.2℃。这个教训让我把 BOM 里这个电阻强制指定为 10ppm/℃ 以下的精密电阻,量产前必须做温度循环测试。有时候硬件设计里最不起眼的元件,才是系统精度的天花板。
5.4 三线制远比两线制实用:现场压降的实测对比
刚开始图省事,我把 PT100 接成两线制,认为“导线电阻很小,就算有 1Ω,校准一次也能消掉”。结果探头到板子距离一拉长,问题就来了:导线电阻会随温度变化,夏天和冬天相差好几欧姆,校准值跟着漂。改用三线制后,一根线专门用于补偿,IDAC 电流回路的压降不再叠加到测量回路中,实测在 50 米线缆下,读数稳定度从 ±1.2℃ 降到 ±0.1℃ 以内。如果你的系统允许走线根数多一点,请直接上三线制或四线制,这是工业测温性价比最高的一个改进项。
5.5 波特率不匹配导致的全是乱码
ADS122U04 的波特率由内部寄存器决定,我这块板子上一开始按 115200 跑,偶尔出现第一个字节丢失或者帧尾 CRC 错。后来示波器抓波形,发现 ADC 实际发送的位宽和标准 115200 有约 2% 的偏差,多字节帧累计误差会让接收端采到错误的位。降到 19200bps 后,位误差占比缩小,通信非常稳定。所以工业现场除非传输数据量真的大到必须高速,否则我建议 UART 口一律 19200 或者 9600。这不算保守,是被现场噪音教育出来的经验。
6. 后续还能怎么扩展:一套硬件吃下更多测点
这个方案的可扩展性很好。ADS122U04 的地址引脚支持在同一 UART 总线上挂多个 ADC,一个 F411 最多能管理 4 路 ADC 节点,每路 ADC 又是 4 通道差分输入,相当于一个主控可以覆盖最多 16 路温度测点。在 HVAC 项目里,通常需要同时采集供水温度、回水温度、室外温度、风管温度、机房温度,一套主控板 + 4 块 ADC 子板就够了。子板和主控之间走 RS-485 电平,抗干扰和传输距离都更可靠。
软件上还可以加一个简单的 MODBUS RTU 从站协议,让楼宇自控的 PLC 或者上位机直接通过 RS-485 读取温度值,这样就不需要额外写上位机协议。F411 的资源完全够用,因为温度采集本身的数据率不高,MODBUS 轮询 20ms 一次也不是问题。我自己在项目里就是这么扩展的:主循环把最新温度值存进一个共享结构体,MODBUS 线程读取时加临界区保护,PLC 端只需要配置寄存器地址就能看到每路温度、报警状态、传感器断线标志。整套系统从测温探头到上位机显示,形成了一个完整链路,后续换探头类型、加测点、改通信协议,都不用重新设计硬件。
最后分享一个小技巧:给每一路温度通道加一个“断线检测”逻辑,通过读回来的电阻值判断。PT100 开路时 ADC 读到的电压接近正满量程,短路时接近零,软件里设置上下阈值,一旦越限就把该通道标记为故障,在 MODBUS 寄存器里用一个位来表示。这个功能在 HVAC 现场特别实用,因为传感器线缆老化、端子松动是迟早的事。有了断线报警,运维人员才能第一时间发现哪一路在“假测温”,而不是等到系统误动作了才去查问题。