STM32-01-认识单片机
STM32-02-基础知识
STM32-03-HAL库
STM32-04-时钟树
STM32-05-SYSTEM文件夹
STM32-06-GPIO
STM32-07-外部中断
STM32-08-串口
STM32-09-IWDG和WWDG
STM32-10-定时器
STM32-11-电容触摸按键
STM32-12-OLED模块
文章目录
- STM32-12-MPU
- 1. 内存保护单元MPU
- 1. MPU简介
- 2. 内存地址映射
- 3. MPU设置内存区域的访问权限
- 4. MPU配置内存区域的访问属性
- 5. 可共享 Master间数据同步
- 6. 缓存缩略
- 2. Cache简介
- 1. 简介
- 2. 读操作与写操作
- 3. 内核基本操作
- 4. 内核读取cache
- 5. 内核写入cache
- 6. 数据不一致解决办法
- 7. Cache配置相关函数
- 3. MPU相关寄存器
- 4. MPU相关HAL库驱动
- 5. MPU基本配置步骤
- 6. 代码实现
STM32-12-MPU
1. 内存保护单元MPU
1. MPU简介
-
内存保护单元:(memory protection unit),简称MPU.
-
MPU功能:
-
设置不同存储区域的存储器访问权限(特权级、用户级)
MPU可以根据特权级或用户级设置存储器区域的读、写、执行等权限,以限制对存储器的访问。
-
设置存储器(内存和外设)属性(可缓存、可缓冲、可共享)
-
-
具体功能:
- 阻止用户应用程序破坏操作系统使用的数据;
- 阻止一个任务访问其他任务的数据区,从而隔离任务;
- 把关键数据区域设置为只读,从根本上解决被破坏的可能;
- 检测意外的存储访问,如堆栈溢出、数组越界等;
- 将SRAM或RAM空间定义为不可执行,防止代码注入攻击;
- 提高嵌入式系统的健壮性,使系统更加安全。
2. 内存地址映射
内核地址映射是指将物理内存地址映射到内核空间的过程,其中MPU可以通过配置保护内存区域来实现对内核地址空间的保护和管理。
在MPU中,可以配置多个内存保护区域,通常编号为Region0到Region15(针对H7芯片),每个区域都有一定的访问属性和权限。这些区域可以用来定义内核地址空间的访问规则,以确保内核的安全性和稳定性。
针对内核地址映射,需要注意以下配置:
- 优先级和编号: MPU中每个内存保护区域都有一个编号,通常编号范围是0到15。编号越小,优先级越低,这意味着编号为15的区域具有最高的权限和访问优先级。
- 访问属性和权限: 每个内存保护区域可以配置不同的访问属性和权限,包括读、写和执行权限。可以根据需要配置这些属性,以确保不同内存区域的安全性。例如,你可以设置某个区域为只读(不可写),或禁用某些区域的执行权限以防止代码注入攻击。
- 重叠和嵌套: 如果存在多个内存保护区域并且它们发生重叠或嵌套,MPU会按照优先级高的区域的配置规则来执行。这意味着在访问重叠或嵌套区域时,遵循优先级最高的区域的访问规则,以确保内存访问的安全性。
- 背景区: MPU通常还会定义一个默认的背景区,其序号通常为-1。背景区用于定义未被其他保护区域覆盖的内存空间的访问规则,这可以确保整个内存空间的完整性,即使没有显式地定义所有区域。
通过配置MPU,可以在STM32中实现不同内存区域的访问控制,确保系统的安全性和稳定性。
3. MPU设置内存区域的访问权限
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
:只读(特权级&用户级都不可以写)。
在配置了MPU的访问权限后,如果程序尝试访问未经授权的区域或者违反了权限配置,将触发错误异常(MemManage
),系统会相应地进行处理,通常是通过异常处理机制中的相关异常处理函数进行处理,例如重启系统或者输出错误信息等。
4. MPU配置内存区域的访问属性
在配置MPU时,可以为每个内存区域指定访问属性,以确保CPU对内存的访问方式符合要求。主要的内存类型包括:
- 正常内存(Normal Memory): 正常内存类型允许CPU使用缓存和优化访问。这种类型的内存通常用于RAM和ROM。访问顺序可以被优化,以提高性能。
- 设备内存(Device Memory): 这种类型的内存用于访问外设,确保访问的顺序和原子性。设备内存禁止缓存,以确保每次访问都直接与硬件通信。这种内存访问被认为是强顺序的。
- 强排序内存(Strongly Ordered Memory): 这种类型的内存访问顺序严格,所有内存访问按照程序顺序进行,不会被重排序。这种类型通常用于控制寄存器和外设。
配置步骤:
初始化MPU:
- 启用MPU并设置默认内存访问权限。
- 可以通过函数
HAL_MPU_Enable()
启用MPU。配置内存区域:
- 使用
MPU_Region_InitTypeDef
结构体定义每个内存保护区域的属性。- 设置区域编号、起始地址、尺寸、访问权限、子区域禁用和其他属性。
- 调用
HAL_MPU_ConfigRegion()
函数配置每个区域。启用MPU保护:
- 在配置所有内存区域后,调用
HAL_MPU_Enable()
函数启用MPU保护。代码示例:
void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct; /* Disable MPU */ HAL_MPU_Disable(); /* Configure the MPU attributes as WT for SRAM */ MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x20000000; MPU_InitStruct.Size = MPU_REGION_SIZE_128KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); /* Enable MPU (Background region) */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); }
这个例子配置了一个128KB大小的SRAM区域,允许全访问权限,并且在启用MPU时启用背景区域。
三种类型对应的情景:
-
Normal Memory
应用场景:
-
用于存储一般数据和程序代码,例如RAM和ROM。
-
适用于需要高速访问和缓存优化的数据存储区域。
特点:
-
可缓存: 允许使用缓存,以提高访问速度和系统性能。
-
访问优化: 访问顺序可以被CPU优化(乱序执行),以更高效地利用总线带宽。
-
读写权限: 根据配置,可以设置为只读、只写或读写。
示例:
-
程序代码存储(Flash memory)
-
动态数据存储(SRAM)
-
-
Device Memory
应用场景:
-
用于与外设通信,例如GPIO、ADC、UART等外设寄存器。
-
适用于必须确保访问顺序和数据完整性的硬件接口。
特点:
-
非缓存: 禁止使用缓存,以确保每次访问都直接与硬件通信。
-
访问顺序严格: 保证访问顺序不被CPU重新排序,以确保硬件操作的原子性和一致性。
-
特殊处理: 适用于需要特定处理的外设寄存器,如读写时有副作用的寄存器。
示例:
-
控制寄存器(GPIO)
-
状态寄存器(UART状态寄存器)
-
-
Strongly Ordered Memory
应用场景:
-
用于需要严格访问顺序的关键系统部分,例如控制寄存器和重要外设寄存器。
-
适用于多处理器系统中的共享资源管理。
特点:
-
严格访问顺序: 所有内存访问严格按程序顺序执行,不允许任何形式的重新排序。
-
数据完整性: 确保多处理器系统中访问共享资源时数据的完整性。
-
非缓存: 通常禁止缓存,确保每次访问的准确性和一致性。
示例:
-
多处理器系统中的共享控制寄存器
-
高优先级系统任务中的关键配置寄存器
-
5. 可共享 Master间数据同步
在多处理器系统或多核系统中确保数据同步和一致性是非常重要的。以下是一些常见的方法:
- 锁机制(Locking):锁是一种最基本的同步机制,它可以确保在任何时刻只有一个线程可以访问共享资源。当一个核心想要访问共享资源时,它会请求获取锁,如果锁已被其他核心持有,则该核心会被阻塞直到锁可用。
- 信号量(Semaphore):信号量是一种更加灵活的同步机制,它允许多个线程同时访问共享资源,但是限制同时访问资源的线程数量。信号量维护一个计数器,当一个核心想要访问资源时,它首先尝试对信号量进行加锁,如果计数器大于零,则可以继续访问资源,并将计数器减一;否则,线程将被阻塞直到计数器变为正数。
- 条件变量(Condition Variables):条件变量允许线程在某些条件下等待或者被唤醒。在多处理器系统中,条件变量常与锁结合使用,以实现更复杂的同步机制。当一个核心等待某个条件时,它会释放锁并进入睡眠状态,直到其他核心改变了条件并唤醒了等待线程。
- 原子操作(Atomic Operations):原子操作是一种特殊的指令序列,可以在不被中断的情况下执行。在多处理器系统中,原子操作通常用于实现简单的同步操作,比如对共享变量的增加或减少。原子操作保证了这些操作的原子性,即它们要么完全执行成功,要么完全不执行,不会出现部分执行的情况。
- 屏障(Barrier):屏障用于确保在多个线程中的所有操作都完成之后才能继续执行下一步操作。在多处理器系统中,屏障可以用于同步多个核心的执行流,确保它们在某个点上同时到达并继续执行。
- 读-写锁(Read-Write Lock):读-写锁允许多个线程同时读取共享资源,但是只有一个线程可以写入共享资源。在多处理器系统中,读-写锁可以提高并发性能,因为多个线程可以同时读取资源而无需互斥。
- 共享内存: 共享内存是一种允许多个核心共享相同物理内存区域的机制。通过共享内存,不同核心可以直接访问同一块内存,从而实现数据的共享和同步。
这些方法通常会根据具体的应用场景和性能需求进行选择和组合,以实现高效的数据同步和一致性保证。
6. 缓存缩略
- Write Through(写透方式): 每次写操作会同步到内存和缓存,可以确保数据一致性,但写操作的性能较低,因为需要等待数据写入到内存后才能继续执行。
- Write Back(写回方式): 写操作首先写入到缓存,然后根据一定策略(如LRU)将数据写回到内存。写回方式可以提高写操作的性能,但可能会导致缓存和内存数据不一致的情况发生。
- No Write Allocate(非写分配方式): 写操作直接写入到内存,不会分配缓存行。这种方式可以减少缓存污染,但会增加内存访问次数,影响性能。
- Read Allocate(读分配方式): 当读操作发生时,如果数据不在缓存中,会将数据加载到缓存中。这种方式可以提高读操作的性能,但会增加缓存的占用。
2. Cache简介
1. 简介
M7内核芯片添加了一级缓存支持,这是个很不错的改进。一级缓存通常用于存储最频繁使用的数据和指令,以提高处理器的性能和效率。这种设计利用了局部性原理,即程序倾向于访问相邻的内存位置。D-Cache
用于存储数据,而I-Cache
则用于存储指令,这样可以更好地优化数据和指令的访问。这种优化可以显著提高处理器的运行速度,尤其是对于需要频繁访问内存的任务来说。
-
数据缓存(D-Cache):
- 数据缓存主要用于存储程序执行期间使用的数据。当处理器需要访问内存中的某个数据时,它首先会检查数据缓存。如果数据在缓存中已经存在(即命中),处理器就可以直接从缓存中读取数据,而不必访问主内存。如果数据不在缓存中(即缓存未命中),处理器就需要从主内存中读取数据,并将其加载到缓存中,以便日后的访问。
-
指令缓存(I-Cache):
- 指令缓存主要用于存储程序的指令,即处理器执行的操作序列。当处理器需要执行某个指令时,它会从指令缓存中获取指令。如果指令在缓存中已经存在,处理器就可以直接执行它,而不必从主内存中读取。如果指令不在缓存中,处理器就需要从主内存中读取指令,并将其加载到缓存中以供后续执行。
2. 读操作与写操作
关键点:
- 命中(Cache Hit):当处理器需要访问的数据或指令在缓存中存在时,发生命中。
- 未命中(Cache Miss):当处理器需要访问的数据或指令在缓存中不存在时,发生未命中,处理器需要从主内存中读取数据或指令。
- 替换策略:当缓存已满且需要将新的数据或指令加载到缓存中时,缓存控制器会根据一定的替换策略选择要替换的缓存行,常见的替换策略包括最近最少使用(LRU)和先进先出(FIFO)等。
- 写策略:数据缓存通常有写回(Write-Back)和写直通(Write-Through)两种写策略,写回会先在缓存中更新数据,然后在某个时机将更新的数据写回主内存,而写直通则会同时更新缓存和主内存。
- 一致性:缓存一致性是指处理器对缓存中的数据进行更新时,要确保所有缓存中的副本都得到相应的更新,以保证数据的一致性。
通过使用数据缓存和指令缓存,处理器可以显著提高程序的执行速度,因为缓存可以提供比主内存更快的访问速度,减少了处理器访问主内存的频率,从而减少了存储系统的瓶颈。
3. 内核基本操作
在STM32系列的M7内核中,Cache支持以下基本操作:
- Invalidate(无效化缓存):
- 无效化操作用于使缓存中的特定数据失效。这通常在其他系统组件(如DMA控制器)更新了内存中的数据时使用,以确保处理器从内存中获取最新数据而不是使用缓存中的旧数据。
- 在STM32的M7内核中,可以通过无效化指令来无效化整个缓存或特定的缓存行。无效化操作不会将缓存中的数据写回到主内存,只是标记为无效。
- Clean(清理缓存):
- 清理操作将缓存中的脏数据(已修改但尚未写回主内存的数据)写回到主内存,但不使缓存行无效。这有助于确保主内存中的数据是最新的,但缓存行仍然保持有效状态。
- 该操作可以通过清理指令实现,可以针对整个缓存或特定的缓存行进行清理。
- Clean and Invalidate(清理并无效化缓存):
- 这是清理和无效化的组合操作。它将缓存中的脏数据写回到主内存,然后使这些缓存行无效。此操作确保数据一致性,并在不需要保留缓存数据时使用。
- 该操作可以通过清理并无效化指令进行,可以针对整个缓存或特定的缓存行进行。
- Enable/Disable(启用/禁用缓存):
- 启用操作会打开缓存,使其开始工作并缓存数据和指令。禁用操作会关闭缓存,处理器将直接访问主内存而不经过缓存。
- 启用和禁用缓存通常通过设置缓存控制寄存器(如CM7的控制寄存器)来实现。
4. 内核读取cache
Cache Hit (命中):
当 CPU 需要读取的数据位于 Cache 中,并且 Cache 中已经加载了该数据时,发生 Cache Hit。
在 Cache Hit 的情况下,CPU 可以直接从 Cache 中读取数据,而无需访问主存,这样可以大大提高读取速度。
Cache Miss (未命中):
当 CPU 需要读取的数据位于主存中,而不在 Cache 中时,发生 Cache Miss。
在 Cache Miss 的情况下,需要从主存中加载数据到 Cache,以便 CPU 可以读取。
有两种常见的处理方式:
Read Through (直接读取): CPU 直接从主存中读取数据,而不将数据加载到 Cache 中。这样做可以确保数据的一致性,但可能会降低读取速度,因为需要额外的主存访问。
Read Allocate (读取分配): CPU 将缺失的数据加载到 Cache 中,然后再从 Cache 中读取数据。这样做可以提高读取速度,因为之后的读取可以直接从 Cache 中进行,但可能会导致 Cache 中的其他数据被替换掉。
5. 内核写入cache
在STM32系列的M7内核中,缓存(Cache)支持多种操作方式,这些操作方式在缓存命中(Cache Hit)和未命中(Cache Miss)的情况下会有所不同。具体而言,可以根据不同的写策略来处理写操作。这些策略包括写透(Write Through)和写回(Write Back)策略,以及在缓存未命中的情况下的写分配(Write Allocate)和非写分配(No Write Allocate)策略。
Cache Hit(命中)操作
-
Write Through(写透):
- 操作: 在缓存命中的情况下,数据同时写入到缓存和内存。
- 优点: 保持内存和缓存的一致性,确保内存中的数据始终是最新的。
- 缺点: 写操作速度较慢,因为每次写操作都需要同时更新内存。
-
Write Back(写回):
- 操作: 在缓存命中的情况下,数据仅写入缓存,而不立即写入内存。只有当缓存行被替换或被明确要求写回时,才会将数据写入内存。
- 优点: 提高写操作的速度,因为写操作只需更新缓存,不需要立即访问内存。
- 缺点: 需要额外的机制来确保缓存和内存之间的一致性,这可能增加系统的复杂度。
Cache Miss(未命中)操作
-
Write Allocate(写分配):
- 操作: 在缓存未命中的情况下,将所需的数据块从内存加载到缓存,然后再执行写入操作。
- 优点: 未来对同一数据块的写操作可以在缓存中直接进行,提高后续的写入速度。
- 缺点: 可能会增加第一次写入的延迟,因为需要先从内存加载数据到缓存。
-
No Write Allocate(非写分配):
- 操作: 在缓存未命中的情况下,直接将数据写入内存,而不将数据块加载到缓存。
- 优点: 避免了加载数据块到缓存的额外开销,适用于写操作频率较低的情况。
- 缺点: 未来对同一数据块的写操作仍需直接访问内存,可能会降低后续写入操作的速度。
6. 数据不一致解决办法
解决数据不一致性问题的两种主要方法分别是设置共享属性和软件进行缓存维护。下面是对这两种方法的详细分析:
-
设置共享属性
-
优点
-
简单直接: 通过将内存区域设置为共享属性,可以确保所有核心或外设都能够看到最新的数据。这个方法通过硬件机制保证数据一致性,而不需要额外的软件干预。
-
保证一致性: 所有访问该内存区域的操作都会直接访问主存,从而确保数据的一致性。
-
-
缺点
- 性能下降: Cache相当于没有开启,因为所有的读写操作直接访问内存,无法利用Cache提升访问速度。特别是在频繁访问内存的场景下,性能可能会显著下降。
- 浪费Cache资源: Cache的优势无法得到发挥,这在处理器设计中是一个浪费,因为Cache本来就是为了提升性能而设计的。
-
-
软件进行Cache维护
2.1 Clean(清空)
-
优点
-
数据一致性: 在数据被修改后,通过Clean操作将Cache中的相应数据写回内存,可以确保内存中的数据与Cache中的数据保持一致。这在需要确保数据一致性的关键操作中非常有用。
-
保持性能: Clean操作后,Cache依然可以继续使用,之后的访问仍然可以从Cache中受益。
-
-
缺点
-
额外复杂性: 需要额外的软件操作来维护Cache,增加了系统的复杂性。
-
性能损失: Clean操作会增加额外的开销,特别是在频繁执行的情况下,可能导致性能损失。
-
2.2 Invalidate(无效化)
-
优点
-
数据一致性: 当内存中的数据被修改但Cache中的数据尚未更新时,通过Invalidate操作使Cache中的数据无效,以便从内存中重新加载最新的数据,从而确保数据一致性。
-
保持性能: 在Invalidate操作之后,Cache依然可以继续使用,后续访问仍然可以从Cache中受益。
-
-
缺点
- 额外复杂性: 需要额外的软件操作来维护Cache,增加了系统的复杂性。
- 性能损失: Invalidate操作会增加额外的开销,特别是在频繁执行的情况下,可能导致性能损失。
总结
设置共享属性是一种简单直接的方法,通过硬件机制确保数据一致性,但无法利用Cache的性能提升优势,适用于需要绝对数据一致性且对性能要求不高的场景。
软件进行Cache维护则允许在使用Cache的同时,通过Clean和Invalidate操作来确保数据一致性。虽然增加了系统的复杂性和一些性能开销,但在需要高性能且仍需确保数据一致性的场景中,这是更为灵活和高效的方法。
选择哪种方法取决于具体的应用场景和系统要求。如果数据一致性至关重要且对性能要求较低,可以选择设置共享属性。如果需要高性能且可以接受一定的复杂性,则可以选择通过软件进行Cache维护。
-
7. Cache配置相关函数
I-Cache 相关函数
SCB_EnableICache
: 启用指令缓存(I-Cache) ;
SCB_DisableICache
: 禁用指令缓存(I-Cache) ;
SCB_InvalidateICache
: 使指令缓存(I-Cache)无效,将其中的所有条目标记为无效 ;D-Cache 相关函数
SCB_EnableDCache
: 启用数据缓存(D-Cache) ;
SCB_DisableDCache
: 禁用数据缓存(D-Cache) ;
SCB_InvalidateDCache
: 使数据缓存(D-Cache)无效,将其中的所有条目标记为无效 ;
SCB_CleanDCache
: 清理数据缓存(D-Cache),将其中的已更改数据写回到主存中 ;
SCB_CleanInvalidateDCache
: 清理并使数据缓存(D-Cache)无效,即执行清理和使无效两个操作 ;
SCB_InvalidateDCache_by_addr
: 通过地址范围使数据缓存(D-Cache)无效 ;
SCB_CleanDCache_by_addr
: 通过地址范围清理数据缓存(D-Cache) ;
SCB_CleanInvalidateDCache_by_add
: 通过地址范围清理并使数据缓存(D-Cache)无效。
3. MPU相关寄存器
寄存器 | 名称 | 功能 |
---|---|---|
MPU_TYPE | MPU类型寄存器 | 用于指明MPU的一些特征 (是否支持MPU、支持多少个Region) |
MPU_CTRL | MPU控制寄存器 | 设置MPU使能 |
MPU_RNR | MPU区域编号寄存器 | 用于选择下一个要配置的region |
MPU_RBAR | MPU基地址寄存器 | 用于设置区域的起始地址 |
MPU_RASR | MPU区域属性和容量寄存器 | 用于设置每个区域的属性 |
-
MPU_TYPE
寄存器
-
MPU_CTRL
寄存器
-
MPU_RNR
寄存器
-
MPU_RBAR
寄存器
-
MPU_RASR
寄存器
-
AP 相关位控制数据的访问权限
-
TEX用来设置Cache策略
在STM32系列的M7内核中,缓存策略的选择对于性能优化和数据一致性维护非常重要。不同的缓存策略有不同的特性和应用场景。以下是对几种常见缓存策略的详细说明:
- Non-cacheable(非缓存)
- 描述: 数据不会被缓存在缓存中,每次访问都会直接读取或写入主存。
- 特性:
- 每次访问主存: 每次数据访问都是直接与主存进行,不经过缓存。
- 数据一致性: 保证数据一致性,因为所有数据访问都是直接访问主存。
- 性能: 可能较低,因为每次访问都要经过较慢的主存。
- 适用场景: 需要绝对数据一致性且对性能要求不高的场景。
- Write-through, read-allocated, no-write-allocate(写透传,读分配,不写分配)
- 描述:
- 写操作: 直接写入主存,并通过缓存透传到主存。
- 读操作: 在缓存中未命中时会分配缓存。
- 特性:
- 写操作: 每次写操作都会直接写入主存,保证内存与缓存数据一致。
- 读操作: 在缓存未命中时,会从主存加载数据到缓存中。
- 性能: 写操作性能较低,因为每次写都需要访问主存;读操作性能较好,命中缓存时可以快速读取。
- 适用场景: 需要高读性能,同时需要确保写操作数据一致性的场景。
- Write-back, read-allocated, no-write-allocate(写回,读分配,不写分配)
- 描述:
- 写操作: 先写入缓存,并标记为脏数据,如果缓存中有相应数据块。
- 读操作: 在缓存未命中时会分配缓存。
- 特性:
- 写操作: 写入缓存并标记为脏数据,提高写操作的性能,不立即写入主存。
- 读操作: 在缓存未命中时,从主存加载数据到缓存中。
- 性能: 写操作性能高,因为多数写操作在缓存中完成;读操作性能也高,未命中时会分配缓存。
- 适用场景: 写操作频繁且对写操作性能有较高要求的场景。
- Write-back, read-allocated, write-allocate(写回,读分配,写分配)
- 描述:
- 写操作: 先写入缓存并标记为脏数据,如果缓存中有相应数据块;如果没有,则分配缓存并写入。
- 读操作: 在缓存未命中时会分配缓存。
- 特性:
- 写操作: 写入缓存并标记为脏数据,未命中时分配缓存,确保缓存命中率。
- 读操作: 在缓存未命中时,从主存加载数据到缓存中。
- 性能: 写操作和读操作性能都很高,因为缓存可以容纳更多的数据块,减少主存访问。
- 适用场景: 读写操作都频繁且对性能有较高要求的场景。
总结
选择适当的缓存策略取决于应用的具体需求:
- Non-cacheable: 适用于需要绝对数据一致性且性能要求不高的场景。
- Write-through, read-allocated, no-write-allocate: 适用于需要高读性能且写操作需要确保数据一致性的场景。
- Write-back, read-allocated, no-write-allocate: 适用于写操作频繁且对写操作性能有较高要求的场景。
- Write-back, read-allocated, write-allocate: 适用于读写操作都频繁且对性能有较高要求的场景。
-
4. MPU相关HAL库驱动
驱动函数 | 关联寄存器 | 功能描述 |
---|---|---|
HAL_MPU_Enable(…) | CTRL | MPU使能 |
HAL_MPU_Disable(…) | CTRL | MPU失能 |
HAL_MPU_ConfigRegion(…) | RASR\RBAR\RNR | 配置MPU参数 |
5. MPU基本配置步骤
-
禁止MPU
void HAL_MPU_Disable();
-
配置某个区域的MPU保护参数
void HAL_MPU_ConfigRegion()
-
使能MPU
void HAL_MPU_Enable();
-
编写MemManage中断服务函数
void MemManage_Handler(void)
MPU基本配置步骤:
-
禁用MPU
-
在配置MPU之前,确保MPU处于禁用状态。
MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
-
-
配置MPU区域
-
MPU支持多个保护区域,每个区域可以单独配置。以下是配置MPU区域的步骤:
-
选择区域
MPU->RNR = region_number; // 选择要配置的区域编号
-
配置区域基地址
MPU->RBAR = base_address & MPU_RBAR_ADDR_Msk; // 设置区域基地址
-
配置区域属性
- 访问权限(AP)
- 缓存策略
- 共享属性
- 子区域禁用
MPU->RASR = (attribute << MPU_RASR_AP_Pos) | // 访问权限 (cache_policy << MPU_RASR_C_Pos) | // 缓存策略 (shareable << MPU_RASR_S_Pos) | // 共享属性 (subregion << MPU_RASR_SRD_Pos) | // 子区域禁用 (size << MPU_RASR_SIZE_Pos) | // 区域大小 MPU_RASR_ENABLE_Msk; // 使能区域
-
-
使能MPU
-
在所有区域配置完成后,启用MPU。
-
启用背景区域(可选)
MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk; // 启用默认背景区域(可选)
-
启用MPU
MPU->CTRL |= MPU_CTRL_ENABLE_Msk; // 启用MPU
-
-
刷新指令和数据缓存
-
在配置完成并启用MPU后,刷新指令和数据缓存以确保所有更改生效。
__DSB(); __ISB();
-
6. 代码实现
-
设置某个区域的MPU保护函数
uint8_t mpu_set_protection(uint32_t baseaddr, uint32_t size, uint32_t rnum, uint8_t de, uint8_t ap, uint8_t sen, uint8_t cen, uint8_t ben) { MPU_Region_InitTypeDef mpu_region_init_handle; /* MPU初始化句柄 */ HAL_MPU_Disable(); /* 配置MPU之前先关闭MPU,配置完成以后在使能MPU */ mpu_region_init_handle.Enable = MPU_REGION_ENABLE; /* 使能该保护区域 */ mpu_region_init_handle.Number = rnum; /* 设置保护区域 */ mpu_region_init_handle.BaseAddress = baseaddr; /* 设置基址 */ mpu_region_init_handle.Size = size; /* 设置保护区域大小 */ mpu_region_init_handle.SubRegionDisable = 0X00; /* 禁止子区域 */ mpu_region_init_handle.TypeExtField = MPU_TEX_LEVEL0; /* 设置类型扩展域为level0 */ mpu_region_init_handle.AccessPermission = ap; /* 设置访问权限 */ mpu_region_init_handle.DisableExec = de; /* 是否允许指令访问 */ mpu_region_init_handle.IsShareable = sen; /* 是否允许共用 */ mpu_region_init_handle.IsCacheable = cen; /* 是否允许cache */ mpu_region_init_handle.IsBufferable = ben; /* 是否允许缓冲 */ HAL_MPU_ConfigRegion(&mpu_region_init_handle); /* 配置MPU */ HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); /* 开启MPU */ return 0; }
-
设置需要保护的存储块函数
void mpu_memory_protection(void) { /* 保护整个DTCM,共128K字节,允许指令访问,禁止共用,允许cache,允许缓冲 */ mpu_set_protection(0x20000000, MPU_REGION_SIZE_128KB, MPU_REGION_NUMBER1, MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE); /* 保护整个AXI SRAM,共512K字节,允许指令访问,禁止共用,允许cache,允许缓冲 */ mpu_set_protection(0x24000000, MPU_REGION_SIZE_512KB,MPU_REGION_NUMBER2, MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE); /* 保护整个SRAM1~SRAM3,共288K字节,允许指令访问,禁止共用,允许cache,允许缓冲 */ mpu_set_protection(0x30000000, MPU_REGION_SIZE_512KB,MPU_REGION_NUMBER3, MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE); /* 保护整个SRAM4,共64K字节,允许指令访问,禁止共用,允许cache,允许缓冲 */ mpu_set_protection(0x38000000, MPU_REGION_SIZE_64KB, MPU_REGION_NUMBER4, MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE); /* 保护MCU LCD屏所在的FMC区域,,共64M字节,允许指令访问,禁止共用,禁止cache,禁止缓冲 */ mpu_set_protection(0x60000000, MPU_REGION_SIZE_64MB, MPU_REGION_NUMBER5, MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_NOT_CACHEABLE, MPU_ACCESS_NOT_BUFFERABLE); /* 保护SDRAM区域,共64M字节,允许指令访问,禁止共用,允许cache,允许缓冲 */ mpu_set_protection(0XC0000000, MPU_REGION_SIZE_64MB, MPU_REGION_NUMBER6, MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE); /* 保护整个NAND FLASH区域,共256M字节,禁止指令访问,禁止共用,禁止cache,禁止缓冲 */ mpu_set_protection(0X80000000, MPU_REGION_SIZE_256MB, MPU_REGION_NUMBER7, MPU_INSTRUCTION_ACCESS_DISABLE, MPU_REGION_FULL_ACCESS, MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_NOT_CACHEABLE, MPU_ACCESS_NOT_BUFFERABLE); }
-
MemManage错误处理中断
void MemManage_Handler(void) { LED1(0); /* 点亮LED1(GREEN LED) */ printf("Mem Access Error!!\r\n"); /* 输出错误信息 */ delay_ms(1000); printf("Soft Reseting...\r\n"); /* 提示软件重启 */ delay_ms(1000); NVIC_SystemReset(); /* 软复位 */ }
-
主函数
#include "stdlib.h" #include "./SYSTEM/sys/sys.h" #include "./SYSTEM/usart/usart.h" #include "./SYSTEM/delay/delay.h" #include "./BSP/LED/led.h" #include "./BSP/KEY/key.h" #include "./BSP/MPU/mpu.h" #if !(__ARMCC_VERSION >= 6010050) /* 不是AC6编译器,即使用AC5编译器时 */ uint8_t mpudata[128] __attribute__((at(0X20002000))); /* 定义一个数组 */ #else uint8_t mpudata[128] __attribute__((section(".bss.ARM.__at_0X20002000"))); /* 定义一个数组 */ #endif int main(void) { uint8_t key = 0; uint8_t t = 0; sys_cache_enable(); /* 打开L1-Cache */ HAL_Init(); /* 初始化HAL库 */ sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */ delay_init(480); /* 延时初始化 */ usart_init(115200); /* 串口初始化为115200 */ led_init(); /* 初始化LED */ key_init(); /* 初始化按键 */ printf("\r\n\r\nMPU closed!\r\n"); /* 提示MPU关闭 */ mpu_memory_protection(); while (1) { key = key_scan(0); if (key == WKUP_PRES) /* 使能MPU保护数组 mpudata */ { mpu_set_protection(0X20002000, MPU_REGION_SIZE_128B, MPU_REGION_NUMBER0, MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_PRIV_RO_URO, MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_NOT_CACHEABLE, MPU_ACCESS_BUFFERABLE); /* 只读,禁止共用,禁止cache,允许缓冲 */ printf("MPU open!\r\n"); /* 提示MPU打开 */ } else if (key == KEY0_PRES) /* 向数组中写入数据,如果开启了MPU保护的话会进入内存访问错误! */ { printf("Start Writing data...\r\n"); sprintf((char *)mpudata, "MPU test array %d", t); printf("Data Write finshed!\r\n"); } else if (key == KEY1_PRES) /* 从数组中读取数据,不管有没有开启MPU保护都不会进入内存访问错误! */ { printf("Array data is:%s\r\n", mpudata); } else { delay_ms(10); } t++; if ((t % 50) == 0) { LED0_TOGGLE(); /* LED0取反 */ } } }
声明:资料来源(战舰STM32F103ZET6开发板资源包)
- Cortex-M3权威指南(中文).pdf
- STM32F10xxx参考手册_V10(中文版).pdf
- STM32F103 战舰开发指南V1.3.pdf
- STM32F103ZET6(中文版).pdf
- 战舰V4 硬件参考手册_V1.0.pdf