ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于PIC18F87J10与PJ85718DM的嵌入式温度监测系统设计与实现

基于PIC18F87J10与PJ85718DM的嵌入式温度监测系统设计与实现 1. 项目缘起与整体设计思路嵌入式温度监测这件事说起来简单做起来坑不少。我最早接触这类需求是在一个环境控制类项目里当时的要求很朴素本地要能看到实时温度远程也要能拿到数据而且整套系统得足够稳不能三天两头掉线。后来拿到 PJ85718DM 和 PIC18F87J10 这两颗料才算把方案彻底跑通。这篇文章就把我踩过的坑、验证过的电路和代码逻辑完整梳理一遍给正在做类似嵌入式温度采集或者 HVAC 控制的朋友一个可直接参考的模板。先说说为什么选这两颗芯片。PJ85718DM 是一颗带 I2C 接口的远程温度传感器本质上是一个远端二极管温度采集前端它可以接一个外部的三极管或者二极管作为感温元件然后把温度值通过 I2C 读出来。它的优势在于感温点可以离主控很远比如你把感温三极管贴在散热器上、风道里、或者管道表面主控板可以放在几米之外中间只用两根线连过去抗干扰能力比直接拉模拟线强太多。PIC18F87J10 则是 Microchip 的一款 8 位单片机自带 I2C 外设、多路 ADC、足够的 Flash 和 RAM跑温度采集、本地显示、串口上报这套逻辑绰绰有余。两者搭配一个负责远端精确测温一个负责本地采集、逻辑处理和通信调度分工非常清晰。整个系统的设计思路可以拆成三条线第一条是本地温度采集线用单片机自身的 ADC 接一个板载热敏电阻或者模拟温度传感器负责监测主控板附近的温度第二条是远程温度采集线通过 I2C 挂载 PJ85718DM读取远端感温点的温度第三条是数据上报线把本地和远程的温度值通过 UART 或者无线模块发出去供上位机或者云端做进一步处理。三条线并行运行互不干扰主循环里做时间片轮询即可。为什么不用单片机直接采远端模拟信号这里涉及一个很实际的工程问题。远端模拟信号在长线传输时线阻、分布电容、电磁干扰都会让 ADC 读数漂移尤其是 HVAC 场景里风机、压缩机、继电器频繁动作干扰非常严重。而 PJ85718DM 把模拟前端放在了远端附近通过 I2C 数字接口回传数字信号对干扰的容忍度远高于模拟信号。这个取舍是我在实际调试中体会最深的一开始图省事直接用长线拉 NTC 到 ADC结果温度读数跳变超过正负三度换成 PJ85718DM 之后波动直接压到零点五度以内。还有一点值得说PIC18F87J10 的 I2C 外设支持标准模式和快速模式PJ85718DM 也支持到 400kHz两者速率匹配没问题。但实际布线时我建议还是跑 100kHz因为长线加上多个从机时400kHz 的上升沿容易变缓导致通信误码。这个后面在实操部分会详细讲。2. 核心器件解析与关键参数计算2.1 PJ85718DM 的工作机制与配置要点PJ85718DM 的核心是一个远端二极管温度采集通道它通过交替注入不同电流到外部三极管测量两次 VBE 的差值从而换算出温度。这个原理的好处是对三极管本身的绝对参数不敏感只跟电流比和理想因子有关所以批次一致性很好。实际使用中外部感温元件我推荐用 MMBT3904 这类小信号 NPN把基极和集电极短接当二极管用焊接在需要测温的位置。配置上PJ85718DM 内部有几个关键寄存器配置寄存器用来设定转换速率、关断模式、报警阈值等温度寄存器直接给出 11 位或者更高精度的温度值还有状态寄存器和上下限寄存器。我一般把转换速率设成 8 次每秒这个速率对 HVAC 这种热惯性大的场景完全够用而且能降低自发热。这里有个细节转换速率越高芯片内部电流越大自发热越明显虽然 PJ85718DM 的自发热很小但在高精度场合还是要注意。温度值的读取格式需要特别注意。PJ85718DM 输出的温度数据是补码格式高字节是整数部分低字节的高几位是小数部分。比如读回来 0x19 和 0x80组合起来是 0x1980右移五位得到 0x00CC也就是 204对应 25.5 摄氏度。这个移位和符号处理如果搞错温度会差出几十度我第一次调试时就因为没处理符号位负温度全读成了正的大数。2.2 PIC18F87J10 的资源分配与 I2C 初始化PIC18F87J10 跑在 8MHz 晶振下经过 PLL 倍频到 32MHz指令周期 8MIPS处理温度采集和串口上报非常轻松。资源分配上我把 RC3 和 RC4 分配给 I2C 的 SCL 和 SDARA0 分配给本地 NTC 的 ADC 输入RC6 和 RC7 给 UART 做数据上报RB 口留给 LCD 或者数码管做本地显示。I2C 初始化有几个参数要算清楚。假设系统时钟 32MHzI2C 目标速率 100kHz那么 SSPADD 的值等于 Fosc 除以4 乘以目标速率再减一也就是 32000000 除以 400000 再减一等于 79。这个值写进 SSPADD 寄存器实际速率就是 100kHz。如果算错了通信要么太慢要么直接失败。我见过有人把 SSPADD 设成 0x27 结果速率跑到 400kHz 以上从机根本来不及响应。本地 NTC 采集这边我用的是 10k 的 B3950 热敏电阻配合 10k 上拉电阻分压。ADC 参考电压用单片机的 VDD也就是 3.3V。温度换算用 Steinhart-Hart 公式或者查表法都行我一般用查表加线性插值速度快而且精度够。这里要注意 ADC 的采样时间NTC 分压点的等效阻抗在 25 度时是 5k 左右采样电容充电需要足够时间我设的采样时间是 8 个 Tad实测下来很稳。2.3 本地与远程温度的精度对比与校准本地 NTC 的精度受限于电阻公差和 ADC 分辨率。10 位 ADC 在 3.3V 参考下每个 LSB 是 3.2mV对应到 NTC 分压点大概 0.1 度左右的分辨率但实际精度受 NTC 本身公差影响不加校准大概正负 1 度。远程 PJ85718DM 的精度标称是正负 1 度实际校准后能到正负 0.5 度以内。校准方法很简单把本地 NTC 和远程三极管放在同一个恒温环境里比如用恒温水浴或者高精度恒温箱分别读取两个温度值记录差值然后在代码里做偏移补偿。我一般取 25 度和 50 度两个点做线性校准这样在整个工作区间内都能保持较好的一致性。校准数据存在单片机的 EEPROM 里上电时读出来用。这里有个经验远程三极管的焊接质量对精度影响很大。如果焊锡不好或者三极管引脚太长热阻会变大导致读数滞后于实际温度变化。我建议把三极管尽量贴近被测表面引脚剪短必要时涂一点导热硅脂。3. 硬件搭建与实操过程详解3.1 电路连接与布线注意事项硬件连接分三块PIC18F87J10 最小系统、PJ85718DM 外围电路、本地 NTC 分压电路。PIC18F87J10 最小系统包括 8MHz 晶振、两个 22pF 负载电容、复位电路和去耦电容。去耦电容我用了 100nF 和 10uF 并联放在 VDD 和 VSS 引脚最近的地方这个对 I2C 通信稳定性影响很大省不得。PJ85718DM 的 SCL 和 SDA 分别接到单片机的 RC3 和 RC4两根线各接一个 4.7k 上拉电阻到 3.3V。上拉电阻的值不能随便选太小功耗大太大上升沿变缓。4.7k 在 100kHz 和 3.3V 下是折中值实测波形很干净。如果总线电容超过 200pF比如线比较长或者挂了多个从机上拉电阻要减小到 2.2k 左右。远程三极管的连接是重点。PJ85718DM 的 D 和 D- 分别接三极管的基极加集电极和发射极。走线要用双绞线或者屏蔽线D 和 D- 尽量靠近减少环路面积。我在一个 HVAC 项目里因为把 D 和 D- 分开走线结果风机一启动温度就跳变后来改成双绞线并加了一个 100nF 的差分滤波电容问题才解决。本地 NTC 分压电路很简单3.3V 经过 10k 上拉电阻到 NTCNTC 另一端接地分压点接 RA0。分压点并联一个 100nF 电容滤波效果很明显。NTC 的引线也不宜过长最好直接焊在板子上如果必须延长用屏蔽线。3.2 固件框架与关键代码实现固件用 C 语言写编译器用 XC8。主循环采用时间片轮询每 100ms 采集一次本地和远程温度每 1s 通过串口上报一次。I2C 读写用状态机实现避免阻塞主循环。I2C 读 PJ85718DM 温度的核心代码如下unsigned int read_remote_temp(void) { unsigned char msb, lsb; unsigned int raw; I2C_Start(); I2C_Write(0x90); // PJ85718DM 写地址 I2C_Write(0x00); // 温度寄存器地址 I2C_Start(); I2C_Write(0x91); // 读地址 msb I2C_Read(1); lsb I2C_Read(0); I2C_Stop(); raw ((unsigned int)msb 8) | lsb; raw 5; if (raw 0x0400) { // 负温度处理 raw | 0xF800; } return raw; }这段代码里地址 0x90 和 0x91 取决于 PJ85718DM 的 A0 到 A2 引脚接法我全接地所以是 0x90。温度值右移五位后是 11 位有效数据最高位是符号位。负温度要扩展符号位否则会读成很大的正数。本地 NTC 的 ADC 读取和换算unsigned int read_local_adc(void) { ADCON0bits.CHS 0; // 选择 AN0 ADCON0bits.GO 1; while (ADCON0bits.GO); return ((unsigned int)ADRESH 8) | ADRESL; } float adc_to_temp(unsigned int adc) { float voltage adc * 3.3f / 1024.0f; float resistance 10000.0f * voltage / (3.3f - voltage); // 查表加线性插值返回摄氏度 return lookup_temp(resistance); }查表法比直接算对数快很多表里存的是电阻值和对应温度每 5 度一个点中间线性插值。实测下来从 ADC 读到温度输出整个换算耗时不到 100 微秒对主循环毫无压力。3.3 串口上报协议与上位机对接串口上报我定义了一个简单的帧格式帧头两个字节 0xAA 和 0x55然后一个字节表示数据长度接着是本地温度高字节、本地温度低字节、远程温度高字节、远程温度低字节最后是校验和。温度值统一放大 10 倍用整数传输比如 25.5 度传 255避免浮点传输的麻烦。上位机用 Python 写一个简单的解析脚本收到帧后校验、解析、显示。这个协议虽然简单但足够可靠我在多个项目里都用这套格式没出过问题。如果要接云端中间加一个无线模块或者网关把串口数据转成网络数据即可。这里提醒一点串口波特率我用的 9600虽然慢但抗干扰好线长一点也没问题。如果数据量大可以提到 115200但线不能太长最好在板子附近。4. 常见问题排查与避坑经验实录4.1 I2C 通信失败与温度读数异常I2C 通信失败是最常见的问题表现是读回来的温度一直是 0 或者 0xFF。排查顺序我一般是这样先用示波器看 SCL 和 SDA 波形确认有没有上拉、上升沿是否够快、时钟频率是否正常。如果波形正常但读不到数据检查从机地址对不对PJ85718DM 的地址由 A0 到 A2 决定接法不同地址就不同。如果地址对但数据不对检查寄存器指针有没有写对温度寄存器地址是 0x00写错就读到别的寄存器了。温度读数异常还有一种情况是符号位没处理好负温度读成很大的正数。这个在代码里加一个符号扩展就能解决。另外如果温度值一直不变或者跳变很大检查感温三极管有没有焊好D 和 D- 有没有接反。接反了读数会完全不对但不会损坏芯片。4.2 本地 NTC 读数漂移与干扰抑制本地 NTC 读数漂移通常有两个原因一是 ADC 参考电压不稳二是分压点受干扰。参考电压不稳的话可以在 VDD 上加一个稳压芯片或者用外部基准。分压点受干扰的话加滤波电容是最直接的办法100nF 不够就上 1uF。如果干扰来自继电器或者电机还要考虑在电源和地上加磁珠或者共模电感。还有一个容易被忽略的点NTC 的自发热。NTC 上一直有电流流过会自己发热导致读数偏高。10k 上拉在 3.3V 下25 度时电流大概 0.16mA自发热功率很小一般可以忽略。但如果用更小的上拉电阻比如 1k电流就到 1.6mA自发热就明显了。所以上拉电阻不要选太小。4.3 长线传输与多从机场景的稳定性优化长线传输时I2C 的上升沿会变缓因为线电容大了。解决办法是减小上拉电阻但功耗会增加。我一般先算总线电容然后根据上升沿时间要求选上拉电阻。上升沿时间要求是 1000ns 以内总线电容假设 200pF上拉电阻最大是 1000ns 除以 200pF 再除以 0.847大概 5.9k所以 4.7k 是安全的。如果线更长电容到 400pF上拉电阻就要降到 2.2k 左右。多从机场景下每个从机的地址不能冲突。PJ85718DM 支持 A0 到 A2 三个地址引脚最多挂 8 个。如果不够可以用 I2C 多路复用器扩展。另外多从机时总线电容是累加的上拉电阻要相应减小。4.4 常见问题速查表问题现象可能原因排查方法解决措施I2C 读不到数据上拉电阻缺失或过大示波器看波形加 4.7k 上拉温度读数恒为 0从机地址错误核对 A0-A2 接法修正地址负温度读成大正数符号位未扩展检查代码移位逻辑加符号扩展本地温度漂移参考电压不稳测 VDD 纹波加稳压或基准远程温度跳变感温线受干扰检查走线双绞线加滤波通信偶发失败总线电容过大算总线电容减小上拉电阻这张表是我在实际调试中总结出来的基本上覆盖了八成以上的问题。遇到新问题先查表查不到再逐步排查。5. 系统扩展与场景适配建议5.1 多路远程温度采集的实现如果项目需要监测多个远程点比如 HVAC 系统里要同时监测回风、送风、盘管等多个位置可以用多个 PJ85718DM 挂在同一条 I2C 总线上通过 A0 到 A2 设置不同地址。每个 PJ85718DM 接一个感温三极管分别放在不同位置。固件里轮询读取每个地址的温度值然后打包上报。多路采集时要注意总线负载。每个 PJ85718DM 的输入电容大概 10pF加上走线电容挂 8 个的话总线电容可能到 300pF 以上上拉电阻要相应调整。另外轮询频率不要太高每个从机每秒读一次就够了读太快反而增加总线冲突概率。5.2 低功耗设计与电池供电场景如果系统要用电池供电低功耗设计就很重要。PIC18F87J10 支持多种低功耗模式可以在两次采集之间进入睡眠用定时器唤醒。PJ85718DM 也支持关断模式不采集时关掉功耗降到微安级。本地 NTC 分压电路在睡眠时也会耗电可以在上拉电阻上串一个 MOS 管睡眠时断开。实测下来采集间隔 10 秒、每次采集 100ms 的方案平均电流可以做到 1mA 以下用两节 AA 电池能跑几个月。如果采集间隔更长功耗还能进一步降低。5.3 与 HVAC 控制系统的联动温度数据采集出来之后最终要用于控制。比如当回风温度超过设定值启动压缩机当送风温度过低防止盘管冻结。这些逻辑可以在 PIC18F87J10 里直接实现通过 GPIO 控制继电器或者固态继电器。也可以把温度数据上报给上位机由上位机做更复杂的控制策略。联动控制时要注意迟滞设计避免温度在设定点附近频繁波动导致执行器频繁动作。我一般设 1 度的迟滞比如设定 25 度启动制冷降到 24 度才停止。这个迟滞值可以根据实际系统惯性调整。6. 个人实操体会与后续优化方向这套方案我从最早的面包板验证到后来的 PCB 量产前后迭代了三四版最大的体会是温度采集这件事精度和稳定性往往不取决于芯片本身而取决于外围电路和布线细节。PJ85718DM 和 PIC18F87J10 本身都很可靠但上拉电阻选错、滤波电容省掉、感温线走不好照样出问题。另一个体会是校准不能省。哪怕芯片标称精度够用实际装到板子上之后受布局和周边器件影响总会有偏差。花半个小时做两点校准后面能省很多调试时间。校准数据存 EEPROM 里产线上一块板子校准一次就行。后续如果要做产品化我会考虑几个方向一是把本地 NTC 换成数字温度传感器比如带 I2C 接口的型号省掉 ADC 和查表逻辑二是把上报接口从串口换成无线方便组网三是加一个看门狗和通信超时复位提高长期运行可靠性。这些改动都不大但能让系统更健壮。最后分享一个小技巧调试 I2C 的时候如果手头没有示波器可以用单片机的 GPIO 模拟 I2C 主机配合逻辑分析仪抓波形成本很低但非常实用。我早期就是用这个方法定位了好几个通信问题。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进