ESP32上的FreeRTOS学习(2)

绝对延时

相对延时:每次延时都是从执行函数vTaskDelay()开始,直到延时指定的时间结束。
绝对延时:每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务。
区别:个人理解,假如一个任务执行完成需要1ms,每10ms要执行一次。使用相对延时的话,就是延时10ms再加上执行任务的1ms,整个过程需要11ms,如果有其他更高优先级的任务,时间还需要更多;而对于绝对延时,这个1ms的任务是在10ms内完成的。

1
2
3
4
5
6
7
8
9
10
void TimingTask(void* param)
{
const TickType_t task_frequency = 2000;
TickType_t last_wake_time = xTaskGetTickCount(); // 自动更新
while(1)
{
Serial.println(last_wake_time);
vTaskDelayUntil(&last_wake_time,task_frequency);
}
}

定时器

FreeRTOS提供的软件定时器类似于单片机的定时中断,但是精度和优先级不如硬件定时器。支持单次模式和周期模式。精度为x个tick。
使用软件定时器的时候要注意,回调函数应该快进快出,绝对不允许使用任何可能引起任务挂起或阻塞的API接口,在回调函数中不能出现死循环。

  • 定义句柄
    1
    TimerHandle_t xTimerUser;
  • 回调函数
    1
    2
    3
    4
    void vTimerCallBack(TimerHandle_t xTimer)
    {

    }
  • 创建定时器
    1
    2
    3
    4
    5
    xTimerUser = xTimerCreate("name", // 定时器名称
    2000, // 定时器触发周期,单位tick
    pdFALSE, // 单次模式
    (void*) 0, // 定时器ID
    vTimerCallback); // 要执行的回调函数
    pdFALSE为单次触发模式,pdTRUE为重复触发
  • 定时器启动
    1
    xTimerStart(xTimerUser, 2000);
    xTimerUser为定时器句柄,由xTimerCreate创建。
    2000为阻塞时间
    启动成功返回pdPASS;启动失败返回pdFAIL

内存分配

ESP32 获得内存信息函数。

1
2
uint32_t getHeapSize(); // 全部的片内内存大小
uint32_t getFreeHeap(); // 当前可用内存大小

每个任务都有自己的堆栈,堆栈的总大小在创建任务的时候就确定了。

1
2
TaskHandle_t TaskHandle;
int WaterMark = uxTaskGetStackHighWaterMark(TaskHandle)

上面的函数用于检查任务从创建好到现在的历史剩余最小值,这个值越小说明任务堆栈溢出的可能性就越大。FreeRTOS 把这个历史剩余最小值叫做“高水位线”。此函数相对来说会多耗费一点时间,所以在代码调试阶段可以使用,产品发布的时候最好不要使用。
任务的推荐值堆栈大小一般为使用的两倍。

任务管理

任务挂起函数

1
vTaskSuspend(TaskHandle);

挂起指定任务。被挂起的任务绝不会得到 CPU 的使用权,不管该任务具有什么优先级。任务可以通过调用 vTaskSuspend() 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到 CPU 的使用权,也不会参与调度,它相对于调度器而言是不可见的,除非它从挂起态中解除。

任务恢复函数

1
vTaskResume(TaskHandle);

任务恢复就是让挂起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂起时的状态继续运行。如果被恢复任务在所有就绪态任务中,处于最高优先级列表的第一位,那么系统将进行任务上下文的切换。

任务删除函数

1
vTaskDelete(TaskHandle);

用于删除一个任务。当一个任务删除另外一个任务时,形参为要删除任务创建时返回的任务句柄,如果是删除自身,则形参为 NULL。

任务延时函数

1
vTaskDelay(tick);

vTaskDelay()在我们任务中用得非常之多,每个任务都必须是死循环,并且是必须要有阻塞的情况,否则低优先级的任务就无法被运行了。

优先级

注意

  • 优先级最大值不要超过32,优先级数值越小,那么此任务的优先级越低,空闲任务的优先级是0。
  • 中断的优先级永远高于任何任务的优先级,即任务在执行的过程中,中断来了就开始执行中断服务程序。中断优先级的数值越小,优先级越高;任务优先级数值越小,任务优先级越低。

相关函数

获取任务优先级

1
UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask);

设置任务优先级

1
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority);

强制任务切换

1
taskYIELD();

调用该函数退让资源,任务调度器会重新评估任务,将资源分配给同等级或更高等级的任务,注意不会把资源给低优先级任务。

问题

如果高优先级任务不进入Block或Suspend状态的话,那么低优先级的任务就永远不会被执行。
所以在高优先级任务中调用 vTaskDelay(),使高优先级的任务进入Block状态,这样低优先级任务就可以运行了。