STM32+AD7124测温板设计:八种热电偶与Pt100兼容方案
月初接了个测温板的需求核心条件有三条主板已经定了用STM32要能支持K、J、T、E、N、R、S、B这八种常见热电偶同时还得接Pt100而且三线制、四线制都要求兼容。ADC芯片我没怎么犹豫就定了AD7124。理由很直接这种24位Σ-Δ芯片自带PGA、自带可编程激励电流源、自带内部温度传感器热电偶和RTD两大类的信号调理工作它能一口气包圆比“上电偶专用芯片RTD专用芯片”的组合方案省事得多。这篇按我实际调试的顺序来写先讲选型为什么落在AD7124上再讲热电偶通道的满量程计算、八种热电偶的线性化、冷端补偿怎么落地然后是Pt100三线制四线制的接线和寄存器配置最后是STM32端的源码结构以及我调这块板时实打实踩过的几个坑。如果你也在做多类传感器兼容的测温主板这套思路可以直接参考。1. 为什么同时测热电偶和Pt100时我选了AD7124而不是专用芯片1.1 专用芯片方案的尴尬类型锁定管不了Pt100热电偶专用数字芯片确实方便内部集成冷端补偿和线性化拉两根线出来接上就能读温度。但这类芯片基本都锁定单一热电偶类型K型的芯片接不了J型热电偶换一种类型就得换一颗芯片。一台设备想兼容八种热电偶光资源选型就要折腾半天更不要说现场维护时备料根本备不过来。更关键的是这类芯片读的是热电偶毫伏信号不具备激励电流源接不了Pt100这种纯电阻传感器。如果板子上同时需要Pt100通道就还得再外挂一颗RTD专用转换芯片SPI口、I2C口、电源轨都要多留一路。板子越做越复杂成本却没有真正省下来。1.2 AD7124那一堆寄存器其实都是给信号链准备的AD7124真正打动我的是它的输入链路配置灵活性。拿热电偶来说信号只有几十毫伏甚至几毫伏它内部有增益可调的PGA我可以通过寄存器把增益设成16、32、64甚至128把小信号放大到ADC量程的合理区间。拿Pt100来说它内部有可编程激励电流源输出电流可以直接接给RTD省掉外部恒流源电路。至于冷端补偿它内置了温度传感器通道虽然我没拿它做过高精度补偿主方案但用来做板级温度监控和开机自检完全够用。配合外部参考电压、sinc滤波器、断偶检测等一颗芯片通吃两类传感器这是专用芯片方案比不了的。这里顺便说一句选型层面的结论如果你的传感器种类单一专用芯片确实省心但凡是“一台设备兼容多种温度传感器”这种需求通用高精度Σ-Δ ADC反而是更稳的路径。你可以通过软件切换配置寄存器来适配不同的传感器这是专用芯片不具备的能力。2. 热电偶通道的满量程计算与输入配置2.1 先把满量程算清楚再决定PGA增益AD7124的模拟输入满量程不是固定的它取决于参考电压和PGA增益。双极性模式下的基本关系是满量程电压范围 ±Vref / PGA假设我用一颗低温漂2.5V外部基准PGA设为32那么双极性量程就是±78.125mV。K型热电偶0到1000°C大概输出0到41mV在这个量程下占一半多一点余量充足。如果PGA设成64量程压缩到±39mVK型到800°C附近就顶到上限了读出来会一直是一个饱和值根本看不出温度继续升高。再回头算有效分辨率。2.5V基准、PGA32时1个LSB对应的电压约为156.25mV / 2^24 ≈ 9.3nV这个数值看起来极其夸张但实际系统噪声远高于一个LSBAD7124在低数据速率下的有效分辨率通常是18到20位量级。热电偶信号本身每1°C才几十微伏只要电路噪声控制在几微伏温度分辨率做到0.1°C以内是没有问题的。各类型热电偶的满量程适配我是按经验分档的热电偶类型约1000°C时热电动势推荐PGA满量程余量K型约41mV32较大J型约58mV32略紧可考虑16E型约76mV16E型信号较大32会饱和T型温度范围到400°C32余量足够N型约39mV32适中R型约11mV64余量充足S型约10mV64余量充足B型约6mV64或128信号很小适合高增益注意E型热电偶是八种里电动势最大的32倍增益在1000°C时几乎要挤爆满量程所以E型通道我会单独配置成PGA16。R、S、B型相反信号小需要提高PGA或者降低数据速率来换取稳定读数具体看你的温度测量范围。2.2 热电偶输入要接成差分别图省事接单端AD7124的每个配置寄存器都可以独立选择AINP和AINM引脚对。热电偶一定要接成差分输入原因很直接热电偶的输出电压是以冷端为参考的相对量如果其中一端直接接到地就等于把冷端参考点硬绑到了板的模拟地上冷端温度测量就失去了意义引线上的共模干扰也会直接进入转换结果。差分接法下热电偶的两根线分别接AINP和AINM信号是两者之差长线上感应的共模噪声可以被ADC的差分输入结构抑制掉。布线上还要注意一点热电偶的输入走线要尽量短并且与板上的数字信号线、SPI时钟线保持距离避免高频噪声耦合到毫伏级信号上。断偶检测是我比较看重的一个功能。把AD7124的烧毁电流源打开正常工作时这个电流流过热电偶只会产生微小的附加电压一旦热电偶断开输入引脚会被拉到电源轨数据接近满码程序里看到这个特征值就能立刻报“传感器开路”。工业现场非常需要这个能力很多出问题的工况其实不是电路坏了而是探头线断了。3. 八种热电偶的电压档位与线性化实现3.1 一张表先看全八种热电偶的性格差异八种热电偶说白了就是八种金属配对每种都有自己的温度范围、电压输出大小和非线性特征。调试时必须知道它们各自的脾气否则配置增益和查表方向都容易出错类型大致测温范围0°C附近灵敏度特点K型-200°C到1370°C约41μV/°C通用型工业上最常用J型-210°C到1200°C约52μV/°C灵敏度较高适合中低温T型-200°C到400°C约43μV/°C低温段性能好E型-200°C到1000°C约60-80μV/°C灵敏度最高信号最大N型-270°C到1300°C约39μV/°C抗老化性能好高温稳定R型0°C到1768°C约10-13μV/°C高温精密测量S型0°C到1768°C约10-13μV/°C高温标准型B型0°C到1820°C低温段几乎为零只适合高温段测量这里要特别提醒B型热电偶它的电动势在600°C以下非常小常温下基本等于零你在室温环境测B型会得到完全没意义的读数反而容易被误判成“传感器短路”。B型就是给高温准备的选型时不要拿它测低温。3.2 线性化方案查表插值和NIST多项式怎么选热电偶的电压和温度不是线性关系K型在低温段和高温段的斜率能差出好几个百分点直接按固定比例换算误差会非常大。工业上最常用的两条路是查表插值和分段多项式拟合。查表法最直观把某类型热电偶的分度表存成数组温度作为索引用二分查找定位区间再做线性插值。这个方法占Flash比较大但实现简单调试也方便。我给个插值函数示意float tc_table_interpolate(const float *v_table, const float *t_table, int n, float voltage) { int lo 0, hi n - 1; if (voltage v_table[0]) return t_table[0]; if (voltage v_table[n-1]) return t_table[n-1]; while (hi - lo 1) { int mid (lo hi) 1; if (voltage v_table[mid]) lo mid; else hi mid; } float ratio (voltage - v_table[lo]) / (v_table[hi] - v_table[lo]); return t_table[lo] ratio * (t_table[hi] - t_table[lo]); }多项式法对应的是ITS-90国际温标给出的分段多项式每个类型的热电偶在一段温度范围内给一组系数用霍纳算法逐段求值。它的优点是省Flash、精度高但系数表很长代码里判断分段边界稍不注意就会算错。我自己在工程上用的折中方案是把NIST多项式系数做成常量数组放在Flash里运行时先根据温度和电压判断落在哪个分段再调用统一的求值函数。这样既不用存几千条分度表又能把误差控制在0.1°C级别。其实对小批量设备我更推荐先用1°C间隔的查表法把系统跑通等稳定了再考虑换多项式。因为查表法出问题容易定位多项式法一旦系数抄错一段出来的温度曲线会在分段点出现明显跳变排查起来反而费劲。4. 冷端补偿的完整工程实现4.1 冷端补偿到底在补偿什么热电偶测得永远不是“热端的绝对温度”而是热端和冷端两个接点之间的电动势之差。冷端通常在接线端子附近温度随环境变化这个变化的电动势会叠加进测量结果导致热端温度跟着漂。补偿的逻辑可以类比成尺子测布尺子的零刻度没有对准布料起点你读出来的数字天然少了一段需要把起点偏移量加回去。热电偶的“起点偏移”就是冷端温度对应的热电动势。完整计算公式是E_total E_measured E_ref(T_cold)其中E_ref(T_cold)是冷端温度对应的热电动势。拿到E_total之后再用分度表或多项式反查热端温度。两个方向都要查先温度查电动势再电动势反查温度缺一个都不对。4.2 冷端温度用谁测NTC、Pt100还是内部温度传感器AD7124内部自带温度传感器用来做冷端补偿理论上可行但实际我不太推荐作为主方案。内部传感器测的是芯片结温而热电偶的冷端参考点在外面端子附近两者之间隔着PCB热阻和板上发热元件误差能达到好几摄氏度。它的作用更适合做开机自检和粗略板温监控。更靠谱的做法是在热电偶接线端子旁边放一颗Pt100或者Pt1000用AD7124的另一个通道配上激励电流源去测量。这样冷端温度传感器和热电偶冷端处于同一块铜皮上热耦合足够好。如果成本敏感用1%的NTC也能做但NTC老化后阻值会漂长期精度不如Pt100。这里我多说一句工程判断冷端温度传感器本身的精度当然重要但真正决定冷端补偿做得好不好的是传感器和热电偶端子之间的热一致性。传感器贴在端子正下方中间铺铜周围没有电源芯片等热源干扰比单纯换一颗高精度传感器带来的收益大得多。4.3 补偿误差传播冷端1°C偏差为什么会导致1°C测量误差很多人会低估冷端补偿的误差影响。以K型热电偶为例冷端温度误差1°C对应的热电动势误差约41μV而41μV在热端造成的温度解析误差大约也是1°C。也就是说冷端补偿的误差几乎是1:1传递到最终测量结果里的。如果你用内部温度传感器做冷端补偿它有±2°C的误差最终整体测温误差先天就背着±2°C。想做到整机±1°C以内冷端这一环必须把误差压到0.5°C以下传感器选型、PCB热设计、校准三点都得跟上。这也是为什么我在这一章反复强调“冷端传感器位置比精度更优先”。4.4 补偿代码的落点两次查表一次加法冷端补偿的代码核心非常短本质就是前面那个公式。我把它封装成独立函数避免散落在业务逻辑里float cold_junction_compensate(float v_measured, uint8_t tc_index, float t_cold) { /* 1. 根据冷端温度查该类型热电偶的电动势 */ float v_cold tc_voltage_from_temp(tc_index, t_cold); /* 2. 加上冷端电动势得到热端对应的总电动势 */ float v_total v_measured v_cold; /* 3. 用总电动势反查热端温度 */ return tc_temp_from_voltage(tc_index, v_total); }注意第三步不能用“总电动势乘以灵敏度”这种近似必须反查否则温度越高误差越大。t_cold的来源可以是恒温槽校准后的Pt100通道读数也可以是NTC标定后的温度值反正只要接口统一上层逻辑不用改。5. Pt100三线制与四线制从接线到寄存器配置5.1 Pt100是电阻信号得先解决“怎么给它供电”Pt100本质上是一个随温度变化的电阻0°C时100Ω100°C时约138.5Ω。ADC读的是电压所以必须给Pt100提供一个激励电流把电阻变化转换成电压变化。最简单的方式是恒流源激励但恒流源本身有温度漂移漂了之后电压跟着漂测温精度就保不住。工程上更稳的是比例测量激励电流同时流过Pt100和一个精密参考电阻R_REFADC的参考电压直接取自R_REF两端。这样一来ADC输出代码正比于Pt100与R_REF的比值R_rtd R_REF × (ADC_code / full_scale)激励电流的绝对大小被约掉了电流源的漂移自然消除。这一点是RTD测量链路里最重要的设计原则AD7124的外部基准输入引脚就是为了配合这种方法。5.2 四线制接线电流线和感应线分开四线制的原理一句话就能说清电流走两根线电压测量走另外两根线感应线回路几乎没有电流所以导线电阻上产生不了压降引线电阻误差完全被隔离。接线时把电流激励接在Pt100两端电压检测线直接从Pt100两端独立引出到ADC的AINP和AINM。这样哪怕传感器离主板有几十米线阻几十欧姆也几乎不影响测量结果。代价是现场要多拉一根线成本稍高。四线制的寄存器配置相对简单选择一个通道配置成差分输入打开激励电流源基准选外部基准PGA根据激励电流大小来定。比如用500μA激励0°C时Pt100上约50mV100°C时约69mV选PGA16或32都合适分辨率足够。5.3 三线制接线两个相等电流源抵消引线电阻三线制是为了解决四线制“线多、成本高”的妥协方案。Pt100现场往往只引出三根线一端一根另一端两根。AD7124支持用两个匹配的激励电流源一个从Pt100的一端流入另一个从另一端流出两条引线上的压降方向相反在差分输入里正好抵消。这样即便导线电阻有几欧姆到几十欧姆只要两个电流源严格相等误差也能控制在很小的范围。实际配置时要注意两点一是两个电流源的输出电流档位必须一致AD7124的IO控制寄存器里能配置电流源输出引脚和电流大小二是两个电流源要同时开启否则单边激励一开引线电阻直接变成测量误差三线制的意义就没了。三线制的残余误差主要来自电流源失配。如果两个电流源之间差0.1%单根引线电阻10Ω那残余误差就是10Ω×0.1%10mΩ折合Pt100温度约0.025°C完全可接受。这就是为什么三线制在工业现场很流行成本低且精度够用。5.4 阻值到温度的换算查表或者用公式拿到了Pt100的阻值下一步就是换算温度。我推荐直接用Callendar-Van Dusen公式它在0°C以上是一个简洁的二次式R(T) R0 × (1 A×T B×T²)R0100ΩA3.9083×10⁻³B-5.775×10⁻⁷。0°C以下才需要加上C项。如果只测0到200°C区间这个二次式误差小于0.1°C足够应付绝大多数工业场景。代码里做一遍浮点运算就行比查表更省Flash。6. STM32工程源码的组织方式6.1 源码模块划分驱动层和应用层分开STM32端的代码我分成五块这样后续加通道、换传感器类型不会牵一发动全身src/ ├── spi_driver.c SPI外设初始化与收发 ├── ad7124_driver.c AD7124寄存器读写、配置、读取数据 ├── tc_linear.c 热电偶分度表/多项式与冷端补偿 ├── rtd_linear.c Pt100阻值-温度转换 └── app_measure.c 通道轮询、数据组装、诊断ad7124_driver.c只负责“把寄存器的值写对、把数据读回来”不掺业务逻辑。tc_linear.c和rtd_linear.c只做数学换算。app_measure.c负责决定某个通道当前是K型热电偶还是三线制Pt100调度整个测量流程。这样谁出问题就去查哪个文件调试效率高很多。6.2 SPI通信和AD7124初始化序列SPI驱动不复杂注意AD7124支持的最高SPI时钟频率要留裕量我一般配到2MHz确保长线和信号质量不好时仍然不出错。AD7124的寄存器读写流程是先发一个命令字节命令字节里包含读写方向、寄存器地址和后续数据长度然后收发数据。DRDY引脚接STM32的外部中断引脚数据准备好时拉低触发STM32读取。初始化序列要严格遵循顺序我调试时吃过的亏就是上电后立刻配置寄存器结果读到一堆复位默认值原因是芯片还没准备好。标准做法是先做一次复位操作等待一段时间再写ADC控制寄存器使能转换然后是各通道的配置寄存器和滤波寄存器。这里给出初始化流程的骨架具体寄存器位定义建议对照AD7124数据手册里那几张寄存器图来填值十六进制组合数太多了只看博主文章记数字容易记错void ad7124_init(void) { /* 1. 复位 */ ad7124_reset(); /* 2. 等待芯片自复位完成 */ delay_ms(10); /* 3. ADC控制双极性、连续转换模式、外部基准 */ ad7124_write_reg(AD7124_REG_ADC_CTRL, value); /* 4. 通道0K型热电偶AIN0/AIN1差分PGA32 */ ad7124_write_reg(AD7124_REG_CFG0, value_cfg_tc_k); /* 5. 滤波器sinc4输出速率50sps */ ad7124_write_reg(AD7124_REG_FILTER0, value_filter_50sps); /* 6. 通道1Pt100四线制激励电流源开PGA16 */ ad7124_write_reg(AD7124_REG_CFG1, value_cfg_rtd); ad7124_write_reg(AD7124_REG_IO_CTRL, value_iocontrol); /* 7. 启动连续转换 */ ad7124_write_reg(AD7124_REG_ADC_CTRL, value_with_conversion_start); }6.3 主流程DRDY中断触发读取按通道类型分派数据读取我用DRDY下降沿触发外部中断。每次中断到来程序依次读取各个使能通道的数据寄存器再根据当前通道的传感器类型去调用不同的处理函数。核心主循环大致这样void ad7124_isr_handler(void) { for (ch 0; ch CH_NUM; ch) { uint32_t raw ad7124_read_data_reg(ch); float voltage ad7124_code_to_voltage(raw, pga[ch], vref); if (channel_type[ch] SENSOR_TC) { float t_cold read_cold_junction_temp(); float temperature cold_junction_compensate(voltage, tc_type[ch], t_cold); store_result(ch, temperature); } else if (channel_type[ch] SENSOR_RTD) { float resistance rtd_resistance_from_code(raw, rref); float temperature rtd_resist_to_temp(resistance); store_result(ch, temperature); } } }read_cold_junction_temp()内部再去读一次冷端Pt100通道这个冷端温度不需要每次热电偶读取都重新转换我实际做的时候是每10次热电偶读取更新一次冷端变化没那么快没必要增加负担。6.4 上电初读的数据先丢掉几个这里必须提醒连续转换模式刚启动时sinc滤波器需要建立时间前几个数据点是不稳定的。尤其sinc4滤波器建立时间更长。正确做法是初始化完成后等待DRDY连续触发几次把前几个采样数据直接丢弃再从后面的数据开始参与温度计算和校准。刚开始我图省事没丢结果板子上电前几十秒的温度值明显有下冲排查了半天才发现是滤波器稳定时间的问题。7. 校准、误差预算和实测踩坑记录7.1 系统校准流程出厂每块板子都该做一遍AD7124支持失调校准和增益校准校准结果会写进偏移寄存器和增益寄存器。失调校准的做法是把通道输入条件设置为“零输入”比如差分两端接同一个电位然后启动系统失调校准。增益校准则需要给通道输入一个已知的精密电压比如标准毫伏源输出100mV然后启动增益校准。如果你的板子对精度要求高建议出厂时用恒温槽做一次整体标定把探头分别放在0°C和100°C的恒温槽里记录ADC输出然后用这两组数据修正线性化的截距和斜率。这样能把ADC本身的失调、参考电压误差、传感器偏差一次性校正掉。有些设备为了省成本跳过了这一步结果同一批次板子之间温差能差出1°C以上现场排查起来很麻烦。7.2 误差预算表测温不准不一定是ADC的锅调试时我习惯先列一张误差预算表把各个环节的典型误差和控制手段写清楚这样出了问题能快速判断问题出在哪个环节误差来源典型量级主要控制手段参考电压温漂数个ppm/°C选低温漂基准做好基准供电滤波ADC量化与噪声几μV降低数据速率选sinc4或sinc3滤波冷端温度误差0.1-2°C冷端传感器紧贴端子避免板上热源引线电阻三线制残余十几mΩ保证两个电流源匹配热电偶分度表插值误差0.05-0.1°C提高查表密度或使用NIST多项式电源纹波和地噪声幅度不定模拟地和数字地单点连接滤波7.3 我踩过的四个实际坑第一个坑是热电偶通道接成单端。我把配置寄存器的通道选择位设置成单端输入模式只接了一个引脚结果读数稳定但温度偏差很大因为共模噪声全进来了。后面改成差分输入问题立刻消失。第二个坑是E型热电偶用了PGA32。读数到了900多°C之后就顶在满码上不涨了第一反应以为是热电偶线性化有问题后来算了算满量程才发现是PGA选大了导致信号饱和。这个前面也提到过改成PGA16后一切正常。第三个坑是冷端传感器放错了位置。最初把Pt100放在了板子角落离热电偶端子隔了差不多两厘米板子上一块DC-DC芯片发热后冷端补偿结果明显跟着漂。后来把Pt100移到端子正下方中间用铺铜连接温度读数才稳定下来。这验证了我前面说的“热一致性比传感器精度更关键”很多冷端补偿做不好的板子根子都在布局。第四个坑是三线制Pt100配置时只开了一个激励电流源。我在调试日志里发现电阻值总是比万用表实测偏大30多Ω像极了引线电阻直接串进了测量回路。检查IO控制寄存器才发现两个电流源的输出配置没写全只配置了一个另一个没使能。这个问题在代码层面非常隐蔽因为程序不报错只有偏差数值能提示你方向。排查时先用短路线替换Pt100如果读数接近0Ω而不是接近Pt100的100Ω就能确认是激励配置问题。后面调试顺利多了的基本经验是每个通道的配置都先在寄存器回读验证一遍别急着读数据。AD7124支持回读配置寄存器上电初始化完成后把关键寄存器内容打印出来跟预期比对很多配置错误几分钟就能揪出来比对着输出数据猜半天强得多。如果让我重新做一版我最想改进两处一是冷端Pt100通道在PCB上做成独立信号采集区域四周加一点隔热处理减少板上发热源影响二是把B型热电偶的低温段在出厂配置里默认屏蔽掉省得现场误用后得到无意义温度值扯不清。做多类型传感器兼容的系统硬件和代码都只是基础真正决定长期稳定性的往往是这些边界情况处理。