1. 项目缘起与整体设计思路
嵌入式温度监测这个方向,看起来简单,真做起来坑不少。我接手过好几个涉及本地温度采集加远程温度上报的项目,从工业控制柜到商用空调控制器都有,最头疼的从来不是传感器本身,而是怎么把本地采集和远程传输这两件事在同一个系统里做稳。这次要聊的方案,核心是用 PJ85718DM 做本地温度采集,用 PIC32MZ1024EFK144 做数据处理和远程通信调度,面向嵌入式和 HVAC 场景。
先说清楚这个组合解决什么问题。HVAC 系统里,温度监测通常分两层:一层是设备本体的本地温度,比如压缩机排气温度、换热器表面温度、风道温度;另一层是远程温度,可能是远端房间的温感探头,也可能是通过通信总线从其他节点获取的温度数据。本地采集要求响应快、精度稳,远程传输要求协议可靠、抗干扰强。PJ85718DM 是一颗红外温度传感器信号调理芯片,配合热电堆传感器使用,输出数字信号,适合做非接触式本地温度采集。PIC32MZ1024EFK144 是 Microchip 的 32 位 MCU,200MHz 主频,1MB Flash,512KB RAM,带以太网、CAN、UART、SPI、I2C 等丰富外设,拿来做 HVAC 控制器的主控非常合适。
为什么选这个组合而不是其他方案?我对比过几种常见做法。第一种是用 NTC 热敏电阻加运放加 ADC,成本低但精度和线性度差,尤其在高低温两端误差大,而且需要校准。第二种是用数字温度传感器比如 DS18B20 这类,单总线接口简单,但测温范围有限,响应速度也一般,做非接触测量更是不可能。第三种就是 PJ85718DM 加热电堆的方案,非接触、响应快、量程宽,配合 PIC32MZ 的浮点运算能力做温度补偿和线性化处理,整体精度可以做到正负 0.5 度以内。HVAC 场景里,风道温度、换热器表面温度这些位置,接触式传感器安装困难或者容易受污染,非接触方案优势明显。
整体设计思路是这样的:PJ85718DM 负责把热电堆传感器的微弱模拟信号转换成数字温度值,通过 I2C 或 SPI 接口传给 PIC32MZ1024EFK144。PIC32MZ 做三件事:第一,对本地温度数据做滤波、补偿、校准;第二,通过 RS485 或 CAN 总线采集远程温度节点的数据;第三,把本地和远程温度汇总后,通过以太网或无线模块上报给上位机或云平台。整个系统的时间基准用 MCU 的内部定时器加外部晶振保证,采样周期可以根据应用需求在 100ms 到 1s 之间调整。
这里有个关键设计决策需要解释:为什么不让 PJ85718DM 直接驱动通信,而是必须经过 MCU?原因是 PJ85718DM 本质上是传感器信号调理芯片,它的输出是温度数据,不具备协议栈处理能力。HVAC 系统里远程通信往往需要 Modbus、BACnet 或者自定义协议,这些必须由 MCU 来完成。另外,多路温度采集时,MCU 可以做轮询调度和数据融合,单靠传感器芯片做不到。所以这个分工是合理的,也是行业里比较成熟的架构。
2. 核心器件解析与选型考量
2.1 PJ85718DM 的工作机制与关键参数
PJ85718DM 这颗芯片,资料不算特别多,但用熟了会发现它在非接触测温领域很实用。它的核心功能是配合热电堆传感器工作,热电堆输出的是微伏级别的电压信号,跟目标温度和环境温度之间的差值成比例。PJ85718DM 内部集成了低噪声放大器、高精度 ADC、环境温度传感器和数字信号处理单元,直接输出校准后的温度值。
关键参数我列一下,这些在实际选型和调试时都要关注:
| 参数项 | 典型值 | 说明 |
|---|---|---|
| 测温范围 | -20 到 300 摄氏度 | 取决于外接热电堆型号 |
| 分辨率 | 0.02 摄氏度 | 内部 ADC 位数决定 |
| 接口 | I2C / SPI | 可配置,默认 I2C |
| 供电电压 | 3.3V | 典型功耗约 1.5mA |
| 刷新率 | 最高 64Hz | 可配置,HVAC 场景 4-10Hz 足够 |
| 精度 | 正负 0.5 摄氏度 | 配合校准后可达 |
这里要重点说的是刷新率和精度的取舍。HVAC 系统里温度变化本身很慢,风道温度从 20 度升到 30 度可能要几分钟,所以采样率不需要太高。我一般设成 4Hz 或者 8Hz,这样既能保证响应速度,又能降低功耗和通信负载。如果你设成 64Hz,数据跳动反而更明显,因为热电堆本身的噪声带宽就那么大,采太快只是把噪声也采进来了。
2.2 PIC32MZ1024EFK144 的资源分配
PIC32MZ1024EFK144 这颗 MCU 在 HVAC 控制器里算是高配了。200MHz 主频,带浮点运算单元,做温度补偿算法绰绰有余。1MB Flash 和 512KB RAM 意味着你可以跑 RTOS,也可以把协议栈、数据缓存、日志存储都塞进去。
我在这个项目里的资源分配是这样的:
- I2C 外设:接 PJ85718DM,速率设 400kHz,用 DMA 传输减少 CPU 占用
- UART 或 CAN:接远程温度节点,RS485 用 UART 加外部收发器,CAN 直接用内置 CAN 控制器
- 以太网:接上位机或路由器,跑 Modbus TCP 或自定义 TCP 协议
- 定时器:系统滴答用 Timer1,采样触发用 Timer2,通信超时检测用 Timer3
- GPIO:状态指示灯、继电器控制、报警输出
- ADC:备用,可以接 NTC 做冗余本地温度监测
为什么用 DMA 传 I2C?因为温度采集是周期性的,如果每次都用中断或者轮询,CPU 会被频繁打断。用 DMA 之后,MCU 可以在这段时间处理通信协议或者控制逻辑,整体效率高很多。实测下来,用 DMA 比不用 DMA,CPU 占用率从 15% 降到 5% 左右。
2.3 本地与远程温度的协同逻辑
本地温度和远程温度在系统里的角色不一样。本地温度是设备自身的状态,比如压缩机壳温、电控箱内温、出风口温度,这些数据用来做设备保护和控制决策。远程温度是环境或者远端节点的数据,用来做区域调节或者系统级联动。
协同逻辑我一般这样设计:本地温度优先级高,采样周期短,比如 250ms 一次;远程温度优先级低,采样周期长,比如 2s 一次。MCU 内部维护两个数据队列,本地队列深度 16,远程队列深度 8。当本地温度超过阈值时,立即触发保护动作,不等远程数据。当远程温度异常时,先做数据有效性检查,确认不是通信误码后再做处理。
这里有个细节:远程温度数据必须带时间戳和校验。我遇到过 RS485 总线上某个节点故障,发出来的温度值是 0xFFFF,如果不做校验直接当 -1 度处理,系统就会误判。所以协议里必须加 CRC 校验和范围检查,超出合理范围的直接丢弃。
3. 硬件连接与实操要点
3.1 最小系统搭建
先说一下最小系统的搭建。PIC32MZ1024EFK144 是 144 引脚封装,手工焊接难度不小,建议直接用官方评估板或者自己画四层板。核心电路包括:3.3V 电源、外部 24MHz 晶振、复位电路、调试接口(ICSP 或者 JTAG)、去耦电容。
电源部分要特别注意。PIC32MZ 的内核电压是 1.8V 左右,需要内部 LDO 或者外部供电,具体看型号后缀。我一般用 3.3V 单电源,MCU 内部有稳压器。去耦电容每对电源引脚配一个 100nF,另外在电源入口放一个 10uF 钽电容。HVAC 环境里电源纹波可能比较大,建议再加一个 LC 滤波。
PJ85718DM 的连接相对简单:VCC 接 3.3V,GND 接地,SCL 和 SDA 接 MCU 的 I2C 引脚,另外热电堆传感器的正负输出接到 PJ85718DM 的差分输入端。热电堆的安装位置很关键,必须正对被测目标,中间不能有遮挡,镜头或者窗口要定期清洁。
3.2 I2C 总线的上拉电阻选择
I2C 总线的上拉电阻是个老生常谈的问题,但实际调试时经常出问题。PJ85718DM 支持标准模式和快速模式,400kHz 时上拉电阻一般选 2.2k 到 4.7k。电阻太小功耗大,电阻太大上升沿变缓,通信误码率上升。
我的经验是:总线长度小于 10cm 时用 4.7k,10cm 到 30cm 用 2.2k,超过 30cm 建议用 I2C 缓冲器或者降低速率到 100kHz。HVAC 控制器内部走线一般不长,4.7k 够用。但如果你的板子上 I2C 挂了多个器件,要算一下总电容,超过 400pF 就要调整。
注意:PJ85718DM 的 I2C 地址可以通过引脚配置,默认地址是 0x48 左右,具体看数据手册。如果总线上有多个同型号芯片,必须改地址,否则会冲突。
3.3 远程通信接口的硬件设计
远程温度节点我一般用 RS485,因为抗干扰能力强,传输距离远,HVAC 现场电磁环境复杂,RS485 比 I2C 或者 SPI 靠谱得多。PIC32MZ 的 UART 接一个 RS485 收发器,比如常见的半双工收发器,DE 和 RE 引脚用 GPIO 控制方向。
RS485 总线的终端电阻不能忘。总线两端各接一个 120 欧姆电阻,中间节点不接。如果总线长度超过 100 米,建议加隔离,用光耦或者磁隔离器把 MCU 侧和总线侧隔开。我踩过一次坑:没加隔离,雷雨天气感应雷把收发器打坏了,连 MCU 都换了。后来加了隔离模块,再没出过问题。
CAN 总线在 HVAC 里也常用,尤其是汽车空调或者大型楼宇系统。PIC32MZ 内置 CAN 控制器,外面接一个 CAN 收发器就行。CAN 的终端电阻也是 120 欧姆,总线上所有节点速率必须一致,我一般用 125kbps 或者 250kbps,兼顾距离和速率。
4. 软件架构与核心代码实现
4.1 温度采集任务的设计
软件架构我采用前后台加定时器调度的方式,没有上 RTOS,因为任务不算多,前后台够用。如果你要跑 Modbus TCP 加多个远程节点轮询,建议上 FreeRTOS,任务划分更清晰。
温度采集任务的核心流程是这样的:
// 伪代码示意,基于常见实践 void TemperatureTask(void) { static uint32_t lastLocalTick = 0; static uint32_t lastRemoteTick = 0; uint32_t now = GetSysTick(); // 本地温度采集,250ms 一次 if (now - lastLocalTick >= 250) { lastLocalTick = now; float localTemp = PJ85718DM_ReadTemperature(); if (IsValidTemperature(localTemp)) { LocalTempQueue_Push(localTemp); LocalTempFilter_Update(localTemp); } } // 远程温度采集,2s 一次 if (now - lastRemoteTick >= 2000) { lastRemoteTick = now; RemoteNode_PollAll(); } }这里的关键是数据有效性检查。PJ85718DM 读出来的原始值可能是 0xFFFF 或者超出量程的值,必须过滤。我一般设一个合理范围,比如 -40 到 150 度,超出范围的直接丢弃,用上一次的有效值代替。
4.2 温度滤波与补偿算法
热电堆传感器的原始数据噪声比较大,必须滤波。我用的是滑动平均加中值滤波的组合。滑动平均窗口取 8 个点,中值滤波取 5 个点。先做中值滤波去掉突发噪声,再做滑动平均平滑数据。
补偿算法主要是环境温度补偿。热电堆输出的是目标温度和环境温度的差值,所以必须知道环境温度才能算出目标温度。PJ85718DM 内部有环境温度传感器,读出来之后代入公式:
目标温度 = 环境温度 + 差值电压 / 灵敏度系数
灵敏度系数跟热电堆型号有关,一般在数据手册里给出,典型值是 50uV/K 左右。实际使用时要校准,我一般用黑体炉或者冰水混合物做两点校准,算出实际的灵敏度系数和偏移量。
float CompensateTemperature(float rawTemp, float ambientTemp) { // rawTemp 是 PJ85718DM 输出的未补偿温度 // ambientTemp 是环境温度 // 基于常见实践的补偿公式 float sensitivity = 50.0f; // uV/K,需根据实际校准 float offset = 0.0f; // 校准偏移 float compensated = rawTemp + (ambientTemp - 25.0f) * 0.02f + offset; return compensated; }这个补偿系数 0.02 是经验值,不同热电堆不一样,必须实测。我一般会在恒温箱里做三个温度点:10 度、25 度、40 度,记录原始值和标准值,然后拟合出补偿曲线。
4.3 远程通信协议的设计
远程温度节点我用的自定义协议,基于 RS485 半双工。协议格式很简单:帧头 2 字节,地址 1 字节,命令 1 字节,数据长度 1 字节,数据 N 字节,CRC16 校验 2 字节。帧头用 0xAA 0x55,地址范围 1 到 247,命令 0x01 读温度,0x02 读状态。
为什么不用 Modbus?Modbus 当然可以,但自定义协议更轻量,解析速度快,而且我可以针对温度数据做优化。比如温度值用 2 字节表示,高字节整数部分,低字节小数部分,精度 0.1 度,够用了。
轮询策略是这样的:MCU 依次向每个远程节点发读温度命令,等待响应,超时时间设 100ms。如果某个节点连续 3 次超时,标记为故障,跳过它继续轮询其他节点。故障节点每隔 30 秒重试一次,恢复正常后重新加入轮询队列。
void RemoteNode_PollAll(void) { for (int i = 0; i < nodeCount; i++) { if (nodes[i].status == NODE_FAULT) { if (GetSysTick() - nodes[i].lastRetry < 30000) { continue; } nodes[i].lastRetry = GetSysTick(); } SendReadCommand(nodes[i].address); if (WaitResponse(nodes[i].address, 100) == RESP_OK) { nodes[i].status = NODE_OK; nodes[i].failCount = 0; RemoteTempQueue_Push(nodes[i].temperature); } else { nodes[i].failCount++; if (nodes[i].failCount >= 3) { nodes[i].status = NODE_FAULT; } } } }4.4 数据上报与上位机对接
数据上报我一般用以太网,跑 TCP 或者 UDP。TCP 可靠但开销大,UDP 快但可能丢包。HVAC 系统里温度数据不是特别关键,丢一两个包没关系,所以我倾向用 UDP,周期上报,每秒一次。
上报的数据格式用 JSON 或者二进制都行。JSON 可读性好,调试方便,但解析慢。二进制紧凑,解析快,但调试麻烦。我一般调试阶段用 JSON,量产用二进制。下面是一个 JSON 示例:
{ "device_id": "HVAC_001", "timestamp": 1234567890, "local_temps": [ {"channel": 0, "value": 25.3}, {"channel": 1, "value": 26.1} ], "remote_temps": [ {"node": 1, "value": 24.8}, {"node": 2, "value": 25.5} ] }上位机那边用 Python 或者 Node.js 写个接收服务,存数据库或者直接展示。如果要做云平台对接,再加一个 MQTT 客户端转发就行。
5. 常见问题与排查技巧实录
5.1 温度读数跳动大怎么办
这是最常见的问题。原因通常有三个:电源噪声、热电堆安装不当、滤波参数不合理。
先查电源。用示波器看 PJ85718DM 的 VCC 引脚,纹波应该小于 10mV。如果纹波大,加 LC 滤波或者换 LDO。我遇到过开关电源给 MCU 供电,纹波 50mV,温度读数跳动正负 2 度,换了 LDO 之后降到正负 0.3 度。
再查热电堆安装。热电堆的视场角要覆盖目标,不能有遮挡。如果目标表面反射率高,比如金属表面,读数会偏低,因为反射了环境辐射。解决办法是在目标表面贴一块黑色胶带或者涂黑漆,提高发射率。
最后调滤波参数。滑动平均窗口从 8 加到 16,中值滤波从 5 加到 9,跳动会明显减小,但响应速度会变慢。HVAC 场景对响应速度要求不高,可以接受。
5.2 I2C 通信失败怎么排查
I2C 通信失败,先看硬件:上拉电阻有没有,阻值对不对,SCL 和 SDA 有没有接反。然后用示波器看波形,时钟频率是不是 400kHz,上升沿是不是太缓。
如果硬件没问题,查软件:I2C 初始化对不对,地址对不对,时序对不对。PIC32MZ 的 I2C 外设配置有点复杂,时钟分频系数要算对。我一般用官方库函数,少踩坑。
还有一个隐蔽问题:I2C 总线死锁。如果从机在传输过程中复位,可能会把 SDA 拉低,导致总线死锁。解决办法是在初始化时发 9 个时钟脉冲,让从机释放总线。
5.3 远程节点通信不稳定
RS485 通信不稳定,先查终端电阻。总线两端各 120 欧姆,中间节点不能接。如果终端电阻不对,信号反射会导致误码。
再查共地。RS485 是差分信号,但共地还是要的。如果节点之间地电位差太大,收发器可能损坏。解决办法是用隔离收发器,或者加共模电感。
最后查轮询策略。如果轮询太快,节点来不及响应,就会超时。我一般设 100ms 超时,轮询间隔 200ms,给节点足够的处理时间。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 温度读数跳动大 | 电源纹波大 | 示波器看 VCC | 加 LC 滤波或换 LDO |
| 温度读数偏低 | 目标发射率低 | 检查目标表面 | 贴黑色胶带或涂黑漆 |
| I2C 通信失败 | 上拉电阻缺失 | 万用表测阻值 | 加 4.7k 上拉电阻 |
| I2C 总线死锁 | 从机复位拉低 SDA | 示波器看波形 | 初始化发 9 个时钟脉冲 |
| RS485 误码率高 | 终端电阻不对 | 万用表测阻值 | 两端各接 120 欧姆 |
| 远程节点超时 | 轮询太快 | 逻辑分析仪看时序 | 增加超时和轮询间隔 |
| 温度补偿不准 | 灵敏度系数错误 | 恒温箱校准 | 重新拟合补偿曲线 |
6. 实操心得与经验分享
6.1 校准是精度保证的关键
PJ85718DM 加热电堆的方案,不校准的话精度可能只有正负 2 到 3 度,校准后能做到正负 0.5 度。校准方法我推荐两点校准:用一个已知温度的黑体源,比如 30 度和 60 度,记录原始读数,算出斜率和截距,写入 MCU 的 Flash。
校准的时候要注意环境温度稳定。如果环境温度变化大,补偿系数会漂移。我一般在校准前让设备在恒温环境里放 30 分钟,等热平衡后再开始。
6.2 采样周期不是越短越好
前面提过,HVAC 场景温度变化慢,采样周期 250ms 到 1s 足够。我试过 50ms 采样,数据跳动反而更大,因为热电堆的噪声带宽有限,采太快只是把噪声也采进来了。而且采样快意味着通信负载大,远程节点轮询不过来。
我的建议是:本地温度 250ms 到 500ms,远程温度 1s 到 2s。如果要做快速保护,比如压缩机排气温度保护,可以单独设一个快速通道,100ms 采样,但只做阈值判断,不做数据上报。
6.3 数据有效性检查不能省
远程温度数据必须做有效性检查。我遇到过 RS485 总线受干扰,某个节点发出来的温度值是 0x7FFF,如果不检查直接当 3276.7 度处理,系统就疯了。检查方法很简单:温度值必须在合理范围内,比如 -40 到 150 度,超出范围的直接丢弃,用上一次的有效值代替。
另外,CRC 校验必须做。自定义协议里加 CRC16,Modbus 里自带 CRC,不管哪种,校验不过的数据一律丢弃。我一般还会加一个变化率检查:如果温度值在 1 秒内变化超过 10 度,大概率是误码,丢弃。
6.4 电磁兼容性设计要提前考虑
HVAC 现场电磁环境复杂,变频器、接触器、电机都在旁边,电磁兼容性设计不能省。我一般做这几件事:电源入口加共模电感加 TVS 管,通信接口加隔离,PCB 布局时模拟部分和数字部分分开,地平面完整。
有一次偷懒没加隔离,RS485 收发器在客户现场连续烧了三个,后来加了隔离模块,再没出过问题。隔离模块成本不高,但能省很多售后麻烦。
6.5 固件升级功能要预留
产品出货后难免要改bug或者加功能,固件升级功能一定要预留。我一般用 UART 或者以太网做升级接口,MCU 的 Flash 分两个区,一个跑应用程序,一个跑 Bootloader。升级时先擦除应用程序区,写入新固件,校验通过后跳转。
升级过程中要防止断电导致变砖。我的做法是 Bootloader 区永不擦除,升级失败后自动回滚到旧固件。另外,升级前要备份当前固件,万一新固件有问题可以回退。
7. 方案扩展与场景延伸
7.1 多路温度采集的扩展
PIC32MZ1024EFK144 的 I2C 总线可以挂多个 PJ85718DM,只要地址不冲突。我做过 4 路本地温度采集,用 4 颗 PJ85718DM,分别监测压缩机、换热器、出风口、电控箱。MCU 轮询读取,每路 250ms 一次,4 路总共 1s 一轮,完全来得及。
如果路数更多,比如 8 路或者 16 路,I2C 总线负载会变大,建议用 I2C 多路复用器,或者改用 SPI 接口。PJ85718DM 支持 SPI,速率比 I2C 快,适合多路高速采集。
7.2 无线远程温度节点的实现
有些场景布线困难,远程温度节点可以用无线。我一般用 LoRa 或者 Zigbee,LoRa 距离远,穿透力强,适合楼宇 HVAC;Zigbee 组网方便,适合家庭或者小型商用。
无线节点用低功耗 MCU 加无线模块加温度传感器,电池供电,休眠唤醒周期 10s 一次。数据通过无线网关汇聚到主控 PIC32MZ,再统一上报。无线方案要注意抗干扰,WiFi 和蓝牙在 HVAC 现场干扰大,LoRa 和 Zigbee 相对好一些。
7.3 与楼宇自控系统的对接
HVAC 系统最终要接入楼宇自控系统,常见协议是 BACnet 或者 Modbus。PIC32MZ 跑 BACnet 协议栈有点吃力,建议用 Modbus TCP 转 BACnet 网关。或者直接用支持 BACnet 的通信模块,通过 UART 跟 MCU 对接。
对接的时候要注意数据点映射。楼宇自控系统里,每个温度点都有对应的对象标识符,MCU 上报的数据要跟这些标识符对应上。我一般做一个配置表,存在 Flash 里,升级的时候可以改。
7.4 数据记录与故障追溯
HVAC 系统出故障时,温度数据是重要的排查依据。我一般会在 MCU 的 Flash 或者外挂 SPI Flash 里做数据记录,每分钟存一次本地和远程温度,循环覆盖。Flash 容量 1MB 的话,可以存好几天的数据。
数据记录格式用二进制,紧凑省空间。每条记录包含时间戳、本地温度、远程温度、状态标志。读取的时候通过通信接口导出,用上位机软件解析成曲线,故障排查很方便。
这个方案我从最早的单点采集做到现在的多路加远程加无线,踩过的坑不少,但整体架构是稳的。PJ85718DM 加 PIC32MZ1024EFK144 这个组合,在嵌入式和 HVAC 温度监测场景里,性价比和可靠性都经得起考验。如果你正在做类似的项目,建议先从单路本地采集做起,跑通了再加远程和无线,一步步来,别一上来就搞大而全。