欢迎光临散文网 会员登陆 & 注册

FreeRTOS函数功能总结

2023-08-03 09:57 作者:自闭选手的Z23  | 我要投稿

1. 任务相关函数

1.1 创建任务xTaskCreate

xTaskCreate参数说明


1.2 删除任务vTaskDelete

vTaskDelete参数说明

怎么删除任务?举个不好的例子:

自刀:vTaskDelete(NULL)

被刀:别的任务执行vTaskDelete(pvTaskCode),pvTaskCode是自己的句柄

刀人:执行vTaskDelete(pvTaskCode),pvTaskCode是别的任务的句柄


1.3 暂停任务vTaskSuspend

参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。


1.4 优先级相关函数

1.4.1 获得任务的优先级uxTaskPriorityGet

使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。

1.4.2 设置任务的优先级vTaskPrioritySet

使用参数xTask来指定任务,设置为NULL表示设置自己的优先级;

参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。


1.5 启动任务调度器vTaskStartScheduler

1.6 空闲任务钩子函数vApplicationIdleHook

空闲任务(Idle任务)的作用:释放被删除的任务的内存。

为什么必须要有空闲任务?

    一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。

    要注意的是:如果使用vTaskDelete()来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。

使用钩子函数的前提

FreeRTOS\Source\tasks.c中,可以看到如下代码,所以前提就是:

    把这个宏定义为1:configUSE_IDLE_HOOK;

    实现vApplicationIdleHook函数。

2. Tick相关函数

2.1 延时函数vTaskDelay

至少等待指定个数的Tick Interrupt才能变为就绪状态

    注意,基于Tick实现的延时并不精确,比如vTaskDelay(2)的本意是延迟2个Tick周期,有可能经过1个Tick多一点就返回了。

使用vTaskDelay函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为Tick。

这样的代码就与configTICK_RATE_HZ无关,即使配置项configTICK_RATE_HZ改变了,我们也不用去修改代码。

2.2 绝对延时函数vTaskDelayUntil

等待到指定的绝对时刻,才能变为就绪态

使用vTaskDelay(n)时,进入、退出vTaskDelay的时间间隔至少是n个Tick中断;

使用xTaskDelayUntil(&Pre, n)时,前后两次退出xTaskDelayUntil的时间至少是n个Tick中断。

2.3 Heap相关的函数

2.3.1 pvPortMalloc/vPortFree

作用:分配内存、释放内存。

如果分配内存不成功,则返回值为NULL。

2.3.2 xPortGetFreeHeapSize

    当前还有多少空闲内存,这函数可以用来优化内存的使用情况。

    比如当所有内核对象都分配好后,执行此函数返回2000,那么configTOTAL_HEAP_SIZE就可减小2000。注意:在heap_3中无法使用。

2.3.3 xPortGetMinimumEverFreeHeapSize

返回:程序运行过程中,空闲内存容量的最小值。

注意:只有heap_4、heap_5支持此函数。

2.3.4 malloc失败的钩子函数

在pvPortMalloc函数内部:

所以,如果想使用这个钩子函数:

    在FreeRTOSConfig.h中,把configUSE_MALLOC_FAILED_HOOK定义为1

    提供vApplicationMallocFailedHook函数

    pvPortMalloc失败时,才会调用此函数

3. 队列相关函数

3.1 创建队列

队列的创建有两种方法:动态分配内存、静态分配内存

3.1.1 动态分配内存创建队列xQueueCreate

3.1.2 静态分配内存创建队列xQueueCreateStatic

静态分配内存创建队列参数

示例:

3.2 复位队列xQueueReset

队列刚被创建时,里面没有数据;使用过程中可以调用xQueueReset()把队列恢复为初始状态,此函数原型为:

3.3 删除队列vQueueDelete

删除队列的数函为vQueueDelete(),只能删除使用动态方法创建的队列,它会释放内存。原型如下:

3.4 写队列

可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:

写队列参数

3.5 读队列xQueueReceive

使用xQueueReceive()函数读队列,读到一个数据后,队列中该数据会被移除。这个函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:

读队列参数

3.6 查询队列

可以查询队列中有多少个数据、有多少空余空间。函数原型如下:

3.7 覆盖/偷看

当队列长度为1时,可以使用xQueueOverwrite()xQueueOverwriteFromISR()来覆盖数据。 注意,队列长度必须为1。当队列满时,这些函数会覆盖里面的数据,这也意味着这些函数不会被阻塞。 函数原型如下:


    如果想让队列中的数据供多方读取,也就是说读取时不要移除数据,要留给后来人。

    那么可以使用"窥视",也就是xQueuePeek()或xQueuePeekFromISR()。这些函数会从队列中复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么"偷看"时会导致阻塞;一旦队列中有数据,以后每次"偷看"都会成功。 函数原型如下:

4. 信号量函数

使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。

4.1 创建信号量

要先创建信号量句柄才能使用信号量;使用信号量时,要使用句柄来表明使用哪个信号量。

对于二进制信号量、计数型信号量,它们的创建函数不一样:

创建二进制信号量的函数原型如下:

