你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32H7的Cache和MPU以及内存分配问题

[复制链接]
STMCU小助手 发布时间:2021-12-22 14:36
一、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大小,总有用完的时候。


" R2 c. V3 `+ Q, f( X

2、操作,分为读操作和写操作

读操作: 如果CPU要读取的SRAM区数据在Cache中已经加载好,这就叫读命中(Cache hit),如果Cache里面没有怎么办,这就是所谓的读Cache Miss。

写操作: 如果CPU要写的SRAM区数据在Cache中已经开辟了对应的区域(专业词汇叫Cache Line,以32字节为单位),这就叫写命中(Cache hit),如果Cache里面没有开辟对应的区域怎么办,这就是所谓的写Cache Miss。

1 K/ _2 y. x1 L; q+ B$ |. J7 J

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中读。>


6 E/ \% A/ `' ?, H

4、风险  M& f  X) x" t6 Y
从上面的图就看出来使用Cache的风险,因为DMA是直接与SRAM交换数据的,而CPU与SRAM之间隔了一个Cache,如果DMA更新了某个数据到SRAM,CPU要去访问,而恰好Cache中有,那么CPU就不会去SRAM中拿,就会拿到Cache中已经过时的数据。因此使用了DMA的内存区要配置为无Cache或者拿数据前清一次Cache。


" q) f/ i" v6 d, @9 ~' X' S

5、相关函数

  1. SCB_EnableICache(void)
复制代码
6 V5 J6 @* n; D/ y# |

上面那个函数用于使能指令Cache,系统上电后优先初始化即可。

  1. SCB_DisableICache(void)
复制代码

上面那个函数用于禁止指令Cache。

  1. SCB_InvalidateICache(void)
复制代码

上面那个函数用于将指令Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的指令。

  1. SCB_EnableDCache(void)
复制代码

上面那个函数用于使能数据Cache,系统上电后优先初始化即可。

  1. SCB_DisableDCache(void)
复制代码

上面那个函数用于禁止数据Cache。

  1. SCB_InvalidateDCache(void)
复制代码

上面那个函数用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。

  1. SCB_CleanDCache(void)
复制代码

上面那个函数用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。

  1. SCB_CleanInvalidateDCache(void)
复制代码

上面那个函数是前面两个函数SCB_InvalidateDCache和SCB_CleanDCache的二合一。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。

  1. SCB_InvalidateDCache_by_Addr(uint32_t *addr,int32_t dsize)
复制代码

* a! T% V3 D! V- F7 u

上面那个函数可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。用于将数据Cache无效化,无效化的意思是将Cache Line标记为无效,等同于删除操作。这样Cache空间就都腾出来了,可以加载新的数据。

  1. SCB_CleanDCache_by_Addr(uint32_t *addr,int32_t dsize)
复制代码

上面那个函数可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。用于将数据Cache清除,清除的意思是将Cache Line中标记为dirty的数据写入到相应的存储区。

  1. SCB_CleanInvalidateDCache_by_Addr(uint32_t *addr,int32_t dsize)
复制代码
' S$ U9 b9 j" t$ C4 e- G0 _7 }

上面那个函数可以指定地址和存储区大小,地址要32字节对齐,大小要是32字节的整数倍。将Cache Line中标记为dirty的数据写入到相应的存储区后,再将Cache Line标记为无效,表示删除。这样Cache空间就都腾出来了,可以加载新的数据。

8 s5 T1 `9 a: ^; S" O/ l! \$ d( _$ u2 Y

二、MPU

1、作用

防止不受信任的应用程序访问受保护的内存区域; 防止用户应用程序破坏操作系统使用的数据;通过阻止任务访问其它任务的数据区;允许将内存区域定义为只读,以便保护重要数据;检测意外的内存访问。 简单的说就是内存保护、外设保护和代码访问保护。

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寄存器来配置,描述如下:
& v  W& B6 g( A) S# |/ v4 A1 P) v) u% q; ]
1)、XN:用于控制这个MPU保护区域能否执行程序代码。

