0xAA55 发表于 前天 07:50

【STM32】STM32H750 配置 MPU 开启「擦车」,使用高速缓存加快运行速度

# 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 缓存和操作系统的调度机制的一种新的领悟。

[参考文章](https://blog.csdn.net/m0_55174167/article/details/144305718)
页: [1]
查看完整版本: 【STM32】STM32H750 配置 MPU 开启「擦车」,使用高速缓存加快运行速度