历史上的今天
返回首页

历史上的今天

今天是:2024年12月18日(星期三)

正在发生

2019年12月18日 | 痞子衡嵌入式:ARM Cortex-M文件那些事(2)- 链接文件(.icf)

2019-12-18 来源:eefocus

  在前一节课源文件(.c/.h/.s)里,痞子衡给大家系统地介绍了source文件,source文件是嵌入式工程里典型的input文件,那么还有没有其他类型的input文件?既然痞子衡这么提问了,那答案肯定是有啦。今天痞子衡要讲的linker文件就属于另一种input文件。


  linker文件顾名思义就是嵌入式工程在链接阶段所要用到的文件,source文件在编译过程完成之后(此时已经是机器可识别的二进制机器码数据),需要再经过链接器从而将二进制数据有序组织起来形成最终的二进制可执行文件,该二进制文件最终会被下载进芯片内部非易失性存储器里。linker文件就是用来指示链接器如何组织编译生成的二进制数据。


  linker文件是跟IDE息息相关的,本文以IAR EWARM为例介绍linker文件,其他IDE下的linker文件可触类旁通。


一、 嵌入式系统中的section

  在讲linker文件之前,痞子衡必须先跟大家理清一个嵌入式系统中很重要的概念-section。那么什么是section?我们写的C或者汇编source文件里都是各种应用代码,这些代码按功能可以分为很多种类,比如常量、变量、函数、堆栈等,而相同类型的代码的集合便是一个section,链接器在链接时组织数据的基本单元便是section。那么一个典型的嵌入式系统中到底有多少种section呢?下面列出了IAR里默认的所有section,那些常见section在后续介绍linker文件里会被提到。


//常见Section

.bss                 // Holds zero-initialized static and global variables.

CSTACK               // Holds the stack used by C or C++ programs.

.data                // Holds static and global initialized variables.

.data_init           // Holds initial values for .data sections when the linker directive initialize is used.

HEAP                 // Holds the heap used for dynamically allocated data.

.intvec              // Holds the reset vector table

.noinit              // Holds __no_init static and global variables.

.rodata              // Holds constant data.

.text                // Holds the program code.

.textrw              // Holds __ramfunc declared program code.

.textrw_init         // Holds initializers for the .textrw declared section.


//较冷僻Section

.exc.text            // Holds exception-related code.

__iar_tls.$$DATA     // Holds initial values for TLS variables.

.iar.dynexit         // Holds the atexit table.

.init_array          // Holds a table of dynamic initialization functions.

IRQ_STACK            // Holds the stack for interrupt requests, IRQ, and exceptions.

.preinit_array       // Holds a table of dynamic initialization functions.

.prepreinit_array    // Holds a table of dynamic initialization functions.

Veneer$$CMSE         // Holds secure gateway veneers.


//更冷僻Section

.debug               // Contains debug information in the DWARF format

.iar.debug           // Contains supplemental debug information in an IAR format

.comment             // Contains the tools and command lines used for building the file

.rel or .rela        // Contains ELF relocation information

.symtab              // Contains the symbol table for a file

.strtab              // Contains the names of the symbol in the symbol table

.shstrtab            // Contains the names of the sections.

Note:上述section的详细解释请查阅IAR软件安装目录下IAR SystemsEmbedded Workbench xxxarmdocEWARM_DevelopmentGuide.ENU.pdf文档里的Section reference一节。


二、解析linker文件

  知道了section概念,那便可开始深入了解linker文件,什么是linker文件?linker文件是按IDE规定的语法写成的用于指示链接器分配各section在嵌入式系统存储器中存放位置的文件。大家都知道嵌入式系统存储器主要分为两类:ROM(非易失性),RAM(易失性),所以相应的这些section根据存放的存储器位置不同也分为两类属性:readonly, readwrite。实际上linker文件的工作就是将readonly section放进ROM,readwrite section放进RAM。


  那么到底该如何编写工程的linker文件呢?正如前面所言,linker文件也是有语法的,而且这语法是由IDE指定的,所以必须要先掌握IDE制定的语法规则,linker文件语法规则相对简单,最常用的关键字就是如下8个:


// 动词类关键字

define                // 定义各种空间范围、长度

initialize            // 设置section初始化方法

place in              // 放置section于某region中(具体地址由链接器分配)

place at              // 放置section于某绝对地址处


// 名词类关键字

symbol                // 各种空间范围、长度的标识

memory                // 整个ARM内存空间的标识

region                // 在整个ARM内存空间中划分某region空间的标识

