ADS122U04+STM32L4高精度温度采集系统:兼容热电偶/PT100/NTC

发布时间:2026/11/5 3:13:00
ADS122U04+STM32L4高精度温度采集系统:兼容热电偶/PT100/NTC

搞温度测量这活儿,说容易也容易,说麻烦也真麻烦。你要是拿个 NTC 热敏电阻加个普通 ADC 糊弄事,那 25 度能测到 26 度也算能用,可一旦放到工业现场或者 HVAC 系统里,客户要求的往往是“0.5 度以内”、“全年不漂”、“现场干扰随便来”,这就不是一颗内置 ADC 能扛得住的事了。我这次用的组合是 ADS122U04IPWR 这个带 UART 接口的 24 位 Δ-Σ ADC,配上 STM32L4A6RG 这颗低功耗 Cortex-M4 主控,把热电偶、PT100、NTC 三种最常见的工业温度传感器全给接了一遍。文章会把选型思路、硬件连接、寄存器配置、温度换算算法和调试踩坑都写清楚,适合正在做工业采集、HVAC 温控或者手持仪表的朋友参考。

1. 方案选型与整体架构

1.1 为什么选 ADS122U04IPWR 而不是普通 ADC

先说结论:普通 MCU 内置 ADC 在温度采集这种场景下,最大的痛点不是分辨率,而是“信号太小 + 噪声太大”这对组合。热电偶 K 型在 0 到 200 度范围内,灵敏度大概只有 40µV/°C 上下,一个 0.1 度的变化对应的电压才 4µV。你就算用 12 位 ADC 也很难分辨出这么小的信号,更别说现场还有电机、变频器、电源纹波这些干扰源。

ADS122U04IPWR 是 TI 家的 24 位 Δ-Σ ADC,内部自带可编程增益放大器(PGA,1 到 128 倍)、2.048V 基准源、温度传感器、以及两路可用于 RTD 激励的电流源。也就是说,它把一整套模拟前端都集成进一颗 TSSOP-16 封装里了,外部只需要加一些去耦电容和电阻就能干活。这里面比较关键的是“24 位分辨率 + PGA”,配合 64 倍或 128 倍增益,热电偶微伏级的信号可以被放大到 ADC 量程范围内,分辨率能到亚微伏级别,温度分辨率做到 0.01 度不是问题。

再说 UART 接口。ADS1220 系列用的是 SPI,这本身没问题,但工业现场经常面临传感器和主控距离比较远的情况。SPI 是同步通信,SCK、MISO、MOSI、CS 至少四根线,距离一长时钟就爱出问题。而 ADS122U04 这个型号里的 U 就代表 UART 输出,主控端只需要一根 TX 一根 RX 加一根 GND 就能通信,距离一长还可以直接挂 RS485 收发器,这个优势在 HVAC 这种传感器分散的场合非常实用。

顺便提醒一句:ADS122U04 和 ADS1220 虽然功能上很像,但引脚和通信协议不兼容,选型的时候别买错了。看丝印,带 U 的是 UART 接口版本。

1.2 STM32L4A6RG 在系统里扮演什么角色

STM32L4A6RG 是 STM32L4 系列里的高配型号,Cortex-M4F 内核,主频跑到 120MHz,Flash 有 1MB,SRAM 320KB。有人可能觉得这配置拿来读个 ADC 是不是太浪费了?但实际做产品你就知道,资源这东西“用不用得光是你的设计问题,够不够用是硬件选型问题”。温度采集只是前端,后面往往还要跑 Modbus RTU 协议、LCD 显示、按键处理、历史数据存储、甚至 LoRa 或以太网通信,这些堆下来,64KB Flash 的单片机就憋屈了,L4A6RG 这个空间基本属于“随便折腾”。

选 L4 系列还有一个重要原因:低功耗。HVAC 温控器很多时候是 24V 交流供电或者电池供电,系统待机电流要求做到微安级。STM32L4 的 STOP 模式加上 LPUART 唤醒、RTC 定时唤醒都是标配,配合 ADS122U04 的关断能力,可以把整个系统的平均功耗压到很低。后面我会单独讲低功耗软件设计。

另一个细节:STM32L4 的大多数 GPIO 是 5V 耐压的(标注 FT),也就是所谓的 5V tolerant。当你的 ADS122U04 用 5V 供电、输出高电平接近 5V 时,MCU 引脚可以直接承受,不会烧。不过我还是建议串一个 1kΩ 电阻做保护,原因后面说。

