去年底接过一个暖通空调控制器的升级项目,需要把原来板子上的分立温度采集方案整体换掉。原方案用的是运放加比较器加NTC,精度和一致性实在撑不住,尤其是客户要求走工业现场总线,温度信号要能跟PLC联动,我就把目光放在了ADS122U04IPWR这颗24位ΔΣ ADC和STM32F439ZI主控的组合上。
整套系统弄下来之后发现,这个组合在工业和HVAC温度测量场景里确实有它的独到之处:ADS122U04IPWR自带PGA和RTD激励电流源,接口是4线UART而不是传统SPI;STM32F439ZI作为Cortex-M4平台,主频180MHz,2MB Flash,跑协议栈、显示、现场总线都有富余。这篇文章就把整个设计过程、寄存器配置、STM32端驱动写法、以及调试中踩过的坑整理出来,适合正在做多路PT100/PT1000或热电偶采集的工程师参考。
1. 方案定位:这个组合到底在解决什么问题
1.1 工业温度传感器背后的共同逻辑
先想清楚一件事:不管是工业过程控制还是HVAC系统,温度采集本质上都不是简单地把传感器接上ADC然后读个电压。
工业现场通常用PT100、PT1000这种热电阻,或者K型、T型热电偶。热电阻的特点是阻值随温度变化,但变化量很小,PT100在0℃时只有100Ω,温度每升高1℃大概只增加0.385Ω,对应的电压信号只有微伏到毫伏级别。这时候一个低噪声、高分辨率的ADC是必须的,否则信号还没进处理器就淹没在噪声里了。
热电偶的情况更特殊,输出的是微弱直流电压,而且存在冷端补偿的问题。虽然热电偶温度范围宽、探头便宜,但信号更脆弱,对前端模拟链路的要求更高。
所以温度采集系统真正要解决的是三个问题:微弱信号的低噪声放大、传感器激励与参考电压的稳定性、以及长线传输下的抗干扰能力。ADS122U04IPWR恰好把这三件事集成到了一颗芯片里。
1.2 为什么选ADS122U04IPWR而不是更常见的ADS1220
ADS1220在论坛里出现的频率更高,也确实是一颗优秀的24位ADC。但我选ADS122U04IPWR,核心原因是它的接口。名字里的“U”代表UART接口,TI给它的定义是4线UART,引脚包括CS、SCLK、DIN、DOUT/DRDY。
这个接口设计很有意思。它不像标准UART那样只靠TX/RX两根线传数据,而是保留了SCLK时钟线,但数据传输格式是带起始位和停止位的UART帧。
好处是长线传输时不容易被时钟边沿干扰。普通SPI在PCB板内短距离传输没问题,但一旦传感器通过连接器、线缆延伸到几十米外的现场设备上,SPI那套电平翻转很容易被EMI影响。UART帧有起始位做同步,抗干扰能力天然好一些。
ADS122U04IPWR的PGA增益支持1到128倍,输入通道有4个,可以配置成差分或单端。采样率从20SPS到1000SPS可选,内部有两个可编程电流源IDAC1、IDAC2,可以直接给热电阻提供激励电流,电流值从250µA到1000µA可调。还有断线检测功能,测热电阻断线时能及时发现,这个在现场太重要了。
封装是TSSOP-16,后缀里的I代表工业级温度范围,-40℃到+125℃,PW是TSSOP封装,R是编带包装。
1.3 STM32F439ZI是不是大材小用
很多人看到F439ZI会觉得性能过剩。确实,如果只做温度采集,一个Cortex-M0就够用了,但实际项目里主控往往还要干别的事:跑Modbus RTU、BACnet协议、人机界面显示,甚至是本地的数据记录和远程升级。
F439ZI是Z后缀的144脚封装,I代表2MB Flash。相比F429,它多了硬件加密引擎,跑网络通信时能省不少CPU负载。这套配置在HVAC控制器里其实很常见,因为整个系统往往要同时管理多个温度传感器、湿度传感器、阀门执行器,还要跟楼宇自控系统对接。温度采集只是其中一个功能模块,但它必须稳定可靠。
所以我个人的建议是:如果你已经有现成的STM32F439ZI平台,直接在这个平台上加一路USART给ADS122U04用就行,不需要为了省成本换低端MCU。如果纯做变送器,那完全可以选更小的芯片,不必照抄这套方案。
2. 硬件设计:把RTD测量链路做扎实
2.1 开尔文连接和四线制的原理
工业上测热电阻,讲究的是四线制还是三线制,本质都是在消除导线电阻的测量误差。
先看最简单的两线制。两根导线从变送器延伸到探头,每根导线本身有电阻,假设是10Ω,串在测量回路里就是20Ω误差。PT100在0℃时100Ω,20Ω误差相当于50多℃的偏差,完全不可接受。
四线制是最彻底的解决办法。两根导线给传感器通恒定电流,另外两根导线接ADC的高阻输入端,没有电流流过,所以导线压降不会进入测量结果。
ADS122U04IPWR支持这种接法。内部IDAC输出恒定电流,经过RTD后流回GND,同时用另外两根线测量RTD两端电压,由于ADC输入阻抗极高,测量线上几乎没有电流,导线电阻不会影响精度。
三线制是工业现场的折中方案,因为大部分传感器探头只有三根引线。经典接法是:IDAC1从AIN0输出电流,经过RTD后从AIN1返回,同时IDAC2从AIN2输出同样的电流到RTD的同一端,用来补偿引线电阻带来的误差。两个电流源做成一样大,导线电阻产生的压降被抵消,测量精度不会比四线制差太多。
2.2 比例式测量的核心:别再依赖内部基准
测电阻最怕的是激励电流漂移。IDAC标称500µA,实际可能随温度变化产生偏移,导致RTD上的电压也跟着变化,ADC采集到的数据就不准了。
解决办法是比例式测量。让IDAC电流同时流过RTD和一个精密参考电阻RREF,ADC的参考电压就取自RREF两端。这样ADC输出的数字量是RTD电压和RREF电压的比值,也就是RRTD和RREF的比值,跟IDAC电流的绝对值完全无关。
举个例子:IDAC输出500µA,RREF选2000Ω,那么RREF上的电压正好是1V,这个1V作为ADC的参考电压。RTD在0℃时是100Ω,两端电压是50mV。如果PGA增益设为8,ADC测量到的就是400mV相对1V满量程的比例值。
这个方案的妙处在于,就算IDAC实际只输出了450µA,RREF上的电压变成0.9V,RTD上的电压变成45mV,比值仍然不变。温度测量的长期稳定性和温漂特性大幅提高。
2.3 WREF和IDAC的路由配置
ADS122U04IPWR的四个模拟输入AIN0到AIN3是复用的,既可以做测量输入,也可以做IDAC输出路由,还可以接参考电压。
比例式测量的典型连接是这样的:RREF一端接模拟地,另一端接到ADS122U04的REFP引脚,同时从REFP处引一路到AIN0作为RTD的激励正端。RTD另一端通过导线接回AIN1,也就是ADC的差分测量负端。IDAC1配置成从AIN0输出电流,这样电流路径就是IDAC1→AIN0→RTD→AIN1→经过导线回到系统地。
同时IDAC2从AIN2输出电流,接到RTD的低端附近,做三线制补偿。而ADC的参考电压输入REFP/REFN就跨接在RREF两端。
寄存器配置上,AIN0/AIN1作为差分输入PGA通道,IDAC1路由到AIN0,IDAC2路由到AIN2,参考电压选择外部参考而非内部参考。
实际配置代码如下:
// 寄存器0:选择AIN0/AIN1差分输入,PGA增益=8 // 二进制 00001 000 = 0x08,其中GAIN编码01000代表8倍 adc_write_reg(0x00, 0x08); // 寄存器1:数据速率20SPS,连续转换模式 adc_write_reg(0x01, 0x00); // 寄存器2:外部参考电压,IDAC电流500µA adc_write_reg(0x02, 0x0A); // 寄存器3:IDAC1路由到AIN0,IDAC2路由到AIN2 adc_write_reg(0x03, 0x10);需要注意RREF的选择。选太大会导致RTD上的电压在满量程比例中占比太小,分辨率不够;选太小又会让参考电压过小,容易受噪声影响。一般让RREF两端的电压在1V到2.048V之间比较合适。IDAC选500µA时,RREF选2kΩ到4kΩ,IDAC选250µA时,RREF可以选4kΩ到8kΩ。
2.4 自热效应和电缆保护电路
给RTD通电流会产生热量,电流越大自热越严重。100Ω的PT100在500µA下的功耗只有25µW,温度影响很小。但如果被测介质本身导热差,或者传感器是细长铠装结构,自热就可能带来0.1℃量级的误差。要求高的话建议用250µA电流源。
硬件上前端输入最好加RC滤波器,形成一阶低通,时间为1ms到10ms之间,能有效衰减工业现场的射频干扰。长线进入板卡的入口加TVS管做ESD保护,正负极之间加一个电容,抑制共模干扰。
3. ADS122U04IPWR的寄存器级配置细节
3.1 四组寄存器的功能划分
ADS122U04IPWR内部有4个8位寄存器,所有工作模式都通过寄存器来定义。
寄存器0控制输入多路复用和PGA增益。高三位MUX决定输入通道选择,低五位GAIN决定增益倍数,编码从1倍到128倍。这个通道选择位支持把负输入接到固定点,也可以配置成单端模式。
寄存器1控制数据速率和转换模式。高四位DR设置采样率档位,顺序对应20SPS、45SPS、90SPS、170SPS、330SPS、600SPS、1000SPS这几档。另外还有连续转换模式和单次转换模式的切换位。
寄存器2控制参考电压和IDAC电流源。最高位选择内部2.048V参考或外部参考,中间位是内部参考电压的微调,低位的IDAC字段设置激励电流的档位。
寄存器3负责IDAC输出路由和DRDY模式。高两位IDAC1选择输出到哪个AIN通道,接着两位是IDAC2的路由,还有位控制DRDY引脚的行为模式和数据传输的CRC使能。
具体每一位的定义建议翻阅最新版数据手册,我这里给出的是工程配置中最常用到的部分。关键是要理解寄存器之间是相互关联的,比如IDAC路由和MUX通道选择配合才能让电流源和测量通道对应上。
3.2 通信命令:WREG、RREG、RDATA
通过UART接口控制ADS122U04IPWR,本质是发命令字节和接收数据字节。
写寄存器命令格式:
// WREG命令:0x40 | (寄存器地址 << 2) // 后跟寄存器计数字节,0x00表示写1个寄存器 // 然后再跟要写入的数据字节 uint8_t cmd[3]; cmd[0] = 0x40 | (reg << 2); cmd[1] = 0x00; cmd[2] = value;读寄存器命令格式:
// RREG命令:0x20 | (寄存器地址 << 2) // 后跟计数字节,然后从DOUT/DRDY读回数据 uint8_t cmd[2]; cmd[0] = 0x20 | (reg << 2); cmd[1] = 0x00;读取转换结果用RDATA命令,发一个0x12字节,器件从DOUT/DRDY输出24位转换数据。
如果配置成连续转换模式,可以不用RDATA命令,而是让ADC持续通过DOUT/DRDY线输出转换结果。这时候MCU只需要等待DRDY信号的下降沿,然后按UART帧读取数据即可。还有一种自动模式叫恒温模式,是把器件配置成DOUT/DRDY引脚直接以某种数据率逐个输出转换结果,不需要MCU发命令。
工程上我更推荐单次转换模式下的“启动转转换-等待DRDY-发RDATA”这个流程。好处是每次转换之间可以切换通道、修改配置,多个传感器复用时逻辑特别清晰,功耗也更低。
3.3 断线检测和热电偶温度传感器模式
ADS122U04IPWR还带一个热电偶模式。MUX可以配置成把AINP和AINN短接到内部某个固定节点,高端温度传感器专用。这种场景下寄存器0的配置和RTD模式不太一样,增益也通常会选大一些,因为热电偶输出电压比RTD小得多。
断线检测功能由寄存器2的低位控制。使能后,ADC内部会周期性给输入加一个很小的偏置电流,如果回路断开,输入会被拉到某个电位,转换结果变成满量程或接近满量程,主控据此判断报警。
HVAC系统里传感器断线是常见故障,特别是冬天冷凝水结冰环境下,探头引线容易折断。断线检测一定要留接口,哪怕不在主程序里处理,至少要在调试阶段验证过。
4. STM32F439ZI固件实现:从CubeMX到温度数据
4.1 同步USART用法
ADS122U04IPWR的4线UART接口,很多第一次用的人会尝试拿标准SPI去驱动,结果发现通信不上。问题在于它的数据帧格式带起始位和停止位。
正确做法是使用STM32的USART外设的同步模式。
STM32F439ZI的USART支持硬件SCLK输出,配置成同步模式后,USART_CK引脚会自动在TX发送数据的时钟节拍上产生SCLK信号。这样正好能匹配ADS122U04IPWR对SCLK的要求。
接线方式:
- STM32的USART_TX → ADS122U04的DIN
- STM32的USART_RX → ADS122U04的DOUT/DRDY
- STM32的USART_CK → ADS122U04的SCLK
- STM32的任意GPIO → ADS122U04的CS
在STM32CubeMX里配置USART为同步模式时,需要打开同步时钟输出,设置数据长度为8位,选择MSB先发,时钟极性和相位要与ADS122U04IPWR的数据手册要求匹配。SCLK默认空闲低电平,第一个边沿采样数据,这是比较常见的配置。
波特率方面,ADS122U04IPWR支持的最高传输速率是1Mbps左右,STM32F439ZI的USART完全可以满足。实际工程中,如果在强干扰环境下长线传输,可以把波特率降到460800甚至更低,传输稳定性会显著提高。
4.2 命令封装层的代码实现
驱动层分成两部分,底层是CS控制加USART收发,上层是具体的寄存器读写和转换数据读取。
底层发送函数:
static void adc_cs_low(void) { HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_RESET); } static void adc_cs_high(void) { HAL_GPIO_WritePin(ADC_CS_GPIO_Port, ADC_CS_Pin, GPIO_PIN_SET); } static void adc_transmit(uint8_t *buf, uint16_t len) { HAL_UART_Transmit(&huart1, buf, len, HAL_MAX_DELAY); } static void adc_receive(uint8_t *buf, uint16_t len) { HAL_UART_Receive(&huart1, buf, len, HAL_MAX_DELAY); }写寄存器函数:
void adc_write_reg(uint8_t reg, uint8_t value) { uint8_t cmd[3]; cmd[0] = 0x40 | (reg << 2); cmd[1] = 0x00; cmd[2] = value; adc_cs_low(); adc_transmit(cmd, 3); adc_cs_high(); }读寄存器函数:
uint8_t adc_read_reg(uint8_t reg) { uint8_t cmd[2]; uint8_t value = 0; cmd[0] = 0x20 | (reg << 2); cmd[1] = 0x00; adc_cs_low(); adc_transmit(cmd, 2); adc_receive(&value, 1); adc_cs_high(); return value; }读取ADC转换结果时要注意,24位数据是二进制补码格式,需要扩展符号位:
int32_t adc_read_data(void) { uint8_t buf[4]; uint8_t cmd = 0x12; int32_t raw; adc_cs_low(); adc_transmit(&cmd, 1); adc_receive(buf, 4); adc_cs_high(); raw = ((int32_t)((buf[0] << 16) | (buf[1] << 8) | buf[2]) << 8) >> 8; return raw; }这里buf[3]是器件返回的CRC校验字节或者状态字节,取决于寄存器3的配置。如果使能了CRC,需要在接收后对前三个字节做CRC校验。
4.3 等待DRDY与转换流程
连续转换模式下,ADC每次转换完成,DOUT/DRDY引脚会产生一个下降沿。MCU可以通过这个下降沿触发外部中断,然后进行数据读取。这也是DRDY这个名字的由来。
如果使用外部中断,需要注意中断服务函数里不要做太耗时的操作。最好只置一个事件标志,主循环里检测到标志后再调用adc_read_data()读取数据,避免中断中阻塞USART通信引发其他优先级任务卡住。
单次转换模式的完整流程:
- 写寄存器配置
- 发START命令或切换模式启动转换
- 等待DRDY下降沿
- 发RDATA命令读取转换结果
- 回到步骤1或继续下一次转换
这个流程里有个容易踩的坑:发完START命令后DRDY不会立刻出现下降沿,因为ADC需要完成一次完整的采样转换周期,时间是1/采样率。如果设置的采样率是20SPS,这个周期是50ms,MCU等待DRDY期间不要用阻塞延时死等,最好使用事件标志加超时机制,否则调试时很容易卡死。
4.4 多通道传感器扫描的状态机
同时管理多路RTD传感器时,一个推荐的做法是定义一张传感器配置表,每个通道包含寄存器配置、校准系数和结果存放地址,然后在主循环里用一个状态机轮询。
#define CH_NUM 6 typedef struct { uint8_t reg0; uint8_t reg1; uint8_t reg2; uint8_t reg3; float temp_result; int32_t raw_result; } sensor_ch_t; sensor_ch_t ch[CH_NUM]; uint8_t current_ch = 0;主循环状态机:
switch (scan_state) { case SCAN_CONFIGURE: adc_write_reg(0x00, ch[current_ch].reg0); adc_write_reg(0x01, ch[current_ch].reg1); adc_write_reg(0x02, ch[current_ch].reg2); adc_write_reg(0x03, ch[current_ch].reg3); scan_state = SCAN_START; break; case SCAN_START: { uint8_t cmd = 0x08; adc_cs_low(); adc_transmit(&cmd, 1); adc_cs_high(); drdy_timeout = HAL_GetTick(); scan_state = SCAN_WAIT; } break; case SCAN_WAIT: if (drdy_flag) { ch[current_ch].raw_result = adc_read_data(); ch[current_ch].temp_result = rtd_to_temp(ch[current_ch].raw_result, current_ch); current_ch++; if (current_ch >= CH_NUM) current_ch = 0; scan_state = SCAN_CONFIGURE; } else if (HAL_GetTick() - drdy_timeout > 100) { scan_state = SCAN_CONFIGURE; } break; }切换通道后最好丢弃前几次转换数据,因为输入多路复用器切换后,PGA需要几个采样周期稳定下来。工业上常用的做法是丢弃新的MUX切换后的前两个转换结果,从第三个开始算有效数据。
4.5 RTD电阻到温度的换算算法
拿到的原始值是24位ADC码值,要先反算电阻值。
比例式测量的关系是:
RRTD = (Code / 2^23) × (2 × VREF / (Gain × IIDAC))但因为测量是比例的,实际简化成:
RRTD = (Code / 2^23) × (2 × RREF / Gain)这个式子里的RREF是精密参考电阻,Gain是PGA增益。用这个公式从ADC原始码直接算出热电阻阻值,整个过程跳过了电压和电流的中间计算,误差来源只剩下RREF本身的精度和ADC的线性度。
得到RRTD后,查分度表或者用Callendar-Van Dusen公式计算温度。PT100在0℃以上用二次多项式就够了:
R(T) = R0 × (1 + A×T + B×T²)这里R0=100Ω,A=3.9083e-3,B=-5.775e-7。0℃以下需要加上C项修正,C=-4.183e-12。如果单片机算力足够,直接用浮点多项式;如果为了性能,可以先用二分法查表,再用线性插值补余数。
温度测量分辨率方面,20SPS采样率加8倍增益配置,实测PT100的温度噪声在0.01℃左右,工程上完全够用。工业标准的A级PT100精度是±0.15℃,更高采样率下的噪声反而对精度没有实际增益。
5. 实测结果与校准经验
5.1 用电阻箱代替探头做端到端验证
调试阶段不建议直接拿真温度探头调程序,因为无法准确判断误差来自传感器本身还是信号链。我习惯用一个0.1%精度的精密电阻箱模拟PT100的输出,直接从0℃对应的100Ω加到100℃对应的138.51Ω,逐一验证每个温度点的读数。
实测下来,ADS122U04IPWR在比例式测量配置下,从ADC原始码反算回的电阻值和电阻箱示值的偏差能控制在±0.05Ω以内。折算成温度大约是±0.13℃,这已经是比较好的信号链状态。
如果发现偏差和电阻值之间存在明显的线性趋势,大概率是RREF的标称值和实际值不一致。解决办法是软件校准:先测一个已知电阻,反推出实际RREF值,然后把这个修正后的RREF写进固件。
5.2 现场环境下的抗干扰验证
HVAC现场和实验室完全不同。空调机房里有变频器、水泵电机、还有和动力电缆并行走的传感器线缆,EMI环境很恶劣。
我做过一次专项测试:把传感器线缆和变频器输出电缆绑在一起走1米,开启变频器满功率运行,ADC读数跳变量小于±0.3℃。这个结果的前提是前端加了RC滤波,并且ADC的模拟供电和数字供电做了隔离处理,模拟电源用LC滤波单独供电,数字地模拟地单点连接。
还有一次测试是模拟传感器断线。把测试线缆中间拨开,故意让连接器虚接,ADS122U04IPWR的断线检测功能在几次转换周期内就报告了断线状态,表现很稳定。HVAC系统冬天出这种故障的频率其实不低,有这个检测功能,维护成本能省不少。
5.3 温度测量链路长期稳定性
长期稳定性方面,连续运行48小时、采样率20SPS、增益8倍,温度数据漂移控制在±0.1℃以内。这个测试里最大的漂移来源其实是环境温度变化对RREF温漂的影响,所以选型时RREF一定要用低温度系数的精密电阻,最好选±0.1%精度、±5ppm/℃温漂的规格。
6. 常见问题与排查技巧实录
6.1 通信不上,DRDY永远没反应
先查CS引脚的接法。ADS122U04IPWR的CS引脚是高有效内部拉低的,也就是说CS为低电平时器件选通。如果直接连到MCU的GPIO,初始状态是高,必须拉低才能通信。有些人刚上电时GPIO默认高电平,器件一直没被选通,表现为读寄存器全是FF。
再查SCLK时钟极性和波特率。USART同步模式下时钟极性和相位与ADS122U04不匹配,会导致读取到的数据全是乱码。这时候用示波器对比SCLK和DOUT线上的电平时序,一眼就能看出来问题。
6.2 数据跳动或读数不稳定
读数跳动先看采样率。采样率选的越高,等效位数越低,跳动越大。工业温度变送器一般不需要1000SPS,20SPS或45SPS已经是绝大多数现场的上限。
再检查模拟电源。ADS122U04IPWR对模拟供电质量敏感,供电纹波大于10mV时,转换结果会周期性波动。可以在AVDD引脚附近加一个10µF和100nF的滤波电容,必要时串联磁珠。
6.3 比例式测量结果偏大或偏小
结果整体偏大或偏小,优先怀疑RREF实际阻值和标称值不一致,或者PGA增益编码配置错了。GAIN编码有规律,1倍、2倍、4倍、8倍的编码分别是00000、00001、00010、00100,不是连续递增关系,写配置时最容易写错。
6.4 温度换算在负温区误差明显
HVAC系统经常要测零下温度,比如冷水管温度-10℃。PT100在0℃以下的电阻-温度关系是非线性的,只用A、B系数计算误差会越来越大。负温区必须用完整的Callendar-Van Dusen公式,加上C修正项。
6.5 多个通道扫描时第一个通道数据异常
多路复用切换后,PGA保持电容需要一定时间达到稳定。前两个转换结果丢弃即可,不要用来做控制或显示。通道切换频率也不要太快,至少留出三个转换周期的时间间隔。
7. 一点实操体会
这套方案让我最满意的地方是设计边界清晰:传感器激励由ADC自行完成,信号调理在模拟前端一次搞定,MCU只做配置和读数据,软件处理逻辑简单直接。即使后面要换传感器类型,只需要改几行寄存器配置,不用重新画板子。
踩过的最大一个坑是ADC初始化顺序。上电后如果立刻写配置寄存器,器件内部可能还没完成上电复位,寄存器写入会丢失。稳妥的做法是上电后延时10ms以上,再发送RREG命令确认能读回0x00,然后才写配置。
最后再分享一个小技巧:你在调试阶段如果把ADS122U04IPWR配置成了连续转换模式,想切换到单次模式,一定要发STOP命令,然后再修改寄存器。我遇到过直接改寄存器导致转换状态机卡死的情况,当时排查了很久才意识到是模式切换顺序的问题。严格按命令流程走,这个系列器件其实非常可靠。
硬件结温在HVAC机房里通常不会太高,但如果你放在太阳直晒的室外防水盒里,注意别超过85℃,ADC和主控都要留足降额。ADS122U04IPWR的工业级规格能扛到125℃,但旁边的LDO和电阻不一定扛得住。