自己动手写RTOS:02-在M3内核上实现pendsvc

2023-11-17

自己动手写RTOS

自己动手写RTOS:01基础知识和理论部分
自己动手写RTOS:02-在M3内核上实现pendsvc



一、M3内核的相关知识

节选自M3权威指南

1.1寄存器

在这里插入图片描述
**R0-R12:**通用寄存器R0‐R12 都是 32 位通用寄存器,用于数据操作。但是注意:绝大多数 16 位 Thumb 指令只能访问 R0‐R7,而 32 位 Thumb‐2 指令可以访问所有寄存器。其中R4-R11,使用前必须前保存,用完后再恢复
Banked R13: 两个堆栈指针Cortex‐M3 拥有两个堆栈指针,然而它们是 banked,因此任一时刻只能使用其中的一个。
主堆栈指针( MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程)
进程堆栈指针( PSP):由用户的应用程序代码使用。堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的。在 ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。除了外部中断外,当有指令执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的 fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入异常状态的(常用于系统调用)。
**R14:**连接寄存器当呼叫一个子程序时,由 R14 存储返回地址
**R15:**程序计数寄存器指向当前的程序地址。如果修改它的值,就能改变程序的执行流(很多高级技巧就在这里面——译注)。

1.2特殊寄存器

在这里插入图片描述
在这里插入图片描述

1.3堆栈

在这里插入图片描述
**

注意非常重要:Cortex‐M3 使用的是“向下生长的满栈”模型。堆栈指针 SP 指向最后一个被压入堆栈的 32位数值。在下一次压栈时, SP 先自减 4,再存入新的数值。POP 操作刚好相反:先从 SP 指针处读出上一次被压入的值,再把 SP 指针自增 4。

**

二、pendSVC实现

1.汇编语句

1.1存储器访问:
1)LDR指令

LDR格式:LDR{条件} 目的寄存器 <存储器地址>

LDR作用:将存储器地址所指地址处连续的4个字节(1个字)的数据传送到目的寄存器

比如想把数据从内存中某处读取到寄存器中,只能使用ldr

比如:
ldr r1, =0x40000000 ;将地址0x4000_0000赋值给r1
ldr r0, [#r1] ;将地址0x4000_0000处的数据赋值给

2)STR和LDRB指令

STR格式:STR{条件} 源寄存器,<存储器地址>

STR作用:STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中比较常用,寻址方式灵活多样,使用方式可参考指令LDR。

LDRB:字节数据加载指令


3)MOV指令

MOV格式:mov source, destination

MOV作用:source 和 destination 的值可以是内存地址,存储在内存中的数据值,指令语句中定义的数据值,或者寄存器。

实例代码

main.c
#define NVIC_INT_CTRL       0xE000ED04      // 中断控制及状态寄存器
#define NVIC_PENDSVSET      0x10000000      // 触发软件中断的值
#define NVIC_SYSPRI2        0xE000ED22      // 系统优先级寄存器
#define NVIC_PENDSV_PRI     0x000000FF      // 配置优先级

#define MEM32(addr)         *(volatile unsigned long *)(addr)
#define MEM8(addr)          *(volatile unsigned char *)(addr)

void triggerPendSVC (void) 
{
    MEM8(NVIC_SYSPRI2) = NVIC_PENDSV_PRI;   // 向NVIC_SYSPRI2写NVIC_PENDSV_PRI,设置其为最低优先级
    MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET;    // 向NVIC_INT_CTRL写NVIC_PENDSVSET,用于PendSV
}

/**********************************************************************************************************
** Function name        :   PendSV_Handler
** Descriptions         :   PendSV异常处理函数。很有些会奇怪,看不到这个函数有在哪里调用。实际上,只要保持函数头不变
**                          void PendSV_Handler (), 在PendSV发生时,该函数会被自动调用
** parameters           :   无
** Returned value       :   无
***********************************************************************************************************/
typedef struct _BlockType_t 
{
    unsigned long * stackPtr;
}BlockType_t;

BlockType_t * blockPtr;

void delay (int count) 
{
    while (--count > 0);
}

int flag;

unsigned long stackBuffer[32];
BlockType_t block;

