ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

STM32温室暖棚物联网毕设源码:传感器采集、MQTT上云与远程控制全解析

STM32温室暖棚物联网毕设源码:传感器采集、MQTT上云与远程控制全解析

简介:面向物联网与嵌入式开发者的智慧农业温室暖棚控制与监测系统源码,基于STM32主控芯片实现,适合在校学生、工程师用于课程设计、毕业设计或项目初期验证。工程由传感器采集、设备控制、云平台通信和微信小程序远程监测构成:使用DHT11温湿度传感器、BH1750光照传感器采集环境数据,通过ESP8266接入OneNET平台,配合OLED显示与继电器控制,实现暖棚环境的自动化调节。资源包共四十五个文件,大小约六百六十六KB,主体为十五个C语言源文件,另有微信小程序前后端描述文件、电路原理图PDF、Gerber PCB制板文件及README说明,目录划分为主控程序、小程序、硬件设计三部分,便于按模块查阅与二次开发。所有核心代码均经过运行测试,功能稳定,作者提供远程讲解支持,下载后如遇编译或调试问题可私信咨询。这套源码已有166人学习下载,可直接作为毕设演示或在此基础上增加更多农业场景功能。

1. 基于物联网 STM32 的温室暖棚系统:一整套能打板、能上云、能远程控制的毕设级源码

做温室暖棚最怕的从来不是热,而是半夜温度骤降、通风不及时、光照不足导致作物徒长。这套基于物联网 STM32 的智慧农业源码,解决的正是「感知—联网—控制—远程操作」全链路问题:底层用 STM32 读取 DHT11 温湿度和 BH1750 光照强度,按阈值逻辑驱动继电器控制加热、加湿、补光和通风设备;联网走 ESP8266 + MQTT 协议上报云平台,手机端用微信小程序实时查看数据、远程下发控制指令。它不是「点个灯」级别的单片机 demo,而是从 PCB 打板文件、电路原理图、固件源码到小程序端的完整毕设级工程。适合物联网、自动化、电子信息的毕设、课设,也适合想从裸机开发跨到物联网全栈的从业者拿来当学习底板。整套源码自带工程目录和硬件资料,边看边跑,比对着开发板手册东拼西凑要高效得多。

2. 固件主控:STM32 外设驱动与工程结构拆解

拿到压缩包先别急着编译,先看它背后的代码组织方式。这套工程把「硬件驱动、协议封装、业务逻辑」分得很清楚,后面改传感器、换平台、加功能都是围绕这个结构展开的。

2.1 从 dht11.c / bh1750.c / oled.c 看外设选型逻辑

先说传感器为什么选这几种。DHT11 是典型的单总线数字温湿度传感器,精度是 ±2℃ 和 ±5%RH,放在温室暖棚这个场景完全够用——你要的不是实验室级测量,而是「温度低于 15℃ 就开加热、湿度低于 40% 就开加湿」这种趋势判断。价格便宜、接口简单、代码成熟,是毕设和课设最常见的选择。BH1750 是 I2C 接口的数字光照传感器,内部自带 16 位 ADC,直接把光照强度换算成 lux 输出,不需要自己搭模拟放大电路,接两根线就能读。至于 OLED,它在这套系统里的角色不只是显示数据,更是调试工具——在没有串口线或者现场接电调试的时候,看一眼屏幕就知道当前温湿度、联网状态和继电器动作,比猜代码强得多。

选型上有个容易被忽略的点:DHT11 的响应速度和精度都不高,但它的时序是公开且固定的;BH1750 的 I2C 地址取决于 ADDR 引脚电平,默认是 0x23,硬件上如果拉高就变成 0x5C。这些细节在阅读源码时都会体现出来,后面接线和移植时尤其要注意。

2.2 固件工程结构:驱动层、协议层与应用层

把压缩包里的 C 文件按职责分类,结构非常清晰,表格如下:

