尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

GD32H7 SRAM配置实战:Cache一致性、TCM与内存布局优化

GD32H7 SRAM配置实战:Cache一致性、TCM与内存布局优化 GD32H7系列的SRAM配置看着是个小事实际能把人逼疯。我之前有个项目代码逻辑怎么查都没问题但设备跑几个小时就随机死机最后定位到是SRAM里的数据被踩了根源就是内存布局和Cache配置不当。从那以后我养成了一个习惯拿到Cortex-M7内核的片子第一步不是写外设而是先把存储架构和SRAM分配策略捋清楚。这篇东西不是翻译参考手册是我在GD32H7上做实时系统、音频处理和ADC高速采集时关于SRAM优化配置踩坑和实战经验的汇总。看完你至少能搞清楚三件事GD32H7的SRAM到底分几块、代码和数据的摆放位置对性能影响有多大、以及为什么DMA和Cache配合不好会让你的数据变得稀碎。1. 为什么SRAM配置会成为性能瓶颈先把存储架构吃透1.1 从Cortex-M7的内核特性说起GD32H7系列用的是Cortex-M7内核这是ARM迄今最强悍的M内核之一主打高性能。但高性能是有代价的——它不像M0/M3/M4那样把所有内存挂在同一条总线上而是搞了一套复杂的内存架构TCM、AXI SRAM、AHB SRAM、Cache等。很多从M3/M4迁过来的工程师拿到H7后发现用老方法写代码性能反而跑不上去问题就出在这里。先记住一句关键结论M7内核的取指和执行效率极大程度上依赖代码和数据的物理存放位置以及Cache的配合策略。换句话说同样一段代码放在ITCM里和放在AXI SRAM里执行速度可能差出两三倍。这不是玄学是总线架构决定的。1.2 GD32H7的SRAM在物理上被拆成了三块我手上这颗GD32H7的具体型号不同SRAM分布可能略有差异但总体框架是一致的。GD32H7系列的SRAM大体分三块ITCM和DTCM紧耦合内存这是和内核直连的SRAM没有总线仲裁延迟访问速度跟内核时钟同步基本上零等待周期。ITCM是给指令用的DTCM是给数据用的它们的地址空间独立于外部SRAM。AXI SRAM挂在高性能总线上容量通常比较大可以同时被CPU和DMA访问但经过总线矩阵有仲裁延迟。AHB SRAM挂在AHB总线上的SRAM一般容量中等也用来做外设缓冲区或以太网描述符等。很多人会对TCM有疑问TCM不也是SRAM吗对它的本质就是SRAM但因为它和CPU之间是专用的紧耦合接口不需要经过总线矩阵和Cache所以延迟极低。而AXI SRAM是经过总线矩阵的SRAM需要靠Cache来弥补访问延迟。这就带来一个核心矛盾SRAM容量有限性能需求无限怎么把有限的SRAM用在刀刃上1.3 大容量SRAM的内部结构差异说到大容量SRAM的寻址和读写电路有个常被忽略的点SRAM容量越大内部行列译码复杂度越高访问延迟和功耗都更大。所以GD32H7里TCM做得比较小几十KB到几百KB级别因为大了以后无法保证零等待而AXI SRAM可以做得很大几百KB因为可以通过Cache来容忍延迟。这引出一个实用经验高频访问的代码和栈放TCM大块数据和缓冲区放AXI SRAM。千万不要反过来把一个大缓冲放TCM里去掉Cache——那你不仅浪费了TCM的低延迟优势还因为TCM容量小导致栈空间不够系统跑着跑着就栈溢出了。2. TCM与Cache的配置实操代码执行速度翻倍的秘密2.1 打开I-Cache和D-Cache的完整配置GD32H7上电默认Cache是关闭的如果你不手动开启就等于花着M7的钱用着M4的性能。开Cache的代码很简单一般放在系统初始化最前面复位之后立即执行void cache_enable(void) { SCB_EnableICache(); // 使能I-Cache SCB_EnableDCache(); // 使能D-Cache }GD32的固件库提供了SCB_EnableICache和SCB_EnableDCache这两个接口底层是操作Cortex-M7内核的SCTLR寄存器。如果你用的是HAL风格或者裸寄存器操作就直接改SCB-SCTLR的I和C位。但这里有个关键点D-Cache不是想开就开的。如果你的程序里用了DMA而且DMA的目标地址在SRAM里那么D-Cache开起来后你必须处理数据一致性问题。这个坑我在后面第3节详细讲这里先记住开D-Cache之前先确认你的DMA缓冲区和Cache策略是否配好了。2.2 使能D-Cache后必须处理的SRAM一致性风险D-Cache的工作原理是CPU读数据时先看Cache里有没有有就直接返回没有才去SRAM里取CPU写数据时先写到Cache里标记为脏等某个时机再回写到SRAM。这个机制在一般的程序运行中很完美但一旦有DMA参与——DMA直接读写SRAM不经过Cache——问题就来了场景A外设通过DMA往SRAM里写数据CPU却读到Cache里的旧数据。比如ADC采样DMA把采样值源源不断写到SRAM缓冲区CPU去缓冲区取值计算。没开Cache时一切正常开了Cache后CPU第一次读了缓冲区内容这些数据就进了Cache之后DMA更新了SRAM里的缓冲区但CPU再读时命中Cache读到的还是旧数据。表现就是采样数据“卡住”了永远是老数据。场景BCPU往SRAM里写数据DMA把数据搬运出去结果DMA读的SRAM还是旧内容。典型场景是SPI发送CPU计算结果放到缓冲区然后触发DMA去发送。开了Cache后CPU的写入还没回写到SRAMDMA就已经去SRAM里读数据了读的是残留的旧数据。表现就是发送的数据是上一个周期的旧数据。这两个场景我全都遇到过排查起来非常隐蔽。最坑的是这类问题不是100%出现而是偶发性、间歇性的复现难度极大。2.3 TCM区域为什么不需要和Cache打交道理解了Cache的坑再回头看TCM就豁然开朗了。TCM和CPU直连不走Cache所以TCM是天然免一致性问题困扰的。把高频中断服务函数、递归函数、嵌入式RTOS内核的调度相关代码丢到ITCM里每次取指都是零等待把任务栈放到DTCM里每次压栈出栈也都是零等待任务切换时间能肉眼可见地缩短。我做过一个音频处理项目需要周期性地处理大量滤波计算。最初所有代码都在AXI Flash里运行单次处理耗时偏高后来把核心处理函数全部搬到ITCM里执行处理耗时直接降了接近一半。为什么因为程序从Flash取指是有等待周期的Flash比CPU慢长期命中Cache不足时取指就是瓶颈。而ITCM零等待取指速度跟CPU同频瓶颈直接消解。2.4 链接脚本里把关键代码段定位到ITCM在工程里怎么把代码放到ITCM方法不复杂核心是修改链接脚本。以GCC环境为例你需要在链接脚本里定义一个ITCM的段然后把关键函数指定到这个段里__attribute__((section(.itcm_text))) void critical_isr_handler(void) { // 高频中断处理逻辑 }链接脚本里对应加上.itcm_text : { . ALIGN(4); *(.itcm_text) *(.itcm_text*) . ALIGN(4); } ITCM关键点在于ITCM这块地址空间需要在编译器的存储器映射里单独定义。GD32H7不同型号的ITCM基地址和容量不同查你所用型号的数据手册就行。这个做法也适用于数据段比如把高频访问的查找表放到DTCM里__attribute__((section(.dtcm_data))) const float lookup_table[256] {...};2.5 实测数据一个典型场景的对比我用GD32H7跑了一个1000次迭代的数学密集计算分别测了三种配置下的耗时结果非常直观配置方式耗时相对值说明全部代码在Flash运行不开Cache基准 100%取指等待周期多性能最差全部代码在Flash运行开I-Cache D-Cache约 60%-65%命中率高时提升明显核心代码放ITCM数据放DTCM约 35%-40%无总线仲裁延迟最低这个数据不是说Cache没用恰恰相反Cache提升巨大。但TCMCache组合拳的效果更极端。对于实时性要求高的中断处理TCM的价值是无法替代的。3. 最容易翻车的DMA与D-Cache数据一致性一次完整的排查链路3.1 故障现象ADC采样数据随机变成0下面复盘一个典型的真实踩坑过程完整呈现我当时从现象到根因的排查思路。项目里用GD32H7的ADC做多通道高速采样DMA开启循环模式采出来的数据放到一个全局数组里构成环形缓冲。工程开了D-Cache之后发现一个诡异的毛病系统启动后前面几十帧数据完全正常运行几分钟后某个通道的数据突然持续出现大段0但又没完全死掉偶尔又有正常数据冒出来。第一个直觉反应是ADC采集本身出问题——通道没配好、引脚虚焊、参考电压不稳定。查了一圈硬件没发现问题。用调试器实时看DMA目标缓冲区的内存地址发现DMA其实一直在正常写入只是有些时间段写入的是0实际是采到了0值但更诡异的是CPU逻辑里的“最新采样缓冲区”却和DMA实际写入的内存对不上。这句话是关键线索。CPU看到的缓冲区和DMA看到的缓冲区对不上——标准的Cache一致性问题。3.2 逐步排查为什么缓冲区内容会错位我的程序里CPU检查DMA更新标志DMA半传输中断和传输完成中断然后去缓冲区处理数据。开D-Cache后CPU第一次读了该区域的数据这些数据就被缓存了。DMA在后台把新的采样数据写进了同一段SRAM但CPU再次访问时命中Cache直接返回了旧数据。由于环形缓冲要反复读写同一段内存Cache里的旧数据和新数据混在一起表现就是数据“错位”和“卡顿”。数据大量为0的原因是初始化后缓冲区的初始值就是0CPU一直从Cache里读到内存里的旧状态就以为采到的都是0。3.3 多种解决路径从寄存器操作到MPU方案解决这个问题有几种路径复杂度不同适用场景也不同。方案一传统的手动Clean/Invalidate在DMA传输方向和CPU访问的边界处手动对Cache做清理和失效操作。/* DMA写入完成后CPU要读取数据之前需使D-Cache失效丢弃缓存的旧数据 */ SCB_InvalidateDCache_by_Addr((uint32_t *)adc_buffer, buffer_size); /* CPU写完数据启动DMA之前需把D-Cache内容回写到SRAM */ SCB_CleanDCache_by_Addr((uint32_t *)spi_tx_buffer, buffer_size);这个方案简单直接但如果数据传输频繁会频繁打断CPU的流水线性能有损耗。方案二把DMA缓冲区定义到非Cacheable区域使用MPU配置让DMA缓冲区的地址空间被标记为non-cacheable不可缓存。这样CPU对这块区域的读写都是直通SRAM的永远不经过Cache一致性天然保证。GD32H7的MPU配置并不复杂。核心是配置一个MPU region把缓冲区所在的地址范围设为普通内存但关闭Cache属性TEX、C、B位的组合设为non-cacheable。void mpu_configure_dma_buffer_region(void) { uint32_t base (uint32_t)dma_buffer; uint32_t size MPU_REGION_SIZE_64KB; // 按实际大小调整 MPU_Region_InitTypeDef MPU_RegionInitStruct; MPU_RegionInitStruct.Enable MPU_REGION_ENABLE; MPU_RegionInitStruct.Number MPU_REGION_NUMBER0; MPU_RegionInitStruct.BaseAddress base; MPU_RegionInitStruct.Size size; MPU_RegionInitStruct.SubRegionDisable 0; MPU_RegionInitStruct.TypeExtField MPU_TEX_LEVEL0; MPU_RegionInitStruct.AccessPermission MPU_FULL_ACCESS; MPU_RegionInitStruct.DisableExec MPU_INSTRUCTION_ACCESS_DISABLE; MPU_RegionInitStruct.IsShareable MPU_ACCESS_SHAREABLE; MPU_RegionInitStruct.IsCacheable MPU_ACCESS_NOT_CACHEABLE; MPU_RegionInitStruct.IsBufferable MPU_ACCESS_NOT_BUFFERABLE; MPU_Init(MPU_RegionInitStruct); MPU_Enable(MPU_REGION_DEFAULT_ENABLE); }用MPU方式的优势一劳永逸DMA和CPU都直接操作SRAM不需要每次传输都做Clean/Invalidate极大简化代码逻辑。代价是这块区域的CPU访问延迟上升——毕竟没有Cache加速了。方案三直接使用DTCM作为DMA缓冲区刚才说过DTCM不经过Cache天然一致。所以如果DMA缓冲区不大直接把缓冲区定义到DTCM里是最省事的。很多老工程师强制要求DMA缓冲区要么放DTCM要么配MPU为non-cacheable就是这个道理。但注意不是所有外设DMA都能访问DTCM——GD32H7的DMA控制器是否路由到了DTCM地址空间手册里有明确的存储映射表有必要先确认。3.4 为什么是“环形缓冲”更容易触发这个问题环形缓冲区的特点是数据读写位置会循环回归。CPU和DMA的访问地址在时间上会反复经过同一段区域。如果没有Cache一致性问题这完全正常有了问题之后环形结构会让新旧数据在Cache和SRAM之间来回“错位”症状杂糅特别难分析。我给的排查建议是遇到这类异常第一时间在调试器里同时查看两个视角——DMA实际写入内存区域的内容和CPU通过C代码读到的变量值。如果两个视角内容不同基本可以断定是Cache一致性问题。这是最快最准的定位方法。4. 内存布局与链接脚本把有限的SRAM用到极致4.1 栈、堆、全局缓冲区的区位分配策略很多人的工程里栈、堆、全局缓冲区全默认放在一个段这在资源充裕的平台上问题不大但在GD32H7这种SRAM被物理分层且性能差异悬殊的平台上是一种浪费。我的分配经验是全局高频繁变量、关键标志位放DTCM访问速度最快且不受Cache一致性影响随便用。任务栈尽量放DTCM。RTOS的任务切换高发于时间片轮转或中断栈在DTCM里能显著缩短上下文切换时间。大块数据缓冲区网络包、音频帧、DMA缓冲放AXI SRAM根据Cache策略决定是否配成non-cacheable。这类数据通常以“批量读写”为主关键是一致性不是单次访问延迟。堆能用静态分配就尽量用静态分配。嵌入式系统里动态内存分配本身就是隐患堆碎片化在长时间运行后是随机死机的元凶之一。如果实在要用堆把它放在AXI SRAM即可。4.2 链接脚本实例GCC环境下的SRAM分区下面是一个实际使用的链接脚本片段展示了如何把不同的数据放入不同的SRAM区域MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 1024K ITCM (rwx) : ORIGIN 0x00000000, LENGTH 64K DTCM (rw) : ORIGON 0x20000000, LENGTH 64K AXI_SRAM (rwx) : ORIGIN 0x24000000, LENGTH 512K AHB_SRAM (rw) : ORIGIN 0x30000000, LENGTH 128K }注意这是示例具体型号的地址和容量以GD32H7对应数据手册为准。有些型号的DTCM和ITCM大小不等要仔细核对。然后在SECTIONS里SECTIONS { . ALIGN(4); .itcm_text : { *(.text.itcm_*) } ITCM . ALIGN(4); .dtcm_data : { *(.data.dtcm_*) *(.bss.dtcm_*) } DTCM . ALIGN(4); .axi_data : { *(.data) *(.bss) *(.data.axi_*) *(.bss.axi_*) } AXI_SRAM .heap : { __HEAP_START .; . ORIGIN(AXI_SRAM) LENGTH(AXI_SRAM) - 4K; __HEAP_END .; } AXI_SRAM }这只是演示结构实际工程要配合启动文件里的栈指针初始化确保初始栈落在DTCM中。4.3 定位栈溢出的一个实用技巧栈溢出是SRAM配置中最难排查的问题之一。症状各种各样——随机死机、函数返回地址错乱、全局变量被莫名修改。最根本的解决方案当然是不溢栈但如何快速确认问题确实来自栈技巧是栈区域用固定模式填充初始化周期性检查水位线。在系统启动时把栈区域全部填充成0xA5extern uint32_t __STACK_START; extern uint32_t __STACK_END; void stack_watermark_init(void) { uint32_t *p __STACK_START; while (p __STACK_END) { *p 0xA5A5A5A5; } }然后在主循环里设置一个定时器周期性检测从栈底往上还有多少个0xA5A5A5A5没被修改就知道栈最深用到了多少离溢出还有多远uint32_t stack_free_watermark(void) { uint32_t free_words 0; uint32_t *p __STACK_START; while (p __STACK_END *p 0xA5A5A5A5) { free_words; p; } return free_words * 4; // 返回剩余字节数 }这个方法在调RTOS任务栈大小时也适用。我经常拿这个方法来做压测把任务栈改小跑极端场景的case观察剩余水位找到一个“再小一点点就溢栈”的临界值然后加上30%-50%的余量。4.4 内存碎片问题静态分配优先原则GD32H7的SRAM虽大但嵌入式环境不适合频繁动态分配。malloc/free在长时间运行后会形成碎片。最典型的现象刚启动时系统正常运行N小时后内存分配失败直接死机。我见过太多类似的案例最后都是把动态分配改成静态分配的。处理方法很粗暴但有效把所有缓冲区定义在编译期。// 原来char *buf malloc(MAX_PACKET_SIZE); // 现在 static uint8_t buf[8][MAX_PACKET_SIZE]; uint8_t *get_packet_buffer(int index) { return buf[index]; }这样做的代价是内存利用率不是100%按最大需求预留但换来的是绝对可靠性和可预测性。在嵌入式实时系统里可预测性比峰值利用率更重要。SRAM再大也是有限资源静态分配反而逼着你认真设计缓冲区大小和复用策略。5. 外设缓冲区与ADC高速采样场景的实战补充5.1 ADC模拟看门狗我自己用起来的几个关键参数Dashboard小组做共享单车的时候ADC模拟看门狗天天要配调试调得头大。结合GD32H7的实际使用经验ADC模拟看门狗有个很重要的点它和DMA采集是可以同时使用的但阈值和滤波要配合好。GD32H7的ADC对多通道扫描、注入组、规则组的区分很明显模拟看门狗既可以用在规则通道也可以用在注入通道。实际项目里我常把一路对电流敏感的通道做模拟看门狗监控——超过阈值就触发中断不用等主循环轮询。几个实用参数参考看门狗阈值比较的是ADC转换结果和参考电压有直接关系。拿3.3V基准来说12位ADC的一位对应约0.8mV如果你要设一个3.0V的监控阈值那么阈值代码就是 3000/0.8 ≈ 3750。使能看门狗后滤波平均值仍然有效。很多人误以为看门狗会把ADC输出锁住其实它只是多了一个超限判定路径并把超限标志置位。如果用了硬件过采样有些H7系列支持超采样倍率和看门狗阈值之间要重新换算。倍率越大一个计数代表的电压越小不换算会把阈值设错导致整机误报警。配合前面讲的SRAM/Cache一致性处理ADC采样数据进入DMA环形缓冲区后CPU端不要再去读“新鲜值”而是通过DMA中断里维护一个抓取序号判断新旧。这样即便Cache有问题也只会影响到中断级的数据读操作不至于影响主循环判断。5.2 DMA环形缓冲区的设计要点ADC多通道高速采样时DMA环形缓冲区是最常见的结构。但很多人设计环形缓冲时没考虑Simultaneous情况下数据和Cache的耦合导致在开D-Cache后出现“数据分叉”。我做设计的几个核心要点缓冲区大小设为2的幂。这样索引回绕可以用位与操作完成#define BUF_SIZE (1 10) // 1024 #define IDX_MASK (BUF_SIZE - 1) uint16_t adc_buf[BUF_SIZE]; volatile uint32_t write_index 0; /* DMA中断里更新 */ void dma_done_isr(void) { write_index samples_per_block; write_index IDX_MASK; }CPU读数据时要基于write_index回访历史数据而不是从0开始重读。这能保证在一些数据仍在Cache里没回写SRAM的情况下CPU访问到的依然是最新数据——因为回访的正好是更新过的内存段而老数据段在Cache里的陈旧副本已经被时间淘汰了。开启D-Cache时DMA缓冲区尽量使用MPU配置成non-cacheable。这是我反复经历的教训——宁可牺牲一点CPU对缓冲区的访问速度也要保证数据绝对一致。在高速ADC场景下正确性远胜那点性能。5.3 数据一致性临时处理路径关全局中断的方式不推荐有工程师为了解决DMA和Cache的一致性问题在DMA传输期间直接关全局中断。看起来有效实际上风险很大DMA传输往往不是瞬时完成的长时间关中断会导致实时性崩坏中断丢失系统控制链路彻底失灵。正确做法是围绕数据类型和访问频率选择合适的策略高频小数据用DTCM中频中量数据用MPU non-cacheable区域低频大批量数据才做Clean/Invalidate。不需要全部搞成一种方式混合运用更符合实际。6. 低功耗模式下SRAM内容的保持与唤醒陷阱6.1 睡眠模式下哪块SRAM数据会丢嵌入式设备免不了要做低功耗设计。GD32H7进入某些深度睡眠模式时不是所有SRAM区域都能保持内容的。有些低功耗模式下内核时钟停摆但SRAM本身有独立供电内容依然保留有些模式则会关闭SRAM的供电数据全部丢失。我遇到过的情况是产品进入睡眠后唤醒时程序跑飞复位后数据全乱。排查发现睡眠唤醒流程里DMA缓冲区所在区域在睡眠期间被断电了唤醒后DMA外设还保留着旧的配置但内存里的数据已经是垃圾。6.2 进入低功耗前必须处理的事无论用哪种低功耗模式进入前做好这几件事Cache回写如果睡眠期间D-Cache里还有脏数据没回写到SRAM唤醒后这些数据可能丢失或者不一致。进入睡眠前做一次全局Cache CleanSCB_CleanDCache();DMA停止与缓冲区内容确认如果DMA还处于使能状态睡眠期间可能会继续写入缓冲区导致唤醒后缓冲区内有半包数据。建议在进入睡眠前关闭DMA或者至少把DMA的缓冲区使用状态整理干净。唤醒后Cache Invalidate唤醒后第一件事把D-Cache整体失效掉SCB_InvalidateDCache();因为睡眠期间可能有外部事件或DMA更新了SRAMCache里的旧数据已不可信。宁可先全部作废让后续访问重新从SRAM加载也不能保留醒前残留的Cache内容。6.3 一个容易被忽略的细节栈指针恢复睡眠模式下如果RST了主栈指针RTOS的任务栈可能无法恢复。这在用低功耗模式RTOS时尤其突出。我的做法是进入睡眠前保存关键任务的栈信息唤醒后由启动代码判断是从冷启动还是从睡眠唤醒分别执行不同的初始化路径。这个判断通常放在启动汇编里通过检查一个特定的SRAM标志位来实现。当标志位为特定值时跳过冷启动的外设初始化直接恢复任务栈指针。7. 一些调试SRAM相关问题的工具和手段7.1 使用调试器实时查看Cache状态排查SRAM和Cache问题时调试器是你的眼睛。不过很多人不会利用调试器直接看Cache状态。在Keil/IAR的寄存器窗口里找到Cortex-M7内核的系统控制块相关寄存器可以查看Cache的使能状态。进一步可以通过SCB-CCR、SCB-CACHEOP等寄存器查询Cache命中/缺失相关信息。如果你的调试器支持直接看内存视图时选择“以非Cache视角读取SRAM”能让你看到DMA更新后的真实SRAM内容而不是CPU视角缓存后的内容。这招在排查一致性问题时极其好用一边看CPU读到变量的值一边看物理SRAM里的值两个一对比问题立刻现形。7.2 MPU设置错误的检查方法MPU配置错误造成的现象也往往是随机性的。查这类问题先列出预期行为再用调试器逐项验证如果某块区域被MPU配置成了禁止执行XN那里面放代码一定是HardFault如果MPU没有给DMA缓冲的区域开non-cacheable那DMA和CPU的Cache一致性问题仍然存在MPU region有优先级和重叠规则你配了一个大region再配一个小region覆盖实际生效的是哪个要看手册的匹配规则我调试MPU问题时习惯先把MPU的region配置打印出来逐条核对该区域的地址范围是否和预期一致。很多“诡异问题”最后都发现是region地址范围算错了覆盖到了别的区域。7.3 硬件调试的最后一个手段引脚翻转这是最朴素的性能分析手段。在关键代码路径前后加GPIO翻转然后用示波器看脉冲宽度就能精确测出一段代码的实际执行时间。这个方法在验证TCM优化效果时非常直观。优化前在中断处理函数入口和出口各翻转一次GPIO量出脉冲宽度把函数搬到ITCM后再量一次脉冲宽度。两个一对比性能提升多少不用猜直接看数据。写到最后的一些经验回头看我做过的这些GD32H7项目SRAM配置相关的坑大部分集中在三个方面Cache一致性、内存布局不合理、栈和堆管理不当。这三个问题的共性是它们都不会在刚上电时爆发而是运行一段时间后随机冒出来特别难复现特别耗时间。其实只要第一次就把存储架构想清楚、把SRAM分区策略定下来、把Cache和DMA的交互机制摸透后续能省下很多调试时间。最后分享一个我个人的习惯每次拿到新板子第一件事就是查内核手册里存储映射那一章把每块SRAM的地址、容量、访问方式、是否支持DMA访问整理成一张表放在工程注释里。这张表看着不起眼但在配置链接脚本、调试DMA问题、评估低功耗方案时能节省大量翻手册的时间。做嵌入式有时候慢就是快。
返回列表