开启任务调度器
vTaskStartScheduler()
作用:用于启动任务调度器,任务调度器启动后,FreeRTOS便会开始进行任务调度
该函数的内部实现,如下:
- 创建空闲任务
- 如果使能软件定时器,则创建定时器任务
- 关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断
- 初始化全局变量,并将任务调度器的运行标志设置为已运行
- 初始化任务运行事件统计功能的时基定时器
- 调用函数xPortStartScheduler()
xPortStartScheduler()
作用:该函数用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务
该函数的内部实现,如下:
- 检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误
- 配置PendSV和SysTick的中断优先级为最低优先级
- 调用函数vPortSetupTimerInterrupt()配置SysTick
- 初始化临界区嵌套计数器为0
- 调用函数prvEnableVFP()使能FPU
- 调用函数prvStartFirstTask()启动第一个任务
启动第一个任务
假设我们要启动的第一个任务是任务A,那么就需要将任务A的寄存器恢复到CPU寄存器
任务A的寄存器值,在一开始创建任务时就保存在任务堆栈里边
注意:
- 中断产生时,硬件自动将xPSR,PC(R15),LR(R14),R12,R3-R0保存和恢复;而R4-R11需要手动保存和恢复
- 进入中断后硬件会强制使用MSP指针,此时LR(R14)的值将会被自动被更新为特殊的EXC_RETURN
prvStartFirstTask()
作用:用于初始化启动第一个任务前的环境,主要是重新设置MSP指针,并使能全局中断
- 什么是MSP指针?
程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时,MCU会自动更新SP指针,ARM Cortex-M内核提供了两个栈空间:
主堆栈指针(MSP):它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)
在FreeRTOS中,中断使用MSP,中断以外使用PSP。
- 为什么是0xE000ED08?
因为需从0xE000ED08获取向量表的偏移,为啥要获得向量表呢?因为向量表的第一个是MSP指针!取MSP的初始值的死了是先根据向量表的位置寄存器VTOR(0xE000ED08)来获取向量表存储的地址;
在根据向量表存储的地址,来访问第一个元素,也就是初始的MSP
vPortSVCHandler()
当使能了全局中断,并且手动触发SVC中断后,就会进入到SVC的中断服务函数中
- 通过pxCurrentTCB获取优先级最高的就绪态任务的任务栈地址,优先级最高的就绪态任务是系统将要运行的任务。
- 通过任务的栈顶指针,将任务栈中的内容出栈到CPU寄存器中,任务栈中的内容在调用任务创建函数的时候,已初始化,然后设置PSP指针
- 通过往BASEPRI寄存器中写0,允许中断
- R14是链接寄存器LR,在ISR中(刺客我们在SVC的ISR中),它记录了异常返回值EXC_RETURN,而EXC_RETURN只有6个合法的值(M4、M7),如下表所示:
描述 | 使用浮点单元 | 未使用浮点单元 |
---|---|---|
中断返回后进入Handler模式,并使用MSP | 0xFFFFFFE1 | 0xFFFFFFF1 |
中断返回后进入线程模式,并使用MSP | 0xFFFFFFE9 | 0xFFFFFFF9 |
中断返回后进入线程模式,并使用PSP | 0xFFFFFFED | 0xFFFFFFFD |
SVC中断只在启动第一次任务时会调用一次,以后均不会调用