ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

十、FreeRTOS之FreeRTOS任务调度

十、FreeRTOS之FreeRTOS任务调度

本节需要掌握的内容如下:

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.1prvStartFirstTask ()   /* 开启第一个任务 */

想象下应该如何启动第一个任务?

假设我们要启动的第一个任务是任务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.2vPortSVCHandler ()   /* 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、滴答定时器中断调用

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

返回列表