做嵌入式控制器或者HVAC温控面板的同行,应该都有过这种纠结:控制板装在铁皮箱子里,既要盯着板子附近空气温度防止继电器粘连、电源过热,又要知道几米外风道口或者水管壁的真实温度。以前我习惯挂两个独立传感器,一个放板上,一个甩到现场,中间拉一长根四线线缆,成本和布线都很狼狈。后来在某项目里改用PJ85718DM + STM32L152ZD的组合,一颗温度传感器芯片解决本地与远程双通道测温,一颗自带LCD驱动的超低功耗MCU负责采集、显示和逻辑控制,整块板子干净了很多。
这套方案适合谁?做嵌入式控制器的、做暖通空调温控面板的、做机柜环境监控和冷链设备的,都可以参考。接下来我会从硬件选型开始讲,把两颗芯片怎么搭、远程测温通道怎么接、I2C寄存器怎么操作、显示和低功耗怎么安排,包括我实测过程中踩过的坑,一次性说清楚。
1. 为什么本地和远程温度监测会同时出现在一块主板上
很多新手看到"本地温度 + 远程温度"这种描述,第一反应是"一个芯片怎么能同时测两个地方的温度"。其实这在温控类产品里非常常见,而且背后是两个完全不同的工程需求。
1.1 本地温度的监测对象是板子自己
本地温度,说白了就是"控制器自身的体温"。继电器、开关电源、功率管、线性稳压器这些发热大户都挤在同一个小空间里,嵌入式主板在密闭金属箱里一跑就是好几年,环境温度可能远高于室温。这时候本地温度监测至少有三个作用:
- 开机自检:温度异常通常意味着风机堵转、散热不良,甚至元器件虚焊。
- 寿命保护:电解电容、液晶屏、锂电池最怕高温,检测到超温后可以主动降额运行,或者提前报警。
- 补偿计算:很多高精度ADC测量、电池电量计算都需要温度补偿,没有本地温度,后面这些算法都缺一条腿。
具体到HVAC控制器里,本地温度还能反映控制箱内环境是否正常。比如夏天配电箱晒在屋顶,箱内温度能到60°C,如果控制器不感知这个温度,继电器绝缘材料老化速度会明显加快。
1.2 远程温度监测的对象才是现场
真正需要参与闭环控制的,往往是几米甚至十几米外的盘管、风道、供水管、回水管或者房间空气。HVAC领域的远程测温方案大体有三种:
- NTC热敏电阻:便宜,但非线性严重,需要校准,而且长线会引入误差,线缆电阻不好补偿。
- 独立数字传感器放到远端:I2C或者单总线,需要额外供电,远端要一块PCB,成本高。
- 二极管测温:传感器本体留在主板上,只通过两根线引出到一个二极管或三极管PN结,远端探头成本几乎可以忽略。
PJ85718DM 这类芯片走的就是第三条路。芯片内部提供恒流源激励远程二极管,通过测量PN结正向压降 (V_{BE}) 随温度变化的规律来计算温度,同时用不同电流下的 (\Delta V_{BE}) 抵消二极管反向饱和电流的差异。远程探头用普通双绞线就能拉出去,线材成本很低。
需要提醒的是,二极管测温距离不是无限远的。常见场景在1米到10米左右,超过之后线缆电阻的影响会变大,读数会整体偏低,这个问题在文章第6部分我会详细讲。
1.3 双通道监测带来的工程实惠
用一个芯片替代两个传感器,最直接的好处是BOM精简、PCB面积减小、驱动代码统一。但更重要的价值在于,两路数据可以配合使用:
- 本地温度用于保护:控制板超温就关加热负载,或者降低PWM占空比。
- 远程温度用于控制:现场温度低于设定值就启动风机或打开阀门。
我在调试阶段还发现一个用法——通过比较两路温度差来判断系统是否真的在工作。比如供暖工况下,远程温度和本地温度应该有一个合理的梯度,如果两路温度几乎一样,那多半是探头没装好或者阀门没动作,现场很容易发现。
2. PJ85718DM 与 STM32L152ZD 这两颗料怎么搭才合理
选型这件事,不能只看某颗芯片的纸面参数,要看两颗芯片配合起来能不能解决产品的主要矛盾。这个组合的核心逻辑是:一颗带远程测温能力的传感器芯片,加一颗自带显示控制器的超低功耗MCU。
2.1 PJ85718DM 核心特性梳理
先把我实际用得上的关键参数列出来。不同批次或封装可能略有差异,具体以手头手册为准。
| 项目 | 参数 | 说明 |
|---|---|---|
| 数字接口 | I2C,最高 400kHz | 标准两线制,主从通信 |
| 本地温度范围 | -40°C ~ +125°C | 覆盖绝大多数嵌入式场景 |
| 远程测温方式 | 外部二极管/三极管PN结 | 两根线引出,探头成本极低 |
| 典型精度 | ±1°C | 本地和远程均有校准手段 |
| 温度分辨率 | 12bit,约0.0625°C/LSB | 读数平滑,适合做控制 |
| 供电电压 | 3.0V ~ 3.6V | 与MCU共用3.3V电源 |
| 报警输出 | 开漏ALERT引脚 | 可配置比较模式或中断模式 |
这颗芯片最特别的地方,就是传感器本体和远端探头分离。芯片放在主板上,采集的是主板附近温度,同时通过D+/D-两个引脚去读远端二极管的温度。寄存器布局和市面上常见的双通道温度传感器比较接近,工程上迁移起来很顺手。
2.2 STM32L152ZD 为什么适合做温度监测主控
STM32L152ZD 这颗料我用了不少次,它在温控面板场景里的优势非常明显:
- Cortex-M3 内核,最高 32MHz:跑传感器驱动、简单PID、显示刷新都绰绰有余,不需要上M4。
- 内置LCD控制器:可以直接驱动段码液晶屏,省掉外部LCD驱动芯片。温控面板最核心的交互就是显示温度和状态符号,这个外设是决定性的。
- 超低功耗:Stop模式下RTC可以继续走时,电流只有微安级,适合7x24小时在线或者电池供电。
- 外设齐全:多路I2C、SPI、USART,后面要挂RS485、LoRa模块都很方便。
选型时我特意确认了一件事:这颗MCU的I2C外设和LCD控制器能否同时工作。实际项目验证下来没有任何冲突,LCD刷新由硬件自动完成,CPU只在要更新内容时写显示RAM,不占额外开销。
2.3 什么情况下不该选这套组合
任何方案都有边界,提前想清楚反而能少走弯路:
- 如果只测一个本地温度,显示三两位数字,那8位MCU加一颗单总线传感器更便宜。
- 如果要做彩色触摸屏界面,STM32L1系列的性能就不够了,需要换带RGB接口或MIPI的Cortex-M4/MPU平台。
- 如果远程探头要放到100米外,二极管方案要慎重,优先考虑RS485分布式采集或无线传感节点。
我在选型文档里把这些边界都写清楚了,免得后面产品迭代时被问"为什么当初不选更便宜的方案"。
3. 硬件连接:传感器通道的布线、电阻匹配与抗干扰设计
芯片选好了,真正的坑从原理图开始。硬件设计如果偷懒,软件再怎么写也救不回来,尤其是远程那两根线。
3.1 电源与最小系统
PJ85718DM 和 STM32L152ZD 都工作在3.3V,电源可以共用一组LDO输出。需要注意几点:
- MCU的VDD和VDDA分开滤波,VDDA引脚通常要串磁珠再接3.3V,避免数字噪声耦合进模拟电路。
- 传感器电源引脚旁边放一个100nF去耦电容,有条件再加1μF,保证I2C通信在转换瞬间电流变化时电压不跌落。
- 传感器不要贴着电源芯片、继电器或功率电阻摆放,否则本地温度读数会失真。我在样板阶段把传感器放在电源芯片旁边测过一次,本地温度比实际环境高了接近8°C,后来挪到PCB角落、靠近进风口位置才正常。
3.2 I2C上拉电阻与电平匹配
I2C的SDA和SCL都是开漏,必须外接上拉电阻。3.3V系统里常见取值是4.7kΩ,总线器件多或者走线超过10cm时,建议降到2.2kΩ。我一般直接按2.2kΩ设计,既满足400kHz的上升沿要求,也不会造成太大灌电流。
如果板上还有5V器件,I2C电平需要转换,不要试图用电阻分压凑合。I2C是双向开漏,分压方式会在从机拉低时出问题,最可靠的是加一颗双向电平转换芯片。这个坑我见过不止一次,处理不好会间歇性死机。
3.3 远程二极管通道的走线与匹配
远程通道是这套方案里最需要花心思的部分。几个要点:
- D+和D-两条线要尽量并行、等长,最好直接使用双绞线,减少共模干扰。
- 远离继电器控制线、电源线、PWM走线,这些线路上的瞬态干扰会耦合进测温通道。
- 远端探头推荐用小信号三极管的BC结短接,比如常见的2N3904/2N3906,PNP/NPN都可以,具体看芯片手册要求。相比普通开关二极管,小信号三极管结特性更稳定。
- 线缆屏蔽层要单端接地,不要两端都接,否则会形成地环路。
- 现场用普通双绞线就行,没必要选大截面电缆,截面大了反而热容大,温度响应变慢。
串联电阻是远程测温的主要误差来源。线缆每增加一些电阻,读数就会偏低一些,因为电阻分压吃掉了部分测量信号。好在很多芯片会提供串联电阻补偿寄存器,按线缆电阻值写入补偿系数即可,这个细节产品化时很重要。
3.4 报警输出引脚接法
PJ85718DM 的ALERT引脚是开漏输出,需要接上拉电阻到3.3V,然后连到MCU的一个外部中断引脚。超温时传感器自己会把引脚拉低,MCU不轮询也能及时响应。
上拉电阻取10kΩ就行,这个引脚不需要很快的边沿,关键是电平可靠。注意不要在ALERT脚上直接接继电器驱动,它只能输出低电平信号,不是功率输出。
3.5 完整接线汇总
下面是我这个项目里的实际接线清单,供参考:
| 传感器引脚 | 连接到 | 说明 |
|---|---|---|
| VDD | 3.3V | 接100nF去耦电容 |
| GND | 地 | 单点接地 |
| SDA | MCU PB9 | 开漏,上拉到3.3V |
| SCL | MCU PB8 | 开漏,上拉到3.3V |
| D+ | 远端二极管阳极 | 双绞线引出 |
| D- | 远端二极管阴极 | 双绞线引出 |
| ALERT | MCU外部中断引脚 | 开漏,上拉10kΩ |
接完线之后,先不要上电,用万用表量一下D+和D-之间的通断,确保没有接反、没有短路。这一步能省掉后面大量排查时间。
4. 软件实现:I2C寄存器操作与温度数据转换的完整流程
硬件焊好了,接下来就是让芯片开口说话。温度传感器的软件其实不复杂,但寄存器操作顺序和数据处理细节容易出问题。
4.1 从零初始化I2C
STM32L152ZD 的I2C工作在400kHz主模式,配置成开漏复用功能。这里用寄存器风格写一版,方便理解底层逻辑,实际用CubeMX生成的工程也是一回事:
void I2C1_Init(void) { // 使能GPIOB和I2C1时钟 RCC->AHBENR |= RCC_AHBENR_GPIOBEN; RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // PB8=SCL, PB9=SDA,复用开漏模式 GPIOB->MODER &= ~(3UL << 16 | 3UL << 18); GPIOB->MODER |= (2UL << 16 | 2UL << 18); GPIOB->OTYPER |= (1UL << 8 | 1UL << 9); GPIOB->AFR[1] |= (4UL << 0) | (4UL << 4); // AF4: I2C1 // 配置I2C时序,此处为示意,实际按系统时钟查表计算 I2C1->TIMINGR = I2C_TIMINGR_400KHz; I2C1->CR1 = I2C_CR1_PE; }有几个细节需要确认:I2C引脚必须配置为开漏,内部上拉不要依赖,宁可不用也要外接上拉;复用功能映射要看引脚手册,不是所有PB8/PB9都能映射到I2C1的同一个AF编号。
4.2 传感器寄存器配置流程
读取温度之前,需要先确认传感器处于连续转换模式,并且分辨率设置正确。常见的寄存器布局是:指针寄存器选择后续操作的目标,然后通过读写数据寄存器完成配置。
#define DEV_ADDR 0x48 // 以手册实际地址为准 #define REG_CONFIG 0x01 #define REG_TEMP_LOCAL 0x00 #define REG_TEMP_REMOTE 0x02 #define REG_THIGH 0x03 #define REG_TLOW 0x04 void PJ85718DM_Init(void) { uint8_t cfg = 0x60; // 连续转换、12bit分辨率 WriteReg(DEV_ADDR, REG_CONFIG, cfg); delay_ms(100); // 等待首次转换完成 }初始化之后不要立刻读温度,一定要等待一段时间让芯片完成首轮转换。不同芯片转换时间不同,短的几十毫秒,长的几百毫秒。这个等待如果省了,首屏读数可能是一个随机值。
4.3 读取温度并转换为实际数值
温度数据一般以两个字节形式存放在寄存器里。以12bit分辨率为例,把高字节和低字节拼接后右移4位,得到一个有符号12位整数值,乘以0.0625就是摄氏温度。
int16_t ReadRawTemp(uint8_t reg) { uint8_t buf[2]; ReadRegs(DEV_ADDR, reg, buf, 2); int16_t raw = ((int16_t)buf[0] << 8) | buf[1]; return (int16_t)(raw >> 4); // 12bit有符号数 } float CalcTemp(int16_t raw) { return raw * 0.0625f; } // 用法 int16_t rawLocal = ReadRawTemp(REG_TEMP_LOCAL); int16_t rawRemote = ReadRawTemp(REG_TEMP_REMOTE); float localC = CalcTemp(rawLocal); float remoteC = CalcTemp(rawRemote);这里最容易犯的错是忘记右移4位。如果不右移,读数会放大16倍,显示出来就像胡言乱语。另一个坑是符号位扩展,低温和零下温度时必须有符号扩展,否则超过0°C的高位可能变成奇怪的正数。用右移而不是除法来处理带符号数,这一点在C语言里要格外注意。
4.4 报警阈值设置与中断处理
温度监测如果只靠MCU轮询,实时性永远受采样周期限制。利用传感器自带的报警比较功能,把阈值判断下沉到硬件,系统更可靠。
void PJ85718DM_SetAlert(float highC, float lowC) { uint16_t highRaw = (uint16_t)(highC / 0.0625f) << 4; uint16_t lowRaw = (uint16_t)(lowC / 0.0625f) << 4; WriteReg16(DEV_ADDR, REG_THIGH, highRaw); WriteReg16(DEV_ADDR, REG_TLOW, lowRaw); }报警模式要分清两种场景:
- 比较模式:温度超过阈值,ALERT持续拉低,直到温度回到滞回区间才恢复。适合驱动风机、加热器等执行机构。
- 中断模式:温度超过阈值,ALERT拉低一次,MCU读取状态寄存器后才清除。适合作为异常上报给上位机。
MCU中断处理代码的基本逻辑是:进入外部中断 -> 读状态寄存器 -> 判读是超上限还是低于下限 -> 执行保护动作 -> 清除中断标志。
4.5 滤波与数据平滑
传感器原始读数多少会有点噪声,尤其远程通道走线比较长时,读数末尾跳动1~2个数字量非常正常。软件上我习惯做滑动平均:保留最近8次采样,去掉一个最大值和一个最小值,剩下6个取平均。这个办法在轻噪声场景里远比一阶低通滤波干净。
一阶低通形式也可以,但要注意系数:
float filtered = 0; filtered = filtered * 0.8f + rawTemp * 0.2f;系数越大越平滑,但响应越慢。控制环用的温度可以加大平滑系数,报警判断则不要用太重的滤波,否则超温都发生好几秒了,报警还因为滤波滞后没触发。我的做法是:控制温度和报警温度各用一路数据,报警路径只用轻滤波或者原始值。
5. 显示、存储与低功耗:把温度数据真正用起来
能读出温度只是第一步,产品里还要考虑怎么显示、怎么记录、怎么省电。这部分做得细致,现场的易用性差别很大。
5.1 用STM32L152ZD内置LCD控制器直接驱动段码屏
STM32L152ZD 内置LCD控制器可以直接驱动段码液晶,省掉外部LCD驱动芯片。初始化LCD外设需要配置引脚和控制器参数,显示时往LCD RAM写数据即可。
void LCD_Init(void) { // 1. 配置LCD引脚为复用功能 // 2. 设置LCD_CR,选择占空比和偏压 // 3. 设置LCD_FCR,配置分频系数和闪烁功能 } void LCD_DisplayDigit(uint8_t pos, uint8_t value) { // 段码表:0-9和特殊符号对应的段位组合 static const uint8_t seg[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; LCD_RAM[pos] = seg[value]; }段码屏的COM和SEG引脚与LCD RAM位之间不是线性对应的,要对照屏厂手册做映射。我第一次接屏时没仔细看手册,按直觉一条条排段码,结果屏幕显示乱码,后来老老实实画表,半小时内就调通了。
我的经验是:把所有数字、小数点和特殊符号的段码组成一个查表数组,更新显示时只调用一个函数,这样软件逻辑和硬件映射完全隔离,以后换屏只用改表。
5.2 温度历史记录:利用Flash做掉电保存
温控设备通常要记录温度曲线,用于事后分析和故障追溯。如果量不大,MCU内部Flash就够了。
关键点是别频繁整片擦写。Flash扇区擦除寿命有限,如果温度每5分钟写一次,每次擦一个扇区,很快就把寿命耗完了。正确做法是:
- 以扇区为单位做环形缓冲,数据攒满一个扇区才擦写一次。
- 每5分钟存一个点,一天288个点,一个扇区能存很长时间。
- 报警阈值、校准偏移这类关键参数单独放在另一个扇区,和温度日志分开。
如果设备要保留几个月的数据,还是老老实实外挂SPI NOR Flash或者EEPROM,MCU内部Flash只存配置参数。
5.3 低功耗调度:从Stop模式定时唤醒采样
HVAC面板一般常供电,低功耗不算核心需求。但如果产品是电池供电的温控器,调度策略就是决定续航的大事了。我的做法是:
- 正常情况MCU进入Stop模式,RTC定时唤醒。
- 唤醒后启动I2C,读取本地和远程温度,刷新LCD,检查报警。
- 数据处理完后立即回到Stop模式。
传感器功耗也要控制。如果传感器没有使能脚,就把它的电源放在一个MOS管控制的电源域下,采样时打开,采样完关闭。注意传感器上电后需要稳定时间,不能刚上电就立刻读,先等转换完成再通信。
实测下来,一分钟采样周期配合采样后断电,整机平均电流能做到非常低,多数的功耗反倒是LCD和待机电源消耗。省电优化一定要以实际示波器测量为准,不要拿芯片手册值估算。
5.4 数据上抛与通信协议
很多HVAC项目需要把温度数据送到网关或者楼宇自控系统。常用的通信方式是RS485加Modbus协议,成本低、抗干扰强。STM32L152ZD的USART外接一个收发器即可。
温度上抛我建议用定点数而不是浮点数,比如把温度乘以100转成整数再发送,可以避免浮点解析偏差,也简化协议。上位机约定好"温度除以100就是摄氏度"就行。通信时还要带数据有效性校验,比如CRC或者累加和,防止线路干扰造成错误数据。
6. 实测中踩过的问题与校准补偿经验
这部分是我最想写的内容。理论计算和实际项目差距,往往就藏在那些不起眼的细节里。下面几个问题都是我在开发中真实遇到的,按排查链路写出来,希望能帮大家少走弯路。
6.1 远程温度读数整体偏低的排查链路
现象:远程通道比水银温度计偏低3°C左右,本地通道读数正常。
排查过程:
- 先确认远程二极管极性:D+接二极管阳极,D-接阴极。接反后读数会异常或者直接不变化。
- 检查二极管类型:探头一定要用芯片手册推荐的类型,最好是小信号三极管的BC结短接。普通开关二极管的结特性差异大,系统偏差会很明显。
- 测量线缆电阻:用万用表量D+和D-回路的整体电阻。线阻越大,读数偏低越明显。每增加一些电阻,温度误差就增加零点几到一度。
- 查芯片的串联电阻补偿寄存器:如果支持,按实测线阻写入补偿值。这个补偿很有效,但不能解决所有问题。
- 软件偏移校准:在转换结果上加上一个固定offset。这种方法只能补偿恒定偏差,线阻随温度变化带来的误差补偿不了。
这个排查顺序非常重要,从硬件到软件,逐个排除比直接调软件offset靠谱得多。我只调软件偏移,当时确实把当前环境下的误差修好了,但天冷了线缆电阻变化,读数又偏了,最后反过来查硬件才算根治。
6.2 报警输出频繁抖动
现象:温度在阈值附近徘徊,ALERT引脚反复拉低,继电器频繁动作,几分钟一次。
原因有两个,都可能导致:
- 滞回区间设得太小。控制回路里的温度天然有小幅波动,阈值上下沿只有零点几度时,报警逻辑就会在边界反复翻转。
- 比较模式和高低阈值关系没搞清楚。有些芯片的高阈值对应的是超上限报警,低阈值对应低于下限报警,如果寄存器赋值反了,状态机就会乱。
解决方法是双管齐下:
// 设置滞回:高阈值 +2°C,低阈值 -2°C PJ85718DM_SetAlert(controlSetpoint + 2.0f, controlSetpoint - 2.0f);另外在软件侧给执行机构加防抖逻辑,比如ALERT低电平要连续确认5次(每次间隔1秒)才真正切换继电器。这样即使传感器输出有一定抖动,执行机构也不会频繁动作。
6.3 I2C总线死锁
远程走线较长时,偶尔会遇到I2C读挂死的情况,尤其是总线上挂多个器件时。排查下来常见原因是:
- 从机状态机跑飞,SDA被从机拉死不释放。
- 主机还在通信过程中掉了电或者复位,导致STOP条件没发出去。
- 走线环境干扰让从机误判到错误的起始条件。
I2C协议本身没有超时机制,一旦从机死锁,主机只能自己恢复。我写了一个总线恢复函数,手动给SCL产生9个时钟脉冲,让从机状态机复位,再发一个STOP条件:
void I2C_BusRecovery(void) { // 把SCL临时配置为普通GPIO输出 // 产生9个时钟脉冲 for (int i = 0; i < 9; i++) { GPIO_SCL_HIGH(); delay_us(5); GPIO_SCL_LOW(); delay_us(5); } // 再发一个STOP条件:SDA先低后高,SCL保持高 GPIO_SDA_LOW(); GPIO_SCL_HIGH(); GPIO_SDA_HIGH(); delay_us(5); }这个方法救回了大多数I2C死锁。如果还是不行,优先检查复位电路和电源纹波,别只盯着通信协议。
6.4 两路温度之间的相互校验
这是一个非常实用的调试技巧:当本地传感器和远程二极管处于同一个热环境时,两路读数应当接近,差1~2°C以内是正常的。如果差异特别大,基本可以判断是远程通道的硬件问题。
我把它做成了系统自检功能:上电后自动比较两路温度,如果差值超过设定范围,就在LCD上显示故障码。这个功能在HVAC项目现场维护时特别有用——维护人员不用拿着测温枪逐个检查探头,直接看面板就能知道是通道开路、接反还是线阻过大。
6.5 批量生产时的校准
如果产品对温度精度要求高,单靠芯片出厂精度可能不够,批量生产时需要考虑整机校准。我的流程是:
- 每台设备在恒温槽里做两点校准,比如0°C和50°C。
- 根据两点误差计算校准斜率和偏移量,写入Flash。
- 软件在每次读取后做线性校正:
float tempCalibrated = tempRaw * gain + offset;这里有一个容易忽略的细节:校准时机要等整机通电稳定后再做,最好是装上外壳、运行半小时以上。否则校的是冷态漂移状态,装进现场壳子里后又偏了。我在产线上加了一步“上电老化半小时再校准”后,精度良率提升明显。
最后再分享一点个人体会。这套方案在我实际项目里最大的收益,不是省了那一颗传感器芯片,而是把“本地通道负责保护、远程通道负责控制”的逻辑固化了下来,软件和硬件都简化了很多。如果你以后要做类似的温控设备,建议先把这个问题想清楚:本地温度用来干什么,远程温度用来干什么,两路之间需要什么关系,然后再去接线和写代码,会顺利很多。至于更进一步的扩展,远程通道改成多路,或者把数据推到云端,都是在这套基础上继续加料的事。