【正点原子STM32】内存保护单元(MPU)实验(内核地址映射、MPU设置内存区域的访问权限和属性、三种内存类型、Cache缓存、MPU相关寄存器介绍、MPU相关HAL库驱动、MPU基本配置步骤)

发布于:2024-05-14 ⋅ 阅读:(170) ⋅ 点赞:(0)

一、内存保护单元(MPU)介绍

二、Cache简介

三、MPU相关寄存器介绍

四、MPU相关HAL库驱动介绍

五、MPU基本配置步骤
六、编程实战

七、总结

一、内存保护单元(MPU)介绍

在这里插入图片描述
内存保护单元(Memory Protection Unit,MPU)是一种硬件单元,常用于嵌入式系统中,用于管理存储器的访问权限和属性。MPU的功能包括:

  1. 设置不同存储区域的存储器访问权限:MPU可以根据特权级或用户级设置存储器区域的读、写、执行等权限,以限制对存储器的访问。

  2. 设置存储器属性:MPU可以设置存储器(包括内存和外设)的属性,如可缓存、可缓冲、可共享等,以优化存储器的使用方式。

  3. 管理权限和通行规则/路径:MPU可以定义存储器区域之间的访问规则和通行路径,以确保数据的安全性和系统的稳定性。

具体好处包括:

  1. 防止用户应用程序破坏操作系统使用的数据:通过限制用户程序对操作系统数据的访问权限,可以防止用户程序对系统关键数据的非法操作。

  2. 隔离任务:MPU可以限制一个任务访问其他任务的数据区,从而有效地隔离任务,提高系统的安全性和稳定性。

  3. 设置关键数据区域为只读:将关键数据区域设置为只读可以防止数据被非法篡改,从根本上保护关键数据的安全性。

  4. 检测意外的存储访问:MPU可以检测到意外的存储访问,如堆栈溢出、数组越界等,从而及时发现并处理异常情况,提高系统的健壮性。

  5. 防止代码注入攻击:将存储器空间定义为不可执行可以防止恶意代码注入并执行,提高系统的安全性。

综上所述,MPU可以提高嵌入式系统的健壮性和安全性,使系统更加稳定和可靠。

1.1、内核地址映射

在这里插入图片描述
内核地址映射是指将物理内存地址映射到内核空间的过程,其中MPU(Memory Protection Unit)可以通过配置保护内存区域来实现对内核地址空间的保护和管理。

在MPU中,可以配置多个内存保护区域,通常编号为Region0到Region15,每个区域都有一定的访问属性和权限。这些区域可以用来定义内核地址空间的访问规则,以确保内核的安全性和稳定性。

以下是一般情况下的内核地址映射配置:

  1. 优先级和编号:MPU中的内存保护区域通常按照优先级从0到15进行编号,优先级越高的区域具有更高的权限和访问优先级。

  2. 访问属性和权限:每个内存保护区域可以配置访问属性和权限,包括读、写、执行等。这些属性可以根据需要进行配置,以满足内核的安全要求。

  3. 重叠和嵌套:如果存在多个内存保护区域并且它们发生重叠或嵌套关系,MPU会按照优先级高的区域的配置规则来执行。这样可以确保对重叠或嵌套区域的访问符合更高优先级的规则。

  4. 背景区:MPU通常还会定义一个默认的背景区(default region),其序号通常为-1。背景区可以用来定义未被其他区域覆盖的内存空间的访问规则,确保整个内存空间的完整性。

通过合理配置MPU的内存保护区域,可以有效地保护内核地址空间,防止未经授权的访问和恶意操作,从而提高系统的安全性和稳定性。

1.2、MPU设置内存区域的访问权限

在这里插入图片描述
MPU(Memory Protection Unit)可以通过设置内存区域的访问权限来控制特权级和用户级的访问。以下是常见的几种访问权限配置:

  1. MPU_REGION_NO_ACCESS:该权限配置表示内存区域不允许任何访问,即特权级和用户级都无法进行读取或写入操作。

  2. MPU_REGION_PRIV_RW:此配置仅支持特权级的读写访问,用户级无法访问。

  3. MPU_REGION_PRIV_RW_URO:该配置允许特权级读写访问,但禁止用户级写入访问。用户级只能读取数据。

  4. MPU_REGION_FULL_ACCESS:该权限配置允许特权级和用户级都可以进行完全访问,包括读取和写入操作。

  5. MPU_REGION_PRIV_RO:此配置仅支持特权级的读取访问,用户级无法进行写入操作。

  6. MPU_REGION_PRIV_RO_URO:该配置允许特权级进行只读访问,同时禁止用户级写入访问。

在配置了MPU的访问权限后,如果程序尝试访问未经授权的区域或者违反了权限配置,将触发错误异常(MemManage),系统会相应地进行处理,通常是通过异常处理机制中的相关异常处理函数进行处理,例如重启系统或者输出错误信息等。

通过合理配置MPU的内存区域访问权限,可以有效地保护系统的安全性和稳定性,防止非法访问和潜在的安全风险。

1.3、MPU配置内存区域的访问属性

在这里插入图片描述
在配置MPU时,可以为每个内存区域指定访问属性,以确保CPU对内存的访问方式符合要求。主要的内存类型包括:

  1. Normal Memory(普通内存):适用于ROM、FLASH、SRAM等常规内存,CPU对于这种内存的加载或存储操作不需要严格按照程序代码的顺序执行。这种内存的访问属性可以使CPU以最高效的方式加载和存储字节、半字和字,从而提高系统的性能。

  2. Device Memory(设备内存):主要用于外设的存储器,对于这种内存区域的加载和存储操作必须严格按照次序进行,确保寄存器按照正确顺序设置。这样可以确保外设的操作正确执行,防止出现数据错位或不完整的情况。

  3. Strongly Ordered Memory(严格顺序内存):程序完全按照代码顺序执行,CPU会等待当前加载存储执行完毕后才执行下一条指令,导致性能下降。这种内存的访问属性适用于对内存访问顺序有严格要求的场景,确保操作的正确性和可靠性。

