先说明一下MPU,MPU有很多含义,我们常见的有:
MPU:Memory Protection Unit,内存保护单元(本文描述的内容);
MPU:Microprocessor Unit,微处理器;
MPU-6050 陀螺仪 跟这个就更是差了十万八千里了
所以请不要搞混
MPU
MPU,即内存保护单元,可以设置不同存储区域的存储器访问特性(如只支持特权访问或全访问)和存储器属性(如可缓存、可缓冲、可共享),对存储器(主要是内存和外设)提供保护,从而提高系统可靠性
通过这些规则可以实现如下功能
- 防止不受信任的应用程序访问受保护的内存区域。
- 防止用户应用程序破坏操作系统使用的数据。
- 通过阻止任务访问其它任务的数据区。
- 允许将内存区域定义为只读,以便保护重要数据。
- 检测意外的内存访问。
也就是内存保护、外设保护和代码访问保护 设置一段内存是只读,还是只允许高权限访问,还是禁止访问等
MPU可以保护的区域为内存映射区 memory map
内存映射区就是 32 位的 CM7 内核整体可以寻址的 0 到 2^32 -1 共计 4GB 的寻址空间。通过这些地址可以访 问 RAM、Flash、外设等。下面是内存映射的轮廓图,IC 厂家使用时,再做细分,添加相应的硬件功能
也就是说 MPU可以保护我们的保护内存区域(SRAM 区)不受非法干扰,也可以保护我们的外设区(比如 FMC)
MPU 区域(region)
STM32H7 的 MPU 提供多达 16 个可编程保护区域(region)每个区域最小要求 256 字节,序号范围是 0 到 15, 每个区域(region)还可以被进一步划分为更小的子区域(sub region),每个区域(region)都有自己的可编程起始地址、大小及设置。
这些内存区可以嵌套和重叠,所以这些区域在嵌套或者重叠的时候有个优先级的问题。MPU 可以配置的 16 个内存区的序号范围是 0 到 15,序号15 的优先级最高,以此递减,还有默认区 default region,也叫作背景区,序号-1,即背景区的优先级最低。这些优先级是固定的
MPU 功能必须开启才会有效,默认条件下,MPU 是关闭的
MPU在执行其功能时,是以“region区域”为单位的。一个region其实就是一段连续的地址,只是它们的位置和范围都要满足一些限制(对齐方式,最小容量等)
MPU 区域(region)优先级
MPU 定义的区域(region)还可以相互交迭。如果某块内存落在多个区域(region)中,则访问属性和权限将由编号最大的 region 来决定。比如,若 3 号 region 与 5 号 region 交迭,则交迭的部分受 5 号 region 控制
比方说5号区域(region)内存是只读模式, 3 号 region内存是读写模式 那么重叠的部分就变成了只读模式,如果对其写入数据则会报错
MPU背景区
MPU在Region区域之外,还允许启用一个背景区域(即没有MPU 设置的其他所有地址空间),上面图片的空白存储区域。背景区域只允许特权访问。在启用 MPU 后,就不得再访问定义之外的地址区间,也不得访问未经授权的区域(region),否则,将以“访问违例”处理,触发 MemManage 异常
具体的我们在寄存器中讲解,你就会有一个清晰的了解。
MPU 设置是由 CTRL、RNR、RBAR 和 RASR 等寄存器控制的,其中最主要的就是RASR寄存器,下面我们来一一介绍
1.MPU 控制寄存器(CTRL),该寄存器只有最低三位有效
- PRIVDEFENA 位用于设置是否开启背景区域(region),通过设置该位为 1 即可打开背景区
- HFNMIENA 位用于控制是否在 NMI 和硬件 fault
- ENABLE 位,则用于控制是否使能 MPU
如果PRIVDEFENA 设置为0 那么就只能访问MPU的设置的内存区域,如果访问其他地址就会报错
2.MPU 区域编号寄存器(RNR)
该寄存器只有低 8 位有效 可以读写
在配置一个区域(region)之前,必须先在 MPU 内选中这个区域,我们可以通过将区域编号写入 MPU_RNR 寄存器来完成这个操作。该寄存器只有低 8 位有效,不过由于 STM32H7最多只支持 16 个区域,所以,实际上只有最低 4 位有效(0~15)。在配置完区域编号以后,我们就可以对区域属性进行设置了。
也就是设置要配置那个一内存区域(region)
3. MPU 区域属性和容量寄存器(RASR)
该寄存器是一个32位的寄存器 其中31:29位 27位 23:22位保存,没有作用
- XN(28位):用于控制是否允许从此区域提取指令,如果 XN=1,说明禁止从区域提取指令,即这块内存区禁止执行程序代码,如果设置XN=0,则允许提取指令 即这块内存区可以执行程序代码
- AP 位(bit[26:24]),用于控制Region区域内数据的访问权限(访问许可)三位就对应8中情况
具体设置如下:
注意: 下面的TEX,C,B 和 S 都是设置Cache高速缓存的,正常使用MPU是做内存保护,用上面的AP位即可,如果没有用到Cache,下面的配置有个了解即可,在HAL库配置的时候直接禁止,不会有任何影响
如果对Cache不了解,具体可看:STM32H7—高速缓存Cache(一)
TEX,C,B 和 S 的定义如下,这仅关注 TEX = 000 和 001,其它的 TEX 配置基本用不到。
- C位:用于使能或者禁止Cache
- B位:用于配合C位实现Cache下是否使用缓冲
- S位:用于位用于控制存储器的共享特性,S=1,则二级存储器不可以缓存(Cache),如果设
置 S=0,则可以缓存(Cache),一般我们设置该位为 0 即可。
按照配置的不同,总共可以分成以下四种:
read/write-through/back/allocate的区别:
一、CPU读Cache
- Read through:直接从内存区读取数据
- Read allocate:先把数据读取到Cache中,再从Cache中读取数据
二、CPU写Cache
若hit命中,有两种处理方式:
- Write-through:在数据更新时,把数据同时写入Cache和存储区
操作简单,但是写入速度慢- Write-back:只有在数据被替换出缓存时,被修改的缓存数据才会被写到后端存储。
写入速度快,但是一旦更新后的数据未被写入时出现断电,则数据无法找回若miss,有两种处理方式:
- Write allocate:先把要写的数据载入到Cache中,写Cache,然后再通过flush的方式写入到内存>中。
- No-write allocate:并不将写入位置读入缓存,直接把要写的数据写入到内存中。
什么叫hit/miss:
一、读操作
如果CPU要读取的SRAM区数据在Cache中已经加载好,这就叫读命 中(Cache hit),如果Cache里面没有怎么办,这就是所谓的读Cache Miss。
二、写操作
如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域(专业词汇叫Cache Line,以32字节为单位),这就叫写命中(Cache hit),如果Cache里面没有开辟对应的区域怎么办,这就是所谓的写Cache Miss。
- SRD位: 用于控制内存区的子区域,一共有8bit,一个bit控制一个子区域,一般都开启
- RegionSIZE位:配置Region内存区域的大小,最小为32位 可以为32kb 64kb 128kb等等
4. MPU 基地址寄存器(RBAR)
- ADDR: Region内存区的首地址
注意一定要保证首地址跟内存区的大小(RegionSIZE位)对齐,例如,我们定义某个 region 的容量(RegionSIZE位)是 64KB,那么它的基址(ADDR)就必须能被 64KB 整除,也就是这个地址对 64KB,即 0x00010000 求余数等于 0,比如0X0001 0000、0X0002 0000、0X0003 0000 等都可以被整除
- VALID 区域编号是否覆盖,如果为1的话将会重新修改ADDR Region内存区的编号
- REGION 段(bit[3:0]) : 新编号,四位地址对应(0~15)
Region内存区的编号在初始化的时候就设置完成,所以一般VALID位设置为0
关于MPU一些基本原理和寄存器就先探讨到这里,下一篇我们来看下HAL库里MPU的配置以及如何使用。
STM32H7内存默认映射和属性
STM32H7 FMC地址
H7的cache配置
Cortex-M7内核的L1 Cache由多行内存区组成,我们的H7是分为 16Kbytes 的 I-Cache 和 D-Cache
每行有32字节,每行都配有一个地址标签。数据缓冲DCache是每4行为一组,称为4-way set associative。而指令缓冲区ICache是2行为一组,2-way set 这样节省地址标签,不用每个行都标记一个地址
- 以16 KBytes 的 D-Cache 来计算,一共128 个组(sets), 512 个缓冲行(lines),每个缓冲行 32 个字节
首先来看下H7的默认内存地址映射范围:
其中,WT 表示 Write-through(透写),WB 表示 Write-back(回写),WA 表示 Write-allocate(写分配),没有明确标注 WA 的就是 RA(读分配)。XN 的意思是 Execute-Never, 其含义为如果相应的地址空间是 XN,是绝不允许执行代码的。
存储器类型为 Normal 的才能使用 cache,并且 TCM 接口是 not cacheable 的。
在H7中 Cache 的配置是通过 MPU 来设置的,通常只用到下几种方式。
Cache 相关操作的函数在 cmsis/include/core_cm7.h 头文件中声明从函数名中可以知道,包括四种 cache 操作:enable、disable、clean 和 invalidate。
当然还有三个不常用的函数:
void SCB_InvalidateDCache_by_Addr(uint32_t *addr, int32_t dsize);
void SCB_CleanDCache_by_Addr(uint32_t *addr, int32_t dsize);
void SCB_CleanInvalidateDCache_by_Addr(uint32_t *addr, int32_t dsize);
下面我们来看一下这些函数。
SCB_EnableICache()
使能指令I-Cache,系统上电后优先初始化即可
SCB_DisableICache()
禁止指令I-Cache。
SCB_InvalidateICache()
使 I-cache 无效,无效化Invalid是将Cache Line标记为无效,即删除操作。
SCB_EnableDCache()
此函数使能数据D-Cache,系统上电后优先初始化即可
SCB_DisableDCache()
禁止数据D-Cache。
SCB_InvalidateDCache()
使 D-Cache 无效,无效化Invalid是将Cache Line标记为无效,即删除操作。
SCB_CleanDCache()
Clean 清空所有的 cache-line,即将 dirty 的 cache-line 全部写到 cache line 对应的真实的物理地址中
所谓的 drity 属性,即写操作时, 更新了相应的 cache-line,但是没有更新到真实的物理地址,而这个 clean 的动作, 就是将 cache 中的内容更新到真实的物理地址中。
SCB_CleanInvalidateDCache()
此函数是前面两个函数SCB_InvalidateDCache和SCB_CleanDCache的二合一。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。
SCB_InvalidateDCache_by_Addr()
根据地址信息无效其对应的 cache-line。
SCB_CleanDCache_by_Addr()
根据地址信息 clean 其对应的 cache-line。
SCB_CleanInvalidateDCache_by_Addr()
根据地址信息 clean 并 invalidate 其对应的 cache-line。
一共就这么多,大部分都是对D-Cache的操作,正常使用的时候,直接调用函数进行初始化就行
SCB_EnableICache();//使能I-Cache
SCB_EnableDCache();//使能D-Cache
但是对于Cache的一致性问题,比方说DMA操作等引起的一致性错误等
1.CPU写数据到内存
当CPU有写物理内存的指令时,CPU 会先去更新相应的 cache-line(Write-back 策略)缓存,在没有 clean 的情况下,会导致其对应的实际物理内存中的数据并没有被更新,如果这个时候有其它的 Host(如 DMA)访问这段内存时,就会出现问题(由于实际物理内存并未被更新,和 D-cache 中的不一致),这就是所谓的 cache 一致性的问题。
2.CPU从内存读取数据
第二种情况是 DMA 更新了某段物理内存(DMA 和 cache 直接没有直接通道),而这个时候CPU 再读取这段内存的时候,由于相对应地址的 cache-line 已经被缓存了,导致CPU 读到的是 cache-line 中的数据,而非被 DMA 更新过的实际物理内存的数据。
为了防止这种问题,保证数据的一致性,我们设置了 D Cache 的强制透写功能(Write Through)
强制透写(Write Through): CPU 每次操作 Cache 里面的数据,同时也会更新到 SRAM 里面,不需要再发送clean操作,保证D Cache 和 SRAM 里面数据一致。
需要将SCB->CACR寄存器的第一位置1 就可以开启强制透写
SCB->CACR|=1<<2; //强制D-Cache透写
那也就得到了Cache的初始化流程:
//使能CPU的L1-Cache
void Cache_Enable(void)
{
SCB_EnableICache();//使能I-Cache
SCB_EnableDCache();//使能D-Cache
SCB->CACR|=1<<2; //强制D-Cache透写,如不开启,实际使用中可能遇到各种问题
}
这样带来的好处就是可以保证D Cache 和SRAM里面数据的一致性,坏处就是会损失一定的性能(每次都要回写数据),如果大家想自己控制 D Cache 数据的回写,以获得最佳性能,则可以关闭 D Cache 透写模式