【ARMv8M Cortex-M33 系列 2.4 -- JFlash 烧写之链接脚本介绍】


请阅读【嵌入式开发学习必备专栏 】


JFlash 烧写之链接脚本介绍

在 RT-Thread 实时操作系统中,链接脚本(Linker Script)定义了如何将代码和数据映射到微控制器的内存中。链接脚本通常以 .ld 为扩展名。对于特定的微控制器,如 Renesas R7FA4M2AC3C,链接脚本中的 MEMORY 部分将详细说明内存的配置,包括不同区域的起始地址和大小。

以下是 MEMORY 部分的一个示例结构,它可能包含在 Renesas RX 系列微控制器的 RT-Thread 链接脚本中:

MEMORY 
{ 
	/* 定义内存区域 */ 
	ROM (rx) : ORIGIN = 0x00000000, LENGTH = 512K  /* 代码段,Flash内存 */ 
	RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K  /* 数据段,RAM内存 */ 
} 

在这个 MEMORY 部分:

  • ROM 是代码存储区域的标签,rx 表示这部分内存是可读(read)和可执行(execute)的。ORIGIN 是区域的起始地址,LENGTH 是区域的大小。在这个示例中,ROM 被配置为从 0x00000000 开始的 512KB 大小的空间,这通常对应于 Flash 存储器。

  • RAM 是数据存储区域的标签,rwx 表示这部分内存是可读(read)、可写(write)和可执行(execute)的。在这个示例中,RAM 被配置为从 0x20000000 开始的 64KB 大小的空间,这通常对应于内部 SRAM。

请注意,以上地址和大小仅为示例。

在实际使用时,链接脚本可能包含更多的内存区域和复杂的配置,如堆(heap)和栈(stack)的定义、特殊功能区域(如存储配置参数的区域)等。开发者可能需要根据应用的需求和微控制器的内存资源来调整链接脚本。

如瑞萨的 4M2AC,有两块 RAM 空间,则需要在链接脚本中为每个 RAM 区域配置一个内存段。对于瑞萨的 RX 系列微控制器,通常会有内部 RAM 和可能的外部 RAM。 以下是一个可能的链接脚本配置,它定义了两个 RAM 区域:

MEMORY { 
/* 定义第一块内存区域,例如内部 RAM */ 
RAM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 128K /* 例如,内部 RAM 的起始地址和大小 */ 

/* 定义第二块内存区域,例如外部 RAM */ 
RAM2 (rwx) : ORIGIN = 0x20020000, LENGTH = 128K /* 例如,外部 RAM 的起始地址和大小 */ 
} 

在这个 MEMORY 部分:

  • RAM1RAM2 是用来标识不同 RAM 区域的标签,rwx 表示这些内存区域是可读(read)、可写(write)和可执行(execute)的。
  • ORIGIN 是每个 RAM 区域的起始地址,LENGTH 是每个区域的大小。

通常情况下,内部 RAM 的地址较低,如果有外部 RAM,其地址将更高。所提供的起始地址 0x200000000x20020000,以及长度 128K,仅为示例。具体值需要根据你的 SoC 的实际内存映射来确定,这些信息可以在瑞萨 4M2AC 的数据手册或技术参考手册中找到。

在定义完 MEMORY 区域后,你需要在链接脚本中的任何相关部分(如堆区、栈区定义)指定应该使用哪一块 RAM。例如:

/* 定义堆和栈的大小 */ 
_estack = ORIGIN(RAM1) + LENGTH(RAM1); /* 假设栈顶位于 RAM1 的顶部 */ 
_heap_start = ORIGIN(RAM2); /* 假设堆起始位于 RAM2 的底部 */ 
_heap_end = ORIGIN(RAM2) + LENGTH(RAM2); /* 假设堆结束位于 RAM2 的顶部 */ 

确保在 RT-Thread 的系统初始化代码中正确初始化这两个 RAM 区域,使得操作系统可以管理和分配这两块内存。在启动 RT-Thread 之前,你可能需要调用微控制器的初始化函数来配置外部 RAM 的控制器(如果使用外部 RAM)。

当使用 ARM GCC 编译器和链接器编译 RT-Thread 项目时,链接脚本(通常是 .ld 文件)用于指导链接器如何将代码、数据、堆栈和其他段映射到目标微控制器的内存中。链接脚本定义了内存布局,包括各个内存段(如 .text.data.bss 等)的位置和大小。