int main () 
{
    block.stackPtr = &stackBuffer[32];
    blockPtr = &block;
    for (;;) {
        flag = 0;
        delay(100);
        flag = 1;
        delay(100);
        
        triggerPendSVC();
    }
    
    return 0;
}

switch.c

__asm void PendSV_Handler ()
{
    IMPORT  blockPtr
    
    // 加载寄存器存储地址
    LDR     R0, =blockPtr
    LDR     R0, [R0]
    LDR     R0, [R0]

    // 保存寄存器
    STMDB   R0!, {R4-R11}
    
    // 将最后的地址写入到blockPtr中
    LDR     R1, =blockPtr
    LDR     R1, [R1]
    STR     R0, [R1]
    
    // 修改部分寄存器,用于测试
    ADD R4, R4, #1
    ADD R5, R5, #1
    
    // 恢复寄存器
    LDMIA   R0!, {R4-R11}
    
    // 异常返回
    BX      LR
}  

在这里插入图片描述
在这里插入图片描述
执行到main.c 65行时跳转到triggerPendSVC函数,然后执行语句MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET,就自动跳转到__asm void PendSV_Handler ()里;
依次执行后
LDR R0, =blockPtr
LDR R0, [R0]
LDR R0, [R0]
R0的值分别为0x2000_0000,0x2000_0008,0x2000_0090,即最后为数组stackBuffer[32]的地址,可能很多人问,为什么要用stackBuffer[32]的地址而不是stackBuffer[31]的,这和接下来的语句有关
STMDB R0!, {R4-R11} ;这条语句的意思是,先R0减去4的地址,也就是stackBuffer[31]的地址,然后依次将R11-R4存进这个数组,最后R0保存执行完的地址0x2000_0070
最后执行15-17后,栈变量的指针就指向栈顶地址也就是R1=0x2000_0070

2.实现任务A和B的切换

main.c

  int main()
  {  
    // 初始化任务1和任务2结构,传递运行的起始地址,想要给任意参数,以及运行堆栈空间
    tTaskInit(&tTask1, task1Entry, (void *)0x11111111, &task1Env[1024]);
    tTaskInit(&tTask2, task2Entry, (void *)0x22222222, &task2Env[1024]);
    
    // 接着,将任务加入到任务表中
    taskTable[0] = &tTask1;
    taskTable[1] = &tTask2;
    
    // 我们期望先运行tTask1, 也就是void task1Entry (void * param) 
    nextTask = taskTable[0];

    // 切换到nextTask, 这个函数永远不会返回
    tTaskRunFirst();
    return 0;
    }

tTaskInit 函数的实现

void tTaskInit (tTask * task, void (*entry)(void *), void *param, uint32_t * stack)
{
    // 为了简化代码,tinyOS无论是在启动时切换至第一个任务,还是在运行过程中在不同间任务切换
    // 所执行的操作都是先保存当前任务的运行环境参数(CPU寄存器值)的堆栈中(如果已经运行运行起来的话),然后再
    // 取出从下一个任务的堆栈中取出之前的运行环境参数,然后恢复到CPU寄存器
    // 对于切换至之前从没有运行过的任务,我们为它配置一个“虚假的”保存现场,然后使用该现场恢复。

    // 注意以下两点:
    // 1、不需要用到的寄存器,直接填了寄存器号,方便在IDE调试时查看效果;
    // 2、顺序不能变,要结合PendSV_Handler以及CPU对异常的处理流程来理解
    *(--stack) = (unsigned long)(1<<24);                // XPSR, 设置了Thumb模式,恢复到Thumb状态而非ARM状态运行
    *(--stack) = (unsigned long)entry;                  // 程序的入口地址
    *(--stack) = (unsigned long)0x14;                   // R14(LR), 任务不会通过return xxx结束自己,所以未用
    *(--stack) = (unsigned long)0x12;                   // R12, 未用
    *(--stack) = (unsigned long)0x3;                    // R3, 未用
    *(--stack) = (unsigned long)0x2;                    // R2, 未用
    *(--stack) = (unsigned long)0x1;                    // R1, 未用
    *(--stack) = (unsigned long)param;                  // R0 = param, 传给任务的入口函数
    *(--stack) = (unsigned long)0x11;                   // R11, 未用
    *(--stack) = (unsigned long)0x10;                   // R10, 未用
    *(--stack) = (unsigned long)0x9;                    // R9, 未用
    *(--stack) = (unsigned long)0x8;                    // R8, 未用
    *(--stack) = (unsigned long)0x7;                    // R7, 未用
    *(--stack) = (unsigned long)0x6;                    // R6, 未用
    *(--stack) = (unsigned long)0x5;                    // R5, 未用
    *(--stack) = (unsigned long)0x4;                    // R4, 未用

    task->stack = stack;                                // 保存最终的值
}

