FreeRTOS 底层架构与寄存器详解——Cortex-M 视角
FreeRTOS 底层架构与寄存器详解——Cortex-M 视角定位本笔记是 FreeRTOS内部机制详解——新工程师入门指南 的底层配套篇。目标是让你从寄存器 / 汇编 / 硬件机制的视角彻底搞清楚 FreeRTOS 是如何在 Cortex-M 上运行的。不了解这些你永远只是在用 FreeRTOS而不是懂 FreeRTOS。 学完以后强烈推荐阅读源码精析篇FreeRTOS_port.c源码逐行精析——ARM_CM3移植层逐行分析真实的 port.c 全程栈图目录一、Cortex-M 寄存器组全景二、两个栈指针MSP 与 PSP三、处理器工作模式Thread Mode vs Handler Mode四、异常与中断机制五、SysTick——心跳发生器六、PendSV——上下文切换的专用异常七、SVC——进入特权模式的门八、上下文切换汇编源码逐行解析九、任务栈初始化——xPortInitialiseStack 解析十、BASEPRI 寄存器——FreeRTOS 的中断屏蔽机制十一、内存屏障指令DSB / ISB / DMB十二、从上电到第一个任务运行的完整流程十三、关键寄存器速查表一、Cortex-M 寄存器组全景Cortex-M3/M4/M7 共有以下寄存器以 M4 为例通用寄存器低寄存器 ┌──────────┬───────────────────────────────────────────────────────┐ │ R0 │ 函数参数1 / 返回值 / 临时变量 │ │ R1 │ 函数参数2 / 临时变量 │ │ R2 │ 函数参数3 / 临时变量 │ │ R3 │ 函数参数4 / 临时变量 │ ├──────────┼───────────────────────────────────────────────────────┤ 通用寄存器高寄存器 │ R4 │ 被调用者保存callee-saved │ │ R5 │ 被调用者保存 │ │ R6 │ 被调用者保存 │ │ R7 │ 被调用者保存 │ │ R8 │ 被调用者保存 │ │ R9 │ 被调用者保存 │ │ R10 │ 被调用者保存 │ │ R11 │ 被调用者保存帧指针 FP可选 │ ├──────────┼───────────────────────────────────────────────────────┤ 特殊寄存器 │ R12(IP) │ 调用内部过程临时寄存器Intra-Procedure scratch │ │ R13(SP) │ 栈指针实际是 MSP 或 PSP 的别名见下文 │ │ R14(LR) │ 链接寄存器保存函数/异常的返回地址 │ │ R15(PC) │ 程序计数器指向下一条将执行的指令 │ ├──────────┼───────────────────────────────────────────────────────┤ 程序状态寄存器 │ xPSR │ 包含 APSR IPSR EPSR见下文展开 │ ├──────────┼───────────────────────────────────────────────────────┤ 特殊目的寄存器只能用 MSR/MRS 访问 │ PRIMASK │ 1屏蔽所有可屏蔽中断0不屏蔽 │ │ FAULTMASK│ 1屏蔽所有中断HardFault0不屏蔽 │ │ BASEPRI │ 优先级低于此值的中断被屏蔽FreeRTOS用这个 │ │ CONTROL │ 控制当前使用 MSP/PSP、特权/非特权见下文 │ └──────────┴───────────────────────────────────────────────────────┘ 浮点寄存器仅 M4/M7 带 FPU 时 │ S0~S31 │ 32个单精度浮点寄存器 │ │ FPSCR │ 浮点状态控制寄存器 │xPSR 展开图程序状态寄存器xPSR32位 31 30 29 28 27 26:25 24 23:20 19:16 15:10 9 8 7 6 5 4:0 ┌──┬──┬──┬──┬──┬─────┬──┬─────┬─────┬─────┬──┬──┬──┬──┬──┬────┐ │N │Z │C │V │Q │IT │T │保留 │GE │IT │保│E │A │I │F │ISR │ └──┴──┴──┴──┴──┴─────┴──┴─────┴─────┴─────┴──┴──┴──┴──┴──┴────┘ ↑ ↑ ↑ ↑ ↑ ↑ ↑ N Z C V Q GE(M4) ISR号(IPSR) (负)(零)(进)(溢)(饱和) 重点关注 • T bitbit24必须为1表示 Thumb 指令集模式Cortex-M 只有 Thumb → 任务初始化栈时xPSR 初始值必须设为 0x01000000T1 → 如果 T0 跳转到函数会立刻触发 UsageFault • ISR号bit0~80Thread Mode1~15系统异常16外部中断二、两个栈指针MSP 与 PSPCortex-M 有两个物理栈指针寄存器但 R13 同一时刻只用其中一个┌─────────────────────────────────────────────────────────┐ │ │ │ MSPMain Stack Pointer主栈指针 │ │ • 上电复位时默认使用 │ │ • 异常/中断处理函数Handler Mode使用 │ │ • FreeRTOS 调度器本身使用 │ │ • 地址从向量表第0个字0x00000000读取 │ │ │ │ PSPProcess Stack Pointer进程栈指针 │ │ • 任务Thread Mode运行时使用 │ │ • 每个 FreeRTOS 任务都有自己独立的 PSP │ │ • 上下文切换时FreeRTOS 改变 PSP 即切换任务 │ │ │ └─────────────────────────────────────────────────────────┘CONTROL 寄存器控制栈选择CONTROL 寄存器3位有效 bit1SPSEL 0 使用 MSP 1 使用 PSP任务运行时必须为1 bit0nPRIV 0 特权模式Thread Mode 1 非特权模式Thread Mode Handler Mode 永远是特权模式bit0 无效// FreeRTOS 启动第一个任务时prvStartFirstTask 汇编 // 会把 CONTROL.SPSEL 设为 1切换到 PSP // 读写 CONTROL 寄存器 __asm volatile (MRS R0, CONTROL); // 读 __asm volatile (MSR CONTROL, R0); // 写 __asm volatile (ISB); // 必须跟 ISB 保证立即生效内存布局全图RAM 地址STM32F4 为例 0x20020000 ─────────────┐ │ MSP 从这里向下增长 │ FreeRTOS 调度器 / 中断处理 ▼ 0x2001F000 MSP 使用区域 ... 0x20010000 ← configTOTAL_HEAP_SIZE 以下是 FreeRTOS heap │ │ heap 中动态分配 │ ┌── Task A TCB │ ├── Task A StackPSP 向下增长 │ ├── Task B TCB │ ├── Task B Stack │ └── 队列/信号量等对象 │ 0x20000000 ─────────────┘ RAM 起始.data / .bss 在低地址三、处理器工作模式Thread Mode vs Handler Mode┌─────────────────────────────────────────┐ │ Cortex-M 工作模式 │ ├────────────────────┬────────────────────┤ │ Thread Mode │ Handler Mode │ │ 线程模式 │ 处理器模式 │ ├────────────────────┼────────────────────┤ │ 正常代码运行时 │ 发生任何异常/中断 │ │ FreeRTOS 任务运行 │ ISR / SysTick / │ │ 使用 PSP │ PendSV / SVC 等 │ │ 可以是特权或非特权 │ 使用 MSP │ │ │ 永远是特权 │ └────────────────────┴────────────────────┘ 关键规则 1. 异常发生 → 自动进入 Handler Mode硬件完成 2. 异常返回 → 回到 Thread Mode执行 BX LRLR 是特殊的 EXC_RETURN 值四、异常与中断机制4.1 向量表Vector Table地址Flash 起始 0x00000000MSP 初始值从这里读取设置主栈指针 0x00000004Reset_Handler复位后跳转这里 0x00000008NMI_Handler 0x0000000CHardFault_Handler 0x00000010MemManage_Handler 0x00000014BusFault_Handler 0x00000018UsageFault_Handler 0x0000001C保留×4 0x0000002CSVC_Handler ← FreeRTOS 用启动第一个任务 0x00000030DebugMon_Handler 0x00000034保留 0x00000038PendSV_Handler ← FreeRTOS 用上下文切换 0x0000003CSysTick_Handler ← FreeRTOS 用时间基准 0x00000040外部中断0...4.2 异常发生时硬件自动做的事入栈当 CPU 响应异常时硬件自动将以下寄存器压入当前使用的栈任务运行中压 PSPHandler 嵌套则压 MSP异常发生前栈顶PSP │ ▼ 硬件自动压栈按顺序 ┌─────────┐ ← 新 PSP入栈后 │ xPSR │ 28含 T bit、中断号等 │ PC │ 24返回地址即被中断指令的下一条 │ LR │ 20R14保存调用者的返回地址 │ R12 │ 16 │ R3 │ 12 │ R2 │ 8 │ R1 │ 4 │ R0 │ 0 ← 新 PSP 指向这里 └─────────┘这 8 个寄存器叫做异常帧Exception Frame。FreeRTOS 还需要额外保存 R4~R11硬件不自动保存由软件PendSV_Handler完成。4.3 EXC_RETURN——异常返回魔法值异常响应时LR 被设置为特殊的EXC_RETURN值不是普通地址EXC_RETURN 可能的值Cortex-M4无FPU 0xFFFFFFF1返回 Handler Mode使用 MSP 0xFFFFFFF9返回 Thread Mode使用 MSP 0xFFFFFFFD返回 Thread Mode使用 PSP ← FreeRTOS 任务用这个 有 FPU 且使用了浮点的情况下 0xFFFFFFE1返回 Handler Mode使用 MSPFPU 状态恢复 0xFFFFFFE9返回 Thread Mode使用 MSPFPU 状态恢复 0xFFFFFFED返回 Thread Mode使用 PSPFPU 状态恢复执行BX LRLR EXC_RETURN时CPU 识别到高位全1知道这是异常返回而不是普通跳转自动从对应栈中弹出 xPSR/PC/LR/R12/R3/R2/R1/R0切换回对应模式Thread/Handler切换到对应栈MSP/PSP五、SysTick——心跳发生器SysTick 是 Cortex-M 内核自带的 24 位递减计数器是 FreeRTOS 的时间基准Tick来源。SysTick 寄存器基地址0xE000E010 偏移 0x00CTRL控制状态寄存器 bit0ENABLE 1启动计数0停止 bit1TICKINT 1计数到0时触发 SysTick 异常 bit2CLKSOURCE1使用处理器时钟0外部参考时钟 bit16COUNTFLAG每次计数到0该位置1读后自动清零 偏移 0x04LOAD重装载值寄存器 计数到0后重新从这个值开始倒计数 计算公式LOAD (CPU频率 / Tick频率) - 1 例168MHz / 1000Hz - 1 167999 偏移 0x08VAL当前计数值 读出当前计数值写任意值清零同时清 COUNTFLAG 偏移 0x0CCALIB校准值只读FreeRTOS 配置 SysTick// port.c 中的实现Cortex-M4 为例 void vPortSetupTimerInterrupt(void) { // 设置重装载值 portNVIC_SYSTICK_LOAD_REG (configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ) - 1UL; // 设置 SysTick 优先级为最低0xFF 255 portNVIC_SHPR3_REG | portNVIC_SYSTICK_PRI; // 清零计数器启动 SysTick允许中断 portNVIC_SYSTICK_CTRL_REG portNVIC_SYSTICK_CLK_BIT | // 使用内核时钟 portNVIC_SYSTICK_INT_BIT | // 允许中断 portNVIC_SYSTICK_ENABLE_BIT; // 启动 } // SysTick 中断处理port.c void xPortSysTickHandler(void) { // 进入临界区屏蔽 FreeRTOS 管理的中断 portDISABLE_INTERRUPTS(); { // 更新 Tick 计数检查是否有任务延时到期 if (xTaskIncrementTick() ! pdFALSE) { // 有更高优先级任务就绪触发 PendSV 进行上下文切换 portNVIC_INT_CTRL_REG portNVIC_PENDSVSET_BIT; } } portENABLE_INTERRUPTS(); }六、PendSV——上下文切换的专用异常6.1 为什么用 PendSV理论上在 SysTick ISR 里直接切换上下文也行但有个问题如果 SysTick 打断了一个更高优先级的中断ISR嵌套在那时切换任务就会乱套。解决方案把上下文切换延迟到所有中断都处理完再执行。PendSV 被设置为最低优先级这样它总是在所有 ISR 都退出后才运行。时间线 │ SysTick ISR │─── 触发 PendSV ───►│ │ 高优先级ISR │ 先执行这个 │ PendSV最低优先级等所有ISR结束 │ │ │ ↓ │ │ │ 上下文切换安全6.2 触发 PendSV// 在任何地方SysTick ISR / 任务调用 yield触发 PendSV // 向 ICSR 寄存器的 PENDSVSET 位写 1 *((volatile uint32_t *) 0xE000ED04) 0x10000000; // 宏定义 #define portNVIC_INT_CTRL_REG (*((volatile uint32_t *) 0xE000ED04)) #define portNVIC_PENDSVSET_BIT (1UL 28UL) portNVIC_INT_CTRL_REG portNVIC_PENDSVSET_BIT;6.3 NVIC 关键寄存器中断控制地址 寄存器 说明 0xE000E100 ISER[0] 中断使能寄存器写1使能写0无效 0xE000E180 ICER[0] 中断清除寄存器写1禁止写0无效 0xE000E200 ISPR[0] 中断悬挂寄存器写1挂起 0xE000E280 ICPR[0] 中断清除悬挂寄存器 0xE000E300 IABR[0] 中断活动状态寄存器只读 0xE000E400 IPR[0] 中断优先级寄存器每个字节对应一个中断 系统控制空间SCS 0xE000ED00 CPUID CPU型号只读 0xE000ED04 ICSR 中断控制状态寄存器触发 PendSV 用这个 0xE000ED08 VTOR 向量表偏移寄存器relocate 用 0xE000ED0C AIRCR 应用中断复位控制复位、大小端 0xE000ED18 SHPR1 系统处理优先级寄存器1MemManage/Bus/UsageFault 0xE000ED1C SHPR2 系统处理优先级寄存器2SVC 优先级在这里 0xE000ED20 SHPR3 系统处理优先级寄存器3SysTick/PendSV 优先级七、SVC——进入特权模式的门SVCSupervisor Call是一条软件中断指令用于从 Thread Mode 主动触发异常进入内核特权代码。SVC #0 ; 触发 SVC 异常参数是立即数0FreeRTOS 使用 SVC 来启动第一个任务// 启动调度器最后一步port.c __asm volatile ( ldr r0, 0xE000ED08 \n // VTOR 地址 ldr r0, [r0] \n // 读向量表地址 ldr r0, [r0] \n // 读向量表第0字 MSP初始值 msr msp, r0 \n // 重置 MSP清理启动代码用的栈 cpsie i \n // 全局开中断PRIMASK0 cpsie f \n // 开 FaultMask dsb \n // 数据同步屏障 isb \n // 指令同步屏障 svc 0 \n // 触发 SVC 异常 → vPortSVCHandler nop \n nop \n ); // SVC Handler启动第一个任务 void vPortSVCHandler(void) { __asm volatile ( ldr r3, pxCurrentTCBConst2 \n // r3 pxCurrentTCB ldr r1, [r3] \n // r1 pxCurrentTCB当前TCB指针 ldr r0, [r1] \n // r0 pxTopOfStack栈顶 ldmia r0!, {r4-r11, r14} \n // 从栈中恢复 R4-R11 和 LR(EXC_RETURN) msr psp, r0 \n // 设置 PSP 任务栈顶 isb \n mov r0, #0 \n msr basepri, r0 \n // 开启所有中断 bx r14 \n // 用 EXC_RETURN 返回切换到 PSP进入任务 ); }八、上下文切换汇编源码逐行解析这是 FreeRTOS 最核心的汇编代码位于port.c的PendSV_Handler以 Cortex-M4 无FPU 为例__attribute__(( naked )) void xPortPendSVHandler( void ) { __asm volatile ( mrs r0, psp \n // ① 读 PSP 到 R0当前任务的栈顶 isb \n // 指令同步屏障 ldr r3, pxCurrentTCBConst \n // ② R3 pxCurrentTCB指向TCB指针的指针 ldr r2, [r3] \n // R2 pxCurrentTCB当前任务TCB地址 tst r14, #0x10 \n // ③ 检查 EXC_RETURN bit4 it eq \n // 如果 bit40使用了FPU vstmdbeq r0!, {s16-s31} \n // 保存浮点寄存器 S16-S31硬件已自动保存S0-S15 stmdb r0!, {r4-r11, r14} \n // ④ 手动保存 R4-R11 和 LREXC_RETURN值到任务栈 // r0 向下移动完成后 r0 新的栈顶 str r0, [r2] \n // ⑤ 把新栈顶 r0 存回 TCB.pxTopOfStack // 至此当前任务的所有寄存器都保存好了 stmdb sp!, {r0, r3} \n // ⑥ 把 r0,r3 压入 MSP 保存临时保护 mov r0, %0 \n // R0 configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0 \n // ⑦ 屏蔽优先级低于阈值的中断进保护区 dsb \n isb \n bl vTaskSwitchContext \n // ⑧ 调用 C 函数选出下一个任务 // 执行后 pxCurrentTCB 已更新为下一个任务 mov r0, #0 \n msr basepri, r0 \n // ⑨ 恢复中断退保护区 ldmia sp!, {r0, r3} \n // 从 MSP 恢复 r0,r3 ldr r1, [r3] \n // ⑩ R1 pxCurrentTCB已是新任务的TCB ldr r0, [r1] \n // R0 新任务的 pxTopOfStack ldmia r0!, {r4-r11, r14} \n // ⑪ 从新任务栈中恢复 R4-R11 和 LR tst r14, #0x10 \n // 检查新任务是否用了FPU it eq \n vldmiaeq r0!, {s16-s31} \n // 如果用了恢复浮点寄存器 msr psp, r0 \n // ⑫ 更新 PSP 新任务的栈顶 isb \n bx r14 \n // ⑬ BX EXC_RETURN0xFFFFFFFD // → CPU 自动从 PSP 弹出 R0-R3,R12,LR,PC,xPSR // → 新任务从上次被打断的地方继续执行 .align 4 \n pxCurrentTCBConst: .word pxCurrentTCB \n ::i(configMAX_SYSCALL_INTERRUPT_PRIORITY) ); }完整栈操作流程图任务A被打断时PendSV 入口 任务A 的栈PSP 管理 高地址 ────────────────────────────── │ │ │ ... 任务A的局部变量 ... │ │ │ │ 【硬件自动压栈入异常时】 │ │ xPSR │ │ PC ← 任务A 被打断时的PC │ │ LR │ │ R12 │ │ R3 │ │ R2 │ │ R1 │ PSP──► │ R0 │ ← mrs r0, psp 读到这里 │ │ │ 【FreeRTOS 手动压栈①~④】 │ │ LREXC_RETURN0xFFFFFFFD│ │ R11 │ │ R10 │ │ R9 │ │ R8 │ │ R7 │ │ R6 │ │ R5 │ PSP──► │ R4 │ ← str r0, [r2] 保存到TCB 低地址 ────────────────────────────── 调用 vTaskSwitchContext() 后pxCurrentTCB → 任务B 任务B 的栈之前保存的 │ LR0xFFFFFFFD │ │ R11 ~ R4 │ ← ldmia r0!, {r4-r11, r14} PSP──► │ R0 │ ← msr psp, r0 │ R1 │ │ R2 │ │ R3 │ │ R12 │ │ LR任务B当时的LR │ │ PC任务B被打断的地址 │ ← bx r14 后CPU从这里继续执行 │ xPSR │ │ │九、任务栈初始化——xPortInitialiseStack 解析第一次运行一个任务时它的栈是空的。FreeRTOS 需要伪造一个被打断过的现场让 PendSV Handler 能正确恢复它。// port.c StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, // 栈顶高地址端 TaskFunction_t pxCode, // 任务函数地址 void *pvParameters // 任务参数 ) { // Cortex-M 是满递减栈Full Descending // 栈从高地址向低地址增长SP 指向最后一个有效数据 // 模拟硬件自动压栈从高到低 pxTopOfStack--; *pxTopOfStack portINITIAL_XPSR; // xPSR 0x01000000T bit1必须 pxTopOfStack--; *pxTopOfStack ((StackType_t) pxCode) portSTART_ADDRESS_MASK; // PC 任务函数地址bit00因为 Thumb 地址是偶数 pxTopOfStack--; *pxTopOfStack (StackType_t) portTASK_RETURN_ADDRESS; // LR prvTaskExitError如果任务意外返回调这里打印错误 pxTopOfStack--; *pxTopOfStack (StackType_t) 0; // R12 0 pxTopOfStack--; *pxTopOfStack (StackType_t) 0; // R3 0 pxTopOfStack--; *pxTopOfStack (StackType_t) 0; // R2 0 pxTopOfStack--; *pxTopOfStack (StackType_t) 0; // R1 0 pxTopOfStack--; *pxTopOfStack (StackType_t) pvParameters; // R0 任务参数第一个参数 // 模拟 FreeRTOS 手动压栈R4-R11LR pxTopOfStack--; *pxTopOfStack portINITIAL_EXC_RETURN; // LR 0xFFFFFFFDThread ModePSP pxTopOfStack - 8; // R4-R11 全部初始化为 0 // 返回新的栈顶存入 TCB.pxTopOfStack return pxTopOfStack; }初始化后的栈布局高地址 ──────────────────────────── │ 0x01000000 (xPSR) │ ← T bit 1 │ 任务函数地址 (PC) │ ← 任务第一次运行从这里开始 │ prvTaskExitError (LR) │ ← 任务意外 return 时的保护 │ 0x00000000 (R12) │ │ 0x00000000 (R3) │ │ 0x00000000 (R2) │ │ 0x00000000 (R1) │ │ pvParameters (R0) │ ← 任务参数通过 R0 传入 ├──────────────────────────┤ │ 0xFFFFFFFD (LR) │ ← EXC_RETURN告诉CPU用PSP │ 0x00000000 (R11) │ │ 0x00000000 (R10) │ │ 0x00000000 (R9) │ │ 0x00000000 (R8) │ │ 0x00000000 (R7) │ │ 0x00000000 (R6) │ │ 0x00000000 (R5) │ pxTopOfStack ─►│ 0x00000000 (R4) │ ← TCB.pxTopOfStack 指向这里 低地址 ────────────────────────────十、BASEPRI 寄存器——FreeRTOS 的中断屏蔽机制FreeRTOS不用PRIMASK 来屏蔽中断那样连时间戳级的中断也会丢失而是用BASEPRI。BASEPRI 工作原理 BASEPRI 0 不屏蔽任何中断正常运行 BASEPRI N 优先级数值 N 的中断被屏蔽 优先级数值 N 的中断仍然响应 例STM32 使用4位优先级0最高15最低 BASEPRI 0x50即十进制的 80对应4位优先级5 优先级 5,6,7...15 → 被屏蔽包括 FreeRTOS 相关中断 优先级 0,1,2,3,4 → 不受影响高实时性中断// FreeRTOS 的临界区实现 #define portDISABLE_INTERRUPTS() \ __asm volatile ( \ mov r0, %0 \n \ msr basepri, r0 \n \ dsb \n \ isb \n \ :: i(configMAX_SYSCALL_INTERRUPT_PRIORITY) : memory \ ) #define portENABLE_INTERRUPTS() \ __asm volatile ( \ mov r0, #0 \n \ msr basepri, r0 \n \ :: : memory \ ) // 注意configMAX_SYSCALL_INTERRUPT_PRIORITY 在寄存器中要左移 // STM324位优先级存在高4位 // 用户配置值5 → 实际写入5 (8-4) 5 4 0x50嵌套临界区Nesting// FreeRTOS 用计数器支持嵌套 void taskENTER_CRITICAL(void) { portDISABLE_INTERRUPTS(); uxCriticalNesting; // 计数1 } void taskEXIT_CRITICAL(void) { uxCriticalNesting--; // 计数-1 if (uxCriticalNesting 0) { // 只有计数为0才真正开中断 portENABLE_INTERRUPTS(); } }十一、内存屏障指令DSB / ISB / DMB这三条指令在 FreeRTOS 汇编中频繁出现必须理解┌──────────┬────────────────────────────────────────────────────────┐ │ 指令 │ 作用 │ ├──────────┼────────────────────────────────────────────────────────┤ │ DSB │ 数据同步屏障Data Synchronization Barrier │ │ │ 确保在 DSB 之前的所有内存访问读/写完成后 │ │ │ 才执行 DSB 之后的指令 │ │ │ 用途写完寄存器后确保硬件已收到写入如写NVIC │ ├──────────┼────────────────────────────────────────────────────────┤ │ ISB │ 指令同步屏障Instruction Synchronization Barrier │ │ │ 冲刷流水线确保 ISB 之后的指令重新从内存取指 │ │ │ 用途修改 CONTROL、BASEPRI 等后确保新设置立即生效 │ ├──────────┼────────────────────────────────────────────────────────┤ │ DMB │ 数据内存屏障Data Memory Barrier │ │ │ 确保屏障前的内存访问在屏障后的访问之前完成 │ │ │ 用途多核或DMA场景保证数据顺序M4单核少用 │ └──────────┴────────────────────────────────────────────────────────┘; 典型使用场景修改 BASEPRI 后立即生效 MOV R0, #0x50 MSR BASEPRI, R0 DSB ; 等待写入完成 ISB ; 冲刷流水线确保后续指令看到新的 BASEPRI 值十二、从上电到第一个任务运行的完整流程上电/复位 │ ▼ ① 硬件从向量表 [0] 读取 MSP 初始值设置 MSP 硬件从向量表 [1] 读取 Reset_Handler 地址跳转 │ ▼ ② Reset_Handlerstartup_stm32f4xx.s • 复制 .data 段从 Flash 到 RAM • 清零 .bss 段 • 初始化 C 静态对象 • 跳转到 main() │ ▼ ③ main() • HAL_Init() / SystemClock_Config() 等板级初始化 • xTaskCreate(task1, ...) 创建任务 → pvPortMalloc(TCB大小) 分配 TCB → pvPortMalloc(栈大小) 分配栈 → xPortInitialiseStack() 伪造初始栈帧 → 将任务加入就绪列表 • vTaskStartScheduler() → 创建 Idle 任务优先级0永远就绪 → 创建 Timer Service 任务如果启用软件定时器 → xPortStartScheduler() → 配置 PendSV 和 SysTick 为最低优先级 → 配置并启动 SysTick开始产生 Tick 中断 → prvStartFirstTask()汇编 → 重置 MSP清理启动代码用的栈 → 开中断cpsie i → SVC #0 触发 SVC 异常 │ ▼ ④ vPortSVCHandler汇编 • 从 pxCurrentTCB优先级最高的就绪任务读取栈顶 • ldmia 恢复 R4-R11 和 LRLR 0xFFFFFFFD • msr psp, r0设置 PSP 第一个任务的栈顶 • bx r14EXC_RETURN 0xFFFFFFFD → 硬件从 PSP 弹出 R0-R3/R12/LR/PC/xPSR → 进入 Thread Mode使用 PSP → 跳转到任务函数PC 任务入口地址 │ ▼ ⑤ 第一个任务开始运行 R0 pvParameters任务参数 处于 Thread Mode PSP 特权模式默认 │一段时间后 ▼ ⑥ SysTick 中断触发 → xTaskIncrementTick() → 如有任务到期触发 PendSV写 ICSR.PENDSVSET │ ▼ ⑦ SysTick 返回后PendSV 立即响应最低优先级 → xPortPendSVHandler 汇编 → 保存当前任务 R4-R11 到 PSP 栈 → 调用 vTaskSwitchContext() 更新 pxCurrentTCB → 恢复新任务 R4-R11 和 LR → 更新 PSP 新任务栈顶 → bx r14 → 新任务运行 从此 ⑥→⑦ 每 Tick 重复十三、关键寄存器速查表Cortex-M 内核寄存器编程用寄存器地址/访问方式FreeRTOS 用途PSPMSR/MRS psp, rx任务栈指针切换任务时修改MSPMSR/MRS msp, rx中断栈指针启动时重置CONTROLMSR/MRS control, rxbit11 切换到 PSPBASEPRIMSR/MRS basepri, rx临界区中断屏蔽PRIMASKCPSID i / CPSIE i全局开关中断NVIC / SCB 寄存器配置用寄存器地址FreeRTOS 用途ICSR0xE000ED04bit28PENDSVSET触发上下文切换SHPR20xE000ED1Cbits[31:24]SVC 优先级SHPR30xE000ED20bits[31:24]SysTickbits[23:16]PendSV 优先级SYSTICK_CTRL0xE000E010SysTick 控制启动、时钟源、中断SYSTICK_LOAD0xE000E014SysTick 重装载值决定 Tick 频率SYSTICK_VAL0xE000E018SysTick 当前计数值FreeRTOSConfig.h 与寄存器的对应关系// configMAX_SYSCALL_INTERRUPT_PRIORITY // ↕ 对应 BASEPRI 写入值 // STM32F44位优先级高4位有效 // 用户填写5 // 实际写入5 (8-4) 0x50 // 含义屏蔽优先级数值5~15放行0~4 // configTICK_RATE_HZ 1000 // ↕ 对应 SYSTICK_LOAD // LOAD HCLK / 1000 - 1 // STM32F407 168MHz167999 // configKERNEL_INTERRUPT_PRIORITY // ↕ SysTick 和 PendSV 的优先级写入 SHPR3 // 通常设为最低0xFFSTM32有效高4位0xF15延伸阅读资料内容ARMv7-M Architecture Reference ManualCortex-M 寄存器/异常机制权威文档Cortex-M3/M4 Devices Generic User Guide寄存器地址、编程手册FreeRTOS 源码portable/GCC/ARM_CM4F/port.c上下文切换汇编原文《ARM Cortex-M3 权威指南》Joseph Yiu中文入门经典本笔记配套FreeRTOS内部机制详解——新工程师入门指南 | 嵌入式架构核心知识——代码重定位、异常中断与处理器模式创建时间2026-06-08 | 分类嵌入式/RTOS/底层架构