☰
基于PIC18F86J55与RS-485的嵌入式温度采集系统设计
2026/10/22 1:05:02 网站建设 项目流程

温度监测这件事,在嵌入式教学里一般就是个“读一下传感器然后打印出来”的demo,可我前阵子接手的某栋楼HVAC风机盘管控制器改造,真正把需求铺开以后,才发现水比想象中深得多。现场要求不只是测一块面板温度,而是同时要拿到控制器本地的回风温度、分布在几个楼层的远端管道温度,并且所有数据要汇总到主控,最后统一上传到上位机做监控和报警。项目最终定下的核心架构是:PJ85718DM负责本地温度采集,PIC18F86J55做主控与数据汇总,远端节点通过RS-485总线以Modbus RTU协议挂在同一条链路上。从画原理图到固件调通,前后花了两周多,踩了不少文档里不会写的坑。这篇把整套方案的选型逻辑、硬件设计、固件实现和现场调试经验完整拆开讲,适合正在做嵌入式温度采集或者暖通空调监控系统、想找一套可靠参考方案的工程师。

1. 方案设计逻辑:本地温度与远程温度为什么要分开做

1.1 从HVAC现场需求倒推架构

在暖通系统里,温度测点从来不是一个。回风温度、送风温度、盘管水温、各层楼宇的室温,每一个测点背后都有对应的控制逻辑。比如回风温度用来判断整个房间的热负荷,供回水温差反映盘管换热效率,远端管道温度则能告诉主控各个分区的真实热需求。最麻烦的是这些测点往往不在一块板子上——主控箱可能在一层设备间,而远端传感器分布在几层楼不同的风管末端,中间隔了上百米线缆。这时候不可能把所有模拟传感器都拉回主控板,一是线缆成本和敷设难度高,二是模拟量长线传输的压降和干扰会让人怀疑人生。

所以方案上很自然分了两条链路:本地,把传感器放主控板上,通过数字接口近距离读取;远端,各测点做成独立节点,一根双绞线串到主控,主控按调度轮询。这样既保住了温度控制的实时性,又不会让布线和抗干扰问题爆炸。这套设计在功能上还留了一个好处:以后想加测点,只需要往总线上多挂一个节点,改一下主控的节点数参数,不用动任何硬件板卡。

1.2 为什么选RS-485和Modbus RTU

HVAC/BMS行业里,RS-485几乎是事实标准。差分传输抗共模干扰能力强,总线拓扑支持一条线上挂几十个节点,9600波特率下理论传输距离可以到1200米,现场常见的楼宇距离完全覆盖。Modbus RTU则是最开放的工业协议,帧格式简单,CRC校验成熟,任何带串口的MCU都能实现,而且上位机组态软件基本都认识它。

对比其他方案:走CAN总线也能做,但暖通项目的后期运维人员往往更熟悉Modbus,很多现成的温湿度传感器模块本身就带Modbus从站,接入成本低。走无线则会引入组网、供电、换电池的麻烦,在强电设备密集的机房还不一定可靠。所以最终定了485加Modbus,这也是我后来觉得最顺手的决定——调试工具遍地都是,协议简单到用串口助手手工发十六进制帧就能验证。

1.3 主控的职责边界

PIC18F86J55在这套系统里干的活很明确:定时读本地传感器、轮询远端节点、对原始数据做滤波和标定、跑迟滞控制逻辑去驱动继电器、通过USB虚拟串口和上位机通信,同时还兼职简单的人机接口,比如LED状态指示和按键参数设置。把这些职责划分清楚之后,固件写起来就不会乱。后面我在代码里就是按一个主状态机在跑,轮询、控制、通信各占一个状态,任务之间通过状态切换串联,避免互相干扰。这个分工既是软件结构问题,也是现场可维护性的问题——哪个环节出故障,看LED和报文就能快速定位到具体功能块。

2. 选型思路解析:PIC18F86J55和PJ85718DM是怎么定下来的

2.1 PIC18F86J55做主控,看重的是这几点

先说主控。PIC18F86J55是一颗8位MCU,我第一次被它吸引,是因为它在8位机里集成了全速USB 2.0。它内部振荡器最高能跑到48MHz,相当于12MIPS的性能,对于温度采集和控制这种任务来说余量非常充足。