通过为不同的内存区域配置合适的访问属性,可以确保CPU的访问行为符合内存的特性和要求,从而保证系统的稳定性、可靠性和性能表现。

1.4、三种内存类型对应的情景

在这里插入图片描述
对于这三种内存类型,它们在系统中的应用场景和特点如下:

  1. Normal Memory(普通内存)

    • 应用场景: 适用于ROM(只读存储器)、FLASH存储器和SRAM(静态随机存储器)等常规内存。
    • 特点: CPU对于这种内存的加载或存储操作不需要严格按照程序代码的顺序执行。数据可以被缓存到CPU的缓存中,或者在缓冲区中暂存,然后再写入到内存中,以实现更高效的数据访问和处理。
  2. Device Memory(设备内存)

    • 应用场景: 适用于外设的寄存器、控制寄存器等设备相关的存储器。
    • 特点: 对于这种内存区域的加载和存储操作必须严格按照次序进行,确保寄存器按照正确的顺序设置。数据通常会经过缓冲区,然后直接写入到设备内存中,以确保外设的操作正确执行,防止出现数据错位或不完整的情况。
  3. Strongly Ordered Memory(严格顺序内存)

    • 应用场景: 适用于对内存访问顺序有严格要求的场景,如内核代码、关键数据结构等。
    • 特点: CPU对这种内存的访问必须按照程序代码的顺序执行,不能乱序执行。每个存储器访问操作都必须严格按照代码顺序执行,CPU会等待当前操作完成后才执行下一条指令。这种内存的访问属性保证了操作的正确性和可靠性,但会导致性能下降,因为CPU需要等待存储器操作完成后才能继续执行下一条指令。

通过合理地选择和配置这三种内存类型,可以满足不同场景下对内存访问的要求,提高系统的性能、稳定性和可靠性。

1.5、可共享 Master间数据同步

在这里插入图片描述
在计算机系统中,特别是在多处理器系统或多核系统中,当存在多个处理器核心(或处理器)时,可能需要进行不同核心之间的数据同步,以确保数据的一致性和正确性。这种情况下,通常会使用一些技术和机制来实现Master间的数据同步,以下是一些常见的方法:

  1. 锁机制: 使用锁机制是一种常见的数据同步方法。通过在共享数据上加锁,可以确保同一时刻只有一个核心可以访问该数据,其他核心需要等待锁释放后才能进行访问。常见的锁包括互斥锁、自旋锁、读写锁等。

  2. 原子操作: 原子操作是不可中断的操作,能够保证在执行过程中不会被其他操作中断。原子操作通常用于对共享数据的更新操作,可以确保更新的完整性,防止出现并发访问导致的数据不一致问题。

  3. 信号量: 信号量是一种用于控制对共享资源访问的同步机制。它可以用来限制同时访问共享资源的核心数量,以及控制核心之间的等待和唤醒操作。

  4. 消息传递: 通过消息传递的方式,不同核心之间可以进行数据交换和通信。这种方式可以通过消息队列、邮箱等机制来实现数据同步和通信,确保数据的一致性和正确性。

  5. 屏障(Barrier): 屏障是一种同步机制,用于确保所有核心在某个点上同时到达并执行某个操作之前,都会被阻塞等待。屏障通常用于控制并发执行的流程,以确保所有核心在某个关键点上同步执行。

  6. 共享内存: 共享内存是一种允许多个核心共享相同物理内存区域的机制。通过共享内存,不同核心可以直接访问同一块内存,从而实现数据的共享和同步。

这些方法和机制可以根据具体的系统架构和需求来选择和应用,以实现Master间的数据同步和共享,确保系统的正确性和性能。

1.6、不同配置下(访问属性: 内存类型,是否缓存,是否缓冲,是否共享),性能情况

在这里插入图片描述
不同配置下的性能情况受多种因素影响,包括内存类型、是否缓存、是否缓冲、是否共享等因素,以及cache策略的选择。下面是针对不同配置的性能情况的一般分析:

  1. 内存类型:

    • Normal Memory(普通内存): 通常情况下,普通内存具有较高的访问速度和较低的成本,适合用于大多数数据存储需求。对于普通内存,性能取决于具体的访问模式、内存带宽以及内存控制器的性能等因素。
    • Device Memory(设备内存): 设备内存通常用于连接外部设备或者特殊用途的存储,其性能可能受到设备本身性能的影响。对于设备内存,性能取决于设备的读写速度以及设备控制器的性能等因素。
    • Strongly Ordered Memory(严格顺序内存): 严格顺序内存要求程序按照顺序执行,性能较低,因为CPU会等待当前加载存储执行完毕后才执行下一条指令,导致性能下降。
  2. 缓存策略:

    • Write Through(写透方式): 每次写操作会同步到内存和缓存,可以确保数据一致性,但写操作的性能较低,因为需要等待数据写入到内存后才能继续执行。
    • Write Back(写回方式): 写操作首先写入到缓存,然后根据一定策略(如LRU)将数据写回到内存。写回方式可以提高写操作的性能,但可能会导致缓存和内存数据不一致的情况发生。
    • No Write Allocate(非写分配方式): 写操作直接写入到内存,不会分配缓存行。这种方式可以减少缓存污染,但会增加内存访问次数,影响性能。
    • Read Allocate(读分配方式): 当读操作发生时,如果数据不在缓存中,会将数据加载到缓存中。这种方式可以提高读操作的性能,但会增加缓存的占用。
  3. 共享属性:

    • 如果内存区域被多个处理器核心共享,可能会增加数据同步和一致性维护的开销,影响性能。
    • 共享内存可以提高多个核心之间的数据共享和通信效率,但也可能会引入竞争和冲突,需要额外的同步机制。