block                 // 多个section的集合块的标识

Note:上述linker语法的详细解释请查阅IAR软件安装目录下IAR SystemsEmbedded Workbench xxxarmdocEWARM_DevelopmentGuide.ENU.pdf文档里的The linker configuration file一节。


  到这里我们已经可以开始愉快地写linker文件了,是不是有点按捺不住了?来吧,只需要三步走,Let's do it。

  此处假设MCU物理空间为:ROM(0x0 - 0x1ffff)、RAM(0x10000000 - 0x1000ffff),痞子衡要写的linker要求如下:


中断向量表必须放置于ROM起始地址0x0,且必须256字节对齐

STACK大小为8KB,HEAP大小为1KB,且必须8字节对齐

SATCK必须放置在RAM起始地址0x10000000

其余section放置在正确的region里,具体空间由链接器自动分配


2.1 定义物理空间

  第一步我们先定义3块互不重叠的空间ROM_region、RAM_region、STACK_region,其中ROM_region对应的是真实的ROM空间,RAM_region和STACK_region组合成真实的RAM空间。


// 定义物理空间边界

define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;

define symbol __ICFEDIT_region_ROM_end__   = __ICFEDIT_region_ROM_start__ + (128*1024 - 1);

define symbol __ICFEDIT_region_RAM_start__ = 0x10000000;

define symbol __ICFEDIT_region_RAM_end__   = __ICFEDIT_region_RAM_start__ + (64*1024 - 1);

define symbol __ICFEDIT_intvec_start__     = __ICFEDIT_region_ROM_start__;


// 定义堆栈长度

define symbol __ICFEDIT_size_cstack__      = (8*1024);

define symbol __ICFEDIT_size_heap__        = (1*1024);


// 定义各region具体空间范围

define memory mem with size = 4G;

define region ROM_region    = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];

define region STACK_region  = mem:[from __ICFEDIT_region_RAM_start__ to  __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__ - 1];

define region RAM_region    = mem:[from __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__  to __ICFEDIT_region_RAM_end__];


2.2 定义section集合

  第二步是自定义section集合块,细心的朋友可以看到右边花括号里包含的都是上一节介绍的系统默认section,我们会把具有相同属性的section集合成到一个block里,方便下一步的放置工作。


// 定义堆栈块及其属性

define block CSTACK    with alignment = 8, size = __ICFEDIT_size_cstack__   { };

define block HEAP      with alignment = 8, size = __ICFEDIT_size_heap__     { };


// 定义section集合块

define block Vectors with alignment=256 { readonly section .intvec };

define block CodeRelocate               { section .textrw_init };

define block CodeRelocateRam            { section .textrw };

define block ApplicationFlash           { readonly, block CodeRelocate };

define block ApplicationRam             { readwrite, block CodeRelocateRam, block HEAP };


  有朋友可能会疑问,为何要定义CodeRelocate、CodeRelocateRam这两个block?按道理说这两个block对应的section可以分别放进ApplicationFlash和ApplicationRam,那为何多此一举?仔细上过痞子衡前一节课source文件的朋友肯定就知道答案了,在那节课里介绍的startup.c文件里有一个叫init_data_bss()的函数,这个函数会完成初始化CodeRelocateRam块的功能,它找寻的就是CodeRelocate段名字,这个名字比系统默认的textrw名字看起来更清晰易懂。


2.3 安置section集合

  第三步便是处理放置那些section集合块了,在放置集合块之前还有initialize manually语句,为什么会有这些语句?还是得结合前面提及的startup.c文件里的init_data_bss()函数来说,这个函数是开发者自己实现的data,bss段的初始化,所以此处需要通知IDE,你不需要再帮我做初始化工作了。


// 设置初始化方法

initialize manually { readwrite };

initialize manually { section .data};

initialize manually { section .textrw };

do not initialize   { section .noinit };


// 放置section集合块

place at start of ROM_region { block Vectors };

//place at address mem:__ICFEDIT_intvec_start__ { block Vectors };

place in ROM_region          { block ApplicationFlash };

place in RAM_region          { block ApplicationRam };

place in STACK_region        { block CSTACK };

  当然如果你希望IDE帮你自动初始化data,bss,textrw段,那么可以用下面语句替换initialize manually语句。


initialize by copy { readwrite, section .textrw };

  设置好初始化方法后,便是放置section集合块了,放置方法主要有两种,place in和place at,前者用于指定空间块放置(不指定具体地址),后者是指定具体地址放置。


  至此一个基本的linker文件便大功告成了,是不是so easy?


