【STM32】sct 分散加载文件的格式与应用

简介

当工程按默认配置构建时,MDK 会根据我们选择的芯片型号,获知芯片的内部FLASH 及内部 SRAM 存储器概况,自动生成一个以工程名命名的后缀为*.sct 的分散加载文件(Linker Control File,scatter loading),链接器根据该文件的配置分配各个节区地址,生成分散加载代码,因此我们通过修改该文件可以定制具体节区的存储位置

一般情况下,不需要编写分散加载文件,由编译器自动生成,将某些数据放到默认位置。但是在某些场合,希望将某些数据放在指定的位置。

例如可以设置源文件中定义的所有变量自动按地址分配到外部 SRAM,这样就不需要再使用关键字“attribute”按具体地址来指定了;

复杂内存映射:如果必须将代码和数据放在多个不同的内存区域中,则需要使用详细指令指定将哪些数据放在哪个内存空间中。

不同类型的内存:许多系统都包含多种不同的物理内存设备,如闪存、 ROM、 SDRAM 和快速 SRAM。分散加载描述可以将代码和数据与最适合的内存类型相匹配。例如,可以将中断代码放在快速 SRAM 中以缩短中断等待时间,而将不经常使用的配置信息放在较慢的闪存中。

位于固定位置的函数:可以将函数放在内存中的固定位置,即使已修改并重新编译周围的应用程序。

使用符号标识堆和堆栈:链接应用程序时,可以为堆和堆栈位置定义一些符号。

利用它还可以控制代码的加载区与执行区的位置,例如可以把程序代码存储到单位容量价格便宜的 NAND-FLASH 中,但在NAND-FLASH 中的代码是不能像内部 FLASH 的代码那样直接提供给内核运行的,这时可通过修改分散加载文件,把代码加载区设定为 NAND-FLASH 的程序位置,而程序的执行区设定为 SRAM 中的位置,这样链接器就会生成一个配套的分散加载代码,该代码会把NAND-FLASH 中的代码加载到 SRAM 中,内核再从 SRAM 中运行主体代码,大部分运行Linux 系统的代码都是这样加载的

sct文件的格式

【STM32】sct 分散加载文件的格式与应用_第1张图片
在默认的 sct 文件配置中仅分配了 Code、RO-data、RW-data 及 ZI-data 这些大区域的地址,链接时各个节区(函数、变量等)直接根据属性排列到具体的地址空间。

sct 文件中主要包含描述加载域及执行域的部分,一个文件中可包含有多个加载域,而一个加载域可由多个部分的执行域组成。同等级的域之间使用花括号“{}”分隔开,最外层的是加载域第二层“{}”内的是执行域

【STM32】sct 分散加载文件的格式与应用_第2张图片
【STM32】sct 分散加载文件的格式与应用_第3张图片

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
 
LR_IROM1 0x08000000 0x00020000  {    
	//定义一个加载域,域地址0x08000000,域大小为0x00020000
    //load region size_region 所有代码需要下载到0x08000000 开始的区域中,且这个区域大小只有0x00020000 
  ER_IROM1 0x08000000 0x00020000  {  
	//load address = execution address 第一个运行时域必须和加载域起始地址相同,其大小一般也相同
    //只能是只读的代码段和只读数据段
   *.o (RESET, +First)        //启动代码的首次执行地址,RO执行域名称为ER_IROM1, 
    //将 RESET 段最先加载到本域的起始地址外
    //首次执行的地址为RESET标号所表示的地址,RESET 存储的是向量表
    //对应启动文件中的AREA    RESET, CODE, READONLY
   *(InRoot$$Sections)     //稍后文件中会单独讲到
   .ANY (+RO) //加载所有匹配目标文件的只读属性数据,包含:Code、 RW-Code、 RO-Data。
  }
  RW_IRAM1 0x20000000 0x00005000  {  
    //再定义一个运行时域,域基址0x20000000 
	//RW data 执行域是以0x20000000 开始的长度为0x00004000  一段区域
   .ANY (+RW +ZI)    //其中包括的是哪些文件
  }
}