综上所述,不同配置下的性能情况需要综合考虑内存类型、缓存策略、共享属性等因素,以及具体的应用场景和需求,才能做出合适的选择和优化。

二、Cache简介

在这里插入图片描述

Cache(缓存)是一种高速临时存储器

Cache(缓存)是一种高速临时存储器,用于存储经常被访问的数据或指令,以提高CPU对这些数据或指令的访问速度。在STM32系列的M7内核中,Cache被分为两种类型:数据缓存(D-Cache)和指令缓存(I-Cache)。

  1. 数据缓存(D-Cache): 数据缓存存储了经常被CPU访问的数据,包括变量、数组等。当CPU需要访问数据时,首先会在数据缓存中查找,如果找到了相应的数据,则可以直接从缓存中获取,从而加快了访问速度。数据缓存的存在可以大大减少CPU访问外部存储器(如SRAM)的次数,提高了系统的整体性能。

  2. 指令缓存(I-Cache): 指令缓存存储了经常被CPU执行的指令,包括程序的代码段。当CPU需要执行指令时,首先会在指令缓存中查找,如果找到了相应的指令,则可以直接从缓存中获取,从而加快了指令的执行速度。指令缓存的存在可以减少从外部存储器(如Flash)中获取指令的次数,提高了程序的执行效率。

Cache支持以下基本操作

在STM32系列的M7内核中,Cache支持以下基本操作:

  1. 使能(Enable): 开启Cache功能,允许CPU使用缓存来加速访问数据或指令。

  2. 禁止(Disable): 关闭Cache功能,禁止CPU使用缓存,所有的访问都直接从外部存储器中获取数据或指令。

  3. 清空(Flush): 清空Cache中的所有数据或指令,将其置为无效状态。

  4. 无效化(Invalidate): 将Cache中的数据或指令置为无效状态,使其失效,需要重新从外部存储器中获取。

在STM32中,MPU(内存保护单元)可以用于指定Cache的访问策略,包括Cache的使能与禁止、Cache的清空与无效化等操作。通过MPU的配置,可以灵活地控制Cache的行为,以满足不同的应用需求和性能优化要求。

对于时钟频率和存储器类型的说明

在 Cortex-M7 内核中,I-Cache 和 D-Cache 分别指代指令缓存和数据缓存。下面是一些相关的说明:

  1. I-Cache(指令缓存):

    • 在 Cortex-M7 中,I-Cache 的大小通常为 4KB 或 16KB,具体取决于型号。F7 系列通常为 4KB,而 H7 系列通常为 16KB。
    • I-Cache 用于缓存指令,这些指令通常是程序代码的一部分。
    • I-Cache 的存在可以加快 CPU 对指令的访问速度,特别是当代码需要反复执行时。
    • 缓存的大小限制了可以存储的指令数量,因此较大的缓存可以提供更好的性能。
  2. D-Cache(数据缓存):

    • 与 I-Cache 类似,D-Cache 也可以是 4KB 或 16KB,具体取决于型号。F7 系列通常为 4KB,而 H7 系列通常为 16KB。
    • D-Cache 用于缓存数据,例如变量、数组等,这些数据通常是程序运行时需要频繁访问的。
    • D-Cache 的存在可以加快 CPU 对数据的访问速度,尤其是当相同的数据需要多次读取或写入时。
    • 缓存的大小限制了可以存储的数据量,因此较大的缓存可以提供更好的性能。

对于时钟频率和存储器类型的说明:

  • 主频为 480MHz,表示 CPU 的运行主频为 480MHz。
  • TCM RAM:TCM(Tightly Coupled Memory)RAM 是与 CPU 紧密耦合的高速存储器,其工作频率也是 480MHz。由于 TCM 与 CPU 相关联,并且具有非常高的访问速度,因此不需要配置 MPU 和 Cache。
  • AXI SRAM、SRAM1、SRAM2:这些是外部存储器,工作频率为 240MHz。它们可能会使用 MPU 和 Cache 来提高访问速度和性能,具体取决于应用的需求和系统的配置。

2.1、读操作和写操作

在这里插入图片描述
Cache 的命中率对系统性能有着重要的影响,高命中率可以提高读写速度,减少访问主存的次数,从而提高整体系统性能。下面是一些关于 Cache 命中率的说明:

  1. 读命中(Cache Hit):

    • 当 CPU 需要读取的数据位于 Cache 中,并且 Cache 中已经加载了该数据时,发生读命中。
    • 读命中可以大大提高数据访问速度,因为 Cache 比主存的访问速度更快。
  2. 读未命中(Cache Miss):

    • 当 CPU 需要读取的数据位于主存中,而不在 Cache 中时,发生读未命中。
    • 读未命中会导致 CPU 从主存中读取数据,这通常比从 Cache 中读取数据要慢。
  3. 写命中(Cache Hit):

    • 当 CPU 需要写入数据到 Cache 中已经分配的区域时,发生写命中。
    • 写命中不需要访问主存,因为数据直接写入 Cache,可以提高写入速度。
  4. 写未命中(Cache Miss):

    • 当 CPU 需要写入数据到 Cache 中未分配的区域时,发生写未命中。
    • 写未命中可能会导致 Cache 中原有的数据被替换,并且需要将新数据写入主存和 Cache。

为了提高 Cache 的命中率,可以采取以下措施:

  • 优化数据访问模式,使得数据的访问更加局部化,减少 Cache Miss 的次数。
  • 合理配置 Cache 的大小和映射方式,以满足特定应用程序的需求。
  • 使用高效的替换算法,如 LRU(Least Recently Used),以最大程度地保留对最近访问数据的缓存。
  • 避免频繁的写入操作,尽可能地延迟写操作以提高写入命中率。

2.2、Core读Cache

