【esp32学习笔记】ESP32基于Arduino框架的多任务系统实现和 loop() 函数的本质(PlatformIO)

问:

ESP32基于Arduino框架在 PlatformIO 的多任务系统,A任务每隔50ms执行一次,B任务每隔100ms执行一次,C任务每隔1000ms执行一次。

 

在ESP32上使用Arduino框架实现多任务系统,可以通过FreeRTOS(已集成在ESP32 Arduino核心中)来实现。以下是实现A、B、C三个任务以不同频率运行的代码示例:

完整代码实现

cpp
 
复制
 
下载
#include <Arduino.h>

// 定义任务句柄
TaskHandle_t TaskAHandle = NULL;
TaskHandle_t TaskBHandle = NULL;
TaskHandle_t TaskCHandle = NULL;

// 任务A:每50ms执行一次
void TaskA(void *pvParameters) {
  const TickType_t xDelay = 50 / portTICK_PERIOD_MS; // 50ms转换为FreeRTOS ticks
  
  for(;;) {
    // 任务A的工作代码
    Serial.println("Task A executed");
    
    vTaskDelay(xDelay); // 延迟50ms
  }
}

// 任务B:每100ms执行一次
void TaskB(void *pvParameters) {
  const TickType_t xDelay = 100 / portTICK_PERIOD_MS; // 100ms转换为FreeRTOS ticks
  
  for(;;) {
    // 任务B的工作代码
    Serial.println("Task B executed");
    
    vTaskDelay(xDelay); // 延迟100ms
  }
}

// 任务C:每1000ms执行一次
void TaskC(void *pvParameters) {
  const TickType_t xDelay = 1000 / portTICK_PERIOD_MS; // 1000ms转换为FreeRTOS ticks
  
  for(;;) {
    // 任务C的工作代码
    Serial.println("Task C executed");
    
    vTaskDelay(xDelay); // 延迟1000ms
  }
}

void setup() {
  Serial.begin(115200);
  
  // 创建任务A
  xTaskCreate(
    TaskA,           // 任务函数
    "TaskA",         // 任务名称
    2048,            // 堆栈大小(字节)
    NULL,            // 任务参数
    1,               // 优先级(0-24,数字越大优先级越高)
    &TaskAHandle     // 任务句柄
  );
  
  // 创建任务B
  xTaskCreate(
    TaskB,
    "TaskB",
    2048,
    NULL,
    1,
    &TaskBHandle
  );
  
  // 创建任务C
  xTaskCreate(
    TaskC,
    "TaskC",
    2048,
    NULL,
    1,
    &TaskCHandle
  );
  
  // 如果不需要在setup中执行其他操作,可以删除以下行
  // 因为FreeRTOS调度器会自动接管
}

void loop() {
  // 在基于FreeRTOS的应用中,loop()通常为空
  // 因为所有工作都在任务中完成
  vTaskDelete(NULL); // 删除loop任务(可选)
}

代码说明

  1. 任务创建

    • 使用xTaskCreate()函数创建三个任务

    • 每个任务有自己的函数、名称、堆栈大小、优先级和句柄

  2. 任务频率控制

    • 使用vTaskDelay()函数控制任务执行频率

    • portTICK_PERIOD_MS是FreeRTOS的tick周期(通常为1ms)

  3. 优先级

    • 所有任务设置为相同优先级(1),这意味着它们将平等分享CPU时间

    • 如果需要,可以调整优先级(0-24,数字越大优先级越高)

  4. 堆栈大小

    • 每个任务分配了2048字节的堆栈,对于简单任务可能过大,可以根据实际需求调整

PlatformIO配置建议

platformio.ini中添加以下配置:

ini
 
复制
 
下载
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

替代方案:使用定时器

如果任务非常简单,也可以使用硬件定时器实现:

cpp
 
复制
 
下载
#include <Arduino.h>
#include <Ticker.h>

Ticker timerA, timerB, timerC;

void taskA() {
  Serial.println("Task A executed");
}

