FreeRTOS
创建任务 任务控制块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 typedef struct tskTaskControlBlock { volatile StackType_t * pxTopOfStack; ListItem_t xStateListItem; ListItem_t xEventListItem; UBaseType_t uxPriority; StackType_t * pxStack; char pcTaskName[ configMAX_TASK_NAME_LEN ]; }tskTCB;
动态创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 xTaskCreate(); BaseType_t xTaskCreate ( TaskFunction_t pxTaskCode, const char * const pcName, const configSTACK_DEPTH_TYPE usStackDepth, void * const pvParamters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask ) pdPass; errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY
将宏configSUPPORT_DYNAMIC_ALLOCATION 配置为 1.
定义任务函数.
创建任务.
动态创建任务内部实现
申请堆栈内存(返回首地址).
申请任务控制块内存(返回首地址).
把申请的堆栈地址,赋值给TCB的堆栈成员.
调用prvlnitialiseNewTask初始化TCB中的成员.
初始化堆栈为0xa5.
记录栈顶,保存至pxTopOfStack.
保存任务名至pxNewTCB->pcTaskName.
保存任务优先级至pxNewTCB->uxPriority.
设置状态列表项的所属控制块,设置事件列表项的值.
列表项的插入从小到大,将高优先级任务的事件列表项值设置小,排到前面.
调用pxPortlnitialiseStack初始化任务堆栈,用于保存当前任务上下文寄存器信息.
任务句柄 = 任务控制块.
调用prvAddNewTaskToReadyList添加新创建任务到就绪列表中
记录任务数量uxCurrentNumberOfTasks++.
判断新创建的任务是否为第一个任务
是,初始化任务列表prvlnitialiseTaskLists
不是,比较新任务与正在执行的任务的优先级大小,新优先级大,则将当前控制块指向新的控制块.
新的任务控制块添加到就绪列表,调用prvAddTaskToReadyList
将uxTopReadyPriority对应bit置1,表示响应优先级就绪列表中存在就绪任务.
将新创建的任务插入对应优先级就绪列表末尾.
如果调度器已经开始运行,且新任务优先级更大的话,进行任务切换.
静态创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 xTaskCreateStatic(); TaskHandler_t xTaskCreateStatic ( taskFunction_t pxTaskCode, const char * const pcName, const uint32_t ulStackDepth, void * const pvParamters, UBaseType_t uxPriority, StackType_t * const puxStackBuffer, StaticTask_t * const pxTaskBuffer ) ;NULL 其他值
将宏configSUPPORT_STATIC_ALLOCATION 配置为1.
定义空闲任务&定时器任务的任务堆栈及TCB.
实现两个接口函数.
vApplicationGetIdleTaskMemory( ); 为空闲任务设置任务堆栈和控制块.
vApplicationGetTimerTaskMemory (); 为定时器任务.
定义任务函数.
创建任务.
静态创建任务内部实现
获取控制块内存(首地址).
获取堆栈内存(首地址).
标记使用静态的方式申请的TCB和堆栈内存.
调用prvlnitialiseNewTask初始化任务块,并将控制块信息返回给任务句柄,以便后续返回句柄信息.
调用prvAddNewTaskToReadyLisst添加新创建的任务到就绪列表中.
删除任务 1 2 3 4 5 void vTaskDelete (TaskHandle_t xTaskToDelete) ;
使用删除任务函数,需将宏INCLUDE_vTaskDelete 配置为1.
入口参数输入需要删除的任务句柄(NULL代表删除本身).
任务挂起与恢复 任务挂起 1 2 void vTaskSuspend (TaskHandle_t xTaskToSuspend) ;
使用时需要将宏INCLUDE_vTaskSuspend配置为1.
挂起的任务将不再被执行,直到任务被恢复.
当传入的参数为NULL,代表挂起本身.
任务挂起内部实现
根据任务句柄获取任务控制块,如果为NULL,表示挂起自身.
将要挂起的任务从相应的状态列表和事件列表中移除.
将待挂起任务的任务状态列表插入到挂起态任务列表末尾.
判断任务调度器是否运行,运行则更新下一次阻塞时间,防止被挂起任务为下一阻塞超时任务.
如果挂起的是任务自身,且调度器正在运行,进行一次任务切换;调度器没有运行,判断挂起任务数量是否等于任务总数量,是则将当前控制块赋值为NULL,否则寻找下一个最高优先级任务.
任务恢复 1 2 void vTaskResume (TaskHandle_t xTaskToResume) ;
使用时需要将宏INCLUDE_vTaskSuspend配置为1.
无论被挂起多少次,恢复都只需要一次,即不存在类似引用的那种机制,需要引用次数为0才删除.
任务恢复内部实现
恢复任务不能是正在运行的任务.
判断任务是否在挂起列表中,是则将该任务从挂起列表中移除,并将该任务添加到就绪列表中.
判断恢复任务的任务优先级是否大于当前正在运行的,是则执行任务切换.
任务恢复(中断中恢复)
1 2 3 4 5 6 BaseType_t xTaskResumeFromISR (TaskHandle_t xTaskToResume) ;
使用时需要将宏INCLUDE_vTaskSuspend和INCLUDE_xTaskResumeFromISR配置为1.
该函数用于中断服务函数中.
中断服务程序中想要调用FreeRTOS的API函数,中断优先级不能高于FreeRTOS所管理的最高优先级.
任务恢复(中断中恢复)内部实现
关闭FreeRTOS可管理中断,防止被其他中断打断,并返回关闭前的basepri寄存器值.
判断是否有挂起任务.
有挂起任务:检查调度器是否被挂起.
调度器未挂起:
判断恢复任务的优先级是否大于正在执行的任务,是的话将xYieldRequired标记为pdTRUE,表示需要执行任务切换.
将被恢复的任务从挂起列表中移除.
插入到就绪列表.
调度器挂起:如果调度器被挂起了,将恢复的任务插入等待就绪列表,知道调度器被恢复再进行任务的处理.
无挂起任务:不需操作.
将前面保存的basepri的值,恢复.
返回xYieldRequired的值,决定是否需要进行任务切换.
中断管理 中断优先级分组 ARM Cortex-M 使用8位宽的寄存器来配置中断的优先级等级,也就是2^8 = 256个优先级,但STM32,只用了其中的高4位,也就是2^4 = 16个优先级.
STM32 的中断优先级分为抢占优先级和子优先级. 抢占优先级 :优先级高的中断可以打断正在执行但抢占优先级低的中断;子优先级 :抢占优先级相同时,子优先级高的可以打断优先级低的中断. STM32中中断优先级数值越小越优先.
中断优先级分组设置
也就是进一步细分,将一共16个优先级,根据不同规则,分为5种不同的方式,FreeRTOS 使用分组4,此规则下,全为抢占优先级,方便FreeRTOS 管理.
中断相关寄存器 三个系统中断优先级配置寄存器:
寄存器
寄存器地址
SHPR1
0xE000ED18
SHPR2
0xE000ED1C
SHPR3
0xE000ED20
“Cortex M3权威指南(中文)” 286页:
FreeRTOS如何配置PendSV和Systick中断优先级
可以看出设置为最低,第3图中左移目的为根据SHPR3 的基地址偏移获取PendSV 和Systick 的优先级寄存器地址. 第4图中左移4位的目的是STM32 中中断寄存器低4位不使用.
中断相关寄存器
FreeRTOS 所使用的中断管理,利用的就是BASEPRI 寄存器. BASEPRI :屏蔽优先级低于某个值的中断. 如BASEPRI设置为0x50,则表示中断优先级在0-15的都被屏蔽.
关中断程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() static portFORCE_INLINE void vPortRaiseBASEPRI ( void ) { uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { msr basepri, ulNewBASEPRI dsb isb } } #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
以FreeRTOS 管理的最高优先级做为阈值,对basepri寄存器赋值.
开中断程序
1 2 3 4 5 6 7 8 #define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) static portFORCE_INLINE void vPortSetBASEPRI ( uint32_t ulBASEPRI ) { __asm { msr basepri, ulBASEPRI } }
即将basepri寄存器设置为0.
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void task1 ( void * pvParameters ) { uint8_t task1_num = 0 ; while (1 ) { if (++task1_num == 5 ) { task1_num = 0 ; printf ("关中断!!\r\n" ); portDISABLE_INTERRUPTS(); delay_ms(5000 ); printf ("开中断!!!\r\n" ); portENABLE_INTERRUPTS(); } vTaskDelay(1000 ); } }
临界段代码保护及调度器挂起与恢复 一个防止中断打断,一个防止任务打断.
临界段代码保护函数
函数
描述
taskENTER_CRITICAL()
任务级进入临界段
taskEXIT_CRITICAL()
任务级退出临界段
taskENTER_CRITICAL_FROM_ISR()
中断级进入临界段
taskEXIT_CRITICAL_FROM_ISR()
中断级退出临界段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 taskENTER_CRITICAL(); { } taskEXIT_CRITICAL() uint32_t save_status;save_status = taskENTER_CRITICAL_FROM_ISR(); { } taskEXIT_CRITICAL_FROM_ISR(save_status );
临界段代码保护函数内部实现
任务级 :进入临界区时,关闭中断,且对一变量++;退出临界区时,对该变量–,如果变量值为0,则开启中断,类似于pv操作.
中断级 :进入临界区时,将目前的baserpi寄存器的值保存,关闭中断后返回出来;退出临界区时,将进入时返回的值设置回去,保证中断状态一致.
任务调度器的挂起和恢复
函数
描述
vTaskSuspendALL()
挂起任务调度器
xTaskResumeALL()
恢复任务调度器
1 2 3 4 5 6 vTaskSuspendAll(); { } xTaskResumeAll();
与临界区不一样,挂起任务调度器,未关闭中断.
仅仅是防止任务之间的资源竞争.
任务调度器的挂起和恢复内部实现
挂起任务调度器 :调用一次挂起调度器,变量uxScheduleSuspended+1.
变量uxSchedulerSuspended的值不为0,则会导致Systick不会触发PendSV,也就是触发任务切换.
恢复任务调度器 :调用一次恢复调度器,变量uxSchedulerSuspended-1.
如果等于0,则允许调度
移除等待就绪列表中的列表项,恢复至就绪列表,直到xPendingReadyList列表为
空.
如果恢复的任务优先级比当前正在执行的任务优先级高,则将xYieldPending赋值为pdTRUE,表示需要执行一次任务切换.
在调度器被挂起期间,是否有丢失会处理的滴答数. xPendedCounts是丢失的滴答数,有则调用xTasklncrementTick()补齐丢失的滴答数.
判断是否允许任务切换.
返回任务是否已经切换,已经切换返回pdTRUE,否则返回pdFALSE.
列表和列表项 列表和列表项简介 FreeRTOS 中的一个数据结构,概念上类似于双向环形链表,列表被用来追踪任务.
列表结构体
1 2 3 4 5 6 7 typedef struct xLIST { listFIRST_LIST_INTEGRITY_CHECK_VALUE volatile UBaseType_t uxNumberOfItems; ListItem_t * configLIST_VOLATILE pxIndex; MiniListItem_t xListEnd; listSECOND_LIST_INTEGRITY_CHECK_VALUE }List_t;
列表项
1 2 3 4 5 6 7 8 9 10 struct xLIST_ITEM { listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE configLIST_VOLATILE TickType_t xItemValue; struct xLIST_ITEM * configLIST_VOLATILE pxNext ; struct xLIST_ITEM * configLIST_VOLATILE pxPrevious ; void * pvOwner; struct xLIST * configLIST_VOLATILE pxContainer ; listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE }; typedef struct xLIST_ITEM ListItem_t ;
xItemValue列表项的值用于列表项插入时升序插入.
pxNext和pxPrevious实现双向环形链表数据结构.
pvOwner指向包含列表项的对象,任务控制块.
pxContainer指向所在列表,如就绪列表…
迷你列表项
1 2 3 4 5 6 7 8 struct xMINI_LIST_ITEM { listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE configLIST_VOLATILE TickType_t xItemValue; struct xLIST_ITEM * configLIST_VOLATILE pxNext ; struct xLIST_ITEM * configLIST_VOLATILE pxPrevious ; }; typedef struct xMINI_LIST_ITEM MiniListItem_t ;
xItemValue,用于排序.
pxNext和pxPrevious
实现双向环形链表数据结构.
相当于链表的头/尾节点,本身不存储信息,用于标记链表的起始/末位置.
列表相关API函数 1 2 3 4 void vListInitialise (List_t * const pxList) ;
1 2 3 void vListInitialiseItem ( ListItem_t * const pxItem ) ;
1 2 3 4 void vListInsert ( List_t * const pxList, ListItem_t * const pxNewListItem ) ;
1 2 3 void vListInsertEnd (List_t * const pxList, ListItem_t * const pxNewListItem) ;
1 2 3 4 5 UBaseType_t uxListRemove ( ListItem_t * const pxItemToRemove ) ;
任务调度 开启任务调度器
创建空闲任务.
如果使能了软件定时器,创建定时器任务.
关闭中断,防止调度器开启之前或过程中,受中断干扰,在运行第一个任务时打开中断.
初始化全局变量,将任务调度器的运行标志设置为已运行.
初始化任务运行时间统计功能的时基定时器.
调用函数xPortStartScheduler().
xPortStartScheduler
检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误. 程序开头部分的一大段断言
配置PendSV和SysTick的中断优先级为最低优先级.
调用函数vPortSetupTimerInterrupt()配置为SysTick.
初始化临界区嵌套计数器为0.
调用函数prvEnableVFP()使能FPU.
调用函数prvStartFirstTask()启动第一个任务.
启动第一个任务
将任务的寄存器值恢复到CPU寄存器中,任务的寄存器值,在一开始创建任务时保存在任务堆栈中.
中断产生时,硬件自动将xPSR,PC(R15),LR(14),R12,R3-R9出、入栈,而R4-R11需要手动出/入栈.
进入中断后硬件会强制使用MSP指针,此时LR(R14)的值将会被自动的更新为特殊的EXC_RETURN.
prvStartFirstTask()
用于初始化启动第一个任务前的环境,主要时重新设置MSP指针,并使能全局中断(在启动任务调度器时关闭了).
MSP指针,程序运行过程中需要一定的栈空间来保存局部变量等一些信息. 当有信息保存至栈中,MCP会自动更新SP指针,ARM CORTEX-M 内核提供了两个栈空间:
主堆栈指针(MSP):它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用.
进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时).
在FreeRTOS中,中断使用MSP,中断以外使用PSP.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 __asm void prvStartFirstTask ( void ) { PRESERVE8 ldr r0, =0xE000ED08 ldr r0, [r0] ldr r0, [r0] msr msp, r0 cpsie i cpsie f dsb isb svc 0 nop nop }
vPortSVCHandler()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 __asm void vPortSVCHandler ( void ) { PRESERVE8 ldr r3, =pxCurrentTCB ldr r1, [r3] ldr r0, [r1] ldmia r0!, {r4-r11} msr psp, r0 isb mov r0, #0 msr basepri, r0 orr r14, #0xd bx r14 }
ldr r3, =pxCurrentTCB 获取当前优先级最高的任务控制块地址.
ldr r1, [r3] 根据地址获取任务控制块.
ldr r0, [r1] 根据任务控制块地址获取第一个元素的即任务堆栈栈顶地址.
ldmia r0!, {r4-r11} 出栈,即将任务控制器R4-R11出栈,加载到CPU寄存器中.
isb同步指令,确保之前的指令已经执行完毕.
mov r0, #0 ; msr basepri, r0 向basepri寄存器写0,允许中断.
bx r14 跳转至任务函数地址执行.
任务切换 任务切换本质
任务切换本质是通过对CPU寄存器的切换,也就是说切换到另一个任务时,首先要保存当前任务的寄存器到任务堆栈,再将另一个任务的寄存器值恢复到CPU寄存器,整个过程称为上下文切换 . 任务切换的过程在PendSV中断服务函数 里完成.
PendSV如何触发
滴答定时器中断调用.
执行Freertos提供的API:portYIELD ().
本质是通过向中断控制寄存器ICSR的bit28写1挂起PendSV来启动中断.
“Cortex M3权威指南(中文)” 131页:
查找最高优先级任务
1 2 3 4 5 6 7 8 9 10 11 12 vTaskSwitchContext(); #define taskSELECT_HIGHEST_PRIORITY_TASK() \ { \ UBaseType_t uxTopPriority; \ \ \ portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \ configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \ }
前导置零指令
uxTopReadyPriority 指的是对应优先级就绪列表前那个标志位,比如当前有优先级为3的ABC任务,以及优先级为28的C任务,那么uxTopReadyPriority的bit28和bit3置1. 如何从中寻找优先级最高的任务,则是通过前导置零指令.
1 #define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
前导置零的意思,计算一个二进制数,头部0的个数. 再以31减去头部0的个数,则得到当前就绪列表中就绪的最高优先级.
获取最高优先级任务的任务控制块
就绪列表,相当于链表数组,对应32个优先级不同的链表,每条链表又将优先级相同的任务串起来,在第一步得知当前就绪的最高优先级后,需要的是从对应的链表pxReadyTasksLists[ uxTopPriority ] 中逐个取出任务控制块执行. 通过如下函数实现.
1 2 3 4 5 6 7 8 9 10 11 12 #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \ { \ List_t * const pxConstList = ( pxList ); \ \ \ ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ if ( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \ { \ ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ } \ ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \ }
任务切换内部实现
总之,vTaskSwitchContext调用前,即保存当前执行任务的寄存器值到任务堆栈,调用之后,更新当前执行任务的TCB,根据任务堆栈的寄存器值,恢复到CPU寄存器中.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 __asm void xPortPendSVHandler ( void ) { extern uxCriticalNesting; extern pxCurrentTCB; extern vTaskSwitchContext; PRESERVE8 mrs r0, psp isb ldr r3, =pxCurrentTCB ldr r2, [r3] stmdb r0!, {r4-r11} str r0, [r2] stmdb sp!, {r3, r14} mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0 dsb isb bl vTaskSwitchContext mov r0, #0 msr basepri, r0 ldmia sp!, {r3, r14} ldr r1, [r3] ldr r0, [r1] ldmia r0!, {r4-r11} msr psp, r0 isb bx r14 nop }
时间管理 vTaskDelay 每次任务执行结束 到下次任务执行开始 之间的时间间隔相同. 间隔即位所填参数.
1 void vTaskDelay (const TickType_t xTicksToDelay ) ;
vTaskDelayUntil 每次任务执行开始 到任务下次执行开始 之间的时间间隔相同.间隔即位所填参数.
1 2 void vTaskDelayUntil ( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void task_1 (void * arg) { TickType_t Start = xTaskGetTickCount(); int i = 0 ; int j = 0 ; while (1 ) { task1flagrun = 1 ; task2flagrun = 0 ; task3flagrun = 0 ; for (i = 0 ; i < rand[j]; i++) printf ("1" ); j++; if (j == 5 ) j = 0 ; #if 0 vTaskDelay(20 ); #else vTaskDelayUntil(&Start, 20 ); #endif } } void main () { xTaskCreate(task_1, "task_1" , 100 , NULL , 2 , &xHandleTask1); }
vTaskDelay内部实现
判断时延时间是否大于0.
挂起任务调度器.
就当前任务从就绪列表移除,添加到阻塞列表.
将该任务从就绪列表中移除. 如果移除之后,就绪列表数量为0,则将uxTopReadyPriority置0,代表此就绪列表为空.
如果使能了挂起操作,且时延时间为最大值(portMAX_DELAY ),且xCanBlockIndefinitely为pdTRUE. 相当于永久阻塞,等同于挂起,插入挂起列表.
如果时延时间小于最大值(portMAX_DELAY )
记录阻塞超时时间,并记录在列表项里.
如果记录阻塞超时时间移除,将该任务状态列表项添加到溢出阻塞列表.
.如果没溢出,则将该任务状态列表项添加到阻塞列表,并判断阻塞超时时间是否小于下
一个阻塞超时时间,是的话就更新当前这个时间为下一个阻塞超时时间.
恢复任务调度器.
执行任务切换.
延时函数流程
正在运行的任务,调用延时函数,将任务移除就绪列表,并添加到阻塞列表中;滴答中断里进行计时,判断阻塞时间是否到达,如果到达将从阻塞列表中移除,并添加到就绪列表.
队列 队列简介
队列是任务到任务,任务到中断,中断到任务数据交流的一种机制(消息传递). Freertos队列特点:
数据入队出队方式,通常为FIFO,也就是先进先出.
数据传递方式,采用值传递.
多任务访问,队列不属于某个任务,任何任务和中断都可以访问.
出队、入队阻塞,当任务像一个队列发送消息时,可以指定一个阻塞时间.
若阻塞时间为0,直接返回不阻塞.
若阻塞时间为0~port_MAX_DELAY,等待设定的阻塞时间.
若阻塞时间为port_MAX_DELAY,死等,一直等到可以入队/出队.
当多个任务同时阻塞时,被唤醒时,优先唤醒优先级最高的任务;如果大家优先级相同,唤醒等待时间最久的任务.
队列结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 typedef struct QueueDefinition { int8_t * pcHead int8_t * pcWriteTo; union { QueuePointers_t xQueue; SemaphoreData_t xSemaphore; } u ; List_t xTasksWaitingToSend; List_t xTasksWaitingToReceive; volatile UBaseType_t uxMessagesWaiting; UBaseType_t uxLength; UBaseType_t uxItemSize; volatile int8_t cRxLock; volatile int8_t cTxLock; } xQUEUE; typedef struct QueuePointers { int8_t * pcTail; int8_t * pcReadFrom; } QueuePointers_t; typedef struct SemaphoreData { TaskHandle_t xMutexHolder; UBaseType_t uxRecursiveCallCount; } SemaphoreData_t;
创建队列
函数
描述
xQueueCreate()
动态方式创建队列
xQueueCreateStatic()
静态方式创建队列
1 2 3 4 5 6 7 8 9 #define xQueueCreate ( uxQueueLength, uxItemSize ) \ xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE )) #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 )
形参
描述
uxQueueLength
队列长度
uxItemSize
队列项目的大小
返回值
描述
NULL
队列创建失败
其他值
队列创建成功,返回队列句柄
创建队列内部实现
实际执行的是xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) ).
计算队列需要多大内存.
为队列申请内存,内存大小为存储内存+队列结构体内存,前面部分存放结构体内存,后面就是队列项大小.
判断内存是否申请成功,成功则计算队列存储区的首地址,即前面申请内存的地址偏移队列结构体的大小.
调用prvInitialiseNewQueue()初始化新队列.
初始化队列结构体变量.
调用xQueueGenericReset()复位队列.
初始化其他队列结构体成员变量.
判断要复位的队列是否为新创建的队列.
不是新创建的队列,那就复位它,将列表xTasksWaitingToSend(因队列满,阻塞在等待发送列表的任务)移除.
是新创建的队列,那就初始化这两个列表xTaskWaitingToSend和xTaskWaitingTo Receive.
向队列写入消息
函数
描述
xQueueSend()
往队列的尾部写入消息
xQueueSendToBack()
同 xQueueSend()
xQueueSendToFront()
往队列的头部写入消息
xQueueOverwrite()
覆写队列消息(只用于队列长度为 1 的情况)
xQueueSendFromISR()
在中断中往队列的尾部写入消息
xQueueSendToBackFromISR()
同 xQueueSendFromISR()
xQueueSendToFrontFromISR()
在中断中往队列的头部写入消息
xQueueOverwriteFromISR()
在中断中覆写队列消息(只用于队列长度为 1 的情况)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) \ xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK ) #define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) \ xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK ) #define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) \ xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT ) #define xQueueOverwrite( xQueue, pvItemToQueue ) \ xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0 , queueOVERWRITE ) #define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) #define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) #define queueOVERWRITE ( ( BaseType_t ) 2 )
1 2 3 4 BaseType_t xQueueGenericSend ( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition ) ;
形参
描述
xQueue
待写入的队列
pvItemToQueue
待写入消息
xTicksToWait
阻塞超时时间
xCopyPosition
写入的位置
返回值
描述
pdTRUE
队列写入成功
errQUEUE_FULL
队列写入失败
向队列写入消息内部实现
进入临界区,关中断.
判断队列是否已满.
队列有空闲位置或为覆写.
当有空闲位置或覆写时,将待写入消息按指定写入方式复制到队列中.
判断是否有因为读不到消息而阻塞的任务,有的话,将解除阻塞,通过函数:xTaskRemoveFromEventList()实现.
xTaskRemoveFromEventList,判断调度器是否被挂起,没有挂起:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表中.
挂起:将移除事件列表项,将事件列表项添加到等待就绪列表 :xPendingReadyList,当调用恢复调度器时xTaskResumeAll(),等待就绪列表中的任务就会被处理.
退出临界区,开中断.
队列已满.
此时不能写入消息,因此要将任务阻塞.
如果阻塞时间为0,代表不阻塞,直接返回队列满错误.
如果阻塞时间不为0,任务需要阻塞,记录下此时系统节拍计数器的值 和溢出次数 ,用于下面对阻塞时间进行补偿.
判断阻塞时间补偿后,是否还需要阻塞.
需要:将任务的事件列表项添加到等待发送列表中,将任务的状态列表项添加到阻塞列表中进行阻塞,队列解锁,恢复调度器.
不需要:队列解锁,恢复调度器,返回队列满错误.
从队列读取消息
函数
描述
xQueueReceive()
从队列头部读取消息,并删除消息
xQueuePeek()
从队列头部读取消息
xQueueReceiveFromISR()
在中断中从队列头部读取消息,并删除消息
xQueuePeekFromISR()
在中断中从队列头部读取消息
1 2 BaseType_t xQueueReceive ( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
形参
描述
xQueue
待读取的队列
pvBuffer
信息读取缓冲区
xTicksToWait
阻塞超时时间
返回值
描述
pdTRUE
读取成功
pdFALSE
读取失败
1 2 BaseType_t xQueuePeek ( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
形参
描述
xQueue
待读取的队列
pvBuffer
信息读取缓冲区
xTicksToWait
阻塞超时时间
返回值
描述
pdTRUE
读取成功
pdFALSE
读取失败
从队列读取消息内部实现
xQueueReceive():
进入临界区,关中断.
判断队列是否为空.
不为空,有数据.
使用函数prvCopyDataFromQueue( )拷贝数据.
队列项目个数减一.
因为前面减少了队列项个数,所以此时队列有空位,如果xTaskWaitingToSend等待发送列表中,有任务,则解除阻塞态,通过函数xTaskRemoveFromEventList.
判断调度器是否被挂起,没有:将移除相应的事件列表项和状态列表项,并且将任务添加到就绪列表.
被挂起:将事件列表项添加到等待就绪列表 :xPendingReadyList,当调用恢复调度器时xTaskResumeAll(),等待就绪列表中的任务就会被处理.
退出临界区,开中断.
为空,没有数据.
此时读取不到消息,因此将任务阻塞.
如果阻塞时间为0 ,代表不阻塞,直接返回队列空错误.
如果阻塞时间不为0,任务需要阻塞,记录下此时系统节拍计数器的值和溢出次数,用
于下面对阻塞时间进行补偿.
判断阻塞时间补偿后,是否还需要阻塞.
需要:将任务的事件列表项添加到等待接收列表中,将任务状态列表项添加到阻塞列表中进行阻塞,队列解锁,恢复调度器
不需要:队列解锁,恢复调度器,返回队列空错误
信号量 二值信号量 二值信号量本质是一个队列长度为1的队列,该队列只有空和满两种状态,这就是二值. 二值信号量通常用于互斥访问或任务同步,与互斥信号量类似,但存在优先级翻转问题,所以更适合用于同步.
函数
描述
xSemaphoreCreateBinary()
使用动态方式创建二值信号量
xSemaphoreCreateBinaryStatic()
使用静态方式创建二值信号量
xSemaphoreGive()
释放信号量
xSemaphoreGiveFromISR()
在中断中释放信号量
xSemaphoreTake()
获取信号量
xSemaphoreTakeFromISR()
在中断中获取信号量
创建二值信号量
1 2 3 4 5 6 SemaphoreHandle_t xSemaphoreCreateBinary (void ) ; #define xSemaphoreCreateBinary( ) \ xQueueGenericCreate( 1 , semSEMAPHORE_QUEUE_ITEM_LENGTH , queueQUEUE_TYPE_BINARY_SEMAPHORE ) #define semSEMAPHORE_QUEUE_ITEM_LENGTH ( ( uint8_t ) 0U )
返回值
描述
NULL
创建失败
其他值
创建成功返回二值信号量的句柄
释放二值信号量
1 2 3 4 5 6 BaseType_t xSemaphoreGive (xSemaphore) ; #define xSemaphoreGive ( xSemaphore ) \ xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ) , NULL , semGIVE_BLOCK_TIME , queueSEND_TO_BACK ) #define semGIVE_BLOCK_TIME ( ( TickType_t ) 0U )
形参
描述
xSemaphore
要释放的信号量句柄
返回值
描述
pdPASS
释放信号量成功
errQUEUE_FULL
释放信号量失败
获取二值信号量
1 BaseType_t xSemaphoreTake (xSemaphore, xBlockTime) ;
形参
描述
xSemaphore
要获取的信号量句柄
xBlockTime
阻塞时间
返回值
描述
pdTRUE
获取信号量成功
pdFALSE
超时,获取信号量失败
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t task2_handler; void task2 ( void * pvParameters ) ;void freertos_demo (void ) { semphore_handle = xSemaphoreCreateBinary(); if (semphore_handle != NULL ) { printf ("二值信号量创建成功!!!\r\n" ); } xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate((TaskFunction_t ) task2, (char * ) "task2" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; BaseType_t err; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { if (semphore_handle != NULL ) { err = xSemaphoreGive(semphore_handle); if (err == pdPASS) { printf ("信号量释放成功!!\r\n" ); }else printf ("信号量释放失败!!\r\n" ); } } vTaskDelay(10 ); } } void task2 ( void * pvParameters ) { uint32_t i = 0 ; BaseType_t err; while (1 ) { err = xSemaphoreTake(semphore_handle,portMAX_DELAY); if (err == pdTRUE) { printf ("获取信号量成功\r\n" ); }else printf ("已超时%d\r\n" ,++i); } }
计数信号量 计数型信号量相当于队列长度大于1 的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的. 计数信号量的释放和获取与二值信号量相同 .
函数
描述
xSemaphoreCreateCounting()
使用动态方法创建计数型信号量。
xSemaphoreCreateCountingStatic()
使用静态方法创建计数型信号量
uxSemaphoreGetCount()
获取信号量的计数值
创建计数信号量
此函数用于创建一个计数信号量.
1 #define xSemaphoreCreateCounting( uxMaxCount , uxInitialCount ) \ xQueueCreateCountingSemaphore( ( uxMaxCount ) , ( uxInitialCount ) )
形参
描述
uxMaxCount
计数值的最大值限定
uxInitialCount
计数值的初始值
返回值
描述
NULL
创建失败
其他值
创建成功返回计数型信号量的句柄
获取信号量的计数值
此函数用于获取信号量当前计数值大小》
1 #define uxSemaphoreGetCount( xSemaphore ) \ uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t task2_handler; void task2 ( void * pvParameters ) ;void freertos_demo (void ) { count_semphore_handle = xSemaphoreCreateCounting(100 , 0 ); if (count_semphore_handle != NULL ) { printf ("计数型信号量创建成功!!!\r\n" ); } xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate((TaskFunction_t ) task2, (char * ) "task2" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { if (count_semphore_handle != NULL ) { xSemaphoreGive(count_semphore_handle); } } vTaskDelay(10 ); } } void task2 ( void * pvParameters ) { BaseType_t err = 0 ; while (1 ) { err = xSemaphoreTake(count_semphore_handle,portMAX_DELAY); if (err == pdTRUE) { printf ("信号量的计数值为:%d\r\n" ,(int )uxSemaphoreGetCount(count_semphore_handle)); } vTaskDelay(1000 ); } }
优先级翻转 高优先级的任务反而慢执行,低优先级的任务反而优先执行. 优先级翻转再抢占式内核中非常常见,在使用二值信号量的时候,经常会遇到优先级翻转的问题.
说明
如以下例子,3个任务,优先级分别为H(high),M(middle),L(low);H任务和L任务在执行时,都需要先获取某二值信号量,执行完任务后释放信号量;M任务普通执行;L任务执行时间较长.
因此可能出现如下问题,L获取信号量,然后开始执行任务;此时H抢占CPU,同样获取信号量,但是此时信号量已经被L占据,所以H被阻塞直到L释放信号量;此时M就绪,抢占L的CPU,执行任务;因为L的执行时间较长,在执行完之前,可能会被M任务多次抢占,而H任务阻塞在信号量上,优先级比M高反而一直得不到执行,这就是优先级翻转.
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t low_task_handler; void low_task ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t middle_task_handler; void middle_task ( void * pvParameters ) ;#define TASK3_PRIO 4 #define TASK3_STACK_SIZE 128 TaskHandle_t high_task_handler; void high_task ( void * pvParameters ) ;void freertos_demo (void ) { semphore_handle = xSemaphoreCreateBinary(); if (semphore_handle != NULL ) { printf ("二值信号量创建成功!!!\r\n" ); } xSemaphoreGive(semphore_handle); xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) low_task, (char * ) "low_task" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &low_task_handler ); xTaskCreate((TaskFunction_t ) middle_task, (char * ) "middle_task" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &middle_task_handler ); xTaskCreate((TaskFunction_t ) high_task, (char * ) "high_task" , (configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK3_PRIO, (TaskHandle_t * ) &high_task_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void low_task ( void * pvParameters ) { while (1 ) { printf ("low_task获取信号量\r\n" ); xSemaphoreTake(semphore_handle,portMAX_DELAY); printf ("low_task正在运行!!!\r\n" ); delay_ms(3000 ); printf ("low_task释放信号量\r\n" ); xSemaphoreGive(semphore_handle); vTaskDelay(1000 ); } } void middle_task ( void * pvParameters ) { while (1 ) { printf ("middle_task正在运行!!!\r\n" ); vTaskDelay(1000 ); } } void high_task ( void * pvParameters ) { while (1 ) { printf ("high_task获取信号量\r\n" ); xSemaphoreTake(semphore_handle,portMAX_DELAY); printf ("high_task正在运行!!!\r\n" ); delay_ms(1000 ); printf ("high_task释放信号量\r\n" ); xSemaphoreGive(semphore_handle); vTaskDelay(1000 ); } }
互斥信号量 互斥信号量是一个拥有优先级继承 的二值信号量,适合于那些需要互斥访问的应用中. 优先级继承指的是:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级任务也尝试获取这个互斥信号量,那么这个高优先级的任务会被阻塞. 但这个高优先级的任务会将低优先级的任务的优先级提升到与自己相同的优先级. 优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响.
互斥信号量不能用于中断服务函数中,原因如下:
互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级, 所以互斥信号量只能用与任务中,不能用于中断服务函数.
中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态.
函数
描述
xSemaphoreCreateMutex()
使用动态方法创建互斥信号量。
xSemaphoreCreateMutexStatic()
使用静态方法创建互斥信号量。
说明
与前面优先级翻转的区别在于,低优先级任务的优先级被提升后,不会被M抢占,会确保自己尽快执行完.
创建互斥信号量
此函数用于创建互斥信号量.
1 #define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
返回值
描述
NULL
创建失败
其他值
创建成功返回互斥信号量的句柄
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t low_task_handler; void low_task ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t middle_task_handler; void middle_task ( void * pvParameters ) ;#define TASK3_PRIO 4 #define TASK3_STACK_SIZE 128 TaskHandle_t high_task_handler; void high_task ( void * pvParameters ) ;void freertos_demo (void ) { mutex_semphore_handle = xSemaphoreCreateMutex(); if (mutex_semphore_handle != NULL ) { printf ("互斥信号量创建成功!!!\r\n" ); } xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) low_task, (char * ) "low_task" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &low_task_handler ); xTaskCreate((TaskFunction_t ) middle_task, (char * ) "middle_task" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &middle_task_handler ); xTaskCreate((TaskFunction_t ) high_task, (char * ) "high_task" , (configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK3_PRIO, (TaskHandle_t * ) &high_task_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void low_task ( void * pvParameters ) { while (1 ) { printf ("low_task获取信号量\r\n" ); xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY); printf ("low_task正在运行!!!\r\n" ); delay_ms(3000 ); printf ("low_task释放信号量\r\n" ); xSemaphoreGive(mutex_semphore_handle); vTaskDelay(1000 ); } } void middle_task ( void * pvParameters ) { while (1 ) { printf ("middle_task正在运行!!!\r\n" ); vTaskDelay(1000 ); } } void high_task ( void * pvParameters ) { while (1 ) { printf ("high_task获取信号量\r\n" ); xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY); printf ("high_task正在运行!!!\r\n" ); delay_ms(1000 ); printf ("high_task释放信号量\r\n" ); xSemaphoreGive(mutex_semphore_handle); vTaskDelay(1000 ); } }
队列集 一个队列只允许任务间传递的消息为同一类型,如果需要在任务间传递不同类型消息时,就可以使用队列集. 队列集用于对多个队列或信号量进行监听,类似于监听文件描述符的select和epoll.
1 2 3 4 5 6 7 8 9 10 11 12 13 task() { 等待接收队列; 获取信号量; } task() { 等待队列集中消息; if (队列还是信号量) { ...; } }
函数
描述
xQueueCreateSet()
创建队列集
xQueueAddToSet()
队列添加到队列集中
xQueueRemoveFromSet()
从队列集中移除队列
xQueueSelectFromSet()
获取队列集中有有效消息的队列
xQueueSelectFromSetFromISR()
在中断中获取队列集中有有效消息的队列
创建队列集
此函数用于创建队列集.
1 QueueSetHandle_t xQueueCreateSet ( const UBaseType_t uxEventQueueLength ) ;
形参
描述
uxEventQueueLength
队列集可容纳的队列数量
返回值
描述
NULL
队列集创建失败
其他值
队列集创建成功,返回队列集句柄
队列集添加队列
此函数用于往队列集中添加队列,要注意的时,队列在被添加到队列集之前,队列中不能有有效的消息.
1 2 BaseType_t xQueueAddToSet (QueueSetMemberHandle_t xQueueOrSemaphore , QueueSetHandle_t xQueueSet) ;
形参
描述
xQueueOrSemaphore
待添加的队列句柄
xQueueSet
队列集
返回值
描述
pdPASS
队列集添加队列成功
pdFAIL
队列集添加队列失败
队列集移除队列
此函数用于从队列集中移除队列, 要注意的是,队列在从队列集移除之前,必须没有有效的消息.
1 2 BaseType_t xQueueRemoveFromSet (QueueSetMemberHandle_t xQueueOrSemaphore, QueueSetHandle_t xQueueSet ) ;
形参
描述
xQueueOrSemaphore
待移除的队列句柄
xQueueSet
队列集
返回值
描述
pdPASS
队列集移除队列成功
pdFAIL
队列集移除队列失败
从队列集中获取有效消息队列
此函数用于在任务中获取队列集中有有效消息的队列
1 2 QueueSetMemberHandle_t xQueueSelectFromSet (QueueSetHandle_t xQueueSet, TickType_t const xTicksToWait )
形参
描述
xQueueSet
队列集
xTicksToWait
阻塞超时时间
返回值
描述
NULL
获取消息失败
其他值
获取到消息的队列句柄
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t task2_handler; void task2 ( void * pvParameters ) ;void freertos_demo (void ) { xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); queueset_handle = xQueueCreateSet( 2 ); if (queueset_handle != NULL ) { printf ("队列集创建成功!!\r\n" ); } queue_handle = xQueueCreate( 1 , sizeof (uint8_t ) ); semphr_handle = xSemaphoreCreateBinary(); xQueueAddToSet( queue_handle,queueset_handle); xQueueAddToSet( semphr_handle,queueset_handle); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate((TaskFunction_t ) task2, (char * ) "task2" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; BaseType_t err = 0 ; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { err = xQueueSend( queue_handle, &key, portMAX_DELAY ); if (err == pdPASS) { printf ("往队列queue_handle写入数据成功!!\r\n" ); } }else if (key == KEY1_PRES) { err = xSemaphoreGive(semphr_handle); if (err == pdPASS) { printf ("释放信号量成功!!\r\n" ); } } vTaskDelay(10 ); } } void task2 ( void * pvParameters ) { QueueSetMemberHandle_t member_handle; uint8_t key; while (1 ) { member_handle = xQueueSelectFromSet( queueset_handle,portMAX_DELAY); if (member_handle == queue_handle) { xQueueReceive( member_handle,&key,portMAX_DELAY); printf ("获取到的队列数据为:%d\r\n" ,key); }else if (member_handle == semphr_handle) { xSemaphoreTake( member_handle, portMAX_DELAY ); printf ("获取信号量成功!!\r\n" ); } } }
事件标志组 事件标志组简介 事件标志位:用一个位来表示某事件是否发送;事件标志组:一组事件标志位的集合. 其特点:
它的每一个位表示一个事件.
每一位的事件由用户自己决定,为1表示事件发生了,为0表示未发生.
任意任务和中断都可以读写这些位.
可以等待某一位成立,或者等待多个位同时成立.
一个事件组就包含了一个 EventBits_t 数据类型的变量,变量类型 EventBits_t 的定义如下所示
1 2 3 4 5 6 7 8 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
高8位用作存储事件标志组的控制信息,其他位存储事件标志,所以一个事件标志组最多可以存储24个事件标志.
事件标志组于队列、信号量的区别
功能
唤醒对象
事件清除
队列、信号量
事件发生时,只会唤醒一个任务
是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了
事件标志组
事件发生时,会唤醒所有符合条件的任务,可以理解为“广播”的作用
被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件
事件标志组相关API函数
函数
描述
xEventGroupCreate()
使用动态方式创建事件标志组
xEventGroupCreateStatic()
使用静态方式创建事件标志组
xEventGroupClearBits()
清零事件标志位
xEventGroupClearBitsFromISR()
在中断中清零事件标志位
xEventGroupSetBits()
设置事件标志位
xEventGroupSetBitsFromISR()
在中断中设置事件标志位
xEventGroupWaitBits()
等待事件标志位
xEventGroupSync()
设置事件标志位,并等待事件标志位
创建事件标志组(动态)
1 EventGroupHandle_t xEventGroupCreate ( void ) ;
返回值
描述
NULL
事件标志组创建失败
其他值
事件标志组创建成功,返回其句柄
清除事件标志位
1 2 EventBits_t xEventGroupClearBits (EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear) ;
形参
描述
xEventGroup
待操作的事件标志组句柄
uxBitsToSet
待清零的事件标志位
返回值
描述
整数
清零事件标志位之前事件组中事件标志位的值
设置事件标志位
1 2 EventBits_t xEventGroupSetBits (EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet) ;
形参
描述
xEventGroup
待操作的事件标志组句柄
uxBitsToSet
待设置的事件标志位
返回值
描述
整数
函数返回时,事件组中的事件标志位值
等待事件标志位
1 2 3 4 5 EventBits_t xEventGroupWaitBits (EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait) ;
形参
描述
xEvenrGroup
等待的事件标志组句柄
uxBitsToWaitFor
等待的事件标志位,可以用逻辑或等待多个事件标志位
xClearOnExit
成功等待到事件标志位后,清除事件组中对应的事件标志位,pdTRUE :清除uxBitsToWaitFor指定位;pdFALSE:不清除
xWaitForAllBits
等待 uxBitsToWaitFor 中的所有事件标志位(逻辑与)pdTRUE:等待的位,全部为1pdFALSE:等待的位,某个为1
xTicksToWait
等待的阻塞时间
返回值
描述
等待的事件标志位值
等待事件标志位成功,返回等待到的事件标志位
其他值
等待事件标志位失败,返回事件组中的事件标志位
同步函数
1 2 3 4 EventBits_t xEventGroupSync (EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait) ;
形参
描述
xEventGroup
等待事件标志所在事件组
uxBitsToSet
达到同步点后,要设置的事件标志
uxBitsToWaitFor
等待的事件标志
xTicksToWait
等待的阻塞时间
返回值
描述
等待的事件标志位值
等待事件标志位成功,返回等待到的事件标志位
其他值
等待事件标志位失败,返回事件组中的事件标志位
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t task2_handler; void task2 ( void * pvParameters ) ;EventGroupHandle_t eventgroup_handle; #define EVENTBIT_0 (1 << 0) #define EVENTBIT_1 (1 << 1) void freertos_demo (void ) { xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); eventgroup_handle = xEventGroupCreate(); if (eventgroup_handle != NULL ) { printf ("事件标志组创建成功!!\r\n" ); } xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate((TaskFunction_t ) task2, (char * ) "task2" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { xEventGroupSetBits( eventgroup_handle, EVENTBIT_0); }else if (key == KEY1_PRES) { xEventGroupSetBits( eventgroup_handle, EVENTBIT_1); } vTaskDelay(10 ); } } void task2 ( void * pvParameters ) { EventBits_t event_bit = 0 ; while (1 ) { event_bit = xEventGroupWaitBits( eventgroup_handle, EVENTBIT_0 | EVENTBIT_1, pdTRUE, pdTRUE, portMAX_DELAY ); printf ("等待到的事件标志位值为:%#x\r\n" ,event_bit); } }
任务通知 任务通知简介 用来通知任务的,类似于队列、信号量、事件标志组的作用;但任务通知无需再借助额外的结构体来在两任务间传递消息,其本身以及存在于任务控制块中:ulNotifiedValue(任务通知值).
使用队列、信号量、事件标志组时都需要另外创建一个结构体.
使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发来的通知.
任务通知值的更新方式:
不覆盖接收任务的通知值.(类似于队列的不覆写)
覆盖接收任务的通知值.(类似于队列的覆写)
更新接收任务通知值的1个或多个bit.(类似于事件标志组)
增加接收任务的通知值.(类似于信号量)
任务通知的优势及劣势
优势:
效率更高,使用任务通知向任务发送事件或数据比使用队列、事件标志组或信号量快得多.
使用内存更小,使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体.
劣势:
无法发送数据给ISR,ISR没有任务结构体,所以无法给ISR发送数据。但是ISR可以使用任务通知的功能,发数据给任务.
无法广播给多个任务,任务通知只能是被指定的一个任务接收并处理.
无法缓存多个数据,任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据.
发送受阻不支持阻塞,发送方无法进入阻塞状态等待.
任务通知值和通知状态
任务都有一个结构体:任务控制块TCB,它里边有两个结构体成员变量:
1 2 3 4 5 6 7 8 9 10 typedef struct tskTaskControlBlock { … … #if ( configUSE_TASK_NOTIFICATIONS == 1 ) volatile uint32_t ulNotifiedValue [ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; volatile uint8_t ucNotifyState [ configTASK_NOTIFICATION_ARRAY_ENTRIES ]; endif … … } tskTCB; #define configTASK_NOTIFICATION_ARRAY_ENTRIES 1
一个是 uint32_t 类型,用来表示通知值.
一个是 uint8_t 类型,用来表示通知状态.
其中任务通知状态有3种取值:
1 2 3 #define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) #define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 ) #define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )
任务未等待通知 :任务通知默认的初始化状态.
等待通知:接收方已经准备好了(调用了接收任务通知函数),等待发送方给个通知.
等待接收:发送方已经发送出去(调用了发送任务通知函数),等待接收方接收.
任务通知相关API函数 任务通知函数主要有两类:1.发送通知;2.接收通知.
发送通知相关API函数:
函数
描述
xTaskNotify()
发送通知,带有通知值
xTaskNotifyAndQuery()
发送通知,带有通知值并且保留接收任务的原通知值
xTaskNotifyGive()
发送通知,不带通知值
xTaskNotifyFromISR()
在中断中发送任务通知
xTaskNotifyAndQueryFromISR()
在中断中发送任务通知
vTaskNotifyGiveFromISR()
在中断中发送任务通知
1 2 3 4 5 6 7 8 9 10 11 12 #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 )
1 2 3 4 5 BaseType_t xTaskGenericNotify (TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t * pulPreviousNotificationValue ) ;
形参
描述
xTaskToNotify
接收任务通知的任务句柄
uxIndexToNotify
任务的指定通知(任务通知相关数组成员)
ulValue
任务通知值
eAction
通知方式(通知值更新方式)
pulPreviousNotificationValue
用于保存更新前的任务通知值(为NULL则不保存)
xTaskGenericNotify函数内部实现
判断是否需要保存原先的任务通知值.
记录目标任务先前的通知状态,然后赋值为当前的任务状态(taskNOTIFICATION_RECEIVED 等待接收状态).
更新通知值,四种不同的更新方式.(更新通知值某些位,类似事件标志组;通知值++,类似信号量;覆写/不覆写的方式更新通知值,类似队列).
如果目标任务为等待通知状态(taskWAITING_NOTIFICATION),代表先前调用了任务接受函数,但是调用时没有发送任务通知值,所以目标任务阻塞.
此时需要将任务解除阻塞,添加到就绪列表.
判断刚恢复的任务是否比当前正在执行的任务优先级更高,是的话就执行一次任务切换.
任务通知方式
1 2 3 4 5 6 7 8 typedef enum { eNoAction = 0 , eSetBits eIncrement eSetValueWithOverwrite eSetValueWithoutOverwrite } eNotifyAction;
接收通知相关API函数:
函数
描述
ulTaskNotifyTake()
获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。
xTaskNotifyWait()
获取任务通知,比 ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位
当任务用作于信号量时,使用此函数获取:ulTaskNotifyTake();
当任务用于事件标志组或队列时,使用此函数:xTaskNotifyWait();
1 2 #define ulTaskNotifyTake( xClearCountOnExit , xTicksToWait ) \ ulTaskGenericNotifyTake(( tskDEFAULT_INDEX_TO_NOTIFY ), \ ( xClearCountOnExit ), \ ( xTicksToWait ) )
此函数用于接收任务通知值,可以设置在退出此函数的时候将任务通知值清零或者减一.
形参
描述
uxIndexToWaitOn
任务的指定通知(任务通知相关数组成员)
xClearCountOnExit
指定在成功接收通知后,将通知值清零或减 1,pdTRUE:把通知值清零;pdFALSE:把通知值减一
xTicksToWait
阻塞等待任务通知值的最大时间
返回值
描述
0
接收失败
非 0
接收成功,返回任务通知的通知值
ulTaskNotifyTake函数内部实现
判断任务通知的通知值是否为0,为0表示没有收到任务通知.
将当前任务的任务状态赋值为等待通知状态.
如果阻塞时间大于0代表需要阻塞,将任务添加到阻塞列表.
如果在此之前,任务被阻塞,则解除阻塞后会执行到这.
收到通知值,获取通知值后,判断是否需要在成功读后后将通知值清零.
xClearCountOnExit为pdture将通知值清0,用于二值信号量.
xClearCountOnExit为pdfalse将通知值减1,用于计数型信号量.
不论接收通知成功或者失败都将任务通知的状态标记为未等待通知状态.
1 2 3 4 5 6 #define xTaskNotifyWait( ulBitsToClearOnEntry, \ ulBitsToClearOnExit, \ pulNotificationValue, \ xTicksToWait) \ xTaskGenericNotifyWait( tskDEFAULT_INDEX_TO_NOTIFY, \ ( ulBitsToClearOnEntry ), \ ( ulBitsToClearOnExit ), \ ( pulNotificationValue ), \ ( xTicksToWait ) )
此函数用于获取通知值和清除通知值的指定位值,适用于模拟队列和事件标志组,使用该函数来获取任务通知.
1 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
等待任务通知失败
xTaskNotifyWait函数内部实现
判断任务通知的状态是否不为等待接收通知状态 ,如果是代表没有任务发送通知值.
等待任务通知前将任务通知通知值的指定比特位清零.
将任务通知状态更新为等待通知状态.
阻塞时间如果大于0,将任务从就绪列表移除,添加到阻塞列表,因为此时没有任务发送通知值.
执行一次任务切换.
如果在此之前,任务被阻塞,则解除阻塞后会执行到这.
不论接收通知成功或者失败都将任务通知的状态标记为未等待通知状态.
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t task2_handler; void task2 ( void * pvParameters ) ;void freertos_demo (void ) { xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate((TaskFunction_t ) task2, (char * ) "task2" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { printf ("任务通知模拟二值信号量释放!\r\n" ); xTaskNotifyGive(task2_handler); } vTaskDelay(10 ); } } void task2 ( void * pvParameters ) { uint32_t rev = 0 ; while (1 ) { rev = ulTaskNotifyTake(pdTRUE , portMAX_DELAY); if (rev != 0 ) { printf ("接收任务通知成功,模拟获取二值信号量!\r\n" ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t task2_handler; void task2 ( void * pvParameters ) ;void freertos_demo (void ) { xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate((TaskFunction_t ) task2, (char * ) "task2" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { printf ("任务通知模拟计数型信号量释放!\r\n" ); xTaskNotifyGive(task2_handler); } vTaskDelay(10 ); } } void task2 ( void * pvParameters ) { uint32_t rev = 0 ; while (1 ) { rev = ulTaskNotifyTake(pdFALSE , portMAX_DELAY); if (rev != 0 ) { printf ("rev:%d\r\n" ,rev); } vTaskDelay(1000 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t task2_handler; void task2 ( void * pvParameters ) ;void freertos_demo (void ) { xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate((TaskFunction_t ) task2, (char * ) "task2" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; 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) { case KEY0_PRES: { printf ("接收到的通知值为:%d\r\n" ,noyify_val); LED0_TOGGLE(); break ; } case KEY1_PRES: { printf ("接收到的通知值为:%d\r\n" ,noyify_val); LED1_TOGGLE(); break ; } default : break ; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t task2_handler; void task2 ( void * pvParameters ) ;void freertos_demo (void ) { xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate((TaskFunction_t ) task2, (char * ) "task2" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { printf ("将bit0位置1\r\n" ); xTaskNotify( task2_handler, EVENTBIT_0, eSetBits ); }else if (key == KEY1_PRES) { printf ("将bit1位置1\r\n" ); xTaskNotify( task2_handler, EVENTBIT_1, eSetBits ); } vTaskDelay(10 ); } } void task2 ( void * pvParameters ) { uint32_t notify_val = 0 ,event_bit = 0 ; while (1 ) { xTaskNotifyWait( 0 , 0xFFFFFFFF , ¬ify_val, portMAX_DELAY ); if (notify_val & EVENTBIT_0) { event_bit |= EVENTBIT_0; } if (notify_val & EVENTBIT_1) { event_bit |= EVENTBIT_1; } if (event_bit == (EVENTBIT_0|EVENTBIT_1)) { printf ("任务通知模拟事件标志组接收成功!!\r\n" ); event_bit = 0 ; } } }
软件定时器 简介
定时器:从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可自定义定时器的周期.
硬件定时器:芯片本身自带的定时器模块,硬件定时器的精度一般很高,每次在定时时间到达之后就会自动触发一个中断,用户在中断服务函数中处理信息.
软件定时器:是指具有定时功能的软件,可设置定时周期,当指定时间到达后要调用回调函数(也称超时函数),用户在回调函数中处理信息.
优劣
优点:硬件定时器数量有限,而软件定时器理论上只需有足够内存,就可以创建多个;使用简单、成本低.
缺点:软件定时器相对硬件定时器来说,精度没有那么高. 对于需要高精度要求的场合,不建议使用软件定时器.
软件定时器的命令队列
FreeRTOS 提供了许多软件定时器相关的 API 函数,这些 API 函数大多都是往定时器的队列中写入消息(发送命令),这个队列叫做软件定时器命令队列,是提供给 FreeRTOS 中的软件定时器使用的,用户是不能直接访问的.
状态转换图
单周期:
周期:
软件定时器结构体成员
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct { const char * pcTimerName ListItem_t xTimerListItem TickType_t xTimerPeriodInTicks; void * pvTimerID TimerCallbackFunction_t pxCallbackFunction; #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxTimerNumber #endif uint8_t ucStatus; } xTIMER;
软件定时器相关API函数
函数
描述
xTimerCreate()
动态方式创建软件定时器
xTimerCreateStatic()
静态方式创建软件定时器
xTimerStart()
开启软件定时器定时
xTimerStartFromISR()
在中断中开启软件定时器定时
xTimerStop()
停止软件定时器定时
xTimerStopFromISR()
在中断中停止软件定时器定时
xTimerReset()
复位软件定时器定时
xTimerResetFromISR()
在中断中复位软件定时器定时
xTimerChangePeriod()
更改软件定时器的定时超时时间
xTimerChangePeriodFromISR()
在中断中更改定时超时时间
创建软件定时器
1 2 3 4 5 TimerHandle_t xTimerCreate (const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction ) ;
形参
描述
pcTimerName
软件定时器名
xTimerPeriodInTicks
定时超时时间,单位:系统时钟节拍
uxAutoReload
定时器模式, pdTRUE:周期定时器, pdFALSE:单次定时器
pvTimerID
软件定时器 ID,用于多个软件定时器公用一个超时回调函数
pxCallbackFunction
软件定时器超时回调函数
返回值
描述
NULL
软件定时器创建失败
其他值
软件定时器创建成功,返回其句柄
开启软件定时器
1 2 BaseType_t xTimerStart (TimerHandle_t xTimer, const TickType_t xTicksToWait ) ;
形参
描述
xTimer
待开启的软件定时器的句柄
xTickToWait
发送命令到软件定时器命令队列的最大等待时间
返回值
描述
pdPASS
软件定时器开启成功
pdFAIL
软件定时器开启失败
停止软件定时器
1 2 BaseType_t xTimerStop (TimerHandle_t xTimer, const TickType_t xTicksToWait) ;
形参
描述
xTimer
待停止的软件定时器的句柄
xTickToWait
发送命令到软件定时器命令队列的最大等待时间
返回值
描述
pdPASS
软件定时器停止成功
pdFAIL
软件定时器停止失败
复位软件定时器
该功能将使软件定时器的重新开启定时,复位后的软件定时器以复位时的时刻作为开启时刻重新定时.
1 2 BaseType_t xTimerReset (TimerHandle_t xTimer, const TickType_t xTicksToWait) ;
形参
描述
xTimer
待复位的软件定时器的句柄
xTickToWait
发送命令到软件定时器命令队列的最大等待时间
返回值
描述
pdPASS
软件定时器复位成功
pdFAIL
软件定时器复位失败
更改软件定时器超时时间
1 2 BaseType_t xTimerChangePeriod (TimerHandle_t xTimer, const TickType_t xNewPeriod, const TickType_t xTicksToWait) ;
形参
描述
xTimer
待更新的软件定时器的句柄
xNewPeriod
新的定时超时时间,单位:系统时钟节拍
xTickToWait
发送命令到软件定时器命令队列的最大等待时间
返回值
描述
pdPASS
软件定时器定时超时时间更改成功
pdFAIL
软件定时器定时超时时间更改失败
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;void timer1_callback ( TimerHandle_t pxTimer ) ;void timer2_callback ( TimerHandle_t pxTimer ) ;void freertos_demo (void ) { xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } TimerHandle_t timer1_handle = 0 ; TimerHandle_t timer2_handle = 0 ; void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); timer1_handle = xTimerCreate( "timer1" , 500 , pdFALSE, (void *)1 , timer1_callback ); timer2_handle = xTimerCreate( "timer2" , 2000 , pdTRUE, (void *)2 , timer2_callback ); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { xTimerStart(timer1_handle,portMAX_DELAY); xTimerStart(timer2_handle,portMAX_DELAY); }else if (key == KEY1_PRES) { xTimerStop(timer1_handle,portMAX_DELAY); xTimerStop(timer2_handle,portMAX_DELAY); } vTaskDelay(10 ); } } void timer1_callback ( TimerHandle_t pxTimer ) { static uint32_t timer = 0 ; printf ("timer1的运行次数:%d\r\n" ,++timer); } void timer2_callback ( TimerHandle_t pxTimer ) { static uint32_t timer = 0 ; printf ("timer2的运行次数:%d\r\n" ,++timer); }
低功耗模式 简介 很多应用场合对于功耗的要求比较高,此时就有必要使用低功耗模式了. Freertos的低功耗模式本质是通过调用WFI指令进入睡眠模式.
STM32低功耗模式
Tickless模式实际思想
通过任务运行时间统计,可以看出,在整个系统的运行过程中,大部分时间在执行空闲任务.
所以在本该执行空闲任务的期间,让MCU进入相应的低功耗模式,当其他任务准备运行时,让MCU退出低功耗模式. 难点:
进入低功耗模式后,多久唤醒?也就是下一个要运行的任务如何被准确唤醒.
任何中断均可唤醒MCU,若滴答定时器频繁中断会影响低功耗的效果,如何处理?
将滴答定时器的中断周期暂时修改为低功耗的运行时间,退出低功耗模式后,补上系统时钟节拍. (Freertos已处理好)
Tickless模式相关配置项 需要配置的相关宏.
1 2 3 4 5 6 7 8 9 10 11 configUSE_TICKLESS_IDLE configEXPECTED_IDLE_TIME_BEFORE_SLEEP configPRE_SLEEP_PROCESSING(x) configPOSR_SLEEP_PROCESSING(x)
系统运行低功耗模式需要满足以下条件:
在FreeRTOSConfig.h文件中配置宏定义configUSE_TICKLESS_IDLE为1.
满足当前空闲任务正在运行,所有其他任务处在挂起或阻塞状态.
当系统可运行于低功耗模式的节拍数大于等于configEXPECTED_IDLE_TIME_BEFORE_SLEEP(默认为2个系统时钟节拍).
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 #include "freertos_demo.h" #define configPRE_SLEEP_PROCESSING( x ) PRE_SLEEP_PROCESSING() #define configPOST_SLEEP_PROCESSING( x ) POST_SLEEP_PROCESSING() #ifndef __FREERTOS_DEMO_H #define __FREERTOS_DEMO_H void freertos_demo (void ) ; void PRE_SLEEP_PROCESSING (void ) ;void POST_SLEEP_PROCESSING (void ) ;#endif #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;#define TASK2_PRIO 3 #define TASK2_STACK_SIZE 128 TaskHandle_t task2_handler; void task2 ( void * pvParameters ) ;QueueHandle_t semphore_handle; void PRE_SLEEP_PROCESSING (void ) { __HAL_RCC_GPIOA_CLK_DISABLE(); __HAL_RCC_GPIOB_CLK_DISABLE(); __HAL_RCC_GPIOC_CLK_DISABLE(); __HAL_RCC_GPIOD_CLK_DISABLE(); __HAL_RCC_GPIOE_CLK_DISABLE(); __HAL_RCC_GPIOF_CLK_DISABLE(); __HAL_RCC_GPIOG_CLK_DISABLE(); } void POST_SLEEP_PROCESSING (void ) { __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); } void freertos_demo (void ) { semphore_handle = xSemaphoreCreateBinary(); if (semphore_handle != NULL ) { printf ("二值信号量创建成功!!!\r\n" ); } xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); xTaskCreate((TaskFunction_t ) task2, (char * ) "task2" , (configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK2_PRIO, (TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 ; BaseType_t err; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { if (semphore_handle != NULL ) { err = xSemaphoreGive(semphore_handle); if (err == pdPASS) { printf ("信号量释放成功!!\r\n" ); }else printf ("信号量释放失败!!\r\n" ); } } vTaskDelay(10 ); } } void task2 ( void * pvParameters ) { uint32_t i = 0 ; BaseType_t err; while (1 ) { err = xSemaphoreTake(semphore_handle,portMAX_DELAY); if (err == pdTRUE) { printf ("获取信号量成功\r\n" ); }else printf ("已超时%d\r\n" ,++i); } }
内存管理 简介 Freertos提供了5种动态内存管理算法:heap_1、heap_2、heap_3、heap_4、heap_5.
算法
优点
缺点
heap_1
分配简单,时间确定
只允许申请内存,不允许释放内存
heap_2
允许申请和释放内存
不能合并相邻的空闲内存块会产生碎片、时间不定
heap_3
直接调用C库函数malloc()和 free() ,简单
速度慢、时间不定
heap_4
相邻空闲内存可合并,减少内存碎片的产生
时间不定
heap_5
能够管理多个非连续内存区域的 heap_4
时间不定
heap_1内存管理算法
heap_1只实现了pvPortMalloc,没有实现vPortFree;也就是说,它只能申请内存,无法释放内存! 如果你的工程,创建好的任务、队列、信号量等都不需要被删除,那么可以使用heap_1内存管理算法.
heap_1的实现最为简单,管理的内存堆是一个数组,在申请内存的时候, heap_1 内存管理算法只是简单地从数组中分出合适大小的内存,内存堆数组的定义如下所示 :
1 2 static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
heap_1内存管理算法的分配过程如下图所示:
heap_2内存管理算法
相比于heap_1算法,heap_2算法使用最适应算法 ,并且支持内存释放.
heap_2算法并不能将相邻的空闲内存块合并成一个大的空闲内存块,所以会不可避免的产生内存碎片.
最适应算法 :
假设heap有3块空闲内存(按内存块大小由小到大排序):5字节、25字节、50字节.
现在新创建一个任务需要申请20字节的内存.
第一步:找出最小的、能满足pvPortMalloc的内存:25字节.
第二步:把它划分为20字节、5字节;返回这20字节的地址,剩下的5字节仍然是空闲状态,留给后续的pvPortMalloc使用.
heap_4内存管理算法
heap_4 内存管理算法使用了首次适应算法 ,也支持内存的申请与释放,并且能够将空闲且相邻的内存进行合并,从而减少内存碎片的现象.
首次适应算法 :
假设heap有3块空闲内存(按内存块地址由低到高排序):5字节、50字节、25字节.
现在新创建一个任务需要申请20字节的内存.
第一步:找出第一个能满足pvPortMalloc的内存:50字节.
第二步:把它划分为20字节、30字节;返回这20字节的地址,剩下30字节仍然是空闲状态,留给后续的pvPortMalloc使用.
heap_5内存管理算法
heap_5算法是在heap_4的基础上实现,但额外实现了管理多个非连续内存区域的能力.
heap_5算法默认并没有定义内存堆,需要用户手动指定内存区域的信息,对其进行初始化.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef struct HeapRegion { uint8_t * pucStartAddress; size_t xSizeInBytes; }HeapRegion_t; const HeapRegion_t xHeapRegions[] = { {(uint8_t *)0x80000000 , 0x10000 }, {(uint8_t *)0x90000000 , 0xA0000 }, {NULL ,0 } }; vPortDefineHeapRegions(xHeapRegions);
内存管理相关API函数
函数
描述
void * pvPortMalloc( size_t xWantedSize );
申请内存
void vPortFree( void * pv );
释放内存
size_t xPortGetFreeHeapSize( void );
获取当前空闲内存的大小
1 2 3 4 5 6 7 8 9 void * pvPortMalloc ( size_t xWantedSize ) ;void vPortFree ( void * pv ) ;size_t xPortGetFreeHeapSize ( void ) ;
内部实现 定义一个大数组作为heap_4管理的内存堆:
内存块结构体:
prcHeapInit()函数内部实现
初始化内存堆.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 static void prvHeapInit ( void ) { BlockLink_t * pxFirstFreeBlock; uint8_t * pucAlignedHeap; size_t uxAddress; size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; uxAddress = ( size_t ) ucHeap; if ( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 ) { uxAddress += ( portBYTE_ALIGNMENT - 1 ); uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK ); xTotalHeapSize -= uxAddress - ( size_t ) ucHeap; } pucAlignedHeap = ( uint8_t * ) uxAddress; xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap; xStart.xBlockSize = ( size_t ) 0 ; uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize; uxAddress -= xHeapStructSize; uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK ); pxEnd = ( void * ) uxAddress; pxEnd->xBlockSize = 0 ; pxEnd->pxNextFreeBlock = NULL ; pxFirstFreeBlock = ( void * ) pucAlignedHeap; pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock; pxFirstFreeBlock->pxNextFreeBlock = pxEnd; xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize; xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof ( size_t ) * heapBITS_PER_BYTE ) - 1 ); }
初始化后的内存堆,如下:
prvInsertBlockIntoFreeList()函数内部实现
将空闲的内存块插入到空闲列表,可以将两个相邻且空闲的内存块进行合并,并且插入的内存块排列顺序有低地址往高地址.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 static void prvInsertBlockIntoFreeList ( BlockLink_t * pxBlockToInsert ) { BlockLink_t * pxIterator; uint8_t * puc; for ( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock ) { } puc = ( uint8_t * ) pxIterator; if ( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert ) { pxIterator->xBlockSize += pxBlockToInsert->xBlockSize; pxBlockToInsert = pxIterator; } else { mtCOVERAGE_TEST_MARKER(); } puc = ( uint8_t * ) pxBlockToInsert; if ( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock ) { if ( pxIterator->pxNextFreeBlock != pxEnd ) { pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize; pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock; } else { pxBlockToInsert->pxNextFreeBlock = pxEnd; } } else { pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock; } if ( pxIterator != pxBlockToInsert ) { pxIterator->pxNextFreeBlock = pxBlockToInsert; } else { mtCOVERAGE_TEST_MARKER(); } }
pvPortMalloc()函数内部实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 void * pvPortMalloc ( size_t xWantedSize ) { BlockLink_t * pxBlock, * pxPreviousBlock, * pxNewBlockLink; void * pvReturn = NULL ; vTaskSuspendAll(); { if ( pxEnd == NULL ) { prvHeapInit(); } else { mtCOVERAGE_TEST_MARKER(); } if ( ( xWantedSize & xBlockAllocatedBit ) == 0 ) { if ( ( xWantedSize > 0 ) && ( ( xWantedSize + xHeapStructSize ) > xWantedSize ) ) { xWantedSize += xHeapStructSize; if ( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 ) { if ( ( xWantedSize + ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ) ) > xWantedSize ) { xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) ); configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 ); } else { xWantedSize = 0 ; } } else { mtCOVERAGE_TEST_MARKER(); } } else { xWantedSize = 0 ; } if ( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) ) { pxPreviousBlock = &xStart; pxBlock = xStart.pxNextFreeBlock; while ( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) ) { pxPreviousBlock = pxBlock; pxBlock = pxBlock->pxNextFreeBlock; } if ( pxBlock != pxEnd ) { pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize ); pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock; if ( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE ) { pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize ); configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 ); pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize; pxBlock->xBlockSize = xWantedSize; prvInsertBlockIntoFreeList( pxNewBlockLink ); } else { mtCOVERAGE_TEST_MARKER(); } xFreeBytesRemaining -= pxBlock->xBlockSize; if ( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining ) { xMinimumEverFreeBytesRemaining = xFreeBytesRemaining; } else { mtCOVERAGE_TEST_MARKER(); } pxBlock->xBlockSize |= xBlockAllocatedBit; pxBlock->pxNextFreeBlock = NULL ; xNumberOfSuccessfulAllocations++; } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } traceMALLOC( pvReturn, xWantedSize ); } ( void ) xTaskResumeAll(); #if ( configUSE_MALLOC_FAILED_HOOK == 1 ) { if ( pvReturn == NULL ) { extern void vApplicationMallocFailedHook ( void ) ; vApplicationMallocFailedHook(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 ); return pvReturn; }
vPortFree()函数内部实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 void vPortFree ( void * pv ) { uint8_t * puc = ( uint8_t * ) pv; BlockLink_t * pxLink; if ( pv != NULL ) { puc -= xHeapStructSize; pxLink = ( void * ) puc; configASSERT( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ); configASSERT( pxLink->pxNextFreeBlock == NULL ); if ( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 ) { if ( pxLink->pxNextFreeBlock == NULL ) { pxLink->xBlockSize &= ~xBlockAllocatedBit; vTaskSuspendAll(); { xFreeBytesRemaining += pxLink->xBlockSize; traceFREE( pv, pxLink->xBlockSize ); prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) ); xNumberOfSuccessfulFrees++; } ( void ) xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } }
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void start_task ( void * pvParameters ) ;#define TASK1_PRIO 2 #define TASK1_STACK_SIZE 128 TaskHandle_t task1_handler; void task1 ( void * pvParameters ) ;void freertos_demo (void ) { xTaskCreate((TaskFunction_t ) start_task, (char * ) "start_task" , (configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE, (void * ) NULL , (UBaseType_t ) START_TASK_PRIO, (TaskHandle_t * ) &start_task_handler ); vTaskStartScheduler(); } void start_task ( void * pvParameters ) { taskENTER_CRITICAL(); xTaskCreate((TaskFunction_t ) task1, (char * ) "task1" , (configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE, (void * ) NULL , (UBaseType_t ) TASK1_PRIO, (TaskHandle_t * ) &task1_handler ); vTaskDelete(NULL ); taskEXIT_CRITICAL(); } void task1 ( void * pvParameters ) { uint8_t key = 0 , t = 0 ; uint8_t * buf = NULL ; while (1 ) { key = key_scan(0 ); if (key == KEY0_PRES) { buf = pvPortMalloc(30 ); if (buf != NULL ) { printf ("申请内存成功!\r\n" ); }else printf ("申请内存失败\r\n" ); }else if (key == KEY1_PRES) { if (buf != NULL ) { vPortFree(buf); printf ("释放内存!!\r\n" ); } } if (t++ > 50 ) { t = 0 ; printf ("剩余的空闲内存大小为:%d\r\n" ,xPortGetFreeHeapSize()); } vTaskDelay(10 ); } }