层级对应源文件职责
基础服务层delay.c、sys.c、usart.c延时、系统时钟、串口收发与中断处理
硬件驱动层dht11.c、bh1750.c、oled.c、key.c具体外设的初始化、读取与显示
协议/通信层esp8266.c、MqttKit.c、onenet.cAT 指令封装、MQTT 报文构造、平台数据点对接
应用层main.c、exti.c、timer.c、led.c、beep.c业务流程、外部中断、定时任务、指示与告警

这种分层最大的好处是解耦。比如你想把 DHT11 换成 SHT30,只需要改驱动层,上层控制逻辑完全不用动;想把上报目标从 OneNET 换成其他物联网平台,只需要重写 onenet.c 里的数据点组装函数,业务层的阈值判断、继电器控制不受影响。实际做项目时,我一般会在这种结构上再加一个config.h,把所有引脚定义、阈值参数、云端鉴权信息集中放,改配置不动逻辑代码,后期调参方便很多。

2.3 DHT11 单总线时序读取与校验

DHT11 的数据线是单总线,主机先拉低 20ms 左右发起读取,然后释放总线,DHT11 会拉低响应信号,之后连续输出 40 bit 数据:8 bit 湿度整数、8 bit 湿度小数、8 bit 温度整数、8 bit 温度小数、8 bit 校验和。典型驱动代码如下:

