ARM Cortex-M4启用MPU后切换至非特权模式触发硬故障求助
Cortex-M4 MPU启用后切换非特权模式触发硬故障(MUNSTKERR+IACCVIOL)
问题描述
我在ARM Cortex-M4设备上配置MPU,此前设备未启用MPU,全程运行在特权模式。启用MPU后切换至非特权模式时,触发硬故障,CFSR寄存器中MUNSTKERR和IACCVIOL位被置位,故障地址指向RAM中的大型静态uint8数组(0x20001900)。
测试验证
- 特权模式下单独启用MPU,工作正常
- 未启用MPU时,特权/非特权模式切换正常
- 先切非特权模式再通过SVC启用MPU,仍触发故障,仅IACCVIOL标志消失
相关代码
MPU初始化函数
#define MPU_RBAR_ADDR_Pos 5U /*!< MPU RBAR: ADDR Position */ #define MPU_RBAR_ADDR_Msk (0x7FFFFFFUL << MPU_RBAR_ADDR_Pos) /*!< MPU RBAR: ADDR Mask */ #define MPU_RBAR_VALID_Pos 4U /*!< MPU RBAR: VALID Position */ #define MPU_RBAR_VALID_Msk (1UL << MPU_RBAR_VALID_Pos) /*!< MPU RBAR: VALID Mask */ #define MPU_RBAR_REGION_Pos 0U /*!< MPU RBAR: REGION Position */ #define MPU_RBAR_REGION_Msk (0xFUL /*<< MPU_RBAR_REGION_Pos*/) /*!< MPU RBAR: REGION Mask */ #define MPU_AP_NOACCESS 0x0 #define MPU_AP_PRIVONLY 0x1 #define MPU_AP_USERREADONLY 0x2 #define MPU_AP_FULLACCESS 0x3 #define MPU_AP_READONLY 0x6 #define MPU_RASR_AP_NOACCESS (MPU_AP_NOACCESS << MPU_RASR_AP_Pos) #define MPU_RASR_AP_PRIVONLY (MPU_AP_PRIVONLY << MPU_RASR_AP_Pos) #define MPU_RASR_AP_USERREADONLY (MPU_AP_USERREADONLY << MPU_RASR_AP_Pos) #define MPU_RASR_AP_FULLACCESS (MPU_AP_FULLACCESS << MPU_RASR_AP_Pos) #define MPU_RASR_AP_READONLY (MPU_AP_READONLY << MPU_RASR_AP_Pos) #define MPU_RASR_XN_Pos 28U /*!< MPU RASR: ATTRS.XN Position */ #define MPU_RASR_XN_Msk (1UL << MPU_RASR_XN_Pos) /*!< MPU RASR: ATTRS.XN Mask */ #define MPU_RASR_AP_Pos 24U /*!< MPU RASR: ATTRS.AP Position */ #define MPU_RASR_AP_Msk (0x7UL << MPU_RASR_AP_Pos) /*!< MPU RASR: ATTRS.AP Mask */ #define MPU_RASR_TEX_Pos 19U /*!< MPU RASR: ATTRS.TEX Position */ #define MPU_RASR_TEX_Msk (0x7UL << MPU_RASR_TEX_Pos) /*!< MPU RASR: ATTRS.TEX Mask */ #define MPU_RASR_S_Pos 18U /*!< MPU RASR: ATTRS.S Position */ #define MPU_RASR_S_Msk (1UL << MPU_RASR_S_Pos) /*!< MPU RASR: ATTRS.S Mask */ #define MPU_RASR_C_Pos 17U /*!< MPU RASR: ATTRS.C Position */ #define MPU_RASR_C_Msk (1UL << MPU_RASR_C_Pos) /*!< MPU RASR: ATTRS.C Mask */ #define MPU_RASR_B_Pos 16U /*!< MPU RASR: ATTRS.B Position */ #define MPU_RASR_B_Msk (1UL << MPU_RASR_B_Pos) /*!< MPU RASR: ATTRS.B Mask */ #define MPU_RASR_SRD_Pos 8U /*!< MPU RASR: Sub-Region Disable Position */ #define MPU_RASR_SRD_Msk (0xFFUL << MPU_RASR_SRD_Pos) /*!< MPU RASR: Sub-Region Disable Mask */ #define MPU_SIZE_64BYTES 0x05 #define MPU_RASR_SIZE_64BYTES (MPU_SIZE_64BYTES << MPU_RASR_SIZE_Pos) #define MPU_RASR_ENABLE_TRUE 0x1 #define MPU_RASR_ENABLE_FALSE 0x0 #define MPU_CTRL_PRIVDEFENA_Pos 2U /*!< MPU CTRL: PRIVDEFENA Position */ #define MPU_CTRL_PRIVDEFENA_Msk (1UL << MPU_CTRL_PRIVDEFENA_Pos) /*!< MPU CTRL: PRIVDEFENA Mask */ #define MPU_CTRL_ENABLE_Pos 0U /*!< MPU CTRL: ENABLE Position */ #define MPU_CTRL_ENABLE_Msk (1UL /*<< MPU_CTRL_ENABLE_Pos*/) /*!< MPU CTRL: ENABLE Mask */ void MPU_Registers_Init(uint32_t addr) { MPU_Type *MpuReg = MPU; uint32_t base_addr = addr; uint32_t region_number = 0x0; // from 0~7 8 region available MPU->RBAR |= base_addr & MPU_RBAR_ADDR_Msk; MPU->RBAR |= ( 0x0u << MPU_RBAR_VALID_Pos) & MPU_RBAR_VALID_Msk; MPU->RBAR |= ( region_number << MPU_RBAR_REGION_Pos) & MPU_RBAR_REGION_Msk; uint32_t xn = 0x1u; // instruction fetches disabled uint32_t tex = 0x000u; uint32_t c = 0x1u; uint32_t b = 0x0u; // no write-back uint32_t s = 0x1u; // shareable uint32_t srd = 0xFF; // disable Subregion MPU->RASR |= (xn << MPU_RASR_XN_Pos) & MPU_RASR_XN_Msk; MPU->RASR |= MPU_RASR_AP_USERREADONLY; //p:RW up:RO MPU->RASR |= (tex << MPU_RASR_TEX_Pos) & MPU_RASR_TEX_Msk; MPU->RASR |= (s << MPU_RASR_S_Pos) & MPU_RASR_S_Msk; MPU->RASR |= (c << MPU_RASR_C_Pos) & MPU_RASR_C_Msk; MPU->RASR |= (b << MPU_RASR_B_Pos) & MPU_RASR_B_Msk; MPU->RASR |= (srd << MPU_RASR_SRD_Pos) & MPU_RASR_SRD_Msk; MPU->RASR |= MPU_RASR_SIZE_64BYTES; MPU->RASR |= MPU_RASR_ENABLE_TRUE; MPU->CTRL |= (0x01u << MPU_CTRL_PRIVDEFENA_Pos) & MPU_CTRL_PRIVDEFENA_Msk; MPU->CTRL |= (0x01u << MPU_CTRL_ENABLE_Pos) & MPU_CTRL_ENABLE_Msk; }
非特权模式切换代码
uint32_t RegControl = __get_CONTROL(); __set_CONTROL( RegControl | CONTROL_nPRIV_Msk );
故障原因与修复方案
核心原因
- MUNSTKERR:切换非特权模式后,CPU使用的栈指针(默认MSP)所在内存区域未配置非特权访问权限,导致栈操作被MPU拦截。
- IACCVIOL:代码执行区域未配置非特权模式下的指令获取权限,或MPU配置误将代码区域标记为XN(禁止指令获取)。
- 原有MPU配置无效:仅配置了一个64字节区域,但设置
SRD=0xFF(禁用所有子区域),等于该区域未启用;且开启PRIVDEFENA仅对特权模式生效,非特权模式无默认访问权限。
修复步骤
1. 配置栈区域的MPU权限
确保非特权模式能访问栈内存:
// 配置栈区域示例(假设栈起始地址0x20000000,大小1KB) void MPU_Config_Stack(void) { // 先关闭MPU避免配置过程触发异常 MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk; // 配置RBAR:区域1,栈基地址对齐到32字节边界 MPU->RBAR = (0x20000000 & MPU_RBAR_ADDR_Msk) | MPU_RBAR_VALID_Msk | 1; // 配置RASR:全权限访问,1KB大小,启用区域 MPU->RASR = MPU_RASR_AP_FULLACCESS | (9 << MPU_RASR_SIZE_Pos) | // 2^(9+1)=1024字节=1KB MPU_RASR_ENABLE_TRUE; // 重新启用MPU MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk; }
2. 配置代码区域的MPU权限
确保非特权模式能执行Flash中的代码:
// 配置Flash代码区域示例(起始地址0x08000000,大小64KB) void MPU_Config_Code(void) { MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk; MPU->RBAR = (0x08000000 & MPU_RBAR_ADDR_Msk) | MPU_RASR_VALID_Msk | 2; MPU->RASR = (0 << MPU_RASR_XN_Pos) | // 允许指令获取 MPU_RASR_AP_FULLACCESS | (15 << MPU_RASR_SIZE_Pos) | // 2^(15+1)=65536字节=64KB MPU_RASR_ENABLE_TRUE; MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk; }
3. 修复原有MPU初始化函数
将寄存器赋值方式从|=改为直接赋值,避免残留旧值;同时启用子区域:
void MPU_Registers_Init(uint32_t addr) { MPU_Type *MpuReg = MPU; uint32_t base_addr = addr; uint32_t region_number = 0x0; // 先关闭MPU MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk; // 直接赋值RBAR,避免旧值干扰 MPU->RBAR = (base_addr & MPU_RBAR_ADDR_Msk) | (0x0u << MPU_RBAR_VALID_Pos) | (region_number << MPU_RBAR_REGION_Pos); // 构建RASR完整值 uint32_t rasr_val = 0; rasr_val |= (0x1u << MPU_RASR_XN_Pos); // 禁止指令获取(适合数据区域) rasr_val |= MPU_RASR_AP_USERREADONLY; // 特权RW,非特权RO rasr_val |= (0x0 << MPU_RASR_TEX_Pos); rasr_val |= (0x1u << MPU_RASR_S_Pos); rasr_val |= (0x1u << MPU_RASR_C_Pos); rasr_val |= (0x0u << MPU_RASR_B_Pos); rasr_val |= (0x0 << MPU_RASR_SRD_Pos); // 启用所有子区域 rasr_val |= MPU_RASR_SIZE_64BYTES; rasr_val |= MPU_RASR_ENABLE_TRUE; MPU->RASR = rasr_val; // 开启特权默认区域并启用MPU MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk; }
4. 规范非特权模式切换流程
切换前确保栈指针配置正确,添加指令同步屏障:
// 示例:使用PSP作为非特权模式栈指针 __set_PSP((uint32_t)&_estack); // _estack为链接脚本中定义的栈顶符号 uint32_t RegControl = __get_CONTROL(); RegControl |= CONTROL_SPSEL_Msk; // 选择PSP作为当前栈指针 RegControl |= CONTROL_nPRIV_Msk; __set_CONTROL(RegControl); __ISB(); // 指令同步屏障,确保控制寄存器修改生效
关键注意事项
- 非特权模式下,所有需要访问的内存区域(栈、代码、数据、外设)必须显式配置MPU权限,
PRIVDEFENA仅对特权模式生效。 - 配置MPU时建议先关闭MPU,完成配置后再开启,避免配置过程中触发异常。
- SRD位是8位掩码,每一位对应一个子区域,
0xFF表示全部禁用,需根据需求设置(0x00启用所有子区域)。
内容的提问来源于stack exchange,提问作者Xiao Wang
相关产品推荐
相关产品推荐

