做温控设备的人大概都有过这种经历:以为测温度是一道送分题,结果在项目现场被现实反复教育。尤其工业和 HVAC 场景,传感器线和主控板隔着几米甚至几十米,变频器一开读数就开始跳舞;想把精度做到 0.1℃ 级别,光靠单片机内置 ADC 根本交不了差。我在这类项目上常用一个组合:ADS122U04IPWR 加 PIC18F86J10。前者是 TI 带 UART 接口的 24 位 Δ-Σ ADC,后者是 Microchip 的 8 位增强型单片机,两个芯片看起来都不算“新潮”,放在一起却能比较干净地解决高精度温度采集问题,而且 PCB 布线和软件栈都比想象中简单。这篇就把硬件连接、传感器前端设计、UART 通信协议、温度换算和现场调试的完整经验写出来,给正在做暖通、冷冻、机房环境监控或者工业温度采集的朋友做个参考。
ADS122U04IPWR 本质上是一个 4 通道、24 位 Δ-Σ ADC,内置可编程增益放大器(PGA 1~128)、两路恒流源、内部 2.048V 基准电压,以及一个用于热电偶冷端补偿的内部温度传感器。最特别的是它没有走传统的 SPI/I2C,而是把 UART 作为主通信接口。这意味着主控这边只需要两根线,就能拿到一个完整的高精度模拟前端。PIC18F86J10 的定位也很有意思——它有多达 5 个独立 EUSART,3.3V 工作电压,几十 KB Flash 足够放温度查表和滤波算法,跑一个或跑五个 ADS122U04 都很从容。下面直接进入正题。
1. 为什么用 UART 接口的 24 位 ADC,而不是 SPI 或 I2C
很多人看到“UART 接口的 ADC”第一反应是奇怪。SPI 和 I2C 不是更快、更常见吗?我的回答是:常见不一定合适。ADS122U04 这类器件解决的场景,恰恰是 SPI/I2C 在工程现场最别扭的几个问题。
1.1 SPI/I2C 在温度采集场景的痛点
SPI 需要至少 4 根线:SCLK、MISO、MOSI,再加上片选 CS。短距离 PCB 上没问题,可一旦传感器板要和主控板分开,SPI 的时钟线和数据线就成了天线,时钟偏移、振铃、误码都会冒出来。I2C 只有两根线,但它是开漏结构,需要上拉电阻,抗干扰能力一般,而且地址仲裁在长线上并不好用。更重要的是,I2C 的时钟同步机制在处理 ADC 这种“随时要读数据”的场景时,代码稍微一复杂就容易卡死在总线等待上。
UART 的优势很直接:异步通信,没有时钟线,没有片选,没有地址仲裁。主控发一个命令字节,ADC 回一帧数据,物理上就是两根线,抗干扰重点只在电平选择和线缆屏蔽上,概念简单得多。而且 UART 是标准外设,PIC18F86J10 的 EUSART 模块直接支持,不需要用 IO 口模拟时序。
下面这张表是我选型时常用的对比维度:
| 项目 | SPI | I2C | UART(ADS122U04 方案) |
|---|---|---|---|
| 信号线数量 | 至少 4 根 | 2 根开漏 | 2 根推挽 |
| 主从关系 | 主控片选控制 | 地址仲裁 | 点对点,1 主 1 从 |
| 长线能力 | 较差,时钟易畸变 | 一般,受上拉和总线电容限制 | 较好,可扩展 RS-485 |
| 抗干扰 | 中 | 中 | 较好 |
| 波特率要求 | 无 | 无 | 需双方匹配 |
| 多从机支持 | 片选扩展 | 地址扩展 | 菊花链或逐片 UART |
ADS122U04 的 UART 波特率默认常用 9600bps,对温度采集来说完全够用。24 位 ADC 在 20SPS 下,每秒只需要传 4 字节左右的数据量,9600bps 的线速宽裕得很。
1.2 PIC18F86J10 为什么合适
PIC18F86J10 本身不是一个以“高性能”著称的片子,但它有几个点很贴合这个应用:第一,工作电压 2.0~3.6V,和 ADS122U04 的 DVDD 电平天然匹配,不需要电平转换芯片;第二,它提供 5 个 EUSART,如果一套系统需要测 5 路高精度温度,一颗单片机就能同时带 5 个 ADS122U04;第三,它的 Flash 容量足够放热电偶的 NIST 查表数据和滤波缓冲,不会像小容量单片机那样捉襟见肘。
有个细节值得说:ADS122U04 的 UART 响应帧第一个字节是状态字节,里面带了数据就绪标志位。这意味着即使不额外接 DRDY 引脚,软件也能知道当前读到的 24 位数据是不是新样本。对于 HVAC 这种需要长时间稳定运行的设备,少一根线就是少一个故障点,状态字节查询比外部中断更省心。
2. 硬件连接:电源域、时钟和 PIC18F86J10 的 UART 引脚
硬件设计上,这个组合的难度不在接线,而在电源和模拟前端。下面是核心连接关系:
| ADS122U04IPWR 信号 | 连接目标 | 说明 |
|---|---|---|
| AVDD | 3.3V 模拟电源 | 加 1μF + 0.1μF 去耦 |
| DVDD | 3.3V 数字电源 | 加 0.1μF 去耦,与 AVDD 推荐同源 |
| GND | 系统地 | 模拟地与数字地单点连接 |
| MCLK | 4.096MHz 晶振 | 最好是干净时钟,不要走长线 |
| RXD | PIC EUSART1 的 TX 引脚 | 交叉连接 |
| TXD | PIC EUSART1 的 RX 引脚 | 交叉连接 |
| AIN0 / AIN1 | 传感器差分输入 | 热电偶或 RTD |
| RESET | 3.3V 上拉或 MCU IO 控制 | 低有效复位 |
2.1 供电和去耦的实操细节
工业板上经常有 24V 开关电源、继电器、变频器,这些都会往电源线上灌噪声。ADS122U04 的 AVDD 我习惯用一个低噪声 LDO 单独供给,比方说 3.3V 输出,输入侧加磁珠和 10μF 电容,输出侧加 1μF 和 0.1μF 并联。DVDD 可以和 PIC18F86J10 共用一路数字 3.3V,但要保证 AVDD 和 DVDD 的地最终在单点汇合,不要让数字电流流经模拟地。
千万别图省事把 AVDD 直接接到开关电源的 3.3V 上。24 位 ADC 对参考电压和电源纹波非常敏感,VREF 内部虽然是 2.048V,但模拟供电不干净,直接表现为转换码字尾部抖动,现场表现就是温度读数在小数点后两位来回跳。实测下来,同样的传感器,电源上多一个 LDO,短期噪声能差出一个数量级。
2.2 时钟:外部晶振还是内部振荡器
ADS122U04 支持外部 MCLK 和内部振荡器两种模式。能用外部 4.096MHz 晶振,我建议尽量用。原因有两个:一是调制器时钟更准,50Hz/60Hz 工频抑制效果更好;二是内部振荡器受温度影响会有频率漂移,虽然对比例式 RTD 测量影响不大,但对热电偶这种需要精确小信号放大的场景,时钟稳定性直接关系到转换噪声。
PIC18F86J10 这边的时钟,如果只是为了 9600bps 的 UART,用内部 8MHz 振荡器就够了,SPBRG 误差可以控制在 0.2% 以内。但如果整个系统还需要跑别的实时任务,可以考虑外部晶振配合 PLL。注意,PIC 的波特率误差超过 2% 时,UART 通信会开始偶发错帧,设计阶段就把晶体频率和波特率算好。
3. 传感器侧设计:热电偶、RTD 与内部温度传感器的配合
这个方案真正的价值在传感器前端。HVAC 和工业现场常见的温度传感器无非三类:热电偶、RTD(PT100/PT1000)、NTC。ADS122U04 一套硬件可以全部覆盖,但接法和配置完全不同。
3.1 热电偶:PGA 增益不能拍脑袋选
热电偶输出的是微伏级差模电压,K 型热电偶在 1000℃ 时输出大约 41mV。如果 PGA 选 128,满量程只有 ±16mV,信号一过 300℃ 就饱和;选 32 则满量程 ±64mV,可以覆盖 K 型热电偶 -200~1350℃ 的绝大部分范围。我实际用下来,K 型热电偶配 PGA=32 是最稳妥的,LSB 等效温度约 0.06℃,加上系统噪声,最终有效分辨率做到 0.1~0.15℃ 没有问题。
热电偶测量必须做冷端补偿。ADS122U04 内部自带温度传感器,不需要外接 NTC 或热敏电阻来测冷端,这是选型时非常省事的一点。接线时让热电偶的冷端尽量贴近芯片和 PCB 的等温区,不要跨过大功率发热元件,否则内部温度传感器测到的温度和实际冷端温度不一致,软件再怎么补偿也是白搭。
3.2 RTD 比例式接法与激励电流选择
RTD 我强烈推荐比例式接法,而不是把 RTD 当普通电阻分压测绝对电压。比例式的好处是:激励电流的漂移不会影响测量结果,因为 ADC 的参考电压和输入电压都来自同一个电流源,电流变化被自动抵消。
以 PT100 为例,用一个 IDAC 输出 1mA 激励电流,电流流过 RTD 后,再流过参考电阻 Rref 回到地。REFP/REFN 差分输入接在 Rref 两端,AIN0/AIN1 接在 RTD 两端。这样转换结果等于 RTD 电阻和 Rref 电阻的比值,只要 Rref 选高精度低温漂电阻,测量精度就主要取决于 Rref 而非电流源。
下面是我常用的一组参考配置,给出之前务必核算输入是否超出满量程:
| 传感器 | 测温范围 | 激励电流 | Rref | PGA | 说明 |
|---|---|---|---|---|---|
| PT100 | 0~300℃ | 1mA | 1kΩ | 4 | 0℃ 时约 100mV,300℃ 约 212mV,FS=±250mV |
| PT1000 | 0~200℃ | 0.5mA | 2kΩ | 1 | Vref=1V,信号 0.5~0.88V,FS=±1V |
| K 型热电偶 | -200~1350℃ | 无 | 内部 2.048V | 32 | FS=±64mV |
如果做 3 线 RTD,用两路 IDAC 分别从 RTD 两端注入电流,可以抵消引线电阻的影响;4 线则更准。对 HVAC 设备,一般 3 线 PT100 已经足够,4 线主要留给实验室级应用。
3.3 NTC:别浪费 24 位精度
NTC 热敏电阻便宜、响应快,HVAC 里大量使用,但它非线性大,本身精度 0.5℃ 就顶天了。用 ADS122U04 去测 NTC 有点“杀鸡用牛刀”,但如果你板上已经有这个 ADC,顺便接一路 10k NTC 分压测量室外环境温度也没问题。我的做法是让 NTC 和一个精密电阻串联,用内部参考电压做比例测量,再通过 B 值公式或查表换算。NTC 的自热要控制住,激励电流不要超过 100μA,否则误差会很大。
4. PIC18F86J10 端的 UART 初始化和寄存器配置代码
这套系统的软件核心就两块:PIC18F86J10 的 EUSART 驱动,以及 ADS122U04 的寄存器配置。代码用 MPLAB X + XC8 写,8 位单片机上的代码不追求花哨,稳定、可读、容易调试最重要。
4.1 EUSART1 初始化与发送接收函数
假设 PIC18F86J10 使用内部 8MHz 振荡器,波特率 9600,8 位数据、无校验、1 位停止位。
#define FOSC 8000000UL #define BAUD_RATE 9600UL void uart1_init(void) { TRISC6 = 0; // TX1 输出 TRISC7 = 1; // RX1 输入 TXSTA1bits.SYNC = 0; // 异步模式 TXSTA1bits.BRGH = 1; // 高速波特率模式 SPBRG1 = (unsigned char)((FOSC / (16UL * BAUD_RATE)) - 1UL); RCSTA1bits.SPEN = 1; // 使能串口 RCSTA1bits.CREN = 1; // 允许接收 TXSTA1bits.TXEN = 1; // 允许发送 } void uart1_tx(unsigned char ch) { while (!TXSTA1bits.TRMT); // 等待发送完成 TXREG1 = ch; } unsigned char uart1_rx(void) { while (!PIR1bits.RC1IF); // 等待接收完成 return RCREG1; }这里有一个很实际的建议:发送命令给 ADS122U04 之后,响应不是立刻回来的,芯片需要一点时间处理命令。如果立即死等接收,逻辑上没问题,但遇到芯片没正确上电或者线路接触不良,程序就会卡死在uart1_rx。批量产品里这种问题最让人头疼。我在实际代码里会给接收加超时,超过比如 50ms 就返回错误并重新发送同步命令,保证系统能自动恢复。
4.2 发送命令与写寄存器
ADS122U04 的 UART 命令机制不复杂。宏定义如下:
#define ADS_CMD_NOP 0x00 #define ADS_CMD_RDATA 0x10 #define ADS_CMD_RREG 0x20 // 读取寄存器,加寄存器号 #define ADS_CMD_WREG 0x40 // 写入寄存器,加寄存器号写寄存器时,先发命令字节,再发配置值:
void ads_write_reg(unsigned char reg, unsigned char val) { uart1_tx(ADS_CMD_WREG + reg); uart1_tx(val); __delay_ms(1); // 留出命令处理时间 } unsigned char ads_read_reg(unsigned char reg) { unsigned char status; unsigned char val; uart1_tx(ADS_CMD_RREG + reg); status = uart1_rx(); // 第一个字节是状态字节,判断通信状态 val = uart1_rx(); // 第二个字节才是寄存器内容 return val; }读回验证这一步很重要。每次配置完寄存器,我都习惯马上ads_read_reg读一遍,确认写入生效。否则传感器测了半天,最后发现是某个配置位没写进去,排查成本会很高。
4.3 初始化配置示例与查手册的提醒
以下配置针对 K 型热电偶:AIN0/AIN1 差分输入、PGA=32、内部 2.048V 参考、连续转换模式。
void ads_init(void) { __delay_ms(50); // 上电稳定 uart1_tx(ADS_CMD_NOP); // 通信字节对齐 __delay_ms(1); // 寄存器0:输入MUX、PGA、数据速率 // 字段组合请对照数据手册的寄存器表,以下示例为 PGA=32、AIN0/AIN1、20SPS ads_write_reg(0, 0xA0); // 寄存器1:参考选择、转换模式 // 我习惯使用内部参考,连续转换模式 ads_write_reg(1, 0x00); // 寄存器2:IDAC 配置,以及内部温度传感器使能位 // 如果做热电偶冷端补偿,需要把数据手册中标为 TS 的字段置为对应值 // 下面这个示例值假设当前场景关闭 IDAC,温度传感器使能字段按手册填入 ads_write_reg(2, 0x00); // 寄存器3:GPIO 等附加功能,默认 0x00 ads_write_reg(3, 0x00); }必须强调一点:ADS122U04 的寄存器位定义在同一系列里会随器件版本有细微差别,时间长了我也不能保证脑子里的位图永远正确。所以我上面写的是“示例值 + 配置思路”,真正做板子时必须打开你手里那颗料对应版本的数据手册,把寄存器字段一个个核对。这不是废话,这是吃过亏之后的习惯。
4.4 读取转换数据的完整函数
查询模式下读取温度数据,核心是发送RDATA命令,然后接收 4 个字节:第一个是状态字节,后面三个是 24 位转换结果,补码表示。
#define ADS_STATUS_DRDY 0x80 // 状态字节中的数据就绪位,以手册为准 int32_t ads_read_data(void) { unsigned char buf[4]; uint32_t raw; int32_t code; uart1_tx(ADS_CMD_RDATA); for (unsigned char i = 0; i < 4; i++) { unsigned int timeout = 0; while (!PIR1bits.RC1IF) { if (++timeout > 30000) return 0x7FFFFFFF; // 超时,返回异常值 } buf[i] = RCREG1; } // 状态字节的 DRDY 位如果为 0,说明数据未更新 // 可以根据应用决定返回上一帧还是继续等待 raw = ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | (uint32_t)buf[3]; if (raw & 0x800000uL) raw |= 0xFF000000uL; // 24 位转 32 位补码 code = (int32_t)raw; return code; }主循环里,我一般是每隔 50ms 查询一次状态字节,或者配合一个定时器同步,50ms 对 20SPS 的转换周期来说刚好能命中每次新数据。PIC18F86J10 的主频不高,但做这个逻辑一点压力都没有。
5. 从原始码到温度:换算公式、冷端补偿与滤波
拿到 24 位原始码只是第一步。很多项目做出来读数不对,问题都出在“码值”到“物理温度”的换算上。
5.1 ADC 码值到电压的换算
ADS122U04 是 24 位 Δ-Σ ADC,满量程对应±Vref / PGA。换算公式为:
V_in = code / 8388608 × (Vref / PGA)其中 8388608 是 2 的 23 次方,code 是已经符号扩展为 32 位的有符号整数。以 K 型热电偶、PGA=32、Vref=2.048V 为例:
float v_input = (float)code * (2.048f / (32.0f * 8388608.0f));要注意的是,如果 code 接近0x7FFFFF或者0x800000,大概率不是真实温度,而是输入超出满量程、参考电压没建立好、或者接线断线。软件里应该加一个范围检查,把这些极端值直接判为故障。
5.2 热电偶冷端补偿:不是简单加一个温度
冷端补偿最常见的问题是有人把冷端温度直接加到测量温度上,这是不对的。热电偶输出的是“热端与冷端之间的电动势差”。当冷端在 25℃,热端在 100℃ 时,热电偶输出的电动势并不是 100℃ 对应的电动势,而是 100℃ 电动势减去 25℃ 电动势。
正规做法分两步:
- 读出冷端温度
T_cj,通过 NIST 多项式或查表,把T_cj换算成对应电动势E_cj。 - 将
E_cj加上 ADC 实测的热电偶电动势E_meas,得到等效 0℃ 冷端下的总电动势,再通过反查表得到热端温度T_hot。
PIC18F86J10 的 Flash 放一张 K 型热电偶的温度-毫伏查表毫无压力。如果不想做复杂的多项式运算,用分段线性插值表,每 1℃ 一个点,内存开销也就几百字节,查表速度还很快。
5.3 RTD 的温度换算
RTD 走比例式测量时,原始码直接对应电阻比值:
R_rtd = (code / 8388608 × PGA) × Rref算出电阻之后,PT100/PT1000 再通过 Callendar-Van Dusen 公式或查表转温度。HVAC 范围通常 -40~150℃,用查表加插值就够了。要注意的是,如果 Rref 不是整数精度,最好在代码里用浮点存校准后的实际电阻值,而不是标称值。
5.4 滤波:20SPS 下的软件平滑
工业现场的读数不可能像实验室那么干净。ADS122U04 在 20SPS 下本身已经有较强的数字滤波,但软件侧再加一层滤波,能让 PID 控制稳很多。
我的做法是先做中值滤波去掉野值,再做 16 点滑动平均。比如每秒取 20 个样本,去掉最大最小,剩下的 18 个求平均,等效噪声能再降一倍左右。HVAC 系统本身响应慢,一秒一次的平滑滤波完全不会影响控制效果。
6. 现场调试中踩过的坑:通信“假死”、数据漂移和变频器干扰
这部分是我最想写的。因为它不是一个一个报错丢给程序员去查,而是整个系统联调时最耗时的环节。
6.1 UART 通信“假死”:换晶振后接上电脑就正常,上设备就断
我遇到过一次很典型的故障:开发板上用 USB 转串口直接连 ADS122U04,读数据一切正常;装进设备后,MCU 和 ADC 之间偶尔就收不到数据了,而且不是立即出问题,是运行几小时后出现。
排查过程是这样的:先示波器抓 MCU 的 TX 引脚,发现波形正常;再抓 ADC 的 TX,发现 MCU 的 RX 引脚上信号电平偏弱,上升沿变缓。原因最后锁定在 MCU 侧 RX 引脚没有做上拉,而 PCB 走线过长导致寄生电容把信号边沿拖慢了。解决方法是把走线缩短到 5cm 以内,同时在 RX 引脚加 10kΩ 上拉。温度采集的 UART 速率不高,但对边沿质量的要求并不低。
6.2 读数一直偏高:不是传感器问题,是芯片自己在发热
另一个常见问题是热电偶冷端温度读数比环境温度高 2~3℃。ADS122U04 本身功耗不大,但如果在 PCB 上靠近 DC-DC 电感、功率电阻,或者被外壳里的风机控制器加热,内部温度传感器测到的就不是真正的环境冷端温度。
解决思路分两层:硬件上把 ADC 和发热源拉开距离,必要时在冷端区域做等温铜块;软件上做单点偏移校准,在整机稳定工作温度下,用一个已知精度的温度计对比,把偏移量存进 Flash。现场标定比实验室精确标定更符合 HVAC 的实际工况。
6.3 变频器一开,温度读数跳变
这是 工业/暖通现场最典型的问题。变频器启动瞬间,UART 误码率升高,温度读数出现瞬间跳变,有时一次跳 5℃ 以上。
我最终的处理方案是三级防护:
- 模拟输入加 RC 低通滤波,把高频耦合挡在 ADC 之前;
- ADS122U04 数据速率固定到 20SPS,利用 Δ-Σ ADC 内置的工频抑制;
- 软件中做数据合理性检查,连续 3 次读数差值超过 2℃ 就丢弃,从而避免 PID 控制器因为一个野值猛调输出。
如果这三层还压不住,就需要考虑把 ADS122U04 放到传感器端,PIC18F86J10 通过隔离 RS-485 和它通信。PIC18F86J10 有 5 个 EUSART,其中一个专门接隔离收发器,另外几个还能继续接其他节点,架构上非常干净。
6.4 参考电压不稳:读数整体比例偏移
比例式 RTD 测量中,如果 Rref 两端电压不稳,但 ADC 却把这个不稳的电压当参考,读数和真实温度会有比例偏差。排查方法是用高精度万用表直接量 REFP/REFN 之间的电压,如果不稳定,优先查 AVDD 的纹波和 Rref 的焊接。Rref 一定要选低温度系数电阻,比如 ±10ppm/℃ 甚至 ±5ppm/℃,否则环境温度变化,整个量程都会跟着漂。
7. 标定与长期运行:把误差锁定在合理范围内的实操办法
温度采集系统不做标定,精度都是纸上谈兵。尤其量产项目,每个传感器个体差异、PCB 焊接差异、芯片内部温度传感器差异,都会让读数出现一致性偏差。
7.1 两点标定流程
我的标定流程不复杂:用精密电阻箱代替传感器,分别给两个温度点对应的电阻值。
比如 PT100,0℃ 是 100Ω,100℃ 是 138.51Ω。把电阻箱设到 100Ω,记录 ADC 原始码,作为零点;设到 138.51Ω,记录原始码,作为增益点。两点就能算出校准斜率和偏移:
真实温度 = 原始读数 × 斜率 + 偏移把斜率和偏移写到 PIC18F86J10 的 Flash 数据区,每次上电读取。生产线上两块板子就能完成校准,效率很高。热电偶的冷端补偿也是同理,在恒温箱里做一个单点偏移校准,就能把内部温度传感器的个体偏差压下去。
7.2 长期稳定性与防潮防腐
HVAC 现场最容易被忽视的是凝露。传感器端子一旦结露,绝缘阻抗下降,高阻抗的热电偶信号会漏电,读数会慢慢漂。PCB 建议做三防漆涂覆,输入端走线尽量短,焊盘之间留足够间距。连接器如果长期处于振动环境,最好选带锁扣的端子,不然用几年后接触电阻变化会让温度读数无规律跳动。
7.3 我最后的经验
这套 ADS122U04IPWR + PIC18F86J10 的组合,我在两代温控器产品上用过,累计运行时间超过一年。最大的感受是:选型时“有特色的芯片”有时比“性能参数最高的芯片”更好用。UART 接口的 ADC 让我在主控板上省掉了 SPI 时序调试的烦恼,PIC18F86J10 的 5 个 EUSART 又给后续扩展留足了余地。做温度采集,真正难的不是把线路跑通,而是把电源、传感器前端、标定、抗干扰这些看似琐碎的事情在一个系统里平衡好。希望这篇能帮你少走几步弯路。