任务函数和调度函数

任务函数
void task1Entry (void * param) 
{
    for (;;) 
    {
        task1Flag = 1;
        delay(100);
        task1Flag = 0;
        delay(100);
        tTaskSched();
    }
}
//任务执行完后的调用切换函数
void tTaskSched () 
{    
    // 这里的算法很简单。
    // 一共有两个任务。选择另一个任务,然后切换过去
    if (currentTask == taskTable[0]) 
    {
        nextTask = taskTable[1];
    }
    else 
    {
        nextTask = taskTable[0];
    }
    
    tTaskSwitch();
}

不管是tTaskSwitch()还是tTaskRunFirst(),主要就是执行如下语句
MEM32(NVIC_INT_CTRL) = NVIC_PENDSVSET; // 向NVIC_INT_CTRL写NVIC_PENDSVSET,用于PendSV

接下来,就是最重要的PendSV处理语句了,在这里我们实现当前任务的现场保存,并恢复下一任务的现场,执行下一任务

__asm void PendSV_Handler ()
{   
    IMPORT  currentTask               // 使用import导入C文件中声明的全局变量
    IMPORT  nextTask                  // 类似于在C文文件中使用extern int variable
    
    MRS     R0, PSP                   // 获取当前任务的堆栈指针
    CBZ     R0, PendSVHandler_nosave  // if 这是由tTaskSwitch触发的(此时,PSP肯定不会是0了,0的话必定是tTaskRunFirst)触发
                                      // 不清楚的话,可以先看tTaskRunFirst和tTaskSwitch的实现
    STMDB   R0!, {R4-R11}             //     那么,我们需要将除异常自动保存的寄存器这外的其它寄存器自动保存起来{R4, R11}
                                      //     保存的地址是当前任务的PSP堆栈中,这样就完整的保存了必要的CPU寄存器,便于下次恢复
    LDR     R1, =currentTask          //     保存好后,将最后的堆栈顶位置,保存到currentTask->stack处    
    LDR     R1, [R1]                  //     由于stack处在结构体stack处的开始位置处,显然currentTask和stack在内存中的起始
    STR     R0, [R1]                  //     地址是一样的,这么做不会有任何问题

PendSVHandler_nosave                  // 无论是tTaskSwitch和tTaskSwitch触发的,最后都要从下一个要运行的任务的堆栈中恢复
                                      // CPU寄存器,然后切换至该任务中运行
    LDR     R0, =currentTask          // 好了,准备切换了
    LDR     R1, =nextTask             
    LDR     R2, [R1]  
    STR     R2, [R0]                  // 先将currentTask设置为nextTask,也就是下一任务变成了当前任务
 
    LDR     R0, [R2]                  // 然后,从currentTask中加载stack,这样好知道从哪个位置取出CPU寄存器恢复运行
    LDMIA   R0!, {R4-R11}             // 恢复{R4, R11}。为什么只恢复了这么点,因为其余在退出PendSV时,硬件自动恢复

    MSR     PSP, R0                   // 最后,恢复真正的堆栈指针到PSP  
    ORR     LR, LR, #0x04             // 标记下返回标记,指明在退出LR时,切换到PSP堆栈中(PendSV使用的是MSP) 
    BX      LR                        // 最后返回,此时任务就会从堆栈中取出LR值,恢复到上次运行的位置
}  

参考连接