在这里插入图片描述
在 Cache 命中和 Cache 未命中的情况下,处理方式是不同的:

  1. Cache Hit (命中):

    • 当 CPU 需要读取的数据位于 Cache 中,并且 Cache 中已经加载了该数据时,发生 Cache Hit。
    • 在 Cache Hit 的情况下,CPU 可以直接从 Cache 中读取数据,而无需访问主存,这样可以大大提高读取速度。
  2. Cache Miss (未命中):

    • 当 CPU 需要读取的数据位于主存中,而不在 Cache 中时,发生 Cache Miss。
    • 在 Cache Miss 的情况下,需要从主存中加载数据到 Cache,以便 CPU 可以读取。有两种常见的处理方式:
      • Read Through (直接读取): CPU 直接从主存中读取数据,而不将数据加载到 Cache 中。这样做可以确保数据的一致性,但可能会降低读取速度,因为需要额外的主存访问。
      • Read Allocate (读取分配): CPU 将缺失的数据加载到 Cache 中,然后再从 Cache 中读取数据。这样做可以提高读取速度,因为之后的读取可以直接从 Cache 中进行,但可能会导致 Cache 中的其他数据被替换掉。

选择适当的处理方式取决于应用程序的需求以及系统设计的考虑。通常情况下,会根据应用程序的访存模式、数据局部性以及对性能和一致性的要求来选择合适的策略。

2.3、Core写Cache

计组之存储系统:8、Cache写策略(全写法、写回法、写分配法、非写分配法、多级Cache)
在这里插入图片描述
在 Cache Hit 和 Cache Miss 的情况下,可以采取不同的处理方式:

  1. Cache Hit (命中):

    • Write Through (写透): 当 CPU 需要写入数据时,在 Cache Hit 的情况下,可以选择将数据直接写入到内存中,并同时将数据写入到 Cache 中。这样可以确保内存和 Cache 中的数据保持同步更新,但可能会导致写入操作的延迟,因为需要等待数据写入内存后才能继续执行。
    • Write Back (写回): 在 Cache Hit 的情况下,也可以选择只将数据写入到 Cache 中,而不立即更新到内存。只有当 Cache 中的数据被替换出去时,才会将被修改的数据写回到内存中。这样可以提高写入操作的速度,因为不需要立即写入内存,但可能会增加 Cache 和内存之间的一致性维护的复杂度。
  2. Cache Miss (未命中):

    • Write Allocate (写分配): 当发生 Cache Miss 时,CPU 需要将缺失的数据加载到 Cache 中,然后再执行写入操作。这样做可以确保之后的写入操作可以直接在 Cache 中进行,但可能会增加缺失的数据加载时间。
    • No Write Allocate (非写分配): 在 Cache Miss 的情况下,可以选择直接将数据写入到内存中,而不将数据加载到 Cache 中。这样做可以避免额外的缓存加载时间,但可能会降低之后的写入操作的速度,因为需要直接与内存进行通信。

2.4、数据不一致问题解决

在这里插入图片描述
上述问题描述了解决数据不一致性问题的两种主要方法。让我们对这两种方法进行分析:

  1. 设置共享属性:

    • 优点: 简单直接,通过将内存区域设置为共享属性,可以确保所有核心或外设都能够看到最新的数据,从而解决了数据不一致性的问题。
    • 缺点: Cache 相当于没有开启,无法利用 Cache 来提升访问速度。这可能会导致性能下降,特别是在访问频繁的场景下。
  2. 软件进行 Cache 维护:

    • Clean 清空:
      • 优点: 可以在数据被修改后,将 Cache 中相应的数据写回到内存中,确保内存中的数据与 Cache 中的数据保持一致。
      • 缺点: 需要额外的软件操作来维护 Cache,增加了系统复杂性,并可能导致性能损失。
    • Invalidate 无效化:
      • 优点: 可以在内存中的数据被修改,但 Cache 中的数据尚未更新时,使 Cache 中的数据无效,以便从内存中重新加载最新的数据。
      • 缺点: 与 Clean 操作类似,需要额外的软件操作来维护 Cache,增加了系统复杂性,并可能导致性能损失。

综上所述,两种方法都可以解决数据不一致性的问题,但在选择时需要权衡性能和复杂性。设置共享属性简单直接,但会牺牲一定的性能;而软件进行 Cache 维护可以最大程度地利用 Cache 提升性能,但会增加系统的复杂性。选择哪种方法取决于系统的需求和性能要求。
在这里插入图片描述
这些函数是用来配置和操作 Cortex-M7 处理器的 Cache 的。下面是它们的功能描述:

  1. I-Cache 相关函数:

    • SCB_EnableICache: 启用指令缓存(I-Cache)。
    • SCB_DisableICache: 禁用指令缓存(I-Cache)。
    • SCB_InvalidateICache: 使指令缓存(I-Cache)无效,将其中的所有条目标记为无效。
  2. 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_addr: 通过地址范围清理并使数据缓存(D-Cache)无效。

这些函数使得程序能够有效地控制 Cache 的状态,从而在需要时清理、使无效或操作 Cache 中的数据。

三、MPU相关寄存器介绍

在这里插入图片描述

3.1、MPU类型寄存器(MPU_TYPE)

在这里插入图片描述
MPU类型寄存器(MPU_TYPE)提供了有关内存保护单元(MPU)的一些基本信息。下面是其位段的说明:

  • IREGION (23:16):只读,MPU 支持的指令 region 的数量。由于 MPU 是统一的(即指令和数据共享相同的配置),所以此字段永远为零。

  • DREGION (15:8):只读,MPU 支持的数据 region 的数量。这个值是用户设置的,取决于芯片配置。可以配置为 8 个或 16 个 MPU regions。

  • SEPARATE (0):只读,指示指令和数据映射是否分开。如果此位为 0,则表示指令和数据映射是统一的。通常通过读取 DREGION 的值来判断芯片中是否支持 MPU。

通过读取 MPU_TYPE 寄存器,可以了解 MPU 在特定芯片上的配置情况,包括支持的 region 数量和指令/数据映射方式。

