ADS122U04与PIC18实现高精度PT100温度采集全解析 ADS122U04IPWR 这颗片子我断断续续用了两年多PIC18LF45K40 也是老熟人了。前段时间刚帮一个做暖通控制柜的朋友调完一批四线制 PT100 的采集板正好借着这个项目把从选型、接线到固件、标定的完整链路从头到尾捋一遍。这东西看着简单——一个 ADC 加一个单片机但真要拿到工业现场去跑坑全藏在细节里。1. 项目概述为什么是“专用 ADC 8 位 MCU”的组合先说结论你完全可以把 ADC 和 MCU 合成一颗芯片比如带24位 ADC 的 ARM 内核单片机市面上多得是。但在这个项目里我坚持用 ADS122U04IPWR 外挂 PIC18LF45K40理由很实际——隔离、布线和调试灵活度。ADS122U04 本质上是 ADS1220 这颗经典 24 位 Δ-Σ ADC 的 UART 版本。它把 SPI 接口换成了两线制的 UART这对 HVAC 场景是降维打击式的友好。工业现场的控制柜里传感器探头的走线动不动就是三五米如果用 SPISCLK 和 MOSI 的时序在长线上衰减得非常难看稍不留神就是通信丢帧。但 UART 天生就是为长距离异步通信设计的而且只有两根线过光耦隔离也方便——一个高速光耦就能把模拟前端和数字主控彻底隔开这在有变频器、接触器的 HVAC 柜子里是刚需。PIC18LF45K40 则负责干粗活读 UART 数据、解析寄存器、跑 PID 或者简单的开关逻辑、驱动数码管或者 RS485 总线。8 位机在这个场景完全够用关键是它便宜、供货稳、开发环境顺手。很多工程师一上来就想上 STM32但真算一笔账一个温控器或者小型 DDC 控制器PIC18 的成本比 M0 核的片子低一大截而性能冗余却不见得少——ADS122U04 内部自带数字滤波和 PGA真正需要 MCU 扛的运算量很小。所以这套方案的适用人群很明确做工业温度采集、HVAC 控制器、冷链监测设备对精度要求高千分之一度级别的分辨率但又不希望为了一个温度采集功能去折腾复杂操作系统的团队。它没有跑 Linux 的本事但也不需要。2. 硬件设计要点ADS122U04 的外围电路与传感器接入方式2.1 ADS122U04 的引脚功能与供电策略ADS122U04 有 16 个引脚需要重点关注的几路引脚如下引脚名称功能我的接法1/10AVDD/AVSS模拟电源AVDD 接 3.3VAVSS 接地3/4AIN0/AIN1模拟输入接 PT100 的差分对5/6AIN2/AIN3模拟输入接热电偶冷端补偿或辅助通道7REFP/REFN外部基准接 REF5025 或直接用电桥激励11/12SCLK/TX时钟/数据输出接 PIC18 的 UART RX13RX数据输入接 PIC18 的 UART TX15RESET复位接 MCU 的 GPIO低有效供电是老生常谈但必须说Δ-Σ ADC 对电源纹波敏感ADS122U04 的模拟电源 AVDD 和数字电源 DVDD 虽然可以共用 3.3V但我实测下来如果 DVDD 和 AVDD 直接连在一起且 3.3V 的 LDO 后面没有做 π 型滤波采样数据的有效位数会掉 2 到 3 位。原因在于数字引脚的开关噪声会耦合进模拟电源而 Δ-Σ 调制器对电源的瞬态变化非常敏感。我的做法是AVDD 单独串一个 10Ω 电阻再并两个电容——10μF 钽电容加 0.1μF 陶瓷电容——滤掉低频和高频噪声DVDD 则走原 3.3V不共用 π 型滤波网络。实测下来短接输入时的峰峰值噪声从原来的 8 个 LSB 降到了 2 个 LSB。2.2 RTD 与热电偶的信号调理为什么要用 PGAADS122U04 内部集成了一个可编程增益放大器PGA增益范围是 1 到 128 倍。这玩意不是锦上添花而是能不能用好的命门。PT100 在 0℃ 时的电阻是 100Ω温度系数大约是 0.385Ω/℃。如果你用 250μA 的恒流源激励0℃ 时产生的电压只有 25mV100℃ 时也才 51.5mV。这么小的信号如果不放大直接送进 24 位 ADC虽然理论分辨率极高但实际噪声会把低位都吃掉。我习惯把 PGA 增益设在 16 倍这样 100℃ 对应的满量程信号大约是 0.8V接近 ADC 的参考电压 2.048V 的 40%既不会削顶又能把信号幅度充分发挥出来。热电偶的情况又不一样。K 型热电偶输出的是微伏级别的电压典型灵敏度大约 41μV/℃所以 PGA 增益要拉到 64 倍甚至 128 倍。但这里有个隐性坑——热电偶的冷端补偿。如果你直接把热电偶的引线怼到 AIN0/AIN1 上测得的是热端相对于冷端的温差电势而冷端温度也就是 PCB 板上的接线端子温度必须另外测。我推荐用 ADS122U04 的第二个通道接一个 10kΩ NTC 热敏电阻测量冷端然后软件里做补偿。算法不复杂先用查表或 Steinhart-Hart 方程算出 NTC 的温度再根据热电偶类型查分度表得到冷端温度对应的电压值加到 ADC 读数换算出的电压上最后再反查分度表得到热端温度。这套逻辑用 PIC18 的查表指令跑起来毫无压力。2.3 传感器接线方式两线、三线还是四线RTD 的接线方式直接决定测量误差的数量级这里单独展开说。两线制是最省事的接法但也是最不准的。比如用 1m 长的普通铜导线电阻大约是 0.1Ω这个电阻直接串联在 PT100 上误差折算成温度就是 0.26℃。对于 HVAC 的室温控制0.26℃ 的偏移勉强可以接受但对于工业过程控制这误差就太大了。三线制是工业和 HVAC 最常用的折中方案。原理是让两根引线分别走激励电流的正负方向然后用第三根线做电压检测从而抵消引线电阻的影响。接线时要注意激励电流源的输出端通常是 REFP 经由电阻和 ADC 的正输入端要在传感器端汇合负输入端则和激励返回线分开走。ADS122U04 的内部结构对这种接法支持得很好它本身就设计了基于比率测量的参考电阻接法。四线制是精度之王。两根线走激励电流另外两根线专门做电压检测电压检测回路的输入阻抗极高PGA 的输入阻抗在 GΩ 级别几乎没有电流流过检测线所以检测线上的压降可以忽略。四线制可以把引线电阻误差压到 0.01℃ 以下代价是多两根线。我在这个项目里给工业现场的管道温度探头用了四线制给 HVAC 的室温探头用三线制。实测下来三线制在 50 米长线上的误差大约 0.3℃四线制同样长度下误差小于 0.05℃。2.4 参考电压的选型内部基准与外部基准ADS122U04 的整流配置决定了整个测量链路的精度。它支持内部 2.048V 参考电压但对于高精度温度测量我强烈建议用外部参考电压而且最好采用比率测量方式。所谓比率测量就是让 ADC 的参考电压和 RTD 的激励电流来自同一个源头。常用的电路是用一个精密基准电压源比如 REF5025产生 2.5V经过一个精密电阻比如 2kΩ产生 1.25mA 的电流这个电流流过 PT100 和参考电阻参考电阻两端产生的电压作为 ADC 的参考电压PT100 两端产生的电压作为信号输入。这样一来即使基准电压漂移了 1%由于信号电压和参考电压同比例变化ADC 的数字输出保持不变。这是工业温度测量的常规做法也是把 24 位 ADC 的性能真正发挥出来的前提。如果你为了省钱用内部基准那必须接受一个现实内部基准的温度漂移指标通常在 10ppm/℃ 到 50ppm/℃ 之间对应的温度误差在 0.05℃ 到 0.25℃ 之间。这在 HVAC 场景或许够用在工业过程控制里就悬了。3. 核心细节解析UART 通信协议与寄存器配置3.1 ADS122U04 的 UART 指令集命令、数据、状态三位一体ADS122U04 的 UART 接口和普通 ADC 的 SPI 接口最大的区别在于它的通信帧格式是固定的每个帧由 1 个起始位 8 个数据位 无校验 1 个停止位组成波特率默认 9600。你不需要手动拼接地址只需要按照数据手册中的命令格式收发字节。常用的指令分为三类读寄存器、写寄存器、启动转换。其中启动转换指令是 0x10这条指令发送之后ADC 会在完成一次完整的转换后自动通过 TX 引脚输出 16 位或 24 位的数据。注意这里有个容易踩的坑——ADS122U04 的输出分辨率可以通过配置寄存器的第 5 位DR来选择DR0 时输出 24 位DR1 时输出 16 位。我一开始没注意默认读 24 位结果一直拿到的数据最高两位是符号扩展位导致温度曲线在高位出现台阶。后来查手册才发现如果选了 16 位模式输出的是左对齐数据读取时直接取高 16 位即可而 24 位模式下必须完整读取三个字节。寄存器方面真正需要配的是地址 0x00配置寄存器 0到 0x03配置寄存器 3。配置寄存器 0 控制 PGA 增益和输入多路开关配置寄存器 1 控制数据速率和工作模式配置寄存器 2 控制参考电压来源和滤波模式配置寄存器 3 控制激励电流源和 IDAC 方向。以我常用的 PT100 四线制配置为例PGA16 倍对应 0x35数据速率 20SPS对应 0x20参考电压外部对应 0x00激励电流 1mA对应 0x40。写寄存器命令是 0x40 加地址然后跟一个字节的数据最后必须发送 0x41 或者 0x00 来表示寄存器写入完成——这个细节很容易漏漏了之后寄存器根本没被写进去但单片机的 UART 缓冲区里会误以为收到了一堆垃圾数据。3.2 通信时序从发送命令到读取数据的精确节奏ADS122U04 的 UART 时序和普通设备有一个显著不同它没有片选信号完全靠帧间隔来区分命令和数据。所以 MCU 端发送命令之后必须等待足够的时间再读取数据否则会把 ADC 自动输出的转换结果当成命令回显。我实测的经验值是在 20SPS 数据速率下从发送启动转换命令到 TX 引脚开始输出数据典型延迟是 25ms 到 30ms。这个时间包含了 Δ-Σ 调制器的转换时间和数字滤波器的建立时间。如果 MCU 在发送命令后立刻去读 UART 接收缓冲区大概率读到的是空的或者上次残留的数据。正确的做法是发送 0x10 启动转换命令后用一个延时函数等待至少 35ms然后再去轮询 UART 的 RCIF 标志位或者开接收中断。还有个更稳妥的协议设计不依赖固定延时而是利用 ADS122U04 的 DRDY数据就绪功能。虽然它没有物理引脚直接输出 DRDY但它的状态字节机制可以让 MCU 查询。具体实现是发送 0x20 命令读取状态寄存器状态的 bit7 如果为 1说明上一次转换的数据还没被读走读取数据后该位自动清零。我在固件里就是先写一个函数发送 0x20检查返回的状态再决定是否读取数据字节。这样即使波特率或系统主频飘了也不会读错。3.3 PIC18LF45K40 的 UART 配置从零开始的手把手设置PIC18LF45K40 内置的 EUSART 模块用起来相当顺手。我用的初始化代码如下核心参数是波特率 9600、8 位数据、无校验、单停止位// PIC18LF45K40 EUSART1 初始化外部晶振 8MHz void UART1_Init(void) { // 波特率寄存器计算SPBRGL FOSC / (16 * Baud) - 1 // 8MHz / (16 * 9600) 52.08取 52实际波特率误差 0.15% SP1BRGL 52; SP1BRGH 0; // 配置 TXSTA8位模式、使能发送、异步模式 TX1STA 0x24; // TXEN1, BRGH1, SYNC0 // 配置 RCSTA8位模式、使能接收、使能串口 RC1STA 0x90; // SPEN1, CREN1 // 引脚配置TX 即 RC6RX 即 RC7 TRISCbits.TRISC6 0; TRISCbits.TRISC7 1; // 清除标志位 PIR3bits.RC1IF 0; PIR3bits.TX1IF 0; }波特率这里有个计算细节要交代清楚。PIC18LF45K40 的 EUSART 在 BRGH1 时波特率计算公式是 FOSC / (16 × (SPBRG 1))。外部晶振用 8MHz目标 9600 波特率计算出来 SPBRG 等于 51.08取整为 52实际波特率是 8,000,000 / (16 × 53) 9433误差 1.73%。这个误差虽然理论上小于 UART 的容错范围约 3%但我建议把晶振换到 7.3728MHz——这个频率除以 16 再除以 48 正好等于 9600一点误差都没有。如果系统里还有其他需要精确时基的地方比如 RTC这个晶振选择就更值了。3.4 关键配置速查表为了方便现场调试我把常用配置整理成了一张表直接照着写寄存器就行。场景PGA 增益数据速率参考电压激励电流寄存器配置值PT100 四线制16 倍20 SPS外部比率式1mA0x35 0x20 0x00 0x40PT1000 三线制8 倍20 SPS外部比率式0.5mA0x25 0x20 0x00 0x20K 型热电偶64 倍20 SPS内部 2.048V无0x55 0x20 0x10 0x00NTC 冷端补偿1 倍20 SPS内部 2.048V无0x00 0x20 0x10 0x00需要注意配置寄存器的 bit 定义在不同批次的数据手册上可能有微小的差异描述但实际寄存器值是一样的。如果你用的是新出的样片建议先读一遍寄存器默认值发送 0x20 命令确认出厂配置再写入自定义值。4. 实操过程与核心环节实现4.1 固件主流程从初始化到稳定输出的完整代码整个固件的流程可以拆成三块初始化、周期采集、数据处理。我的代码结构大致如下void main(void) { // 系统初始化 System_Init(); // 时钟、GPIO、UART ADC122U04_Init(); // 配置 ADC 寄存器 while(1) { // 启动一次转换 ADC122U04_StartConversion(); // 等待转换完成轮询状态寄存器 while(!ADC122U04_IsDataReady()) { // 喂看门狗或者处理其他任务 } // 读取 24 位转换结果 int32_t raw ADC122U04_ReadData(); // 计算实际温度 float temp Calculate_Temperature(raw); // 通过 RS485 或数码管输出 Send_Result(temp); // 按需延时控制采集周期 __delay_ms(100); } }这里我要强调一个工程上的习惯不要在等待转换完成的循环里干等。PIC18LF45K40 有独立的看门狗定时器如果转换时间超过看门狗周期系统会在等待期间被复位。我在调试时遇到过一次整机每隔几百毫秒重启一次的诡异现象查了半天才发现是看门狗没喂。后来在等待循环里加了喂狗操作同时用状态机把等待和数据处理拆成两步问题立刻消失。这就是 8 位机做工业应用的现实——没有操作系统帮你调度每个任务的执行时间都得自己心里有数。4.2 温度计算与线性化从原始码到摄氏度的三步转换拿到 24 位原始码之后转换成温度需要三个步骤。第一步是计算 ADC 输入端的电压。ADS122U04 的 24 位码是二进制补码格式参考电压设为外部 2.048V 时对应的电压计算公式是V_in (raw_code / 8388608) × (V_REF / PGA_Gain)这是最简单的一步关键是把类型处理好。PIC18 编译器我用的 XC8的 int 类型默认是 16 位如果直接把 raw 左移或右移再做除法会溢出。我的做法是把 24 位码读入联合体扩展成 32 位有符号整数再做浮点运算。XC8 的浮点库效率虽说不高但每秒处理 10 个点完全够用。第二步是反查电阻值。因为做的是比率测量V_ref 和激励电流同源可以直接用比例关系算出 PT100 的电阻R_pt100 (raw_code / 8388608) × R_ref其中 R_ref 是串联的精密参考电阻我用的 2kΩ0.01% 精度。这一步不需要知道具体的电压值天然消除了基准漂移的影响。第三步是根据电阻值查温度。这里我推荐用 Callendar-Van Dusen 方程而不是查表。CV 方程的精度在 -200℃ 到 850℃ 范围内可以达到 0.01℃而且计算量小对于 0℃ 以上 R_t R_0 × (1 A×t B×t²)对于 0℃ 以下 R_t R_0 × (1 A×t B×t² C×(t-100)×t³)其中 A3.9083e-3B-5.775e-7C-4.183e-12。反过来求温度就是解一元二次方程。我在代码里用的是迭代法先猜一个温度算电阻和实测电阻比较误差大了就修正猜测值循环五次以内就能收敛到 0.001℃。4.3 热电偶的冷端补偿与线性化细节如果你的系统同时要测热电偶那冷端补偿的细节必须单独说。我的做法是在 ADS122U04 的 AIN2/AIN3 通道接一个 NTC测量冷端温度然后软件补偿。补偿的完整流程是读 NTC 通道的 ADC 码用 Steinhart-Hart 方程算出冷端温度 T_cold单位℃。查热电偶分度表找到 T_cold 对应的热电势 E_cold单位 μV。读热电偶通道的 ADC 码换算成热电势 E_measured。实际热电势 E_total E_measured E_cold。再用 E_total 反查分度表得到热端温度。这里最容易被忽略的是热电偶通道的 ADC 码换算时所用的 PGA 增益和参考电压必须和校准时的条件完全一致。我出现过因为 PGA 增益从 64 倍改到 128 倍但补偿代码没同步修改导致温度读数差了整整一倍的问题。排查了半天最后发现是宏定义没改全。工程上这种低级错误最致命所以我在代码里把所有关键参数都集中放在同一个头文件的宏定义中改一处全生效。4.4 现场实测数据稳定性和精度的实际表现这个项目做完后我把 8 路 PT100 采集板放到恒温槽里做了 24 小时稳定性测试。测试条件是恒温槽设定 50.00℃数据速率 20SPSPGA 增益 16 倍四线制。实测结果非常能说明问题统计项数值平均值50.013℃标准偏差0.008℃峰峰值0.04℃最大偏差0.03℃这个峰峰值 0.04℃ 的水平对于 24 位 ADC 来说属于正常发挥不是顶尖水平。如果你用数字滤波器比如 50Hz 工频陷波模式并把数据速率降到 5SPS峰峰值可以压到 0.01℃ 以内。但 HVAC 应用里我反而不建议把响应速度拖太慢——空调控制的温度变化本来就慢但 PID 输出需要实时性采样周期超过 200ms 会让控制品质明显下降。20SPS 是个合理的中间值。5. 常见问题与排查技巧实录5.1 问题一ADC 数据周期性跳变量值约为满量程的 1/3这是我在第一版 PCB 上遇到的怪问题。现象是温度读数每 7~8 个数据点就跳一次跳变的幅度大概相当于温度的 20℃。用示波器看 TX 引脚波形发现数据帧的第二个字节在高电平时出现了明显的台阶持续几百微秒后又恢复正常。排查思路先排除了 UART 通信问题——MCU 收到的数据帧本身是完整的校验和也对。再怀疑数字电源干扰——把 DVDD 和 AVDD 完全分开供电问题依旧。最后发现罪魁祸首是 PCB 布线ADS122U04 的 SCLK 引脚这个芯片的 UART 时钟引脚旁边走了一条开关电源的反馈线高频振荡耦合进了 SCLK导致内部移位寄存器偶尔多移一位。解决方法是把 SCLK 走线换到地平面另一侧并在 SCLK 和 GND 之间加了一个 10pF 的电容做低通滤波。改完之后跳变彻底消失。这个教训让我在那之后的布局规划里把所有模拟引脚AIN0~AIN3、REF、SCLK都放在地平面完整覆盖的区域远离高频走线。5.2 问题二POR 后第一次读数始终不准后面才恢复正常这是一个非常典型的 Δ-Σ ADC 冷启动问题。ADS122U04 在上电后需要大约 20ms 的稳定时间内部参考电压建立后才能进行准确的转换。如果你的固件在上电后立刻发送启动命令读到的第一个数据几乎必然是垃圾值。解决办法分两层。硬件层在 ADS122U04 的 AVDD 引脚上加一个 100μF 的电解电容延长电源建立时间但这个方法的缺点是延长了整体上电时间。软件层更推荐在初始化时读取两次数据第一次丢弃只保留第二次的结果。我习惯用一个简单的状态机上电后先发送 0x10 启动转换读回数据但不参与计算紧接着发送第二次转换命令这次读回的数据才用于输出。这样即使电源建立时间不可控也能保证每次上电后 100ms 内输出正确读数。另外还要注意PIC18LF45K40 的复位引脚在掉电瞬间可能会输出一段毛刺信号如果这个毛刺恰好连接到 ADS122U04 的 RESET 引脚会导致 ADC 被意外复位。我的做法是把 RESET 引脚用 100kΩ 上拉电阻拉到 3.3V并串联 1kΩ 电阻到 MCU 的 GPIO形成 RC 滤波既保证正常复位速度又滤掉毛刺。5.3 问题三冷端补偿温度读取出现毫伏级跳变HVAC 现场经常有强电磁干扰源比如压缩机、变频器等。NTC 的走线如果和强电线束共用线槽干扰会直接感应进来。我的解决方案有三个在 NTC 的电源端并联一个 0.1μF 电容形成一个低通滤波器截止频率大约在 1.6kHz足以滤掉大部分开关电源的纹波。在固件里对 NTC 通道的数据做一个简单的滑动平均滤波取最近 4 个样本的平均值。这个滤波只对冷端通道做热电偶主通道不做——因为主通道本身已经有 Δ-Σ 的数字滤波再加一层反而会把 50Hz 工频陷波的效果抵消掉。如果现场特别恶劣我建议冷端传感器不要用 NTC改用数字温度传感器比如 TMP117直接通过 I2C 读温度彻底避免模拟干扰。5.4 问题四PIC18LF45K40 的 EUSART 接收数据偶尔丢字节这个问题出现在波特率误差叠加的场景。如果用 8MHz 晶振跑 9600 波特率理论误差是 1.73%。如果 PIC 的晶振本身精度只有 ±20ppm再加上 UART 收发双方各自的容差偶尔丢字节并不意外。解决办法最彻底的是换 7.3728MHz 晶振我之前已经提过。如果板子已经做好不能换晶振那就把 UART 中断的优先级调高并且在中断服务函数里不做任何耗时操作只是把收到的字节丢进环形缓冲区数据处理放到主循环里完成。此外接收数据时用软件判断帧起始和结束通过检测帧之间的静默期比如超过 1 个字节时间没有新数据就认为当前帧结束可以防止半帧数据被误拼接。5.5 问题速查表现象可能原因排查方法数据周期性跳变 1/3 满量程数字线与模拟线串扰示波器观察 TX 波形重新布线上电首个数据异常参考电压未稳定丢弃第一次转换结果温度读数整体偏移引线电阻未补偿检查接线方式是否为三线/四线冷端温度跳动几毫伏NTC 线路受干扰加 RC 滤波或改用数字温度传感器UART 偶发丢字节波特率误差 晶振漂移换 7.3728MHz 晶振或优化中断读数在最高位跳变24/16 位模式配置错误确认配置寄存器 DR 位写在最后的个人体会这套“ADS122U04IPWR PIC18LF45K40”的方案我在工业管道测温、HVAC 风管温度采集、冷水机组进出水温差检测三个项目里都完整跑过。整体感受是选型上非常稳性能上限对于绝大多数温度测量场景来说绰绰有余真正决定成败的反而是那些不起眼的细节——电源滤波、布线规划、固件时序、冷端补偿、标定流程。每次调试这类高精度 ADC 电路我都会想起一个比喻24 位 ADC 就像一把出厂精度极高的卡尺但如果测量台面本身在晃动、卡尺的弹簧片上有锈迹那再高的卡尺精度也白搭。处理电源、布局和线性化就是在把测量台面和弹簧片打磨平整。如果你手头正好要做类似的温度采集项目我的建议是第一版 PCB 尽量留出测试点每一个关键电压节点AVDD、DVDD、REF、AIN 输入都要能方便地用示波器探头接触。调试时先把硬件调稳再写固件逻辑不要一上来就全速跑数据。稳扎稳打这套方案能给你带来非常可靠的收益。