1.3 整体信号链

这套系统的信号链路是这样的:

传感器(热电偶/RTD/NTC)→ 模拟输入差分或单端 → ADS122U04 内部 PGA 放大 → 24 位 Δ-Σ 调制器 → 数字滤波器 → UART 输出 → STM32L4A6RG 的 USART 接收 → DMA 搬运 → 软件解析原始码 → 滤波算法 → 温度换算 → 显示/通信

这里有一个很重要的设计思路:ADC 只负责把模拟信号“忠实地”变成数字码,所有温度换算在 MCU 端做。为什么?因为温度传感器是非线性的,热电偶需要查表或者多项式拟合,RTD 需要 Callendar-Van Dusen 方程,NTC 需要 Steinhart-Hart 方程,这些计算放 MCU 端做才有灵活性。而且上位机调试时可以直接读原始 ADC 码,方便定位问题到底出在模拟前端还是算法上。

三种传感器的配置大致可以整理成下面这个表:

传感器类型接入方式ADS122U04 内部资源目标精度
K/J 型热电偶差分输入 AIN0/AIN1PGA 32~64 倍,内部温度传感器做冷端补偿±1℃ 全温度段
PT100 三线制差分输入 + 双电流源激励电流源 1mA,第二电流源匹配线阻±0.2℃(两点校准后)
NTC 10k单端输入,外部精密电阻分压PGA 1~4 倍,内部基准 2.048V±0.3℃(单点校准)

这样一块板子就能覆盖绝大多数工业温度和 HVAC 的测温需求,硬件不用改,改软件配置就行。这也是这套方案的长期价值:一个硬件平台,兼容多种传感器。

2. 硬件连接与模拟前端设计

2.1 电源和去耦:这是 24 位 ADC 能不能跑出 24 位性能的前提

很多人在 24 位 ADC 上得不到理想精度,八成是供电和地线没处理好。ADS122U04 的模拟供电 AVDD 支持 2.3V 到 5.5V,数字供电 DVDD 也可以独立接。我建议如果系统主电源是 3.3V,那就 AVDD 和 DVDD 都接 3.3V,简单省事;如果系统里有现成的 5V,ADC 用 5V 供电也行,但要注意此时 UART 高电平和 3.3V MCU 之间的电平匹配,后面会说。

去耦电容不能省,而且要按规格放:AVDD 对 AVSS 之间放一个 10µF 钽电容 + 100nF 陶瓷电容,DVDD 对 DGND 之间放 1µF + 100nF,基准电压 REFP 对 REFN 之间放 1µF 也不能少。这些电容要尽量靠近芯片引脚,走线短粗。我见过有人把去耦电容放着没焊,结果 ADC 读数跳动十几个 LSB,怎么调寄存器都没用,焊上电容瞬间就稳了。

还有一个关键细节:24 位 Δ-Σ ADC 的模拟地和数字地建议在 PCB 上单点连接,不要让数字信号的返回电流流进模拟信号的参考地回路。实际布线时,把 UART 的 TX/RX 走线远离 AIN0 到 AIN3 的模拟输入走线,两路之间留出地线隔离。

另外,内部基准电压源虽然省事,但它的温漂和噪声无论如何都不如一颗好的外部基准。如果你这个项目要跨 0 到 70 度环境,而且客户对精度要求是“全温度范围 ±0.5℃”,我强烈建议改用外部基准,比如 REF3325(2.5V)或者 REF5025(低温漂 2.5V)。此时 REFP 接基准输出,REFN 接模拟地,ADC 内部基准选择寄存器的对应位也要改掉。

2.2 UART 连接:电平匹配和长线传输

STM32L4 的 USART2 或 USART3 都可以接 ADS122U04。连线就三根:MCU 的 TX 接 ADC 的 UART_RX,MCU 的 RX 接 ADC 的 UART_TX,GND 必须连。这里最容易出的问题是共地,最高频的 UART 不稳定故障就是两个板子各用各的电源、GND 又没连牢,导致信号参考点不一致。

