ESP32 使用FreeRTOS的多线程

凌顺实验室(lingshunlab.com)这次主要简单介绍ESP32如何使用FreeRTOS的多线程。

什么是多线程?

是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

ESP32中S和S3系列是双核,也就是一个CPU在工作的时候同时运行另一个CPU。ESP32的Arduino core在使用多任务的前提下具备Arduino功能。Arduino 核心程序是使用称为 FreeRTOS 的实时操作系统创建的。

ESP32 FreeRTOS 文档
https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/api-reference/system/freertos.html

最简单的多线程例子

TaskHandle_t th_p[1];  // 任务句柄,对xTaskCreate的调用返回。可用作参数到vTaskDelete以删除任务。

long a_Task = 0;
long b_Task = 0;

void Core0task(void *args) {
    while(1){ // 多线程中必须使用一个死循环
        b_Task++;
        delay(1); // 添加延迟1ms,可以有效防止卡死,爆错,提高效率。
    }
}

void setup() {
    Serial.begin(115200);
    xTaskCreatePinnedToCore(Core0task, "Core0task", 4096, NULL, 3, &th_p[0], 0); 
}

void loop() {
    a_Task++; 
    Serial.print("a_Task = ")
    Serial.println(a_Task);
    Serial.print("b_Task = ")
    Serial.println(b_Task);
    delay(1000);
}

执行结果:

WX20230312-2235512x

代码说明:

1,定义句柄

必须定义用于存储多线程的任务句柄。任务句柄需要通过地址传递,所以在这里被设置为数组会更方便管理。

TaskHandle_t th_p[1]; 

2,定义多线程的程序

定义一个void(无类型)的函数,用作多线程的任务,改

void Core0task(void *args) {
    while(1){ // 多线程中必须使用一个死循环
        b_Task++;
        delay(1); // 添加延迟1ms,可以有效防止卡死,爆错,提高效率。
    }
}

3,启动多线程

在 void setup 中启动多线程

void setup() {
    ...
    xTaskCreatePinnedToCore(Core0task, "Core0task", 4096, NULL, 3, &thp[0], 0); 
    ...
}

xTaskCreatePinnedToCore 说明

创建一个有特定关联的线程,此函数类似于 xTaskCreate,但允许在 SMP 系统中设置任务关联。

BaseType_t xTaskCreatePinnedToCore(
  TaskFunction_t pvTaskCode,  // 指向任务入口函数的指针。任务必须实现永不返回(即连续循环),或者应该使用 vTaskDelete 函数终止。
  const char *constpcName,  // 任务的描述性名称。这个主要是用来方便调试的。
  const uint32_t usStackDepth, // 指定为字节数的任务堆栈的大小。请注意,这与普通的 FreeRTOS 不同。
  void *constpvParameters, // 将用作正在创建的任务的参数的指针。

  UBaseType_t uxPriority, // 任务运行的优先级。数字越大,优先级越高。
  TaskHandle_t *constpvCreatedTask, // 用于传回一个句柄,创建的任务可以通过该句柄引用
  const BaseType_t xCoreID // 值 0 或 1 表示任务应固定到的 CPU 的索引号。指定大于 (portNUM_PROCESSORS - 1) 的值将导致函数失败。
)

创建更多的线程的例子

TaskHandle_t th_p[2];

long a_Task = 0;
long b_Task = 0;
long c_Task = 0;
void Core0task(void *args) {
    while(1){ // 多线程中必须使用一个死循环
        b_Task++;
        delay(1); // 添加延迟1ms,可以有效防止卡死,爆错,提高效率。
    }
}

void Core1task(void *args) {
    while(1){ // 多线程中必须使用一个死循环
        c_Task++;
        delay(2);
    }
}

void setup() {
    Serial.begin(115200);
    xTaskCreatePinnedToCore(Core0task, "Core0task", 4096, NULL, 3, &th_p[0], 0); 
    xTaskCreatePinnedToCore(Core1task, "Core1task", 4096, NULL, 4, &th_p[1], 1); 
}

void loop() {
    a_Task++; 
    Serial.print("a_Task = ")
    Serial.println(a_Task);
    Serial.print("b_Task = ")
    Serial.println(b_Task);
  Serial.print("c_Task = ")
    Serial.println(c_Task);
    delay(1000);
}

参考:

12 行在 ESP32 上尝试多核
https://qiita.com/Ninagawa_Izumi/items/5c3a9d40996836bd825f

ESP32 FreeRTOS 文档
https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/api-reference/system/freertos.html