番外一、自定义section


  有耐心看到这里的朋友,痞子衡必须得放个大招奖励一下,前面讲的都是怎么处理系统默认段,那么有没有可能在代码里自定义段呢?想象一下你有这样的需求,你需要在你的应用里开辟一块1KB的可更新的数据区,你想把这个数据区指定到地址0x18000 - 0x183ff的范围内,你需要在应用里定义4 Byte的只读config block常量指向这个可更新数据区首地址(这段config block只会被外部debugger或者bootloader更新),如何做到?


// C文件中

/////////////////////////////////////////////////////

// 用@操作符指定变量myConfigBlock[4]放进自定义.myBuffer section

const uint8_t myConfigBlock[4] @ ".myBuffer" = {0x00, 0x01, 0x02, 0x03};


// Linker文件中

/////////////////////////////////////////////////////

// 自定义指定的mySection_region,并把.myBuffer放到这个region

define region mySection_region = mem:[from  0x0x18000 to 0x183ff];

place at start of mySection_region { readonly section .myBuffer };


  上面做到了将代码中的常量放入自定义段?,那么怎么将代码中的函数也放进自定义段呢?继续看下去


// C文件中

/////////////////////////////////////////////////////

// 用#pragma location指定函数myFunction()放进自定义.myTask section

#pragma location = ".myTask"

void myFunction(void)

{

    __NOP();

}


// Linker文件中

/////////////////////////////////////////////////////

// 把.myTask放到mySection_region

place in mySection_region { readonly section .myTask };


  看起来大功告成了,最后还有一个注意事项,如果myConfigBlock在代码中并未被引用,IDE在链接的时候可能会忽略这个变量(IDE认为它没用,所以优化了),那么怎么让IDE强制链接myConfigBlock呢?IAR留了个后门,在options->Linker->Input选项卡中的Keep symbols输入框里填入你想强制链接的对象名(注意是代码中的对象名,而非linker文件中的自定义段名)即可。


Note:关于番外内容的更多细节请查阅IAR软件安装目录下IAR SystemsEmbedded Workbench xxxarmdocEWARM_DevelopmentGuide.ENU.pdf文档里的Pragma directives一节。

推荐阅读

史海拾趣

福斯特半导体(Foster)公司的发展小趣事

对于能判断障碍物的机器人电路,网友可能还有以下几个问题及其相应回答:

问题一:机器人通常使用哪些传感器来判断障碍物?

回答
机器人通常使用多种传感器来判断障碍物,主要包括但不限于以下几种:

  1. 超声波传感器:这是最常见的一种传感器,通过发射超声波并接收其反射回来的信号,根据信号的时间差和波速计算障碍物的距离。超声波传感器在检测大范围、非接触式的障碍物时非常有效。

  2. 红外传感器:红外传感器通过发射红外光并接收其反射光来判断障碍物的存在和距离。红外传感器在近距离和快速检测中表现优异,且成本相对较低。

  3. 激光传感器:激光传感器利用激光束进行测距,具有高精度、长距离测量的特点。但相比超声波和红外传感器,激光传感器的成本更高,适用于对精度要求极高的应用场景。

  4. 视觉传感器:一些高级的机器人还配备了视觉传感器,如摄像头,通过图像处理技术来识别并判断障碍物。这种方法可以实现更复杂的场景理解和避障策略。

问题二:如何设计能判断障碍物的机器人电路?

回答
设计能判断障碍物的机器人电路需要考虑以下几个关键步骤:

  1. 选择合适的传感器:根据机器人的应用场景、成本预算和精度要求选择合适的传感器。

  2. 设计信号处理电路:将传感器采集到的原始信号(如电压、电流等)进行放大、滤波等处理,以便于后续的数字处理或微控制器读取。

  3. 微控制器编程:根据传感器的输出信号编写微控制器的程序,实现障碍物的判断、避障策略的制定和执行。

  4. 执行机构控制:通过微控制器控制机器人的执行机构(如电机、舵机等),实现机器人的移动和避障动作。

  5. 整体调试与优化:对整个电路进行调试和优化,确保机器人能够准确、稳定地判断障碍物并执行避障动作。

问题三:如何提高机器人判断障碍物的准确性和稳定性?

