启动调度器

  1. void vTaskStartScheduler( void )
  2. {
  3. /* 手动指定第一个运行的任务 */
  4. pxCurrentTCB = &Task1TCB; (1)
  5. /* 启动调度器 */
  6. if ( xPortStartScheduler() != pdFALSE )
  7. {
  8. /* 调度器启动成功,则不会返回,即不会来到这里 */ (2)
  9. }
  10. }
  • pxCurrentTCB
    • 是一个在 task.c 定义的全局指针,用于指向当前正 在运行或者即将要运行的任务的任务控制块。
  • 调用函数 xPortStartScheduler()启动调度器
    • 调度器启动成功,则不会返回。

xPortStartScheduler()函数

  1. /*
  2. * 参考资料《STM32F10xxx Cortex-M3 programming manual》4.4.3,百度搜索“PM0056”即可找到这个文档
  3. * 在 Cortex-M 中,内核外设 SCB 中 SHPR3 寄存器用于设置 SysTick 和 PendSV 的异常优先级
  4. * System handler priority register 3 (SCB_SHPR3) SCB_SHPR3:0xE000 ED20
  5. * Bits 31:24 PRI_15[7:0]: Priority of system handler 15, SysTick exception
  6. * Bits 23:16 PRI_14[7:0]: Priority of system handler 14, PendSV
  7. */
  8. #define portNVIC_SYSPRI2_REG (*(( volatile uint32_t *) 0xe000ed20))
  9. #define portNVIC_PENDSV_PRI (((uint32_t) configKERNEL_INTERRUPT_PRIORITY ) << 16UL)
  10. #define portNVIC_SYSTICK_PRI (((uint32_t) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
  11. BaseType_t xPortStartScheduler( void ) 14
  12. {
  13. /* 配置 PendSV 和 SysTick 的中断优先级为最低 */ (1)
  14. portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
  15. portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
  16. /* 启动第一个任务,不再返回 */
  17. prvStartFirstTask(); (2)
  18. /* 不应该运行到这里 */
  19. return 0;
  20. }
  • SysTick 和 PendSV
    • 都会涉及到系统调度,系统调度的优先级要低于系统的其它硬件中断优先级
    • 即优先相应系统中的外部硬件中断,所以 SysTick 和 PendSV 的中断优先级配置为最低。
  • 调用函数 prvStartFirstTask()启动第一个任务
    • 启动成功后,则不 再返回,该函数由汇编编写,在 port.c 实现

prvStartFirstTask()函数

prvStartFirstTask()函数用于开始第一个任务,主要做了两个动作

  • 一个是更新 MSP 的值
  • 二是产生 SVC 系统调用到 SVC 的中断服务函数里面真正切换到第一个任务。
  1. __asm void prvStartFirstTask( void )
  2. {
  3. PRESERVE8 (2)//当前栈需按照 8 字节对齐,因为有浮点计算
  4. /* 在 Cortex-M 中,0xE000ED08 是 SCB_VTOR 这个寄存器的地址, (3)
  5. 里面存放的是向量表的起始地址,即 MSP 的地址 */
  6. ldr r0, =0xE000ED08 (4) //将 0xE000ED08 这个立即数加载到寄存器 R0。
  7. ldr r0, [r0] (5)//将 0xE000ED08 这个地址指向的内容加载到寄存器 R0,此时 R0等于 SCB_VTOR 寄存器的值,等于 0x00000000,即 memory 的起始地址。
  8. ldr r0, [r0] (6)//将 0x00000000 这个地址指向的内容加载到 R0,此时 R0 等于0x200008DB
  9. /* 设置主堆栈指针 msp 的值 */
  10. msr msp, r0 (7)
  11. //将 R0 的值存储到 MSP,此时 MSP 等于 0x200008DB,这是主堆栈的栈顶指针。
  12. /* 使能全局中断 */ (8)
  13. cpsie i
  14. cpsie f
  15. dsb
  16. isb
  17. /* 调用 SVC 去启动第一个任务 */
  18. svc 0 (9)
  19. nop
  20. nop
  21. }
  • 0xE000ED08
    • 在 Cortex-M 中,是 SCB_VTOR 寄存器的地址,里面存放的是向量表的起始地址,即 MSP 的地址。
    • 向量表通常是从内部 FLASH 的起始地址开始存放,那么可知 memory:0x00000000 处存放的就是 MSP 的值。

image.png

★CPS的用法

  1. CPSID I //PRIMASK=1 ;关中断
  2. CPSIE I //PRIMASK=0 ;开中断
  3. CPSID F //FAULTMASK=1 ;关异常
  4. 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

SVC/PendSV介绍

vPortSVCHandler()函数

  1. __asm void vPortSVCHandler( void )
  2. {
  3. extern pxCurrentTCB; (1)
  4. PRESERVE8//8字节对齐
  5. ldr r3, =pxCurrentTCB (2)//加载 pxCurrentTCB 的地址到 r3。
  6. ldr r1, [r3] (3)//加载 pxCurrentTCB 指向的任务控制块到 r0
  7. ldr r0, [r1] (4)//加载 pxCurrentTCB 指向的任务控制块到r0,任务控制块的第一个成员就是栈顶指针
  8. ldmia r0!, {r4-r11} (5)//以 r0 为基地址,将栈中向上增长的 8 个字的内容加载到 CPU 寄存器 r4~r11,同时 r0 也会跟着自增。
  9. msr psp, r0 (6)//将新的栈顶指针 r0 更新到 psp,任务执行的时候使用的堆栈指针是psp。
  10. isb
  11. mov r0, #0 (7)//将寄存器 r0 清 0。
  12. msr basepri, r0 (8)//设置 basepri 寄存器的值为 0,即打开所有中断。basepri 是一个中断屏蔽寄存器,大于等于此寄存器值的中断都将被屏蔽。
  13. orr r14, #0xd (9)//当从 SVC 中断服务退出前,通过向 r14 寄存器最后 4 位按位或上0x0D,使得硬件在退出时使用进程堆栈指针 PSP 完成出栈操作并返回后进入任务模式、返
  14. bx r14 (10)//异常返回,这个时候出栈使用的是 PSP 指针,,自动将栈中的剩下内容加载到 CPU 寄存器: xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0
  15. }
  • extern pxCurrentTCB
    • 声明外部变量 pxCurrentTCB,pxCurrentTCB 是一个在 task.c 中定 义的全局指针
    • 用于指向当前正在运行或者即将要运行的任务的任务控制块。

image.png

汇编指令

指令名称 作用
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打过来