如果 ADC 用 5V 供电,它的 UART 输出高电平接近 5V。STM32L4 的 GPIO 虽然标了 5V 耐压,但那是“引脚承受得住不烧坏”,不代表你可以在 5V 电平下长期稳定工作。稳妥做法是在 RX 线上串一个 1kΩ 电阻,利用单片机引脚内部的保护二极管把电压钳在 VDD + 0.3V 左右,既保护了引脚,又不影响信号。MCU 的 TX 输出 3.3V 到 ADC 的 RX,ADC 的 RX 一般能识别 0.8×DVDD 以上的高电平,如果 DVDD 也是 5V,那 3.3V 就不够高,这时需要加一个简单的电平转换电路,比如用两个 2N7002 搭的 I2C 级别的双向电平转换,或者直接用 5V 供电的单片机一侧接口。

长线传输的情况下,建议加 RS485 收发器,比如 MAX485。ADC 的 UART 经过 RS485 转成差分信号传到 50 米外的 MCU,接收端再接回 UART。工业现场能用隔离就隔离,用光耦或者带隔离的 RS485 模块,把传感器端和主控端的电气彻底断开,这样地环路噪声就不会进入 ADC 了。

2.3 三种传感器接线实操模板

热电偶接线最简单:正极接 AIN0,负极接 AIN1,差分输入。但有两个容易忽略的坑:第一,热电偶的引线是合金材料,和铜导线连接的地方本身就是一个“寄生热电偶”,会额外产生一个热电势。所以连接端子要尽量靠近冷端温度传感器,并且保持两侧端子等温。第二,热电偶输出的是微伏级信号,PGA 增益如果设得不够大,量化噪声就把信号淹没了。K 型热电偶测 0 到 200 度,信号范围大约 0 到 8mV,我建议 PGA 设 64 倍,8mV × 64 = 512mV,接近满量程但不会溢出,分辨率最佳。

PT100 三线制接法要稍微动点脑筋。三根线的目的是消除引线电阻。工作在 1mA 激励电流下,PT100 在 0 度时电阻 100Ω,电压 100mV;在 100 度时约 138.5Ω,电压 138.5mV。假设每根引线电阻 5Ω(100 米 0.5mm² 铜线大概就是这量级),如果不做补偿,5Ω 线阻带来的误差相当于 5Ω/0.385Ω/°C ≈ 13 度,这误差绝对不能忍。三线制的思路是:一根线走激励电流,另一根线走电压检测,第三根线走回流。ADS122U04 有两个可编程电流源 IDAC1 和 IDAC2,把 IDAC1 接到 AIN0 引线端,IDAC2 接到同一点,两个电流源大小相等,这样在检测回路里,两条引线上的压降相互抵消,ADC 测到的电压就只剩 RTD 本身的压降了。

NTC 更简单,用外部精密分压电阻接在电源和 NTC 之间,ADC 测分压点电压。这里注意:分压电阻一定要选低温漂的,1% 精度的 0603 贴片电阻通常温漂 100ppm/°C,用在 NTC 分压上会直接变成零点几度的误差。建议至少用 0.1% 精度、25ppm/°C 以下的电阻,比如金属薄膜电阻。

3. ADS122U04 寄存器配置与 UART 通信实现

3.1 寄存器配置:目标功能怎么定,配置值怎么算

ADS122U04 的寄存器结构跟 ADS1220 系列同源,都是通过 UART 命令读写,配置的核心就四项:输入多路选择(MUX)、PGA 增益、数据速率、基准电压来源。每一类传感器对应一套配置。

我先说一个开发阶段的习惯动作:拿到芯片先读一遍所有寄存器默认值,和手册上的复位值对一下,万一 UART 波特率配错、连到了奇怪的状态,读出的值会明显不对,能省大量排查时间。

写寄存器配置时,最重要的是理解增益和数据速率的关系。数据速率越高,Δ-Σ ADC 内部数字滤波器用到的采样点越少,噪声越大。比如 ADS122U04 在 20SPS 下有效分辨率能到 24 位,而 2000SPS 下有效分辨率可能跌到 17、18 位。反过来,工业 50Hz 工频干扰会让读到的信号叠加明显的 50Hz 纹波。合理做法是:如果只需要慢速测量(比如 HVAC 室温,响应时间几秒就够了),数据速率选 20SPS 或 60SPS,既能抑制工频又能拿到最高分辨率;如果需要快速响应(比如电机绕组温度监测),可以选 330SPS 或更高,配合软件均值滤波。

常见配置我整理成下面这个表(具体寄存器位以你所拿到的芯片数据手册为准):

场景MUX 选择PGA 增益数据速率基准电流源
K 型热电偶AIN0-AIN1 差分6420 SPS内部 2.048V关闭
PT100 三线制AIN0-AIN1 差分860 SPS内部 2.048V1mA,IDAC1/IDAC2
NTC 10k 分压AIN2-AIN3 单端1~420~60 SPS内部 2.048V关闭

