Cortex-M3 堆栈指针寄存器分类、专用汇编指令 C语言完整示例自学《嵌入式系统原理及应用——基于ARM Cortex-M3内核的STM32F103系列微操控制器》-王益涵等著学习疑问点和笔记一、堆栈指针寄存器分类SPCortex-M3 拥有两个独立硬件堆栈指针硬件自动切换不可同时生效MSP主堆栈指针 Main Stack Pointer用途复位默认堆栈、中断服务程序、内核异常、操作系统内核调度、普通主函数全局栈复位上电后CPU 自动使用 MSP所有异常/中断触发时硬件强制切到 MSP中断返回后切回原SP。PSP进程堆栈指针 Process Stack Pointer用途仅用于用户主程序线程任务OS 多任务场景常用只有处于线程模式非特权/特权时代码手动切换才会启用 PSP中断触发时硬件自动切回 MSP中断结束自动恢复 PSP。配套两个运行模式决定用哪根SP线程模式 Thread Mode普通主循环/业务代码可手动切换 MSP/PSP处理模式 Handler Mode中断、HardFault、SysTick 等异常强制只能使用 MSP无法使用 PSP。寄存器访问控制CONTROL 寄存器 bit1SPSEL控制当前SPSPSEL0当前使用 MSPSPSEL1当前使用 PSP二、堆栈专用汇编指令C编译器底层自动调用也可内嵌汇编1. 堆栈数据操作指令指令作用PUSH {R0-R3,LR}压栈把寄存器存入堆栈栈地址自减满栈递减M3栈规则POP {R0-R3,PC}出栈从堆栈恢复寄存器栈地址自增2. SP 读写/切换专用指令指令功能MRS R0, MSP读取当前主堆栈指针到通用寄存器MRS R0, PSP读取进程堆栈指针到通用寄存器MSR MSP, R0写入设置主堆栈指针MSR PSP, R0写入设置进程堆栈指针MRS R0, CONTROL读取控制寄存器获取SPSEL位MSR CONTROL, R0修改CONTROL切换MSP/PSPM3堆栈规则满递减堆栈 Full Descending栈从高地址向低地址增长SP 永远指向最后一个入栈有效数据。入栈SP SP - 4 → 存入数据出栈取出数据 → SP SP 4。三、C语言实操示例基于STM32 Cortex-M3示例1默认场景——全程只用MSP无OS裸机项目上电后默认使用MSPmain函数、中断全部共用MSP。#includestm32f10x.h// 全局数组存放局部变量栈空间在MSP指向内存voidTest_MSP_Only(void){// 局部变量全部压入MSP主堆栈uint32_ta10;uint32_tbuf[128];// 占用大量栈空间走MSPfor(uint32_ti0;i128;i){buf[i]ai;}}// 中断服务函数硬件自动强制使用MSP无法切换PSPvoidEXTI0_IRQHandler(void){uint32_ttemp;// 临时变量存MSP栈tempGPIOA-IDR;}intmain(void){Test_MSP_Only();while(1);}底层行为说明上电启动文件startup_stm32f10x.s预先设置MSP 0x20005000SRAM高地址main 线程模式默认SPSEL0使用MSP进入中断自动切Handler模式强制MSP中断退出恢复线程模式MSP。示例2裸机C代码内嵌汇编手动切换 MSP / PSP适用于简单多任务、裸机分层业务手动分离业务栈与中断栈。#includestm32f10x.h// 自定义进程栈内存放在全局RAM不占用MSP空间uint32_tProcessStack[256]__attribute__((aligned(8)));#definePSP_STACK_TOP((uint32_t)ProcessStacksizeof(ProcessStack))voidSwitch_To_PSP(void){__asmvolatile(// 1. 设置PSP栈顶LDR R0, %0\nMSR PSP, R0\n// 2. 修改CONTROL寄存器SPSEL1切换到PSPMRS R0, CONTROL\nORR R0, R0, #0x02\n// bit1 SPSEL1MSR CONTROL, R0\n// 3. 同步流水线避免指令乱序ISB\n::i(PSP_STACK_TOP));}voidSwitch_To_MSP(void){__asmvolatile(MRS R0, CONTROL\nBIC R0, R0, #0x02\n// 清零SPSEL切回MSPMSR CONTROL, R0\nISB\n);}// 业务函数运行在PSP进程栈voidBusiness_Task(void){// 局部变量全部存PSP堆栈不占用中断MSPuint32_tdata[64];for(uint32_ti0;i64;i){data[i]i*2;}}intmain(void){// 切换到PSP进程堆栈Switch_To_PSP();// 以下代码所有局部变量使用PSPBusiness_Task();while(1){// 主线程持续使用PSP}}关键特性Business_Task局部变量分配在PSP自定义栈中断使用独立MSP互不挤占栈空间一旦触发中断硬件自动切回MSP中断内无法读写PSP中断返回后自动切回PSP继续运行主线程。示例3读取当前MSP/PSP栈地址C内嵌汇编用于栈溢出检测、调试打印栈使用量#includestdio.huint32_tGet_MSP_Value(void){uint32_tmsp_val;__asmvolatile(MRS %0, MSP:r(msp_val));returnmsp_val;}uint32_tGet_PSP_Value(void){uint32_tpsp_val;__asmvolatile(MRS %0, PSP:r(psp_val));returnpsp_val;}intmain(void){uint32_tmspGet_MSP_Value();uint32_tpspGet_PSP_Value();printf(MSP 0x%08X\r\n,msp);printf(PSP 0x%08X\r\n,psp);Switch_To_PSP();// 切换后再次读取mspGet_MSP_Value();pspGet_PSP_Value();printf(After switch PSP, MSP0x%08X, PSP0x%08X\r\n);while(1);}示例4中断栈溢出风险对比体现双SP设计意义场景1只用MSP单栈缺陷主函数大数组占用绝大多数栈中断触发时MSP空间不足 →栈溢出HardFault死机voidmain(void){uint32_tbig_buf[512];// 大量占用MSP栈while(1){// 中断进来共用MSP剩余栈极小极易溢出}}场景2MSPPSP分离双栈隔离解决溢出PSP主线程大缓存、业务局部变量MSP仅中断、异常使用栈空间预留充足互不干扰。四、核心总结两个SP硬件寄存器MSP复位默认、中断/异常强制使用PSP仅线程模式业务代码手动切换使用切换核心逻辑修改CONTROL寄存器bit1(SPSEL)搭配MSR/MRS专用SP汇编指令C语言层面普通裸机程序默认只用MSP多任务/大栈业务通过内嵌汇编切换PSP实现双栈隔离硬件强制规则进入中断Handler模式无视SPSEL自动切MSP中断内无法使用PSP。