esp32_S3多任务处理

多任务介绍

  • 多任务的概念:同一时间内执行多个任务,它充分利用CPU资源,提高程序的执行效率。
  • 对于单核CPU处理多任务,操作系统会给每个运行的任务一小段运行的时间,时间一到,然后立马切换任务,由于交替切换的速度过快,以人的眼光去看感觉每个程序都是同时执行的错觉。
  • 相对于多核CPU,操作系统会给每个内核安排一个执行的软件同时运行,从而达到同一个时间内执行多任务的效果。
  • ESP32的任务和操作系统的进程的概念是一样的
  • ESP32有两颗CPU,包含ProtocolcPU(称为CPUO或PRO_CPU)和ApplicationcPu(称为CPU1或APP_CPU)。这两个核实际上是相同的,并且共享相同的内存
  • 我们之前用的setup和loop方法都是在CPU1上执行的CPUO一直不干活,我们要使用多任务让它动起来。
  • 保证所有的任务都以合理正确的速率推进,不被其它任务所阻塞。
1
2
3
4
void1oop(){
task1()//这个需要较长的操作,比如59oms
task2();//这个需要50ms执行一次
}

有如上代码,任务一的时间较长,但任务二时间较短,就会有一定冲突。此时就适合双线程来完成任务。

时间片轮转调度

参考:(8条消息) UCOS学习笔记(四)时间片轮转调度_ucosii时间片轮转调度_爱吃肉的大高个的博客-CSDN博客

多任务处理相关函数

头文件

1
2
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

创建任务

1
2
3
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,其它值都是失败。

任务函数原型

1
void task(void* param);

获取任务的优先级

如果在任务函数里获取本任务的优先级可以使用uxTaskPriorityGet(NULL)

1
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask);

获取本任务在哪个CPU上运行

1
BaseType_t IRAM_ATTR xPortGetcoreID(void);

删除任务

如果在任务函数体内使用vTaskDelete(NULL)来结束本任务

1
void vTaskDelete(TaskHandle_t xTaskToDelete)

 

 

互斤量(xSemaphoreHandle)

互压量又称互床信号量(本质是信号量),是一种特殊的二值信号量,它用于实现对临界资源的独占式处理(它不会屏蔽CPU的中断处理)任意时刻互压量的状态只有两种,开锁或闭锁。当互斤量被任务持有时,该互压量处于闭锁状态,这个任务获得互压量的所有权。

1
xSemaphoreHandle//互斤锁,也算是一种信号量

创建一个互斤锁

1
xSemaphoreHandle xMutex = xSemaphoreCreateMutex()

获取互斤锁

1
xSemaphoreTake (xSemaphore,xBlockTime)

功能:在普通任务中获取信号量
参数:xSemaphore信号量句柄
xBlockTime等待的节拍数,立即返回,portMAX_DELAY等待到信号到来
ESP32默认的一节拍是1ms
返回值:pdTRUE:获取成功1pdFALSE:获取失败

释放互压锁

1
xSemaphoreGive(xSemaphore)

功能:在普通任务中释放信号量,也就是将信号量设为有信号的状态返回值:pdTRUE:设置成功 ,pdFALSE:设置失败

 

使用方法

1
2
3
4
if (xSemaphoreTake(xMutex,portMAX_DELAY))
//临界资源处理
xSemaphoreGive(xMutex);
}

 

综合例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#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*)&param, 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不会影响到其它任务的运行
}

使用方法

1
2
3
4
if (xSemaphoreTake(xMutex, portMAX_DELAY)){ 
//临界资源处理
xSemaphoreGive(xMutex);
}

多并行任务创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#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;

/*
Blink
Turns 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, check
the 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 stability
digitalWrite(45, LOW); // turn the LED off by making the voltage LOW
vTaskDelay(100); // one tick delay (15ms) in between reads for stability
}
}

void TaskAnalogReadA3(void *pvParameters) // This is a task.
{
(void) pvParameters;

/*
AnalogReadSerial
Reads 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
}
}

 

基于多核并行任务创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*
// 多线程基于FreeRTOS,可以多个任务并行处理;
// ESP32具有两个32位Tensilica Xtensa LX6微处理器;
// 实际上我们用Arduino进行编程时只使用到了第一个核(大核),第0核并没有使用
// 多线程可以指定在那个核运行;
*/

#include <Arduino.h>
#define USE_MULTCORE 1

void 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_MULTCORE

xTaskCreate(
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);

#endif

while (1)
{
Serial.printf("XTask is running\r\n");
delay(1000);
}
}