做高精度测温这件事最容易踩的坑不是传感器选型而是“我明明用了24位ADC为什么测出来还是跳来跳去、温飘感人”。这个问题的根子往往不在ADC本身而在电压基准、传感器激励方式和寄存器配置这三个环节。这篇就围绕STM32ADS1220这套组合从电压基准怎么选、比例式测温怎么接、四个配置寄存器每个bit怎么填到最后源码怎么读完整走一遍。适合正在做PT100/PT1000测温、工业采集、物联网温控或者拿STM32做毕设的同学照着搭一套0.1℃级别的高精度测温系统不成问题。1. 方案整体设计与芯片选型逻辑1.1 测温系统的精度指标到底怎么定先说目标。很多人一说“高精度测温”就直接要0.01℃但实际工程里这个指标是要拆解的。比如量程-50℃到400℃分辨率做到0.01℃、系统误差控制在±0.1℃以内对绝大多数工业现场已经非常够用了。再往上追成本和调试复杂度是指数上升的普通场景真没必要。拆解一下PT100在0℃时电阻100Ω温度系数约0.385Ω/℃也就是说要分辨0.01℃电阻分辨率要做到0.00385Ω。如果激励电流取1mA对应电压分辨率就是3.85μV。这个信号量级普通12位ADC根本没戏16位ADC勉强摸边但加上噪声、温漂、基准误差就全毁了。所以ADS1220这种24位Δ-Σ ADC才是有意义的——它带PGA可编程增益、内部基准、双路IDAC电流源一颗芯片把信号链上最难的部分全包了。1.2 为什么选ADS1220而不是普通ADC或现成模块ADS1220是TI的一颗24位Δ-Σ ADC拿它和常见的ADS1115比差距非常明显。ADS1115虽然是16位但内部基准温漂和噪声特性决定了它做高精度测温上限很低而且没有内置电流源你还得自己搭恒流源。ADS1220的优势在三个地方内置两个IDAC电流源电流大小可编程从50μA到1.5mA可以直接用来激励RTD省掉外部恒流源电路。内置PGA最高128倍增益小信号不用额外放大。内置2.048V基准但更关键的是它支持外部基准输入可以搭比例式测量把基准和电流源的误差一起消掉。市面上也有不少ADS1220模块几十块钱一块买回来方便但模块上的基准、滤波、接线未必适合你的具体量程。自己画板或者自己搭电路才能把精度吃透。1.3 系统整体架构整套系统的信号链路是这样的PT100由ADS1220内置IDAC电流源激励产生随温度变化的电压信号该电压经过ADS1220内部PGA放大由24位Δ-Σ ADC数字化ADC的基准不取内部2.048V而是取外部串联在激励回路里的精密参考电阻两端的电压这样激励电流的温漂和波动会被自动抵消STM32通过SPI读取转换结果在固件里完成电阻到温度的换算。整体链路里没有外部运放、没有分立恒流源模拟前端非常干净。2. 电压基准高精度测温真正的门槛2.1 内部基准和外部基准该怎么选ADS1220内部集成2.048V基准初始精度和温漂指标对于一般应用够用。但做高精度测温我强烈建议你直接绕过内部基准用外部参考电阻搭比例式测量。原因很简单内部基准温漂典型值约5ppm/℃到12ppm/℃算下来温度每变化10℃读数就可能偏零点几摄氏度这对于追求0.1℃的系统是不可接受的。而且内部基准是固定的2.048V你激励电流一变信号量程利用率也会变化。外部基准方案听起来高大上其实不复杂。用一颗低温漂精密电阻串在激励回路里让同一个电流既流过PT100又流过这颗参考电阻然后用这颗电阻两端的电压做ADC的基准。妙处在于如果激励电流变了PT100上的电压和参考电阻上的电压会同步变化ADC转换结果是两者的比值电流的绝对值被消掉了。2.2 比例式测量的核心思想用一个例子说透很多人第一次接触比例式测量会被绕晕我用买菜类比解释一下。假设你称东西用的秤砣本身质量不是标准的1kg而是0.9kg你去称1kg的菜读数肯定不对。但如果菜价不是按秤的绝对值算而是按“菜重/秤砣重”的比值算那秤砣偏轻反而是好事——菜和砝码一起缩水比值不变。PT100测温也一样。激励电流相当于秤砣如果它从1mA漂到0.98mAPT100上的电压会从100mV掉到98mVADC参考电阻上的电压也会等比例从1.8V掉到1.764V。ADC只认这两个电压的比值所以结果依然是100Ω/1800Ω跟电流漂没漂没关系。这就是比例式测量的本质。2.3 具体电路参数计算电流、参考电阻、量程匹配我采用的激励电流是1mA。这个值要综合考虑电流太小信号电压低对噪声更敏感电流太大PT100自身发热明显会引入自热误差。PT100在0℃时功耗约0.1mW自热温升通常可以控制在0.01℃以内可以接受。参考电阻RREF我选1.8kΩ这样在1mA激励下VREF1.8V。PT100在-50℃时约80.31Ω对应80.31mV400℃时约247.04Ω对应247.04mV。输入信号与基准的比值范围是4.46%到13.72%24位ADC在这个区间内的量化噪声仍然远低于0.001℃主要限制在模拟噪声和基准电阻温漂上。RREF这颗电阻本身必须用低温漂高精度型建议温漂5ppm/℃以内精度0.1%以上。不要用普通贴片电阻温度系数太差会让整个系统的温度稳定性崩溃。用金属箔电阻或者精密薄膜电阻都行。2.4 四线制接法和滤波细节PT100接线有三种两线、三线、四线。两线最简单但导线电阻直接串进测量回路几十厘米的普通导线就能引入零点几欧误差换算成温度就是一两度完全不能接受。三线制能抵消大部分导线电阻是工业现场最常见的折中方案。四线制最准适合实验室和高精度场景。我用的四线接法是这样的IDAC1电流从AIN2引脚输出流经RREF再经过激励导线到PT100PT100另一端直接回AVSSPT100两端再用独立的两根感测线接到AIN0和AIN1。AIN0-AIN1作为差分输入测量的是PT100两端的真实电压感测线上几乎没电流所以导线电阻的压降不会进测量回路。RREF两端的电压接到REFP和REFN作为ADC基准。硬件上还有几个细节需要注意REFP和REFN之间要加1μF到10μF的电容做滤波AIN0和AIN1各串一个1kΩ电阻再并联一个10nF到100nF电容做RC抗混叠滤波AVDD和AVSS之间放10μF0.1μF并联去耦模拟地和数字地尽量单点连接。2.5 PCB布线的实践经验ADS1220这类高分辨率Δ-Σ ADC布局布线比电路原理还重要。实测中我踩过的坑主要有三个第一REF和AIN的走线要短而且要远离SPI的SCLK和DOUT这类数字信号否则数字开关噪声会耦合进模拟通路第二参考电阻的PCB焊盘附近不要铺大块铜否则铜箔的热电偶效应在温度变化时会引入微小电势第三RTD的激励线和感测线最好用双绞线能显著抑制共模干扰。3. 寄存器配置逐位拆解四个字节决定系统性格3.1 寄存器总览ADS1220只有四个配置寄存器ADS1220的配置空间非常精简就是寄存器0到寄存器3总共四个字节。但麻雀虽小五脏俱全输入通道选择、PGA增益、转换模式、数据速率、基准源、IDAC电流大小和路由全部压在这四个字节里。下面是我最终使用的配置值然后逐位解释为什么这么填。寄存器地址配置值含义Reg00x000x00AIN0-AIN1差分输入PGA1倍Reg10x010x00连续转换模式20SPSFIR滤波Reg20x020x0A外部基准REFP-REFNIDAC电流1mAReg30x030x80IDAC1路由到AIN2PSW关闭3.2 Reg0输入选择和PGA增益先算满量程再定增益Reg0的高两位MUX[1:0]选择输入通道00表示AIN0-AIN1差分输入。这里注意我们测量的是PT100两端的电压一定要用差分方式不要用单端。差分方式能天然抑制地弹和共模干扰而且ADS1220在差分输入下线性度更好。PGA增益位是PGA[2:0]000表示增益1倍。为什么不是4倍或者8倍我算过1mA激励下PT100在400℃时压降也只有约0.247V而基准电压1.8V映射到满量程才占不到14%。看起来没用满但在24位ADC面前这不是问题反而PGA增益越高噪声和输入偏置电流的影响会放大。实测下来增益1倍噪声最低系统最稳。如果你的信号非常微弱可以考虑增益4到8倍但要额外注意输入偏置电流对RTD分压的影响。3.3 Reg1转换模式和速率20SPS是精度和速度的平衡点Reg1里MODE[1:0]我填00选连续转换模式。单次转换模式其实也能用每次读取前发命令触发一次转换功耗低但代码逻辑更复杂还要注意启动时间。我做实时测温连续模式配合DRDY引脚中断最简单。数据速率DR[3:0]填0000对应20SPS。ADS1220内部是Δ-Σ调制器加数字滤波器速率越低数字滤波器抽取得越多噪声越低。20SPS模式下有效分辨率最高配合50Hz和60Hz工频陷波实测读数稳定性最好。如果你要测快速变化的温度可以往上调到45SPS、90SPS但噪声会变大。我的经验是测温本来就是一个慢物理过程20SPS完全够用。3.4 Reg2基准源和IDAC电流比例式测量的核心开关Reg2是整个配置的灵魂。REFSEL选择基准源为外部REFP-REFN这是我前面讲的比例式测量的根基——ADC的比较基准来自RREF两端的电压而不是内部带隙基准。IDAC_MAG我配置为101对应1mA输出。这里有个容易忽略的点IDAC电流源也有建立时间和输出电容要求如果从低电流切换到高电流最好等几个毫秒让模拟前端稳定了再开始转换。3.5 Reg3IDAC路由和辅助功能电流走哪个引脚自己说了算Reg3负责把IDAC电流源路由到具体引脚。我配置IDAC1路由到AIN2也就是让电流从AIN2引脚流出去经过RREF、经过PT100、最后回到AVSS。第二路IDAC2关闭不用。PSW低侧功率开关我关闭了如果开启它可以省一个外部开关管但没必要。实际操作里有一个细节IDAC路由位的编码在不同手册版本上画法有差异我第一次照着网上的老代码填结果电流从错误的引脚流出读数完全不对。建议写完Reg3之后先用万用表量一下对应引脚的电压确认电流确实从你预期的引脚出去了再做后续调试。3.6 SPI时序和命令机制DRDY是数据就绪的脉搏ADS1220走标准SPI我用的是Mode1也就是CPOL0、CPHA1时钟空闲为低数据在下降沿采样。SCLK最高可以到4MHz但实际我控制在1MHz左右抗干扰余量更大。读写寄存器是通过命令字节完成的写命令是0x40寄存器地址读命令是0x20寄存器地址之后跟着数据字节。最关键的信号是DRDY。每一次转换完成后DRDY引脚会拉低此时MCU再去读数据才能拿到最新结果。很多人SPI读出来乱码或者数据全是0xFF十有八九是没等DRDY就匆忙读数据了。正确流程是初始化完成之后循环里先等待DRDY变低然后读24位转换结果读完DRDY会自动拉高继续等下一次。4. 源码实现从SPI底层到温度值步步可查4.1 STM32的SPI初始化我用的是STM32F103SPI2做通信代码直接从标准外设库写CubeMX配置也一样。关键是SPI参数8位数据帧、Mode1、MSB First、预分频后SCLK约1MHz。要注意STM32的SPI从机模式CPHA配置对照ADS1220手册的时序图时钟空闲为低、数据在第二个边沿采样这个错一个相位读出来的数就是乱的。void SPI2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_13 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_SetBits(GPIOB, GPIO_Pin_12); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_64; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI2, SPI_InitStructure); SPI_Cmd(SPI2, ENABLE); }4.2 寄存器读写封装SPI读写封装最重要的就是CS引脚的控制。写寄存器时CS全程拉低发一个命令字节加一个数据字节读寄存器时发命令字节后发一个哑字节同时接收数据。Dout在SCLK下降沿更新、上升沿被采样STM32在第二次边沿采样匹配Mode1。uint8_t SPI_ReadWriteByte(uint8_t txData) { while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI2, txData); while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) RESET); return SPI_I2S_ReceiveData(SPI2); } void ADS1220_WriteRegister(uint8_t regAddr, uint8_t value) { GPIO_ResetBits(GPIOB, GPIO_Pin_12); SPI_ReadWriteByte(0x40 | (regAddr 1)); SPI_ReadWriteByte(value); GPIO_SetBits(GPIOB, GPIO_Pin_12); } uint8_t ADS1220_ReadRegister(uint8_t regAddr) { uint8_t value; GPIO_ResetBits(GPIOB, GPIO_Pin_12); SPI_ReadWriteByte(0x20 | (regAddr 1)); value SPI_ReadWriteByte(0xFF); GPIO_SetBits(GPIOB, GPIO_Pin_12); return value; }注意ADS1220的寄存器寻址是把地址左移一位再放进命令字节的因为bit0保留为读写方向标识这一点和很多ADC不同。第一次写的时候我忘了左移导致写入失败但代码不报错排查了很久。4.3 初始化函数写入四个寄存器初始化步骤先等待上电稳定然后把四个寄存器依次写入。Reg2写入IDAC电流源后要加延时等电流源建立稳定再开始转换。void ADS1220_Init(void) { delay_ms(10); ADS1220_WriteRegister(0x00, 0x00); ADS1220_WriteRegister(0x01, 0x00); ADS1220_WriteRegister(0x02, 0x0A); ADS1220_WriteRegister(0x03, 0x80); delay_ms(10); GPIO_ResetBits(GPIOB, GPIO_Pin_12); SPI_ReadWriteByte(0x08); GPIO_SetBits(GPIOB, GPIO_Pin_12); delay_ms(10); }最后那个0x08是START命令用来启动转换。如果配置了连续转换模式发送一次START之后芯片就一直转换下去不需要每次重新触发。4.4 读取24位转换结果DRDY引脚我用PB11配置成下降沿中断或者轮询都行。轮询代码最简单但要注意必须在DRDY拉低之后尽快读取如果两次DRDY之间的时间窗内没有读完会读到旧的或者错位的数据。int32_t ADS1220_ReadADC(void) { int32_t raw 0; uint8_t data[3]; while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) ! RESET); GPIO_ResetBits(GPIOB, GPIO_Pin_12); data[0] SPI_ReadWriteByte(0xFF); data[1] SPI_ReadWriteByte(0xFF); data[2] SPI_ReadWriteByte(0xFF); GPIO_SetBits(GPIOB, GPIO_Pin_12); raw ((int32_t)data[0] 16) | ((int32_t)data[1] 8) | data[2]; if (raw 0x800000) { raw | 0xFF000000; } return raw; }ADS1220输出的是24位二进制补码最高位是符号位。正电压对应的码值从0到0x7FFFFF负电压从0x800000到0xFFFFFF。如果直接用32位无符号整数累加负数会计算出错必须先做符号扩展。4.5 从原始码值到PT100电阻再到温度比例式测量的电阻计算很简单。ADC转换结果是输入电压和基准电压之比乘以满量程后公式如下#define RREF 1800.0f #define FS 8388607.0f float ADS1220_GetRTDResistance(int32_t raw) { float ratio (float)raw / FS; float rtd ratio * RREF; if (rtd 0.0f) rtd 0.0f; return rtd; }比如读出原始值约46603对应比例0.005556乘上1800Ω就是10Ω。等等这个数不对。回顾一下1mA激励PT100在0℃是100Ω压降100mV基准1.8V所以码值应该是0.05556×8388607≈466033才对。用这个码值算出来就是100Ω。所以上面的数字虽然看着像但量级要对。拿到电阻之后算温度有两条路。工程上最常用的是查表加线性插值把PT100分度表写进ROM每隔10℃一个点中间线性插值精度在0.1℃以内。如果追求更精确用Callendar-Van Dusen方程这是PT100的物理模型公式在-200℃到850℃范围内误差极小。我用的是查表法简单可靠。4.6 完整的主循环框架int main(void) { int32_t raw; float rtd, temp; SystemInit(); delay_init(); SPI2_Init(); ADS1220_Init(); while (1) { raw ADS1220_ReadADC(); rtd ADS1220_GetRTDResistance(raw); temp PT100_ResistanceToTemp(rtd); printf(raw%ld, rtd%.3f ohm, temp%.2f C\r\n, (long)raw, rtd, temp); delay_ms(500); } }这个框架看着简单但实际工程里务必加上数据异常判断比如电阻值超出了传感器量程或者读回连续多个相同的满量程值都说明系统可能有问题。不要盲目信任每一次读数。5. 校准、实测与踩坑记录5.1 为什么必须校准再精确的设计也有误差比例式测量消掉了激励电流的误差RREF电阻的温漂也经过精心选型但系统里仍然残留三类误差。第一是ADC本身的偏移误差来自输入放大器和Δ-Σ调制器表现为输入为零时读数不为零第二是增益误差来自内部参考链路和PGA的失配表现为比例系数有偏差第三是传感器本身的误差PT100在0℃时不一定是严格100Ω不同等级偏差不同。所以真正做高精度系统光靠标称值算是不行的必须做校准。最简单的做法是两点校准用精密电阻箱或者标准温度源给系统一个已知基准点计算出偏移修正量和增益修正量。5.2 用SYSOCAL和SYSGCAL命令做初步校准ADS1220自带偏移校准和增益校准命令。SYSOCAL命令会让ADC自动测量输入短路时的偏移然后内部修正SYSGCAL命令需要你提供已知的差分输入电压它根据实际码值和理想码值的比值修正增益。硬件初始化之后先发0x06做偏移校准再在输入端加一个已知精密电压发0x07做增益校准能省掉不少外部数学校准的麻烦。但注意系统校准命令执行时需要满足芯片规定的输入条件差分输入电压不能超过参考电压范围而且校准期间DRDY状态会变化查询手册确认时序再动手。我实际使用中系统校准做完之后再用标准电阻验证误差能大幅缩小。5.3 常见问题速查表现象可能原因排查方法DRDY一直不拉低未发送START命令或SPI配置错误检查SPI Mode1极性相位手动发START后量DRDY电平读到的数据固定0xFFFFFFMISO线没接好或CS时序不对用示波器查MISO引脚波形确认从机有应答读数跳动范围太大电源纹波、参考电容太差、数据速率太高检查AVDD去耦电容降低到20SPSREF两端加大电容温度读数和实际偏差大没有校准或IDAC电流被导线漏电分流做两点校准检查PCB是否污染导致漏电流只能读到正码值没有负值没有做24位符号扩展检查符号扩展代码负数补码处理正确5.4 几个值得记住的实战经验DRDY轮询加500ms延时这个组合在实际运行中表现很稳但如果你想做多通道采集或者降低MCU占用一定要改成中断触发。另外尽量减少在ADS1220转换期间去操作SPI总线上的其他器件SCLK上的毛刺会干扰转换结果。还有一点我吃了不少亏ADS1220的电源顺序。上电时如果AVDD先于DVDD芯片可能进入异常状态。STM32系统里我统一用3.3V供电上电顺序没问题但如果你的系统是多电源必须加电源时序控制或者至少确保两者同时上电。参考电阻的温度稳定性是比例式测量的最后短板。我做过对比实验同一套硬件把RREF从普通1206贴片换成一个5ppm/℃的精密电阻满量程温度读数稳定性明显提升尤其是环境温度变化时原来会跟着漂零点几度换完以后基本纹丝不动。这套系统装进外壳后尽量让RREF远离发热源比如功率电阻和稳压器。最后多说一句调试ADS1220时不要把寄存器配置只写在注释里。把DRDY接到示波器上把SPI的SCLK、CS、DOUT三根线同时抓出来看一遍时序能省掉大量猜测时间。硬件调试没有玄学每个异常都对应着一个具体的电气原因只是你还没找到它。
