一、Cache 1、介绍 Cache又分数据缓存D-Cache和指令缓冲I-Cache,STM32H7的数据缓存和指令缓存大小都是16KB。STM32H7主频是400MHz,除了TCM和Cache以400MHz工作,其它AXI SRAM,SRAM1,SRAM2等都是以200MHz工作。数据缓存D-Cache就是解决CPU加速访问SRAM。 如果每次CPU要读写SRAM区的数据,都能够在Cache里面进行,自然是最好的,实现了200MHz到400MHz的飞跃,实际是做不到的,因为数据Cache只有16KB大小,总有用完的时候。
9 M" a. q) V* ?: w$ D6 ^" ^4 Q/ N
2、操作,分为读操作和写操作 读操作: 如果CPU要读取的SRAM区数据在Cache中已经加载好,这就叫读命中(Cache hit),如果Cache里面没有怎么办,这就是所谓的读Cache Miss。 写操作: 如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域(专业词汇叫Cache Line,以32字节为单位),这就叫写命中(Cache hit),如果Cache里面没有开辟对应的区域怎么办,这就是所谓的写Cache Miss。 , a% _1 y4 V6 y& O- G( r' ?) I
3、H7支持的Cache策略,共4种 <回写:如果Cache中有,写数据只写到Cache,不写到RAM。> <透写:如果Cache中有,写数据也要同时写到Cache和RAM。> <write allocate:写数据时,如果Cache中没有,那么就要在Cache中开辟一个空间,把数据写入Cache,同时把RAM中的相邻数据加载进来填充Cache。> <no write allocate:写数据时,如果Cache中没有,那么把数据直接写入RAM。> <read allocate:读数据时,如果Cache中没有,那么就要在Cache中开辟一个空间,把数据从RAM中加载进来,后续的读操作,就可以直接从Cache中读取了。> <no read allocate:读数据时,如果Cache中没有,那么直接从RAM中读。>
1 z" h$ |% v! O* {
4、风险# q' D: G; z) v/ O3 T
从上面的图就看出来使用Cache的风险,因为DMA是直接与SRAM交换数据的,而CPU与SRAM之间隔了一个Cache,如果DMA更新了某个数据到SRAM,CPU要去访问,而恰好Cache中有,那么CPU就不会去SRAM中拿,就会拿到Cache中已经过时的数据。因此使用了DMA的内存区要配置为无Cache或者拿数据前清一次Cache。
( x ~* @; x2 J% c6 S; c
5、相关函数
% P5 s! O! g* g上面那个函数用于使能指令Cache,系统上电后优先初始化即可。 上面那个函数用于禁止指令Cache。 - SCB_InvalidateICache(void)
复制代码上面那个函数用于将指令Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的指令。 上面那个函数用于使能数据Cache,系统上电后优先初始化即可。 上面那个函数用于禁止数据Cache。 - SCB_InvalidateDCache(void)
复制代码上面那个函数用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。 上面那个函数用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。 - SCB_CleanInvalidateDCache(void)
复制代码上面那个函数是前面两个函数SCB_InvalidateDCache和SCB_CleanDCache的二合一。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。 - SCB_InvalidateDCache_by_Addr(uint32_t *addr,int32_t dsize)
复制代码
/ M/ w4 H9 U5 S* V/ d上面那个函数可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。 - SCB_CleanDCache_by_Addr(uint32_t *addr,int32_t dsize)
复制代码上面那个函数可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。 - SCB_CleanInvalidateDCache_by_Addr(uint32_t *addr,int32_t dsize)
复制代码
3 w1 ^3 q" t1 p. x) e I q9 p7 a上面那个函数可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。
: e q+ k1 A1 O6 S+ J- Y 二、MPU1、作用 防止不受信任的应用程序访问受保护的内存区域; 防止用户应用程序破坏操作系统使用的数据;通过阻止任务访问其它任务的数据区;允许将内存区域定义为只读,以便保护重要数据;检测意外的内存访问。 简单的说就是内存保护、外设保护和代码访问保护。 2、MPU可以配置的三种内存类型 1)、Normal memory CPU以最高效的方式加载和存储字节、半字和字,对于这种内存区,CPU的加载或存储不一定要按照程序列出的顺序执行。 2)、Device memory 对于这种类型的内存区,加载和存储要严格按照次序进行,这样是为了确保寄存器按照正确顺序设置。 3)、Strongly ordered memory 程序完全按照代码顺序执行,CPU需要等待当前的加载/存储指令执行完毕后才执行下一条指令。这样会导致性能下降。 3、MPU的使用 MPU可以配置保护16个内存区域(这16个内存域是独立配置的),每个区域最小要求256字节,每个区域还可以配置为8个子区域。由于子区域一般都相同大小,这样每个子区域的大小就是32字节,正好跟Cache的Cache Line大小一样。 使用时把一段连续的内存区配置为一个MPU保护区域,然后再配置这个MPU保护区域的特性。比如128KB的DTCM、64KB的SRAM4、32MB的SDRAM。MPU保护区域的特性使用MPU_RASR寄存器来配置,描述如下:
& h$ H3 z/ O% c' D/ E' B5 w ! s& _# E! ~3 C4 {. w9 v' `8 @
1)、XN:用于控制这个MPU保护区域能否执行程序代码。 2)、AP:用于控制这个MPU保护区域的特权级和非特权级的读写访问权限。2 `1 R! R r, E$ y. }( a
# I$ @/ h) G! s# P
3)、TEX、C、B、S:H7支持4种Cache策略,这几位就是用来控制这个MPU保护区域使用哪一种。: r5 q* F2 D: m4 A

" F; m1 {: U; `" V# D( m) C1 ^S位用于解决多总线或者多核访问的共享问题,一般不要开启。 4)、SRD:这个位用于控制内存区的子区域 ,使用的是bit[15:8],共计8个bit,一个bit控制一个子区域, 0表示使能此子区域, 1表示禁止。一般情况下,取值0x00,表示8个子区域都使能。
2 ?: Z& P) D. D5)、SIZE:配置这个MPU保护区域的大小。
, Y! F( n4 O6 U0 C: `. G# r三、HAL配置例程
- //设置某个区域的MPU保护8 {9 _ b3 K0 o5 O( T* r! s8 ~
- //baseaddr:MPU保护区域的基址(首地址)4 H4 D* S; b5 \: W- D; J( N
- //size:MPU保护区域的大小(必须是32的倍数,单位为字节),可设置的值参考:CORTEX_MPU_Region_Size/ |- h+ L. N! Y2 h
- //rnum:MPU保护区编号,范围:0~7,最大支持8个保护区域,可设置的值参考:CORTEX_MPU_Region_Number' y* i( F0 `0 _: ?0 {4 ~8 ~
- //ap:访问权限,访问关系如下:可设置的值参考:CORTEX_MPU_Region_Permission_Attributes
8 G: t5 E4 X9 C- x5 Y g3 C - //0,无访问(特权&用户都不可访问)
2 W" N# F. ~' j. F - //1,仅支持特权读写访问
, b4 l5 y4 @/ A) @+ X1 H - //2,禁止用户写访问(特权可读写访问)( [# A4 b0 J5 l! [6 K
- //3,全访问(特权&用户都可访问)& d+ O/ L* o. O" R t% b( }, c* D
- //4,无法预测(禁止设置为4!!!)! A+ e* q4 }- c" n: V
- //5,仅支持特权读访问
' a9 G+ D/ [* |' l5 c# p8 D: M - //6,只读(特权&用户都不可以写)( E1 c$ e7 g2 u/ y0 e
- //详见:STM32F7 Series Cortex-M7 processor programming manual.pdf,4.6节,Table 89.! _1 |) }! P {) b/ W1 q: B
- //sen:是否允许共用;0,不允许;1,允许$ f* ?7 a3 ]9 e# n. K9 e
- //cen:是否允许catch;0,不允许;1,允许6 W0 Q+ U/ j! O7 Q& L
- //返回值;0,成功.& {4 T" Z( E% D; |6 p( J
- // 其他,错误.
- L# u$ j; b$ n& u - u8 MPU_Set_Protection(u32 baseaddr,u32 size,u32 rnum,u32 ap,u8 sen,u8 cen,u8 ben,u8 Tex)
) J; r. f' F6 n$ a8 X7 u" y6 l - {8 K, n0 m0 i! @; V9 A
- MPU_Region_InitTypeDef MPU_Initure;, M9 {5 U, j8 ^ W/ p
- HAL_MPU_Disable(); //配置MPU之前先关闭MPU,配置完成以后在使能MPU
: k' L; ^- \3 t& B1 ~ - 4 v8 ~9 `! p- r# U8 L X7 A
- MPU_Initure.Enable=MPU_REGION_ENABLE; //使能该保护区域 1 ~; I) K) A: I
- MPU_Initure.Number=rnum; //设置保护区域 k6 d/ s' Z+ f" r6 B
- MPU_Initure.BaseAddress=baseaddr; //设置基址
' u: _, v' j6 ]. T8 B/ @0 I - MPU_Initure.Size=size; //设置保护区域大小
2 J+ K: ]" D# S( u: u1 B, u- R - MPU_Initure.SubRegionDisable=0X00; //禁止子区域
1 a! }) f" r# ]- }) y - MPU_Initure.TypeExtField=Tex; //设置类型扩展域
0 |) t' s6 T- Q8 ^0 _* h* y - MPU_Initure.AccessPermission=(u8)ap; //设置访问权限,2 x, r9 y# C' x% O$ G3 u- c$ @( `
- MPU_Initure.DisableExec=MPU_INSTRUCTION_ACCESS_ENABLE; //允许指令访问(允许读取指令)
8 p; q5 {/ o" Z; F5 i$ i& Q - MPU_Initure.IsShareable=sen; //是否允许共用
6 }8 h9 h4 p- _" O) x - MPU_Initure.IsCacheable=cen; //是否允许cache
4 C' U; k& ^8 ~+ h4 f- h - MPU_Initure.IsBufferable=ben; //是否允许缓冲
/ O, U' f- Z0 V" ^ - HAL_MPU_ConfigRegion(&MPU_Initure); //配置MPU
0 b0 c/ P5 V; M2 w+ R/ a! f4 i2 |: J - HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); //开启MPU
- X+ `; y- X. g - return 0;' o1 z. H( B$ v0 E# n
- }. \$ m8 `! t# m" i6 b
- , Y5 d6 T# g, R. K7 c% J0 ]
- //设置需要保护的存储块! r; _ J( G" |
- //必须对部分存储区域进行MPU保护,否则可能导致程序运行异常
2 v- R- C4 K5 s) Y' s/ \0 g - //比如MCU屏不显示,摄像头采集数据出错等等问题... W' Y1 A, z9 M9 M5 d9 o* X3 K
- void MPU_Memory_Protection(void) //特意把SRAM4设置为不允许cache,使用DMA的变量可以放在这里。但要注意相应DMA能否访问SRAM4
+ g( }* f) u3 b - {
% f; Q6 e; ?+ u" X7 c - MPU_Set_Protection(0x20000000,MPU_REGION_SIZE_128KB,MPU_REGION_NUMBER1,MPU_REGION_FULL_ACCESS,0,1,1,MPU_TEX_LEVEL0); //保护整个DTCM,共128K字节,禁止共用,允许cache,允许缓冲 C0 q6 d) Y2 N; Q, O
- 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字节4 r$ k- X: E5 q4 t i, ~
- 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,允许缓冲
7 T6 B& j2 ?1 {" [; S l I. ` - 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 @7 |8 F n* @6 X4 v - 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,禁止缓冲
# N; X& |. T1 {6 L - MPU_Set_Protection(0xC0000000,MPU_REGION_SIZE_64MB ,MPU_REGION_NUMBER6,MPU_REGION_FULL_ACCESS,0,1,1,MPU_TEX_LEVEL0); //保护SDRAM区域,共32M字节,禁止共用,允许cache,允许缓冲
# S0 V! V' s4 r4 Q! j! C. O g* B& m - }4 T; ` S+ i [, |( U
复制代码 $ G0 ?9 f8 v$ f0 o% K
7 C* ^) w2 a' u/ s
四、其他 - 值得一提的是,LTDC也是直接从RAM拿数据的,如果你使用了GUI(比如EMWIN),你的显示数据可能会暂存在Cache,而LTDC直接从RAM拿数据,就可能造成画面撕裂、重影、斑点之类的问题。解决方法是,把显存设置成透写。
) B1 \* N U7 m8 \, l D Z7 ~ - # M, z% S' W1 m1 R# @! P i' ~
- 从下面的图可以看到,Cache是在M7那个框里面的。而框外面的外设都可以直接与RAM交换数据,因此使用外设操作数据时都要考虑一下Cache的影响,不然异常可能难以预料。7 u/ |" o& s* Z( ^- Y5 H7 y, M
复制代码 # F2 }- g7 U% I3 q& S5 F