在 RT-Thread 的链接脚本中,通常有以下几个关键部分:

  1. MEMORY 区段: 定义微控制器的内存布局,包括起始地址和长度。
  2. SECTIONS 区段: 定义如何将各个段映射到 MEMORY 区段中定义的内存区域。
  3. 符号定义: 如 _estack(栈顶地址),_sdata(已初始化数据段起始地址)等,这些符号会在链接脚本和 RT-Thread 的启动代码(通常是汇编写的启动文件)之间共享。

MEMORY 区段示例

MEMORY { 
	ROM (rx) : ORIGIN = 0x08000000, LENGTH = 512K 
	RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 96K 
} 

在上述示例中:

  • ROM 是微控制器 Flash 的标识符,rx 表示可读和可执行。
  • ROM 的起始地址是 0x08000000,长度是 512K
  • RAM 是微控制器 RAM 的标识符,rwx 表示可读、可写和可执行。
  • RAM 的起始地址是 0x20000000,长度是 96K

SECTIONS 区段示例

SECTIONS { 
	.text : 
	{ 
		*(.vectors) 
		*(.text) 
		*(.rodata) 
	} > ROM 

	.data : AT(ADDR(.text) + SIZEOF(.text)) 
	{ 
		_sdata = .; 
		*(.data) 
		_edata = .; 
	} > RAM 

	.bss : 
	{ 
		_sbss = .; 
		*(.bss) *(COMMON) 
		_ebss = .; 
	} > RAM 
} 

在上述示例中:

  • .text 段包括向量表、代码段和只读数据,被放置在 ROM 中。
  • .data 段包括已初始化的数据,被放置在 RAM 中,并且是从 ROM.text 段之后的地址开始(使用 AT() 关键字)。
  • .bss 段包括未初始化的数据,被放置在 RAM 中。

符号定义

链接脚本中还定义了一些符号,这些符号在启动代码中被引用来初始化全局数据。
例如:

_sdata = .; /* 定义全局数据的起始地址 */ 
_edata = .; /* 定义全局数据的结束地址 */ 
_sbss = .; /* 定义未初始化数据的起始地址 */ 
_ebss = .; /* 定义未初始化数据的结束地址 */ 

这些符号的值在启动代码中被用来执行数据复制(.data 从 Flash 到 RAM)和将 .bss 区域清零等操作。

启动代码

当 RT-Thread 启动时,启动代码(通常是汇编写的 startup.sstartup.S 文件)会使用这些链接脚本中定义的符号来准备运行环境。它会:

  1. .data 段从 Flash 复制到 RAM。
  2. .bss 段清零。
  3. 设置栈指针(使用 _estack)。
  4. 跳转到 RT-Thread 的 main 函数或初始化函数。

实际使用

在实际编译过程中,链接脚本将被指定给链接器,通常在 Makefile 或构建系统配置文件中:

LDFLAGS += -T path/to/linker_script.ld 

链接器会根据这个脚本来生成可执行文件,确保代码和数据正确地定位在微控制器的内存中。因此,正确配置链接脚本对于生成正确的固件至关重要。

在 RT-Thread 的链接脚本中,.data 段是已初始化数据的一部分,它需要从 Flash(非易失性存储)复制到 RAM(易失性存储)中,因为当程序运行时,它需要对这些数据进行读写操作。该操作是在启动代码(通常是汇编语言编写的启动文件)中完成的。

AT 关键字在链接脚本中用于指定虽然段在执行期间在 RAM 中,但在程序文件中这部分数据的位置应该是在 Flash 中的某个位置。ADDRSIZEOF 是链接器的内建函数,用来分别获得段的起始地址和大小。

下面的链接脚本代码片段:

.data : AT (ADDR(.text) + SIZEOF(.text)) 
{ 
	/* 数据段定义 */ 
	_sdata = .; /* 已初始化数据段的起始地址 */ 
	*(.data) 
	_edata = .; /* 已初始化数据段的结束地址 */ 
} > RAM 