这里有一个容易搞混的点:PGA 增益的配置值和实际倍数不是“1 对应 1 倍”这么简单,寄存器里是二进制编码。比如增益 64 对应的二进制值是 110,要写进增益位段。我的习惯是写程序时先在注释里写清楚“目标增益”,再写“编码值”,免得过一个月回来看代码已经看不懂当初为什么填这么个数字。

配置下发的代码结构大概是:

// 伪代码,命令字以手册为准 uint8_t cfg[4]; // 热电偶模式:AIN0-AIN1 差分,PGA=64,20SPS,内部基准 cfg[0] = ADS122U04_REG0_MUX_DIFF_AIN0_AIN1 | ADS122U04_REG0_GAIN_64; cfg[1] = ADS122U04_REG1_DATARATE_20SPS | ADS122U04_REG1_CONTINUOUS; cfg[2] = ADS122U04_REG2_VREF_INTERNAL; cfg[3] = ADS122U04_REG3_IDAC_OFF | ADS122U04_REG3_TEMP_SENSOR_OFF; ads122u04_write_regs(cfg, 4);

每一行配置都对应一个明确的意图:测热电偶不能用内部电流源,因为电流源会在热电偶内阻上产生额外的压降,直接污染微伏级信号;测 RTD 必须开电流源,因为需要恒流激励;测 NTC 用外部电阻分压,ADC 内部电流源反而多余。这些“为什么”想通了,寄存器配置就不是背数据手册,而是水到渠成的事。

3.2 命令帧与数据帧格式

ADS122U04 的 UART 通信本质是主从式的,MCU 永远是主机,ADC 是被动响应。MCU 发命令,ADC 执行后返回应答或者转换数据。常用命令包括复位、读寄存器、写寄存器、启动转换、读取结果。

这里要特别说一下“启动转换”和“读取结果”的关系。如果配置成连续转换模式,ADC 会一直转换,数据准备好了会自动发到 UART 上,MCU 只需要接收;如果配置成单次转换模式,MCU 每次要发一个启动命令,然后等转换完成再读结果。单次模式的好处是灵活,适合电池供电的低功耗场景——平时 MCU 睡觉,醒来发一个命令,等几百毫秒,收完数据继续睡。连续模式的好处是省事,适合实时监测。

数据格式可以是二进制(3 字节)也可以是 ASCII(6 个十六进制字符),我推荐二进制。转换结果是 24 位二进制补码,正数范围 0x000000 到 0x7FFFFF,负数从 0x800000 到 0xFFFFFF。MCU 端接收时注意做符号扩展,不然负数会变成很大的正数,后面换算温度就全乱了。

我给出主控端一个简单的 UART 接收和解析代码:

uint8_t rbuf[3]; int32_t raw_code; // 等待并读取 3 字节转换结果 HAL_UART_Receive(&huart2, rbuf, 3, 100); // 合成 24 位数据 raw_code = ((int32_t)rbuf[0] << 16) | ((uint16_t)rbuf[1] << 8) | rbuf[2]; // 24 位补码符号扩展 if (raw_code & 0x800000) { raw_code |= 0xFF000000; }

符号扩展这一行看着简单,但真有人在这个地方翻车。如果不做扩展,比如 ADC 量程负半段读数 0x800000(代表 -4.096V 对应满量程),直接当正数算就会得出一个完全错误的温度值。

3.3 从原始码换算电压和温度

收到原始码之后,第一步是换算成电压。公式是:

电压 = 原始码 × 基准电压 / (PGA 增益 × 2^23)

举例:假设基准 2.048V,PGA 64,原始码是 0x123456(十进制 1193046),那么电压:

1193046 × 2.048 / (64 × 8388608) ≈ 0.00455V = 4.55mV

这个 4.55mV 如果是 K 型热电偶,查表可知大约对应 110 度左右。

注意公式里的分母是 2^23 而不是 2^24。因为 24 位补码表示的范围是对称的 ±2^23,所以满量程对应基准电压,而不是两倍基准。这个细节很多人会算错,写代码时小心点。

NTC 的换算则是先算分压电阻,再套 Steinhart-Hart 方程:

Rntc = Rref × (Vref_actual - Vout) / Vout

