嵌入式与HVAC温度监测实战:NTC选型、ADC采样到Modbus调通

发布时间:2026/10/22 6:37:37
嵌入式与HVAC温度监测实战:NTC选型、ADC采样到Modbus调通 做嵌入式这块的跟温度传感器打交道基本是家常便饭。我最近在一个暖通控制器的项目里就用了一颗 PJ85718DM 的 NTC 热敏电阻配一颗 R7FA4L1BD4CFPRA4L1 系列做主控把本地板温和远端多个房间的温度监测做进同一套系统。这活儿听着简单实际落地的时候从分压电阻选型、ADC 采样滤波到 RS485 总线上的 Modbus 轮询每一步都有不少能让人加班到深夜的细节。这篇就把我从选型到调通的全过程拆开聊聊尤其是硬件计算和软件滤波那部分。如果你也在做嵌入式测温、智能楼宇或者 HVAC 控制应该能直接抄作业。1. 项目拆解本地与远程温度到底要解决什么问题1.1 这个标题背后的真实需求先看标题里的两个关键词嵌入式和 HVAC。这两个场景对温度监测的要求其实不太一样。嵌入式设备侧重点往往是“贴身测量”。比如控制器主板上的温度、功率器件附近的散热温度、电池包的温度。这类测点离 MCU 近传感器可以直接用板上的 ADC 采集典型应用就是“本地温度监测”。它要解决的是设备自身的保护、控制补偿和故障预警。HVAC 场景就完全不是一回事了。暖通系统里一个控制器往往要管好几层楼的温度回风温度、送风温度、厂房不同分区的温度这些测点可能分布在几十米甚至上百米之外不可能都拉一根模拟线到主控板。这时候需要的是“远程温度监测”也就是把温度采集节点布到远端再通过总线把数据汇总回主机。标题里的两个器件正好对应这两条路径PJ85718DM 作为本地温度感知元件R7FA4L1BD4CFP 作为主控把本地 ADC 采集和远程总线读取统一处理。整套系统的价值在于本地快、远程全一个主控就能把设备自己的“健康温度”和系统环境的“空间温度”都管起来。1.2 架构选型本地用 ADC远程走 RS485 总线确定需求后架构其实没太多悬念本地温度用主控内部 ADC 直接采远程温度用分布式小节点采集再通过 RS485 总线把数据送回主控。为什么远程不用无线很多做智能家居的人喜欢用 Zigbee 或者私有 2.4G但 HVAC 项目里可靠性永远是第一位尤其是风机盘管、冷站这类控制场景一旦通信不稳温度数据一断控制策略就容易乱。RS485 有线方案布线成本不高抗干扰强点对多点轻松支持几十个节点Modbus RTU 协议栈又是公开的调试工具满地都是可以说是 HVAC 领域的“标准答案”。主从关系也明确R7FA4L1BD4CFP 这边做主机定时轮询总线上每一个远程温度节点本地 PJD 85718DM 通过 ADC 通道采样。两路数据汇聚后统一参与控制逻辑。1.3 为什么是 PJ85718DM 和 R7FA4L1BD4CFP先说传感器。PJ85718DM 本质是 NTC 热敏电阻标称 10kΩ25℃B 值 3950K。这是行业内最主流、货源最稳定、数据模型最成熟的规格。做 HVAC 的温度测量典型的范围就是 -20℃ 到 80℃ 左右这个区间正好是 10k 3950 这颗 NTC 的“甜蜜区”线性度不算最好但配合查表或者公式校正工程精度完全够用。再说 MCU。R7FA4L1BD4CFP 属于 RA4L1 系列这个系列让我最看中的点是核是 Cortex-M33主频 100MHz搞温度采集和一轮 Modbus 轮询性能富余得像开跑车买菜。内部集成 14 位 ADC这个精度在通用 MCU 里比较少见普通 12 位 ADC 在 NTC 分压测量时25℃ 附近分辨率会略吃力14 位就能多出不少余量。低功耗能力不错如果项目后续要改用电池供电的室内温控面板也能撑得住。串口资源充足至少两个 UART一个用来和 RS485 收发器接远程总线一个留着调试输出。这里我想强调一下如果只是应付课题或者 Demo随便一个 8 位单片机加 10k NTC 也能测出温度。但工程产品不是测出“能变化”的温度就完了要在全温度范围内稳定、可重复、抗干扰这就要看 ADC 位数、参考电压设计、通信可靠性和软件滤波了。2. 硬件接线与关键参数计算2.1 NTC 分压电路上拉电阻到底怎么选NTC 是电阻型传感器MCU 没法直接读电阻必须通过分压电路把电阻变化变成电压变化。最常用的接法是参考电压加一个固定上拉电阻NTC 放低侧ADC 采样点取两者中间。我实际用的是 10kΩ ±1% 上拉、PJ85718DM 在下侧、VREF 直接接 3.3V 电源。温度升高时 NTC 阻值下降采样点电压下降ADC 读数也随之下降。电路简单且安全不会出现高温时采样电压超过参考电压的情况。上拉电阻的选择要算一笔账NTC 在 25℃ 时阻值是 10kΩ如果上拉也取 10kΩ那么 25℃ 时中点电压正好是 VREF 的 1/2整个测量量程的中间值给正负方向都留了充足的余量。如果量程偏向高温可以把上拉取小一点比如 4.7kΩ如果量程偏低温上拉取 20kΩ 更好。我这次用在 HVAC 环境-20℃ 到 80℃ 都有可能出现10kΩ 对 10kΩ 的对称配置最稳。下面这组数据就是我用 PJ85718DM 在 10k 上拉下算出来的关键点温度NTC 阻值计算采样点电压VREF3.3V14 位 ADC 读数0℃约 33.6kΩ约 2.55V约 1264525℃10kΩ1.65V819150℃约 3.59kΩ约 0.87V约 433080℃约 1.23kΩ约 0.37V约 1830从这张表也能看出一个重要结论ADC 值并不是线性对应温度的越到高温区温度变化 1℃ 导致的 ADC 变化越小。所以后端要么用对数公式要么用查表法不能简单拿 ADC 值做等比缩放。2.2 采样分辨率14 位 ADC 够不够用很多朋友会问温度测量到底需要多少位 ADC我用 RA4L1 的 14 位 ADC 算给你看。在 25℃ 附近NTC 的温度系数大约是 -4.4%/℃也就是 10kΩ 的阻值每升高 1℃ 大约下降 444Ω。在 10kΩ 上拉分压下1℃ 对应的电压变化量是dV/dT 3.3V × 10000 × (-444) / (10000 10000)² ≈ -0.366mV/℃14 位 ADC 满量程 3.3V 时1 LSB 约等于 0.2mV。也就是说25℃ 附近每摄氏度对应不到 2 个 LSB。如果不做任何处理单次采样分辨力也就 0.5℃ 出头。听起来好像不够但别忘了ADC 采样后面还有软件滤波和过采样。我实际做 8 次采样取中值再加滑动平均等效分辨力可以做到 0.05℃ 的稳定显示完全满足 HVAC 控制的精度需求。如果你用的是 12 位 ADC单次分辨力会更差就必须依赖滤波和一定倍数的过采样了这也是我选 RA4L1 的一个重要原因。2.3 本地 ADC 通道的引脚保护与滤波ADC 输入引脚不要直接裸奔接 NTC 分压点我习惯在采样点和 MCU 引脚之间串一个 1kΩ 电阻再对地并联一个 100nF 电容构成简单的低通滤波。这个 RC 有两个作用一是滤掉高频耦合噪声二是限制引脚的灌电流防止外部静电或者误操作把 MCU 引脚打坏。如果你的板子工作环境有继电器、接触器、变频器这类强干扰源最好再在采样点加一颗 3.6V 的 TVS 管到地防止瞬间浪涌传到 ADC 引脚。HVAC 产品里电源开关瞬间的毛刺非常常见这里省一颗管子的钱后期现场调试就要多花十倍时间。2.4 远程节点与 RS485 收发电路远程温度采集节点这边硬件也不复杂。每个节点可以理解为一个独立的测温小板一颗 PJ85718DM 或同规格 NTC加一个小 MCU 做从机再加一颗 RS485 收发器。RS485 收发器供电我用 3.3V 版本和 RA4L1 的串口电平直接兼容省去电平转换。A、B 差分对之间要加 120Ω 终端电阻这个电阻的位置有讲究只能加在总线最远的两端而不是每个节点都加。我见过有同事图省事在每一块板上都焊了 120Ω结果总线阻抗被拉得很低通信距离和抗干扰能力双双下降。另外现场长距离通信我强烈建议做隔离。用带隔离的 RS485 模块或者至少把收发器的地通过磁珠和控制器数字地单点连接。楼宇里不同设备经常是不同回路供电地电位差几十伏都可能不隔离轻则通信乱码重则烧收发器。3. 软件实现从原始 ADC 到稳定的温度值3.1 采样策略短通电、多次采、周期执行NTC 有一个被很多人忽略的特性自热效应。电流流过 NTC 会发热发热之后阻值会变测出来的温度自然偏高。PJ85718DM 的功耗只要超过 0.1mW 就会产生可感知的自热误差。算一下我上面那个电路在 25℃ 时的功耗回路电流约 0.165mANTC 功耗约 0.27mW在静止空气里大约会造成 0.1℃ 左右的温升。单看不大但如果你让 NTC 一直通电这个误差会稳定叠加让设备默认“体温偏高”。解决思路是用“间歇式激励采样”平时断开采样回路等到要测温度的瞬间才通电、延时几毫秒、快速采几次、然后立刻关断。结合低占空比平均自热功率能降两个数量级误差几乎可忽略。实际实现时我用一个 1ms 软件定时器维护温度采样状态机空闲状态等待 500ms → 进入“预热”状态导通 NTC 分压电路并等待 10ms → 连续采集 8 次 ADC每次间隔 1ms → 关断激励 → 进入滤波计算状态。这样既保证电压稳定又不让 NTC 一直热着。3.2 滤波算法中值加滑动平均是成本最低的稳值方案采集到的 ADC 原始值不滤波直接换算温度你会看到数字在 0.5℃ 范围乱跳尤其在设备带继电器的 HVAC 场景里跳得更欢。我常用的组合是先做一次中值滤波再做一次滑动平均。为什么要先中值再平均中值滤波对尖峰脉冲特别有效比如继电器吸合瞬间耦合进来的一个高幅值毛刺取中间值可以直接干掉滑动平均则能把白噪声压平。顺序反过来的话一个尖峰会被平均算法“摊”到连续几个采样点里造成一小段平台期表现就是不自然的数字粘连。代码骨架大概是这样的#define SAMPLE_BUF_LEN 8 static uint8_t compare_uint16(const void *a, const void *b) { return (*(uint16_t*)a *(uint16_t*)b) ? 1 : -1; } uint16_t median_value(uint16_t buf[], uint8_t len) { uint16_t tmp[SAMPLE_BUF_LEN]; memcpy(tmp, buf, len * sizeof(uint16_t)); qsort(tmp, len, sizeof(uint16_t), compare_uint16); return tmp[len / 2]; } uint32_t adc_sum 0; uint16_t last_median 0; void temp_sample_filter(uint16_t new_adc) { static uint16_t window[4]; static uint8_t idx 0; window[idx] new_adc; idx (idx 1) % 4; uint32_t sum 0; for (int i 0; i 4; i) sum window[i]; last_median sum / 4; }实际采集完 8 次原始值取中值得到一个原始 ADC 数再进入 4 点滑动窗口做平均。这套组合代码量不大但应对 HVAC 现场的开关噪声和电源纹波绰绰有余。3.3 温度换算B 值公式和查表法哪个实用拿到稳定的 ADC 值后第一步是根据分压电路把 ADC 读数还原成 NTC 当前阻值。我的 NTC 在低侧公式是R_ntc R_pull × (ADC / ADC_FULL) / (1 - ADC / ADC_FULL)然后用 B 值公式换算温度T 1 / (1 / T25 ln(R_ntc / R25) / B) - 273.15对应代码#define R25 (10000.0f) #define BETA (3950.0f) #define T25 (298.15f) #define ADC_FULL (16383.0f) #define R_PULL (10000.0f) float adc_to_temperature(uint16_t adc) { if (adc ADC_FULL) adc ADC_FULL - 1; float ratio (float)adc / ADC_FULL; float r_ntc R_PULL * ratio / (1.0f - ratio); if (r_ntc 1.0f) return -273.15f; float t_k 1.0f / (1.0f / T25 logf(r_ntc / R25) / BETA); return t_k - 273.15f; }B 值公式的优点是简单、依赖参数少工程上也够用缺点是它在全量程范围的逼近误差不是均匀的高温段偏差可能略大。如果你的产品要覆盖 -40℃ 到 105℃ 且希望整体误差控制在 0.1℃ 级别建议改用 Steinhart-Hart 方程或者直接上查表加线性插值。查表法的本质是用空间换计算时间。把整段量程按 1℃ 间隔预存一份 ADC 值表运行时在表里找到当前 ADC 对应的区间做线性插值。当 MCU 没有 FPU 或者不想每次调用 logf 的时候这个方案最优。RA4L1 的 M33 内核带 FPUlogf 也快所以我在这个项目里直接用了公式法代码可读性更好。3.4 本地与远程温度的数据聚合本地温度和远程温度采集完不能各算各的要在应用层统一成一种数据格式。我给每个温度点定义了一个结构体温度值统一放大 10 倍存成 int16_t也就是寄存器里存 255 表示 25.5℃。这么做的好处是避免浮点数在通信结构体里带来的字节序问题Modbus 寄存器也天然适配这种格式。应用层拿到所有温度点后集中做三件事超限报警判断、温度参与控制逻辑、历史数据缓存上传。报警阈值我放在配置区比如送风温度超过 50℃ 报警回风温度低于 5℃ 报警不同项目改配置不改代码。4. 远程温度采集Modbus RTU 协议实现4.1 帧结构、功能码与寄存器规划远程节点端我让每个从机实现标准的 Modbus RTU 从站功能主从之间最常用的功能码是 0x03 读保持寄存器。我规划的温度寄存器地址为 0x0100寄存器内容是带一位小数的温度值int16_t取值范围在 -400 到 1250换算出来就是 -40.0℃ 到 125.0℃。一个典型的读温度请求帧是字段值从机地址0x01功能码0x03起始寄存器高字节0x01起始寄存器低字节0x00寄存器数量高字节0x00寄存器数量低字节0x01CRC16 低字节0x0BCRC16 高字节0xFC从机回的应答帧则是地址、功能码、字节数、寄存器高字节、寄存器低字节、CRC。比如回传 0x00 0xFF就表示 25.5℃。4.2 CRC16 校验代码直接抄Modbus RTU 的 CRC 计算是 16 位 CRC多项式 0x8005初始值 0xFFFF但实现的时候是用查表或逐位异或 0xA001 的方式。我放一个逐位版本代码不多理解也直观uint16_t modbus_crc16(const uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; for (uint16_t i 0; i len; i) { crc ^ data[i]; for (int bit 0; bit 8; bit) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }注意协议规定 CRC 在帧里的字节序是低字节在前、高字节在后。我踩过这个坑看着手册上例子算出来的 CRC 一模一样发出去就是不对后来才发现高低温字节序放反了。4.3 主机轮询逻辑超时、重试和离线判断R7FA4L1BD4CFP 作为主机轮询逻辑说穿了就是一个定时触发的状态机。每个远程节点给它分配一个状态在线、请求中、离线。主循环里只处理当前节点不阻塞等待。我的轮询参数总线波特率 9600bps单帧请求约 8 字节物理传输时间 10ms 左右应答最长 8 字节加上从机处理时间整个问答周期给 100ms 超时。超时之后不是立刻判死而是记一次失败连续 3 次失败才标记离线。这样现场短暂干扰不会让系统动不动就把一个点位剔除。如果节点数量变多比如超过 10 个轮询延迟要改成分批策略每次主循环最多处理两个节点甚至把温度采集频率降到 2 秒一轮。HVAC 场景对温度实时性的要求没那么苛刻1 秒一次已经算很快了轮询策略不需要贪快。4.4 总线拓扑与通信速率的权衡RS485 是差分总线但不是说随便拉根线就能跑得远。HVAC 楼宇里的情况往往是大楼装修完了再补布控制线线缆质量参差不齐。我这次的远程温度节点布在一个约 150m 的走廊区域用了 0.5mm² 双绞屏蔽线9600bps 下通信非常稳定。如果你把速率提到 38400bps距离一定要相应缩短否则总线信号反射会明显恶化表现为偶尔一个节点读不到、或者数据偶发错位。节点数量上RS485 收发器一般都能支持 32 个标准负载实际我建议单条总线不要超过 20 个节点留出噪音和地址分配的余量。一旦到 20 个以上就该考虑分两条总线或换更高负载能力的收发器了。5. 误差分析与工程校准5.1 自热误差、连接线电阻和环境散热现场实测时我发现一个典型现象把温度探头用扎带固定在铜管上时测出来总是偏高一些这不是传感器坏了而是铜管本身温度高导热效应让 NTC 直接感受到了设备表面温度。这也是为什么 HVAC 测温点位要和被测表面做隔热或者干脆用套管悬空安装测的是空气温度而不是壁面温度。自热误差前面提过一次这里补一个定量概念PJ85718DM 这类玻璃封装 NTC 在静止空气中的耗散系数大约是 2~5mW/℃我电路里 0.27mW 的功耗换算下来温升约 0.05~0.1℃属于可接受范围。但如果把分压电压换成 5V、上拉电阻又取太小功耗翻几倍自热会直接变成 0.5℃ 级误差这就不能忍了。5.2 电阻误差、参考电压误差与两点校准硬件上最容易被忽略的误差来源是上拉电阻的精度和参考电压的实际值。1% 的 10kΩ 上拉电阻在 25℃ 附近大约会造成 0.25℃ 到 0.3℃ 的固定偏差而 3.3V 电源如果是普通 DC-DC 输出实际值可能在 3.28V 到 3.35V 波动这也会直接进入 ADC 测量链路。考虑到这两点软件里不能死板地拿“设计值”算温度。我最终在设备出厂前做了一次两点校准用 0℃ 冰水混合物和 50℃ 恒温水浴分别给设备一个基准读数然后按公式修正T_real T_raw - offset - slope × (T_raw - T_ref)这个公式里 offset 主要吸收固定电阻偏差slope 主要吸收 B 值和 ADC 增益偏差。做完校准之后整个测温链路在 -20℃ 到 80℃ 范围内的误差基本能控制在 ±0.3℃ 内HVAC 控制完全够用。5.3 断线检测和异常值处理NTC 测温系统里最怕的不是不准而是“测不出来但代码还在跑”。如果 NTC 开路分压点电压会被拉到接近 VREFADC 读数接近满量程如果短路ADC 读数接近 0。这两种情况本质上是传感器故障软件识别后应该直接上报错误状态而不是换算成某个离谱的温度去参与控制。我的处理策略是设定两个边界ADC 值大于满量程的 98% 判定传感器开路小于满量程的 2% 判定短路或分压电阻断路。异常状态下将该通道温度置为无效标志控制逻辑自动切换到安全模式比如 HVAC 系统保持当前输出而不是盲目加热或制冷。6. 实测遇到的坑与排查记录6.1 现象本地温度值在 0.5℃ 范围内不停跳动排查思路先判断是电源噪声还是采样时序问题。用示波器看 ADC 采样点的纹波发现分压点电压本身是稳的但继电器一动作ADC 值就出现一个毛刺。最终解法分两层硬件上在采样点加了 100nF 电容软件上把采样时间挪到继电器动作完成 20ms 之后再配合前面说的中值滤波问题彻底解决。这件事给我的教训是嵌入式测温不能只盯着 NTC 自身精度采样时机和外围噪声才是现场稳定性的关键。6.2 现象远程总线上偶尔一个节点读不到排查思路最初怀疑是节点地址冲突检查一遍地址配置没有重复于是怀疑信号质量用万用表量总线末端 A/B 之间电阻发现 120Ω 终端电阻缺失因为那一端是后补的节点板装配人员漏焊了。补上终端电阻后问题消失。这个现象很典型离终端最近的那个节点最容易不稳定因为总线末端没有阻抗匹配信号会反射叠加可能导致接收端的波形严重畸变。6.3 现象高温区温度偏差越来越大排查思路我一开始用的 B 值公式参数是从规格书抄的 B(25/50)3950。结果在 80℃ 实测软件显示比标准温度计低了差不多 1.2℃而 25℃ 附近误差很小。原因就是 B 值公式在超出标定区间后的逼近误差被放大。解决办法是改用 Steinhart-Hart 方程参数从规格书提供的三个典型温度点反算或者直接生成一个查表区间放到代码里。现在我做高温量程的 NTC 测量默认用三参数公式不再用单 B 值。6.4 排查表速查问题现象可能原因解决方案温度显示跳变继电器干扰、采样时序重叠采样避开开关动作引脚加 RC软件做中值滤波温度整体偏高NTC 自热、线缆过长电阻降低激励功耗缩短采样占空比检查线路电阻温度整体偏移固定值上拉电阻精度、参考电压偏差两点校准或改用高精度电阻高温区偏差大B 值公式近似误差改用 Steinhart-Hart 方程或查表法远程节点间歇离线终端电阻缺失、接地电位差补 120Ω 终端电阻采用隔离 RS485完全无通信A/B 接反、收发器方向未切换核对差分线序确认发送接收使能时序这个表基本就是我这次项目遇到的问题全集。最后有一个小体会想分享温度项目调试最容易忽略的其实是“参考电压校准”这一环。很多人拿 3.3V 当固定值测出偏差就去怀疑 NTC 和电路来回折腾好久才发现电源实际输出是 3.42V。后来我每次做测温都会先通过 ADC 读一遍内部基准或者用精密电阻测一下实际分压比把这步写进初始化流程里。测温这东西链路不长但每一环的误差都会老老实实叠加到最终结果上做工程少一点“差不多”现场就能少跑几趟。