搞嵌入式温度监测这个需求乍一看好像很简单——读个传感器、算个温度、显示出来完事。但真等你把设备装进HVAC系统里面对“本地温度”和“远程温度”两路采集需求的时候问题就来了一路探头就在主控板旁边另一路探头可能挂在几十米外的风管里这两路的电气环境、干扰程度、校准方式完全是两码事。这次的模拟项目就是在PIC18LF45K40做主控的方案里配上PJ85718DM前端模块把本地和远程温度采集这套逻辑完整落地。这套方案解决的核心问题其实很直白本地温度要准远程温度要稳两者还不能互相干扰。PIC18LF45K40这颗MCU本身带12位ADC、多个UART和SPI低功耗特性也合适做温控类应用算是顺手。而PJ85718DM在这里承担的是远程温度前端采集和信号调理的角色把远端探头的模拟信号先在源头处理一遍再通过总线发给主控。这样主控端的负担小远距离传输的抗干扰能力也更强。接下来把整个项目的拆解、硬件设计、软件流程、校准实测和踩坑记录都展开聊聊。1. 项目需求拆解与方案选型逻辑1.1 本地温度与远程温度的本质差异很多刚做HVAC控制器的工程师容易犯一个错误以为本地温度和远程温度只是传感器位置不同代码里换个通道读就行。实际上这两路的信号特征差异非常大。本地温度传感器离主控近走线短信号衰减和干扰都小直接用MCU的ADC读分压电压就可以。但远程温度传感器要走长线可能是屏蔽双绞线也可能是普通电缆走线槽沿途会耦合电机启动、继电器吸合带来的电磁干扰。这时候如果还是简单地把模拟信号拉到主控板ADC读数会在几个摄氏度之间来回跳根本没法用。这次项目里我做的第一件事就是把需求掰开本地温度给谁看远程温度给谁看。本地温度主要用于设备内部的过温保护、面板显示、以及PID控制中作为快速反馈项要求响应快、精度中等即可。远程温度用于风管温度监测、房间温度回传要求长期稳定、抗干扰、能适应长线传输。这两类的采集链路必须分开设计。1.2 为什么选PIC18LF45K40做主控PIC18LF45K40的选择基于几个实际考量。首先是供电电压LF后缀代表低电压版本工作在2.3V到3.6V之间而HVAC控制器板子很多是3.3V逻辑不需要额外电平转换。其次是ADC性能12位分辨率对于NTC热敏电阻的分压采集来说已经够用分辨率大约能到0.1摄氏度级别配合校准可以做到正负0.3摄氏度以内。另外这芯片内部有64KB Flash和4KB RAM跑一个完整的温度采集协议栈加上Modbus RTU从站程序存储空间也不算紧张。真正让我决定用它的原因是外设资源。PIC18LF45K40带三个UART和一个SPI本地温度走SPI接数字传感器远程温度走UART接PJ85718DM模块还留一个UART做调试串口接口分配非常从容。有些同价位的MCU外设没这么全做这种多路采集就得软件模拟串口稳定性和开发效率都受影响。1.3 PJ85718DM模块的角色定位PJ85718DM不是我随便选的它在这个项目里的定位是远程温度采集前端。这个模块集成了信号调理和数字化转换远程探头接入后直接在模块端完成模拟信号的放大、ADC转换和线性化处理然后通过总线协议输出数字温度值。这样一来长线上传输的不再是容易被干扰的模拟小信号而是抗干扰能力强的数字信号。模块供电我用了24V HVAC系统的常规电源然后板载降压到3.3V给模块逻辑部分。远程探头距离在30米内时这种供电方式完全没问题。超过50米的场景建议提高线径或者采用恒流源驱动方式但一般家用和轻型商用HVAC系统不会拉那么远。2. 硬件电路设计与关键参数计算2.1 本地NTC采集电路本地温度我用的是10k NTC热敏电阻B值3950这是HVAC行业里最常见的一类传感器采购方便、一致性也不错。电路结构是标准的串联分压NTC一端接参考电压另一端串联一个精密电阻后接地中间抽头进MCU ADC。分压电阻的选择需要算一笔账。NTC在25摄氏度时阻值10k在最关心的0到70摄氏度范围内阻值变化范围大约是33.5k到3.4k左右。为了让ADC输入电压在这段温度区间内保持较好的灵敏度我选的串联电阻是10k。计算很简单R为电阻值Vref为3.3V则ADC输入电压Vadc Vref * Rntc / (Rntc 10k)。在25摄氏度时正好是1.65V0摄氏度时约2.56V70摄氏度时约0.83V整个电压区间跨了1.7V对应12位ADC能占到约2100个码值平均每摄氏度约30个码完全够用。实际布局时参考电压我用的是MCU内部的2.048V固定参考而不是直接用VDD。因为HVAC板子上继电器动作时VDD会有波动直接用VDD做ADC参考会引入误差。内部固定参考电压的温度稳定性虽然不算极佳但在这个精度要求下表现远好于VDD。2.2 本地NTC的线性化与校准NTC的阻值和温度关系是非线性的这决定了软件里不能简单用比例换算。常见的处理办法有两种查表法或者Steinhart-Hart公式法。我采用的是查表加线性插值在固件里存储一张从零下20摄氏度到100摄氏度、每1摄氏度一个点的阻值对照表实际运行时先用当前阻值找到相邻的两个表项再做线性插值。这张表必须和实际使用的NTC批次匹配。我最初直接用厂商规格书里的标准表结果0到50摄氏度区间误差在0.5摄氏度左右偏大。后来把传感器放进恒温槽做了从5摄氏度到60摄氏度的9个标准温度点采样修正了B值和25摄氏度标称阻值这两个参数后整段误差才压到0.2摄氏度以内。校准的关键操作很简单在冰水混合物实际温度约0摄氏度中读取ADC值在30摄氏度温水中读取ADC值根据这两点的实际阻值反推B值和R25。算出之后直接写进固件里的配置区产品出厂前每台设备都做一次两点校准。这个方法比用单一B值公式整体平移准确得多因为B值在不同温度段本来就有细微变化。2.3 远程温度采集链路设计远程部分的核心是PJ85718DM模块配合PT1000铂电阻探头。选择PT1000而非NTC是考虑到远程探头的长期稳定性要求——HVAC系统的检修周期以年为单位NTC老化带来的漂移不好接受。而PT1000的阻值随温度变化线性度极好0摄氏度时1000欧每变化1摄氏度阻值变化约3.85欧。PT1000的采集由PJ85718DM内部完成模块输出的是已经线性化校正过的温度值。主控通过UART与模块通信我这里跑的是9600波特率的Modbus RTU协议8数据位、1停止位、无校验。之所以选Modbus而不是自定义协议一是模块原生支持二是以后如果想接入楼宇自控系统或者其他主站设备协议层面完全兼容不用重写。物理层用了RS485接口远程探头和模块放置在远端模块的RS485 A/B线接到主控的RS485收发器上。总线末端加了120欧终端电阻这是RS485的常规要求。曾经有段时间我为了省事没加终端电阻结果10米以上的通信偶尔就出现数据错误加上之后问题消失。2.4 长线供电与保护措施远程模块的供电我从主控板引出24V电源在模块端用一颗低压差稳压器降到3.3V。远程线缆我选的是四芯屏蔽线两芯供电、两芯RS485信号屏蔽层单端接地。这个屏蔽层接地位置有讲究在主控板机箱地单端接地不要在远端模块端也接地。两端接地容易形成地环路在雷击或大功率设备启动时会产生很大的共模电压轻则通信误码重则损坏接口芯片。线缆的压降问题也得算一下。PJ85718DM模块的功耗约60mA24V供电线缆如果用0.5平方毫米线径每米电阻约0.037欧30米来回共60米线阻约2.2欧压降不到1V24V降到大约23V稳压器完全能扛住。如果线径太细、距离太长模块供电电压跌破稳压器的低压差阈值通信就会间歇性掉线检修起来非常头疼。3. 软件架构与核心实现流程3.1 状态机驱动的采集任务调度温度采集程序我用了一个简单的状态机来调度而不是裸奔式的顺序轮询。整体分为五个状态本地采样、远程读取、数据滤波、故障自检、数据输出。状态机每100ms调度一次每个状态在本次调度中完成自己的工作然后切换到下一个状态。这样程序结构清晰也方便将来增加更多传感器通道时扩展。采集周期选择100ms是从两个方向权衡的。HVAC系统对温度响应的实时性要求并不苛刻温度变化本身是慢过程100ms的周期足够。而如果周期太短ADC连续采样会占用MCU太多时间影响其他任务周期太长则远端通信的响应延迟会累积。主循环里还嵌入了一个每秒一次的慢任务负责LED状态指示和看门狗喂狗。PIC18LF45K40的看门狗定时器在工业环境里一定要使能不然程序跑飞之后设备就会一直处于失控状态。我遇到过主控被干扰复位后外部传感器没初始化完成设备不工作的状况后来在启动流程里加了初始化标志检查才算彻底解决。3.2 本地NTC采集与温度计算代码本地ADC采样我做的是连续采集8次去掉最大值和最小值后取平均然后查表计算温度。8次采样对于NTC分压电路来说已经足够太多会拖慢响应太少则抗不过偶发干扰。ADC读取部分的代码示例uint16_t read_ntc_adc(void) { uint32_t sum 0; uint16_t samples[8]; uint16_t min_val 0xFFFF, max_val 0; uint8_t i; ADCON0bits.CH 0; // 选择AN0通道 for (i 0; i 8; i) { ADCON0bits.GO 1; while (ADCON0bits.GO); samples[i] (uint16_t)((ADRESH 8) | ADRESL); __delay_us(50); } for (i 0; i 8; i) { if (samples[i] min_val) min_val samples[i]; if (samples[i] max_val) max_val samples[i]; sum samples[i]; } sum - min_val; sum - max_val; return (uint16_t)(sum / 6); }有了ADC值之后需要反算NTC阻值。分压电路的公式是Rntc 10k * Vadc / (Vref - Vadc)其中Vadc adc_code * 2.048 / 4096。这里有个细节容易写错ADC的参考电压用的是内部2.048V而不是VDD 3.3V计算时必须用2.048V代入否则算出的阻值会整体偏移。查表插值代码uint16_t ntc_table[121]; // -20~100摄氏度对应的NTC阻值表 int16_t calc_temperature(uint16_t adc_code) { float vadc (float)adc_code * 2.048f / 4096.0f; float rntc 10.0f * vadc / (2.048f - vadc); for (int i 0; i 120; i) { uint16_t r_high ntc_table[i 1]; uint16_t r_low ntc_table[i]; if (rntc r_high rntc r_low) { float ratio (rntc - r_high) / (float)(r_low - r_high); return (int16_t)(-20 i ratio); } } return 0xFF; // 超范围错误标志 }3.3 远程模块Modbus读取流程远程温度读取流程基于Modbus RTU的03功能码读保持寄存器。PJ85718DM模块把温度值放在两个寄存器里一个存整数部分一个存小数部分。主控每次读取需要发送8个字节的请求帧然后接收7字节的响应帧最后做CRC校验。帧格式和关键代码如下uint8_t modbus_frame[8] {0x01, 0x03, 0x00, 0x01, 0x00, 0x02, 0xC0, 0x0B}; // 地址1, 功能码3, 起始寄存器地址0x0001, 读取2个寄存器, CRC校验高位0xC0低位0x0BCRC16是Modbus协议里必须自己实现的算法查表法速度最快。我这里给出一段标准的CRC16查表实现uint16_t modbus_crc16(uint8_t *data, uint8_t len) { uint16_t crc 0xFFFF; for (uint8_t i 0; i len; i) { crc ^ data[i]; for (uint8_t j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }接收处理要设置超时机制。我的做法是UART接收中断里逐字节接收每收到一个字节就重置一个10ms的软件定时器如果超过10ms没有后续字节就认为一帧结束。这个10ms的判断要和9600波特率的字节间隔匹配好。9600波特率下传输一个字节大约1ms正常的帧字节之间间隔不超过1.5ms10ms的超时完全够用。3.4 数据滤波处理策略温度数据的滤波我用的是滑动中值滤波加一阶低通。滑动中值滤波主要应对偶发的尖刺干扰比如继电器吸合瞬间对信号的冲击一阶低通则用来平滑传感器本身的细微波动。两个滤波器串联而不是只用一个是因为它们各自擅长抑制不同类型的问题。滑动窗口长度取5次。窗口太短对尖刺抑制不够窗口太长则信号延迟增大。5窗口中值滤波的效果是单次尖刺不会影响输出因为排序后尖刺数据会被剔除。一阶低通的公式是out out_prev k * (sample - out_prev)。k值我取0.3这个值的选取看系统对响应速度和平滑度的平衡。k太大则滤波效果不明显k太小则温度变化响应迟钝。用0.3时从25摄氏度跳到30摄氏度大约需要5到6个采集周期也就是0.5到0.6秒达到最终值在HVAC应用里这个延迟无关紧要。滤波过程也暴露过一个有意思的问题如果远程通信暂时中断模块返回的旧值会被滤波器保留导致系统在通信恢复前一直显示旧温度。后来我加了超时标志——超过5秒没有成功读取远程模块就在滤波器中把远程温度标记为无效并让输出直接使用最后有效值加上一个偏差提示这样面板上能看到状态异常而不是被一个看似正常的数值误导。4. 校准流程与实测数据分析4.1 温度校准的环境与方法校准是整个项目里最容易被低估的环节。很多人把传感器焊好、程序写完一测读数大概差不多就出货了。这种做法的隐患在于不同批次NTC的B值离散度很大同一个B值表套到不同批次传感器上误差能到1摄氏度以上。这次项目我搭建了一个简易校准环境一个保温桶装满水通过加热棒和冰袋调整水温分别在0、10、20、30、40、50、60摄氏度的标准点进行采样。虽然精度比不上专业恒温槽但对于正负0.5摄氏度的目标绰绰有余。标准温度计用的是工业级水银温度计最小刻度0.1摄氏度。每个标准点让设备稳定10分钟后再记录数据确保传感器和水的温度完全一致。这里有个实操细节NTC传感器要用热缩管或者导热硅胶做好防水直接泡水里容易进水导致读数偏低。我最初用普通绝缘胶带缠了一下泡了半小时胶带吸水数据明显漂了换成环氧树脂封装之后一劳永逸。4.2 校准数据的修正方法校准的核心是在固件里修正两个参数25摄氏度的标称阻值R25以及B值。修正方法就是最小二乘拟合。我记录每个标准温度下实际ADC读数和理论ADC读数之间的偏差算出偏差与温度的线性关系然后在软件里加一个校准补偿。校准补偿我用了线性插值的方法把偏差分成段存储在EEPROM里。因为没有用高级拟合算法代码很简单定义三个校准点在1到59摄氏度之间允许线性插值补偿。这样比做全范围二次曲线拟合省事而且对于HVAC的民用场景已经达到相当好的效果。校准前后的数据差异明显校准前0摄氏度时读数为零下0.7摄氏度50摄氏度时读数为50.9摄氏度校准后两个点分别变成零下0.1摄氏度和50.1摄氏度全程最大误差0.3摄氏度。这个精度对应HVAC系统里面的回风温度控制、风管温度监测完全满足需求。4.3 远程与本地温度实测对比两个通道的实测数据我放在一个表格里直观对比标准温度摄氏度本地通道实测摄氏度本地误差远程通道实测摄氏度远程误差0-0.1-0.10.20.2109.9-0.110.10.12020.10.119.9-0.13030.20.230.00.04040.10.140.20.25049.9-0.150.10.16060.20.259.8-0.230米线缆下远程通道的读数没有出现任何跳变和漂移这验证了PJ85718DM在源头做ADC转换并传输数字信号方案的可靠性。本地通道在继电器动作的瞬间会有非常轻微的跳动但中值滤波完全将其抑制住了。4.4 模数转换误差与参考电压选择的影响误差分析里面模数转换参考电压的影响最容易被忽略。如果直接用3.3V供电轨做参考而3.3V由板载稳压器输出输出电压在负载变化时会有几毫伏的波动。看起来很小但换算到温度上可能带来0.2摄氏度左右的误差。我选择内部2.048V固定参考之后这部分误差就基本消除了。PIC18LF45K40内部参考电压的绝对精度一般但客户端的应用并不需要和标准温度计做绝对比对出厂校准时会把它在校准流程里吸收掉。校准之后系统精度就不再依赖参考电压的绝对准确性这是很重要的一个思路。PJ85718DM远程模块内部也有自己的参考电压好在模块出厂时已经做过一轮校准我实测下来每个模块之间的一致性约为0.2摄氏度多个探头之间互相替换也不会引起明显的示值变化。5. 常见问题排查与实操心得5.1 远程通信间歇性失败的排查RS485通信间歇性失败是这类项目最折磨人的问题。现象是设备运行几分钟后远程温度就停留在某个值不动过一会儿自己又恢复。第一反应通常会怀疑是程序bug或者模块死机但仔细排查后发现是RS485总线电平冲突。问题根源在主控端的RS485收发器控制引脚。RS485是半双工发送和接收共用一个总线需要DE/RE引脚控制方向。我在发送完成后立刻把DE引脚拉低此时收发器回到接收模式但如果芯片还有最后一位数据正在发送提前切换方向会导致最后一个字节被截断。模块端收不到完整帧自然不回复。解决方案是在发送完最后一个字节后增加一个延时等待移位寄存器完全清空再拉低DE引脚。在9600波特率下延时1.5ms就够了稳妥起见我设了2ms。这种细节问题在示波器上才能看出来纯靠逻辑分析仪不一定能发现因为逻辑分析仪抓的是引脚电平抓不到总线上的电气冲突。5.2 NTC读数跳变的奇怪原因排查过一个让我印象深刻的案例本地NTC读数在压缩机启动的瞬间会跳低1.5摄氏度左右持续几百毫秒后恢复。一开始怀疑是ADC参考电压受干扰但测试发现VDD和参考电压波形都很稳定。最后用示波器探头直接点在NTC分压节点的引出线上复位键按下去的时候探头画出一条很窄的负脉冲。原因是PCB布局上NTC分压节点距离继电器线圈走线太近继电器吸合瞬间的磁场耦合到高阻抗分压节点上了。NTC的阻值通常是10k到40k分压节点的输出阻抗非常高容易感应噪声。解决方法是把ADC输入端到MCU之间串联一个1k电阻并在ADC引脚对地并联一个100nF电容构成一个低通滤波器。这个改动不影响温度响应速度但把高频耦合噪声衰减掉了大半。另外把分压节点附近的继电器走线拉开了一些距离问题彻底消失。做温度采集的模拟前端时高阻抗节点要远离任何开关型信号这算是一条用实践买来的教训。5.3 快速问题排查速查表故障现象可能原因排查步骤解决办法远程温度长时间不更新RS485 A/B接线反了检查接线端子和模块定义对调A/B线远程通信时好时坏缺少终端电阻用万用表量总线末端阻抗两端各并联120欧电阻本地读数整体偏低NTC进水或封装破损用万用表量NTC阻值与规格表比对更换传感器重新密封本地读数缓慢漂移传感器老化或焊点沾锡不良观察一天数据趋势补焊并重新校准模块上电无响应模块供电电压不足量模块端VCC电压加粗供电线或升压所有通道读数都不对程序里参考电压常量用错检查代码中的Vref定义统一改为2.048V5.4 校准补偿与老化漂移的实践经验最后分享一个经验。HVAC设备在高温高湿环境下运行半年后传感器漂移问题会逐渐显现。NTC虽然在短时间内稳定性不错但长期老化后的漂移在高温段会明显一些机房常年50摄氏度以上的环境会加速这个过程。我在固件里预留了一个校准补偿表放在EEPROM中。设备运行一段时间后维护人员可以带着标准温度计重新做一次两点校准通过串口命令更新补偿值不需要重新烧录程序。这个功能看着不起眼但在实际项目中帮了大忙因为现场返回的每一台设备如果都要返厂重刷固件运维成本完全不可接受。还有一个小技巧EEPROM写入前一定要先备份旧值写入完成后再回读校验一次。温度采集设备最怕的并不是测量不准而是运行中配置数据丢失后静默地输出错误数据。加一道校验和能在每次上电时发现配置损坏主动提示故障这比带着错误数据继续运行要好得多。6. 项目的可扩展方向这套温控采集架构的价值在于它的可扩展性。PJ85718DM支持Modbus协议意味着只要总线上还有别的从站设备主控通过修改地址列表就能在同一个RS485网络上挂接更多的温度点。我在项目里实际挂过四个模块每个模块带一个远程探头轮询到四个点的时间在9600波特率下大约80ms完全不影响采集实时性。如果后续要接入楼宇自控系统可以把主控的调试串口改成Modbus从站模式让上位机通过RS485直接读取本地和远程温度这样既不需要改变现有硬件又能让设备从单机运行升级为网络组网。本地通道也可以考虑增加一路湿度传感器HVAC应用里温湿度联动控制的需求很普遍。PIC18LF45K40剩下来的ADC通道足够支持两到三路湿度采集软件里复用现有的查表插值和滤波逻辑即可。我个人在实际项目里最强的体会是温度监测这类看起来极为基础的功能问题往往不在于原理而在于把原理落到工业现场时各种干扰和细节的纠缠。本地与远程分开设计、前端数字化、软件做好滤波和故障自检、出厂前认真校准这一套流程走下来设备运行的稳定性和可维护性都会明显上一个台阶。最后再分享一个小技巧NTC分压节点上并联的100nF电容不是越大越好。我试过用1uF电容滤波效果确实更好但温度响应变得非常迟钝传感器已经快速变化时读数还慢吞吞地爬。100nF在高阻抗源下已经能有效抑制几kHz以上的干扰再大就得不偿失了。选这个值是被实测数据说服的。