也就是分压点电压 Vout 和 Rref 已知,反推 NTC 当前电阻,然后把 Rntc 代入方程求温度。这里我强烈建议:电压计算全程用浮点,别用整数省那点算力,STM32L4 有 FPU,浮点运算一个周期就完成了,精度远比那点效率重要。

4. 温度算法:热电偶、RTD、NTC 的换算与校准

4.1 热电偶冷端补偿:别以为测到电压就能查表

热电偶测的是热端和冷端(参考端)之间的温差电动势。你 ADC 读到的电压本质上是“热端温度对应的电动势减去冷端温度对应的电动势”。如果冷端是 0 度,那直接查表就行;但实际冷端就在接线端子那里,温度随环境变,所以必须做冷端补偿。

冷端补偿的标准做法:先用一个温度传感器测出冷端的实际温度(比如用 ADS122U04 内置温度传感器,或者外接的 TMP117 ),然后用这个温度反查热电偶分度表,得到“冷端在这个温度下应该产生的电动势”,把这个电动势加到 ADC 测量值上,得到等效于冷端为 0 度时的电动势,再查表或者用多项式拟合得到热端温度。

ADS122U04 内置温度传感器很方便,因为它和模拟输入在同一芯片上,如果你把冷端连接器紧贴着 ADC 芯片,芯片温度基本就代表冷端温度。这样省一颗外部温度传感器,电路设计简化不少。但代价是精度有限,因为 ADC 芯片自身有功耗发热,会让芯片温度比环境高 1~2 度,造成冷端补偿误差。如果你的产品对成本敏感且要求不是超高,内置传感器够用;如果你要挑战 ±0.5℃ 全温精度,建议用一颗外部NTC 贴到端子上。

查表方式有讲究。最准的是 NIST ITS-90 多项式,但那个公式最高到 9 次方,算起来比较烦。工程上常见做法是把分度表做成 lookup table,每 10 度存一个值,中间用线性插值。K 型热电偶在 0 到 300 度之间的近似灵敏度约 40µV/°C,做 10 度间隔的线性插值,插值误差一般在 0.1 度以内,完全够用。我在项目里甚至用过更简化的办法:只补偿一个平均灵敏度 40.44µV/°C × 冷端温度,10 度冷端误差约 2µV,对应 0.05 度温度误差,绝大多数场景都能接受。

注意,PID 参数、报警阈值这些逻辑跑出来不对,先怀疑不是代码 bug 而是冷端补偿没做好,这是热电偶测温最典型的隐性误差源。

4.2 RTD 线性化与三线制推导

PT100 在 0 到 100 度之间电阻从 100Ω 增加到约 138.5Ω,基本线性,平均灵敏度 0.385Ω/°C。但你要拿到高精度,不能只按线性算。标准做法是用 IEC 60751 标准的 Callendar-Van Dusen 方程:

R(T) = R0 × (1 + A×T + B×T² + C×(T-100)×T³),其中 0 度以上 C 项为 0

A = 3.9083×10⁻³,B = -5.775×10⁻⁷,C = -4.183×10⁻¹²(仅 0 度以下使用)

MCU 端实现这个方程很简单,直接带入 T 的初值迭代几次也行,或者反过来用牛顿法从电阻求解温度。实际上更工程化的做法是查表加插值,PT100 的电阻表到处都是,存一个 -50 到 200 度、每 1 度的电阻值表格,也就几百字节,查表加线性插值误差远小于 0.1 度,完全够用。

推导三线制为什么能消线阻:假设两根引线电阻都是 Rw。IDAC1 输出 1mA 经过引线 Rw 到 RTD,RTD 电压降为 1mA×RTD,再经过第二根引线返回。ADC 差分输入端 AIN0 和 AIN1 之间看到的电压包括:RTD 上的压降、上下两段引线上的压降。在理想匹配情况下,上下两段引线压降都是 1mA×Rw,差分输入两端对称,实际输入电压就等于 RTD 的压降,引线电阻被抵消。这就是为什么三线制要有“第二个电流源”从同一点注入电流——它制造了一个对称的电流通路。

实际调试中检流电阻和引线不可能完全一致,剩下的残余误差通常只有零点几欧,对应零点几度,可以接受。如果你要追求极致,就用四线制,激励和检测完全分开,线阻理论误差为零。

4.3 NTC 热敏电阻的 Steinhart-Hart 方程

NTC 的电阻和温度关系是高度非线性的,常见近似公式是 B 值公式,但那东西在宽温度范围误差较大。想精度高,用 Steinhart-Hart 方程:

