esp32_S3多任务处理
多任务介绍
- 多任务的概念:同一时间内执行多个任务,它充分利用CPU资源,提高程序的执行效率。
- 对于单核CPU处理多任务,操作系统会给每个运行的任务一小段运行的时间,时间一到,然后立马切换任务,由于交替切换的速度过快,以人的眼光去看感觉每个程序都是同时执行的错觉。
- 相对于多核CPU,操作系统会给每个内核安排一个执行的软件同时运行,从而达到同一个时间内执行多任务的效果。
- ESP32的任务和操作系统的进程的概念是一样的
- ESP32有两颗CPU,包含ProtocolcPU(称为CPUO或PRO_CPU)和ApplicationcPu(称为CPU1或APP_CPU)。这两个核实际上是相同的,并且共享相同的内存
- 我们之前用的setup和loop方法都是在CPU1上执行的CPUO一直不干活,我们要使用多任务让它动起来。
- 保证所有的任务都以合理正确的速率推进,不被其它任务所阻塞。
void1oop(){
task1()//这个需要较长的操作,比如59oms
task2();//这个需要50ms执行一次
}
有如上代码,任务一的时间较长,但任务二时间较短,就会有一定冲突。此时就适合双线程来完成任务。
时间片轮转调度
参考:(8条消息) UCOS学习笔记(四)时间片轮转调度_ucosii时间片轮转调度_爱吃肉的大高个的博客-CSDN博客
多任务处理相关函数
头文件
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
创建任务
BaseType_t tXTaskCreatePinnedToCore(TaskFunctiont_t pvTaskcode,
const char * const pcName,const uint32_t usstackDepth,
void* const pvParameters,UBaseType_t uxPriority,TaskHandle_t const pvCreatedTask,const BaseType_t xCoreID);
- pvTaskCode:指向任务输入函数的指针。任务必须被实现为永不Return(如:死循环),或者应该使用
- vtTaskDelete:函数终止
- pcName:该任务的描述性名称,最大长度16字节
- usStackDepth:指定为字节数的任务堆栈的大小
- pvParameters:将用作所创建的任务的参数的指针,在创建任务的时候可以向任务传递参数。
- uxPriority:任务运行的优先级。目前ESP32的优先级有25级,0-24,数字越大优先级越高,Idle为0,loop任务的优先级是1
- pvCreatedTask:用于传递回所创建任务的句柄
- xCoreID:如果值为tskNOAFFINITY,则创建的任务不会固定到任何CPU,调度程序可以在任何可用的核心上运行。值0或1表示任务应固定到的CPU的索引编号。指定大于(portNUMPROCESSORS-1)的值将导致函数失败
- 函数成功返回pdPASS,其它值都是失败。
任务函数原型
void task(void* param);
获取任务的优先级
如果在任务函数里获取本任务的优先级可以使用uxTaskPriorityGet(NULL)
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask);
获取本任务在哪个CPU上运行
BaseType_t IRAM_ATTR xPortGetcoreID(void);
删除任务
如果在任务函数体内使用vTaskDelete(NULL)来结束本任务
void vTaskDelete(TaskHandle_t xTaskToDelete)
互斤量(xSemaphoreHandle)
互压量又称互床信号量(本质是信号量),是一种特殊的二值信号量,它用于实现对临界资源的独占式处理(它不会屏蔽CPU的中断处理)任意时刻互压量的状态只有两种,开锁或闭锁。当互斤量被任务持有时,该互压量处于闭锁状态,这个任务获得互压量的所有权。
xSemaphoreHandle//互斤锁,也算是一种信号量
创建一个互斤锁
xSemaphoreHandle xMutex = xSemaphoreCreateMutex()
获取互斤锁
xSemaphoreTake (xSemaphore,xBlockTime)
功能:在普通任务中获取信号量
参数:xSemaphore信号量句柄
xBlockTime等待的节拍数,立即返回,portMAX_DELAY等待到信号到来
ESP32默认的一节拍是1ms
返回值:pdTRUE:获取成功1pdFALSE:获取失败
释放互压锁
xSemaphoreGive(xSemaphore)
功能:在普通任务中释放信号量,也就是将信号量设为有信号的状态返回值:pdTRUE:设置成功 ,pdFALSE:设置失败
使用方法
if (xSemaphoreTake(xMutex,portMAX_DELAY))
//临界资源处理
xSemaphoreGive(xMutex);
}
综合例子
#include <FreeRTOSConfig.h>
xSemaphoreHandle xMutex; //互斥量
int number = 0; //互斥资源void task1(void* param)
{static int count = 0;int p = *((int*)param);while(count++ < 200){int core = xPortGetCoreID(); //获取当前核Serial.printf("Core %d -> ", core);Serial.print("I am task1, Param: ");Serial.print(p);if(xSemaphoreTake(xMutex, portMAX_DELAY)){Serial.printf(" number: %d", number);xSemaphoreGive(xMutex);}Serial.println();delay(2000);}vTaskDelete(NULL); //结束任务
}void task2(void* param)
{static int count = 0;while(count++ < 200){int core = xPortGetCoreID(); //获取当前核Serial.printf("Core %d -> ", core);Serial.println("I am task2");if(xSemaphoreTake(xMutex, portMAX_DELAY)){number++;xSemaphoreGive(xMutex);}delay(2000);}vTaskDelete(NULL); //结束任务
}void setup() {Serial.begin(115200);TaskHandle_t handle1;int param = 30;xMutex = xSemaphoreCreateMutex();xTaskCreatePinnedToCore(task1, "task1", 2048, (void*)¶m, 15, &handle1, 0);xTaskCreatePinnedToCore(task2, "task2", 2048, NULL, 15, NULL, 1);
}void loop() {int core = xPortGetCoreID(); //获取当前核Serial.printf("Core %d -> I am loop ", core);auto pri = uxTaskPriorityGet(NULL);Serial.printf(" priority: %d", pri);Serial.println();delay(2000);//一个任务的delay不会影响到其它任务的运行
}
使用方法
if (xSemaphoreTake(xMutex, portMAX_DELAY)){
//临界资源处理
xSemaphoreGive(xMutex);
}
多并行任务创建
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif/*
FreeRTOS任务优先级:任务优先级数值越小,任务优先级越低。
一、 FreeRTOS 中任务的最高优先级是通过 FreeRTOSConfig.h 文件中的 configMAX_PRIORITIES 进行
配置的,用户实际可以使用的优先级范围是 0 到 configMAX_PRIORITIES – 1。比如我们配置此宏定
义为 5,那么用户可以使用的优先级号是 0,1,2,3,4,不包含 5。
二、用户配置任务的优先级数值越小,那么此任务的优先级越低,空闲任务的优先级是 0。
三、用户配置宏定义 configMAX_PRIORITIES 的最大值不要超过 32,即用户任务可以使用的优先级
范围是0到31
*/
// define two tasks for Blink & AnalogRead
void TaskBlink( void *pvParameters );
void TaskAnalogReadA3( void *pvParameters );// the setup function runs once when you press reset or power the board
void setup() {// initialize serial communication at 115200 bits per second:USBSerial.begin(115200); // Now set up two tasks to run independently.xTaskCreatePinnedToCore(TaskBlink, "TaskBlink" // A name just for humans, 1024 // This stack size can be checked & adjusted by reading the Stack Highwater, NULL, 2 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest., NULL , ARDUINO_RUNNING_CORE);xTaskCreatePinnedToCore(TaskAnalogReadA3, "AnalogReadA3", 1024 // Stack size, NULL, 1 // Priority, NULL , ARDUINO_RUNNING_CORE);// Now the task scheduler, which takes over control of scheduling individual tasks, is automatically started.
}void loop()
{// Empty. Things are done in Tasks.
}/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/void TaskBlink(void *pvParameters) // This is a task.
{(void) pvParameters;/*BlinkTurns on an LED on for one second, then off for one second, repeatedly.If you want to know what pin the on-board LED is connected to on your ESP32 model, checkthe Technical Specs of your board.
*/// initialize digital LED_BUILTIN on pin 13 as an output.pinMode(45, OUTPUT);for (;;) // A Task shall never return or exit.{digitalWrite(45, HIGH); // turn the LED on (HIGH is the voltage level)vTaskDelay(100); // one tick delay (15ms) in between reads for stabilitydigitalWrite(45, LOW); // turn the LED off by making the voltage LOWvTaskDelay(100); // one tick delay (15ms) in between reads for stability}
}void TaskAnalogReadA3(void *pvParameters) // This is a task.
{(void) pvParameters;/*AnalogReadSerialReads an analog input on pin A3, prints the result to the serial monitor.Graphical representation is available using serial plotter (Tools > Serial Plotter menu)Attach the center pin of a potentiometer to pin A3, and the outside pins to +5V and ground.This example code is in the public domain.
*/for (;;){// read the input on analog pin A3:int sensorValueA3 = analogRead(A3);// print out the value you read:USBSerial.print("A3->");USBSerial.println(sensorValueA3);vTaskDelay(100); // one tick delay (15ms) in between reads for stability}
}
基于多核并行任务创建
/*
// 多线程基于FreeRTOS,可以多个任务并行处理;
// ESP32具有两个32位Tensilica Xtensa LX6微处理器;
// 实际上我们用Arduino进行编程时只使用到了第一个核(大核),第0核并没有使用
// 多线程可以指定在那个核运行;*/#include <Arduino.h>
#define USE_MULTCORE 1void xTaskOne(void *xTask1)
{while (1){USBSerial.printf("Task1 \r\n");delay(500);}
}void xTaskTwo(void *xTask2)
{while (1){USBSerial.printf("Task2 \r\n");delay(1000);}
}void setup()
{USBSerial.begin(115200);delay(10);}void loop()
{#if !USE_MULTCORExTaskCreate(xTaskOne, /* Task function. */"TaskOne", /* String with name of task. */4096, /* Stack size in bytes. */NULL, /* Parameter passed as input of the task */1, /* Priority of the task.(configMAX_PRIORITIES - 1 being the highest, and 0 being the lowest.) */NULL); /* Task handle. */xTaskCreate(xTaskTwo, /* Task function. */"TaskTwo", /* String with name of task. */4096, /* Stack size in bytes. */NULL, /* Parameter passed as input of the task */2, /* Priority of the task.(configMAX_PRIORITIES - 1 being the highest, and 0 being the lowest.) */NULL); /* Task handle. */#else//最后一个参数至关重要,决定这个任务创建在哪个核上.PRO_CPU 为 0, APP_CPU 为 1,或者 tskNO_AFFINITY 允许任务在两者上运行.xTaskCreatePinnedToCore(xTaskOne, "TaskOne", 4096, NULL, 1, NULL, 0);xTaskCreatePinnedToCore(xTaskTwo, "TaskTwo", 4096, NULL, 2, NULL, 1);#endifwhile (1){Serial.printf("XTask is running\r\n");delay(1000);}
}