创建计数型信号量的函数原型如下:

4.2 删除信号量vSemaphoreDelete

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。

vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:

4.3 give/take

二进制信号量、计数型信号量的give、take操作函数是一样的。这些函数也分为2个版本:给任务使用,给ISR使用。列表如下:

xSemaphoreGive的函数原型如下:

xSemaphoreGive函数的参数与返回值列表如下:

xSemaphoreGiveFromISR函数的参数与返回值列表如下:

xSemaphoreTake的函数原型如下:

xSemaphoreTake函数的参数与返回值列表如下:

xSemaphoreTakeFromISR的函数原型如下:

xSemaphoreTakeFromISR函数的参数与返回值列表如下:

5. 互斥量函数

5.1 创建互斥量

互斥量是一种特殊的二进制信号量。

使用互斥量时,先创建、然后去获得、释放它。使用句柄来表示一个互斥量。

创建互斥量的函数有2种:动态分配内存,静态分配内存,函数原型如下:

要想使用互斥量,需要在配置文件FreeRTOSConfig.h中定义:

5.2 互斥量其他函数

要注意的是,互斥量不能在ISR中使用

各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

6.  递归锁

递归锁(Recursive Mutexes),它的特性如下:

    任务A获得递归锁M后,它还可以多次去获得这个锁

    "take"了N次,要"give"N次,这个锁才会被释放

递归锁的函数根一般互斥量的函数名不一样,参数类型一样,列表如下:

函数原型如下:

7.  事件组函数

7.1 创建事件组

要先创建事件组句柄才能使用事件组;使用事件组时,要使用句柄来表明使用哪个事件组。

有两种创建方法:动态分配内存、静态分配内存。函数原型如下:

7.2 删除事件组vEventGroupDelete

对于动态创建的事件组,不再需要它们时,可以删除它们以回收内存。

vEventGroupDelete可以用来删除事件组,函数原型如下:

7.3 设置事件xEventGroupSetBits

可以设置事件组的某个位、某些位,使用的函数有2个:

任务中使用xEventGroupSetBits()

ISR中使用xEventGroupSetBitsFromISR()

有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。

函数原型如下:

  值得注意的是,ISR中的函数,比如队列函数xQueueSendToBackFromISR、信号量函数xSemaphoreGiveFromISR,它们会唤醒某个任务,最多只会唤醒1个任务。

  值得注意的是,ISR中的函数,比如队列函数xQueueSendToBackFromISR、信号量函数xSemaphoreGiveFromISR,它们会唤醒某个任务,最多只会唤醒1个任务。

     如果后台任务的优先级比当前被中断的任务优先级高,xEventGroupSetBitsFromISR会设置*pxHigherPriorityTaskWoken为pdTRUE。

    如果daemon task成功地把队列数据发送给了后台任务,那么xEventGroupSetBitsFromISR的返回值就是pdPASS。

7.4 等待事件xEventGroupWaitBits

使用xEventGroupWaitBits来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位;等到期望的事件后,还可以清除某些位。

函数原型如下:

    一个任务在等待事件发生时,它处于阻塞状态;

 函数参数说明列表如下:

举例如下:

 可以使用xEventGroupWaitBits()等待期望的事件,它发生之后再使用xEventGroupClearBits()来清除。但是这两个函数之间,有可能被其他任务或中断抢占,它们可能会修改事件组。

    可以使用设置xClearOnExit为pdTRUE,使得对事件组的测试、清零都在xEventGroupWaitBits()函数内部完成,这是一个原子操作。

7.5 同步xEventGroupSync

有一个事情需要多个任务协同,比如:

任务A:炒菜、任务B:买酒、任务C:摆台

A、B、C做好自己的事后,还要等别人做完;大家一起做完,才可开饭。

使用xEventGroupSync()函数可以同步多个任务:

    可以设置某位、某些位,表示自己做了什么事

    可以等待某位、某些位,表示要等等其他任务

    期望的时间发生后,xEventGroupSync()才会成功返回。

  xEventGroupSync成功返回后,会清除事件。

xEventGroupSync函数原型如下:

参数列表如下:

8. 任务通知函数

8.1 发出/取出通知

任务通知有2套函数,简化版、专业版,列表如下:

    简化版函数的使用比较简单,它实际上也是使用专业版函数实现的

    专业版函数支持很多参数,可以实现很多功能

8.2 xTaskNotifyGive/ulTaskNotifyTake

在任务中使用xTaskNotifyGive函数,在ISR中使用vTaskNotifyGiveFromISR函数,都是直接给其他任务发送通知:

1.使得通知值加一

2.并使得通知状态变为"pending",也就是taskNOTIFICATION_RECEIVED,表示有数据了、待处理

3.可以使用ulTaskNotifyTake函数来取出通知值:

4.如果通知值等于0,则阻塞(可以指定超时时间)

5.当通知值大于0时,任务从阻塞态进入就绪态

6.在ulTaskNotifyTake返回之前,还可以做些清理工作:把通知值减一,或者把通知值清零

