Skip to content

第十一章:综合项目实战

项目:IoT 环境监测节点

功能概述

硬件:ESP32-S3-N16R8 开发板
      + DHT22 温湿度传感器(GPIO4)
      + SSD1306 OLED 显示屏(I2C,GPIO21/22)
      + 状态 LED(GPIO2)
      + 按键(GPIO0)

功能:
  1. 每 5 秒采集温湿度数据
  2. OLED 实时显示数据
  3. 通过 Wi-Fi 上报到 MQTT Broker
  4. 支持 BLE 配网(首次使用)
  5. 数据存储到 SPIFFS(断网时本地缓存)
  6. 按键切换 OLED 显示页面
  7. 支持 OTA 固件升级

系统架构

┌─────────────────────────────────────────────────────┐
│                    app_main                          │
│                       │                             │
│         ┌─────────────┼─────────────┐               │
│         ▼             ▼             ▼               │
│   sensor_task    display_task   network_task         │
│   (Core 0)       (Core 0)       (Core 1)             │
│       │               │             │               │
│       └───────────────┴─────────────┘               │
│                       │                             │
│              sensor_data_queue                      │
│              (任务间通信)                            │
└─────────────────────────────────────────────────────┘

11.1 项目结构

iot_monitor/
├── CMakeLists.txt
├── partitions.csv          # 自定义分区表
├── sdkconfig.defaults      # 默认配置
├── main/
│   ├── CMakeLists.txt
│   ├── main.c              # 入口,初始化和任务创建
│   ├── app_config.h        # 全局配置宏
│   └── app_config.c        # 配置管理(NVS)
├── components/
│   ├── sensor/
│   │   ├── sensor.h
│   │   └── sensor.c        # DHT22 驱动
│   ├── display/
│   │   ├── display.h
│   │   └── display.c       # OLED 显示
│   ├── network/
│   │   ├── wifi_manager.h
│   │   ├── wifi_manager.c  # Wi-Fi 连接管理
│   │   ├── mqtt_client.h
│   │   └── mqtt_client.c   # MQTT 上报
│   └── ble_prov/
│       ├── ble_prov.h
│       └── ble_prov.c      # BLE 配网

11.2 核心代码

app_config.h

c
#pragma once

// 版本信息
#define APP_VERSION         "1.0.0"
#define APP_NAME            "IoT Monitor"

// 硬件引脚
#define PIN_DHT22           GPIO_NUM_4
#define PIN_LED_STATUS      GPIO_NUM_2
#define PIN_BTN_MODE        GPIO_NUM_0
#define PIN_OLED_SDA        GPIO_NUM_21
#define PIN_OLED_SCL        GPIO_NUM_22

// 采集间隔
#define SENSOR_INTERVAL_MS  5000

// MQTT
#define MQTT_BROKER_URI     "mqtt://broker.emqx.io:1883"
#define MQTT_TOPIC_DATA     "iot/monitor/%s/data"
#define MQTT_TOPIC_CMD      "iot/monitor/%s/cmd"

// NVS 命名空间
#define NVS_NS_CONFIG       "app_cfg"
#define NVS_KEY_WIFI_SSID   "wifi_ssid"
#define NVS_KEY_WIFI_PASS   "wifi_pass"
#define NVS_KEY_DEVICE_ID   "device_id"
#define NVS_KEY_BOOT_CNT    "boot_cnt"

// 事件位
#define EVT_WIFI_READY      BIT0
#define EVT_MQTT_READY      BIT1
#define EVT_SENSOR_OK       BIT2
#define EVT_BLE_PROV_DONE   BIT3

main.c

c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_mac.h"

#include "app_config.h"
#include "sensor.h"
#include "display.h"
#include "wifi_manager.h"
#include "mqtt_client_app.h"

static const char *TAG = "MAIN";

// 全局句柄
QueueHandle_t     g_sensor_queue;
EventGroupHandle_t g_app_events;
char              g_device_id[16];

// 传感器数据结构
typedef struct {
    float    temperature;
    float    humidity;
    int64_t  timestamp_ms;
    bool     valid;
} sensor_data_t;

// 传感器采集任务
static void sensor_task(void *arg)
{
    sensor_data_t data;
    ESP_LOGI(TAG, "传感器任务启动");

    while (1) {
        data.timestamp_ms = esp_timer_get_time() / 1000;
        data.valid = sensor_read(&data.temperature, &data.humidity);

        if (data.valid) {
            ESP_LOGI(TAG, "温度: %.1f°C  湿度: %.1f%%",
                     data.temperature, data.humidity);
            xQueueOverwrite(g_sensor_queue, &data);  // 覆盖旧数据
            xEventGroupSetBits(g_app_events, EVT_SENSOR_OK);
        } else {
            ESP_LOGW(TAG, "传感器读取失败");
        }

        vTaskDelay(pdMS_TO_TICKS(SENSOR_INTERVAL_MS));
    }
}

