ARM Cortex-M底层技术(十一)KEIL MDK 分散加载示例3-单独函数/变量的指定加载

转载地址:  单独函数/变量的指定加载

小编我一向主张在实战中学习,不主张直接去去学习规则&定义,太枯燥,在实际应用中去摸索,才会真正理解具体的技术细节,下面我们就通过实际的简单用例来搞清楚分散加载。

将函数和变量放到特定的指定加载地址的方法:

 

通常,编译器通过单个源文件生成RO、RW和ZI节。要将单个函数或者数据固定放在特定的地址上,我们必须允许链接器单独处理这个函数或数据并且与其他的部分分开。

 

一共有4种方法可以完成这个神奇的操作,我们会逐一介绍:

<1>使用--split_sections这个编译器选项,为每个函数单独生成一个节(分散加载操作以节为单位),再单独分配这些节;

<2>使用__attribute__((at(address)))来指定放置;

<3>使用__attribute__((section("name")))来放置一个被命名的节;

<4>使用汇编来指定生成节,然后再放置这些节。

汇编我们不讨论,因为很少有人用(其实是小编我汇编太差,搞不定……),我们重点讨论前3种方法。

使用--split_sections生成单独的ELF节

在如下图的位置上勾选“One ELF Section pre Function”然后在编译器控制串窗口里面,就是最下面的红色方框中就可以看到多了一个--split_sections这个编译器选项。

这个编译器选项啥意思?简单来说就是让每个函数生成一个独立的节。不勾选这个选项,是以文件为单位生成节的,比如一个main.c文件,会生成一个main.o的节,一个startup.s(汇编)也会生成一个startup.o的节,但并不是以函数为单位的。勾选了上图的选项够每个函数都会生成一个独立的节,我们看下以下的对比图就比较清晰了。

【图1】未勾选--split_sections的.map文件节选

【图2】勾选--split_sections的.map文件节选

如上,图1只有几个文件生成的节,而图2明显多出了几个节,比如i.GPIOInit以及i.main都成了独立的节,而在这之前,他们只是main.o这个节里面的一部分。勾选了--split_sections选项生成的节都带有“i.”的前缀,这个标记方便大家识别。

我们把各个函数独立成一个节之后,我们就可以在分散加载中放置他们了,比如我们把程序中的GPIOInit()函数放到SRAM中,分散加载需要按照如下调整:

LR_IROM1 0x00000000 0x00008000  {    
  ER_IROM1 0x00000000 0x00008000  { 
   *.o (RESET, +First)
   *(InRoot$$Sections) 
   .ANY (+RO)
  }
  RW_IRAM1 0x10001000 0x00001000  {  
   .ANY (+RW +ZI)
  }
}

LR_IROM2  0x10000000 0x00001000
{
  ER_IROM2 0x10000000 0x00001000{
	main.o(i.GPIOInit)
  }	
}

我们验证下刚才的操作,查看.map文件:

OK,GPIOInit函数被加载到了SRAM的地址上。

使用--split_sections生成单独的ELF节存在的问题

这种方式会导致一定量的存储空间的浪费,并会使性能略微降低。如果大家使用的是8KB、16KB、32KB这样Flash较小的MCU,不建议用这种方式来做,但如果Flash空间较大同时又需要制定很多函数、数据的地址的时候,这种方法就高效得多。

使用__attribute__((at(address)))来指定放置

这种比较简单,直接看示例:

int var1 __attribute__((at(0x10000000))) = 0x55;

代码直接向0x10000000地址分配一个变量,并赋值0x55;

然后编译&链接,我们看下.map文件:

非常直接,直接多了一个.ARM.__AT_0x10000000的节,被放到了0x10000000的地址上。

DEBUG一下看看Memory数据:

0x10000000地址上0x55的值已经存在,证明变量被成功定位。

改一下程序我们再看看:

int var1 __attribute__((at(0x10000000))) = 0x55;
int var2 __attribute__((at(0x10001000))) = 0x55;

变量被成功定位。

按照如上再增加一个变量var2,并分配到0x10001000地址上,大家猜一下会是什么结果?会如愿达成我们的目的吗?

抱歉,出错了,链接器报错(注意不是编译器是链接器报错)错误原因是地址冲突不匹配,而且是跟STACK的栈空间不匹配:

WHY???为什么var1就没事呢?

看出错提示来看是与栈空间冲突了,但实际还真不是这样的,这个错误的真正原因小编我其实到现在也没有真正找到,怀疑是在栈空间后面的地址空间不能分配其他节,因为var1成功了而var2却失败了,var2地址是在栈空间后面,我怀疑是栈空间之后不能正常分配导致的,但没有在官方文档里面找到确切的理论支持。

