项目中采用了ARM cortex-M7的架构进行芯片设计,在随后的开发中遇到了关于Cache配置等问题,花费了一段时间查阅资料与参考STM32H743开发板代码,现在记录总结一下。
1、关于Cache
1.1 Cache是什么
Cache是位于core与内存之间的一个高速缓存区域,在cortexM7内可以进行设置有16kb、32kb等在硬件集成的时候进行选择。
1.2 Cache的主要功能
coretexM7中Cache分为ICache以及DCache,分别对应指令缓存以及数据缓存,core为处理器,内存则是保存要处理的数据或者需要执行的代码,内存根据位置可以分为片内以及片外,当core执行程序时需要访问相应内存代码段、数据段等,那么其中就涉及到访问速度,查阅资料显示一般core访问内存需要的时间大概需要60+ns,但是core执行速度很快,这样就会出现core访问内存时出现无事可做的情况,那么为了解决这种问题,就出现了Cache,Cache作为一个core与内存之间的一个高速缓存器,core访问Cache需要的时间在1-15ns左右(不太确定,但是相比于core直接访问内存)能够显著减少core内存访问时间,提高core执行效率。
1.3 Cache的工作模式
关于Cache的工作模式个人只是大概了解,网上内容也挺多,这里大概按照我的思路大概介绍一下。以ICache举例,当开启了Cache功能后,core要执行的代码的位置会进行先对比Cache内缓存的地址,当地址在Cache缓存的范围内会直接访问Cache,这称为命中,若不在Cache内,称为未命中,Cache会有按照不太清楚的规则对Cache缓存内的指令进行替换(cache内存储也有一定规则,个人感觉不需要了解太深,所以也没有仔细看)。Cache内缓存的指令段都是成段存在的,不是简单的单条指令,所以个人认为除了在执行指令时存在函数跳转或者产生中断等情况频繁出现的话,基本都能够很大提升执行效率。
当进行Cache配置时由于不同的配置信息会使得Cache的工作模式不尽相同,其中Cache工作模式是通过MPU进行设置的,Cache的工作模式包含4中,分别如下(其中都是MPU寄存器配置,与Cache寄存器无关)
(1)non-Cache
不进行Cache
(2)Write-Back,write and read allocate
读:read allocate(开启了Cache就有此种配置,MPU寄存器内无该项配置)-当未命中时,对Cache缓存的数据进行更新;命中时若内存中数据改变了那么不会更新Cache缓存的数据。
写:write allocate-未命中时,core同时写入内存与Cache内(更新Cache内缓存数据),当下次访问时能够提高速度;当Cache缓存中有core要改变内存数据的时候,仅改变Cache内的数据,不改变内存中的数据。
此种配置能够最大限度提升core的执行效率,但是会造成数据不一致的情况。
(3)Write-Though,no write allocate
读:read allocate(开启了Cache就有此种配置,MPU寄存器内无该项配置)-当未命中时,对Cache缓存的数据进行更新,命中时若内存中数据改变了那么不会更新Cache缓存的数据。
写:no write allocate-未命中时不会更新Cache内缓存数据,当Cache缓存中有core要改变内存数据的时候,改变Cache内的数据,同时改变内存中的数据。
不会出现写操作时Cache与内存数据不一致的情况,同时效率没有第四种高。
(4)Write-Back,no write allocate
读:read allocate(开启了Cache就有此种配置,MPU寄存器内无该项配置)-当未命中时,对Cache缓存的数据进行更新,命中时若内存中数据改变了那么不会更新Cache缓存的数据。
写:no write allocate-未命中时不会更新Cache内缓存数据,当Cache缓存中有core要改变内存数据的时候,改变Cache内的数据,不改变内存中的数据。
会出现写操作时Cache与内存数据不一致的情况,但是效率没有第二种高。
1.4 Cache的隐患分析与解决思路
因为Cache本质上是一个能够让core进行高速访问的缓存,当外设或者core对某地址的数据进行修改时,若Cache内也同样缓存了这段地址的数据,那么就会出现两者不一致,以及两者数据如何同步的问题。
所以本质上,当外设修改或者读取了某段内存的数据的时候,判断Cache内是否有相应地址缓存,进行更新,即可解决数据不一致的问题,但是很大可能也会造成效率下降,个人认为需要在关键的不那么频繁使用的数据访问或者修改段(eg:core一直进行高速计算,实时更新一个参数,外设只有偶尔用到这个参数的时候进行写回,保证外设获取的数据为实时数据)添加更新代码应该能够最大限度保留Cache的效率。
1.5 Cache的寄存器配置
关于Cache的启动与禁用在cm7_core.h内已经封装完成,可以直接调用(调用应先配置MPU 寄存器,见下一节):
void SCB_DisableICache(); //禁用ICache;
void SCB_EnableICache(); //使能ICache;
void SCB_DisableDcache(); //禁用DCache;
void SCB_EnableDCache(); //使能DCache;
2、关于MPU
2.1 MPU介绍及功能
内存保护单元(Memory Protection Uint),顾名思义表示对内存(0x00000000-0xFFFFFFFF)进行保护的寄存器设置,参考资料表示在cortexM7以前的产品中(M3.M4)中关于此类设置基本不用,但是在cortexM7中如果要开启Cache就需要对内存进行设置,此时必须用到MPU的一些设置。
其位于core与内存之间,按我的理解就是如果我按照全部内存不可访问这种模式设置了MPU,那么设置以后core所有的访问内存的操作都会被阻止(可能会触发硬件中断?不确定需要试验进行验证),那么MPU其实就是起的一个core与内存之间访问的权限的相关设置,那么Cache与MPU以及core有什么样的关系呢?无论怎么样Cache工作肯定与core有交互,而cache需要通过core对内存中代码或者数据进行缓冲存储等,所以若是MPU设置不可访问、不可Cache、不可缓冲的话那么肯定会影响到相应Cache的执行类型。
MPU具体类型见下表
三种内存类型Strongly ordered、Device、Normal,不同的内存类型能够开启的Cache模式也不同,当MPU内存类型设置为Strongly Ordered时就算开启了Cache也是没有起到作用的。
2.2 与MPU相关的寄存器
需要用到的分别为CTRL、RNR、RBAR、RASR寄存器
(1)MPU->CTRL
使能与禁止MPU功能。
(2)MPU->RNR
MPU将存储分为0-15个区域,区域可以自行设置,不同区域可以重叠覆盖,数值越大,优先级越高,比如说以0为编号,设置0x00000000-0x00080000区域为不可访问,但是又以2为编号,设置0x00040000-0x00080000为读写访问,那么0x00040000-0x00080000为读写访问,0x00000000-0x00040000为不可访问。
(3)MPU->RBAR
设置MPU->RNR之后再次配置MPU->RBAR,此寄存器位配置MPU->RNR对应编号的区域的基地址((1)中的0x00000000、0x00040000)。
(4)MPU->RASR
按照不同的配置TEX、C、B的设置可以得到不同MPU类型
根据上述信息,查阅stm32 hal库代码得到其怎么配置MPU的代码
typedef struct
{
uint8_t Enable; /*!< Specifies the status of the region.
This parameter can be a value of @ref CORTEX_MPU_Region_Enable */
uint8_t Number; /*!< Specifies the number of the region to protect.
This parameter can be a value of @ref CORTEX_MPU_Region_Number */
uint32_t BaseAddress; /*!< Specifies the base address of the region to protect. */
uint8_t Size; /*!< Specifies the size of the region to protect.
This parameter can be a value of @ref CORTEX_MPU_Region_Size */
uint8_t SubRegionDisable; /*!< Specifies the number of the subregion protection to disable.
This parameter must be a number between Min_Data = 0x00 and Max_Data = 0xFF */
uint8_t TypeExtField; /*!< Specifies the TEX field level.
This parameter can be a value of @ref CORTEX_MPU_TEX_Levels */
uint8_t AccessPermission; /*!< Specifies the region access permission type.
This parameter can be a value of @ref CORTEX_MPU_Region_Permission_Attributes */
uint8_t DisableExec; /*!< Specifies the instruction access status.
This parameter can be a value of @ref CORTEX_MPU_Instruction_Access */
uint8_t IsShareable; /*!< Specifies the shareability status of the protected region.
This parameter can be a value of @ref CORTEX_MPU_Access_Shareable */
uint8_t IsCacheable; /*!< Specifies the cacheable status of the region protected.
This parameter can be a value of @ref CORTEX_MPU_Access_Cacheable */
uint8_t IsBufferable; /*!< Specifies the bufferable status of the protected region.
This parameter can be a value of @ref CORTEX_MPU_Access_Bufferable */
}MPU_Region_InitTypeDef;
#if (__MPU_PRESENT == 1)
/** @defgroup CORTEX_MPU_HFNMI_PRIVDEF_Control MPU HFNMI and PRIVILEGED Access control
* @{
*/
#define MPU_HFNMI_PRIVDEF_NONE ((uint32_t)0x00000000)
#define MPU_HARDFAULT_NMI ((uint32_t)0x00000002)
#define MPU_PRIVILEGED_DEFAULT ((uint32_t)0x00000004)
#define MPU_HFNMI_PRIVDEF ((uint32_t)0x00000006)
/**
* @}
*/
/** @defgroup CORTEX_MPU_Region_Enable CORTEX MPU Region Enable
* @{
*/
#define MPU_REGION_ENABLE ((uint8_t)0x01)
#define MPU_REGION_DISABLE ((uint8_t)0x00)
/**
* @}
*/
/** @defgroup CORTEX_MPU_Instruction_Access CORTEX MPU Instruction Access
* @{
*/
#define MPU_INSTRUCTION_ACCESS_ENABLE ((uint8_t)0x00)
#define MPU_INSTRUCTION_ACCESS_DISABLE ((uint8_t)0x01)
/**
* @}
*/
/** @defgroup CORTEX_MPU_Access_Shareable CORTEX MPU Instruction Access Shareable
* @{
*/
#define MPU_ACCESS_SHAREABLE ((uint8_t)0x01)
#define MPU_ACCESS_NOT_SHAREABLE ((uint8_t)0x00)
/**
* @}
*/
/** @defgroup CORTEX_MPU_Access_Cacheable CORTEX MPU Instruction Access Cacheable
* @{
*/
#define MPU_ACCESS_CACHEABLE ((uint8_t)0x01)
#define MPU_ACCESS_NOT_CACHEABLE ((uint8_t)0x00)
/**
* @}
*/
/** @defgroup CORTEX_MPU_Access_Bufferable CORTEX MPU Instruction Access Bufferable
* @{
*/
#define MPU_ACCESS_BUFFERABLE ((uint8_t)0x01)
#define MPU_ACCESS_NOT_BUFFERABLE ((uint8_t)0x00)
/**
* @}
*/
/** @defgroup CORTEX_MPU_TEX_Levels MPU TEX Levels
* @{
*/
#define MPU_TEX_LEVEL0 ((uint8_t)0x00)
#define MPU_TEX_LEVEL1 ((uint8_t)0x01)
#define MPU_TEX_LEVEL2 ((uint8_t)0x02)
/**
* @}
*/
/** @defgroup CORTEX_MPU_Region_Size CORTEX MPU Region Size
* @{
*/
#define MPU_REGION_SIZE_32B ((uint8_t)0x04)
#define MPU_REGION_SIZE_64B ((uint8_t)0x05)
#define MPU_REGION_SIZE_128B ((uint8_t)0x06)
#define MPU_REGION_SIZE_256B ((uint8_t)0x07)
#define MPU_REGION_SIZE_512B ((uint8_t)0x08)
#define MPU_REGION_SIZE_1KB ((uint8_t)0x09)
#define MPU_REGION_SIZE_2KB ((uint8_t)0x0A)
#define MPU_REGION_SIZE_4KB ((uint8_t)0x0B)
#define MPU_REGION_SIZE_8KB ((uint8_t)0x0C)
#define MPU_REGION_SIZE_16KB ((uint8_t)0x0D)
#define MPU_REGION_SIZE_32KB ((uint8_t)0x0E)
#define MPU_REGION_SIZE_64KB ((uint8_t)0x0F)
#define MPU_REGION_SIZE_128KB ((uint8_t)0x10)
#define MPU_REGION_SIZE_256KB ((uint8_t)0x11)
#define MPU_REGION_SIZE_512KB ((uint8_t)0x12)
#define MPU_REGION_SIZE_1MB ((uint8_t)0x13)
#define MPU_REGION_SIZE_2MB ((uint8_t)0x14)
#define MPU_REGION_SIZE_4MB ((uint8_t)0x15)
#define MPU_REGION_SIZE_8MB ((uint8_t)0x16)
#define MPU_REGION_SIZE_16MB ((uint8_t)0x17)
#define MPU_REGION_SIZE_32MB ((uint8_t)0x18)
#define MPU_REGION_SIZE_64MB ((uint8_t)0x19)
#define MPU_REGION_SIZE_128MB ((uint8_t)0x1A)
#define MPU_REGION_SIZE_256MB ((uint8_t)0x1B)
#define MPU_REGION_SIZE_512MB ((uint8_t)0x1C)
#define MPU_REGION_SIZE_1GB ((uint8_t)0x1D)
#define MPU_REGION_SIZE_2GB ((uint8_t)0x1E)
#define MPU_REGION_SIZE_4GB ((uint8_t)0x1F)
/**
* @}
*/
/** @defgroup CORTEX_MPU_Region_Permission_Attributes CORTEX MPU Region Permission Attributes
* @{
*/
#define MPU_REGION_NO_ACCESS ((uint8_t)0x00)
#define MPU_REGION_PRIV_RW ((uint8_t)0x01)
#define MPU_REGION_PRIV_RW_URO ((uint8_t)0x02)
#define MPU_REGION_FULL_ACCESS ((uint8_t)0x03)
#define MPU_REGION_PRIV_RO ((uint8_t)0x05)
#define MPU_REGION_PRIV_RO_URO ((uint8_t)0x06)
/**
* @}
*/
/** @defgroup CORTEX_MPU_Region_Number CORTEX MPU Region Number
* @{
*/
#define MPU_REGION_NUMBER0 ((uint8_t)0x00)
#define MPU_REGION_NUMBER1 ((uint8_t)0x01)
#define MPU_REGION_NUMBER2 ((uint8_t)0x02)
#define MPU_REGION_NUMBER3 ((uint8_t)0x03)
#define MPU_REGION_NUMBER4 ((uint8_t)0x04)
#define MPU_REGION_NUMBER5 ((uint8_t)0x05)
#define MPU_REGION_NUMBER6 ((uint8_t)0x06)
#define MPU_REGION_NUMBER7 ((uint8_t)0x07)
#define MPU_REGION_NUMBER8 ((uint8_t)0x08)
#define MPU_REGION_NUMBER9 ((uint8_t)0x09)
#define MPU_REGION_NUMBER10 ((uint8_t)0x0A)
#define MPU_REGION_NUMBER11 ((uint8_t)0x0B)
#define MPU_REGION_NUMBER12 ((uint8_t)0x0C)
#define MPU_REGION_NUMBER13 ((uint8_t)0x0D)
#define MPU_REGION_NUMBER14 ((uint8_t)0x0E)
#define MPU_REGION_NUMBER15 ((uint8_t)0x0F)
/**
* @}
*/
#endif /* __MPU_PRESENT */
void MPU_Config( void )
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable();
/* 配置0x80000000内存段的MPU属性为Write back, Read allocate,Write allocate */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x80000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_2MB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
故可以在main函数内调用MPU_config()进行MPU配置,其函数内容可以借鉴修改。
{
MPU_Config();
SCB_EnableICache();
SCB_EnableDCache();
SCB_CleanDCache();
}
__STATIC_INLINE void SCB_EnableICache (void)
{
#if defined (__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U)
__DSB();
__ISB();
SCB->ICIALLU = 0UL; /* invalidate I-Cache */
__DSB();
__ISB();
SCB->CCR |= (uint32_t)SCB_CCR_IC_Msk; /* enable I-Cache */
__DSB();
__ISB();
#endif
}
/**
\brief Disable I-Cache
\details Turns off I-Cache
*/
__STATIC_INLINE void SCB_DisableICache (void)
{
#if defined (__ICACHE_PRESENT) && (__ICACHE_PRESENT == 1U)
__DSB();
__ISB();
SCB->CCR &= ~(uint32_t)SCB_CCR_IC_Msk; /* disable I-Cache */
SCB->ICIALLU = 0UL; /* invalidate I-Cache */
__DSB();
__ISB();
#endif
}
/**
\brief Enable D-Cache
\details Turns on D-Cache
*/
__STATIC_INLINE void SCB_EnableDCache (void)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t ccsidr;
uint32_t sets;
uint32_t ways;
SCB->CSSELR = 0U; /*(0U << 1U) | 0U;*/ /* Level 1 data cache */
__DSB();
ccsidr = SCB->CCSIDR;
/* invalidate D-Cache */
sets = (uint32_t)(CCSIDR_SETS(ccsidr));
do {
ways = (uint32_t)(CCSIDR_WAYS(ccsidr));
do {
SCB->DCISW = (((sets << SCB_DCISW_SET_Pos) & SCB_DCISW_SET_Msk) |
((ways << SCB_DCISW_WAY_Pos) & SCB_DCISW_WAY_Msk) );
#if defined ( __CC_ARM )
__schedule_barrier();
#endif
} while (ways-- != 0U);
} while(sets-- != 0U);
__DSB();
SCB->CCR |= (uint32_t)SCB_CCR_DC_Msk; /* enable D-Cache */
__DSB();
__ISB();
#endif
}
/**
\brief Disable D-Cache
\details Turns off D-Cache
*/
__STATIC_INLINE void SCB_DisableDCache (void)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
register uint32_t ccsidr;
register uint32_t sets;
register uint32_t ways;
SCB->CSSELR = 0U; /*(0U << 1U) | 0U;*/ /* Level 1 data cache */
__DSB();
SCB->CCR &= ~(uint32_t)SCB_CCR_DC_Msk; /* disable D-Cache */
__DSB();
ccsidr = SCB->CCSIDR;
/* clean & invalidate D-Cache */
sets = (uint32_t)(CCSIDR_SETS(ccsidr));
do {
ways = (uint32_t)(CCSIDR_WAYS(ccsidr));
do {
SCB->DCCISW = (((sets << SCB_DCCISW_SET_Pos) & SCB_DCCISW_SET_Msk) |
((ways << SCB_DCCISW_WAY_Pos) & SCB_DCCISW_WAY_Msk) );
#if defined ( __CC_ARM )
__schedule_barrier();
#endif
} while (ways-- != 0U);
} while(sets-- != 0U);
__DSB();
__ISB();
#endif
}
/**
\brief Clean D-Cache
\details Cleans D-Cache
*/
__STATIC_INLINE void SCB_CleanDCache (void)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t ccsidr;
uint32_t sets;
uint32_t ways;
SCB->CSSELR = 0U; /*(0U << 1U) | 0U;*/ /* Level 1 data cache */
__DSB();
ccsidr = SCB->CCSIDR;
/* clean D-Cache */
sets = (uint32_t)(CCSIDR_SETS(ccsidr));
do {
ways = (uint32_t)(CCSIDR_WAYS(ccsidr));
do {
SCB->DCCSW = (((sets << SCB_DCCSW_SET_Pos) & SCB_DCCSW_SET_Msk) |
((ways << SCB_DCCSW_WAY_Pos) & SCB_DCCSW_WAY_Msk) );
#if defined ( __CC_ARM )
__schedule_barrier();
#endif
} while (ways-- != 0U);
} while(sets-- != 0U);
__DSB();
__ISB();
#endif
}
其中SCB_EnableICache()以及后面2个函数都已经定义好了,这里复制处出来供大家学习参考,内还有其他对ICache或者DCache的操作,感兴趣可以到core_cm7.h头文件去了解一下。
3 总结
(1)在cortexM7中若需要使用Cache则必须先配置MPU
(2)按照不同的MPU配置能够使用的Cache属性也不同
(3)Cache一共有4种模式
(4)Cache作为一个高速缓存,肯定存在缓存与实际内存不一致的情况,只要能够保证在关键数据或者代码变更时(非core操作)更新ICache或者DCache虽然会降低性能但能够保证数据一致。