
1. 项目概述为什么我最后选了STM32ADS1247这套组合做温度采集这个方向前前后后折腾了不少方案。最早图省事用DS18B20单总线确实方便但测温范围撑死125℃而且传感器本身和探头封装绑在一块很多工业场景根本塞不进去。后来换过NTC热敏电阻成本低是低可非线性太严重查表表能查到怀疑人生每批料的一致性也不理想。直到换了PT100铂电阻方案才算找到了真正能落地的东西。PT100这个传感器0℃时标称阻值100Ω温度每升高1℃阻值大约增加0.385Ω线性度好、重复性好、测温范围宽-200℃到850℃工业现场遍地都是。但PT100本身只输出电阻变化要把它变成数字温度需要恒流源激励、精密ADC采集、信号调理一路配合下来。刚开始我试过直接用STM32内部ADC去采结果凉凉——12位分辨率、参考电压波动、共模干扰叠加上PT100本身那微弱的阻值变化分辨率根本不够看。后来换成ADS1247这颗24位Δ-Σ ADC才算真正把这套系统做到能用、够准、可量产。ADS1247是TI专门为高精度传感器测量设计的芯片内置PGA可编程增益放大器和两个匹配的恒流源IDAC最贴心的是它原生支持和PT100三线制接法对接一路恒流源输出、一路做导线电阻补偿整个模拟前端的基本框架一颗芯片全包了。再加上STM32负责SPI通信、数据处理和对外交互整个系统架构非常清爽。这篇内容我会把当时做这块板子的完整思路和踩坑经历捋一遍从PT100的接法选择、ADS1247的原理图设计到STM32的SPI驱动、寄存器配置、温度换算公式再到标定和排查问题的方法全部摊开讲。适合正在做工业测温、仪器仪表或者毕设里需要高精度温度采样的朋友参考代码和思路可以直接往自己的项目里搬。2. 硬件设计PT100的接法决定你的精度上限2.1 两线制、三线制、四线制到底差在哪PT100本质上是一个可变电阻测量电路的核心任务就是把这个电阻值准确读出来。这里第一个坑就是导线电阻。想象一下如果直接用两线制恒流源的电流走线过去在PT100上产生压降再走同一根线回来。问题是导线本身也有电阻普通铜线一米就有几十毫欧到几百毫欧。比如用0.5平方的铜线拉10米来回20米导线电阻可能就有0.4Ω左右——注意PT100在0℃时才100Ω温度系数才0.385Ω/℃0.4Ω的导线误差相当于1℃多的温度偏差这在工业现场完全不可接受。所以就有了三线制和四线制。三线制最常用原理是两根线给PT100供电第三根线单独用来采样电压。因为恒流源回路里两根导线的压降会同时叠加在信号上但采样端用第三根线接到PT100的一端这根采样线上几乎不流电流ADC输入阻抗极高所以测到的电压就是PT100本身加上供电线上一半的压降。ADS1247内部两个IDAC恒流源设计成完全匹配正式利用这个原理把导线电阻误差抵消掉。四线制精度更高但需要PT100探头引出四根线现场探头往往是三线制的而且ADS1247的两个恒流源刚好对应三线制补偿所以我最终选了三线制方案。2.2 ADS1247这颗芯片的神奇之处ADS1247是24位Δ-Σ型ADC2个差分输入通道内置PGA增益从1到128可调内置两个匹配的恒流源各自可配置为0.5mA、1mA、1.5mA、2mA。这些参数放在一起基本就是为RTD测量量身定做的。关键是这两个恒流源的匹配度。我用万用表实测过在1mA档位下两个通道的电流差值大概在0.1%以内这直接决定三线制补偿的效果。如果电流不匹配补偿就会引入额外误差。这也是为什么我不建议用普通运放和三极管自己搭恒流源——自己搭的电路温漂和匹配度根本没法保证。另一个考虑是参考电阻R_REF的选型。ADS1247做比例测量时恒流源流过PT100和参考电阻ADC以参考电阻两端的电压作为参考基准这样恒流源的绝对精度就不重要了因为PT100上的电压和参考电阻上的电压之比只取决于它们的阻值之比。参考电阻用低温漂的精密电阻我选的是0.1%精度、25ppm/℃温漂的金属箔电阻阻值取1.5kΩ。为什么取1.5k因为1mA恒流源流过1.5k电阻产生1.5V参考电压接近ADS1247的满量程范围模拟电源3.3V时满量程输入为±2.048V左右经过PGA动态范围用得比较充分。2.3 原理图要点与电源设计整个模拟前端其实非常简洁AVDD、DVDD统一用3.3V供电模拟地和数字地在ADC芯片下方单点连接PT100三根线分别接IDAC1输出、AIN0、AIN1其中AIN0和AIN1是差分输入IDAC1接PT100一端IDAC2接参考电阻后回流REFP和REFN接精密电阻两端作为ADC参考电压输入通道上并联一个100nF的电容做差模滤波同时每个引脚对地加1nF电容滤共模噪声电源部分我只用了AMS1117从5V降到3.3V然后在模拟电源输入处串了一个磁珠加10μF钽电容和0.1μF陶瓷电容退耦。ADS1247对电源噪声比较敏感尤其是参考电压和模拟电源的内部基准如果电源纹波大24位的分辨率就是摆设。实测如果直接用电荷泵类DC-DC供电AD值跳动会明显增大换成LDO之后数据立刻稳定下来。还有一个细节必须提醒SPI的SCLK和MOSI走线不要和模拟输入线平行长距离走线数字信号跳变会通过寄生电容耦合到模拟端。我第一版PCB就是没注意这个结果adc跳码特别厉害后来割线飞线才解决。3. 底层驱动让STM32和ADS1247先跑起来3.1 SPI通信初始化ADS1247的SPI是16位命令字/数据字格式支持CPOL1、CPHA1模式也就是SPI Mode 3。我用的是STM32F103C8T6的SPI2主模式2MHz时钟——这里注意别把SPI时钟拉太高ADS1247最高支持4MHz左右但实际走线长了或者有干扰2MHz更稳。初始化代码大概长这样void SPI2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // CS引脚设为通用推挽输出不用硬件NSS GPIO_InitStructure.GPIO_Pin GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; 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_High; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI2, SPI_InitStructure); SPI_Cmd(SPI2, ENABLE); }CS引脚我习惯用软件控制这样读写命令和数据时能灵活控制片选时序尤其是ADS1247读数据时CS需要在每次传输之间保持高电平或低电平具体要求要看数据手册的时序图。3.2 ADS1247寄存器配置ADS1247上电后需要配置几个关键寄存器MUX0输入多路复用器、VBIAS偏置电压、MUX1内部参考、恒流源开关、SYS0增益、数据速率、IDAC0和IDACMUX恒流源电流大小和输出引脚映射。我的配置目标是1mA恒流源从IDAC1输出到PT100差分输入AIN0A正端、AIN1A负端PGA增益设为8数据速率20SPS内部参考使能恒流源输出到AIN0通道。uint8_t ADS1247_WriteRegister(uint8_t reg, uint8_t value) { uint8_t tx[3]; tx[0] 0x40 | (reg 1); // WREG命令bit61表示写寄存器 tx[1] 0x00; // 写1个寄存器 tx[2] value; CS_LOW(); SPI_SendByte(tx[0]); SPI_SendByte(tx[1]); SPI_SendByte(tx[2]); CS_HIGH(); return 0; } void ADS1247_Init(void) { // 配置MUX0: AINPAIN0, AINNAIN1 ADS1247_WriteRegister(0x00, 0x02); // 配置MUX1: 内部参考使能恒流源1、2都开启 ADS1247_WriteRegister(0x01, 0x28); // 配置SYS0: PGA8数据速率20SPS ADS1247_WriteRegister(0x03, 0x14); // 配置IDAC0: 两个恒流源电流1mA ADS1247_WriteRegister(0x04, 0x02); // 配置IDACMUX: IDAC1输出到AIN0IDAC2输出到AIN2(参考电阻) ADS1247_WriteRegister(0x05, 0x02); }稍微解释一下SYS0寄存器0x14二进制是0001 0100前三位是PGA设置0001对应增益8后四位是数据速率0100对应20SPS。为什么选20SPSΔ-Σ ADC的数据速率越低数字滤波器抑制工频干扰50Hz的能力越强。工业现场50Hz工频干扰几乎是绕不开的20SPS时数字滤波器的陷波点正好落在50Hz附近实测读数非常稳。如果追求速度可以开到1000SPS但50Hz干扰就会很头疼后续去噪全靠软件。3.3 读取转换结果的正确姿势ADS1247有两个关键操作一是发送RDATA命令0x10读取转换结果二是在连续转换模式下要等DRDY引脚变低才去读。这个顺序千万别搞反否则读到的可能是上次的旧数据甚至会拿到中间状态的半截数据。uint32_t ADS1247_ReadData(void) { uint8_t tx[4]; uint8_t rx[3]; uint32_t raw 0; // 等待DRDY变低 while (GPIO_ReadInputDataBit(DRDY_PORT, DRDY_PIN) SET); tx[0] 0x10; // RDATA命令 CS_LOW(); SPI_SendByte(tx[0]); // 发送命令 rx[0] SPI_SendByte(0x00); // 读取高字节 rx[1] SPI_SendByte(0x00); // 读取中字节 rx[2] SPI_SendByte(0x00); // 读取低字节 CS_HIGH(); raw ((uint32_t)rx[0] 16) | ((uint32_t)rx[1] 8) | rx[2]; return raw; }ADS1247的输出编码是有符号的二进制补码格式24位数据左对齐。如果测量值超过了满量程一半最高位为1就表示负值。PT100的电压方向是正的在读回来的uint32_t值判断最高位bit23是否为1如果是1说明芯片输出的是负编码需要按有符号数处理。4. 温度换算从AD值到真实温度的数学之旅4.1 把原始码变成电阻值读回来的24位AD码怎么变成PT100的电阻值这一步很多人卡住。ADS1247是比例测量架构转换结果满足这个关系AD码满量程对应的输入电压是±V_REF/(2×PGA)V_REF是参考电压。所以输入电压V_IN和AD码的关系是V_IN (AD码 / 0x7FFFFF) × (V_REF / PGA)而V_IN I_IDAC × R_PT100V_REF I_IDAC × R_REF。因为同一个恒流源流过PT100和参考电阻三线制下两个IDAC电流匹配电流项可以约掉直接得到R_PT100 (AD码 / 0x7FFFFF) × (R_REF / PGA)这个公式非常优雅——最终结果只取决于AD码和参考电阻精度恒流源的绝对精度完全不参与计算这就是比例测量的好处。代入实际参数PGA8R_REF1500Ω那么R_PT100 AD码 × 1500 / (0x7FFFFF × 8) AD码 × 1500 / (8388607 × 8)用浮点算就行了STM32F103没有硬件浮点但单次换算也就几个浮点乘法完全不虚。想省事也可以把0x7FFFFF×8换算成常量用全长整型运算加速。4.2 Callendar-Van Dusen方程拿到电阻R后下一步转温度。PT100的阻值和温度的关系不是严格线性国际标准IEC 60751给出了Callendar-Van Dusen方程0℃以上温度t ≥ 0℃ R(t) R0 × (1 A×t B×t²)0℃以下温度t 0℃ R(t) R0 × (1 A×t B×t² C×(t-100)×t³)其中R0100Ω系数A3.9083×10⁻³B-5.775×10⁻⁷C-4.183×10⁻¹²。我们实际应用中是从电阻反推温度也就是解这个方程。正温区间直接解一元二次方程t [-A √(A² - 4×B×(1 - R/R0))] / (2×B)负温区间就得靠迭代逼近了我一般直接用牛顿迭代法几次循环就收敛。4.3 代码实现与查表法替代float PT100_ResToTemp(float R) { float t; float R0 100.0f; float A 3.9083e-3f; float B -5.775e-7f; float C -4.183e-12f; if (R R0) { // t 0℃, 解一元二次方程 float discriminant A*A - 4.0f*B*(1.0f - R/R0); t (-A sqrtf(discriminant)) / (2.0f*B); } else { // t 0℃, 牛顿迭代 t 0.0f; for (int i 0; i 10; i) { float Rt R0 * (1.0f A*t B*t*t C*(t-100.0f)*t*t*t); float dRt R0 * (A 2.0f*B*t 4.0f*C*t*t*t - 300.0f*C*t*t); t t - (Rt - R) / dRt; } } return t; }如果MCU性能太弱或者不想每次做浮点运算也可以做成查表方案把-50℃到400℃每1℃对应的电阻值预先算好存成数组运行时二分查找。但工程上我更推荐直接算——STM32F103跑这几个浮点运算也就几微秒查表的存储开销反而更麻烦。负温迭代10次完全够收敛误差在0.001℃以内。5. 标定与精度优化让数据真正可信5.1 两点标定法实操即便用了0.1%精度的参考电阻系统还是会有系统性误差主要来源是参考电阻的实际值偏离标称值、PGA增益误差、PCB布线电阻等。标定就是把这些系统性误差消掉。最简单有效的是两点标定准备冰水混合物0℃和沸水100℃两个标准温度点分别读取AD码转换成电阻值算出实际增益误差并进行补偿。我的标定流程是在0℃冰水混合物中稳定5分钟后连续读100次取平均记为R_cold。在100℃沸水中注意海拔不同沸点会变最好用标准温度计实测稳定后读100次取平均记为R_hot。实际温度点与测量值作线性修正T_true T_measured × k b其中k (100 - 0) / (T_hot_measured - T_cold_measured)b -T_cold_measured × k。这套操作做下来实测整个量程的误差能控制在±0.3℃以内。如果精度要求更极致可以加第三点标定修正二阶非线性但工业现场一般不用那么夸张。5.2 软件滤波的实战选择即使是24位ADC在工业现场还是会有偶发毛刺可能是接触不良、电机启停、继电器动作造成的电磁脉冲。这种毛刺不是白噪声用简单平均滤波效果不好。我常用的组合是先做中值滤波去除野值再做滑动平均平滑#define FILTER_N 5 float Temperature_Filter(float new_value) { static float buf[FILTER_N]; static uint8_t index 0; float temp[FILTER_N]; float sum 0; buf[index] new_value; index (index 1) % FILTER_N; // 拷贝并排序 for (int i 0; i FILTER_N; i) temp[i] buf[i]; // 简单冒泡排序取中值 for (int i 0; i FILTER_N - 1; i) for (int j 0; j FILTER_N - 1 - i; j) if (temp[j] temp[j1]) swap(temp[j], temp[j1]); // 去掉最大最小值后取平均 for (int i 1; i FILTER_N - 1; i) sum temp[i]; return sum / (FILTER_N - 2); }这个滤波方式看起来土但实际效果非常好。在继电器频繁动作的配电柜旁边不用滤波的话显示温度能跳0.5℃用了之后基本纹丝不动。注意滤波会引入响应延迟和显示滞后如果系统用于快速控温需要平衡滤波深度和实时性。5.3 关键布线经验PCB布局对ADS1247的性能影响极大这里有几条吃过大亏后的硬核经验模拟输入走线尽量短而粗参考电阻要紧贴REFP和REFN引脚中间不要放过孔PT100的三根线最好用屏蔽线屏蔽层单端接地别两头都接地形成地环路恒流源的走线要对称两根激励线的走线电阻差越小三线制补偿效果越好数字地与模拟地单点连接我是在芯片正下方把两者用一颗0Ω电阻连起来芯片电源引脚一定要按要求接去耦电容且电容要尽量靠近引脚6. 常见问题与排查技巧实录6.1 SPI通信异常读回来的数据全是0xFF或0x00这个问题我调试时遇到过很多次。先确认CS片选时序再确认时钟极性和相位。ADS1247是SPI Mode3CPOL1、CPHA1如果配成Mode0数据全错。还有一个坑是STM32的SPI发送时发送缓冲寄存器会阻塞等待如果芯片没有正确响应MISO读到的就是全1。建议先用逻辑分析仪抓SPI波形确认SCLK、MOSI、MISO有没有信号逐级排查。没有逻辑分析仪的话可以先把SPI的MISO引脚配置成普通GPIO输入用代码持续读取并发送到串口观察电平变化。6.2 读数跳变严重低位一直乱跳低位跳动先区分是电源噪声还是外部干扰。把PT100短接看AD值是否稳定——如果短接后还是跳问题在板子本身如果短接后读数很稳说明是传感器端拾取了干扰。排查方向依次是模拟电源是否干净用示波器看纹波、参考电压是否稳定、SPI线路是否干扰了模拟输入。我之前有一版板子就是SPI的SCLK走线穿过了模拟输入差分对的旁边结果每次SPI通信时AD值就跳一下改了走线后立马解决。6.3 温度读数整体偏高或偏低但很稳定系统性偏差基本都是参考电阻精度或PGA增益引起的。先测一下参考电阻的实际值用精度高的万用表把实测值写进程序里的R_REF常量而不是继续用标称值。另外检查一下恒流源配置是不是1mA如果实际设置了1.5mA虽然比例测量理论上不受电流影响但如果超出芯片内部某个限值仍会影响精度。6.4 三线制补偿失效导线一长误差就变大三线制补偿的前提是IDAC1和IDAC2两个恒流源严格匹配。ADS1247的IDAC匹配度在数据手册标称范围内但如果你把两个恒流源配置成不同档位比如一个1mA一个1.5mA补偿就完全失效了导线电阻差会被直接当成温度误差。所以一定要用相同档位。另外三根线的线径、材质最好一致导线电阻不对称也会引入误差。7. 后续扩展与升级思路这套测温方案跑通之后扩展空间其实很大。最常见的是加通信接口——STM32把算好的温度通过串口、RS485Modbus RTU协议或者CAN总线上传在工业监控系统里对接触摸屏或上位机组态软件就能形成完整的一套温度监测节点。我后来在项目里加了一路继电器输出做超温报警还扩展成了四通道测温只需要再挂两片ADS1247SPI片选分开就行。如果对功耗有要求可以把STM32切到睡眠模式定时唤醒读取一次温度再继续睡用锂电池供电做无线测温节点这方案比现成的无线温度传感器便宜不少而且灵活性更高。软件层面还可以做个历史温度曲线数据存到Flash或外部存储方便事后分析。这套方案从头到尾我实际迭代了三版板子第一版纯验证原理第二版开始纠结精度第三版才算工程上可用。回头看不复杂但里面每一步取舍都踩过真实的坑——选对传感器、选对ADC芯片再配合合理的电路和算法才能让温度数据真正可信。如果你正卡在类似的项目上希望这篇文章能帮你少走几段弯路。