本节需要掌握的内容如下:
1,开启任务调度器(熟悉)
2,启动第一个任务(熟悉)
3,任务切换(掌握)
一,开启任务调度器(熟悉)
vTaskStartScheduler()
作用:用于启动任务调度器,任务调度器启动后, FreeRTOS 便会开始进行任务调度
该函数内部实现,如下:
1、创建空闲任务
2、如果使能软件定时器,则创建定时器任务
3、关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
4、初始化全局变量,并将任务调度器的运行标志设置为已运行
5、初始化任务运行时间统计功能的时基定时器
6、调用函数 xPortStartScheduler()
xPortStartScheduler()
作用:该函数用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务
该函数内部实现,如下:(感兴趣的小伙伴可以自行对源码进行解读哈,还挺有意思的)
1、检测用户在 FreeRTOSConfig.h 文件中对中断的相关配置是否有误
2、配置 PendSV 和 SysTick 的中断优先级为最低优先级
3、调用函数 vPortSetupTimerInterrupt()配置 SysTick
4、初始化临界区嵌套计数器为 0
5、调用函数 prvEnableVFP()使能 FPU
6、调用函数 prvStartFirstTask()启动第一个任务
源码如下:
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,configIDLE_TASK_NAME,ulIdleTaskStackSize,( void * ) NULL, /*lint !e961. The cast is not redundant for all compilers. */portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */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 /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */{/* The Idle task is being created using dynamically allocated RAM. */xReturn = xTaskCreate( prvIdleTask,configIDLE_TASK_NAME,configMINIMAL_STACK_SIZE,( void * ) NULL,portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */&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 ){/* freertos_tasks_c_additions_init() should only be called if the user* definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is* the only macro called by the function. */#ifdef FREERTOS_TASKS_C_ADDITIONS_INIT{freertos_tasks_c_additions_init();}#endif/* 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.* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html* for additional information. */_impure_ptr = &( pxCurrentTCB->xNewLib_reent );}#endif /* configUSE_NEWLIB_REENTRANT */xNextTaskUnblockTime = portMAX_DELAY;xSchedulerRunning = pdTRUE;xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;/* 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. NOTE: If configGENERATE_RUN_TIME_STATS* is set to 0 and the following line fails to build then ensure you do not* have portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() defined in your* FreeRTOSConfig.h file. */portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();traceTASK_SWITCHED_IN();/* 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;/* OpenOCD makes use of uxTopUsedPriority for thread debugging. Prevent uxTopUsedPriority* from getting optimized out as it is no longer used by the kernel. */( void ) uxTopUsedPriority;
}
/*-----------------------------------------------------------*/
二,启动第一个任务(掌握)
2.1,prvStartFirstTask () /* 开启第一个任务 */
想象下应该如何启动第一个任务?
假设我们要启动的第一个任务是任务A,那么就需要将任务A的寄存器值恢复到CPU寄存器
任务A的寄存器值,在一开始创建任务时就保存在任务堆栈里边!
注意:
1、中断产生时,硬件自动将xPSR,PC(R15),LR(R14),R12,R3-R0出/入栈;
而R4~R11需要手动出/入栈
2、进入中断后硬件会强制使用MSP指针 ,此时LR(R14)的值将会被自动被更新为特殊的EXC_RETURN
用于初始化启动第一个任务前的环境,主要是重新设置MSP 指针,并使能全局中断
1、什么是MSP指针?
程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时,
MCU 会自动更新 SP 指针,ARM Cortex-M 内核提供了两个栈空间:
主堆栈指针(MSP):它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用
进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。
在FreeRTOS中,中断使用MSP(主堆栈),中断以外使用PSP(进程堆栈)
2.2,vPortSVCHandler () /* SVC中断服务函数 */
当使能了全局中断,并且手动触发 SVC 中断后,就会进入到 SVC 的中断服务函数中
注意:SVC中断只在启动第一次任务时会调用一次,以后均不调用
三、任务切换(掌握)
任务切换的本质:就是CPU寄存器的切换。
假设当由任务A切换到任务B时,主要分为两步:
第一步:需暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈,这个过程叫做保存现场;
第二步:将任务B的各个寄存器值(被存于任务堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场;
对任务A保存现场,对任务B恢复现场,这个整体的过程称之为:上下文切换

PendSV中断是如何触发的?
1、滴答定时器中断调用
2、执行FreeRTOS提供的相关API函数:portYIELD()
本质:通过向中断控制和状态寄存器 ICSR 的bit28 写入 1 挂起 PendSV 来启动 PendSV 中断

上表摘取于《Cortex M3权威指南(中文)》第131页。
PendSV中断是如何触发的?
1、滴答定时器中断调用

1、PendSV的任务切换操作(出栈,即恢复现场)