// 显示任务
static void display_task(void *arg)
{
    sensor_data_t data = {0};
    uint8_t page = 0;
    uint32_t boot_count = 0;

    nvs_read_int(NVS_NS_CONFIG, NVS_KEY_BOOT_CNT,
                 (int32_t *)&boot_count, 0);

    display_init();
    display_show_boot(APP_NAME, APP_VERSION, boot_count);
    vTaskDelay(pdMS_TO_TICKS(2000));

    while (1) {
        // 非阻塞读取最新传感器数据
        xQueuePeek(g_sensor_queue, &data, 0);

        switch (page) {
            case 0:
                display_show_sensor(data.temperature, data.humidity);
                break;
            case 1:
                display_show_network(wifi_get_ip(), wifi_is_connected());
                break;
            case 2:
                display_show_system(esp_get_free_heap_size(),
                                    esp_timer_get_time() / 1000000);
                break;
        }

        // 检测按键切换页面
        if (button_pressed()) {
            page = (page + 1) % 3;
        }

        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

// 网络任务
static void network_task(void *arg)
{
    sensor_data_t data;
    char topic[64];
    char payload[128];

    ESP_LOGI(TAG, "网络任务启动,等待 Wi-Fi...");

    // 等待 Wi-Fi 连接
    xEventGroupWaitBits(g_app_events, EVT_WIFI_READY,
                        pdFALSE, pdTRUE, portMAX_DELAY);

    // 初始化 MQTT
    mqtt_init(MQTT_BROKER_URI, g_device_id);
    xEventGroupWaitBits(g_app_events, EVT_MQTT_READY,
                        pdFALSE, pdTRUE, pdMS_TO_TICKS(10000));

    snprintf(topic, sizeof(topic), MQTT_TOPIC_DATA, g_device_id);

    while (1) {
        // 等待新的传感器数据
        if (xQueueReceive(g_sensor_queue, &data, pdMS_TO_TICKS(10000))) {
            if (data.valid && mqtt_is_connected()) {
                snprintf(payload, sizeof(payload),
                    "{\"device\":\"%s\",\"temp\":%.1f,"
                    "\"humi\":%.1f,\"ts\":%lld}",
                    g_device_id, data.temperature,
                    data.humidity, data.timestamp_ms);

                mqtt_publish(topic, payload, 1);
                ESP_LOGI(TAG, "数据已上报: %s", payload);
            }
        }
    }
}

void app_main(void)
{
    // 1. 基础初始化
    ESP_ERROR_CHECK(nvs_flash_init());

    // 2. 生成设备 ID(基于 MAC 地址)
    uint8_t mac[6];
    esp_read_mac(mac, ESP_MAC_WIFI_STA);
    snprintf(g_device_id, sizeof(g_device_id),
             "%02X%02X%02X%02X", mac[2], mac[3], mac[4], mac[5]);
    ESP_LOGI(TAG, "设备 ID: %s", g_device_id);

    // 3. 更新开机计数
    int32_t boot_count = 0;
    nvs_read_int(NVS_NS_CONFIG, NVS_KEY_BOOT_CNT, &boot_count, 0);
    boot_count++;
    nvs_write_int(NVS_NS_CONFIG, NVS_KEY_BOOT_CNT, boot_count);
    ESP_LOGI(TAG, "第 %"PRId32" 次开机", boot_count);

    // 4. 创建全局对象
    g_sensor_queue = xQueueCreate(1, sizeof(sensor_data_t));
    g_app_events   = xEventGroupCreate();

    // 5. 初始化传感器
    sensor_init(PIN_DHT22);

    // 6. 检查 Wi-Fi 配置
    char ssid[32] = {0}, pass[64] = {0};
    nvs_read_str(NVS_NS_CONFIG, NVS_KEY_WIFI_SSID, ssid, sizeof(ssid));

    if (strlen(ssid) == 0) {
        // 无配置,启动 BLE 配网
        ESP_LOGI(TAG, "无 Wi-Fi 配置,启动 BLE 配网...");
        ble_prov_start(g_device_id, g_app_events);
        xEventGroupWaitBits(g_app_events, EVT_BLE_PROV_DONE,
                            pdFALSE, pdTRUE, portMAX_DELAY);
        nvs_read_str(NVS_NS_CONFIG, NVS_KEY_WIFI_SSID, ssid, sizeof(ssid));
        nvs_read_str(NVS_NS_CONFIG, NVS_KEY_WIFI_PASS, pass, sizeof(pass));
    } else {
        nvs_read_str(NVS_NS_CONFIG, NVS_KEY_WIFI_PASS, pass, sizeof(pass));
    }

    // 7. 连接 Wi-Fi
    wifi_init();
    if (wifi_connect(ssid, pass) == ESP_OK) {
        xEventGroupSetBits(g_app_events, EVT_WIFI_READY);
    }

    // 8. 创建任务
    xTaskCreatePinnedToCore(sensor_task,  "sensor",  4096, NULL, 10, NULL, 0);
    xTaskCreatePinnedToCore(display_task, "display", 4096, NULL, 5,  NULL, 0);
    xTaskCreatePinnedToCore(network_task, "network", 8192, NULL, 8,  NULL, 1);

    ESP_LOGI(TAG, "系统初始化完成,所有任务已启动");
}

11.3 项目扩展方向

扩展1:数据可视化

ESP32-S3 → MQTT → Node-RED → Grafana 仪表盘
                           → 手机 App(MQTT Dashboard)

扩展2:低功耗设计

c
// 深度睡眠(Deep Sleep):最低功耗
#include "esp_sleep.h"

// 配置唤醒源(定时器唤醒)
esp_sleep_enable_timer_wakeup(60 * 1000000ULL);  // 60 秒

// 保存数据到 RTC 内存(深度睡眠后保留)
RTC_DATA_ATTR int boot_count = 0;

// 进入深度睡眠
esp_deep_sleep_start();
// 唤醒后从 app_main 重新开始

扩展3:本地 Web 界面

ESP32-S3 运行 HTTP 服务器
手机浏览器访问 http://192.168.x.x
  → 实时数据图表(WebSocket 推送)
  → 配置页面
  → 历史数据查询

📝 综合项目练习

项目 A:智能温控器(入门级)

功能

  • 读取温度传感器
  • 设定目标温度(按键调节)
  • 温度超过阈值:继电器控制风扇/加热器
  • OLED 显示当前温度和目标温度
  • 通过 UART 命令修改参数

涉及知识:GPIO + ADC/I2C + UART + NVS


项目 B:Wi-Fi 时钟(中级)

功能

  • 连接 Wi-Fi 后通过 NTP 同步时间
  • OLED 显示时间、日期、星期
  • 可设置闹钟(NVS 保存)
  • 闹钟触发时 LED 闪烁 + 蜂鸣器响

涉及知识:Wi-Fi + SNTP + OLED + NVS + PWM


项目 C:BLE 遥控小车(中级)

功能

  • 手机 App 通过 BLE 发送方向命令
  • ESP32-S3 控制 4 个电机(L298N 驱动)
  • PWM 控制速度
  • 超声波传感器避障
  • 电池电量监测(ADC)

涉及知识:BLE + GPIO + PWM + ADC + I2C


项目 D:IoT 数据采集平台(高级)

功能

  • 多传感器采集(温湿度、光照、CO2)
  • Wi-Fi 上报到云平台(阿里云 IoT / AWS IoT)
  • 本地 SPIFFS 缓存(断网时存储,联网后补传)
  • OTA 远程升级
  • BLE 配网
  • Web 配置界面

涉及知识:全部章节综合


学习路线回顾

第1章  环境搭建    → 能编译烧录程序
第2章  GPIO        → 能控制 LED 和读取按键
第3章  中断/定时器 → 能响应实时事件
第4章  UART        → 能与外部设备串口通信
第5章  I2C/SPI     → 能驱动传感器和显示屏
第6章  ADC         → 能采集模拟信号
第7章  FreeRTOS    → 能编写多任务程序
第8章  存储        → 能持久化保存数据
第9章  Wi-Fi       → 能联网上报数据
第10章 BLE         → 能与手机蓝牙通信
第11章 综合项目    → 能完成完整 IoT 产品

推荐学习资源

资源链接
ESP-IDF 官方文档https://docs.espressif.com/projects/esp-idf/zh_CN/
ESP32-S3 技术参考手册https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_cn.pdf
ESP-IDF GitHubhttps://github.com/espressif/esp-idf
乐鑫官方示例https://github.com/espressif/esp-idf/tree/master/examples
FreeRTOS 文档https://www.freertos.org/Documentation/RTOS_book.html
nRF Connect(BLE 调试)手机应用商店搜索
MQTT Explorerhttps://mqtt-explorer.com/

个人知识库