stm32官方示例代码(STM32链接脚本详解)

程序的编译分为四个步骤:预处理、汇编、编译、链接。在开发STM32时,我们只要在IDE中点击编译就能一次性完成这4个步骤,实际上IDE也是要经过这些步骤的,只不过IDE为我们屏蔽了很多细节。

首先我们需要了解一个image文件的构成。image即编译的产物,我们编译STM32生成的bin文件此处称之为image。一个image文件由RO段和RW段组成,RO段包含只读的代码段和常量,RW段包含可读可写的全局变量和静态变量。因为程序刚运行时,RW段还在FLASH中,需要一段程序将这些变量复制到RAM中,STM32的启动文件的__main函数帮我们完成了这一动作。RW段中初始值为0的段为ZI段,image文件无需包含ZI段,因为ZI段包含的是全局或静态初始值为0的变量,只要在程序运行后,将对应的RAM区域清零即可。

这里又涉及到另一个概念:加载地址和运行地址。加载地址是指读取程序的地址,运行地址是指程序运行的入口地址。STM32因为有XIP(executed in place)技术,加载地址和运行地址是一样的,都是0x08000000。简而言之,如果程序在FLASH中运行,加载地址和运行地址是相同的,例如STM32等单片机;如果程序存放在FLASH里,而运行是在RAM里,那么加载地址指向FLASH,运行地址指向RAM,例如跑Linux系统的一些芯片。上面的RW段,其加载地址指向FLASH,而运行地址指向RAM,因此需要拷贝。

如何指定各个C文件的编译产物(.o格式)在RO段的顺序?又如何确定程序的加载地址和运行地址呢?这都是靠一个脚本来完成的,即链接脚本。在Linux下,链接脚本为lds文件;在keil中,链接脚本为sct文件;在IAR中,链接脚本为icf文件。本文以KEIL下的sct文件为例,讲解链接脚本结构。

我们可以通过编写一个分散加载文件来指定 ARM 连接器在生成映像文件时如何分配 Code、RO-Data, RW-Data, ZI-Data 等数据的存放地址。称为分散加载文件实际上就是链接脚本,如果不修改KEIL的链接脚本,那么会使用默认的链接脚本,我们按照下图的操作方式来查看默认的链接脚本,方法为点击工程设置,找到Link选项,去掉“Use Memory Layout from Target Dialog”前面的勾选,然后点击Edit。

stm32官方示例代码(STM32链接脚本详解)(1)

不使用KEIL的默认链接脚本

查看到的默认链接脚本如***:本例中使用的MCU型号为STM32F103RC,FLASH容量为256KB,RAM大小为64KB。

; *************************************************************; *** Scatter-Loading Description File generated by uVision ***; ************************************************************* LR_IROM1 0x08000000 0x00040000 { ; 加载时域起始地址为0x08000000,大小为0x40000 ER_IROM1 0x08000000 0x00040000 { ; 第一个运行时域,运行地址为0x08000000,大小为0x40000 *.o (RESET, First) ; RESET段最先链接,RESET段在启动文件中有声明 *(InRoot$$Sections) ; 链接__main函数,该函数用于RW段数据的拷贝和ZI段数据的清零 .ANY ( RO) ; 剩余的code、RO数据随意链接 } RW_IRAM1 0x20000000 0x0000C000 { ; 第二个运行时域,运行地址为0x20000000,大小为0xC000 .ANY ( RW ZI) ; 存放所有的RW段数据和ZI段数据 }}

分散加载文件主要由一个加载时域和多个运行时域组成。

加载时域,顾名思义用于加载并存储数据,包括 Code、 RO-Data 和 RW-Data。

运行时域, 用于为运行时分配变量及代码映射空间, 包含 Code、 ZI-Data、 RW-Data。

stm32官方示例代码(STM32链接脚本详解)(2)

分散加载文件的组成

分散加载有3条规则需要特别注意:

1、第一个运行时域的基址必须与加载域基址相同。

2、第一个运行时域存放的代码不会进行额外拷贝。

3、一个加载时域,有且仅有一个不拷贝的运行时域,FIXED关键字修饰除外。

分散加载的用途有很多,例如:

1、我们可以通过修改分散加载文件将部分代码或整个代码放到RAM中运行以提高运行速度。

2、可以将一组函数放在特定地址上,作为Firmware供app程序调用。

3、将程序分成boot和app,实现升级功能。实现这个功能可以不修改分散加载文件,直接在keil里设置即可。

4、定义section来灵活地存放特定的数据。

关于分散加载的更多知识,可以参考周立功写的一篇文档《keil分散加载文件浅释》,我已经传到百度网盘:

链接:https:///s/1heC-pLmi_eeqS_SU19dmIA 提取码:iguq

有时候我们需要在程序运行时知道各个段的起始地址、结束地址、大小等信息,这些信息链接器已经帮我们导出了,下面给出了一个使用的例子,这个例子实际上完成了__main的部分功能,即把FLASH中的RW段数据拷贝到RAM的运行地址上,并将RAM中的ZI段数据清零。

void RW_And_ZI_Init (void){ extern unsigned char Image$$ER_IROM1$$Limit; // 获取RW段在FLASH中的加载地址 extern unsigned char Image$$RW_IRAM1$$Base; // 获取RW段在RAM中的运行地址 extern unsigned char Image$$RW_IRAM1$$RW$$Limit; // 获取RW段在RAM中的结束地址 extern unsigned char Image$$RW_IRAM1$$ZI$$Limit; // 获取ZI段在RAM中的结束地址 unsigned char * psrc, *pdst, *plimt; psrc = (unsigned char *)&Image$$ER_IROM1$$Limit; pdst = (unsigned char *)&Image$$RW_IRAM1$$Base; plimt = (unsigned char *)&Image$$RW_IRAM1$$RW$$Limit; while(pdst < plimt) // 将FLASH中的RW段拷贝到RAM的RW段运行地址上 { *pdst = *psrc ; } psrc = (unsigned char *)&Image$$RW_IRAM1$$RW$$Limit; plimt = (unsigned char *)&Image$$RW_IRAM1$$ZI$$Limit; while(psrc < plimt) // 将RAM中的ZI段清零 { *psrc = 0; }

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页