BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName, //任务名字
const configSTACK_DEPTH_TYPE usStackDepth,//为该任务分配的栈大小
void * const pvParameters,//任务函数的参数
UBaseType_t uxPriority,//任务优先级
TaskHandle_t * const pxCreatedTask )//任务句柄
void vTaskStartScheduler( void );
void vTaskDelay( const TickType_t xTicksToDelay );//单位:系统时钟节拍周期
调用之后,任务会在调用处进入阻塞状态,经过指定系统时钟节拍之后,解除阻塞
#define vTaskDelayUntil( pxPreviousWakeTime, xTimeIncrement )
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,//任务函数
const char * const pcName, //任务名字
const uint32_t ulStackDepth,//任务栈大小(对于stm32为32位)
void * const pvParameters,//任务函数参数
UBaseType_t uxPriority,//任务优先级
StackType_t * const puxStackBuffer,//为该任务静态分配的内存指针(大小要和参数三匹配)
StaticTask_t * const pxTaskBuffer )//任务句柄
此函数使能需要两个条件
#if configSUPPORT_STATIC_ALLOCATION==1
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
#endif
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
void vTaskResume( TaskHandle_t xTaskToResume )
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )
返回值:
pdTRUE:恢复后,需要进行任务切换(手动切换!)
pdFALSE:恢复后,不需要进行任务切换
当恢复后,正在执行的任务优先级小于被恢复的任务,被恢复的任务抢占正在执行的任务,发生任务切换
freertos规定:
如果要在中断服务函数里面调用freertos的API函数,必须保证中断优先级低于freertos所管理的最高优先级
portDISABLE_INTERRUPTS();//关闭中断
portENABLE_INTERRUPTS();//打开中断
注意:
这两个函数只能用于控制freertos控制范围之内的中断,即优先级在宏configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY与configLIBRARY_LOWEST_INTERRUPT_PRIORITY之间的中断
注意:
挂起任务调度器不会关闭中断,只是禁止任务切换而已,而进入临界区会禁止中断和任务切换
函数原型:
void vTaskSuspendAll( void );//挂起任务调度器
BaseType_t xTaskResumeAll( void );//恢复任务调度器
使用示例:
vTaskSuspendAll();//挂起任务调度器
{
......//内容
}
xAlreadyYielded = xTaskResumeAll();//恢复任务调度器
挂起任务调度器之后,执行内容部分,不会切换任务,但是会被中断打断
注意与临界区区分,临界区的代码既不会切换任务,也不会被中断打断
(1)任务级临界区代码保护
taskENTER_CRITICAL();
{
....../*临界区*/
}
taskEXIT_CRITICAL();
(2)中断级临界区代码保护
uint32_t save_status;
save_status = taskENTER_CRITICAL_FROM_ISR();//将中断状态保存在save_status变量中
{
....../*临界区*/
}
taskEXIT_CRITICAL_FROM_ISR(save_status );//恢复save_status标记的中断状态
进入临界段之前会关闭所有中断(freertos管理的所有中断),而任务调度是靠pendsv中断实现的,它也在freertos管理范围内,所以任务调度也会被关掉,因此在临界区中,中断被禁止,任务切换也被禁止
void vListInitialise( List_t * const pxList );//初始化列表
void vListInitialiseItem( ListItem_t * const pxItem );//初始化列表项
void vListInsertEnd( List_t * const pxList,
ListItem_t * const pxNewListItem );//列表末尾插入列表项
void vListInsert( List_t * const pxList,
ListItem_t * const pxNewListItem );//插入列表项
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );//删除列表项
freertos中列表的本质是双向循环链表,列表的概念非常重要,因为freertos源码的很多基本数据结构都是通过列表实现的。
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask )//获取任务优先级
参数一表示任务句柄,如果为NULL,则获取调用任务的优先级
void vTaskPrioritySet( TaskHandle_t xTask , UBaseType_t uxNewPriority )
参数一:任务句柄,NULL代表任务自身
参数二:新设置的优先级
UBaseType_t uxTaskGetNumberOfTasks( void )
UBaseType_t uxTaskGetSystemState( TaskStatus_t * const pxTaskStatusArray,
const UBaseType_t uxArraySize,
configRUN_TIME_COUNTER_TYPE * const pulTotalRunTime )
参数一:接收的任务状态信息的数组首地址
参数二:参数一的元素总个数
参数三:接收的系统运行总时间变量地址,为NULL表示不获取系统运行总时间
TaskStatus_t 类型变量是一个结构体变量
typedef struct xTASK_STATUS
{
TaskHandle_t xHandle; /* 任务句柄 */
const char * pcTaskName; /*任务名 */
UBaseType_t xTaskNumber; /* 任务编号 */
eTaskState eCurrentState; /* 任务状态 */
UBaseType_t uxCurrentPriority; /* 任务当前优先级 */
UBaseType_t uxBasePriority; /* 任务原始优先级 */
configRUN_TIME_COUNTER_TYPE ulRunTimeCounter; /* 任务运行时间 */
StackType_t * pxStackBase; /* 任务栈基地址 */
configSTACK_DEPTH_TYPE usStackHighWaterMark; /* 任务栈历史剩余最小值 */
} TaskStatus_t;
void vTaskGetInfo( TaskHandle_t xTask,
TaskStatus_t * pxTaskStatus,
BaseType_t xGetFreeStackSpace,
eTaskState eState )
参数一:任务句柄
参数二:接收任务信息的变量
参数三:是否统计任务栈历史剩余最小值(pdFALSE表示不统计,pdTRUE表示统计)
参数四:是否统计任务状态(eInvalid表示统计其他表示不统计)
typedef enum
{
eRunning = 0, /* 运行 */
eReady, /* 就绪 */
eBlocked, /* 阻塞 */
eSuspended, /*挂起 */
eDeleted, /* 删除 */
eInvalid /* 无效 */
} eTaskState;
TaskHandle_t xTaskGetCurrentTaskHandle( void )
TaskHandle_t xTaskGetHandle( const char * pcNameToQuery )
参数一:指定的任务名字
返回值:返回任务句柄,如果不存在,则返沪NULL
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask )
eTaskState eTaskGetState(TaskHandle_t xTask)
参数一:任务句柄
返回值:eTaskState 类型结构体,上文已提及
void vTaskList(char * pcWriteBuffer)
参数一:表格信息缓存指针
void vTaskGetRunTimeStats( char * pcWriteBuffer )
参数一:表格信息缓存指针
表格有三列:
Task:表示任务名字
Abs time:任务的绝对运行时间(时基定时器的中断次数)
Time:任务时间相对于CPU总时间的占比
(1)动态方式创建队列:
#define xQueueCreate ( uxQueueLength, uxItemSize ) \
xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE ))
参数一:队列长度(队列项个数)
参数二:队列项大小(字节)
返回值:NULL,创建失败;非空,返回队列句柄
(2)向队列写入消息:
向队列尾部写入消息,提供了两个API,它们的功能是相同的
#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 )
(3)向队列读取消息:
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
参数一:队列句柄
参数二:读取数据缓存指针
参数三:指定等待阻塞时间
返回值:pdTRUE,读取成功;pdFALSE,读取失败
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
计数型信号量,二值信号量和互斥信号量的获取和释放都是使用同一组函数的,它们的创建函数不同。二值信号量有一个致命的缺点—可能导致优先级反转,这对于实时操作系统尤为致命,因此引入互斥信号量来解决它的这一缺点
(1)二值信号量与计数型信号量
#define xSemaphoreCreateBinary( ) \
xQueueGenericCreate( 1 , semSEMAPHORE_QUEUE_ITEM_LENGTH , queueQUEUE_TYPE_BINARY_SEMAPHORE )
返回值:NULL,创建失败;其他值,返回二值信号量句柄,它是一个SemaphoreHandle_t类型的变量
BaseType_t xSemaphoreGive( xSemaphore ) //这是宏定义转化之后的函数原型
参数一:信号量句柄
返回值:pdPASS,释放信号量成功;errQUEUE_FULL释放信号量失败,说明信号量此时已满
BaseType_t xSemaphoreTake( xSemaphore, xBlockTime ) //这是宏定义转换之后的函数原型
参数一:信号量句柄
参数二:指定阻塞时间
返回值:pdTRUE,获取信号量成功;pdFALSE,超时,获取信号量失败
#define xSemaphoreCreateCounting( uxMaxCount , uxInitialCount ) \
xQueueCreateCountingSemaphore( ( uxMaxCount ) , ( uxInitialCount ) )
参数一:指定最大计数
参数二:指定初始计数值
返回值:NULL,创建失败;其他,返回信号量句柄,它是一个SemaphoreHandle_t类型的变量
-获取计数型信号量的当前计数值:
#define uxSemaphoreGetCount( xSemaphore ) \
uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )
参数一:信号量句柄
返回值:整数,返回当前信号量的计数值
(2)互斥信号量
互斥信号量通过优先级继承尽可能的降低由优先级反转造成的危害(但是还是会存在优先级反转),互斥信号量不能用于中断服务函数。
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
返回值:返回信号量句柄,它是一个SemaphoreHandle_t类型的变量
如果一个任务想要同时接收一个队列和获取一个信号量,但是这个队列和这个信号量目前都会使任务阻塞(队列为空,信号量未满),那么它应该阻塞等待。但是呢,这个任务如果单独的只等待队列,或者只等待信号量,都不太合理。比如阻塞等待队列期间,如果信号量置满了,那么任务应该去执行信号量的操作,可是此时任务正在阻塞等待队列,所以没法处理这个到来的信号量。如果能同时等待队列和信号量就可以解决这个问题了,怎么实现呢?队列集就是为这个使用场景而生的!
队列集可以同时等待多个信号,在代码上大概体现问如下形式:
接收任务()
{
等待队列集中的消息
if(队列还是信号量)
{
/*相关处理。。。*/
}
}
(1)创建队列集
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength );
参数一:可容纳的队列数量
返回值:NULL调用失败;其他,QueueSetHandle_t类型的队列集句柄
(2)向队列集中添加队列:
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore ,
QueueSetHandle_t xQueueSet );
参数一:要添加的队列句柄
参数二:队列集句柄
返回值:pdPASS添加成功;pdFAIL添加失败
(3)从队列集中移除队列:
BaseType_t xQueueRemoveFromSet( QueueSetMemberHandle_t xQueueOrSemaphore ,
QueueSetHandle_t xQueueSet );
参数一:要移除的队列句柄
参数二:队列集句柄
返回值:pdPASS移除成功;pdFAIL移除失败
(4)获取队列集中有有效消息的队列句柄:
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,
TickType_t const xTicksToWait )
参数一:队列集
参数二:指定阻塞时间
返回值:成功返回有有效消息队列句柄;超时失败返回NULL
事件标志组包含多个事件标志位,每一个事件标志位可以表示一个事件是否发生。事件标志位可以被任何中断或者任务读写。事件标志组可以等待每一位成立,也可以等待某几位都成立,如果不成立,则阻塞调用的任务或者中断
(1)队列,信号量,事件标志组的区别与联系
队列,信号量的事件发生的时候,只会唤醒一个任务;事件信号量发生的的时候,可以唤醒所有符合条件的任务,类似”广播“的作用;
队列信号量是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了
而被事件标志组唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件
(2)创建事件标志组
EventGroupHandle_t xEventGroupCreate ( void ) ;
返回值:返回事件标志组句柄
(3)清除事件标志位
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear )
参数一:事件标志组句柄
参数二:要请除的位数
返回值:清零事件标志位之前事件组中事件标志位的值
(4)设置事件标志位
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet )
参数一:事件标志组句柄
参数二:要设置的位数
返回值:函数返回时,事件组中的事件标志位值
(5)等待事件标志位
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait )
参数一:事件标志组句柄
参数二:等待的事件标志位,可以用逻辑或等待多个事件标志位,然后通过参数四来决定是逻辑与等待这些位还是逻辑或等待这些位
参数三:成功等待到事件标志位后,选择是否清除
pdTRUE :清除参数二指定位;
pdFALSE:不清除
参数四:等待 uxBitsToWaitFor 中的所有事件标志位
pdTRUE:等待的位,全部为1
pdFALSE:等待的位,某个为1
参数五:指定阻塞时间
返回值:成功返回等待到的事件标志位,失败返回事件组中的事件标志位
(6)同步函数
同步函数会阻塞等待某个标志位置位之后(达到同步点),置位参数二指定某位(表示此刻同步事件发生)
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait)
参数一:事件标志组句柄
参数二:达到同步点后,要设置的事件标志
参数三:等待的事件标志
参数四:设置阻塞时间
队列,信号量等通知方式需要相关的结构体中间变量作为通讯对象,实现任务之间的间接通讯,而任务通知的通讯对象直接包含在任务控制块pcb中,不需要额外定义相关的结构体,因此任务通知更节省内存,效率更高
只要合理灵活的运用任务通知,就可以在一些场合使用任务通知模拟队列,信号量,事件标志组!
(1)发送通知:
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue )
参数一:接收任务通知的任务句柄(指定接收方)
参数二:任务的指定通知(任务通知相关数组成员索引,一般为0,及选择第一个成员作为任务通知变量)
参数三:任务通知值
参数四:通知更新方式,下文介绍
参数五:用于保存更新前的任务通知值(为NULL则不保存)
参数四eNotifyAction是枚举类型:
typedef enum
{
eNoAction = 0, /* 无操作 */
eSetBits /* 更新指定bit */
eIncrement /* 通知值加一 */
eSetValueWithOverwrite /* 覆写的方式更新通知值 */
eSetValueWithoutOverwrite /* 不覆写通知值 */
} eNotifyAction;
以通用发送函数为基础,通过宏定义定义如下几个函数:
#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 )
(2)接收通知:
#define ulTaskNotifyTake( xClearCountOnExit , xTicksToWait ) \
ulTaskGenericNotifyTake( ( tskDEFAULT_INDEX_TO_NOTIFY ), \
( xClearCountOnExit ), \
( xTicksToWait ) )
参数一:指定在成功接收通知后,将通知值清零或减 1,
pdTRUE:把通知值清零;pdFALSE:把通知值减一
参数二:指定接收阻塞时间
返回值:0,接受失败;非0,接收成功,返回任务通知值
#define xTaskNotifyWait( ulBitsToClearOnEntry, \
ulBitsToClearOnExit, \
pulNotificationValue, \
xTicksToWait) \
xTaskGenericNotifyWait( tskDEFAULT_INDEX_TO_NOTIFY, \
( ulBitsToClearOnEntry ), \
( ulBitsToClearOnExit ), \
( pulNotificationValue ), \
( xTicksToWait ) )
参数一:等待前清零指定任务通知值的比特位(旧值对应bit清0)
参数二:成功等待后清零指定的任务通知值比特位(新值对应bit清0)
参数三:用来取出通知值(如果不需要取出,可设为NULL)
参数四:设置阻塞时间
返回值:pdTRUE等待任务通知成功;pdFALSE等待任务通知失败
使用软件定时器相关功能,需要把宏configUSE_TIMERS 置1 ,软件定时器的优先级configTIMER_TASK_PRIORITY = 31;配置为最高,保证第一时间调用软件定时器的超时函数(回调函数),以保证软件定时器的实时性
所有的软件定时器由同一个软件定时器任务维护,这个任务和Linux中的守护进程很相似,在开启任务调度器的那一刻,它就会一直工作
由于软件的超时回调函数使由软件定时器守护任务调用的,而守护任务会一直存在,维护所有的软件定时器,因此,回调函数中一定要快速执行,尽量避免阻塞,延时等慢操作!
(1)软件定时器结构体
typedef struct
{
const char * pcTimerName, /* 软件定时器名字 */
ListItem_t xTimerListItem, /* 软件定时器列表项 */
TickType_t xTimerPeriodInTicks;/* 软件定时器的周期 */
void * pvTimerID, /* 软件定时器的ID */
TimerCallbackFunction_t pxCallbackFunction,/* 软件定时器的回调函数 */
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTimerNumber, /* 软件定时器的编号,调试用 */
#endif
uint8_t ucStatus, /* 软件定时器的状态 */
} xTIMER;
(2)创建软件定时器:
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
参数一:软件定时器名字
参数二:定时器超时时间,单位:系统时钟节拍(1ms)
参数三:定时器模式, pdTRUE:周期定时器, pdFALSE:单次定时器
参数四:软件定时器 ID,用于多个软件定时器公用一个超时回调函数
参数五:软件定时器超时回调函数
返回值:NULL,创建失败;其他,创建成功,返回软件定时器 句柄
(3)开启软件定时器:
BaseType_t xTimerStart( TimerHandle_t xTimer,
const TickType_t xTicksToWait );
参数一:软件定时器句柄
参数二:发送启动命令到软件定时器命令队列的最大等待时间
返回值:pdPASS,发送启动命令成功;pdFAIL,发送启动命令失败
注意:单次软件定时器
(4)停止软件定时器:
BaseType_t xTimerStop( TimerHandle_t xTimer,
const TickType_t xTicksToWait);
参数一:软件定时器句柄
参数二:发送停止命令到软件定时器命令队列的最大等待时间
返回值:pdPASS,发送停止命令成功;pdFAIL,发送停止命令失败
(5)软件定时器复位(重新开始计时)
BaseType_t xTimerReset( TimerHandle_t xTimer,
const TickType_t xTicksToWait);
参数一:软件定时器句柄
参数二:发送复位命令到软件定时器命令队列的最大等待时间
返回值:pdPASS,发送复位命令成功;pdFAIL,发送复位命令失败
(6)更改软件定时器超时时间
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
const TickType_t xNewPeriod,
const TickType_t xTicksToWait);
参数一:软件定时器句柄
参数二:新的定时超时时间,单位:系统时钟节拍
参数三:发送更改超时时间命令到软件定时器命令队列的最大等待时间
返回值:pdPASS,发送更改超时时间命令成功;pdFAIL,发送更改超时时间命令失败
(7)获取软件定时器ID
如果要多个软件定时器公用一个超时回调函数,应该先调用此函数传入回调函数的参数(软件定时器句柄)获取触发本次回调函数的软件定时器的ID,然后根据这个ID来确定是哪个定时器触发本次超时函数
void * pvTimerGetTimerID( const TimerHandle_t xTimer );
注意事项:由于事先无法确定ID的类型,所有函数返回值为泛型指针类型,具体应用的时候注意强转为创建定时器的时候的ID类型
单次软件定时器状态机:
周期软件定时器状态机:
软件定时器在运行态只能调用停止,调用其他的会被忽略
freertos低功耗模式的设计理念是,在本该空闲任务执行的期间,让MCU 进入相应的低功耗模式;当其他任务准备运行的时候,唤醒MCU退出低功耗模式
下面介绍一些低功耗宏定义配置项:
(1)使能低功耗
configUSE_TICKLESS_IDLE
(2)定义系统进入低功耗模式的最短时长
至少大于2,单位:系统时钟节拍
configEXPECTED_IDLE_TIME_BEFORE_SLEEP
(3)定义进入低功耗模式之前要做的事情
configPRE_SLEEP_PROCESSING(x)
(4)定义退出低功耗模式之后要做的事情
configPOSR_SLEEP_PROCESSING(x)
以freertos的heap_4为例
(1)分配内存
void * pvPortMalloc( size_t xWantedSize );
(2)释放内存
void vPortFree( void * pv );
(3)获取当前剩余内存大小
size_t xPortGetFreeHeapSize( void );