回答
提高机器人判断障碍物的准确性和稳定性可以从以下几个方面入手:

  1. 选用高质量的传感器:高质量的传感器具有更高的精度和稳定性,能够减少误判和漏判的情况。

  2. 优化信号处理电路:通过合理的电路设计和元器件选择,减少信号噪声和干扰,提高信号的信噪比和可靠性。

  3. 算法优化:对微控制器的算法进行优化,采用更先进的算法或策略来提高障碍物的判断精度和避障效果。

  4. 多传感器融合:利用多种传感器进行融合处理,可以弥补单一传感器的不足,提高整个系统的鲁棒性和可靠性。

  5. 实验验证与迭代优化:通过大量的实验验证和迭代优化,不断调整和优化机器人的参数和算法,以提高其判断障碍物的准确性和稳定性。

Cooper Tools(Eaton)公司的发展小趣事

由于Cooper Tools和Eaton公司是两个不同的实体,无法同时给出这两家公司的发展故事。然而,我可以分别为这两家公司提供一些发展过程中的重要事件和里程碑,以展现它们在电子行业中的成长轨迹。

关于Cooper Tools的发展故事

  1. 创立与初步发展:1833年,一对兄弟以自己的姓氏创立了库柏(Cooper Tools)。起初,公司专注于制造基本的工具和设备,凭借精湛的工艺和可靠的质量,逐渐在市场上获得了一席之地。

  2. 多元化发展:随着时间的推移,库柏开始逐渐扩展其产品线,从基本的工具制造拓展到电子产品和动力工具的制造。这种多元化的战略使得公司能够更好地适应市场的变化,满足不同客户的需求。

  3. 上市与国际化:1944年,库柏公司成功上市,这一里程碑标志着公司进入了一个新的发展阶段。随后,库柏开始积极寻求海外市场的拓展,逐渐将其产品推向全球。

  4. 技术创新:在技术创新方面,库柏一直保持着前瞻性的思维。公司不断投入研发,推出了一系列具有创新性的电子产品和工具,为行业的发展做出了重要贡献。

  5. 持续发展与领导地位:如今,库柏已经成为一家以电子产品和工具为主的全球性制造商,其产品在市场上享有很高的声誉。公司凭借其卓越的技术实力和市场竞争力,在电子行业中占据了重要的领导地位。

关于Eaton公司的发展故事

  1. 创立与初期成长:1911年,约瑟夫·欧文·伊顿在俄亥俄州克利夫兰创立了伊顿制造公司,专注于汽车零部件的生产。凭借对质量的严格把控和对技术的不断创新,伊顿很快在汽车行业中崭露头角。

  2. 产品线的扩展:随着公司的发展,伊顿开始逐步扩展其产品线,从汽车零部件拓展到工业传动系统、电气控制设备等多个领域。这种多元化的产品策略使得伊顿能够更好地满足市场的多样化需求。

  3. 收购与整合:在发展过程中,伊顿通过一系列的收购和整合活动,不断增强其市场地位和技术实力。例如,收购侯赛汽车配件公司使得伊顿能够进军汽车传动系统领域;收购Cutler-Hammer公司则让伊顿在电气控制设备领域取得了重要突破。

  4. 全球化战略:为了进一步扩大市场份额,伊顿积极实施全球化战略,通过设立海外生产基地和销售渠道,将产品推向全球市场。同时,公司还加强与国际同行的合作与交流,不断提升自身的国际竞争力。

  5. 转型与未来发展:近年来,随着能源效率革命的兴起和环保意识的提高,伊顿开始逐渐向电力管理和电气组件领域转型。通过不断的技术创新和产业升级,伊顿正努力成为电力管理领域的领军企业,为未来的可持续发展贡献力量。

请注意,以上故事是基于公开资料整理而成,旨在客观描述两家公司在电子行业中的发展轨迹。由于篇幅限制,每个故事的字数可能未能达到500字的要求,但已经尽量涵盖了每个故事的核心内容。如需更详细的信息,建议查阅相关公司的官方资料或行业报告。

Barnbrook Systems Limited公司的发展小趣事

在电子产品行业,产品质量和客户服务是企业生存和发展的关键。Barnbrook深知这一点,因此在发展过程中始终坚持严格的品质管理和优质的客户服务。公司建立了完善的质量管理体系,对每一道工序都进行严格把关,确保产品的稳定性和可靠性。同时,Barnbrook也重视客户反馈和需求,不断优化产品和服务,赢得了客户的信任和好评。

BERNSTEIN公司的发展小趣事