, j; i& h# m( v! ]& z" o$ W8 ]* m) V0 h8 n8 ^
三、H7的内存分配以及总结首先呢,大家在使用H743 的时候,在keil或者iar中,有一个内存的勾选,如下图
" u+ j0 \9 @+ _: z/ c. U$ L$ zIROM1表示flash的内存地址,h743是2M,所以,大小自然是2M,起始地址可以在参考手册中找到,如下图,这个比较容易理解,程序就是从这开始运行:
; W3 \7 N) x2 a4 A $ }9 Y* [9 M: |* N
其次,是RAM,743内部有1M,那么这1M怎么分的呢,直接说结论,从地址0x2000 0000开始到(0x3880 0000+64k)结束,加起来是1M,问题来了,为什么我们配置的时候要用0x2400 0000开始的512k呢?不知道大家看参考手册看的怎么样,开头讲了ram 的分配。/ Q s5 Z" h+ V, e* p
1M的RAM,分成了5大类,TCM、AXI 、SRAM1/SRAM2/SRAM3/SRAM4、BACK,他们所在的区域不同,TCM包含ITCM和DTCM,地址0x2000 0000的128K,位于D1域,ITCM主要用来存取程序代码,也就是执行命令,DTCM用于数据存取。先把所有的内存分配做个总结再说。6 z. y: k" }6 x1 \4 e
{9 i9 Z5 ^& C4 M1 x2 `4 I1 {! D; V
1、TCM分为ITCM(运行代码)和DTCM(数据存取)6 r$ x% @ Q3 \# B/ |& l
2、速度400MHZ
+ k3 d, r0 C/ f3、DTCM地址:0x2000 0000,大小128KB# |# t5 M1 X* K& q; @* P9 V/ i
ITCM地址:0x0000 0000,大小64KB
+ _# K9 ?& U' D+ d" n$ T% X}( i6 X$ K5 j/ a5 R1 i, F
AXI SRAM 区4 ]% Q! v& j- E* Y! p# W. }; z
{- O3 R0 \% s5 D" I2 O6 _% ]
1、位于D1域,挂在A**线
# _* J! e @5 e) Y. j! N( R# A2、速度200MHZ9 g& i5 i, u' \9 a* u- u
3、地址:0x2400 0000,大小512KB }
. e# B3 E5 e* C& I5 [8 H& xSRAM1,SRAM2,SRAM3 区
+ x0 L5 T6 w) Z{
. M6 L% q& Q: Z D8 l1、位于D2域,挂在AHB总线
3 l" K4 {+ t+ S( U2、速度200MHZ- U: x) U$ d5 q1 v1 z, ?% K1 i7 s, r% m
3、SRAM1地址:0x3000 0000,大小128KB
! E3 z9 v9 @4 l% m4 r2 q( C! r1 zSRAM2地址:0x3002 0000,大小128KB6 \+ H2 s3 g* h: S% g- m3 R* k
SRAM3地址:0x3004 0000,大小32KB& A* n9 b! h' G# n0 t/ r- {
}- H- M4 o# m3 k4 A9 u1 P0 X" U$ b
SRAM4区5 A i) S G: R" f( }
{ E2 z& _( u4 C: R2 x; M% `' i& J+ M
1、位于D3域,挂在AHB总线$ M. x2 M3 u3 h; e; @
2、速度200MHZ
) k! N( X) z& K3、地址:0x3800 0000,大小64KB }, h/ x* h% S- J+ o+ R+ g7 y6 J( V& Q
Backup SRAM区
6 i* K. a9 Z' _. x C% s; x{
: c" D8 g2 P# w4 T1、位于D3域,挂在AHB总线
2 V \4 y' B! A1 H5 `1 [2 A, m2、速度200MHZ
, o; n& I; V6 {( p$ c5 W3、地址:0x3880 0000,大小4KB } ok,还是原先的问题,为什么要用0x2400 0000开始做程序的主RAM呢,原因在于,如果用户使用了SDMMC1,也就是比如SD卡的dma功能,或者其他用到了SDMMC1的话,该主设备只接AXI RAM,不能使用其他RAM读取,可以不用,声明变量的时候使用at section指向AXI地址也是可以的。 其他几个RAM,需要跟BDMA用的时候进行区分,有些是不支持BDMA的。剩下的就没啥了,其他内容可以看下参考手册第二章。 如果大家功能少的话,可以使用DTCM来当主RAM,毕竟速度最快,但是用到了相关DMA的话,还是换成AXI吧。野火的代码里面默认是使用这个。
1 [5 I! A r: J# S) D5 U& z! Y |