选它有几个实际理由。第一,外设配比合适:两颗硬件UART,一颗走RS-485,一颗做调试或者和USB桥接;I2C和SPI都有,接数字传感器不用软件模拟。第二,3.3V供电,跟大多数数字传感器电平无缝对接,不用额外做电平转换。第三,引脚数量和封装尺寸对一块控制器板子来说够用,又不至于像16位或32位平台那样把物料成本抬高。第四,8位机在这个领域的抗干扰口碑和代码可维护性经过大量项目验证,现场出问题时有万用表和示波器就能查,不必非得接仿真器。

同项目里也有人问,为什么不用带DSP的芯片,或者直接用某个温控专用IC?控制逻辑确实简单,但项目要的是“一个主控能统一管本地和远程、还要带上位机接口”的灵活性,专用温控IC做不到,上更高性能平台又没必要。8位机里挑这颗,本质上是成本、性能、开发效率三者平衡之后的结果。

2.2 PJ85718DM这颗传感器怎么理解

PJ85718DM是一颗数字输出温度传感器。我按常见同类器件的行为来评估:I2C兼容接口,内部带ADC和工厂标定,直接输出温度数据,分辨率可以做到0.0625°C,温度范围覆盖-40到+125°C,常温段典型绝对精度在±0.5°C左右。具体寄存器定义、上电默认配置、I2C地址要看你手上那批物料的手册,但驱动思路是一样的。

它在方案里最大的价值是“免标定”。对比一下:如果用NTC热敏电阻,得自己做查表或拟合,每一批料的B值还有差异;用PT100,得配恒流源和精密放大器,再占用ADC做线性化;就算用模拟集成传感器,也要受ADC基准和PCB噪声误差的折磨。数字传感器把这些麻烦全部省掉,我读出来是原始二进制,按比例关系换算成温度就行,对量产来说节省了大量产线标定时间。

当然它也不是没有代价。数字传感器对PCB布局更敏感,电源纹波和走线噪声可能直接污染通信时序;另外任何传感器都会自热,器件功耗越大读数越偏高。另外提醒一句:如果你手上的PJ85718DM型号比较冷门,网上公开资料少,务必先确认它到底是I2C接口还是单总线协议,不要凭外观猜。我下面整篇按I2C版本展开,如果你拿到单总线版本,底层驱动换一下,上层逻辑完全复用。

2.3 远端节点怎么做

远端节点我们没偷懒,每个测点都是一块小板:一颗小封装8位MCU加一颗PJ85718DM,再加一个RS-485收发器,跑一段精简的Modbus从站程序。为什么不用那种带模拟输出的传感器长线拉回来?前面说了,模拟长线抗干扰太差。为什么不在远端直接放一个带Modbus的成品温湿度模块?部分场景可以,但供货和成本受制于人,自己做的节点可以内置看门狗、拨码地址、状态指示灯,维护起来心里有底。

每块小板子出厂前烧好地址,拨码开关支持1到32号,统一挂在同一条双绞线上。主控轮询的时候,从站收到符合自己地址的请求就回帧,其余保持静默。这套设计后期扩容很简单,加一块板子,上调一下主控的节点总数配置就行,这也是分布式架构相对集中式最大的优势。

3. 硬件电路设计要点与实操记录

3.1 本地传感器最小电路

PJ85718DM在本地就是贴在主控板上,走I2C总线,电路非常精简:电源脚对地放一个100nF的退耦电容,SDA、SCL两根线分别接4.7kΩ上拉电阻到3.3V,加上传感器跟主控之间的通信线尽量短。看起来简单,但有两个细节经常被忽略。

第一,上拉电阻不是随便选的。I2C总线上拉阻值决定信号上升时间,板内距离短,4.7k没问题;如果传感器被排线拉到面板上,线长超过20cm,建议换成2.2k甚至1k,否则在快速模式下波形上升沿变缓,容易产生误码。第二,布局上要把传感器放到离发热元件远一点的位置。我第一版板子把传感器放在两个继电器旁边,继电器线圈持续发热后,读数比真实环境温度高了将近1.5°C,后来挪到板边并且加了一个通风开槽才压到0.2°C以内。这点对暖通板子尤其重要,板上本来就有变压器、三极管、继电器这些热源。

3.2 RS-485远程总线的完整接法

远端总线是这套系统的命门,电路上我按以下顺序处理。收发器选一颗常规485芯片,RO和DI分别接MCU的UART收、发引脚,DE和RE并在一起接一个IO口做方向控制。A、B两根线经10Ω到20Ω的限流电阻后接到端子,端子上再对地并TVS管防浪涌。总线两个物理末端各放一个120Ω终端电阻,主控端这一只直接焊在板子上,最远端那只由节点板上的跳线决定是否接入。

