启动调度器
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 icpsie fdsbisb/* 调用 SVC 去启动第一个任务 */svc 0 (9)nopnop}
- 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 指向的任务控制块到 r0ldr r0, [r1] (4)//加载 pxCurrentTCB 指向的任务控制块到r0,任务控制块的第一个成员就是栈顶指针ldmia r0!, {r4-r11} (5)//以 r0 为基地址,将栈中向上增长的 8 个字的内容加载到 CPU 寄存器 r4~r11,同时 r0 也会跟着自增。msr psp, r0 (6)//将新的栈顶指针 r0 更新到 psp,任务执行的时候使用的堆栈指针是psp。isbmov 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打过来 |
