2023.5.11
1.Task
创建任务常用API:
任务函数 | 描述 |
---|---|
xTaskCreate() | 使用动态的方法创建一个任务 |
xTaskCreatePinnedToCore | 指定任务的运行核心(最后一个参数) |
vTaskDelete(NULL) | 删除当前任务 |
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode, // 任务函数名const char *const pcName, // 任务备注const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小void *const pvParameters, // 传入的参数UBaseType_t uxPriority, // 任务优先级TaskHandle_t *const pxCreatedTask); // 任务句柄
任务间传参
任务间传参可以使用多种方式,常见的为:
- 使用全局变量:需要注意并发读写的问题,当有两个任务及以上对全局变量进行读写时,需要使用信号量或互斥量进行保护。
- 使用队列:需要注意队列的大小和数据类型的一致性,不需要使用信号量或互斥量进行保护。
队列的读写效率相比全局变量慢一些
使用全局变量进行传参时:
- 传入参数:传递的为指针,且必须进行强制类型转换为空指针
(void *)pt
- 接收参数:把传递过来的空指针进行强制类型转换,转换为对应传输的类型指针
传递整数
#include <Arduino.h>int a = 1;void mytask(void *pt)
{int *b = (int *)pt;Serial.println(*b);while (1){}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024 * 3, (void *)&a, 1, NULL, 1);
}void loop() {}
输出结果为1
传递数组
#include <Arduino.h>int arr[] = {1, 2, 3};void mytask(void *pt)
{int *b = (int *)pt;int len = sizeof(arr) / sizeof(int); // 数组的长度,注意这里指针占4个字节,要用原数组名Serial.println(len);for (int i = 0; i < len; i++){Serial.print(*(b + i)); // 输出数组元素Serial.print(",");}while (1){}
}void setup()
{Serial.begin(115200);// 数组名代表数组元素的首地址,所以不需要&xTaskCreatePinnedToCore(mytask, "", 1024 * 3, (void *)arr, 1, NULL, 1);vTaskDelete(NULL);
}void loop() {}
传递结构体
#include <Arduino.h>typedef struct
{int a;int b;
} Mystruct;Mystruct test1 = {1, 2};void mytask(void *pt)
{Mystruct *test2 = (Mystruct *)pt; // 强制类型转换为结构体指针Serial.println(test2->a);Serial.println(test2->b);while (1) {}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024 * 3, (void *)&test1, 1, NULL, 1);vTaskDelete(NULL);
}void loop() {}
传递字符串
#include <Arduino.h>const char *str = "hello,world!";void mytask(void *pt)
{char *pstr = (char *)pt;Serial.println(pstr); // 输出hello,worldvTaskDelete(NULL);
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024 * 3, (void *)str, 1, NULL, 1);vTaskDelete(NULL);
}void loop() {}
任务的优先级
注意:中断任务的优先级永远高于任何任务的优先级。
在ESP32中,默认一共有25个优先级别,最低为0,最高为24。(可修改相关的配置函数进行修改优先级的数目超过25,但是不建议,级别越高,越占内存)。
- 同优先级的任务:FreeRTOS将采用循环调度算法来运行他们,也就是交替执行同优先级的任务。每个任务执行一个时间片,然后将CPU时间片分配给另一个任务。
- 优先级别高的任务先被创建和运行。
任务的调度: - 在FreeRTOS中,
vTaskDelay()
和vTaskDelayUntil()
函数可以暂停当前任务的执行,等待一段时间后再继续执行。(让其他任务有机会执行) taskYIELD()
函数:立即将CPU时间片退让给同等级或更高优先级的任务,如果没有其他任务等待执行,则当前任务会立即继续执行。(简单的说,就是让其他任务执行)
任务的挂起和恢复
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VvEfLKT9-1683944157100)(images/1.png)]
任务的状态:running、ready、blocked、suspended(挂起,暂停)
- running:运行状态,如果MCU只有一个内核,那么在任何给定时间内只能有一个任务处于运行状态。
- ready:准备状态(任务刚被创建时,准备执行),不处于堵塞或挂起状态(没有获得CPU执行权限,等待执行状态),因为同等级或更高优先级的任务正在执行
- blocked:使用了
vTaskDelay()或delay()
函数 - suspended:挂起状态,挂起之后,任务被恢复才能继续执行
// API:
TaskHandle_t pxtask = NULL; // 创建任务的句柄
xTaskCreatePinnedToCore(task1, "", 1024 * 2, NULL, 1, &pxtask, 1);vTaskSuspend(pxtask); // 挂起任务,任务不再执行
vTaskResume(pxtask); // 恢复被挂起的任务,继续执行
vTaskSuspendAll(); // 挂起所有函数,挂起后不可以执行
vTaskResumeAll(); // 恢复所有挂起函数
任务的堆栈设置和调试
创建任务时,如果给任务分配的内存空间过小,会导致程序不断重启。如果分配的内存空间过多,会造成资源浪费。
// API:
ESP.getHeapSize() // 本程序Heap最大尺寸(空间总大小)
ESP.getFreeHeap() // 当前Free Heap最大尺寸(当前可用剩余空间大小)
uxTaskGetStackHighWaterMark(taskHandle) // 计算当前任务剩余多少内存
示例程序:
TaskHandle_t taskHandle; // 创建任务的句柄
void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024*3 , NULL, 1, &taskHandle, 1);int waterMark = uxTaskGetStackHighWaterMark(taskHandle);Serial.print("Task Free Memory: "); // 任务剩余空间Serial.print(waterMark);vTaskDelete(NULL);
}
vTaskDelay()和delay()
一个tick的时间是由FreeRTOS的时钟节拍周期和时钟频率决定的,可以通过配置文件进行设置。默认情况下1 tick = 1ms
vTaskDelay()
函数:以系统时钟节拍(tick)为单位进行延时,例如vTaskDelay(100)表示让任务暂停100个系统时钟节拍的时间。delay()
函数:是一个简单的延时函数,它通常在不需要多任务处理和系统保护的应用中使用。使用后会后边的程序都会被延迟执行。
vTaskDelayUntil()
vTaskDelayUntil
函数比vTaskDelay
函数定时精准。
// API
TickType_t xLastWakeTime = xTaskGetTickCount(); // 获取当前时间
const TickType_t xFrequency = 3000; // 需要的时间间隔
vTaskDelayUntil(&xLastWakeTime, xFrequency);while(1){vTaskDelayUntil(&xLastWakeTime, xFrequency);// 下边为需要运行的函数
}
//
示例程序:
#include <Arduino.h>
void mytask(void *pt)
{TickType_t xLastWakeTime = xTaskGetTickCount(); // 获取当前时间const TickType_t xFrequency = 1000; // 需要的时间间隔while (1){vTaskDelayUntil(&xLastWakeTime, xFrequency);Serial.println(xTaskGetTickCount()); // 输出当前时间进行验证}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(mytask, "", 1024 * 3, NULL, 1, NULL, 1);vTaskDelete(NULL);
}void loop() {}
2.Queue
2023.5.12
队列:先入先出(FIFO,first in first out)
使用方法:
- 创建队列:长度,尺寸(每个内存空间存储的数据大小)
- 发送数据到队列中
- 从队列中取数据
// portMAX_DELAY - 无限Block
// TickType_t timeOut = portMAX_DELAY; // 无限等待,直到队列中有数据,或者等待数据有空位置可以存储新数据
TickType_t timeOut = 10;
xStatus = xQueueSend(Qhandle, &i, timeOut); // 往队列里发送数据,如果队列里内容是满的就等待10ms再次尝试发送
API | 描述 |
---|---|
xQueueCreate() | 创建一个队列 |
xQueueSend() | 往队列里写数据 |
xQueueReceive | 从队列里读数据 |
uxQueueMessagesWaiting(队列句柄) | 返回值为队列中参数的个数,可用于接收数据时,先判断一下队列里是否有数据 |
// 创建一个队列
QueueHandle_t Qhandle = xQueueCreate(5, sizeof(int)); // 创建一个队列,长度为5,每个空间的大小为int
队列存储int数据
#include <Arduino.h>// 创建队列的句柄
QueueHandle_t Qhandle = xQueueCreate(5, sizeof(int));void send(void *pt)
{int i = 0;while (1){if (xQueueSend(Qhandle, &i, portMAX_DELAY) != pdPASS){Serial.println(F("队列数据发送失败"));}else{Serial.print(F("发送成功:"));Serial.println(i);}i++;if (i == 8)i = 0;vTaskDelay(1000);}
}void receive(void *pt)
{int j = 0; // 存储接收的队列数据while (1){if (xQueueReceive(Qhandle, &j, portMAX_DELAY) != pdPASS){Serial.println(F("接收失败"));}else{Serial.print(F("接收成功:"));Serial.println(j);}}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(send, "", 1024 * 5, NULL, 1, NULL, 1); // 发送数据xTaskCreatePinnedToCore(receive, "", 1024 * 5, NULL, 1, NULL, 1); // 接收数据vTaskDelete(NULL);
}void loop() {}
运行结果:
发送成功:0
接收成功:0
发送成功:1
接收成功:1
发送成功:2
接收成功:2
发送成功:3
接收成功:3
队列传递结构体(重点)
跟上面的案例类似,只是队列中每个元素类型为struct
,并且发送和接收的数据存储也要设置为struct
类型
#include <Arduino.h>// 创建一个结构体
typedef struct
{int a;int b;
} Mystruct;// 创建队列的句柄
QueueHandle_t Qhandle = xQueueCreate(5, sizeof(Mystruct));void send(void *pt)
{Mystruct struct1 = {1, 2};while (1){if (xQueueSend(Qhandle, &struct1, portMAX_DELAY) != pdPASS){Serial.println(F("队列数据发送失败"));}else{Serial.print(F("发送成功:"));struct1.a++;Serial.println(struct1.a);}vTaskDelay(1000);}
}void receive(void *pt)
{Mystruct struct2; // 接收结构体数据while (1){if (xQueueReceive(Qhandle, &struct2, portMAX_DELAY) != pdPASS){Serial.println(F("接收失败"));}else{Serial.print(F("接收成功:"));Serial.println(struct2.a);}}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(send, "", 1024 * 5, NULL, 1, NULL, 1); // 发送数据xTaskCreatePinnedToCore(receive, "", 1024 * 5, NULL, 1, NULL, 1); // 接收数据vTaskDelete(NULL);
}void loop() {}
运行结果:按照FIFO的规则进行数据的发送和接收
发送成功:2
接收成功:1
发送成功:3
接收成功:2
发送成功:4
接收成功:3
队列传递大型数据时
例如传递字符串。传递大型数据时,把指针对应的数据进行传递。
malloc()
函数:在使用malloc开辟空间时,使用完一定要释放空间,如果不释放会造成内存泄漏。malloc()函数返回的实际是一个无类型指针,必须在其前面加上指针类型强制转换才可以使用。指针自身 = (指针类型*)malloc(sizeof(指针类型)*数据数量)
int *p = NULL;
p = (int *)malloc(sizeof(int)*10);// 使用完之后采用free()进行释放
free(p);
p = NULL; // 让其重新指向NULL
队列的多进单出:多个任务写,一个任务读
多个任务把数据写入一个队列,一个任务进行读。设置写入的任务级别为同级别,读任务的优先级别要比写任务高一级别。
- 不推荐这种方式:容易造成系统工作混乱。最好的工作方式是一个队列只有一个写操作,可以有多个读操作,但是写操作只能有一个。
队列集合(常用):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vphoMIcK-1683944157102)(images/2.png)]
多个队列,但是每个队列只有一个写操作,一个读操作(读取所有队列)
实现步骤:
- 创建队列集合的句柄:同时指定队列集合的总长度
- 将已创建的队列添加到集合中
- 创建一个句柄:从队列集合中获取有数据的队列
QueueHandle_t Qhandle1 = xQueueCreate(5, sizeof(int)); // 队列1
QueueHandle_t Qhandle2 = xQueueCreate(5, sizeof(int)); // 队列2QueueSetHandle_t QueueSet = xQueueCreateSet(10); // 队列集合句柄,10为队列的总长度xQueueAddToSet(Qhandle1, QueueSet); // 把队列1加入到队列集合中
xQueueAddToSet(Qhandle2, QueueSet); // 把队列2加入到队列集合中QueueSetMemberHandle_t QueueData = xQueueSelectFromSet(QueueSet, portMAX_DELAY); // 从队列集合中获取有数据的队列, QueueData为句柄
示例程序:这个程序编译不成功,还没有解决
#include <Arduino.h>QueueHandle_t Qhandle1 = xQueueCreate(5, sizeof(int)); // 队列1
QueueHandle_t Qhandle2 = xQueueCreate(5, sizeof(int)); // 队列2QueueSetHandle_t QueueSet = xQueueCreateSet(10); // 队列集合句柄xQueueAddToSet(Qhandle1, QueueSet); // 把队列1加入到队列集合中
xQueueAddToSet(Qhandle2, QueueSet); // 把队列2加入到队列集合中QueueSetMemberHandle_t QueueData = xQueueSelectFromSet(QueueSet, portMAX_DELAY); // 从队列集合中获取有数据的队列void send1(void *pt)
{int i = 1; // 任务1要发送的数据while (1){if (xQueueSend(Qhandle1, &i, portMAX_DELAY) != pdPASS){Serial.println("发送失败");}else{Serial.println("发送成功");}vTaskDelay(1000);}
}void send2(void *pt)
{int i = 2; // 任务2要发送的数据while (1){if (xQueueSend(Qhandle2, &i, portMAX_DELAY) != pdPASS){Serial.println("发送失败");}else{Serial.println("发送成功");}vTaskDelay(1000);}
}void receive(void *pt)
{int i; // 存储接收数据while (1){if (xQueueReceive(QueueData, &i, portMAX_DELAY) != pdPASS) // portMAX_DELAY,一直等待,直到队列中有数据{Serial.println("接收失败");}else{Serial.print("接收成功:");Serial.println(i);}// vTaskDelay(1000); // 采用了portMAX_DELAY,这里就不需要delay了}
}void setup()
{Serial.begin(9600);Serial.println("队列创建成功");xTaskCreatePinnedToCore(send1, "", 1024 * 5, NULL, 1, NULL, 1); // 两个相同的优先级别,轮流发送数据xTaskCreatePinnedToCore(send2, "", 1024 * 5, NULL, 1, NULL, 1);xTaskCreatePinnedToCore(receive, "", 1024 * 5, NULL, 2, NULL, 1); // 优先级别2,只要队列中有数据,就读
}void loop()
{
}
队列邮箱(常用):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4qyks8JB-1683944157103)(images/3.png)]
只有一个队列,一个任务写,多个任务读
// API
QueueHandle_t Mailbox = xQueueCreate(5, sizeof(int)); // 创建一个队列邮箱
xQueueOverwrite(); // 往队列中写数据
xQueuePeek(); // 从队列中读数据
示例程序:运行不成功
#include <Arduino.h>QueueHandle_t Mailbox = xQueueCreate(5, sizeof(int));void send(void *pt)
{int i = 1; // 任务1要发送的数据while (1){if (xQueueOverwrite(Mailbox, &i) != pdPASS){Serial.println("发送失败");}else{Serial.println("发送成功");i++;}vTaskDelay(1000);}
}void receive1(void *pt)
{int i; // 存储接收数据while (1){if (xQueuePeek(Mailbox, &i, 1000) != pdPASS) // portMAX_DELAY,一直等待,直到队列中有数据{Serial.println("接收失败");}else{Serial.print("接收成功:");Serial.println(i);}}
}
void receive2(void *pt)
{int i; // 存储接收数据while (1){if (xQueuePeek(Mailbox, &i,1000) != pdPASS) // portMAX_DELAY,一直等待,直到队列中有数据{Serial.println("接收失败");}else{Serial.print("接收成功:");Serial.println(i);}}
}void setup()
{Serial.begin(9600);Serial.println("队列创建成功");xTaskCreatePinnedToCore(send, "", 1024 * 5, NULL, 2, NULL, 1);xTaskCreatePinnedToCore(receive1, "", 1024 * 5, NULL, 2, NULL, 1);xTaskCreatePinnedToCore(receive2, "", 1024 * 5, NULL, 2, NULL, 1);
}void loop()
{
}
信号量
信号量分类:二进制信号量、计数信号量、互斥信号量。
信号量就像红绿灯一样,控制车辆的通行。
信号量常用于控制对共享资源的访问和任务同步。信号量对于控制共享资源访问的场景相当于一个上锁机制,代码只有获得这个锁的钥匙才能执行。
二进制信号量(常用)
二值信号量常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。
- 二进制信号量可以用于一个任务控制另一个任务的运行与堵塞。
- 二进制信号量表示只有两个值:0和1
- 二值信号量相当于长度为1的队列
- 二进制信号量只有两种状态:已触发和未触发,类似于一个开关。当一个任务等待一个已经触发的二进制信号量是,它会立即获得信号量,如果信号量未被触发,任务将被堵塞直到信号量被触发。
- 可以避免资源冲突和死锁问题,提高系统的可靠性和效率
// API
SemaphoreHandle_t xHandler = xSemaphoreCreateBinary(); // 创建二进制信号量
xSemaphoreGive(xHandler); // 释放信号量
xSemaphoreTake(xHanlder, timeout); // 在指定时间内获取信号量,返回值为pdPASS, 或者pdFAIL
示例程序1:按键控制LED的亮灭(已验证)
#include <Arduino.h>SemaphoreHandle_t xHandler = xSemaphoreCreateBinary(); // 创建二进制信号量
TickType_t timeOut = 1000;void task1(void *pt)
{pinMode(23, OUTPUT);while (1){if (xSemaphoreTake(xHandler, timeOut) == pdTRUE){digitalWrite(23, !digitalRead(23));}}
}void task2(void *pt)
{pinMode(22, INPUT_PULLUP);while (1){if (digitalRead(22) == LOW){xSemaphoreGive(xHandler);vTaskDelay(120); // button debounce}}
}void setup()
{Serial.begin(9600);xTaskCreatePinnedToCore(task1, "", 1024 * 5, NULL, 1, NULL, 1); // 两个相同的优先级别,轮流发送数据xTaskCreatePinnedToCore(task2, "", 1024 * 5, NULL, 1, NULL, 1);
}void loop()
{
}
示例2:采用二进制信号量对任务进行管理,对全局变量进行读写
- 该示例验证了,使用二进制信号量可以很好的控制任务的执行顺序。
#include <Arduino.h>
int a = 0;
SemaphoreHandle_t xHandler = xSemaphoreCreateBinary(); // 创建二进制信号量void task1(void *pt)
{while (1){xSemaphoreTake(xHandler, portMAX_DELAY); // 无限等待,直到获取信号量for (int i = 0; i < 10; i++){a++;printf("mytask1 a = %d\n", a);}xSemaphoreGive(xHandler); // 执行完之后,需要再次释放信号量vTaskDelay(1000);}
}void task2(void *pt)
{while (1){xSemaphoreTake(xHandler, portMAX_DELAY); // 无限等待,直到获取信号量for (int i = 0; i < 10; i++){a++;printf("mytask2 a = %d\n", a);}xSemaphoreGive(xHandler); // 执行完之后,需要再次释放信号量vTaskDelay(1000);}
}void setup()
{Serial.begin(115200);xSemaphoreGive(xHandler); // 首先释放一次信号量,不然运行不了xTaskCreatePinnedToCore(task1, "", 1024 * 5, NULL, 1, NULL, 1); // task1先获取信号量,执行一次xTaskCreatePinnedToCore(task2, "", 1024 * 5, NULL, 1, NULL, 1); // 然后task2获取信号量,执行一次,task11再执行
}void loop()
{
}
运行结果:
- 采用二进制信号量:
mytask1 a = 1
mytask1 a = 2
mytask1 a = 3
mytask1 a = 4
mytask1 a = 5
mytask1 a = 6
mytask1 a = 7
mytask1 a = 8
mytask1 a = 9
mytask1 a = 10
mytask2 a = 11
mytask2 a = 12
mytask2 a = 13
mytask2 a = 14
mytask2 a = 15
mytask2 a = 16
mytask2 a = 17
mytask2 a = 18
mytask2 a = 19
mytask2 a = 20
mytask1 a = 21
- 不采用二进制信号量
ytask1 a = 1
mytask1 a = 2
mytask1 a = 3
mytask1 a = 4
mytask1 a = 6
mytask1 a = 7
mytask1 a = 8
mytask1 a = 9
mytask1 a = 10
mytask1 a = 11
mytask2 a = 5
mytask2 a = 12
mytask2 a = 13
计数信号量
与二进制不同的是,计数信号量可以有更多的状态。
- 计数信号量相当于长度大于1的队列,同二值信号量一样,不需要关系队列中存储了什么数据,只需要关心队列是否为空即可。
- 例如在一个停车场中有10个车位,车辆进入时,车位被占用(计数器减1),车开出去后(计数器加1),为0时表示没有可用的车位。
// API
uxSemaphoreGetCount( semphrHandle); // 获得计数型信号量的值
SemaphoreHandle_t semphrHandle = xSemaphoreCreateCounting(10,0);// 创建计数型信号量,参数1:最大值,参数2:初始值
xSemaphoreGive(semphrHandle); // 释放信号量
xSemaphoreTake(semphrHandle); // 获取信号量
使用场合:事件计数、资源管理
- 共享资源的访问控制:当多个任务需要共享同一个资源时,可以使用计数信号量来控制资源的访问。每个任务需要访问资源时,都需要获取一个计数信号量,如果计数信号量的值为0,则任务会被堵塞,直到其他任务释放资源并增加计数信号量的值。这种方式可以避免资源冲突和死锁等问题。
- 控制任务的执行顺序:有些情况下,需要控制任务的执行顺序,例如任务A必须在任务B执行完成之后才能执行。可以使用计数信号量来实现这种控制。任务B执行完成后,可以增加计数信号量的值,任务A等待计数信号量的值为1时,可以获取信号量并开始执行。
示例1:模拟停车场的停车位
#include <Arduino.h>// 创建计数型信号量,参数1:最大值,参数2:初始值
SemaphoreHandle_t semphrHandle = xSemaphoreCreateCounting(5, 5); // 初值为5,代表初始有5个空车位void carintask(void *pt)
{int emptySpace = 0; // 空的停车位BaseType_t iResult;while (1){emptySpace = uxSemaphoreGetCount(semphrHandle);printf("emptySpace = %d\n", emptySpace);iResult = xSemaphoreTake(semphrHandle, 0); // 获取信号量if (iResult == pdPASS)printf("One car in\n");elseprintf("No Space\n");vTaskDelay(1000);}
}void caroutTask(void *pt)
{while (1){vTaskDelay(6000);xSemaphoreGive(semphrHandle); // 释放信号量printf("One car out\n");}
}void setup()
{Serial.begin(115200);xTaskCreatePinnedToCore(carintask, "", 1024 * 5, NULL, 1, NULL, 1);xTaskCreatePinnedToCore(caroutTask, "", 1024 * 5, NULL, 1, NULL, 1);
}void loop()
{
}
互斥信号量(常用)
与二进制信号量十分相似。Mutex的工作原理可以想象成共享的资源被锁在一个箱子里,只有一把钥匙,有钥匙的任务才能对共享资源进行访问。
- 互斥量与二进制信号量的区别:优先级继承,在FreeRTOS中,当一个任务持有一个互斥量时,该任务对共享资源的访问是独占的,其他试图获取该互斥量的任务将被堵塞。当一个优先级更高的任务试图获取已经被持有的互斥量时,FreeRTOS会自动暂时提高持有互斥量任务的优先级别,使其具有与试图获取互斥量的任务相同的优先级别。这样可以确保高优先级别的任务在获取共享资源时能够及时执行,并避免低优先级别任务长时间持有共享资源。当持有互斥量的任务释放互斥量时,其优先级别将恢复到原始值,而不是保持被继承的优先级。这样可以确保任务在不需要共享资源时恢复其原始优先级,以避免低优先级任务一直持有高优先级任务的优先级,导致高优先级任务无法及时执行其他任务。
- 注意:使用完立即释放钥匙
SemaphoreHandle_t xHandler = xSemaphoreCreateMutex(); // 创建一个Mutex互斥量
xSemaphoreGive(xHandler); // 释放信号量
xSemaphoreTake(xHanlder, timeout); // 在指定时间内获取信号量,返回值为pdPASS, 或者pdFAIL