综合实战:智能环境监测站
把前 11 章串起来:ESP32 + DHT11 + OLED + 按键,做一个会显示、能联网上报、可切换页面的完整作品。
把前 11 章串起来:ESP32 + DHT11 + OLED + 按键,做一个会显示、能联网上报、可切换页面的完整作品。
page 表示当前显示哪一页(0=实时、1=统计),按键切换它,刷屏函数根据 page 画不同内容。这种"用状态变量 + 事件驱动切换"的写法就是状态机,是组织复杂交互逻辑的利器,比一堆嵌套 if 清晰得多。readSensor()、updateDisplay()、uploadData()、handleButton() 等独立函数,loop 里只做"调度"。函数职责单一、命名清晰,项目大了也不乱——这是从"能跑"到"好维护"的分水岭。下面是可直接编译上传的完整工程(ESP32,需装 U8g2、DHT sensor library)。填入你的 WiFi 后即可运行:
#include <WiFi.h> #include <HTTPClient.h> #include <U8g2lib.h> #include <DHT.h> // ---------- 引脚与配置 ---------- #define DHTPIN 4 #define DHTTYPE DHT11 const int btnPin = 5; const int ledPin = 2; const float TEMP_ALARM = 30.0; // 温度报警阈值 ℃ const char* ssid = "你的WiFi名"; const char* pass = "你的WiFi密码"; // ---------- 对象 ---------- DHT dht(DHTPIN, DHTTYPE); U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0); // ---------- 全局状态 ---------- float temp = 0, humi = 0; float tMax = -99, tMin = 99; int page = 0; // 0=实时页 1=统计页 bool alarm = false; // 三组非阻塞定时器 unsigned long tSensor = 0, tDisp = 0, tUpload = 0; unsigned long lastBtn = 0; bool lastBtnState = HIGH; // ---------- 功能函数 ---------- void readSensor() { float t = dht.readTemperature(); float h = dht.readHumidity(); if (isnan(t) || isnan(h)) return; // 读失败就跳过 temp = t; humi = h; if (t > tMax) tMax = t; // 更新统计 if (t < tMin) tMin = t; alarm = (t >= TEMP_ALARM); // 判阈值 digitalWrite(ledPin, alarm ? HIGH : LOW); } void updateDisplay() { u8g2.firstPage(); do { u8g2.setFont(u8g2_font_ncenB08_tr); if (page == 0) { // ---- 实时页 ---- u8g2.drawStr(0, 12, "== Live =="); char buf[24]; sprintf(buf, "Temp: %.1f C", temp); u8g2.drawStr(0, 32, buf); sprintf(buf, "Humi: %.0f %%", humi); u8g2.drawStr(0, 48, buf); if (alarm) u8g2.drawStr(0, 63, "! ALARM !"); } else { // ---- 统计页 ---- u8g2.drawStr(0, 12, "== Stats =="); char buf[24]; sprintf(buf, "Max: %.1f C", tMax); u8g2.drawStr(0, 32, buf); sprintf(buf, "Min: %.1f C", tMin); u8g2.drawStr(0, 48, buf); } } while (u8g2.nextPage()); } void uploadData() { if (WiFi.status() != WL_CONNECTED) return; HTTPClient http; http.begin("http://example.com/api/data"); http.addHeader("Content-Type", "application/json"); String body = "{\"temp\":" + String(temp) + ",\"humi\":" + String(humi) + "}"; http.POST(body); http.end(); } void handleButton() { bool s = digitalRead(btnPin); // 下降沿 + 50ms 消抖 → 切换页面 if (lastBtnState == HIGH && s == LOW && millis() - lastBtn > 50) { page = 1 - page; // 0↔1 翻转 lastBtn = millis(); } lastBtnState = s; } // ---------- 初始化 ---------- void setup() { pinMode(btnPin, INPUT_PULLUP); pinMode(ledPin, OUTPUT); dht.begin(); u8g2.begin(); WiFi.begin(ssid, pass); // 后台连,不阻塞主流程太久 } // ---------- 主循环:纯调度,无 delay ---------- void loop() { unsigned long now = millis(); if (now - tSensor >= 2000) { tSensor = now; readSensor(); } if (now - tDisp >= 200) { tDisp = now; updateDisplay(); } if (now - tUpload >= 30000) { tUpload = now; uploadData(); } handleButton(); // 每轮都查,按键最灵敏 }
先看 loop()——它只有四行调度,一眼看清"每隔多久干什么"。再顺着点进每个函数看细节。这种"顶层调度极简、细节封装进函数"的结构,正是你未来读别人代码、写大项目时该追求的。任何复杂系统,拆开都是这样一层层的小函数。
① OLED 不亮先跑第 8 章的 I2C 扫描确认地址;② DHT11 读到 NaN 检查间隔≥1s 和上拉;③ 想在 Uno 上跑?去掉 WiFi/上报部分即可(Uno 无 WiFi),显示+按键+采集完全通用;④ 把 uploadData 换成 PubSubClient 发 MQTT,就能接入巴法云被小爱同学播报。整个骨架高度可裁剪、可扩展。
| 方向 | 怎么加 | 用到的章 |
|---|---|---|
| 接语音助手 | 上报改 MQTT + 巴法云 | 第 11 章 |
| 多传感器 | 加光照/气压/PM2.5(I2C) | 第 6、10 章 |
| 自动联动 | 超阈值继电器开风扇/加湿器 | 第 3、7 章 |
| 数据可视化 | ThingSpeak/自建后端画曲线 | 第 11 章 |
| 本地网页 | WebServer 手机看实时数据 | 第 11 章 |
恭喜走到最后一章!这个环境监测站把整门课串成了闭环:用 DHT11 采集(第 6 章)、U8g2 驱动 OLED 显示(第 8 章)、按键消抖切换页面(第 3 章)、状态机组织交互、millis 非阻塞让采集/刷屏/上报三节奏并行(第 9 章,全程无 delay)、WiFi + HTTPClient 上报云端(第 11 章),还实现了统计与阈值报警的完整"采集→处理→决策→执行"闭环。真实项目的精髓不是某个炫技,而是把零件拼成可维护的整体:顶层 loop 只做调度、功能拆成单一职责的小函数、共享状态清晰、绝不用 delay 阻塞。掌握了这套方法,后面接 MQTT、加传感器、做联动、上云画曲线,都只是往这个框架里添砖加瓦。从第 1 章点亮第一颗 LED,到现在做出联网监测站——你已经具备独立完成 Arduino 创客项目的能力了。接下来,去动手做点自己想做的东西吧。