我尝试了下把STACK空间挪到后面,就OK了,分散加载如下(还需要配套修改启动代码,大家有兴趣可以自己试一试,启动代码修改的地方较多,我就不列出来了,有兴趣的根据我们之前文章的内容自己去尝试修改):

LR_IROM1 0x00000000 0x00008000  {    
  ER_IROM1 0x00000000 0x00008000  {  
   *.o (RESET, +First)
   *(InRoot$$Sections) 
   .ANY (+RO)
  }
  RW_IRAM1 0x10000000 0x00002000  { 
   .ANY (+RW +ZI)
  }
  ARM_LIB_STACK 0x10002000 EMPTY -0x200{}
}

如上,我把STACK空间挪到了片内SRAM的尾部,再根据需要修改启动代码,重新编译,就可以成功,DEBUG一下也可以看到var1和var2都被正确的分散加载以及正确的赋值。

限制:

• __at 节地址范围不能重叠,除非将重叠节放在不同的重叠区中

• 不允许在与位置无关的运行域中使用 __at 节

• 不能在 System V 和 BPABI 可执行文件和 BPABI DLL 中使用 __at 节

• __at 节地址必须是其对齐边界的倍数

• __at 节忽略所有 +FIRST 或 +LAST 排序约束。

使用__attribute__((section("name")))来放置一个被命名的节

还是直接上代码:

int var __attribute__((section("mySection"))) = 0x55;

这里我们构建了一个名叫“mySection”的节(其实就是变量var的别称),然后我们修改对应的分散加载文件:

LR_IROM1 0x00000000 0x00008000  {    
  ER_IROM1 0x00000000 0x00008000  {  
   *.o (RESET, +First)
   *(InRoot$$Sections) 
   .ANY (+RO)
  }
  ER_mySection 0x10000000
  {
	main.o(mySection)
  }
  RW_IRAM1 0x1000000 0x00002000  {  
   .ANY (+RW +ZI)
  }
  ARM_LIB_STACK 0x10002000 EMPTY -0x200{}
}

编译&链接我们看一下结果(对应的.map文件):

可以看到运行域地址被放到0x10000000地址上了,加载地址在0x680上。这样我们就把var变量放到了我们指定的地址上了。

使用__attribute__((section("name")))来放置一个函数可以吗?

可以,而且用法一样,如下:

__attribute__((section("mySection"))) void GPIOInit (void) 
{
    xxxx

    xxxx
}

然后按照之前一样的方法修改分散加载描述文件,可以把这个函数加载到指定地址上:

结果如下(对应的.map文件):

可以直观的看到“mySection”这个节被加载到了0x10000000的运行域了,而且与上一个变量加载例子不同的是,类型变成了RO属性“Code”(上一个例子是RW属性的DATA),因为我们这次加载的是一个函数,所以类型当然是Code了。

过程执行完毕后,Memory内部空间分布示意图如下:

