做工业仪表和 HVAC 控制器的人,早晚都会跟温度传感器较上劲。PT100 的灵敏度只有 0.385Ω/℃左右,K 型热电偶每摄氏度也才产生几十微伏的电动势,这种“弱信号”放在 MCU 内置 ADC 上,经常是分辨率够、噪声压不住,或者噪声压住了、温漂又跑出来。我去年做的测温板,干脆把采集前端换成了 ADS122U04IPWR,主控用一棵 PIC32MX675F512L,一个是 24 位带 UART 接口的 ΔΣ ADC,一个是 80MHz 的 32 位 MCU,一个管前端测量,一个管处理与通信,在工业温控和 HVAC 场景里把 PT100、热电偶、NTC 三种传感器都跑了一遍。
这篇文章是我这次项目从选型到落地的一段记录,不绕概念,直接说清楚:为什么是这套组合、硬件怎么接、软件怎么写、现场有什么坑。打算做温度变送器、空调控制器或者设备数据采集板的人,可以参考着抄作业。
1. 为什么是“独立ADC+主控”而不是“MCU内置ADC”
1.1 测温前端真正缺的是分辨率和稳定性
有朋友问过我:PIC32MX675F512L 内部又不是没有 ADC,为什么非要外挂一个 ADS122U04IPWR 把简单问题搞复杂?
这个问题问得很实在,但真拿内置 ADC 去量温度,就知道差距在哪。普通 MCU 内置 ADC 大多是 12 位逐次逼近型,ENOB 做到 10 位已经算不错;你要分辨 0.1℃ 对应的 PT100 阻值变化,大约是 0.0385Ω,如果传感器满量程是 300Ω,需要多少分辨率?满量程 300Ω、分辨率 0.0385Ω,相当于要能识别万分之一左右的信号变化,也就是大约 13 位有效分辨率,这还只是理想情况。一旦叠加上参考电压温漂、内部噪声和电源波动,12 位 ADC 想在工业现场稳定分辨 0.1℃,非常勉强。
热电偶就更不用说了。K 型热电偶在室温附近灵敏度大约 40μV/℃,如果你要测到 0.5℃ 以内,至少要分辨 20μV。PGA 放大之后,噪声依然是主要矛盾。ADS122U04IPWR 这样的 24 位 ΔΣ ADC,优势不在“位数多好看”,而在它对微弱信号的处理方式:内置 PGA 先把信号放大,再用 ΔΣ 调制器做过采样和数字滤波,配合内部基准,能把热噪声和量化噪声一起压下去。
还有一点经常被忽略:工业现场的共模噪声。传感器到采集板的线往往有几米甚至几十米,变频器、接触器、水泵电机都会在外线上感应出干扰。内置 ADC 的输入结构一般没有很强的共模抑制能力,而 ADS122U04IPWR 的差分输入配合可配置 PGA,在抑制共模干扰上天然比单端 ADC 强不少。
1.2 UART接口的ADS122U04IPWR有哪些值得用的东西
ADS122U04IPWR 最特别的地方是通信接口:它不走 SPI,而是用 UART 和 MCU 打交道。我第一次看到这个设计也觉得奇怪,实际用下来才发现对某些场景非常友好。
节省引脚是一方面。PIC32MX675F512L 的 UART 外设很丰富,但很多应用里引脚仍不够用,尤其要同时接按键、LCD、RS485、继电器驱动的时候。UART 接口只需要 TX、RX 两根线,比 SPI 的 SCK、MOSI、MISO、CS 四根线省了一半引脚,而且这两个引脚用普通 GPIO 模拟也能跑,选型余地大很多。
另一方面,UART 在长距离传输上更自然。ADC 和 MCU 如果分属两块板子,距离稍远一点,SPI 高速时钟容易受寄生电容影响,而 UART 的异步通信只要波特率合理、电平标准一致,抗干扰表现明显更好。HVAC 设备里经常有“传感器板挂在风管上,主控板装在电控箱里”这种结构,两块板之间用排线连接,UART 接口反而比 SPI 更稳。
芯片内部的针对性集成也很明显。它带了两个可编程 IDAC 恒流源、内部基准、PGA 和输入多路开关,RTD 需要的恒流激励、比例测量所需的参考电压,基本不需要额外搭电路。整块板子可以压缩到很小的面积,BOM 也比“仪表放大器+SAR ADC+基准源”那种老式方案干净得多。
1.3 PIC32MX675F512L在系统里的分工
PIC32MX675F512L 在这套方案里不是“打杂”的,它承担了所有非测量类的工作,而且做得比较从容。
首先,它负责维护采集节拍。虽然 ADS122U04IPWR 可以连续转换,但实际工业设备往往需要按固定周期采样,比如每 200ms 出一个温度值。PIC32 可以用定时器中断触发“读一次最新转换结果”,保证每次上报的数据间隔均匀,配合上位机做趋势曲线很舒服。
其次,它要做数据加工。24 位 ADC 的原始码值不能直接当温度用,PT100 要查分度表或者按 Callendar-Van Dusen 方程换算,热电偶要考虑冷端补偿和线性化,NTC 要做指数曲线拟合。这些计算在 80MHz 的 32 位内核上跑起来毫无压力,512KB Flash 也够写复杂一点的查表和滤波算法。
再就是对外通信和逻辑控制。HVAC 现场设备经常要接 RS485 走 Modbus,PIC32 的 UART 引出来做 Modbus 从站协议很成熟。工业变送器还可能要输出 4-20mA,MCU 定时器产生 PWM、再加 RC 滤波做 DAC 也够用。一块板子同时管测量、算法、通信和输出,不用再挂第二颗 MCU,系统成本和故障点都下来了。
2. 硬件连接和电路设计实操
2.1 PT100三线制接法和比例测量
PT100 在工业测温里用得最多,接法却最容易出问题。两线制简单,但导线电阻直接叠加进传感器阻抗;四线制最准,可现场电缆成本高、接线端子多。我在这块板上做的是三线制,既能消掉导线电阻,又不会让现场接线太复杂。
三线制的核心思想是“比例测量加导线补偿”。用 ADS122U04IPWR 的 IDAC1 输出一路恒流,比如 1mA,流经 PT100 后从远端地回路返回;同时在 PT100 的两侧各引一根感测线到 ADC 的差分输入端。测到的电压是“PT100 电阻 + 一根导线电阻”上的压降。理论上确实多了一根导线的误差,但工程上通过把感测线设计成等长、同材质,两根导线的电阻基本一致,再用软件做一次零点校正就能抵消大部分。
更关键的是参考电压的处理。我习惯把 IDAC2 的恒流源也设置为同样电流大小,让它流过一颗精密参考电阻,然后把这颗电阻上的电压作为 ADC 的外部基准输入。这样做的原理很直接:ADC 在测量 PT100 电压的同时也在测参考电阻电压,最终结果是两者之比。只要两颗电阻是同一种激励电流驱动,电流源的温漂和抖动会同时作用于信号和基准,结果自动抵消,这就是工程上常说的比例测量。
ADS122U04IPWR 的寄存器配置里,要特别留意 IDAC 电流大小的选择。PT100 自热效应明显,电流太大会导致传感器自身发热,实测中 1mA 电流在薄膜 PT100 上就会出现可感知的自热温升,所以我最终用的是 500μA。自热误差可以通过减小电流来压,但电流太小又会降低信噪比,500μA 是灵敏度和自热之间的折中。仪表放大器输入端我加了一级 RC 低通滤波,电阻取 10Ω,电容取 1nF,主要用来滤掉高频共模干扰,同时不影响 IDAC 的直流激励。
2.2 热电偶输入与冷端处理
热电偶测高温很划算,但它的输出信号极小,而且必须做冷端补偿,这两个问题比 RTD 更考验电路。
K 型热电偶的灵敏度大约 40μV/℃,如果测量 400℃ 温度,热端电动势也只有 16mV 左右。这个信号对 24 位 ADC 来说不缺分辨率,缺的是合适的增益。我把 PGA 设置为 32 倍,把热电偶信号放大到 ADC 输入量程的合适区间,实测下来噪声明显变小。PGA 增益也不要一味加大,增益过高会把前级失调电压一起放大,反而导致低端温度偏差变大。
冷端补偿是热电偶方案里最容易犯的错。热电偶测的是热端和冷端的温差电动势,冷端如果没有一个已知温度,热端温度就无从谈起。我的做法是在热电偶接线端子附近放一颗 NTC 温度传感器,用同一个 ADC 的另一个通道去读冷端温度。这颗 NTC 必须紧贴冷端端子,最好用导热胶固定,保证它感受到的温度就是冷端实际温度。
软件补偿逻辑不复杂:先读热电偶通道电动势 E1,再读冷端温度 T_cold,把 T_cold 换算成对应的热电偶电动势 E2,最后用 E1 + E2 作为“等效热端参考电动势”,查表得到真实温度。ADS122U04IPWR 有四个模拟输入通道,通道 0、1 留给 PT100,通道 2 接热电偶,通道 3 接 NTC,一个芯片刚好覆盖整套方案,不需要再挂第二颗 ADC。
2.3 电源、PCB布局与现场线缆
ADS122U04IPWR 和 PIC32MX675F512L 都是对电源质量敏感的东西,工业现场又不能指望 24V 电源很干净,所以电源方案我花了比较多时间。
板级电源我采用“DC-DC 隔离 + 线性稳压”的结构。输入端是 24V 现场电源,先经过隔离 DC-DC 模块变成 5V,再用低压差 LDO 降到 3.3V 给 MCU 和 ADC 供电。之所以不用普通 DC-DC 直接给 ADC 供电,是因为开关电源的纹波会直接耦合到模拟信号链,即使 ADC 有高 PSRR,也不可能完全免疫。隔离模块还有额外好处:现场长线上的浪涌和共模干扰被隔在电源模块外面,不会直接冲击测量电路。
模拟地和数字地我做了单点连接。ADC 的模拟电源引脚、基准输入、传感器信号都以模拟地为参考,PIC32 的数字地和模拟地通过一颗 0Ω 磁珠连接,磁珠靠近 ADC 的模拟地引脚放置,避免数字开关噪声通过地平面串到模拟端。这个细节前期没注意时,ADC 输出噪声明显偏大,改成分割地平面后底噪才恢复正常。
现场电缆也要提前规划。PT100 三线最好用屏蔽双绞线,屏蔽层单端接地。很多现场师傅习惯把屏蔽层两端都接地,结果形成接地环路,低频干扰反而从屏蔽层耦合进信号线。HVAC 设备里变频器是主要的干扰源,信号线布线时尽量远离变频器输出电缆,实在躲不开就加金属穿线管。
3. 固件从零到能用的完整路径
3.1 UART初始化与读写时序
ADS122U04IPWR 的 UART 接口,需要 MCU 主动发起通信,这个定位和“ADC 自动输出数据”的常规思路不太一样。PIC32 这边我把 UART2 配置成 8 位数据、无校验、1 位停止位,波特率用的 9600。有人会觉得 9600 太慢,但测温本来就不是高速场景,9600 波特率在工业电缆上抗干扰能力最好,而且芯片内部转换速率上限并不高,慢波特率不会成为瓶颈。
初始化时序上有个值得注意的点:上电后不能立刻发命令,要等 ADS122U04IPWR 完成内部上电复位。我实测等待时间大概需要几十毫秒,稳妥起见在固件里延时 100ms 再开始通信,避免第一条命令发送时芯片还没就绪。
读写寄存器我封装成了两个基础函数。发送命令时注意 UART 发送完成要等待,不能连续硬怼;接收数据时要有超时保护,否则 MCU 会一直卡在等待响应。我见过不少人在这一步卡住,以为是协议问题,结果只是发送缓冲没清空,把上一次残留数据读进来了。
// PIC32 UART2 初始化,用于连接 ADS122U04IPWR void uart2_init(uint32_t baud) { U2BRG = (GetPeripheralClock() / 16 / baud) - 1; U2MODE = UART_ENABLE | UART_IDLE_CON | UART_8BIT_PAR; U2STA = UART_TX_ENABLE; U2MODE = UART_ON; } uint8_t uart2_read_byte(uint32_t timeout_ms) { uint32_t t = GET_TICK(); while (!(U2STA & UART_RX_DATA) && (GET_TICK() - t < timeout_ms)); if (U2STA & UART_RX_DATA) { return U2RXREG; } return 0xFF; // 超时返回 }这段代码不算复杂,但要确保每次发送命令后,给芯片留出足够响应时间。ADS122U04IPWR 在连续转换模式下会持续更新内部数据,MCU 要读最新结果时,直接发“读取数据”命令,然后按返回值处理即可。
3.2 配置寄存器和启动转换
ADS122U04IPWR 的配置寄存器并不多,但每个位的含义都很重要。我的初始化顺序是:先复位芯片,再依次配置输入多路选择、PGA 增益、基准来源和 IDAC 电流。
通道切换的逻辑要提前想清楚。因为 PT100 和热电偶需要不同的 PGA 增益,而增益寄存器是全通道共享的,我采用了“每次测量前先写寄存器、再触发转换、再读结果”的方式。这种方法看起来多几条命令,但避免了在连续模式下频繁切换通道带来的数据污染。温度变化本身很慢,每个通道每秒钟测几次就足够,命令开销完全可接受。
基准配置是比例测量的关键。我把外部基准选择为“IDAC2 驱动的参考电阻”,同时关掉内部基准。内部基准的好处是方便,但它不参与比例测量,电流源漂移无法自动抵消。用外部基准电阻后,PT100 通道的测量结果定义为传感器电压与基准电压的比值,从原理上消除了激励电流的温漂。
启动转换我用的是单次转换模式。每次发启动命令后,芯片完成一次转换并把结果锁存,MCU 再发读取命令取回 24 位数据。单次转换比连续转换多几条命令,但好处是可预测性强,转换完成状态明确,尤其适合多通道轮流测量的场景。
void adc_start_conversion(void) { adc_send_command(CMD_START); // 等待转换完成,可用延时或者查询状态 delay_ms(50); } int32_t adc_read_single(void) { uint8_t buf[3]; adc_send_command(CMD_RDATA); buf[0] = uart2_read_byte(100); buf[1] = uart2_read_byte(100); buf[2] = uart2_read_byte(100); return ((int32_t)buf[0] << 16) | ((int32_t)buf[1] << 8) | (int32_t)buf[2]; }需要注意,ADC 输出的是二进制补码格式,负电压对应负的编码值,换算时要先转为有符号数,不能直接当无符号数用。热电偶在冷端温度高于热端温度时,测量结果确实是负值,处理不当会出现“温度跳变到几千万”的乌龙。
3.3 把原始码换算成真实温度
拿到 24 位原始码之后,下一步不是直接算温度,而是先把它换算成电压。ADS122U04IPWR 的数据输出是基于参考电压和 PGA 增益的,电压计算公式为:
V = raw × VREF / (gain × 2^23)
这里的 2^23 来自 24 位 ADC 的满量程范围。比如参考电压为 2.048V、增益为 32、原始码为 1000000,算出来的电压就是 1000000 × 2.048 / (32 × 8388608) ≈ 7.63mV。这段换算固件里必须用浮点运算或者高精度定点运算,PIC32 有硬件浮点单元,直接用 float 也没问题。
PT100 的线性化我用的查表法加线性插值。查表法是工程上最稳妥的方式,因为 PT100 的温度-阻值关系在 IEC 60751 标准里有标准分度表,我生成了一张从 -50℃ 到 500℃ 的分度表,间隔 1℃。实际测量时先根据阻值定位到相邻两个表项,再做线性插值,精度能到±0.1℃以内。这样比硬套公式更直观,也方便后续校准修正。
热电偶的线性化更麻烦,因为不同分度号的曲线差异很大。K 型热电偶我用的是 ITS-90 标准的多项式展开,但多项式系数复杂,PIC32 上直接算也能跑,只是代码量大。后来我改成查表加二次多项式拟合,表项间隔 10℃,在 0-1000℃ 范围内精度实测在±0.3℃以内,对 HVAC 和大部分工业应用足够。冷端补偿用的是软件方法:读冷端 NTC 温度,查热电偶分度表得到该温度对应的补偿电动势,加到测量电动势上再反查热端温度,整个过程封装成一个函数。
3.4 一个可落地的PIC32端代码框架
把上面所有功能串起来后,主循环的逻辑比较简单:定时采集信号、换算温度、然后送去显示或通信。下面是一个简化但能跑的框架:
void main(void) { system_init(); uart2_init(9600); adc_reset(); delay_ms(100); while (1) { // 测量 PT100 通道 adc_config_pt100(); adc_start_conversion(); int32_t raw_pt100 = adc_read_single(); float v_pt100 = adc_raw_to_voltage(raw_pt100); float r_pt100 = v_pt100 / (IDAC_CURRENT * (1.0 / REF_GAIN)); float t_pt100 = pt100_resist_to_temp(r_pt100); // 测量热电偶通道 adc_config_tc(); adc_start_conversion(); int32_t raw_tc = adc_read_single(); float v_tc = adc_raw_to_voltage(raw_tc); float t_cold = read_ntc_cold_temp(); float t_tc = tc_linearize(v_tc, t_cold); // 上报结果 send_temperature_to_host(t_pt100, t_tc, DEVICE_ADDR); delay_ms(200); } }实际项目里还要加看门狗、通信错误重试、异常数据剔除等逻辑,但主干就是这个流程。把“配置-启动-读取-换算”封装成独立函数后,换传感器类型只改配置函数,耦合很小,维护起来也轻松。
4. 实测表现、调优手段与故障排查
4.1 噪声来源与数字滤波
ADS122U04IPWR 本身是 ΔΣ ADC,配合内部数字滤波器,对工频干扰有不错的抑制能力。但实测中我发现,板级噪声往往会掩盖芯片本身的性能,尤其是开关电源噪声和数字信号耦合。
第一次跑通后,我把 PT100 通道接了一个 100Ω 精密电阻做稳定测试,ADC 输出码值跳动了大约 8 个 LSB,折算成温度大约是 0.2℃。这个水平不能算差,但离“0.05℃级别稳定”还有距离。我先后试了三种手段:
第一,加大外部 RC 滤波电容,把 1nF 改到 10nF,高频噪声明显下降,但注意电容不能太大,否则会跟传感器电阻形成低通,影响响应速度。第二,把 MCU 主频降到最低稳定频率,PIC32 的数字开关噪声减小后,ADC 底噪又低了一点。第三,在固件里加滑动平均滤波,取最近 10 个采样值做平均值,输出码值稳定到了 2 个 LSB 以内。
滑动平均对温度这种缓变量非常合适,代价只是更新速度变慢。在 HVAC 应用里,温度变化本来就很慢,一秒更新一次都嫌快,滑动窗口不会带来可感知的滞后。如果用在需要快速响应的工业过程控制里,建议改用一阶低通滤波,系数取 0.2-0.3,兼顾响应和稳定。
4.2 温度点校验操作记录
固件写好后,我做了几组标准温度点校验。0℃ 用冰水混合物,100℃ 用沸水,另外用一台干式炉校验了 200℃ 和 400℃ 两个点。
0℃ 校验最能反映冷端补偿和零点误差。把 PT100 探头插入冰水混合物后,读数稳定在 0.08℃,偏差主要来自冰水温度并不是严格 0℃ 和传感器分度误差。热电偶通道用同样的冰水混合物做冷端,把热端也放在冰水里,理论上读数应该也是 0℃,实测差了约 0.3℃,说明冷端 NTC 和热电偶测量值之间存在温度梯度误差。改善办法是把 NTC 和热电偶端子贴得更近,并且用铜片把两者压紧,让热接触尽量均匀。
100℃ 沸水点校验时发现了一个问题:PT100 读数 99.6℃,偏低 0.4℃。排查后确认是探头插入深度不够,传感元件没有完全浸入沸水蒸汽区。这个案例提醒我,现场校验时传感器安装位置对精度的影响,往往比测量系统本身更大。干式炉校验时系统表现很稳定,200℃ 点误差 0.2℃,400℃ 点误差 0.4℃,误差呈现“温度越高偏差越大”的趋势,后续做两点校准即可修正。
4.3 现场高频问题速查表
项目交付后整理了一份故障排查表,发给现场调试的同事,很多问题不用再电话来回确认,这里直接贴出来:
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 读数恒为满码或 0 | 传感器断线或短路 | 检查探头接线;PT100 开路时输入悬空 |
| 数据跳动幅度大 | 屏蔽层未可靠接地 | 屏蔽线单端接地;远离变频器电缆 |
| 通信偶发失败 | UART 波特率偏差或干扰 | 改用 9600 波特率;缩短排线;加校验 |
| 热电偶读数在室温附近明显偏大 | 冷端补偿失效 | 检查冷端 NTC 是否紧贴端子;校准 NTC |
| PT100 读数随线长变化 | 三线制补偿未生效 | 确认两根感测线等长同材质;做零点校正 |
| 经过一段时间读数缓慢漂移 | 传感器自热或接线氧化 | 降低 IDAC 电流;重新压接端子 |
其中“读数恒为满码”这个问题我在调试时遇到过,印象很深。当时 PT100 探头没接,ADC 输入相当于悬空,PGA 的偏置电流把输入拉到了接近正满量程的位置,读数直接跳到最大。这个现象后来反而成了我判断“传感器是否断线”的一个快捷标志。
5. 继续用这个组合时,我想提醒你的事
5.1 新板子回板后先做“裸码检查”
每次 PCB 打样回来,我都建议先不焊传感器,只把 ADS122U04IPWR 和 PIC32MX675F512L 的最小系统跑起来,读一遍所有通道的原始码。
这个“裸码检查”能暴露很多硬件问题。每个通道悬空时如果读到的码值在一个可预期的范围内,说明通信链路和芯片电源基本正常;如果某个通道读回满码或者全零,要检查对应引脚是否有虚焊、连锡,或者配置寄存器没写进去。有一次我 PCB 回来直接接上传感器测,数据一直不对,折腾了两天才发现是 ADC 某个输入引脚的焊盘在回流焊时连到了旁边的接地过孔,这类问题裸码检查五分钟就能定位。
裸码检查时还要顺手验证一下 IDAC 电流。外接一颗精密电阻到 IDAC 输出,用万用表测电阻两端电压,确认电流源输出跟寄存器配置一致。这一步能提前发现芯片使能位配置错误,避免后面把所有通道的数据都测歪。
5.2 保持“数据手册为准”的习惯
ADS122U04IPWR 的寄存器定义和命令字在 TI 官方手册里写得非常详细,但型号后缀不同,个别寄存器位可能有差异。我在项目里遇到过一个问题:按旧版代码配置 IDAC 电流值,芯片输出电流始终不对,最后对比手册才发现是新版本芯片对某个寄存器位的定义做了调整。
我的建议是,代码库里的配置寄存器最好都写上注释,标明对应手册的哪个表哪个寄存器。这样芯片批次换了、手册更新了,排查差异有依据,不至于把整套配置推倒重来。纯粹的“抄以前项目的代码”在这种细节上风险很大,因为测温方案的指标最终由模拟前端决定,配置差一位,可能就是零点几度的误差。
5.3 后期可以继续扩展的方向
这套组合的扩展空间其实比想象中大。ADS122U04IPWR 四个输入通道,目前用了三个,还剩一个通道可以接湿度传感器或者压力传感器;PIC32MX675F512L 的 USB 外设和 DMA 都还没用上,如果后续产品要做 USB 配置接口或者更高效的数据流传输,不需要换 MCU。
如果哪天要同时测四路以上的温度,可以考虑用两颗 ADS122U04IPWR 共享同一个 I2C 或者 UART 总线,PIC32 这边只需要加一个小型模拟开关做片选,或者用 UART 地址帧做区分。成本增加不多,通道数却可以翻倍,这种“单芯片四通道、多芯片级联”的思路很适合温控器、数据记录仪这类产品。
做这类项目,我自己最大的体会是:“高精度温度测量”拼的往往不是某一颗芯片的参数,而是从传感器接法、PCB 布局、电源设计到固件滤波和校准这一整条链路的一致性。芯片给了你 24 位的天花板,但能用到多少位,取决于系统里的每个环节有没有拖后腿。每次踩过坑之后回头想,那些出问题的地方几乎都不是芯片本身,而是我没把它的前提条件当回事。如果这篇文章能让你少走一段我走过的弯路,就值了。