1/T = A + B×ln(R) + C×(ln(R))³

这里 T 是开尔文温度,R 是当前电阻。A、B、C 三个系数在 25 度附近由传感器厂家提供,或者用三点法自己求解。常见 10k 3950 的 NTC,参数大致在 A≈1.129×10⁻³、B≈2.341×10⁻⁴、C≈8.767×10⁻⁸ 附近,但每颗传感器实际参数都不同,最好从你的传感器数据手册里抄,或者自己标定。

我这里强烈建议用浮点计算 ln(R),STM32L4 的 FPU 虽有但数学库的 ln 函数还是要消耗少量时间,不过 20SPS 的采样率下,每秒钟算 20 次,CPU 占用几乎可以不考虑。算完之后把开尔文温度减去 273.15 得到摄氏度,输出前再加一个线性化的斜率校正——NTC 在 25 度附近灵敏度约 -4.4%/°C,所以哪怕 ADC 电压误差 1mV,温度误差也可能达到 0.3 度,实际使用时最好做一次单点校准来校正分压电阻误差。

4.4 校准流程:不校准的 24 位 ADC 是浪费

24 位 ADC 的线性度很好,但系统级的精度受三个东西影响:基准电压误差、分压电阻误差、传感器本身的个体差异。这三样都不是靠提高 ADC 位数能解决的,必须做校准。

我的推荐流程是:

先把系统打到一个已知温度点,最简单是冰水混合物(0℃)。把传感器插入冰水,稳定 5 分钟,读取 ADC 码,和理论值比较,记下偏差。再把系统放到第二个已知温度点,通常用精密恒温槽或者沸水(100℃,但海拔不同沸点也不同,最好用标准温度计现场标定)。两个点可以求出斜率和截距,校准公式就是:

T_corrected = T_raw × slope + offset

把这两个参数存到 STM32L4 的 Flash 里,掉电不丢。每次产品出厂做一次两点校准,配合低漂移基准,系统精度做到 ±0.2℃(RTD)和 ±0.5℃(热电偶)是没问题的。

如果现场不具备两点条件,至少做一个单点偏移校准:用精密参考温度计和系统放一起,记下偏差,补偿掉系统偏移。这个操作在 HVAC 现场调试时很常用——把参考温度计往管道里一插,板子上调个偏移参数,5 分钟搞定。

5. STM32L4A6RG 软件架构与低功耗设计

5.1 UART 接收:DMA + 空闲中断的组合

读取 ADS122U04 数据,看起来就是接收 3 个字节,但如果用阻塞式 HAL_UART_Receive,在等待数据时 CPU 被卡死,系统啥也干不了。正确做法是用 DMA 接收 + 空闲中断(UART IDLE)来检测一帧数据的结束。

基本思路:配置 USART2 的 DMA 接收,环形缓冲,任意时刻数据来了 DMA 自动搬到内存。IDLE 中断触发时,当前收到的字节数就是这一帧的长度。这种方式对连续转换模式下的不定长数据流特别好用:ADC 持续往 UART 发 3 字节一帧的数据,DMA 自动收,不占用 CPU,MCU 的 while 循环可以专心干别的活。

DMA 初始化时注意:ADC 的 UART 空闲时是空闲电平,不会触发 IDLE 中断,只有真正收到数据、然后线上恢复空闲时 IDLE 才置位。所以 IDLE 中断代表“一帧传完了”,正是我们想要的边界。

帧长度校验一定要做:收到 3 字节才是一个完整帧,多收少收都要清掉缓冲重新同步。工业现场电磁干扰可能把一帧数据冲散,接收状态机要能在错帧后自动恢复。我的做法是开一个环形缓冲区,每收到一帧就解析一次帧头和长度,不匹配就继续找,这样几帧数据被干扰也不会导致整个系统死锁。

5.2 数字滤波:工频抑制和时间常数

Δ-Σ ADC 内置数字滤波器可以抑制工频,方法就是选择跟 50Hz 成整倍数关系的数据速率。比如 20SPS、50SPS、60SPS 这些速率下,数字滤波器的凹陷点正好落在 50Hz 或 60Hz 上。这是硬件就帮我们干完的事,我们要做的就是别去选那些和工频没有整倍数关系的速率,比如 330SPS 就很容易引入工频纹波。