<think>嗯,我现在需要介绍一下ARM Cortex-M系列微控制器的处理架构、相关知识以及开发流程。首先,我得回忆一下自己对这方面的了解。ARM Cortex-M系列是针对微控制器设计的,主要用于嵌入式系统,比如智能家居、物联网设备、汽车电子等。它们的处理架构应该和一般的处理器有所不同,可能更注重低功耗和实时性能。 首先,处理架构方面,我记得Cortex-M系列有多个子系列,比如M0、M3、M4、M7等,每个子系列有不同的特性和性能。例如,M0和M0+是入门级的,功耗很低,适合成本敏感的应用;而M4和M7可能有DSP指令和浮点单元,适合需要数字信号处理的应用。架构上,它们应该都是基于ARMv6-M或ARMv7-M的指令集架构,采用Thumb-2指令集,这样可以在代码密度和性能之间取得平衡。 然后,内存架构方面,Cortex-M可能使用哈佛架构,有分离的指令和数据总线,提高执行效率。中断处理方面,它们应该有一个嵌套向量中断控制器(NVIC),用来管理中断优先级和快速响应。此外,可能还有低功耗模式,比如睡眠模式、停止模式等,帮助节省能源。 关于开发流程,通常包括硬件选型、开发环境搭建、编写代码、编译调试、烧录程序、测试验证等步骤。开发工具方面,可能需要用到Keil MDK、IAR Embedded Workbench、或者开源的GCC工具链。调试可能会用JTAG或SWD接口,配合调试器如J-Link、ST-Link等。此外,实时操作系统(RTOS)如FreeRTOS也可能被用到,特别是在复杂的多任务应用中。 不过,我可能有些地方记不太清楚,比如具体每个Cortex-M型号的特性差异,或者NVIC的具体工作机制。需要确认一下这些细节是否正确。另外,开发流程中的每一步可能涉及更多具体内容,比如如何配置时钟、外设驱动开发、使用CMSIS库等,这些可能需要详细说明。 还有,是否应该提到Cortex-M的生态系统,比如丰富的第三方库和支持,或者与ARM其他产品的比较?可能用户更关注实际开发中的关键点,比如如何选择适合的Cortex-M芯片,开发中常见的挑战和解决方法等。 总之,我需要整理出一个结构清晰的回答,分点介绍处理架构的关键部分,然后逐步说明开发流程,确保涵盖主要方面,同时避免错误的信息。可能需要参考一些资料来验证自己的记忆,确保正确性,比如确认指令集架构版本、NVIC的功能、各子系列的特性差异等。</think>### ARM Cortex-M系列微控制器处理架构及认识 #### 一、核心架构特点 1. **指令集架构** - 基于**ARMv6-M**(Cortex-M0/M0+/M1)或**ARMv7-M**(Cortex-M3/M4/M7)设计,采用**Thumb-2指令集**,兼顾代码密度与性能。 - **单周期乘法/硬件除法**(部分型号支持),提升运算效率。 2. **内存模型** - **哈佛架构**(指令与数据总线分离),支持并行访问。 - 统一寻址空间(4GB),外设、内存、寄存器通过地址映射访问。 3. **中断处理(NVIC)** - 集成**嵌套向量中断控制器(NVIC)**,支持低延迟中断响应。 - 可配置优先级,支持尾链优化(Tail-chaining)减少中断切换时间。 4. **低功耗设计** - 多级电源管理模式(如睡眠模式、深度睡眠模式),通过WFI/WFE指令触发。 - 部分型号支持动态电压频率调节(DVFS)。 5. **型号差异** - **M0/M0+**:入门级,面积小、功耗低,无硬件除法。 - **M3**:增强中断、支持更复杂应用。 - **M4/M7**:集成DSP指令(如SIMD)、浮点单元(FPU),适合信号处理。 --- #### 二、开发流程详解 **1. 硬件选型** - **性能需求**:根据主频、内存(Flash/RAM)、外设(ADC/PWM/USB)选择型号。 - 示例:电机控制需M4/M7(带FPU),传感器采集可选M0。 - **功耗评估**:参考芯片手册的休眠电流与运行功耗。 - **生态支持**:优先选择主流厂商(如ST、NXP、Microchip)的成熟型号。 **2. 开发环境搭建** - **工具链选择**: - 商业IDE:Keil MDK、IAR Embedded Workbench(集成调试器)。 - 开源工具:GCC + VS Code/Eclipse + OpenOCD(调试)。 - **必备组件**: - **CMSIS(Cortex Microcontroller Software Interface Standard)**:提供标准外设驱动、DSP库、RTOS接口。 - 芯片厂商SDK(如STM32CubeMX生成初始化代码)。 **3. 代码开发** - **启动文件配置**: - 定义堆栈、中断向量表(通过`.s`汇编文件或厂商模板)。 - **外设驱动开发**: - 使用寄存器操作或厂商HAL库(如STM32 HAL)。 - 示例:配置GPIO输出: ```c GPIO_InitTypeDef gpio; gpio.Pin = GPIO_PIN_5; gpio.Mode = GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(GPIOA, &gpio); ``` - **中断服务例程(ISR)**: - 通过`__attribute__((interrupt))`或厂商宏定义ISR函数。 **4. 调试与烧录** - **调试接口**: - **SWD(2线)**或**JTAG**,搭配ST-Link/J-Link调试器。 - **常用调试手段**: - 断点、变量监视、实时内存查看。 - 逻辑分析仪(如Saleae)抓取外设时序。 **5. 测试与优化** - **功能验证**:单元测试(如Ceedling框架)、硬件回环测试。 - **性能优化**: - 使用CMSIS-DSP库加速算法(如FFT、滤波)。 - 启用编译器优化(-O2/-O3),避免冗余外设时钟使能。 - **功耗优化**: - 合理配置休眠模式,关闭未使用外设时钟。 --- #### 三、关键认识 - **实时性保障**:Cortex-M设计为硬实时系统,中断响应延迟低至**12周期(M3)**。 - **生态优势**:ARM与芯片厂商、RTOS(FreeRTOS/Zephyr)深度合作,降低开发门槛。 - **安全扩展**:部分型号支持TrustZone(如Cortex-M33),实现硬件级安全隔离。 --- ### 总结 Cortex-M系列通过**高能效比**、**丰富生态**及**模块化设计**,成为嵌入式领域的核心选择。开发时需结合具体需求选型,并善用CMSIS与厂商工具提升效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值