解释如下:

  • .data:这指定了 .data 段在链接脚本中的部分。
  • AT (ADDR(.text) + SIZEOF(.text)):该表达式告诉链接器将 .data 段的内容放置在程序文件中的位置。它是紧接在 .text 段之后的位置。ADDR(.text) 获取 .text 段的起始地址,SIZEOF(.text) 获取 .text 段的大小。这意味着 .data 段在物理文件(例如:你的 .bin.elf 文件)中紧随 .text 段之后。
  • _sdata = .;:该行定义了一个全局符号 _sdata,它标记了 .data 段在 RAM 中的起始地址。
  • *(.data):这是一个通配符,它指示链接器包括所有 .data 段的内容。
  • _edata = .;:该行定义了另一个全局符号 _edata,它标记了 .data 段在 RAM 中的结束地址。
  • > RAM:这说明 .data 段应该被加载到 RAM,即上文 MEMORY 区段定义中的 RAM

当微控制器启动时,启动代码会利用 _sdata_edata 符号来确定需要将多少数据从 Flash 复制到 RAM 中,以及复制的目标地址。这个过程保证了在程序开始执行前,所有初始化的全局变量和静态变量都被设置为它们在编译时被赋予的值。

Reset_Handler:
  ldr   sp, =_estack      /* set stack pointer */

/* copy data section from flash to SRAM */
  movs  r1, #0
  b  data_section_copy

CopyDataInit:
  ldr  r3, =_sidata
  ldr  r3, [r3, r1]
  str  r3, [r0, r1]
  adds  r1, r1, #4

data_section_copy:
  ldr  r0, =_sdata
  ldr  r3, =_edata
  adds  r2, r0, r1
  cmp  r2, r3
  bcc  CopyDataInit
  ldr  r2, =_sbss
  b  bss_section_init

/* Zero fill the bss segment. */
bss_clear:
  movs  r3, #0
  str  r3, [r2], #4

bss_section_init:
  ldr  r3, = _ebss
  cmp  r2, r3
  bcc  bss_clear

ARM BCC 指令介绍

在 ARM 架构的指令集中,BCC 是一个条件跳转指令,其中 CC 代表条件码(Condition Code)。这个指令会根据处理器状态寄存器(CPSR)中的标志位来决定是否跳转到指定的目标地址。如果条件满足,处理器将跳转到标签指定的地址执行指令;如果条件不满足,则继续执行下一条指令。

ARM 架构提供了一系列的条件码,每个条件码对应不同的状态寄存器标志位的组合。以下是一些常见的条件码及其代表的意义:

    • EQ:相等(Zero flag is set, Z=1)
    • NE:不相等(Zero flag is clear, Z=0)
    • CS/HS:进位置位/无符号数大于等于(Carry flag is set, C=1)
    • CC/LO:进位清除/无符号数小于(Carry flag is clear, C=0)
    • MI:负数(Negative flag is set, N=1)
    • PL:正数或零(Negative flag is clear, N=0)
    • VS:溢出(Overflow flag is set, V=1)
    • VC:无溢出(Overflow flag is clear, V=0)
    • HI:无符号数大于(C=1 and Z=0)
    • LS:无符号数小于或等于(C=0 or Z=1)
    • GE:有符号数大于等于(N=V)
    • LT:有符号数小于(N!=V)
    • GT:有符号数大于(Z=0 and N=V)
    • LE:有符号数小于或等于(Z=1 or N!=V)

BCC 指令使用举例

考虑下面的 ARM 汇编代码片段,我们使用 BCC 指令作为例子:

CMP R1, R2 ; 比较 R1 和 R2 的值 
BCC target_label ; 如果不产生进位(即 R1 < R2),则跳转到标签 'target_label' 
; 否则继续执行下面的指令 

... 
target_label: 
	; 如果满足条件跳转,则会执行到这里的代码 
	... 

在这个例子中,CMP 指令用来比较寄存器 R1R2 的值,并设置条件码。如果 R1 小于 R2,则不会产生进位(C=0),BCC 条件成立,处理器会跳转到 target_label。如果 R1 大于等于 R2,则会产生进位(C=1),BCC 条件不成立,处理器会继续执行下一条指令。

记住,ARM 指令的条件跳转依赖于 CPSR 中的标志位,而这些标志位可能会被之前执行的指令如 CMPADDSUB 等设置。因此,你总是需要仔细考虑代码的逻辑顺序和条件标志的状态。

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

主公CodingCos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值