一、 CMD文件基本概念及语法
CMD的专业名称叫链接器配置文件,是存放链接器的配置信息的,我们简称为命令文件。从其名称可以看出,该文件的作用是指明如何链接程序的。
那么我们知道,在编写TI DSP程序时,是可以将程序分为很多段,比如text、bss等,各段的作用均不相同。实际在片中运行时,所处的位置也不相同。比如text代码一般应该放在flash内,而bss的变量应该放在ram内。等等。但是对于不同的芯片,其各存储器的起止地址都是不一样的,而且,用户希望将某一段,尤其是自定义段,放在什么存储器的什么位置,这也是链接器不知道的。为了告诉链接器,即将使用的芯片其内部存储空间的分配和程序各段的具体存放位置,这就需要编写一个配置文件,即CMD文件了。
所以,CMD文件里面最重要的就是两段,即由MEMORY和SECTIONS两个伪指令指定的两段配置。简单的说,MEMORY就是用来建立目标存储器的模型,而SECTIONS指令就是根据这个模型来安排各个段的位置。
MEMORY 伪指令
MEMORY 用来建立目标存储器的模型,SECTIONS 指令就可以根据这个模型来安排各个段的位置,MEMORY 指令可以定义目标系统的各种类型的存储器及容量。MEMORY 的语法如下:
MEMORY
{
PAGE 0 : name1[(attr)] : origin = constant,length = constant
name1n[(attr)] : origin = constant,length = constant
PAGE 1 : name2[(attr)] : origin = constant,length = constant
name2n[(attr)] : origin = constant,length = constant
PAGE n : namen[(attr)] : origin = constant,length = constant
namenn[(attr)] : origin = constant,length = constant
}
PAGE 关键词对独立的存储空间进行标记,页号 n 的最大值为 255,实际应用中一般分为两页,PAGE0 程序存储器和 PAGE1 数据存储器。name 存储区间的名字,不超过 8 个字符,不同的 PAGE 上可以出现相同的名字(最好不用,免的搞混),一个 PAGE 内不许有相同的 name。attr 的属性标识,为 R 表示可读;W 可写 X 表示区间可以装入可执行代码;I 表示存储器可以进行初始话,什么属性代码也不写,表示存储区间具有上述的四种属性,基本上我们都选择这种写法。下面为例子:
MEMORY
{
PAGE 0 :
BEGIN : origin = 0x000000, length = 0x000002
RAMM0 : origin = 0x000050, length = 0x0003B0
PAGE 1 :
RAMM1 : origin = 0x000480, length = 0x000380
RAML2 : origin = 0x008C00, length = 0x000400
}
SECTIONS 伪指令
SECTIONS 指令的语法如下:
SECTIONS
{
.text: {所有.text 输入段名} load=加载地址 run =运行地址
.cinit: {所有.data 输入段名} load=加载地址 run =运行地址
.bss: {所有.bss 输入段名} load=加载地址 run =运行地址
.other: {所有.other 输入段名} load=加载地址 run =运行地址
} 例如: .text : > RAML0L1, PAGE = 0
SECTIONS 必须用大写字母,其后的大括号里是输出段的说明性语句,每一个输出段的说明都是从段名开始,段名之后是如何对输入段进行组织和给段分配存储器的参数说明:
以.text 段的属性语句为例,“{所有.text 输入段名}”这段内容用来说明连接器输出段的.text 段由哪些子目标文件的段组成,举例如下
SECTIONS
{
.text:{ file1.obj(.text) file2(.text) file3(.text,cinit)}略
}
指明输出段.text 要链接 file1.obj 的.text 和 file2 的.text 还有 file3 的.text 和.cinit。在 CCS 的 SECTIONS里通常只写一个中间没有内容的“{ }”就表示所有的目标文件的相应段接下来说明“load=加载地址 run =运行地址”链接器为每个输出段都在目标存储器里分配两个地址:一个是加载地址,一个是运行地址。通常情况下两个地址是相同的,可以认为输出段只有一个地址,这时就可以不加“run =运行地址”这条语句了;但有时需要将两个地址分开,比如将程序加载到 FLASH,然后放到 RAM中高速运行,这就用到了运行地址和加载地址的分别配置了,如下例所示:
.const :{略} load = PROG run = 0x0800
常量加载在程序存储区,配置为在 RAM 里调用。“load=加载地址”的几种写法需要说明一下,首先“load”关键字可以省略,“=”可以写成“>”, “加载地址”可以是:地址值、存储区间的名字、PAGE 关键词等,所以大家见到“.text:{ } > 0x0080”这样的语句可千万不要奇怪。“run =运行地址”中的“ = ”可以用“>”,其它的简化写法就没有了。大家不要乱用。
自定义段(C 语言)
#pragma DATA_SECTION(全局变量名,"用户自定义在数据空间的段名");
#pragma CODE_SECTION(函数名,"用户自定义在程序空间的段名");
CODE_SECTION用来定义代码段
DATA_SECTION用来定义数据段
例如:在c文件中定义了一个data段my_sect和一个code段ramfuncs,如下:
#pragma DATA_SECTION(bufferB, ”my_sect”)
char bufferB[512];
#pragma CODE_SECTION(dragon_update,"ramfuncs");
Uint16 dragon_update(UPDATE_SOURCE_TYPE *update_flag)
{
。。。
}
然后再在cmd文件中指定这两个section的位置就可以了。
如果想在汇编中指定段,使用方法,在代码前用.sect “XXX”开始则标示接下来的一段代码都是在xxx的代码段中。
例子:
PieVectTableFile : > PIE_VECT, PAGE = 1 // nonBIOS. cmd
#pragma DATA_SECTION(PieVectTable,"PieVectTableFile"); // globalVariableDefs.c
struct PIE_VECT_TABLE PieVectTable; // globalVariableDefs.c
注意:
不能再函数体内声明#pragma;
必须在符号被定义和使用之前声明#pragma
#pragma 可以阻止对未调用的函数的优化
备注:
CMD 文件中还可以写上注释,用“/”和“/”包围起来,但不允许用“//” ,这一点和 C 语言不同。
可以直接在CMD文件中写编译命令,如:
-l rts2800_ml.lib 连接系统文件rts2800_ml.lib
-o filename.out 最终生成的二进制文件命名为filename.out
-m filename.map 生成映射文件filename.map
二、DSP 28035的内存映射图如下:
三、 烧写FLASH的CMD文件示例及注解(F28035.cmd)
//###########################################################################
// 文件: F28035.cmd
// 说明: F28035连接命令文件
//###########################################################################
/*
定义F28035内存块的起始地址及长度
PAGE 0 为代码段
PAGE 1 为数据段
注意:
F2803x的内存块对于PAGE 0和PAGE 1是共用的。
同一个内存块不能同时定义为PAGE 0和PAGE 1。会造成程序的混乱。
L0内存块是被镜像的:可以在高地址内存或都低地址内存被访问。
在这里只使用低地址内存。当需要一片很大的的内存块时,SARAM内存块
或者FLASH段可以连接在一起。
*/
MEMORY
{
PAGE 0: /* 程序内存块 */
RAML0 : origin = 0x008000, length = 0x000800 /* 片上 RAM block L0 */
RAML1 : origin = 0x008800, length = 0x000400 /* 片上 RAM block L1 */
OTP : origin = 0x3D7800, length = 0x000400 /* 片上 OTP */
FLASHH : origin = 0x3E8000, length = 0x002000 /* 片上 FLASH */
FLASHG : origin = 0x3EA000, length = 0x002000 /* 片上 FLASH */
FLASHF : origin = 0x3EC000, length = 0x002000 /* 片上 FLASH */
FLASHE : origin = 0x3EE000, length = 0x002000 /* 片上 FLASH */
FLASHD : origin = 0x3F0000, length = 0x002000 /* 片上 FLASH */
FLASHC : origin = 0x3F2000, length = 0x002000 /* 片上 FLASH */
FLASHA : origin = 0x3F6000, length = 0x001F80 /* 片上 FLASH */
CSM_RSVD : origin = 0x3F7F80, length = 0x000076
/* FLASHA的一部分. Program with all 0x0000 when CSM is in use. */
BEGIN : origin = 0x3F7FF6, length = 0x000002
/* FLASHA的一部分. 使用了启动引导FLASH模式 */
CSM_PWL_P0 : origin = 0x3F7FF8, length = 0x000008
/* FLASHA的一部分. CSM password locations in FLASHA */
/*
说明
CSM_RSVD是Code Security Module_reserved的意思,
是指当使用代码 安全模块时,origin = 0x3F7F80, length = 0x000076
这个内存块是受保留的reserved。不能放程序代码或者数据块。且必须初始化为0x0000.
CSM_PWL是password locations的意思,即存放密码的地方
BEGIN是程序启动的开始地址。
*/
IQTABLES : origin = 0x3FE000, length = 0x000B50 /* 在引导ROM中的IQ数学表 */
IQTABLES2 : origin = 0x3FEB50, length = 0x00008C /* 在引导ROM中的IQ数学表 */
IQTABLES3 : origin = 0x3FEBDC, length = 0x0000AA /* 在引导ROM中的IQ数学表 */
ROM : origin = 0x3FF27C, length = 0x000D44 /* 启动引导 ROM */
RESET : origin = 0x3FFFC0, length = 0x000002
/* 启动引导 ROM的一部分 复位地址BROM矢量 */
VECTORS : origin = 0x3FFFC2, length = 0x00003E
/* 启动引导 ROM的一部分 BROM矢量*/
PAGE 1 : /* 数据内存块 */
BOOT_RSVD : origin = 0x000000, length = 0x000050
/* M0的一部分, 预留给启动引导 ROM时的栈空间 */
RAMM0 : origin = 0x000050, length = 0x0003B0 /* 片上 RAM M0 */
RAMM1 : origin = 0x000400, length = 0x000400 /* 片上 RAM M1 */
RAML2 : origin = 0x008C00, length = 0x000400 /* 片上 RAM L2 */
RAML3 : origin = 0x009000, length = 0x001000 /* 片上 RAM L3 */
FLASHB : origin = 0x3F4000, length = 0x002000 /* 片上 FLASH */
}
/*
将各个段分配到内存块.
注意: codestart段在 DSP28_CodeStartBranch.asm中被定义,用于引导FLASH时,重定向代码。
ramfuncs段内的函数会从FLASH被转移到RAM中运行。
*/
SECTIONS
{
.cinit : > FLASHA PAGE = 0 /* 初始化的全局变量和static变量表*/
.pinit : > FLASHA PAGE = 0 /* 全局对象的构造函数表 C++范畴*/
.text : > FLASHA PAGE = 0 /* 可执行代码和常数段 */
codestart : > BEGIN PAGE = 0 /* 代码启动段 */
ramfuncs : LOAD = FLASHD, /* 将定义到段ramfuncs上的代码,载入到FLASHD */
RUN = RAML0, /* 定义到ramfuncs上的代码,复制到RAML0上运行 */
LOAD_START(_RamfuncsLoadStart), /* 所要加载程序在Flash里的初始地址 */
LOAD_END(_RamfuncsLoadEnd), /* 所要加载程序在Flash里的结束地址 */
RUN_START(_RamfuncsRunStart), /* 程序运行的起始地址 */
PAGE = 0
csmpasswds: > CSM_PWL_P0 PAGE = 0 /* 密码段 */
csm_rsvd : > CSM_RSVD PAGE = 0/* 使用代码安全模块时,需要预留的段 */
/* 未初始化数据段: */
.stack : > RAMM0 PAGE = 1 /* 栈空间*/
.ebss : > RAML2 | RAML3 PAGE = 1
/* 长调用的全局或static变量,初始化和未初始化变量*/
.esysmem : > RAML2 | RAML3 PAGE = 1
/* 已初始化的段 */
.econst : > FLASHA PAGE = 0
/* 字符串常量和far const定义的全局和静态变量(static const)*/
.switch : > FLASHA PAGE = 0
/* 存放switch语句产生的常数表格*/
/* 分配 IQ 数学表区域 : */
IQmath : > FLASHA PAGE = 0 /* Math Code */
IQmathTables : > IQTABLES, PAGE = 0, TYPE = NOLOAD /* 不载入*/
/*
.reset是由编译器使用的。它包含了C代码的开始地址_c_int00。
使用启动引导ROM的这部分和CPU向量表是不需要的。默认的类型被设置为dsect
*/
.reset : > RESET, PAGE = 0, TYPE = DSECT
/* DSECT 说明这一块地址并不会真的加载数据,只是链接一下symbol。*/
vectors : > VECTORS PAGE = 0, TYPE = DSECT
}
/*说明
编译器生成的包含代码和数据的多个部分,称为段。这个段被分为两个不同的组:初始化了的和没被初始化的。
初始化的部分是由所有的代码,常量和初始化表组成的。下面列出了由编译器产生的初始化段。
初始化段
段名 内容 限制
.cinit 初始化的全局变量和static变量表 代码
.const 初始化的全局const变量和static const变量和字符串常量 不超过64K长度
.econst 长调用的常量 数据中的任何地方
.pinit 全局对象的构造函数表 代码
.switch switch语句产生的表 代码或者数据
.text 可执行代码和常数 代码
没初始化的段是由未初始化的变量,堆栈和malloc产生的内存。下表列出了由编译器产生的没初始化段。
没初始化段
段名 内容 限制
.bss 全局变量和static变量 不超过64K长度
.ebss 长调用的全局变量和static变量 数据中的任何地方
.stack 栈空间 不超过64K长度
.sysmem malloc函数产生的内存 不超过64K长度
.esysmem far_malloc函数产生的内存 数据中的任何地方
已初始化的段:.text,.cinit,.const,.econst,.pinit和.switch..
.text:所有可以执行的代码和常量
.cinit:全局变量和static变量的C初始化记录,
包含未用const声明的外部(extern)或静态(static)数据表
.const:包含字符串常量和const定义的全局和static变量
.econst:包含字符串常量和初始化的全局变量和static变量(由far const)
的初始化和说明,与.const不同的是.const分配范围被限制在低64K
16位数据区,而.econst的分配范围是4M 22位数据区
.pinit:全局构造器(C++)程序列表,注意是全局的构造器。
.switch:包含大switch段声明的列表。Jump tables for large switch statements。
非初始化的段:.bss,.ebss,.stack,.sysmem,和esysmem.
.bss: 为全局变量和局部变量保留的空间,在程序上电时.cinit空间中的数据复制出来并存储在.bss空间中。
.ebss:为使用大寄存器模式时的全局变量和静态变量预留的空间,在程序上电时,
cinit空间中的数据复制出来并存储在.ebss中,与.ebss不同的是.bss分配范围被限制在低64K 16位数据区,
而.ebss的分配范围是4M 22位数据区
.stack:为系统堆栈保留的空间,用于和函数传递变量或为局部变量分配空间。
.sysmem:为动态存储分配保留的空间。如果有宏函数,此空间被宏函数占用,如果没有的话,此空间保留为0
.esysmem:为动态存储分配保留的空间。如果有far函数,此空间被相应的占用,如果没有的化,此空间保留为0.
ramfuncs: LOAD = FLASHD,表示ramfuncs段的装载在PAGA0的FLASHD中。
RUN = RAML0,表示ramfuncs段的运行地址在PAGE0的RAML0中
LOAD_START(_RamfuncsLoadStart),
令编译器创建了一个变量RamfuncsLoadStart,该变量指向段
ramfuncs的装载地址的首地址。
LOAD_END(_RamfuncsLoadEnd),
令编译器创建了一个变量RamfuncsLoadEnd,该变量指向段ramfuncs的装载地址的末地址。
RUN_START(_RamfuncsRunStart),
令编译器创建了一个变量RamfuncsRunStart,该变量指向段ramfuncs的运行地址的首地址。
解释一下什么叫长调用,指的是程序指令的寻指范围。
上面说了绝对调用的寻指范围是16位数据区,长调用的寻指范围是22位数据区 。
*/