但选了低数据速率不代表读数绝对平稳。工业现场还有变频器的高频干扰、接触器的浪涌,这些汇总到输入端可能超出 ADC 的差分输入范围,导致转换结果异常。我的习惯是在 ADC 模拟输入端加一个简单的 RC 低通:每路输入串联 1kΩ 电阻,对地接 100nF 电容,截止频率约 1.6kHz,不会影响温度信号的带宽,又能有效抑制高频噪声。这个 RC 在 PCB 上面积也不大,强烈建议预留。

软件上,对温度这类“慢变量”再做一阶低通滤波很有效:

filtered = filtered + alpha × (new_value - filtered)

alpha 的取值取决于你想要的系统响应时间。比如采样率 20SPS,想要系统 2 秒的时间常数,alpha ≈ 1/(2×20) = 0.025。这样既能平滑波动,又不会让系统响应变得迟钝。

5.3 低功耗:让整个系统睡眠

HVAC 温控器如果常年在 220V 或者 24V 供电下,功耗没那么敏感。但如果是电池供电的无线温度传感器,低功耗就是刚需了。这里可以用 STM32L4 的 STOP 2 模式加 RTC 定时唤醒。

设计思路是:系统每 10 秒醒来一次,给 ADC 供电(如果 ADC 有独立供电电路,可以用 MOS 管开关),等待 20ms 稳定,发启动转换命令,等转换完成收数据,算温度,然后掉 ADC 电源,进入 STOP 2。平均功耗可以做到几十微安到几百微安,两节 AA 电池用一年以上没问题。

注意 ADC 上电后不能立刻读数。基准和内部模拟电路需要建立时间,去耦电容充电、基准稳定都需要时间,经验值至少等待 5ms 后再发第一个转换命令,转换完成后再等数据稳定 1 到 2 个数据周期再采数。这个等待时间看着费电,但远比读回来的数据无效要省。

还有一个小技巧:如果 ADC 有独立的 RESET 引脚,低功耗模式下把 RESET 拉低可以让 ADC 进入最低功耗状态。如果没有,可以通过命令把 ADC 内部的所有电路都关成默认禁用状态,再让 UART 总线空闲。更彻底的办法是在 ADC 的电源引脚加一颗负载开关,由 MCU 的 GPIO 控制,不给 ADC 供电就是真正的零功耗。

6. 常见问题与排查实录

6.1 读回的全是 0 或者满量程

现象:ADC 返回的原始码要么全是 0x000000,要么全是 0x7FFFFF 附近。排查顺序:

第一步,确认 MUX 配置没把输入选成不用通道。有人接的是 AIN0-AIN1,寄存器里却选成了 AIN2-AIN3,当然读不到信号。第二步,确认传感器接线极性,热电偶负极接反就是负满量程。第三步,用万用表量传感器两端电压,如果信号本身只有几毫伏,而 PGA 设成 1 倍,ADC 分辨率不足以分辨这么多信号也正常,先把增益提上去。

如果以上都正常,检查基准电压对不对。内部基准未使能时会读到一个很奇怪的值。此时读寄存器确认配置是否生效,命令写成错的地址,静默失效是最难查的。

6.2 UART 通信时好时坏

这个我遇到太多了。首先查波特率。ADS122U04 的波特率通常由硬件引脚 BM 的电平状态决定,而不是全靠软件配。上电时引脚状态不对,实际波特率和你代码里配置的 115200 就不一致,表现为第一帧能收到偶尔一两个字节,后面全是乱码。把 BM 引脚按手册要求设定,用示波器量 ADC TX 脚的实际波特率,和 MCU 配置比对。

第二查共地。两个板子各用各的电源,UART 的 GND 不连,信号完全没有参考点,现象就是“偶尔通一下,多数时候乱码”。UART 必须共地,这没有商量的余地。

第三查线路电容。高速率 115200 下,如果线比较长而且没有屏蔽,上升沿会变缓。示波器看到弯曲的边沿就说明线电容太大,要么降波特率到 9600,要么换带屏蔽的线或者加 RS485 收发器。

6.3 温度读数跳变 ±0.5℃

排除传感器本身噪声后,先看是不是数据速率和工频没有匹配好。如果你选了 330SPS 而且现场有变频器,那读数波动是必然的。降到 60SPS 或者 20SPS,波形立刻平稳。这是最省事也最有效的手段。

然后看模拟输入前级滤波。NTC 分压网络阻抗较高,如果引脚没有对地电容,外界电场会在高阻节点上感应出噪声电压。加一个 100nF 对地电容通常能解决,但注意加的电容不能太大,不然温度响应会变慢——RC 时间常数 Rntc×C 如果到几十毫秒,20SPS 采样下读数会滞后明显。