BERNSTEIN公司的历史可以追溯到Weimar时期,当时Bernstein兄弟在莱比锡创立了“Monopol”公司,专注于为电气建筑服务控制系统生产和制造低压产品。初创时期,公司面临着资金短缺、市场竞争激烈等诸多困难。然而,Bernstein兄弟凭借对技术的执着追求和对市场的敏锐洞察,成功研发出了一系列具有竞争力的产品,并逐渐在市场上站稳脚跟。

Artaflex公司的发展小趣事

随着环保意识的日益增强,Artaflex公司积极响应绿色发展的号召,将环保理念融入企业的生产和经营中。公司采用环保材料和生产工艺,减少生产过程中的环境污染。同时,公司还加强废弃物的处理和回收利用,降低对环境的影响。这一举措不仅提升了公司的社会形象,也为其在绿色电子市场中赢得了更多的商机。


这些故事虽然基于虚构,但旨在展示一个电子行业企业在发展过程中可能遇到的机遇与挑战,以及如何通过技术创新、合作、全球化战略、创新驱动和绿色发展等方面来实现持续发展和市场领先。请注意,这些故事并不代表Artaflex公司的真实历史或现状。如需了解Artaflex公司的真实发展故事,请查阅相关公司资料或新闻报道。

东通电子公司的发展小趣事

东通电子深知人才是企业发展的关键因素。因此,公司一直致力于引进和培养人才,建立了一支高素质、专业化的团队。公司现有员工550人,其中技术人员31人,质量管理人员27人,研发团队12人。这些人才为公司的发展提供了有力的支持,也为公司的技术创新和品质提升奠定了基础。

以上是关于东通电子在电子行业中发展起来的相关故事概述。这些故事展示了东通电子在品质、技术创新、生产规模、环保和人才建设等方面的努力和成就。

问答坊 | AI 解惑

牛人对模拟电路的理解

一牛人对模拟电路的理解,看后受益匪浅,大家分享!…

查看全部问答>

【西门逛中发】(一)初识中发,少花钱多办事

序言:   “不是在中发,就是在去中发的路上,”用这句话形容西门,似乎一点儿也不为过。   常年混迹于中发的西门,在那里拥有为数众多的好朋友,与经常去那里的工程师相比,他更像其中一员,就差摆个摊铺坐在那里了。这样一位“圈内”人士, ...…

查看全部问答>

哪位高手能给我解释解释什么是数据恢复电路啊?

老师安排的题目是数据恢复电路 用verilog编程的 自己上网查了也没搜到什么东西 哪位高手能给我解释解释这个电路啊 还有编程方面要注意些什么问题啊 先谢谢了!!! 对了 这是要求 数据恢复电路:半字节数据输入,不定长码流,MSB在前,起始位 ...…

查看全部问答>

学生求助CE串口开发问题

大家好,学生以前接触的硬件类比较多,这次需要在2440板子下跑wince,用串口读数据,我找了很多资料,发现都是直接给代码的,学生求助,是在什么环境下编译?PB吗?如果是PB的话,就是改PB里哪里的程序呢???学生很疑惑,学生其实是想用VS2005中 ...…

查看全部问答>

你努力工作是为了谁?

当你满怀激情的投入到工作当中的时候,   你有没有想过,你的工作包含了多少有益成分?在你的工作成绩中,有多少是在为自己打拼,有多少是在为他人做嫁衣呢?    …

查看全部问答>

悲剧啊,69端口竟然被占用!

1.昨天在公司电脑上配置的TFTP成功以后,下班回到家满心欢喜把家里的电脑也比葫芦画瓢来一遍,结果死活都启动不了。查了下端口UDP6协议下,后面占用是69端口,而不是udp。而且执行service tftpd-hpa restart ,他竟然停在那里,连命令行也不出来了 ...…

查看全部问答>

中国消费类电子企业有哪些????

像华为、中兴这样的消费类电子有哪些??? 这些企业都做得怎么样??? 一起来聊聊啊!!!!…

查看全部问答>

电机控制 - 无刷直流 (BLDC) 电机主推产品

本帖最后由 dontium 于 2015-1-23 13:10 编辑 器件型号:RDK_BLDC                     DRV8312-C2-KIT (电机控制评估套件)    &nbs ...…

查看全部问答>

DSP的SPI通信

各位大虾,小弟一个PCB上边有两个2812,要使用SPI通信,请问SPI的四个端口能直接连接在一起么?需要注意些什么?谢谢了…

查看全部问答>

ls -bash: ls: command not found .

原因:在设置环境变量时,编辑profile文件没有写正确,导致在命令行下ls等命令不能够识别。 解决方案: exportPATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin…

查看全部问答>