一、CMD文件
CMD文件,全名连接命令文件(linkercommand files),以后缀.cmd结尾。它是用来分配rom和ram空间用的,告诉链接程序怎样计算地址和分配空间,所以不同的芯片就有不同大小的rom和ram,放用户程序的地方也不尽相同。所以要根据芯片进行修改。分两部分:MEMORY和SECTIONS.
(1):存储器(MEMORY)伪指令,用来定义目标系统的存储器空间。MEMORY可以定义存储器的区域,并指定起始地址和长度。
(2):段(SECTOINS)伪指令,告诉链接器如何将输入段结合成输出段并告诉链接器将输出段放在存储器的何处。
C28x的编译器把存储空间划分为两个部分进行管理,包括:
1. 程序存储空间:包含可执行的代码,初始化的记录和switch-case使用的表。
2. 数据存储空间:包含外部变量,静态变量以及系统的栈;一般情况下,各个寄存器对应的存储空间也归类在数据空间里。
为了方便管理,不同种类的代码、变量等往往又被分别分配到不同的段(section)之中,然后对存储空间的划分就变成了对段的地址分配问题了。例如,在下面的代码中,就规定了.text这个段会存放在RAM中Page0下面的RAML1中,RAML1的起始地址是0x009000,长度是0x001000。
MEMORY
{
/* 省略不在此显示的代码 */
PAGE 0 :
RAML1 : origin = 0x009000, length = 0x001000
RAML2 : origin = 0x00A000, length = 0x001000
/* 省略不在此显示的代码 */
}
SECTIONS
{
/* 省略不在此显示的代码 */
.text : > RAML1, PAGE = 0
/* 省略不在此显示的代码 */
}
一般情况下,我们的代码不会大到无法存储,但是也有可能因为代码特别多导致无法存储,产生.text的实际大小是size xxx,但是RAML1的size只有yyy这样的链接错误,以至于无法生成输出文件。此时我们可以把上面对应的RAML1的长度,即length增大,使得.text段所分配的地址空间变多。但是RAML1地址空间扩大之后,挤占了RAML2的空间,导致地址重叠,此时RAML2的起始位置要后移,其长度也要相应地缩减,才能不产生地址覆盖错误;修改之后可以为:
RAML1 : origin = 0x009000, length = 0x001500
RAML2 : origin = 0x00A500, length = 0x000500
还有一个解决方法则是把.text给分配到其它更长的地址空间里去;如果没有现成的地址范围比较长的段,也可以合并现有的段,修改方法比如把RAML2删除,把它的地址全部合并到RAML1中去,而.text还是分配在RAML1,就没有问题了。删除RAML2的时候要注意,它在没有被任何段使用的情况下才能操作,否则编译、链接的时候又提示其它的段找不到对应的存储单元了。
在DSP28335工程文件里(不用BIOS产生CMD文件),手写CMD文件一般有两个,RAM里调试时用的两个CMD文件分别为DSP2833x_Headers_nonBIOS.cmd和28335_RAM_lnk.cmd,烧写到flash里时用的两个CMD文件分别为DSP2833x_Headers_nonBIOS.cmd和F28335.cmd,其中DSP2833x_Headers_nonBIOS.cmd文件可以在所有工程文件中通用,主要作用是把外设寄存器产生的数据段映射到对应的存储空间,可以跟DSP2833x_GlobalVariableDefs.c文件对照一下看看。一般情况下直接用TI给的,不需要做修改即可满足调试用,模式较固定,当然你也可以做相应的修改用到哪块RAM存储空间,在CMD文件里做相应的分配即可。
编写用于flash烧写的F28335.cmd文件时相对来说较复杂些,根据不同的情况需要做一些修改。
1 不需要把部分代码copy到RAM里,一般情况不需要外扩RAM等时直接用TI的F28335.cmd即可。
2 需要把部分代码从flash 复制到RAM里,如延时函数DSP2833x_usDelay.asm等,这时CMD文件需要做相应的修改。(似乎加入MemCopy函数)
3 从时间开销方面考虑,需要把整个程序从flash复制到RAM里,这时程序及CMD文件都要做相应的修改。
CMD文件其实就是用户的“声明”,包括两方面的内容:
1、用户声明的整个系统里的存储器资源。无论是DSP芯片自带的,还是用户外扩的,凡是可以使用的、需要用到的存储器和空间,用户都要一一声明出来:有哪些存储器,它们的位置和大小。如果有些资源根本用不到,可以视为不存在,不必列出来;列出来也无所谓。
2、用户如何分配这些存储器资源,即关于资源分配情况的声明。用户根据自己的需要,结合芯片的要求,把各种数据分配到适当种类、适当特点、适当长度的存储器区域
1 资源清单
如上文所述,CMD文件包含两大内容,首先就是存储器的资源清单,或者说,系统中(电路板上)可用的存储器资源。
TI规定,CMD文件的资源清单用关键字“MEMORY”作为标识,具体内容写在后面的大括号{ }里面。如下面的形式:
MEMORY
{
PAGE 0:
xxx : org = 0x1234 , length =0x5678 /*This is my house.*/
PAGE 1:
aaa : org = 0x1357 , length =0x2468 /*My home here.*/
}
其中,MEMORY,PAGE n,org,length,包括冒号、等于号、花括号,都是关键字符,必不可少。
PAGE n表示把可用的资源空间再划分成几个大块,最多允许分256块,从PAGE0到PAGE 255。
很多关键字,还允许有别的写法,比如“org”可以写为“o”,“length”可以写为“len”。这些规定和其他细节,可以去查阅TI的pdf文档。
2 资源的分配
首先,SECTIONS,PAGE,包括花括号、冒号,都是关键字符。注意:SECTIONS字符是复数形式。在花括号内,每一行最左侧的“.vectors”、“.text”、“.cinit” 、“.bss”、“.stack”这些名称,包括小数点,都是TI默认的关键字符,只有“.extdata”是用户自己定义的名称。另外,“VECS”、“PROG”、“SARAM”、“B0B1”、“ExtSRAM”必须是在MEMORY里声明过的资源名称。除此以外,有些字符也允许有别的写法。这样就把这些内存用一个个的符号表示,使用此符号即可。
在CCS编程中,如果我们不指定变量/代码的存放位置,编译器会自动的给变量/代码分配一个位置,但是如果有的时候需要把变量放在一个特定的空间内,我们应该如何操作呢,CCS提供了如下的两个指令
#pragma CODE_SECTION
#pragma DATA_SECTION
其中data_section是针对数据空间的,
#pragma DATA_SECTION(bufferB, ”my_sect”)
char bufferB[512];
在.cmd文件中建立对应的section就可以使用了.
#pragma CODE_SECTION是针对代码空间的。
#pragma CODE_SECTION(dragon_update,"ramfuncs");
Uint16 dragon_update(UPDATE_SOURCE_TYPE *update_flag){}
然后再在cmd文件中指定这两个section的位置就可以了。
如果想在汇编中指定段,使用方法,在代码前用.sect "XXX"开始则标示接下来的一段代码都是在xxx的代码段中。
二、DSP的可执行文件的结构
段分为两类:已初始化段(Initialized Sections)和未初始化段(Uninitialized Sections)。
注意:
1.主要有PAGE0和PAGE1,PAGE0上的memory可以覆盖到PAGE1 等
2. origin和length都是22bit的常数,在以前的一些dsp由于它的地址总线是16bit的,所以相应的origin和length只能为16bit的常数。
3. Sections以name开始,name就是定义的输出段。
4. load的格式有: load=allocation or allocation or >allocation
5. run的格式有:run =allocation or run>allocation
TI段的定义:
已初始化的段:.text,.cinit,.const,.econst,.pinit和.switch..
.text:所有可以执行的代码和常量
.cinit:全局变量和静态变量的C初始化记录,包含未用const声明的外部(extern)或静态(static)数据表
.const:包含字符串常量和初始化的全局变量和静态变量(由const)的初始化和说明
.econst:包含字符串常量和初始化的全局变量和静态变量(由farconst)的初始化和说明,与.const不同的是.const分配范围被限制在低64K 16位数据区,而.econst的分配范围是4M 22位数据区
.pinit:全局构造器(C++)程序列表
.switch:包含switch声明的列表
非初始化的段:.bss,.ebss,.stack,.sysmem,和esysmem.(更好的理解就是,这些段就是存储空间而已)
.bss: 为全局变量和局部变量保留的空间,在程序上电时.cinit空间中的数据复制出来并存储在.bss空间中。
.ebss:为使用大寄存器模式时的全局变量和静态变量预留的空间,在程序上电时,cinit空间中的数据复制出来并存储在.ebss中,与.bss不同的是.bss分配范围被限制在低64K 16位数据区,而.ebss的分配范围是4M22位数据区
.stack:为系统堆栈保留的空间,用于和函数传递变量或为局部变量分配空间。
需要注意的是,.stack段只能使用低64K地址的数据存储单元,因为CPU的SP寄存器是16位的,它无法读取超过64K的地址范围。此外,编译器无法检查栈的溢出错误(除非我们自己编写某些代码来检测),这将导致错误的输出结果,所以要为栈分配一个相对较大的存储空间,它的默认值是1K字。改变栈的大小的操作可以通过编译器选项--stack_size来完成。
.sysmem:为动态存储分配保留的空间。如果有宏函数,此空间被宏函数占用,如果没有的话,此空间保留为0
.esysmem:为动态存储分配保留的空间。如果有far函数,此空间被相应的占用,如果没有的化,此空间保留为0.
已初始化段相当于通常系统的TEXT, DATA, BSS, 只不过ti将有些段进行了进一步的深化。而未初始化段中,堆对应的是sysmem(esysmem), 栈对应stack。 bss和ebss在任何系统中都是必须的,但是可能其它系统把这些细节掩盖了,比如arm直接让__main操作了。TI也有相应的系统函数完成上述的数据复制搬运工作(主要是一些有初始化值的常量,存储的时候需要将这些值保存在flash中,运行的时候需要在ram中),这个函数就是cint00函数。
我们可以定义自己的段么?可以,使用如下语句:
1. #pragma CODE_SECTION(symbol, "section name");
2. #pragma DATA_SECTION(symbol, "section name");
symbol——符号,可以是函数名/变量名
sectionname——自定义的段名
CODE_SECTION用来定义代码段
DATA_SECTION用来定义数据段
注意:
不能再函数体内声明#pragma;
必须在符号被定义和使用之前声明#pragma
(__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()。所以说,前者是库函数,后者就是我们自己编写的main()主函数)
一旦编译器生成的这些段,连接器会从各个源文件中取出这些段,并结合它们来创建一个输出文件。连接器命令文件(.cmd)就是用来告诉连接器去哪里找这些段的。初始化段必须分配到非易失性存储器,如flash/ROM,当电源被撤除时,程序不会消失。未初始化的段可以被分配到RAM中,因为它们是在代码执行期间被初始化的。
当需要把程序从flash复制到RAM里时,一般各个段分配参考如下:(把程序划分成各个段应该是方便bootloader对程序的搬移。一开始都在flash中,经过bootloader把各段放入指定的位置。这句话不知正确否)
.cinit
Flash
.cio
RAM
.const
Flash
.econst
Flash
.pinit
Flash
.switch
Flash
.text
Flash
.bss
RAM
.ebss
RAM
.stack
Lower 64Kw RAM
.sysmem
RAM
.esysmem
RAM
.reset
TI给的例程,在CMD一开始的NOTES里有这么几句话,
Notes:
Memory blocks on F28335 are uniform(ie same
physical memory) in both PAGE 0 andPAGE 1.
That is the same memory region shouldnot be
defined for both PAGE 0 and PAGE 1.
Doing so will result in corruption ofprogram
and/or data.
程序空间(一般为PAGE0)和数据空间(一般为PAGE1)是统一编址的,即28335的这4M内存既可以做程序空间也可以做数据空间,看你CMD中怎么分配了,但是CMD中指定的一段连续空间只能指定为程序空间或者指定为数据空间,不可以既指定成程序空间又指定成数据空间,即CMD中指定的每一段连续空间都不可以重叠。PAGE0作为程序空间,PAGE1作为数据空间只是一种习惯做法,你指定成其他数字也无妨,PAGE最多可以指定256个(0~255),每个PAGE的大小没有限制,不超出DSP内存大小就行,一般也用不到256个页,PAGE只是提供逻辑上的一种划分,让我们在写程序的时候更有条理。