1. 为什么要拿一颗24位ADC加一颗MCU专门做温度采集
工业现场和HVAC项目里,温度测不准带来的麻烦我见过太多:PID调节乱跳、能耗报表对不上、压缩机频繁启停……很多时候不是控制算法不行,而是温度采集这一环就埋了雷。所以最近做一个多通道温度采集模块时,我直接选了ADS122U04IPWR搭配MK20DN128VFM5这套组合,目的很简单:把"模拟信号处理"和"数字控制"彻底分离,让MCU只负责算温度、跑逻辑,不再去跟毫伏级信号较劲。
选择独立ADC而不是用MCU内置ADC,核心原因有两个。第一,内置ADC的分辨率和噪声指标很难满足工业温度测量要求。MK20DN128VFM5内部虽然有16位ADC,但实际有效位数在毫伏级输入下通常只有12~13位,配合常温漂的参考电压,能把温度做到±0.5°C以内已经算不错,而要稳定做到±0.1°C就得靠外部24位ADC。第二,MCU本身要跑通信、控制、人机交互,如果还要频繁进中断采集模拟量,CPU资源会被吃掉一大块,而且模拟开关切换和数字噪声耦合会让结果更难看。
ADS122U04IPWR是TI的一款24位ΔΣ ADC,四通道输入,内置PGA、两个可编程激励电流源、内部温度传感器和参考电压,还带了一个我特别喜欢的接口:UART。对,你没看错,这颗ADC直接用串口通信,不需要SPI引脚。这在做长线传输和隔离时非常省事,尤其HVAC设备里主控板和传感器板经常分开布置,两根线就能把数据传回来,比SPI四线制简单得多。关于封装,IPWR后缀是TSSOP-16,体积不大,手工贴片也没问题。
MK20DN128VFM5这边,FM5后缀是QFN-32封装,5mm见方,Cortex-M4内核,72MHz主频,128KB Flash,24KB RAM,跑温度换算查表、滤波算法绰绰有余。选它还有一个原因:UART资源够用,一路接ADC,一路接上位机或触摸屏,不会冲突。整颗MCU静态功耗控制在毫安级,适合需要做节能逻辑的HVAC控制器。这套架构不是唯一解,但在我做的这个场景里,它把"模拟性能"和"接口自由度"平衡得相当好。
1.1 ADS122U04 这颗ADC到底强在哪
ADS122U04的定位是"面向传感器测量的低功耗ADC"。给我的感觉是,TI把以前分立方案的模拟前端、激励源、基准源全部集成到了一颗芯片里,省掉了大量外部元件。它的PGA增益范围是1~128,输入通道可以配成四路单端、两路差分或者混合使用。对于热电偶这种输出只有几毫伏到几十毫伏的传感器,用128倍增益能把信号放大到ADC量程的合适区间;对于RTD铂电阻,则可以用内部激励电流源配合比例测量,把基准温漂的影响降到最低。
UART接口的引入是个很聪明的设计。传统SPI接口ADC在传感器板上有时候会因为时序问题丢数据,而UART只要波特率对得上、帧格式对得上,数据流天然就是字节序列,解析起来更直接。ADS122U04内部有完整的命令集,包括复位、读写寄存器、启动转换、读取数据。更关键的是它支持连续转换模式,可以设定每秒钟输出若干组转换结果,MCU只需要在串口中断里不断收数据即可,不需要主动发起请求。对嵌入式系统来说,这种"数据自己送上门"的模式极大减小了控制逻辑的复杂度。
1.2 MK20DN128VFM5 在这里承担的职责
MK20DN128VFM5作为主控,在这个项目里实际上只做三件事:初始化串口并管理ADC的通信状态机;把收到的24位原始码换算成电阻或电压;再经过线性化算法得到温度值,最后通过Modbus或CAN送到上层控制器。这个MCU有硬件乘除法和FPU,虽然温度算法用浮点也跑得动,但我实际测试下来,Cortex-M4的FPU确实让计算速度快了不少,尤其是热电偶查表插值那一段,浮点吞吐明显比软件浮点强。
另外,MK20DN128VFM5还有丰富的定时器、GPIO中断和独立看门狗。对于HVAC应用来说,除了温度测量,还要处理风机启停、报警输出、按键输入等任务。我在这颗MCU上跑了一个不带RTOS的裸机调度,用SysTick做1ms时基,ADC数据到来时置标志,主循环按照周期处理,实测下来CPU占用率不到30%,后续再加逻辑完全够用。
2. 硬件接线的几个关键位置,决定系统精度的上限
ADC选得再好,硬件接线随便搞,精度照样上不来。ADS122U04和MK20DN128VFM5的连接虽然简单,但模拟前端设计里有很多容易被忽略的细节。我按照RTD输入、热电偶输入、电源参考、串口电平这几条线依次说明。
2.1 电源与参考电压的处理
ADS122U04的模拟电源AVDD和数字电源DVDD建议分别走LDO输出,不要直接从DC-DC开关电源拉电。工业现场里DC-DC的开关噪声会通过电源引脚耦合到ADC内部,直接影响转换结果的跳动。我实测过,供电从开关电源直接接到AVDD时,ADC输出码的峰峰值跳动大概有±20个LSB(24位下),而换用低噪声LDO后再加一个磁珠和10µF钽电容滤波,跳动降到了±3个LSB以内。MK20DN的电源同样建议单独用一颗LDO,避免数字电路的地弹噪声影响ADC的模拟地。
参考电压方面,ADS122U04可以配置内部参考电压,也可以使用外部参考。对于温度测量,强烈建议使用比例式测量:让激励电流同时流过参考电阻和RTD,再用RTD电压与参考电阻电压的比值计算阻值。这样即使激励电流本身有温漂,由于ADC参考电压同样来自该电流驱动参考电阻产生的压降,最终比值基本不受电流漂移影响。参考电阻RREF建议选0.1%精度、温漂系数低于10ppm/°C的金属箔电阻,位置尽量靠近ADC的REF输入引脚,走线加粗并包地。
2.2 RTD三线制接法
RTD常用PT100和PT1000。三线制是为了消除导线电阻带来的误差。原理是:两根导线分别接在RTD两端,第三根线作为感应线连接到ADC的高阻抗输入,这样导线上压降主要落在激励电流路径上,而测量回路几乎不取电流,所以导线电阻影响被大幅抵消。ADS122U04有两个可编程激励电流源IDAC1和IDAC2,可以把它们配置成分别从两个引脚输出电流,再通过RTD汇流到公共端,实现真正的三线制比例测量。
实际接线中我建议这样配置:IDAC1输出激励电流,经过参考电阻RREF后连接到RTD一端;RTD另一端回到AIN1;同时IDAC2从RTD的同一端再输出一路电流,经过另一根导线直接回到公共地。ADC的差分输入测量RTD两端电压,参考电压则取自RREF两端。这样一个典型配置的精度可以做到±0.05°C级别,前提是导线电阻尽量对称。对于PT100,激励电流我通常设500µA,自热效应约0.05°C,可以接受;PT1000则用250µA,自热更小。
2.3 热电偶输入与冷端补偿
热电偶输出是微伏级差分信号,K型热电偶在0°C到100°C之间大约只有4mV。因此必须把PGA增益开到64或128倍,并且输入走线要短,远离电源和数字信号线。ADS122U04的AIN引脚输入阻抗很高,但外部加一个RC滤波会更稳妥。RC时间常数建议设在10ms左右,能有效滤掉高频干扰,又不会明显拖慢转换响应。
冷端补偿是整个热电偶测量最容易出错的地方。热偶引脚接入端子时,端子本身就是一个"冷端",它的温度必须被精确测量。我的做法是在接线端子旁边放一颗数字温度传感器(有时候直接用ADS122U04内部传感器也行),然后把这颗传感器的读数代入冷端补偿公式。很多人犯的错是把温度传感器放在MCU旁边,那个位置离接线端子太远,热阻不一样,测出来的冷端温度跟真实值差好几度,最终温度误差自然大得离谱。
2.4 UART电平匹配与长线传输
MK20DN的UART引脚是3.3V电平,ADS122U04的供电也是3.3V,直接连接没问题。但如果传感器板与主控板距离超过30cm,我建议用RS-485、CAN或者隔离式UART转发。最简单且稳妥的方案是加一颗带隔离的RS-485芯片,比如ISO3082或MAX3157,把ADC板上的UART转成差分信号,到主控那边再转回UART。这样不仅抗共模干扰,还能避免地环路。工业现场长距离走线时,UART信号如果直接裸奔,很容易因为电平参考地不一致造成误码甚至烧芯片。
还需要注意,ADS122U04的UART波特率由内部振荡器决定,默认在外部无晶振下精度有限,一般选9600或19200bps比较稳妥,别盲目上115200。数据手册里明确标注了不同波特率下允许的振荡器误差范围,超过这个范围通信就不稳定。我之前在样机上测试时用57600bps,有时连续转换数据会偶尔丢一个字节,降到19200bps后稳定运行再没出过问题。
3. 固件侧的初始化、寄存器配置与数据读取
硬件就绪后,最核心的活儿就是让MK20DN通过串口把ADC控制起来。ADS122U04的命令协议和寄存器映射其实不太复杂,但有几个细节如果没注意,会卡你好几天。我先把完整的初始化流程和代码结构拆开讲。
3.1 ADS122U04 寄存器配置实战
这颗ADC有四个主要寄存器,地址分别是0x00到0x03。要让PT100正常出数据,至少要配好三件事:PGA增益、通道选择、激励电流值和参考电压模式。以我用的PT100三线制为例,配置如下表所示:
| 寄存器 | 地址 | 关键位 | 配置值 | 说明 |
|---|---|---|---|---|
| CONFIG0 | 0x00 | PGA增益、通道选择 | 选择差分输入AIN1-AIN2,PGA=16 | RTD信号通常几毫伏到几十毫伏 |
| CONFIG1 | 0x01 | 数据速率、工作模式 | 20SPS连续转换 | 兼顾响应速度和工频抑制 |
| CONFIG2 | 0x02 | IDAC电流、参考电压 | IDAC=500µA,使用外部参考 | 三线制比例测量 |
| CONFIG3 | 0x03 | 转换模式、传感器 | 使能IDAC1/IDAC2,禁用内部TS | RTD模式不需要内部温度传感器 |
写寄存器通过WREG命令完成。ADS122U04的命令格式是:先发送命令字节,再发送数据字节。比如写CONFIG0和CONFIG1,可以发送0x40(代表写寄存器地址0x00、连续写1个寄存器之后还需要配置2,所以一般用0x43一次写4个寄存器,或者分两次)。我习惯一次性把四个寄存器全部写进去:
uint8_t configBuf[4] = {0x0A, 0x22, 0x78, 0x00}; ads122SendCommand(0x43); // 从地址0x00开始写,共4个配置寄存器 ads122SendBytes(configBuf, 4); // 发送配置数据其中0x0A对应CONFIG0:位2~1选择通道AIN1-AIN2差分输入,位5~4设PGA=16。0x22对应CONFIG1:低位00000代表20SPS连续模式?这里需要逐位核对数据手册,不同版本代码不相同,但关键是理解位域含义。写完后可以用RREG命令回读校验,我建议每次初始化后务必回读一遍,防止串口干扰导致配置写错。
3.2 MK20DN 的UART驱动初始化
MK20DN128VFM5的UART使用是比较标准的流程。先开启UART模块时钟,配置引脚复用为UART功能,然后设置波特率。以UART0为例,核心代码如下:
void uart0_init(uint32_t baud) { SIM->SCGC4 |= SIM_SCGC4_UART0_MASK; SIM->SCGC5 |= SIM_SCGC5_PORTA_MASK; PORTA->PCR[1] = PORT_PCR_MUX(2); // UART0_RX PORTA->PCR[2] = PORT_PCR_MUX(2); // UART0_TX UART0->BDH = (uint8_t)((baud >> 8) & 0x03); UART0->BDL = (uint8_t)(baud & 0xFF); UART0->C1 = 0; UART0->C2 = UART0_C2_RE_MASK | UART0_C2_TE_MASK; }波特率具体数值需要根据总线时钟计算,BDH/BDL寄存器配合SIM_CLKDIV设置。如果用了外部晶振作为MCU时钟源,公式里要填入实际外设时钟频率。ADSUART这边波特率在寄存器里配置好以后,两边保持一致即可。我采取的做法是:初始化时直接用10ms延时发一条复位命令,再等芯片启动完成,之后才读写寄存器。
3.3 连续模式下的数据读取与解析
我习惯把ADS122U04配置成连续转换模式,这样它每隔一个转换周期就自动输出一组数据。数据帧的格式是固定的:24位转换结果,加上状态字节。MK20DN这边只需要在UART接收中断里把字节缓存到一个环形缓冲区,然后主循环对帧进行解析。
static volatile uint8_t rxbuf[4]; void UART0_IRQHandler(void) { if (UART0->S1 & UART0_S1_RDRF_MASK) { uint8_t b = UART0->D; static uint8_t idx = 0; rxbuf[idx++] = b; if (idx >= 4) { idx = 0; dataReady = 1; } } }注意这里24位结果是有符号数,因为传感器的差分信号可能为正也可能为负,尤其是热电偶测温低于冷端温度时,原始码会是负数。状态字节的高位可以用来判断输入是否超出量程。解析时要把三个字节拼成一个32位变量,再根据符号扩展成有符号数:
int32_t raw = (rxbuf[0] << 16) | (rxbuf[1] << 8) | rxbuf[2]; if (raw & 0x800000) raw |= 0xFF000000;实测中代码的坑主要在字节顺序上,不同版本的手册对MSB在前还是LSB在前描述有时容易混淆。我的经验是直接把三字节分别打印出来,输入一个已知电压比对,确认顺序后再写解析逻辑,别一上来就假设。
4. 从原始码到温度:算法才是精度的一半
拿到24位原始码只是开始,把原始码变成稳定、准确的温度值才是整个系统价值的体现。ADS122U04内部没有帮你做RTD线性化,热电偶查表也要自己处理。这部分算法虽然经典,但实现细节直接影响最终精度。
4.1 比例测量法计算RTD阻值
对于RTD比例测量,ADC转换结果是差分输入电压与参考电压的比值乘以满量程。假设PGA增益为G,参考电压为VREF,那么测得的电压Vin = (raw / 0x7FFFFF) * VREF / G。而在比例测量中,VREF同时就是参考电阻RREF上的压降,IDAC电流I流入RREF,所以VREF = I * RREF。RTD上的电压Vrtd = I * RRTD。两个电压都经过PGA,如果通道增益一致,那么最终比例式简化为:
RRTD = RREF * (raw / 0x7FFFFF)
这个公式里没有I,也没有VREF,所以激励电流的温漂、参考电压的漂移全部被抵消了。这是我特别推荐比例测量的原因,它让RTD测温系统的长期稳定性大幅提升。实际代码里只需要一次乘法和一次除法:
float rtd_resistance(float rref, int32_t raw) { return rref * (float)raw / 8388607.0f; }这里的8388607就是2^23 - 1,对应24位ADC满量程正码值。
4.2 RTD温度换算:Callendar-Van Dusen 与查表插值
铂电阻的阻值和温度关系并不是完全线性的。PT100在0~850°C范围内,阻值与温度的关系是:
R(T) = R0 * (1 + AT + BT^2 + C*(T-100)*T^3)
其中A约3.9083e-3,B约-5.775e-7,C约-4.183e-12(0°C以下分段)。直接解这个方程需要迭代,在MCU上虽然不算慢,但如果每秒处理四次,占用的CPU时间也不容忽视。我的做法是预先生成一张{-200°C到+850°C、每1°C一个点}的查表,运行时用二分查找或者分段线性插值。
查表法在RTD这种平滑曲线上精度很高,1°C间隔下线性插值的误差可以忽略。内存占用也不大:若存int32阻值表,1051个点约4.2KB,MK20DN的Flash完全放得下。配合比例测量,从原始码到温度的整体误差可以控制在±0.1°C以内。
4.3 热电偶冷端补偿与非线性校正
热电偶测温的难点在于冷端补偿。设热端温度为T_hot,冷端温度为T_cold,热电偶输出的热电势E_meas实际上是T_hot和T_cold之间的差值产生的。要得到真实温度,先要测量冷端温度,并通过查表找到冷端温度对应的热电势E_cold,然后将E_actual = E_meas + E_cold,再用E_actual反查热端温度。
K型热电偶的非线性非常严重,尤其是在低温段,直接用线性系数算会差十几度。常用的处理方式是NIST多项式,但多项式阶数高,计算量偏大。工程上更常见的做法是分段查表加线性插值:把热电偶分度表按1°C间隔存成uint16数组,实际计算时先加法到热电势域,再反查温度。反查需要一个小循环,因为温度随电势递增,所以可以用二分查找,几百us内搞定。
冷端温度测量本身也要选用高精度传感器。我用的ADT7420数字传感器,精度±0.1°C,放在接线端子最近的一个铜片下面,用导热硅胶固定。实测冷端补偿后的热电偶通道误差在0~100°C范围内约±0.3°C,已经满足HVAC控制需求。
5. 实测中的校准、滤波与抗干扰
再漂亮的算法也怕实际环境的噪声。ADS122U04虽然硬件性能不错,但如果不在固件层做校准和滤波,测量数据依然会有不少毛刺。这一章我分享实测中验证过的几种方案。
5.1 两点校准消除系统偏差
ADC模块、PGA、参考电阻各自都有初始误差,最简单的办法是两点校准。常温下做一个零点校准:把输入短路,记录原始码的偏差offsetRaw;再用一个精密电阻箱接入RTD位置,设一个已知阻值R_cal,记录原始码calRaw。之后实际测量值的修正公式为:
R_actual = RREF * (raw - offsetRaw) / calRaw
注意如果PGA增益、激励电流不同,校准值要重新标定。工业产品出厂时最好在25°C和85°C下做两点温漂测试,把增益误差随温度变化的曲线存进Flash,运行时查表修正。这个办法能让系统在全温区内的误差从±0.3°C缩小到±0.15°C。
5.2 数据滤波参数的选择
HVAC系统的温度变化速率很慢,一般每秒变换不超过0.1°C,所以不需要追求ADC本身的高输出速率。我在20SPS的转换速率下,固件做了前级IIR滤波。一阶IIR公式为:
y[n] = y[n-1] + alpha * (x[n] - y[n-1])
alpha取0.2左右,时间常数约250ms,既能平滑噪声,又不会让温度控制的响应滞后太多。如果现场有电磁干扰导致偶发尖峰,建议再加一层中值滤波:取最近5个点的中间值作为IIR的输入。这个组合在我做过的空压机余热回收项目中效果很好,实测温度曲线非常平稳。
5.3 工频干扰和PCB布局的血泪教训
ADS122U04的ΔΣ调制器自带数字滤波,数据速率选20SPS时对50Hz工频有很强的抑制,但如果PCB布局不合理,再好的滤波也救不回来。我踩过一个坑:ADC输入走线底下直接铺了大面积的开关电源地平面,结果50Hz分量耦合进输入,原始码每隔20个点就跳一大截。后来把输入走线改成包地、增加输入RC滤波、并把模拟地与数字地在ADC下方用0欧电阻单点连接后,问题消失。
另一个教训是AIN输入的RC滤波电容要选C0G或NP0材质,X7R电容在直流偏压下的容量变化可能达到30%,会改变滤波器截止频率,甚至引起非线性。虽然对温度测量影响不大,但对追求高精度的人来说,这是值得注意的细节。
6. 从样板到稳定运行的HVAC产品还缺这些功课
实验室里跑通和现场长期稳定运行是两码事。HVAC设备环境温度波动大、电机启停频繁、线路可能长达几十米,这些都会影响系统可靠性。我总结了几项必须补的功课。
6.1 传感器断线检测
RTD断线时,激励电流会通过高阻抗路径流向ADC输入,导致输入电压瞬间超出量程。ADS122U04的状态字节里有一个超量程标志,MCU读到该标志后就能判断输入异常。更可靠的做法是利用内部比较器,将输入电压阈值设为接近量程顶端,一旦超过就触发报警。热电偶断线同样会使输入电压趋于供电轨或地,可以在输入两端并联一个高阻值偏置电阻,断线后输入被拉到一个确定电压,ADC读到的原始码会出现明显的极性跳变,据此判定故障。
HVAC系统不允许传感器故障时继续输出错误温度,否则控制器会误判。我在固件里定义了一个故障状态:连续三次读到超量程或者校验失败,就把温度值置为非法值,上位机收到后进入安全保护逻辑。
6.2 电气隔离与供电分区
ADC板、MCU板与现场传感器之间如果存在地电位差,共模电压可能高达几十伏。此时UART和传感器信号都需要隔离。UART侧我用数字隔离器(如ISO7721)把TX/RX隔开,隔离电源用B0505S模块输出5V再转3.3V。传感器侧,RTD是浮空的,问题不大,但热电偶如果外壳接地,则需要注意别形成地环路。
供电分区也是关键:MCU数字电源、ADC模拟电源、传感器激励电源最好各自再加一级LC滤波,防止开关电源的纹波串进ADC参考和输入。实测中,将三者分开后,ADC输出码的标准差从约8个LSB降到了2个LSB左右。
6.3 看门狗与通信错误恢复
MK20DN有独立看门狗,我配置成1秒超时。正常工作时,每个控制循环都会喂狗。万一主循环因为异常死循环,看门狗复位后程序会从一个恢复模式启动:先重新初始化ADC,再校验寄存器配置,如果校验不过,就发软复位命令给ADC重新配置。UART通信这边,如果连续收到错误帧,我除了丢弃以外,还会强制重新同步:发一条SDATAC命令然后立即发RDATAC,让数据流从头开始,这一步能解决大部分字节错位问题。
7. 实战中踩过的坑,说几个经常被文档坑到的点
最后聊几个我实际调试时遇到的问题。这些坑不一定在数据手册里写得特别明显,但对项目进度的影响非常大。
7.1 串口命令的起始字节丢失
ADS122U04的UART没有像SPI那样的片选信号,所以芯片如何识别"这是一条命令而不是数据"很重要。手册里建议在每次发送命令前先发一个Break信号或者特定的同步序列。我第一次调试时没注意,直接连续发送WREG命令,结果芯片经常把命令当作数据,寄存器里写的值乱了套。后来看应用笔记才明白,正确的做法是每次写寄存器前先发0x06复位命令,等500µs,再发写命令;或者用SDATAC退出连续模式之后,等待一个字节时间再发新命令。这种时序细节最难查,往往是"看起来不工作"的元凶。
7.2 热电偶冷端补偿位置影响巨大
前面提过冷端传感器要贴近接线端子。这里再强调一次:我做过对比,把温度传感器放在PCB角落和放在端子旁,测得的冷端温度能差2°C以上。K型热电偶热电势大约40µV/°C,2°C的冷端误差对应80µV,放大128倍后是10mV级别的ADC输入偏差,换算成温度就是整整2°C的误差。所以冷端补偿传感器的位置是热电偶精度最容易被忽视的因素,产品结构设计阶段就要考虑好。
7.3 参考电阻的封装与热电动势
我用过一款便宜的1206封装精密电阻做RREF,结果在温度变化时读数出现了周期性漂移,后来发现是电阻两端焊点温度不一致,产生了微小热电动势,叠加在参考电压上。换成四线开尔文接法,并且在PCB两个焊盘下方各打一个热平衡孔后,这种情况消失了。工业温度测量里,微小热电动势往往是高精度系统里最难查的误差源,遇到莫名温漂时不妨往这个方向想。
这套方案做到现在,已经在我手里的三个HVAC项目里稳定跑了半年。ADS122U04IPWR加MK20DN128VFM5的组合给我的体会是:接口简单、精度余量足、调试省心。如果你也在做类似的温度采集系统,建议多花点时间在硬件布局和校准策略上,这两块比选哪颗芯片更决定最终测量质量。