1、https://blog.csdn.net/qq_36300069/article/details/123850429

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自己动手写RTOS:02-在M3内核上实现pendsvc 的相关文章

  • STM32 F072上的软件如何跳转到bootloader(DFU模式)?

    STM32应用笔记2606对此进行了讨论 但没有简单的代码示例 该答案已使用 IAR EWARM 在 STM32F072 Nucleo 板上进行了测试 这个答案使用 STM32标准外设库 仅此而已 请注意 验证您是否成功进入引导加载程序模式
  • 140-基于stm32单片机智能晾衣杆控制系统Proteus仿真+源程序

    资料编号 140 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 DHT11传感器 ds1302时钟 光敏传感器 蜂鸣器 LED灯 制作一个基于stm32单片机智能晾衣杆控制系统Proteus仿真 2 通过光敏传感器
  • 133-基于stm32单片机停车场车位管理系统Proteus仿真+源程序

    资料编号 133 一 功能介绍 1 采用stm32单片机 4位数码管 独立按键 制作一个基于stm32单片机停车场车位管理系统Proteus仿真 2 通过按键进行模拟车辆进出 并且通过程序计算出当前的剩余车位数量 3 将剩余的车位数量显示到
  • 135-基于stm32单片机超声波非接触式感应水龙头控制系统Proteus仿真+源程序

    资料编号 135 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 DHT11传感器 电机 超声波传感器 制作一个基于stm32单片机超声波非接触式感应水龙头控制系统Proteus仿真 2 通过DHT11传感器检测当前
  • 物联网网关

    物联网网关是 连接物联网设备和互联网的重要桥梁 它负责将物联网设备采集到的数据进行处理 存储和转发 使其能够与云端或其它设备进行通信 物联网网关的作用是实现物联网设备与云端的无缝连接和数据交互 物联网网关功能 数据采集 物联网网关可以从物联
  • STM32F103概要

    The STM32F103x4 STM32F103x6 STM32F103xC STM32F103xD and STM32F103xE are a drop in replacement for STM32F103x8 B medium d
  • SHT10温湿度传感器——STM32驱动

    实验效果 硬件外观 接线 3 3V供电 IIC通讯 代码获取 查看下方 END
  • 硬件基础-电容

    电容 本质 电容两端电压不能激变 所以可以起到稳定电压作用 充放电 电容量的大小 想使电容容量大 使用介电常数高的介质 增大极板间的面积 减小极板间的距离 品牌 国外 村田 muRata 松下 PANASONIC 三星 SAMSUNG 太诱
  • 1.69寸SPI接口240*280TFT液晶显示模块使用中碰到的问题

    1 69寸SPI接口240 280TFT液晶显示模块使用中碰到的问题说明并记录一下 在网上买了1 69寸液晶显示模块 使用spi接口 分辨率240 280 给的参考程序是GPIO模拟的SPI接口 打算先移植到FreeRtos测试 再慢慢使用
  • 串口通讯第一次发送数据多了一字节

    先初始化IO再初始化串口 导致第一次发送时 多出一个字节数据 优化方案 先初始化串口再初始化IO 即可正常通讯
  • STM32 暂停调试器时冻结外设

    当到达断点或用户暂停代码执行时 调试器可以停止 Cortex 中代码的执行 但是 当皮质停止在暂停状态下执行代码时 调试器是否会冻结其他外设 例如 DMA UART 和定时器 您只能保留时间 r 取决于外围设备 我在进入主函数时调用以下代码
  • CMSIS & STM32,如何开始? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我想在 STM32 上使用 CMSIS 启动项目 网上一搜 没找到具体的教程 有些使用 SPL 开始项
  • Arm:objcopy 如何知道 elf 中的哪些部分要包含在二进制或 ihex 中?

    我正在开发一个项目 其中涉及解析arm elf 文件并从中提取部分 显然 elf 文件中有很多部分没有加载到闪存中 但我想知道 objcopy 到底如何知道要在二进制文件中包含哪些部分以直接闪存到闪存中 以arm elf文件的以下reade
  • 特殊寄存器

    特殊寄存器 文章目录 前言 一 背景 二 2 1 2 2 总结 前言 前期疑问 STM32特殊寄存器到底是什么 特殊寄存器怎么查看和调试代码 本文目标 记录和理解特殊寄存器 一 背景 最近在看ucosIII文章是 里面提到特殊寄存器 这就进
  • 核心耦合内存在 STM32F4xx 上可执行吗?

    尝试从 STM32F429s CCM 运行代码 但每当我命中 CCM 中的第一条指令时 我总是会遇到硬故障 并且 IBUSERR 标志被设置 该指令有效且一致 STM32F4xx 是否可能不允许从 CCM 执行 数据访问效果良好 alios
  • STM32 Nucleo 上的上升沿中断多次触发

    我正在使用 STM32 NUCLEO F401RE 微控制器板 我有一个扬声器 经过编程 当向上 向下推操纵杆时 可以按设定的量改变频率 我的问题是 有时 通常 当向上 向下推动操纵杆时 频率会增加 减少多次 这意味着 ISR 正在执行多次
  • 从没有中断引脚并且在测量准备好之前需要一些时间的传感器读取数据的最佳方法

    我正在尝试将压力传感器 MS5803 14BA 与我的板 NUCLEO STM32L073RZ 连接 根据 第 3 页 压力传感器需要几毫秒才能准备好读取测量值 对于我的项目 我对需要大约 10 毫秒来转换原始数据的最高分辨率感兴趣 不幸的
  • Micriμm μC/OS-III RTOS 中的分配和释放

    我们使用 Micrium 的 C OS III RTOS 和 Renesas 的 RX62N 我们构建了一个必须动态分配和释放数据的系统 我们发现了功能malloc and free 与 RTOS 配合得不好 然而 RTOS 为此提供了一个
  • 小型 ARM 微控制器的 RTOS 内核之间的可量化差异 [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 有许多不同的 RTOS 可用于微控制器 我专门寻找支持 ARM Cortex M 处理器的 RTOS 另外 我对闭源解决方案不感兴趣 试图从网站
  • 当端点和 PMA 地址均更改时,CubeMX 生成的 USB HID 设备发送错误数据

    我正在调试我正在创建的复合设备的问题 并在新生成的仅 CubeMX 代码中重新创建了该问题 以使其更容易解决 我添加了少量代码main 让我发送 USB HID 鼠标点击 并在按下蓝色按钮时使 LED 闪烁 uint8 t click re

随机推荐

  • unity开发android游戏(一)搭建Unity安卓开发环境

    1 下载安装Java的JDK http www oracle com technetwork java javase downloads index html JDK中 包含JRE 如果是64位的系统 推荐安装64位的java 2 下载An
  • Maven本地仓库有jar包却提示找不到 / 生成.lastUpdated文件

    Maven本地仓库有jar包却提示找不到 生成 lastUpdated文件 Maven仓库 remote repositories文件的作用 存在的问题 使用Maven管理项目时 如果连不到远程仓库 但是明明本地仓库中有对应的jar包 此时
  • 解决AttributeError: module ‘cv2‘ has no attribute ‘CV_HAAR_SCALE_IMAGE‘

    解决AttributeError module cv2 has no attribute CV HAAR SCALE IMAGE 问题描述 代码 frontalFaces faceCascade detectMultiScale image
  • Array.fill()用法

    Arrays fill 用于快速填充数组 但是只适用于一维数组 若是想填充二维数组则需要循环 详细用法 Arrays fill int a from to int var int a 需要填充的数组 from 数组填充的起始位置 包括此位置
  • Python类的构造方法深入剖析:详解与案例分析

    在Python中 类是面向对象编程的重要概念之一 类是对象的蓝图 通过定义类可以创建具有相同属性和方法的多个对象 类中的构造方法 init 方法 在对象创建时被调用 用于初始化对象的属性 本文将深入剖析Python类的构造方法 并通过案例分
  • Java中JSON把引用相同的对象变为"$ref":问题的分析与解决

    Java中JSON把引用相同的对象变为 ref 问题的分析与解决 后台返回给前端的数据一般是JSON格式的 使用com alibaba fastjson时 在把后台的响应数据转化为JSON格式时 具有相同引用的对象会变成 r e f
  • Postman的使用教程

    一 Postman背景介绍 用户在开发或者调试网络程序或者是网页B S模式的程序的时候是需要一些方法来跟踪网页请求的 用户可以使用一些网络的监视工具比如著名的Firebug等网页调试工具 今天给大家介绍的这款网页调试工具不仅可以调试简单的c
  • 目录的作用

    根目录 通常不在这里存储文件 bin 可执行文件 ls cd sbin 可执行文件 boot 开机启动的文件 包括linux内核以及开机菜单与开机所需配置文件等 dev 设备文件 任何设备与接口设备都是以文件形式存在于这个目录的 root
  • 一些重要站点

    linux常用命令集 http linux chinaitlab com special linuxcom nmon工具 http pkgs repoforge org nmon 一些源码工具的下载 http code google com
  • jdbc操作Date

    作为一个开发者 使用jdbc是最基本的要求 但是jdbc对应操作date类型的数据和hibernate是不同的 hibernate不需要考虑date是util date还是sql date也不需要考虑存入的date是否存有时分秒了 建议 J
  • [Python

    目录 一 问题简介 二 解决方案 1 全局搜索sign 2 文件局部搜索 3 寻找目标函数 4 调用函数 5 补全JS代码 6 token的获取 三 Python代码 1 UI类 2 爬虫逻辑类 四 完整代码 JS代码 Python代码 一
  • 软件工程-第七章-实现

    软件工程 第七章 实现 7 实现 7 2 软件测试基础 7 2 3 测试方法 7 2 4 测试步骤 7 3 单元测试 7 3 2 代码审查 7 4 集成测试 7 4 1 自顶向下集成 7 4 2 自底向上集成 7 5 确认测试 7 5 3
  • 公众号开放,关注软件开发过程中的哪些坑

    一位十年码农的碎碎念 扫码关注获取更多精彩内容
  • 使用HEXO搭建个人博客时遇到的问题日志 PART.1

    我都没想到光连建站都能遇到这么多问题 1 解决 Failed to connect to github com port 443 connection timed out 当使用hexo d的时候 一直连接超时 大概率应该是vpn导致的 方
  • 成都计算机学校怎么样,成都计算机学校计算机专业怎么样?

    目前 根据最新数据显示 我国由于互联网的快速发展 使得计算机专业人才匮乏 需要的计算机人才数量将会越来越大 而根据老师的调查发现 从学校毕业的学生是比较吃香的 由于最近有很多学生在咨询到成都计算机学校学习计算机专业怎么样 所以老师在下方整理
  • 二叉树的非递归遍历算法 - 树结构

    遍历是树结构算法中的重要部分 前面发了有关递归遍历的内容 要知道 递归就是函数调用函数本身 运行起来就是函数嵌套函数 层层嵌套 所以函数调用 参数堆栈都是不小的开销 但是程序简单 然而 非递归即不断地对参数入栈 出栈 省去了函数层层展开 层
  • C#键盘输入方法(Input.GetKey()和Input.GetKeyUp())需要注意的一个问题(一个U3D初学者的总结)

    大家好 我用的是2017 1 1f1Personal版本 最近在复习蓄力发射时 按下空格键的时间越长 游戏对象Sphere发射的越远 发现这样一个问题 代码如下 using System Collections using System C
  • CSS 3D图片翻转 ——3D Flipping Effect (3D 翻转效果——

    效果 代码如下
  • 【好工具】 深度学习炼丹,你怎么能少了这款工具!

    欢迎来到 好工具 专栏 本次我们给介绍一款可以进行远程深度学习炼丹的工具 JupyterLab 及其配置流程 帮助读者在本地进行调试 Max 开发效率 作者 编辑 Leong 导言 不知道读者们有没有发现 如果你用 Anaconda 中的
  • 自己动手写RTOS:02-在M3内核上实现pendsvc

    自己动手写RTOS 自己动手写RTOS 01基础知识和理论部分 自己动手写RTOS 02 在M3内核上实现pendsvc 文章目录 自己动手写RTOS 一 M3内核的相关知识 1 1寄存器 1 2特殊寄存器 1 3堆栈 二 pendSVC实