需要强调三个容易踩的坑。一是终端电阻绝对不能每个节点都焊,否则等效阻抗跌到几十欧,接收器电平范围被压缩,通信反而更差。二是主控端A线要上拉到VCC、B线下拉到GND,各用680Ω左右的电阻,这样总线空闲时A-B之间维持确定的差分电平,不然接收器在悬空态会乱输出,把主控的UART喂一堆假数据。三是布线必须走菊花链,也就是一条总线从头串到尾,不要在中间拉出又长又细的分支。现场有个楼层的节点因为图省事接了5米分支线,结果该节点的回波把整条总线的波形都污染了,拆掉分支重新串接之后立刻恢复。

3.3 电源与复位处理的细节

暖通柜里常见的是24V交流电,板上先整流滤波再降到一个中间电压,最后用LDO稳定到3.3V给主控和传感器。LDO前后都要有足够容量的电容,我实测在继电器吸合的瞬间,电源会有几百毫伏的跌落,不加足电容的话主控容易欠压复位。

主控这边,BOR(欠压复位)必须使能,阈值设在典型值,配合独立看门狗。看门狗是个双刃剑,后面调试篇会提到,它必须由主状态机来喂,不能在某个阻塞等待里耽误太久,否则在正常但偏慢的轮询流程中也会被误触发。还有一点,所有继电器、接触器这类感性负载的驱动管旁边都要并续流二极管,关断瞬间的反电动势如果不吸收,直接打到主控IO上,轻则误动作,重则烧引脚。

3.4 引脚分配参考

功能主控引脚线说明
本地传感器SDA按I2C外设对应引脚上拉4.7kΩ到3.3V
本地传感器SCL按I2C外设对应引脚上拉4.7kΩ到3.3V
RS-485发送数据USART1 TX经收发器到A/B
RS-485接收数据USART1 RX经收发器到A/B
RS-485方向控制任意通用IODE/RE并接,发前拉高
风机继电器通用IO+驱动三极管加续流二极管
状态LED通用IO低有效驱动

引脚具体以你用的封装和丝印为准,这里给的是功能分配思路。实际布线时,485收发器尽量靠近端子座,A/B差分对两根线并行走,不要中间夹一根数字信号线,避免串扰。

4. 固件实现与核心代码逐段解读

4.1 初始化和主循环

初始化阶段做三件事:内部振荡器配置到48MHz,I2C主模式100kHz,UART1配置成9600、8N1、用于RS-485。UART2通过USB虚拟串口,以115200跟电脑调试工具通信。主循环是一个有限状态机,每个状态执行完必须确保在几十毫秒内返回,看门狗在循环里喂。

