ARM学习(42)CortexM3/M4 MPU配置

笔者之前学习过CortexR5的MPU配置,现在学习一下CortexM3/M4 MPU配置

1、背景介绍

笔者在工作中遇到NXP MPU在访问异常地址时,就会出现总线挂死,所以需要MPU抓住异常,就需要配置MPU。具体背景情况可以参考ARM学习(41)NXP MCU总线挂死,CPU could not be halted以及无法连接Jink。笔者之前还研究了Cortex-R5的MPU配置

比如经常出现如下场景:

NXP的MCU使用的是LPC64016系列,是有配置MPU的。功能特点如下:

然后笔者又搜索了一下ST的芯片,主要针对F1和F4系列,F1大部分MCU没有配置MPU,F4基本都配置了MPU。具体得可以去下载ST的MCU手册,ST官网MCU系列STMCU文档下载地址

STM32F1系列只有STM32F10xxF、10xxG的才有MPU,比如101xF、103xF或者105xF系列,这些系列有个共同的特点就是Flash大,从768K到1MB。

STM32F4基本都配置MPU,其属于中高端产品。

STM32的产品 性能以及内核架构如下图所示。

2、MPU配置介绍

CortexM3、M4的MPU配置主要是几个寄存器的配置,可以配置MPU的region、访问权限和地址大小等属性。

寄存器主要有如下几个:

寄存器名字 访问权限 地址 说明
MPU_Type RO(只读) 0xE000ED90 说明MPU的region个数,默认是8,不可修改
MPU_CTRL RW(读写) 0xE000ED94 主要是使能MPU
MPU_RNR RW(读写) 0xE000ED98 选择使能的MPU region,可选
MPU_RBAR RW(读写) 0xE000ED9C MPU 属性以及size配置
MPU_RASR RW(读写) 0xE000EDA0 MPU保护的基地址配置

接下来具体看一下其配置信息。

2.1 MPU Type

只读寄存器,中间8位有效,可以读出数据来。

实际测试如下:

c 复制代码
printf("mpu region=%d\r\n",(MPU->TYPE >>8)&0xff);

2.2 MPU CTRL

可读可写寄存器,

  • 背景region的作用,如果不打开,默认都不可访问,可以少一个region
  • NMI以及hardfault中断对mpu的操作
  • 使能MPU的操作

    笔者来测试一下BIT2,不打开背景region,然后把region 0的4G区域关闭,然后使能MPU,代码如下:
c 复制代码
    /* Disable MPU */
    ARM_MPU_Disable();
	
	/**省略保护区域配置**/
	
     /* Enable MPU */
    ARM_MPU_Enable(0);

异常代码还一样

c 复制代码
    int* test_data_ptr = ( int*)0xF0000000;
    *test_data_ptr = 0x1234;
    printf("*test_data_ptr=%d\r\n",*test_data_ptr);

测试结果如下:可以看到异常已经抓到,数据地址是0xF0000000,代码地址10000348A,可以在trace32上面看到,说明背景region起作用了,保护了非法地址。

再来看一下,打开region,使得可以全部可以访问,但是异常地址没有保护,代码如下:

c 复制代码
    /* Disable MPU */
    ARM_MPU_Disable();
	
	/**省略保护区域配置**/

     /* Enable MPU */
    ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_HFNMIENA_Msk);

测试结果如下,总线直接挂死,然后没有任何反应。

结论:

  • 如果是裸机系统,该BIT位可以作为一个背景region,其他region可以保护更多的区域,相当于增加了一个全局region的处理。
  • 如果有操作系统的情况下,可以限制用户对特殊代码的访问。

2.3 MPU RNR

选择对应的region,进行属性和地址配置,往往不用这个寄存器,通过其他方式处理。

2.4 MPU RASR

MPU属性配置寄存器,配置权限访问,以及cacheable,共享等属性。

