第六章:6.1 ESP32教学:多任务处理与FreeRTOS实战

一、FreeRTOS简介

ESP32内置了FreeRTOS实时操作系统内核,这是一个专为嵌入式系统设计的开源实时操作系统。它支持:

  • 多任务并行处理

  • 任务优先级管理

  • 内存管理

  • 任务间通信

  • 定时器管理

二、任务创建与管理

1. 任务创建(xTaskCreate)

BaseType_t xTaskCreate(
    TaskFunction_t pvTaskCode,    // 任务函数指针
    const char * const pcName,    // 任务名称
    configSTACK_DEPTH_TYPE usStackDepth, // 堆栈大小
    void *pvParameters,          // 任务参数
    UBaseType_t uxPriority,      // 任务优先级
    TaskHandle_t *pxCreatedTask  // 任务句柄指针
);
void setup() {
  Serial.begin(115200);
  
  // 创建两个任务
  xTaskCreate(
    task1,          // 任务函数
    "Task1",         // 任务名称
    2048,            // 堆栈大小(字节)
    NULL,            // 任务参数
    1,               // 优先级(0-24)
    NULL             // 任务句柄
  );

  xTaskCreate(task2, "Task2", 2048, NULL, 2, NULL);
}

void task1(void * parameter) {
  while(1) {
    Serial.println("Task1正在运行");
    vTaskDelay(1000 / portTICK_PERIOD_MS);  // 延时1秒
  }
}

void task2(void * parameter) {
  while(1) {
    Serial.println("Task2正在执行高优先级任务");
    vTaskDelay(500 / portTICK_PERIOD_MS);  // 延时500ms
  }
}

void loop() {}  // Arduino主循环留空

2. 关键函数解析

  • vTaskDelay():非阻塞延时
    
    void vTaskDelay(const TickType_t xTicksToDelay);
    参数使用pdMS_TO_TICKS()宏转换时间,例如:
    
    vTaskDelay(pdMS_TO_TICKS(100));  // 精确延时100ms
    vTaskDelete():删除任务
    
    void vTaskDelete(TaskHandle_t xTaskToDelete);
    
    	

三、任务间通信

1. 队列(Queue)

队列是先进先出(FIFO)的数据结构,用于任务间安全传递数据。

创建队列:

QueueHandle_t xQueueCreate(
    UBaseType_t uxQueueLength,  // 队列长度
    UBaseType_t uxItemSize      // 单个元素大小(字节)
);

完整示例:

QueueHandle_t dataQueue;

void setup() {
  Serial.begin(115200);
  
  // 创建能存储10个int的队列
  dataQueue = xQueueCreate(10, sizeof(int));
  
  xTaskCreate(producerTask, "Producer", 2048, NULL, 1, NULL);
  xTaskCreate(consumerTask, "Consumer", 2048, NULL, 2, NULL);
}

// 生产者任务
void producerTask(void * parameter) {
  int counter = 0;
  while(1) {
    xQueueSend(dataQueue, &counter, portMAX_DELAY);
    Serial.printf("生产数据:%d\n", counter);
    counter++;
    vTaskDelay(800 / portTICK_PERIOD_MS);
  }
}

// 消费者任务
void consumerTask(void * parameter) {
  int receivedData;
  while(1) {
    if(xQueueReceive(dataQueue, &receivedData, 1000 / portTICK_PERIOD_MS)){
      Serial.printf("消费数据:%d\n", receivedData);
    }
  }
}

void loop() {}

2. 信号量(Semaphore)

用于任务同步和资源共享控制

二进制信号量示例:

SemaphoreHandle_t binSemaphore;

void setup() {
  Serial.begin(115200);
  
  binSemaphore = xSemaphoreCreateBinary();
  xSemaphoreGive(binSemaphore);  // 初始可用
  
  xTaskCreate(taskA, "TaskA", 2048, NULL, 2, NULL);
  xTaskCreate(taskB, "TaskB", 2048, NULL, 1, NULL);
}

void taskA(void * parameter) {
  while(1) {
    if(xSemaphoreTake(binSemaphore, portMAX_DELAY)){
      Serial.println("TaskA获得信号量");
      vTaskDelay(1000 / portTICK_PERIOD_MS);
      xSemaphoreGive(binSemaphore);
    }
  }
}

void taskB(void * parameter) {
  while(1) {
    if(xSemaphoreTake(binSemaphore, portMAX_DELAY)){
      Serial.println("TaskB获得信号量");
      vTaskDelay(500 / portTICK_PERIOD_MS);
      xSemaphoreGive(binSemaphore);
    }
  }
}

void loop() {}

四、最佳实践建议

  1. 堆栈大小设置原则:

    • 简单任务:1-2KB

    • 复杂任务(涉及字符串处理):4KB+

    • 使用uxTaskGetStackHighWaterMark()监控堆栈使用

  2. 优先级管理

    • 0(最低)~24(最高)

    • 避免"优先级反转"问题

  3. 队列使用技巧

    • 大数据建议传递指针

    • 使用xQueueSendToFront()实现紧急消息插队

    • 超时设置避免死锁

五、常见问题排查

  1. 任务无法启动

    • 检查堆栈是否过小

    • 确认任务函数为无限循环

  2. 队列数据丢失

    • 增加队列长度

    • 检查生产者速度是否过快

  3. 系统崩溃

    • 使用ESP32异常解码工具

    • 检查内存越界访问

六、综合应用案例

实现温湿度传感器数据采集与网络上传的协同工作:

#include <WiFi.h>
#include <DHT.h>

#define DHTPIN 4
#define DHTTYPE DHT22

DHT dht(DHTPIN, DHTTYPE);
QueueHandle_t sensorQueue;
SemaphoreHandle_t wifiSemaphore;

void setup() {
  Serial.begin(115200);
  dht.begin();
  
  sensorQueue = xQueueCreate(5, sizeof(float[2]));
  wifiSemaphore = xSemaphoreCreateBinary();
  
  xTaskCreate(readSensorTask, "Sensor", 4096, NULL, 2, NULL);
  xTaskCreate(uploadTask, "Upload", 4096, NULL, 1, NULL);
  
  WiFi.begin("SSID", "password");
  while(WiFi.status() != WL_CONNECTED){
    vTaskDelay(500 / portTICK_PERIOD_MS);
  }
  xSemaphoreGive(wifiSemaphore);
}

void readSensorTask(void * parameter) {
  float data[2];
  while(1) {
    data[0] = dht.readTemperature();
    data[1] = dht.readHumidity();
    
    if(!isnan(data[0]) && !isnan(data[1])){
      xQueueSend(sensorQueue, data, 0);
    }
    vTaskDelay(2000 / portTICK_PERIOD_MS);
  }
}

void uploadTask(void * parameter) {
  float receivedData[2];
  while(1) {
    if(xSemaphoreTake(wifiSemaphore, 1000 / portTICK_PERIOD_MS)){
      if(xQueueReceive(sensorQueue, receivedData, 0)){
        // 模拟网络上传
        Serial.printf("上传数据:温度%.1f℃ 湿度%.1f%%\n", 
                     receivedData[0], receivedData[1]);
      }
      xSemaphoreGive(wifiSemaphore);
    }
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void loop() {}

总结

通过合理使用FreeRTOS的多任务功能,可以充分发挥ESP32的双核优势。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙大大L

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值