一、Cache
3 A. L: I2 c& V( M, e# O3 l& r6 E/ \
1、介绍, l; [6 |+ R( @# ~# V k N/ \ Z
$ h3 Y% o* C. m `9 N b( q
Cache又分数据缓存D-Cache和指令缓冲I-Cache,STM32H7的数据缓存和指令缓存大小都是16KB。STM32H7主频是400MHz,除了TCM和Cache以400MHz工作,其它AXI SRAM,SRAM1,SRAM2等都是以200MHz工作。数据缓存D-Cache就是解决CPU加速访问SRAM。0 _8 v# B8 x4 U9 S1 t8 x: c
d" M- ]% [! m( X& ] 如果每次CPU要读写SRAM区的数据,都能够在Cache里面进行,自然是最好的,实现了200MHz到400MHz的飞跃,实际是做不到的,因为数据Cache只有16KB大小,总有用完的时候。
4 ^ G- I6 l! V! U- p5 M) h; M+ O3 U5 W* y! o g7 W
2、操作,分为读操作和写操作
! Y% v+ K: J9 j/ a- e8 P) c
0 p) l4 M3 M( ?9 }" h- ` 读操作: 如果CPU要读取的SRAM区数据在Cache中已经加载好,这就叫读命中(Cache hit),如果Cache里面没有怎么办,这就是所谓的读Cache Miss。8 e* `. i+ P- s9 F( s7 L1 ]% k5 u1 j
; O% E: O' B, {2 {
写操作: 如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域(专业词汇叫Cache Line,以32字节为单位),这就叫写命中(Cache hit),如果Cache里面没有开辟对应的区域怎么办,这就是所谓的写Cache Miss。" X& k; ^4 {8 M P6 V; q( p8 Q
7 I5 H9 W" G3 p' k: G8 J( A M
3、H7支持的Cache策略,共4种
3 A( f7 f. v+ r' Y/ c- @( q( y7 P! C* L
8 n1 f$ J8 _* M) V% P- v7 i( r
4 g' F7 a2 q' K<回写:如果Cache中有,写数据只写到Cache,不写到RAM。>
+ f9 ~+ o1 x8 F& ^: l
) A- G. [' R9 F2 Y1 g<透写:如果Cache中有,写数据也要同时写到Cache和RAM。>( W8 Z: u( x" @0 x+ ]
: B' r. V$ I3 C0 n; y4 i" w<write allocate:写数据时,如果Cache中没有,那么就要在Cache中开辟一个空间,把数据写入Cache,同时把RAM中的相邻数据加载进来填充Cache。>
9 W' M! f0 y$ L
9 e9 K4 ~7 W3 l4 S" _3 H9 _<no write allocate:写数据时,如果Cache中没有,那么把数据直接写入RAM。>
: {; n- ?/ G5 v ?3 G) S( u
. k3 z' c' m7 @5 M! K<read allocate:读数据时,如果Cache中没有,那么就要在Cache中开辟一个空间,把数据从RAM中加载进来,后续的读操作,就可以直接从Cache中读取了。>( @, q% L6 v1 F" Q& `1 F. d8 w
6 Q7 A8 i/ U! R<no read allocate:读数据时,如果Cache中没有,那么直接从RAM中读。>" h) T5 j x& \8 |
9 L- {7 f" r! _1 {
4、风险
- i5 s# Y1 v$ `' K/ ~/ Y& J" \
- r& s+ v% Z; g: p+ S+ U _6 a' F! ~7 I/ m5 u& P$ E) P& z( ?& e6 b9 Q, n
8 T" x9 `, L6 P 从上面的图就看出来使用Cache的风险,因为DMA是直接与SRAM交换数据的,而CPU与SRAM之间隔了一个Cache,如果DMA更新了某个数据到SRAM,CPU要去访问,而恰好Cache中有,那么CPU就不会去SRAM中拿,就会拿到Cache中已经过时的数据。因此使用了DMA的内存区要配置为无Cache或者拿数据前清一次Cache。6 \3 n m0 D9 Z9 ^, h1 O4 t6 U
9 L9 t2 I; z* }5 b) c2 R, Q$ J
5、相关函数: ^& i/ ?: z% G2 ^
; [+ ^' ?( Z. N
SCB_EnableICache(void) :用于使能指令Cache,系统上电后优先初始化即可。
9 e: ?! _' h3 N- k. \. w/ e# e9 ~/ b0 V u
SCB_DisableICache(void) :用于禁止指令Cache。
$ c J" W- P& L* U M
. p# R+ H' o+ p/ a' s SCB_InvalidateICache(void) :用于将指令Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的指令。( u4 T) W; k4 C& A- b0 z
% ]" Z* Y/ y x1 _1 r; b( s# c- a- O SCB_EnableDCache(void) :用于使能数据Cache,系统上电后优先初始化即可。
% r* K2 H5 c1 |7 I' i4 x* H: q8 @$ x. g3 y1 h9 C. X1 N/ A- K( \
SCB_DisableDCache(void) :用于禁止数据Cache。8 E; a9 C5 s( s' L) i0 U
. D/ X8 _2 _7 k: O- x
SCB_InvalidateDCache(void) :用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。
D9 m3 L* ], O Y+ Q9 h5 L4 |1 M0 h! W1 [4 l8 C
SCB_CleanDCache(void):用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。) l! u6 L9 [+ C6 P2 d
3 _2 F4 U+ O$ L( m. d) Z! E, O" k SCB_CleanInvalidateDCache(void) :此函数是前面两个函数SCB_InvalidateDCache和SCB_CleanDCache的二合一。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。
' N3 C4 A9 y$ h$ I! p: x
`- _1 D5 ?( G% v$ r SCB_InvalidateDCache_by_Addr(uint32_t *addr,int32_t dsize):可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。
' P: o- x2 H" P! a. v$ D! c. e2 @ P4 v" f0 D
SCB_CleanDCache_by_Addr(uint32_t *addr,int32_t dsize):可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。
" x) k5 M3 _4 `+ ]) Y: i! L+ ~8 j- t2 m) g, l& j
SCB_CleanInvalidateDCache_by_Addr(uint32_t *addr,int32_t dsize):可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。$ h" J" d8 U: U% D) h* E! D
! U' K- G3 Z5 \ C0 o! z b" a$ m
8 g" p* E. y1 w& `3 q
0 ?& O9 W1 q% L2 ]
二、MPU9 o* [1 `: T. I8 e- G$ i5 F6 ~
- t& T* m: i* ]# ^$ V1、作用
7 \: Z( w" t1 ?2 m2 \! l4 g) j, p j6 e D
防止不受信任的应用程序访问受保护的内存区域; 防止用户应用程序破坏操作系统使用的数据;通过阻止任务访问其它任务的数据区;允许将内存区域定义为只读,以便保护重要数据;检测意外的内存访问。 简单的说就是内存保护、外设保护和代码访问保护。& d# n F) c& |" y3 ?
' m1 A" M6 b2 f% f1 t. V2 F$ l6 t2、MPU可以配置的三种内存类型
: G% \) f+ H) P3 \1 F5 `$ K2 U5 K: ^1 d' O6 [7 U8 ]
1)、Normal memory
- }( W7 p5 W& d1 N. Y* N3 O8 ]
' {% ~# r8 L, k# J9 ]4 P CPU以最高效的方式加载和存储字节、半字和字,对于这种内存区,CPU的加载或存储不一定要按照程序列出的顺序执行。
2 t3 F" v( U* L3 L6 m" ?0 |) H7 N D& D* W h4 M
2)、Device memory
7 p6 s- ? R+ f! _( q+ W: W* D# O- I" n4 w4 M- m& E* i
对于这种类型的内存区,加载和存储要严格按照次序进行,这样是为了确保寄存器按照正确顺序设置。
4 p' V6 r2 f7 N2 ~! ?* y* ~
5 g$ I/ W2 ?. o6 X) Q& V3)、Strongly ordered memory
- Q5 b1 N' n7 t) I' Y: f% h
9 C: N. p! g- {: }& J5 ? 程序完全按照代码顺序执行,CPU需要等待当前的加载/存储指令执行完毕后才执行下一条指令。这样会导致性能下降。
4 a A, Q: w% B% p$ u: l) c, C
0 X- A3 Z+ ~9 J+ G) X0 F3、MPU的使用
/ \1 G! B) K9 R4 r: F0 q) m+ W$ _
) T) L/ k" n" U- `$ z* k MPU可以配置保护16个内存区域(这16个内存域是独立配置的),每个区域最小要求256字节,每个区域还可以配置为8个子区域。由于子区域一般都相同大小,这样每个子区域的大小就是32字节,正好跟Cache的Cache Line大小一样。9 x/ p3 S' y0 A+ d5 [! B& v- r; g& [0 ^
3 Z% i4 @% z* C8 x% X
使用时把一段连续的内存区配置为一个MPU保护区域,然后再配置这个MPU保护区域的特性。比如128KB的DTCM、64KB的SRAM4、32MB的SDRAM。MPU保护区域的特性使用MPU_RASR寄存器来配置,描述如下:
6 c4 d( ^9 L7 G' Y
! Q1 n+ n4 Z# U- o4 R
2 _/ q- |5 V6 S! u+ a0 \6 n6 o. T, z' T4 r
1)、XN:用于控制这个MPU保护区域能否执行程序代码。+ V$ G9 t) v8 j$ y0 r5 ^, E
, v9 u+ O9 f: b: u2)、AP:用于控制这个MPU保护区域的特权级和非特权级的读写访问权限。6 w% r" s' ~9 W" S( ~& ~5 R" d
& k7 M- S" k- y0 k' i* z& i# W
+ g( G. y$ n! h9 p# ?2 l, l- q" v* t: `# ~! y7 @
3)、TEX、C、B、S:H7支持4种Cache策略,这几位就是用来控制这个MPU保护区域使用哪一种。9 u w, c' Y- g' }5 r) u
. |9 L6 s A* n( _
! F, ?) k2 ~, |1 H/ c D8 F O. _% h
/ [' b( e, @, H: V
S位用于解决多总线或者多核访问的共享问题,一般不要开启。3 }! ~6 \! o; q0 O# d; g
L- @/ K7 d, A+ ]# F
4)、SRD:这个位用于控制内存区的子区域 ,使用的是bit[15:8],共计8个bit,一个bit控制一个子区域, 0表示使能此子区域, 1表示禁止。一般情况下,取值0x00,表示8个子区域都使能。
4 r8 c# O; D" p6 I) Y3 P( ^2 x B$ D3 ~9 k
5)、SIZE:配置这个MPU保护区域的大小。/ ^: i- g' G$ G; H
4 q3 s7 a7 }/ r# V* j三、HAL配置例程4 }* h7 S8 \; u
2 Z8 g1 T! }. w. v- //设置某个区域的MPU保护
9 ?. O% X' B6 Q, e" Y. F5 i- W - //baseaddr:MPU保护区域的基址(首地址)
0 v2 U9 B8 A/ E* e6 F - //size:MPU保护区域的大小(必须是32的倍数,单位为字节),可设置的值参考:CORTEX_MPU_Region_Size
; H- V7 ~: G2 C% } J( p" `8 L& [ - //rnum:MPU保护区编号,范围:0~7,最大支持8个保护区域,可设置的值参考:CORTEX_MPU_Region_Number0 n. h; \8 [- N- } @) @1 I* D
- //ap:访问权限,访问关系如下:可设置的值参考:CORTEX_MPU_Region_Permission_Attributes
$ Q- Z- J. o L* _8 o; u6 P4 q" W) g - //0,无访问(特权&用户都不可访问)
; e5 b( q- ?- ]3 S2 A - //1,仅支持特权读写访问' U: e. K5 F4 r6 P& ^; S1 o7 h
- //2,禁止用户写访问(特权可读写访问)
- S9 E! O% H7 V: D* g* l& ^7 E2 K - //3,全访问(特权&用户都可访问)
* m6 q/ }8 v* \# c3 ^ - //4,无法预测(禁止设置为4!!!)% R+ f6 U( z- @! @9 S ~2 A
- //5,仅支持特权读访问
4 n% j+ J* Y6 N! W9 S - //6,只读(特权&用户都不可以写)7 f8 y( c4 e" e% S) y8 [
- //详见:STM32F7 Series Cortex-M7 processor programming manual.pdf,4.6节,Table 89., z5 n: _& f9 ]' W
- //sen:是否允许共用;0,不允许;1,允许
. c! C: F9 E, l0 m( Z. H3 l9 ^ - //cen:是否允许catch;0,不允许;1,允许) M. u9 d6 i* \0 s: U$ n5 B
- //返回值;0,成功.- Y$ U- i; d) x2 c$ |$ }; `
- // 其他,错误.
+ r" E5 d! |4 n! G" } - u8 MPU_Set_Protection(u32 baseaddr,u32 size,u32 rnum,u32 ap,u8 sen,u8 cen,u8 ben,u8 Tex)
+ s8 r" f0 `" Z g - {
+ Y) A$ B$ R$ ~8 I - MPU_Region_InitTypeDef MPU_Initure;
c6 H) b& [" F( ~ - HAL_MPU_Disable(); //配置MPU之前先关闭MPU,配置完成以后在使能MPU
2 a8 `( x' f. F7 X; ] ?- `
% a+ X/ b# H# t- B# I. J: M- MPU_Initure.Enable=MPU_REGION_ENABLE; //使能该保护区域 / Z' D, C/ j+ W) Z- f1 g
- MPU_Initure.Number=rnum; //设置保护区域* f! u s) u7 w; Q. X# \9 f
- MPU_Initure.BaseAddress=baseaddr; //设置基址/ J9 V# a# i* z5 f+ _ p" W9 u- p# |
- MPU_Initure.Size=size; //设置保护区域大小; e9 H; }, t. v4 F- V
- MPU_Initure.SubRegionDisable=0X00; //禁止子区域
E' h; w( K7 U' z3 E- q# m8 S - MPU_Initure.TypeExtField=Tex; //设置类型扩展域
+ T8 v8 [ _- r" g) J. a - MPU_Initure.AccessPermission=(u8)ap; //设置访问权限,2 f) b9 n% s, h! [4 ]1 X0 d7 C
- MPU_Initure.DisableExec=MPU_INSTRUCTION_ACCESS_ENABLE; //允许指令访问(允许读取指令)2 Z8 s' b( ~$ H5 @
- MPU_Initure.IsShareable=sen; //是否允许共用7 n8 U9 ^: |3 M* \% q3 M
- MPU_Initure.IsCacheable=cen; //是否允许cache3 p6 r, E& E( R+ a
- MPU_Initure.IsBufferable=ben; //是否允许缓冲
6 L6 ?! U( y, F- U$ d; }: e- s - HAL_MPU_ConfigRegion(&MPU_Initure); //配置MPU/ I& v, m( p0 k: I
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); //开启MPU# w- E; z$ y( ]7 _
- return 0;
7 Y8 B* l. k* _1 w7 f- a" K/ W - }
) ?- l: s9 N% L' f; M1 W
! _: u- W% ^1 _2 g. ^, j; x. m) `. {- //设置需要保护的存储块
+ k! }) T2 K3 O( [! A& G - //必须对部分存储区域进行MPU保护,否则可能导致程序运行异常
6 T+ C2 I0 e7 |. c' B' s - //比如MCU屏不显示,摄像头采集数据出错等等问题.../ f& q9 m2 z; |3 Y
- void MPU_Memory_Protection(void) //特意把SRAM4设置为不允许cache,使用DMA的变量可以放在这里。但要注意相应DMA能否访问SRAM4% K1 C* |8 P9 ~2 I0 E: H
- {" t4 h8 N6 b4 Y
- MPU_Set_Protection(0x20000000,MPU_REGION_SIZE_128KB,MPU_REGION_NUMBER1,MPU_REGION_FULL_ACCESS,0,1,1,MPU_TEX_LEVEL0); //保护整个DTCM,共128K字节,禁止共用,允许cache,允许缓冲! u0 E: ?$ G3 t: T9 F1 I4 m$ z
- MPU_Set_Protection(0x24000000,MPU_REGION_SIZE_512KB,MPU_REGION_NUMBER2,MPU_REGION_FULL_ACCESS,0,1,1,MPU_TEX_LEVEL0); //保护整个内部SRAM,包括SRAM1,SRAM2和DTCM,共512K字节
7 x" s8 P5 z0 \3 ^0 f3 d - MPU_Set_Protection(0x30000000,MPU_REGION_SIZE_512KB,MPU_REGION_NUMBER3,MPU_REGION_FULL_ACCESS,0,1,1,MPU_TEX_LEVEL0); //保护整个SRAM1~SRAM3,共288K字节,禁止共用,允许cache,允许缓冲
( X8 t8 g7 V$ L. K+ Z - MPU_Set_Protection(0x38000000,MPU_REGION_SIZE_64KB ,MPU_REGION_NUMBER4,MPU_REGION_FULL_ACCESS,0,0,1,MPU_TEX_LEVEL0); //保护整个SRAM4,共64K字节,禁止共用,不允许cache,允许缓冲0 G: w) Z( E4 |( B, ?( E; m
- MPU_Set_Protection(0x60000000,MPU_REGION_SIZE_64MB ,MPU_REGION_NUMBER5,MPU_REGION_FULL_ACCESS,0,0,0,MPU_TEX_LEVEL0); //保护MCU LCD屏所在的FMC区域,,共64M字节,禁止共用,禁止cache,禁止缓冲
# r. L+ {$ J4 V7 n - MPU_Set_Protection(0xC0000000,MPU_REGION_SIZE_64MB ,MPU_REGION_NUMBER6,MPU_REGION_FULL_ACCESS,0,1,1,MPU_TEX_LEVEL0); //保护SDRAM区域,共32M字节,禁止共用,允许cache,允许缓冲$ Y* f- u& X2 K6 o% ]) e
- }
复制代码
4 ^& ]: J. ~1 Z: S* U四、其他
) U U$ c0 s! [$ E' b) l/ p
q# c" H4 g; X* P* X4 _ 值得一提的是,LTDC也是直接从RAM拿数据的,如果你使用了GUI(比如EMWIN),你的显示数据可能会暂存在Cache,而LTDC直接从RAM拿数据,就可能造成画面撕裂、重影、斑点之类的问题。解决方法是,把显存设置成透写。
' G5 Q3 H0 K3 A) ]$ x/ O7 o
& n- x% L1 ~" \: [ 从下面的图可以看到,Cache是在M7那个框里面的。而框外面的外设都可以直接与RAM交换数据,因此使用外设操作数据时都要考虑一下Cache的影响,不然异常可能难以预料。. v! b8 ~1 I3 a# s d1 a$ T
& `9 T: D4 r2 H! N8 S( V; R
6 T* [# J) q+ ?7 E+ v! }
% o9 e$ L, p: }$ X$ P0 n
' u0 Q2 D- C& E) r0 Y9 z |