主要是两个属性:

  • 一个是访问属性,如上图所示
  • 另外一个是cache等属性,如下表所示
TEX C B S 存储器类型 描述说明
000 0 0 1 严格顺序 严格顺序,总是共享
000 0 1 1 设备 共享的设备
000 1 0 0/1 普通 片外或者片内写通cache,不写分配
000 1 1 0/1 普通 片外或者片内写回cache,不写分配
001 0 0 0/1 普通 None-Cacheable
001 0 1 0/1 N/A N/A
001 1 0 0/1 N/A N/A
001 1 1 0/1 普通 片外或者片内写回cache,读写分配
010 1 x 0/1 设备 不共享的设备
010 0 1 0/1 N/A N/A
010 1 x 0/1 N/A N/A
1BB A A 0/1 N/A Cache Memory,BB是片外,AA是片内
AA and BB Cache类型
00 None-Cachelable(没有cache)
01 write back,write and read allocate(写回,读写分配cache)
10 write through,no write allocate(写通,不写分配)
11 write back,no write allocate(写回,不写分配)

再来看一下一般的设备如何配置

Type memory Type 配置信息
ROM/Flash/ Normal memory TEX=0,C=1,B=0,S=0,没有share,写通cache
01 Normal memory TEX=0,C=1,B=0,S=1,共享,写通cache
10 Normal memory TEX=0,C=1,B=1,S=1,共享,写回cache
11 Device TEX=0,C=0,B=1,S=1,共享Devices

2.5 MPU RBAR

可以通过region字段覆盖 RNR寄存器的region配置,所以减少一个寄存器的操作。

  • 基地址要以容量的大小进行对齐(即基地址可以整除容量),这样设计会让硬件设计简单。
  • 容量的大小最低从32开始,都是2的次幂,用编码表示其大小。 容量=1<< (容量编码+1)
c 复制代码
#define ARM_MPU_REGION_SIZE_32B      ((uint8_t)0x04U) ///!< MPU Region Size 32 Bytes
#define ARM_MPU_REGION_SIZE_64B      ((uint8_t)0x05U) ///!< MPU Region Size 64 Bytes
#define ARM_MPU_REGION_SIZE_128B     ((uint8_t)0x06U) ///!< MPU Region Size 128 Bytes
#define ARM_MPU_REGION_SIZE_256B     ((uint8_t)0x07U) ///!< MPU Region Size 256 Bytes
#define ARM_MPU_REGION_SIZE_512B     ((uint8_t)0x08U) ///!< MPU Region Size 512 Bytes
#define ARM_MPU_REGION_SIZE_1KB      ((uint8_t)0x09U) ///!< MPU Region Size 1 KByte
#define ARM_MPU_REGION_SIZE_2KB      ((uint8_t)0x0AU) ///!< MPU Region Size 2 KBytes
#define ARM_MPU_REGION_SIZE_4KB      ((uint8_t)0x0BU) ///!< MPU Region Size 4 KBytes
#define ARM_MPU_REGION_SIZE_8KB      ((uint8_t)0x0CU) ///!< MPU Region Size 8 KBytes
#define ARM_MPU_REGION_SIZE_16KB     ((uint8_t)0x0DU) ///!< MPU Region Size 16 KBytes
#define ARM_MPU_REGION_SIZE_32KB     ((uint8_t)0x0EU) ///!< MPU Region Size 32 KBytes
#define ARM_MPU_REGION_SIZE_64KB     ((uint8_t)0x0FU) ///!< MPU Region Size 64 KBytes
#define ARM_MPU_REGION_SIZE_128KB    ((uint8_t)0x10U) ///!< MPU Region Size 128 KBytes
#define ARM_MPU_REGION_SIZE_256KB    ((uint8_t)0x11U) ///!< MPU Region Size 256 KBytes
#define ARM_MPU_REGION_SIZE_512KB    ((uint8_t)0x12U) ///!< MPU Region Size 512 KBytes
#define ARM_MPU_REGION_SIZE_1MB      ((uint8_t)0x13U) ///!< MPU Region Size 1 MByte
#define ARM_MPU_REGION_SIZE_2MB      ((uint8_t)0x14U) ///!< MPU Region Size 2 MBytes
#define ARM_MPU_REGION_SIZE_4MB      ((uint8_t)0x15U) ///!< MPU Region Size 4 MBytes
#define ARM_MPU_REGION_SIZE_8MB      ((uint8_t)0x16U) ///!< MPU Region Size 8 MBytes
#define ARM_MPU_REGION_SIZE_16MB     ((uint8_t)0x17U) ///!< MPU Region Size 16 MBytes
#define ARM_MPU_REGION_SIZE_32MB     ((uint8_t)0x18U) ///!< MPU Region Size 32 MBytes
#define ARM_MPU_REGION_SIZE_64MB     ((uint8_t)0x19U) ///!< MPU Region Size 64 MBytes
#define ARM_MPU_REGION_SIZE_128MB    ((uint8_t)0x1AU) ///!< MPU Region Size 128 MBytes
#define ARM_MPU_REGION_SIZE_256MB    ((uint8_t)0x1BU) ///!< MPU Region Size 256 MBytes
#define ARM_MPU_REGION_SIZE_512MB    ((uint8_t)0x1CU) ///!< MPU Region Size 512 MBytes
#define ARM_MPU_REGION_SIZE_1GB      ((uint8_t)0x1DU) ///!< MPU Region Size 1 GByte
#define ARM_MPU_REGION_SIZE_2GB      ((uint8_t)0x1EU) ///!< MPU Region Size 2 GBytes
#define ARM_MPU_REGION_SIZE_4GB      ((uint8_t)0x1FU) ///!< MPU Region Size 4 GBytes

