文件说明:
该文件主要根据FreeRTOS系统的功能,对FreeRTOS系统应用API函数进行项目开发进行指导和快速阅览,方便用户通过该文件快速使用FreeRTOS的内部资源来进行项目开发。其中涉及任务、时间管理、队列、信号量、定时器、内存管理、任务通知等多种功能的使用指导。学习FreeRTOS,一定要搞懂它的编程风格,下面将就FreeRTOS系统里面的数据类型、变量名、函数名和宏这几个方面做简单介绍。
1、数据类型
FreeRTOS中的数据类型都是标准C里面的数据类型,只不过对他们进行了重定义;在FreeRTOS中不使用int型,只使用short和long型,在Cortex-M内核的MCU中,short为16位,long为32位。FreeRTOS 中详细的数据类型重定义在 portmacro.h 这个头文件中实现,常用的有如下几个: _____________________________________________________________________________
新定义的数据类型 | 实际的数据类型(C标准类型)
portCHAR | char【在 FreeRTOS 中,我们都需要明确的指定变量 char
是有符号的还是无符号的】
portSHORT | short
portLONG | long
portTickType | unsigned short int/unsigned int【取决于
宏 configUSE_16_BIT_TICKS】
portBASE_TYPE | long【根据处理器的架构来决定是多少位的,
如果是32/16/8bit 的处理器则是32/16/8bit的数据类型】
____________________________________________________________________________
2、变量名
在FreeRTOS中,定义变量的时候往往会把变量的类型当作前缀加在变量上,这样的好处是让用户一看到这个变量就知道该变量的类型。比如char型变量的前缀是c,short型变量的前缀是 s,long 型变量的前缀是 l, portBASE_TYPE类型变量的前缀是 x。还有其他的数据类型,比如数据结构,任务句柄,队列句柄等定义的变量名的前缀也是 x。
如果一个变量是无符号型的那么会有一个前缀 u,如果是一个指针变量则会有一个前缀 p。因此,当我们定义一个无符号的 char 型变量的时候会加一个 uc 前缀,当定义一个char 型的指针变量的时候会有一个 pc 前缀。
3、函数名
函数名包含了函数返回值的类型、函数所在的文件名和函数的功能,如果是私有的函数则会加一个 prv(private)的前缀。举例说明一下: _____________________________________________________________________________
vTaskPrioritySet() | 函数的返回值为 void 型,在 task.c这个文件中定义。
xQueueReceive() | 返回值为 portBASE_TYPE型,在 queue.c 文件中定义。
vSemaphoreCreateBinary() | 函数的返回值为 void 型,在 semphr.h 这个文件中定义。 _____________________________________________________________________________
4、宏定义
宏均是由大写字母表示,并配有小写字母的前缀,前缀用于表示该宏在哪个头文件定义,举例说明一下: _____________________________________________________________________________
前缀 | 宏定义的文件
port (举例, portMAX_DELAY) | portable.h
task (举例, taskENTER_CRITICAL()) | task.h
pd (举例, pdTRUE) | projdefs.h
config(举例, configUSE_PREEMPTION) | FreeRTOSConfig.h
err (举例, errQUEUE_FULL) | projdefs.h _____________________________________________________________________________
(注意:信号量的函数都是一个宏定义,但是它的函数的命名方法是遵循函数的命名方法而不是宏定义的方法。) _____________________________________________________________________________
宏 | 实际值
pdTRUE | 1
pdFALSE | 0
pdPASS | 1
pdFAIL | 0 _____________________________________________________________________________
一、临界段代码保护
基本介绍:
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。
函数介绍:
任务级临界段代码:taskENTER_CRITICAL()和 taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临界段,一个是退出临界段,这两个函数是成对使用的;
中断级临界段代码:
taskENTER_CRITICAL_FROM_ISR()和 taskEXIT_CRITICAL_FROM_ISR()中断级别临界段代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY【超出管控范围】
二、中断开启与关闭
基本介绍:
FRTOS可通过configLIBRARY_LOWEST_INTERRUPT_PRIORITY和configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY管理STM32的系统中断,高于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY的中断优先级不能被FRTOS系统所管理(不能屏蔽),在两个定义宏之间的裸机中断可以被RTOS系统管理(可以开启或者关闭).
使用条件:systick中断优先级配置为最高,本次函数在FRTOS库中被频繁调用用于临界代码保护处理,也可用户自己调用
函数原型:
portDISABLE_INTERRUPTS():关闭中断【可关闭所有优先级低于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY的裸机中断】
portENABLE_INTERRUPTS() :开启中断【可开启所有优先级低于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY的裸机中断】
三、任务创建与删除
基本介绍:
RTOS系统的核心就任务管理,RTOS调度器决定具体运行哪个任务,RTOS调度器必须确保当一个任务开始执行的时候其上下文环境(寄存器值,堆栈内容等)和任务上一次退出的时候相同,这就要求:每个任务都必须有个堆栈,当任务切换的时候将上下文环境保存在堆栈中,这样当任务再次执行的时候就可以从堆栈中取出上下文环境,任务恢复运行。创建任务时,任务需要RAM来保存与任务有关的状态信息(任务控制块),任务也需要一定的 RAM 来作为任务堆栈,在进行动态任务创建时,所需的RAM就会自动的从FreeRTOS 的堆中分配,因此必须提供内存管理文件(heap_4.c)。
任务状态:
运行态->正在使用处理器运行的任务
就绪态->已经准备就绪(这些任务没有被阻塞或者挂起),可以运行的任务(等待同级/高优先级任务运行结束,进入运行态)
阻塞态->一个任务当前正在等待某个外部事件的话就说它处于阻塞态,如调用延时函数、等待队列、信号量、事件组、通知或互斥信号量(阻塞态有一个超时时间,超过退出)
挂起态->任务进入挂起态以后也不能被调度器调用进入运行态,挂起态的任务没有超时时间
任务等级:
每个任务都可以分配一个从0~(configMAX_PRIORITIES-1)的优先级 ,优先级数字越低表示任务的优先级越低,空闲任务的优先级最低,为0。
任务要素:
任务函数、任务控制块、任务堆栈为任务的三要素,具体功能如下介绍:
任务函数 :
由用户编写真正执行的任务内容;没有返回值,死循环(要跳出循环要调用任务删除函数),要有引发调度的函数
任务控制块:
主要用来存放任务属性信息,在使用动态任务创建函数 xTaskCreate()创建任务的时候会自动的给每个任务分配一个任务控制块
任务堆栈 :
每个任务都必须有任务堆栈,用来保护现场,保证任务能恢复运行,调用 xTaskCreate()任务堆栈自动创建;使用xTaskCreateStatic()需要用户自建任务堆栈,任务堆栈的类型为 StackType_t 类型,4个字节为单位。
任务函数:
1、动态任务创建函数xTaxkCreate()
简要说明:通过该函数创建任务,由系统自动分配任务控制块和堆栈,新创建的认任务默认就是就绪态,不管在任务调度器启动前还是启动后,都可以创建任务。
使用条件:宏configSUPPORT_DYNAMIC_ALLOCATION 必须为1
函数原型:BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, //任务函数
const char * const pcName, //任务名称【一般用于追踪和调试】
const uint16_t usStackDepth, //任务堆栈大小【*4】
void * const pvParameters, //传递给任务函数的参数【一般不NULL】
UBaseType_t uxPriority, //任务优先级
TaskHandle_t * const pxCreatedTask ) //任务句柄【用户定义,任务创建完成返回任务句柄->该句柄实际上就是任务堆栈,供其他函数使用】
函数返回值:pdPASS : 任务创建成功。
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY : 任务创建失败,因为堆内存不足!
2、静态任务创建函数xTaskCreateStatic()
简要说明:此函数也是用来创建任务,但创建的任务需要的RAM需要用户来提供:包括任务控制块、任务堆栈。
使用条件:宏configSUPPORT_STATIC_ALLOCATION 定义为 1
函数原型:TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, //任务函数
const char * const pcName, //任务名称
const uint32_t ulStackDepth, //任务堆栈大小【用户定义的任务堆栈数组的大小】
void * const pvParameters, //传递给任务函数的参数【一般不用NULL】
UBaseType_t uxPriority, //任务优先级
StackType_t * const puxStackBuffer, //任务堆栈【一般为数组,数组类型要为 StackType_t 类型】
StaticTask_t * const pxTaskBuffer ) //任务控制块
函数返回值:NULL : 任务创建失败,puxStackBuffer 或 pxTaskBuffer 为 NULL 的时候会导致这个错误的发生。
其他值: 任务创建成功,返回任务的任务句柄。
3、任务删除函数 vTaskDelete()
简要说明:该函数用来删除任务,被删除的任务将不再存在,也就是不会进入运行态,任务句柄也不能再使用。如果被删除的任务由动态方法创建,那么在此任务被删除以后此任务之前申请
的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数 vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。
只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数 pvPortMalloc()分配了 500
字节的内存,那么在此任务被删除以后用户也必须调用函数 vPortFree()将这 500 字节的内存释放掉,否则会导致内存泄露。
使用条件:默认正常使用
函数原型:vTaskDelete( TaskHandle_t xTaskToDelete ) //要删除的任务的任务句柄
函数返回值:无
4、任务挂起函数 vTaskSuspend()
简要说明:任务挂起用于暂停某一个任务,过一段时间再重新运行该任务;此函数用于将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用
任务恢复函数 vTaskResume()或 xTaskResumeFromISR()
使用条件:默认正常使用
函数原型:void vTaskSuspend( TaskHandle_t xTaskToSuspend) //要挂起的任务的任务句柄
函数返回值:无
5、任务恢复函数 vTaskResume()
简要说明:将一个任务从挂起态恢复到就绪态,只有通过函数 vTaskSuspend()设置为挂起态的任务才可以使用 vTaskRexume()恢复!
使用条件:默认正常使用
函数原型:void vTaskResume( TaskHandle_t xTaskToResume) //要恢复的任务的任务句柄
函数返回值:无
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume) //要恢复的任务的任务句柄【vTaskResume()的中断版本,用于在中断服务函数(FRTOS可管理的中断)
中恢复一个任务】
函数返回值: pdTRUE : 恢复运行的任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数以后必须进行一次上下文切换。
pdFALSE: 恢复运行的任务的任务优先级低于当前正在运行的任务(被中断打断的任务),这意味着在退出中断服务函数的以后不需要进行上下文切换。
6、列表与列表项【待补充】
简要说明:列表是FreeRTOS中的一个数据结构,概念上和链表有点类似,列表被用来跟踪FreeRTOS中的任务。与列表相关的全部东西都在文件list.c和list.h中。列表项就是存放在列表中的项
目,FreeRTOS 提供了两种列表项:列表项和迷你列表项。
使用条件:
结构体 :1.列表结构体
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE (1)用来检测列表完整性【一般用不到】
configLIST_VOLATILE UBaseType_t uxNumberOfItems; (2)用来记录列表中列表项的数量
ListItem_t * configLIST_VOLATILE pxIndex; (3)用来记录当前列表项索引号,用于遍历列表
MiniListItem_t xListEnd; (4)列表中最后一个列表项,用来表示列表结束,此变量类型为 MiniListItem_t,这是一个迷你列表项
listSECOND_LIST_INTEGRITY_CHECK_VALUE (5)用来检测列表完整性【一般用不到】
} List_t;
2.列表项结构体
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)用来检测列表项完整性【一般用不到】
configLIST_VOLATILE TickType_t xItemValue; (2)列表项值
struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)指向下一个列表项
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)指向前一个列表项,和 pxNext 配合起来实现类似双向链表的功能
void * pvOwner; (5) 记录此链表项归谁拥有,通常是任务控制块
void * configLIST_VOLATILE pvContainer; (6)用来记录此列表项归哪个列表
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE (7)用来检测列表项完整性【一般用不到】
};typedef struct xLIST_ITEM ListItem_t;
3.迷你列表项
struct xMINI_LIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)用来检测列表项完整性【一般用不到】
configLIST_VOLATILE TickType_t xItemValue; (2)列表项值
struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)指向下一个列表项
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)指向上一个列表项
};typedef struct xMINI_LIST_ITEM MiniListItem_t;
函数原型:
7、基础辅助任务函数
简要说明:FreeRTOS 还有很多与任务相关的 API 函数,不过这些 API函数大多都是辅助函数了。
使用条件:使用这些函数需要将相应的宏定义配置开启
函数原型:1.任务相关函数
uxTaskPriorityGet(TaskHandle_t xTask) //查询某个任务的优先级【入口参数为:任务句柄】
vTaskPrioritySet(TaskHandle_t xTask,UBaseType_t uxNewPriority) //改变某个任务的任务优先级【入口参数为:任务句柄,任务优先级】
xTaskGetApplicationTaskTag(TaskHandle_t xTask) //获取某个任务的标签(Tag)值【入口参数为:任务句柄】
xTaskGetCurrentTaskHandle(void) //获取当前正在运行的任务的任务句柄
xTaskGetHandle(const char * pcNameToQuery) //根据任务名字查找某个任务的句柄【入口参数为:任务名称】
xTaskGetIdleTaskHandle(void) //获取空闲任务的任务句柄【入口参数为:空】
uxTaskGetStackHighWaterMark(TaskHandle_t xTask) //获取任务的堆栈的历史剩余最小值,FreeRTOS中叫做“高水位线”【入口参数为:任务句柄】
eTaskGetState(TaskHandle_t xTask) //获取某个任务的运行壮态,这个壮态是 eTaskState 类型【入口参数为:任务句柄】
pcTaskGetName(TaskHandle_t xTaskToQuery ) //获取某个任务的任务名字【入口参数为:任务名称】
xTaskGetTickCount(void) //获取系统时间计数器值【使用要考虑到溢出】
xTaskGetTickCountFromISR(void) //在中断服务函数中获取时间计数器值【要考虑到溢出】
xTaskGetSchedulerState(void) //获取任务调度器的壮态,开启或未开启【入口参数为:空】
uxTaskGetNumberOfTasks(void) //获取当前系统中存在的任务数量【入口参数为:空】
2.辅助处理函数
vPortFree(StatusArray); //释放内存
8、任务状态函数
简要说明:用于任务运行状态的查询,包括任务的任何信息,主要函数包含:uxTaskGetSystemState()、vTaskGetInfo()、eTaskGetState()和 vTaskList()
使用条件:使用这些函数需要将相应的宏定义配置开启
结构体 :
1.任务状态结构体
typedef struct xTASK_STATUS
{
TaskHandle_t xHandle; //任务句柄
const char * pcTaskName; //任务名字
UBaseType_t xTaskNumber; //任务编号
eTaskState eCurrentState; //当前任务壮态,eTaskState 是一个枚举类型
UBaseType_t uxCurrentPriority; //任务当前的优先级
UBaseType_t uxBasePriority; //任务基础优先级
uint32_t ulRunTimeCounter; //任务运行的总时间
StackType_t * pxStackBase; //堆栈基地址
uint16_t usStackHighWaterMark; //从任务创建以来任务堆栈剩余的最小大小,此值如果太小的话说明堆栈有溢出的风险。
} TaskStatus_t;
2.任务状态结构体
typedef enum
{
eRunning = 0, //运行壮态
eReady, //就绪态
eBlocked, //阻塞态
eSuspended, //挂起态
eDeleted, //任务被删除
eInvalid //无效
} eTaskState;
函数原型:1.任务状态获取函数
uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray, //任务状态结构体
const UBaseType_t uxArraySize, //任务壮态数组的数组的大小
uint32_t * const pulTotalRunTime) //保存系统总的运行时间
函数返回值: 统计到的任务壮态的个数
2.任务信息获取函数
vTaskGetInfo(TaskHandle_t xTask, //要查找的任务的任务句柄
TaskStatus_t * pxTaskStatus, //指向类型为 TaskStatus_t 的结构体变量
BaseType_t xGetFreeStackSpace, //任务堆栈剩余的历史最小大小
eTaskState eState ) //保存任务运行壮态
函数返回值: 无
9、任务运行时间函数
简要说明:用于进行系统任务运行时间统计,包括任务占用的运行时间百分比,任务已经运行的时间长度
使用条件:使用这些函数需要将相应的宏定义配置开启
函数原型:vTaskList(char * pcWriteBuffer) //以一种表格的形式输出当前系统中所有任务的详细信息【包含:任务名,状态,优先级,高位线,编号】
vTaskGetRunTimeStats(char *pcWriteBuffer) //获取每个任务的运行时间,将统计到的信息填充到一个表里面【入口参数:保存时间信息的存储区】
10、消息队列函数
简要说明:FRTOS队列主要用于完成任务与任务,或者任务与中断之间进行信息传达的桥梁,在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,在FRTOS中可以使用队列
替代该传统方式。
通常队列采用先进先出(FIFO)的存储缓冲机制,FreeRTOS 中的队列也提供了 LIFO 的存储缓冲机制;FreeRTOS中使用队列传递消息的话虽然使用的是数据拷贝,但是也可以使用引用来
传递消息【直接往队列中发送指向这个消息的地址指针就可以了】
队列支持多任务访问,队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。
当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间【阻塞时间选择:之间走,等待指定时间,一
直等待】;同样消息队列进行入队操作时,也有入队阻塞时间设置【当队列满时有效,队列不满时直接入队】。
使用条件:使用这些函数需要将相应的宏定义配置开启
函数原型:1.动态创建消息队列函数【队列内存由FRTOS系统自动分配】
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, //要创建的队列的队列长度,这里是队列的项目数
UBaseType_t uxItemSize) //队列中每个项目(消息)的长度,单位为字节
函数返回值: 其他值: 队列创捷成功以后返回的队列句柄!
NULL : 队列创建失败
2.静态创建消息队列函数【队列内存由用户自行分配】
QueueHandle_t xQueueCreateStatic(UBaseType_t uxQueueLength, //要创建的队列的队列长度,这里是队列的项目数
UBaseType_t uxItemSize, //队列中每个项目(消息)的长度,单位为字节
uint8_t * pucQueueStorageBuffer,//指向队列项目的存储区,需要用户自行分配;必须指向uint8_t类型的数组,大小
//(uxQueueLength * uxItemsSize)字节
StaticQueue_t * pxQueueBuffer) //此参数指向一个 StaticQueue_t 类型的变量,用来保存队列结构体
函数返回值: 其他值: 队列创捷成功以后返回的队列句柄!
NULL : 队列创建失败
3.入队函数【分在 任务函数中/在中断服务函数中 使用】
(1)任务级发送消息到队列尾部(后向入队)
BaseType_t xQueueSendToBack(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列发送数据
const void* pvItemToQueue, //指向要发送的消息,发送时候会将这个消息拷贝到队列中
TickType_t xTicksToWait); //阻塞时间【0,指定时间, portMAX_DELAY(INCLUDE_vTaskSuspend必须为1)】
函数返回值: pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
(2)任务级发送消息到队列头(前向入队)
BaseType_t xQueueSendToToFront(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列发送数据
const void *pvItemToQueue, //指向要发送的消息,发送时候会将这个消息拷贝到队列中
TickType_t xTicksToWait); //阻塞时间【0,指定时间, portMAX_DELAY(INCLUDE_vTaskSuspend必须为1)】
函数返回值: pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
(3)任务级发送消息到队列,带覆写功能,当队列满了以后自动覆盖掉旧的消息【这个函数常用于向那些长度为 1 的队列发送消息】
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列发送数据
const void * pvItemToQueue); //指向要发送的消息,发送时候会将这个消息拷贝到队列中
函数返回值: pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
(4)中断级发送消息到队列尾部(后向入队)
BaseType_t xQueueSendToBackFromISR(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列发送数据
const void * pvItemToQueue, //指向要发送的消息,发送时候会将这个消息拷贝到队列中
BaseType_t * pxHigherPriorityTaskWoken);//标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
(5)中断级发送消息到队列头(前向入队)
BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列发送数据
const void * pvItemToQueue, //指向要发送的消息,发送时候会将这个消息拷贝到队列中
BaseType_t * pxHigherPriorityTaskWoken);//标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
(6)中断级发送消息到队列,带覆写功能,当队列满了以后自动覆盖掉旧的消息【这个函数常用于向那些长度为 1 的队列发送消息】
BaseType_t xQueueOverwriteFromISR(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列发送数据
const void * pvItemToQueue, //指向要发送的消息,发送时候会将这个消息拷贝到队列中
BaseType_t * pxHigherPriorityTaskWoken);//标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdPASS: 向队列发送消息成功!
errQUEUE_FULL: 队列已经满了,消息发送失败。
3.出队函数【分在 任务函数中/在中断服务函数中 使用】
(1)任务级读取队列项(消息)【读完后,删除该队列项的内容(消息)】
BaseType_t xQueueReceive(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列读取数据【读取长度为每个队列项的长度】
void * pvBuffer, //读取消息保存缓存区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
TickType_t xTicksToWait); //阻塞时间【0,指定时间, portMAX_DELAY(INCLUDE_vTaskSuspend必须为1)】
函数返回值: pdTRUE: 从队列中读取数据成功
pdFALSE: 从队列中读取数据失败
(2)任务级读取队列项(消息)【读完后,不删除该队列项的内容(消息)】
BaseType_t xQueuePeek(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列读取数据【读取长度为每个队列项的长度】
void * pvBuffer, //读取消息保存缓存区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
TickType_t xTicksToWait); //阻塞时间【0,指定时间, portMAX_DELAY(INCLUDE_vTaskSuspend必须为1)】
函数返回值: pdTRUE: 从队列中读取数据成功
pdFALSE: 从队列中读取数据失败
(3)中断级读取队列项(消息)【读完后,删除该队列项的内容(消息)】
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列读取数据【读取长度为每个队列项的长度】
void* pvBuffer, //读取消息保存缓存区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
BaseType_t * pxTaskWoken); //标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdTRUE: 从队列中读取数据成功
pdFALSE: 从队列中读取数据失败
(4)中断级读取队列项(消息)【读完后,不删除该队列项的内容(消息)】
BaseType_t xQueuePeekFromISR(QueueHandle_t xQueue, //队列句柄,指明要向哪个队列读取数据【读取长度为每个队列项的长度】
void * pvBuffer) //读取消息保存缓存区,读取队列的过程中会将读取到的数据拷贝到这个缓冲区
函数返回值: pdTRUE: 从队列中读取数据成功
pdFALSE: 从队列中读取数据失败
11、信号量函数【二值信号量,计数型信号量,互斥型信号量(为避免二值信号量优先级反转而设计的二值信号量)】
简要说明:信号量常用于控制对共享资源的访问和任务同步。信号量用于控制共享资源访问的场景相当于一个上锁机制,另一个重要的应用场合就是任务同步,用于任务与任务或中断与任务之间
的同步。信号量可代替裸机代码中断中修改标记,其他地方根据标记进行具体处理的功能。
(1)二值信号量
二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号
另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问。
和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一
个信号量上的话那么优先级最高的那个任务优先获得信号量,这样当信号量有效的时候高优先级的任务就会解除阻塞状态。
(2)计数型信号量
二值信号量相当于长度为 1 的队列,那么计数型信号量就是长度大于 1 的队列。同二值信号量一样,用户不需要关心队列中存储了什么数据,只需要关心队列是否为空即可。二值信
号量可用于事件计数和资源管理。在事件计数中,每次事件发生的时候就在事件处理函数中释放信号量(增加信号量的计数值),其他任务会获取信号量(信号量计数值减一)来处理事件。
在资源管理中,信号量值代表当前资源的可用数量,比如停车场当前剩余的停车位数量。一个任务要想获得资源的使用权,首先必须获取信号量,信号量获取成功以后信号量值就会减
一。当信号量值为 0 的时候说明没有资源了。当一个任务使用完资源以后一定要释放信号量,释放信号量以后信号量值会加一。
(3)互斥信号量
优先级反转,指在使用信号量的时候,低优先级任务占用信号量,高优先级任务在等待信号量释放,而低优先级任务此时被中优先级任务抢占,导致中优先级任务优先执行完,再运行完
低优先级任务,释放二值信号量后才被高优先级任务获取信号量,最后才执行高优先级任务的现象,工程开发中一定要避免优先级反转现象的发生。
互斥信号量可以说是特殊的二值信号量,主要为了降低优先级反转现象的发生,在高优先级任务等待互斥信号量时,会将此时占用信号量的低优先级任务提高到与其同等优先级,以此减
小优先级反转现象的发生;需要注意的是互斥信号量不能在中断中使用。
(4)递归信号量【使用较少】
递归互斥信号量可以看作是一个特殊的互斥信号量,已经获取了互斥信号量的任务就不能再次获取这个互斥信号量,但是递归互斥信号量不同,已经获取了递归互斥信号量的任务可以
再次获取这个递归互斥信号量,而且次数不限!
一个任务使用函数 xSemaphoreTakeRecursive()成功的获取了多少次递归互斥信号量就得使用函数 xSemaphoreGiveRecursive()释放多少次!比如某个任务成功的获取了 5 次递归信号
量,那么这个任务也得同样的释放 5 次递归信号量。
使用条件:使用这些函数需要将相应的宏定义配置开启
函数原型:(1)动态创建二值信号量函数
SemaphoreHandle_t xSemaphoreCreateBinary( void ) //无入口参数
函数返回值: NULL : 二值信号量创建失败
其他值: 创建成功的二值信号量的句柄
(2)任务级释放信号量函数【用于释放二值信号量、计数型信号量或互斥信号量】
BaseType_t xSemaphoreGive( xSemaphore ) //要释放的信号量句柄
函数返回值: pdPASS : 释放信号量成功
errQUEUE_FULL : 释放信号量失败
(3)中断级释放信号量函数【只能用来释放二值信号量和计数型信号量】
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, //要释放的信号量句柄
BaseType_t * pxHigherPriorityTaskWoken) //标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdPASS : 释放信号量成功
errQUEUE_FULL : 释放信号量失败
(4)任务级获取信号量函数【用于获取二值信号量、计数型信号量和互斥信号量】
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, //要获取的信号量句柄
TickType_t xBlockTime) //阻塞时间【阻塞时间为 0 的话就立即返回 errQUEUE_EMPTY,不为0则将任务添加到延时列表中】
函数返回值: pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败
(5)中断级获取信号量函数【用于获取二值信号量和计数型信号量】
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore, //要获取的信号量句柄
BaseType_t * pxHigherPriorityTaskWoken) //标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败
(6)动态创建计数型信号量
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, //计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
UBaseType_t uxInitialCount ) //计数信号量初始值
函数返回值: NULL : 计数信号量创建失败
其他值: 创建成功的二值信号量的句柄
(7)动态创建互斥型信号量函数【具有优先级继承的二值信号量,但是互斥信号量不能在中断中使用】
SemaphoreHandle_t xSemaphoreCreateMutex( void ) //无入口参数
函数返回值: NULL : 互斥信号量创建失败
其他值: 创建成功的互斥信号量的句柄
12、软件定时器函数
简要说明:软件定时器允许设置一段时间,当设置的时间到达之后就执行指定的功能函数,被定时器调用的这个功能函数叫做定时器的回调函数。回调函数的两次执行间隔叫做定时器的定时周期,
简而言之,当定时器的定时周期到了以后就会执行回调函数。
定时器服务任务 :定时器是一个可选的、不属于 FreeRTOS 内核的功能,它是由定时器服务(或 Daemon)任务来提供的;
定时器命令队列 :FreeRTOS 提供了很多定时器有关的 API 函数,这些 API 函数大多都使用 FreeRTOS的队列发送命令给定时器服务任务,这个队列叫做定时器命令队列;
软件定时器分两种:单次定时器和周期定时器,单次定时器的话定时器回调函数就执行一次,比如定时 1s,当定时时间到了以后就会执行一次回调函数,然后定时器就会停止运行;单
次定时器可以手动重新启动,不能自动启动。周期定时器一旦启动以后就会在执行完回调函数以后自动的重新启动,这样回调函数就会周期性的执行。
复位软件定时器 :在定时器等待定时时间到的过程中,复位软件定时器,之前的计时时间作废,重新进行周期时间进行计算;
使用条件:软件定时器的回调函数是在定时器服务任务中执行的,所以一定不能在回调函数中调用任何会阻塞任务的API函数!比如,定时器回调函数中千万不能调用vTaskDelay(),vTaskDelayUnti(),
还有一些访问队列或者信号量的非零阻塞时间的API函数也不能调用。使用软件定时器需要开启以下宏:
1.configUSE_TIMERS :设置为 1 的话定时器服务任务就会在启动 FreeRTOS 调度器的时候自动创建
2.configTIMER_TASK_PRIORITY :设置软件定时器服务任务的任务优先级,可以为 0~( configMAX_PRIORITIES-1)
3.configTIMER_QUEUE_LENGTH :此宏用来设置定时器命令队列的队列长度
4.configTIMER_TASK_STACK_DEPTH:此宏用来设置定时器服务任务的任务堆栈大小,单位为字,不是字节!对于 STM32 来说一个字是 4 字节
函数原型:(1)创建软件定时器【新创建的软件定时器,默认为休眠状态】
TimerHandle_t xTimerCreate( const char * const pcTimerName, //软件定时器名字,名字是一串字符串,用于调试使用
TickType_t xTimerPeriodInTicks, //软件定时器的定时器周期,单位是时钟节拍数【周期为500ms,设置:(500/ portTICK_PERIOD_MS)】
UBaseType_t uxAutoReload, //设置定时器模式,单次定时器【pdFALS】还是周期定时器【pdTRUE】
void * pvTimerID, //定时器ID号,在回调函数中根据定时器的ID号来处理不同的定时器
TimerCallbackFunction_t pxCallbackFunction ) //定时器回调函数,当定时器定时周期到了以后就会调用这个函数
函数返回值: NULL : 软件定时器创建失败
其他值: 创建成功的软件定时器句柄
(2)任务级开启软件定时器【用于在任务函数中开启软件定时器,如果定时器已经开启运行,再次开启则相当于复位了定时器】
BaseType_t xTimerStart( TimerHandle_t xTimer, //要开启的软件定时器的句柄
TickType_t xTicksToWait ) //设置阻塞时间
函数返回值: pdPASS: 软件定时器开启成功,其实就是命令发送成功
pdFAIL: 软件定时器开启失败,命令发送失败
(3)中断级开启软件定时器【用于在中断服务函数中开启软件定时器,如果定时器已经开启运行,再次开启则相当于复位了定时器】
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer, //要开启的软件定时器的句柄
BaseType_t * pxHigherPriorityTaskWoken) //标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdPASS: 软件定时器开启成功,其实就是命令发送成功
pdFAIL: 软件定时器开启失败,命令发送失败
(4)任务级停止软件定时器【用于在任务函数中停止软件定时器】
BaseType_t xTimerStop ( TimerHandle_t xTimer, //要停止的软件定时器的句柄
TickType_t xTicksToWait ) //设置阻塞时间
函数返回值: pdPASS: 软件定时器停止成功,其实就是命令发送成功
pdFAIL: 软件定时器停止失败,命令发送失败
(5)中断级停止软件定时器【用于在中断服务函数中停止软件定时器】
BaseType_t xTimerStop ( TimerHandle_t xTimer, //要停止的软件定时器的句柄
BaseType_t * pxHigherPriorityTaskWoken) //标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdPASS: 软件定时器停止成功,其实就是命令发送成功
pdFAIL: 软件定时器停止失败,命令发送失败
(6)任务级复位软件定时器【用于在任务函数中复位软件定时器】
BaseType_t xTimerReset( TimerHandle_t xTimer, //要复位的软件定时器的句柄
TickType_t xTicksToWait ) //设置阻塞时间
函数返回值: pdPASS: 软件定时器复位成功,其实就是命令发送成功
pdFAIL: 软件定时器复位失败,命令发送失败
(7)中断级复位软件定时器【用于在中断服务函数中复位软件定时器】
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer, //要复位的软件定时器的句柄
BaseType_t * pxHigherPriorityTaskWoken ) //标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdPASS: 软件定时器复位成功,其实就是命令发送成功
pdFAIL: 软件定时器复位失败,命令发送失败
12、事件标志组函数
简要说明:事件标志组可以实现任务与多个任务或者事件之间进行同步的功能(信号量只能进行单个同步)。事件标志组有几个定义,分别如下:
(1)事件位(事件标志)
事件位用来表明某个事件是否发生,事件位通常用作事件标志,一般当有数据或者信息需要处理时,将某个位(标志)置1,当队列中没有数据或者信息需要处理时,将某个位(标志)置0。
(2)事件组
一个事件组就是一组的事件位,事件组中的事件位通过位编号来访问。事件标志组的数据类型为 EventGroupHandle_t,当 configUSE_16_BIT_TICKS 为 1 的时候事件标志组可以存储
8 个事件位,当 configUSE_16_BIT_TICKS 为 0 的时候事件标志组存储 24个事件位。【对于 STM32 来说一个事件标志组最多可以存储 24 个事件位】
使用条件:使用这些函数需要将相应的宏定义配置开启
函数原型:(1)创建事件标志组【事件标志组可用的bit数取决于configUSE_16_BIT_TICKS,为1有8个事件位,为0有24个事件位】
EventGroupHandle_t xEventGroupCreate( void ) //无入口参数
函数返回值: NULL : 事件标志组创建失败
其他值: 创建成功的事件标志组句柄
(2)任务级设置事件位
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, //要操作的事件标志组的句柄
const EventBits_t uxBitsToClear ) //要清零的事件位,比如要清除 bit3 的话就设置为 0X08【一次可清除多个位】
函数返回值: 任何值:将指定事件位清零之前的事件组值
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, //要操作的事件标志组的句柄
const EventBits_t uxBitsToClear ) //要置位的事件位,比如要置位 bit3 的话就设置为 0X08【一次可置位多个位】
函数返回值: 任何值:在将指定事件位置 1 后的事件组值
(3)中断级设置事件位
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, //要操作的事件标志组的句柄
const EventBits_t uxBitsToSet ) //要清零的事件位,比如要清除 bit3 的话就设置为 0X08【一次可清除多个位】
函数返回值: pdPASS : 事件位清零成功
pdFALSE: 事件位清零失败
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, //要操作的事件标志组的句柄
const EventBits_t uxBitsToSet, //要置位的事件位,比如要置位 bit3 的话就设置为 0X08【一次可置位多个位】
BaseType_t * pxHigherPriorityTaskWoken )//标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户
只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行
一次任务切换。
函数返回值: pdPASS : 事件位置位1成功
pdFALSE: 事件位置位1失败
(4)任务级获取事件标志组值【用于在任务中获取当前事件标志组的值,也就是各个事件位的值】
EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup ) //要获取的事件标志组的句柄
函数返回值: 任何值:当前事件标志组的值
(5)中断级获取事件标志组值【用于在中断中获取当前事件标志组的值,也就是各个事件位的值】
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup ) //要获取的事件标志组的句柄
函数返回值: 任何值:当前事件标志组的值
(6)等待指定事件【某个任务可能需要与多个事件进行同步,那么这个任务就需要等待并判断多个事件位(标志)】
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, //指定要等待的事件标志组
const EventBits_t uxBitsToWaitFor, //指定要等待的事件位【比如要等待bit0和(或)bit2的时候此参数就是0X05】
const BaseType_t xClearOnExit, //函数执行完是否清除等待的事件位【pdTRUE的话清除,pdFALSE的话不清除】
const BaseType_t xWaitForAllBits, //事件并发性选择【pdTRUE的话等待标志全部为1才执行; pdFALSE的话等待事件标志有一个为1就执行】
const TickType_t xTicksToWait ) //设置阻塞时间,单位为节拍数
函数返回值: 任何值:返回当所等待的事件位置 1 以后的事件标志组的值(根据此值可知哪些事件位置1了),或者阻塞时间到(无意义)