启动调度器
void vTaskStartScheduler( void )
{
/* 手动指定第一个运行的任务 */
pxCurrentTCB = &Task1TCB; (1)
/* 启动调度器 */
if ( xPortStartScheduler() != pdFALSE )
{
/* 调度器启动成功,则不会返回,即不会来到这里 */ (2)
}
}
- pxCurrentTCB
- 是一个在 task.c 定义的全局指针,用于指向当前正 在运行或者即将要运行的任务的任务控制块。
- 调用函数 xPortStartScheduler()启动调度器
- 调度器启动成功,则不会返回。
xPortStartScheduler()函数
/*
* 参考资料《STM32F10xxx Cortex-M3 programming manual》4.4.3,百度搜索“PM0056”即可找到这个文档
* 在 Cortex-M 中,内核外设 SCB 中 SHPR3 寄存器用于设置 SysTick 和 PendSV 的异常优先级
* System handler priority register 3 (SCB_SHPR3) SCB_SHPR3:0xE000 ED20
* Bits 31:24 PRI_15[7:0]: Priority of system handler 15, SysTick exception
* Bits 23:16 PRI_14[7:0]: Priority of system handler 14, PendSV
*/
#define portNVIC_SYSPRI2_REG (*(( volatile uint32_t *) 0xe000ed20))
#define portNVIC_PENDSV_PRI (((uint32_t) configKERNEL_INTERRUPT_PRIORITY ) << 16UL)
#define portNVIC_SYSTICK_PRI (((uint32_t) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
BaseType_t xPortStartScheduler( void ) 14
{
/* 配置 PendSV 和 SysTick 的中断优先级为最低 */ (1)
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/* 启动第一个任务,不再返回 */
prvStartFirstTask(); (2)
/* 不应该运行到这里 */
return 0;
}
- SysTick 和 PendSV
- 都会涉及到系统调度,系统调度的优先级要低于系统的其它硬件中断优先级
- 即优先相应系统中的外部硬件中断,所以 SysTick 和 PendSV 的中断优先级配置为最低。
- 调用函数 prvStartFirstTask()启动第一个任务
- 启动成功后,则不 再返回,该函数由汇编编写,在 port.c 实现
prvStartFirstTask()函数
prvStartFirstTask()函数用于开始第一个任务,主要做了两个动作
- 一个是更新 MSP 的值
- 二是产生 SVC 系统调用到 SVC 的中断服务函数里面真正切换到第一个任务。
__asm void prvStartFirstTask( void )
{
PRESERVE8 (2)//当前栈需按照 8 字节对齐,因为有浮点计算
/* 在 Cortex-M 中,0xE000ED08 是 SCB_VTOR 这个寄存器的地址, (3)
里面存放的是向量表的起始地址,即 MSP 的地址 */
ldr r0, =0xE000ED08 (4) //将 0xE000ED08 这个立即数加载到寄存器 R0。
ldr r0, [r0] (5)//将 0xE000ED08 这个地址指向的内容加载到寄存器 R0,此时 R0等于 SCB_VTOR 寄存器的值,等于 0x00000000,即 memory 的起始地址。
ldr r0, [r0] (6)//将 0x00000000 这个地址指向的内容加载到 R0,此时 R0 等于0x200008DB
/* 设置主堆栈指针 msp 的值 */
msr msp, r0 (7)
//将 R0 的值存储到 MSP,此时 MSP 等于 0x200008DB,这是主堆栈的栈顶指针。
/* 使能全局中断 */ (8)
cpsie i
cpsie f
dsb
isb
/* 调用 SVC 去启动第一个任务 */
svc 0 (9)
nop
nop
}
- 0xE000ED08
- 在 Cortex-M 中,是 SCB_VTOR 寄存器的地址,里面存放的是向量表的起始地址,即 MSP 的地址。
- 向量表通常是从内部 FLASH 的起始地址开始存放,那么可知 memory:0x00000000 处存放的就是 MSP 的值。
★CPS的用法
CPSID I //PRIMASK=1 ;关中断
CPSIE I //PRIMASK=0 ;开中断
CPSID F //FAULTMASK=1 ;关异常
CPSIE F //FAULTMASK=0 ;
寄存器名字 | 功能描述 |
---|---|
PRIMASK | 这是个只有单一比特的寄存器。 在它被置 1 后,就关掉所有可屏蔽的异常,只剩下 NMI 和硬 FAULT 可以响应。它的缺省值是 0,表示没有关中断。 |
FAULTMASK | 这是个只有 1 个位的寄存器。 它置 1 时,只有 NMI 才能响应,所有其它的异常,甚至是硬 FAULT,也通通闭嘴。它的缺省值也是 0,表示没有关异常。 |
BASEPRI | 这个寄存器最多有 9 位( 由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低)。但若被设成 0,则不关闭任何中断, 0 也是缺省值。 |
★SVC
SVC(系统服务调用,亦简称系统调用)
SVC 用于产生系统函数的调用请求。
例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。
SVC 异常通过执行”SVC”指令来产生。
该指令需要一个立即数,充当系统调用代号。SVC异常服务例程稍后会提取出此代号,从而解释本次调用的具体要求,再调用相应的服务函数。例如,
SVC 0x3 ; 调用3 号系统服务11
vPortSVCHandler()函数
__asm void vPortSVCHandler( void )
{
extern pxCurrentTCB; (1)
PRESERVE8//8字节对齐
ldr r3, =pxCurrentTCB (2)//加载 pxCurrentTCB 的地址到 r3。
ldr r1, [r3] (3)//加载 pxCurrentTCB 指向的任务控制块到 r0
ldr r0, [r1] (4)//加载 pxCurrentTCB 指向的任务控制块到r0,任务控制块的第一个成员就是栈顶指针
ldmia r0!, {r4-r11} (5)//以 r0 为基地址,将栈中向上增长的 8 个字的内容加载到 CPU 寄存器 r4~r11,同时 r0 也会跟着自增。
msr psp, r0 (6)//将新的栈顶指针 r0 更新到 psp,任务执行的时候使用的堆栈指针是psp。
isb
mov r0, #0 (7)//将寄存器 r0 清 0。
msr basepri, r0 (8)//设置 basepri 寄存器的值为 0,即打开所有中断。basepri 是一个中断屏蔽寄存器,大于等于此寄存器值的中断都将被屏蔽。
orr r14, #0xd (9)//当从 SVC 中断服务退出前,通过向 r14 寄存器最后 4 位按位或上0x0D,使得硬件在退出时使用进程堆栈指针 PSP 完成出栈操作并返回后进入任务模式、返
bx r14 (10)//异常返回,这个时候出栈使用的是 PSP 指针,,自动将栈中的剩下内容加载到 CPU 寄存器: xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0
}
- extern pxCurrentTCB
- 声明外部变量 pxCurrentTCB,pxCurrentTCB 是一个在 task.c 中定 义的全局指针
- 用于指向当前正在运行或者即将要运行的任务的任务控制块。
汇编指令
指令名称 | 作用 |
---|---|
EQU | 给数字常量取一个符号名,相当于C语言中的define |
AREA | 汇编一个新的代码段或者数据段 |
SPACE | 分配内存空间 |
PRESERVE8 | 当前文件栈需按照8字节对齐 |
EXPORT | 声明一个标号具有全局属性,可被外部的文件使用 |
DCD | 以字为单位分配内存,要求4字节对齐,并要求初始化这些内存 |
PROC | 定义子程序,与ENDP成对使用,表示子程序结束 |
WEAK | 弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的 标号,如果外部文件没有定义也不出错。要注意的是:这个不是ARM 的指令,是编译器的,这里放在一起只是为了方便。 |
IMPORT | 声明标号来自外部文件,跟C语言中的EXTERN关键字类似 |
---|---|
B | 跳转到一个标号 |
ALIGN | 编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即 数,缺省表示4字节对齐。要注意的是:这个不是ARM的指令,是 编译器的,这里放在一一起只是为了方便。 |
END | 到达文件的末尾,文件结束 |
IF,ELSE,ENDIF | 汇编条件分支语句,跟C语言的if else 类似 |
MRS | 加载特殊功能寄存器的值到通用寄存器 |
MSR | 存储通用寄存器的值到特殊功能寄存器 |
CBZ | 比较,如果结果为0就转移 |
CBNZ | 比较,如果结果非0就转移 |
LDR | 从存储器中加载字到一个寄存器中 |
LDR[伪指令] | 加载一个立即数或者一个地址值到一个寄存器。举例: LDR Rd, = label, 如果label是立即数,那Rd等于立即数,如果label是一个标识符,比如 指针,那存到Rd的就是label这个标识符的地址 |
LDRH | 从存储器中加载半字到一个寄存器中 |
LDRB | 从存储器中加载字节到一个寄存器中 |
STR | 把一个寄存器按字存储到存储器中 |
STRH | 把一个寄存器存器的低半字存储到存储器中 |
STRB | 把一个寄存器的低字节存储到存储器中 |
L DMIA | 将多个字从存储器加载到CPU寄存器,先操作,指针在递增。 |
STMDB | 将多个字从CPU寄存器存储到存储器,指针先递减,再操作 |
LDMFD | |
ORR | 按位或 |
BX | 直接跳转到由寄存器给定的地址 |
BL | 跳转到标号对应的地址,并且把跳转前的下条指令地址保存到LR |
BLX | 跳转到由寄存器REG给出的的地址,并根据REG的LSB切换处理器状 态,还要把转移前的下条指令地址保存到LR。 ARM(LSB=0), Thumb(LSB=1)。CM3只在Thumb中运行,就必须保证reg的LSB=l,否 则一个fault打过来 |