加载区描述

加载域名 (基地址 | ("+" 地址偏移)) [属性列表] [最大容量]
{
	执行区域描述
}
LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
......
}

加载域名:名称,在 map 文件中的描述会使用该名称来标识空间。如本例中只有一个加载域,该域名为 LR_IROM1。
基地址+地址偏移:这部分说明了本加载域的基地址,可以使用+号连接一个地址偏移,算进基地址中,整个加载域以它们的结果为基地址。如本例中的加载域基地址为 0x08000000,刚好是 STM32 内部 FLASH 的基地址。
属性列表:属性列表说明了加载域的是否为绝对地址、N 字节对齐等属性,该配置是可选的。本例中没有描述加载域的属性。
最大容量:最大容量说明了这个加载域可使用的最大空间,该配置也是可选的,如果加上这个配置后,当链接器发现工程要分配到该区域的空间比容量还大,它会在工程构建过程给出提示。本例中的加载域最大容量为 0x00080000,即 512KB,正是本型号 STM32 内部 FLASH 的空间大小。

执行域

执行域名 (基地址 | "+" 地址偏移) [属性列表] [最大容量 ]
{
///输入节区描述
}

执行域的格式与加载域是类似的,区别只是输入节区的描述有所不同

输入节区描述 .o .ANY (+RO)等的用法

配合加载域及执行域的配置,在相应的域配置“输入节区描述”即可控制该节区存储到域中

 //除模块选择样式部分外,其余部分都可选选填
 模块选择样式"("输入节区样式",""+"输入节区属性")"
 模块选择样式"("输入节区样式",""+"节区特性")"

 模块选择样式"("输入符号样式",""+"节区特性")"
 模块选择样式"("输入符号样式",""+"输入节区属性")"
 -----------------------------------------------------------------------------
    *.o (RESET, +First)        //启动代码的首次执行地址,RO执行域名称为ER_IROM1, 
    //将 RESET 段最先加载到本域的起始地址外
    //首次执行的地址为RESET标号所表示的地址,RESET 存储的是向量表
    //对应启动文件中的AREA    RESET, CODE, READONLY
   *(InRoot$$Sections)     //稍后文件中会单独讲到
   .ANY (+RO) //加载所有匹配目标文件的只读属性数据,包含:Code、 RW-Code、 RO-Data。
  ------------------------------------------------------------------------------

模块选择样式:模块选择样式可用于选择 o 及 lib 目标文件作为输入节区,它可以直接使用目标文件名或“*”通配符,也可以使用“.ANY”。
“.o”可以选择所有 o 文件.
使用“.lib”可以选择所有 lib 文件.
使用“”或“.ANY”可以选择所有的 o 文件及 lib 文件
其中“.ANY”选择语句的优先级是最低的,所有其它选择语句选择完剩下的数据才会被“.ANY”语句选中。

示例文件中“(RESET,+First)”语句的 RESET 就是输入节区样式,它选择了名为 RESET 的节区,并使用后面介绍的节区特性控制字“+First”表示它要存储到本区域的第一个地址。示例文件中的“(InRoot$$Sections)”是一个链接器支持的特殊选择符号,它可以选择所有标准库里要求存储到 root 区域的节区,如__main.o、__scatter.o 等内容。

输入符号样式:同样地,使用输入符号样式可以选择要控制的符号,符号样式需要使用“:gdef:”来修饰。例如可以使用“*(:gdef:Value_Test)”来控制选择符号“Value_Test”。

输入节区属性:通过在模块选择样式后面加入输入节区属性,可以选择样式中不同的内容,每个节区属性描述符前要写一个“+”号,使用空格或“,”号分隔开.

节区属性描述符 说明
RO-CODE 及 CODE 只读代码段
RO-DATA 及 CONST 只读数据段
RO 及 TEXT 包括 RO-CODE 及 RO-DATA
RW-DATA 可读写数据段
RW-CODE 可读写代码段
RW 及 DATA 包括 RW-DATA 及 RW-CODE
ZI 及 BSS 初始化为 0 的可读写数据段
XO 只可执行的区域
ENTRY 节区的入口点

