1、第第4 4章章 DSP软件开发软件开发 4.1 程序定位方式的比较程序定位方式的比较 4.2 公共目标文件格式公共目标文件格式 4.3 DSP汇编程序简介汇编程序简介 4.4 DSP C语言程序基础语言程序基础 4.5 TI DSP软件开发平台软件开发平台 4.6 本章小结本章小结习题与思考题习题与思考题在第3章中,简单介绍了DSP软件开发的一般流程以及TI公司的eXpressDSP,从中我们可以看到,DSP软件的开发与普通软件的开发有很大的不同。本章重点讲述TI公司DSP的软件开发基础、软件开发方法以及软件的架构。一般来说程序是由代码和数据组成的。要运行的程序其代码和数据必须存放在CPU能够
2、寻址的存储空间里,而代码和数据是以代码块和数据块的形式存放的。代码块和数据块是程序的最小单元,一个代码块或数据块在存储空间中连续、顺序存放,不同的代码块或数据块,可以存放于不同的存储空间中。如何确定这些代码块或数据块在存储空间的地址,就是我们通常说的程序的定位。程序的定位有以下三种。4.1 程序定位方式的比较程序定位方式的比较1.编译时定位编译时定位编程时由ORG语句确定代码块和数据块的绝对地址,编译器以此地址为首地址,连续、顺序地存放该代码块或数据块,也就是说我们在编程的时候已经知道程序存放的大概位置。MCU系统常采用这种方式。这种定位方式属于绝对定位,其优点是简单、容易上手;缺点是程序员必
3、须熟悉硬件资源,模块化编程差且不支持工程化管理。2.链接时定位链接时定位编程时以“SECTIONS”伪指令区分不同的代码块或数据块。编译器每遇到一个“SECTIONS”伪指令,从0地址重新开始一个代码块或数据块。链接器将同名的“SECTIONS”合并,并按.cmd文件中的“SECTIONS”命令进行实际的定位。程序员在编写程序的时候并不知道程序最终的具体地址。具体的“SECTIONS”伪指令将在本章的后面部分加以介绍。DSP系统常采用这种方式。这种定位方式属于相对定位,其缺点是复杂灵活、上手较难;优点是程序员不必熟悉硬件资源,模块化编程强且支持工程化管理。3.加载时定位加载时定位编程、编译和链
4、接时均未对程序进行绝对定位。程序运行前,由操作系统对程序进行重新定位,并加载到存储空间中。例如,在Windows操作系统下运行一个应用软件,运行前我们并不知道应用软件将会被加载到内存的什么位置。PC机系统常采用这种方式。这种定位方式也属于相对定位,其缺点是必须要有操作系统支持;优点是模块化编程强且支持工程化管理。由上我们可以看出,DSP软件的定位方式介于MCU与GPP之间。下面我们来介绍“SECTIONS”伪指令的具体情况以及相关的知识。4.2.1 段段(sections)汇编器和链接器建立的目标文件,是一个可以在TMS320C55X器件上执行的文件。这些目标文件的格式称为公共目标文件格式,即
5、COFF(Common Object File Format)。由于COFF在编写汇编语言程序时采用代码和数据块的形式,会使模块化编程和管理变得更加方便。这些代码和数据段称为“段”。因为当编写一个汇编语言程序时,它可按照代码段和数据段来考虑问题。汇编器和链接器都有一些命令建立并管理各种各样的段。4.2 公共目标文件格式公共目标文件格式段(sections)是COFF文件中最重要的概念。每个目标文件都被分成若干个段。一个段就是最终在存储器映像中占据连续空间的一个数据或代码块。在编制汇编语言源程序时,程序按段组织,每行汇编语句都从属一个段,且由段汇编伪指令标明该段的属性。目标文件中的每一个段是相互
6、独立的。在编程时,段没有绝对定位,每个段都认为是从“0”地址开始的一块连续的存储空间,所以软件开发人员只需要将不同的代码块和数据块放到不同的段中,而无需关心这些段究竟定位于系统何处。采用段的优点是便于程序的模块化编程,便于工程化管理,可将软件开发人员和硬件开发人员基本上分离开。一般地,COFF目标文件都包含3个缺省的段:.text段:通常包含可执行代码;.data段:通常包含已初始化数据;.bss段:通常为未初始化变量保留存储空间。另外,汇编器与链接器允许程序员建立和链接自定义的段,这些段的用法与上述3个缺省段的用法相类似。所有的段可以分为两大类,即已初始化段和未初始化段。4.2.2 汇编器对
7、段的处理汇编器对段的处理汇编器对段的处理是通过段伪指令区分出各个段,且将段名相同的语句汇编在一起。每个程序都可以由几个段结合在一起形成。汇编器有5个伪指令支持该功能,分别是:.bss(未初始化段).usect(未初始化段).text(已初始化段).data(已初始化段).sect(已初始化段)如果汇编语言程序中一个段伪指令都没有用,那么汇编器把程序中的内容都汇编到.text段。1.未初始化段未初始化段未初始化段(Uninitialized Sections)由.bss和.usect伪指令建立。未初始化段就是TMS320C55X在目标存储器中的保留空间,以供程序运行过程中的变量作为临时存储空间使
8、用。在目标文件中,这些段中没有确切的内容,通常将他们定位到RAM区。未初始化段分为默认的和命名的两种,分别由.bss和.usect产生,其句法如下:.bss 符号,字数符号 .usect “段名”,字数其中:符号对应于保留的存储空间第一个字的变量名称,这个符号可让其它段引用,也可以用.global命令定义为全局符号;字数表示在.bss段或标有名字的段中保留多少个存储单元;段名程序员为自定义未初始化段起的名字。每调用.bss伪指令一次,汇编器在相应的段保留更多的空间;每调用.usect伪指令一次,汇编器在指定的命名段保留更多的空间。2.已初始化段已初始化段已初始化段(Initialized Se
9、ctions)由.text、.data和.sect伪指令建立,包含可执行代码或初始化数据。这些段中的内容都在目标文件中,当加载程序时再放到TMS320C55X的存储器中。每个初始化段都是可以重新定位的,并且可以引用其他段中所定义的符号。链接器在链接时自动处理段间的相互吸引。三种伪指令的句法如下:.text 段起点.data 段起点.sect “段名”,段起点其中,段起点是任选项。如果选用,它就是为段程序计数器(SPC)定义的一个起始值,SPC值只能定义一次,而且必须在第一次遇到这个段时定义。如果省略,则SPC从0开始。当汇编器遇到.text、.data或.sect命令时,将停止对当前段的汇编(
10、相当于一条结束当前段汇编的命令),然后将紧接着的程序代码或数据,汇编到指定的段中,直到再遇到另一条.text、.data或.sect命令为止。当汇编器遇到.bss或.usect命令时,并不结束当前段的汇编,只是暂时从当前段脱离出来,并开始对新的段进行汇编。.bss和.usect命令可以出现在一个已初始化段的任何位置上,而不会对它的内容发生影响。段的构成要经过一个反复过程。例如,当汇编器第一次遇到.data命令时,这个.data段是空的,接着将紧跟其后的语句汇编到.data段,直到汇编器遇到一条.text或.sect命令。如果汇编器再遇到一条.data命令,他就将紧跟这条命令的语句汇编后加到已经
11、存在的.data段中。这样就建立了单一的.data段,段内数据都是连续地安排到存储器中的。3.自定义段自定义段自定义段与缺省的.text、.data和.bss段一样使用,但与缺省段分开汇编,也可将初始化的数据汇编到与.data段不同的存储器中,或者将未初始化的变量汇编到与.bss段不同的存储器中。产生命名段的伪指令为:符号.usect“段名”,字数 .sect“段名”,段起点4.子段子段子段(Subsections)是大段中的小段。链接器可以像处理段一样处理子段。采用子段可以使存储器图更加紧密。子段的命名句法为:基段名:子段名子段也有两种,用.sect命令建立的是已初始化段,用.usect命令
12、建立的是未初始化段。5.段程序计数器段程序计数器(SPC)汇编器为每个段安排一个独立的程序计数器,即段程序计数器(SPC)。SPC表示一个程序代码段或数据段内的当前地址。开始时,汇编器将每个SPC置0,当汇编器将程序代码或数据加到一个段内时,相应的SPC增加。如果汇编器再次遇到相同段名的段,继续汇编至相应的段,且相应的SPC在先前的基础上继续增加。4.2.3 链接器对段的处理链接器对段的处理链接器在处理段的时候,有如下两个主要任务:(1)将由汇编器产生的COFF格式的OBJ文件作为输入块;当有多个文件进行链接时,将相应的段结合在一起,产生一个可执行的COFF输出模块。(2)将输出段分配到存储器
13、的指定地址,定义到实际的存储空间中。有两条链接命令支持上述任务:MEMORY命令定义目标系统的存储器配置图,包括对存储器各部分命名,以及规定它们的起始地址和长度;SECTIONS命令告诉链接器如何将输入段组合成输出段,以及将输出段放在存储器中的什么位置。子段可用来更精细地编排段。可用链接器的SECTIONS命令指定子段。若没有明显的子段,则子段将和具有相同基段名称的其他段结合在一起。链接器通过.cmd文件来获取上述的这些信息。并不是总需要使用这些链接器命令的。若不使用它们,则链接器将使用目标处理器默认的分配方法。如果使用链接器命令,就必须在链接器命令文件中进行说明。链接器还将检查各输出段是否重
14、叠、是否超界,避免了人工检查边界带来的隐患。图4-1表示了链接器对段的处理。在图4-1中,file1.obj和file22.obj已经被汇编,作为链接器的输入,都包含有缺省的.text、.data、.bss段。可执行的输出模式表示已合并的段。链接器先将两个文件的.text合并成一个,再合并.data和.bss段,将自定义的段放在最后。图4-1中的存储器图说明了如何将段放入存储器,在缺省的情况下,链接器从地址080h开始依次放入。图4-1 链接器对段的处理4.2.4 重新定位重新定位1.链接时重新定位链接时重新定位汇编器处理每个段都从地址0开始,而所有需要重新定位的符号(标号)在段内都是相对于地
15、址0的。事实上,所有段都不可能从存储器中地址0单元开始,因此链接器必须通过以下方法对各个段进行重新定位:(1)将各个段定位到存储器图中,每个段都从一个恰当的地址开始;(2)将符号的数值调整到相对于新的段地址的数值;(3)调整对重新定位后符号的引用。汇编器在需要引用重新定位的符号处都留了一个重定位入口。链接器在对符号重定位时,利用这些入口修正对符号的引用值。2.运行时重新定位运行时重新定位有时,希望将代码装入存储器的一个地方,而运行在另一个地方。例如,一些关键的执行代码必须装在系统的ROM中,但希望在较快的RAM中运行。链接器提供一个处理该问题的简单方法,利用SECTIONS伪指令选项可让链接器
16、定位两次。第一次使用装入关键字设置装入地址,再用运行关键字设置它的运行地址。装入地址确定段的原始数据或代码装入的地方,而任何对段的引用(例如其中的标号)则参考它的运行地址。在应用中必须将该段从装入地址复制到运行地址,这并不能简单地自动运行,因为指定的运行地址是分开的。若仅为段提供了一个定位(装入或运行),则该段将只定位一次,并且装入和运行地址相同;若提供两个地址,则段将被自动地定位,就好像是两个同样大小的不同段一样。未初始化的段不能装入,所以它仅有的有意义的地址为运行地址。链接器只定位未初始化段一次。若为它指定运行和装入地址,则链接器将发出警告并忽略装入地址。4.2.5 程序装入程序装入链接器
17、产生可执行的COFF目标文件。可执行的目标模块与链接器输入的目标文件具有相同的COFF格式,但在可执行的目标文件中,对段进行结合并在目标存储器中进行重新定位。为了运行程序,在可执行模块中的数据必须传输或装入目标系统存储器。有几种方法可以用来装入程序,取决于执行环境。下面说明两种常用的情况。(1)硬件仿真器和CCS集成开发环境,具有内部的装入器,调用装入器的LOAD命令即可装入可执行程序。(2)将代码固化在片外存储器中,采用Hex转换工具(Hex conversion utility),例如Hex500将可执行的COFF目标模块(.out文件)转换成几种其他目标格式文件,然后将转换后的文件用编程
18、器将代码写入EPROM/Flash。4.2.6 .cmd文件文件.cmd文件是用来分配ROM和RAM的,告诉链接程序怎样计算地址和分配空间。它包括三个部分:1.输入输入/输出定义输出定义 obj文件:链接器要链接的目标文件;lib文件:链接器要链接的库文件;map文件:链接器生成的交叉索引文件,.map文件中能看到各“段”实际定位的情况,所以强烈建议链接产生此文件,便于对系统整个存储空间的实际使用情况有清晰的理解;out文件:链接器生成的可执行代码;链接器选项:这一部分现在基本上在CCS集成调试环境中的编译选项中设置,所以在.cmd文件中不再需要。2.MEMORY命令命令该命令用来描述系统实际
19、的硬件资源。3.SECTIONS命令命令该命令用来描述“段”如何定位。.cmd文件是存储空间的分配文件,也就是要指明目标板上实际的物理存储空间是如何分配给DSP系统使用的,也就是程序要放在哪里,数据要放在哪里,堆栈放在哪里,中间向量表要放在哪里。实际物理空间的分配一般与MP/MC、OVLY、DROM的设定或设置有关,同时也与选用的DSP有关。比如同是54或55系列的,但是不同的DSP型号(比如5402、5410、5416、5502、5509等)内部的SARAM和DARAM的大小是不同的,反过来这也确定你能使用的外部扩展空间的地址范围。MEMORY指令分配哪些是程序区的地址范围,哪里是中断向量的
20、入口,哪些是数据空间的地址范围。SECTIONS指令具体分配.text段放在哪里,.data和.bss放在哪里。自己指定的.usect和.sect段对应在什么地方,等等。这样链接器就知道把各段不一定连续的代码汇成.out文件,才能load到DSP上去运行。所以在用CCS自带的tutorial例子时必须修改相应的.cmd文件,使之与目标板上的物理空间能够对应,这样实际代码才能load到DSP系统的存储空间中,程序才能运行起来。下面是任务2的.cmd文件清单:/*=hello.cmd=*/MEMORY DATA(RWI):origin=0 x6000,len=0 x4000 PROG:origin
21、=0 x200,len=0 x5e00 VECT:origin=0 xd000,len=0 x100/*注:DATA后面的括号中为可选项,规定存储器的属性:R,可对存储器进行读操作;W,可对存储器执行写操作;X命名的存储器可能含有可执行的代码;I,可以对存储器进行初始化。origm:起始地址,可写成org或o;length:长度,可写成len或l。*/SECTIONS .vectors:VECT .trcinit:PROG .gblinit:PROG frt:PROG .text:PROG .cinit:PROG .pinit:PROG .sysinit:PROG .bss:DATA .far
22、:DATA .const:DATA .switch:DATA .sysmem:DATA .cio:DATA .MEM$obj:DATA .sysheap:DATA .sysstack:DATA .stack:DATA该.cmd文件中的MEMORY部分定义了程序存储空间、数据存储空间、中断向量的入口地址,其中程序存储空间是从地址0 x200开始,大小为0 x5e00。SECTIONS部分中说明将.text段存放在程序空间中,将.bss段放在数据空间中等。4.3.1 寻址模式及指令系统寻址模式及指令系统TMS320C55X的助记符指令是由操作码和操作数两部分组成的。在汇编前,操作码和操作数都是用助
23、记符表示的,例如:LD#0FFH,A该条指令的执行结果是将立即数0FFH传送至累加器A。TMS320C55X和C54X类似,都提供7种基本的数据寻址方式:4.3 DSP汇编程序简介汇编程序简介(1)立即数寻址:指令中嵌有一个固定的立即数。(2)绝对地址寻址:指令中有一个固定的地址,指令按照此地址进行数据寻址。(3)累加器寻址:将累加器内的当前值作为地址去访问程序存储器中的该单元。(4)直接寻址:指令中的7 bit是一个数据页内的偏移地址,而所在的数据页由数据页指针DP或SP决定。该偏移加上DP和SP的值决定了在数据存储器中的实际地址。(5)间接寻址:按照辅助寄存器中的地址访问存储器。(6)存储
24、器映射寄存器寻址:修改存储器映射寄存器中的值,而不影响当前DP或SP的值,以存储器映射寄存器中的修改值去寻址。(7)堆栈寻址:把数据压入和弹出系统堆栈,按照后进先出原则进行。TMS320C54X和C55X可以使用两套指令系统:助记符方式和代数表达式方式。其可分成四种基本类型:算术指令;逻辑指令;程序控制指令;装入和存储指令。在附录里介绍了C54X的指令概要。4.3.2 C55X汇编语言指令系统的特点汇编语言指令系统的特点由于C55X指令集对C54X的兼容性,限于本书篇幅,这里就不再将C55X指令一一列出,仅在本小节后列出C54X的部分指令。C55X和C54X指令集最大的区别在于C55X结构中比
25、C54X增加的并行硬件,如双MAC、双ALU以及相应增加的总线等。反映在指令集里,就是增加了并行指令,即在一个机器周期里可以并行完成的指令。1.并行特性并行特性C55X并行指令的种类有:(1)单个指令里的内在并行。有些指令可以并行地完成两个操作,在助记符里用两个冒号()将两个操作分开来表示。例如:MPY AR3,CDP,AR0 MPY AR4,*CDP,AC1该条指令表示用辅助寄存器AR3寻址得到的值,和用CDP寻址得到的值相乘,结果存入AR0。同时,用辅助寄存器AR4寻址得到的值,和用CDP寻址得到的值相乘,结果存入AC1。(2)用户定义两个指令并行。由用户或C编译器决定的并行,用将两个指令
26、分开来表示。例如:MPYM AR1-,CDP,AC1 XOR AR2,T1 第一个指令在D单元里运行;第二个指令在A单元的ALU里运行。(3)隐含的并行和用户定义的并行组合在一起。例如:MPYM T3=*AR3+,AC1,AC2MOV#5,ARl 第一个指令里已经包含了并行性;第二个并行指令是由用户定义的。2.并行的规则并行的规则如果以下的所有规则都得到遵守的话,就只允许两个指令并行。(1)规则1:如果增加的指令长度不超过6 byte,两个指令可以并行。(2)规则2:两个指令可以并行需满足:如果两个指令中的一个具有并行使能位。这种由硬件支持的并行,称为并行使能机制。如果两个指令都像规则8所指定
27、的,在间接寻址模式下访问一个存储器。硬件支持的这种类型的并行性,称为软件双机制。(3)规则3:如果存储器总线、跨单元总线以及常数总线相互不妨碍,两个指令可以并行。(4)规则4:对A单元、D单元以及P单元之间的并行性没有限制。(5)规则5:处理器允许以下子单元之间的任何并行性:P单元装入通道;P单元存储通道;P单元控制操作。(6)规则6:处理器允许以下子单元之间的任何并行性:D单元装入通道;D单元存储通道;D单元交换操作;D单元的ALU、移位器、DMAC操作,被看成是一个操作;D单元移位和存储通道。(7)规则7:除X、Y、C以及SP地址产生单元的操作,处理器允许以下子单元之间的任何并行性:A单元
28、装入通道;A单元存储通道;A单元交换操作;A单元的ALU操作。(8)规则8:数据地址产生单元(DAGEN)包含4个操作:DAGEN X和DAGEN Y是最常用的操作,允许产生以下的寻址模式:单个数据存储器寻址Smem,db1(Lmem)间接双数据存储器寻址(Xmem,Ymem)系数数据存储器寻址(Cmem)寄存器位寻址(Baddr)DAGEN X和DAGEN Y也在指令mar()里作指针修改;DAGEN C是一个专门的操作,用于系数数据存储器寻址;DAGEN SP是一个专门的操作,用于数据及系统的堆栈寻址。(9)规则9:如果在一个指令里使用了以下寻址修改,则该指令不能与其他指令并行:*ARn(
29、k16);*+ARn(k16);*CDP(k16);*+CDP(k16);*absl6(#k16);*(#k23);*port(#k16)。(10)规则10:如果两个并行的指令的目的资源冲突,则高地址的指令(第二个指令)更新目的资源。4.4.1 DSP软件的设计方式软件的设计方式通常DSP芯片软件设计有以下三种方式。4.4 DSP C语言程序基础语言程序基础1完全用完全用C语言开发语言开发TI公司提供了用于C语言开发的CCS平台。该平台包括了优化ANSI C编译器,从而可以在C源程序级进行开发调试方式。这种方式大大提高了软件的开发速度和可读性,方便了软件的修改和移植。但是,在某些情况下,C代码
30、的效率还是无法与手工编写的汇编代码的效率相比,如FFT编程。这是因为即使最佳的C编译器,也无法在所有的情况下都能够最合理地利用DSP芯片所提供的各种资源。此外,用C语言实现DSP芯片的某些硬件控制也不如汇编程序方便,有些甚至无法用C语言实现。2完全用汇编语言开发完全用汇编语言开发TI公司提供了用于汇编语言开发的针对TMS320C55X的汇编语言,用户可以用它进行软件开发。此种方式可以更为合理地充分利用DSP芯片提供的硬件资源,其代码效率高,程序执行速度快,但是用DSP芯片的汇编语言编写程序是比较繁杂的。一般来说,不同公司的芯片汇编语言是不同的,即使是同一公司的芯片,由于芯片类型的不同(如定点和
31、浮点),芯片的升级换代,其汇编语言也不同。因此,用汇编语言开发基于某种DSP芯片的产品周期较长,并且软件的修改和升级较困难,这些都是因为汇编语言的可读性和可移植性较差所致。3.用用C语言和汇编语言混合编程开发语言和汇编语言混合编程开发为了充分利用DSP芯片的资源,更好地发挥C语言和汇编语言进行软件开发的各自的优点,可以将两者有机结合起来,兼顾两者的优点,避免其弊端。因此,在很多情况下,采用混合编程方法能更好地达到设计要求,完成设计功能。但是,采用C语言和汇编语言混合编程必须遵循一些有关的规则,否则会遇到一些意想不到的问题,给开发设计带来许多麻烦。因此,在DSP软件的开发中我们应该以C语言为主,
32、并且在需要的情况下适当采用汇编语言的混合编程的开发方法。4.4.2 C语言软件开发过程语言软件开发过程在第3章中,我们了解了软件开发过程涉及编译器(compiler),汇编器(assembler),连接器(linker),归档器(archiver),建库器(library-build utility),运行支持库(run time support library),HEX转换器(hex conversion utility),交叉引用列表器(cross reference lister),绝对列表器(absolute lister)等。其大都设置既可通过命令,也可通过CCS的projectbu
33、ild options设置。图4-2显示了从.c文件到最终.hex文件简化的软件build流程。图4-2 软件build流程如前所述,在这过程中,目标文件地址是浮动的,能被重定位,链接器用.cmd文件对链接目标,进行重定位,列出目标文件、库文件和链接器选项,用MEMORY命令描述目标系统存储空间配置,用SECTIONS命令描述“段”如何定位。Hex转换程序也使用.cmd文件,配置转换选项。4.4.3 C语言运行环境语言运行环境1.存储模型存储模型虽然,C语言是一种相对高效的高级语言,并且TI提供的C编译器还结合硬件特点支持三级优化功能,但生成的汇编代码效率仍可能会不尽人意。因此,用户对C编译器
34、究竟是如何进行存储分配的,应有一定的了解。C55X将存储器处理为程序存储器和数据存储器两个线性块。程序存储器包含可执行代码;数据存储器主要包含外部变量、静态变量和系统堆栈。编译器的任务是产生可重定位的代码,允许链接器将代码和数据定位进合适的存储空间。使用者可以使用系统定义的段也可以自己定义所需要的段。1)系统定义.cinit:存放C程序中的变量初值和常量;.const:存放C程序中的字符常量、浮点常量和用const声明的常量;.switch:存放C程序中switch语句的跳针表;.text:存放C程序的代码;.bss:为C程序中的全局和静态变量保留存储空间;.far:为C程序中用far声明的全
35、局和静态变量保留空间;.stack:为C程序系统堆栈保留存储空间,用于保存返回地址、函数间的参数传递、存储局部变量和保存中间结果;.sysmem:用于C程序中malloc、calloc和realloc函数动态分配存储空间。2)用户定义#pragma CODE_SECTION(symbol,section name);#pragma DATA_SECTION(symbol,section name)。其中.stack不同于DSP汇编指令定义的堆栈。DSP汇编程序中要将堆栈指针SP指向一块RAM,用于保存中断、调用时的返回地址,存放PUSH指令的压栈内容。.stack定义的系统堆栈实现的功能是保护
36、函数的返回地址,分配局部变量,在调用函数时用于传递参数,保护临时结果。.stack定义的段大小(堆栈大小)可用链接器选项STACK SIZE设定,链接器还产生一个全局符号_ _STACK_SIZE,并赋给它等于堆栈长度的值,以字为单位,缺省值为1K。2.寄存器使用规则寄存器使用规则在C环境中,定义了严格的寄存器规则。寄存器规则明确了编译器如何使用寄存器以及在函数调用过程中如何保护寄存器。调用函数时,被调用函数负责保护某些寄存器,这些寄存器不必由调用者来保护。如果调用者需要使用没有保护的寄存器,则调用者在调用函数前必须予以保护。下面具体说明寄存器规则:(1)辅助寄存器AR1、AR6、AR7由被调
37、用函数保护,即可以在函数执行过程中修改,但在函数返回时必须恢复。AR0、AR2、AR3、AR4、AR5可以自由使用,即在函数执行过程中可以修改,而且不必恢复。(2)堆栈指针SP在函数调用时必须予以保护,但其是自动保护的,即在返回时,压入椎栈的内容都将被全部弹出。(3)ARP在函数进入和返回时,必须为0,即当前辅助寄存器为AR0。函数执行时可以是其他值。(4)在缺省的情况下,编译器总是认为OVM为0。因此,若在汇编程序中将OVM置为1,则在返回C环境时,必须将其恢复为0。(5)其他状态位和寄存器在子程序中可以任意使用,不必恢复。3.函数调用规则函数调用规则C编译器规定了一组严格的函数调用规则。除
38、了特殊的运行支持函数外,任何调用C函数或被C函数所调用的函数都必须遵循这些规则,否则就会破坏C环境,造成不可预测的结果。1)参数传递函数间的参数传递通过寄存器和系统堆栈进行。函数调用前,将参数以逆序压入运行堆栈,即最右边的参数最先入栈,然后自右向左将参数依次入栈。但是,对于TMS320C54X,在函数调用时,第一个参数放入累加器A中进行传递。若参数是长整型和浮点数时,则低位字先压栈,高位字后压栈。若参数中有结构形式,则调用函数给结构分配空间,其地址通过累加器A 传递给被调用函数。2)结果返回函数调用结束后,将返回值置于累加器A中。整数和指针在累加器A的低16位中返回;浮点数和长整型数在累加器A
39、的32位中返回。3)函数调用时需注意的一些问题调用函数与被调用函数必须对各自的寄存器进行保护,从被调用函数返回前,被调用函数必须归还所有已占用的堆栈空间。4.C语言和汇编语言的混合编程语言和汇编语言的混合编程C语言和汇编语言的混合编程有以下几种方法。独立编写汇编程序和C程序,分开编译或汇编,形成各自的目标代码模块,再用链接器将C模块和汇编模块链接起来。这种方法灵活性较大,但用户必须自己维护各汇编模块的入口和出口代码,自己计算传递的参数在堆栈中的偏移量,工作量较大,但能做到对程序的绝对控制。在C程序中直接内嵌汇编语句。用此种方法可以在C程序中实现C语言无法实现的一些硬件控制功能,如修改中断控制寄
40、存器,中断标志寄存器等。将C程序编译生成相应的汇编程序,手工修改和优化C编译器生成的汇编代码。采用此种方法时,可以控制C编译器,使之产生具有交叉列表的C程序和与之对应的汇编程序,而程序员可以对其中的汇编语句进行修改。优化之后,对汇编程序进行汇编,产生目标文件。根据编者经验,只要程序员对C和汇编均很熟悉,这种混合汇编方法的效率可以做的很高。但是,由交叉列表产生的C程序对应的汇编程序往往读起来颇为费劲,因此对一般程序员不提倡使用这种方法。下面就前面三种方法逐一介绍。1)独立的C模块和汇编模块接口独立的C模块和汇编模块接口是一种常用的C和汇编语言接口方法。采用此种方法在编写C程序和汇编程序时,必须遵
41、循有关的调用规则和寄存器规则。如果遵循了这些规则,那么C和汇编语言之间的接口是非常方便的。C程序可以直接引用汇编程序中定义的变量和子程序,汇编程序也可以引用C程序中定义的变量和子程序。在编写独立的汇编程序时,必须注意以下几点:(1)不论是用C语言编写的函数还是用汇编语言编写的函数,都必须遵循寄存器使用规则。(2)必须保护函数要用到的几个特定寄存器。(3)中断程序必须保护所有用到的寄存器。(4)从汇编程序调用C函数时,第一个参数(最左边)必须放入累加器A中,剩下的参数按自右向左的顺序压入堆栈。(5)调用C函数时,注意C函数只保护了几个特定的寄存器,而其他是可以自由使用的。(6)长整型和浮点数在存
42、储器中存放的顺序是低位字在高地址,高位字在低地址。(7)如果函数有返回值,返回值存放在累加器A中。(8)汇编语言模块不能改变由C模块产生的.cinit段,如果改变其内容将会引起不可预测的后果。(9)编译器在所有标识符(函数名、变量名等)前加下划线“_”。(10)任何在汇编程序中定义的对象或函数,如果需要在C程序中访问或调用,则必须用汇编指令.global定义。(11)编辑模式CPL指示采用何种指针寻址,如果CPL=1,则采用堆栈指针SP寻址;如果CPL=0,则选择页指针DP进行寻址。下面给出具体例子。C程序:Extern int asmfunc();/*声明外部的汇编子程序*/*注意函数名前不
43、要加下划线*/int gvar;/*定义全局变量*/main()int I=3;I=asmfunc(i);/*进行函数调用*/汇编程序:_asmfunc:;函数名前一定要有下划线ADD*(-gvar),A;I的值在累加器A中STL A,*(-gvar);返回结果在累加器A中RETD;子程序返回2)从C程序中访问汇编程序变量从C程序中访问汇编程序中定义的变量或常数时,根据变量和常数定义的位置和方法的不同,可分为三种情况。(1)访问在.bbs段中定义的变量,方法如下:采用.bss命令定义变量;用.global将变量说明为外部变量;在汇编语言名前加下划线“_”;在C程序中将变量说明为外部变量,然后就
44、可以象访问普通变量一样访问它。(2)访问未在.bbs段定义的变量,如当C程序访问在汇编程序中定义的常数表时,则方法更复杂一些。此时,定义一个指向该变量的指针,然后在C程序中间访问它。在汇编程序中定义此常数表时,最好定义一个单独的段。然后,定义一个指向该表起始地址的全局标号,可以在链接时将它分配至任意可用的存储器空间。如果要在C程序中访问它,则必须在C程序中以extern方式予以声明,并且变量名前不必加下划线“_”。这样就可以像访问其他普通变量一样进行访问。(3)对于那些在汇编中以.set和.global定义的全局常数,也可以在C程序中访问,不过要用到一些特殊的方法。一般说来,在C程序中和汇编程
45、序中定义的变量,其符号表包含的是变量的地址。而对于汇编程序中定义的常数,符号表包含的是常数值。编译器并不能区分哪些符号表包含的是变量的地址,哪些是变量的值。因此,如果要在C程序中访问汇编程序中的常数,则不能直接用常数的符号名,而应在常数符号名前加一个地址操作符&,以示与变量的区别,这样才能得到常数值。3)C程序中直接嵌入汇编语句在C程序中直接嵌入汇编语句是一种直接的C和汇编的接口方法。此种方法可以在C程序中实现C语言无法实现的一些硬件控制功能,如修改中断控制寄存器、中断标志寄存器等。嵌入汇编语句的方法比较简单,只需在汇编语句的两边加上双引号和括号,并且在括号前加上asm标识符即可。即:asm(
46、汇编语句);注意:括号中引号内的汇编语句和语法通常的汇编编程的语法一样。不要破坏C环境,因为C编译器并不检查和分析嵌入的汇编语句。插入跳转语句和标号会产生不可预测的结果。不要让汇编语句改变C程序中变量的值,不要在汇编语句中加入汇编器选项而改变汇编环境。修改编译器的输出可以控制C编译器,从而产生具有交叉列表的汇编程序。而程序员可以对其中的汇编语句进行修改,之后再对汇编程序进行汇编,产生最终的目标文件。注意,修改汇编语句时切勿破坏C环境。采用这种方法一方面可以在C程序中实现用C语言难以实现的一些硬件控制功能。另一方面,也可以用这种方法在C程序中的关键部分用汇编语句代替C语句以优化程序。采用这种方法
47、的一个缺点是它比较容易破坏C环境,因为C编译器在编译嵌入了汇编语句的C程序时并不检查或分析所嵌入的汇编语句。4)何时使用混合编程技术(1)当程序中需要操作与硬件密切相关的设备,而用C语言较难实现时。比如:在中断程序设计时需要设置中断向量表,向量表中空间有限用C语言语句有困难,且需向量表要在内存中精确定位,这时可将设置中断向量表的部分用汇编语言代替。(2)当需要绕开C编译器的规定,进行特殊操作时。比如:C语言规定,程序不能访问程序代码区,而系统功能需要进行类似访问时可采用限制较小的汇编语言程序设计。(3)当需要提高模块的效率(包括空间上和时间上两方面的),而C语言程序无法达到要求时。5)使用混合
48、编程时的注意事项(1)在汇编程序中使用其他C语言模块中定义的变量或函数名称时,需要在引用的名称前加下划线。如:C中定义的变量为x,在汇编中引用时要用_x。(2)汇编语言写的子程序需要符合C语言的调用规则,尤其是在默认的辅助寄存器使用上和栈的使用上要求兼容。(3)在汇编语言模块中,需要编程者自己消除流水线冲突。(4)在使用内嵌汇编技术时,需要考虑以下内容:要非常小心地处理,以免破坏C语言操作环境。编译器在遇到内嵌汇编语句时,不会对其中的汇编语句进行分析处理。避免从内嵌汇编语句跳转到C语言模块中,那将极容易造成寄存器使用上的混乱,从而产生难以预料的结果。不要在内嵌汇编语句中改变C语言模块中变量的值
49、,但可以安全地读取它们的值。在汇编程序中不要使用内嵌汇编。5.中断服务程序在编写中断服务程序时要注意以下问题。(1)汇编语言编写的中断服务程序必须对所有用到寄存器进行保护,以免破坏C运行环境。(2)C编写的中断服务程序应用interrupt关键字声明。(本书将在第5章中介绍DSP中断的内容)6.系统初始化系统初始化在运行C程序前,必须建立C运行环境,此任务由C引导程序_c_int00完成。_c_int00包含在库函数中,build时自动将其链接到可执行程序中,程序的入口地址必须设为_c_int00起始地址。在前面的任务之中,程序load以后,我们发现程序的指针指向_c_int00位置。_c_i
50、nt00的源程序存放在由rts.src分离出来的boot.asm中,用户可根据需要修改,如:设置堆栈指针。初始化全局变量:将.cinit段中数据拷贝到.bss段中。调用C程序的主函数main()。对于TI公司不同系列的DSP,其C编译器对C运行环境的处理略有不同,具体参考各自的C编译器用户指南。4.5.1 传统软件开发方法传统软件开发方法在大多数的情况下,我们采用传统的软件开发方法。传统的软件开发方法按照以下的方法进行:(1)用汇编语言或C语言混合编程,从零开始。4.5 TI DSP软件开发平台软件开发平台(2)编写硬件资源头文件。DSP片内寄存器资源头文件。描述片内寄存器地址;描述片内寄存器