去年我接了一个工业温度采集模块的活儿传感器类型五花八门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的差异化价值串口通信的ADCADS122U04是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带单精度FPUFlash有1MBRAM有512KB。在温度采集系统里它的任务不只是“读ADC”还包括负责多片ADS122U04的UART通信管理做传感器线性化计算比如热电偶查表、PT100的Callendar-Van Dusen方程计算这些运算需要浮点PIC32MZ的FPU帮了大忙跑Modbus或者自定义协议把温度值上报给PLC或上位机如果后面要扩展它自身还带以太网、CAN、USB等接口直接可以做边缘网关。选它而不是普通8位MCU核心原因就是计算余量。以热电偶为例K型分度表的反算如果不用FPU用查表加插值也能做但在多个通道同时采集、同时处理时CPU占用率高会影响UART接收的实时性。用PIC32MZ后所有这些都能扛下来。我把ADS122U04和传统SPI ADC做了个简单对比对比项ADS122U04UART传统SPI接口ADC接口线数2条TX/RX4条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配合PGA128理论上分辨率能到亚微伏级别实际受噪声限制但温度分辨率做到0.01℃级别没问题。NTC接法NTC最简单用ADC内部激励电流源或者外部精密电阻分压都行。我们HVAC通道用的是外部10K精密电阻和10K NTC组成分压电路然后把分压点接到AIN0单端输入。NTC精度要求不高分压电路足以满足±0.3℃的需求。三种传感器接法汇总传感器类型接入通道激励方式PGA增益基准PT100AIN0/AIN1差分内部1mA电流源8/162.048V内部基准K型热电偶AIN2/AIN3差分无64/1282.048V内部基准10K NTCAIN0单端外部10K精密电阻分压12.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初始化直接操作寄存器不绕弯子。波特率我们配置为4608008数据位、无校验、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.048VPGA增益为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℃。排查到基准电压温漂上去了——内部基准精度尚可但温漂系数不够对需要长期稳定性的精密测量来说不够用。后来把基准改为外部基准REF50252.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布局和通信协议这整套系统。与其急着接线跑代码不如先花半天时间想清楚传感器类型、量程、精度和现场环境再回头选器件配置。把基础打牢后面会顺利得多。