例如,示例文件中使用“.ANY(+RO)”选择剩余所有节区 RO 属性的内容都分配到执行域ER_IROM1中,使用“.ANY(+RW +ZI)”选择剩余所有节区 RW及 ZI属性的内容都分配到执行域 RW_IRAM1 中。

节区特性:节区特性可以使用“+FIRST”或“+LAST”选项配置它要存储到的位置,FIRST 存储到区域的头部,LAST 存储到尾部。通常重要的节区会放在头部,而CheckSum(校验和)之类的数据会放在尾部。

例如示例文件中使用“(RESET,+First)”选择了 RESET 节区,并要求把它放置到本区域第一个位置,而 RESET 是工程启动代码中定义的向量表

    *.o (RESET, +First)        //启动代码的首次执行地址,RO执行域名称为ER_IROM1, 
    //将 RESET 段最先加载到本域的起始地址外
    //首次执行的地址为RESET标号所表示的地址,RESET 存储的是向量表

如图,启动文件中该向量表中定义的堆栈顶和复位向量指针必须要存储在内部 FLASH 的前两个地址,这样 STM32 才能正常启动,所以必须使用 FIRST 控制它们存储到首地址。

; Vector Table Mapped to Address 0 at Reset
 AREA RESET, DATA, READONLY
 EXPORT __Vectors
 EXPORT __Vectors_End
 EXPORT __Vectors_Size

 __Vectors DCD __initial_sp; Top of Stack
 DCD Reset_Handler; Reset Handler
 DCD NMI_Handler; NMI Handler

通过 MDK 配置选项来修改 sct 文件

.sct文件一般在project文件夹中。

选择 sct 文件的产生方式

首先需要选择 sct 文件产生的方式,选择使用 MDK 生成还是使用用户自定义的 sct 文件。在 MDK 的“Options for Target->Linker->Use Memory Layout from Target Dialog”选项可配置该选择。
【STM32】sct 分散加载文件的格式与应用_第4张图片
1.勾选时,由keil根据配置生成sct文件。keil会根据“Options for Target”对话框中的选项生成 sct 文件,这种情况下,即使我们手动打开它生成的 sct文件编辑也是无效的,因为每次构建工程的时候,MDK 都会生成新的 sct 文件覆盖旧文件。该选项在 MDK 中是默认勾选的,若希望 MDK 使用我们手动编辑的 sct 文件构建工程,需要取消勾选,并通过 Scatter File 框中指定 sct 文件的路径。

由keil生成时

勾选Use Memory Layout from Target Dialog,由keil根据配置生成sct文件。keil会根据“Options for Target”对话框中的选项生成 sct 文件。

MDK 选项中的内部 ROM 选项最多只可以填充两个选项位置,若想把内部 ROM 分成多片地址管理就无法实现了;另外 MDK 配置可控的最小粒度为文件,若想控制特定的节区需要直接编辑 sct 文件

配置keil选项 改变sct文件

1.在 Device 标签页中选择芯片的类型
【STM32】sct 分散加载文件的格式与应用_第5张图片
2.选定了芯片的型号为 STM32F103ZE后,在 Target 标签页中的存储器信息会根据芯片更新。
在 Target 标签页中存储器信息分成只读存储器(Read/Only Memory Areas)和可读写存储器(Read/Write Memory Areas)两类,即 ROM 和 RAM,而且它们又细分成了片外存储器(off-chip)和片内存储器(on-chip)两类。

修改 Target标签页中的这些存储信息,例如,把 STM32内部的 SRAM分成两等份
【STM32】sct 分散加载文件的格式与应用_第6张图片
修改了 IRAM1 基地址后的 sct 文件内容

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00080000 { ; load region size_region
	ER_IROM1 0x08000000 0x00080000 { ; load address = execution address
		*.o (RESET, +First)
		*(InRoot$$Sections)
		.ANY (+RO)
	 }
	 RW_IRAM1 0x20000000 0x00008000 { ; RW data
	 .ANY (+RW +ZI)
	 }
	 RW_IRAM2 0x20008000 0x00008000 {
	 .ANY (+RW +ZI)
	 }
}