3.2、MPU控制寄存器(MPU_CTRL)

在这里插入图片描述
MPU 控制寄存器(MPU_CTRL)包含了控制 MPU 行为的各个位段。下面是对这些位段的说明:

  • PRIVDEFENA (位2):可读写。此位控制是否允许特权级使用默认内存映射。具体如下:

    • 0:禁止特权级使用默认内存映射。当 MPU 处于使能状态时,对使能的 region 外的地址区进行访问将引发 fault。
    • 1:允许特权级使用默认内存映射,即背景 region。
  • HFNMIENA (位1):可读写。此位控制在 hard fault、NMI 和 FAULTMASK handlers 中是否使能 MPU 操作。具体如下:

    • 当 MPU 使能时:
      • 0:在上述 handlers 中,MPU 失能,即使 ENABLE 位为 1 也不起作用。
      • 1:在上述 handlers 中,MPU 使能。
    • 当 MPU 失能时:设置为 1 的行为不可预测。
  • ENABLE (位0):可读写。此位用于使能或失能 MPU。

    • 0:MPU 失能。
    • 1:MPU 使能。

MPU 控制寄存器允许控制 MPU 的使能状态以及在不同处理器状态下 MPU 的行为。

3.3、MPU区域编号寄存器(MPU_RNR)

在这里插入图片描述
MPU 区域编号寄存器(MPU_RNR)用于选择下一个要配置的内存区域。下面是对其位段的说明:

  • REGION (位7:0):可读写。这些位用于选择要配置的 MPU 区域。根据设置,MPU 可提供 8 个或者 16 个内存区域。这些位可以设置为 0 到 7(如果 MPU 支持 8 个区域)或者 0 到 15(如果 MPU 支持 16 个区域)。

在配置任何一个内存区域之前,都需要通过 MPU_RNR 寄存器选择相应的区域编号。

3.4、MPU基地址寄存器(MPU_RBAR)

在这里插入图片描述
MPU 基地址寄存器(MPU_RBAR)用于设置 MPU 区域的基址。以下是对其位段的说明:

  • ADDR (位31:N):可读写。这些位是用来设置内存区域的基址字段。N 取决于区域的容量,使得基址在数值上能够被容量整除。例如,如果区域容量为 64KB,则 N 的值为 16。

  • VALID (位4):可读写。在读取时,该位一直为 0。在写入时:

    • 当该位为 0 时,MPU_RNR 寄存器没有改变,处理器将根据你的设置执行以下操作之一:
      1. 更新 MPU_RNR 中指定的区域的基址。
      2. 忽略 REGION 字段的值。
    • 当该位为 1 时,处理器将根据你的设置执行以下操作之一:
      1. 更新 MPU_RNR 的值为 REGION 字段的值。
      2. 更新在 REGION 字段中指定的区域的基址。
  • REGION (位3:0):可读写。在写操作中,这些位用于指定要更新的区域编号。在读操作中,返回由 MPU_RNR 指定的当前区域号。

N 的值是根据区域大小(以字节为单位)计算的,通过取以 2 为底的对数,例如,如果区域容量为 64KB,则 N 的值为 16。

3.5、MPU区域属性和容量寄存器(MPU_RASR)

在这里插入图片描述
MPU 区域属性和容量寄存器(MPU_RASR)用于设置 MPU 区域的属性和容量。以下是对其位段的详细解释:

  • XN (位28):可读写。指示该内存区域是否允许执行代码。

    • 0:允许取指。
    • 1:禁止取指。
  • AP (位26:24):可读写。指示访问权限。

  • TEX,C,B (位21:19, 17, 16):可读写。用于设置内存访问属性,包括 Cache 策略、是否使用缓冲等。

  • S (位18):可读写。指示是否共享该内存区域。

  • SRD (位15:8):可读写。子region 失能位。容量大于 128 字节的 region 被划分为 8 个容量相同的子region。每设置 SRD 的一个位,就会失能与之对应的一个子region。

  • SIZE (位5:1):可读写。用于设置区域的容量,单位为字节,最小容量为 4 字节。实际容量计算公式为 2^(SIZE+1) 字节。

  • ENABLE (位0):可读写。用于使能或失能该区域。

    • 0:失能此区域。
    • 1:使能此区域。

这些位段用于设置 MPU 区域的各种属性,包括访问权限、Cache 策略、是否允许执行代码等。通过正确配置这些位段,可以确保系统对内存区域的访问符合预期的安全和性能要求。

3.6、AP 相关位控制数据的访问权限(访问许可),控制关系如下表:

在这里插入图片描述
AP 相关位用于控制数据的访问权限,包括特权级下的许可和用户级下的许可。以下是各个值对应的权限描述:

  • 0b000:禁止访问。

    • 特权级下的许可:禁止访问。
    • 用户级下的许可:禁止访问。
    • 所有访问都会产生取消权限错误。
  • 0b001:RW。

    • 特权级下的许可:读写。
    • 用户级下的许可:禁止访问。
    • 只支持特权访问。
  • 0b010:RW。

    • 特权级下的许可:读写。
    • 用户级下的许可:只读。
    • 非特权写入会产生权限错误。
  • 0b011:RW。

    • 特权级下的许可:读写。
    • 用户级下的许可:读写。
    • 全访问。
  • 0b101:RO。

    • 特权级下的许可:只读。
    • 用户级下的许可:禁止访问。
    • 仅支持特权读。
  • 0b110:RO。

    • 特权级下的许可:只读。
    • 用户级下的许可:只读。
    • 仅可通过特权或非特权读(只读)。
  • 0b111:RO。

    • 特权级下的许可:只读。
    • 用户级下的许可:只读。
    • 仅可通过特权或非特权读(只读)。

通常情况下,选择的是全访问,即 0b011。

3.7、TEX用来设置Cache策略