void taskB() {
  Serial.println("Task B executed");
}

void taskC() {
  Serial.println("Task C executed");
}

void setup() {
  Serial.begin(115200);
  
  timerA.attach_ms(50, taskA);
  timerB.attach_ms(100, taskB);
  timerC.attach_ms(1000, taskC);
}

void loop() {
  // 空循环
}

选择哪种方案取决于任务复杂度和实时性要求。FreeRTOS任务更适合复杂的、需要独立堆栈的任务,而定时器适合简单的回调函数。

 

使用FreeRTOS后还会执行loop()函数吗

在使用 FreeRTOS 的 ESP32 Arduino 环境中,loop() 函数仍然会被执行,但它本身也是一个 FreeRTOS 任务(称为 loopTask),其优先级通常较低(默认为 1)。具体行为如下:


1. loop() 的运行机制

  • 在 Arduino-ESP32 框架中,loop() 实际上是在一个 FreeRTOS 任务中运行的,其实现类似于:

    cpp
     
    复制
     
    下载
    void loopTask(void *pvParameters) {
      setup();  // 先执行 setup()
      for (;;) {
        loop();  // 循环执行 loop()
      }
    }
  • 这个任务的优先级通常为 1(与示例代码中的 TaskA/B/C 相同),因此它会与其他同优先级任务平等分享 CPU 时间(通过 FreeRTOS 的时间片轮转调度)。


2. loop() 与其他任务的交互

  • 如果其他任务优先级更高(例如优先级 >1),则高优先级任务会抢占 loop()

  • 如果优先级相同(如示例代码中的任务优先级均为 1),则 loop() 会与其他任务按时间片轮流执行。

  • 如果 loop() 中有阻塞操作(如 delay()),会主动让出 CPU 给其他任务。


3. 是否需要保留 loop()

  • 保留 loop():可以用于低优先级任务(如状态监测、非实时逻辑)。

  • 删除 loop():如果所有功能都已通过 FreeRTOS 任务实现,可以在 setup() 中调用 vTaskDelete(NULL) 删除 loopTask(如示例代码末尾所示),以节省资源。


4. 关键注意事项

  1. 避免在 loop() 中使用阻塞函数
    如 delay(1000) 会阻塞整个 loopTask,改用 vTaskDelay() 以释放 CPU。

    cpp
     
    复制
     
    下载
    void loop() {
      // 错误:阻塞整个 loopTask
      // delay(1000);
      
      // 正确:释放 CPU 给其他任务
      vTaskDelay(pdMS_TO_TICKS(1000));
    }
  2. 优先级冲突
    如果 loop() 和其他任务优先级相同,且 loop() 中有大量计算,可能导致其他任务无法及时执行。此时可以:

    • 降低 loop() 的优先级(需修改 Arduino-ESP32 底层配置)。

    • 将 loop() 中的逻辑移到独立任务中。

  3. 默认 loop() 的堆栈大小
    Arduino-ESP32 中 loopTask 的默认堆栈较小(约 4KB),复杂操作可能导致栈溢出。可通过修改 menuconfig 调整(PlatformIO 中需自定义 sdkconfig)。


5. 如何彻底禁用 loop()

如果需要完全移除 loop(),可以在 setup() 中删除 loopTask

cpp
 
复制
 
下载
void setup() {
  // 创建其他任务...
  vTaskDelete(NULL); // 删除当前任务(即 loopTask)
}

void loop() {
  // 不会执行到这里
}

总结

场景 loop() 的行为
不主动干预 作为优先级=1 的任务与其他任务并发运行
loop() 中有阻塞操作 使用 delay() 会冻结任务,改用 vTaskDelay()
删除 loopTask 调用 vTaskDelete(NULL) 可释放资源

建议在复杂的多任务系统中,将逻辑拆分到独立的 FreeRTOS 任务中,并减少 loop() 的使用,以提高可预测性和实时性。

posted @ 2025-05-16 08:31  FBshark  阅读(542)  评论(0)    收藏  举报