控制文件分配到指定的存储空间

设定好存储器的信息后,可以控制各个源文件定制到哪个部分存储器,,在 MDK 的工程文件栏中,选中要配置的文件,右键,并在弹出的菜单中选择“Options for File xxxx”即可弹出一个文件配置对话框,在该对话框中进行存储器定制。
【STM32】sct 分散加载文件的格式与应用_第7张图片
对话框中有一个“Memory Assignment”区域(存储器分配),在该区域中可以针对文件的各种属性内容进行分配,如 Code/Const 内容(RO)、Zero Initialized Data 内容(ZIdata)以及 Other Data 内容(RW-data),IROM1、IRAM1、IRAM2 等存储器。

例如图中我们把这个 bsp_led.c 文件的 Other Data 属性的内容分配到了 IRAM2 存储器(在 Target 标签页中我们勾选了 IRAM1 及 IRAM2),当在bsp_led.c 文件定义了一些 RW-data 内容时(如初值非 0 的全局变量),该变量将会被分配到IRAM2 空间,配置完成后点击 OK,然后编译工程,查看到的 sct 文件内容。

LR_IROM1 0x08000000 0x00080000{ ; load region size_region
	ER_IROM1 0x08000000 0x00080000{ ; load address = execution address
		*.o (RESET, +First)
		*(InRoot$$Sections)
		.ANY (+RO)
	}
	RW_IRAM1 0x20000000 0x00008000 { ; RW data
		.ANY (+RW +ZI)
	}
	 RW_IRAM2 0x20008000 0x00008000 { 
	 bsp_led.o (+RW)
	 .ANY (+RW +ZI)
	}
 }

可以看到在 sct 文件中的 RW_IRAM2 执行域中增加了一个选择 bsp_led.o 中 RW 内容的语句。

手动修改sct文件

虽然 MDK 的这些存储器配置选项很方便,但有很多高级的配置还是需要手动编写 sct文件实现的,例如 MDK 选项中的内部 ROM 选项最多只可以填充两个选项位置,若想把内部 ROM 分成多片地址管理就无法实现了;另外 MDK 配置可控的最小粒度为文件,若想控制特定的节区也需要直接编辑 sct 文件

补充“_ _attribute __”关键字的说明

当需要指定某个变量的内存地址时, MDK提供了 一 个关键字“_ _ attribute _ _”实现该功能。

/*要指定的地址*/
 #define USER_ADDR((uint32_t)0x20005000)
 
 uint8_t testValue __attribute__((at(USER_ADDR)));/*使用 atribute 指定该变量存储到 USER_ADDR,这种方式必须定义成全局变量*/
 testValue = 0xDD;

式使用 “attribute((at()))”来指定变量的地址,代码中指定 testValue 存储到USER_ADDR地址 0x20005000中.若把该地址改为外部存储器 SRAM的地址,变量就会被存储到外部 SRAM 了,因而利用该关键字在一定程度上可以定制各种存储器的空间分配。要注意使用这种方法定义变量时,必须在函数外把它定义成全局变量,才可以存储到指定地址上。

当有多个这样的变量时,为了防止变量占用的空间重叠,或减少碎片空间进行充分利用,就需要手动计算各个变量的地址了,非常麻烦,利用sct文件让链接器自动分配全局变量到指定的存储区域并进行管理,可以使得利用指定存储区域时就跟普通的变量定义一样简单。

例:自动分配变量到指定的 SRAM 空间

取消勾选Options for Target->Linker->Use Memory Layout from Target Dialog

(1) 修改启动文件,在__main 执行之前初始化“指定的存储空间”的硬件
(2) 在 sct 文件中增加“指定的存储空间”对应的执行域;
(3) 使用节区选择语句选择要分配到“指定的存储空间”的内容;
(4) 编写测试程序,编译正常后,查看 map 文件的空间分配情况。

