1. 项目缘起与整体设计思路
嵌入式温度监测这个方向,看起来简单,实际上坑特别多。我最早接触这类需求是在一个 HVAC 控制板的项目里,当时的需求很朴素:本地要能看到当前温度,远程上位机也要能读到同一路温度数据,而且两边的数据不能打架。最开始想用一颗带模拟输出的温度传感器直接进 MCU 的 ADC,结果发现精度和抗干扰都撑不住,尤其是压缩机启停那一下,读数能飘好几度。后来换成了数字温度传感器方案,主控用 PIC18F85K90,温度采集用 PJ85718DM,这套组合跑下来稳定性明显好很多,也就成了我后面几个项目里反复复用的一个基础架构。
先把这套方案说清楚:PJ85718DM 是一颗本地温度传感器,输出的是数字信号,直接给 MCU 读取,不需要额外的 ADC 采样和校准;PIC18F85K90 是一颗 8 位单片机,片上集成了比较丰富的外设,包括多路串口、I2C、SPI、定时器等,用来做温度数据的采集、处理、本地显示和远程上报非常合适。整个系统的核心任务就三件事:第一,把本地温度准确读出来;第二,把温度数据在本地做显示或者做阈值判断;第三,把同一份数据通过通信接口送到远程端。听起来简单,但真正做起来,采样时序、通信协议、数据一致性、抗干扰这几块都得认真处理。
为什么选这套组合而不是别的?我当时的考量主要有几点。一是成本,HVAC 这类产品对 BOM 成本非常敏感,8 位 MCU 加一颗数字温度传感器,整体成本可控;二是开发周期,PIC18 系列的开发工具链成熟,寄存器操作直接,调试起来心里有底;三是可靠性,数字传感器省掉了模拟链路的校准环节,批量生产时一致性更好。这三点加起来,基本就决定了方案选型。
这个项目适合谁来参考?如果你正在做嵌入式温度采集、HVAC 控制器、环境监测设备,或者你手上有 PIC18 平台的项目需要接入温度传感器,这套思路都可以直接借鉴。哪怕你用的是别的 MCU,采集和远程上报的分层设计逻辑也是通用的。下面我会把整体设计、核心细节、实操过程、常见问题这几块拆开讲,尽量把踩过的坑和验证过的做法都写出来。
2. 核心器件解析与选型考量
2.1 PJ85718DM 温度传感器的工作特性
PJ85718DM 这颗传感器,我在多个项目里用过,它的核心价值在于把温度感知和数字输出集成在一起。它内部有感温元件、信号调理电路和数字接口逻辑,对外呈现的就是一个可以直接读取的温度寄存器。和传统的热敏电阻加运放加 ADC 的方案相比,它省掉了模拟链路里最容易出问题的几个环节:运放失调、参考电压漂移、ADC 量化误差。这些在实验室里可能不明显,但到了现场,尤其是 HVAC 设备那种电机、继电器频繁动作的环境里,模拟方案的读数抖动会非常明显。
从使用角度看,PJ85718DM 的读取流程一般是:MCU 通过通信接口发送读取命令,传感器返回温度数据,MCU 再把原始数据转换成实际温度值。这里有个细节要注意,温度数据的格式和分辨率在不同配置下可能不一样,有的配置是整数精度,有的是小数精度,读取之前一定要确认当前配置,否则转换出来的温度会差一个量级。我第一次用的时候就因为没注意分辨率设置,读出来的温度比实际高了十几度,排查了半天才发现是转换系数用错了。
另外,这颗传感器的供电范围、通信速率、上电稳定时间这些参数,在数据手册里都有明确说明,实际使用时建议留出足够的余量。比如供电,如果系统里有大功率负载,电源纹波可能会影响传感器内部参考,最好在传感器供电脚附近加去耦电容,位置越靠近引脚越好。通信线如果走线较长,也要考虑加适当的滤波或者降低通信速率,保证数据可靠。
2.2 PIC18F85K90 作为主控的适配性分析
PIC18F85K90 这颗 MCU,在 8 位机里算是外设比较全的。它有多路通信接口,可以同时接本地显示模块和远程通信模块,不会因为接口不够而被迫做取舍。它的定时器资源也比较丰富,可以用来做采样周期控制、通信超时判断、看门狗喂狗等。对于温度监测这种需要周期性采集、周期性上报的应用来说,定时器的作用非常关键,它决定了整个系统的节奏。
我选它还有一个原因是它的中断系统比较灵活,可以给温度采集和通信分别分配不同的优先级。比如温度采集可以放在定时器中断里,保证采样周期稳定;通信上报可以放在主循环里,避免中断里做太多耗时操作。这种分层处理的方式,能让系统在通信繁忙的时候也不会丢掉温度采样,数据完整性更好。
还有一个实际考量是它的 I/O 驱动能力。HVAC 控制板上经常要直接驱动一些指示灯、蜂鸣器或者小继电器,PIC18F85K90 的 I/O 在合理配置下可以直接承担这些任务,省掉额外的驱动芯片。当然,如果负载电流较大,还是要加三极管或者驱动芯片,这个后面讲实操的时候再细说。
2.3 本地与远程双通道的设计逻辑
本地和远程这两个通道,看起来只是把同一份数据发到两个地方,但实际设计的时候要考虑的问题不少。首先是数据一致性,本地显示和远程上报必须是同一时刻的同一份数据,不能本地显示的是上一次采样的值,远程收到的是这一次的值,这样在调试或者故障分析的时候会非常混乱。我的做法是在采样完成后,先把数据存到一个全局的温度变量里,本地显示和远程上报都从这个变量取值,保证源头一致。
其次是更新频率的差异。本地显示通常要求响应快,人眼看到温度变化要即时;远程上报则要考虑通信带宽和上位机处理能力,频率可以低一些。这两者如果处理不好,要么本地显示卡顿,要么远程通信拥塞。我的做法是本地显示每次采样后都更新,远程上报则按一个较低的固定周期发送,两者互不干扰。
最后是异常处理。如果远程通信断了,本地显示不能受影响,系统要继续正常工作,同时要记录通信异常状态,等通信恢复后能继续上报。这个逻辑在代码里要明确处理,不能因为通信失败就把整个系统卡死。
3. 硬件连接与关键细节处理
3.1 传感器与 MCU 的接口连接要点
PJ85718DM 和 PIC18F85K90 之间的连接,核心就是通信线和电源线。通信线一般走 I2C 或者类似的数字接口,具体用哪种要看传感器型号和 MCU 外设配置。连接的时候有几个细节必须注意。第一,上拉电阻。数字通信线在没有驱动的时候需要上拉电阻保持高电平,阻值一般选 4.7k 到 10k 之间,具体要看通信速率和总线电容。速率高、线长的时候,阻值要小一些,保证上升沿够快;速率低、线短的时候,阻值可以大一些,降低功耗。
第二,走线布局。传感器如果离 MCU 较远,通信线要尽量短,并且远离电机、继电器、电源开关这些干扰源。如果实在避不开,可以考虑用屏蔽线或者双绞线,并且做好接地。我在一个项目里因为通信线和大电流负载线捆在一起走,结果温度读数每隔几秒就跳一次,后来把线分开走,问题立刻消失。这个坑很典型,硬件布局上的问题,软件再怎么滤波都很难完全解决。
第三,电源去耦。传感器的供电脚旁边一定要加一个 0.1uF 的陶瓷电容,位置越靠近引脚越好。这个电容的作用是滤掉高频噪声,保证传感器内部电路稳定工作。如果系统里还有其他数字器件,建议在电源入口再加一个 10uF 的钽电容或者电解电容,做低频滤波。
3.2 本地显示与远程通信的硬件资源分配
本地显示我一般用段码 LCD 或者小尺寸的字符屏,接口简单,功耗低,适合 HVAC 这种常年运行的应用。连接的时候要注意 LCD 的偏压电阻和对比度调节,这些参数如果不对,显示会偏淡或者偏浓,影响可读性。远程通信我一般用串口转其他物理层的方式,比如串口转 RS485 或者串口转无线模块,具体看现场布线条件。RS485 适合有线长距离传输,抗干扰好;无线模块适合不方便布线的场合,但要注意通信协议和配对管理。
资源分配上,PIC18F85K90 的串口资源要合理规划。如果本地显示也用串口,那就要注意两个串口的波特率和中断优先级不能冲突。我的做法是本地显示用并行接口或者 I2C,把串口留给远程通信,这样资源不打架,调试也方便。
3.3 抗干扰与电源设计的实操经验
HVAC 环境的干扰源主要是电机、压缩机、继电器这些感性负载。它们启停的时候会在电源线和空间里产生很大的瞬变干扰。对付这种干扰,硬件上要做几件事:一是电源入口加 TVS 管或者压敏电阻,吸收浪涌;二是通信线加共模电感或者磁珠,抑制高频噪声;三是传感器和 MCU 的接地要处理好,模拟地和数字地分开走,最后单点汇合。
软件上也要配合,比如通信数据加校验,发现校验错就丢弃重读;温度数据做滑动平均滤波,滤掉突发的跳变;关键寄存器定期回读,防止干扰导致配置丢失。这些措施加起来,系统在恶劣环境下的稳定性会好很多。我在一个压缩机控制柜里跑过这套方案,连续运行几个月,温度数据没有出现过异常跳变,说明这套抗干扰设计是有效的。
4. 软件架构与核心代码实现
4.1 温度采集任务的调度与实现
温度采集的调度,我一般用定时器中断来做。比如设定定时器每 500ms 中断一次,在中断里启动一次温度读取。这样采样周期稳定,不受主循环里其他任务的影响。读取过程一般是:发送读取命令,等待转换完成,读取温度寄存器,转换成实际温度值,存入全局变量。这里要注意等待时间,传感器从收到命令到数据准备好需要一定时间,这个时间在数据手册里有说明,不能省略,否则读到的可能是上一次的数据或者无效数据。
代码实现上,我习惯把温度读取封装成一个函数,输入是传感器地址或者通道号,输出是浮点温度值。函数内部处理通信时序、数据校验、单位转换。这样主循环里调用起来很简洁,也方便以后换传感器型号时只改这一个函数。
float read_temperature(uint8_t sensor_addr) { uint8_t raw_data[2]; float temperature; // 发送读取命令 i2c_start(); i2c_write(sensor_addr << 1); i2c_write(TEMP_READ_CMD); i2c_stop(); // 等待转换完成 delay_ms(TEMP_CONVERSION_TIME); // 读取温度数据 i2c_start(); i2c_write((sensor_addr << 1) | 1); raw_data[0] = i2c_read_ack(); raw_data[1] = i2c_read_nack(); i2c_stop(); // 数据校验与转换 if (check_crc(raw_data)) { temperature = convert_to_celsius(raw_data); } else { temperature = last_valid_temperature; // 校验失败沿用上次有效值 } return temperature; }这段代码里,校验失败沿用上次有效值是一个实用技巧。温度变化通常比较慢,短时间内沿用上次值不会造成明显误差,但能避免因为一次通信干扰就显示异常值。
4.2 本地显示刷新与远程上报的协同
本地显示和远程上报的协同,核心是数据源统一和更新节奏分离。我在主循环里这样安排:温度采集在定时器中断里完成,更新全局变量;主循环里先检查本地显示是否需要刷新,如果需要就调用显示刷新函数;然后检查远程上报周期是否到了,到了就调用上报函数。两个任务的周期可以独立设置,互不影响。
本地显示刷新我一般做一个小优化:只有温度值变化超过一定阈值才刷新显示,避免显示数字频繁跳动。比如温度变化小于 0.1 度就不刷新,这样显示看起来更稳定。远程上报则每次都发当前值,保证上位机拿到的是最新数据。
void main_loop(void) { static float last_display_temp = -100.0; static uint32_t last_report_time = 0; while (1) { // 本地显示刷新 if (fabs(current_temperature - last_display_temp) >= 0.1) { update_local_display(current_temperature); last_display_temp = current_temperature; } // 远程上报 if (get_system_tick() - last_report_time >= REPORT_INTERVAL_MS) { send_remote_report(current_temperature); last_report_time = get_system_tick(); } // 其他任务 handle_communication_timeout(); feed_watchdog(); } }4.3 通信协议设计与数据校验
远程通信协议我一般设计得比较简单,但必须有校验。一个典型的数据帧包括:帧头、设备地址、温度数据、状态字节、校验和、帧尾。帧头用来同步,设备地址用来区分不同节点,状态字节可以带传感器故障、通信异常等信息,校验和用来验证数据完整性。
校验和的计算方式有很多种,简单一点的用累加和,复杂一点的用 CRC。累加和实现简单,对单字节错误检测效果不错;CRC 检测能力更强,但计算量稍大。在 8 位 MCU 上,如果通信速率不高,CRC 也完全跑得动。我一般用 CRC-8,兼顾检测能力和计算开销。
uint8_t calculate_crc8(uint8_t *data, uint8_t length) { uint8_t crc = 0x00; for (uint8_t i = 0; i < length; i++) { crc ^= data[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x80) { crc = (crc << 1) ^ 0x07; } else { crc <<= 1; } } } return crc; }接收端收到数据后,先找帧头,再按固定长度取数据,计算校验和并比对,校验通过才处理数据,否则丢弃。这个流程看起来简单,但实际调试的时候,帧头误判和缓冲区溢出是两个常见问题,后面讲排查的时候会细说。
5. 实操过程与调试记录
5.1 从零搭建测试环境的步骤
我搭建测试环境一般分几步走。第一步,先把 MCU 最小系统跑起来,确认供电、晶振、复位电路正常,能下载程序并点亮一个 LED。这一步看起来基础,但很多问题都是在这里埋下的,比如晶振不起振、复位脚被拉低、电源纹波太大,都会导致后面调试困难。
第二步,接上温度传感器,写一个最简单的读取程序,把原始数据通过串口打印出来。这一步不急着转换温度,先确认通信能通,能读到数据。如果读不到,先用示波器看通信线上的波形,确认时序对不对,上拉电阻有没有起作用。
第三步,加上温度转换和显示,确认读到的温度值合理。可以用手捏住传感器或者用吹风机加热,看温度值是否跟着变化。这一步能验证传感器和转换逻辑是否正常。
第四步,加上远程通信,用上位机或者串口助手接收数据,确认数据帧格式正确、校验通过。这一步能验证通信协议和硬件连接是否可靠。
第五步,做长时间运行测试,至少跑 24 小时,观察温度数据是否稳定,通信是否丢包,系统是否死机。这一步能暴露很多偶发问题,比如看门狗复位、通信超时、内存泄漏等。
5.2 温度数据校准与精度验证
温度数据的校准,我一般用冰水混合物和沸水做两个参考点。冰水混合物是 0 度,沸水在标准大气压下是 100 度,用这两个点可以大致验证传感器的线性度和偏移。如果偏差较大,可以在软件里加一个偏移量或者做两点校准。校准的时候要注意,传感器要充分浸入参考环境,等待读数稳定后再记录,不能刚放进去就读。
精度验证则要用更高精度的参考温度计做对比,在多个温度点分别记录传感器读数和参考读数,计算偏差。如果偏差在允许范围内,就认为精度合格;如果偏差较大,要分析是传感器本身的问题还是电路的问题。我遇到过一次偏差大的情况,最后发现是传感器供电电压偏低,导致内部参考漂移,调整供电后偏差就正常了。
5.3 长时间运行稳定性测试
长时间运行测试是验证系统可靠性的关键。我一般会记录几个指标:温度数据的最大值、最小值、平均值,通信丢包率,系统复位次数。如果温度数据出现异常跳变,或者通信丢包率偏高,或者系统频繁复位,都说明有问题需要排查。
测试环境也很重要,最好能模拟实际工作环境,比如加上电机负载、继电器动作、电源波动等。如果条件不允许,至少要在温度变化较大的环境下测试,比如从空调房拿到室外,观察系统是否能正常工作。我在一个项目里就是因为没有做温度循环测试,结果产品到了北方冬天户外,低温下传感器通信失败,后来加了低温补偿才解决。
6. 常见问题与排查技巧实录
6.1 温度读数异常的问题排查
温度读数异常是最常见的问题,表现有几种:读数固定不变、读数跳变剧烈、读数偏差大、读数偶尔无效。固定不变一般是通信没通,或者读的是同一个寄存器;跳变剧烈一般是干扰或者电源不稳;偏差大一般是转换系数或者校准问题;偶尔无效一般是通信时序或者校验问题。
排查的时候,我一般先用示波器看通信波形,确认时序和电平正常;然后看电源纹波,确认供电稳定;再检查转换系数和校准参数,确认计算正确;最后看校验逻辑,确认无效数据被正确处理。这个顺序从硬件到软件,从简单到复杂,能比较快地定位问题。
6.2 通信丢包与数据冲突的处理
通信丢包的原因很多,常见的有:波特率不匹配、通信线太长、上拉电阻不合适、干扰太大、缓冲区溢出。排查的时候,先确认两边的波特率、数据位、停止位、校验位完全一致;然后检查通信线长度和上拉电阻,必要时降低波特率或者加中继;再看是否有干扰源,必要时加屏蔽或者滤波;最后检查接收缓冲区,确保不会因为处理不及时而溢出。
数据冲突一般发生在多节点通信的时候,比如两个节点同时发送。解决办法是加仲裁机制,比如主从模式,只有主机能发起通信,从机只能应答;或者用令牌环,拿到令牌的节点才能发送。在温度监测这种应用里,一般用主从模式就够了,主机轮询各个从机,从机收到命令才回复。
6.3 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 温度读数固定不变 | 通信未通、读错寄存器 | 示波器看波形、检查命令 | 修正通信配置、确认寄存器地址 |
| 温度读数跳变剧烈 | 干扰、电源不稳 | 看电源纹波、检查走线 | 加滤波、分开走线、加去耦电容 |
| 温度偏差大 | 转换系数错、未校准 | 核对数据手册、做校准 | 修正系数、加偏移量 |
| 通信丢包 | 波特率不匹配、线太长 | 核对配置、测线长 | 统一配置、降低速率、加中继 |
| 系统频繁复位 | 看门狗、电源跌落 | 查看门狗配置、测电源 | 调整喂狗周期、加稳压 |
| 显示闪烁 | 刷新太频繁、电源不稳 | 看刷新逻辑、测电源 | 加刷新阈值、加滤波电容 |
6.4 独家避坑经验分享
第一个坑是上拉电阻的位置。很多人把上拉电阻放在 MCU 端,觉得这样方便,但实际上如果传感器端离得远,通信线上的电容会让上升沿变缓,导致通信失败。正确的做法是把上拉电阻尽量靠近传感器端,或者两端都放,保证整条线的电平稳定。
第二个坑是温度转换的整数除法。在 8 位 MCU 上做浮点运算比较耗时,有些人为了省时间用整数运算,结果转换的时候用了整数除法,把小数部分直接截断了,导致温度精度损失。如果要用整数运算,一定要先做乘法再做除法,保留足够的中间精度。
第三个坑是看门狗喂狗周期。看门狗是为了防止程序跑飞,但如果喂狗周期设得太短,正常的长耗时操作也会触发复位;设得太长,又起不到保护作用。我的经验是喂狗周期设为最长正常操作时间的 1.5 到 2 倍,并且在多个任务点都喂狗,确保任何一个任务卡死都能被检测到。
第四个坑是通信缓冲区的管理。接收中断里如果直接把数据存入缓冲区,主循环里如果处理不及时,缓冲区就会溢出。解决办法是加环形缓冲区,并且加溢出标志,溢出时丢弃最旧的数据或者复位缓冲区。这个逻辑一定要在代码里明确处理,不能假设主循环一定能及时处理。
7. 方案扩展与个人体会
这套 PJ85718DM 加 PIC18F85K90 的温度监测方案,基础功能跑通之后,扩展空间其实挺大的。比如可以加多路温度传感器,做多点温度监测,适合大型 HVAC 系统;可以加历史数据存储,用 EEPROM 或者外部 Flash 记录温度曲线,方便事后分析;可以加报警功能,温度超限时本地声光报警并远程推送;还可以加自适应采样,温度变化快的时候提高采样率,变化慢的时候降低采样率,节省功耗。
我在实际使用中发现,这套方案最值得投入精力的地方其实是抗干扰和异常处理。功能实现本身不难,难的是在各种恶劣环境下都能稳定运行。每次遇到问题,我都会把现象、排查过程、解决方法记录下来,时间长了就形成了一套自己的排查流程,再遇到类似问题就能很快定位。
最后分享一个小技巧:在通信协议里加一个设备状态字节,把传感器的健康状态、通信质量、供电状态都编码进去。这样上位机不仅能拿到温度值,还能知道这个值可不可信。这个字节平时看起来多余,但真出问题的时候,它能帮你快速判断是传感器的问题还是通信的问题,省掉很多排查时间。