还有一种容易被忽略的情况:ADC 断断续续的“漂移”其实是来自内部基准电压的温漂。温度变化时基准变了,ADC 读到的码也会变。排查方法:用外部基准换掉内部基准,如果漂移消失,就是内部基准的问题。此时就要考虑换外部基准或者做温度补偿。

6.4 RTD 读数系统性偏大

这是三线制接线的典型问题。故障现象是:读数在 0 度时偏大 5 到 10 度,而且偏差随温度变化不大。原因多半是电流源配置只有一个生效,另一个电流源没有开或者接错点,导致引线电阻没有抵消成功。

用万用表测 RTD 两端实际压降,再测 AIN0 和 AIN1 之间的 ADC 输入电压,两者做对比,就能看出来到底引线压降有没有被抵消。三线制的核心是两个电流源要“输出相同电流到同一点”,少一根线、少一个电流源都是白搭。

6.5 校准后的设备过几天又漂了

这种“软漂移”首先要怀疑机械应力:RTD 或热电偶的端子松动、线材受热应力产生额外热电势、PCB 焊点冷热疲劳。其次怀疑传感器是否受潮,湿度变化会让 NTC 和 RTD 的绝缘电阻下降,导致读数漂移。建议产品出厂前做一次高低温循环老化,把应力释放掉再校准,现场漂移会小很多。

7. 实测数据与调试心得

7.1 三组传感器的实测表现

用油浴标定炉做了一轮对比测试,参考温度计是二等标准铂电阻。K 型热电偶 + PGA64 + 20SPS + 软件线性化,0 到 200 度范围内系统输出和参考最大偏差约 0.8 度,冷端补偿用的是 ADS122U04 内部温度传感器,如果能换成贴在端子上的高精度 NTC,偏差还能压到 0.5 度以内。

PT100 三线制 + 1mA 双电流源 + 60SPS + 查表插值,两点校准之后 0 到 150 度范围内偏差约 ±0.1 度,这个精度拿来当现场校准参考问题不大。NTC 10k + 外部 0.1% 分压电阻,单点校准后在 10 到 40 度这个 HVAC 常用区间偏差约 ±0.3 度,用在温控器上完全满足。

数据速率的权衡在测试里感受很明显:330SPS 下读数跳动 ±1 度,降到 20SPS 后同一个传感器跳动 ±0.05 度。这就是 Δ-Σ ADC 的特性——它用时间换分辨率,你要快就得不到那么高的稳定度。

7.2 调试过程中的几个实战心得

第一,开发前期把 UART 收到的原始字节全部打印成 hex 到串口调试助手。不要急着写温度换算,先把“配置是否正确下发”——通过读寄存器确认,“转换数据是否正常”——通过看原始码范围确认,“电压值是否符合传感器理论输出”——通过手动换算确认。这三层都通了,再上温度算法,不然出了问题你根本分不清是通信错了还是算法错了。

第二,原始码、电压、温度三个量同时打印。现场调试时,客户说“读数不对”,你先看原始码是否变化、电压是否合理、再判断是传感器问题还是算法问题。有些故障是热电偶断线,原始码直接跳满量程,你要是只看最终温度,可能陷入“温度显示异常”的排查陷阱。

第三,PCB 上给 ADC 留一个跳线帽,可以把 AIN0 直接短接到 AVDD 或 GND,做自检用。上电自检时读一下这个已知电平点的 ADC 码,如果和理论值差太多,说明模拟前端已经出问题了,系统可以主动报故障而不是默默输出错误温度。这个小设计成本为零,但能省很多售后排查时间。

7.3 后续可以扩展的方向

这套平台其实还能继续做下去。ADS122U04 是 4 通道的,目前只接了一路传感器,还有三路空闲,可以扩展成多路温度采集器。STM32L4A6RG 的资源也没用满,加一个 Modbus RTU 从站协议,让上位机通过 RS485 远程读取所有通道温度,就是一个完整的工业温度采集模块。

如果要做无线版本,L4A6RG 的 LPUART 可以直接挂 LoRa 或 NB-IoT 模块,传感器端和主控端之间依然是 UART 连接,整个软件架构不用大改。我个人经验是,模板化的信号链设计和分层软件架构是嵌入式产品做系列化的核心——硬件改不改看需求,但 ADC + 主控 + UART 这条骨架可以一直沿用下去。