TMS320C54x C编译器的使用
1. 前言
TMS320C54x 是TI公司针对通信应用推出的中高档16位定点DSP系列器件。该系列器件功能强大、灵活,较之TMS320C2xx ,具有以下突出优点:
(1)速度更快:40~100MIPS;
(2)指令集更为丰富;
(3)更多的寻址方式选择;
(4)两个40位的累加器;
(5)硬件堆栈指针;
(6)支持块重复和环型缓冲区管理。
TMS320C54x 的应用一般较为复杂,因此,完全采用汇编语言进行编程将是一件很困难、也很低效的事情。而C语言以其灵活性、易移植性,已成为单片机开发的一种趋势,对于更为复杂的DSP应用来说,更是如此。
TI公司为TMS320C54x提供了功能强大的C编译器,支持优化功能。下面就对使用TMS320C54x C编译器中的一些体会介绍给读者。作者在实践中使用的TMS320C54x C编译器的版本为1.10版,以下的讨论都基于该版本。
2. C语言的移植
在进行复杂算法开发时,一般的做法是先在PC上用高级语言(如C语言)进行仿真,然后再移植到DSP平台中。考虑到效率问题,可进一步进行手工汇编的调整。编译器的工作方式大致可分为两类:一类直接由高级语言产生目标代码;另一类则是先生成中间的汇编代码,再汇编成目标代码。TI公司提供的C编译器属于后者。这样,用户可方便地根据中间的汇编代码进行手工调整和改写。
在进行C语言移植时,涉及到两个问题:一是库函数的使用,二是字长问题。TI的TMS320C54x C编译器提供符合ANSI C的函数库。至于字长,要取决于硬件机器字长和操作系统,但一般来说,在不同的系统中,short型为16位,long型为32位,而int型却有所不同。如,在VC环境中,int型为32位,而对于TMS320C54x C编译器来说,int型则为16位。因此,在将C源程序由仿真环境向TMS320C54x平台移植时,应根据各变量的取值范围,尽可能地将int型归类为short型或long型。
3. 段的分配
虽然,C语言是一种相对高效的高级语言,并且TI提供的C编译器还结合硬件特点支持三级优化功能,但生成的汇编代码效率仍可能会不尽人意。如预使用环型缓冲区管理功能,这就要求该缓冲区应被定位到相对特定的位置。因此,用户对C编译器究竟是如何进行存储分配的,应有一定的了解。
目标码是以段为单位组织的。这里,结合C语言的特点,简单介绍一下几种常用段的使用。
总的来说,可将所有的段分为两类:已初始化段和未初始化段。
已初始化段中包含数据表和可执行代码,常用的有3个:.text 段、.cinit段和.const段。其中,.text 段中包含所有可执行的代码以及常量;.cinit段中包含未用const声明的外部(external)或静态(static)数据表;.const段中则包含已用const声明的外部或静态数据表以及字符串常量。
未初始化段在存储器(通常为RAM)中保留空间,用于程序运行时创建和存储变量,常用的有两个:.bss段和.stack段。其中,.bss段用于为全局和静态变量保留空间,在程序开始执行时,由C引导程序将.cinit段中的已初始化数据复制到.bss段中。.stack段用作C的系统堆栈,向被调函数传递参数,并为局部变量分配空间。
在上述五个常用段中,.text 段和.cinit段被固定连接至程序空间,存储器类型可以是ROM或RAM(一般为ROM,具体取决于编译时RAM或ROM方式的选择);.bss段和.stack段则被固定连接至数据空间,存储器类型只能是RAM。
而.const段的使用则较为灵活。.const段被固定连接至数据空间,但存储器类型可以是ROM或RAM。这就有别于.cinit段:.cinit段被连接至程序空间,程序执行时,再被复制到数据空间中的.bss段中。这样,一张未用const声明的数据表要同时占用程序(.cinit段)和数据空间(.bss段)的一部分。与之相比较,如果系统支持数据ROM,则该数据表改用const声明后,只需占用数据空间(.const段)的一部分。
如果程序较为复杂,由C编译器生成的.bss段会比较庞大,这种情况下对.bss段进行特定操作就比较困难。如将几个常用的数据表连接至片内RAM,而非片外RAM;或需对某个数据表进行特殊寻址。这时,即使目标系统不支持数据ROM,也有必要使用.const段。这就需要在连接时对.const段作如下规定:在程序空间中加载,而在数据空间中运行。C引导程序对其处理的方法类似与.cinit段,将其从程序空间复制到数据空间。但在使用1.10版的TMS320C54x C编译器时发现,该版本的引导程序并不支持.const段的加载。通过阅读C编译器所带的库函数的源代码,参照引导程序对.cinit段的处理,修改了该段引导程序,使之支持对.const段的加载。
除上述五个常见段外,TMS320C54x C编译器还可能产生两个段:.switch段和.sysmem段。其中,.switch段包含用于开关(switch)语句的数据表;而.sysmem段则用于动态存储分配。如果直接使用了汇编,还可能用到.data段以及自命名的已初始化(代码)段和未初始化(变量)段。
4. C与汇编语言的混用
在C语言中使用汇编语言,可采取两种方式:一是调用汇编语言子程序;二是使用嵌入汇编。
4.1 调用汇编语言子程序
在TMS320C54x C程序中调用汇编语言子程序,必须遵循其函数调用规则和寄存器使用规则。
TMS320C54x C编译器在处理函数调用时,关于如何传递入口参数以及如何返回出口参数制定了一套严格的规则。详细内容请参见文献[2]。
TMS320C54x 共有8个通用寄存器(AR0~AR7),其中AR1、AR6和AR7可能会被编译器所使用,其中AR1一般用作局部变量暂存,而AR7常被用作全局变量。所以,如果所调用的汇编语言子程序中使用了这3个寄存器,应作上下文保护。
TMS320C54x C编译器支持寄存器变量的使用,最多两个,分别为:AR1和AR6。通常的用法是创建全局寄存器变量。较之一般存储单元,C54x 对寄存器的存取速度最快。因此,对一些使用频率很高的变量来说,将其声明为寄存器变量,可显著提高处理速度。
如果使用了全局寄存器变量,应采用-r选项对所有代码(包括库函数)进行重新编译,以禁止C编译器对AR1或AR6的使用。
4.2 嵌入汇编
虽然TMS320C54x C编译器提供了三级优化功能,但作者实际使用的体会仍不够满意。而在C中调用汇编语言子程序,又要求严格遵循其参数传递规则。由于TMS320C54x C编译器支持在C源代码中直接用asm语句嵌入汇编,故在C编译器所产生的中间汇编代码的基础上,可局部使用嵌入汇编。这样,一方面可大大提高一些频繁使用的代码段的效率,另一方面,又不用改变原来的函数框架,也便于对照仿真结果进行调试。
在进行嵌入汇编时,要特别注意流水线的冲突。TMS320C54x 采用6级流水线结构,在出现流水线冲突的地方,可插入空操作(nop)指令来解决。常见的易产生流水线冲突的指令如STLM、ADDM等。
关于1.10版,还有一个值得注意的问题,即如何由两个短整数获得32位的长整数乘积。
如:
short m1,m2;
long result;
result = (long) (m1 * m2); (1)
result = ((long) m1 * (long) m2); (2)
对于(1),1.10版的编译器只是将乘积的低16位存入result;而对于(2),得到的是一个32位的长整数乘积,但却是调用了两个长整数相乘的库函数(L$$MPY)而得到的,这对于含有大量乘加运算的DSP应用来说,效率上难以令人接受。为此,采用嵌入汇编可方便地解决这个问题。
5.CCS的安装
(1) 安装Setup CCS软件
(2) 打开Setup CCS.exe文件如下图所示
图3-1
中间一列是可安装的硬件设备,选中所需设备后双击打开
图3-2
选择Bord Proparties 项,将I/O Port 的值改为0x378,
图3-3
再选择Processor Configuration 项,在左边的Available Processors方框中选择所需的处理器,然后按Add Single 键添加后完成对处理器的安装设置.
6.CCS的使用(以CCS C5000 1.2为例)
图3-4
1. 创建 .gel 文件
.gel文件是DSP芯片初始化程序.其中包含分配内存、设置引脚电平等内容.要注意的是,不同的DSP芯片所使用的.gel文件也不相同,如果使用的.gel文件与所用的芯片不符合,则会在将源程序生成的.out文件导入DSP芯片时发生错误.
具体做法为:先打开如5-4图中左上方框内的GEL files ①文件夹,将原有默认的.gel文件删除后用鼠标右键点击GEL files 文件夹,选择Load GEL项,然后将与所使用的DSP型号相匹配的.gel文件导入.
以下是‘C5402的.gel文件
StartUp()
{
GEL_Reset(); /* reset the target */
GEL_MapOn(); /* enable the memory map */
GEL_MapReset();
/* Uncomment for extended addressing support
GEL_XMDef(0, 0x1E, 1, 0x8000, 0x7F);
GEL_XMOn();
GEL_MapAdd(0x18000,0,0x8000 ,1,1);
*/
GEL_MapAdd(0x0000, 0,0x10000,1,1); /* int vectors */
GEL_MapAdd(0x0000, 1,0x10000,1,1); /* data space */
GEL_MapAdd(0x0000, 2,0x0010 ,1,1); /* uart */
PMST = 0xffa0; /* on-chip program */
SWWSR = 0x7492; /* 2 wait states */
BSCR = 0x0002; /* bank-switch */
*(int*)0x0004@io = 1; /* enable external ram at 0x8000 */
}
2. 建立.mak文件
.mak文件是一项目文件.
具体做法为:点击工具栏中的project④项,在下拉菜单种选择New,然后在保存时将文件属性改为.mak即可.
3.将源程序生成为.out文件
CCS可以使用C语言源程序(*.C)和汇编语言源程序(*.asm)两种.在将源程序写入DSP中前必须将其生成为.out文件.
具体做法为: 点击工具栏中的project项,在下拉菜单种选择Add Files to Project,选择所需的源程序,便将源程序导入到.mak下的Source 文件夹中 ②.双击后可在右上方框中显示该程序内容.点击控制栏中Rebuild⑤,对源程序进行编译,并将结果显示在左下方框中.如所编的源程序中存在错误,则会出现”The program contains compile error Do you wish to continue linking?”的对话框,取消后可根据方框中的提示对源程序进行修改. 编译通过后,在源文件同一文件加下会生成一源文件同名的.out和.obj文件.out文件使用以写入DSP的输出文件,而.obj则是一链接文件.
4.将.out文件写入DSP
在工具栏中点击File项,在下拉菜单种选择Load Program,选中已生成的.out文件,便将程序写入DSP中.
5.运行程序
在控制栏中选择Register Window⑦则会出现如图5-4中右下方框,这是用以显示各寄存器的状态的.在此框中单击鼠标右键会出现一可选菜单,选择Edit Register项,在弹出的控制框中将PC(程序计数器)值改为0x0080(程序默认入口).点击控制栏中的Run⑥,程序运行.
如希望程序以单步执行可点击step by step12,点击halt13则程序暂停。
6.复位与重新开始
在工具栏中选择Debug⑧,在下拉菜单中选Reset DSP或Restart.
当重新写入新的程序时应先将DSP芯片复位.当需要重新执行现有成序时可使用Restart
将程序计数器指针复原.
7.更改寄存器和存储器的值
在硬件调试过程中, 更改寄存器和存储器的值是一种非常有效的手段.工具栏中的Edit⑨中有Memory和Edit register两项可实现该功能.
8.查看各存储空间
软件调试中了解各存储空间的值是非常重要的.控制栏中的View memory⑩可查看Data,Program和I/O共192K字存储空间中各地址的内容.而View Stack11可查看堆栈中的值.
9.常见问题的处理
在编译时时常出现以下警告:” warning: entry point symbol _c_int00 undefined”,这可通过选择工具栏中的Project④项,在出现的下拉菜单中选择Option项,则会有如下控制框弹出如图 5-5所示控制框。
选择Linker项,在如图所示的下拉菜单中选择No Autoinitialization项,然后再Rebuild即可.
图3-5