3、实际例子测试

笔者来看一下MPU的配置代码。

region号 基地址 长度 属性
0 0x00000000 4G 不可访问,背景region,TEX=0,S=0,C=1,B=0,不共享,写通cache
1 0x00000000 256KB 可访问,TEX=0,S=0,C=1,B=0,不共享,片外或者片内写通cache,不写分配,普通memory
2 0x03000000 64KB 可访问,TEX=0,S=0,C=1,B=0,不共享,片外或者片内写通cache,不写分配,普通memory
3 0x10000000 8MB 只读,TEX=0,S=0,C=1,B=0,不共享,片外或者片内写通cache,不写分配,普通memory
4 0x20000000 128KB 可访问,TEX=0,S=0,C=1,B=0,不共享,片外或者片内写通cache,不写分配,普通memory
5 0x40000000 256MB 可访问,TEX=0,S=1,C=0,B=1,共享Devices,没有cahce
6 0xE0000000 1MB 可访问,EX=0,S=1,C=0,B=1,共享Devices,没有cahce
c 复制代码
void mpu_config()
{
    /* Disable MPU */
    ARM_MPU_Disable();

    /* Region 0 setting: Memory with no access type, not shareable, write trough */
    MPU->RBAR = ARM_MPU_RBAR(0, 0x00000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_NONE, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_4GB);

    /* Region 1 setting: Memory with  full access type, not shareable, write trough */
    MPU->RBAR = ARM_MPU_RBAR(1, 0x00000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_256KB);

    /* Region 2 setting: Memory with  full  access type, shareable, none-cacheable */
    MPU->RBAR = ARM_MPU_RBAR(2, 0x03000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_64KB);

    /* Region 3 setting: Memory with  read-only  access type, not shareable, write trough */
    MPU->RBAR = ARM_MPU_RBAR(3, 0x10000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_RO, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_8MB);

    /* Region 4 setting: Memory with  full  access type, not shareable, write trough */
    MPU->RBAR = ARM_MPU_RBAR(4, 0x20000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_128KB);

    /* Region 5 setting: Memory with  full  access type, shareable, none-cacheable */
    MPU->RBAR = ARM_MPU_RBAR(5, 0x40000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 1, 0, 1, 0, ARM_MPU_REGION_SIZE_256MB);

    /* Region 6 setting: Memory with  full  access type, shareable, none-cacheable */
    MPU->RBAR = ARM_MPU_RBAR(6, 0xE0000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 1, 0, 1, 0, ARM_MPU_REGION_SIZE_1MB);

     /* Enable MPU */
    ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_HFNMIENA_Msk);
}

笔者是基于这款芯片的memory创立的,然后根据芯片的地址等情形,可以确认相关memory的MPU配置属性。

然后笔者还碰到一个问题,之前不了解FW会调用0x03000000 ROM Code 的函数,所以这个区域没有配置,结果就访问异常了。错误类型也是指令访问异常。

笔者尝试配置rom code为只读测试一下,之前配置的时候,可读可写。

结论:配置这样也可以访问。

c 复制代码
    /* Region 2 setting: Memory with  full  access type, shareable, none-cacheable */
    MPU->RBAR = ARM_MPU_RBAR(2, 0x03000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 0, 1, 0, 0, ARM_MPU_REGION_SIZE_64KB);

2.6 subregion

再介绍一下subregion,就是一个region区域比较大,会被等分成8份,每份区域可以由一个bit去禁止。

所以可以配置更大的region,然后通过subregion再去细分。

这样笔者再新增一个region,然后禁止某些subregion,来做一些测试。

如下图代码所示,笔者打开了一个异常区域,0xF0000000,但是该区域的前128KB禁止,意味着其受背景region保护,访问进异常,但是其他区域可以访问,会导致总线挂死。

c 复制代码
    /* Region 7 setting: Memory with  full  access type, shareable, none-cacheable */
    MPU->RBAR = ARM_MPU_RBAR(7, 0xF0000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 1, 0, 1, 1, ARM_MPU_REGION_SIZE_1MB);

如下图所示,该区域访问被抓住现场,地址0x100034AE,数据地址:0xF0000000。

如果全部区域都访问,则总线挂死,无法调试。

c 复制代码
    /* Region 6 setting: Memory with  full  access type, shareable, none-cacheable */
    MPU->RBAR = ARM_MPU_RBAR(7, 0xF0000000);
    MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL, 0, 1, 0, 1, 0, ARM_MPU_REGION_SIZE_1MB);

4、参考

1、STM32F1-F4参考手册以及下载
2、Cortex M3/CM4权威指南
3、Cortex M3权威指南中文版

相关推荐
BuiderCodes6 小时前
STM32 中 GPIO 的八种工作模式介绍
stm32·单片机·嵌入式硬件
Ronin-Lotus8 小时前
嵌入式硬件篇---PWM&电机&舵机
c语言·stm32·单片机·嵌入式硬件·学习·51单片机·硬件工程
嵌入式小强工作室20 小时前
STM32的DMA作用
stm32·单片机·嵌入式硬件
小猪写代码21 小时前
STM32 FreeRTOS时间片调度---FreeRTOS任务相关API函数---FreeRTOS时间管理
stm32·单片机·嵌入式硬件
黄金右肾21 小时前
STM32网络通讯之LWIP下载移植项目设计(十六)
stm32·嵌入式软件·网络通讯·lwip移植
code_snow1 天前
STM32--定时器输出pwm知识点_stm32 pwm-CSDN博客
stm32·单片机·嵌入式硬件
隼玉1 天前
【STM32-学习笔记-10-】BKP备份寄存器+时间戳
c语言·笔记·stm32·学习
隼玉1 天前
【STM32-学习笔记-7-】USART串口通信
笔记·stm32·学习
隼玉1 天前
【STM32-学习笔记-11-】RTC实时时钟
c语言·笔记·stm32·学习