在这里插入图片描述
在计算机系统中,缓存是用于存储最近或频繁访问的数据副本,以提高数据访问速度的一种技术。而不同的缓存策略会影响数据在缓存中的存储和管理方式,进而影响系统的性能和一致性。

以下是根据您提供的信息,对不同的缓存策略进行解释:

  1. Non-cacheable(非缓存):

    • 数据不会被缓存在缓存中,每次访问都会直接读取或写入主存。
  2. Write-through, read-allocated, no-write-allocate(写透传,读分配,不写分配):

    • 写操作直接写入主存,并通过缓存透传到主存,读操作在缓存中未命中时会分配缓存。
  3. Write-back, read-allocated, no-write-allocate(写回,读分配,不写分配):

    • 写操作会先写入缓存(如果缓存中有相应数据块),并标记为脏数据,读操作在缓存未命中时会分配缓存。
  4. Write-back, read-allocated, write-allocate(写回,读分配,写分配):

    • 写操作会先写入缓存(如果缓存中有相应数据块,如果没有则会分配缓存),并标记为脏数据,读操作在缓存未命中时会分配缓存。

这些缓存策略在实际系统中会根据具体的应用场景和性能需求进行选择和配置,以达到最佳的性能和数据一致性。不同的缓存策略会影响数据在缓存和主存之间的传输方式和一致性管理。

四、MPU相关HAL库驱动介绍

在这里插入图片描述
MPU(Memory Protection Unit)相关的HAL库驱动提供了以下函数:

  1. HAL_MPU_Enable(...)

    • 关联寄存器:CTRL
    • 功能描述:使能MPU,允许MPU开始监视内存访问并根据配置的规则进行操作。
  2. HAL_MPU_Disable(...)

    • 关联寄存器:CTRL
    • 功能描述:失能MPU,停止MPU对内存访问的监视和控制。
  3. HAL_MPU_ConfigRegion(...)

    • 关联寄存器:RASR、RBAR、RNR
    • 功能描述:配置MPU的区域参数,包括访问权限、Cache策略、缓冲、共享等。具体的配置通过修改RASR(Region Attribute and Size Register)、RBAR(Region Base Address Register)和RNR(Region Number Register)来实现。

4.1、void HAL_MPU_Enable (uint32_t MPU_Control)

在这里插入图片描述
HAL_MPU_Enable 函数用于设置 MPU 控制寄存器(MPU_CTRL)的各个位,具体功能包括操作 PRIVDEFENA 和 HFNMIENA 位,其操作方式如下:

  • 操作 PRIVDEFENA 位:该位控制特权级下的默认内存映射访问权限。具体操作如下:

    • 若 PRIVDEFENA 为 0,则禁止背景区。这意味着任何未使能 MPU 区域的访问都会引发内存异常(MemFault)。
    • 若 PRIVDEFENA 为 1,则使能背景区。这意味着特权级下可以正常访问任何未使能的 MPU 区域。
  • 操作 HFNMIENA 位:该位控制在硬件故障中断(HardFault)和不可屏蔽中断(NMI)中是否继续开启 MPU。具体操作如下:

    • 若 HFNMIENA 为 0,则在 HardFault 和 NMI 中 MPU 会被强制关闭。
    • 若 HFNMIENA 为 1,则在 HardFault 和 NMI 中 MPU 会继续保持开启状态。

根据参数 MPU_Control 的不同取值,可以选择以下模式:

  • MPU_HFNMI_PRIVDEF_NONE:将 PRIVDEFENA 设置为 0,HFNMIENA 设置为 0。
  • MPU_HARDFAULT_NMI:将 PRIVDEFENA 设置为 0,HFNMIENA 设置为 1。
  • MPU_PRIVILEGED_DEFAULT:将 PRIVDEFENA 设置为 1,HFNMIENA 设置为 0。
  • MPU_HFNMI_PRIVDEF:将 PRIVDEFENA 设置为 1,HFNMIENA 设置为 1。

这些模式的选择取决于系统的需求和安全策略。
在这里插入图片描述
该函数用于启用 MPU(Memory Protection Unit),具体功能包括设置 MPU 控制寄存器(MPU_CTRL)、使能内存异常(MemFault)和确保 MPU 设置生效。函数的代码如下:

/**
  * @brief  启用MPU
  * @param  MPU_Control 指定MPU在硬件故障、NMI、FAULTMASK和特权访问默认内存时的控制模式
  *         此参数可以是以下值之一:
  *            @arg MPU_HFNMI_PRIVDEF_NONE
  *            @arg MPU_HARDFAULT_NMI
  *            @arg MPU_PRIVILEGED_DEFAULT
  *            @arg MPU_HFNMI_PRIVDEF
  * @retval 无
  */
void HAL_MPU_Enable(uint32_t MPU_Control)
{
  /* 启用MPU */
  MPU->CTRL = MPU_Control | MPU_CTRL_ENABLE_Msk;

  /* 启用内存异常 */
  SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;

  /* 确保MPU设置生效 */
  __DSB();
  __ISB();
}

在函数内部,主要的操作包括:

  • 将 MPU 控制寄存器(MPU_CTRL)设置为指定的控制模式(MPU_Control)并启用 MPU。
  • 通过设置系统控制寄存器(SCB_SHCSR)的 MEMFAULTENA 位,启用内存异常(MemFault)。
  • 使用 Data Synchronization Barrier (__DSB()) 和 Instruction Synchronization Barrier (__ISB()) 确保 MPU 的设置生效。

这个函数的参数 MPU_Control 可以指定 MPU 在硬件故障(HardFault)、不可屏蔽中断(NMI)、FAULTMASK 和特权级访问默认内存时的控制模式。可以根据系统需求选择适当的模式。

#if (__MPU_PRESENT == 1)
/** @defgroup CORTEX_MPU_HFNMI_PRIVDEF_Control MPU HFNMI和特权访问控制
  * @{
  */
