STM32H750 配置 MPU 开启「擦车」,使用高速缓存加快运行速度
STM32H750 的配置
CPU 主频高达 480 MHz,但是各种总线的速度都得 ÷ 2、÷ 4,其中各种 SRAM 都在这些总线上。一般情况下让 CPU 按照 480 MHz 这个频率运行的话,它就会变得弔烫(维持在 50℃,虽然烫了点,但这就是在它的合理工作温度范围内的)。很多人喜欢设置主频到 400 MHz 来降低它的温度,要么就是贴个散热片。其实都不需要。
现在好像一片 STM32H750 的价格约等于三片 STM32F103……这性价比,以后谁还用 STM32F103 呀?480 MHz 带擦车带各种外设还能扩展片外内存和 FLASH,以及自带 SDMMC 外设,全方位吊打 72 MHz 的 STM32F103。
CPU 的内存,以及指令缓存和数据缓存
STM32H750 有很多个内存区域,各自有各自的工作频率和用途,所有的内存的工作频率都不会超过 CPU 频率的一半。如果 CPU 直接读写这些内存区域,速度就会降低到一半,本来是 480 MHz 的 CPU 就会变得像 240 MHz 一样慢。解决 CPU 读写它们速度缓慢的方式就是开启「擦车」,这是最普遍的做法。而提升 CPU 性能的另一个方案是使用 ITCM、DTCM 这两块「时间紧凑型内存」(速度和 CPU 同频)。
CPU 的「擦车」有 I-cache 和 D-cache,都是 16KB,并且都和 CPU 同频。这些「擦车」的作用,就是让你在访问各种总线上的慢速存储器的时候,能把存储器上的数据缓存到 CPU 里,然后能够得到重复执行的指令可以被 CPU 从缓存里重复利用;能够得到重复读写的数据可以被 CPU 从缓存里重复读写。这是使 CPU 在访问慢速存储器时能提升性能(提升到接近 CPU 的频率)的关键法宝。
不论是 I-cache 还是 D-cache,「擦车」的颗粒度是 32 字节,可以缓存 512 个不同地址上的 32 字节数据。
这两个「擦车」其实也是 RAM,但是没有地址。它们就是专门用来做缓存的。如果需要让 CPU 能够去使用它们,那就必须配置 MPU 内存保护单元,根据不同的地址区域,设置不同的缓存策略。
「擦车」策略
在 STM32H750 上,「擦车」策略除了「开启」与「关闭」以外,其实它还有细分的策略。
read_through
:读的时候,不论缓存里是否有,直接从内存里拿数据,同时将拿到的数据存入「擦车」。
- 「擦车」里如果有对应地址的数据,则用读取的数据更新「擦车」里的数据。
- 「擦车」里如果没有对应地址的数据,则在「擦车」里开辟一片区域来缓存数据。
write_through
:写的时候,不论「擦车」里是否有,同时写「擦车」和内存。
- 「擦车」里如果有对应地址的数据,则更新「擦车」里的数据。
- 「擦车」里如果没有对应地址的数据,则在「擦车」里开辟一片区域来缓存数据。
read_allocate
:读的时候只从「擦车」里拿数据。
- 如果「擦车」里有对应地址的数据就直接拿。
- 如果「擦车」里没有对应地址的数据,就从内存里拿数据,同时在「擦车」里开辟一片区域来缓存数据。
write_allocate
:写的时候只写到「擦车」里。
- 如果「擦车」里有对应地址的数据,就只把数据写到「擦车」里。
- 如果「擦车」里如果没有对应地址的数据,则在「擦车」里开辟一片区域来缓存数据。
这个过程中,它从「擦车」里「开辟」区域的方式是这样的:
- 「擦车」里有「无效区域」的时候(「擦车」里有空闲空间的时候),把这个区域拿过来「开辟」。
- 「擦车」里没有「无效区域」的时候(「擦车」被用满的时候),把最早放入「擦车」的数据写回它所在的地址上去,然后把那片区域拿过来「开辟」。
除此以外,在往内存里面写入数据的时候,还有两种写入策略:
bufferable
:有 buffer 的写。这种写,可以让 CPU 把数据往 buffer 上一丢,就可以继续执行后续的指令了,除非 CPU 要连续写。开启这种模式后,写入数据的时间顺序是不确定的。
non_bufferable
:没 buffer 的写。这种写,CPU 会等待写完成了才会继续去执行后续的指令。这种写可以保证写入数据的时间顺序。
到了这一步,你肯定会发现:read_alloc
+ write_alloc
+ bufferable
这种组合策略应该是最高效的策略,也就是尽可能使用「擦车」,「擦车」用满了再去和内存交换数据,只要数据能够一直得到重复的使用,就可以一直以 CPU 的频率来运行程序,并且「擦车」和内存交换数据的时候,还有一个 buffer 能临时帮你接一下数据。
开启「擦车」第一步:配置 MPU
配置 MPU 第一步:了解你的内存布局
STM32H750 的内存布局是这样的:
- 地址
0x0
大小 64 KB
:ITCM 指令内存。这片区域用来放 CPU 指令。它和 CPU 同频,因此针对这片区域 不要启用 「擦车」。启用了的话,不会提升性能,反而浪费「擦车」区域。
- 地址
0x08000000
大小 128 KB
:AXI 总线上的 FLASH,也就是你的程序的主要部分是烧录在这里的。它和 AXI 总线同频,因此针对这片区域的 读取 启用 read_allocate
策略,而写入则让它按 write_through
+ non_bufferable
来。因为本来这片区域是不可写的,一旦对它进行写入,就会触发 HardFault
,而发生这种情况的时候基本上就是你 PC
跑飞了的时候,那这样的话,它要写,就得让它赶紧写,这样就能早点触发 HardFault
,以便于调试。
- 地址
0x20000000
大小 128 KB
:DTCM 数据内存。这片区域和 CPU 同频,用来放数据。因此对它也不要启用「擦车」,免得浪费「擦车」区域。
- 地址
0x24000000
大小 512 KB
:AXI 总线上的 SRAM,速度按 AXI 总线来。它是你的程序主要使用的内存。对它启用 read_allocate
+write_allocate
+ bufferable
是最合适的。但是需要注意以下问题:
- DMA 可以访问这片内存区域。被 DMA 修改过的区域,并不会自动同步到你的「擦车」上。同样,你的 CPU 往这片区域的地址上写数据的时候,会写入到「擦车」里,不会同步到这片区域的内存上,如果用 DMA 去读,DMA 会读到旧数据。这就是 缓存一致性问题。
- 使用
SCB_CleanDCache()
函数可以直接把整个 D-cache 里面的数据写出到内存上, 使内存上的数据更新,这样 DMA 就能拿到最新的数据了。但是,D-cache 里面的数据依然有效,CPU 可以继续使用它里面的数据。
- 使用
SCB_CleanInvalidateDCache()
不仅可以把整个 D-cache 里面的数据写出到内存上,还可以使整个 D-cache 里面的数据无效化。这样的话,CPU 不能继续利用 D-cache 里面原有的数据了,会强制 CPU 在下一次访问内存时先更新它。
- 使用
SCB_CleanDCache_by_Addr()
、SCB_CleanInvalidateDCache_by_Addr()
可以 只把特定区域和大小的内存 更新为「擦车」里的数据。微操专用。如果你有很少量的数据需要 DMA 去读,又不想一下子把整个「擦车」都给它整了,那就用这两个函数。
- 这些函数都在
core_cm7.h
里有定义,并且都是强制内联函数。
- 如果你想在这片区域上跑 CPU 指令,当你把指令写入到特定地址上去后,要先用一下
SCB_InvalidateICache_by_Addr()
把你的指令所在的地址对应的 I-cache 区域无效化,然后你才能跳转到这个地址上去执行指令,这个时候 CPU 才会从这个内存里取指(取到 I-cache 里然后执行)。否则是从 I-cache 里面取(旧的)指。
- 地址
0x30000000
大小 288 KB
:D2 供电区 AHB 总线上的三片 SRAM 内存,地址是连续的,速度是一样的,分别由两个 128 KB
的 SRAM 和一个 32 KB
的 SRAM 构成。虽然结构很奇怪(估计这种设计是因为它芯片内部的布局不好整,所以就这样了),但是可以当成一整块的普通内存来使用。正常也是启用 read_allocate
+write_allocate
+ bufferable
策略,然后在针对 DMA 的访问上手动处理数据一致性问题。
- 地址
0x38000000
大小 64 KB
:D3 供电区 AHB 总线上的一篇 SRAM 内存。用法同上。
- 地址
0x38800000
大小 4 KB
:呃,这个怎么说呢,这是超低功耗模式下,别的 SRAM 都不供电了的时候,这片 SRAM 作为备份内存可以存就这么点儿数据。当你想要进入这种超低功耗模式,并且也确实有一些数据想要存到这里面的时候,不管用不用「擦车」你肯定都是要确保数据都写进去了以后才进入超低功耗模式。那就干脆对它不开启「擦车」了,这样的话可以省去一条 SCB_CleanDCache()
的调用。
- 地址
0x40000000
到 0x5FFFFFFF
:各种外设寄存器的地址。这片区域 绝对不要 开启「擦车」。如果开启了「擦车」,你读写外设寄存器就不太好读写,因为「擦车」会拦着你。你想设置 read_through
+ write_through
+ non_bufferable
也行,但是这会导致外设寄存器地址上的数据占着你的 D-cache。所以肯定是不要对这片区域启用「擦车」的了。
- 地址
0x60000000
到 0x6FFFFFFF
:如果你焊接了 PSRAM,那么这片区域就映射到了 PSRAM,应当启用 read_allocate
+write_allocate
+ bufferable
策略。这是片外内存,读取和写入都会有延迟,因此启用「擦车」会更好。
- 地址
0x80000000
到 0x8FFFFFFF
:同上。
- 地址
0x90000000
到 0x9FFFFFFF
:QSPI 外设的映射地址。QSPI 可以用来接 FLASH,然后把这片 FLASH 映射到这个地址上,并且 CPU 可以在这个地址上执行。这叫 execute in place,直译为 「就地正法」(正常的翻译是「就地执行」,但是我不喜欢正常的翻译)。CPU「正法」的速度完全取决于 QSPI 读 FLASH 的速度,对它开启 read_allocate
+ write_through
+ non_bufferable
是最合适的,因为这片区域也是不可写的,而且有延迟。
- 地址
0xC0000000
到 0xDFFFFFFF
:如果你焊接了 SDRAM,那么这片区域就映射到了 SDRAM,应当启用 read_allocate
+write_allocate
+ bufferable
策略。片外内存都是有延迟的。
配置「擦车」第二步:了解 MPU 的「保护策略」
MPU 不仅可以针对每片内存指定「擦车」策略,还可以指定保护策略,而保护策略也有特定的规则,如下:
- 权限控制
privileged
:「有权者」,你可以理解成「内核态」,负责提供 API 给「用户态」使用。注意:「用户态」不能调用 SCB
开头的函数去处理 I-cache 和 D-cache,只有「内核态」可以。
non_privileged
:「没权者」,也就是「用户态」。
正常情况下肯定是让「有权者」可以调度、控制整个 STM32 芯片的所有资源,而「没权者」则负责使用「有权者」提供的服务和功能来进行正常的工作事务(这个过程如果要花钱,那就叫「权力寻租」,整不好的话「有权者」就会成为贪官污吏,抓起来!)
所谓恶魂漏洞就是「没权者」通过骗 CPU 的「分支预判」,通过各种假动作来「闪」,然后导致 CPU 在试图预填充 I-cache 的时候误以为它的 if 的某个条件为 true
或者 false
,然后从错误的地方读取了不会被执行的条件分支到 I-cache 上后,CPU 就忘了它是不是「没权者」了,然后「没权者」借此机会就可以偷「有权者」的数据。在现代操作系统上的体现就是可以在「有权者」不知道的情况下,跨进程读取数据,比如攻击者的软件程序可以通过恶魂漏洞做到在 360 杀毒不知道的情况下偷了你的 360 浏览器里存储的帐号密码,然后就可以登你的各种帐号了。
扯远了。
- 读写控制
read_only
:只读。在 MPU 里你如果把某片区域的内存设置为只读,那么对它进行写入操作就会触发 MemManage_Handler()
中断。这个时候,你就可以通过死机的方式「惩戒」写入者,也可以采取其它的行为比如在液晶屏上显示「我死机了」(这就和 Windows 的蓝屏是一样的策略)。
read_write
:可读可写。
- 是否可执行
instruct_access
:可以跑指令。某片内存区域里的这个选项为 true
那就可以用这片内存区域来跑指令,否则就不能跑指令。
- 一般不要设置允许各种 SRAM 能跑指令,而是设置只允许 FLASH 可以跑指令。免得「没权者」把指令放到 SRAM 上,然后骗你用「有权者」的身份跳转进入 SRAM 来执行「没权者」的指令。
这就开配
首先,我们打开 STM32CubeMX,在 Pinout & Configuration
界面上,找到左上角 System Core
,然后找到 CORTEX_M7
,点开它,你就能配 MPU 了。
Speculation default mode
:这个东西的英文我也看不懂,但是关键是 翻译成中文后 我还是看不懂:「推测执行默认模式」
- 其实就是是否开启「分支预测」的开关。CPU 要跑指令的时候,如果
PC
指向的内存区域被你开启了「擦车」,那 CPU 就要先把指令加载到「擦车」里,然后去执行,但是你肯定会写 if
呀,switch
呀,while
呀,for
呀,这些会导致 CPU 要跳转的语句,那 CPU 就得猜着去加载指令了。这就叫「分支预测」。绝大多数情况下,CPU 都能猜对,但是你的指令如果不停地做假动作,它就会不小心把不会被执行的指令加载到 I-cache。但是它会在做完跳转的条件判断的时候「意识到」加载的是应当被跳过或者不应当被执行的指令。然后它会重新去加载指令。也就是猜错了分支后造成少量的延迟。配合「擦车」打开「分支预测」,可以提升 CPU 的性能,至少可以做到先加载指令到 I-cache,然后再执行指令。
Cortex Interface Settings
:这里的两个选项 CPU ICache
和 CPU DCache
就是你要开启的「擦车」。统统打开。
Cortex Memory Protection Unit Control Settings
:这里用来控制「是否启用 MPU」,肯定要开呀,不然 CPU 不知道哪些内存区域可以用「擦车」,以及应该使用什么样的「擦车」策略。但是要怎么开呢?它有以下几个选项:
MPU NOT USED
不使用 MPU。一旦你选了这个,后面的其它选项全部都会消失;而如果你选成了别的选项,则你必须关掉 STM32CubeMX 再重新打开你的 .ioc
文件,才能重新看到后面的其它选项。
Background Region Access Not Allowed + MPU Disabled during hard fault, NMI and FAULTMASK handlers
Background Region Access Not Allowed + MPU Enabled during hard fault, NMI and FAULTMASK handlers
Background Region Privileged accesses only + MPU Disabled during hard fault, NMI and FAULTMASK handlers
Background Region Privileged accesses only + MPU Enabled during hard fault, NMI and FAULTMASK handlers
妈的好长。这里面的 Background Region
指的是没有被你的配置覆盖到的内存区域谁可以访问,而后面的则是在各种 fault 发生的情况下或者 NMI(不可屏蔽中断)是否启用 MPU。
这个我选的是第三个,没有覆盖到的地方只有「有权者」可以访问,而进到 fault 的时候或者 NMI 的时候关闭 MPU——这个时候擦车的效果就没了,你要是想调试一下你的程序或者处理一下 fault 的时候方便一些。
然后开始配 Cortex Memory Protection Unit Region X Settings
。这里是配置各个内存区域的地址、大小、各种属性的,MPU 会按照你配置的属性来应用「擦车」策略,权限控制,是否可读写,是否可执行。
需要注意的是:这里配置内存区域的地址和大小的时候,内存区域是可以重叠的。 当内存区域重叠的时候,你的区域号越大,优先级越高。 并且你必须连续使用区域号,不能随便用比如用了第零个,没有使用第一个,却使用第二个的话,第二个往后的是不生效的。
既然如此,那么第零个显然要配置成能 覆盖全部内存地址范围 的区域,也就是地址 0x0
,大小 4 GB
。它是第零个,优先级最低的一个,用它处理默认的行为。
MPU Region
:设为 Enabled
。
MPU Region Base Address
:设置地址。
MPU Region Size
:设置大小。
MPU SubRegion Disable
:这个是配置子区域禁用 MPU 的,具体我没看它的文档,我想的是,既然要配 MPU 的话肯定是全部都应用我的配置呀。所以设为 0x0
MPU TEX field level
:这个配的是擦车策略。PDF 讲的太复杂,我已经帮你看过了。我给你描述一下你就知道了:
level 0
:相当于 read_through
+ write_through
。适合用在不想开启擦车的区域。
level 1
:相当于 read_allocate
+ write_allocate
。适合用在可读写的区域开启擦车。
level 2
:相当于 read_allocate
+ write_through
。适合用在只读的区域开启擦车。
MPU Access Permission
:这个配的是内存区域的访问权限,是上文的「权限控制」和「是否可读写」的组合,有以下选项:
ALL ACCESS NOT PERMITTED
:不可读不可写,谁都没有权。
Privileged READS\WRITES Permission
:「有权者」可读写。
Privileged READS\WRITES Permission + Unrivileged READS Permission
:「有权者」可读写,「没权者」只读。
ALL ACCESS PERMITTED
:谁都有权读写。
Privileged READS Permission
:「有权者」只能读。
Privileged READS Permission + Unrivileged READS Permission
:「有权者」「没权者」都只能读。
MPU Instruction Access
:这片内存区域的数据能否让 CPU 当成指令来跑?也就是是否可执行。
MPU Shareability Permission
:是否共享。正点原子说「开了共享等于关了擦车」。实际上是不对的,这里的共享指的是该内存区域是否可能被多个总线Master(如CPU,DMA,或其他核心)共享,在单核系统(如H750) 中:
- 设置
Shareable
:意味着该区域数据可能被 DMA 修改,提示擦车系统内存被 DMA 动了。不等于关闭擦车。
- 不设置
Shareable
:CPU 无法感知该区域数据被 DMA 修改。此时就需要手动维护数据一致性了,调用 SCB_CleanDCache()
、SCB_CleanInvalidateDCache()
完成操作。
MPU Cacheable Permission
:是否允许擦车。这个就是控制能不能开启擦车的选项了。
MPU Bufferable Permission
:是否开启 bufferable
,我前文介绍了 bufferable
的作用,一般来讲,开启擦车的地方开启 bufferable
它运行更顺畅。
每个内存区域的选项就这些。显然第零个要配置成:
- 不开启擦车。
- 「有权者」可读写。
- 不能跑指令。
- 不启用
bufferable
。
- 要启用
shareable
。
这样的话,我们就可以不用去配置外设地址区域了,因为第零个的默认设定确实适合外设地址区域的要求。
接下来挨个配每一块内存的 MPU 保护选项。
- 第一个:给 ITCM 配,地址:
0x0
,大小 64 KB
:
- 不开启擦车(
TEX level
设为 level 0
)
- 「有权者」可读写。
- 可执行。
- 开启
bufferable
以便于写入的时候顺畅些。
- 第二个:给 AXI FLASH 配,地址:
0x08000000
,大小 128 KB
:
- 开启擦车(
TEX level
设为 level 2
)
- 「有权者」只读。
- 可执行。
- 不开启
bufferable
。
- 第三个:给 DTCM 配,地址:
0x20000000
,大小 128 KB
:
- 不开启擦车(
TEX level
设为 level 0
)
- 「有权者」可读写。
- 不可执行。
- 开启
bufferable
。
- 第四个:给 AXI SRAM 配,地址:
0x24000000
,大小:512 KB
:
- 开启擦车(
TEX level
设为 level 1
)
- 所有人都可读写。
- 不可执行。
- 开启
bufferable
。
- 第五个:给 D2 供电域的三个 SRAM 一起配,地址:
0x30000000
,大小:512 KB
(这三个 SRAM 加起来是 288 KB
,但是嘞,MPU Region Size
只允许你配置成 2 的 N 次方大小,所以只能选 512 KB
)
- 开启擦车(
TEX level
设为 level 1
)
- 「有权者」可读写。
- 不可执行。
- 开启
bufferable
。
- 第六个:给 D3 供电域的 SRAM 配,地址:
0x38000000
,大小:64 KB
:
- 第七个:给 QSPI 映射的 FLASH 配,地址:
0x90000000
,大小:你买的 FLASH 有多大你就配多大,我的是 W25Q64,大小是 8 MB
,但是我将来可能要改成更大的 FLASH,所以干脆配满一整个「银行」,也就是 256 MB
。
- 开启擦车(
TEX level
设为 level 2
)
- 「有权者」「没权者」只读。
- 可执行。
- 不开启
bufferable
。
- 这么做,是因为我考虑到我可以调度 AXI FLASH 和 ITCM DTCM 的存储区域,在软件上提供「内核态」接口,封装 API 给「用户态」调用,「用户态」使用某种方式(比如我提供编译器工具链)开发「用户态」的应用程序,不需要去碰触 STM32 的外设寄存器,然后可以使用 AXI 上的主 SRAM。「用户态」的应用程序以「没权者」身份执行。
- 第八个往后的都
Disabled
了。
生成代码
我的代码是这样的:
/* MPU Configuration */
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct = {0};
/* Disables the MPU */
HAL_MPU_Disable();
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.BaseAddress = 0x00000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_4GB;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.BaseAddress = 0x08000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL2;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RO;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Number = MPU_REGION_NUMBER3;
MPU_InitStruct.BaseAddress = 0x20000000;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Number = MPU_REGION_NUMBER4;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Number = MPU_REGION_NUMBER5;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Number = MPU_REGION_NUMBER6;
MPU_InitStruct.BaseAddress = 0x38000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/** Initializes and configures the Region and the memory to be protected
*/
MPU_InitStruct.Number = MPU_REGION_NUMBER7;
MPU_InitStruct.BaseAddress = 0x90000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256MB;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL2;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RO_URO;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enables the MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
总结
当你阅读到这里的时候,相信你通过对单片机的内存保护机制和缓存策略的学习,你会对 CPU 缓存的理解会上升到对现代个人用计算机和服务器的 CPU 缓存和操作系统的调度机制的一种新的领悟。
参考文章