文章目录
前言:
跟着B站“孤独的二进制”老师粗略学习了下ESP32上FreeRTOS的使用,为了加深理解,对所学习的API进行归纳整理。
与其临渊羡鱼,不如退而结网
一、内存管理
1.用途
任务内存管理优化(提高分配内存的效率避免堆栈溢出等问题)
2.相关API
ESP.getHeapSize() //获取Heap(堆)内存的总大小
ESP.getFreeHeap() //当前可用堆内存的总大小
uxTaskGetStackHighWaterMark(taskHandle) //用于获取指定任务的堆栈高水位标记,即任务执行过程中堆栈的最大利用率
3.注意事项
分配给任务栈的大小一般为最大使用量的两倍。
二、任务管理
1、任务创建
TaskHandle_t TaskHandle = NULL; //创建任务句柄(任务控制块指针)
xTaskCreate(
TaskFunction_t pvTaskCode, //任务函数
const char * const pcName, //任务名
configSTACK_DEPTH_TYPE usStackDepth,//任务栈大小
void *pvParameters, //传参指针
UBaseType_t uxPriority, //任务优先级
TaskHandle_t *pxCreatedTask //任务句柄
);
返回值 pdPass 表示任务创建成功
xTaskCreatePinnedToCore(task1,“task”,1024*4,NULL,1,NULL,0 or 1);//将任务放在核心0或核心1上(在双核主控上使用)
Serial.println(xPortGetCoreID());//打印当前任务是工作在哪个核心上
2、任务暂停
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
该函数会将任务句柄指定的任务从就绪队列中移除,并将其状态设置为暂停状态。
3、任务恢复
void vTaskResume( TaskHandle_t xTaskToResume );
该函数会将任务句柄指示的之前被暂停的任务重新放入就绪队列,使其可以被调度器调度并继续执行。
4、任务删除
void vTaskDelete( TaskHandle_t xTask );
使用此函数会将任务句柄所指示的任务删除
5、任务优先级
void vTaskPrioritySet( TaskHandle_t xTask,UBaseType_t uxNewPriority );//设置优先级
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask );//获取TaskHandle任务优先级
BaseType_t uxTaskPriorityGet(NULL);//获取当前任务优先级
taskYIELD(); //资源退让给同等级或者更高级的任务
6、获取任务状态
xTaskGetSchedulerState()
6、看门狗
概况:
看门狗是针对Task任务的
Arduion-ESP32 默认在 Core 0 的 IDLE 任务开启了看门狗 时间为 5000 ticks = 5秒
Core 0 和 Core 1 都运行了 FreeRTOS的IDLE任务,优先级为 0
IDLE(空闲)任务是用于清理被删除任务的内存
Core 1 loopBack任务就是Arduino的 setup 和 loop 优先级为 1
//手动关闭CPU上的TWDT - 慎重操作
disableCore0WDT();
disableCore1WDT();
esp_task_wdt_add(NULL);//给本任务添加看门狗(NULL代表本任务)
esp_task_wdt_delete(NULL)//移除本任务的看门狗(NULL代表本任务)
二、互斥量Mutex
1、作用/特点
使用Mutex相互排斥来解决多个任务同时对共享资源访问造成的问题
有多任务同时写入,或者数据大小超过cpu内存通道时,或者对共享资源的访问时候,需要有防范机制
MUTEX的工作原理可以想象成
共享的资源被锁在了一个箱子里,只有一把钥匙,有钥匙的任务才能对该资源进行访问
2、相关API
SemaphoreHandle_t xHandler; 创建Handler
xHandler = xSemaphoreCreateMutex(); 创建一个MUTEX 返回NULL,或者handler
xSemaphoreGive(xHandler); 释放
xSemaphoreTake(xHanlder, timeout); 指定时间内获取信号量 返回pdPASS, 或者pdFAIL
三、队列Queue
1.作用/特点
队列(queue)可以用于"任务到任务"、“任务到中断”、“中断到任务"直接传输信息。
队列可以包含若干个数据:队列中有若干项,这被称为"长度”(length)
每个数据大小固定
创建队列时就要指定长度、数据大小
数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读
2.相关API
//创建消息队列
QueueHandle_t xQueueCreate(
UBaseType_t uxQueueLength,//长度,数据个数
UBaseType_t uxItemSize ); //数据大小
发送数据到队列中
BaseType_t xQueueSend(
QueueHandle_t xQueue, //消息队列句柄
const void * pvItemToQueue, //待发送数据的地址
TickType_t xTicksToWait //等待时间
);
从队列中往外读取数据
BaseType_t xQueueReceive(
QueueHandle_t xQueue, //消息队列句柄
void *pvBuffer, //存储接收到数据的地址
TickType_t xTicksToWait //等待时间
);
四、信号量
1、作用/特点
一个任务(生产者)发出信号。另外一个任务(消费者)接受信号
二进制信号量可以想成就是一个整数 0 或者 1
计数信号量是0-max
Give就是+1
Take就是-1
Take的时候如果这个整数是0的话,就等待一直到timeout
2、相关API
SemaphoreHandle_t xHandler; 创建信号量Handler
xHandler = xSemaphoreCreateBinary(); 创建一个二进制信号量 返回NULL,或者handler
xHandler=xSemaphoreCreateCounting(3, 0);//创建计数信号量
xSemaphoreGive(xHandler); 生产者+1
xSemaphoreTake(xHanlder, timeout); 消费者-1 返回pdPASS, 或者pdFAIL
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );//删除信号量
五、事件标志组
1.作用/特点
// 3 Bytes 24bit 每一位表示一个事件
可以实现等待和同步的操作
2.相关API
EventGroupHandle_t xEventPurchase = NULL; //创建事件组 handler
xEventGroupCreate //用于创建一个事件标志组,并返回一个事件标志组句柄
xEventGroupSetBits //用于设置事件标志组的位
xEventGroupWaitBits //用于等待指定的事件标志位被设置
xEventGroupGetBits //用于获取当前事件标志组的位状态
xEventGroupClearBits //用于清除指定的事件标志位
用于同步多个任务或事件组的状态,并返回已设置的事件位(bit)
xEventGroupSync( EventGroupHandle_t xEventGroup, //事件组句柄
const EventBits_t uxBitsToSet, //将哪些位设置为1,然后等待
const EventBits_t uxBitsToWaitFor, //等待这些bit被设置为一
TickType_t xTicksToWait ); //等待时间
七、流媒体缓存
程序: 使用Stream Buffer 对流媒体数据,在任务间进行传输
流媒体,读和写的大小都没有任何的限制
读和写的大小可以不一致, 比如写入100 bytes, 可以分成两次每次50 bytes读取出来
注意: 适合于一个任务写,另外一个任务读
不适合多任务读写
如果必须要用在多任务的读写,请将内容放入CRITICAL SECTION
可以使用 MUTEX 或者 TASK Notification
八、消息缓存
Message Buffer基于Stream Buffer上实现的, 在传输的时候用4个字节记录了sent的内容大小
这样子读取的话,也可以一次读取对应大小的数据
所以很适合 串口 接收和发送数据,每次的大小不定,但是接受和发送的数据量需要相同
八、直接任务通知
1.作用/特点
每个任务都有一个结构体:TCB(Task Control Block),包括两个成员。
一个是uint32_t类型,用来表示通知值。
一个是uint8_t类型,用来表示通知状态。
通知状态有3种取值:
taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
taskWAITING_NOTIFICATION:任务在等待通知
taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为pending(有数据了,待处理)
我们不能对通知状态进行直接的读写操作,是系统自动的。只能对通知值进行操作。
2.相关API
使用直接任务通知(Task Notification)来取代二进制信号量(Binary Semaphore)
xTaskNotifyGive // 相当于精简化的 xTaskNotify() + eIncrement
ulTaskNotifyTake // 等待通知,然后重置为0
专业版本
BaseType_t xTaskNotify(
TaskHandle_t xTaskToNotify, //被通知任务的句柄
uint32_t ulValue, //怎么使用ulValue,由eAction 参数决定
eNotifyAction eAction );//见下表
返回值 pdPASS/pdFAIL
eNotifyAction取值 | 说明 |
---|---|
eNoAction | 仅仅是更新通知状态为"pending",未使用ulValue。 这个选项相当于轻量级的、更高效的二进制信号量。 |
eSetBits | 通知值 = 原来的通知值 按位或 ulValue。 相当于轻量级的、更高效的事件组。 |
eIncrement | 通知值 = 原来的通知值 + 1,未使用ulValue。 相当于轻量级的、更高效的二进制信号量、计数型信号量。 相当于**xTaskNotifyGive()**函数。 |
eSetValueWithoutOverwrite | 不覆盖。 如果通知状态为"pending"(表示有数据未读), 则此次调用xTaskNotify不做任何事,返回pdFAIL。 如果通知状态不是"pending"(表示没有新数据), 则:通知值 = ulValue。 |
eSetValueWithOverwrite | 覆盖。 无论如何,不管通知状态是否为"pendng", 通知值 = ulValue。 |
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry, //在通知到达前,将对应位清零
uint32_t ulBitsToClearOnExit, //在得到数据后退出时,将对应位清零。
uint32_t *pulNotificationValue, //将通知值赋给变量
TickType_t xTicksToWait );//任务进入阻塞态的超时时间
九、软件定时器
1.作用/特点
在FreeRTOS中,我们可以设置无数个软件定时器,他们都是基于系统滴答中断的
软件定时器有一次性和周期性两种。他们会在定时时间到达时执行回调函数。
要想使用软件定时器,要先指定周期,类型,回调函数。
2.相关API
TimerHandle_t lockHandle, checkHandle;//定义软件定时器句柄
创建定时器
lockHandle=xTimerCreate(
const char * const pcTimerName, //定时器的名字
const TickType_t xTimerPeriodInTicks, //周期
const UBaseType_t uxAutoReload, //类型,pdTRUE,自动加载 pdFAIL,一次性
void * const pvTimerID, /回调函数可以使用此参数, 比如分辨是哪个定时器
TimerCallbackFunction_t pxCallbackFunction);//回调函数
//创建成功则返回 TimerHandle_t ,否则返回NULL