DSP芯片以其极高的精度,性能及运算速度等无与伦比的优点使它得到了十分广泛的应用,TMS320F2812是TI公司生产的到目前为止用于数字控制领域的最好的DSP芯片,在对它的仿真开发过程中,编译器生成的代码和数据要由链接器分配到合适的存储空间,通常链接器的命令文件.cmd文件是由用户自己编写的,编写不当,就会使仿真开发不能进行,TI公司虽然在《TMS320C28x Optimizing C/C++ Compiler User’s Guide 6》和《TMS320C28x Assembly Language Tools User’s Guide 6》做了介绍,但内容却比较散乱而且要求读者对整个开发系统要有较全面的认识,这对于初学者来说是比较困难的。下面以TMS320F2812芯片为例,结合具体的仿真调试实例加以说明使大家能够既快速又准确的掌握.cmd文件的分配方法。 1存储空间的配置TMS320F2812的DSP存储器分为三个独立选择的空间-程序空间、数据空间和I/O空间,其中程序存储器存放待执行的指令和执行中所用的系数(常数),可使用片内或片外的RAM、ROM或EPROM等来构成;数据存储器存放指令执行中产生的数据,可使用片内或片外的RAM和ROM来构成;I/O存储器存放与映象外围接口相关的数据,也可以作为附加的数据存储空间使用。表1是TMS320F2812的存储空间分布。
2CMD文件的分配方法TI公司新的汇编器和链接器创建的目标文件采用一种COFF(通用目标文件格式),该目标文件格式更利于模块化编程,为管理代码段和目标系统存储器提供了强有力和灵活的编程方法。用户可以通过编写链接命令文件(.cmd文件)将链接信息放在一个文件中,以便在多次使用同样的链接信息时调用。在命令文件中使用两个十分有用的伪指令MEMORY和SECTIONS,来指定实际应用中的存储器结构和进行地址的映射。Memory用来指定目标存储器结构,Memory下可以通过PAGE选项配置地址空间,链接器把每一页都当作一个独立的存储空间。通常情况下,PAGE0代表程序存储器用来存放程序,PAGE1代表数据存储器,用来存放数据。由编译器生成的可重定位的代码和数据块叫做“SECTIONS”(段),SECTIONS用来控制段的构成与地址分配。对于不同的系统配置,“SECTION”的分配方式也不相同,链接器通过“SECTIONS”来控制地址的分配,所以“SECTIONS”的分配就成了配置.cmd文件的重要环节。以下是对“SECTIONS”的定义及分配的详细介绍。(1)被初始化的“SECTIONS”(包括数据表和可执行代码).text它包括所有的可执行代码和常数,必须放在程序页;.cinit它包括初始化的变量和常量表,要求放在程序页;.pinit它包括全局构造器(C++)初始化的变量和常量表,要求放在程序页;.const它包括字符串、声明、以及被明确初始化过的全局和静态变量,要求放在低地址的数据页;.econst它是在使用大存储器模式时使用的,包括字符串、声明、以及被明确初始化过的全局变量和静态变量,可以放在数据页的任何地方。.switch它包括为转换声明设置的表格,可以放在程序页也可以放在低地址的数据页。(2)未被初始化的“SECTIONS”(为程序运行中创建和存放的变量在存储器中保留空间).bss它为全局变量和静态变量保留空间。在程序开始运行时,C导入路径把数据从.cinit节复制出去然后存在.bss节中,要求放在低地址的数据页;.ebss它是在远(far)访问(只用于C)和大存储模式下使用,它为全局变量和静态变量保留空间。在程序开始运行时,C导入路径把数据从.cinit段复制出去然后存在.ebss节中,可以放在数据页的任何地方;.stack为C系统堆栈保留空间,这部分存储器为用来将声明传给函数及为局部变量留出空间,要求放在低地址的数据页;.system动态存储器分配保留空间。这个空间用于malloc函数,如果不使用malloc函数,这个段的大小就是0,要求放在低地址的数据页;.esystem动态存储器分配保留空间,这个空间用于外部malloc函数,如果不使用外部malloc函数,这个段的大小就是0,可以放在数据页的任何地方。 3举例说明.cmd文件的分配方法 以下是仿真调试串行通信接口SCI时的.cmd文件的分配,已经在TMS320F2812仿真调试中得到了很好的应用。MEMORY {PAGE0:/*ProgramMemory*/ RAMH0:origin=0x3F8000,length=0x001000 RAML0:origin=0x008000,length=0x001000 RAML1:origin=0x009000,length=0x001000 ROM:origin=0x3FF000,length=0x000FC0 RESET: origin=”0x3FFFC0”,length=0x000002M VECTORS:origin=0x3FFFC2,length=0x00003EM PAGE1:/*DataMemory*/ RAMM0:origin=0x000000,length=0x000400 RAMM1:origin=0x000400,length=0x000400 RAMH0:origin=0x3F9000,length=0x001000,,,} SECTIONS{/*Allocateprogramareas:*/ .cinit:>RAMH0PAGE=0 .pinit:>RAMH0PAGE=0 .text:>RAMH0PAGE=0 .reset:>RESET,PAGE=0,TYPE=DSECT Vectors :>VECTORS,PAGE=0,TYPE=DSEC /*Allocateuninitalizeddatasections:*/ .stack:>RAMM0PAGE=1 .ebss:>RAMH0PAGE=1 .esysmem :>RAMH0PAGE=1 .econst :>RAMM1PAGE=1 .switch :>RAMM1PAGE=1 ,,,} 为充分利用18k×16位的SARAM,本例将高地址的8k×16位的H0 SARAM区分成两部分,一部分用做存放程序放在PAGE0里,一部分用做存放数据放在PAGE1中以达到合理的分配;对实际仿真调试过程中的外围帧frame0,frame1,frame2等的分配因为篇幅问题就不做具体介绍了。 4查看段的分配及使用情况 在cmd文件中包括各种各样的链接器选项,每种选项代表不同的含义。其中,使用-m选项可以创建一个扩展名为.map的链接器(存储器)分配映射文件,其语法为:-m filename(文件名)。链接器的map文件描述以下内容:存储器结构输入和输出段的定位在重新定位后外部符号的地址 通过map文件可以查看各段的分配情况,包括段的起始地址,使用的字节数等配合cmd文件的使用,可确定各个段的使用情况,从而保证程序的正常运行和最小的空间使用。 5 VisualLinker可视化链接器 TI公司出品的DSP软件开发环境CCS还提供了一种可视化生成存储器配置文件的工具:VisualLinker可视化链接器。如果程序原来包含了一个链接器命令文件(.cmd文件),则当创建可视化链接文件的时候,原来cmd文件中的内存配置仍然会被使用。如果读者想修改内存配置,双击.rcp文件就会在CCS中打开可视化链接器的图形界面,调整每个内存模块的大小,直到认为合适,然后只需要重新连编,程序即可生成新的输出文件,重复上面的步骤,直到出现满意的结果。 6总结 不同的DSP芯片内集成的存储器大小各异,但其配置方式是类似的。大家可通过查阅DSP芯片的数据手册,了解芯片内部存储空间大小。在实际的配置过程中,可根据开发程序的实际代码,正确的划分程序和数据空间中各段的大小,使其空间配置达到最优。 DSP的命令文件CMD2007-07-15 18:51一、在给出命令文件示例前,首先介绍该文件常用到的几种伪指令。.cinit 程序编译连接时该标号表示部分存放已明确初始化的全局变量和静态变量;.const 程序编译连接时该标号表示部分存放已明确初始化的字符串常量、全局变量和静态变量;.switch 程序编译连接时该标号表示部分存放对于大型的switch语句的跳转表;.text 程序编译连接时该标号表示部分存放可执行代码和浮点数常量;.bss 该标号表示部分用于存放没有初始化的全局变量和静态变量;.stack 用该标号定义软件堆栈。 命令文件名后缀为.CMD的文件实现对程序存储器空间和数据存储器空间的分配。该文件经常用到的伪指令有MEMORY伪指令和SECTIONS伪指令。 当然用户也可以用C语言编程实现所需的功能。 MEMORY伪指令用来表示实际存在的目标系统中可被使用的存储器范围,每个存储器范围都有名字、起始地址和长度。MEMORY伪指令的一般语法为:MEMORY{ PAGE0: name[(attr)]: origin=constant, length=constant; PAGE1: name[(attr)]: origin=constant, length=constant;}PAGE 标示存储器空间.用户可以规定多达255页,通常PAGE0规定程序存储器,PAGE1规定数据存储器.name 命名存储器范围.存储器可以是1~8个字符,在不同页上的存储器范围可以具有相同的名字,但在一页之内所有的存储器范围必须具有唯一的名字且必须不重叠.attr 规定与已命名范围有关的1~4个属性.未规定属性的存储器具有所有4个属性.有效的4个属性包括: R 规定存储器只读; W 规定存储器只写; X 规定存储器可以包含可执行代码; I 规定存储器可以被初始化.origin 规定存储器范围的起始地址.length 规定存储器范围的长度. SECTIONS 伪指令的作用是:描述输入段怎样被组合到输出段内;在可执行程序内定义输出段;规定在存储器内何处存放置输出段;允许重命名输出段.SECTIONS伪指令的一般语法是: SECTIONS{ name:[property, property, property, …] name:[property, property, property, …] name:[property, property, property, …] } 每一个以name(名字)开始的段说明定义了一个输出段.在段名之后是特性列表,定义段的内容以及它们是怎样被分配的.特性可以用逗号来分开,段可能具有的特性是: 装载位置,它规定段装载在存储器内何处; 运行位置,它定义段在存储器内何处运行; 输入段, 它定义组成输出段的输入段; 段类型, 它定义特定段类型的标志; 填充段, 它定义用于填充未初始化空间的数值.二、下面是一个简单实用的命令文件示例。-stack 40/*---------------------------------------*//* 命令文件-存储空间F2407 *//*---------------------------------------*/ MEMORY{ PAGE0: VECS :origin=0h, length=40h /* 程序复位 */ PVECS :origin=40h, length=70h /* 外围模块中断向量 */ PROG :origin=0b0h, length=7F50h /* 在片FLASH */ PAGE1:MMRS :origin=0h, length=05Fh /*MMRS */ B2 :origin=0060h, length=020h /*DARAM B2块 */ B0 :origin=0200h, length=100h /*DARAM B0块 */ B1 :origin=0300h, length=100h /*DARAM B1块 */ SARAM :origin=0800h, length=0800h /*SARAM块 */ EXT :origin=8000h, length=8000h /*外部存储器 */}/*-----------------------------------------*//* SECTIONS ALLOCATION *//*----------------------------------------*/SECTIONS{ .reset :{ } 〉 VECS PAGE 0 /* 复位中断向量表 */ .vectors :{ } 〉 VECS PAGE 0 /* 中断向量表 */ .pvecs :{ } 〉 PVECS PAGE 0 /* 外围模块中断向量表 */ .text :{ } 〉 PROG PAGE 0 /* 代码 */ .cinit :{ } 〉 PROG PAGE 0 .bss :{ } 〉 SARAM PAGE 1 /* 块 B2 */ .const :{ } 〉 SARAM PAGE 1 /* 块 B2 */ .stack :{ } 〉B1 PAGE 1 /* 堆栈—40个单元 */ ”hello,DSP world”工程实例 本人所用版本CCS2.2 一个完整的工程,至少需要四个文件构成: 1、以.cmd结尾的命令文件,用来分配存储空间。 2、C语言系统库rts2xx.lib。系统库包括了编译器所提供的所有功能:初始化C语言环境(入口地址为—_c_int0),设置堆栈,标准C的函数库等,工程中还可以添加其他的库文件(.lib)。 3、有且必须有一个含有main()函数的C语言源程序文件(.c)。系统库初始化完毕后,将控制权交给main()函数。 4、矢量跳转文件,通常为汇编文件(.asm)形式。此文件需要准确的定位在函数起始地址,其内容是汇编文件中的无条件跳转语句“B”。 程序执行的常规流程为:矢量表的第一条指令可设置为“B _c_int0”,从而在上电复位后,把控制权交给系统库,系统库初始化完毕后,把控制权交给main()函数。 注意: 1、不用添加头文件(.h),编译时,根据设定的路径,头文件会自动扫描进工程。 “hello,DSP world”整个工程流程: 建立前必须保证CCS setup已经设置成功,并完成驱动设置,连接并打开了仿真器和目标板。(我是这样做的) 1)打开CCS2.2,单击Project-New,对话框中输入工程名“hello”,初学建议工程文件保存在CC安装目录下的myprojects目录中。 2)单击File-New-Source File,建立三个文本编辑窗口,分别编写cvextors.asm roam.cam和hello.c三个文件。 3)单击Projext-Add Files to Projects,添加三个文件到工程中。 4)添加库文件rts2xx.lib(位于目录C: ic2400cgtoolslib), 这四个文件构成了工程文件hello.pjt。 5)单击Project-Build Options,选择Linker,在Output Filename[-o]中设置生成的二进制文件名字hello.out,确定。(里面还有一个compiler签页,内有Assembly,可以设置include路径,我调试时,仍按照内为空白) 6)单击Project-Build,会在工程文件DEBUG子目录下生成hello.out文件。 7)单击file-load program,将hello.out文件下载到LF2407目标板,下载程序前,LF2407必须设置成MP方式,将引脚mp//mc拉高。 8)单击debug-run或者F5,程序在DSP中运行,窗口Stdout中显示“hello, DSP world”。 恭喜成功!! 附带程序代码: .ref _c_int0 ;.ref前需要加一个空格 .sect “vectors” ;.sect前需要加一个空格 rset: B _c_int0 int1: B int1 int2: B int2 int3: B int3 int4: B int4 int5: B int5 int6: B int6 -stack 512 -m hello.map MEMORY { PAGE 0: VECS :origin=0x0000,length=0x0040 flash : origin=0x1044,length=0x6f00 SARAM_P : origin=0x8000,length=0x0800 PAGE 1: B0B1 : origin=0x0200,length=0x0200 B2 : origin=0x0060,length=0x0020 SARAM_D : origin=0x0800,length=0x0800 RAM : origin=0xa000,length=0x7ff0 } SECTIONS { vectors : load=VECS PAGE 0 .text : load=flash PAGE 0 .cinit : load=flash PAGE 0 .switch : load=flash PAGE 0 .const : load=RAM PAGE 1 .bss : load=RAM PAGE 1 .stack : load=B0B1 PAGE 1 #define WDCR (*((volatile unsigned int*)0x7029)) #define WDKEY (*((volatile unsigned int*)0x7025)) #include void main(void) { WDCR=0x0068; WDKEY=0x0055; WDKEY=0x00aa; printf(“hello,DSP world
”); for(;;); } 1.cmd文件写法可以参照CC‘C2000帮助文件中的例子,还可以查阅TI文档spru024D的2.8.3.2.中断向量表的第一条语句应该跳转到_c_int0对于这点我最初不是很明白,因为我看到的程序都是以main开始的.后来逐渐明白了,_c_int0是程序真正开始的地方,只是这个开始不是开发者写出来的,而是编译器自动为我们做好的,你要配合它做的是就是在Build Option中对linker的C Initialization的选项选择ROM Autoinitialization Model或RAM Autoinitialization Model,而不是汇编中的No Autoinitialization,开发者的程序要以main函数开始,初始化结束后会跳转到main函数.在反汇编代码中可以看到这些过程.两种初始化的方式详见上面文档的同一节. DSP CMD 文件写法 一、在给出命令文件示例前,首先介绍该文件常用到的几种伪指令。.cinit 程序编译连接时该标号表示部分存放已明确初始化的全局变量和静态变量;.const 程序编译连接时该标号表示部分存放已明确初始化的字符串常量、全局变量和静态变量;.switch 程序编译连接时该标号表示部分存放对于大型的switch语句的跳转表;.text 程序编译连接时该标号表示部分存放可执行代码和浮点数常量;.bss 该标号表示部分用于存放没有初始化的全局变量和静态变量;.stack 用该标号定义软件堆栈。 命令文件名后缀为.CMD的文件实现对程序存储器空间和数据存储器空间的分配。该文件经常用到的伪指令有MEMORY伪指令和SECTIONS伪指令。 当然用户也可以用C语言编程实现所需的功能。 MEMORY伪指令用来表示实际存在的目标系统中可被使用的存储器范围,每个存储器范围都有名字、起始地址和长度。MEMORY伪指令的一般语法为:MEMORY{ PAGE0: name[(attr)]: origin=constant, length=constant; PAGE1: name[(attr)]: origin=constant, length=constant;}PAGE 标示存储器空间.用户可以规定多达255页,通常PAGE0规定程序存储器,PAGE1规定数据存储器.name 命名存储器范围.存储器可以是1~8个字符,在不同页上的存储器范围可以具有相同的名字,但在一页之内所有的存储器范围必须具有唯一的名字且必须不重叠.attr 规定与已命名范围有关的1~4个属性.未规定属性的存储器具有所有4个属性.有效的4个属性包括: R 规定存储器只读; W 规定存储器只写; X 规定存储器可以包含可执行代码; I 规定存储器可以被初始化.origin 规定存储器范围的起始地址.length 规定存储器范围的长度. SECTIONS 伪指令的作用是:描述输入段怎样被组合到输出段内;在可执行程序内定义输出段;规定在存储器内何处存放置输出段;允许重命名输出段.SECTIONS伪指令的一般语法是: SECTIONS{ name:[property, property, property, …] name:[property, property, property, …] name:[property, property, property, …] } 每一个以name(名字)开始的段说明定义了一个输出段.在段名之后是特性列表,定义段的内容以及它们是怎样被分配的.特性可以用逗号来分开,段可能具有的特性是: 装载位置,它规定段装载在存储器内何处; 运行位置,它定义段在存储器内何处运行; 输入段, 它定义组成输出段的输入段; 段类型, 它定义特定段类型的标志; 填充段, 它定义用于填充未初始化空间的数值.二、下面是一个简单实用的命令文件示例。-stack 40 PVECS :origin=40h, length=70h PROG :origin=0b0h, length=7F50h PAGE1:MMRS :origin=0h,