
简介这是一份基于ESP8266的智能热泵控制系统完整源码面向物联网开发者、嵌入式爱好者及智能家居项目实践者。系统集成WiFi与MQTT通信协议提供可视化Web界面支持热泵状态读取与参数修改、WiFi网络配置、MQTT服务器对接以及OTA远程固件升级适合用于智能家居节能控制或实验室原型开发。资源包共15个文件约579KB以6个cpp源文件、3个h头文件为主另有platformio.ini工程配置、README说明与原理图/连接图方便读者直接导入PlatformIO环境编译运行。压缩包目录包含MQTT、WiFiConfiguration、RedHeatPump、webserver等核心模块结构清晰便于按功能学习和二次开发。目前已有53人浏览学习。通过该资源可快速搭建一套具备联网交互能力的热泵控制方案掌握ESP8266在MQTT通信、Web服务与OTA更新中的实际应用方法。1. 基于ESP8266的智能热泵控制系统先用最小成本把热泵管起来某水产养殖场的恒温车间里一台三匹空气源热泵被一个巴掌大的ESP8266模块管着回差设了2.5℃连续跑了三个冬天没出过岔子。没有PLC、没有触摸屏固件用Arduino框架写全部代码不到800行。基于ESP8266的智能热泵控制系统核心思路是把温度采集、回差控制、远程设定三件事塞进一个十几块钱的WiFi模块里再用MQTT协议跟手机或上位机联动。它解决的是热泵控制里最实际的痛点温度波动要小压缩机不能频繁启停半夜还能在被窝里远程改目标温度。这套方案适合水产恒温、温室大棚、热水循环、小型烘干房这些场景也适合想用IOT思路改造传统热泵控制柜的电气工程师下面所有接线和代码参数都是可以直接照着做的。2. 系统拆解ESP8266做热泵主控的硬件选型与接线2.1 为什么是ESP8266而不是PLC、Arduino UNO或ESP32选型是很多人一上来就纠结的问题。传统热泵控制柜里最常见的是温控器加继电器再讲究一点就是小型PLC。PLC一台几百到上千编程软件单独装固件下载和调试链路长对一两台热泵、三五个传感器这种规模明显是杀鸡用牛刀。Arduino UNO是Mega328核心回差控制写得出来但联网得外挂ENC28J60以太网模块或者ESP8266 AT固件多一层通信就多一层故障点串口还被占掉两个。ESP32性能更强、模拟引脚也多但价格是ESP8266的两三倍控温这种每秒推理一次的活双核和蓝牙完全用不上功耗和发热反倒成了负担。ESP8266在这个场景里的定位是“够用且省事”板载WiFiArduino生态里PubSubClient、DallasTemperature、ArduinoOTA全部有现成库GPIO只占两三个3.3V逻辑配合光耦继电器驱动热泵的启停信号端子绰绰有余。它的短板也要先讲清楚只有一个模拟输入A0且模拟参考电压不准所以NTC这类模拟温度传感器在这个平台上做不高精度Flash只有4MB日志不能一直写GPIO大多是弱上拉弱下拉驱动继电器这种功率外设必须过光耦不能直连。把边界先划清楚后面排错能省一半时间。2.2 硬件清单与电气接线继电器和电源这两处不能省我一般会把系统设计成两路温度采集、一路继电器输出、一路按键备用。元器件清单如下器件规格/型号数量作用主控板NodeMCU或ESP-12F核心板1主控与WiFi温度传感器DS18B20防水探头0.5米线2测回水/水箱温度继电器模块5V低电平触发、带光耦隔离1输出启停干接点电源模块220V转5V隔离电源1A以上1给主控板供电上拉电阻4.7kΩ 1/4W1DS18B20数据线上拉交流接触器线圈电压与热泵控制回路匹配1大功率热泵必加轻触按钮常开型1GPIO0配置/恢复默认接线就三路信号DS18B20的Data脚接GPIO4数据线与3.3V之间并一只4.7kΩ上拉继电器模块的IN脚接GPIO5GND与主控共地按钮一端接GPIO0、一端接GND用于进烧录模式和恢复默认参数。GPIO5到继电器模块之间我习惯串一个1kΩ电阻防止继电器模块输入端异常时反向灌电流打坏GPIO。这里两个坑必须提前说。第一绝大多数廉价继电器模块是低电平触发IN脚接低电平时吸合不是高电平触发。按“高电平等于启动”去写代码上电瞬间GPIO浮空就会导致继电器误吸合压缩机毫无预警地启动这是新手翻车率最高的一处。第二不要用ESP8266的3.3V直接驱动继电器线圈线圈吸合瞬间电流能到70mA以上超出GPIO和板载稳压的承受能力。正确做法是继电器模块单独从5V取电依赖模块内部光耦隔离。超过两匹的热泵继电器只负责触发交流接触器线圈主回路启停全部交给接触器完成这是最基本的电气安全边界。3. 跑通最小系统WiFi/MQTT连接与回差控制代码拿到源码包之后别急着编译整个工程。我一般先把代码按文件拆开理解主文件放setup和loop网络文件管WiFi和MQTT传感器文件管DS18B20采集控制文件管回差逻辑和保护逻辑。下面两段是把系统跑起来的最小骨架先让板子能上网、能读温度、能控继电器再谈优化。3.1 WiFi连接与MQTT长连接不掉线才是好固件#include ESP8266WiFi.h #include PubSubClient.h const char* ssid your-wifi-ssid; // 只支持2.4G频段5G连不上 const char* password your-wifi-pass; const char* mqttHost 192.168.1.50; // MQTT Broker地址如Mosquitto const int mqttPort 1883; WiFiClient espClient; PubSubClient mqtt(espClient); const char* TOPIC_TEMP hp/temp; // 上报温度 const char* TOPIC_STATE hp/state; // 上报运行状态 const char* TOPIC_SET hp/set/target; // 接收目标温度 bool connectMQTT() { if (mqtt.connect(heatpump-01)) { // clientId多设备必须唯一 mqtt.subscribe(TOPIC_SET, 1); // QoS 1保证参数修改不丢 return true; } return false; } void setupNet() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); uint32_t t0 millis(); while (WiFi.status() ! WL_CONNECTED millis() - t0 10000) { delay(200); // 最多等10秒不把setup卡死 } mqtt.setServer(mqttHost, mqttPort); mqtt.setCallback(onMqttMessage); connectMQTT(); } void loopNet() { if (WiFi.status() ! WL_CONNECTED) { WiFi.reconnect(); // 断网后快速回连 return; } if (!mqtt.connected()) { connectMQTT(); // MQTT掉线每轮重试 delay(2000); // 重试间隔避免暴力重连 } mqtt.loop(); }这段代码的逻辑重点是WiFi连接不放进setup里死等而是用超时10秒跳出这样即使现场路由器没通电设备也能继续跑控制逻辑。MQTT重连每轮循环执行一次每次失败后延时2秒防止连续重连把Broker当机。clientId必须唯一如果两套设备用了同一个ID后连的会把先连的踢下线这是远程维护时最容易忽略的玄学问题。注意ESP8266只支持2.4G频段如果现场路由器开了双频合一设备会频繁掉线建议在路由器里把2.4G和5G分开。3.2 DS18B20温度采集与回差控制主逻辑#include OneWire.h #include DallasTemperature.h #define DS_PIN 4 // GPIO4 接 DS18B20 Data #define RELAY_PIN 5 // GPIO5 接继电器 IN低电平触发 OneWire oneWire(DS_PIN); DallasTemperature sensors(oneWire); float targetTemp 50.0; // 目标温度单位℃可通过MQTT修改 float deadband 2.5; // 回差总区间2.5℃实际偏差±1.25℃ bool relayOn false; void setupSensor() { pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, HIGH); // 初始化时强制断开继电器不吸合 sensors.begin(); sensors.setResolution(10); // 10位分辨率转换约94ms控温够用 } // 中值滤波采5次取中间值滤掉继电器吸合产生的温度毛刺 float readMedianTemp() { float v[5]; for (int i 0; i 5; i) { sensors.requestTemperatures(); // 阻塞等待转换完成 v[i] sensors.getTempCByIndex(0); delay(150); // 采样间隔错开干扰 } for (int i 0; i 4; i) for (int j i 1; j 5; j) if (v[i] v[j]) { float t v[i]; v[i] v[j]; v[j] t; } return v[2]; // 取中值丢弃最大最小 } void updateRelay(float temp) { // 低于下限启动高于上限停机中间区间保持现状 if (!relayOn temp targetTemp - deadband / 2) { relayOn true; digitalWrite(RELAY_PIN, LOW); // 低电平触发吸合 } if (relayOn temp targetTemp deadband / 2) { relayOn false; digitalWrite(RELAY_PIN, HIGH); // 断开 } } void loop() { loopNet(); static uint32_t lastRead 0; if (millis() - lastRead 2000) { // 2秒一个控制周期 lastRead millis(); float t readMedianTemp(); if (t -126 || t 125) return; // DS18B20断线返回-127不动作 updateRelay(t); } }这段代码的核心是回差控制也叫滞回比较目标温度50℃、回差2.5℃实际启动点是48.75℃停机点是51.25℃中间这一段是“死区”继电器保持原状态。这样做的意义在于如果没有死区温度在50℃附近抖动时压缩机会频繁启停每启停一次都要承受大电流冲击和冷媒回流压力用不了几个星期压缩机就废了。传感器这里用了10位分辨率而不是12位单个转换时间从750ms降到94ms控温精度0.125℃对热泵完全够但响应速度快了一个量级。断线检测直接用返回值判断DS18B20断线时返回-127℃见到这个值直接跳过控制动作保持当前继电器状态不让热泵在传感器丢失时乱跳。4. 控制参数调优回差、滤波与压缩机保护三件事代码跑通只是开始真正让热泵“听话”的是参数。这套系统里值得反复调的只有三个参数回差deadband、控制周期、最小启停时间。三个参数互相咬合调任何一个都可能让另外两个失效。4.1 回差参数怎么定目标温度加减偏差不是越小越好回差设置直接决定压缩机的启停频率和温控精度。我按不同现场给过一套参考值直接照这个起点调应用场景目标温度建议回差选用理由水产恒温池25~30℃1.5℃水温惯性大温差太大会应激温室大棚18~22℃2.5~3℃空气热容小回差小了启停过频热水循环45~55℃2~2.5℃管道滞后大需要更大死区烘干房35~40℃1~1.5℃精度优先允许短循环回差点位的确定有个底层规律传感器到热泵换热器之间的滞后越大回差就要越大。水产池里传感器泡在水里温度变化极慢回差1.5℃也不会让压缩机频繁起停大棚里空气热容小温度跑得快回差低于2℃时压缩机基本在不停歇地短循环。我一开始在做温室项目时把回差设成1℃结果一天之内压缩机启停了四十多次听声音就不对劲。把回差放到3℃之后压缩机一天启停控制在十次以内棚内温度波动反而更小因为热泵每次启动都能持续运行一段时间把热量真正送进棚里。4.2 传感器滤波与断线兜底上一章的中值滤波已经是第一道防线但真实现场还会遇到更恶劣的情况继电器吸合的瞬间电磁干扰会通过线缆耦合到DS18B20的数据线上让某一次采样跳到-55℃或者85℃这类极端值。中值滤波能滤掉单点毛刺但如果是连续两次采样都被干扰中值也会被污染。我习惯的做法是连续采5个点、间隔150ms这样任何单次吸合干扰最多污染一个样本中值得不到假值。断线处理同样不能只靠一次判断。DS18B20端子接插件氧化、线缆被老鼠咬断都是真实会发生的事。我在代码里做了连续失败计数连续3个控制周期读不到有效温度强制停压缩机并上报告警而不是让热泵带着一个假温度一直运行下去uint8_t sensorFailCount 0; const uint8_t MAX_FAIL 3; void loop() { // 省略网络处理... static uint32_t lastRead 0; if (millis() - lastRead 2000) { lastRead millis(); float t readMedianTemp(); if (t -126 || t 125) { sensorFailCount; if (sensorFailCount MAX_FAIL) { forceRelayOff(); // 连续3次失败强制停机 mqtt.publish(TOPIC_STATE, {\alarm\:\sensor_lost\}, true); } return; } sensorFailCount 0; updateRelay(t); } } void forceRelayOff() { relayOn false; digitalWrite(RELAY_PIN, HIGH); }这里有个安全设计思路传感器失效时宁可让热泵停掉也不能让它一直开着。热泵如果失去了温度反馈理论上会无限加热把水池煮开或者把温室烤坏是真实事故。强制停机加MQTT告警配合前面的断线判断保证设备只有在拿到可信温度时才有权限控制继电器。4.3 控制周期与压缩机最小启停间隔回差解决了“过度频繁切换”的一半问题另一半要靠最小启停时间保护。热泵压缩机从启动到建立稳定的冷媒循环需要几十秒如果刚启动两分钟就因为温度越限被关掉下次启动又要重新建立循环反复折腾会加速压缩机阀片磨损。对热泵行业熟悉的人都清楚压缩机的命不是按运行时长算的是按启停次数算的。#define MIN_RUN_MS 180000UL // 最小运行时间3分钟 #define MIN_STOP_MS 120000UL // 最小停机时间2分钟 static uint32_t lastRunMs 0, lastStopMs 0; void updateRelayWithProtect(float temp) { uint32_t now millis(); // 启动条件温度低于下限且已停机超过MIN_STOP_MS if (!relayOn temp targetTemp - deadband / 2 (now - lastStopMs) MIN_STOP_MS) { relayOn true; lastRunMs now; digitalWrite(RELAY_PIN, LOW); // 吸合 } // 停机条件温度高于上限且已运行超过MIN_RUN_MS if (relayOn temp targetTemp deadband / 2 (now - lastRunMs) MIN_RUN_MS) { relayOn false; lastStopMs now; digitalWrite(RELAY_PIN, HIGH); // 断开 } }加了最小运行时间之后会有一个表现由于压缩机必须运行满3分钟才能停机温度会越过上限再高一点这个超调量在0.3~0.5℃左右对绝大多数恒温场景是可接受的。反过来停机后温度没回升到启动点之前压缩机也不理会。这两条保护与回差叠加实际的启停曲线会变成一段段至少3分钟的平滑运行而不是锯齿状的高频抖动。控制周期我建议固定2秒不要用delay把整个loop堵死因为MQTT的心跳还得靠loop去跑网络线程被控制逻辑卡死是另一个经典事故来源。5. 避坑指南ESP8266热泵控制最常见的五个故障与排查这一章列的是我在几个现场项目里真实处理过的故障按现象、原因、解决的思路写清楚。每一条都是花过时间换来的血泪经验。5.1 现象设备上电瞬间热泵压缩机自己启动用户描述是“插电就跳”现场听得到接触器吸合的声音。原因几乎都是同一个ESP8266复位瞬间GPIO处于浮空状态电平不确定而廉价继电器模块是低电平触发浮空偶尔会等效成低电平继电器就吸合了。解决分两层硬件上在继电器IN脚对GND加一只10kΩ下拉电阻把复位期间的浮空电平拉高同时在GPIO5和IN脚之间串1kΩ电阻限流。软件上setup里第一件事就是pinMode之后立刻digitalWrite为HIGH断开态然后再延时200ms让电平彻底建立。这两层都做了之后上电误动作基本能杜绝。5.2 现象WiFi掉线后设备从此失联但现场温度还在控制典型的现场场景是市电闪断路由器重启了设备自己连不上网。原因首先是循环里没有做WiFi状态检测只检查了MQTT其次是部分固件在长时间阻塞后底层WiFi回调被系统挂起不主动连接就永远不回去。解决方法是每轮循环做WiFi.status()检查断开时调用WiFi.reconnect()并打印日志同时把整个控制逻辑里的delay全部替换成基于millis()的非阻塞周期调用保证loop每轮都能快速穿过让底层协议栈有足够时间处理重连。重连成功后强制调用mqtt.connect()而不是等PubSubClient自己发现掉线——显式重连比隐式恢复快得多。5.3 现象温度在目标值附近时压缩机几分钟内多次启停现场表现是热泵“哆嗦”启停声音间隔不到三分钟。除了回差设得太小这一条原因还有两个干扰源一是DS18B20数据线与继电器线平行走线吸合瞬间的电磁干扰串进传感器读数造成假温度跳变二是控制周期太短温度还没稳定就被重复触发判断。解决措施是把回差调到2.5℃以上传感器改10位分辨率并做5点中值滤波物理上把传感器线用屏蔽线替换屏蔽层单端接地并且与继电器输出线分槽布置至少隔开20厘米。这是性价比最高的做法比在代码里加再多滤波都管用。5.4 现象断电重启后目标温度、回差参数全部恢复默认用户设置好参数断电一晚上第二天又回到25℃。原因在ESP8266的EEPROM设计上它的EEPROM不是真正的独立存储而是Flash扇区模拟出来的。如果代码里用了EEPROM.write()却没有调用EEPROM.commit()数据只写在内存缓冲里断电就丢还有的工程把多个参数写到了同一个偏移地址后写的把先写的覆盖了。解决方法是每个设置参数定义固定偏移写完统一commit一次更牢靠的做法是把一组参数打包成结构体连同CRC校验字节一起写入读出来先校验CRC校验不过再用默认值。注意Flash是有擦写寿命的一般10万次每天写一次也是两百年参数保存频率不用太担心但日志类高频写入一定要避开EEPROM。5.5 现象连续运行几十小时后设备死机指示灯也不再闪这种间歇性死机最耗时间排查。常见的两个根因一是代码里用了String字符串拼接比如把温度转换成String再拼JSON在回调函数里高频触发堆碎片越积越多最终malloc失败导致看门狗复位循环二是loop里某一段路径耗时过长超过软看门狗超时系统认定卡死就自动复位。解决方法是全部使用char数组和snprintf格式化禁止在循环和回调里new对象在loop关键位置调用yield()让系统处理WiFi事件调试阶段把system_get_free_heap_size()每10秒打印一次观察内存曲线是否单调下降能看到内存泄漏在场时就能当场定位到是哪一段代码在造垃圾。6. 进阶OTA远程升级与MQTT在线调参6.1 不拆机箱的OTA升级热泵控制柜装在天花板底下或者室外机旁边每次改代码都要拆盖板接串口太折腾了。ESP8266的ArduinoOTA库可以直接用WiFi把固件推上去前提是setup里把OTA服务初始化好#include ArduinoOTA.h void setupOTA() { ArduinoOTA.setPort(8266); ArduinoOTA.setPassword(hwp-ota-2024); // 不设密码任何人都能刷 ArduinoOTA.onStart([]() { digitalWrite(RELAY_PIN, HIGH); // 升级前强制断继电器防误动作 }); ArduinoOTA.begin(); } // loop里要加上这一句 ArduinoOTA.handle();OTA第一个原则是升级过程中绝不能带动继电器否则固件跑到一半GPIO被复位继电器可能吸合热泵在无人盯守时启动这是实打实出过事故的点。我吃过这个亏某次升级测试时没做继电器锁定烧录过程中接触器“啪”地一声吸合从那之后onStart强制拉高RELAY_PIN成了每版固件的固定动作。6.2 MQTT消息回调与参数掉电保存的配合远程调参通过MQTT的订阅回调实现收到主题hp/set/target就把目标温度更新到内存同时写进EEPROM保证掉电不丢void onMqttMessage(char* topic, byte* payload, unsigned int len) { if (strcmp(topic, hp/set/target) 0) { char buf[8] {0}; memcpy(buf, payload, len 7 ? 7 : len); float newTarget atof(buf); if (newTarget 10.0 newTarget 80.0) { // 做合法范围校验 targetTemp newTarget; saveSettings(); } } }这里有两个习惯值得保持第一任何从网络进来的数值都必须做范围校验MQTT报文可能被乱发的客户端干扰一个-20℃的目标值会让热泵全功率运转第二参数合法才落盘保存非法值直接丢弃不给现场制造“设备自己乱改参数”的错觉。最后说一句我自己总结的教训给设备加远程控制能力之前先把最底层的物理安全守住传感器丢失要停机、OTA升级要锁继电器、网络掉线要保持本地控制不中断。这三条是这套系统所有功能的底座底座没立住远程调参再方便也是悬在空中的体验。希望帮到你。本文还有配套的精品资源点击获取