前一阵给厂里的HVAC控制板做升级,客户对温度精度的要求从原来的±0.5℃收紧到了±0.1℃。这块板子主控用的就是STM32F091RC,一开始想靠内部ADC蒙混过关,账一算就放弃了,老老实实加了一颗ADS122U04IPWR。这是一颗带UART接口的24位四通道低功耗Δ-Σ ADC,片内集成PGA、双IDAC电流源、内部温度传感器和2.048V基准,用来测RTD和热电偶非常顺手。这篇文章把从选型、传感器接线、串口协议、温度换算到调试踩坑的完整过程整理出来,适合正在做工业采集、楼宇自控、风柜盘管控制以及各类HVAC仪表的朋友参考,也适合第一次用UART接口ADC的嵌入式工程师避雷。
1. 为什么这块板子最后选了ADS122U04IPWR而不是SPI接口ADC
1.1 STM32F091RC内置12位ADC的温度分辨率算了一笔账就凉了
STM32F091RC是Cortex-M0内核,48MHz主频,片上确实带了12位ADC,但拿它测高精度温度纯属为难它。3.3V参考电压下,12位ADC一个LSB大约是0.806mV。PT100在0℃附近的灵敏度约为0.385Ω/℃,如果用1mA恒流源激励,电压灵敏度大约是0.385mV/℃。换算下来,内置ADC一位就对应大约2.1℃,这还没算上ADC本身的噪声和失调,测个大概的过温保护还行,想稳定输出0.1℃分辨率就是天方夜谭。
外挂ADC几乎是必然选择。ADS122U04IPWR这类24位ADC在内部2.048V参考、PGA=1的时候,满量程是±2.048V,理论LSB约为0.244µV,对应到PT100测温大约是0.0006℃/位,即便实际有效位数只有18~19位,余量也远超需求。真正决定系统精度的不是ADC的位数,而是前端传感器、激励电流和参考源的稳定性,这个后面会详细算。
1.2 SPI版和UART版的取舍,关键是总线和传输距离
选型时其实犹豫过ADS1220这类SPI接口的经典型号。SPI版资料多、社区案例丰富,代码范例随手就能搜到,但它有两个问题让我最终放弃。
第一是总线占用。当时的板子上已经有SPI Flash和数字隔离器,再挂一颗ADC虽然技术上可行,但总线上设备多了,时序调试和故障排查都会更麻烦。ADS122U04IPWR直接用UART,STM32F091RC的USART资源充足,单独划一个串口给ADC,剩下的串口继续留给Modbus或调试,分工干净。
第二是传输距离。HVAC现场传感器线通常要走几米甚至十几米,SPI的SCK/MOSI/MISO在长线上容易受寄生电容和串扰影响,速率稍高波形就变形。UART是异步串行,线缆阻抗要求宽松得多,配合RS485电平转换芯片还能轻松拉出百米传输。特别是有时候一个控制箱要采集多个风管温度点,ADS122U04的地址模式允许多片挂在同一条UART总线上,这点比SPI版本灵活太多。
1.3 ADS122U04IPWR和F091的资源匹配度
ADS122U04IPWR的PWR后缀是TSSOP-16封装,手工焊接没问题,引脚间距也够常规PCB走线。片内集成了24位Δ-Σ ADC、可编程增益PGA(增益最高128倍)、两路IDAC恒流源、内部温度传感器、内部2.048V基准,测RTD不需要外部恒流源芯片,测热电偶不需要前端仪表放大器,一颗芯片把模拟信号链全包了。
STM32F091RC这边压力也很小,256KB Flash跑完协议栈和算法之后还剩很多,32KB SRAM做查表插值和滑动滤波绰绰有余。整体器件成本不高,适合量产的中小型控制器。如果你的产品只需要测一个点,也可以考虑用I2C接口的专用温度芯片,成本更低,但灵活性差很多:换传感器类型、接热电偶、做多路采集、远程传输全都受限。外挂高精度ADC方案的意义在于,一块板子适配多种传感器输入,硬件重新投板次数能省掉不少。
2. 传感器接线与模拟前端配置:RTD和热电偶的差异比想象的大
2.1 三线制RTD接线时,两个IDAC配合才能抵消线阻
PT100在0℃附近等效电阻约100Ω,但传感器到控制板的线缆电阻可能在1~5Ω之间,这直接对应好几度的误差,所以HVAC现场几乎都用三线制而不是两线制。
三线制是一种很巧妙的补偿办法。把PT100一端接AIN0,另一端接AIN1,同时从AIN1这一端再引一根线接到IDAC2输出,IDAC1则从AIN0那一端进入。两个IDAC配置成相同的电流值,一路电流经线缆0流过RTD再经线缆1回来,另一路经线缆2单独走。这样在差分输入端,线缆0和线缆1上的压降方向相反,做差之后基本抵消,剩下的才是RTD本体的电压。
实际使用中有个容易翻车的地方:两个IDAC的匹配度并不是100%,典型失配在0.1%~0.2%水平,对应到线阻1Ω时会产生0.1~0.2%的残余误差,大约0.01~0.02℃,通常可以接受。要注意IDAC2必须接到RTD的同一节点,而不是随便接个GND,否则补偿逻辑就错了。四线制则简单粗暴,IDAC用于激励,AIN0/AIN1走采样线,采样线上几乎没有电流,线阻压降自然消失,精度最高但需要多一根线。HVAC传感器一般预埋三根线,所以三线制是最常用选择。两线制只在精度要求不高的检测场景用,用于闭环控制容易出问题。
2.2 热电偶虽然信号小,PGA高增益下更要注意共模电压
热电偶的输出是毫伏级信号,K型热电偶灵敏度约40µV/℃,从-20℃到150℃的测量范围信号大约7mV。用PGA=64时满量程约±32mV,HVAC范围内余量充足。如果以后要测工业炉膛到700℃以上,需要把增益降到32甚至16,避免信号削波。
但热电偶比RTD麻烦的是冷端补偿。热电偶测到的是测量端与参考端(冷端)的温差,接线端子处的温度必须知道。ADS122U04内部集成了温度传感器,可以直接拿来当冷端参考,读出芯片附近温度后查表转成对应热电势,再与测量电压相加。这个方法省成本,但内部温度传感器的精度一般,出厂需要做偏移标定,标定后冷端补偿大约±1℃水平,总体精度取决于传感器等级。想要更高精度可以把冷端传感器换成TMP117这类0.1℃精度的数字温度芯片,贴在接线端子旁边。
还有一个非常隐蔽的坑:PGA高增益下,输入共模电压范围很窄。我把热电偶负极直接接GND试过,读出来的码值要么跳变要么直接贴满量程,看起来像过压保护,其实是PGA内部输入级的共模工作点跑到范围外了。正确做法是用两个偏置电阻把AIN0和AIN1的共模电压抬到接近VREF/2,再以差分方式接入热电偶信号。别嫌这步麻烦,不做的话高增益下很难稳定。
2.3 PGA增益、IDAC电流和参考电压要按场景一起配
这几个参数是相互耦合的,不是随便设个值就能跑。下面是我实际用过的配置组合,可以直接照搬。
| 应用场景 | PGA增益 | IDAC电流 | 参考源 | 数据速率 |
|---|---|---|---|---|
| PT100三线制 0~150℃ | 16 | 500µA | 内部2.048V | 20SPS |
| K型热电偶 -20~150℃ | 64 | 关闭 | 内部2.048V | 20SPS |
| NTC热敏电阻 10kΩ | 4 | 250µA | 内部2.048V | 20SPS |
增益的选择原则是让满量程信号占到ADC输入范围的50%以上,但又不能超量程。以PT100配500µA电流为例,150℃时PT100电阻约158Ω,差分电压约为79mV,PGA=16后放大到1.26V,在±2.048V满量程内留有充足余量。IDAC电流也不是越大越好,500µA在PT100上产生的自热功率只有25µW,实际影响大约0.02℃,但如果换成1mA则自热会明显上升,HVAC风管里传感器长期被气流吹,自热误差会随风速变化,很难校准掉,所以500µA是个折中值。
参考电压方面,我不建议直接用MCU供电的3.3V,虽然ADS122U04内部基准精度不算顶尖,但比普通稳压芯片靠谱。对精度有更高要求可以外接REF5025这类精密基准,代价是成本和面积都增加。去耦电容不要省,AVDD和DVDD各放一个0.1µF陶瓷电容,再并联一个10µF钽电容,PCB上的AGND和DGND建议单点连接而不是直接整片铺地了事,这一步直接影响ADC底噪。
3. STM32F091RC和ADS122U04的串口链路搭建与时序配合
3.1 给ADC一个稳定的CLK,波特率才谈得上准确
ADS122U04的UART波特率不是像普通芯片那样用外部晶振定死的,而是由CLK输入时钟通过内部逻辑分频得到。CLK我强烈建议用有源晶振提供4.096MHz信号,别图省事直接用STM32F091RC的MCO输出。
我一开始就是用的MCO,调试时发现每次上电后第一帧数据经常乱码,后面才恢复。抓了CLK引脚的波形才发现,MCO的输出在MCU时钟初始化阶段还没稳定,ADC已经上电开始按错误的参考频率初始化串口参数了。换上有源晶振之后,在程序里等晶振稳定200ms再初始化ADC,乱码问题彻底消失。波特率不用追求高,整个采集链路只有20SPS的读出量,用9600或者19200完全够,反而抗脉冲干扰余量更好。115200在强电磁环境下面临误码风险,工业现场不建议。
3.2 寄存器配置序列和命令帧
ADS122U04的UART命令风格和SPI版本的ADS122x系列很像,有RREG、WREG、RDATA等命令,但具体命令字节编码和寄存器位域定义要以TI数据手册为准。下面给出的是程序框架,重点是先写配置、回读校验、再启动测量。
static void ADS122U04_Init(void) { uint8_t cfg[4]; uint8_t rbuf[4]; /* 等待芯片上电稳定和外部时钟稳定 */ System_DelayMs(200); /* CONFIG0: AIN0/AIN1差分输入,PGA增益16 */ /* CONFIG1: 20SPS,内部滤波器 */ /* CONFIG2: 内部2.048V基准,正常极性 */ /* CONFIG3: IDAC1=500uA, IDAC2=500uA, 使能内部温度传感器 */ cfg[0] = 0x10; cfg[1] = 0x20; cfg[2] = 0x04; cfg[3] = 0x30; /* WREG命令写入 */ ADS122U04_SendCmd(0x40 | (0x00 << 2), cfg, 4); /* RREG回读校验 */ ADS122U04_SendCmd(0x20 | (0x00 << 2), rbuf, 4); if (memcmp(cfg, rbuf, 4) != 0) { /* 配置未写成功,这里置错误标志让主循环处理 */ g_adc_error = 1; } }这段框架代码里的命令编码逻辑是:WREG命令高位是0x40,带上起始寄存器地址和寄存器数量;RREG类似。实际项目里建议把回读校验做进初始化流程,因为UART链路偶发错误时,寄存器可能没写进去,而转换结果看起来又像是正常的,这种隐性故障最磨人。回读能保证每次上电后配置都是确定的。
3.3 DRDY信号:从芯片到主循环的时序配合
ADS122U04的DRDY引脚是转换完成标志,数据准备好时会拉低。无论用单次读取模式还是连续读取模式,都要让DRDY参与控制,否则很容易读到旧数据或者读到一半被新数据覆盖。
我的做法是把DRDY接到STM32F091RC的EXTI引脚,比如PB0,配置成下降沿触发。中断里只置一个标志位,主循环检测到标志后发RDATA命令并读取3字节数据。数据格式是24位二进制补码,占用3个字节:
volatile uint8_t g_data_ready = 0; void EXTI0_IRQHandler(void) { if (EXTI->PR & EXTI_PR_PR0) { EXTI->PR = EXTI_PR_PR0; g_data_ready = 1; } } int32_t ADS122U04_ReadTemperatureRaw(void) { uint8_t buf[3]; int32_t raw; if (!g_data_ready) { return 0; } g_data_ready = 0; /* 发送RDATA命令 */ ADS122U04_SendSingleCmd(0x10); /* 接收3字节 */ ADS122U04_UartReceive(buf, 3, 100); raw = (int32_t)((buf[0] << 16) | (buf[1] << 8) | buf[2]); /* 24位补码符号扩展 */ if (raw & 0x800000) { raw |= 0xFF000000; } return raw; }有个细节:在单次转换模式下,DRDY拉低后要及时读取,如果拖太久下一次转换可能已经开始,旧数据寄存器可能已被覆盖。连续读取模式下DRDY的脉冲时序又与单次模式不同,不要把网上SPI版本ADS1220的代码直接搬过来,必须用示波器或者逻辑分析仪量一下实际波形。
3.4 用DMA加UART空闲中断优化接收效率
温度采集虽然数据量不大,但主循环如果同时跑PID调节、Modbus协议、按键显示,轮询等待3字节串口数据会严重影响实时性。合理做法是用UART接收DMA加空闲中断:DMA把数据直接搬进缓冲区,UART空闲中断表示一帧接收结束。
STM32F091RC的UART支持DMA,配置好之后,主循环只负责判断帧接收完成标志。一次温度读取的完整流程是:DRDY触发标志,主循环发RDATA命令,DMA等待3字节,最终解码。实测下来主循环单次温度采集耗时不到几毫秒,即使把滤波器处理放进去也完全不影响其他任务。多片ADS122U04挂在同一条UART线上时,地址帧和广播帧的命令设计需要额外加状态机,这一块建议先把单片的时序摸透再扩展,否则排查起来会非常头疼。
4. 从24位原始码到温度值:换算、校准与误差账
4.1 RTD写入部分芯片直接按公式算,别在M0上跑浮点方程组
ADS122U04返回的24位码值要先转成电压。以差分输入、双极性输出为例,码值转电压公式大致是:
Vdiff = (raw / 2^23) * VREF其中VREF是2.048V,raw是符号扩展后的24位补码。注意如果配置成单极性模式,分母要换成2^23以外的对应值,这个细节在不同配置里容易搞错,建议仔细看数据手册的编码表。
得到差分电压后,除以IDAC电流就得到RTD电阻。PT100的标准电阻温度关系在0~850℃范围用IEC60751的多项式描述:
R(t) = R0 * (1 + A*t + B*t^2) A = 3.9083e-3 B = -5.775e-7但是在Cortex-M0上跑浮点开方解一元二次方程并不划算,我实际项目里用的是查表加线性插值。0~200℃范围内按1℃步长生成201个电阻值,放在const数组里,运行时先二分查找再线性插值,温度分辨率轻松到0.01℃,Flash消耗才几百字节。HVAC控制环完全够用,代码也更可读。
4.2 热电偶的线性化处理和冷端补偿计算
热电偶的电压-温度关系是高阶多项式,NIST给了ITS-90标准系数,直接在MCU上跑完整多项式不现实。我的做法是只保留-40℃到+200℃这一段的NIST表,步长1℃,总共约241个点,Flash占用不大。
换算流程分成三步:
- 读取ADC原始码值,转换成热电偶差分电压Vtc。
- 读取冷端温度Tc(来自ADS122U04内部温度传感器或外部TMP117),查表得到Tc对应的热电势E(Tc)。
- 将E(Tc)加上Vtc得到补偿后的总热电势,再查表反推实际测量端温度。
注意冷端补偿和测量端查表用的是同一张表,方向要一致:温度到电压查一次,电压到温度再查一次。内部温度传感器的输出精度会直接影响最终结果,如果对冷端补偿精度要求高,建议用独立数字温度传感器,并且在校准时把冷端传感器的偏移一起标掉。
4.3 两点校准:短路测失调,精密电阻测增益
高精度采集板出厂前必须做两点校准,否则每一片板子的PGA失调和增益误差都不同,贴得再好的器件也变不成一致性。校准流程不复杂,但方法要对:
- 失调校准:把ADC输入端短接(短的粗铜线直接搭在接线端子上),读码值平均64次,记录为Offset码。注意不是通过PCB走线或长导线短接,否则引入的噪声和热电势会把校准搞砸。
- 增益校准:用0.05%精度的电阻箱替换RTD,分别设100Ω和150Ω两个标准值,读码算增益系数,或者直接配置一个标准电压源给热电偶通道灌入10mV信号做增益标定。
校准后把Offset和Gain系数存到EEPROM或STM32F091RC的Flash里,上电时加载。增益64的高增益场景下,PGA失调往往比分辨率更影响小信号精度,所以增益校准对热电偶通道尤其重要。
4.4 误差账要系统算,不能只盯着ADC分辨率
很多初学者把24位ADC当成了无敌信号链,其实温度测量系统的长期精度受限于一堆小误差的叠加。我一般按误差来源排队:
| 误差源 | 典型量级 | 应对方式 |
|---|---|---|
| PT100传感器自身离散 | 0.1~0.3℃ | 选A级或1/3DIN级探头 |
| IDAC电流温漂 | 0.05~0.2℃ | 选低温漂版本,做增益校准 |
| 内部参考温漂 | 0.1℃量级 | 高要求时外部精密基准 |
| PGA失调和温漂 | 小信号时明显 | 两点校准覆盖 |
| 线缆残余线阻 | 三线制下约0.01~0.05℃ | 保证IDAC匹配 |
| PCB漏电、焊接残留 | 高阻时明显 | 清洗板面、加护环 |
真正限制长期精度的是参考源和电流源的温漂,而不是ADC位数。ADS122U04的24位精度只是在给信号链留出足够大的余量,让你有机会把上述误差逐项校准掉。如果板子用在-10~60℃的室外HVAC环境,一个30ppm/℃的基准漂移在50℃温差下就是0.15%的误差,反映到PT100在100℃时可能达到0.4℃,这一项就吃掉大部分指标预算。所以选器件时别只看分辨率,要连温度范围一起规划。
5. 实测中的故障排查与几个让我差点换方案的问题
5.1 上电第一帧总是乱码:CLK配置背锅,量波形才定位到
第一版样机出来,最明显的毛病是每次复位后第一次读温度总是错误值,程序里加个重试机制又好了,这种偶发问题最难受。排查链路是先看串口工具上的数据,发现错误帧的字节间隔都不对,说明波特率不对;再看ADS122U04引脚上的CLK波形,发现MCO输出的频率在上电后头几百毫秒并不在标称值上。原因很简单:ADC先上电,MCO后起振,ADC按错误时钟锁定了波特率,直到复位后才正常。
修复方法是改用有源晶振给CLK单独供时钟,在程序初始化ADC之前先延时200ms等晶振稳定。之后批量测试再没出现第一帧乱码。这个经验对使用外部时钟的ADC和通信芯片都适用:时钟稳定之前,不要急着给外围芯片发任何数据。
5.2 温度卡旧值、偶尔跳变:DRDY和读取时刻脱节了
有一版固件图省事,没有用DRDY中断,而是在主循环里发RDATA命令然后接收,想当然地认为每次读到的都是新数据。结果发现温度值会莫名其妙地一段时间不变,然后又跳一下。
用逻辑分析仪把DRDY波形和UART读取时刻对齐后发现:主循环发RDATA命令的时机有时落在两次DRDY之间,读到的数据是上一轮转换结果,而且连续读两次时第二次可能读到同一个值,表现为温度“卡住”。修复很直接:把DRDY接到EXTI下降沿,严格在数据就绪后再启动一次读取流程。改完后连续运行72小时,温度输出很干净,没有出现旧值现象。
5.3 高增益下码值贴满量程:不是过压,是共模电压超限
热电偶通道调试时,增益设为64,输入一个正常的室温热电偶,读到的码值却一直接近满量程。最开始怀疑信号过压或接反线,反复检查也没发现问题。后来查阅PGA输入级共模范围,才意识到热电偶负极直接接地后,输入共模跑到接近GND,PGA内部摆幅受限出现饱和。
解决方法是加偏置网络:用两个10kΩ电阻把AIN0和AIN1的共模拉到VREF/2附近,热电偶信号通过差分方式接到偏置点上方。改完再读码,电压输出回归正常。这个坑在低温漂、高增益的精密采集里很典型,排查时不要只盯着码值本身,要看PGA的工作条件是否满足。
5.4 风管里测温误差随风速变化:IDAC自热在作怪
有客户反映同一个传感器在静止空气和风管吹风条件下读数差了0.1℃以上。排查后发现IDAC电流设置偏大,RTD探头的自热在无风条件下积累得更明显,有风时热量被气流带走,探头温度更接近真实空气温度,于是出现随风速变化的读数偏差。
处理办法是降低IDAC电流,从1mA降到500µA,实测自热误差降了一半以上。如果目标是0.1℃以内,500µA甚至250µA更稳妥,代价是信号幅度变小但24位ADC余量大,完全够用。这个场景提醒我们:激励电流的选择不能只盯着ADC分辨率,还要考虑传感器探头本身的热模型。
5.5 变频器一启动温度就跳:地环路和大功率干扰
现场调试时最头疼的是变频器一开,ADC读数跳动从±0.02℃扩大到±0.3℃。查了很久发现两个问题叠加:传感器屏蔽层两端都接地,形成地环路,把变频器的共模干扰直接引入模拟地;同时板子上AGND和DGND没有做单点隔离,数字部分的开关噪声串进了ADC参考源。
修复方案是屏蔽层单端接地,去掉板子上一处不必要的铺地连接,在ADC输入端加共模电容和差分电容,参考引脚处再补一组去耦。改完再开变频器,温度跳动回复到正常范围。HVAC现场往往有变频风机、压缩机这类强干扰源,模拟前端的地回路问题比ADC芯片本身更容易成为瓶颈。
调试这类板子时我的习惯是准备一个外置USB转UART小板,把ADC的TXD信号单独引出来监听,再用示波器同时量DRDY和串口帧,基本能覆盖绝大多数通信类和时序类问题。另外在PCB上预留CLK、DRDY、TXD三个测试点,调试时不用拿镊子扒拉着芯片引脚量信号,能省下一大堆时间。ADS122U04IPWR和STM32F091RC这套组合整体稳定性不错,只要把时钟、地、DRDY时序这三件事处理到位,长期跑HVAC应用基本不需要再操心温度采集本身。