【esp32学习笔记】ESP32基于Arduino框架的多任务系统实现和 loop() 函数的本质(PlatformIO)
问:
ESP32基于Arduino框架在 PlatformIO 的多任务系统,A任务每隔50ms执行一次,B任务每隔100ms执行一次,C任务每隔1000ms执行一次。
在ESP32上使用Arduino框架实现多任务系统,可以通过FreeRTOS(已集成在ESP32 Arduino核心中)来实现。以下是实现A、B、C三个任务以不同频率运行的代码示例:
完整代码实现
#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任务(可选)
}
代码说明
- 
任务创建:
- 
使用
xTaskCreate()函数创建三个任务 - 
每个任务有自己的函数、名称、堆栈大小、优先级和句柄
 
 - 
 - 
任务频率控制:
- 
使用
vTaskDelay()函数控制任务执行频率 - 
portTICK_PERIOD_MS是FreeRTOS的tick周期(通常为1ms) 
 - 
 - 
优先级:
- 
所有任务设置为相同优先级(1),这意味着它们将平等分享CPU时间
 - 
如果需要,可以调整优先级(0-24,数字越大优先级越高)
 
 - 
 - 
堆栈大小:
- 
每个任务分配了2048字节的堆栈,对于简单任务可能过大,可以根据实际需求调整
 
 - 
 
PlatformIO配置建议
在platformio.ini中添加以下配置:
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
替代方案:使用定时器
如果任务非常简单,也可以使用硬件定时器实现:
#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 任务中运行的,其实现类似于: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. 关键注意事项
- 
避免在
loop()中使用阻塞函数:
如delay(1000)会阻塞整个loopTask,改用vTaskDelay()以释放 CPU。void loop() { // 错误:阻塞整个 loopTask // delay(1000); // 正确:释放 CPU 给其他任务 vTaskDelay(pdMS_TO_TICKS(1000)); } - 
优先级冲突:
如果loop()和其他任务优先级相同,且loop()中有大量计算,可能导致其他任务无法及时执行。此时可以:- 
降低
loop()的优先级(需修改 Arduino-ESP32 底层配置)。 - 
将
loop()中的逻辑移到独立任务中。 
 - 
 - 
默认
loop()的堆栈大小:
Arduino-ESP32 中loopTask的默认堆栈较小(约 4KB),复杂操作可能导致栈溢出。可通过修改menuconfig调整(PlatformIO 中需自定义sdkconfig)。 
5. 如何彻底禁用 loop()?
如果需要完全移除 loop(),可以在 setup() 中删除 loopTask:
void setup() {
  // 创建其他任务...
  vTaskDelete(NULL); // 删除当前任务(即 loopTask)
}
void loop() {
  // 不会执行到这里
}
总结
| 场景 | loop() 的行为 | 
|---|---|
| 不主动干预 | 作为优先级=1 的任务与其他任务并发运行 | 
loop() 中有阻塞操作 | 
使用 delay() 会冻结任务,改用 vTaskDelay() | 
删除 loopTask | 
调用 vTaskDelete(NULL) 可释放资源 | 
建议在复杂的多任务系统中,将逻辑拆分到独立的 FreeRTOS 任务中,并减少 loop() 的使用,以提高可预测性和实时性。
                    
                
                
            
        
浙公网安备 33010602011771号