使用ulTaskNotifyTake函数可以实现轻量级的、高效的二进制信号量、计数型信号量。

这几个函数的原型如下:

xTaskNotifyGive函数的参数说明如下:

vTaskNotifyGiveFromISR函数的参数说明如下:

ulTaskNotifyTake函数的参数说明如下:

8.3 xTaskNotify/xTaskNotifyWait

xTaskNotify 函数功能更强大,可以使用不同参数实现各类功能,比如:

1.让接收任务的通知值加一:这时xTaskNotify()等同于xTaskNotifyGive()

2.设置接收任务的通知值的某一位、某些位,这就是一个轻量级的、更高效的事件组

3.把一个新值写入接收任务的通知值:上一次的通知值被读走后,写入才成功。这就是轻量级的、长度为1的队列

4.用一个新值覆盖接收任务的通知值:无论上一次的通知值是否被读走,覆盖都成功。类似xQueueOverwrite()函数,这就是轻量级的邮箱。


xTaskNotify()xTaskNotifyGive()更灵活、强大,使用上也就更复杂。xTaskNotifyFromISR()是它对应的ISR版本。

这两个函数用来发出任务通知,使用哪个函数来取出任务通知呢?

使用xTaskNotifyWait()函数!它比ulTaskNotifyTake()更复杂:

1.可以让任务等待(可以加上超时时间),等到任务状态为"pending"(也就是有数据)

2.还可以在函数进入、退出时,清除通知值的指定位


这几个函数的原型如下:

xTaskNotify函数的参数说明如下:

eNotifyAction参数说明:

9. 软件定时器

9.1 创建定时器

要使用定时器,需要先创建它,得到它的句柄。

有两种方法创建定时器:动态分配内存、静态分配内存。函数原型如下:

回调函数的类型是:

9.2 删除定时器

动态分配的定时器,不再需要时可以删除掉以回收内存。删除函数原型如下:

定时器的很多API函数,都是通过发送"命令"到命令队列,由守护任务来实现。

如果队列满了,"命令"就无法即刻写入队列。我们可以指定一个超时时间xTicksToWait,等待一会。

9.3 启动/停止定时器

启动定时器就是设置它的状态为运行态(Running、Active)。

停止定时器就是设置它的状态为冬眠(Dormant),让它不能运行。

涉及的函数原型如下:

    注意,这些函数的xTicksToWait表示的是,把命令写入命令队列的超时时间。命令队列可能已经满了,无法马上把命令写入队列里,可以等待一会。

    xTicksToWait不是定时器本身的超时时间,不是定时器本身的"周期"。

    创建定时器时,设置了它的周期(period)。xTimerStart()函数是用来启动定时器。假设调用xTimerStart()的时刻是tX,定时器的周期是n,那么在tX+n时刻定时器的回调函数被调用。

    如果定时器已经被启动,但是它的函数尚未被执行,再次执行xTimerStart()函数相当于执行xTimerReset(),重新设定它的启动时间。

9.4 复位定时器

    从定时器的状态转换图可以知道,使用xTimerReset()函数可以让定时器的状态从冬眠态转换为运行态,相当于使用xTimerStart()函数。

    如果定时器已经处于运行态,使用xTimerReset()函数就相当于重新确定超时时间。假设调用xTimerReset()的时刻是tX,定时器的周期是n,那么tX+n就是重新确定的超时时间。

复位函数的原型如下:

9.5 修改定时器周期xTimerChangePeriod

    从定时器的状态转换图可以知道,使用xTimerChangePeriod()函数,处理能修改它的周期外,还可以让定时器的状态从冬眠态转换为运行态。

    修改定时器的周期时,会使用新的周期重新计算它的超时时间。假设调用xTimerChangePeriod()函数的时间tX,新的周期是n,则tX+n就是新的超时时间。

相关函数的原型如下:

9.6 获取/设置定时器ID

定时器的结构体如下,里面有一项pvTimerID,它就是定时器ID:

怎么使用定时器ID,完全由程序来决定:

1.可以用来标记定时器,表示自己是什么定时器

2.可以用来保存参数,给回调函数使用

10. 资源管理

10.1 屏蔽中断

屏蔽中断有两套宏:任务中使用、ISR中使用:

1.任务中使用:taskENTER_CRITICA()/taskEXIT_CRITICAL()

2.ISR中使用:taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR()

10.1.1 在任务中屏蔽中断

在任务中屏蔽中断的示例代码如下:

任务调度依赖于中断、依赖于API函数,所以:这两段代码之间,不会有任务调度产生。

10.1.2 在ISR中屏蔽中断

要使用含有"FROM_ISR"后缀的宏,示例代码如下:

10.2 暂停/恢复调度器

    如果有别的任务来跟你竞争临界资源,你可以把中断关掉:这当然可以禁止别的任务运行,但是这代价太大了。它会影响到中断的处理。

    如果只是禁止别的任务来跟你竞争,不需要关中断,暂停调度器就可以了:在这期间,中断还是可以发生、处理。

示例代码如下:


FreeRTOS函数功能总结的评论 (共 条)

分享到微博请遵守国家法律