1. FreeRTOS源码文件夹
名称 | 描述 | 是否必须 |
---|---|---|
include文件夹 | 内包含FreeRTOS的头文件 | √ |
portable文件夹 | 内包含FreeRTOS的移植文件 | √ |
croutine.c | 协程相关文件 | × |
event_groups.c | 事件相关文件 | × |
list.c | 列表相关文件 | √ |
queue.c | 队列相关文件 | √ |
stream_buffer.c | 流式缓冲区相关文件 | × |
tasks.c | 任务相关文件 | √ |
timers.c | 软件定时器相关文件 | × |
portable 文件夹是软件层面的东西,其目的是为了能够和硬件进行连接,是一个桥梁。对于我们使用MDK开发只需要其中的 Keil
RVDS
MemMang
就可以了,其他的文件暂时是不需要的
名称 | 描述 |
---|---|
Keil | 指向RVDS文件夹 |
RVDS | 不同内核芯片的移植文件 |
MemMang | 内存管理文件 |
3. FreeRTOS移植步骤
- 添加FreeRTOS源码:将FreeRTOS源码添加至基础工程、头文件路径等
- FreeRTOSConfig.h:添加FreeRTOSConfig.h配置文件
- 修改中断相关文件:修改Systick中断、SVC中断、PendSV中断
- 添加应用程序:验证移植是否成功
4. 详细移植步骤
-
将源码中的
Source
文件夹下的所有文件复制到模板中 -
删除
portable
文件夹中的除keil
MemMang
RVDS
的所有文件 -
将文件加载到
keil
中,在Keil中添加分组FreeRTOS_CORE
中有croutine.c
event_groups.c
list.c
queue.c
tasks.c
timers.c
FreeRTOS_PORTABLE
中有port.c
heap_4.c
-
移植源码中的
FreeRTOSConfig.h
-
修改
FreeRTOSConfig.h
文件中的时钟配置为#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__) #include <stdint.h> extern uint32_t SystemCoreClock; #endif
-
屏蔽掉
stm32f4xx_it.h
文件中的SVC_Handler
PendSV_Handler
SysTick_Handler
三个函数 -
将没有用到的钩子函数配置为
0
-
注释掉
#define xPortSysTickHandler SysTick_Handler
如果采用的是ST的标准库没有问题,但如果采用的是HAL库,由于HAL库需要SysTick中断才能稳定运行,所以不能采用宏定义,而是在stm32f4xx_it.c文件中的SysTick中断响应函数中调用xPortSysTickHandler函数。如10中的配置
-
在
stm32f4xx_it.h
中添加extern void xPortSysTickHandler(void); //systick 中断服务函数,使用 OS 时用到 void SysTick_Handler(void) { if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)//系统已经运行 { xPortSysTickHandler(); } HAL_IncTick(); }
5. FreeRTOS 相关api函数
5.1 任务的创建和删除API
API函数 | 描述 |
---|---|
xTaskCreate() | 动态方式创建任务 |
xTaskCreateStatic() | 静态方式创建任务 |
vTaskDelete() | 删除任务 |
5.1.1 动态创建任务API函数使用详解
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask
)
参数:
**pxTaskCode:**任务函数。
**pcName:**任务名字,一般用于追踪和调试,任务名字长度不能超过 configMAX_TASK_NAME_LEN
。
**usStackDepth:**任务堆栈大小,注意实际申请到的堆栈是 usStackDepth
的 4 倍。其中空闲任务的任务堆栈大小为 configMINIMAL_STACK_SIZE
。
**pvParameters:**传递给任务函数的参数。
**uxPriotiry:**任务优先级,范围 0~configMAX_PRIORITIES-1
。
**pxCreatedTask:**任务句柄,任务创建成功以后会返回此任务的任务句柄, 这个句柄其实就是任务的任务堆栈。 此参数就用来保存这个任务句柄。其他 API 函数可能会使用到这个句柄。
返回值:
**pdPASS:**任务创建成功。
**errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:**任务创建失败,因为堆内存不足!
5.1.2 动态创建任务流程
使用动态创建任务只需要三步:
- 将宏
configSUPPORT_DYNAMIC_ALLOCATION
配置为1
- 定义函数入口参数
- 编写任务函数
此函数创建的任务会立刻进入就绪态,由任务调度器调度运行。
动态创建任务函数内部实现:
- 申请堆栈内存&任务控制块内存
- TCB结构体成员赋值
- 添加新任务到就绪列表中
5.1.3 任务的删除API函数使用详解
void vTaskDelete (TaskHandle_t xTaskToDelete);
形参 | 描述 |
---|---|
vTaskDelete | 待删除任务的任务句柄 |
用于删除已被创建的任务,被删除的任务将从就绪态任务列表、阻塞态任务列表、挂起态任务列表和事件列表中移除
注意:
- 当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)
- 空闲任务会负责释放被删除任务中由系统分配的内存,但是由用户在任务删除前申请的内存,则需要由用户在任务被删除前提前释放,否则将导致内存泄露
如果在task1中删除task2是在当前任务task1中释放堆栈空间,如果是在task1中删除NULL也就是自身则是在空闲任务中释放
5.1.4 任务的删除流程
使用删除任务只需要两步:
- 使用删除任务函数,需将宏
INCLUDE_vTaskDelete
配置为1
- 入口参数输入需要删除的任务句柄(NULL代表删除本身)
删除任务函数内部实现:
- 获取所要删除任务的控制块:通过传入的任务句柄,判断所需要删除哪个任务,NULL代表删除自身
- 将被删除任务,移除所在列表:将该任务在所在列表中移除,包括:就绪阻塞、挂起、事件等列表
- 判断所需要删除的任务:删除任务自身,需先添加到等待删除列表,内存释放将在空闲任务执行;删除其他任务,释放内存,任务数量--
- 更新下个任务的阻塞时间:更新下一个任务的阻塞超时时间,以防被删除的任务就是下一个阻塞超时的任务
5.2 任务的挂起与恢复API函数
API函数 | 描述 |
---|---|
vTaskSuspend() | 挂起任务 |
vTaskResume() | 恢复被挂起的任务 |
xTaskResumeFromlSR() | 在中断中恢复被挂起的任务 |
挂起:挂起任务类似暂停,可恢复;删除任务,无法恢复,类似“人死两清“
恢复:恢复被挂起的任务
“FromlSR”:带FromISR后缀是在中断函数中专用的API函数
5.2.1 任务挂起API函数使用详解
void vTaskSuspend(TaskHandle_t xTaskToSuspend)
形参 | 描述 |
---|---|
xTaskToSuspend | 待挂起任务的任务句柄 |
此函数用于挂起任务,使用时需将宏 INCLUDE_vTaskSuspend
配置为1
无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复。
注意:当传入的参数为NULL,则代表挂起任务自身(当前正在运行的任务)
5.2.2 任务恢复API函数使用详解
void vTaskResume(TaskHandle_txTaskToResume)
形参 | 描述 |
---|---|
xTaskToResume | 待恢复任务的任务句柄 |
使用该函数注意宏:INCLUDE_vTaskSuspend
必须定义为 1
注意:任务无论被 vTaskSuspend()
挂起多少次,只需在任务中调用 vTakResume()
恢复一次,就可以继续运行。且被恢复的任务会进入就绪态
5.2.3 中断中的任务恢复API函数使用详解
BaseType_t xTaskResumeFromlSR(TaskHandle_t xTaskToResume)
形参 | 描述 |
---|---|
xTaskToResume | 待恢复任务的任务句柄 |
返回值 | 描述 |
---|---|
pdTRUE | 任务恢复后需要进行任务切换 |
pdFALSE | 任务恢复后不需要进行任务切换 |
使用该函数注意宏:INCLUDE_vTaskSuspend
和 INCLUDE_xTaskResumeFromISR
必须定义为 1
该函数专用于中断服务函数中,用于解挂被挂起任务
注意:中断服务程序中要调用freeRTOS的API函数则中断优先级不能高于FreeRTOS所管理的最高优先级
使用实时操作系统时的相关性
建议将所有优先级位指定为抢占优先级位,不留下任何优先级位作为子优先级位。任何其他配置都会使
configMAX_SYSCALL_INTERRUPT_PRIORITY
设置与分配给各个外设中断的优先级之间的直接关系复杂化。
大多数系统默认为所需的配置,但STM32驱动程序库明显例外。如果将STM32与STM32驱动程序库一起使用,则在启动RTOS之前,通过调用 NVIC_PriorityGroupConfig (NVIC_PriorityGroup_4) ;
来确保将所有优先级位分配为抢占优先级位。
使用实时操作系统时的相关性
以"FromlSR"结尾的FreeRTOS函数是中断安全的,但即使是这些函数也不能从逻辑优先级高于
configMAX_SYSCALL_INTERRUPT_PRIORITY
定义的优先级的中断中调用(configMAX_SYSCALL_INTERRUPT_PRIORITY
在 FreeRTOSConfig.h
头文件中定义)。因此,任何使用RTOS API函数的中断服务例程都必须将其优先级手动设置为数值上等于或大于 configMAX_SYSCALL_INTERRUPT_PRIORITY
设置的值。这可确保中断的逻辑优先级等于或小于
configMAX_SYSCALL_INTERRUPT_PRIORITY
设置。
Cortex-M中断默认为优先级值为零。零是可能的最高优先级值。因此,切勿将使用中断安全RTOS API的中断的优先级保留为其默认值。不能将中断优先级设置的比RTOS所能管理的中断优先级更高。
5.3 FreeRTOS中断管理
5.3.1 stm32 的中断管理
ARM Cortex-M使用了8位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器。但STM32,只用了中断优先级配置寄存器的高4位[7:4],所以STM32提供了最大16级的中断优先等级
STM32的中断优先级可以分为抢占优先级和子优先级:
- 抢占优先级:抢占优先级高的中断可以打断正在执行但抢占优先级低的中断
- 子优先级:当同时发生具有相同抢占优先级的两个中断时,子优先级数值小的优先执行
注意:在STM32中如果抢占优先级相同,子优先级低的中断正在进行运行,子优先级高的中断不会打断子优先级低的中断。
5.3.2 stm32 中断优先级分组设置
一共有5种分配方式,对应着中断优先级分组的5个组
优先级分组 | 抢占优先级 | 子优先级 | 优先级配置寄存器高4位 |
---|---|---|---|
NVIC_PriorityGroup_0 | 0级抢占优先级 | 0-15级子优先级 | 0bit用于抢占优先级 4bit用于子优先级 |
NVIC_PriorityGroup_1 | 0-1级抢占优先级 | 0-7级子优先级 | 1bit用于抢占优先级 3bit用于子优先级 |
NVIC_PriorityGroup_2 | 0-3级抢占优先级 | 0-3级子优先级 | 2bit用于抢占优先级 2bit用于子优先级 |
NVIC_PriorityGroup_3 | 0-7级抢占优先级 | 0-1级子优先级 | 3bit用于抢占优先级 1bit用于子优先级 |
NVIC_PriorityGroup_4 | 0-15级抢占优先级 | 0级子优先级 | 4bit用于抢占优先级 0bit用于子优先级 |
通过调用函数 HAL_NVIC_SetPriorityGrouping(NVIC PRIORITYGROUP_4);
即可完成设置在 HAL_Init
中设置
特点:
- 低于
configMAX_SYSCALL_INTERRUPT_PRIORITY
优先级的中断里才允许调用FreeRTOS的API函数 - 建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理
- 中断优先级数值越小越优先,任务优先级数值越大越优先
5.3.3 FreeRTOS 管理中断的源码
ARM汇编指令 | 功能 |
---|---|
MRS | 状态寄存器到通用寄存器的传送指令 |
MSR | 通用寄存器到状态寄存器的传送指令 |
DMB | 数据存储器隔离 DMB 指令保证: 仅当所有在它前面的存储器访问操作都执行完毕后,才提交在它后面的存储器访问操作 |
DSB | 数据同步隔离 比 DMB 严格: 仅当所有在它前面的存储器访问操作都执行完毕后,才执行在它后面的指令(亦即任何指令都要等待存储器访问操作) |
DSB | 指令同步隔离。 最严格:它会清洗流水线,以保证所有它前面的指令都执行完毕之后,才执行它后面的指令 |
-
关闭中断源码
BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断
关中断程序示例:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() static portFORCE_INLINE void vPortRaiseBASEPRI(void) { uint32_t ulNewBASEPRl = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { msr basepri, ulNewBASEPRI dsb isb } } #define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_JINTERRUPT_PRIORITY <<(8-configPRIO_BITS)) #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /*FreeRTOS可管理的最高中断优先级*/
-
开启中断源码
BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断
开中断程序示例:
#define portENABLE_INTERRUPTS () vPortsetBASEPRI(0) static portFORCE_INLINE void vPortSetBASEPRI (uint32_t ulBASEPRI) { __asm { msr basepri, ulBASEPRI } }
5.4 临界段代码保护API
临界区是直接屏蔽了中断,系统任务调度靠中断,ISR也靠中断
FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断
函数 | 描述 |
---|---|
taskENTER_CRITICAL() | 任务级进入临界段 |
taskEXIT_CRITICAL() | 任务级退出临界段 |
taskENTER_CRITICAL_FROM_ISR() | 中断级进入临界段 |
taskEXIT_CRITICAL_FROM_ISR() | 中断级退出临界段 |
任务级临界区调用格式示例:
taskENTER_CRITICAL();
{
..... /*临界区*/
}
taskEXIT_CRITICAL();
中断级临界区调用格式示例:
uint32_t save_status;
save_status = taskENTER_CRITICAL_FROM_ISR();
{
..... /*临界区*/
}
taskEXIT_CRITICAL_FROM_ISR(save_status);
特点:
- 成对使用
- 支持嵌套
- 尽量保持临界段耗时短
5.5 任务调度器的挂起与恢复API
挂起任务调度器,调用此函数不需要关闭中断
函数 | 描述 |
---|---|
vTaskSuspendAll() | 挂起任务调度器 |
xTaskResumeAll() | 恢复任务调度器 |
使用格式示例:
vTaskSuspendAll();
{
..... /*内容*/
}
xTaskResumeAll();
- 与临界区不一样的是,挂起任务调度器,未关闭中断;
- 它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应;
- 挂起调度器的方式,适用于临界区位于任务与任务之间;既不用去延时中断,又可以做到临界区的安全
5.6 任务状态查询API
函数 | 描述 |
---|---|
uxTaskPriorityGet() | 获取任务优先级 |
vTaskPrioritySet() | 设置任务优先级 |
uxTaskGetNumberOfTasks() | 获取系统中任务的数量 |
uxTaskGetSystemState() | 获取所有任务状态信息 |
vTaskGetInfo() | 获取指定单个的任务信息 |
xTaskGetCurrentTaskHandle() | 获取当前任务的任务句柄 |
xTaskGetHandle() | 根据任务名获取该任务的任务句柄 |
uxTaskGetStackHighWaterMark() | 获取任务的任务栈历史剩余最小值 |
eTaskGetState() | 获取任务状态 |
vTaskList() | 以“表格”形式获取所有任务的信息 |
vTaskGetRunTimeStats() | 获取任务的运行时间 |
5.6.1 获取任务优先级API使用详解
UBaseType_t uxTaskPriorityGet(const TaskHandle_t xTask)
此函数用于获取指定任务的任务优先级,使用该函数需将宏 INCLUDE_uxTaskPriorityGet
置 1
形参 | 描述 |
---|---|
xTask | 任务句柄,NULL代表任务自身 |
返回值 | 描述 |
---|---|
整数 | 任务优先级数值 |
5.6.2 设置任务优先级API使用详解
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority)
此函数用于改变某个任务的任务优先级,使用该函数需将宏 INCLUDE_vTaskPrioritySet
置 1
形参 | 描述 |
---|---|
xTask | 任务句柄,NULL代表任务自身 |
uxNewPriority | 需要设置的任务优先级 |
5.6.3 获取系统中任务的数量API使用详解
UBaseType_t uxTaskGetNumberOfTasks(void)
此函数用于获取系统中任务的任务数量
返回值 | 描述 |
---|---|
整数 | 系统中任务的数量 |
5.6.4 获取所有任务状态信息API使用详解
UBaseType_t uxTaskGetSystemState
(
TaskStatus_t* const pxTaskStatusArray,
const UBaseType_t uxArraySize,
configRUN_TIME_COUNTER_TYPE* const pulTotalRunTime
)
此函数用于获取系统中所有任务的任务状态信息,使用该函数需将宏 configUSE_TRACE_FACILITY
置 1
形参 | 描述 |
---|---|
xTaskStatusArray | 指向IaskStatus_t结构体数组首地址 |
uxArraySize | 接收信息的数组大小 |
pulTotalRunTime | 系统总运行时间,为NULL则省略总运行时间值 |
返回值 | 描述 |
---|---|
整型 | 获取信息的任务数量 |
typedef struct xTASK_STATUS
{
TaskHandle_t xHandle; /*任务句柄*/
const char* pcTaskName; /*任务名*/
UBaseType_t xTaskNumber; /*任务编号*/
eTaskState CurrentState; /*任务状态*/
UBaseType_t uxCurrentPriority; /*任务优先级*/
UBaseType_t uxBasePriority; /*任务原始优先级*/
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /*任务运行时间*/
StackType_t* pxStackBase; /*任务栈基地址*/
configSTACK_DEPTH_TYPE usStackHighWaterMark; /*任务栈历史剩余最小值*/
} TaskStatus_t;
5.6.5 获取指定单个的任务信息API使用详解
void vlaskGetlnfo
(
TaskHandle_t xTask,
TaskStatus_t* pxTaskStatus,
BaseType_t xGetFreeStackSpace,
eTaskState eState
)
此函数用于获取指定的单个任务的状态信息,使用该函数需将宏 configUSE_TRACE_FACILITY
置 1
形参 | 描述 |
---|---|
xTask | 指定获取信息的任务的句柄 |
pxTaskStatus | 接收任务信息的变量 |
xGetFreeStackSpace | 任务栈历史剩余最小值, 当为“pdFALSE”则跳过这个步骤, 当为“pdTRUE”则检查历史剩余最小堆栈 |
eState | 任务状态,可直接赋值,如想获取代入“elnvalid” |
typedef enum
{
eRunning=0, /*运行态*/
eReady, /*就绪态*/
eBlocked, /*阻塞态*/
eSuspended, /*挂起态*/
eDeleted, /*任务被删除*/
elnvalid /*无效*/
}eTaskState;
5.6.6 获取当前任务的任务句柄API使用详解
TaskHandle_t xTaskGetCurrentTaskHandle(void)
此函数用于获取当前任务的任务句柄,使用该函数需将宏 INCLUDE_xTaskGetCurrentTaskHandle
置 1
返回值 | 描述 |
---|---|
TaskHandle_t | 当前任务的任务句柄 |
5.6.7 根据任务名获取该任务的任务句柄API使用详解
TaskHandle_t xTaskGetHandle(const char* pcNameToQuery)
此函数用于通过任务名获取任务句柄,使用该函数需将宏 INCLUDE_xTaskGetHandle
置1
形参 | 描述 |
---|---|
pcNameToQuery | 任务名 |
返回值 | 描述 |
---|---|
TaskHandle | 任务句柄 |
5.6.9 获取任务的任务栈历史剩余最小值API使用详解
UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask)
此函数用于获取指定任务的任务栈历史最小剩余堆栈;
使用该函数需将宏 INCLUDE_uxTaskGetStackHighWaterMark
置1
形参 | 描述 |
---|---|
xTask | 任务句柄 |
返回值 | 描述 |
---|---|
UBaseType_t | 任务栈的历史剩余最小值 |
5.6.10 获取任务状态API使用详解
eTaskState eTaskGetState(TaskHandle_t xTask)
此函数用于查询某个任务的运行状态,使用此函数需将宏 INCLUDE_eTaskGetState
置1
形参 | 描述 |
---|---|
xTask | 待获取状态任务的任务句柄 |
返回值 | 描述 |
---|---|
eTaskState | 任务状态 |
typedef enum
{
eRunning=0, /*运行态*/
eReady, /*就绪态*/
eBlocked, /*阻塞态*/
eSuspended, /*挂起态*/
eDeleted, /*任务被删除*/
elnvalid /*无效*/
}eTaskState;
5.6.11 以“表格”形式获取所有任务的信息API使用详解
void vTaskList(char* pcWriteBuffer)
此函数用于以“表格”的形式获取系统中任务的信息;
使用此函数需将宏 configUSE_TRACE_FACILITY
和 configUSE_STATS_FORMATTING_FUNCTIONS
置1
形参 | 描述 |
---|---|
pcWriteBuffer | 接收任务信息的缓存指针 |
表格如下所示:
**Name:**创建任务的时候给任务分配的名字。
**State:**任务的壮态信息,B是阻塞态,R是就绪态,S是挂起态,D是删除态Priority :任务优先级。
**Stack:**任务堆栈的“高水位线”,就是堆栈历史最小剩余大小。
**Num:**任务编号,这个编号是唯一的,当多个任务使用同一个任务名的时候可以通过此编号来做区分
5.6.12 任务运行时间统计API使用详解
1、任务运行时间统计API使用详解
void vTaskGetRunTimeStats(char* pcWriteBuffer)
此函数用于统计任务的运行时间信息,使用此函数需将宏 configGENERATE_RUN_TIME_STAT
、configUSE_STATS_FORMATTING_FUNCTIONS
置 1
形参 | 描述 |
---|---|
pcWriteBuffer | 接收任务运行时间信息的缓存指针 |
Task:任务名称
Abs Time:任务实际运行的总时间(绝对时间)
%Time:占总处理时间的百分比
2、时间统计API函数使用流程
- 将宏configGENERATE_RUN_TIME_STATS置1
- 将宏configUSE_STATS_FORMATTING_FUNCTIONS 置1
- 当将此宏configGENERATE_RUN_TIME_STATS置1之后,还需要实现2个宏定义:portCONFIGURE_TIMER.FOR RUNTIME STATS():用于初始化用于配置任务运行时间统计的时基定时器;注意:这个时基定时器的计时精度需高于系统时钟节拍精度的10至100倍!portGET_RUN_TIME_COUNTER_VALUE():用于获取该功能时基硬件定时器计数的计数值。
3、举例
使用一个定时器每隔10us中断一次,并在中断服务函数中将FreeRTOSRunTimeTicks++
5.7 延时函数API
函数 | 描述 |
---|---|
vTaskDelay() | 相对延时 |
xTaskDelayUntil() | 绝对延时 |
**相对延时:**指每次延时都是从执行函数 vTaskDelay()
开始,直到延时指定的时间结束
**绝对延时:**指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务
1、为任务主体,也就是任务真正要做的工作
2、是任务函数中调用 vTaskDelayUntil()
对任务进行延时
3、为其他任务在运行(高优先级)
绝对延时api函数的使用示例:
// Perform an action every 10 ticks.
void vTaskFunction(void* pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 10;
// Initialise the xLastWakeTime variable with the current time.
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
// Wait for the next cycle.
vTaskDelayUntil(&xLastWakeTime,xFrequency);
// Perform action here.
}
}
5.8 队列
FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、递归互斥信号量,因此很有必要深入了解FreeRTOS的队列。
FreeRTOS队列特点:
1、数据入队出队方式:队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为“后进先出”LIFO方式
2、数据传递方式:FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递,FreeRTOS采用拷贝数据传递也可以传递指针,所以在传递较大的数据的时候采用指针传递
3、多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息
4、出队、入队阻塞:当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队
- 若阻塞时间为0︰直接返回不会等待
- 若阻塞时间为0~port_MAX_DELAY︰等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待
- 若阻塞时间为port_MAX_DELAY︰死等,一直等到可以入队为止。出队阻塞与入队阻塞类似
**问题:**当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?
1、优先级最高的任务
2、如果大家的优先级相同,那等待时间最久的任务会进入就绪态
5.8.1 创建队列API使用详解
使用队列的主要流程:创建队列→写队列→读队列。
1、创建队列相关API函数介绍
函数 | 描述 |
---|---|
xQueueCreate() | 动态方式创建队列 |
xQueueCreateStatic() | 静态方式创建队列 |
2、创建队列API函数使用详解
QueueHandle_t xQueueCreate(uxQueueLength, uxltemSize)
形参 | 描述 |
---|---|
uxQueueLength | 队列长度 |
uxltemSize | 队列项目的大小 |
返回值 | 描述 |
---|---|
NULL | 队列创建失败 |
其他值 | 队列创建成功,返回队列句柄 |
3、创建队列API函数原理
#define xQueueCreate(uxQueueLength, uxltemSize) \
xQueueGenericCreate((uxQueueLength), (uxltemSize), (queueQUEUE_TYPE_BASE))
此函数用于使用动态方式创建队列,队列所需的内存空间由FreeRTOS 从 FreeRTOS管理的堆中分配
前面说FreeRTOS基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的queue.h文件中有
#define queueQUEUE_TYPE_BASE ((uint8_t)0u) /*队列*/
#define queueQUEUE_TYPE_SET ((uint8_t)0U) /*队列集*/
#define queueQUEUE_TYPE_MUTEX ((uint8_t)1U) /*互斥信号量*/
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ((uint8_t)2U) /*计数型信号量*/
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ((uint8_t)3u) /*二值信号量*/
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ((uint8_t)4U) /*递归互斥信号量*/
5.8.2 队列写入消息API使用详解
1、往队列写入消息API函数
函数 | 描述 |
---|---|
xQueueSend() | 往队列的尾部写入消息 |
xQueueSendToBack() | 同xQueueSend() |
xQueueSendToFront() | 往队列的头部写入消息 |
xQueueOverwrite() | 覆写队列消息(只用于队列长度为1的情况) |
xQueueSendFromlSR() | 在中断中往队列的尾部写入消息 |
xQueueSendToBackFromlSR() | 同xQueueSendFromISR() |
xQueueSendToFrontFromlSR() | 在中断中往队列的头部写入消息 |
xQueueOverwriteFromlSR() | 在中断中覆写队列消息(只用于队列长度为1的情况) |
2、往队列写入消息API函数使用详解
BaseType_t xQueueGenericSend(
QueueHandle_t xQueue,
const void* const pvltemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition
)
形参 | 描述 |
---|---|
xQueue | 待写入的队列 |
pvltemToQueue | 待写入消息 |
xTicksToWait | 阻塞超时时间 |
xCopyPosition | 写入的位置 |
返回值 | 描述 |
---|---|
pdTRUE | 队列写入成功 |
errQUEUE_FULL | 队列写入失败 |
3、队列写入消息API函数原理
#define xQueueSend(xQueue, pvltemToQueue, xTicksToWait) \
xQueueGenericSend((xQueue), (pvltemToQueue), (xTicksToWait), queueSEND_TO_BACK)
#define xQueueSendToBack(xQueue, pvltemToQueue, xTicksToWait) \
xQueueGenericSend((xQueue), (pvltemToQueue), (xTicksToWait), queueSEND_TO_BACK)
#define xQueueSendToFront(xQueue, pvltemToQueue, xTicksToWait) \
xQueueGenericSend((xQueue), (pvltemToQueue), (xTicksToWait), queueSEND_TO_FRONT)
#define xQueueOverwrite(xQueue, pvltemToQueue) \
xQueueGenericSend((xQueue), (pvltemToQueue), 0, queueOVERWRITE)
可以看到这几个写入函数调用的是同一个函数 xQueueGenericSend()
,只是指定了不同的写入位置!
5.8.3 队列读出消息API使用详解
1、从队列读取消息API函数
函数 | 描述 |
---|---|
xQueueReceive() | 从队列头部读取消息,并删除消息 |
xQueuePeek() | 从队列头部读取消息 |
xQueueReceiveFromlSR() | 在中断中从队列头部读取消息,并删除消息 |
xQueuePeekFromlSR() | 在中断中从队列头部读取消息 |
2、从队列读取消息API函数使用详解
BaseType_t xQueueReceive(QueueHandle_t xQueue, void* const pvBuffer, TickType_t xTicksToWait)
此函数用于在任务中,从队列中读取消息,并且消息读取成功后会将消息从队列中移除。
BaseType_t xQueuePeek(QueueHandle_t xQueue, void* const pvBuffer, TickType_t xTicksToWait)
此函数用于在任务中,从队列中读取消息,但与函数xQueueReceive()不同,此函数在成功读取消息后并不会移除已读取的消息!
形参 | 描述 |
---|---|
xQueue | 待读取的队列 |
pvBuffer | 信息读取缓冲区 |
xTicksToWait | 阻塞超时时间 |
返回值 | 描述 |
---|---|
pdTRUE | 读取成功 |
pdFALSE | 读取失败 |
5.9 信号量
队列 | 信号量 |
---|---|
可以容纳多个数据; 创建队列有两部分内存:队列结构体+队列项存储空间 |
仅存放计数值,无法存放其他数据; 创建信号量,只需分配信号量结构体 |
写入队列:当队列满时,可阻塞; | 释放信号量:不可阻塞,计数值++, 当计数值为最大值时,返回失败 |
读取队列:当队列为空时,可阻塞; | 获取信号量:计数值-- 当没有资源时,可阻塞 |
5.9.1 二值信号量
二值信号量的本质是一个队列长度为1的队列,该队列就只有空和满两种情况,这就是二值。
二值信号量通常用于互斥访问或任务同步,与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题,所以二值信号量更适合用于同步!
使用二值信号量的过程:创建二值信号量→释放二值信号量→获取二值信号量
1、二值信号量API函数
函数 | 描述 |
---|---|
xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 |
xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 |
xSemaphoreGive() | 释放信号量 |
xSemaphoreGiveFromlSR() | 在中断中释放信号量 |
xSemaphoreTake() | 获取信号量 |
xSemaphoreTakeFromISR() | 在中断中获取信号量 |
2、创建二值信号量API函数使用详解
SemaphoreHandle_t xSemaphoreCreateBinary(void)
返回值 | 描述 |
---|---|
NULL | 创建失败 |
其他值 | 创建成功返回二值信号量的句柄 |
3、创建二值信号量API函数原理
#define xSemaphoreCreateBinary() \
XQueueGenericCreate(1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE)
#define semSEMAPHORE_QUEUE_ITEM_LENGTH ((uint8_t)0u)
#define queueQUEUE_TYPE_BASE ((uint8_t)0u) /*队列*/
#define queueQUEUE_TYPE_SET ((uint8_t)0U) /*队列集*/
#define queueQUEUE_TYPE_MUTEX ((uint8_t)1U) /*互斥信号量*/
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ((uint8_t)2U) /*计数型信号量*/
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ((uint8_t)3u) /*二值信号量*/
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ((uint8_t)4U) /*递归互斥信号量*/
4、释放二值信号量API函数使用详解
BaseType_t xSemaphoreGive(xSemaphore)
形参 | 描述 |
---|---|
xSemaphore | 要释放的信号量句柄 |
返回值 | 描述 |
---|---|
pdPASS | 释放信号量成功 |
errQUEUE_FULL | 释放信号量失败 |
5、释放二值信号量API函数原理
#define xSemaphoreGive(xSemaphore) \
xQueueGenericSend((QueueHandle_t) (xSemaphore), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK)
#define semGIVE_BLOCK_TIME ((TickType_t)0u)
6、获取二值信号量API函数使用详解
BaseType_t xSemaphoreTake(xSemaphore, xBlockTime)
形参 | 描述 |
---|---|
xSemaphore | 要获取的信号量句柄 |
xBlockTime | 阻塞时间 |
返回值 | 描述 |
---|---|
pdPASS | 获取信号量成功 |
errQUEUE_FULL | 超时,获取信号量失败 |
5.9.2 计数型信号量
计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的
计数型信号量适用场合:
- 事件计数:当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1),其他任务会获取计数型信号量(计数值-1),这种场合一般在创建时将初始计数值设置为0
- 资源管理:信号量表示有效的资源数目。任务必须先获取信号量(信号量计数值-1)才能获取资源控制权。当计数值减为零时表示没有的资源。当任务使用完资源后,必须释放信号量(信号量计数值+1)。信号量创建时计数值应等于最大资源数目
使用计数型信号量的过程:创建计数型信号量→释放信号量→获取信号量
1、计数型信号量API函数
函数 | 描述 |
---|---|
xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量。 |
xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
uxSemaphoreGetCount() | 获取信号量的计数值 |
计数型信号量的释放和获取与二值信号量相同!
2、创建计数型信号量API函数使用详解
QueueHandle_t xSemaphoreCreateCounting(uxMaxCount, uxInitialCount)
形参 | 描述 |
---|---|
uxMaxCount | 计数值的最大值限定 |
uxInitialCount | 计数值的初始值 |
返回值 | 描述 |
---|---|
NULL | 创建失败 |
其他值 | 创建成功返回计数型信号量的句柄 |
3、创建计数型信号量API函数原理
#define xSemaphoreCreateCounting(uxMaxCount, uxInitialCount) \
xQueueCreateCountingSemaphore((uxMaxCount), (uxInitialCount))
4、获取信号量当前计数值API函数使用详解
UBaseType_t uxSemaphoreGetCount(xSemaphore)
形参 | 描述 |
---|---|
xSemaphore | 信号量句柄 |
返回值 | 描述 |
---|---|
整数 | 当前信号量的计数值大小 |
5、获取信号量当前计数值API函数原理
#define uxSemaphoreGetCount(xSemaphore) \
uxQueueMessagesWaiting((QueueHandle_t) (xSemaphore))
5.9.3 互斥信号量
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!
优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响
注意:互斥信号量不能用于中断服务函数中,原因如下︰
- 互斥信号量有任务优先级继承的机制,但是中断不是任务,没有任务优先级,所以互斥信号量只能用与任务中,不能用于中断服务函数。
- 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
使用互斥信号量:首先将宏 configUSE_MUTEXES
置 1
使用流程:创建互斥信号量→(task)获取信号量→(give)释放信号量
1、创建互斥信号量API函数
函数 | 描述 |
---|---|
xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量 |
xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量 |
互斥信号量的释放和获取函数与二值信号量相同!只不过互斥信号量不支持中断中调用
注意:创建互斥信号量时,会主动释放一次信号量
2、创建互斥信号量API函数使用详解
QueueHandle_t xSemaphoreCreateMutex()
返回值 | 描述 |
---|---|
NULL | 创建失败 |
其他值 | 创建成功返回互斥信号量的句柄 |
3、创建互斥信号量API函数原理
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
5.10 队列集
5.10.1 队列集的简介
一个队列只允许任务间传递的消息为同一种数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集!
作用:用于对多个队列或信号量进行“监听”,其中不管哪一个消息到来,都可让任务退出阻塞状态
假设:有个接收任务,使用到队列接收和信号量的获取,如下:
接收任务()
{
等待接收队列;
获取信号量;
}
接收任务()
{
等待队列集中消息;
if(队列还是信号量);
... ...
}
5.10.2 队列集使用流程
- 启用队列集功能需要将宏configUSE_QUEUE_SETS配置为1
- 创建队列集
- 创建队列或信号量
- 往队列集中添加队列或信号量
- 往队列发送信息或释放信号量
- 获取队列集的消息
5.10.3 队列集相关API函数
函数 | 描述 |
---|---|
xQueueCreateSet() | 创建队列集 |
xQueueAddToSet() | 队列添加到队列集中 |
xQueueRemoveFromSet() | 从队列集中移除队列 |
xQueueSelectFromSet() | 获取队列集中有有效消息的队列 |
xQueueSelectFromSetFromlSR() | 在中断中获取队列集中有有效消息的队列 |
5.10.4 创建队列集API函数使用详解
QueueSetHandle_t xQueueCreateSet(const UBaseType_t uxEventQueueLength)
形参 | 描述 |
---|---|
uxEventQueueLength | 队列集可容纳的队列数量 |
返回值 | 描述 |
---|---|
NULL | 队列集创建失败 |
其他值 | 队列集创建成功,返回队列集句柄 |
5.10.5 往队列集中添加队列API函数使用详解
BaseType_t xQueueAddToSet(QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet)
注意:队列在被添加到队列集之前,队列中不能有有效的消息
形参 | 描述 |
---|---|
xQueueOrSemaphore | 待添加的队列句柄 |
xQueueSet | 队列集 |
返回值 | 描述 |
---|---|
pdPASS | 队列集添加队列成功 |
pdFAIL | 队列集添加队列失败 |
5.10.6 从队列集中移除队列API函数使用详解
BaseType_t xQueueRemoveFromSet(QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet )
注意:队列在从队列集移除之前,必须没有有效的消息
形参 | 描述 |
---|---|
xQueueOrSemaphore | 待移除的队列句柄 |
xQueueSet | 队列集 |
返回值 | 描述 |
---|---|
pdPASS | 队列集移除队列成功 |
pdFAIL | 队列集移除队列失败 |
5.10.7 获取队列集中有有效消息队列API函数使用详解
QueueSetMemberHandle_t xQueueSelectFromSet(QueueSe etHandle_t xQueueSet, TickType_t const xTicksToWait)
形参 | 描述 |
---|---|
xQueueSet | 队列集 |
xTicksToWait | 阻塞超时时间 |
返回值 | 描述 |
---|---|
NULL | 获取消息失败 |
其他值 | 获取到消息的队列句柄 |
5.11 事件标志组
5.11.1 事件标志组简介
事件标志位:用一个位,来表示事件是否发生
事件标志组是一组事件标志位的集合,可以简单的理解事件标志组,就是一个整数。
事件标志组的特点:
-
它的每一个位表示一个事件(高8位不算)
-
每一位事件的含义,由用户自己决定,如:bit0表示按键是否按下,bitl表示是否接受到消息…
这些位的值为1:表示事件发生了;值为0:表示事件未发生
-
任意任务或中断都可以读写这些位
-
可以等待某一位成立,或者等待多位同时成立
一个事件组就包含了一个EventBites_t数据类型的变量,变量类型EventBits_t的定义如下所示:
typedef TickType_t EventBits_t;
#if(configUSE_16_BIT_TICKS == 1)
typedef uint16_t TickType_t;
#else
typedef uint32_t TickType_t;
#endif
#define configUSE_16_BIT_TICKS 0
EventBits _t 实际上是一个16位或32位无符号的数据类型
虽然使用了32位无符号的数据类型变量来存储事件标志,但其中的高8位用作存储事件标志组的控制信息,低24位用作存储事件标志,所以说一个事件组最多可以存储24个事件标志!
5.11.2 时间标志组与队列、信号量的区别
功能 | 唤醒对象 | 事件清除 |
---|---|---|
队列、信号量 | 事件发生时,只会唤醒一个任务 | 是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了 |
事件标志组 | 事件发生时,会唤醒所有符合条件的任务,可以理解为“广播”的作用 | 被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件 |
5.11.3 事件标志组相关API函数
函数 | 描述 |
---|---|
xEventGroupCreate() | 使用动态方式创建事件标志组 |
xEventGroupCreateStstic() | 使用静态方式创建事件标志组 |
xEventGroupClearBits() | 清零事件标志位 |
xEventGroupClearBitsFromlSR() | 在中断中清零事件标志位 |
xEventGroupSetBits() | 设置事件标志位 |
xEventGroupSetBitsFromlSR() | 在中断中设置事件标志位 |
xEventGroupWaitBits() | 等待事件标志位 |
xEventGroupSync() | 设置事件标志位,并等待事件标志位 |
还有更多的api函数。。。。
5.11.4 创建事件标志组API使用详解
EventGroupHandle_t xEventGroupCreate(void)
返回值 | 描述 |
---|---|
NULL | 事件标志组创建失败 |
其他值 | 事件标志组创建成功,返回其句柄 |
5.11.5 清除事件标志组API使用详解
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear)
形参 | 描述 |
---|---|
xEventGroup | 待操作的事件标志组句柄 |
uxBitsToSet | 待清零的事件标志位 |
返回值 | 描述 |
---|---|
整数 | 清零事件标志位之前事件组中事件标志位的值 |
5.11.6 设置事件标志组API使用详解
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet)
形参 | 描述 |
---|---|
xEventGroup | 待操作的事件标志组句柄 |
uxBitsToSet | 待设置的事件标志位 |
返回值 | 描述 |
---|---|
整数 | 事件组中的事件标志位值 |
5.11.7 等待事件标志组API使用详解
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait)
形参 | 描述 |
---|---|
xEventGroup | 等待的事件标志组句柄 |
uxBitsToWaitFor | 等待的事件标志位,可以用逻辑或等待多个事件标志位 |
xClearOnExit | 成功等待到事件标志位后,清除事件组中对应的事件标志位, pdTRUE:清除uxBitsToWaitFor指定位; pdFALSE:不清除 |
xWaitForAllBits | 等待uxBitsToWaitFor中的所有事件标志位(逻辑与) pdTRUE:等待的位,全部为1 pdFALSE:等待的位,某个为1 |
xTicksToWait | 等待的阻塞时间 |
返回值 | 描述 |
---|---|
等待的事件标志位值 | 等待事件标志位成功,返回等待到的事件标志位 |
其他值 | 等待事件标志位失败,返回事件组中的事件标志位 |
5.11.8 同步事件标志组API使用详解
EventBits_t xEventGroupSync(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait)
形参 | 描述 |
---|---|
xEventGroup | 等待事件标志所在事件组 |
uxBitsToSet | 达到同步点后,要设置的事件标志 |
uxBitsToWaitFor | 等待的事件标志 |
xTicksToWait | 等待的阻塞时间 |
返回值 | 描述 |
---|---|
等待的事件标志位值 | 等待事件标志位成功,返回等待到的事件标志位 |
其他值 | 等待事件标志位失败,返回事件组中的事件标志位 |
5.12 任务通知
5.12.1 任务通知简介
任务通知:用来通知任务的,任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。
只要合理,灵活的利用任务通知的特点,可以在一些场合中替代队列、信号量、事件标志组!
-
使用队列、信号量、事件标志组时都需另外创建一个结构体,通过中间的结构体进行间接通信!
-
使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的"通知"
任务通知值的更新方式有多种类型:
- 计数值(数值累加,类似信号量)
- 相应位置一(类似事件标志组)
- 任意数值(支持覆写和不覆写,类似队列)
其中任务通知状态共有3种取值:
#define taskNOT_WAITING_NOTIFICATION ((uint8_t)0) /*任务未等待通知*/
#define taskWAITING_NOTIFICATION ((uint8_t)1) /*任务在等待通知*/
#define taskNOTIFICATION_RECEIVED ((uint8_t)2) /*任务在等待接收*/
- 任务未等待通知:任务通知默认的初始化状态
- 等待通知:接收方已经准备好了(调用了接收任务通知函数),等待发送方给个通知
- 等待接收:发送方已经发送出去(调用了发送任务通知函数),等待接收方接收
5.12.2 任务通知的优势和劣势
-
任务通知的优势:
- 效率更高:使用任务通知向任务发送事件或数据比使用队列、事件标志组或信号量快得多
- 使用内存更小:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体
-
任务通知的劣势:
- 无法发送数据给ISR:ISR没有任务结构体,所以无法给ISR发送数据。但是ISR可以使用任务通知的功能发数据给任务。
- 无法广播给多个任务:任务通知只能是被指定的一个任务接收并处理
- 无法缓存多个数据:任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据。
- 发送受阻不支持阻塞:发送方无法进入阻塞状态等待
5.12.3 任务通知发送通知
1、任务通知发送通知API函数
注意:发送通知API函数可以用于任务和中断服务函数中;接收通知API函数只能用在任务中
函数 | 描述 |
---|---|
xTaskNotify() | 发送通知,带有通知值 |
xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接收任务的原通知值 |
xTaskNotifyGive() | 发送通知,不带通知值 |
xTaskNotifyFromlSR() | 在中断中发送任务通知 |
xTaskNotifyAndQueryFromISR() | |
vTaskNotifyGiveFromISR() |
2、 任务通知发送通知API函数使用详解
BaseType_t xTaskGenericNotify(TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t* pulPreviousNotificationValue)
形参 | 描述 |
---|---|
xTaskToNotify | 接收任务通知的任务句 |
uxIndexToNotify | 任务的指定通知(任务通知相关数组成员) |
ulValue | 任务通知值 |
eAction | 通知方式(通知值更新方式) |
pulPreviousNotificationValue | 用于保存更新前的任务通知值(为NULL则不保存) |
任务通知方式共有以下几种:
typedef enum
{
eNoAction = 0, /*无操作*/
eSetBits /*更新指定bit */
elncrement /*通知值加一*/
eSetValueWithOverwrite /*覆写的方式更新通知值*/
eSetValueWithoutOverwrite /*不覆写通知值*/
} eNotifyAction;
3、任务通知发送通知API函数原理
#define xTaskNotifyAndQuery(xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue) \
xTaskGenericNotify((xTaskToNotify),
(tskDEFAULT_INDEX_TO_NOTIFY), (ulValue),
(eAction),
(pulPreviousNotifyValue))
#define xTaskNotify(xTaskToNotify, ulValue, eAction) \
XTaskGenericNotify((xTaskToNotify), (tskDEFAULT_INDEX_TO_NOTIFY), (ulValue), (eAction), NULL)
#define xTaskNotifyGive(xTaskToNotify) \
xTaskGenericNotify((xTaskToNotify), (tskDEFAULT_INDEX_TO_NOTIFY), (0), eIncrement, NULL)
5.12.4 任务通知接收通知
1、任务通知发送通知API函数
函数 | 描述 |
---|---|
ulTaskNotifyTake() | 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一 当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。 |
xTaskNotifyWait() | 获取任务通知,比ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位 |
总结:
当任务通知用作于信号量时,使用函数获取信号量: ulTaskNotifyTake()
当任务通知用作于事件标志组或队列时,使用此函数来获取:xTaskNotifyWait()
2、 任务通知发送通知API函数使用详解
ulTaskGenericNotifyTake((tskDEFAULT_INDEX_TO_NOTIFY), ( xClearCountOnExit ), (xTicksToWait))
形参 | 描述 |
---|---|
uxlndexToWaitOn | 任务的指定通知(任务通知相关数组成员) |
xClearCountOnExit | 指定在成功接收通知后,将通知值清零或减1, pdTRUE:把通知值清零; pdFALSE:把通知值减一 |
xTicksToWait | 阻塞等待任务通知值的最大时间 |
返回值 | 描述 |
---|---|
0 | 接收失败 |
非0 | 接收成功,返回任务通知的通知值 |
BaseType_t xTaskGenericNotifyWait(UBaseType_t uxIndexToWaitOn, uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t* pulNotificationValue, TickType_t xTicksToWait)
形参 | 描述 |
---|---|
uxIndexToWaitOn | 任务的指定通知(任务通知相关数组成员) |
ulBitesToClearOnEntry | 等待前清零指定任务通知值的比特位(旧值对应bit清0) |
ulBitesToClearOnExit | 成功等待后清零指定的任务通知值比特位(新值对应bit清0) |
pulNotificationValue | 用来取出通知值(如果不需要取出,可设为NULL) |
xTicksToWait | 阻塞等待任务通知值的最大时间 |
返回值 | 描述 |
---|---|
pdTRUE | 等待任务通知成功 |
pdFALSE | 等待任务通知失败 |
3、 任务通知发送通知API函数原理
#define ulTaskNotifyTake(xClearCountOnExit, xTicksToWait) \
ulTaskGenericNotifyTake((tskDEFAULT_INDEX_TO_NOTIFY), (xClearCountOnExit), (xTicksToWait))
4、示例
模拟二值信号量
/*任务一,发送任务通知值*/
void task1(void* pvParameters)
{
key = key_scan(0);
if(key == KEYO_PRES)
{
printf("任务通知模拟二值信号量释放! \r\n") ;
xTaskNotifyGive(task2_handler) ;
}
vTaskDelay(10);
}
/*任务二,接收任务通知值*/
void task2(void* pvParameters)
{
uint32_t rev = 0;
while(i)
{
rev =ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if(rev != 0)
{
printf("接收任务通知成功,模拟获取二值信号量! \r\n");
}
}
}
模拟计数型信号量
/*任务一,发送任务通知值*/
void task1(void* pvParameters)
{
key = key_scan(0);
if(key == KEYO_PRES)
{
printf("任务通知模拟计数型信号量释放! \r\n");
xTaskNotifyGive(task2_handdler);
}
vTaskDelay(10);
}
/*任务二,接收任务通知值*/
void task2 (void* pvParameters)
{
uint32_t rev = 0;
while(1)
{
rev = ulTaskNotifyTake(pdFALSE , portMAX_DELAY);
if(rev != 0)
{
printf("rev: %d", rev);
}
vTaskDelay(1000);
}
}
模拟邮箱消息通知
/*任务一,发送任务通知值*/
void task1(void* pvParameters)
{
while(1)
{
key =key_scan(0);
if((key != 0) && (task2_handler != NULL))
{
printf(任务通知模拟消息邮箱发送,发送的键值为: %d\r\n" key);
xTaskNotify(task2_handler, key, eSetValueWithOverwrite);
}
vTaskDelay(10);
}
}
/*任务二,接收任务通知值*/
void task2 (void* pvParameters)
{
uint32_t noyify_val = 0;
while(1)
{
xTaskNotifyWait(0, 0xFFFFFFFF, &noyify_val, portMAX_DELAY);
switch(noyify_val)
{
}
}
}
模拟事件标志组
5.13 软件定时器
5.13.1 软件定时器简介
1、定时器分类:
- 定时器:
从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可自定义定时器的周期 - 硬件定时器:
芯片本身自带的定时器模块,硬件定时器的精度一般很高,每次在定时时间到达之后就会自动触发一个中断,用户在中断服务函数中处理信息。 - 软件定时器:
是指具有定时功能的软件,可设置定时周期,当指定时间到达后要调用回调函数(也称超时函数)﹐用户在回调函数中处理信息
注意:软件定时器的超时回调函数是由软件定时器服务任务调用的,软件定时器的超时回调函数本身不是任务,因此不能在该回调函数中使用可能会导致任务阻塞的API函数。
2、软件定时器的相关配置
- 当FreeRTOS的配置项
configUSE_TIMERS
置为1
,在启动任务调度器时,会自动创建软件定时器的服务/守护任务prvTimerTask( )
- 软件定时器服务任务的优先级为
configTIMER_TASK_PRIORITY = 31
- 定时器的命令队列长度为
configTIMER_QUEUE_LENGTH = 5
注意:软件定时器的超时回调函数是在软件定时器服务任务中被调用的,服务任务不是专为某个定时器服务的,它还要处理其他定时器。
- 回调函数要尽快实行,不能进入阻塞状态,即不能调用那些会阻塞任务的API函数,如:
vTaskDelay(0)
- 访问队列或者信号量的非零阻塞时间的API函数也不能调用。
3、两种软件定时器
单次定时器:单次定时器的一旦定时超时,只会执行一次其软件定时器超时回调函数,不会自动重新开启定时,不过可以手动重新开启。
周期定时器:周期定时器的一旦启动以后就会在执行完回调函数以后自动的重新启动,从而周期地执行其软件定时器回调函数。
5.13.2 软件定时器优缺点
- 优点:
硬件定时器数量有限,而软件定时器理论上只需有足够内存,就可以创建多个;使用简单、成本低 - 缺点:
软件定时器相对硬件定时器来说,精度没有那么高(因为它以系统时钟为基准,系统时钟中断优先级又是最低,容易被打断)。对于需要高精度要求的场合,不建议使用软件定时器。
5.13.2 软件定时器相关API函数
函数 | 描述 |
---|---|
xTimerCreate() | 动态方式创建软件定时器 |
xTimerCreateStatic() | 静态方式创建软件定时器 |
xTimerStart() | 开启软件定时器定时 |
xTimerStartFromlSR() | 在中断中开启软件定时器定时 |
xTimerStop() | 停止软件定时器定时 |
xTimerStopFromlSR() | 在中断中停止软件定时器定时 |
xTimerReset() | 复位软件定时器定时 |
xTimerResetFromlSR() | 在中断中复位软件定时器定时 |
xTimerChangePeriod() | 更改软件定时器的定时超时时间 |
xTimerChangePeriodFromISR() | 在中断中更改定时超时时间 |
5.13.2 创建软件定时器API函数使用详解
TimerHandle_t xTimerCreate(const char* const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void* const pvTimerlD, TimerCallbackFunction_t pxCallbackFunction)
形参 | 描述 |
---|---|
pcTimerName | 软件定时器名 |
xTimerPeriodInTicks | 定时超时时间,单位:系统时钟节拍 |
uxAutoReload | 定时器模式,pdTRUE:周期定时器,pdFALSE:单次定时器 |
pvTimerlD | 软件定时器ID,用于多个软件定时器公用一个超时回调函数 |
pxCallbackFunction | 软件定时器超时回调函数 |
返回值 | 描述 |
---|---|
NULL | 软件定时器创建失败 |
其他值 | 软件定时器创建成功,返回其句柄 |
5.13.3 开启软件定时器API函数使用详解
BaseType_t xTimerStart(TimerHandle_t xTimer, const TickType_t xTicksToWait)
形参 | 描述 |
---|---|
xTimer | 待开启的软件定时器的句柄 |
xTickToWait | 发送命令到软件定时器命令队列的最大等待时间 |
返回值 | 描述 |
---|---|
pdPASS | 软件定时器开启成功 |
pdFAIL | 软件定时器开启失败 |
5.13.4 停止软件定时器API函数使用详解
BaseType_t xTimerStop(TimerHandle_t xTimer, const TickType_t xTicksToWait)
形参 | 描述 |
---|---|
xTimer | 待停止的软件定时器的句柄 |
xTickToWait | 发送命令到软件定时器命令队列的最大等待时间 |
返回值 | 描述 |
---|---|
pdPASS | 软件定时器停止成功 |
pdFAIL | 软件定时器停止失败 |
5.13.5 复位软件定时器API函数使用详解
BaseType_t xTimerReset(TimerHandle_t xTimer, const TickType_t xTicksToWait)
该功能将使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻重新定时
形参 | 描述 |
---|---|
xTimer | 待复位的软件定时器的句柄 |
xTickToWait | 发送命令到软件定时器命令队列的最大等待时间 |
返回值 | 描述 |
---|---|
pdPASS | 软件定时器复位成功 |
pdFAIL | 软件定时器复位失败 |
5.13.6 更改软件定时器API函数使用详解
BaseType_t xTimerChangePeriod(TimerHandle_t xTimer, const TickType_t xNewPeriod, const TickType_t xTicksToWait)
形参 | 描述 |
---|---|
xTimer | 待更新的软件定时器的句柄 |
xNewPeriod | 新的定时超时时间,单位:系统时钟节拍 |
xTickToWait | 发送命令到软件定时器命令队列的最大等待时间 |
返回值 | 描述 |
---|---|
pdPASS | 软件定时器定时超时时间更改成功 |
pdFAIL | 软件定时器定时超时时间更改失败 |
评论区