目录
开启任务调度器vTaskStartScheduler函数
xPortStartScheduler开启任务调度器函数
启动第一个任务
prvStartFirstTask开启第一个任务函数
vPortSVCHandler SVC中断服务函数
出栈/压栈汇编指令详解
开启任务调度器vTaskStartScheduler函数
作用:用于启动任务调度器,任务调度器启动后, FreeRTOS 便会开始进行任务调度。
该函数内部实现,如下步骤:
1.创建空闲任务
2.如果使能软件定时器,则创建定时器任务
3.关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
4.初始化全局变量,并将任务调度器的运行标志设置为已运行
5.初始化任务运行时间统计功能的时基定时器
6.调用函数xPortStartScheduler()
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;
}
/*-----------------------------------------------------------*/
xPortStartScheduler开启任务调度器函数
作用:该函数用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务
该函数内部实现,如下:
1.检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误
2.配置PendSV和SysTick的中断优先级为最低优先级
3.调用函数vPortSetupTimerlnterrupt()配置SysTick
4.初始化临界区嵌套计数器为0
5.调用函数prvEnableVFP()使能FPU6(M3没有)、调用函数prvStartFirstTask()启动第一个任务
BaseType_t xPortStartScheduler( void )
{
/*检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误*/
#if( configASSERT_DEFINED == 1 )
{
volatile uint32_t ulOriginalPriority;
volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;
/* Determine the maximum priority from which ISR safe FreeRTOS API
functions can be called. ISR safe functions are those that end in
"FromISR". FreeRTOS maintains separate thread and ISR API functions to
ensure interrupt entry is as fast and simple as possible.
Save the interrupt priority value that is about to be clobbered. */
ulOriginalPriority = *pucFirstUserPriorityRegister;
/* Determine the number of priority bits available. First write to all
possible bits. */
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
/* Read the value back to see how many bits stuck. */
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
/* Use the same mask on the maximum system call priority. */
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
/* Calculate the maximum acceptable priority group value for the number
of bits read back. */
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
/* Shift the priority group value back to its position within the AIRCR
register. */
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
/* Restore the clobbered interrupt priority register to its original
value. */
*pucFirstUserPriorityRegister = ulOriginalPriority;
}
#endif /* conifgASSERT_DEFINED */
/* Make PendSV and SysTick the lowest priority interrupts. */
/*配置PendSV和SysTick的中断优先级为最低优先级*/
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* Start the timer that generates the tick ISR. Interrupts are disabled
here already. */
/*初始化滴答定时器*/
vPortSetupTimerInterrupt();
/* Initialise the critical nesting count ready for the first task. */
/*初始化临界区嵌套计数器为0*/
uxCriticalNesting = 0;
/* Start the first task. */
/*启动第一个任务*/
prvStartFirstTask();
/* Should not get here! */
return 0;
}
启动第一个任务
假设我们要启动的第一个任务是任务A,那么就需要将任务A的寄存器值加载到CPU寄存器中,任务A的寄存器值,在一开始创建任务时就保存在任务堆栈里边!
注意:
1.中断产生时,硬件自动将xPSR, PC(R15), LR(R14), R12, R3-R0保存和恢复;而R4~R11需要手动保存和恢复。
2.进入中断后硬件会强制使用MSP指针,此时LR(R14)的值将会被自动被更新为特殊的EXC_RETURN
prvStartFirstTask开启第一个任务函数
prvStartFirstTask开启第一个任务函数
用于初始化启动第一个任务前的环境,主要是重新设置MSP指针,并使能全局中断。
1.什么是MSP指针?
程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时,MCU会自动更新SP(R13)指针, ARM Cortex-M内核提供了两个栈空间:
主堆栈指针(MSP):它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。
在FreeRTOS中,中断使用MSP (主堆栈) ,中断以外使用PSP (进程堆栈)
__asm void prvStartFirstTask( void )
{
/*1首先是使用了 PRESERVE8,进行 8 字节对齐,这是因为,
栈在任何时候都是需要 4 字节对齐的,而在调用入口得8字节对齐,
在进行C编程的时候,编译器会自动完成的对齐的操作,而对于汇编,
就需要开发者手动进行对齐。*/
/*8字节对齐*/
PRESERVE8
/* Use the NVIC offset register to locate the stack. */
/*向量表开始寄存器地址*/
ldr r0, =0xE000ED08
/* 获取向量表的值*/
ldr r0, [r0]
/*获取MSP的初始值(栈底指针)*/
ldr r0, [r0]
/* Set the msp back to the start of the stack. */
/*初始化MSP*/
msr msp, r0
/* Globally enable interrupts. */
/*使能全局中断*/
cpsie i
cpsie f
dsb
isb
/* Call SVC to start the first task. */
/*触发svc中断启动第一个任务*/
svc 0
nop
nop
}
vPortSVCHandler SVC中断服务函数
注意:SVC中断只在启动第一次任务时会调用一次,以后均不调用
当使能了全局中断,并且手动触发SVC中断后,就会进入到SVC的中断服务函数中
1.通过 pxCurrentTCB 获取优先级最高的就绪态任务的任务栈地址,优先级最高的就绪态任务是系统将要运行的任务。
2.通过任务的栈顶指针,将任务栈中的内容出栈到CPU寄存器中,任务栈中的内容在调用任务创建函数的时候,已初始化,然后设置 PSP 指针。
3.通过往 BASEPRI 寄存器中写 0,允许中断。
4. R14是链接寄存器LR,在ISR中(此刻我们在SVC的ISR中) ,它记录了异常返回值EXC_RETURN而EXC_RETURN只有6个合法的值(M4、M7),如下表所示:
__asm void vPortSVCHandler( void )
{
/*首先是使用了 PRESERVE8,进行 8 字节对齐,这是因为,
栈在任何时候都是需要 4 字节对齐的,而在调用入口得8字节对齐,
在进行C编程的时候,编译器会自动完成的对齐的操作,而对于汇编,
就需要开发者手动进行对齐。*/
/*8字节对齐*/
PRESERVE8
/*获取当前任务优先级最高的任务地址*/
ldr r3, =pxCurrentTCB /* Restore the context. */
/*通过任务地址获取首成员地址*/
ldr r1, [r3] /* Use pxCurrentTCBConst to get the pxCurrentTCB address. */
/*通过首成员获取首成员值*/
ldr r0, [r1] /* The first item in pxCurrentTCB is the task top of stack. */
/*出栈,把任务控制块里的变量赋值到CPU的R4-R11*/
ldmia r0!, {r4-r11} /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
/*设置PSP为任务栈指针此时R0地址是R11,把R0地址赋值给PSP,
为的是退出中断时由硬件恢复其他寄存器*/
msr psp, r0 /* Restore the task stack pointer. */
isb
/*basepri等于0的时候使能所有中断*/
mov r0, #0
msr basepri, r0
/*使用 PSP 指针,并跳转到任务函数*/
orr r14, #0xd
/*最后通过 r14指令,跳转到任务的任务函数中执行*/
bx r14
}
出栈/压栈汇编指令详解
1.出栈(恢复现场),方向:从下往上(低地址往高地址):假设r0地址为0x04汇编指令示例:
/*任务栈r0地址由低到高,将r0存储地址里面的内容手动加载到CPU寄存器r4、r5、r6*/
Idmia r0!, {r4-r6}
r0地址(0x04)内容加载到r4,加载完后地址需要加4此时地址r0= r0+4 = 0x08
R0地址(0x08)内容加载到r5,加载完后地址需要加4此时地址r0=r0+4 = 0x0C
R0地址(0x0C)内容加载到r6,加载完后地址需要加4此时地址r0=r0+4 = 0x10
2.压栈(保存现场),方向:从上往下(高地址往低地址):假设r0地址为0x10汇编指令示例:
/*r0的存储地址由高到低递减,将r4、r5、r6里的内容存储到r0的任务栈里面。*/
stmdb r0!, {r4-r6}}
地址: r0=r0-4 = 0x0C,将r6的内容(寄存器值)存放到r0所指向地址(0x0C)
地址: r0=r0-4 = 0x08,将r5的内容(寄存器值)存放到r0所指向地址(0x08)
地址: r0=r0-4 = 0x04,将r4的内容(寄存器值)存放到r0所指向地址(0x08)