去年我接了一个工业温度采集模块的活儿,传感器类型五花八门:PT100热电阻、K型热电偶、10K NTC热敏电阻全都有,还要兼顾HVAC现场的风管温度、冷水机组进出水温这些点,精度要求并不低。最开始想用常规SPI接口ADC,但布线、时序、抗干扰这些事越往后越头疼,最后换成了ADS122U04IPWR加PIC32MZ1024EFK144的方案。这个组合很多做嵌入式的人不熟,因为ADS122U04是少见的UART接口24位ADC,而PIC32MZ1024EFK144又是MIPS内核的MCU,两者搭配起来解决工业和HVAC温度测量,效果比我预想的好很多。
这篇文章就把整个项目的思路、电路、协议、算法和踩坑过程完整写出来,适合正在做高精度温度采集、或者想了解UART型ADC怎么用的工程师参考。我会尽量把“为什么这么干”讲透,而不是只丢一堆寄存器配置。
1. 为什么选UART接口的24位ADC,以及PIC32MZ1024EFK144在这里的角色
1.1 项目原始需求:多类型传感器、混合输入、长线传输
这个模块要装在HVAC控制柜里,同时又有一批工业现场的测温点要接进来。传感器类型有三种:
- PT100热电阻,用来测冷水机组进出水和风管温度,精度要求±0.2℃以内;
- K型热电偶,测工业设备表面温度,范围到400℃;
- 10K NTC热敏电阻,HVAC里最常用的回风/送风温度传感器,精度要求不高但数量多。
三种传感器混在一个板卡上,输入通道还得复用,这就对ADC提出了几个硬性要求:分辨率要够高、输入通道要能做差分/单端切换、最好内置激励电流源(给PT100用),而且因为传感器可能分布在现场不同位置,ADC和MCU之间的通信距离不能太短。
如果按传统方案,用SPI接口ADC,比如ADS124S08这类,本身没什么问题,但SPI的片选、时钟、MISO/MOSI四条线在远距离传输时容易受干扰,而且MCU读取数据时要严格跟着时钟时序走。现场布线稍微乱一点,通信就容易出错。
1.2 ADS122U04的差异化价值:串口通信的ADC
ADS122U04是TI的一款24位ΔΣ ADC,最大亮点是把UART接口做成了主通信方式。UART在嵌入式里是“万金油”,两条线就能通信,波特率可以按需调,而且单片机侧几乎都有硬件UART/FIFO,不需要像SPI那样精确控制片选时序。
打个比方:SPI像两个人在房间里面对面快速传纸条,速度快但对方手必须伸得准;UART像两个人隔着一堵墙喊话,速度慢一点,但距离远、抗干扰强,只要约好节奏就行。
ADC侧和MCU侧之间只需要连接发送、接收和DRDY(数据就绪)三条关键信号线,如果现场距离超过一米,还可以把UART电平转成RS485或者加光耦隔离,这对工业现场和HVAC机房这种环境非常友好。ADS122U04内部还集成了PGA(可编程增益放大器)、2.048V基准、内部温度传感器和两个激励电流源,PT100、热电偶、NTC都能直接接,省掉一堆外部调理电路。
另外要注意“IPWR”这个后缀:它是TSSOP-16封装,和某些其他封装(比如QFN)的引脚定义不一样。如果照着不同封装的原理图绕板,很容易出错。我们最后统一用IPWR封装,采购也方便。
1.3 PIC32MZ1024EFK144能干什么
PIC32MZ1024EFK144是Microchip的MIPS内核MCU,主频可以到200MHz,带单精度FPU,Flash有1MB,RAM有512KB。在温度采集系统里,它的任务不只是“读ADC”,还包括:
- 负责多片ADS122U04的UART通信管理;
- 做传感器线性化计算,比如热电偶查表、PT100的Callendar-Van Dusen方程计算,这些运算需要浮点,PIC32MZ的FPU帮了大忙;
- 跑Modbus或者自定义协议,把温度值上报给PLC或上位机;
- 如果后面要扩展,它自身还带以太网、CAN、USB等接口,直接可以做边缘网关。
选它而不是普通8位MCU,核心原因就是计算余量。以热电偶为例,K型分度表的反算如果不用FPU,用查表加插值也能做,但在多个通道同时采集、同时处理时,CPU占用率高会影响UART接收的实时性。用PIC32MZ后,所有这些都能扛下来。
我把ADS122U04和传统SPI ADC做了个简单对比:
| 对比项 | ADS122U04(UART) | 传统SPI接口ADC |
|---|---|---|
| 接口线数 | 2条(TX/RX) | 4条(SCLK/MOSI/MISO/CS) |
| 远距离传输 | 容易转RS485/隔离 | 需要对时序做特殊处理 |
| MCU侧对接成本 | 任意UART外设 | 需要SPI外设并处理片选 |
| 数据读取方式 | 发命令后从UART拿字节 | 按SCLK边沿移位读 |
| 适合场景 | 分布式、多板卡、工业现场 | 板上近距离、高速采集 |
这个对比是基于我实际使用的感受,并不是说SPI方案不好。如果传感器全部在同一个PCB上,SPI的吞吐率确实更高;但一旦出现“传感器远离采集板”这种场景,UART型ADC的优点就非常突出。
2. 硬件接线与传感器前端设计
2.1 最小系统:三个信号线加一个复位
ADS122U04和PIC32MZ1024EFK144之间的连接非常简洁。我们这一版只用了四个引脚:
- ADC的发送脚接PIC32MZ的UART接收脚;
- ADC的接收脚接PIC32MZ的UART发送脚;
- DRDY接PIC32MZ的一个外部中断输入,用来触发数据读取;
- RESET接一个普通GPIO,用于系统启动时对ADC做硬件复位。
PIC32MZ1024EFK144自带多个UART外设,如果后续要做多片级联,比如6片ADS122U04接24路传感器,只需要每片占用一组UART加一个中断引脚,资源完全够用。
电源方面,模拟电源AVDD和数字电源DVDD我们都给了3.3V,但分别用两个LDO供电,避免数字噪声通过电源串进模拟前端。模拟地AGND和数字地DGND在PCB上做单点连接,连接点选在ADC芯片下方的过孔区域,不要铺一整片连通,否则数字噪声容易从地平面耦合到模拟信号。
DRDY这根线特别重要。ADS122U04转换完成后,DRDY会拉低,MCU看到下降沿后就可以通过UART读取转换结果。我们把这个引脚直接接到PIC32MZ的INT0外部中断脚,中断服务程序里只置一个标志位,主循环看到标志位再去读UART数据,避免在中断里做耗时操作。
2.2 三种传感器的输入电路
传感器输入电路这一块,我按传感器类型分别处理。
PT100接法:使用ADC内置的激励电流源,配置为1mA输出,流经PT100后在AIN0/AIN1之间产生差分电压。为什么要用差分而不是单端?因为PT100引线通常比较长,线电阻会造成额外压降,差分测量配合开尔文接法可以把引线电阻的影响降到最低。如果是两线制PT100,线电阻没法完全消除,只能通过校准补偿;三线制会好很多。我们这个项目大部分是两线制,所以校准环节做了线电阻补偿。
K型热电偶接法:热电偶输出电压很小,K型大约41μV/℃,测量范围到400℃时输出只有16mV左右,所以PGA增益要拉到64或128。热电偶输出是差分信号,AIN2接正极、AIN3接负极。输入端要并联一个TVS管做浪涌保护,再串一个几百欧的电阻限制浪涌电流,最后加一个RC低通滤波。ADS122U04内置基准2.048V,配合PGA=128,理论上分辨率能到亚微伏级别,实际受噪声限制,但温度分辨率做到0.01℃级别没问题。
NTC接法:NTC最简单,用ADC内部激励电流源或者外部精密电阻分压都行。我们HVAC通道用的是外部10K精密电阻和10K NTC组成分压电路,然后把分压点接到AIN0单端输入。NTC精度要求不高,分压电路足以满足±0.3℃的需求。
三种传感器接法汇总:
| 传感器类型 | 接入通道 | 激励方式 | PGA增益 | 基准 |
|---|---|---|---|---|
| PT100 | AIN0/AIN1差分 | 内部1mA电流源 | 8/16 | 2.048V内部基准 |
| K型热电偶 | AIN2/AIN3差分 | 无 | 64/128 | 2.048V内部基准 |
| 10K NTC | AIN0单端 | 外部10K精密电阻分压 | 1 | 2.048V内部基准 |
2.3 PCB布局的几条经验
这板子做了三版才稳定下来,第一版最大的问题就是ADC下面铺了太多数字走线。ADS122U04虽然内置数字滤波器,但毕竟是24位ADC,对模拟前端噪声非常敏感。我的建议是:
- ADS122U04周围尽量空出模拟地铜皮,数字信号线和电源走线不要绕到ADC芯片正下方;
- UART的TX/RX线和模拟输入线保持距离,不要平行走线;
- 传感器输入连接器放在板边,模拟信号进来后先经过保护器件和RC滤波,再进ADC;
- 基准引脚REFP/REFN附近要放去耦电容,2.048V基准电压的稳定性直接决定测量精度,电容用0.1μF并联10μF;
- 分给ADC的电源走线要用星形拓扑,别让数字电路的大电流先流过模拟电源网络。
PCB布局这个问题听起来是老生常谈,但在24位ADC项目里,它和“能不能达到标称精度”是直接相关的。不要指望算法能救回一个硬件上被噪声污染的模拟信号。
3. 通信协议、寄存器配置与PIC32MZ驱动代码
3.1 ADS122U04的命令机制和帧格式
ADS122U04的通信流程和普通UART外设很像:MCU作为主机,发送命令字节,ADC收到后执行对应操作并返回数据。常用的命令有这么几类:
- 复位命令:让ADC回到上电默认状态;
- 读数据命令(RDATA):读取最新一次转换结果;
- 写寄存器命令(WREG):配置输入多路复用器、PGA增益、数据速率、激励电流等;
- 读寄存器命令(RREG):回读当前寄存器配置,做调试时很有用。
这里提醒一下:在写代码前,一定要仔细看数据手册里这些命令对应的具体字节编码,以及WREG命令后面要跟的寄存器地址、字节数。不同芯片系列并不通用,不要凭经验照抄其他TI ADC的代码。
转换结果以24位二进制补码形式返回,MSB在前。三个字节依次通过UART传输,读取完三个字节后,把24位数据做符号扩展成32位int32,再进行电压计算。
3.2 初始化时序
ADS122U04的初始化顺序搞错,会出现读不到数据或者数据异常的问题。我踩过的顺序是这样的:
- 拉低RESET引脚,保持至少几十微秒,然后拉高释放;
- 等待DRDY引脚出现第一次下降沿,说明ADC已完成上电自检并准备好转换数据;
- 通过UART发送复位命令,再次等待DRDY下降沿;
- 发送WREG命令,连续配置寄存器0、寄存器1、寄存器2,把多路复用器通道、PGA增益、连续转换模式、激励电流源都设好;
- 再次等待DRDY下降沿,之后就可以随时用RDATA读取最新转换结果。
为什么复位后要等DRDY下降沿而不是直接发配置命令?因为ADC上电后内部基准和时钟需要稳定时间,DRDY信号可以看作“内部准备完成”的握手信号。如果跳过这一步,寄存器写入可能失败或者转换结果不稳定。
我们配置的是连续转换模式,也就是说ADC一直在转换,DRDY每个转换周期都会拉低一次,MCU不需要发启动命令,只需要在DRDY下降沿产生的中断里读UART数据。
3.3 PIC32MZ UART初始化与读取函数
PIC32MZ1024EFK144我用的是MPLAB X IDE加XC32编译器。UART初始化直接操作寄存器,不绕弯子。波特率我们配置为460800,8数据位、无校验、1停止位。这个速率对24位ADC来说非常宽裕:ADS122U04最高数据速率也就2000SPS,也就是每500μs出一个新结果,而460800波特率下传三个字节不到100μs,读取完全来得及。
#define UART1_CLK 100000000UL // 根据实际外设时钟调整 #define UART1_BAUD 460800UL void uart1_init(void) { U1BRG = (UART1_CLK / (16UL * UART1_BAUD)) - 1; U1MODEbits.PDSEL = 0; // 8位数据,无校验 U1MODEbits.STSEL = 0; // 1位停止位 U1MODEbits.BRGH = 0; // 标准波特率模式 U1STAbits.UTXEN = 1; U1STAbits.URXEN = 1; U1MODEbits.ON = 1; } void uart1_send_byte(uint8_t cmd) { while (!U1STAbits.UTXIF); U1TXREG = cmd; } uint8_t uart1_receive_byte(void) { while (!U1STAbits.URXIF); return U1RXREG; }读取ADC原始值的函数如下。需要注意,发送RDATA命令后,要连续读取三个字节,这三个字节分别对应24位结果的高字节、中字节和低字节。读完再做符号扩展。
void ads122u04_read_raw(int32_t *raw_value) { uint8_t buf[3]; uart1_send_byte(ADS_CMD_RDATA); // 命令字节按数据手册定义 buf[0] = uart1_receive_byte(); buf[1] = uart1_receive_byte(); buf[2] = uart1_receive_byte(); int32_t value = ((int32_t)buf[0] << 16) | ((int32_t)buf[1] << 8) | ((int32_t)buf[2] & 0xFF); if (value & 0x800000) { value |= 0xFF000000; // 24位补码扩展到32位 } *raw_value = value; }这个函数看起来简单,但最容易出错的就是符号扩展。如果忘记处理最高位,负温度(比如K型热电偶测0℃以下)就会变成一个很大的正数,后面算出来的温度完全是错的。
WREG配置过程类似,只是命令字节后还要跟寄存器地址、要写的寄存器个数以及配置数据。具体配置位我根据传感器接入方式在初始化函数里做分支处理,PT100通道和热电偶通道用不同的PGA设置。由于ADC的寄存器配置位在不同版本数据手册中可能略有差异,我强烈建议你对着手册逐位核对,不要全凭记忆。
4. 线性化、数字滤波与校准,温度精度的最后三公里
4.1 从原始码到实际电压
拿到24位原始码后,第一步是换算成ADC输入端的电压。ADS122U04的基准为2.048V,PGA增益为G,那么输入电压的计算公式是:
电压 = 原始码 / 8388608 × 2.048 / G
这里的8388608就是2^23。因为24位补码数据范围是-8388608到+8388607,所以除以2^23再乘上满量程电压,就得到实际电压。
这块建议用浮点运算。PIC32MZ带FPU,做这种计算完全没压力。把电压算出来后,再根据传感器类型做线性化。
4.2 不同传感器的线性化处理
PT100处理:PT100的温度-电阻关系并不是线性的,常用Callendar-Van Dusen方程来描述。如果你不想用手动公式,也可以查表然后用线性插值。我们为了达到±0.2℃的要求,用了两段处理:先根据测得的电压除以1mA激励电流得到电阻值,再把电阻值代进CVD方程反算出温度。CVD方程包含R0、A、B、C四个系数,不同厂家的PT100差异不大,直接用标准系数即可。
K型热电偶处理:热电偶输出电压和温度之间是分度表关系,反向计算最稳妥的办法是查NIST分度表,然后用三段三次多项式拟合或线性插值。PIC32MZ的Flash足够放下一张完整分度表,我们直接把-20℃到500℃的分度表按0.1℃间隔存成了数组,运行时用二分查找加线性插值,速度快、精度高。
热电偶有个关键问题:它测到的是“温差”,不是“绝对温度”。冷端(也就是热电偶与接线端子连接处)的温度会影响测量结果。ADS122U04内部有温度传感器,可以测芯片附近的温度,把它当作冷端温度,然后在算法里做补偿。但内部温度传感器精度一般,直接用会引入1到2℃的误差。我后来在PCB靠近接线端子的位置加了一颗高精度NTC来测冷端温度,补偿效果显著提升。如果只追求粗略测量,芯片内部温度传感器凑合能用;要做精密测量,强烈建议外部加冷端补偿传感器。
NTC处理:10K NTC用Steinhart-Hart方程拟合,三个参数A、B、C可以通过厂商提供或者三点校准得到。HVAC测温范围通常在-10℃到60℃之间,Steinhart-Hart方程在这个区间能轻松做到±0.1℃以内。
4.3 数字滤波策略
ADS122U04本身内置了数字滤波器,对50Hz/60Hz工频干扰有抑制能力。但在工业现场,仅仅靠芯片内置滤波还不够。我在MCU侧加了二级滤波:
第一级是中值滤波,对连续5个转换值排序,取中间值,用来剔除偶发的尖峰干扰;第二级是滑动平均,对中值滤波后的结果再取8次平均。这两级滤波加起来只引入很少的延迟,但能把现场的随机噪声压得很低。
要注意的是,滤波会降低响应速度。HVAC风管温度变化慢,完全没问题;如果以后要测快速变化的温度,滑动平均窗口要缩短。
4.4 标定与校准方法
新板子做出来后,第一件事不是直接装现场,而是用标准温度源校准。我们的做法是:
- 把PT100探头放入冰水混合物(0℃基准),记录ADC原始码,算出零点偏差;
- 再放入恒温水槽(我们设了100℃),记录满度偏差;
- 用这两个点做两点校准,把偏差写进PIC32MZ的Flash存储区域。
校准系数为什么要存Flash而不是编译时写死?因为同一型号传感器也存在个体差异,批量生产时每块板子校准系数不一样,运行时通过校准命令写入最方便。我们在设备里加了一个简单校准协议:上位机下发校准指令、目标温度值,设备采集稳定后的数值,自动计算并保存新的零点偏差和增益偏差。
经过校准后,PT100通道在整个0-100℃区间的实测误差在±0.15℃以内,完全满足HVAC和一般工业现场要求。
4.5 滤波与校准的联动
这里有一个容易被忽略的点:校准时也要走完整的数字滤波链路,不能用单次原始值去算系数。我用单次值做校准时得到过一组看起来合理的系数,但实际运行时因为滤波延迟和目标值不一致,输出的温度始终偏差了0.3℃左右。重新用滤波后的值校准,偏差就消失了。原因是滑动平均会引入一个短时间片内的“平均效应”,校准值和实时值必须处在同一个数据链路上,系数才能真正起作用。
5. 现场调试踩过的坑,以及排查思路
5.1 DRDY时序错位导致的数据“跳变”
第一次上电联调时,主循环轮询DRDY状态,什么时候读到低电平就去读UART。问题是我没有锁存DRDY下降沿,结果在一个转换周期内偶尔读了两次,后一次读到的其实是同一个转换周期的旧数据,而且UART接收字节可能跨过了数据更新边界,导致24位数据出现“上下字节撕裂”,温度值突然跳变几度。
修复方法是把DRDY接到PIC32MZ的外部中断,用下降沿触发,中断里只置标志位,主循环检测到标志位后只读取一次数据,读完马上清标志。这样保证每次转换结果只被读一次,数据永远对齐。
5.2 PGA高增益下的输入偏置问题
测K型热电偶时,PGA设成128,发现一个小信号变化会被放大得很明显,但零点也漂得比较厉害。排查后确认是PGA输入偏置电流在高增益下对高阻抗传感器影响较大。热电偶源阻抗本身不高,但前端加的1kΩ保护电阻和RC滤波电阻叠起来,让源阻抗变得不可忽略,偏置电流就在这些电阻上产生了额外压降。
解决办法是把RC滤波电阻从10kΩ降到了2.2kΩ,同时选用低漏电流的TVS管,零点漂移明显改善。如果你以后遇到高增益通道读数不稳,先查一下输入源阻抗和偏置电流的匹配。
5.3 内部基准温漂在高精度要求下撑不住
项目最初为了省PCB面积,直接用ADS122U04的2.048V内部基准驱动PT100测量。实验室环境一切正常,但把板子放到工业配电柜里后,柜内温度升到40℃以上,PT100通道的读数整体偏了约0.3℃。排查到基准电压温漂上去了——内部基准精度尚可,但温漂系数不够,对需要长期稳定性的精密测量来说不够用。
后来把基准改为外部基准REF5025(2.5V精度基准),同时把PGA增益和满量程电压重新计算一遍。换基准后,温度读数的长期稳定性明显提升,设备在60℃环境下连续工作一周,偏差维持在±0.1℃以内。
5.4 UART通信屏蔽与隔离
第一批设备装到现场后,出现了一个棘手问题:其中一块板子离变频器很近,采集到的温度偶尔会无规律跳变,甚至出现丢数据。用示波器看UART波形,发现RX线上叠加了大量毛刺。
处理方案分两步:第一,UART信号线换成了屏蔽双绞线,屏蔽层单端接地;第二,在UART接收脚上增加了RC滤波。改完以后现象消失。这里要提醒一点,UART波特率越高,对线路质量越敏感。如果你的现场电磁环境很差,不要盲目追求高波特率,115200或230400在多数工况下足够用,稳定性更好。
5.5 通信异常后的自恢复机制
工业设备不能掉线后人工断电重启。我加了两个保护机制:
- 看门狗超时检查:PIC32MZ的看门狗定时器周期复位,但每次UART成功采集到有效数据时都会喂狗,一旦程序跑飞或者卡死,看门狗会复位整个系统。
- ADC通信看门狗:连续5秒收不到DRDY下降沿,主动对ADS122U04发复位命令,然后重新执行初始化序列。
加了这两个机制后,即使现场出现强干扰把UART打死,设备也能在几秒内自动恢复,不需要人工干预。
6. 多通道扩展、数据上报和后续改进方向
6.1 用PIC32MZ的多UART外设做级联
单颗ADS122U04只有4通道,如果实际项目需要更多点位,最直接的办法就是多片级联。PIC32MZ1024EFK144集成多组UART,我们可以让每颗ADS122U04独占一组UART和一路外部中断。以6颗ADC为例,总共就能采集24路模拟量,所有温度换算、滤波和校准代码通过一个带通道号参数的结构体数组来复用,代码量不会随通道数翻倍。
需要注意的一点是:每一片ADC的初始化顺序要错开,避免多片同时复位、同时拉低DRDY造成中断风暴。我们在代码里给每片ADC增加了一个几十毫秒的启动延时,保证DRDY中断按顺序到来,主循环处理压力更均衡。
6.2 数据上报与上位机协议
温度值算出来后,后续要交给PLC或者上位机。我们预留了Modbus RTU从站协议,把每路温度值组织成保持寄存器,PLC每秒钟轮询一次。PIC32MZ的UART资源很丰富,读传感器用一组UART,和上位机通信可以用另一组UART,互不干扰,这在电路设计时就要提前规划好引脚分配。
我个人在实际项目里还有一个体会:调试这类系统时,最好在固件里留一个“原始值模式”的命令开关。现场出问题时,先用这个模式直接把ADC原始码和配置寄存器值上报上来,几秒钟就能判断是传感器问题、接线问题还是算法问题,不用反复拆盖板量电压。
6.3 后续改进方向
如果之后要进一步提升精度,有几个方向值得尝试:一是把PT100通道改成真正的三线制或四线制接法,彻底消除引线电阻误差;二是把冷端补偿温度传感器直接贴在接线端子板上,减小冷端温度和ADC内部温度之间的差值;三是在ADC前端加入可编程增益自动调整逻辑,让不同量程的传感器都能工作在最佳信噪比区间。
整个项目做下来,我最大的体会是:ADS122U04的UART接口设计确实解决了分布式温度采集的不少麻烦,但它的性能能不能发挥出来,取决于前端模拟电路、基准、PCB布局和通信协议这整套系统。与其急着接线跑代码,不如先花半天时间想清楚传感器类型、量程、精度和现场环境,再回头选器件配置。把基础打牢,后面会顺利得多。