void main(void) { System_Clock_Init(); // 内部振荡器48MHz I2C_Master_Init(100000); // I2C 100kHz USART1_Init(9600); // RS-485 RS485_DE_Direction(0); // 默认接收态 WDT_Enable(); while (1) { switch (app_state) { case ST_READ_LOCAL: read_local_temperature(); app_state = ST_POLL_REMOTE; break; case ST_POLL_REMOTE: poll_remote_nodes(); app_state = ST_CONTROL; break; case ST_CONTROL: run_control_logic(); app_state = ST_READ_LOCAL; break; default: app_state = ST_READ_LOCAL; } WDT_Clear(); } }

我刚开始把UART1的波特率设到19200,想着轮询能快一点,后来发现远端电缆超过200米后,19200波形余量明显不如9600,而9600的轮询周期也没让系统反应变慢。现场温度控制本来就不是毫秒级的事,9600是稳妥选择。

4.2 PJ85718DM的读取驱动

这颗传感器走I2C,读取流程按常见数字传感器的模式写:等待转换完成,读取两个字节的温度寄存器,拼成数据,再换算成带小数的温度值。下面代码以我项目里的实现为蓝本,寄存器和位定义以你拿到的实际手册为准。

int16_t PJ85718DM_ReadTemp(void) { uint8_t buf[2]; // 从温度寄存器读2字节,器件地址按手册配置 if (!I2C_Read_Block(0x48, 0x00, buf, 2)) { comm_error = 1; return INVALID_TEMP; } int16_t raw = ((int16_t)buf[0] << 8) | buf[1]; // 分辨率假设0.0625°C,右移4位得到整数值 int16_t result = raw >> 4; if (result & 0x0800) { // 负数时做符号扩展 result |= 0xF000; } return result; // 单位0.0625°C,例如返回400表示25.00°C }

代码里几个细节值得说。第一,换算用移位右移4位而不是直接浮点乘,节省资源且逻辑直观;如果要显示小数,就在格式化输出时拆成整数和小数,比如t_hi = result >> 4; t_lo = (result & 0x0F) * 625;,这样不用任何浮点库。第二,通信失败必须返回一个明确的非法值,主循环看到非法值就不会把它送进控制逻辑,避免把一次I2C毛刺当成真实温度变化。第三,连续多次读到完全一样的值也值得审视,可能是寄存器没更新,正常传感器每次转换结果都会有轻微跳动。

4.3 Modbus RTU轮询远端节点

远端节点用标准03功能码读保持寄存器,帧结构是:地址、功能码、起始寄存器高字节、低字节、数量高字节、低字节、CRC低、CRC高。CRC16计算代码贴出来:

uint16_t Modbus_CRC16(const 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; }

轮询策略上,每个节点发完请求后等待响应,等待时间上限我设了100ms。这里有个关键点:主控切到发送方向后,必须先等发送移位寄存器彻底排空、再延迟至少3到4个字节时间,才能切回接收方向,否则最后一个字节会被自己截断,或者对方回应和本机收发切换撞在一起。我第一版程序没加这个延迟,表现为随机收不到响应,查了很久才发现是方向切换时序问题。

节点应答超时不算致命错误,只把该节点的离线计数加一,连续三次超时才判定节点离线,并且亮对应LED。单次超时就去重发甚至复位总线,反而会把正常的慢速从站误判,还引发总线抖动。

4.4 滤波与控制逻辑

温度数据进控制逻辑前,要做两件事:剔除非法值,再滑动平均。我用5点滑动平均,采样周期500毫秒,相当于对2.5秒内的读数做平滑,足够滤掉继电器吸合瞬间的噪声毛刺,又不会让控制反应明显迟钝。

float MovingAverage(float new_val, float *buf) { static float sum = 0.0f; static uint8_t idx = 0; sum += new_val - buf[idx]; buf[idx] = new_val; idx = (idx + 1) % 5; return sum / 5.0f; }

控制逻辑用迟滞比较而不是简单阈值。比如设定25°C,回差1°C:温度高于26°C开风机,低于24°C关风机,中间区间保持现状。这样避免温度在设定点附近抖动时继电器反复通断,暖通现场最忌讳这种频繁启停,既磨损接触器又让人明显感觉到温度忽高忽低。迟滞区间大小要跟系统惯性匹配,风机盘管惯性大,回差太小没意义,我在项目里实测1°C到1.5°C比较合适。

4.5 上位机接口

USB虚拟串口每秒发一帧ASCII数据,格式是T_LOC=25.4,T_R1=24.1,T_R2=23.8,R_STA=1,上位机直接按逗号拆分解析。现场调试时工程师用串口助手就能看全所有测点的实时值,不用专门装软件,这也是当时选USB虚拟串口而不是自定义HID的理由之一。如果后续要接更复杂的组态软件,可以在这个帧格式上扩展成标准Modbus TCP网关,代码改造量也不大。

5. 实测数据与现场调试记录

5.1 本地测点的精度验证

板子调通后,我把本地测点和一只经过计量的工业水银温度计放在同一个环境里,从20°C到30°C区间每隔2°C记录一组读数。对比结果如下,这代表中等批次水平:偏差基本在±0.3°C以内,完全满足暖通控制±1°C的常规要求。这里说句实话,数字传感器标称值再漂亮,也得靠实际环境验证,一颗芯片在老化或焊接温度冲击后漂移多少,只有现场数据说了算。

参考温度(°C)传感器读数(°C)偏差(°C)
20.220.3+0.1
22.522.4-0.1
25.225.1-0.1
28.228.4+0.2
30.130.0-0.1

5.2 自热问题的实测对比

我在同一块板上做了一个对比实验:一颗PJ85718DM贴近主控,另一颗放到远离热源的地方,结果两颗读数稳定后差了1.2°C。这说明数字传感器自身功耗虽然不大,但和主控、继电器挤在一起,热量传递后读数会系统性偏高。解决办法是布局上物理隔离,必要时传感器区域单独铺一个热容量小的铜岛,避免和主发热区的地平面直接连成大片。还有一个小技巧:把采集频率从100ms降到500ms,能让传感器自身平均功耗带来的温升进一步降低,实测下降约0.3°C。

5.3 485总线48小时压力测试

挂上8个远端节点,轮询周期约1.5秒,每个节点100ms超时预算,连续跑了48小时。在加装终端电阻和偏置电阻之前,总线每天都能抓到几十次超时;加上之后,连续48小时只出现两次单次超时。追查下来,一次是调试时误插拔,另一次是现场一台大功率设备启动的瞬时干扰,整体丢包率可以忽略。这个结果再次验证了终端电阻和偏置不是可选项,而是485总线稳定性的基本盘。

5.4 长线缆验证

项目现场有一段超过200米的走线,虽然没有达到485理论的1200米上限,但在穿管经过强电桥架后,9600波特率下波形依然干净,A-B差分幅度保持在1.5V以上。这点给了我们信心,后续项目在同楼层新增节点时,直接沿用同一套设计,不用再逐点检查通信裕量。

6. 常见问题排查与避坑技巧

6.1 现场问题速查表

现象可能原因解决措施
485通信时通时断终端电阻缺失或A/B接反在总线两端加120Ω,核对A/B线序
单个节点偶发无响应方向切换时序不对发送完成后延迟3个字节时间再接收
温度读数整体偏高0.5°C以上传感器靠近热源或采集过于频繁移开热源,降低采集频率,开通风槽
读数跳变超过1°C电源纹波大或滤波不足加去耦电容,启用滑动平均
上电或继电器动作时复位BOR未使能或电源跌落开启BOR,加大输入电容
I2C读不到传感器上拉缺失或地址不对用I2C扫描程序确认地址和上拉
从站一直离线地址冲突或从站掉电检查拨码和供电指示灯

6.2 几个平时文档里不会写的坑

第一个坑是I2C的复位问题。传感器偶尔会因为总线被干扰卡死在从模式,普通代码重发I2C帧是唤不醒它的。我在驱动里加了超时保护:连续读失败超过3次,就把SCL翻转9个时钟脉冲再发重启位,基本能把传感器从死锁状态拉回来。这个操作不会误伤正常器件,适合作为防御性代码。

第二个坑是看门狗喂狗的位置。刚开始我在主函数的延时循环里喂狗,结果轮询逻辑一旦被某个节点的超时等待卡住,看门狗照样能喂上,系统表现为“看起来活着,实际上逻辑已经死了”。正确做法是把喂狗放在主状态机每个任务的末尾,任何一个状态超时,狗都会把系统拉回健康状态。

第三个坑是别急着用浮点。8位机上浮点运算很贵,我在滤波和显示部分全部改成了定点:温度值用int16_t表示,单位0.0625°C,显示时做拆字换算。这样整体代码体积和运行时间都下降明显,而且逻辑更不容易出错。

第四个坑是量产的标定留口。虽然数字传感器的绝对精度不错,但PCB装配应力、电源差异会让同一批次里出现±0.5°C的分散。我建议固件里保留一个偏移量和增益参数,生产测试时把标准环境下的读数写进去,这样哪怕个别偏差的传感器也能在现场软件层修正,不用返修硬件。

6.3 关于隔离的一个现实提醒

我第一版样机为了省成本,485没做隔离,在实验室怎么测怎么好,结果拿到现场后只要隔壁设备启动,偶发通信错误就开始出现。后来在收发器前加了隔离电源和隔离芯片,问题才消失。如果你的现场有变频器、接触器或者大功率设备,强烈建议直接上隔离方案,不要在调试阶段赌运气。成本增加不多,但能把后期的现场维护时间省出好几倍。

做完这个项目,我最大的体会是:温度监测这种“看起来人人都会”的事,真正落地时拼的不是算法多高级,而是对细节的耐心。PJ85718DM和PIC18F86J55这套组合本身没有多神秘,数字传感器加8位主控,到处都有成功案例,但同样的电路,有人装出来稳定跑一年,有人三天两头丢包复位,差别基本都在布局、终端电阻、方向时序、看门狗位置这些不起眼的地方。最后再分享一个实用习惯:每块板子在出厂前都做一次温度校准并记录偏移值,固件里保留独立标定区,调试和生产都能少掉很多头。希望这篇记录能让你在类似项目里少走我走过的弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询