
前言 本文会从结构,原理以及应用方面对MPU和Cache进行分析,主要目的是希望读者对Cache有基本的了解,在具体的实际应用中,使用带有一级cache的MCU时,避免常见的错误。 + [. u+ V/ i, Q4 C Cache介绍 S# y. k% }* A/ z! H, l - e" I* C* x1 C! l$ z8 V( k Cache及其原理 Cache,高速缓存,一般指的是L1 cache,即和Core紧耦合的,从STM32F7系列开始, 基于ARM cortex-M7内核,增加了对L1 Cache的支持。 K+ Q$ |) }: e8 P( ?" ^ ![]() 从这张图片可以看出来,无论是指令Cache(I-cache)还是数据Cache(D-cache),一旦使能之后,就分别与Core的prefetch unit(I-cache)和load-store unit(D-cache)相连,以D-cache为例,因为2 I" X* {$ Y$ A. w! U+ ]/ F0 J+ @ 直接与LSU相连,访问速度会比直接访问SRAM或外部RAM快很多,只要保证cache有足够高的命中率(由cache策略保证),尽量少的cache miss,读/写的速度会有比较大的提高。3 K0 x6 |( \2 z( K+ S+ K) q+ J 结构及策略( A: ]4 `0 C: n- C \ 同样这里以D-cache为例,看一下D-cache的构成: ![]() 包括Address和cache-line,Address表明其地址,对应一条包含32bytes的cache-line:; D8 |% {) j1 J. y8 U3 z 读数据时,当地址命中时即cache-hit,便可以直接从cache line中取出相应的数据,反之,当遍历了address都没有找到,就会产生cache-miss,这时便会从实际的内存单元(如SRAM)中取出相应的数据,并更新到某一条cache-line中并修改相应的cache-line信息;: M. E9 b! \8 S 写数据时,就有点不同了,包括write-through策略和write-back策略,当使用write-though策略时,更新cache-line的同时,同样会更新其对应的实际物理地址的区域,当采用write-back策略时,更cache-line的同时,并不是马上去更新其对应的实际物理地址的内容,而是在其认为合适或者所有的cache-line都dirty的时候才会去更新,当然,也可以通过软件让其强制更新,即clean的动作,这一块会在后面的cache一致性问题上也会有体现; 同样,对于为什么将cache拆分为2-way或是4-way,这和cache自身的策略如查找算法等相关,由于本文侧重讨论cache的应用相关问题,所以关于cache本身的策略这里不再详述。 3 B `7 w! ?) e Cache及MPU属性/ P. A) R* c k 这里需要注意的是,cache一般是配合MPU(memory protection unit)一起使用的,首先需要通过MPU配置相应memory的属性(normal, strongly-ordered, device, XN etc.),如下表所示:/ u- g, [# [$ o$ U4 ^2 x' k/ b ![]() 选取几个有特点的作为示例: 0~0x1FFF_FFFF: flash空间,属性为normal,cache的属性为Write-through,即更新cache的同时,将数据同时写入相应的物理地址空间; Y! v. V9 R" t6 m$ x* u9 S$ } 0x2000_0000~0x3FFF_FFFF: SRAM空间,属性为normal,cache的属性为write-back,即仅更新cache,在合适的时候(由cache策略决定或者软件强制更新)将数据更新到相应的SRAM空间3 Z; I6 K: D$ \2 v 0x4000_0000~0x5FFFF_FFFF: 芯片内部的外设空间,属性为device,这一版是外设寄存器所处的位置,对其读写的过程中不会经过cache8 `. z! A& e' c8 K c9 y% o XN的意思是Execute-Never,其含义为如果相应的地址空间是XN,是绝不允许执行代码的。 Q; m* @6 M' @' {! H & ?2 K: A }) U. |0 v8 B" N Cache相关函数及作用7 Y* d5 p3 E: t O 这里以core_cm7.h里对cache封装的函数为例 (C:\Program Files (x86)\IAR Systems\Embedded Workbench 7.2\arm\CMSIS\Include) 1 H- |( V; N0 Z SCB_EnableDCache- r9 [* I8 k7 B$ y" B2 M ## 使能D-cache - }3 }; S1 J% A" f- S SCB_DisableDCache ## 禁用D-cache) l: Z' B0 h8 j1 @* t( P # v( u& X! p1 {: @/ s* `3 ^3 b+ t: a SCB_EnableICache ## 使能I-cache SCB_DisableICache ## 禁用I-cache 0 h) v/ h: O- ] Z6 _6 A SCB_CleanDCache : E7 i& c$ ~ c% u7 q6 d8 @ ## Clean所有的cache-line,即将dirty的cache-line全部写到cache line对应的真实的物理地址中 所谓的drity属性,即写操作时,更新了相应的cache-line,但是没有更新到真实的物理地址, 而这个clean的动作,就是将cache中的内容更新到真实的物理地址中 ' J8 l5 T( z1 }3 I SCB_CleanDCache_by_Addr ## 根据地址信息clean其对应的cache-line SCB_InvalidateDCache 6 U7 y- o7 e, \- s ## 无效D-cache,D-cache被invalidate之后,当有Host(如core,DMA等)读取数据时,会忽略相应的cache-line中的内容(因为被validate了),从真实的物理地址中去获取相应的数据; c- i# `3 P& M+ o : a5 R: w$ y: V* }2 n SCB_InvalidateDCache_by_Addr ## 根据地址信息无效其对应的cache-line Cache一致性问题 所谓的Cache一致性问题,主要指的是由于D-cache存在时,表现在有多个Host(典型的如MCU的core,DMA等)访问同一块内存时,由于数据会缓存在D-cache中而没有更新实际的物理内存。3 ?- F, y' ?8 E: f 第一种情况是当有写物理内存的指令时,core会先去更新相应的cache-line(Write-back策略),在没有clean的情况下,会导致其对应的实际物理内存中的数据并没有被更新,如果这个时候有其它的Host(如DMA)访问这段内存时,就会出现问题(由于实际物理内存并未被更新,和D-cache中的不一致),这就是所谓的cache一致性的问题!1 n8 w$ f7 o) h7 S& D( p: } 7 x2 w3 c2 i% m0 R: G: `: b+ I3 } 第二种情况是DMA更新了某段物理内存(DMA和cache直接没有直接通道),而这个时候Core再读取这段内存的时候,由于相对应地址的cache-line没有被invalidate,导致Core读到的是cache-line中的数据,而非被DMA更新过的实际物理内存的数据,下面这张图比较清晰的展示了上述两个过程:. e" v0 Q; \* B/ W# L ●第一种情况+ h( x. ~0 P9 g& ]5 O ![]() ●第二种情况 ![]() 下面以一个实例来分析cache一致性问题,展示的是上面的第一种情况,如下图所示: ![]() 先看一下这个例程数据的传输流程和路径: ● SRAM1_Buffer先全部写入0x55 ●Core将Flash中的Const_Buffer写入SRAM1_Buffer(这里会先经过d-cache) ● 配置DMA,将SRAM1_Buffer中的数据通过DMA写入另一段内存DTCM_Buffer ● 比较DTCM_Buffer中的数据和Flash中的Const_Buffer数据,看是否一致! n; x/ W8 u% g9 X8 T3 v v1 [) i$ P/ k! C7 B 代码示例如下: * F6 Q3 C1 Y1 e3 D {2 Y ●MPU对memory的配置 - - - - step1; ~/ u9 L0 K" F# B ![]() 重点介绍一下高亮部分的配置: ● BaseAddress为要配置的存储空间的起始地址; ● Size为要配置的存储空间的大小& B( b* d, K4 [4 }/ i ● IsCacheable表明这段存储空间是否可以cache ● IsBufferable表明使能cache之后,策略是write-through还是write-back(bufferable) 这里需要特别注意的1点:配置的BaseAddress需要被Size整除,以上述配置为例,即0x20010000除以256K需要是整数! ) r( ~# T; P( z2 N' \' y) r ● 使能Cache - - - - step2 ![]() % ~+ F: v1 p2 e8 b ● 初始化SRAM1_Buffer为0x55 - - - - step3 ( L9 k$ m3 i- Z) Y% h0 l ● Copy Flash中的Const_Buffer到SRAM1_Buffer - - - - step4 ● 配置DMA,将SRAM1_Buffer写入DTCM_Buffer - - - - step5 ● 比较DTCM_Buffer和Const_Buffer,看是否一致 - - - - step6 从结果上看,最后一步比较的结果并不一致,原因比较简单,由于设定的WB策略,所以在step4的时候,数据会暂存在D-cache当中,并没有更新到SRAM1_Buffer,所以当SRAM1_Buffer被DMA拷到DTCM_Buffer中的时候,有一部分可能还是初始值,导致最终的比较不一样,而解决的方法有以下几个:0 k% M0 P: ]- o# ~8 }; y 1. MPU配置的代码,将属性改为MPU_ACCESS_BUFFERABLE,即使用write-though策略: e/ d% ~! @! z' N6 J" k, [! r 2. 通过cache控制寄存器,将所有cacheable的空间全部强制write-though ![]() ![]() 3. 将dirty cache-line更新到真实的物理地址中 在step5操作之前,先调用SCB_CleanDCache或SCB_CleanDCache_by_Addr将相应cache-line中的数据写入SRAM1_Buffer,就解决了这个问题!0 K L3 [* F, [% S( J0 I & Z0 b3 k1 g& d) R6 t 这是最常用的方法,在实际的开发过程中,为了提高性能,一般都会使能cache,同时将其配置为WB策略,这就需要开发者在使用时特别小心!同样如之前的第二种情况,需要先调用SCB_InvalidateDCache或SCB_InvalidateDCache_by_Addr去Invalidate相应的cache-line,这样当core在读取时,会忽略D-cache中的内容,去真实的物理地址读取对应的数据! c" l& }- c1 c, y5 l Z/ { " g! n9 D! Y0 E# q. d ' c4 `( V: C3 I( g1 K' V5 u 文档下载8 } P/ C/ R, a' L/ q7 c5 u + o. }8 W& A* J { 更多实战经验7 Y) Z& L8 N8 l9 Y* D d- K: j |
【实战经验】基于STM32F7的网络时间同步例程
STM32硬件结构学习
STM32中BOOT的作用
利用STM32MP1和STM32MP2为嵌入式Linux提供有效的安全措施:供当今决策者参考的3条宝贵经验
【STM32F769I-DISC1】开发板刷入Micropython并完成点灯、读取内部温度测试
有奖直播 | STM32MP2x 资源隔离架构 (RIF) 介绍与使用
【STM32F769I-DISC1】测评01:创建STM32cube IDE 工程,点个灯
【STM32F769】创建deepseek本地服务,并实现http请求
白皮书下载|边缘 AI 变革:MCU集成 NPU 的破局与领航
OpenSTLinux:为STM32 MPU生态系统带来超强助力
! N" u2 ~8 R& f* \
另外,执行 SCB_InvalidateDCache或SCB_InvalidateDCache_by_Addr 的时候会调用“__DSB(); __ISB();”这两个操作,即等待当前数据cache操作由指令完成,彻底打断了当前CPU的执行节奏(清空指令cache)。假如当前DMA从外设读取了10字节数据到SRAM中,在DMA中断里更新接收计数器为10(这个计数器操作是CPU完成的,将写入到cache中),如果中断后调用了InvalidateDCache,会导致计数器的写操作无效,因为写cache操作已经完成了,其策略是WB,它写入的是Cache,并没有写入到物理存储器中。当下次CPU要读取这个计数器值时,仍从Cache中读取,发现这个地址已经被Invalidate了,那么要从SRAM中读,结果是0。
火哥好
管理员好
学习了