内存保护(MPU)实验- m4 s" m+ M- I5 [* i
STM32的Cortex M4(STM32F3/F4系列)和Cortex M7(STM32H7/H7系列)系列的产品,都带有内存保护单元(memory protection unit),简称:MPU。使用MPU可以设置不同存储区域的存储器访问特性(如只支持特权访问或全访问)和存储器属性(如可缓存、可共享),从而提高嵌入式系统的健壮性,使系统更加安全。接下来,我们将以STM32H750为例,给大家介绍STM32H7内存保护单元(MPU)的使用。/ e& G0 w }! Q' c
# ^& [3 ~: _6 r/ \; ~
24.1 MPU简介
6 J/ Z8 X0 U6 e0 ? ]- p5 |MPU,即内存保护单元,可以设置不同存储区域的存储器访问特性(如只支持特权访问或全访问)和存储器属性(如可缓存、可缓冲、可共享),对存储器(主要是内存和外设)提供保护,从而提高系统可靠性:
7 l8 \6 \2 z9 M- d! v$ U+ z1,阻止用户应用程序破坏操作系统使用的数据。
9 [# c. T7 G% V( d& P4 q2 e2,阻止一个任务访问其他任务的数据区,从而隔离任务。
' k' X A, S8 r. g% _1 e3,可以把关键数据区域设置为只读,从根本上解决被破坏的可能。
2 ~' u, z+ u6 i: V; i6 A5 A- }4,检测意外的存储访问,如堆栈溢出,数组越界等。' \7 u; R" G& J% T8 a5 {9 \6 m
5,将SRAM或者RAM空间定义为不可执行(用不执行,XN),防止代码注入攻击。# ?% Z0 y7 `. \
注意,MPU不仅可以保护内存区域(SRAM区),还可以保护外设区(比如FMC)。我们可以通过MPU设置存储器的访问权限,当存储器访问和MPU定义的访问权限相冲突的时候,则访问会被阻止,并且触发一次错误异常(一般是MemManage异常)。然后,在异常处理的时候,就可以确定系统是否应该复位或者执行其他操作。+ o% H& T& B9 R$ f0 Z
STM32H7的MPU提供多达16个可编程保护区域(region),每个区域(region)都有自己的可编程起始地址、大小及设置。MPU功能必须开启才会有效,默认条件下,MPU是关闭的,所以,我们要向使用MPU,必须先打开MPU才行。
q" m* b! n9 {# w! F' _( P5 o16个可编程保护区域(region),一般来说是足够使用的了,如果觉得不够,每个区域(region)还可以被进一步划分为更小的子区域(sub region),另外,还允许启用一个背景区域(即没有MPU设置的其他所有地址空间),背景区域只允许特权访问。在启用MPU后,就不得再访问定义之外的地址区间,也不得访问未经授权的区域(region),否则,将以“访问违例”处理,触发MemManage异常。9 g4 Y/ N$ F$ i
此外,MPU定义的区域(region)还可以相互交迭。如果某块内存落在多个区域(region)中,则访问属性和权限将由编号最大的region来决定。比如,若2号region与5号region交迭,则交迭的部分受5号region控制。
' N5 {8 L; w; s9 oMPU设置是由CTRL、RNR、RBAR和RASR等寄存器控制的,接下来,我们分别介绍一下这几个寄存器。; M+ v, p0 }# g! O
首先是MPU控制寄存器(CTRL),该寄存器只有最低三位有效,其描述如表24.1.1所示:
. S5 T7 r7 g3 t4 R6 Y+ P2 D' u& \, _ l0 N% u/ Y8 D. b/ p
* S$ w: w( [. E Z- Y9 E
! f$ [5 X& B8 _表24.1.1 MPU_CTRL寄存器各位描述8 W4 Z9 o- S# g4 L! ?
PRIVDEFENA位用于设置是否开启背景区域(region),通过设置该位为1,可以在没有建立任何region就使能MPU的情况下,依然允许特权级程序访问所有地址,而只有用户级程序被卡死。但是,如果设置了其它的region(最多16个region)并使能MPU,则背景region与这些region重合的部分,就要受各region的限制。HFNMIENA位用于控制是否在NMI和硬件fault中断服务例程中禁止MPU,一般设置为0即可。ENABLE位,则用于控制是否使能MPU,我们一般在MPU配置完)以后,才对其进行使能,从而开启MPU。
U5 ~1 u8 M8 C& Z- A4 D接下来,介绍MPU区域编号寄存器(RNR),该寄存器只有低8位有效,其描述如表24.1.2所示:2 U: b3 I1 d9 x: m3 p- ]6 N$ y
5 [3 Z9 R$ R) K: F
/ u9 P* Y, S$ L
" ~* w6 y' }9 V' V$ j表24.1.2 MPU_RNR寄存器各位描述
5 i. G! d% G |5 W( x8 v t$ `在配置任何一个区域(region)之前,必须先在MPU内选中这个区域,我们可以通过将区域编号写入MPU_RNR寄存器来完成这个操作。该寄存器只有低8位有效,不过由于STM32H7最多只支持16个区域,所以,实际上只有最低4位有效(0~15)。在配置完区域编号以后,我们就可以对区域属性进行设置了。! B. P: B" C: f% }1 s0 {+ {
接下来,介绍MPU基地址寄存器(RBAR),该寄存器各位描述如表24.1.3所示:
8 b% E& a& ^, q9 Y+ o- _, H( A" Z8 i0 D" H' _
! b7 G Y: z. y& u1 Z
: \) V/ i. j4 U e表24.1.3 MPU_RBAR寄存器各位描述; i" M4 T% ]+ G/ T$ f6 b( q3 S
注意,表中ADDR字段所设置的基址必须对齐到区域(region)容量的边界。例如,我们定义某个region的容量是64KB(通过RASR寄存器设置),那么它的基址(ADDR)就必须能被64KB整除,比如0X0001 0000、0X0002 0000、0X0003 0000等(低16位全为0)。
& ^ s! e5 L( U4 iVALID用于控制REGION段(bit[3:0])的数据是否有效,如果VALID=1,则REGION段的区域编号将覆盖MPU_RNR寄存器所设置的区域编号,否则将使用MPU_RNR所设置的区域编号。我们一般设置VALID为0,这样MPU_RBAR寄存器的低5位就没有用到。
$ y9 q3 K8 g$ n8 p0 |4 I2 `6 w特别注意,表17.1.3中的N值最少也是5,所以,基址必须是32的倍数,从而可以知道我们设置region的容量,必须是32字节的倍数。) m6 C- K+ v) k# {" ^ ?' b8 Q
最后,我们介绍MPU区域属性和容量寄存器(RASR),该寄存器各位描述如表24.1.4所示:9 o* g* |$ D% a" l5 ~
0 M S9 s8 R$ V9 V/ A3 t9 ]: F
( q" Q" z- K" s' B7 D7 t3 W9 D
8 n. S/ P6 L$ o+ X$ N表24.1.4 MPU_RASR寄存器各位描述0 {* E0 r, @& f# O8 q$ w
XN位,用于控制是否允许从此区域取指,如果XN=1,说明禁止从区域取指,如果强行取指,将产生一个MemManage异常。如果设置XN=0,则允许取指。
$ w; b, n5 x+ A5 m5 sAP位,由3个位(bit[26:24])组成,用于控制数据的访问权限(访问许可),控制关系如表24.1.5所示:. H' A. c% G# E+ g
1 e \, e1 K0 p- u# L6 l7 d" a
& e9 s8 h- @, Z: z, N
7 W2 D8 Q5 p- J S表24.1.5 不同AP设置及其访问权限" Z! B) z5 E* m4 t
TEX、S、C和B等位,对应着存储系统中比较高级的概念,可以通过对这些位段的编程,来支持多样的内存管理模型,这些位组合的详细功能如表24.1.6所示:/ E9 A \5 o5 w G9 S/ P5 M f, {
9 ] y6 p! k, F" q, }# q
$ x2 U% I; o7 ^ w% i
, J2 @8 y" k4 `, m% b0 G/ G0 n表24.1.6 TEX、S、C和B对存储器类型的定义: ~2 i4 ~$ `6 N. d& g5 @0 r/ L5 n
有些情况下,内部和外部内存可能需要不同的缓存策略,次数需要设置TEX的第二位为1,这样TEX[1:0]的定义就会变为外部策略表(表17.1.6种表示为BB),而C和B位则会变为内部策略表(表17.1.6种表示为AA)。缓存策略的定义(AA和BB)如表24.1.7所示:
# p) h* a# \- c) X5 ?$ q6 o+ ]- M; W$ c. k* a$ t
# ^6 Z/ x% M1 ^1 z" N( d7 M* q4 v2 r8 U% H& F) q
表24.1.7 TEX最高位为1时内外缓存策略编码6 B0 P4 D: m4 J- f2 v4 p2 X) C, u
S位用于控制存储器的共享特性,设置S=1,则二级存储器不可以缓存(Cache),如果设置S=0,则可以缓存(Cache),一般我们设置该位为0即可。
5 a" y" Y; e9 D' {/ H, k* f4 d8 ^C位用于控制存储器的缓存特性,也就是是否可以Cache,STM32H7自带Cache,如果我们想要某个存储器可以被Cache,则必须设置C=1。此位需要根据具体的需要设置。
) A: ^ u) `5 hB位用于控制存储器的缓冲特性,设置B=1,则二级存储器可以缓冲,即写回模式,设置B=0,则二级存储器不可以缓冲,即写通模式。此位需根据具体的需要进行设置。
4 {0 X9 [) V7 _. G: K) ASRD[15:8],这8个位用于控制子区域(sub region)使能。前面提到,STM32H7的MPU最多支持8个region,有时候可能不够用,通过子区域的概念,可以将每个region的内部进一步划分成更小的块,这就是sub region,每个sub region可以独立地使能或除能(相当于可以部分地使能一个region)。* c6 r" G: I! U! w/ H4 y$ B
sub region的使用必须满足:" q$ t+ w# C! c2 ^9 i+ N
1,每个region必须8等分,每份是一个sub region,其属性与主region完全相同。
$ {2 P. D& k6 I# Z" @; u8 \4 [2 I2,可以被分为8个sub region的region,其大小必须大于等于256字节。: [$ O' N. j+ N+ ?+ y
SRD中的8个位,每个位控制一个sub region是否被除能。如SRD.4=0,则4号sub region被除能。如果某个sub region被除能,且其对应的地址范围又没有落在其它region中,则对该区的访问将引发fault。+ r7 [7 }- j* [8 T
REGIONSIZE[5:1],这5个位用于控制region的容量(大小),计算关系如下:. _ i; k% l* r, e( v& a
rsize=2^(REGIONSIZE+1)3 G6 `) ?8 f. c2 ?4 E
rsize即region的容量,必须大于等于32字节,即REGIONSIZE必须大于等于4。region的容量范围为:32B~4GB,根据实际需要进行设置。
! ~2 w) ^6 d" QSZENABLE位,用于设置region的使能。该位一般最后设置,设置为1,则启用此region,使能MPU保护。1 p% }/ l5 }: |7 {# L" }
至此,关于MPU的简介就介绍完了,关于MPU更详细的说明,请参考:《STM32H7编程手册.pdf》 、《STM32 MPU说明》和《Cortex M3权威指南(中文)》第14章。
4 s5 t8 S, k: x# a$ b24.2 硬件设计' ~2 E) b; O6 J+ q e( {
1.例程功能; [& O8 }* k8 _/ q4 F
本实验使用STM32H7自带的MPU功能,对一个特定的内存空间(数组,地址:0X20002000)进行写访问保护。开机时,串口调试助手显示:MPU closed,表示默认是没有写保护的。按KEY0可以往数组里面写数据,按KEY1,可以读取数组里面的数据。按KEY_UP则开启MPU保护,此时,如果再按KEY0往数组写数据,就会引起MemManage错误,进入MemManage_Handler中断服务函数,此时LED1点亮,同时打印错误信息,最后软件复位,系统重启。LED0用于提示程序正在运行,所有信息都是通过串口1输出,需要用串口调试助手查看。- m& P' u! x( i2 v: j1 ^
2.硬件资源$ D9 c6 j' G" m# p
1)RGB灯
% X# u4 y8 D$ W5 F% dRED :LED0 - PB4
4 h" o7 b. A5 J# D' b- q- Y7 z2 g3 cGREEN :LED1 - PE6
+ Z+ y5 V9 i" n# c9 E& C9 u5 f2)独立按键6 n d/ P1 O" [3 O
KEY0 - PA1
# `: `8 h' O/ a3 |7 I! H3 A5 Y* fKEY1 - PA15* V& s5 h2 A) m. V6 o
WK_UP - PA0
9 r y' t2 L* g; p$ [3 y: d4 m3)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面); E( h6 u: _4 d% K7 n
3.原理图! }! Z4 V3 | e
MPU属于STM32H750的内部资源,只需要软件设置好即可正常工作。我们借助按键和LED灯验证MPU工作是否正常,然后通过电脑串口上位机软件观察打印出来的信息。) y- R* `; Z" L9 \
! q; A! ?- H4 s0 E# T
24.3 程序设计
4 ~; w* ]1 i# C& p; o: J Y7 |4 x24.3.1 MPU的HAL库驱动
c$ m {1 c% }% z' }2 F- |1 C0 l3 I* W4 xMPU在HAL库中的驱动代码在stm32h7xx_hal_cortex.c文件(及其头文件)中。5 [1 I4 E" S, m5 y7 R( \
4.HAL_MPU_ConfigRegion函数
( N, V" U# C1 I) L' Y2 tMPU的初始化函数,其声明如下:
/ e+ Y' M& v7 u8 X+ L. T& Wvoid HAL_MPU_ConfigRegion(MPU_Region_InitTypeDef *MPU_Init);7 w/ s$ _0 ]- ^7 G1 @
函数描述:
/ ^- d2 R4 I: S3 b用于配置MPU。8 ~) J2 C( o0 ]# M$ ]
函数形参:( Z& @; |& z8 g' R
形参1是MPU_Region_InitTypeDef结构体类型指针变量,其定义如下:) D$ [5 \' I8 X: H. S3 u; v5 K
- typedef struct! @7 Y* g+ v$ p# J+ L1 \% Y
- {
1 y$ G. u* i. S5 O8 P- S# ]$ ? - uint8_t Enable; /* 区域使能/禁止 */$ Y+ l7 T4 K& F8 e2 Q# X9 i
- uint8_t Number; /* 区域编号 */ ) H; C7 T& T" \& y- X* i) Y# P
- uint32_t BaseAddress; /* 配置区域基地址 */
* G0 y" E) o8 U( m - uint8_t Size; /* 区域容量 */
9 T B2 ~; A/ d9 j& k - uint8_t SubRegionDisable; /* 子region除能位段设置 */ . m% c: n3 W+ Z5 X& N0 A1 [
- uint8_t TypeExtField; /* 类型扩展级别 */ 4 i2 T* r8 S5 U
- uint8_t AccessPermission; /* 设置访问权限 */
5 m( | {1 A: u6 Z - uint8_t DisableExec; /* 允许/禁止取指 */
- r! n9 Z8 E2 k2 ] - uint8_t IsShareable; /* 禁止/允许共享 */ ( K! O r- q" |$ q$ V
- uint8_t IsCacheable; /* 禁止/允许缓存 */
& |9 q M p, h) C; m - uint8_t IsBufferable; /* 禁止/允许缓冲 */
- h) m9 _% }1 r) I* f& ^3 q6 f8 C - }MPU_Region_InitTypeDef;
复制代码 - p p4 U" ?$ \' V/ X( I3 Q) c
该结构体成员变量很多,每个成员变量的含义我们都在上面定义中有注释。这里大家注意,除了BaseAddress和Number两个成员变量是分别用来配置MPU->RBAR和MPU->RNR寄存器之外,其他成员变量都是用来配置MPU->RASR寄存器相关位,大家如果对这些配置项不理解的话,可以直接对照我们前面讲解的寄存器MPU->RASR各个位含义来理解。
9 X! \3 P, h% \7 e# {6 l函数返回值:
5 }! o$ `+ g. P" e# o d无
7 Z5 N' h& H- H7 ^/ J1 N2. HAL_MPU_Enable函数$ `- }" u1 O, J" X% }! @! h
HAL_MPU_Enable函数是MPU使能函数。其声明如下:: l! y5 ~! l0 y9 I' u; h0 s& R
void HAL_MPU_Enable(uint32_t MPU_Control);
0 g, U, B7 Q' J2 W2 a9 x函数描述:
6 v' `# M$ ^( |: L! \$ O该函数用于使能MPU,形参一般使用MPU_PRIVILEGED_DEFAULT。+ d4 E& Q/ k3 t$ p; x- Y
函数形参:- Q1 L' J3 V' S5 \1 S
形参1是uint32_t类型变量,共有四个选择,对应设置如表24.1.1的 MPU_CTRL寄存器,四个选择参数设置情况如下:2 l6 b7 l) [! e; f
#define MPU_HFNMI_PRIVDEF_NONE ((uint32_t)0x00000000) }( v1 l, F8 i) g. U: _2 |
该参数设置MPU的CTL控制寄存器的PRIVDEFENA位为0,即禁止了背景区,访问任何未使能 MPU的区域均会造成内存异常MemFault。
9 P7 L3 r5 z1 [8 L4 M/ B此参数设置MPU的CTL控制寄存器的HFNMIENA位为0,即NMI不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会强制关闭MPU。- w# X" _0 x( ]* u
#define MPU_HARDFAULT_NMI ((uint32_t)0x00000002)( f9 f p3 _( z
该参数设置MPU的CTL控制寄存器的PRIVDEFENA位为0,即禁止了背景区,访问任何未使能 MPU 的区域均会造成内存异常MemFault。
9 j# F+ ]. T( s; n- W* r此参数设置MPU的CTL控制寄存器的HFNMIENA位为1,即NMI不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会继续开启MPU。
9 p3 r1 c z0 f6 Y/ q {#define MPU_PRIVILEGED_DEFAULT ((uint32_t)0x00000004)
7 b% w8 n8 U8 _& C* w该参数设置MPU的CTL控制寄存器的PRIVDEFENA位为1,即使能了背景区,特权级下可以正常访问任何未使能MPU的区域。
4 {( i7 s6 Z8 b! i, i3 D7 R% y此参数设置MPU的CTL控制寄存器的HFNMIENA位为0,即NMI不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会强制关闭MPU。
+ r8 m/ u7 I( _9 j% d* p+ s; U7 Y6 j. W#define MPU_HFNMI_PRIVDEF ((uint32_t)0x00000006)
$ R* [* C3 w/ }/ e5 Z3 w! R) q该参数设置MPU的CTL控制寄存器的PRIVDEFENA位为1,即使能了背景区,特权级下可以正常访问任何未使能MPU的区域。. P( j- S/ h$ V
此参数设置MPU的CTL控制寄存器的HFNMIENA位为1,即NMI不可屏蔽中断服务程序和硬件异常中断服务程序执行期间会继续开启MPU。, e% I9 a! a, u" N3 i L
函数返回值:
5 }: r. D, V( {& h3 H% k) g无# M% Z5 D7 \' p2 Q3 h- r9 F
3. HAL_MPU_Disable函数( j2 S& \5 V" ]6 w
HAL_MPU_Disable函数是MPU失能函数。其声明如下:" ^8 O5 O: \1 ~0 ^8 y+ M
void HAL_MPU_Disable (void);2 z2 D: Q( D `8 C& _& @9 {1 l6 y- L
函数描述:
6 j0 s- t! c; M" t6 U该函数用于禁止MPU,我们配置MPU前需要先调用该函数禁止MPU,才能进行配置。
2 E# z j$ c) }8 d$ ~7 z: j函数形参:
& F @4 u8 f8 ]+ Q5 H+ _无% e \1 B6 T: h1 s" k6 S
函数返回值:
9 Q7 R6 i" f' d( M6 J无* n2 o% Q0 @6 V* M, m9 o/ O# `
MPU配置步骤* l: Y8 P# W) ~- p
1)禁止MPU以及MemManage中断# T' v4 m2 y! [( u5 ^" Z
调用HAL_MPU_Disable函数禁止MPU以及MemManage中断。$ X2 d [- _4 E
2)配置某个区域的MPU保护参数
. W/ V/ c+ O1 t: a2 k/ I在进行MPU配置之前我们必须先通过MPU_RNR区域编号寄存器用来选择下一个要配置的区域,然后通过配置MPU_RBAR基地址寄存器来配置基地址,最后通过区域属性和容量寄存器RASR来配置区域相关属性和参数。这些过程都可以通过调用HAL_MPU_ConfigRegion函数来完成。除了BaseAddress和Number两个成员变量是分别用来配置MPU->RBAR和MPU->RNR寄存器之外,其他成员变量都是用来配置MPU->RASR寄存器相关位,大家如果对这些配置项不理解的话,可以直接对照我们前面讲解的寄存器MPU->RASR各个位含义来理解。
2 | a9 c X, j( B7 }3)使能MPU以及MemManage中断
9 S5 J& C' n/ r1 e2 l) v调用HAL_MPU_Enable函数禁止MPU以及MemManage中断。+ t' A i( ~1 B D9 I
* c5 l0 b" X6 B24.3.2 程序流程图
1 i. q0 e; f3 e C. w I9 |2 ^- W# n. S N4 Y
' G7 a5 q: @2 K
, [5 r, d u& \. B图24.3.2.1 内存保护(MPU)实验程序流程图
0 z, a4 I' t( q3 |4 Z2 x Y1 M: t
, ^4 a# ?5 A/ j9 V" n$ @, I; M24.3.3 程序解析
/ A4 B; p- u. ]1 s+ Z8 F9 T1.MPU驱动代码# K' d5 l; p# ]- F* {8 c& x: Q; s8 I
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。MPU驱动源码包括两个文件:mpu.c和mpu.h。
' T7 }+ j4 t) j, B" b0 c6 _. fmpu.h头文件只有函数声明,就不看了,所以我们直接看mpu.c文件,先看设置某个区域的MPU保护函数:
2 i$ r- t/ ~) f+ F7 f- /**
2 t; }# y! n# O1 w# b- V - * @brief 设置某个区域的MPU保护
5 {& M+ Q4 m* L! y- g' H* o - * @param baseaddr: MPU保护区域的基址(首地址)& ?1 X. I) b' M8 F# p
- * @param size:MPU保护区域的大小(必须是32的倍数,单位为字节)
: Z a& h5 W: p ~ - * @param rnum:MPU保护区编号,范围:0~7,最大支持8个保护区域: K' d0 N# v. H& }$ H( J( D( _
- * @param de:禁止指令访问;0,允许指令访问;1,禁止指令访问2 G6 w) Y- @2 H w1 K( a# ?6 S+ J
- * @param ap:访问权限,访问关系如下:
. q# f1 z0 N. S2 S! f' g - * @arg 0,无访问(特权&用户都不可访问)
; D9 |. |4 Z$ s$ @' V C - * @arg 1,仅支持特权读写访问 V- {3 B9 |" f6 _: _9 ?
- * @arg 2,禁止用户写访问(特权可读写访问)
- `/ }* v' s, [( X, P - * @arg 3,全访问(特权&用户都可访问)
" |, F- M+ O& v( [ - * @arg 4,无法预测(禁止设置为4!!!)
5 b# [3 r2 }6 w - * @arg 5,仅支持特权读访问
- A3 n \6 ~& B3 Q0 g" M - * @arg 6,只读(特权&用户都不可以写)
3 c% n% F. J( {- _: f8 Z - * @note 详见:STM32H7编程手册.pdf,4.6.6节,Table 91.
1 |+ @, a. i( E) C% v7 i - * @param sen:是否允许共用;0,不允许;1,允许
# N) b" \3 q/ F3 W) @4 N( f - * @param cen:是否允许cache;0,不允许;1,允许$ z5 K+ W8 n+ y5 }$ K
- * @param ben:是否允许缓冲;0,不允许;1,允许4 ?$ u3 O$ D3 ^5 {! Q7 l ~: f
- * @retval 0, 成功; 1, 错误;0 A; `8 |& N& O! U$ `
- */
. G' @# }# V0 ~# H) b4 n - uint8_t mpu_set_protection(uint32_t baseaddr, uint32_t size, uint32_t rnum,
! g( F0 R8 @( z5 I6 p - uint8_t de, uint8_t ap, uint8_t sen, uint8_t cen, uint8_t ben), Y3 V+ }1 y% S+ A
- {7 ~+ W, }; w p l" [2 a% ^
- MPU_Region_InitTypeDef mpu_region_init_handle; /* MPU初始化句柄 */
9 n% z8 L t1 r" q, F# X/ p - HAL_MPU_Disable(); /* 配置MPU之前先关闭MPU,配置完成以后在使能MPU */( T) G5 h( j" F) [$ D. F# t* e' }
3 |. r% r/ M9 P4 O, n% Y- mpu_region_init_handle.Enable = MPU_REGION_ENABLE; /* 使能该保护区域 */
6 F1 H* p* }* S+ u - mpu_region_init_handle.Number = rnum; /* 设置保护区域 */
`+ |& W) @, v6 D3 Q+ D" c/ \2 | - mpu_region_init_handle.BaseAddress = baseaddr; /* 设置基址 */2 f% R; h0 I9 n0 }0 h7 I" J" o, J
- mpu_region_init_handle.Size = size; /* 设置保护区域大小 */% {( e- U5 C4 Q x# [
- mpu_region_init_handle.SubRegionDisable = 0X00; /* 禁止子区域 */
' {" ]5 W2 ]1 a - mpu_region_init_handle.TypeExtField =MPU_TEX_LEVEL0;/*设置类型扩展域为level0*/
5 S; i# e; J4 f! j8 `3 U - mpu_region_init_handle.AccessPermission = ap; /* 设置访问权限 */' [& q7 `1 ?) C3 z
- mpu_region_init_handle.DisableExec = de; /* 是否允许指令访问 */0 E; |0 O' E" |4 c5 K0 U) m) K; M
- mpu_region_init_handle.IsShareable = sen; /* 是否允许共用 */
3 m" ?! T0 J* J0 \( `( H - mpu_region_init_handle.IsCacheable = cen; /* 是否允许cache */' W/ |! h! Q' S) h @
- mpu_region_init_handle.IsBufferable = ben; /* 是否允许缓冲 */
% G( k/ ~# e1 _% T: y R - HAL_MPU_ConfigRegion(&mpu_region_init_handle); /* 配置MPU */ Q3 ?% \) a$ j y, j- a7 Q
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); /* 开启MPU */. m5 |# F# T! O+ W6 \3 @
- return 0;9 Y( `/ g/ ^ O0 s5 u- P8 e
- }
复制代码 - V, s! r/ j1 c |) O' a
该函数使用HAL_MPU_ConfigRegion函数对MPU_Region_InitTypeDef结构体变量进行配置。注意:配置之前我们要调用HAL_MPU_Disable函数禁止MPU才能进行配置,最后再调用HAL_MPU_Enable函数开启MPU。
9 P0 W7 ]2 Z Z1 Y接着,要介绍的是mpu_memory_protection函数,其定义如下:7 L& F6 z4 K- }3 C
5 d# ~9 h9 p* X5 w- K: ~. `
- /**6 v& P0 T9 i3 u
- * @brief 设置需要保护的存储块
5 U# T2 x: \: P9 Y% f" x$ z - * @note 必须对部分存储区域进行MPU保护,否则可能导致程序运行异常: ?" E! g% p* I T
- * 比如MCU屏不显示,摄像头采集数据出错等等问题0 C& ^" a% ^) B
- * @param 无
0 Y' y3 b4 J* R9 e( C - * @retval nbytes以2为底的指数值* S& ?9 B9 {2 `( X
- */' Z7 q1 `/ n: P
- void mpu_memory_protection(void)
; Q8 n& I% x, P2 t - {3 s; e% J# s0 ^6 A: X& e6 `+ D7 @
- /* 保护整个DTCM,共128K字节,允许指令访问,禁止共用,允许cache,允许缓冲 */
+ i0 v2 ^% R* f: l2 g) r! x - mpu_set_protection(0x20000000, MPU_REGION_SIZE_128KB, MPU_REGION_NUMBER1, o/ K" [4 R1 h9 ^- G! J
- MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, 2 }2 D7 j: ~$ G
- MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE);; y' _+ d0 j2 }
3 A: Q7 a* [7 G/ j" v! u- /* 保护整个AXI SRAM,共512K字节,允许指令访问,禁止共用,允许cache,允许缓冲 */
5 j2 x9 ^# [, |' D - mpu_set_protection(0x24000000, MPU_REGION_SIZE_512KB,MPU_REGION_NUMBER2,
* W7 Z, r8 ?: _" \' z5 X! e - MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS,
+ d# b, W9 I: G - MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE);
s. z8 z3 s# Z+ D; S5 Q( f - . M, o- p( O/ Y5 J
- /* 保护整个SRAM1~SRAM3,共288K字节,允许指令访问,禁止共用,允许cache,允许缓冲 */
4 q u4 ?4 T- t- B8 C - mpu_set_protection(0x30000000, MPU_REGION_SIZE_512KB,MPU_REGION_NUMBER3,
1 C: c7 t+ G0 Y( J2 k* O - MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS,9 N% R) M0 a1 c1 Z$ X5 \
- MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE);9 O) b7 F: v# m8 E+ x
- 1 i: z' ~7 P. K% P* t& h
- /* 保护整个SRAM4,共64K字节,允许指令访问,禁止共用,允许cache,允许缓冲 */$ }) T7 D& e1 `: [8 h! c
- mpu_set_protection(0x38000000, MPU_REGION_SIZE_64KB, MPU_REGION_NUMBER4,
, c% h! c8 _. M2 l3 Z! N - MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, q3 H3 G3 @' L1 o# |/ g3 j# `9 p* B
- MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE);
) ~! C5 y2 e+ x, H
4 r" a# y9 f K4 g. W# I- /*保护MCU LCD屏所在的FMC区域,,共64M字节,允许指令访问,禁止共用,禁止cache,禁止缓冲*/
2 v w& X: W$ b$ M7 ? - mpu_set_protection(0x60000000, MPU_REGION_SIZE_64MB, MPU_REGION_NUMBER5, , l6 j9 Q9 I2 c4 H' `/ q
- MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, 3 {( N h- {) Y3 Q- f! {
- MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_NOT_CACHEABLE,
& F9 u/ x# ?0 K% _ - MPU_ACCESS_NOT_BUFFERABLE); O9 D6 W7 e6 r3 f9 B
- + ~; }; |: k3 J0 ]
- /* 保护SDRAM区域,共64M字节,允许指令访问,禁止共用,允许cache,允许缓冲 */- W% P3 P2 _( J
- mpu_set_protection(0XC0000000, MPU_REGION_SIZE_64MB, MPU_REGION_NUMBER6,
: \, I: m" [. t6 r/ |$ f- f6 l - MPU_INSTRUCTION_ACCESS_ENABLE, MPU_REGION_FULL_ACCESS, I0 W, v" b& f% a. S9 g; l$ X
- MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_CACHEABLE, MPU_ACCESS_BUFFERABLE);: S2 @: L3 I Q( Q# B' P
; B/ |, A( [# [: q* r4 ^% N% ~+ p* \- /* 保护整个NAND FLASH区域,共256M字节,禁止指令访问,禁止共用,禁止cache,禁止缓冲 */$ e3 z; j; Z t/ i/ C
- mpu_set_protection(0X80000000, MPU_REGION_SIZE_256MB, MPU_REGION_NUMBER7, ) W: p- [* N- m% h" t
- MPU_INSTRUCTION_ACCESS_DISABLE, MPU_REGION_FULL_ACCESS, : ?4 t. k! @: ^% J% W
- MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_NOT_CACHEABLE, 9 d! T9 D! L# S% K9 D
- MPU_ACCESS_NOT_BUFFERABLE);
& a# l& U6 r; f - }
复制代码
- E" ~" v; D" Dmpu_memory_protection函数,用于设置整个代码里面,我们需要保护的存储块,这里我们对7个存储块(使用了7个区域(region))进行了保护:. i7 N6 ?3 g' r2 B. z
1,从0x20000000地址开始的128KB DTCM地址空间,允许指令访问,禁止共用,允许cache,允许缓冲。
4 _% M$ D# a9 A! ?4 c* y8 ~2,从0x24000000地址开始的512KB AXI SRAM地址空间,允许指令访问,禁止共用,允许cache,允许缓冲。$ |: Z) p) `- r* z* k
3,从0x30000000地址开始的288KB SRAM1~SRAM3地址空间,允许指令访问,禁止共用,允许cache,允许缓冲。6 a9 t7 V, I/ }: q
4,从0x38000000地址开始的64KB SRAM4地址空间,允许指令访问,禁止共用,允许cache,允许缓冲。
9 ?4 O/ Z: Q# ~7 \8 ^& l( t6 L5,从0x60000000地址开始的64MB地址空间,允许指令访问,禁止共用,禁止cache,禁止缓冲,保护MCU LCD屏的访问地址取件,如不进行设置,可能导致MCU LCD白屏。# L0 ]- [$ E7 p" S
6,从0XC0000000地址开始的64MB地址空间,即SDRAM的地址范围,允许指令访问,禁止共用,允许cache,允许缓冲。$ J' J5 ?0 ]# o6 J* p3 S. o6 |# H# O
7,从0X80000000地址开始的256MB地址空间,即NAND FLASH区域,禁止指令访问,禁止共用,禁止cache,禁止缓冲,如不进行设置,可能导致NAND FLASH访问异常。
. o! O- \0 C: }" m. {- h这七个地址空间的保护设置,可以提高代码的稳定性(其实就是减少使用cache导致的各种莫名奇妙的问题),请大家不要随意改动。此函数在本例程没有用到,不过我们在后续代码都会用到。
% J5 e: o& f# s最后,MemManage_Handler函数,用于处理产生MemManage错误的中断服务函数,在该函数里面点亮了LED1,并输出一些串口信息,对系统进行软复位,以便观察本例程的实验结果。函数定义如下:% s/ {! P+ w6 o) T
5 e4 [4 Y+ [( m4 a- /**' g4 E% v, t- M
- * @brief MemManage错误处理中断
5 v8 R9 D% h, e - * @note 进入此中断以后,将无法恢复程序运行!!' R) g+ H I' S% a" {
- *; m V) w& u) g- C) o# |
- * @param 无, G# O, P9 l# p0 A6 V5 ^/ V3 H
- * @retval nbytes以2为底的指数值' Y2 c6 X$ p ?, C6 ` ?
- */) A* z# P* x. r9 a d% a1 A$ Y0 ]
- void MemManage_Handler(void)
1 N: D( M1 @0 D6 H5 O% W/ w - {
( b4 M! f" \6 m& N+ d) F0 h - LED1(0); /* 点亮LED1(GREEN LED) */2 m, o! e9 @4 N# f' Q, e$ J N
- printf("Mem Access Error!!\r\n"); /* 输出错误信息 */
5 R% S5 |! {7 i& H( |( X - delay_ms(1000);4 }" N0 |% n! q ]2 P1 t+ i' m+ G
- printf("Soft Reseting...\r\n"); /* 提示软件重启 */
# l" V2 k* i* ~* ?1 q - delay_ms(1000);
+ E4 z' K8 X9 }$ X" S% Y: @) j0 ~4 A - NVIC_SystemReset(); /* 软复位 */- ~5 L5 D& n/ W: q' }
- }
复制代码
. e! s& h2 k/ s4 ompu.c的内容介绍就到此,下面开始main.c文件的介绍。' }& x) }0 N, H9 x. |
2. main.c代码
1 h! _2 ~" E9 i在main.c文件代码如下:
! R# w+ _- z) ~3 e" m* f( y0 Z7 d5 W4 P! A$ [( O. i1 g
- uint8_t mpudata[128] __attribute__((at(0X20002000))); /* 定义一个数组 */
+ \. ?; i: Q$ ? _$ S; V
, W7 Z) K) Z" m- int main(void)
: W$ G) f* n7 |+ S% ] - {
1 P2 h' j+ k) I% V: D! ` - uint8_t key = 0;
* r# [1 V5 H% [0 y, ]* T1 F* w% b - uint8_t t = 0;
7 E0 M3 X, \0 c" X - sys_cache_enable(); /* 打开L1-Cache */, \8 h5 ?4 b3 H- k! w
- HAL_Init(); /* 初始化HAL库 */
! J% h h! k+ N3 c x' L - sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */5 I' _# d- ]3 o6 I6 I. d/ b6 {
- delay_init(480); /* 延时初始化 */) I, f4 M% N5 b5 i+ g% n
- usart_init(115200); /* 串口初始化为115200 */
# t7 |1 i7 ~1 o; {; \$ r' K3 b5 t - led_init(); /* 初始化LED */0 Z2 _1 @, q8 |4 ^1 g8 U! z0 Z
- key_init(); /* 初始化按键 */
9 i: B/ g& b7 @) B( e - printf("\r\n\r\nMPU closed!\r\n"); /* 提示MPU关闭 */7 p- E# E- h( B1 m; e* w
4 X8 K' W* o& \- while (1)
, l/ ]; B: A9 c/ B - {3 D/ V6 R: W+ {. j) z4 n/ @
- key = key_scan(0);
4 j% K! ~2 N* S6 l! P - % K9 C5 l/ ~1 I
- if (key == WKUP_PRES) /* 使能MPU保护数组 mpudata */
/ g0 l) h4 e1 G/ l; t7 b - {
+ B' `) }1 ^4 a0 q q$ ~! @& ~ - mpu_set_protection(0X20002000, MPU_REGION_SIZE_128B, - h) |. k5 J$ P
- MPU_REGION_NUMBER0, MPU_REGION_FULL_ACCESS, MPU_REGION_PRIV_RO_URO,
, N% p8 e4 g! _- d/ [% F2 s - MPU_ACCESS_NOT_SHAREABLE, MPU_ACCESS_NOT_CACHEABLE, # n" [7 J1 x% f1 X* Y/ M
- MPU_ACCESS_BUFFERABLE); /* 只读,禁止共用,禁止catch,允许缓冲 */% T6 T0 M! E9 ?" J+ h8 _' B- Z( c
- printf("MPU open!\r\n"); /* 提示MPU打开 */
: [( q& S% b9 C( m7 ` - }$ G, r- V6 N* }# h1 @
- /* 向数组中写入数据,如果开启了MPU保护的话会进入内存访问错误! */
" _: ]7 {1 d0 f/ |( [ - else if (key == KEY0_PRES)$ L# l/ F9 Z4 ?8 Y7 _2 y
- {
- n% N1 j% `8 y6 z, P - printf("Start Writing data...\r\n");0 y6 j. G7 M3 I% o+ H m" V# z8 E. ]
- sprintf((char *)mpudata, "MPU test array %d", t);; F! u3 O8 E2 L9 Q. ?( X: o
- printf("Data Write finshed!\r\n");
, A2 I& L5 C1 Q/ i - }
. B6 ~9 Z, N4 W$ P% ?7 f4 d! B - /* 从数组中读取数据,不管有没有开启MPU保护都不会进入内存访问错误! */
, U$ `0 C7 F: I5 N - else if (key == KEY1_PRES)
1 [# U9 r$ f, {4 S. V/ l0 Y! r* { - {! N- @- h6 M( J' G* _
- printf("Array data is:%s\r\n", mpudata);4 r& r0 E* d7 y6 ]: J* I; b
- }8 {. e# a, e% E' V
- else 3 D8 \! D8 h$ X$ q) w
- {
" ^* w; h' t0 I2 P* } - delay_ms(10);
. i; M, A% l% ] - }2 k) Z$ K7 h! S' t4 w
4 y5 v' B4 I" e% ?' _$ E. G- t++;) ~" C8 T! l" [ K
I4 e% B! S, b( V4 t- if ((t % 50) == 0) LED0_TOGGLE(); /* LED0取反 */# I' b6 F! l$ K5 ~
- }# d5 m4 V& O, Y% o6 A9 Q4 s5 b
- }
) n' W- X" ?- w3 |0 ]' _
复制代码
0 H9 {2 B% b0 z* _1 L& H: N在main函数前面,我们定义了一个128字节大小的数组:mpudata,其首地址为0X20002000,默认情况下,MPU保护关闭,可以对该数组进行读写访问。当我们按下KEY_UP按键的时候,通过mpu_set_protection函数,对其0X20002000为起始地址,大小为128字节的内存空间进行保护,仅支持特权读访问,此时如果再按KEY0,对数组进行写入操作,则会引起MemManage访问异常,进入MemManage_Handler中断服务函数,执行相关操作。: j9 k! `6 P" Z3 h
其他的代码比较简单,这里就不多做说明了,在整个代码编译通过之后,我们就可以开始下载验证了。9 A- q, z& u) A4 G" \
24.4 下载验证3 h, E9 j2 H8 i, @8 f2 _/ H4 L
下载代码后,LED0不停的闪烁,提示程序已经在运行了。然后,打开串口调试助手(XCOM V2.6),设置串口为开发板的USB转串口(CH340虚拟串口,得根据你自己的电脑选择,我的电脑是COM8,另外,请注意:波特率是115200),可以看到如图24.4.1所示信息(如果没有提示信息,请先按复位):
6 z) _$ m" E& S- |$ h
& E- J W* j' J/ d
4 Y+ B6 C7 t4 m2 _+ W
# o1 S7 n+ L b
图24.4.1 复位后串口调试助手收到的信息
$ `# t% w, {2 G2 {从图24.4.1可以看出,此时串口助手提示:MPU Closed,即MPU保护是关闭的,我们可以按KEY0往数组里面写入数据,按KEY1,可以读取刚刚写入的数据,按KEY_UP,则开启MPU保护,提示:MPU open!,此时,如果再按KEY0,往数组里面写数据的话,则会引起MemManage访问异常,进入MemManage_Handler中断服务函数,点亮LED1,并提示:Mem Access Error!!,并在1秒钟以后,重启系统(软复位),如图24.4.2所示:+ c' W O7 D3 [) R/ z2 l
) K: g& ]/ r6 c9 P5 }3 m9 Y
4 J' c) `5 I: l" b; q
; {0 S" H! a' ?图24.4.2 串口调试助手显示运行结果$ \7 `. c9 z+ e5 \/ Y; X; R
整个过程,验证了我们代码的正确性,通过MPU实现了对特定内存的写保护功能。通过MPU,我们可以提高系统的可靠性,使代码更加安全的运行。
6 x/ }" q/ f/ c% ~/ J———————————————— {, n1 y' V1 Q
版权声明:正点原子2 r7 x4 V X5 U' c- |" U+ L
! v) W* w& N; D* b+ b! l8 D! t' }5 `( K9 F* p/ x
|