一、Cache
& C; g" \7 A; A" Y: @4 Q, U- h [2 A. \7 p9 L) R! \; ]7 |
1、介绍
7 X& l) r( c5 t+ s6 e& W. V5 a4 T( M: W& [( U! @, ` h' U: S4 c. ^) \
Cache又分数据缓存D-Cache和指令缓冲I-Cache,STM32H7的数据缓存和指令缓存大小都是16KB。STM32H7主频是400MHz,除了TCM和Cache以400MHz工作,其它AXI SRAM,SRAM1,SRAM2等都是以200MHz工作。数据缓存D-Cache就是解决CPU加速访问SRAM。
: E$ C. P( p4 j1 v2 q& l3 @7 V! m1 F
如果每次CPU要读写SRAM区的数据,都能够在Cache里面进行,自然是最好的,实现了200MHz到400MHz的飞跃,实际是做不到的,因为数据Cache只有16KB大小,总有用完的时候。: D0 c$ y$ b1 r. C
0 W; A0 p6 r, b) t" A2、操作,分为读操作和写操作
" \& _! J! \5 J4 W- J! @) O* v8 f7 s9 U& F& x
读操作: 如果CPU要读取的SRAM区数据在Cache中已经加载好,这就叫读命中(Cache hit),如果Cache里面没有怎么办,这就是所谓的读Cache Miss。
9 h, T# Y, q) o, X$ r$ Z: ^$ U5 o8 Q7 b$ t
写操作: 如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域(专业词汇叫Cache Line,以32字节为单位),这就叫写命中(Cache hit),如果Cache里面没有开辟对应的区域怎么办,这就是所谓的写Cache Miss。
4 d8 t3 s3 h& d9 l" s
h- f( ?) M- ~2 z$ Y& M3、H7支持的Cache策略,共4种
4 Q6 A+ ~8 J: f+ n
: D: s) P- Y- O2 X2 t/ M, X ]; d$ F: Q! P* T5 E
" @6 g6 i( s0 \4 M3 s
<回写:如果Cache中有,写数据只写到Cache,不写到RAM。>5 U( b- j8 a; F
* i: T% [4 E) E& _<透写:如果Cache中有,写数据也要同时写到Cache和RAM。>
b# j2 }, q" u1 N9 F a1 y& \1 m/ M' |2 U. v
<write allocate:写数据时,如果Cache中没有,那么就要在Cache中开辟一个空间,把数据写入Cache,同时把RAM中的相邻数据加载进来填充Cache。>
9 @ Q |+ w9 G; e! t/ d1 H
2 [- t- h7 M ]<no write allocate:写数据时,如果Cache中没有,那么把数据直接写入RAM。>
+ W' q! ~ X! F% w2 E6 s, ~
( J' Y- }0 L9 n% Y5 z<read allocate:读数据时,如果Cache中没有,那么就要在Cache中开辟一个空间,把数据从RAM中加载进来,后续的读操作,就可以直接从Cache中读取了。>
6 @2 V; l+ L; P- f, v) H9 w4 G; j; K9 v( i6 Y* z' Q1 O
<no read allocate:读数据时,如果Cache中没有,那么直接从RAM中读。>
. `+ Z! J% U& ^, W& G
' E! Y% p+ Z( n5 p l4、风险
5 Q; q4 i0 p' v' r; d& d: u/ t/ S
- G7 `+ ~" q0 `+ t h- y$ i( X8 N. P8 ]9 d' } {! c
f2 Z% v) Q' \. V; S 从上面的图就看出来使用Cache的风险,因为DMA是直接与SRAM交换数据的,而CPU与SRAM之间隔了一个Cache,如果DMA更新了某个数据到SRAM,CPU要去访问,而恰好Cache中有,那么CPU就不会去SRAM中拿,就会拿到Cache中已经过时的数据。因此使用了DMA的内存区要配置为无Cache或者拿数据前清一次Cache。
3 N* R1 H) k2 I/ ] y
9 i6 C# @8 I: G- y5 b# ]' j5、相关函数
7 Q" |' W9 {/ V4 c- c( w# p' Q4 Z1 g# e# z
SCB_EnableICache(void) :用于使能指令Cache,系统上电后优先初始化即可。
8 \6 V' }$ o* s* j, K0 I4 t; a- d
SCB_DisableICache(void) :用于禁止指令Cache。) J7 ]/ J) K& Z7 W+ d1 i+ F8 C
8 c' I9 O3 d) B6 }: t, a
SCB_InvalidateICache(void) :用于将指令Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的指令。7 [4 c% U7 ~ k, N
( b/ N0 A) n' {4 x SCB_EnableDCache(void) :用于使能数据Cache,系统上电后优先初始化即可。2 W8 U, d+ u) _/ W% l* F
1 u) B. A' q: x h/ O SCB_DisableDCache(void) :用于禁止数据Cache。
|! b7 e" M& a; R- G/ {# \9 i
SCB_InvalidateDCache(void) :用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。
$ q4 s# E: Q" a. w
# f% ^! f( Z, k, f- z, [, J( |6 w$ z9 v SCB_CleanDCache(void):用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。
- T" [5 `0 B2 H0 t( F0 u# p) a* D2 q% K8 X! J6 W4 R- v
SCB_CleanInvalidateDCache(void) :此函数是前面两个函数SCB_InvalidateDCache和SCB_CleanDCache的二合一。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。4 k! s: }2 ?* k; P( ]8 b/ i
5 @4 H. ]- V9 s. u. j
SCB_InvalidateDCache_by_Addr(uint32_t *addr,int32_t dsize):可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。( b4 U( q) O5 P; W/ R/ \
% O. |) q ~* P3 }. y SCB_CleanDCache_by_Addr(uint32_t *addr,int32_t dsize):可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。
# \- y& V& |2 U: X
3 W1 f. J1 w& V: J6 n5 v SCB_CleanInvalidateDCache_by_Addr(uint32_t *addr,int32_t dsize):可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。
$ G" v7 d* T) s3 K: s' Z
. \( d4 A- b9 t/ t N5 r2 p- b) `. C+ X7 B" i# @
" M; c( S4 i8 B" V7 o3 [
二、MPU- E: Y; g$ i# o$ h
' g8 V: |4 ]; v; `0 J. M1、作用) T1 p& \3 k, U8 k6 \4 \
* Y5 q! G, ~) Y; [4 P3 {7 X 防止不受信任的应用程序访问受保护的内存区域; 防止用户应用程序破坏操作系统使用的数据;通过阻止任务访问其它任务的数据区;允许将内存区域定义为只读,以便保护重要数据;检测意外的内存访问。 简单的说就是内存保护、外设保护和代码访问保护。0 n* L& C4 T4 h& H0 }' a
k5 @1 W- N. }' u
2、MPU可以配置的三种内存类型
i( ?6 A6 p& s0 O1 r6 N6 ^' n% I
. o: k: y& E: _. n+ L" D. g1)、Normal memory
* p/ R/ t: v. C, w U3 f
5 L( Y/ |8 j: X! V$ A) z CPU以最高效的方式加载和存储字节、半字和字,对于这种内存区,CPU的加载或存储不一定要按照程序列出的顺序执行。
1 \, { L9 A7 V" n$ ^& D
& I. l% F" e" l, J `& ~2)、Device memory# N7 L& K& B9 m! P/ K
5 E/ H- A/ \7 w3 H1 U- ]3 ?* n7 v 对于这种类型的内存区,加载和存储要严格按照次序进行,这样是为了确保寄存器按照正确顺序设置。
; T% u4 U8 r3 b e" }8 f/ o' i$ g
i" _6 `' Z0 ]: N3)、Strongly ordered memory
" g/ O+ Q% p9 T/ P: E
7 D0 ^7 F# s }$ [/ h, B 程序完全按照代码顺序执行,CPU需要等待当前的加载/存储指令执行完毕后才执行下一条指令。这样会导致性能下降。" L Z# D1 `, h
4 U8 U% p/ |7 L1 _
3、MPU的使用" s: ^7 A# g$ h. H7 q: L8 e
! g2 T6 k' c {# g! O MPU可以配置保护16个内存区域(这16个内存域是独立配置的),每个区域最小要求256字节,每个区域还可以配置为8个子区域。由于子区域一般都相同大小,这样每个子区域的大小就是32字节,正好跟Cache的Cache Line大小一样。) H" E! d4 q% F! l1 \
# S# E. d1 M5 ^1 R
使用时把一段连续的内存区配置为一个MPU保护区域,然后再配置这个MPU保护区域的特性。比如128KB的DTCM、64KB的SRAM4、32MB的SDRAM。MPU保护区域的特性使用MPU_RASR寄存器来配置,描述如下:
. H% y- }2 C6 N2 m( @; z( J7 n7 l4 u! N
: y0 F" M. ]+ b6 D% U5 ]9 R! i6 H# `7 o' z K! r
1)、XN:用于控制这个MPU保护区域能否执行程序代码。
9 d Z- @: t0 Z5 j2 c) v$ A
' `7 ?% h' Q5 ~6 J2)、AP:用于控制这个MPU保护区域的特权级和非特权级的读写访问权限。7 |5 E+ l* F3 z
/ ?0 \* K2 y& X2 P& l* |1 K! @6 U& m7 E8 I7 O j
/ L$ c8 q D5 ^2 r5 m3)、TEX、C、B、S:H7支持4种Cache策略,这几位就是用来控制这个MPU保护区域使用哪一种。
( R; i" M7 a. T _6 b2 ?/ c6 S
+ [" R, x5 L: {" l/ n9 s
( X) `0 y$ ~" W& C5 t0 Q5 M/ a! {9 w; C
S位用于解决多总线或者多核访问的共享问题,一般不要开启。
5 ?- k$ V% P: ~9 v2 W+ u6 q8 v. |4 y4 I { i1 S
4)、SRD:这个位用于控制内存区的子区域 ,使用的是bit[15:8],共计8个bit,一个bit控制一个子区域, 0表示使能此子区域, 1表示禁止。一般情况下,取值0x00,表示8个子区域都使能。& p8 X; {, e& Q9 c. C6 m0 y! N- H/ ^
( D3 e% K I1 A4 ~* T; j5)、SIZE:配置这个MPU保护区域的大小。
$ N: C( V/ u6 m" ^' p" O# ^+ W4 Y& C, z. K9 \. _) ?( N
三、HAL配置例程7 f5 K" K& d* P; F
+ g- L0 L8 k$ h6 v
- //设置某个区域的MPU保护
. m+ A& W: m. X, N) X/ P8 d @ - //baseaddr:MPU保护区域的基址(首地址)6 L8 C4 d7 u6 _6 L5 c m( p: E
- //size:MPU保护区域的大小(必须是32的倍数,单位为字节),可设置的值参考:CORTEX_MPU_Region_Size
( a4 l; g( K" d. z* ] - //rnum:MPU保护区编号,范围:0~7,最大支持8个保护区域,可设置的值参考:CORTEX_MPU_Region_Number. A1 H) @" ~/ j B8 ^+ a6 i$ G& @
- //ap:访问权限,访问关系如下:可设置的值参考:CORTEX_MPU_Region_Permission_Attributes
. i+ r1 N' d ^1 S& g$ l( e - //0,无访问(特权&用户都不可访问)5 A- K0 d6 ~3 E9 d3 b5 j6 ~9 V( |
- //1,仅支持特权读写访问1 w) z, g, |- Z6 }' ]; y
- //2,禁止用户写访问(特权可读写访问)
& Z& h ~8 M& y* `! o) w - //3,全访问(特权&用户都可访问)
0 }6 E; D/ ^0 }" f0 g7 l R4 E1 o5 E - //4,无法预测(禁止设置为4!!!)" ]# Q+ A% y" a$ p+ c [6 s: J# i
- //5,仅支持特权读访问
; X* R& A3 D5 F# e - //6,只读(特权&用户都不可以写)
- V! X! ]" N! c! e3 p+ S0 [, S3 [ - //详见:STM32F7 Series Cortex-M7 processor programming manual.pdf,4.6节,Table 89. q5 {8 `, T: W& {4 e# ^
- //sen:是否允许共用;0,不允许;1,允许& ?3 l+ S- P1 t+ [% |4 ]; t! [
- //cen:是否允许catch;0,不允许;1,允许* [; b8 ]. [3 r) n5 g3 a4 c" d$ I
- //返回值;0,成功.1 ?$ j+ R( |# e, P. H: K
- // 其他,错误.
; S, A; B; O: Y8 R+ x7 w) ~& b - u8 MPU_Set_Protection(u32 baseaddr,u32 size,u32 rnum,u32 ap,u8 sen,u8 cen,u8 ben,u8 Tex)
7 o/ a. |: Q3 q! z& } - {
9 Q* g b3 \; W$ `' w& D - MPU_Region_InitTypeDef MPU_Initure;
) \8 O0 O" v4 Q: {' z- F F/ T - HAL_MPU_Disable(); //配置MPU之前先关闭MPU,配置完成以后在使能MPU+ V; r+ a9 {: G2 N, ^
- 9 e- R3 u7 B- I% Q5 ?
- MPU_Initure.Enable=MPU_REGION_ENABLE; //使能该保护区域 % l6 N' ~7 [$ T( [: z
- MPU_Initure.Number=rnum; //设置保护区域/ h' m, q6 Z' m7 v& f' q7 ~
- MPU_Initure.BaseAddress=baseaddr; //设置基址
+ b& q d; g7 Q, M2 Q8 S - MPU_Initure.Size=size; //设置保护区域大小# x1 {5 M4 b! S2 j4 X. ?% C
- MPU_Initure.SubRegionDisable=0X00; //禁止子区域
2 {8 V% U& J! y ^8 d/ s - MPU_Initure.TypeExtField=Tex; //设置类型扩展域& @( a4 d- d- u1 z
- MPU_Initure.AccessPermission=(u8)ap; //设置访问权限,* |6 \% \# q7 _( s6 b) O' D
- MPU_Initure.DisableExec=MPU_INSTRUCTION_ACCESS_ENABLE; //允许指令访问(允许读取指令): U7 b2 A) m: W8 [
- MPU_Initure.IsShareable=sen; //是否允许共用
+ f9 Q! V7 l0 p& W1 [: I; i4 S - MPU_Initure.IsCacheable=cen; //是否允许cache5 s/ x1 \' }* J2 X I2 m% r
- MPU_Initure.IsBufferable=ben; //是否允许缓冲* j1 Q" f n" p. B8 ?! W
- HAL_MPU_ConfigRegion(&MPU_Initure); //配置MPU6 h1 I. g, s P9 E/ u7 _
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); //开启MPU+ e" _& X$ V" T- s
- return 0;* T1 _- z, R( f3 F- H( K% C
- }
& S0 {0 U9 @1 w+ c, U1 W% I |) ? - ) }: k* m7 Z/ ^0 N2 H+ r
- //设置需要保护的存储块
3 L- O' A- @3 C Z - //必须对部分存储区域进行MPU保护,否则可能导致程序运行异常
7 d5 ?$ J' N2 o, O7 T' w# L - //比如MCU屏不显示,摄像头采集数据出错等等问题...
' [1 y) L9 L! [- F! Y - void MPU_Memory_Protection(void) //特意把SRAM4设置为不允许cache,使用DMA的变量可以放在这里。但要注意相应DMA能否访问SRAM4$ u8 Z* ~$ k9 @, c7 I
- {! I/ i( `: E8 n9 `4 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,允许缓冲
% C& r8 e. E$ h7 u3 i - 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字节9 e: i, _; i- p' ~! C* F8 o) _- o. {' G
- 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,允许缓冲
0 B: c5 J ~( B0 q - MPU_Set_Protection(0x38000000,MPU_REGION_SIZE_64KB ,MPU_REGION_NUMBER4,MPU_REGION_FULL_ACCESS,0,0,1,MPU_TEX_LEVEL0); //保护整个SRAM4,共64K字节,禁止共用,不允许cache,允许缓冲
5 M% U' g. f0 v% l$ X8 F - 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,禁止缓冲
& A Q' X* u5 c+ ` - MPU_Set_Protection(0xC0000000,MPU_REGION_SIZE_64MB ,MPU_REGION_NUMBER6,MPU_REGION_FULL_ACCESS,0,1,1,MPU_TEX_LEVEL0); //保护SDRAM区域,共32M字节,禁止共用,允许cache,允许缓冲
0 }6 _9 k$ h) i* D6 i* s. M/ Q - }
复制代码
6 a, ~# _6 m E1 e: r/ a, J四、其他0 g9 p! H" u' M6 o
( H7 ~6 i. ?& Q1 P" O E1 X% q 值得一提的是,LTDC也是直接从RAM拿数据的,如果你使用了GUI(比如EMWIN),你的显示数据可能会暂存在Cache,而LTDC直接从RAM拿数据,就可能造成画面撕裂、重影、斑点之类的问题。解决方法是,把显存设置成透写。 a0 J: G5 Y0 p z: X9 {4 X# ?: O
6 ~7 o$ e- d6 g9 n 从下面的图可以看到,Cache是在M7那个框里面的。而框外面的外设都可以直接与RAM交换数据,因此使用外设操作数据时都要考虑一下Cache的影响,不然异常可能难以预料。2 a, D' p2 W. p6 J9 T
7 @2 x- {( B$ b* Z5 J% M( n
5 V$ E8 h7 n3 J# e
0 t/ D6 r* Z. J/ k2 ~1 F: B
) [, K k' x: S6 g7 A |