项目概述与硬件清单
本章构建一个室内/室外环境监测站,具备以下功能:实时采集温度、湿度、气压和空气质量;在本地 E-ink 显示屏上显示当前数值;通过 MQTT 上报到云端并展示在 Web Dashboard;支持 OTA 远程升级;深度睡眠省电,电池可用数月。
系统架构设计
项目文件结构
env_monitor/
├── CMakeLists.txt
├── partitions.csv # 自定义分区表,含 OTA + LittleFS
├── sdkconfig.defaults # 预设 menuconfig 选项
├── data/ # LittleFS 内容(烧录时打包)
│ └── index.html # Web Dashboard 页面
└── main/
├── CMakeLists.txt
├── main.c # app_main,系统初始化
├── sensor.c / .h # BME280 + MQ135 驱动封装
├── display.c / .h # E-ink 显示逻辑
├── mqtt_client.c / .h
├── http_server.c / .h # Web Dashboard HTTP 服务器
├── ota.c / .h
├── storage.c / .h # NVS 配置读写封装
└── power.c / .h # 深度睡眠控制
核心数据结构
/* sensor.h */
#pragma once
#include "esp_err.h"
typedef struct {
float temperature; // °C
float humidity; // %RH
float pressure; // hPa
uint16_t aqi_raw; // MQ135 ADC 原始值 0~4095
uint32_t timestamp; // Unix 时间戳
int battery_mv; // 电池电压 mV
} env_data_t;
typedef struct {
char wifi_ssid[33];
char wifi_pass[65];
char mqtt_uri[128];
char device_id[20];
uint32_t sleep_seconds; // 深度睡眠时长,默认 600
bool eink_enabled;
} app_config_t;
esp_err_t sensor_init(void);
esp_err_t sensor_read(env_data_t *data);
多任务架构实现
/* main.c — 系统初始化与任务编排 */
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "nvs_flash.h"
#include "sensor.h"
#include "storage.h"
#define EVT_WIFI_UP BIT0
#define EVT_TIME_SYNC BIT1
QueueHandle_t g_data_queue;
EventGroupHandle_t g_system_events;
app_config_t g_config;
/* 传感器任务:每 5 秒读一次,发队列 */
static void sensor_task(void *arg)
{
sensor_init();
env_data_t data;
while (1) {
if (sensor_read(&data) == ESP_OK) {
xQueueOverwrite(g_data_queue, &data); // 覆盖旧数据(取最新)
ESP_LOGI("SNS", "T=%.1f H=%.1f P=%.1f AQI=%d",
data.temperature, data.humidity, data.pressure, data.aqi_raw);
}
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
/* 显示任务:等待新数据后刷新 E-ink */
static void display_task(void *arg)
{
eink_init();
env_data_t data;
while (1) {
if (xQueuePeek(g_data_queue, &data, pdMS_TO_TICKS(10000))) {
eink_display_env(&data); // 刷新 E-ink 显示,约 1~2 秒
}
}
}
/* MQTT 上报任务:连接成功后定期发布 */
static void mqtt_task(void *arg)
{
xEventGroupWaitBits(g_system_events,
EVT_WIFI_UP | EVT_TIME_SYNC, pdFALSE, pdTRUE, portMAX_DELAY);
mqtt_app_start(g_config.mqtt_uri, g_config.device_id);
env_data_t data;
while (1) {
if (xQueuePeek(g_data_queue, &data, pdMS_TO_TICKS(1000))) {
mqtt_publish_env_data(&data);
}
vTaskDelay(pdMS_TO_TICKS(30000)); // 每 30 秒上报一次
}
}
void app_main(void)
{
/* 基础初始化 */
nvs_flash_init();
storage_load_config(&g_config);
/* 共享资源 */
g_data_queue = xQueueCreate(1, sizeof(env_data_t));
g_system_events = xEventGroupCreate();
/* Wi-Fi + NTP */
wifi_init_sta(g_config.wifi_ssid, g_config.wifi_pass, g_system_events);
sntp_init(g_system_events);
/* 启动任务 */
xTaskCreatePinnedToCore(sensor_task, "sensor", 4096, NULL, 6, NULL, 1);
xTaskCreatePinnedToCore(display_task, "display", 4096, NULL, 4, NULL, 1);
xTaskCreatePinnedToCore(mqtt_task, "mqtt", 8192, NULL, 5, NULL, 0);
/* HTTP 服务器(Web Dashboard + OTA)*/
http_server_start();
}
Web Dashboard(ESP32 做 HTTP 服务器)
ESP32 可以用 esp_http_server 组件在局域网内托管一个简单的 Web Dashboard,手机浏览器直接访问 ESP32 的 IP 即可查看实时数据,无需云端。
#include "esp_http_server.h"
/* GET /api/data → 返回 JSON */
static esp_err_t api_data_handler(httpd_req_t *req)
{
env_data_t data;
xQueuePeek(g_data_queue, &data, 0);
char resp[256];
snprintf(resp, sizeof(resp),
"{\"temperature\":%.1f,\"humidity\":%.1f,"
"\"pressure\":%.1f,\"aqi\":%d,\"battery\":%d}",
data.temperature, data.humidity, data.pressure,
data.aqi_raw, data.battery_mv);
httpd_resp_set_type(req, "application/json");
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
return httpd_resp_sendstr(req, resp);
}
/* GET / → 返回 LittleFS 中的 index.html */
static esp_err_t index_handler(httpd_req_t *req)
{
FILE *f = fopen("/littlefs/index.html", "r");
if (!f) {
httpd_resp_send_404(req);
return ESP_OK;
}
httpd_resp_set_type(req, "text/html");
char buf[512];
size_t n;
while ((n = fread(buf, 1, sizeof(buf), f)) > 0) {
httpd_resp_send_chunk(req, buf, n);
}
fclose(f);
return httpd_resp_send_chunk(req, NULL, 0);
}
void http_server_start(void)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.stack_size = 8192;
httpd_handle_t server = NULL;
httpd_start(&server, &config);
httpd_uri_t uri_index = { .uri = "/", .method = HTTP_GET, .handler = index_handler };
httpd_uri_t uri_data = { .uri = "/api/data", .method = HTTP_GET, .handler = api_data_handler };
httpd_register_uri_handler(server, &uri_index);
httpd_register_uri_handler(server, &uri_data);
}
OTA 自动升级集成
在 HTTP 服务器上增加 OTA 端点,或定时检查 OTA 服务器,实现远程自动升级:
#include "esp_https_ota.h"
/* POST /ota → 触发 OTA 升级 */
static esp_err_t ota_trigger_handler(httpd_req_t *req)
{
char url[256];
int ret = httpd_req_recv(req, url, sizeof(url) - 1);
if (ret <= 0) return ESP_FAIL;
url[ret] = '\0';
esp_http_client_config_t ota_http_cfg = {
.url = url,
.timeout_ms = 30000,
};
esp_https_ota_config_t ota_cfg = { .http_config = &ota_http_cfg };
httpd_resp_sendstr(req, "OTA 开始,设备将重启...");
esp_err_t err = esp_https_ota(&ota_cfg);
if (err == ESP_OK) {
esp_restart();
}
return ESP_OK;
}
电路接线参考
用两个电阻构成分压器(如 100kΩ + 100kΩ),将 3.3~4.2V 的锂电池电压降至 1.65~2.1V,连接到 ADC1 测量(注意用 ADC_ATTEN_DB_6 衰减,量程 0~2.2V)。然后根据电压对照表转换为剩余电量百分比。
项目扩展思路
多节点 Mesh 网络
使用 ESP-MDF(Mesh Development Framework)将多个 ESP32 节点组成自愈合的 Wi-Fi Mesh,扩大监测范围,数据汇聚到根节点上传云端。
Grafana 可视化
MQTT Broker → Telegraf → InfluxDB → Grafana 数据流,实现专业级时序数据可视化与告警,完全开源,可自托管。
Home Assistant 集成
通过 MQTT Discovery 协议自动发现设备,无需编写插件即可接入 Home Assistant 智能家居平台,支持自动化联动。
机器学习异常检测
将长期历史数据用 TensorFlow Lite 或 Edge Impulse 训练异常检测模型,部署到 ESP32-S3 的 AI 加速器,实现本地推理告警。
你已经掌握了 ESP32 从硬件架构到完整 IoT 产品的全链路开发技能:GPIO/ADC/PWM 控制、Wi-Fi 联网与 HTTP/MQTT 通信、BLE 无线传感器、FreeRTOS 多任务、传感器驱动、Flash 存储、低功耗深度睡眠,以及将所有技能整合到完整项目的工程实践。IoT 的世界无比广阔,祝你在物联网领域创造出有价值的产品!