直接做项目的时候,最常碰到的需求就是“把温度测准、传得远、看得见”。前阵子我基于PJ85718DM 温度变送模块和PIC18F87K22 单片机,搭了一套同时支持本地和远程温度监测的嵌入式采集系统,应用场景锁定在 HVAC(暖通空调)和控制柜环境监控上。这套方案不算花哨,但胜在稳定、便宜、可复现,而且把“本地就地显示 + 远程总线传输”两条路都打通了,很适合做设备配套、机房检测或者小型控制器二次开发参考。
先说结论:如果你手头也有一颗 8 位单片机,想接模拟量温度变送器、做多点采集、再通过 RS485 把数据送给触摸屏或上位机,这套思路可以直接抄作业。下面把选型理由、硬件连接、固件代码、通信协议和踩坑记录全部展开,尽量给到能落地的细节。
1. 方案选型与整体设计思路
1.1 为什么选 PIC18F87K22 这颗单片机
PIC18F87K22 是 Microchip 8 位家族里资源比较“豪横”的型号,80 引脚封装,128 KB Flash、4 KB SRAM、1 KB EEPROM,主频最高能跑到 64 MHz。在 8 位机里,它给我最大的信心来自三个地方:第一,内置 12 位 ADC,通道多、分辨率足够处理 0-5V 或 0-10V 的模拟温度变送器信号;第二,片上直接带两个增强型 EUSART,一个接本地调试口,一个走 RS485 远程通信,不用软件模拟串口;第三,工作电压范围 1.8V 到 5.5V,在工业控制柜里电源波动大一点的场合,比 3.3V 专用芯片更抗造。
实际项目里,我没有选带浮点运算单元的 ARM 或者更高端的 MCU,核心原因是这类 HVAC 网关/温控器设备不需要跑复杂算法,Modbus RTU 轮询和 PID 控温都是轻量任务,8 位机绰绰有余。而且这类芯片货源稳定、单价低、封装通用,打样板和手焊都方便,对中小批量产品非常友好。
1.2 PJ85718DM 是什么,信号如何接入
PJ85718DM 是一个典型的导轨式或壁挂式温度变送模块,内部把 NTC 热敏电阻或 PT100 的阻值变化转换成标准模拟电压输出,输出范围常见为 0-5V 或 0-10V,对应测量温度区间可配置为 0-100℃ 或 -20-80℃。选它的理由只有一个:省去自己设计桥式电路和运放调理电路,模块内部已经做了线性化和温度补偿,我这边只需要用 ADC 采样电压,再做软件标定就行。
这类模块通常有三根线:电源正、电源负、信号输出。供电一般用 DC 12V 或 24V,信号输出是共地单端电压。接 PIC 的时候注意一点:如果模块输出 0-10V,而单片机 ADC 参考电压是 5V,中间必须加分压电阻或运放跟随器转成 0-5V,千万别直接怼到引脚上。我这次用的 0-5V 版本,省掉了电平转换,直接接入 ADC 通道,硬件上干净利落。
1.3 本地监测与远程监测的架构区别
“本地”和“远程”在这套系统里分工明确。本地温度指设备安装点附近(比如控制柜内部、风管回风处)的温度,由 PJ85718DM 直接测量,单片机采样后一方面驱动本地的数码管或 LCD 显示,另一方面参与逻辑判断(比如超温报警、风机启停)。远程温度指分布在其他位置的温度点,比如多个楼层的机房、远端空调箱,这些信号通过 RS485 总线汇集到主机,或者把本机测得的数据上传给上位机组态软件。
我做的这套设备本质上是一个“就地采集 + 总线从站”的小网关:本地温度实时显示,远程请求通过 Modbus RTU 读取寄存器时,把本地温度连同从另一个串口读取的外部变送器温度一起返回。这样既满足了现场人员肉眼看温度的需求,也满足了中控室集中监控的需求,一举两得。
2. 硬件连接与信号调理的实操细节
2.1 电源设计与去耦布局
PJ85718DM 模块标称工作电压 DC 12V,但实际用 24V 供电也常见,取决于模块规格。我建议在控制柜里统一用 24V 开关电源,板上再加一颗 12V 稳压(比如 7812)给变送器供电,PIC18F87K22 则用 5V 稳压(AMS1117-5.0 或 7805)。这样 24V -> 12V -> 5V 的链路有一个好处:变送器和单片机之间电源隔离度好,免受柜内大功率设备启停造成的电压跌落影响。
这里有个重点:PJ85718DM 的输出地必须要和单片机的模拟地共地,否则信号无法采样。如果现场干扰严重,建议信号地单点接入 PCB 模拟地,不要和高频数字地混在一起。电源去耦上,每个 IC 的 VDD 引脚旁边放 0.1uF 陶瓷电容,靠近引脚放置;单片机电源入口再放一个 10uF 电解电容。变送器供电入口也串一个磁珠,能滤掉不少开关电源的高频噪声。
2.2 ADC 参考电压的选择
PIC18F87K22 的 12 位 ADC 有一个很关键的可选项:参考电压可以使用内部固定参考或外部 VREF+ / VREF- 引脚。我的经验是,既然要测“绝对温度”,就不要省这颗基准芯片。用外部基准(比如 REF3030 输出 3.0V,或 TL431 搭 4.096V)接到 VREF+ 引脚,ADC 量化单位就稳定了。否则直接用 VDD=5V 做参考,而 7805 的 5V 会随负载和温度漂移,直接反映到读数上,温度波动一两摄氏度很常见。
用 4.096V 基准还有个数学上的好处:12 位 ADC 的是 4096 个码,每个码正好对应 1mV,软件里做电压-温度换算的时候特别顺手,不用做浮点除法,直接用整数移位就能算。这个细节在批量校准时能省很多事。
2.3 输入滤波和防反接保护
PJ85718DM 输出的模拟信号虽然模块内部做了滤波,但连接线如果走得过长,还是容易耦合工频干扰和电机噪声。我习惯在单片机的 ADC 引脚前加一个简单的 RC 低通滤波:1kΩ 电阻串联 + 0.1uF 电容到地,截止频率大概 1.6 kHz,能有效抑制高频毛刺,对温度这种慢变信号完全不影响响应速度。
同时,信号输入端两个二极管做钳位保护,把电压限制在 GND-0.7V 到 VDD+0.7V 之间。这个保护很必要,因为现场维护人员有可能在设备带电时拔插探头,瞬间的浪涌或反接会直接打坏 ADC 引脚。用两颗 BAT54 或 1N4148 就能解决,成本两分钱,能救一块板子。
2.4 本地显示与远程通信的接口预留
本地显示我这次选用的是 4 位数码管,用两个 74HC595 移位寄存器驱动,只占单片机 3 个 GPIO(数据、时钟、锁存),刷新率 50 Hz 无闪烁。如果你想显示更丰富的信息(比如同时显示本地和远程两个温度),用 1602 LCD 也行,PIC18F87K22 的引脚资源完全够。远程通信则用一颗 MAX485 芯片把 UART 转成 RS485 半双工总线,注意 A/B 输出端要加 120Ω 终端匹配电阻(在总线两端各加一个,不是每个节点都加),并且 DE/RE 引脚由单片机一个 GPIO 控制收发方向。
3. 固件实现:采集、标定与滤波
3.1 单片机初始化与 ADC 采样配置
固件流程很简单:上电延时 -> 配置系统时钟(16 MHz 晶振 + 4 倍 PLL 到 64 MHz)-> 配置 UART1 为调试口,UART2 为 RS485 -> 配置 ADC 引脚 AN0 接本地变送器,AN1 接远程变送器 -> 配置定时器 0 做 10ms 时基 -> 主循环里轮询采样、滤波、刷新显示、处理 Modbus 请求。
ADC 采样的关键参数是采样保持时间和转换时钟。PIC18F87K22 的 ADC 需要设置合适的 ADCON2 寄存器,保证采样时间足够长。变送器输出阻抗加上 RC 滤波后,等效内阻偏高,如果采样时间太短,内部采样电容充不满,读数会偏低且不稳定。我实测 1kΩ 串联电阻下,采样时间配置为 20 个 Tad 以上比较安全,转换时钟选 2 分频或 4 分频都能满足 12 位转换精度。
3.2 采样值到温度的换算:两点标定法
理论上 PJ85718DM 模块在 0-5V 输出、0-100℃ 量程下,温度 = 电压 × 20,也就是 250 mV / ℃。但模块本身存在零点和增益误差,直接套公式会带来 ±1-2℃ 偏差。所以正规做法是两点标定:用冰水混合浴(0℃)和沸水浴(100℃,需考虑当地大气压修正)或者精密恒温槽做标定,采集两个点的 ADC 码值,然后按线性插值得到:
T = T_low + (ADC - ADC_low) × (T_high - T_low) / (ADC_high - ADC_low)
这个公式用整数实现即可,温度值乘 100 存储成定点数,比如 25.43℃ 存成 2543,避免浮点运算拖慢 8 位机。把标定好的斜率 K 和截距 B 存到 EEPROM,掉电不丢失。以后每次采样,温度 = K × ADC + B,只做乘法和加法,主循环里跑几十个点毫无压力。
3.3 滤波算法:移动平均和去毛刺
温度信号变化缓慢但偶有尖峰干扰,我在代码里做了两层处理。第一层是中值滤波,取连续 5 次采样排序后取中间值,能干掉电源开关或继电器动作引起的单点毛刺;第二层是滑动平均,把最近 20 个中间值做平均,等价于一个 FIR 低通滤波器,响应时间大约 1 秒,对于 HVAC 这种时间常数很大的对象完全够用。注意滤波窗口不能太长,否则温度报警响应会延迟,我调试时发现 20 点平均在 10ms 采样周期下也就是 0.2 秒延迟,实际体感很跟手。
3.4 多点通道轮询的顺序问题
采集两个温度点时存在一个隐蔽问题:ADC 切换通道后,内部采样电容还残留上一个通道的电压,立刻采样会“串扰”。我在软件里做了两次采样的处理:每次切换通道后先做一次“空采样”丢弃,延时 100us 让电荷释放,然后再连续采样 5 次进入滤波队列。这个细节对两个变送器输出相差很大时尤其重要。比如本地 10℃ 和远程 90℃,如果通道串扰严重,可能互相拉偏 0.5℃。
3.5 核心代码片段参考
下面给出一段简化的 ADC 采样和定点换算代码,思路可以直接移植。
// PIC18F87K22 ADC 采样示例,AN0 为本地温度,AN1 为远程温度 // 温度值用 int16 表示,实际值 = 返回值 / 100 #define LOCAL_CH 0 #define REMOTE_CH 1 // 标定参数,烧录前通过串口命令写入 EEPROM int16_t g_k_local = 2000; // 每 ADC 码对应的温度 0.01℃ int16_t g_b_local = 0; // 截距 0.01℃ uint16_t read_adc_average(uint8_t channel) { uint32_t sum = 0; // 切换通道 ADCON0 = (ADCON0 & 0xC3) | (channel << 2); // 空采样,释放残留电荷 ADCON0bits.GO = 1; while (ADCON0bits.GO); // 正式采样 5 次 for (uint8_t i = 0; i < 5; i++) { ADCON0bits.GO = 1; while (ADCON0bits.GO); sum += ADRES; } return (uint16_t)(sum / 5); } // 定点换算,返回 0.01℃ int16_t calc_temperature(uint8_t channel, int16_t k, int16_t b) { uint16_t adc = read_adc_average(channel); int32_t t = (int32_t)adc * k / 10 + b; // 简化运算,实际按标定系数调整 return (int16_t)t; }注意:ADC 结果的读取必须在 GO 位清零后立即读取 ADRESH 和 ADRESL,不能延迟,否则寄存器可能被下一次转换覆盖。PIC18 系列高字节结果寄存器需要先读 ADRESH,再读 ADRESL,顺序不要颠倒。
4. 本地显示和远程 Modbus 通信的落地实现
4.1 本地数码管动态扫描驱动
数码管驱动我用了 3 个 IO 口加两片 74HC595,在 10ms 定时中断里做动态扫描。显示内容设计为:正常状态轮流显示本地温度和远程温度,各显示 2 秒,中间用一位小数点区分(带小数点的表示远程,不带小数点的表示本地)。这样现场人员不用按键就能同时看到两个温度。如果有按键需求,可以再留一个 GPIO 做模式切换,在“本地/远程/两者交替”之间循环。
给 74HC595 写数据用的是 SPI 主模式,但不用硬件 SPI,直接 GPIO 模拟时序更灵活。需要注意 595 的输出锁存引脚(RCK)要拉高至少几十纳秒才能稳定输出,我把锁存操作放在最后,三个数据位写完后再统一拉高 RCK,避免中间状态闪烁。
4.2 RS485 收发控制时序
MAX485 的 DE 和 RE 通常接在一起,由单片机一个引脚控制。发送数据前先把该引脚拉高(使能发送),然后等一小段时间(至少一个字节的传输时间)再往 UART 发送寄存器写数据。同样,发送完成后不要立即把引脚拉低,要等最后一个字节完全移位出去之后再拉低,否则帧尾会截断。这个时序问题最常见,很多初学者写 RS485 通信时出现“最后一字节丢失”就是这个原因。
判断发送完成的稳妥办法是串口发送中断全部清空,或者直接延时 1.5 个字节时间。我实测 9600 波特率下,用 2ms 延时可靠。如果波特率是 115200,这个延时可以缩短到 0.2ms,但为了保险,我通常留双倍余量。
4.3 Modbus RTU 从站寄存器映射
远程读取我用 Modbus RTU 协议,设备地址可配置(拨码开关或 EEPROM),波特率 9600 8N1。寄存器映射规划如下:
| 寄存器地址 | 功能 | 数据类型 |
|---|---|---|
| 0x0001 | 本地温度值(×100) | 保持寄存器,int16 |
| 0x0002 | 远程温度值(×100) | 保持寄存器,int16 |
| 0x0003 | 温度报警状态(bit0 本地超温,bit1 远程超温) | 保持寄存器,uint16 |
| 0x0004 | 设备运行状态(bit0 传感器断线,bit1 通信正常) | 保持寄存器,uint16 |
Modbus RTU 从机核心任务就是收帧、校验 CRC、解析功能码(03 读保持寄存器)、组帧回复。在 8 位机上处理 CRC16 用查表法速度很快,把 256 字节的表放 Flash 里,每字节查表两次即可。中断收帧时把数据存入环形缓冲区,主循环里解析,避免在中断里做耗时运算。
4.4 简化版 CRC16 计算代码
Modbus CRC16 算起来不难,但容易写错,尤其是初始值和多项式。直接上代码:
// Modbus CRC16 查表法,计算前将 crc 初始化为 0xFFFF uint16_t modbus_crc16(uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; for (uint16_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; }收到完整一帧后,先校验 CRC,低字节在前,高字节在后。如果校验不过就不回复任何内容,等待主站超时重发。这比回复异常码更符合 Modbus 惯例,主站那边也好排查总线问题。
4.5 看门狗与异常自恢复
HVAC 现场设备常年通电,最怕程序跑飞后无人复位。PIC18F87K22 内置看门狗定时器(WDT),我用它来做异常自恢复。正常主循环每轮喂狗一次,如果死循环或外部晶振故障导致循环卡死,WDT 超时复位系统,把设备拉回正常状态。
需要提醒一点:WDT 的使用要配合“残骸复位检测”。复位后先读复位状态寄存器,如果是看门狗复位,就在报警寄存器里标记一次异常重启,同时跳过至少 5 秒的初始化延时,让变送器信号稳定后再开始采样。否则设备会出现在“复位-救急-再复位”的循环里,看起来像电源不稳但其实是初始化时序问题。
5. 常见问题与排查技巧实录
5.1 温度读数漂移不定,是硬件还是软件问题
我调试时遇到过一次典型的“读数漂移”:室内温度没变,显示值从 25.3℃ 慢慢飘到 26.8℃。先怀疑滤波不够,但把滤波窗口拉到 50 点还是飘。后来用万用表测 PJ85718DM 的输出电压,发现电压本身就随供电电压在小幅波动。查下去找到原因:变送器用的是柜内 24V 电源,而那一路电源还带了一个电磁阀,电磁阀动作瞬间 24V 被拉低,模块内部稳压不足导致输出偏移。
解决思路有两个,一是给变送器单独一路稳压供电,二是软件上做电源补偿。我选了前者,因为更彻底。从那以后我把变送器和单片机电源彻底分开了:24V 进来先给变送器,经过 7812 再给 7805 供单片机,模拟部分和数字部分电源回路不相交。这个改动之后,读数稳得像钉子一样。
5.2 RS485 通信偶尔丢帧,单独测又正常
单独接一台设备,上位机读写都正常,挂到现场总线上就 30 秒丢一帧。排查过程可以说一波三折。最终发现是终端电阻的问题:总线上有三台设备,我把每台设备的板上都加了 120Ω 电阻,结果相当于三只电阻并联,总线阻抗远低于特性阻抗,信号反射反而更严重。正确的做法是只用最远端的两个设备加终端电阻,中间设备不焊。
另外还有一点,A/B 线一定要用双绞屏蔽线,屏蔽层单端接地。如果现场已经布了普通平行线,短线(10 米内)还能凑合,长线丢帧就没跑了。现在我做项目一律提前跟结构说清楚:RS485 线必须是双绞屏蔽线,这比换芯片、调波特率都管用。
5.3 ADC 读到的值跳字,像蒙了一层毛刺
把变送器接上后,ADC 数值在 ±20 个码左右跳,折合温度大约 ±0.2℃,对 HVAC 来说勉强能用,但要显示 0.1℃ 分辨率就很尴尬。用示波器看变送器输出,发现高频噪声有几十毫伏。加了 RC 滤波之后降到 ±3 个码。如果还想再稳,可以把 ADC 采样结果每 100ms 做一次平均,但要注意报警判定的延时。另外,如果主板上有继电器或者晶闸管,尽量让它们和模拟输入拉远一点,或者在层间铺地隔离。
5.4 上电瞬间数码管乱码、RS485 收乱码
这种现象多是上电时序问题。单片机上电后 IO 默认是高阻输入,74HC595 的输出锁存端如果被悬空,输出就可能乱闪。我处理方式是板上加一个 RC 延时复位电路,让单片机复位后再初始化 IO 状态。RS485 收乱码通常是 DE/RE 引脚在上电瞬间被外部干扰拉高,导致 RS485 芯片处于发送状态,或者收发切换没有初始化为接收模式。固件里第一步就把 DE/RE 拉低再配置 UART,就能避免。
5.5 温度显示不准,标定流程怎么定才靠谱
两点标定只适合线性度好的模块。PJ85718DM 这类变送器出厂前已经校准,但如果安装位置、供电电压变化,零点仍然会有偏差。我的标定流程是:先让传感器和标准温度计在同一环境下静置 30 分钟,记录 ADC 值和标准温度作为低点;再用恒温槽或精密干井设定 80℃ 作为高点。两点拟合之后,全量程误差可以控制在 ±0.3℃ 以内。如果现场没有恒温槽,至少也要用冰水混合物做零点标定,参考意义很大。
5.6 常见问题速查表
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 读数持续偏高 | 变送器供电偏低或模块零点漂移 | 单独供电,重新两点标定 |
| 读数持续偏低 | ADC 参考电压偏高或采样时间不足 | 检查 VREF,增大采样时间至 20 Tad |
| 刷新时数字跳动 | 滤波窗口太短或显示扫描冲突 | 增加平滑点数,把显示刷新和采样错开 |
| 远程读数为 0 | Modbus 地址映射错误或 CRC 校验失败 | 检查寄存器地址,串口监视收发帧 |
| 总线无响应 | RS485 方向切换时序错误 | 示波器看 DE 引脚,增加发送完成延时 |
| 设备反复重启 | WDT 超时或电源跌落 | 读取复位原因寄存器,改善供电走线 |
最后分享点个人心得
这套项目做完之后,我自己有个很深的体会:温度监测系统看起来简单,真正考验人的不是单片机代码,而是电源、地和信号调理这些“看不见的战场”。PIC18F87K22 是一颗很成熟的芯片,在项目里给我很大自由度,而 PJ85718DM 这种模块化的变送器则帮我省掉了模拟前端的设计调试时间,两者搭在一起,属于“8 位机也能干漂亮活”的典型组合。如果你正在规划类似的小型温控设备、HVAC 外援监测板或者 Modbus 从站,顺着这条线往下做基本不会跑偏。后面我还会把这套代码整理成带注释的全量工程放到网上,供大家直接参考。