最近希望将 ADC 性能跑到尽可能的极限,所以研究了一下相关的外设功能。
其中 cache 和 MPU 属性适配的部分我以前一直没搞太懂,这次总算弄得比较清楚了。在具有 cache 功能的 arm 芯片中,一定要配合 MPU 外设功能配置才可以使用 cache 功能。
MPU 与 MMU 的主要区别
MPU | MMU | |
---|---|---|
缓冲命中周期 | 固定1指令周期 | 1~20周期不等 |
管理精细度 | 有限的区域(十数个)管理 | 可以 RAM 逐页精细化 |
多任务支持 | 划分物理区域进行有限隔离 | 虚拟地址进程完全隔离 |
说白了 | 做实时性应用的 | 搞 linux 的 |
其他很多芯片当然都有类似的 cache 和 MPU 功能,但是这里主要讨论的 arm 芯片,他们的功能都是类似的,只是管理粒度、划区数量、或者不可管理固定属性,其中的属性和对应功能都是类似的。
MCU 芯片的外设分布在不同总线上,而同总线的外设和内存间读写相对会快。我们仅就 ADC 、DMA、以及内存的配合,理解总线。
在我的这个应用中,因为 DMA 只能操作同总线上的外设和内存,所以使用到了 ADC1/2/3、DMA1/2、BDMA1。
结合以下的总线示意图理解。ADC1 结合 DMA1 采样同时占用部分 D2 域内存、ADC3 结合 BDMA 采样同时占用部分 D3 域内存,并最终将数据初期处理后将采样值放在 D1 域内存供 CPU 后续计算。这样分配尽可能减少跨域的性能损耗。
MPU 配置控制 cache 属性,有最重要的 3 个配置项如下,我有一些自己的简单总结
以及对应的 chat GPT 解释,比我说的清楚详细。
-IsCacheable 控制是否使用 D-Cache,以及在 M7 上配合 TEX/C/B 决定缓存策略。
- IsBufferable 允许写缓冲(write buffering/combining),可能延后对外可见时间,以换取更高吞吐。
- IsShareable 标记该区“可被多个主机共享”,在 M7 上还会改变缓存策略:Cacheable+Shareable 会强制为 Write-Through/No-Write-Allocate。
详细说明
- IsCacheable
- 关心点:是否走 D-Cache;配合 TEX/C/B 决定是 Write-Back/Write-Allocate 还是 Write-Through/No-Allocate。
- 影响:CPU访问延迟与带宽、总线流量;不直接改变外设采样速率,但影响CPU处理速度。
- IsBufferable
- 含义:允许将写入先进入写缓冲并可能合并,再异步刷到内存/总线,减少总线事务、提高吞吐。
- 影响:
- 性能:连续写(memcpy、流式写)更快。
- 可见性/顺序:写入对其他主机(DMA/外设)的“可见时刻”可能延后;必要时用屏障指令保证顺序。
- 典型用法:Normal 内存常设为 BUFFERABLE;Device/MMIO 通常不设以避免重排/合并。
- IsShareable
- 含义:该区域可能被多个主机共享(CPU、DMA等)。在 Cortex-M7 上对 Cacheable 的 Normal 区有特别影响:
- Cacheable + Shareable = 写直达(Write-Through)、不写分配(No-Write-Allocate),减少其他主机看到旧数据的窗口,但CPU侧性能较 WB/WA 略降。
- Cacheable + Non-shareable = 通常为 Write-Back/Write-Allocate(配合 TEX=1,C=1,B=1),CPU性能最佳。
- 影响:
- 顺序与可见性语义更保守,有利于与其他主机协作。
- 不等于“硬件缓存一致性”,但改变了缓存写策略与内存屏障语义范围。
常见组合速览(Normal 内存)
- B=1, C=1, Shareable=0, TEX=1 → Write-Back, Write-Allocate(CPU最快,适合大多数计算缓冲)
- B=1, C=1, Shareable=1, TEX=1 → Write-Through, No-Write-Allocate(更保守的共享语义)
- B=0/1, C=0 → Non-cacheable(可选择是否允许写缓冲;B=0 更严格时序)
见此前的总线示意,由于 cache 实际属于 CPU 的一部分,所以 DMA 获得的内存数据(D2/D3域内存区)是必然跨域的。在跨域的应用中,必须解决内存一致性问题,这需要一个内存区大小的 while 循环来强制命中解决一致性问题,反而引起不必要的消耗。
所以在 MPU 配置中,D2、D3域对应的地址范围不开启 Cache,对应上述的第三个组合。D1域的常用内存区则可以开启 IsCache、开启 IsBuffer、关闭 IsShare 以获得最大的性能,对应上述的第一个组合。
而上述的第二个组合,我暂时没有遇到过使用场景,也想象不出来。
以下是上述配置的匹配代码,可供参考以及我自己回档。
/**
* @brief MPU配置
* @param None
* @retval None
*/
static void MPUInit(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable( );
/* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate
最佳性能,用于CPU处理计算 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
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);
/* 配置D2域MPU
D2域外设的DMA使用 禁止cache */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置以太网收发描述符部分为Strongly Ordered */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30040000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置D3域MPU
D3域外设的DMA使用 禁止cache */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x38000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER3;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置FMC 片选3 的支持*/
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x68000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; // 此外设需配置为无cache,否则会重复片选和读写使能
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER4;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}