基于PJ85718DM与STM32F091RC的本地远程双路温度监测方案
1. 从一颗温度传感器说起为什么本地与远程双路监测值得单独做嵌入式温度监测这件事看起来简单真做起来坑不少。我接触过不少 HVAC暖通空调和工业控制类的项目客户最常提的需求就是帮我测几个点的温度超了阈值就报警或者联动。听起来一句话的事但落到电路板和固件上涉及的东西远比想象中多传感器选型、接口协议、本地与远程的区分、采样精度、抗干扰、通信可靠性每一项都能单独写一篇。这次要聊的方案核心是围绕PJ85718DM这颗温度传感芯片搭配STM32F091RC这颗 ARM Cortex-M0 微控制器做一套能同时监测本地温度和远程温度的系统。所谓本地通常指传感器直接贴在主控板附近测的是设备自身或机箱内部的温度远程则是传感器通过一段线缆延伸到几米甚至十几米外测的是另一个物理位置的温度比如 HVAC 系统里的回风管、出风管、水箱或者室外机。为什么要把本地和远程放在一套系统里做因为很多实际场景下这两个温度是相互关联的。举个例子一个空调控制柜本地温度反映的是控制板自身的工作环境远程温度反映的是被控空间的真实温度。如果只看远程温度你可能会忽略控制柜内部过热导致的器件漂移如果只看本地温度你又不知道实际被控对象的状态。两者结合才能做出更靠谱的判断逻辑。STM32F091RC 这颗芯片在这个场景里挺合适。它是 Cortex-M0 内核主频 48MHz带 256KB Flash 和 32KB SRAM外设资源对于温度监测这种任务绰绰有余——多路 ADC、多个定时器、I2C、SPI、UART 都有。关键是它功耗低、成本可控在 HVAC 这种量大面广的应用里BOM 成本敏感选它是有道理的。而 PJ85718DM 作为温度传感前端负责把物理温度转换成可读的数字或模拟信号是整个测量链路的起点。这篇文章我会从硬件接口、通信协议、固件架构、本地与远程的差异化处理、实测踩坑几个角度把这套方案讲透。不管你是刚接触嵌入式温度采集的新手还是做过类似项目想找参考的老手应该都能从中拿到能直接用的东西。2. PJ85718DM 与 STM32F091RC 的接口设计选型逻辑与硬件连接细节2.1 为什么是这两颗芯片的组合先说说选型背后的逻辑。温度传感器大致分几类热敏电阻NTC/PTC、热电偶、RTD铂电阻、以及集成式温度传感 IC。热敏电阻便宜但线性度差、需要校准热电偶适合高温但需要冷端补偿RTD 精度高但成本也高、需要激励电流源。集成式温度传感 IC 则是把传感元件和信号调理电路封装在一起输出数字信号或标准模拟电压用起来最省心。PJ85718DM 属于集成式温度传感这一类它的优势在于输出接口灵活、精度够用、外围电路简单。对于 HVAC 这种温度范围通常在 -40°C 到 125°C 之间的应用它的量程和精度都能覆盖。而且它支持远距离布线时的抗干扰设计这一点对远程温度监测至关重要。STM32F091RC 这边我选它的理由有三条。第一它有多路 12 位 ADC采样速率和分辨率对于温度这种慢变量完全够用而且可以配置成扫描模式同时采多路。第二它有丰富的通信外设I2C 可以接数字温度传感器UART 可以做远程通信SPI 可以接外部 ADC 或存储器。第三Cortex-M0 的内核虽然简单但对于温度采集这种任务中断响应和低功耗模式都够用而且开发工具链成熟ST 的 HAL 库和 CubeMX 能省不少事。提示选型时不要只看参数表上的最高精度要看你的实际应用需不需要那么高。HVAC 场景下 ±0.5°C 的精度通常就够了追求 ±0.1°C 只会让成本和调试难度飙升。2.2 本地温度通道的硬件连接本地温度这一路因为传感器和 MCU 在同一块板子上走线短、干扰小可以用最简单的连接方式。PJ85718DM 如果输出的是模拟电压直接接到 STM32F091RC 的 ADC 输入引脚即可。这里有几个细节要注意。第一ADC 的参考电压要稳。STM32F091RC 的 VREF 如果直接接 VDDA而 VDDA 又和数字电源共用那数字电路开关噪声会耦合到参考电压上导致采样值跳动。我的做法是在 VDDA 和 VREF 引脚旁边各放一个 100nF 加一个 1uF 的电容必要时再串一个磁珠隔离。实测下来这样能把采样噪声压到 1 个 LSB 以内。第二模拟输入引脚要配置成模拟模式关闭内部上拉下拉。这个在 CubeMX 里点一下就行但很多人忘了结果引脚内部的上拉电阻和传感器输出形成分压读数直接偏掉。第三如果 PJ85718DM 输出的是数字信号比如 I2C 或单总线那本地通道就直接走数字接口不经过 ADC。数字接口的好处是不受参考电压和模拟噪声影响但要注意上拉电阻的选取和总线电容。I2C 在 100kHz 下4.7kΩ 上拉是常见值如果走 400kHz上拉要减小到 2.2kΩ 左右同时总线电容不能超过 400pF。2.3 远程温度通道的布线策略与抗干扰远程温度才是这套方案里真正考验设计功力的地方。传感器离 MCU 几米远线缆上会引入各种干扰工频噪声、开关电源的尖峰、电机启停的浪涌。如果处理不好读数跳变、漂移、甚至通信失败都是常事。我的经验是远程通道优先选数字接口而不是模拟接口。模拟电压经过长线传输压降和噪声耦合很难避免数字信号虽然也有干扰问题但可以通过差分传输、屏蔽线、协议校验来对抗。如果 PJ85718DM 支持差分输出或者电流环输出那是最好的电流信号在长线上的抗干扰能力远强于电压信号。如果只能用模拟电压传输那必须做几件事用屏蔽双绞线屏蔽层单端接地接 MCU 侧的地不要两端都接否则形成地环路在传感器输出端加 RC 低通滤波截止频率根据你的采样率来定比如采样 10Hz截止频率设 1Hz 左右在 MCU 的 ADC 输入端加钳位二极管防止浪涌打坏引脚。线缆长度和线径也要考虑。太细的线电阻大如果传感器是电流输出型线阻会影响精度如果是电压输出型线阻和 ADC 输入阻抗形成分压。一般 0.5mm² 的线20 米以内问题不大再长就要算一下压降了。传输方式适用距离抗干扰能力成本推荐场景模拟电压 5m弱低板内或短距离模拟电流 50m强中工业现场I2C 数字 1m中低板内多传感器RS-485 差分 1000m很强中高远程多点单总线 20m中低分布式测温注意远程通道的采样不要和本地通道用同一个 ADC 扫描序列最好分开采样、分开滤波。因为远程通道的噪声特性完全不同用同一套滤波参数会顾此失彼。3. 固件架构从 ADC 采样到温度换算的完整链路3.1 采样时序与多通道调度STM32F091RC 的 ADC 支持规则通道和注入通道。规则通道适合按顺序扫描多路注入通道可以打断规则通道做紧急采样。在温度监测里我一般把本地和远程都放在规则通道里用定时器触发固定周期采样。比如用 TIM2 产生 100ms 的触发信号ADC 每次触发扫描两路采完产生 DMA 请求把数据搬到内存数组里。这样做的好处是采样周期严格由硬件定时器决定不受程序执行时间影响数据的时间戳是均匀的。如果你在 while 循环里手动启动 ADC那采样间隔会随程序负载波动做趋势分析时会有问题。DMA 的配置要注意ADC 的 DMA 请求是每次转换完成触发一次所以 DMA 的缓冲区大小要等于通道数乘以采样次数。比如两路通道每次触发采 16 次做平均那缓冲区就是 32 个半字。DMA 配置成循环模式半传输和传输完成中断各处理一半数据这样能实现乒乓缓冲采样和处理互不干扰。3.2 温度换算的数学处理ADC 读回来的是 0 到 4095 的原始值要变成温度值中间要经过几步换算。假设 PJ85718DM 输出的是模拟电压灵敏度是 10mV/°C0°C 时输出 500mV那换算公式是// 假设 VREF 3.3V, ADC 12位 float voltage (adc_value / 4095.0f) * 3.3f; float temperature (voltage - 0.5f) / 0.01f;但实际代码里我建议用定点数或者整数运算避免浮点开销。Cortex-M0 没有硬件浮点单元浮点运算是软件模拟的一次除法可能几百个周期。对于 100ms 一次的采样这点开销不算什么但如果采样率提高到 1kHz浮点就会成为瓶颈。更好的做法是预先算好系数用整数乘加// 预先计算: temperature adc_value * K B // K (3.3 / 4095.0) / 0.01 0.080586 // B -0.5 / 0.01 -50 // 放大 1000 倍用整数: K_int 81, B_int -50000 int32_t temp_milli ((int32_t)adc_value * 81 - 50000) / 1000;这样得到的是毫摄氏度精度足够而且运算快。注意除法那里如果编译器支持除以 1000 会被优化成乘法加移位比直接浮点快得多。3.3 滤波策略本地与远程不能用同一套参数本地通道因为走线短噪声主要是高频开关噪声用简单的滑动平均或者一阶低通就能压住。我一般用 8 点滑动平均计算量小效果够用。远程通道就复杂了。除了高频噪声还有工频干扰50Hz 或 60Hz和低频漂移。滑动平均对工频干扰的抑制取决于采样率和窗口长度的关系如果采样率是工频的整数倍那滑动平均能完美抵消工频如果不是就会有残余。更稳妥的做法是用中值滤波加滑动平均的组合先取 5 个点做中值去掉脉冲干扰再做 8 点滑动平均平滑随机噪声。#define MEDIAN_WIN 5 #define AVG_WIN 8 int32_t remote_filter(int32_t new_sample) { static int32_t median_buf[MEDIAN_WIN] {0}; static int32_t avg_buf[AVG_WIN] {0}; static uint8_t idx_m 0, idx_a 0; static uint8_t count 0; // 中值滤波 median_buf[idx_m] new_sample; idx_m (idx_m 1) % MEDIAN_WIN; int32_t sorted[MEDIAN_WIN]; memcpy(sorted, median_buf, sizeof(sorted)); // 简单冒泡排序 for (int i 0; i MEDIAN_WIN - 1; i) { for (int j 0; j MEDIAN_WIN - 1 - i; j) { if (sorted[j] sorted[j1]) { int32_t t sorted[j]; sorted[j] sorted[j1]; sorted[j1] t; } } } int32_t median sorted[MEDIAN_WIN / 2]; // 滑动平均 avg_buf[idx_a] median; idx_a (idx_a 1) % AVG_WIN; if (count AVG_WIN) count; int64_t sum 0; for (int i 0; i count; i) sum avg_buf[i]; return (int32_t)(sum / count); }这段代码在 STM32F091RC 上跑一次滤波大概几十微秒完全不影响主循环。提示滤波窗口不是越大越好。窗口越大平滑效果越好但响应越慢。温度这种慢变量窗口大一点没关系但如果你的系统需要快速响应温度突变比如保护逻辑那窗口要适当减小或者用两套滤波结果一套平滑的用于显示一套快速的用于报警判断。4. 本地与远程的差异化处理不只是距离的区别4.1 校准策略的分野本地温度传感器因为和 MCU 在同一环境校准相对简单。你可以在已知温度下比如冰水混合物 0°C或者恒温箱 25°C读一组数据算出一个偏移量写进 Flash 里上电时加载。单点校准就能把精度提到 ±0.5°C 以内。远程传感器就麻烦了。线缆电阻、接插件接触电阻、传感器本身的离散性都会引入误差。而且远程传感器可能安装在难以拆卸的位置现场校准不现实。我的做法是出厂前在标准温度下做两点校准比如 0°C 和 50°C把斜率和截距都算出来存进 MCU 的 Flash。现场安装后如果发现偏差可以通过通信接口远程下发一个修正值叠加到原始校准上。这里有个坑远程传感器的线缆电阻会随温度变化铜线的温度系数是 0.4%/°C。如果线缆很长环境温度变化 30°C线阻变化 12%对于电流输出型传感器这会导致读数漂移。解决办法是用三线制或四线制接法把线阻的影响抵消掉。如果传感器不支持那只能在软件里做温度补偿用本地温度近似线缆温度算出一个修正系数。4.2 故障检测与容错本地通道的故障检测相对直接如果 ADC 读数一直是 0 或者一直是满量程那大概率是传感器断线或短路。可以在固件里设一个合理范围比如 -40°C 到 125°C 之外的值都判为故障。远程通道的故障模式更多线缆断开、接插件氧化、传感器进水、屏蔽层破损引入干扰。除了范围判断还可以做变化率判断如果温度在 1 秒内跳变超过 10°C那肯定是干扰或故障不是真实温度变化。这时候不要立即报警而是标记为可疑连续几次都这样再确认故障。#define TEMP_MIN -40000 // -40.000°C #define TEMP_MAX 125000 // 125.000°C #define RATE_LIMIT 10000 // 10°C/s uint8_t check_remote_fault(int32_t temp, int32_t last_temp, uint32_t dt_ms) { if (temp TEMP_MIN || temp TEMP_MAX) return 1; // 超范围 int32_t rate (temp - last_temp) * 1000 / dt_ms; if (rate RATE_LIMIT || rate -RATE_LIMIT) return 2; // 变化率异常 return 0; }故障确认后系统要有一个降级策略。比如远程温度失效时自动切换到本地温度作为控制依据同时上报故障码。不要让整个系统因为一个传感器故障就停机这在 HVAC 里是不可接受的。4.3 通信上报的数据组织本地和远程温度采集完之后怎么上报给上位机或云端也有讲究。我一般用 Modbus RTU 或者自定义的二进制协议。Modbus 的好处是通用组态软件直接支持自定义协议的好处是紧凑适合低带宽场景。不管用哪种数据组织上我建议把本地温度、远程温度、状态字、时间戳打包成一帧。状态字里用位域表示每个通道的健康状态、是否在校准模式、是否有未确认的报警。这样上位机拿到一帧数据就能知道全部信息不用再发查询命令。字段长度说明帧头2字节固定 0xAA55本地温度4字节毫摄氏度有符号远程温度4字节毫摄氏度有符号状态字2字节位0-1本地状态位2-3远程状态时间戳4字节上电后的毫秒数CRC162字节校验帧长度固定 18 字节用 DMA 加 UART 空闲中断发送不占用 CPU 时间。5. 实测中的那些坑从噪声到温漂的完整排查记录5.1 远程通道读数跳变的排查过程项目第一次联调时本地温度很稳波动不超过 0.1°C但远程温度一直在跳幅度有 2-3°C。这种问题不能靠猜得一步步排查。第一步我先用示波器看远程传感器输出端的波形。发现波形上叠加了明显的 50Hz 正弦幅度大概 20mV。这说明工频干扰耦合进来了。耦合途径可能是空间电磁场也可能是地环路。第二步我把传感器和 MCU 之间的连线换成屏蔽双绞线屏蔽层接 MCU 侧的地。再测50Hz 幅度降到 5mV 左右但还有。说明空间耦合被屏蔽了大半但地环路还在。第三步我检查了两端的地连接。发现传感器侧的地和 MCU 侧的地通过线缆里的地线连在一起而两个设备又各自接了大地形成了地环路。解决办法是在传感器侧做隔离用一个小型的隔离放大器或者光耦。加上隔离后50Hz 彻底消失读数稳定在 ±0.2°C 以内。这个排查过程告诉我远程温度监测里地环路是最隐蔽也最麻烦的问题。如果你的系统里两个设备都接了大地那一定要考虑隔离。5.2 本地通道的温漂问题本地通道虽然稳但有个问题开机后半小时内读数会慢慢漂移 1°C 左右之后才稳定。一开始我以为是传感器的问题换了几颗都一样。后来才想明白是 MCU 自身的发热导致的。STM32F091RC 在 48MHz 全速运行时功耗大概几十毫安芯片表面温度会比环境高几度。而本地传感器如果离 MCU 太近测到的就是 MCU 的表面温度不是环境温度。开机后 MCU 温度逐渐上升传感器读数也跟着漂直到热平衡。解决办法有两个一是把本地传感器远离 MCU放在板子边缘或者用排线引出二是在固件里做温度补偿用 MCU 的内部温度传感器读芯片温度算出一个修正值。我选了第一种因为更直接不增加软件复杂度。把传感器移到板边后漂移从 1°C 降到 0.2°C 以内。注意如果你用 MCU 内部温度传感器做补偿要知道它的精度很差通常只有 ±1.5°C只能用来做趋势判断不能做精确补偿。5.3 ADC 参考电压波动导致的批量偏差小批量试产时发现不同板子之间的温度读数有系统性偏差有的偏高 0.5°C有的偏低 0.3°C。这个偏差不是随机的而是每块板子固定。排查后发现是 VREF 的实际电压和标称 3.3V 有差异。STM32F091RC 的 VREF 如果直接接 VDDA而 VDDA 来自 LDOLDO 的输出精度通常是 ±2%3.3V 的话就是 ±66mV。反映到温度上就是 ±0.66°C 的偏差。这个偏差对于要求 ±0.5°C 精度的应用是不可接受的。解决办法是用高精度基准源比如 2.5V 或 3.0V 的基准芯片精度 ±0.1%。或者在出厂校准环节用高精度万用表测出每块板子的实际 VREF 电压写进 Flash固件里用这个实测值做换算。我选了后者因为不增加 BOM 成本只是产线多一道工序。// 从 Flash 读取校准的 VREF 值单位 mV uint16_t vref_mv *(uint16_t*)VREF_CAL_ADDR; float voltage (adc_value / 4095.0f) * (vref_mv / 1000.0f);这样处理后板间偏差从 ±0.66°C 降到 ±0.1°C 以内。5.4 通信超时与数据丢失的处理系统跑起来后偶尔会出现上位机收不到数据的情况。查下来是 UART 发送时如果正好遇到高优先级中断DMA 传输会被打断导致帧不完整。虽然概率很低但一天出现几次对于监控系统来说不可接受。解决办法是给 UART 发送加一个软件队列主循环把要发的帧放进队列DMA 发送完成中断里从队列取下一帧。这样即使某一帧发送被延迟也不会丢失只是延后一点。同时在帧里加序号上位机发现序号不连续就知道有丢帧可以请求重发。#define TX_QUEUE_SIZE 8 typedef struct { uint8_t data[18]; uint8_t len; } tx_frame_t; tx_frame_t tx_queue[TX_QUEUE_SIZE]; volatile uint8_t tx_head 0, tx_tail 0; void uart_send_frame(uint8_t *data, uint8_t len) { uint8_t next (tx_head 1) % TX_QUEUE_SIZE; if (next tx_tail) return; // 队列满丢弃 memcpy(tx_queue[tx_head].data, data, len); tx_queue[tx_head].len len; tx_head next; if (!dma_busy) start_dma_tx(); }这个队列机制加上后连续跑了一周没有再出现丢帧。6. 把这套方案用起来几个可以直接抄的配置要点6.1 CubeMX 里的关键配置如果你用 STM32CubeMX 生成初始化代码有几个地方要特别注意。ADC 配置里Resolution 选 12 位Scan Conversion Mode 使能Continuous Conversion Mode 关闭我们用定时器触发DMA Continuous Requests 使能。定时器配置成 Update Event 触发 ADC触发频率根据你的采样需求定我一般用 100Hz 触发然后软件做 10 次平均得到 10Hz 的有效数据率。UART 配置里波特率 1152008 数据位1 停止位无校验。DMA 发送使能发送完成中断使能。NVIC 里把 UART 中断优先级设得比定时器低避免通信打断采样。6.2 低功耗模式的取舍HVAC 设备很多是电池供电或者要求低功耗。STM32F091RC 支持 Sleep、Stop、Standby 三种低功耗模式。温度监测这种应用如果采样率要求不高比如 1 秒一次可以用 Stop 模式定时器唤醒采样完继续睡。这样平均电流能降到几十微安。但要注意Stop 模式下 ADC 和 DMA 都停了唤醒后要重新初始化。而且 Stop 模式下 UART 也停了如果上位机这时候发命令会丢失。所以低功耗和通信实时性要权衡。我的做法是正常模式 100ms 采样一次如果 5 分钟没有通信自动进入 Stop 模式1 秒采样一次一旦收到通信请求立即唤醒回正常模式。6.3 产线校准的流程设计前面提到出厂校准这里说一下具体流程。产线需要一台恒温箱能稳定在 0°C 和 50°C。每块板子先放 0°C 环境等热平衡后大概 10 分钟通过 UART 发送校准命令固件记录此时的 ADC 值。然后升到 50°C再记录一次。用这两组数据算出斜率和截距写进 Flash。校准命令要加保护比如需要先发送一个解锁序列防止误触发。校准数据写 Flash 后要回读验证确保写入成功。整个流程自动化操作员只需要放板子、按开始、等结果。typedef struct { int32_t adc_at_0c; int32_t adc_at_50c; uint16_t vref_mv; uint16_t crc; } cal_data_t; void save_calibration(cal_data_t *cal) { cal-crc crc16((uint8_t*)cal, sizeof(cal_data_t) - 2); flash_unlock(); flash_erase_page(CAL_PAGE_ADDR); flash_write(CAL_PAGE_ADDR, (uint8_t*)cal, sizeof(cal_data_t)); flash_lock(); }6.4 现场安装的注意事项最后说几个现场安装的经验。远程传感器的线缆不要和动力线走同一个线槽如果必须交叉要垂直交叉不要平行走。传感器探头要完全浸入被测介质如果测管道温度要保证探头和管壁紧密接触必要时涂导热硅脂。户外安装要做好防水接线盒朝下防止积水。本地传感器如果装在控制柜里要注意柜内空气流通不要被其他发热器件烤着。如果柜内有变频器传感器要远离至少 20cm 以上否则变频器的开关噪声会耦合进来。这套 PJ85718DM 加 STM32F091RC 的方案我从原型到小批量跑了大概半年中间踩的坑基本都写在这里了。温度监测看起来简单但要做到稳定可靠细节非常多。希望这些经验能帮你少走点弯路。如果你也在做类似的项目欢迎交流。