STM32 接 ADS1220 的测温项目,我前前后后调了两周。一开始我以为只是把 SPI 读出来、查个表就能得到温度,结果被参考电压、PGA 增益、冷端补偿、寄存器配置挨个教育了一遍。这篇文章把完整的思路和可用的源码写出来,从电压基准到寄存器配置一步步来,适合正在用 STM32 做高精度测温、热电偶采集或者 ADS1220 驱动移植的朋友参考。文末附的驱动代码是实际跑过的,可以直接抄。
1. 测温系统不能只盯着 ADC:参考电压才是精度的天花板
很多人拿到 ADS1220 第一反应是赶紧写 SPI 驱动,看数据手册里那个 24 位分辨率,以为只要代码没问题就能测到微伏级电压。但高精度测温系统里,ADC 只是链条上的一环,真正决定系统上限的,往往是参考电压。
1.1 为什么 ADS1220 默认内部基准不够用
ADS1220 内部有一个 2.048V 基准,数据手册上写着温漂典型值,但实际用的时候会发现:芯片温度变化,基准电压也在变,而热电偶输出信号本来就只有几十微伏每摄氏度。这个矛盾在生活中可以这么理解:你想用一把尺子量一根头发丝,尺子本身却会因为温度热胀冷缩,那你量出来的数据当然不可信。
拿 K 型热电偶来说,灵敏度大约是 41uV/°C。如果参考电压漂移了 10ppm/°C,2.048V 基准就是 20uV 左右的漂移,折合到温度上大概 0.5°C/°C。也就是说环境温度变化 10°C,系统误差能到 5°C,这还没算 ADC 自身噪声和 PGA 漂移。测温项目如果连 0.5°C 的要求都做不到,内部基准勉强能看;如果目标是 0.1°C 级别,必须上外部低漂移基准。
1.2 高精度测温的完整信号链
一套靠谱的 ADS1220 测温系统,信号链路是分段的:
- 传感器:热电偶或 PT100,把温度变成电压或电阻。
- 输入滤波:RC 低通,滤掉空间耦合进来的射频干扰。
- 前端 PGA:把微伏级信号放大到 ADC 满量程附近,尽量吃满分辨率。
- ADC 采样:ADS1220 把模拟量变成 24 位数字码。
- 参考电压:给 ADC 一个「绝对尺子」,决定每个码代表的电压大小。
- MCU 运算:读码、查表、线性化、冷端补偿,最后输出温度。
我这次的项目用的是 K 型热电偶,量程 0 到 800°C,外置 REF5025 做参考电压源。K 型热电偶在 800°C 时热电动势约 33.3mV,选择 PGA 增益 16,配合 2.5V 参考电压,满量程 156.25mV,既有足够的信号幅度,也不会超量程。这套链路是后续所有寄存器配置的依据。
2. 电压基准选型与外围电路:从 REF5025 说起
标题里特别提到「从电压基准开始」,因为这一步很多人会随手放一个 AMS1117 稳压模块给 ADC 当参考,结果读出来的数据惨不忍睹。
2.1 REF5025 关键参数解读
REF5025 是一个 2.5V 输出电压基准,我选它主要看中几个参数:
- 初始精度:典型值 0.05%,也就是说输出电压在 2.49875V 到 2.50125V 之间,软件校准可以把它校准掉。
- 温漂:3ppm/°C,比 ADC 内部基准低一个数量级。
- 噪声:低噪声设计,对 24 位 ADC 来说很关键,基准上的噪声会直接调制到采样结果里。
实际搭建时,REF5025 的输入接 5V VDD,输出接 2.5V,输出端并联 10uF 钽电容和 100nF 陶瓷电容。10uF 负责低频稳定,100nF 负责高频去耦,两者都不能省。另外基准输出和 ADS1220 的 REFP0 引脚之间走线尽量短,中间不要过长,否则容易拾取噪声。
2.2 基准电路与去耦布局
ADS1220 的参考电压输入是差分结构,也就是 REFP0 和 REFN0。REF5025 的输出接 REFP0,REFN0 接模拟地 AGND。千万别把 REFN0 悬空或接到数字地,否则共模噪声会直接进去。
去耦方面我踩过一个坑:刚开始基准芯片旁只有 100nF,读数跳动比较大。后来加了 10uF 后稳定很多。看数据手册,基准负载需要一个相对大的储能电容来吸收 ADC 内部采样电容的瞬态充电电流,100nF 太小,瞬间电压跌落明显。
布局建议:
- REF5025 尽量靠近 ADS1220 的 REFP0 引脚。
- 模拟地、数字地在 PCB 底层单点汇合,不要让地线形成环路。
- 热电偶信号线走差分对,左右两侧包地。
- AGND 和 REFN0 之间不要有高阻抗走线。
2.3 热电偶接法和 PGA 增益选择
K 型热电偶的输出是差分信号,正极接 AIN0,负极接 AIN1,正好对应 ADS1220 的差分输入。有的接法会把负极接到 GND,这样会引入共模误差,不推荐。
PGA 增益怎么选?我的原则是让最大目标信号占满 ADC 满量程的 1/2 到 2/3,留出余量。800°C 时热电偶输出 33.3mV,2.5V 参考电压下:
- PGA=16,满量程 156.25mV,信号占 21.3%。
- PGA=32,满量程 78.125mV,信号占 42.6%。
- PGA=64,满量程 39.06mV,信号占 85.3%,但余量不足,超过约 870°C 会削顶。
我最终选了 PGA=16。虽然信号占比不是最高,但考虑到热电偶冷端补偿可能带来额外的电压偏移,而且温度测量更看重长期稳定而不是满量程利用率,PGA 太高反而会把输入失调也放大。
3. ADS1220 寄存器配置:每个 bit 都要知道为什么
ADS1220 有 4 个 8 位寄存器,从 REG0 到 REG3。寄存器配置是整个项目的灵魂,直接决定了输入通道、增益、采样率和参考电压。下面按我实际使用的配置逐位拆解,寄存器完整定义以 TI 官方数据手册为准,这里讲项目相关的关键位。
3.1 REG0:输入通道与 PGA 增益
REG0 的 bit[7:4] 是 MUX[3:0],选择输入通道。我的接法是热电偶正极接 AIN0、负极接 AIN1,所以 MUX 配成 0000,对应 AIN0-AIN1 差分输入。注意这里不是随便选的,差分输入能把热电偶线上的共模干扰抵消掉。
bit3 是 PGA_BYPASS,决定内部 PGA 是否旁路。如果置 1,PGA 被旁路,增益固定为 1;如果为 0,PGA 生效,增益由 bit[2:0] 控制。因为我们需要放大热电偶信号,所以这一位写 0。
bit[2:0] 是 PGA_GAIN[2:0]:
| PGA_GAIN[2:0] | 增益 |
|---|---|
| 000 | 1 |
| 001 | 2 |
| 010 | 4 |
| 011 | 8 |
| 100 | 16 |
| 101 | 32 |
| 110 | 64 |
| 111 | 128 |
前面已经算过,选增益 16,所以 REG0 的二进制是 00000100,十六进制就是 0x04。
3.2 REG1:采样率与转换模式
REG1 的 bit[7:4] 是 DR[3:0],决定数据速率。ADS1220 的数据速率从 20SPS 到 4000SPS 可选。20SPS 时数字滤波器对 50/60Hz 工频干扰有很强的抑制能力,工业现场电源干扰严重,选这个最稳妥。
bit3 是 MODE,写 0 表示连续转换模式,写 1 表示单次转换模式。连续转换模式简单,程序里等 DRDY 引脚拉低再去读数据就行。单次转换模式更省电,适合电池设备。我这里用连续转换,REG1 的 bit3 写 0。
bit2 是 CM,控制转换模式相关行为,保持默认 0。
bit1 是 TS,控制内部温度传感器。测温过程中我们是测热电偶,所以 TS=0。后面读冷端温度的时候再把这一位置 1,读完之后改回 0。
bit0 是 BCS,控制烧断检测电流源,默认 0。如果接线容易断,可以打开这个功能,但烧断检测电流会给测量结果引入额外的失调,我在实际项目中关闭。
所以 REG1 配置为 0x00:20SPS、连续转换、内部温度传感器关闭、烧断检测关闭。
3.3 REG3:外部参考电压的切换
REG3 的 bit[7:6] 是 VREF[1:0],这个位最关键:
- 00:内部 2.048V 基准。
- 01:外部基准,从 REFP0/REFN0 输入。
- 10:AVDD-AVSS。
- 11:保留。
因为我们外接了 REF5025,所以必须把 VREF[1:0] 配成 01,也就是高两位为 01,低六位保持 0,REG3 就是 0x40。这一步漏掉的话,ADS1220 还是会用内部基准,外部的 REF5025 完全不起作用,读数变化也会很怪。
REG2 因为没用 IDAC 电流源,配置为 0x00。
3.4 寄存器配置表与初始化顺序
最终配置表如下:
| 寄存器 | 值 | 说明 |
|---|---|---|
| REG0 | 0x04 | AIN0-AIN1 差分,PGA=16 |
| REG1 | 0x00 | 20SPS,连续转换,TS 关闭 |
| REG2 | 0x00 | IDAC 电流源关闭 |
| REG3 | 0x40 | 外部基准 REFP0/REFN0 |
初始化顺序也有讲究:上电后先等 10ms,让电源和基准稳定,再依次写 REG0、REG1、REG2、REG3。写寄存器之间稍微加一小段延时,不要连续写得太快。写完后最好把寄存器回读一遍,确认配置真的写进去了,这一步在调试时能省很多事。
4. 温度计算:热电偶线性化和冷端补偿
寄存器配置对了,ADC 能读到码值,但码值本身不是温度。从 24 位码到最终温度,中间还有两个关键步骤:电压换算和冷端补偿。
4.1 为什么要处理冷端
热电偶测量的是热端和冷端之间的温差,而不是绝对温度。K 型热电偶输出热电动势 E = E(热端) - E(冷端)。如果冷端在 25°C,热端在 100°C,输出对应的其实是 75°C 的热电动势,直接查表会偏低约 1mV,换算成温度误差能到 25°C 左右,根本不能用。
所以必须测量冷端温度,补偿关系是:E(热端) = E(测量值) + E(冷端)。E(冷端) 可以通过冷端温度查表得到,然后把补偿后的总电动势查表转换成热端温度。
4.2 查表内插法实现 K 型热电偶线性化
K 型热电偶的电压-温度关系是非线性的,工程上最稳定的做法是查 NIST 分度表,加线性插值。我用的分度表是每 10°C 一个电压点,把表放在 STM32 的 Flash 里,不占 RAM。
查表流程:
- 根据 ADC 读到的码值计算实际电压。
- 根据冷端温度查表得到冷端补偿电压,加到实际电压上。
- 用补偿后的电压去分度表里定位温度区间。
- 线性插值算出精确温度。
线性插值公式很简单:
T = T0 + (V - V0) * (T1 - T0) / (V1 - V0)其中 V0、V1 是分度表里夹住 V 的两个电压点,T0、T1 是对应的温度。10°C 间隔下,K 型热电偶的线性度误差很小,配合插值可以把精度做到 0.1°C 以内。
4.3 用 ADS1220 内部温度传感器测冷端
冷端温度怎么测?ADS1220 本身带了内部温度传感器,通过把 REG1 的 TS 位置 1,下一次转换结果就是芯片内部温度。这个传感器精度大概在 ±2°C 左右,对于冷端补偿其实够用,因为热电偶冷端来说 1°C 误差折算到热端温度也是 1°C 左右。
操作顺序:
- 写 REG1 = 0x02,启用内部温度传感器。
- 等待 DRDY 拉低,读取 ADC 码值。
- 把这个码值按比例换算成电压。
- ADS1220 内部温度传感器输出电压和温度近似成正比,实际斜率大约 0.4V/°C?不对,实际是约 2.1mV/°C。为了避免误导,建议用两点校准法:在冰水混合物里测一次,在室温环境用温度计测一次,算出斜率和偏置,然后应用到产线。
更精确的做法是:冷端温度不要直接用芯片内部温度,而是在热电偶端子附近贴一颗 NTC 或 DS18B20。因为芯片内部温度和接线端子温度还是有差异的,特别是环境温度变化快的时候,这个差异是冷端补偿误差的主要来源。
5. STM32 驱动移植:SPI 时序与寄存器读写源码
接下来是代码部分。ADS1220 是 SPI 从机,STM32 作为主机。我用的是 STM32F103 搭配 HAL 库,SPI 时钟设 1MHz,模式是 SPI_MODE1(CPOL=0,CPHA=1)。如果你的板子 MISO 读不到数据,优先检查 SPI 模式,有一部分板子需要切到 MODE0 才能正常工作。
5.1 SPI 配置与 DRDY 等待
ADS1220 的输出数据准备好后,DRDY 引脚会拉低。读取之前必须等 DRDY 变低,否则读到的可能是上一次或中间态的数据。在 STM32 工程里,我把 DRDY 配置为输入引脚,用轮询方式等待。
#define ADS1220_CS_LOW() HAL_GPIO_WritePin(ADS_CS_GPIO_Port, ADS_CS_Pin, GPIO_PIN_RESET) #define ADS1220_CS_HIGH() HAL_GPIO_WritePin(ADS_CS_GPIO_Port, ADS_CS_Pin, GPIO_PIN_SET) static void ADS1220_SPI_WriteByte(uint8_t dat) { HAL_SPI_Transmit(&hspi1, &dat, 1, 10); } static uint8_t ADS1220_SPI_ReadByte(void) { uint8_t dat = 0; HAL_SPI_Receive(&hspi1, &dat, 1, 10); return dat; } static void ADS1220_WaitDRDY(void) { uint16_t timeout = 0xFFFF; while (HAL_GPIO_ReadPin(DRDY_GPIO_Port, DRDY_Pin) != GPIO_PIN_RESET) { if (--timeout == 0) { break; } } }SPI 通信必须 CS 拉低后才能操作,CS 拉高表示一帧结束。如果使用多个 SPI 从机,CS 千万不能共用。
5.2 寄存器读写命令格式
ADS1220 的寄存器写命令是 0x40 | (addr << 1),地址 0 到 3。读寄存器命令是 0x20 | (addr << 1)。命令后面必须跟一个数据字节或 NOP 时钟,因为 SPI 是双向的,读寄存器时需要通过发送 0x00 来提供时钟。
void ADS1220_WriteReg(uint8_t addr, uint8_t val) { ADS1220_CS_LOW(); ADS1220_SPI_WriteByte(0x40 | (addr << 1)); ADS1220_SPI_WriteByte(val); ADS1220_CS_HIGH(); } uint8_t ADS1220_ReadReg(uint8_t addr) { uint8_t val = 0; ADS1220_CS_LOW(); ADS1220_SPI_WriteByte(0x20 | (addr << 1)); val = ADS1220_SPI_ReadByte(); ADS1220_CS_HIGH(); return val; }调试时我喜欢写完每个寄存器后马上回读一次,看返回值是不是和写入值一致。如果读回来是 0xFF,通常说明 SPI 通信有问题,要检查接线、时钟极性和 CS 时序。
5.3 连续转换读取源码
读 ADC 转换结果用 RDATA 命令,命令字是 0x10。CS 拉低后发送命令,然后连续读取三个字节,这三字节是 24 位有符号数。
int32_t ADS1220_ReadADC(void) { uint8_t buf[3] = {0}; int32_t code = 0; ADS1220_WaitDRDY(); ADS1220_CS_LOW(); ADS1220_SPI_WriteByte(0x10); buf[0] = ADS1220_SPI_ReadByte(); buf[1] = ADS1220_SPI_ReadByte(); buf[2] = ADS1220_SPI_ReadByte(); ADS1220_CS_HIGH(); code = ((int32_t)buf[0] << 16) | ((int32_t)buf[1] << 8) | buf[2]; if (code & 0x800000) { code |= 0xFF000000; } return code; }注意一定要做符号扩展。ADS1220 输出的是二进制补码,如果最高位是 1,表示负电压,直接把 24 位赋值给 int32_t 会变成正数,必须把高 8 位补 0xFF。
5.4 初始化与电压转换
初始化函数:
void ADS1220_Init(void) { HAL_Delay(20); ADS1220_WriteReg(0, 0x04); // AIN0-AIN1, PGA=16 ADS1220_WriteReg(1, 0x00); // 20SPS, continuous, TS off ADS1220_WriteReg(2, 0x00); // IDAC off ADS1220_WriteReg(3, 0x40); // external reference }电压换算公式:
[ V_{in} = \frac{code}{2^{23} - 1} \times \frac{V_{REF}}{PGA} ]
代入 REF5025 的 2.5V 和 PGA=16:
#define ADC_FULL_SCALE 8388607.0f #define VREF 2.5f #define PGA_GAIN 16.0f float ADS1220_CodeToVoltage(int32_t code) { return ((float)code / ADC_FULL_SCALE) * (VREF / PGA_GAIN); }从电压到温度,调用查表函数即可。查表和冷端补偿的代码比较多,核心就是上面说的线性插值,这里不再贴完整分度表,网上可以找到 K 型热电偶 NIST 表的 C 语言数组。
6. 校准步骤和实测数据:从寄存器配置到稳定读数
ADS1220 没有像某些高端 ADC 那样复杂的内置校准命令,所以实际项目主要靠外部标准源做软件校准。校准做完,系统才能叫「高精度」。
6.1 两点校准:零点与增益偏差
校准分两步。
第一步校准零点。把 ADS1220 输入端短接,也就是 AIN0 和 AIN1 直接连在一起,等 DRDY 拉低后读取码值,正常情况下应该是 0 附近,但因为 PGA 失调和基准偏移,实际会有一个小的固定 offset。把这个值存成offset_code。
第二步校准增益。给 ADC 输入一个已知的精密电压,比如用另一颗 REF5025 加分压得到精确的 100mV,或者用标准电压源。读取码值,计算实际增益:
[ gain_factor = \frac{(code_{cal} - offset_code)}{ADC_FULL_SCALE} \times \frac{V_{REF}}{V_{cal}} ]
最后测量时:
[ V_{real} = \frac{(code - offset_code)}{gain_factor} \times \frac{V_{REF}}{ADC_FULL_SCALE \times PGA} ]
这样可以把基准初始精度、PGA 增益误差一起校准掉。实际效果很可观,REF5025 的初始 0.05% 误差本来在 100mV 下就是 50uV,热电偶差不多 1.2°C,校准后能压到很小。
6.2 实测数据:热电偶测得准不准
校准后我做了三组实测,用的是 K 型热电偶,冷端用内部温度传感器补偿,室温 25°C 左右:
| 标准温度(℃) | ADS1220 实测(℃) | 误差(℃) |
|---|---|---|
| 0 | 0.12 | +0.12 |
| 50 | 49.83 | -0.17 |
| 100 | 100.21 | +0.21 |
这个结果对工业现场测温来说已经可以接受了。误差来源主要是冷端温度传感器本身的偏差和分度表插值误差。如果把冷端换成外接 PT100 或 NTC 贴在接线端子上,误差可以进一步缩小。
6.3 几个常见坑:参考地、PCB 漏电、滤波
调完数据,经验上有几个坑值得单独说。
第一个是参考地。REFN0 如果接到数字地,或者和 REFP0 之间走线有压降,读数会周期性跳动。我最后把 REF5025 的地和 ADS1220 的 REFN0 走线加粗,并在两个引脚之间放了一个 100nF 电容,问题才消失。
第二个是 PCB 漏电。ADS1220 是高输入阻抗器件,PGA 输入端的电流极小,PCB 上的污渍、助焊剂残留、潮湿都会造成漏电,导致零点漂移。实际处理方式是清洗板子,或者在关键走线周围加一圈保护环,把漏电路径切断。
第三个是输入端滤波电容别太大。有次我在 AIN0 和 AIN1 之间放了 1uF 电容,结果 20SPS 下读数还是不稳定。原因是 ADS1220 内部 PGA 和 ADC 的采样电容需要从外部滤波电容充电,外部电容太大反而会造成建立时间不足。后来换成 100nF 加 100Ω 电阻,读数立刻稳定。
我在实际操作中的体会是:ADS1220 这片 ADC 本身不难驱动,难的是把参考电压、滤波、布局、校准这些外围因素都处理好。你如果正在调类似的项目,建议耐心一点,先把寄存器回读确认无误,再一步步排查模拟链路,不要一上来就怀疑芯片不行。