首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >嵌入式物联网实战:基于 FreeRTOS 与 ESP32 的实时传感数据采集与 MQTT 上云

嵌入式物联网实战:基于 FreeRTOS 与 ESP32 的实时传感数据采集与 MQTT 上云

原创
作者头像
用户12687280
发布2026-08-20 14:09:04
发布2026-08-20 14:09:04
1370
举报

嵌入式物联网工程师的核心挑战在于资源受限下的实时性与可靠性。本文不赘述 Arduino 式简单循环,而是深入底层,基于 ESP32 双核处理器与 FreeRTOS 实时操作系统,构建一个多任务协作的传感数据采集系统,并通过 MQTT 协议将数据稳定上传至云端。全程涉及任务调度、互斥锁、队列通信、低功耗策略及网络重连机制,代码完整可运行。


1. 系统架构与任务划分

选用 ESP32-WROOM 模块,搭载 FreeRTOS(ESP-IDF 默认 RTOS)。系统分为四个独立任务:

任务名称

优先级

功能

周期/触发

Sensor Task

3 (高)

读取 DHT22 温湿度传感器,经队列发送数据

每 2 秒

Processing Task

2 (中)

对原始数据进行滤波(移动平均),格式化 JSON

队列有数据时

MQTT Task

1 (低)

连接 MQTT Broker,发布 JSON 数据

每 5 秒批量发送

LED Task

4 (最高)

心跳指示(闪烁)与网络状态指示

每 500ms 切换

任务间通信通过 FreeRTOS 队列(xQueue)信号量(xSemaphore) 实现,避免共享全局变量带来的竞态条件。


2. 传感器采集与任务创建

使用 ESP-IDF 的 DHT22 驱动(需自行移植或使用第三方库)。Sensor Task 在堆栈中分配局部变量,减少内存碎片。

代码语言:javascript
复制
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "dht22.h"   // 假设已有驱动

#define DHT_PIN  GPIO_NUM_4
#define QUEUE_LEN 10

typedef struct {
    float temperature;
    float humidity;
    uint64_t timestamp;  // 微秒级时间戳
} sensor_data_t;

QueueHandle_t sensor_queue;

void sensor_task(void *pvParameters) {
    dht22_t sensor;
    dht22_init(&sensor, DHT_PIN);
    TickType_t last_wake = xTaskGetTickCount();
    const TickType_t period = pdMS_TO_TICKS(2000);

    while (1) {
        sensor_data_t data;
        if (dht22_read(&sensor, &data.temperature, &data.humidity) == ESP_OK) {
            data.timestamp = esp_timer_get_time();
            // 向队列发送,若队列满则等待 100ms(避免阻塞)
            if (xQueueSend(sensor_queue, &data, pdMS_TO_TICKS(100)) != pdPASS) {
                ESP_LOGW("SENSOR", "Queue full, dropping data");
            }
        } else {
            ESP_LOGE("SENSOR", "Read failed");
        }
        vTaskDelayUntil(&last_wake, period);
    }
}

3. 数据处理与队列通信

Processing Task 从队列中取出原始数据,进行简单的移动平均滤波(窗口为 3),并封装为 JSON 字符串(使用 cJSON 库)。

代码语言:javascript
复制
#include "cJSON.h"

void processing_task(void *pvParameters) {
    sensor_data_t raw_buf[3] = {0};
    int idx = 0;
    char json_buf[256];

    while (1) {
        sensor_data_t new_data;
        if (xQueueReceive(sensor_queue, &new_data, portMAX_DELAY) == pdPASS) {
            raw_buf[idx % 3] = new_data;
            idx++;
            if (idx < 3) continue;  // 积累足够样本

            // 计算平均
            float avg_temp = 0, avg_humi = 0;
            for (int i = 0; i < 3; i++) {
                avg_temp += raw_buf[i].temperature;
                avg_humi += raw_buf[i].humidity;
            }
            avg_temp /= 3;
            avg_humi /= 3;

            // 构建 JSON
            cJSON *root = cJSON_CreateObject();
            cJSON_AddNumberToObject(root, "temp", avg_temp);
            cJSON_AddNumberToObject(root, "humi", avg_humi);
            cJSON_AddNumberToObject(root, "ts", new_data.timestamp);
            char *json_str = cJSON_PrintUnformatted(root);
            strncpy(json_buf, json_str, sizeof(json_buf) - 1);
            free(json_str);
            cJSON_Delete(root);

            // 发送到 MQTT 任务(通过另一个队列或直接全局变量,但需加互斥锁)
            // 此处采用全局缓冲区 + 互斥锁(更简单)
            // 实际可使用 xQueueOverwrite 等
        }
    }
}

为避免两个任务竞争 json_buf,我们定义一个互斥锁并封装为结构体。但为了展示 FreeRTOS 同步原语,改为使用 消息缓冲区(Message Buffer)流缓冲区,这里简化为二值信号量保护共享区域。