1.修改启动文件 在__main 之前初始化外部“指定的存储空间”的硬件

芯片启动后,ResetHandler中会通过__main 函数调用分散加载代码__scatterload,分散加载代码会把存储在 FLASH 中的 RW-data 复制到 RAM中,然后在 RAM 区开辟一块 ZI-data 的空间,并将其初始化为0值。

因此,为了保证在程序中定义到“指定的存储空间”中的变量能被正常初始化,我们需要在系统执行分散加载代码之前使该空间使用的存储器正常运转,使它能够正常保存数据。如果把存储器的初始化放到 C 语言的 main 函数才执行,那么由于__scatterload 时存储器没有正常工作,所以拷贝过程无效,那么 RW-data 类型变量的初值都会不正常,这会导致程序运行不符合预期。

所以需要修改 启动文件
【STM32】sct 分散加载文件的格式与应用_第8张图片

; Reset handler//注释
Reset_Handler 	PROC //Reset_Handler程序开始
				EXPORT Reset_Handler[WEAK]
				IMPORT SystemInit//导入  SystemInit函数
				IMPORT __main
// 从外部文件引入声明, 格式:IMPORT 要调用的初始化函数名
// 以下语句仅作演示,使用外部存储器时请去掉注释用的“; ”号,本工程使用内部 SRAM,无需初始化
				; IMPORT FSMC_SRAM_Init//使用外部RAM的时候,需要初始化FSMC这个外设
				 LDR R0, =SystemInit//将 SystemInit函数地址赋值到R0 执行R0 也就是说执行 SystemInit函数,初始化时钟等
				 BLX R0

				; 在__main 之前调用 FSMC_SRAM_Init 进行初始化
//以下语句仅作演示,使用外部存储器时请去掉注释用的“; ”号,本工程使用内部 SRAM,无需初始化
				; LDR R0, =FSMC_SRAM_Init//使用外部RAM的时候,需要初始化FSMC这个外设
				; BLX R0
		
		 		LDR R0, =__main
				 BX R0
		 		ENDP

如果需要使用到扩充SRAM,那么需要在进入__main函数前启动FSMC外设,使得芯片可以操作外置的SRAM。STM32外设FSMC没有使用到外置的SRAM就不需要这么操作了。

引入用户在其它 C 语言文件中定义的名为 FSMC_SRAM_Init 的函数,接着使用 LDR 指令加载函数的代码地址到寄存器 R0,最后使用BLX R0 指令跳转到 FSMC_SRAM_Init 的代码地址执行。

2.修改sct文件 在 sct 文件中增加“指定的存储空间”对应的执行域

来修改 sct 文件,控制使得在 C 源文件中定义的全局变量都自动由链接器分配到“指定的存储空间”(这里的例子是0x20005000 至 0x2000C000 地址的内部 SRAM 空间)

 *************************************************************
 *** Scatter - Loading Description File generated by uVision ***
 *************************************************************

LR_IROM1 0x08000000 0x00080000{; 加载域
	ER_IROM1 0x08000000 0x00080000{; 加载地址 = 执行地址
		* .o(RESET, +First)
		* (InRoot$$Sections)
		ANY(+RO)
	 }
	 RW_IRAM1 0x20000000 0x00005000 {; 内部 SRAM
		 * .o(STACK); 选择 STACK 节区,栈
		 stm32f10x_rcc.o(+RW); 选择 stm32f10x_rcc 的 RW 内容
		.ANY(+RW + ZI); 其余的 RW / ZI - data 都分配到这里
	 }
	 RW_ERAM1 0x20005000 0x00007000{; “指定的存储空间”
		.ANY(+RW + ZI); 其余的 RW / ZI - data 都分配到这里
 		}
 }

有两个注意点:
1.RW_ERAM1 0x20005000 0x00007000{}

RW_ERAM1 是我们配置的“指定的存储空间”的执行域,该执行域的名字是可以随便取的,最重要的是它的基地址及空间大小,在这个案例中,起始地址和偏移之后都在片上RAM之内。

