选用 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) 实现,避免共享全局变量带来的竞态条件。
使用 ESP-IDF 的 DHT22 驱动(需自行移植或使用第三方库)。Sensor Task 在堆栈中分配局部变量,减少内存碎片。
#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);
}
}Processing Task 从队列中取出原始数据,进行简单的移动平均滤波(窗口为 3),并封装为 JSON 字符串(使用 cJSON 库)。
#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) 或 流缓冲区,这里简化为二值信号量保护共享区域。
使用 ESP-MQTT 组件(基于 lwip)。MQTT Task 每 5 秒检查一次最新数据并发布,同时处理掉线重连。
#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 自带重连机制,但可自定义)。
物联网设备常电池供电,需进入轻睡眠(Light Sleep)模式。在空闲时间(所有任务阻塞等待时)自动进入睡眠,由定时器唤醒。在 ESP-IDF 中启用 CONFIG_FREERTOS_IDLE_TASK_HOOK,在空闲钩子中调用 esp_light_sleep_start()。但注意 MQTT 维持长连接时需保持 Wi-Fi 唤醒,可配置 DTIM 间隔实现周期性接收。
此外,添加独立看门狗(IWDG)防止任务死锁:
#include "esp_task_wdt.h"
// 在初始化时启用,超时 10 秒
esp_task_wdt_init(10, true);
// 将每个任务加入看门狗
esp_task_wdt_add(NULL); // 当前任务在每轮循环中重置看门狗(esp_task_wdt_reset()),确保任务未饿死。
在 app_main() 中依次初始化硬件、创建队列、信号量、启动任务并注册看门狗:
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 任务分配在不同核心,实现并行处理。
使用 esp_timer 记录各任务执行时长,通过 vTaskGetRunTimeStats() 获取 CPU 占用率,优化堆栈大小。在串口日志中监控队列使用率(uxQueueSpacesAvailable),当队列接近满时提高 Processing Task 优先级。
本文构建了一个具备多任务协作、实时传感、MQTT 云上传和低功耗意识的完整嵌入式物联网系统,核心代码均基于 FreeRTOS 原生 API,不依赖高级封装。实际产品中还可加入 OTA 固件升级、安全 TLS 连接和本地数据缓存,但基础框架已覆盖绝大多数工业场景。嵌入式工程师的价值在于对系统资源的精准把控和对异常情况的稳健处理,希望这篇实战能够为你提供扎实的参考,助力从原型走向量产。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。