4. MQTT 连接与发布(含自动重连)

使用 ESP-MQTT 组件(基于 lwip)。MQTT Task 每 5 秒检查一次最新数据并发布,同时处理掉线重连。

代码语言:javascript
复制
#include "mqtt_client.h"

esp_mqtt_client_handle_t mqtt_client;
SemaphoreHandle_t data_ready_mutex;
char shared_json[256];

void mqtt_task(void *pvParameters) {
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = "mqtt://your_broker_ip:1883",
        .credentials.username = "device001",
        .credentials.authentication.password = "pass123"
    };
    mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
    esp_mqtt_client_start(mqtt_client);

    TickType_t last_pub = xTaskGetTickCount();
    while (1) {
        // 尝试获取数据锁并复制 JSON
        if (xSemaphoreTake(data_ready_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
            char local_json[256];
            strcpy(local_json, shared_json);
            xSemaphoreGive(data_ready_mutex);
            // 发布
            int msg_id = esp_mqtt_client_publish(mqtt_client, "/dev/sensor", local_json, 0, 0, 0);
            if (msg_id == -1) {
                ESP_LOGE("MQTT", "Publish failed, will retry");
            }
        }
        vTaskDelayUntil(&last_pub, pdMS_TO_TICKS(5000));
    }
}

同时,需要注册 MQTT 事件回调,处理连接断开事件并触发重连(ESP-MQTT 自带重连机制,但可自定义)。


5. 低功耗优化与看门狗

物联网设备常电池供电,需进入轻睡眠(Light Sleep)模式。在空闲时间(所有任务阻塞等待时)自动进入睡眠,由定时器唤醒。在 ESP-IDF 中启用 CONFIG_FREERTOS_IDLE_TASK_HOOK,在空闲钩子中调用 esp_light_sleep_start()。但注意 MQTT 维持长连接时需保持 Wi-Fi 唤醒,可配置 DTIM 间隔实现周期性接收。

此外,添加独立看门狗(IWDG)防止任务死锁:

代码语言:javascript
复制
#include "esp_task_wdt.h"
// 在初始化时启用,超时 10 秒
esp_task_wdt_init(10, true);
// 将每个任务加入看门狗
esp_task_wdt_add(NULL);  // 当前任务

在每轮循环中重置看门狗(esp_task_wdt_reset()),确保任务未饿死。


6. 启动流程与完整性检查

app_main() 中依次初始化硬件、创建队列、信号量、启动任务并注册看门狗:

代码语言:javascript
复制
void app_main(void) {
    sensor_queue = xQueueCreate(QUEUE_LEN, sizeof(sensor_data_t));
    data_ready_mutex = xSemaphoreCreateMutex();
    xTaskCreatePinnedToCore(sensor_task, "sensor", 4096, NULL, 3, NULL, 1);
    xTaskCreatePinnedToCore(processing_task, "process", 8192, NULL, 2, NULL, 0);
    xTaskCreatePinnedToCore(mqtt_task, "mqtt", 6144, NULL, 1, NULL, 1);
    // LED 任务略
    ESP_LOGI("MAIN", "All tasks started");
}

将传感器任务和 MQTT 任务分配在不同核心,实现并行处理。


7. 调试与性能分析

使用 esp_timer 记录各任务执行时长,通过 vTaskGetRunTimeStats() 获取 CPU 占用率,优化堆栈大小。在串口日志中监控队列使用率(uxQueueSpacesAvailable),当队列接近满时提高 Processing Task 优先级。


结语

本文构建了一个具备多任务协作、实时传感、MQTT 云上传和低功耗意识的完整嵌入式物联网系统,核心代码均基于 FreeRTOS 原生 API,不依赖高级封装。实际产品中还可加入 OTA 固件升级、安全 TLS 连接和本地数据缓存,但基础框架已覆盖绝大多数工业场景。嵌入式工程师的价值在于对系统资源的精准把控和对异常情况的稳健处理,希望这篇实战能够为你提供扎实的参考,助力从原型走向量产。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 嵌入式物联网工程师的核心挑战在于资源受限下的实时性与可靠性。本文不赘述 Arduino 式简单循环,而是深入底层,基于 ESP32 双核处理器与 FreeRTOS 实时操作系统,构建一个多任务协作的传感数据采集系统,并通过 MQTT 协议将数据稳定上传至云端。全程涉及任务调度、互斥锁、队列通信、低功耗策略及网络重连机制,代码完整可运行。
    • 1. 系统架构与任务划分
    • 2. 传感器采集与任务创建
    • 3. 数据处理与队列通信
    • 4. MQTT 连接与发布(含自动重连)
    • 5. 低功耗优化与看门狗
    • 6. 启动流程与完整性检查
    • 7. 调试与性能分析
    • 结语
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档