在 RW_ERAM1 执行域内部,它使用“.ANY(+RW +ZI)”语句,选择了所有的 RW/ZI 类型的数据都分配到这个“指定的存储空间”,所以我们在工程中的 C 文件定义全局变量时,它都会被分配到该区域,若使用外扩存储器,这些数据就会存储到相应的存储器中。

2.RW_IRAM1 执行域
简单来说使用到了外部存储器SRAM需要加一句 “ .o(STACK) 及stm32f10x_rcc.o(+RW)”语句到RW_IRAM1 执行域(接下来的大段是理由,可以不看)
若“指定的存储空间”若是属于内部 SRAM,“.o(STACK)及stm32f10x_rcc.o(+RW)”语句不添加也是没有问题

这是原本sct文件中的执行域,在它的执行域配置中增加了 “ *.o(STACK) 及stm32f10x_rcc.o(+RW)”语句

本来上面配置外部 SRAM 执行域后已经达到使全局变量分配的目的,为何还要修改原内部 SRAM 的执行域呢?

当使用的是外部挂载的SRAM时。

这是由于如果在__main 之前调用的 FSMC_SRAM_Init 外部存储器初始化函数调用了很多库函数,且这些函数内部定义了一些局部变量,而函数内的局部变量是需要分配到“栈”空间(STACK),所以在 FSMC_SRAM_Init 函数执行之前,栈空间必须要被准备好,然而在 FSMC_SRAM_Init 函数执行之前,外部存储器却并未正常工作,这样的矛盾导致栈空间不能被分配到外部存储器区域。

虽然内部 SRAM 的执行域 RW_IRAM1 及“指定的存储空间”执行域 RW_ERAM1中都使用“.ANY(+RW +ZI)”语句选择了所有 RW 及 ZI 属性的内容,但对于符合两个相同选择语句的内容,链接器会优先选择使用空间较大的执行域,即这种情况下只有当“指定的存储空间”执行域的空间使用完了,RW/ZI 属性的内容才会被分配到内部SRAM。

所以在大部分情况下,内部 SRAM 执行域中的“.ANY(+RW +ZI)”语句是不起作用的,而栈节区(STACK)又属于 ZI-data 类,如果我们的内部 SRAM 执行域还是按原来的默认配置的话,栈节区会被分配到“指定的存储空间”,若此时“指定的存储空间”使用的是外部存储器,将会导致出错。为了避免这个问题,我们把栈节区使用“*.o(STACK)”语句分配到内部 SRAM 的执行域。

增加“stm32f10x_rcc.o(+RW)”语句是因为初始化外部存储器的 FSMC_SRAM_Init函数可能会调用stm32f10x_rcc.c文件中的RCC_AHBPeriphClockCmd函数,而查看map文件后了解到 stm32f10x_rcc.c定义了一些 RW-data类型的变量。不管这些数据是否在 FSMC_SRAM_Init 调用过程中使用到,保险起见,我们直接把这部分内容也分配到内部 SRAM 的执行区。

例 优先使用内部 SRAM 并把堆区分配到指定空间

配置 sct 文件,使得默认情况下优先使用内部 SRAM 空间,在需要的时候使用一个关键字指定变量存储到“指定的存储空间”,另外,我们还把系统默认的堆空间(HEAP)映射到“指定的存储空间”,从而可以使用 C 语言标准库的 malloc 函数动态从中分配变量,利用标准库对该空间内存管理,这在外部内存管理中非常有用。

1.修改启动文件 在__main 之前初始化外部“指定的存储空间”的硬件

修改工程 startup_stm32f10x.s 启动文件中的 Reset_handler 函数,在__main 函数之前调用该存储器的初始化函数使硬件正常运转

; Reset handler//注释
Reset_Handler 	PROC //Reset_Handler程序开始
				EXPORT Reset_Handler[WEAK]
				IMPORT SystemInit//导入  SystemInit函数
				IMPORT __main
