去年冬天给一栋商业楼宇的冷站做 AHU(空气处理机组)送回风温度改造,原来的方案是 NTC 热敏电阻加单片机内置 ADC,结果一到工况切换的时候送风温度误差能到 1℃上下,甲方意见很大。后面我换成了 TI 的 ADS122U04IPWR 这颗 24 位 ΔΣ ADC,搭配 STM32F030R8 这个 Cortex-M0 控制器,问题才算彻底解决。这套组合特别适合工业和 HVAC 应用里的多点高精度温度采集,ADS122U04IPWR 最大的特点是把 UART 接口做进了高精度 ADC 里,意味着传感器可以离控制器几十米甚至上百米远,依然能保持稳定的数字传输。这篇文章我会把选型思路、硬件设计、UART 协议、温度换算、现场抗干扰和调试验证完整记录一遍,给同样在搞高精度测温的朋友一个可以直接参考的工程样本。
1. 选型逻辑:24位UART式ADC和F030这颗M0为什么能搭到一块
1.1 内置ADC做温度测量的死穴
STM32F030R8 本身带 12 位 ADC,很多人第一反应是“既然已经有 ADC 了,为什么还要外挂一片”。
算一笔账就清楚了。F030 的内置 ADC 参考电压按 3.3V 算,12 位分辨率对应的 LSB 大约是 0.8mV。PT100 在 0℃附近的灵敏度是 0.385Ω/℃,如果激励电流用 1mA,温度每变化 1℃ 产生的电压变化只有 0.385mV,也就是约 0.5 个 LSB。也就是说,拿内置 12 位 ADC 直接测 PT100,一个字的跳变就相当于 2℃ 左右的误差,这还没有计算参考电压漂移、输入噪声和增益误差。
这种情况在 HVAC 场景里完全不够用。楼宇自控里水管供回水温度通常要求误差在 ±0.2℃以内,风管温度稍微宽松一点也得 ±0.5℃。要在这种精度下工作,24 位 ADC 不是“发烧配置”,而是基本门槛。
1.2 独立ADC的两大流派,为什么这次选UART
外挂高精度 ADC 主流有两种接口:SPI/I2C 和 UART。ADS1220、ADS1262 这类经典的精密 ADC 基本都是 SPI,MAX31865 这种 RTD 专用芯片走 SPI 的也很多。SPI 本身没有错,板级通信非常快,但如果传感器要放到现场,控制器放在配电间,SPI 的短板就很明显。
SPI 需要四条线(时钟、数据进、数据出、片选),时钟线和数据线在长距离传输时对寄生电容和时序非常敏感。I2C 虽然线少,但开漏结构、上拉电阻、总线电容限制让它的有效距离更短,而且在线缆上受到干扰后总线容易锁死。曾经我为了省成本试过用 I2C 接一个温湿度传感器放到 10 米外,结果是波形已经完全失真,通信时好时坏,从那之后我就对长距离 I2C 彻底死心了。
ADS122U04IPWR 把 UART 集成进 24 位 ADC,这点刚好踩在工业现场的需求上。UART 只有 TX/RX 两根线,可以非常方便地转成 RS485,用一对双绞线拉几百米没有问题。要做电气隔离也只需要两个数字隔离器或光耦,SPI 要隔离的话,四条线全部得处理,时序还要小心,成本高出不少。
| 对比维度 | SPI | I2C | UART转RS485 |
|---|---|---|---|
| 线缆数量 | 4根 | 2根+上拉 | 2根(差分) |
| 有效传输距离 | 板级为主 | 一般不超过1~2米 | 几十米到几百米 |
| 隔离成本 | 高 | 中 | 低 |
| 抗共模干扰 | 弱 | 弱 | 强(差分) |
| 多设备扩展 | 片选线多 | 地址冲突 | RS485地址或独占串口 |
1.3 STM32F030R8在这里的真实角色
F030 这颗芯片在很多人眼里只是“廉价的 M0”,但在这个项目里它反而非常合适。温度采集本质是慢速数据处理,48MHz 主频完全够用,真正需要的是足够多的 USART 外设和稳定的时钟源。STM32F030R8 提供多个 USART,我最后用了 4 片 ADS122U04IPWR,每片挂一个独立串口,结构上互不干扰,软件逻辑也简单。
另外一点是成本。做产品不是做毕设,BOM 成本直接决定方案能不能量产。ADS122U04IPWR 在独立 ADC 里性价比突出,STM32F030R8 在 M0 里也是价格便宜、供货稳定的型号。两者组合之后,单通道测温的成本比起用专用测温芯片加外围电路还低,精度却可以做到逐通道校准后 ±0.1℃。
2. 硬件设计:决定最终精度的不是ADC芯片,而是这几处细节
2.1 电源树设计:数字噪声是怎么混进温度读数的
ADS122U04IPWR 的模拟电源 AVDD 和数字电源 DVDD 都接 3.3V,STM32F030R8 也由同一路 3.3V 供电。起初我图省事,直接用一个 LDO 输出 3.3V 给整板供电,结果发现 ADC 转换码在小数点后第三位来回跳。
排查后发现,F030 的 GPIO 翻转,尤其是 USART 发送时,会在 3.3V 电源线上产生十几毫伏的毛刺。这些毛刺通过 ADC 的电源引脚进入内部参考电路,最终表现为转换码噪声。
解决方法是把电源分开走:
- LDO 输出后先经过磁珠,分成模拟 3.3V 和数字 3.3V 两路;
- ADC 的 AVDD 和 DVDD 就近放置 10μF 钽电容加 100nF 陶瓷电容;
- F030 的电源单独走数字 3.3V;
- 整个板的模拟地数字地单点汇合,避免大电流地环路。
实测改完电源布局之后,ADS122U04IPWR 的转换码噪声从原来的 ±10 个字降到了 ±2 个字以内。记住一句话:24 位 ADC 对电源纹波极其敏感,电源设计不好,再贵的 ADC 也白搭。
2.2 PT100 传感器接法:两线、三线、四线的工程取舍
RTD(热电阻)的接法直接决定测量误差的下限。ADS122U04IPWR 内置两个可编程激励电流源 IDAC1 和 IDAC2,可以很灵活地配置成两线、三线、四线制测量。
两线制最简单,把 RTD 两根线直接接到 AIN0 和 AIN1,IDAC 电流流过 RTD 后返回。问题在于两根引线的电阻全部串联进了测量回路。假设现场布线 10 米,铜线 0.5mm² 单根电阻大约 0.35Ω,两根就是 0.7Ω,折算到 PT100 上相当于 1.8℃ 的误差,完全不可接受。
三线制是用两个 IDAC 分别从 RTD 两端提供相同电流,其中一路经过引线电阻产生的压降会被另一路抵消。这种方案在 HVAC 风管温度采集里用得最多,能解决大部分现场的精度问题,前提是两根引线电阻尽量一致。
四线制是我在工业管道测温里坚持采用的方案。两根线提供激励电流,另外两根线作为 Sense 线接 ADC 高阻输入,Sense 线上几乎没有电流,所以引线电阻不会进入测量路径。四线制接线多两根,但对长线缆和精度要求高的场景,这是唯一让自己省心的接法。
2.3 STM32F030R8 最小系统容易被忽略的三个细节
F030R8 的最小系统看上去简单,BOOT0 下拉、NRST 上拉、SWD 四线引出、8MHz 外部晶振,但有几个细节会直接影响后续调试:
第一,外部晶振一定要加。ADS122U04IPWR 的 UART 通信依赖波特率同步机制,如果 F030 用内部 HSI 振荡器,默认精度不到 ±1%,同步失败的概率会明显增加。我量产板全部采用 8MHz 外部晶振,故障率低很多。
第二,USART 的 TX 和 RX 引脚选择要避开与 SWD 复用冲突。调试口和串口抢引脚是新手最容易踩的坑,建议直接参考芯片的数据手册引脚复用表,提前规划。
第三,电源去耦不能省。F030 的电源脚如果漏放电容,内部数字电路电流突变会通过电源网络干扰 ADC,这种问题到现场之后就变得极其难排查。
2.4 PCB布局的实做调整
这板子我在 PCB 布局上改了两版。
第一版我犯了典型错误:把 ADS122U04IPWR 和 F030 靠得很近,UART 走线在 ADC 下方横穿。结果虽然通信正常,但 F030 的串口信号通过地平面耦合到了 ADC 模拟输入端,测温结果始终有一个周期性的跳动。
第二版我做了几个调整:ADC 模拟输入走线放在 PCB 顶层,两侧包地;UART 信号走线远离模拟输入,必要时用内层地平面隔开;传感器接插件放在板边,靠近 ADC 输入引脚;磁珠尽量靠近 LDO 输出。改完之后,ADC 输出从周期性跳变恢复到稳定状态。
另外,模拟输入入口我串了 100Ω 电阻加 100nF 电容组成的低通滤波器。RC 滤波会略微增加信号建立时间,但 ADS122U04IPWR 最高只跑 20SPS,建立时间完全宽裕,而抗高频干扰的能力提升非常明显。
3. 打通UART协议:波特率同步、寄存器写入与数据读取
3.1 先把ADC和MCU对上“话”:波特率同步机制
ADS122U04IPWR 这类带 UART 接口的 ADC,内部并没有一个和 MCU 约定好的固定波特率频率。实际上它通过测量主机发送字节的位时序来锁定波特率,这就是所谓的自动波特率同步。
具体操作上,在 ADC 上电完成之后,主机需要连续发送同步字节,ADC 通过 UART 帧的起始沿和停止沿来推算位宽,锁定之后再开始正常的命令通信。我最初按手册推荐发送时延没有控制好,同步经常失败,读回的寄存器内容全是 0xFF。后来我改成发送时在字节之间加稳定的延时,并且连续发至少 20 个同步字节,成功率才上去。
我的经验是:同步做完之后,紧接着发一个 NOP 或读寄存器命令验证一下通信,如果返回结果不是预期值,就重新做一次同步。不要抱着侥幸心理直接往下走,否则后续所有转换数据都会是错的。
3.2 寄存器配置:一套实测可用的初始化流程
主控和 ADC 建立同步之后,接下来要配置寄存器。ADS122U04IPWR 的寄存器不多,但每个位都对应实际的模拟行为,配置前必须仔细核对数据手册的位定义。
我这次的配置目标:两路 PT100、四线制接法、单次转换模式、20SPS 数据速率、IDAC 激励电流 0.5mA。
配置流程大概是这样:
// 等待ADC上电稳定 HAL_Delay(20); // 发送复位命令,确保ADC处于已知状态 adc_send_command(CMD_RST); HAL_Delay(10); // 重新同步波特率 adc_uart_sync_baudrate(); HAL_Delay(1); // 写入配置寄存器(实际寄存器地址和位定义以数据手册为准) // 配置1:选择AIN0-AIN1通道,PGA增益=8,单次转换模式 adc_write_reg(REG_CONFIG0, 0xXX); // 配置2:数据速率20SPS,使能FIR滤波 adc_write_reg(REG_CONFIG1, 0xXX); // 配置3:IDAC1从AIN2输出,IDAC2从AIN3输出,电流0.5mA adc_write_reg(REG_IDAC, 0xXX); // 启动单次转换 adc_send_command(CMD_START);这里我没有把每个寄存器的具体十六进制值列出来,因为不同固件版本和参考设计里位定义有差异,直接抄数反而容易踩坑。我的建议是配置完后立即读回所有寄存器,打印出来和手册对照一遍,确认无误再进入数据采集流程。
3.3 读数据、CRC校验与错误处理
ADS122U04IPWR 在转换完成后,主机可以通过发 RDATA 命令读取 24 位转换结果。如果使能了 CRC 校验,返回的数据里还会跟一个 CRC 字节。
这里有个实际工程细节:24 位结果是二进制补码格式,在 STM32F030R8 上用 int32_t 接收后必须做符号扩展,否则负温度会变成很大的正数。我是这样处理的:
int32_t raw = 0; raw = (received_data[0] << 16) | (received_data[1] << 8) | received_data[2]; // 符号扩展:如果第23位为1,则把高字节的其余位补1 if (raw & 0x00800000) { raw |= 0xFF000000; }CRC 校验只靠软件实现,多项式可以从数据手册里找到。在现场长线缆环境下,CRC 几乎每天都可能遇到偶发错误,处理逻辑是:丢弃当前帧,重新发 RDATA 读取,连续失败三次就将该通道标记为故障。
我在代码里还给每个通道加了超时处理。如果发完命令之后超过 100ms 没有响应,就认为是 UART 断开或 ADC 掉电,不再盲目等待。
4. 从转换码到温度值:RTD换算、校准和故障处理
4.1 查表法还是Callendar-Van Dusen公式
拿到 ADC 原始码之后,要换算成温度。PT100 的电阻-温度关系并非严格的线性,在 0℃ 附近的灵敏度是 0.385Ω/℃,到 100℃ 时灵敏度已经略有变化,如果直接用线性比例计算,全温程误差会超过 0.5℃。
常用的两种换算方式:
第一种是查表法。把 PT100 分度表按 0.1℃ 间隔做成数组存到 Flash 里,测量到电阻值后二分查找相邻两个表项,再做线性插值。F030 虽然只有 64KB Flash,放一张 0 到 200℃ 的表也就几十KB 级别的存储,完全放得下。查表法代码简单可控,HVAC 场景 0 到 150℃ 范围足够。
第二种是解析法,用 Callendar-Van Dusen 方程计算。正温区公式是:
Rt = R0 * (1 + A*t + B*t²)
其中 A=3.9083×10⁻³,B=-5.775×10⁻⁷。负温区还要加 C 项。M0 没有 FPU,直接跑浮点运算稍慢,但每秒只处理一次温度,运算耗时完全不是问题。
我实际做的是两者结合:在代码里用解析法做标定基准,在产线上用更高精度的温度校验仪反过来修正系数,最终每通道保存一组偏移和增益系数。
4.2 现场三点校准:为什么不能只做零点偏移
ADS122U04IPWR 本身是 24 位 ADC,但系统精度还受传感器本身误差、接线方式和参考基准共同影响。我曾经天真地以为换了高精度 ADC 就能直接拿到精确温度,实际上不做校准的话,全量程误差可能仍有 ±0.3℃。
校准流程我建议分三步:
- 零点:使用冰水混合物或干井炉的 0℃ 点,稳定 20 分钟后记录读数;
- 中间点:用恒温水浴在 50℃ 或 100℃ 记录读数,这一步用来修正增益误差;
- 验证点:在校准范围中间取一个点(比如 25℃)验证,确认修正后误差落在指标内。
校准数据的计算很简单,认为实际温度和 ADC 换算温度之间是线性关系 y = a*x + b,其中 a 是增益修正系数,b 是零点偏移。每个通道都独立求一组 a、b,存到 F030 内部 Flash。产品正常运行时读出系数并套用。
这里有个容易漏掉的细节:校准前整个系统必须预热 15 分钟以上。内部基准和 PGA 的温漂需要时间稳定,如果一上电就急着校准,校准完的系数在设备真正热起来之后会重新偏离。
4.3 断线检测、自热效应和线阻补偿
高精度测温系统还要处理故障状态。RTD 断线是现场最常见的问题,如果软件不识别断线,温度读数会直接跳到满量程甚至错误值,进而导致控制器误动作。
ADS122U04IPWR 的输入结构和 IDAC 电流源可以辅助断线检测。我采取的做法是:两路通道各加一个高阻值上拉电阻到 AVDD,当 RTD 断线时,ADC 输入引脚被拉到接近电源电压,转换码会落在一个明显异常的高值区间,软件一旦发现这个区间就立刻上报“传感器断路”。
自热效应是另一个容易被忽视的误差源。IDAC 激励电流流过 PT100 时会产生热量,电流越大信号越强,但自热误差也越大。对于薄膜 PT100,1mA 电流在 100Ω 电阻上的耗散功率是 0.1mW,对应温度升高约 0.05℃ 到 0.1℃,不同封装差异很大。我的最终配置是 0.5mA,兼顾了信号电平和自热影响。
线阻问题前面说了,四线制能从根本上解决。如果现场因为历史原因只能用两线制,可以把线缆电阻实测出来,在软件里做固定补偿。但要注意线缆电阻会随环境温度变化,这种补偿方案只适合温差小的场景。
5. 现场调试记录:变频器干扰、工频噪声和长线缆
5.1 读数跳了0.3℃,最后锁定到屏蔽层接地
这套系统在实验室里测试时一切正常,结果装到项目现场,我把一个采集模块放在变频器柜旁边,温度读数在 25℃ 附近来回跳,幅度有 0.3℃。
按照经验,我第一反应是“电源被干扰了”,于是换了一套隔离电源,跳变没有改善。接着用示波器去量 ADC 的供电引脚,发现 3.3V 波形很干净,这就排除了电源传导干扰。
进一步排查发现,干扰路径其实是传感器电缆的屏蔽层。原施工队把屏蔽层在传感器端和控制柜端都做了接地,结果形成了一个大地环路,变频器产生的共模电流在这个环路里流动,通过 ADC 的地线系统转换成差模电压,最终体现在转换码里。
处理方式很简单:把屏蔽层改成只在控制器端单点接地,传感器端悬空,同时保护接地线单独走,和黄绿线分开。改完再测试,读数立刻稳定到 ±0.05℃ 以内。
这个教训让我重新认识到:24 位 ADC 的精度优势,只有在完整的信号链设计正确时才能体现出来。ADC 芯片再强,也扛不住一个错误的地环路设计。
5.2 20SPS采样率下的工频抑制与响应速度平衡
ADS122U04IPWR 的数据速率最高是 20SPS。很多人嫌这个速度慢,但对工业和 HVAC 应用来说,20SPS 是刻意的设计选择,因为 20SPS 配合器件内部滤波器,可以很好地抑制 50Hz 工频及其谐波干扰。
我在实际项目里把每个通道的采样率固定为 20SPS,然后软件里再叠加 3 次滑动中值滤波。这样最终温度更新周期大约 150ms,对于 HVAC 的送风温度控制来说响应足够快,同时读数噪声被压得很低。
如果你的应用需要更快的响应速度,可以选用更高的数据速率,但越高的速率意味着数字滤波器的带内噪声越明显,工频抑制能力也越差。我自己的选择原则是:先把精度和稳定性做扎实,再考虑速度,多数温控应用根本不需要毫秒级刷新率。
5.3 多通道轮询与F030任务调度
4 片 ADS122U04IPWR 各挂一个 USART,每片接两路 PT100,一共 8 个通道。我的采集周期设计是 1 秒轮询一遍。
流程是这样的:每个通道在收到 START 命令后等待转换完成,读取数据,然后切换下一通道。ADS122U04IPWR 在 20SPS 下单次转换约 50ms,8 个通道全部读完也就 400ms,剩下 600ms 可以用来做校准系数计算、RS485 上报和本地报警判断。
F030 在这个负载下主频跑到 48MHz 已经非常充裕,USART 中断频率最高也就每 50ms 一次,根本不会占用多少 CPU。如果将来通道数量翻倍,也只需要多挂几片 ADC,软件上按队列依次轮询即可。
6. 项目里踩过的几个坑,按痛苦程度排序
6.1 波特率锁定失败与F030内部时钟误差
这个坑让我在实验室里耗了整整两天。现象是:部分板子上电后第一次通信就失败,复位之后再试又好了,毫无规律。
我抓了逻辑分析仪去看同步波形,发现 F030 用内部 HSI 时钟时,USART 的实际波特率和我设定的 9600 相差接近 1%。ADS122U04IPWR 的自动波特率锁定在这种偏差下并不是每次都能成功。
解决方法是两个:第一,所有 F030 改用外部 8MHz 晶振,波特率误差降到 0.1% 以内;第二,在同步发送期间关闭该 USART 的中断,避免一个串口中断进来把同步字符流打断。改完之后,这个问题在量产板上再没有出现过。
6.2 上电顺序导致第一次转换永远为0
还有一个很隐蔽的问题:如果 MCU 和 ADC 同时上电,F030 启动很快,立即发 START 命令,而 ADC 此时可能还没完成内部上电初始化,命令被当成无效处理。结果是第一个转换周期读回的数据恒为 0,而且会顺序影响后续初始化流程。
我在代码里加上了一个严格的上电状态机:上电后先等 20ms,发复位命令,再等 10ms,同步波特率,之后先做一次“丢弃读”,丢掉第一次转换结果,再进入正常采集循环。这个顺序固定下来之后,所有板子首次上电的数据都是对的。
6.3 TTL串口在30米线缆上的惨痛教训
第一版样机拿到客户现场时,传感器和控制器之间走线约 30 米,我图省事直接用了 TTL 电平的 UART 连线。结果就是通信误码率极高,CRC 错误频繁,系统压根没法正常跑。
后来改成 RS485 物理层。在 F030 的 USART 上接了一颗便宜的半双工 RS485 收发器,ADC 侧也做了同样的转换,两根双绞线传输,末端并联 120Ω 终端电阻。改完之后 30 米线缆下通信非常稳定,后来试过拉长到 80 米也完全没问题。
所以我的结论是:只要传感器和控制器不在同一块电路板上,就不要考虑 TTL 直连,一步到位上 RS485,省下来的调试时间远比那颗收发器芯片值钱。
6.4 校准数据的存储与掉电丢失问题
每台设备在生产线上都会做校准,得到一组 a、b 系数。最初我把这些系数放在一个外置 EEPROM 里,结果某批次供货出了问题,校准数据读出来全是 0xFF,设备全部报警。
后面我改成把校准系数存到 STM32F030R8 的内部 Flash 最后两页,写入时附带 CRC 校验,读取时校验失败就使用默认系数并上报“校准异常”。
这里有个细节:内部 Flash 的擦写次数有限,不适合频繁写入,但校准是出厂时一次性行为,运行过程中不会改写,所以寿命完全不是问题。我把这个考虑写进了设计文档,避免后续维护的人误在运行周期里调用 Flash 擦写。
写在后面
这个项目真正让我体会到,高精度温度测量不是“把 ADC 位数堆上去就行”那么简单。ADS122U04IPWR 提供了很好的转换核心,STM32F030R8 提供了足够的控制资源,但最终能不能稳定到 ±0.1℃,拼的是电源设计、传感器接法、屏蔽接地、软件滤波和校准流程这些看起来不起眼的细节。
最后分享一个小技巧:调试 UART 同步时,我习惯把同步字节设成 0x55,也就是二进制的 01010101。这种波形在逻辑分析仪和示波器上特征极其明显,很容易确认“已经发出去了”和“ADC 有没有收到”,排查问题比用全 0 字节省事很多。如果你也在做类似的高精度测温采集板,不妨试一下。