2)、AP:用于控制这个MPU保护区域的特权级和非特权级的读写访问权限。/ D, j2 g/ Z+ k7 U
  `0 O! o9 T: H2 j0 A1 w6 o
3)、TEX、C、B、S:H7支持4种Cache策略,这几位就是用来控制这个MPU保护区域使用哪一种。
* c8 o% K% l+ ~* P% S5 A8 a- R5 _0 A9 h6 x( l  N
S位用于解决多总线或者多核访问的共享问题,一般不要开启。

4)、SRD:这个位用于控制内存区的子区域 ,使用的是bit[15:8],共计8个bit,一个bit控制一个子区域, 0表示使能此子区域, 1表示禁止。一般情况下,取值0x00,表示8个子区域都使能。
* l: {8 |) l- n( g$ A5)、SIZE:配置这个MPU保护区域的大小。

+ K2 @. P+ _" K; s. V! l* |( e# J
三、HAL配置例程

  1. //设置某个区域的MPU保护+ t# M" C0 G) M# N' V
  2. //baseaddr:MPU保护区域的基址(首地址)- S* _4 r8 |& t. d9 ]& T1 V# J
  3. //size:MPU保护区域的大小(必须是32的倍数,单位为字节),可设置的值参考:CORTEX_MPU_Region_Size
    1 t* c- M& c5 Q( P
  4. //rnum:MPU保护区编号,范围:0~7,最大支持8个保护区域,可设置的值参考:CORTEX_MPU_Region_Number
    9 d7 n3 X9 f& b# E
  5. //ap:访问权限,访问关系如下:可设置的值参考:CORTEX_MPU_Region_Permission_Attributes4 @9 M) L% o% R/ K7 P$ I4 N" ]' R
  6. //0,无访问(特权&用户都不可访问)
    3 x1 S: o  D$ @" r2 c
  7. //1,仅支持特权读写访问2 o) j5 r: Q9 ?
  8. //2,禁止用户写访问(特权可读写访问)" i. \- l' E" |& V: E6 g; l
  9. //3,全访问(特权&用户都可访问). X3 M& k* E* A* Z
  10. //4,无法预测(禁止设置为4!!!)
    9 G4 |$ [$ p! k7 I1 s
  11. //5,仅支持特权读访问% J. c& l- N% D0 J3 T/ {
  12. //6,只读(特权&用户都不可以写)  ~" K4 {2 u: i# E
  13. //详见:STM32F7 Series Cortex-M7 processor programming manual.pdf,4.6节,Table 89., s4 Z. t8 Q3 l$ h& \2 V
  14. //sen:是否允许共用;0,不允许;1,允许" V9 ^* j! O; T6 Q
  15. //cen:是否允许catch;0,不允许;1,允许, i/ w( G" F, h# O/ }6 H
  16. //返回值;0,成功.! z! A- h( |1 s& y8 Y
  17. //    其他,错误.
    ' g- j$ S# X3 |& b, y5 E2 O
  18. u8 MPU_Set_Protection(u32 baseaddr,u32 size,u32 rnum,u32 ap,u8 sen,u8 cen,u8 ben,u8 Tex)% d' L$ K! N# O% p* |
  19. {4 K- s& H' ^: [2 G% _
  20.         MPU_Region_InitTypeDef MPU_Initure;( U* f: ]- I9 A
  21.         HAL_MPU_Disable();                                                                        //配置MPU之前先关闭MPU,配置完成以后在使能MPU
    " M3 V8 _2 ~% h' X) a+ ?1 P

  22. 3 b* o5 f) x7 A$ d6 o
  23.         MPU_Initure.Enable=MPU_REGION_ENABLE;                                //使能该保护区域 - B7 p& k6 u$ h% K& w) \
  24.         MPU_Initure.Number=rnum;                                            //设置保护区域; X8 C) o  @; B8 g9 D
  25.         MPU_Initure.BaseAddress=baseaddr;                            //设置基址
    , M! L# r; \& O9 P+ ~& b9 F
  26.         MPU_Initure.Size=size;                                                    //设置保护区域大小* e2 _' G+ W; H
  27.         MPU_Initure.SubRegionDisable=0X00;                      //禁止子区域
    2 O4 \: ?' G4 T9 Z" }; t0 @( r
  28.         MPU_Initure.TypeExtField=Tex;                           //设置类型扩展域3 `' L  W1 B0 {  B0 ?4 ^/ U4 Q& h7 c) ^! z
  29.         MPU_Initure.AccessPermission=(u8)ap;                            //设置访问权限,
    1 i$ N  q# k( E! J
  30.         MPU_Initure.DisableExec=MPU_INSTRUCTION_ACCESS_ENABLE;        //允许指令访问(允许读取指令)( u6 M8 H+ B7 C+ _( ^/ k
  31.         MPU_Initure.IsShareable=sen;                            //是否允许共用
    % @0 \1 A" v" N. b
  32.         MPU_Initure.IsCacheable=cen;                            //是否允许cache
    % R# w, f+ H; l
  33.         MPU_Initure.IsBufferable=ben;                           //是否允许缓冲
    2 [% B5 ]% N6 o
  34.         HAL_MPU_ConfigRegion(&MPU_Initure);                     //配置MPU
    0 N  V- E( {) r8 m  F% M
  35.         HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);                                //开启MPU
    ' n* z* A# _5 Y  l. x; x
  36.     return 0;0 D1 c8 K, z; j
  37. }
    ' U, i& ]0 X  i$ r
  38. % I% W, P5 k3 @' i
  39. //设置需要保护的存储块7 }$ c2 I( Z" b2 |& i& O1 e, B
  40. //必须对部分存储区域进行MPU保护,否则可能导致程序运行异常
    ' a& L7 J. a4 L5 a2 M; p
  41. //比如MCU屏不显示,摄像头采集数据出错等等问题...2 k* i+ M# Z% E8 r' j$ [# b1 o& i
  42. void MPU_Memory_Protection(void)   //特意把SRAM4设置为不允许cache,使用DMA的变量可以放在这里。但要注意相应DMA能否访问SRAM4
    , J8 m, n$ |) c. p  F) q* g' N
  43. {
    3 K7 m( K' U& l7 W2 {
  44.         MPU_Set_Protection(0x20000000,MPU_REGION_SIZE_128KB,MPU_REGION_NUMBER1,MPU_REGION_FULL_ACCESS,0,1,1,MPU_TEX_LEVEL0);        //保护整个DTCM,共128K字节,禁止共用,允许cache,允许缓冲
    5 D5 V2 D4 q2 E4 I# g
  45.         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字节% T1 X2 j2 {7 z. U4 T5 g1 w
  46.         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,允许缓冲; u+ }1 I- s( L% A7 v1 ?6 G
  47.         MPU_Set_Protection(0x38000000,MPU_REGION_SIZE_64KB ,MPU_REGION_NUMBER4,MPU_REGION_FULL_ACCESS,0,0,1,MPU_TEX_LEVEL0);        //保护整个SRAM4,共64K字节,禁止共用,不允许cache,允许缓冲8 t" t) J% t; Z  F3 K! B
  48.         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,禁止缓冲
    , D1 @1 W" y! o: Z$ ~1 k
  49.         MPU_Set_Protection(0xC0000000,MPU_REGION_SIZE_64MB ,MPU_REGION_NUMBER6,MPU_REGION_FULL_ACCESS,0,1,1,MPU_TEX_LEVEL0);        //保护SDRAM区域,共32M字节,禁止共用,允许cache,允许缓冲) F8 S6 k2 K1 G$ @
  50. }: P& v; t7 F, y' p8 ]! v
复制代码
2 e5 ^* K# w% k

& F# E& U& @  ~1 a2 N( P- s

四、其他

  1.     值得一提的是,LTDC也是直接从RAM拿数据的,如果你使用了GUI(比如EMWIN),你的显示数据可能会暂存在Cache,而LTDC直接从RAM拿数据,就可能造成画面撕裂、重影、斑点之类的问题。解决方法是,把显存设置成透写。
    3 L4 x; {2 |) ]* a6 W

  2. # ]( w+ p& o! U
  3.     从下面的图可以看到,Cache是在M7那个框里面的。而框外面的外设都可以直接与RAM交换数据,因此使用外设操作数据时都要考虑一下Cache的影响,不然异常可能难以预料。2 b* g2 J9 n) q* d; X5 R/ M# u  A
