Keil5 MDK在Cortex M系列关于分散加载文件说明指导
类别内容摘要本文结合 SRAM 示例工程说明如何在Cortex M LCM32F067 工程中使用 Keil 分散加载文件将部分函数固定到指定 Flash 地址运行并将部分函数搬运到指定 RAM 地址运行。源代码路径链接: https://pan.baidu.com/s/1De7GAP6OVmd0leHCKNNtag?pwddesm 提取码: desm目 录适用范围基础知识分散加载文件概述分散加载文件语法分散加载应用实例常见问题与验证方法1. 适用范围有时候用户希望在单片机工程中实现如下需求某些函数固定在某个 Flash 地址作为固定接口或服务入口某些函数在上电后搬运到 RAM 中执行以提升速度或避免 Flash 操作时取指冲突普通代码和普通变量仍然按照默认方式分别放在 Flash 和 SRAM 中在 Keil MDK 工程中这类需求通常通过分散加载文件来实现。本文结合Project/Examples/SRAM示例工程重点讲解如何通过section(name)给函数打标签如何在.sct文件中把这些 section 分配到不同地址如何理解加载域、执行域、FIXED、__scatterload如何通过 map 验证最终结果2. 基础知识2.1 基本概念在理解分散加载前需要先清楚下面几类数据的含义Code表示程序代码。RO-Data表示只读数据例如const常量。RW-Data表示已初始化的全局变量或静态变量。ZI-Data表示未初始化的全局变量或静态变量。在嵌入式工程中通常有如下结论Code和RO-Data一般放在 Flash 中RW-Data运行时在 RAM 中但其初值镜像也要占用 FlashZI-Data只占用 RAM不占用 Flash 初值空间因此一个工程的空间占用通常可理解为Flash 占用 Code RO-Data RW-DataRAM 占用 RW-Data ZI-Data2.2 启动阶段谁在搬运代码和数据很多学习者第一次接触分散加载时最容易产生的疑问是“既然某个函数要在 RAM 中运行它是怎么从 Flash 到 RAM 的”答案在启动代码中。在 Cortex-M Keil 的典型启动流程里Reset_Handler进入运行库启动过程__main调用__scatterload__scatterload根据 scatter 文件描述把需要搬运的代码或数据从装载地址复制到执行地址RW数据完成初始化ZI数据完成清零最后才跳入main()因此只要 scatter 文件描述正确那么main()运行之前所有要求“在 RAM 中执行”的函数就已经被搬运到目标 RAM 地址了2.3 本工程中的 4 个目标函数本工程中专门设计了 4 个函数用于演示RAM_FillPatternWindow()RAM_MixAndAccumulate()FLASH_FixedSignature()FLASH_FixedParity()它们分别用于演示两个不同 RAM 地址运行两个不同 Flash 固定地址运行最终链接结果为函数名最终符号地址说明RAM_FillPatternWindow0x20000001在 SRAM 执行区 A 运行RAM_MixAndAccumulate0x20000101在 SRAM 执行区 B 运行FLASH_FixedSignature0x08000801固定在 Flash 窗口 AFLASH_FixedParity0x08000901固定在 Flash 窗口 B说明地址最低位为1是 Thumb 状态位实际区域基地址仍然分别是0x20000000、0x20000100、0x08000800、0x080009003. 分散加载文件概述分散加载文件是一个文本文件用于描述链接器如何组织最终映像并决定程序镜像装载到哪里代码和数据执行时位于哪里不同 section 应该进入哪个执行区如果不使用 scatter 文件链接器会按默认规则放置代码和数据。而在下面这些场景中通常就需要显式编写 scatter 文件代码必须放在多个不同内存区域部分函数必须在 RAM 中执行部分函数必须固定在某个 Flash 地址多块 RAM 或多块 Flash 需要分别利用需要精确控制向量表、启动区、应用区、固定接口区的地址布局对本工程而言使用分散加载的直接目的就是固定两段 Flash 代码区划出两段 RAM 执行区普通代码和数据继续走默认的应用区与数据区4. 分散加载文件语法4.1 加载时域的描述一个典型的加载域写法如下LR_IROM1 0x08000000 0x00010000 { ... }这里LR_IROM1是加载域名称0x08000000是加载域起始地址0x00010000是该加载域大小在本工程中这表示整个映像的主装载空间仍然是片上 Flash其组织起点是0x080000004.2 运行时域的描述一个典型的执行域写法如下ER_FLASH_FIXED_A 0x08000800 FIXED 0x00000100 { *.o (FLASH_FIXED_A) }这里ER_FLASH_FIXED_A是执行域名称0x08000800是执行地址FIXED表示装载地址与执行地址相同0x00000100表示执行域大小如果某个执行域不在 Flash而是在 SRAM 中例如ER_RAM_EXEC_A 0x20000000 0x00000100 { *.o (RAM_EXEC_A) }那么它的含义是这段代码最终在0x20000000执行但初始镜像仍由主加载域统一组织在 Flash 中上电后由__scatterload拷贝到该 SRAM 地址4.3 输入段描述scatter 文件中的输入段匹配规则用来决定“哪些内容放进这个执行域”。例如*.o (RAM_EXEC_A)它表示匹配所有目标文件中的RAM_EXEC_A输入段并把这些输入段放到当前执行域中本工程中在 C 代码里写了__attribute__((section(RAM_EXEC_A)))这就会让该函数进入名为RAM_EXEC_A的输入段。scatter 文件再用*.o (RAM_EXEC_A)将其准确分配到ER_RAM_EXEC_A。类似地FLASH_FIXED_A对应固定 Flash 区 AFLASH_FIXED_B对应固定 Flash 区 BRAM_EXEC_B对应第二个 RAM 执行区4.4.ANY的意义.ANY可以理解成“兜底规则”。例如.ANY (RO)表示所有还没有被前面规则收走的只读代码、只读数据都放到当前执行域因此在写 scatter 文件时通常建议顺序为先写专用 section 的匹配规则最后再写.ANY否则很容易出现你想单独分配的函数先被默认.ANY区域收走后面的专用区域反而匹配不到目标5. 分散加载应用实例5.1 本工程的地址规划本工程使用的关键地址如下区域起始地址用途ER_ROOT0x08000000向量表、启动代码、运行库根区ER_FLASH_FIXED_A0x08000800固定 Flash 函数 AER_FLASH_FIXED_B0x08000900固定 Flash 函数 BER_IROM10x08000A00普通应用代码区ER_RAM_EXEC_A0x20000000RAM 执行函数 AER_RAM_EXEC_B0x20000100RAM 执行函数 BRW_IRAM10x20000200普通数据区用图示表示如下flowchart TB subgraph FLASH[Flash 地址空间] F0[0x08000000\nER_ROOT\n向量表 / 启动代码 / __main / __scatterload] F1[0x08000800\nER_FLASH_FIXED_A\nFLASH_FixedSignature()] F2[0x08000900\nER_FLASH_FIXED_B\nFLASH_FixedParity()] F3[0x08000A00\nER_IROM1\n普通应用代码区] end subgraph SRAM[SRAM 地址空间] R0[0x20000000\nER_RAM_EXEC_A\nRAM_FillPatternWindow()] R1[0x20000100\nER_RAM_EXEC_B\nRAM_MixAndAccumulate()] R2[0x20000200\nRW_IRAM1\nRW / ZI 数据] end5.2 本工程的 scatter 文件配置本工程实际采用的 scatter 文件结构如下LR_IROM1 0x08000000 0x00010000 { ER_ROOT 0x08000000 0x00000800 { *.o (RESET, First) *(InRoot$$Sections) } ER_FLASH_FIXED_A 0x08000800 FIXED 0x00000100 { *.o (FLASH_FIXED_A) } ER_FLASH_FIXED_B 0x08000900 FIXED 0x00000100 { *.o (FLASH_FIXED_B) } ER_IROM1 0x08000A00 FIXED 0x0000F600 { .ANY (RO) .ANY (XO) } ER_RAM_EXEC_A 0x20000000 0x00000100 { *.o (RAM_EXEC_A) } ER_RAM_EXEC_B 0x20000100 0x00000100 { *.o (RAM_EXEC_B) } RW_IRAM1 0x20000200 0x00002600 { .ANY (RW ZI) } }其含义可概括为ER_ROOT放根区内容ER_FLASH_FIXED_A和ER_FLASH_FIXED_B放两个固定地址函数ER_IROM1放其余普通应用代码ER_RAM_EXEC_A和ER_RAM_EXEC_B放两段不同地址的 RAM 执行代码RW_IRAM1放普通全局变量和静态变量5.3 在 C 文件中定义自定义 section本工程在main.c中使用了下面的写法#define RAM_EXEC_A_ATTR __attribute__((noinline, section(RAM_EXEC_A))) #define RAM_EXEC_B_ATTR __attribute__((noinline, section(RAM_EXEC_B))) #define FLASH_FIXED_A_ATTR __attribute__((noinline, section(FLASH_FIXED_A))) #define FLASH_FIXED_B_ATTR __attribute__((noinline, section(FLASH_FIXED_B)))然后分别将 4 个函数放入这些 sectionRAM_EXEC_A_ATTR uint32_t RAM_FillPatternWindow(uint32_t seed); RAM_EXEC_B_ATTR uint32_t RAM_MixAndAccumulate(uint32_t value); FLASH_FIXED_A_ATTR uint32_t FLASH_FixedSignature(void); FLASH_FIXED_B_ATTR uint32_t FLASH_FixedParity(uint32_t value);其中noinline的作用是防止编译器把函数直接内联到调用者中便于在链接报告中清楚看到每个函数的独立落点5.4 双 RAM 运行函数配置实例本工程中两个运行在 RAM 的函数分别是RAM_FillPatternWindow()RAM_MixAndAccumulate()它们的功能并不复杂目的是让学习者把注意力集中在“地址分配”上而不是业务逻辑本身。对应关系如下函数section执行区执行地址RAM_FillPatternWindow()RAM_EXEC_AER_RAM_EXEC_A0x20000000RAM_MixAndAccumulate()RAM_EXEC_BER_RAM_EXEC_B0x20000100程序启动时运行库进入__scatterload将RAM_EXEC_A对应的镜像从 Flash 拷贝到0x20000000将RAM_EXEC_B对应的镜像从 Flash 拷贝到0x20000100main()才开始执行因此在main()中调用这两个函数时它们已经在 RAM 中。5.5 固定 Flash 地址函数配置实例本工程中两个固定在 Flash 中的函数分别是FLASH_FixedSignature()FLASH_FixedParity()对应关系如下函数section执行区固定地址FLASH_FixedSignature()FLASH_FIXED_AER_FLASH_FIXED_A0x08000800FLASH_FixedParity()FLASH_FIXED_BER_FLASH_FIXED_B0x08000900由于这两个执行区都带FIXED因此装载地址 执行地址函数直接在 Flash 中原地执行不需要启动时拷贝这类配置很适合实现固定服务入口版本接口固件标识函数固定跳转入口5.6 普通应用区为什么也要使用 FIXED本工程里普通应用区写成了ER_IROM1 0x08000A00 FIXED 0x0000F600这里的FIXED很重要。原因是前面已经人为切出了两个固定 Flash 小窗普通应用区从0x08000A00开始执行如果这里不加FIXED链接器可能会把装载镜像往前压缩造成 Flash 中装载布局和执行布局发生交叉覆盖风险本工程第一次修改 scatter 文件时就出现了这类错误。最终修复办法就是给普通应用区也加上FIXED这是一条很重要的实践经验只要 Flash 代码区需要精确切片而且这些区域本身就在 Flash 原地执行就优先考虑FIXED5.7 本工程中的 section、执行域与函数对应关系总表名称类型位置作用RAM_EXEC_A自定义输入段C 文件定义作为 RAM 函数 A 的标签RAM_EXEC_B自定义输入段C 文件定义作为 RAM 函数 B 的标签FLASH_FIXED_A自定义输入段C 文件定义作为固定 Flash 函数 A 的标签FLASH_FIXED_B自定义输入段C 文件定义作为固定 Flash 函数 B 的标签ER_RAM_EXEC_A执行域SRAM接收RAM_EXEC_AER_RAM_EXEC_B执行域SRAM接收RAM_EXEC_BER_FLASH_FIXED_A执行域Flash接收FLASH_FIXED_AER_FLASH_FIXED_B执行域Flash接收FLASH_FIXED_B6. 常见问题与验证方法6.1 如何看 map 文件map文件更适合检查整体空间分布与是否重叠。重点检查各执行区是否超出分配大小例如ER_RAM_EXEC_A 0x00000100是否真的足够。SRAM 代码区和普通数据区是否重叠本工程中ER_RAM_EXEC_A从0x20000000ER_RAM_EXEC_B从0x20000100RW_IRAM1从0x20000200固定 Flash 区和普通应用区是否冲突本工程中0x080008000x080009000x08000A00这些边界都应保持清晰独立。6.2 初学者常见错误常见错误主要有以下几类只把顶层函数放到 RAM却忽略其子函数仍在 Flash把.ANY (RO)写在专用 section 规则前面忘记给演示函数加noinline没有给 RAM 代码区与普通数据区留出边界在多块 Flash 切片时忘记使用FIXED结束语对分散加载最实用的理解方式可以归纳为四句话先在 C 文件中用section(name)给函数打标签再在.sct文件中把这个标签映射到指定执行域如果执行地址在 RAM启动时由__scatterload搬运过去如果执行地址在固定 Flash使用FIXED让其原地执行掌握了这套方法后就可以在单片机Cortex M系列工程中灵活实现固定地址函数接口RAM 中运行的关键函数多执行区的代码布局设计更复杂的 Boot / App 分区结构
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2422616.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!