PendSV_Handler代码详解

2018-03-12 11:36:28 +0000 李述铜

感谢同学 @勇敢的心,@捕硅锗 的提问

有同学在看完视频后,对PendSV_Handler中的实现代码仍有很多不解,所以这里用文档再详细标注。如果你在学习视频时遇到同样的问题,请先参考此文档。

该课时中的PendSV_Handler实现,主要用于模拟任务切换时的CPU内核寄存器的保存与恢复。这段代码非常重要,请务必弄懂。主要流程如下:

进入异常硬件自动保存部分寄存器到当前堆栈中 执行STM指令将其余未自动保存的寄存器R4~R11保存到自定义的缓冲区中 执行LDM指令将从自定义缓冲区中恢复之前保存的寄存器值到R4~R11 执行BX LR,硬件自动从当前堆栈中恢复之前保存的寄存器值 源码详解:

__asm void PendSV_Handler ()
{
  // blockPtr在main.c中定义,定义为:BlockType_t * blockPtr。
// 在汇编代码中,如要引用C中的符号,必  须先用IMPORT导入。有些类似于在C语言中的extern
    IMPORT  blockPtr
    
    // 加载寄存器存储地址
    // LDR R0, =blockPtr ; 将blockPtr变量的地址加载到R0中
    // LDR R0, [R0] ; 再从该地址加载32位数据值,也就是将blockPtr的值加载到R0中。在main()中,我们已经设置:
    // BlockType_t block;
    // block.stackPtr = &stackBuffer[1024];
    // blockPtr = █
    // 所以,此时R0的值将会是block结构的起始地址。

    // LDR R0, [R0]  ; 再次加载32位数据,显示此次就是从block结构开始处加载32位值,根据block结构的定义:
    // typedef struct _BlockType_t {
     //    unsigned long * stackPtr;
    // }BlockType_t
    // 显然,此时R0的值就是stackPtr的值,也就是&stackBuffer[1024];
    LDR     R0, =blockPtr
    LDR     R0, [R0]
    LDR     R0, [R0]

    // 保存寄存器
   // 此时R0的值就是&stackBuffer[1024]。然后,将R4, R5, .. R11写入到stackBuff缓冲区中,顺序不重要,你只需知道其与下面LDMIA的加载次序正好相反即可。写入前,以R0中地址为基准,每写入一个寄存器前,先对地址减去4,然后再写入寄存器值。完成所有写入之后,将最后的地址覆盖到R0寄存器中
    STMDB   R0!, {R4-R11}
    
    // 将最后的地址写入到blockPtr中
    // LDR R1, =blockPtr ; 将blockPtr变量的地址加载到R1中
    // LDR R1, [R1] ; 再从该地址加载32位数据值,也就是将blockPtr的值加载到R1中。在main()中,我们已经设置:
    // BlockType_t block;
    // block.stackPtr = &stackBuffer[1024];
    // blockPtr = █
    // 所以,此时R1的值将会是block结构的起始地址。
    // STR R0, [R1]  ; 将前面执行STM执行后,R0的值也就是最后写入的地址写入R1中地址指向的位置。
    // typedef struct _BlockType_t {
     //    unsigned long * stackPtr;
    // }BlockType_t
    // 显然,此时R1的值就是block结构的地址,也就是向将R0的值写入到stackPtr。最终实现将最后的写stackBuff的 地址保存到block.stackPtr
    LDR     R1, =blockPtr
    LDR     R1, [R1]
    STR     R0, [R1]
    
    // 修改部分寄存器,用于测试
   // 测试代码。用于检查前面的保存(STMDB),以及后面的恢复(LDMIA)是否正确。如果保存和恢复正确,那么执行LDMIA后,R4,R5的值应为恢复之前的值。
   // 如果需要,可在此添加对其它寄存器的修改测试代码。

    ADD R4, R4, #1
    ADD R5, R5, #1
    
    // 恢复寄存器
    // 与STMDB正好相反,从R0地址对应的存储单元中读取多个32位的单元,恢复到R4~R11寄存器。
    // 注意到,此时,R0为之前向存储单元写R4~R11的最后单元的地址,所以此时可以正确恢复。
    // 具体执行时,首先从当前地址对应的单元处加载数据恢复到寄存器,再将地址+4,如此反复,直到所有寄存器恢复完毕。

    LDMIA   R0!, {R4-R11}
    
    // 异常返回
    // 异常返回指令,不同于从函数调用。此时LR的值不是函数的返回地址,而是一串特定的值,如0xFFFF_FFFx,见《芯片内核简介》中退出异常小节。

    BX      LR
} 

rtos 无标签

推荐课程

  • 自己动手写嵌入式操作系统

    循序渐进,40余次迭代,写出不到2000行代码的嵌入式操作系统

  • 手把手教你学用嵌入式操作系统

    从实用角度出发,教你如何使用嵌入式操作系统

  • 深入理解ARM调试原理

    深入理解Jlink/Ulink等仿真调试工具背后工作的秘密

  • 轻松掌握Git & GitHub

    掌握当今最流行的版本管理工具,帮你找回过去任意时间的文件版本

  • 自己动手学用Keil(MDK)

    轻松掌握最主流的ARM开发工具Keil(MDK)