#define  MPU_HFNMI_PRIVDEF_NONE      ((uint32_t)0x00000000)  // HFNMI和特权访问关闭
#define  MPU_HARDFAULT_NMI           ((uint32_t)0x00000002)  // 仅在硬件故障和NMI中使能MPU
#define  MPU_PRIVILEGED_DEFAULT      ((uint32_t)0x00000004)  // 在特权级使用默认内存映射
#define  MPU_HFNMI_PRIVDEF           ((uint32_t)0x00000006)  // 在硬件故障、NMI和特权级使用默认内存映射

4.2、MPU_Region_InitTypeDef

在这里插入图片描述

#if (__MPU_PRESENT == 1)
/** @defgroup CORTEX_MPU_Region_Initialization_Structure_definition MPU区域初始化结构体定义
  * @brief  MPU区域初始化结构体
  * @{
  */
typedef struct
{
  uint8_t                Enable;                /*!< 指定区域的状态。
                                                     此参数可以是 @ref CORTEX_MPU_Region_Enable 中的值之一                 */
  uint8_t                Number;                /*!< 指定要保护的区域编号。
                                                     此参数可以是 @ref CORTEX_MPU_Region_Number 中的值之一                 */
  uint32_t               BaseAddress;           /*!< 指定要保护的区域的基地址。                                               */
  uint8_t                Size;                  /*!< 指定要保护的区域的大小。
                                                     此参数可以是 @ref CORTEX_MPU_Region_Size 中的值之一                   */
  uint8_t                SubRegionDisable;      /*!< 指定要禁用的子区域保护数量。
                                                     此参数必须是介于Min_Data = 0x00和Max_Data = 0xFF之间的数值           */
  uint8_t                TypeExtField;          /*!< 指定TEX字段级别。
                                                     此参数可以是 @ref CORTEX_MPU_TEX_Levels 中的值之一                    */
  uint8_t                AccessPermission;      /*!< 指定区域的访问权限类型。
                                                     此参数可以是 @ref CORTEX_MPU_Region_Permission_Attributes 中的值之一  */
  uint8_t                DisableExec;           /*!< 指定指令访问状态。
                                                     此参数可以是 @ref CORTEX_MPU_Instruction_Access 中的值之一            */
  uint8_t                IsShareable;           /*!< 指定受保护区域的共享状态。
                                                     此参数可以是 @ref CORTEX_MPU_Access_Shareable 中的值之一              */
  uint8_t                IsCacheable;           /*!< 指定受保护区域的可缓存状态。
                                                     此参数可以是 @ref CORTEX_MPU_Access_Cacheable 中的值之一              */
  uint8_t                IsBufferable;          /*!< 指定受保护区域的可缓冲状态。
                                                     此参数可以是 @ref CORTEX_MPU_Access_Bufferable 中的值之一             */
}MPU_Region_InitTypeDef;

这个结构体包含以下成员:

  • Enable: 区域的使能状态。
  • Number: 要保护的区域编号。
  • BaseAddress: 区域的基地址。
  • Size: 区域的大小。
  • SubRegionDisable: 禁用的子区域数量。
  • TypeExtField: TEX 字段级别。
  • AccessPermission: 区域的访问权限类型。
  • DisableExec: 指令访问状态。
  • IsShareable: 受保护区域的共享状态。
  • IsCacheable: 受保护区域的可缓存状态。
  • IsBufferable: 受保护区域的可缓冲状态。

这些成员可以用来配置 MPU 区域的各种属性,以实现对内存的保护和访问控制。

注意:BaseAddress 值必须被 Size 值整除。
在这里插入图片描述
在这里插入图片描述

  • Size: 指定了要保护的区域的大小。该参数可以是以下值之一:

    • CORTEX_MPU_REGION_SIZE_32B: 区域大小为 32 字节。
    • CORTEX_MPU_REGION_SIZE_64B: 区域大小为 64 字节。
    • CORTEX_MPU_REGION_SIZE_128B: 区域大小为 128 字节。
    • 其他可用的大小选项,具体取决于硬件和支持的 MPU 功能。
  • Number: 指定了要保护的区域的编号。在配置 MPU 时,必须指定要配置的区域编号。

  • TypeExtField: 指定了区域的 TEX 字段级别。TEX 字段用于配置内存访问属性,例如缓存策略。该参数可以是以下值之一:

    • CORTEX_MPU_TEX_LEVEL0: TEX 级别为 0。
    • CORTEX_MPU_TEX_LEVEL1: TEX 级别为 1。
    • CORTEX_MPU_TEX_LEVEL2: TEX 级别为 2。
    • CORTEX_MPU_TEX_LEVEL3: TEX 级别为 3。
  • AccessPermission: 指定了区域的访问权限类型。该参数可以是以下值之一:

    • CORTEX_MPU_REGION_NO_ACCESS: 禁止访问该区域。
    • CORTEX_MPU_REGION_PRIV_RW: 仅支持特权级读写访问。
    • CORTEX_MPU_REGION_PRIV_RW_URO: 禁止用户写访问,但支持特权级读写访问。
    • CORTEX_MPU_REGION_FULL_ACCESS: 全访问,支持特权级和用户级读写访问。
    • 其他可用的访问权限选项,具体取决于硬件和支持的 MPU 功能。

注意:在配置 MPU 时,确保 BaseAddress 的值要被 Size 的值整除,以确保设置的区域大小和基地址是有效的。

五、MPU基本配置步骤

