做温度采集的工程师几乎都纠结过一个问题MCU内置ADC到底够不够用。常温环境测个NTC、测个DS18B20凑合凑合也就过了可一旦走进工业现场和HVAC系统面对PT100/PT1000、B型或K型热电偶要求在-40℃到150℃区间内做到0.1℃级别的分辨率和长期稳定内置ADC基本就是天花板。我这次用ADS122U04IPWR这颗24位UART接口ADC搭配PIC18F87K22单片机搭了一套完整的温度采集方案覆盖RTD、热电偶、热敏电阻三种常见传感器实测下来效果稳定。这篇文章把整个项目的来龙去脉、硬件设计、通信协议、代码实现和踩坑记录全部拆开讲清楚给正打算做工业或HVAC温度采集的朋友一个可以直接参考的落地范本。1. 项目背景与方案选型1.1 为什么是“外部ADC 老牌8位MCU”的组合先说结论在工业级测温场景里外部独立ADC和MCU的分工协作远比“一颗高精度MCU包打天下”更容易控成本、更容易过认证、维护也更省心。ADS122U04IPWR是TI推出的一款四通道、24位ΔΣ ADC内置可编程增益放大器PGA、两个匹配电流源IDAC、内部电压基准、片上温度传感器和振荡器最重要的是它走的是UART接口而不是常见的SPI或I2C。这点很关键——在工业现场传感器探头和控制板之间的距离动辄几十米UART配合RS-485驱动器后传输距离和抗干扰能力都很从容而且只需要两根线TX/RX不需要单独的片选信号从布线上就省了不少事。另一个实用的地方是ADS122U04支持两种供电模式AVDD3.3V或5V可以灵活匹配系统已有的电源轨。PIC18F87K22是Microchip的老牌8位MCU80引脚大封装资源非常充足两个增强型EUSART、64KB以上Flash、大量GPIO。在温度采集系统中它承担的角色很纯粹——通过UART控制ADC的寄存器配置、发起转换、读取数据把原始码换算成温度值再通过Modbus或自定义协议上传。为什么选PIC而不是STM32项目面向的是长期供货稳定的工业产品PIC18系列生命周期长、工具链成熟、货源稳定XC8编译器用起来也顺这个选择在量产阶段体现出的优势远大于纸面参数上的“先进”。这套组合实际能做什么单颗ADS122U04最多支持4路差分输入或7路单端输入我当前项目用4路差分接了3路PT100和1路K型热电偶。PIC18F87K22作为主控按时序轮询四通道最终系统实现了全通道0.1℃分辨率的稳定输出。HVAC的应用场景很典型空调箱的送风温度、回风温度、冷水盘管进出水温、压缩机排气管壁温这些点分散在设备各个角落传统的模拟信号长距离传输会衰减、受干扰而把ADC靠近传感器、再用UART数字信号回传主控精度和抗干扰都拿到了。1.2 选型对比UART接口ADC与SPI/I2C ADC的取舍刚开始选型时很多人会拿ADS1220或ADS124S08这类SPI接口ADC来做对比。我把几个主流方案的差异整理成了表格方便看得更清楚接口典型型号通信线数适合距离主机资源占用优势劣势SPIADS1220、ADS124S08、AD7124SCLK、MISO、MOSI、CS至少4根板级30cm需要占用SPI外设额外GPIO速度高资料多远距离布线麻烦CS管理复杂I2CADS122C04、MCP3421SDA、SCL两根需地址配置板级50cm需要I2C外设多设备需地址规划总线可挂多设备速度受限地址冲突常见电平匹配麻烦UARTADS122U04TX、RX两根板级到现场级加RS-485可达百米以上任意带UART的MCU即可线少、距离长、协议自带CRC最高速率不及SPI但测温场景完全够用从表格能看出ADS122U04的核心价值在于“通信链路”。我还是那句话在20SPS这种测温数据率下SPI的带宽优势毫无意义UART的布线便利性和远距离能力才是真实收益。项目里把ADC直接放在传感器接线端子旁边PCB只需要引出VCC、GND、TX、RX四根线到主控板一路数字信号抗干扰能力远超模拟信号走线的方案。另外还要提一个很多工程师容易忽略的点ADS122U04的UART接口是芯片内置的硬件协议栈命令帧、回应帧、CRC8校验都由电路自动处理一部分逻辑MCU这边只要按帧格式正确收发即可不用软件模拟时序出错概率低很多。这对工业现场极为重要——凡是软件模拟时序的通信长期运行后大概率会出低频偶发错误排查起来非常痛苦。2. 硬件设计要点确定了“外部ADC PIC18”这条主线之后硬件设计就是整个项目成败的关键。温测系统的精度上限不是由ADC位数单独决定的而是由模拟前端设计、参考源选择、PCB布局共同决定的ADC本身24位分辨率只是“理论天花板”。2.1 模拟前端RTD比例测量电路ADS122U04内部有两个匹配IDAC电流源这是为RTD比例测量量身定做的功能。比例测量的思路不复杂让同一个IDAC电流先后流过精密参考电阻RREF和RTD热电阻把RREF两端的电压作为ADC的外部参考电压把RTD两端的电压作为输入信号ADC转换结果就直接等于RTD与RREF的比值与电流源的绝对值漂移、温漂统统无关。这个方案在原理上就消掉了激励电流源不稳定的影响非常实用。以三线PT100为例我给出一个可以直接抄的接法。在AIN0和AIN1之间接入RTDIDAC1通过引脚IEXC1输出1mA电流依次流过RTD和RREF1000Ω/0.1%/25ppm精密电阻RREF两端接到REFP0和REFN0作为外部基准。计算一下PT100在0℃时约为100Ω1mA电流流过产生100mV信号满量程按420Ω对应约850℃算信号也才420mV配合PGA8倍放大满量程信号约3.36V接近参考电压4.096VRREF4.096kΩ时这样ADC的量程利用率很高。三线RTD的好处在于可以补偿导线电阻。两条等长度、相同材质的导线分别串联在RTD的激励端和测量端于是导线电阻引起的误差在差分测量中相互抵消。实操时需要注意三条引线必须用同一型号、同一长度的导线材质最好是铜屏蔽绞线不能图便宜用镀锡铁丝。RTD的引线尽量不要接长后随意盘绕要固定走线减少耦合噪声。如果现场只能用两线制接法务必在软件里做一个“导线电阻补偿”通道短接传感器端的两个端子测出引线总电阻然后用这个值修正每路读数。2.2 热电偶与冷端补偿热电偶的电压信号非常小K型热电偶在室温附近大约40μV/℃测量端在0℃时输出为0mV在500℃时输出约20mV。如此小信号要求PGA至少32倍以上同时要处理冷端补偿。ADS122U04有个非常方便的功能——片上集成温度传感器可以通过配置寄存器直接把芯片结温读出来。有人问芯片结温能当冷端温度用吗前提是热电偶的冷端即接线端子与芯片处于同一等温块上。所以我在PCB设计上把热电偶的冷端接线端子紧贴ADS122U04放置并在端子附近铺一块独立铜皮让两者温度尽量一致。实测下来只要PCB不是放在风口直吹位置冷端温差能控制在0.5℃以内对应热电偶读数误差约0.5℃K型可以接受。如果要求更高可以外接一颗PT100装在端子上用高精度通道测量冷端温度再把补偿数据写入算法。另外要提醒一点热电偶是低阻抗电压源测量回路阻抗很低但共模电压可能超出ADC输入范围。解决方式是在热电偶输入的正、负端分别接一个1MΩ的偏置电阻到AVDD或AVSS给输入信号建立一个明确的直流工作点。TI手册推荐的BIAS电阻电路实测下来对抑制共模干扰很有效。2.3 PIC侧与电源/PCB布局PIC18F87K22与ADS122U04之间就是一对标准的UART连接PIC的TX接ADC的RXPIC的RX接ADC的TX共地。电平方面两者同为3.3V供电时可以直接相连如果ADC侧用5V供电则需要电平转换或分压电阻。我的项目用3.3V统一供电PIC内部振荡器运行在16MHzEUSART1工作在9600bps或115200bps实测两种速率都稳定。电源设计是重中之重直接关系到ADC的噪声表现。给ADS122U04的AVDD和DVDD建议分开走线并分别加0.1μF和10μF去耦电容。模拟参考电压REFP/REFN之间的电容用100nF低ESR电容。数字地AGND和DGND在芯片下方单点连接避免数字开关噪声灌入模拟地。PCB布局的经验值RREF电阻距离REFP/REFN引脚越近越好走线要短而粗不要经过过孔。ADC输入端的100Ω串阻和1nF差模电容要贴近AINx引脚放置。数字信号线UART的TX/RX不要与模拟输入线平行长距离走线如果必须交叉就垂直走并拉开间距。传感器接线端子放在板边方便现场接线ADC芯片与端子距离尽量短。3. 通信协议与固件实现3.1 ADS122U04通信帧格式与CRC8计算ADS122U04的UART协议严格定义了命令帧和响应帧的格式。命令帧由主机PIC发出一般结构是1字节命令码 N字节数据 1字节CRC8。响应帧由ADC返回以状态字节开头后面跟数据最后也是CRC8。状态字节里最重要的位是DRDY——转换数据就绪标志主机在DRDY为1时才能读取新数据。我整理了一份常用命令速查表依据ADS122U04手册整理兄弟们以自己手里最新版本的datasheet为准命令字节码功能NOP0x00空操作/唤醒START0x08启动连续转换POWERDOWN0x02进入关断模式RDATA0x10读取转换结果24位RREG0x20 | (寄存器地址2)读取配置寄存器WREG0x40 | (寄存器地址2)写入配置寄存器RESET0x06复位芯片CRC8的多项式按手册来说是0x07初始值0xFF。我直接写了一份通用CRC8计算函数在PIC端和调试电脑端共用保证数据一致性uint8_t crc8(uint8_t *data, uint8_t len) { uint8_t crc 0xFF; for (uint8_t i 0; i len; i) { crc ^ data[i]; for (uint8_t bit 0; bit 8; bit) { if (crc 0x80) { crc (crc 1) ^ 0x07; } else { crc 1; } } } return crc; }注意CRC计算要覆盖“命令码 数据段”的全部字节响应帧则是“状态字节 数据段”的全部字节。刚开始调试时最容易犯的错就是把CRC算漏了帧头或状态字节结果一整包数据全部被丢弃。3.2 PIC18F87K22初始化与UART通信状态机PIC18F87K22的UART配置并不复杂我用EUSART18位数据、无校验、1停止位波特率用16MHz时钟计算。16MHz下BRG161、BRGH1时波特率公式是波特率 Fosc / (4 * (SPBRG 1))要得到9600bpsSPBRG 16e6 / (4*9600) - 1 416.67取整数416实际波特率9603.8偏差0.04%完全在容差范围内。115200bps时SPBRG取34实际偏差稍大但也在±2%以内实测通信没问题。初始化代码核心片段void UART1_Init(void) { TRISCbits.TRISC7 1; // RX引脚输入 TRISCbits.TRISC6 0; // TX引脚输出 BAUDCON1bits.BRG16 1; TXSTA1bits.BRGH 1; SPBRG1 416; // 9600bps 16MHz TXSTA1bits.TXEN 1; RCSTA1bits.CREN 1; RCSTA1bits.SPEN 1; PIE1bits.RC1IE 1; // 接收中断使能 INTCONbits.PEIE 1; INTCONbits.GIE 1; }通信状态机是这套固件里我认为最值得讲的部分。ADC作为从机PIC作为主机PIC每轮先发命令再等响应命令和响应的长度都不是固定的比如写寄存器命令带1字节数据读数据命令带3字节数据因此接收中断里不能简单一个字节一个字节存数组。我设计了一个有限状态机按“预期字节数”来接收typedef enum { RX_IDLE, RX_STATUS, RX_DATA, RX_CRC } RX_STATE; RX_STATE rxState RX_IDLE; uint8_t rxBuf[6]; uint8_t rxCount 0; uint8_t rxExpectedDataLen 0; void UART1_RX_ISR(void) { uint8_t byte RCREG1; if (rxState RX_IDLE) { rxBuf[0] byte; rxCount 1; // 根据当前发出的命令判断后续字节数设置 rxExpectedDataLen // 例如发送RDATA后期望 1字节STATUS 3字节DATA 1字节CRC rxState RX_STATUS; } else if (rxState RX_STATUS) { rxBuf[rxCount] byte; rxState RX_DATA; } else if (rxState RX_DATA) { rxBuf[rxCount] byte; if (rxCount 2 rxExpectedDataLen) { rxState RX_CRC; } } else if (rxState RX_CRC) { rxBuf[rxCount] byte; // 判断CRC后置命令完成标志 rxState RX_IDLE; } }实际工程里我加了一个超时看门狗如果3ms内没收到预期字节状态机强制回到IDLE并丢弃残包防止ADC异常时主循环永久阻塞。这个细节让系统在通信异常时能自动恢复而不是死等。4. 温度测量核心流程通信链路打通之后剩下的核心工作就是ADC配置和温度换算。这节我给出完整的寄存器配置序列和换算算法大部分代码稍作修改就能挪到自己的工程里。4.1 寄存器配置实例ADS122U04有4个配置寄存器编号0到3每个8位。我以最常见的“3路三线PT100PGA8IDAC1mA 1路K型热电偶PGA128冷端用片内温度传感器”为例给出完整配置序列首先写配置寄存器0选择AIN0-AIN1差分输入PGA8正常工作模式。二进制值0x0E对应的含义是AIN0AINP0、AIN1AINN0PGA增益0118倍。配置寄存器1设置数据率20SPS内部振荡器正常转换模式。20SPS对应的编码是100写入值0x64具体按手册数据率表推算。配置寄存器2开启IDAC1电流设为1000μA参考电压选择外部参考。1000μA对应编码1001参考选择外部REFP/REFN综合写入值按手册表得到。配置寄存器3IDAC1路由到REFP引脚用于比例测量内部温度传感器关断RTD模式不需要。这个寄存器的值比较固定我直接写0x10。初始化时通过WREG把4个寄存器依次写入然后发START命令启动连续转换void ADS122U04_Init(void) { uint8_t cmd[3]; // 写寄存器0 cmd[0] 0x40 | (0 2); // WREG 寄存器0 cmd[1] 0x0E; // 寄存器0值 cmd[2] crc8(cmd, 2); UART1_Send(cmd, 3); _delay_ms(2); // 依次写寄存器1、2、3 // ...类比写法略 // 启动连续转换 cmd[0] 0x08; cmd[1] crc8(cmd, 1); UART1_Send(cmd, 2); }换到热电偶通道时只需重写寄存器0为AIN2-AIN3差分输入、PGA128寄存器2保持不变热电偶不用IDAC寄存器3开启片内温度传感器。每次改寄存器后建议加50ms稳定时间再启动转换。4.2 从原始码到温度RTD与热电偶的换算算法ADS122U04的转换结果是24位有符号数存放在RDATA返回的3个字节里用补码表示。我这里贴一个通用的RAW值转电压函数// raw为24位补码值vref为参考电压pga为增益 float raw_to_voltage(int32_t raw, float vref, float pga) { return (float)raw * 2.0f * vref / (8388608.0f * pga); }RTD比例测量的换算更简洁。因为是同一个电流ADC的参考电压正比于RREF输入电压正比于RTD所以不必先转电压再除电流直接按比值算电阻R_RTD RREF * raw_RTD / raw_FS这里的raw_FS是满量程码22位分辨率下为4194304。但更稳妥的做法是读一次参考电压通道把AIN0-AIN1短接RREF两端用同一通道读回满量程值作为校准系数这样可以把ADC的增益误差也一起消掉。我在量产固件里保留了上电自校准过程每次上电后用内部短接开关测一次参考电压的原始码存到EEPROM后续所有通道都用这个校准码计算长时间运行温漂明显改善。PT100分度表处理我的建议是不要直接存整个0-850℃的表格占Flash太多而是存稀疏表每隔50℃一个点中间温度用线性插值。0℃时PT100阻值100Ω0.385Ω对应约1℃线性插值精度足够。如果要求极致精度再套用Callendar-Van Dusen公式IEC 60751标准R(t) R0 * (1 At Bt^2 C*(t-100)*t^3) 0℃以上忽略C项只保留A、B项A3.9083e-3B-5.775e-7C-4.183e-12。这个公式在-40~150℃范围内的拟合误差远小于0.1℃对HVAC场景绰绰有余。实际代码里我用的是“稀疏查表 二次插值”兼顾速度和精度整个换算过程PIC18F87K22在16MHz下耗时不到1ms。热电偶的换算要比RTD多一步冷端补偿。算法流程如下用片内温度传感器读出冷端温度T_cold℃。把RLT_cold代入NIST ITS-90多项式算出冷端对应的热电势V_cold。读出热电偶通道的电压V_meas经过PGA放大折算回原始电压。V_total V_meas V_cold。用V_total查NIST电压-温度表得到最终测量温度。NIST多项式在代码里一般用一个数组保存系数体积不大。我在PIC里存了K型热电偶的稀疏电压表插值精度约0.2℃。这一步是热电偶测量最容易出错的地方很多人直接拿V_meas查表结果在室温附近读数偏差就有好几度——就是因为漏了冷端补偿。5. 常见问题与排查技巧实录项目前后跑了三个多月中间碰到的问题不少挑几个有代表性的记录一下。这些问题在TI官方手册上基本不会写全是实际调试中折腾出来的。5.1 数据跳字/噪声偏大现象ADC读数在低几位持续跳动换算成温度后能看到±0.3℃以上的波动远达不到预期。排查过程一条条过先看电源。用示波器抓AVDD纹波发现100mV以上的开关电源纹波直接串进了模拟电路。换用LDO如LM1117-3.3并加10μF钽电容滤波后纹波降到5mV以内。再看参考电压。RREF两端波形是否有毛刺发现RREF引脚旁边没有就近放置滤波电容IDAC的开关噪声影响参考电压稳定性。加100nF后明显好转。第三查布线。某块样板把UART的TX线从AIN0输入旁边走过串扰直接体现在读数的低频抖动上。重新走线后问题消失。最后降PGA。热电偶通道PGA128时噪声指标本身就紧张数据率降到20SPS后有效分辨率从19位提升到了21位。实测温度跳动从±0.3℃降到±0.05℃。总结ADS122U04的噪声性能非常依赖PCB尤其是PGA高增益时模拟输入线的每一个走线电容和每一毫伏共模噪声都会被放大。如果数据率要提升到160SPS建议PGA不超过64。5.2 通信超时/CRC错误频发现象PIC偶尔收不到ADC的响应帧或者收到但CRC校验失败系统复位后恢复。排查思路用示波器同时抓TX和RX确认帧时序。发现PIC发出命令后ADC的响应晚到了将近2ms而我的状态机超时设为3ms电源纹波大时偶发超时。把超时放宽到5ms后问题消失。波特率偏差是另一个坑。115200bps模式下SPBRG取34时偏差达到1.4%在高温或芯片个体偏差下偶尔误码。稳妥起见我把量产固件固定用9600bps传输距离和可靠性都更好。如果通信线长度超过2米RX/TX线上要加靠近接收端的1kΩ串联电阻和100pF对地电容滤波高频噪声。也有同行用光耦隔离效果更好但成本上去了。5.3 温度读数整体偏差且非线性现象所有通道读数的趋势正确但绝对温度偏差约0.5℃左右。原因分析和解决RTD通道先做硬件排查确认RREF精度。我最初用普通厚膜电阻温度系数100ppm/℃随环境温度变化明显换用0.1% 25ppm的精密箔电阻后问题大幅改善。然后做软件校准。在0℃和100℃两个恒温源上标定获得增益误差和偏移误差在换算公式里补偿。校准系数写进EEPROM批量生产时每台设备单独标定。热电偶通道的0.5℃偏差通常来自冷端温度测量不准。检查PCB布局确保热电偶端子与片内温度传感器尽量同温。我改了一版PCB把冷端端子与芯片间距从15mm缩短到5mm偏差从0.8℃降到0.2℃。5.4 寄存器写入无效/配置丢失现象初始化时写入的寄存器值在读取时对不上或者偶尔配置全部变回默认。这类问题多半是上电时序或WREG命令的CRC计算有问题。我在固件里增加了上电后的“读回校验”写入每个寄存器后立即读回并比对不匹配就重试3次仍失败则报错。实践证明这个校验机制在批量生产时救了很多板——尤其是一些劣质电源板导致ADC复位软件能自动检测并重新配置。6. 后续扩展思路这套方案其实还有很多可深挖的地方。第一个方向是把ADS122U04从单颗扩展成多颗并联PIC18F87K22有多个EUSART可以接2~4颗ADC每一路都覆盖4路传感器这样一套主控就能管理16路甚至更多测温点非常适合楼宇自控里的分布式采集。好处是每颗ADC的传感器接线都保持在短距离信号质量几乎不受影响。第二个方向是把UART升级为RS-485总线。PIC侧加一个MAX485这类收发器ADC侧也加一颗就可以实现长距离多点串行链路现场布线会简洁很多。注意RS-485的终端电阻和控制器的方向切换时序别在发送命令前没拉低RE/DE否则整个总线会被自己卡死。第三个方向是加传感器断线检测。ADS122U04的内部PGA带有激励电流功能可以配置一个很小的检测电流通过测量输入电压是否超出量程来判断RTD是否开路或短路这对HVAC设备的运维很有价值。我计划在下一版固件里实现这个功能。最后一个建议无论方案多完善量产前必须做高温老化测试。把设备放在70℃环境下连续通电72小时对比老化前后的温度读数。我经历过的所有看似“玄学”的温漂最终都指向同一句话——精密模拟系统的可靠性是要用测试数据来背书的不是靠理论推出来的。
阅读完成 · 觉得有帮助?