uint8_t dht11_read_data(uint8_t *hum, uint8_t *temp) { uint8_t data[5] = {0}; uint8_t i, j, tmp; // 主机拉低总线 18-30ms 触发读取 DHT_GPIO_MODE_OUT(); DHT_GPIO_RESET(); delay_ms(20); DHT_GPIO_SET(); delay_us(30); DHT_GPIO_MODE_IN(); // 等待 DHT11 响应:先拉低 80us,再拉高 80us if (!DHT_GPIO_READ()) { while (!DHT_GPIO_READ()); // 等待拉高 while (DHT_GPIO_READ()); // 等待响应结束 for (i = 0; i < 5; i++) { for (j = 0; j < 8; j++) { while (!DHT_GPIO_READ()); // 等待 50us 低电平结束 delay_us(40); // 40us 后采样 tmp = 0; if (DHT_GPIO_READ()) tmp = 1; while (DHT_GPIO_READ()); data[i] = (data[i] << 1) | tmp; } } } // 校验:前 4 字节之和等于第 5 字节 if ((uint8_t)(data[0] + data[1] + data[2] + data[3]) == data[4]) { *hum = data[0]; *temp = data[2]; return 0; } return 1; }

逻辑上最关键的坑在采样点:每一位数据的时间结构是 50us 低电平 + 26~28us(逻辑 0)或 70us(逻辑 1)的高电平。上面这段代码的做法是等低电平结束后延时 40us 再读电平,这个 40us 就是区分 0 和 1 的分水岭。参数上需要注意两点:起始低电平时间要在 18~30ms,太短 DHT11 不响应,太长会拉长整个读取周期;采样间隔建议大于 1s,否则传感器自身来不及刷新,读出来的永远是旧值。

2.4 BH1750 光照读取与测量模式

BH1750 走 I2C,写入测量指令后需要等待内部转换完成再读数据。常见的连续高分辨率模式指令是 0x10,转换时间约 120ms,读出来的两个字节拼成 16 位数据再除以 1.2 就是实际 lux 值。核心代码如下:

uint16_t bh1750_read_lux(void) { uint8_t buf[2]; uint16_t lux; // 发送 0x10:连续高分辨率模式 i2c_start(); i2c_write(BH1750_ADDR << 1); // 地址 0x23,写方向 i2c_write(0x10); i2c_stop(); delay_ms(180); // 等待转换完成,高分辨率模式约 120ms // 读取 2 字节,大端拼接 i2c_start(); i2c_write((BH1750_ADDR << 1) | 0x01); // 读方向 buf[0] = i2c_read_ack(); buf[1] = i2c_read_nack(); i2c_stop(); lux = (buf[0] << 8) | buf[1]; return lux / 1.2; }

这里 180ms 的延时是保守值,实测高分辨率模式下 120ms 就能读到稳定数据,但如果测量模式改成一字节能更快,代价是分辨率减半。参数说明:BH1750 的地址由 ADDR 引脚决定,接地是 0x23,接高是 0x5C,如果你的板子和源码里不一致,改宏定义就行,不需要动电路。读回来的原始值如果接近 65535,说明光照超出量程,需要换低分辨率模式或者增加遮光处理。

3. 联网链路:ESP8266 与 MQTT 上云,从 AT 指令到数据上报

温室监控的价值在于「不在现场也能知道棚内状态」。这一章把 STM32 如何通过 ESP8266 接入云平台的完整链路拆开讲清楚。整条链路是:STM32 串口发 AT 指令 → ESP8266 连 WiFi → 建立 MQTT 连接 → 订阅/发布主题 → 云平台转发给微信小程序。

3.1 STM32 与 ESP8266 的串口分工

ESP8266 模块在整套系统里承担 WiFi 和 MQTT 协议栈的运算,STM32 只需要通过 USART 和它对话。固件里 esp8266.c 负责拼装和发送 AT 指令,usart.c 负责接收模块返回的响应,MqttKit.c 负责把 MQTT 报文按协议格式填字节流。这三个文件的配合关系就像「命令发出、响应接收、协议编解码」三层流水线。

串口参数上,ESP8266 模块出厂波特率常见 115200 或 9600,固件里大概率会有一段AT+UART_DEF的初始化把模块配置到目标波特率。调试时要注意:如果模块当前波特率和源码配置不一致,你会发现发指令没任何回应,这是最常见的翻车点之一,后面避坑章会专门讲。

3.2 AT 指令配置流程:入网到建立 MQTT 连接

模块上电后需要依次完成:设置 Station 模式、连接 WiFi、配置 MQTT 用户名密码、建立连接。这套序列在 esp8266.c 里体现得比较完整,核心流程如下:

// 1. 设置 Station 模式 esp8266_send_cmd("AT+CWMODE=1", "OK", 3000); // 2. 连接 WiFi,SSID 和密码从配置项读取 esp8266_send_cmd("AT+CWJAP=\"MyGreenHouse\",\"12345678\"", "WIFI GOT IP", 15000); // 3. 配置 MQTT 参数:clientid, username, password esp8266_send_cmd("AT+MQTTUSERCFG=0,1,\"greenhouse01\",\"productID\",\"auth_token\",0,0,\"\"", "OK", 2000); // 4. 建立 MQTT 连接,服务器地址和端口 esp8266_send_cmd("AT+MQTTCONN=0,\"iot-platform.example.com\",1883,1", "OK", 5000);

参数说明:第 2 步的AT+CWJAP要等「WIFI GOT IP」而不是「OK」,因为入网成功的标志是拿到 IP;第 3 步的MQTTUSERCFG中,第一个参数 0 是连接 ID,1 表示使能 SSL,后面依次是 clientid、用户名、密码;第 4 步MQTTCONN里的参数是连接 ID、服务器域名、端口、是否使能重连。每个指令都要设置超时时间,WiFi 连接给 15s,MQTT 建连给 5s,响应超时做重试。实际经验是:ESP8266 模块偶尔会吞指令或者返回乱码,所以每条指令发出后要清空接收缓冲区再等待目标关键字,否则容易出现「上次的 OK 和这次的 OK 混在一起」的错乱。

3.3 MQTT 报文结构与 MqttKit.c 的封装逻辑

MQTT 报文分为固定头、可变头和载荷三部分。固定头第一个字节的高 4 位表示报文类型(1 是 CONNECT,3 是 PUBLISH,8 是 SUBSCRIBE),低 4 位是标志位;剩余长度用变长编码表示。MqttKit.c 做的事情就是把协议字段填进字节流。下面是构造一个 CONNECT 报文的典型代码:

void mqtt_connect_packet(uint8_t *buf, const char *client_id) { uint8_t idx = 0; uint8_t payload_len; const char *proto = "MQTT"; const char *cid = client_id; // 固定头:类型 1 (CONNECT),剩余长度暂填 0 buf[idx++] = 0x10; buf[idx++] = 0x00; // 稍后回填 // 可变头:协议名 MQTT,级别 4 buf[idx++] = 0x00; buf[idx++] = 0x04; memcpy(&buf[idx], proto, 4); idx += 4; buf[idx++] = 0x04; // protocol level buf[idx++] = 0xC2; // 连接标志:用户名+密码+清理会话 buf[idx++] = 0x00; buf[idx++] = 0x3C; // keepalive 60s // 载荷:client_id 长度前缀 + 字符串 uint16_t cid_len = strlen(cid); buf[idx++] = cid_len >> 8; buf[idx++] = cid_len & 0xFF; memcpy(&buf[idx], cid, cid_len); idx += cid_len; // 回填剩余长度 buf[1] = idx - 2; }

逻辑说明里最重要的一点是连接标志0xC2:二进制是 1100 0010,表示启用密码和用户名、并请求清理会话。如果你的设备端证书鉴权方式不同,这个字节要相应变化。keepalive 设置为 60s,意味着 60s 内没有业务报文,设备必须发心跳包 PINGREQ 保持连接。MqttKit.c 的本质,就是把这些字节编排逻辑从 main 函数的业务代码里剥离出来,避免满屏都是十六进制魔法数。

3.4 云平台接入与数据点设计

设备端把数据上传到云平台,不是直接把温湿度明文发上去,而是按照平台定义的数据流格式组装 JSON。常见做法是上报到数据流主题,核心负载结构如下:

{ "datastreams": [ { "id": "temp", "datapoints": [ { "value": 25.3 } ] }, { "id": "humi", "datapoints": [ { "value": 62.0 } ] }, { "id": "lux", "datapoints": [ { "value": 1200 } ] } ] }

代码里的 onenet.c 实际上就是把你读到的传感器值填进这个 JSON 模板,然后通过 MQTT 的 PUBLISH 报文发出去。数据点的 id 要在云平台「产品—设备—数据流」里预先创建好,名称必须和代码里一致,否则平台会直接丢弃这条数据。另外,设备端和平台的鉴权信息一般分为用户名和密码,密码往往是设备的 access token,烧录前要确认固定设备和 token 的对应关系,很多人换了设备但没换 token,导致连不上网。

3.5 心跳、掉线检测与自动重连

温室现场的网络不稳定是常态,所以链路设计里必须有掉线重连机制。实际工程里常见的做法是:主循环里定时发布一条心跳数据;当发现 MQTT 连接断开(串口被动的收到模块上报的+MQTTDISCONNECTED),延迟 5s 后重新执行 MQTTUSERCFG 和 MQTTCONN,并增加失败后的重试退避逻辑,连续失败 5 次后把模块复位重新初始化。

重连参数建议:心跳间隔 30~60s 比较合适,太短消耗流量和模块资源,太长容易被服务器踢掉。退避策略上,第一次失败等 5s,第二次 10s,最多等 60s 封顶。这套在 onenet.c 的底层驱动里如果没实现,你可以自己在 main 循环里用状态机补上。

4. 控制逻辑与调参:阈值判断、状态切换与手动/自动模式

硬件驱动读到了数据,联网链路把数据送上了云平台,接下来是整套系统真正干活的部分:怎么根据温湿度光照决定打开还是关掉加热、加湿、补光和通风设备。

4.1 main.c 顶层循环与业务状态流

主循环的设计思路是「采集—上报—控制—响应按键—等待」的流水线。由于 DHT11 需要 1s 以上的采样间隔,整个循环一般用 1s 作为节拍,光照传感器每隔几个节拍读一次。伪代码如下:

while (1) { // 1. 读取传感器 if (dht11_read_data(&humi, &temp) == 0) { lux = bh1750_read_lux(); } // 2. 上报云平台:仅在成功读取后上报 if (link_ok && need_report) { mqtt_publish_status(temp, humi, lux); } // 3. 自动模式下执行阈值控制 if (mode == MODE_AUTO) { climate_control(temp, humi, lux); } // 4. 按键和外部事件响应 key_scan(); exti_process(); delay_ms(500); }

这个结构最大的好处是每一轮都有明确的入口和出口,出了故障可以直接定位到对应环节。注意need_report这个标志位,我一般不会每个循环都上报,而是每 5s 一次,一方面省流量,另一方面云平台的图表曲线也不会被刷得太密。上报频率每 5~10s 一次在农业场景完全够用,如果要做实时性更强的控制,把控制判断放在上报前面即可。

4.2 阈值控制与滞回区间:防止继电器频繁吸合

直接拿传感器值和一个固定阈值比较,是新手最常见的控制写法,但实际运行几天后就会发现问题:温度在 20℃ 附近抖动,继电器跟着反复吸合释放,加热器寿命堪忧。正确的做法是加入滞回区间,也就是「低于下限启动,高于上限停止,中间维持现状」。核心控制逻辑如下:

void climate_control(uint8_t temp, uint8_t humi, uint16_t lux) { // 温度控制:低于 20℃ 开加热,高于 24℃ 关加热 if (temp < TEMP_LOW) heater_ctrl(ON); else if (temp > TEMP_HIGH) heater_ctrl(OFF); // 湿度控制:低于 40% 开加湿,高于 60% 关加湿 if (humi < HUMI_LOW) humidifier_ctrl(ON); else if (humi > HUMI_HIGH) humidifier_ctrl(OFF); // 光照控制:低于 2000lux 且温度允许时补光 if (lux < LUX_LOW && temp >= TEMP_LOW) light_ctrl(ON); else if (lux > LUX_HIGH) light_ctrl(OFF); }

参数说明:TEMP_LOW、TEMP_HIGH之间就是滞回区间,温差 4℃ 是一个比较保守的数值。如果设置成同样的 20/20,控温设备会在 1℃ 范围内的抖动中反复通断,这个现象在继电器咔嗒声里听得特别明显。湿度控制同理,40%~60% 的区间既保证作物生长环境,又给执行器足够的休息时间。光照控制额外加了温度判断,是为了避免补光灯在高温天同时启动,进一步加剧棚内升温。

4.3 手动/自动切换与按键中断处理

现场调试时,不会永远都希望系统自动控制。按键模块承担手动/自动模式切换和执行器的手动启停。key.c 负责扫描按键状态并去抖,exti.c 负责响应外部中断(比如紧急停止按钮),beep.c 在模式切换和执行器动作时发出提示音。

典型的状态机逻辑是:短按模式键在「自动」和「手动」之间切换;在手动模式下,短按某个执行器对应的按键就翻转该执行器开关状态。代码层处理如下:

void key_scan(void) { if (KEY_MODE_PRESSED) { delay_ms(20); // 消抖 if (KEY_MODE_PRESSED) { g_mode = !g_mode; beep_on(50); } } }

外部中断配合蜂鸣器还有一个用途:当温度超过安全上限(比如 35℃)时,强制打开通风并报警,这类保护逻辑不应该依赖云平台下发指令,必须在设备端独立完成,相当于给系统上了最后一道保险。

4.4 控制参数调整建议

参数建议值影响
TEMP_LOW18~20℃过低的启动阈值会导致加热过晚,作物受冻
TEMP_HIGH24~26℃过高的关闭阈值会让加热器持续工作,浪费能源
HUMI_LOW40~45%大棚内湿度过低,叶片容易失水
HUMI_HIGH60~65%高于 65% 容易诱发病害
LUX_LOW1500~2500 lux补光启动点,阴天和夜间适用
滞回区间4~6℃数值越小控制越精准,但执行器磨损越大

参数调整的核心原则是「先保安全,再谈节能」。整套系统的控制闭环完全可以在设备端独立运转,即使云平台断开、小程序连不上,棚内的温控和通风依然正常,这点务必在答辩或验收时讲清楚,它是整套设计的可靠性格栅。

5. 避坑指南:ESP8266 静默失败、DHT11 时序翻车与供电玄学

整个工程跑通不难,但「能编译通过」和「在现场稳定运行一周」是两码事。以下这几条坑,基本覆盖了接手这类项目时最容易浪费时间的排查点。

5.1 DHT11 读出来的数据全是 0xFF 或长时间无响应

现象:串口打印温湿度显示 255、85 之类的固定值,或者 OLED 上数据完全不刷新。

原因:大概率是单总线上拉电阻缺失导致信号边沿变缓,DHT11 的低电平响应无法被 STM32 正确识别;另一可能是中断被高频触发,导致 40us 采样时序被打乱。

解决:数据线上加 4.7kΩ~10kΩ 上拉电阻到 3.3V,这是最有效的修正。代码层面,在读取 DHT11 的完整时序函数前后关闭中断(__disable_irq()和__enable_irq()),防止定时器或串口中断打断采样窗口。注意 DHT11 采样间隔至少 1s,如果你在 while 循环里连续读取,第二个周期失败是因为传感器还没准备好,不是硬件坏了。

5.2 ESP8266 连接云平台后几分钟就掉线,再也连不上

现象:MQTT 连接刚建立时上报正常,约 3~5 分钟后串口出现+MQTTDISCONNECTED,重启模块又能恢复,但几分钟后再次掉线。

原因:设备没有在 keepalive 时间内发送心跳包,被服务器判定为死连接。部分 ESP8266 AT 固件版本在配置了AT+MQTTCONN后不会自动发心跳,必须由 MCU 周期性地发送 PINGREQ,即AT+MQTTPING。

解决:在 main 循环里用计数器统计距离上次上报的时间,如果超过 30s 没有业务数据发送,就主动执行一次AT+MQTTPING,并检查返回值是否为 OK。同时确认 keepalive 参数和实际心跳间隔匹配,例如 keepalive 设为 60s,心跳周期应设为 30s 左右,留一半余量给网络延迟。

5.3 ESP8266 串口收到的响应是乱码或完全沉默

现象:向模块发送AT,没收到预期OK,收到的是#或乱码字符。

原因:模块当前波特率和源码配置不一致。常见出厂固件波特率是 115200,但如果模块经过了二次配置或被刷过非标准固件,实际的波特率可能是 9600 或 74880(固件启动日志专用)。

解决:先用 USB-TTL 直连模块,逐个测试 115200、9600、74880 等常见波特率,确认能收到OK后,发送AT+UART_DEF=115200,8,1,0,0把模块默认波特率固定为和源码一致的速率,然后断电重启再接到 STM32 上。这个操作要记住:改完默认波特率后,旧波特率就失效了,相当于给模块换了一套「语言」。

5.4 继电器频繁通断,加热器发出高频咔嗒声

现象:控制器处于自动模式,温度在阈值附近波动时,继电器 1~2 秒切换一次。

原因:代码里只做了单点阈值比较,没有滞回处理。20℃ 开加热、20℃ 关加热,传感器数值在 19.8~20.2℃ 抖动,执行器必然跟着抖动。

解决:把控制逻辑改成temp < 20℃启动、temp > 24℃停止的滞回区间,中间状态维持上一次的输出。继电器驱动电路上还可以并接续流二极管,防止感性负载产生反向电动势干扰 MCU 复位。这个改动同时也是答辩时的加分点,因为体现了「工程思维」而不只是「抄代码」。

5.5 OLED 花屏、闪烁或亮度过低

现象:屏幕显示混乱字符,或者内容能看清但整体发虚,偶发白屏。

原因:I2C 地址不匹配会直接白屏;电源纹波过大导致屏幕初始化失败是更隐蔽的原因,尤其是继电器吸合的瞬间,大电流导致 3.3V 供电跌落超过 100mV。

解决:先确认屏幕实际地址是 0x3C 还是 0x3D,这和屏幕模组上的拉高/拉低电阻有关。电源侧在 OLED 的 VCC 和 GND 之间就近加 100nF 去耦电容,不要把继电器供电和模块供电接在同一组杜邦线上,用独立电源给执行器供电,这是现场稳定运行的基本功。

5.6 云平台不更新数据,但设备端串口显示上报成功

现象:OLED 显示数据在变,串口日志显示 publish 返回 OK,但手机端小程序页面数值长期不变。

原因:上报的数据流 id 和云平台产品里创建的 id 不一致,或 JSON 格式里 datapoints 的 value 类型不匹配(字符串 vs 数字)。

解决:在 onenet.c 的数据组装函数里,把设备端发送的完整 JSON 字符串通过串口原样打印出来,然后在平台调试页手动模拟同一条数据推送。对比后通常是 id 拼写错误或漏了引号。有一类情况是云平台把重复 value 值合并了,比如连续两次上报 25.3,平台只记录变化值,界面不更新是正常的。

6. 进阶玩法:从阈值开关到策略调度,把毕设做成可落地的系统

前面几章已经把全套源码跑通了,但从「会跑」到「跑得好」之间,还有一段路。这章分享几个提升方向,都是我在类似项目上验证过成本不高、收益明显的改法。

把固定阈值升级成分时段策略。温室暖棚温控和光照策略,白天和夜间应该完全不同。白天有太阳辐射,棚内温度可能短时间冲到 35℃,这时加热器必须锁死;夜间失去辐射热源,20℃ 启动加热就是合理的。做法很简单:在控制函数入口加一个依据系统时间的判断分支,白天使用一套参数,夜间使用另一套参数。参数表如下:

时段温度下限温度上限补光启动 lux
06:00~18:0022℃30℃1500
18:00~06:0016℃24℃2000

增加趋势判断,提前动作。单纯靠阈值判断是「事后补偿」,靠趋势判断可以做「事前干预」。连续采集 5 分钟温度,如果每分钟下降超过 0.5℃,说明棚体保温性能在变差,可以提前启动加热而不必等到跌破下限。参考逻辑:

float temp_slope(float *buf, uint8_t len) { // 用最后 5 个采样点做线性斜率估计 float sum_x = 0, sum_y = 0, sum_xy = 0, sum_xx = 0; for (uint8_t i = 0; i < len; i++) { sum_x += i; sum_y += buf[i]; sum_xy += i * buf[i]; sum_xx += i * i; } float n = len; return (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x); } // 主循环内:趋势为负且斜率绝对值超过阈值时提前启动加热 if (temp_slope(history, 5) < -0.5f) { heater_ctrl(ON); }

这段代码里 5 个点的窗口是经验值,窗口太短斜率被传感器噪声淹没,太长则提前量落后于实际变化,0.5℃/min 的触发阈值也需要按自家棚体的保温能力标定。迭代方式很简单:先记录三天数据,把斜率打印出来,找出「温度自然下降但还不需要加热」的区间,把阈值设在那之上。

离线验收三板斧。验收这套系统时不需要真的去大棚里冻一晚上。用一块湿毛巾包住 DHT11 的探头,湿度数值应该快速上升到 90% 以上;用电吹风在探头附近吹热风,温度上升后加热器应关闭、通风设备应启动;用手机热点给 ESP8266 配网测试联网链路,比蹭现场 WiFi 靠谱得多。整个过程焊个转接板放在桌面上就能完成,花不了半小时。

把调试信息变成你的后悔药。整个系统跑起来后,我强烈建议保底做一件事:串口日志分级。普通信息打印温度、湿度、光照和继电器状态;警告信息打印 MQTT 重连、DHT11 读取失败的事件;错误信息只打印无法恢复的故障。同时在 OLED 上留一页「系统状态页」,标准展示当前模式(自动/手动)、WiFi 是否连接、云平台是否在线、剩余可用内存。这样现场跑飞了,你不用猜是传感器挂了还是网络断了,看屏幕就好。

这套源码的价值不在代码本身,而在它把传感器、主控、通信、平台、小程序这几层之间的接口关系完整地串起来了。我接手类似项目时,第一件事就是先看它的消息流连接关系,再决定改哪里。从那以后,我每次给这类系统重新布线或换传感器,都会强制自己先画一遍「数据从传感器到手机屏幕走了哪几跳」,把每一跳的串口日志和状态输出提前埋好。做到这一步,后面所有调试都是在填参数,而不是在猜逻辑。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

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