// 从外部文件引入声明, 格式:IMPORT 要调用的初始化函数名
// 以下语句仅作演示,使用外部存储器时请去掉注释用的“; ”号,本工程使用内部 SRAM,无需初始化
				; IMPORT FSMC_SRAM_Init//使用外部RAM的时候,需要初始化FSMC这个外设
				 LDR R0, =SystemInit//将 SystemInit函数地址赋值到R0 执行R0 也就是说执行 SystemInit函数,初始化时钟等
				 BLX R0

				; 在__main 之前调用 FSMC_SRAM_Init 进行初始化
//以下语句仅作演示,使用外部存储器时请去掉注释用的“; ”号,本工程使用内部 SRAM,无需初始化
				; LDR R0, =FSMC_SRAM_Init//使用外部RAM的时候,需要初始化FSMC这个外设
				; BLX R0
		
		 		LDR R0, =__main
				 BX R0
		 		ENDP

2.修改sct文件 在 sct 文件中增加“指定的存储空间”对应的执行域

 *************************************************************
 *** Scatter - Loading Description File generated by uVision ***
 *************************************************************

LR_IROM1 0x08000000 0x00080000{; 加载域
	ER_IROM1 0x08000000 0x00080000{; 加载地址 = 执行地址
		* .o(RESET, +First)
		* (InRoot$$Sections)
		ANY(+RO)
	 }
	 RW_IRAM1 0x20000000 0x00005000 {; 内部 SRAM
		 * .o(STACK); 选择 STACK 节区,栈
		 stm32f10x_rcc.o(+RW); 选择 stm32f10x_rcc 的 RW 内容
		.ANY(+RW + ZI); 其余的 RW / ZI - data 都分配到这里
	 }
	 RW_ERAM1 0x20005000 0x00007000{; “指定的存储空间”
	 	*.o(HEAP) ;选择堆区
		.ANY (EXRAM) ;选择 EXRAM 节区
 		}
 }

使用了 “ *.o(HEAP) ” 把所有堆区分配到了RW_ERAM1 ,使用“.ANY(EXRAM)”语句把名为“EXRAM”的节区分也配到 RW_ERAM1。

“EXRAM”节区是由我们自定义的的,在语法上就跟在 C 文件中定义全局变量类似,只要它跟工程中的其它原有节区名不一样即可。有了这个节区选择配置,我们需要定义变量到“指定的存储空间”时,只需要指定该变量分配到该节区,它就会被分配到该空间中。

3.指定变量分配到节区

之前1.2两点已经设置完毕,接下来就是使用了。

使用方式1

//使用 __attribute__ 关键字定义指定变量定义到某节区

//语法: 变量定义 __attribute__ ((section ("节区名"))) = 变量值; 
uint32_t testValue __attribute__((section("EXRAM"))) = 7;

“变量定义 attribute ((section (“节区名”))) = 变量值;”,它的主体跟普通的 C 语言变量定义语法无异.
在赋值“=”号前(可以不赋初值),加了个“__attribute__ ((section ("节区名")))”描述它要分配到的节区。本例中的节区名为“EXRAM”,即我们在 sct 文件中选择分配到“指定的存储空间”执行域的节区,所以该变量就被分配到该存储器中了。

使用方式2

//使用宏封装 
//设置变量定义到“EXRAM”节区的宏
#define __EXRAM __attribute__((section("EXRAM")))

//使用该宏定义变量到“指定的存储空间”
 uint32_t testValue __EXRAM = 7;

由于“__attribute__”关键字写起来比较繁琐,我们可以使用宏定义把它封装起来,简化 代 码 。 本 例 中 我 们 把 指 定 到 “ EXRAM ” 的 描 述 语 句 “__attribute__ ((section ("EXRAM")))”封装成了宏“ __EXRAM”,应用时只需要使用宏的名字替换原来“attribute”关键字的位置即可,如“uint32_t testValue __EXRAM =7 ;”。有 51 单片机使用经验的读者会发现,这种变量定义方法就跟使用 keil 51 特有的关键字“xdata”定义变量到外部 RAM 空间差不多。

你可能感兴趣的:(STM32,stm32)