完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
1)实验平台:alientek 阿波罗 STM32F767 开发板
2)摘自《STM32F7 开发指南(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子 第十七章 内存保护(MPU)实验 STM32 的 Cortex M4(STM32F3/F4 系列)和 Cortex M7(STM32F7 系列)系列的产品,都带有内存保护单元(memory protection unit),简称:MPU。使用 MPU 可以设置不同存储区域的存储器访问特性(如只支持特权访问或全访问)和存储器属性(如可缓存、可共享),从而提高嵌入式系统的健壮性,使系统更加安全。接下来,我们将以 STM32F767 为例,给大家介绍 STM32F7 内存保护单元(MPU)的使用。 本章分为如下几个小节: 17.1 MPU 简介 17.2 硬件设计 17.3 软件设计 17.4 下载验证 17.1 MPU 简介 MPU,即内存保护单元,可以设置不同存储区域的存储器访问特性(如只支持特权访问或全访问)和存储器属性(如可缓存、可缓冲、可共享),对存储器(主要是内存和外设)提供保护,从而提高系统可靠性: 1,阻止用户应用程序破坏操作系统使用的数据。 2,阻止一个任务访问其他任务的数据区,从而隔离任务。 3,可以把关键数据区域设置为只读,从根本上解决被破坏的可能。 4,检测意外的存储访问,如堆栈溢出,数组越界等。 5,将 SRAM 或者 RAM 空间定义为不可执行(用不执行,XN),防止代码注入攻击。 注意,MPU 不仅可以保护内存区域(SRAM 区),还可以保护外设区(比如 FMC)。我们 可以通过 MPU 设置存储器的访问权限,当存储器访问和 MPU 定义的访问权限相冲突的时候, 则访问会被阻止,并且触发一次错误异常(一般是 MemManage 异常)。然后,在异常处理的时 候,就可以确定系统是否应该复位或者执行其他操作。 STM32F7 的 MPU 提供多达 8 个可编程保护区域(region),每个区域(region)都有自己 的可编程起始地址、大小及设置。MPU 功能必须开启才会有效,默认条件下,MPU 是关闭的, 所以,我们要向使用 MPU,必须先打开 MPU 才行。 8 个可编程保护区域(region),一般来说是足够使用的了,如果觉得不够,每个区域(region) 还可以被进一步划分为更小的子区域(sub region),另外,还允许启用一个背景区域(即没有 MPU 设置的其他所有地址空间),背景区域只允许特权访问。在启用 MPU 后,就不得再访问 定义之外的地址区间,也不得访问未经授权的区域(region),否则,将以“访问违例”处理, 触发 MemManage 异常。 此外,MPU 定义的区域(region)还可以相互交迭。如果某块内存落在多个区域(region) 中,则访问属性和权限将由编号最大的 region 来决定。比如,若 2 号 region 与 5 号 region 交迭, 则交迭的部分受 5 号 region 控制。 MPU 设置是由 CTRL、RNR、RBAR 和 RASR 等寄存器控制的,接下来,我们分别介绍一 下这几个寄存器。 首先是 MPU 控制寄存器(CTRL),该寄存器只有最低三位有效,其描述如表 17.1.1 所示: 表 17.1.1 MPU_CTRL 寄存器各位描述 PRIVDEFENA 位用于设置是否开启背景区域(region),通过设置该位为 1,可以在没有建 立任何 region 就使能 MPU 的情况下,依然允许特权级程序访问所有地址,而只有用户级程序 被卡死。但是,如果设置了其它的 region(最多 8 个 region)并使能 MPU,则背景 region 与这 些 region 重合的部分,就要受各 region 的限制。HFNMIENA 位用于控制是否在 NMI 和硬件 fault 中断服务例程中禁止 MPU,一般设置为 0 即可。ENABLE 位,则用于控制是否使能 MPU,我 们一般在 MPU 配置完)以后,才对其进行使能,从而开启 MPU。 接下来,介绍 MPU 区域编号寄存器(RNR),该寄存器只有低 8 位有效,其描述如表 17.1.2 所示 表 17.1.2 MPU_RNR 寄存器各位描述 在配置任何一个区域(region)之前,必须先在 MPU 内选中这个区域,我们可以通过将区 域编号写入 MPU_RNR 寄存器来完成这个操作。该寄存器只有低 8 位有效,不过由于 STM32F7 最多只支持 8 个区域,所以,实际上只有最低 3 位有效(0~7)。在配置完区域编号以后,我们 就可以对区域属性进行设置了。 接下来,介绍 MPU 基地址寄存器(RBAR),该寄存器各位描述如表 17.1.3 所示: 表 17.1.3 MPU_RBAR 寄存器各位描述 注意,表中 ADDR 字段所设置的基址必须对齐到区域(region)容量的边界。例如,我们 定义某个 region 的容量是 64KB(通过 RASR 寄存器设置),那么它的基址(ADDR)就必须能 被 64KB 整除,比如 0X0001 0000、0X0002 0000、0X0003 0000 等(低 16 位全为 0)。 VALID 用于控制 REGION 段(bit[3:0])的数据是否有效,如果 VALID=1,则 REGION 段的区域编号将覆盖 MPU_RNR 寄存器所设置的区域编号,否则将使用 MPU_RNR 所设置的区域 编号。我们一般设置 VALID 为 0,这样 MPU_RBAR 寄存器的低 5 位就没有用到。 特别注意,表 17.1.3 中的 N 值最少也是 5,所以,基址必须是 32 的倍数,从而可以知道我 们设置 region 的容量,必须是 32 字节的倍数。 最后,我们介绍 MPU 区域属性和容量寄存器(RASR),该寄存器各位描述如表 17.1.4 所 示: 表 17.1.4 MPU_RASR 寄存器各位描述 XN 位,用于控制是否允许从此区域取指,如果 XN=1,说明禁止从区域取指,如果强行取 指,将产生一个 MemManage 异常。如果设置 XN=0,则允许取指。 AP 位,由 3 个位(bit[26:24])组成,用于控制数据的访问权限(访问许可),控制关系如 表 17.1.5 所示: 表 17.1.5 不同 AP 设置及其访问权限 TEX、S、C 和 B 等位,对应着存储系统中比较高级的概念,可以通过对这些位段的编程, 来支持多样的内存管理模型,这些位组合的详细功能如表 17.1.6 所示: 表 17.1.6 TEX、S、C 和 B 对存储器类型的定义 有些情况下,内部和外部内存可能需要不同的缓存策略,次数需要设置 TEX 的第二位为 1, 这样 TEX[1:0]的定义就会变为外部策略表(表 17.1.6 种表示为 BB),而 C 和 B 位则会变为内 部策略表(表 17.1.6 种表示为 AA)。缓存策略的定义(AA 和 BB)如表 17.1.7 所示: 表 17.1.7 TEX 最高位为 1 时内外缓存策略编码 S 位用于控制存储器的共享特性,设置 S=1,则二级存储器不可以缓存(Cache),如果设 置 S=0,则可以缓存(Cache),一般我们设置该位为 0 即可。 C 位用于控制存储器的缓存特性,也就是是否可以 Cache,STM32F7 自带 Cache,如果我 们想要某个存储器可以被 Cache,则必须设置 C=1。此位需要根据具体的需要设置。 B 位用于控制存储器的缓冲特性,设置 B=1,则二级存储器可以缓冲,即写回模式,设置 B=0,则二级存储器不可以缓冲,即写通模式。此位需根据具体的需要进行设置。 SRD[15:8],这 8 个位用于控制子区域(sub region)使能。前面提到,STM32F7 的 MPU 最多支持 8 个 region,有时候可能不够用,通过子区域的概念,可以将每个 region 的内部进一 步划分成更小的块,这就是 sub region,每个 sub region 可以独立地使能或除能(相当于可以部 分地使能一个 region)。 sub region 的使用必须满足: 1,每个 region 必须 8 等分,每份是一个 sub region,其属性与主 region 完全相同。 2,可以被分为 8 个 sub region 的 region,其大小必须大于等于 256 字节。 SRD 中的 8 个位,每个位控制一个 sub region 是否被除能。如 SRD.4=0,则 4 号 sub region 被除能。如果某个 sub region 被除能,且其对应的地址范围又没有落在其它 region 中,则对该 区的访问将引发 fault。 REGIONSIZE[5:1],这 5 个位用于控制 region 的容量(大小),计算关系如下: rsize=2^(REGIONSIZE+1) rsize 即 region 的容量,必须大于等于 32 字节,即 REGIONSIZE 必须大于等于 4。region 的容量范围为:32B~4GB,根据实际需要进行设置。 SZENABLE 位,用于设置 region 的使能。该位一般最后设置,设置为 1,则启用此 region, 使能 MPU 保护。 至此,关于 MPU 的简介就介绍完了,关于 MPU 更详细的说明,请参考:《STM32F7 编程 手册》 、 《STM32 MPU 说明》和《Cortex M3 权威指南(中文)》第 14 章。接下来我们看看使 用 HAL 库 配 置 MPU 相 关 函 数 和 配 置 方 法 。 MPU 相 关 的 配 置 分 布 在 头 文 件 stm32f7xx_hal_cortex.h 和对应的源文件 stm32f7xx_hal_cortex.c 中。 1) 禁止和使能 MPU 以及 MemManage 中断。 HAL 库中使能和禁止 MPU 以及 MemManage 中断的方法非常简单,使能函数为: __STATIC_INLINE void HAL_MPU_Enable(uint32_t MPU_Control); 禁止函数为: __STATIC_INLINE void HAL_MPU_Disable(void); 2) 配置某个区域的 MPU 保护参数。 上面我们讲过,在进行 MPU 配置之前我们必须先通过 MPU_RNR 区域编号寄存器用来选 择下一个要配置的区域,然后通过配置 MPU_RBAR 基地址寄存器来配置基地址,最后通过区 域属性和容量寄存器 RASR 来配置区域相关属性和参数。这些过程在 HAL 库中是通过函数 HAL_MPU_ConfigRegion 来实现的,函数声明如下: void HAL_MPU_ConfigRegion(MPU_Region_InitTypeDef *MPU_Init); 该函数只有一个入口参数MPU_Init,该参数为 MPU_Region_InitTypeDef 结构体指针类型, 该结构体定义如下: typedef struct { uint8_t Enable; //区域使能/禁止 uint8_t Number; //区域编号 uint32_t BaseAddress; //配置区域基地址 uint8_t Size; //区域容量 uint8_t SubRegionDisable; //子 region 除能位段设置 uint8_t TypeExtField; //类型扩展级别 uint8_t AccessPermission; //设置访问权限 uint8_t DisableExec; //允许/禁止取指 uint8_t IsShareable; //禁止/允许共享 uint8_t IsCacheable; //禁止/允许缓存 uint8_t IsBufferable; //禁止/允许缓冲 }MPU_Region_InitTypeDef; 该结构体成员变量很多,每个成员变量的含义我们都在上面定义中有注释。这里大家注意, 除了 BaseAddress 和 Number 两个成员变量是分别用来配置 MPU->RBAR 和 MPU->RNR 寄存器 之外,其他成员变量都是用来配置 MPU->RASR 寄存器相关位,大家如果对这些配置项不理解 的话,可以直接对照我们前面讲解的寄存器 MPU->RASR 各个位含义来理解。 MPU 配置相关 HAL 库函数我们就给大家讲解到这里。接下来我们看看硬件设计。 17.2 硬件设计 本章实验功能简介:本实验,我们将利用 STM32F7 自带的 MPU 功能,对一个特定的内存 空间(数组,地址:0X20002000)进行写访问保护。开机时,串口调试助手显示:MPU closed, 表示默认是没有写保护的。按 KEY0 可以往数组里面写数据,按 KEY1,可以读取数组里面的 数据。按KEY_UP则开启MPU保护,此时,如果再按KEY0往数组写数据,就会引起MemManage 错误,进入 MemManage_Handler 中断服务函数,此时 DS1 点亮,同时打印错误信息,最后软 件复位,系统重启。DS0 用于提示程序正在运行,所有信息都是通过串口 1 输出(115200),请 用串口调试助手查看。 本实验需要用到的硬件资源有: 1)指示灯 DS0 2)串口 1 3)按键 KEY0、KEY1 和 KEY_UP(也称之为 WK_UP) 这些硬件资源,我们在之前的例程,都已经介绍过了,请参考之前的例程。 17.3 软件设计 打开本章 MPU 实验工程可以看到,我们在 HARDWARE 分组之下添加了 mpu.c 源文件, 同时将对应的头文件 mpu.h 引入工程。打开 mpu.c 文件,代码如下: //设置某个区域的 MPU 保护 //baseaddr:MPU 保护区域的基址(首地址) //size:MPU 保护区域的大小(必须是 32 的倍数,单位为字节) //可设置的值参考:CORTEX_MPU_Region_Size //rnum:MPU 保护区编号,范围:0~7,最大支持 8 个保护区域 //可设置的值参考:CORTEX_MPU_Region_Number //ap:访问权限,访问关系如下: //可设置的值参考:CORTEX_MPU_Region_Permission_Attributes //MPU_REGION_NO_ACCESS,无访问(特权&用户都不可访问) //MPU_REGION_PRIV_RW,仅支持特权读写访问 //MPU_REGION_PRIV_RW_URO,禁止用户写访问(特权可读写访问) //MPU_REGION_FULL_ACCESS,全访问(特权&用户都可访问) //MPU_REGION_PRIV_RO,仅支持特权读访问 //MPU_REGION_PRIV_RO_URO,只读(特权&用户都不可以写) //详见:STM32F7 Series Cortex-M7 processor programming manual.pdf,4.6 节,Table 89. //返回值;0,成功. // 其他,错误. u8 MPU_Set_Protection(u32 baseaddr,u32 size,u32 rnum,u32 ap) { MPU_Region_InitTypeDef MPU_Initure; HAL_MPU_Disable(); //配置 MPU 之前先关闭 MPU,配置完成以后在使能 MPU MPU_Initure.Enable=MPU_REGION_ENABLE; //使能该保护区域 MPU_Initure.Number=rnum; //设置保护区域 MPU_Initure.BaseAddress=baseaddr; //设置基址 MPU_Initure.Size=size; //设置保护区域大小 MPU_Initure.SubRegionDisable=0X00; //禁止子区域 MPU_Initure.TypeExtField=MPU_TEX_LEVEL0; //设置类型扩展域为 level0 MPU_Initure.AccessPermission=(u8)ap; //设置访问权限, MPU_Initure.DisableExec=MPU_INSTRUCTION_ACCESS_ENABLE; //允许指令访问 MPU_Initure.IsShareable=MPU_ACCESS_NOT_SHAREABLE; //禁止共用 MPU_Initure.IsCacheable=MPU_ACCESS_CACHEABLE; //使能 cache MPU_Initure.IsBufferable=MPU_ACCESS_BUFFERABLE; //允许缓冲 HAL_MPU_ConfigRegion(&MPU_Initure); //配置 MPU HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); //开启 MPU return 0; } //设置需要保护的存储块 //必须对部分存储区域进行 MPU 保护,否则可能导致程序运行异常 //比如 MCU 屏不显示,摄像头采集数据出错等等问题... void MPU_Memory_Protection(void) { MPU_Set_Protection(0x60000000,MPU_REGION_SIZE_64MB, MPU_REGION_NUMBER0,MPU_REGION_FULL_ACCESS); //保护 MCU LCD 屏所在的 FMC 区域,,共 64M 字节 MPU_Set_Protection(0x20000000,MPU_REGION_SIZE_512KB, MPU_REGION_NUMBER1,MPU_REGION_FULL_ACCESS); //保护整个内部 SRAM,包括 SRAM1,SRAM2 和 DTCM,共 512K 字节 MPU_Set_Protection(0XC0000000,MPU_REGION_SIZE_32MB, MPU_REGION_NUMBER2,MPU_REGION_FULL_ACCESS); //保护 SDRAM 区域,共 32M 字节 MPU_Set_Protection(0X80000000,MPU_REGION_SIZE_256MB, MPU_REGION_NUMBER3,MPU_REGION_FULL_ACCESS); //保护整个 NAND FLASH 区域,共 256M 字节 } //MemManage 错误处理中断 //进入此中断以后,将无法恢复程序运行!! void MemManage_Handler(void) { LED1(0); //点亮 DS1 printf("Mem Access Error!!rn"); //输出错误信息 delay_ms(1000); printf("Soft Reseting...rn"); //提示软件重启 delay_ms(1000); NVIC_SystemReset(); //软复位 } 此部分总共 3 个函数: MPU_Set_Protection 函数,用于设置某个区域(region)的详细参数,详见代码说明,通过 该函数,我们可以设置某个存储区域的具体特性,从而实现内存保护。 MPU_Memory_Protection 函数,用于设置整个代码里面,我们需要保护的存储块,这里我 们对 4 个存储块(使用了 4 个区域(region))进行了保护: 1,从 0x60000000 地址开始的 64MB 地址空间,禁止共用,禁止 cache,禁止缓冲,保护 MCU LCD 屏的访问地址取件,如不进行设置,可能导致 MCU LCD 白屏。 2,从 0x20000000 地址开始的 512KB 地址空间,包括 SRAM1,SRAM2 和 DTCM,禁止 共用,允许 cache,允许缓冲。 3,从 0XC0000000 地址开始的 32MB 地址空间,即 SDRAM 的地址范围,禁止共用,允许 cache,允许缓冲。 4,从 0X80000000 地址开始的 256MB 地址空间,即 NAND FLASH 区域,禁止共用,禁 止 cache,禁止缓冲,如不进行设置,可能导致 NAND FLASH 访问异常。 这四个地址空间的保护设置,可以提高代码的稳定性(其实就是减少使用 cache 导致的各 种莫名奇妙的问题),请大家不要随意改动。此函数在本例程没有用到,不过我们在后续代码都 会用到。 最后,MemManage_Handler 函数,用于处理产生 MemManage 错误的中断服务函数,在该 函数里面点亮了 DS1,并输出一些串口信息,对系统进行软复位,以便观察本例程的实验结果。 头文件 mpu.h 内容非常简单,主要是函数声明,这里我们不做过多解释。 最后,打开 main.c 文件,代码如下: u8 mpudata[128] __attribute__((at(0X20002000))); //定义一个数组 int main(void) { u8 i=0; u8 key; Cache_Enable(); //打开 L1-Cache HAL_Init(); //初始化 HAL 库 Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz delay_init(216); //延时初始化 uart_init(115200); //串口初始化 LED_Init(); //初始化 LED KEY_Init(); //按键初始化 printf("rnrnMPU closed!rn"); //提示 MPU 关闭 while(1) { key=KEY_Scan(0); if(key==WKUP_PRES) //使能 MPU 保护数组 mpudata; { MPU_Set_Protection(0X20002000,128,0,MPU_REGION_PRIV_RO_URO, 0,0,1); //只读,禁止共用,禁止 catch,允许缓冲 printf("MPU open!rn"); //提示 MPU 打开 }else if(key==KEY0_PRES) //向数组中写入数据,如果开启了 MPU 保护的 //话会进入内存访问错误! { printf("Start Writing data...rn"); sprintf((char*)mpudata,"MPU test array %d",i); printf("Data Write finshed!rn"); }else if(key==KEY1_PRES) //从数组中读取数据,不管有没有开启 MPU 保护 //都不会进入内存访问错误! { printf("Array data is:%srn",mpudata); }else delay_ms(10); i++; if((i%50)==0) LED0(led0sta^=1); //LED0 取反 } } 此部分代码,我们定义了一个 128 字节大小的数组:mpudata,其首地址为 0X20002000, 默认情况下,MPU 保护关闭,可以对该数组进行读写访问。当我们按下 KEY_UP 按键的时候, 通过 MPU_Set_Protection 函数,对其 0X20002000 为起始地址,大小为 128 字节的内存空间进 行保护,仅支持特权读访问,此时如果再按 KEY0,对数组进行写入操作,则会引起 MemManage 访问异常,进入 MemManage_Handler 中断服务函数,执行相关操作。 其他的代码比较简单,这里就不多做说明了,在整个代码编译通过之后,我们就可以开始 下载验证了。 17.4 下载验证 我们把程序下载到阿波罗 STM32F767 开发板,可以看到板子上的 DS0 开始闪烁,说明程 序已经在跑了。然后,打开串口调试助手(XCOM V2.0),设置串口为开发板的 USB 转串口 (CH340 虚拟串口,得根据你自己的电脑选择,我的电脑是 COM3,另外,请注意:波特率是 115200),可以看到如图 17.4.1 所示信息(如果没有提示信息,请先按复位): 图 17.4.1 串口调试助手收到的信息 从图 17.4.1 可以看出,此时串口助手提示:MPU Closed,即 MPU 保护是关闭的,我们可 以按 KEY0 往数组里面写入数据,按 KEY1,可以读取刚刚写入的数据,按 KEY_UP,则开启 MPU 保护,提示:MPU open!,此时,如果再按 KEY0,往数组里面写数据的话,则会引起 MemManage 访问异常,进入 MemManage_Handler 中断服务函数,点亮DS0,并提示:Mem Access Error!!,并在 1 秒钟以后,重启系统(软复位),如图 17.4.2 所示: 图 17.4.2 串口调试助手显示运行结果 整个过程,验证了我们代码的正确性,通过 MPU 实现了对特定内存的写保护功能。通过 MPU,我们可以提高系统的可靠性,使代码更加安全的运行。 |
|
相关推荐
|
|
AD7686芯片不传输数据给STM32,但是手按住就会有数据。
499 浏览 1 评论
1943 浏览 0 评论
如何解决MPU-9250与STM32通讯时,出现HAL_ERROR = 0x01U
1053 浏览 1 评论
hal库中i2c卡死在HAL_I2C_Master_Transmit
1469 浏览 1 评论
LL库F030进行3个串口收发,2个串口为232,一个为485,长时间后,会出现串口1停止运行,另外两个正常,只有重启复原
1909 浏览 1 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-18 22:16 , Processed in 0.553696 second(s), Total 66, Slave 48 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号