FreeRTOS函数功能总结
1. 任务相关函数
1.1 创建任务xTaskCreate

1.2 删除任务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 暂停/恢复调度器
如果有别的任务来跟你竞争临界资源,你可以把中断关掉:这当然可以禁止别的任务运行,但是这代价太大了。它会影响到中断的处理。
如果只是禁止别的任务来跟你竞争,不需要关中断,暂停调度器就可以了:在这期间,中断还是可以发生、处理。
示例代码如下: