完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980
第23章 STM32H7的MPU内存保护单元(重要)
本章节为大家讲解STM32H7学习中的一个重要知识点MPU(Memory Protection Unit,内存保护单元),早在STM32F1和F4芯片上面也是有这个功能的,但是基本用不上。但是到了H7就得用上了,因为要设置Cache。
23.1 初学者重要提示
23.2 MPU简介
23.3 MPU的功能实现
23.4 MPU可以配置的三种内存类型
23.5 MPU的寄存器和对应的库参数
23.6 MPU的配置函数
23.7 总结
23.1 初学者重要提示
- 本章节主要是为下一章Cache的讲解做个铺垫,需要初学者把本章节涉及到的基础知识点掌握了。
- 初学MPU时,可能有些知识点无法一下子就搞明白,在后续章节的各种应用后,会有一个较深的认识。这个知识点基本会贯穿整个教程。
23.2 MPU简介
MPU可以将memory map内存映射区分为多个具有一定访问规则的区域,通过这些规则可以实现如下功能:
- 防止不受信任的应用程序访问受保护的内存区域。
- 防止用户应用程序破坏操作系统使用的数据。
- 通过阻止任务访问其它任务的数据区。
- 允许将内存区域定义为只读,以便保护重要数据。
- 检测意外的内存访问。
简单的说就是内存保护、外设保护和代码访问保护。
- 内存映射
内存映射就是32位的CM7内核整体可以寻址的0 到2^32 -1共计4GB的寻址空间。通过这些地址可以访问RAM、Flash、外设等。下面是内存映射的轮廓图,IC厂家使用时,再做细分,添加相应的硬件功能。
23.3 MPU的功能实现
MPU可以配置保护16个内存区域(这16个内存域是独立配置的),每个区域最小要求256字节,每个区域还可以配置为8个子区域。由于子区域一般都相同大小,这样每个子区域的大小就是32字节,正好跟Cache的Cache Line大小一样。
MPU可以配置的16个内存区的序号范围是0到15,还有默认区 default region,也叫作背景区,序号-1。由于这些内存区可以嵌套和重叠,所以这些区域在嵌套或者重叠的时候有个优先级的问题。序号15的优先级最高,以此递减,序号-1,即背景区的优先级最低。这些优先级是固定的。
下面通过一个具体的实例帮助大家理解。如下所示共有7个区,背景区和序号0-5的区。内存区4跟内存区0和1有重叠部分,那么重叠部分将按照内存区4的配置规则执行;内存区5被完全包含在内存区3里面,那么这部分内存区将按照内存区5的配置规则执行。
23.4 MPU可以配置的三种内存类型
MPU可以配置的三种内存类型如下:
- Normal memory
CPU以最高效的方式加载和存储字节、半字和字,对于这种内存区,CPU的加载或存储不一定要按照程序列出的顺序执行。
- Device memory
对于这种类型的内存区,加载和存储要严格按照次序进行,这样是为了确保寄存器按照正确顺序设置。
- Strongly ordered memory
程序完全按照代码顺序执行,CPU需要等待当前的加载/存储指令执行完毕后才执行下一条指令。这样会导致性能下降。
23.5 MPU的寄存器和对应的库参数
关于MPU的寄存器介绍在STM32H7的编程手册有专门的讲解说明,我们这里重点讲解寄存器MPU_RASR和控制寄存器,此寄存器的定义如下:
23.5.1 RASR寄存器的XN位
XN=0表示使能指令提取,即这块内存区可以执行程序代码,XN=1表示禁止指令提取,即这块内存区禁止执行程序代码。
对应的HAL库MPU参数如下:
/** @defgroup CORTEX_MPU_Instruction_Access CORTEX MPU Instruction Access * @{ */ #define MPU_INSTRUCTION_ACCESS_ENABLE ((uint8_t)0x00) #define MPU_INSTRUCTION_ACCESS_DISABLE ((uint8_t)0x01)
23.5.2 RASR寄存器的AP位
AP的具体定义如下:
这几个参数对应的HAL库MPU参数如下:
/** @defgroup CORTEX_MPU_Region_Permission_Attributes CORTEX MPU Region Permission Attributes * @{ */ #define MPU_REGION_NO_ACCESS ((uint8_t)0x00) #define MPU_REGION_PRIV_RW ((uint8_t)0x01) #define MPU_REGION_PRIV_RW_URO ((uint8_t)0x02) #define MPU_REGION_FULL_ACCESS ((uint8_t)0x03) #define MPU_REGION_PRIV_RO ((uint8_t)0x05) #define MPU_REGION_PRIV_RO_URO ((uint8_t)0x06)
23.5.3 RASR寄存器的TEX,C,B和S位
(注,这几个位非常重要,当前先了解知识点即可,下个章节专门讲解具体的作用)
TEX,C,B和S的定义如下,这仅关注TEX = 0b000和0b001,其它的TEX配置基本用不到。
TEX用于配置Cache策略,支持如下四种情况,需要配合C和B位的配置才能实现。
TEX对应的HAL库MPU参数给了三个,实际应用中仅用到前两个MPU_TEX_LEVEL0和MPU_TEX_LEVEL1
/** @defgroup CORTEX_MPU_TEX_Levels MPU TEX Levels * @{ */ #define MPU_TEX_LEVEL0 ((uint8_t)0x00) #define MPU_TEX_LEVEL1 ((uint8_t)0x01) #define MPU_TEX_LEVEL2 ((uint8_t)0x02)
C位对应的HAL库MPU参数如下,用于使能或者禁止Cache。
/** @defgroup CORTEX_MPU_Access_Cacheable CORTEX MPU Instruction Access Cacheable * @{ */ #define MPU_ACCESS_CACHEABLE ((uint8_t)0x01) #define MPU_ACCESS_NOT_CACHEABLE ((uint8_t)0x00)
B位对应的HAL库MPU参数如下,用于配合C位实现Cache模式下是否使用缓冲。
/** @defgroup CORTEX_MPU_Access_Bufferable CORTEX MPU Instruction Access Bufferable * @{ */ #define MPU_ACCESS_BUFFERABLE ((uint8_t)0x01) #define MPU_ACCESS_NOT_BUFFERABLE ((uint8_t)0x00)
S位对应的HAL库MPU参数如下,用于解决多总线或者多核访问的共享问题。
/** @defgroup CORTEX_MPU_Access_Shareable CORTEX MPU Instruction Access Shareable * @{ */ #define MPU_ACCESS_SHAREABLE ((uint8_t)0x01) #define MPU_ACCESS_NOT_SHAREABLE ((uint8_t)0x00)
23.5.4 RASR寄存器的SRD位
这个位用于控制内存区的子区域,使用的是bit[15:8],共计8个bit,一个bit控制一个子区域,0表示使能此子区域,1表示禁止此子区域。
一般情况,基本不使用子区域的禁止功能,所以配置HAL库的SubRegionDisable参数时,直接取值0x00即可,表示8个子区域均使能。
23.5.5 RASR寄存器的SIZE位
SIZE位使用的是bit[5:1],共计5个bit,可以表示2^5 = 32种大小。 对应的HAL库给出了可以配置的28个参数:
/** @defgroup CORTEX_MPU_Region_Size CORTEX MPU Region Size * @{ */ #define MPU_REGION_SIZE_32B ((uint8_t)0x04) #define MPU_REGION_SIZE_64B ((uint8_t)0x05) #define MPU_REGION_SIZE_128B ((uint8_t)0x06) #define MPU_REGION_SIZE_256B ((uint8_t)0x07) #define MPU_REGION_SIZE_512B ((uint8_t)0x08) #define MPU_REGION_SIZE_1KB ((uint8_t)0x09) #define MPU_REGION_SIZE_2KB ((uint8_t)0x0A) #define MPU_REGION_SIZE_4KB ((uint8_t)0x0B) #define MPU_REGION_SIZE_8KB ((uint8_t)0x0C) #define MPU_REGION_SIZE_16KB ((uint8_t)0x0D) #define MPU_REGION_SIZE_32KB ((uint8_t)0x0E) #define MPU_REGION_SIZE_64KB ((uint8_t)0x0F) #define MPU_REGION_SIZE_128KB ((uint8_t)0x10) #define MPU_REGION_SIZE_256KB ((uint8_t)0x11) #define MPU_REGION_SIZE_512KB ((uint8_t)0x12) #define MPU_REGION_SIZE_1MB ((uint8_t)0x13) #define MPU_REGION_SIZE_2MB ((uint8_t)0x14) #define MPU_REGION_SIZE_4MB ((uint8_t)0x15) #define MPU_REGION_SIZE_8MB ((uint8_t)0x16) #define MPU_REGION_SIZE_16MB ((uint8_t)0x17) #define MPU_REGION_SIZE_32MB ((uint8_t)0x18) #define MPU_REGION_SIZE_64MB ((uint8_t)0x19) #define MPU_REGION_SIZE_128MB ((uint8_t)0x1A) #define MPU_REGION_SIZE_256MB ((uint8_t)0x1B) #define MPU_REGION_SIZE_512MB ((uint8_t)0x1C) #define MPU_REGION_SIZE_1GB ((uint8_t)0x1D) #define MPU_REGION_SIZE_2GB ((uint8_t)0x1E) #define MPU_REGION_SIZE_4GB ((uint8_t)0x1F)
23.5.6 CTRL寄存器的各个位
控制寄存器各个位定义如下如下:
23.6 MPU的配置函数
HAL库的stm32h7xx_hal_cortex.c文件为MPU的配置提供了三个函数:
- HAL_MPU_Disable
- HAL_MPU_Enable
- HAL_MPU_ConfigRegion
三个函数的使用都比较简单,但是要让配置的内存区最大限度的发挥性能是需要大量的经验积累和测试的。具体工程要具体分析。
23.6.1 函数HAL_MPU_Disable
函数原型:
void HAL_MPU_Disable(void) { /* Make sure outstanding transfers are done */ __DMB(); /* Disable fault exceptions */ SCB->SHCSR &= ~SCB_SHCSR_MEMFAULTENA_Msk; /* Disable the MPU and clear the control register*/ MPU->CTRL = 0; }
函数描述:
此函数用于禁止MPU。配置MPU前要优先调用此函数禁止MPU,然后才可以配置。
23.6.2 函数HAL_MPU_Enable
函数原型:
void HAL_MPU_Enable(uint32_t MPU_Control) { /* Enable the MPU */ MPU->CTRL = MPU_Control | MPU_CTRL_ENABLE_Msk; /* Enable fault exceptions */ SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; /* Ensure MPU setting take effects */ __DSB(); __ISB(); }
函数描述:
此函数用于使能MPU,一般情况使用参数MPU_PRIVILEGED_DEFAULT。
函数参数:
此函数支持以下几个参数:
1、 MPU_HFNMI_PRIVDEF_NONE ((uint32_t)0x00000000)
- 此参数设置MPU的CTL控制寄存器的PRIVDEFENA位为0。
表示禁止了背景区,访问任何未使能MPU的区域均会造成内存异常MemFault。
- 此参数设置MPU的CTL控制寄存器的HFNMIENA位为0。
表示NMI不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会关闭MPU。
2、 MPU_HARDFAULT_NMI ((uint32_t)0x00000002)
- 此参数设置MPU的CTL控制寄存器的PRIVDEFENA位为0。
表示禁止了背景区,访问任何未使能MPU的区域均会造成内存异常MemFault。
- 此参数设置MPU的CTL控制寄存器的HFNMIENA位为1。
表示NMI不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会保持继续开启MPU。
3、 MPU_PRIVILEGED_DEFAULT ((uint32_t)0x00000004)
- 此参数设置MPU的CTL控制寄存器的PRIVDEFENA位为1。
表示使能了背景区,特权级模式可以正常访问任何未使能MPU的区域。
- 此参数设置MPU的CTL控制寄存器的HFNMIENA位为0。
表示NMI不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会关闭MPU。
4、 MPU_HFNMI_PRIVDEF ((uint32_t)0x00000006)
- 此参数设置MPU的CTL控制寄存器的PRIVDEFENA位为1。
表示禁止了背景区,访问任何未使能MPU的区域均会造成内存异常MemFault。
- 此参数设置MPU的CTL控制寄存器的HFNMIENA位为1。
表示NMI不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会保持继续开启MPU。
23.6.3 函数HAL_MPU_ConfigRegion
函数原型:
void HAL_MPU_ConfigRegion(MPU_Region_InitTypeDef *MPU_Init) { /* Check the parameters */ assert_param(IS_MPU_REGION_NUMBER(MPU_Init->Number)); assert_param(IS_MPU_REGION_ENABLE(MPU_Init->Enable)); /* Set the Region number */ MPU->RNR = MPU_Init->Number; if ((MPU_Init->Enable) != RESET) { /* Check the parameters */ assert_param(IS_MPU_INSTRUCTION_ACCESS(MPU_Init->DisableExec)); assert_param(IS_MPU_REGION_PERMISSION_ATTRIBUTE(MPU_Init->AccessPermission)); assert_param(IS_MPU_TEX_LEVEL(MPU_Init->TypeExtField)); assert_param(IS_MPU_ACCESS_SHAREABLE(MPU_Init->IsShareable)); assert_param(IS_MPU_ACCESS_CACHEABLE(MPU_Init->IsCacheable)); assert_param(IS_MPU_ACCESS_BUFFERABLE(MPU_Init->IsBufferable)); assert_param(IS_MPU_SUB_REGION_DISABLE(MPU_Init->SubRegionDisable)); assert_param(IS_MPU_REGION_SIZE(MPU_Init->Size)); MPU->RBAR = MPU_Init->BaseAddress; MPU->RASR = ((uint32_t)MPU_Init->DisableExec << MPU_RASR_XN_Pos) | ((uint32_t)MPU_Init->AccessPermission << MPU_RASR_AP_Pos) | ((uint32_t)MPU_Init->TypeExtField << MPU_RASR_TEX_Pos) | ((uint32_t)MPU_Init->IsShareable << MPU_RASR_S_Pos) | ((uint32_t)MPU_Init->IsCacheable << MPU_RASR_C_Pos) | ((uint32_t)MPU_Init->IsBufferable << MPU_RASR_B_Pos) | ((uint32_t)MPU_Init->SubRegionDisable << MPU_RASR_SRD_Pos) | ((uint32_t)MPU_Init->Size << MPU_RASR_SIZE_Pos) | ((uint32_t)MPU_Init->Enable << MPU_RASR_ENABLE_Pos); } else { MPU->RBAR = 0x00; MPU->RASR = 0x00; } }
函数描述:
此函数用于配置MPU。
函数参数:
此函数的形参是一个MPU_Region_InitTypeDef类型的结构体变量,定义如下:
typedef struct { uint8_t Enable; uint8_t Number; uint32_t BaseAddress; uint8_t Size; uint8_t SubRegionDisable; uint8_t TypeExtField; uint8_t AccessPermission; uint8_t DisableExec; uint8_t IsShareable; uint8_t IsCacheable; uint8_t IsBufferable; }MPU_Region_InitTypeDef;
除了参数Number和BaseAddress,其它几个参数在本章23.5小节进行了说明。
- 结构体成员Number
这个成员是用来设置内存区序号的,用户配置的时候,推荐从Number0开始配置,最多到Number15,共计16个。对应的HAL库参数如下:
/** @defgroup CORTEX_MPU_Region_Number CORTEX MPU Region Number * @{ */ #define MPU_REGION_NUMBER0 ((uint8_t)0x00) #define MPU_REGION_NUMBER1 ((uint8_t)0x01) #define MPU_REGION_NUMBER2 ((uint8_t)0x02) #define MPU_REGION_NUMBER3 ((uint8_t)0x03) #define MPU_REGION_NUMBER4 ((uint8_t)0x04) #define MPU_REGION_NUMBER5 ((uint8_t)0x05) #define MPU_REGION_NUMBER6 ((uint8_t)0x06) #define MPU_REGION_NUMBER7 ((uint8_t)0x07) #define MPU_REGION_NUMBER8 ((uint8_t)0x08) #define MPU_REGION_NUMBER9 ((uint8_t)0x09) #define MPU_REGION_NUMBER10 ((uint8_t)0x0A) #define MPU_REGION_NUMBER11 ((uint8_t)0x0B) #define MPU_REGION_NUMBER12 ((uint8_t)0x0C) #define MPU_REGION_NUMBER13 ((uint8_t)0x0D) #define MPU_REGION_NUMBER14 ((uint8_t)0x0E) #define MPU_REGION_NUMBER15 ((uint8_t)0x0F)
- 结构体成员BaseAddress
这个结构体成员用来设置内存区的首地址。这个参数跟结构体成员Size的配置是比较考究的,一定要保证首地址跟内存区的大小对齐,比如配置的是64KB大小的内存区,那么设置的首地址务必是64KB对齐的,也就是这个地址对64KB,即0x00010000求余数等于0,切记。
使用举例:
下面配置了两组。
/* ********************************************************************************************************* * 函 数 名: MPU_Config * 功能说明: 配置MPU * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void MPU_Config( void ) { MPU_Region_InitTypeDef MPU_InitStruct; /* 禁止 MPU */ HAL_MPU_Disable(); /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x24000000; MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x60000000; MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER1; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /*使能 MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); }
23.7 总结
本章节就为大家讲解这么多,还是本章开头那句话,可能有些知识点无法一下子就搞明白,在学习了后续章节的各种应用后,会有一个较深的认识。