本文是《ALIENTEK STM32F429 FreeRTOS 开发教程》第十章学习笔记
第一章笔记–FreeRTOS简介与源码下载
第二章笔记–FreeRTOS在STM32F4上移植
第三章笔记-FreeRTOS系统配置
第四章笔记-FreeRTOS中断分析
第四章笔记补充-FreeRTOS临界段代码
第五章笔记-FreeRTOS任务基础
第六章笔记-FreeRTOS任务API函数的使用
第七章笔记-FreeRTOS列表和列表项
第八章笔记-1-FreeRTOS任务创建
第八章笔记-2-FreeRTOS任务调度器开启
第九章笔记-FreeRTOS任务切换
FreeRTOS内核控制函数就是内核使用的函数,一般应用程序不使用这些函数
函数 | 描述 |
---|---|
taskTIELD() | 任务切换 |
taskENTER_CRITICAL() | 进入临界区,用于任务中 |
taskEXIT_CRITICAL() | 退出临界区,用于任务中 |
taskENTER_CRITICAL_FROM_ISR() | 进入临界区,用于中断服务函数中 |
taskEXIT_CRITICAL_FROM_ISR() | 退出临界区,用于中断服务函数中 |
taskDISABLE_INTERRUPTS() | 关闭中断 |
taskENABLE_INTERRUPTS() | 打开中断 |
vTaskStartScheduler() | 开启任务调度器 |
vTaskEndScheduler() | 关闭任务调度器 |
vTaskSuspendAll() | 挂起任务调度器 |
xTaskResumeAll() | 恢复任务调度器 |
vTaskStepTick() | 设置系统节拍值 |
#define taskYIELD() portYIELD()
#define portYIELD()
{
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
__dsb( portSY_FULL_READ_WRITE );
__isb( portSY_FULL_READ_WRITE );
}
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT:
#define portNVIC_INT_CTRL_REG ( * ( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT ( 1UL << 28UL )
中断控制及状态寄存器ICSR(地址:0xE000_ED04),向ICSR的第28位写入1悬起PendSV(启动PendSV中断),这样即可在PendSV中断服务函数中进行任务切换
__dsb( portSY_FULL_READ_WRITE );__isb( portSY_FULL_READ_WRITE ): dsb和isb 完成数据同步隔离和指令同步隔离 完成作用是保证之前存储器访问操作和指令都执行完(个人认为是要确保开启了PendSV)
中断级任务切换函数为portYIELD_FROM_ISR():
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( xSwitchRequired != pdFALSE ) portYIELD()
#define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
可以看到中断级任务切换函数也是通过调用函数portYIELD()来完成
用做任务级临界段代码保护
进入临界段:taskENTER_CRITICAL()
退出临界段:taskEXIT_CRITICAL()
这两个函数其实是一个宏定义,定义为:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
分别封装了个函数portENTER_CRITICAL()和portEXIT_CRITICAL(),而被封装的两个函数也是宏定义,定义为:
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
函数vPortEnterCritical()和vPortExitCritical()在文件port.c里,函数原型为:
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
进入函数vPortEnterCritical()先执行portDISABLE_INTERRUPTS()函数(关闭中断
再执行uxCriticalNesting++ 将变量加一 变量表示临界段嵌套次数
接下来如果为1,进入一个断言函数
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
进入函数vPortExitCritical() 执行configASSERT( uxCriticalNesting );
再对uxCriticalNesting减1 只有当变量为0时才调用函数portENABLE_INTERRUPTS()使能中断
这样做保证了,在有多个临界段代码时不会因为某一临界段代码退出而打乱其他临界段的保护,只有所有临界段代码都退出后才使能中断
用在中断服务程序中的临界段代码保护
进入临界段:taskENTER_CRITICAL_FROM_ISR()
退出临界段:taskEXIT_CRITICAL_FROM_ISR()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
ulPortRaiseBASEPRI():
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
mrs ulReturn, basepri
msr basepri, ulNewBASEPRI
dsb
isb
}
return ulReturn;
}
首先读出BASEPRI寄存器的值保存在ulReturn
将configMAX_SYSCALL_INTERRUPT_PRIORITY写入BASEPRI寄存器中
返回ulReturn的值
vPortSetBASEPRI():
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
向BASEPRI寄存器中写入一个值
中断级临界代码保护使用:
{
status_valie=taskENTER_CRITICAL_FROM_ISR();
...
taskEXIT_CRITICAL_FROM_ISR(STATUS_VALUE);
}
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
/*-----------------------------------------------------------*/
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
msr basepri, ulNewBASEPRI
dsb
isb
}
}
portENABLE_INTERRUPTS()即是向 BASEPRI寄存器中写入0,即屏蔽优先级不高于0的中断,因为0优先级已经最高,所以停止屏蔽中断(打开中断)
portDISABLE_INTERRUPTS()是向BASEPRI寄存器中写入configMAX_SYSCALL_INTERRUPT_PRIORITY(最低优先级),低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断被屏蔽,所以屏蔽中断
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
/* Add the idle task at the lowest priority. */
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
StaticTask_t *pxIdleTaskTCBBuffer = NULL;
StackType_t *pxIdleTaskStackBuffer = NULL;
uint32_t ulIdleTaskStackSize;
/* The Idle task is created using user provided RAM - obtain the
address of the RAM then create the idle task. */
vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
xIdleTaskHandle = xTaskCreateStatic( prvIdleTask,
"IDLE",
ulIdleTaskStackSize,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
pxIdleTaskStackBuffer,pxIdleTaskTCBBuffer ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
if( xIdleTaskHandle != NULL )
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
}
#else
{
/* The Idle task is being created using dynamically allocated RAM. */
xReturn = xTaskCreate( prvIdleTask,
"IDLE", configMINIMAL_STACK_SIZE,
( void * ) NULL,
( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
#if ( configUSE_TIMERS == 1 )
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TIMERS */
if( xReturn == pdPASS )
{
/* Interrupts are turned off here, to ensure a tick does not occur
before or during the call to xPortStartScheduler(). The stacks of
the created tasks contain a status word with interrupts switched on
so interrupts will automatically get re-enabled when the first task
starts to run. */
portDISABLE_INTERRUPTS();
#if ( configUSE_NEWLIB_REENTRANT == 1 )
{
/* Switch Newlib's _impure_ptr variable to point to the _reent
structure specific to the task that will run first. */
_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
}
#endif /* configUSE_NEWLIB_REENTRANT */
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;
xTickCount = ( TickType_t ) 0U;
/* If configGENERATE_RUN_TIME_STATS is defined then the following
macro must be defined to configure the timer/counter used to generate
the run time counter time base. */
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
/* Setting up the timer tick is hardware specific and thus in the
portable interface. */
if( xPortStartScheduler() != pdFALSE )
{
/* Should not reach here as if the scheduler is running the
function will not return. */
}
else
{
/* Should only reach here if a task calls xTaskEndScheduler(). */
}
}
else
{
/* This line will only be reached if the kernel could not be started,
because there was not enough FreeRTOS heap to create the idle task
or the timer task. */
configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
}
/* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
meaning xIdleTaskHandle is not used anywhere else. */
( void ) xIdleTaskHandle;
}
if( configSUPPORT_STATIC_ALLOCATION == 1 ){……}:创建空闲任务,如果使用静态内存使用函数xTaskCreateStatic()来创建空闲任务,优先级为(tskIDLE_PRIORITY | portPRIVILEGE_BIT),宏tskIDLE_PRIORITY和portPRIVILEGE_BIT都为0,即说明空闲任务的优先级最低
else { xReturn = xTaskCreate(…… #endif: 此时使用动态内存使用函数xTaskCreate()来创建任务
if(configUSE_TIMES==1){…..} #endif : 如果使用软件定时则通过函数xTimerCreateTimerTask()来创建定时器服务任务。定时器服务任务主要代码:
xReturn = xTaskCreate( prvTimerTask,
"Tmr Svc",
configTIMER_TASK_STACK_DEPTH,
NULL,
( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
&xTimerTaskHandle );
if(xReturn==pdPASS)portDISABLE_INTERRUPTS(): 如果空闲任务和定时器任务创建成功关闭中断。
if (configUSE_NEWLIB_REENTRANT == 1) …… #endif: 如果配置了相关宏则使能NEWLIB
xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE;xTickCount = ( TickType_t ) 0U; :将相关全局变量进行设置,当变量xSchedulerRunning设置为pdTRUE,表示调度器开始运行
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():当宏configGENERATE_RUN_TIME_STATS为1时则需要使能时间统计功能,需要配置一个定时器/计数器,在这里不使能此宏
if(xPortStartScheduler() != pdFALSE)…else{}:调用函数xPortStartScheduler()来初始化跟调度器启动有关的硬件(滴答定时器,FPU单元和PendSV中断等等) 如果调度器启动成功,不会运行到if和else里面,因为函数没有返回值
else{configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );}: 程序运行到这里说明系统内核没有启动成功,原因是在创建空闲任务或者定时器任务的时候没有足够的内存,此时用断言函数进行报错
关闭任务调度器,FreeRTOS中说此函数仅用于X86架构的处理器
void vTaskEndScheduler( void )
{
portDISABLE_INTERRUPTS();
xSchedulerRunning = pdFALSE;
vPortEndScheduler();
}
先关闭中断,再标记任务调度器停止运行,再执行vPortEndScheduler()函数,是硬件层关闭中断的处理函数,在源码中没有具体操作,根据实际处理器编写
void vPortEndScheduler( void )
{
/* Not implemented in ports where there is nothing to return to.
Artificially force an assert. */
configASSERT( uxCriticalNesting == 1000UL );
}
void vTaskSuspendAll( void )
{
++uxSchedulerSuspended;
}
函数将挂起嵌套计数器(uxSchedulerSuspended)加一,使用函数xTaskResumeAll()可以恢复任务调度器,调用几次vTaskSuspendAll(),则需要调用几次xTaskResumeAll()才会最终恢复调度器
当调度器挂起时,本来从阻塞态到就绪态的优先级为x的任务,不添加到任务就绪列表pxReadyTaskLists[x]中,而添加到xPendingReadyList的列表中
BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;
/* If uxSchedulerSuspended is zero then this function does not match a
previous call to vTaskSuspendAll(). */
configASSERT( uxSchedulerSuspended );
/* It is possible that an ISR caused a task to be removed from an event
list while the scheduler was suspended. If this was the case then the
removed task will have been added to the xPendingReadyList. Once the
scheduler has been resumed it is safe to move all the pending ready
tasks from this list into their appropriate ready list. */
taskENTER_CRITICAL();
{
--uxSchedulerSuspended;
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
{
/* Move any readied tasks from the pending list into the
appropriate ready list. */
while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
/* If the moved task has a priority higher than the current
task then a yield must be performed. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
if( pxTCB != NULL )
{
/* A task was unblocked while the scheduler was suspended,
which may have prevented the next unblock time from being
re-calculated, in which case re-calculate it now. Mainly
important for low power tickless implementations, where
this can prevent an unnecessary exit from low power
state. */
prvResetNextTaskUnblockTime();
}
/* If any ticks occurred while the scheduler was suspended then
they should be processed now. This ensures the tick count does
not slip, and that any delayed tasks are resumed at the correct
time. */
{
UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */
if( uxPendedCounts > ( UBaseType_t ) 0U )
{
do
{
if( xTaskIncrementTick() != pdFALSE )
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--uxPendedCounts;
} while( uxPendedCounts > ( UBaseType_t ) 0U );
uxPendedTicks = 0;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
if( xYieldPending != pdFALSE )
{
#if( configUSE_PREEMPTION != 0 )
{
xAlreadyYielded = pdTRUE;
}
#endif
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return xAlreadyYielded;
}
configASSERT( uxSchedulerSuspended):如果uxSchedulerSuspended证明调度器没有被挂起,跟调度器挂起函数对应数量不匹配,则断言报错
taskENTER_CRITICAL():进入临界区
–uxSchedulerSuspended:调度器挂起嵌套计数器减一
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ):如果uxSchedulerSuspended为0则说明所有挂起都被启动,调度器开始工作
if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U ):若任务数大于0个,开始执行
while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE ):循环处理列表xPendingReadyList,只要列表不为空则说明有任务挂到列表xPendingReadyList上,需要将这些任务从列表xPendingReadyList上去除并添加到任务对应的就绪列表中
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) ):遍历列表xPendingReadyList,获取挂到列表xPendingReadyList上的任务对应的任务控制块
( void ) uxListRemove( &( pxTCB->xEventListItem ) ):将任务从事件列表上移除
( void ) uxListRemove( &( pxTCB->xStateListItem ) ):将任务从状态列表上移除
prvAddTaskToReadyList( pxTCB ):调用函数将从xPendingReadyList列表上获得的任务添加到就绪列表中
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ){……}:判断任务的优先级是否高于当前正在运行的任务,如果是将xYieldPending赋值为pdTRUE,表示需要进行任务切换
if( pxTCB != NULL ){prvResetNextTaskUnblockTime();}:当任务调度顺利进行,为了防止未来解锁时间被重新计算,在这种情况下,重新计算解锁时间
UBaseType_t uxPendedCounts = uxPendedTicks…mtCOVERAGE_TEST_MARKER()}}:挂起任务调度气候,每个滴答定时器的中断不会更新xTickCount,而是用uxPendedTicks来记录调度器挂起过程中的时钟数,如果在调度程序时发生了时钟中断,此时应该处理,可以确保滴答计数不会滑动,任何延迟任务都会在正确的时间恢复
if( xYieldPending != pdFALSE ):如果之前判断任务的优先级 高于当前运行的任务即需要进行任务切换
xAlreadyYielded = pdTRUE:标记在函数xTaskResumeAll()中进行了任务切换
taskYIELD_IF_USING_PREEMPTION():调用此函数进行任务切换
#define taskYIELD_IF_USING_PREEMPTION() portYIELD_WITHIN_API()
#define portYIELD_WITHIN_API portYIELD
实际上是用portYIELD()来进行任务切换
taskEXIT_CRITICAL():退出临界区
return xAlreadyYielded:返回汴梁,表示是否在xTaskResumeAll()中进行了任务切换
设置系统节拍值,使用FreeRTOS的低功耗tickless模式时,即宏configUSE_TICKLESS_IDLE为1时用到
#if ( configUSE_TICKLESS_IDLE != 0 )
void vTaskStepTick( const TickType_t xTicksToJump )
{
configASSERT( ( xTickCount + xTicksToJump ) <= xNextTaskUnblockTime );
xTickCount += xTicksToJump;
traceINCREASE_TICK_COUNT( xTicksToJump );
}
#endif
给滴答计数器加上滴答跳跃值