复制代码

& J. D& R2 L) m+ a) T  i2 S8 i* a; K


2 p1 X/ D: J, w

  F+ T8 r! J$ E- f- I三、H7的内存分配以及总结

首先呢,大家在使用H743 的时候,在keil或者iar中,有一个内存的勾选,如下图" w2 f5 O6 e6 Z
IROM1表示flash的内存地址,h743是2M,所以,大小自然是2M,起始地址可以在参考手册中找到,如下图,这个比较容易理解,程序就是从这开始运行:1 v8 w6 R  y- Y# d$ b) j
8 P/ H, g' w( q4 K
其次,是RAM,743内部有1M,那么这1M怎么分的呢,直接说结论,从地址0x2000 0000开始到(0x3880 0000+64k)结束,加起来是1M,问题来了,为什么我们配置的时候要用0x2400 0000开始的512k呢?不知道大家看参考手册看的怎么样,开头讲了ram 的分配。
5 Q6 \. f* a9 v0 |) e5 P- E2 k1M的RAM,分成了5大类,TCM、AXI 、SRAM1/SRAM2/SRAM3/SRAM4、BACK,他们所在的区域不同,TCM包含ITCM和DTCM,地址0x2000 0000的128K,位于D1域,ITCM主要用来存取程序代码,也就是执行命令,DTCM用于数据存取。先把所有的内存分配做个总结再说。7 D: w2 u1 ^3 I; @7 Z) g
{+ I7 u, U! M' [5 {/ X
1、TCM分为ITCM(运行代码)和DTCM(数据存取)
$ A8 h) f' ]- ?+ d0 X' C2、速度400MHZ
3 m" m: |" u) f. Y& i* N) b* W; h8 e3、DTCM地址:0x2000 0000,大小128KB: A7 z# `# B; s& J8 o, F4 }
ITCM地址:0x0000 0000,大小64KB
, s' o" N! ?5 G9 @& w}0 p+ V- O, r' g! q* F
AXI SRAM 区* Y: Y, C8 ?. P$ r
{
2 f1 v: w. ~/ J/ t0 B8 H/ b) _1 @1、位于D1域,挂在A**线
% K4 s9 p! H, z9 M2、速度200MHZ, `- z. `9 c3 d4 \
3、地址:0x2400 0000,大小512KB

}7 M5 `7 [9 ~- L' n* G7 s% \
SRAM1,SRAM2,SRAM3 区
8 ]7 N' n1 A, s; T{# c# M; q3 V" W; r* g
1、位于D2域,挂在AHB总线' D& G  `; I% ?/ {6 H. |
2、速度200MHZ
3 s$ X3 L2 c- f, d# z3 u( C, ]0 z( v3、SRAM1地址:0x3000 0000,大小128KB
* N0 m& J8 z7 F7 LSRAM2地址:0x3002 0000,大小128KB+ p2 X. `( f1 r2 n
SRAM3地址:0x3004 0000,大小32KB  b' @" w. B7 x. a% I) n
}- I; O% O5 X/ Y
SRAM4区
; l0 s$ E: Q3 X) P+ c{5 c: y* Z2 P; j8 ~
1、位于D3域,挂在AHB总线0 V. {; P7 B% Q, o
2、速度200MHZ
# I/ v$ c, F0 }7 E( L5 l1 F1 B3、地址:0x3800 0000,大小64KB

}
: B2 N* f1 K% e7 l0 I' RBackup SRAM区
1 S" ?# ^" R% {' l2 t{* }. i4 P. V9 x  G5 s, Y
1、位于D3域,挂在AHB总线; _1 r1 ^) l6 C
2、速度200MHZ/ d1 K0 M; n) ~* h
3、地址:0x3880 0000,大小4KB

}

ok,还是原先的问题,为什么要用0x2400 0000开始做程序的主RAM呢,原因在于,如果用户使用了SDMMC1,也就是比如SD卡的dma功能,或者其他用到了SDMMC1的话,该主设备只接AXI RAM,不能使用其他RAM读取,可以不用,声明变量的时候使用at section指向AXI地址也是可以的。

其他几个RAM,需要跟BDMA用的时候进行区分,有些是不支持BDMA的。剩下的就没啥了,其他内容可以看下参考手册第二章。

如果大家功能少的话,可以使用DTCM来当主RAM,毕竟速度最快,但是用到了相关DMA的话,还是换成AXI吧。野火的代码里面默认是使用这个。


( \) A9 t, W8 L/ _+ ^
收藏 评论1 发布时间:2021-12-22 14:36

举报

1个回答
3737 回答时间:2021-12-23 20:16:27
core_cm7.h更新到5.1.1版本后,发现该函数对dsize做了32字节对齐,但是op_addr地址32字节对齐却注释掉了?,图片的这句话是不是说SCB->DCCIMVAC寄存器仅31-5位是有效的,本身已经强制32位对齐了?我在编程手册上也没找到对DCCIMVAC寄存器的详细描述,请问调用该函数是不是可以直接传入地址和数据了,不需要再对地址和数据进行32位对齐了?

微信图片_20211223104932.png

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版