在这里插入图片描述
基本的 MPU 配置流程,通常用于在 Cortex-M 架构的处理器上配置内存保护单元 (MPU)。

  1. 禁止 MPU
    在配置 MPU 之前,首先需要禁止 MPU,以确保在配置期间不会产生任何干扰。可以通过调用 HAL_MPU_Disable() 函数来实现。

  2. 配置某个区域的 MPU 保护参数
    使用 HAL_MPU_ConfigRegion() 函数来配置某个特定区域的 MPU 保护参数。该函数通常需要提供以下参数:

    • 区域编号
    • 区域基址
    • 区域大小
    • 访问权限
    • Cacheability 等属性
  3. 使能 MPU
    在配置完成后,需要使能 MPU 以使其生效。可以通过调用 HAL_MPU_Enable() 函数来启用 MPU。

  4. 编写 MemManage 中断服务函数
    在 MPU 使能后,如果发生了非法的内存访问或其他与内存保护相关的异常,将触发 MemManage 中断。为了处理这些异常,需要编写 MemManage 中断服务函数 (MemManage_Handler)。在该函数中,可以执行一些处理操作,例如记录错误信息、重置系统状态或者进行异常处理。

通过这个基本的 MPU 配置步骤,可以有效地配置并管理处理器的内存保护单元,以提高系统的安全性和稳定性。

六、编程实战

mpu.c

#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/MPU/mpu.h"


/**
 * @brief       设置某个区域的MPU保护
 * @param       baseaddr: MPU保护区域的基址(首地址)
 * @param       size:MPU保护区域的大小(必须是32的倍数,单位为字节)
 * @param       rnum:MPU保护区编号,范围:0~15,最大支持16个保护区域
 * @param       de:禁止指令访问;0,允许指令访问;1,禁止指令访问
 * @param       ap:访问权限,访问关系如下:
 *   @arg       0,无访问(特权&用户都不可访问)
 *   @arg       1,仅支持特权读写访问
 *   @arg       2,禁止用户写访问(特权可读写访问)
 *   @arg       3,全访问(特权&用户都可访问)
 *   @arg       4,无法预测(禁止设置为4!!!)
 *   @arg       5,仅支持特权读访问
 *   @arg       6,只读(特权&用户都不可以写)
 *   @note      详见:STM32H7编程手册.pdf,4.6.6节,Table 91.
 * @param       sen:是否允许共用;0,不允许;1,允许
 * @param       cen:是否允许cache;0,不允许;1,允许
 * @param       ben:是否允许缓冲;0,不允许;1,允许
 * @retval      0, 成功; 1, 错误;
 */
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;
}

/**
 * @brief       设置需要保护的存储块
 * @note        必须对部分存储区域进行MPU保护,否则可能导致程序运行异常
 *              比如MCU屏不显示,摄像头采集数据出错等等问题
 * @param       无
 * @retval      无
 */
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);
}

/**
 * @brief       MemManage错误处理中断
 *   @note      进入此中断以后,将无法恢复程序运行!!
 *
 * @param       无
 * @retval      无
 */
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();                 /* 软复位 */
}

mpu.h

#ifndef __MPU_H
#define __MPU_H

#include "./SYSTEM/sys/sys.h"


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);
void mpu_memory_protection(void);


#endif

mpu.h

#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取反 */
        }
    }
}

Mini Pro H750 MPU设置

在这里插入图片描述
对于使用 Mini Pro H750 开发板的 MPU 设置,通常的步骤如下:

  1. 确定需要保护的区域:根据应用程序的需求和安全性要求,确定需要保护的内存区域。这可能涉及到代码区、数据区、堆栈区等。

  2. 配置 MPU 区域:使用 MPU 区域初始化结构体 MPU_Region_InitTypeDef 来配置每个需要保护的内存区域的参数,包括基地址、大小、访问权限、Cache 属性等。确保配置时考虑到内存对齐的要求。

  3. 调用 HAL_MPU_ConfigRegion() 配置 MPU:使用 HAL 库提供的函数 HAL_MPU_ConfigRegion() 来配置每个 MPU 区域的参数。在配置完成后,确保调用 HAL_MPU_Enable() 使能 MPU。

  4. MemManage 中断处理:编写 MemManage 中断服务函数 MemManage_Handler(),用于处理任何与内存保护相关的异常。在这个函数中,可以记录错误信息、采取必要的补救措施或者重置系统状态。

  5. 测试和验证:配置完成后,进行测试和验证,确保 MPU 的设置能够正确地保护指定的内存区域,并且不会导致不必要的系统异常或性能影响。

  6. 优化和调整:根据实际情况,对 MPU 的配置进行优化和调整,以满足特定应用程序的要求,同时确保系统的性能和稳定性。

通过以上步骤,可以有效地配置 Mini Pro H750 的 MPU,提高系统的安全性和稳定性。

在这里插入图片描述
在这里插入图片描述
要调试内核 MPU(Memory Protection Unit),你可以采取以下步骤:

  1. 检查 MPU 配置:首先,确保你的 MPU 配置是正确的。检查每个区域的参数,包括基地址、大小、访问权限等是否设置正确。

  2. 检查 MPU 中断:如果发生了与内存访问权限相关的错误,通常会触发 MemManage 中断。确保你的 MemManage_Handler() 函数正确地捕获和处理这些中断。在处理函数中,你可以记录错误信息、重置系统状态或采取其他必要的措施来处理异常情况。

  3. 使用调试工具:你可以使用支持 MPU 调试的调试工具,如 J-Link 调试器或者 Keil MDK 自带的调试器,来检查 MPU 的配置和运行状态。这些工具通常提供了查看 MPU 寄存器的功能,可以帮助你确认 MPU 的设置是否符合预期,并且可以监视 MPU 异常的触发情况。

  4. 逐步调试:如果你怀疑某个特定的代码段导致了 MPU 异常,你可以使用调试器进行逐步调试。逐步执行代码,并观察是否会触发 MPU 异常。通过逐步调试,你可以更精确地定位问题所在,并尝试解决它。

  5. 记录日志:在调试过程中,记录关键的信息和观察结果是很重要的。可以通过日志输出或者调试器的事件记录功能来记录 MPU 异常的发生情况,以便后续分析和排查问题。

通过以上步骤,你可以更好地调试内核 MPU,并解决与内存保护相关的问题。记得在调试过程中谨慎操作,确保不会对系统造成额外的影响。

七、总结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到