1、第6章 基于C语言的DSP芯片开发 第第6章章 基于基于C语言的语言的DSP芯片开发芯片开发 6.1 引言6.2 ANSI C编译器6.3 C语言编程基础知识6.4 基于C语言的DSP芯片开发的运行环境6.5 C语言与汇编语言的混合编程本章小结思考题与习题第6章 基于C语言的DSP芯片开发 6.1 引引 言言DSP的C语言编程的主要内容包括ANSI C语言编译器,运行环境,运行支持库的使用与建立,I/O编程,C语言编程的各种工具的使用,以及C调试器的使用等主要内容。限于篇幅,本章主要对ANSI C编译器和与C语言编程息息相关的运行环境以及有关C语言基础知识做一介绍。第6章 基于C语言的DSP芯
2、片开发 6.2 ANSI C编译器编译器6.2.1 优化优化ANSI C编译器编译器TMS320C3x/C4x编译器支持ANSI(American National Standards Institute)开发的C语言标准。该标准是使用最广泛的C语言标准,它规定C语言的一些特征受目标处理器、运行环境或主机环境影响。下面就TMS320C3x/C4x C语言的一些不同于标准C语言的特征给予介绍。第6章 基于C语言的DSP芯片开发 1标识符和常数(1)所有标识符的前100个字符有意义,区分大小写。这些标示符适用于内部和外部的标识符。(2)源(主机)和执行(目标)字符集为ASCII码,不存在多字节字符
3、。(3)字符常数或字符串常数中的十六进制或八进制转义序列可具有高达32位的值。(4)具有多个字符的字符常数按序列中的最后一个字符来编码,例如:abc=c。第6章 基于C语言的DSP芯片开发 2数据类型数据类型(1)整数、双精度等数据类型长度与常见编译器中的数据类型不同,所有的浮点数型都是用二进制浮点数格式来表示的。(2)size_t(sizeof操作符的结果)定义为unsigned int。(3)ptrdiff_t(指针加减的结果)定义为int。3数据转换数据转换 (1)浮点数到整数的转换部分取整数部分。(2)指针和整数可自由转换。第6章 基于C语言的DSP芯片开发 4表达式表达式(1)当两个
4、带符号的整数相除时,若其中一个为负,则其商为负,余数的符号与分子的符号相同。“/”表示求商,“%”表示求余。带符号的模运算取被除数的符号。如:10/-3=-3;-10/3=-3;10%-3=1;-10%3=-1(2)带符号数的右移为算术右移,即保留符号。第6章 基于C语言的DSP芯片开发 5声明声明(1)寄存器存储类对所有character、short、integer和pointer类型都有效。(2)结构成员不能被打包成字,除位域外,每个成员对准在16位字的边界。(3)整数类型的位域带有符号,位域从字的低位开始被打包成字,并且不超过字边界。6预处理器预处理器预处理器忽略所有不支持的#pragm
5、a伪指令,支持#pragma的伪指令见后面的内容。第6章 基于C语言的DSP芯片开发 ANSI C编译器的主要功能是:首先将DSP的C语言程序转化为汇编语言程序,然后按汇编语言程序调试手段进行调试;ANSI C编译器支持代码级调试。采用ANSI C语言进行DSP软件编程具有的优点包括:1标准化标准化C语言是一种非常便于移植的编程语言,尤其是在语言的扩展方面,但缺乏标准化,而ANSI C则为这些扩展提供了新的标准。第6章 基于C语言的DSP芯片开发 2兼容性兼容性ANSI C标准是K&R标准的超集。在绝大多数编译器下调试并运行的程序都可以在优化ANSI C编译器下运行。ANSI C的兼容性还有另
6、一个含义,就是通过简单的修改即可适用于TI公司的其他芯片。3补充新类型补充新类型补充的新类型主要是指新的const以及Volatile类型。它们允许对C语言程序进行更有效的优化。第6章 基于C语言的DSP芯片开发 4改进的函数约定改进的函数约定函数原型允许改进的类型检查并允许对函数调用进行优化。优化ANSI C编译器时主要考虑了三个方面的优化:(1)产生的汇编程序在效率上可与手工编写的汇编程序相比;(2)提供简单的C运行环境的程序接口,使得关键的DSP算法可用汇编语言实现;(3)为用C语言开发高性能DSP应用程序建立一定规模的库,它们都可以被方便地使用。第6章 基于C语言的DSP芯片开发 总的
7、来说,优化ANSI C编译器的主要特征包括:与ANSI C功能规范完全兼容。具有ANSI C标准运行支持库。程序可转化为ROM装载的格式,可重定位、重入。提供与汇编语言的接口,允许对时间敏感的程序用汇编语言编写。灵活而全面的链接器可以对内存重定位、内存设置、部分链接进行全面的控制,而且还可以对代码进行运行时的重定位。第6章 基于C语言的DSP芯片开发 提供了一个外壳程序(shell),允许进一步将C语言转化为可执行文件。快速的开发速度。提供大量有用的提示,以支持对C程序进行方便的编程和调试。支持行内扩展。可以对库进行管理,包括对库进行文件的增加、删除、或替换,还可以将目标文件库用作链接器的输入
8、。第6章 基于C语言的DSP芯片开发 提供丰富的列表文件,如源代码和汇编的交叉列表、预处理输出文件等。TMS320C3x/4x ANSI C编译器具有可为全局变量、静态变量和常数提供无限空间的大存储器模式。缺省的小存储模式,其空间限制为64K字,使编码执行速度更快、效率更高。C外壳程序提供了许多选项用于控制C编译汇编和链接的过程。C语言编译汇编和链接的常用的选项如表6.1所示。第6章 基于C语言的DSP芯片开发 表表6.1 C语言编译汇编和链接的常用选项语言编译汇编和链接的常用选项第6章 基于C语言的DSP芯片开发 6.2.2 优化编译器优化编译器C编译器提供了一个优化编译器。采用优化编译可以
9、生成高效率的汇编代码,从而提高程序的运行速度,减少目标代码的长度。在一定程度上可以认为,C编译器的效率主要取决于C编译器所能进行优化的范围和数量。C编译器的优化方法可以分为两类,即通用优化和特定优化。第6章 基于C语言的DSP芯片开发 对C语言的通用优化的范围主要包括:简化表达式;优化数据流;删除公共子表达式和冗余分配;优化跳转;简化控制流;优化与循环有关的重复;将循环体内的计算值不变的表达式移至循环体前;运行支持库函数的行内扩展;第6章 基于C语言的DSP芯片开发 对DSP芯片的特定优化的主要内容包括:有效地使用寄存器;自动增减寄存器寻址方式;使用块重复;使用并行指令;使用延迟跳转。第6章
10、基于C语言的DSP芯片开发 下面主要对浮点DSP芯片的特定优化作一详细介绍。1)优化寄存器变量的使用编译器将最大程度地利用寄存器存储局部变量、参数及中间值。将数据存储在寄存器中要比将数据存储在存储器中的程序的运行效率高。在将数组结构转化为循环时,这一优化显得尤其有效。第6章 基于C语言的DSP芯片开发 例6.1 优化寄存器变量的使用。C语言程序:int gvar;reg(int i,int j)gvar=call()&i;j=gvar+i;浮点DSP编译器输出程序:_reg:*R4分配给用户变量i *R5分配给用户变量j第6章 基于C语言的DSP芯片开发 CALL _call ;R0=call
11、()AND R4,R0 ;R0&=i STI R0,_gvar ;gvar=R0 ADDI R4,R0,R5 ;将gvar保留在R0中 ;将结果放到R5(j)中编译器在编译时,将i,j分别赋予R4,R5,将gvar保留在R0。这样gvar的运算成了3操作数运算,结果j直接放在RS中。可见,充分利用寄存器可提高程序的运行效率。第6章 基于C语言的DSP芯片开发 2)效率最优的寄存器分配编译器根据用户变量和中间值的类型与使用频率来对寄存器进行分配,在循环体内的变量比其他变量有更高的优先级,相互之间不覆盖的变量可以被分配给同一寄存器。3)并行指令许多类型的指令,如装载/装载、存储/操作/乘/加,可以
12、配对并行执行。当相邻符号满足并行运行的寻址要求时,编译器使它们并行运行。虽然这一功能已由代码生产工具完成,但优化还是对提高上述情况的运行效率起了很大的作用,因为操作数被更多地放到了寄存器中,进一步提高了运行速度。第6章 基于C语言的DSP芯片开发 4)自增寻址模式对于*P+,*-P这样的表达式,编译器可以使用高效的自增寻址模式。在许多场合,循环中的数组值被逐个使用。如:for(i=0;i,=N;+i)ai;这时,编译器通过自增寄存器变量指针把数组参数转化为间接寻址参数。5)块重复浮点编译器通过RPTS和RPTB指令实现过零循环。编译器能发现通过计算机控制的循环,并使用高效的RPTB和RPTS来
13、实现循环。这两种方式中,调整量可以是常数,也可以是表达式。第6章 基于C语言的DSP芯片开发 例6.2 块重复,自增寻址模式和并行运算。C语言程序:float a1,b10;scale(float k)int i;for(i=0;i10;+i)ai=bi*k;第6章 基于C语言的DSP芯片开发 浮点DSP编译器输出:_scaleLDI CONST+0,AR4 ;AR4=&a0LDI CONST+1,AR5 ;AR5=&b0MPYF R4,*AR5+,R0 ;计算第一个结果RPTS 8 ;STF R0,*AR4+;保存这一结果|MPYF R4,*AR5+,R0 ;开始计算另一个结果STF R0,
14、*AR4+;保存最后一个结果第6章 基于C语言的DSP芯片开发 6)延迟跳转指令浮点DSP支持延迟跳转指令。它与普通的跳转指令比起来,可以节省三个指令周期的运行时间。延迟跳转指令是在执行完其后的三条指令后才发生真正的跳转,编译器尽可能实现无条件的延迟跳转指令,并在计数循环的场合使用条件延迟跳转指令。第6章 基于C语言的DSP芯片开发 例6.3 延迟跳转指令。C语言程序:wait(volatile int*p)for(;)if(*p&0 x80)*p|=0 xf0;浮点DSP编译器输出程序:_wait:L6:第6章 基于C语言的DSP芯片开发 LDI *AR4,R0;R0=*p(AR4被分配给p
15、)TSTB 128,R0 ;test*p&0 x80BZ L6 ;结果为假,采用直接跳转返回BD L6 ;结果为真,采用延迟跳转返回LDI *AR4,R0 ;R0=*pOR 0F0h,R0 ;R0=*p|0 xF0STI R0,*AR4 ;*p=R0*BL6 ;结果为真时的真正跳转发生在这里第6章 基于C语言的DSP芯片开发 7)使用寄存器避免冲突编译器支持一项新的可选用的调用顺序。它通过寄存器而非压入堆栈的方法来避免冲突。这在程序调用时,可以明显改善程序的性能。8)条件指令在C语言中往往有条件语句,如:a=conditional expr1:expr2 或(condition)a=b这时编译
16、器使用条件指令来减少分支。第6章 基于C语言的DSP芯片开发 9)循环体重复块重复并不是用的越多越好,这要视具体情况而定。当编译器发现循环次数与循环体长度都很小时,它将不使用循环结构,改为使用多次重复运行代码段的循环体重复的方式。这样可以避免分支或块重复造成的跳转或判断开销,提高运行效率。第6章 基于C语言的DSP芯片开发 6.3 C语言编程基础知识语言编程基础知识6.3.1 数据类型数据类型所有整数类型(char、short、int、long以及对应的无符号数)都是相同的,即都是由32位的二进制数来表示的。有符号类型由基2的补码表示;字符型类型是有符号类型,等同于整型;枚举类型的对象用32位
17、数来表示,在表达上与整型相似;第6章 基于C语言的DSP芯片开发 所有的浮点型(float、double)都是相似的,在TMS320C3x/4x中都是由32位的单精度浮点格式表示的;Long double 型浮点格式在TMS320C3x/4x中是由40位的扩展精度格式表示的;数据类型的字长、表达方法、数据范围列于下表6.2;一些值的范围在头文件limits.h作为标准宏利用,头文件limits.h应用在编译器中。注:TMS320C3x/4x类型为32位 第6章 基于C语言的DSP芯片开发 表表6.2 数数 据据 格格 式式第6章 基于C语言的DSP芯片开发 长双精度浮点数据类型不同于其他的浮点
18、数据类型。长双精度浮点数据的特性如下:(1)长双精度浮点数据类型不是用浮点和双精度所用的单精度32位格式表示的,而是用40位扩展精度格式表示。(2)长双精度要求两个存储器字:第一个字保存高24位,第二个字保存低24位,操作的数据长度为40位,在装载和保存该长双精度浮点格式需要两条指令。第6章 基于C语言的DSP芯片开发 (3)长双精度保存在一个浮点寄存器中,像短浮点和双精度浮点一样,在寄存器中传输参数。(4)寄存器变量都不是40位值;因此长双精度不能通过CALL保存为寄存器变量。在CALL之前保存在存储器中,在需要时还可以重新装载。(5)在浮点数/双精度浮点到长双精度浮点转换时,40位寄存器的
19、低8位用0填满。在长双精度浮点到浮点/双精度浮点转换时,常用RND指令把此值取舍为最接近的单精度浮点数值。第6章 基于C语言的DSP芯片开发 (6)若加、减和负值(取反)指令用扩展精度浮点数作为输入,可以用汇编指令实现,如C3X乘法用函数MPY_LD,除法用函数DIV_LD实现。(7)40位的汇编函数链接到名为.float40的自身的段中。第6章 基于C语言的DSP芯片开发 6.3.2 关键字关键字C编译器中涉及的关键字主要包括C寄存器关键字和说明C编译器如何访问全局和静态变量以及如何调用函数的near和far关键字。1C寄存器关键字寄存器关键字TMS320C3x/4x编译器通过增加C寄存器关
20、键字扩展C语言,从而使高级语言访问寄存器。第6章 基于C语言的DSP芯片开发 当用户在目标文件中用C寄存器关键字时,编译器就会把目标文件名称与为C3x/C4x列出的标准控制寄存器的名称作比较。如果名称匹配,则编译器就生成控制寄存器的参考代码。如果名称不匹配,则编译器就报告出错。有效的控制寄存器如表6.3所示。第6章 基于C语言的DSP芯片开发 表表6.3 有效的控制寄存器有效的控制寄存器第6章 基于C语言的DSP芯片开发 C寄存器关键字仅用在显示文件中,不必在函数中作任何的声明。可应用在整数或指针类型对象中,但不允许应用在浮点或结构型或逻辑对象中。C寄存器关键字指明对象不能是变量。如果所用的控
21、制寄存器是变量(即可通过外部控制来改变其值),那么这个对象也可以用变量关键字声明。如果要用表6.3中的控制寄存器,用户必须按照下面方式声明:extern cregister volatile unsigned int register;一旦声明了寄存器,用户就可以直接用这个寄存器的名称。第6章 基于C语言的DSP芯片开发 例6.4 定义和使用控制寄存器extern creglsger volatile unsigned int IE;extern creglster volatilt unsigned int IF;extern creglsger volatile unsigned int
22、IOF;extern creglster volatilt unsigned int ST;unsigned myfunc unsigned int mask while ST&mask int mask 0 /*Do nothing;wait*/;return IOF;第6章 基于C语言的DSP芯片开发 2near和和far关键字关键字C3x/C4x的C编译器扩展了C语言功能,增加的near和far关键字是用来说明C编译器是如何访问全局和静态变量以及如何调用函数的。在句法上,near和far关键字被看做存储类变址数,它们可以出现在保存类声明和类型的之前、之后和中间。两个保存类变址不可以在一个
23、声明中同时应用,例如:far static int x;static near int x;static int fat x;far int f();static far int f();第6章 基于C语言的DSP芯片开发 1)near和far数据对象全局和静态数据对象可以通过下面两种方式访问:near关键字编译器对数据的访问采取相对数据页指针方式,如:sti r0,_var far关键字编译器不能通过DP访问数据。但是如果数据的量大于DP所允许的32K字时,就可以采用DP访问,例如:ldiu cll,ar0 sti r0,*ar0这里cll是包含在.bss中的变量的地址。第6章 基于C语言的
24、DSP芯片开发 默认情况下,编译器产生小的存储模型代码,这意味每个数据对象都被当作near关键字,除非它被声明为far关键字。如果对象声明为near关键字,一般的用相对于数据页指针的偏移寻址。数据页指针指向.bss段的开始。如果-ml,-mb和-mf这些选项被用,则可改变默认的设定值。大存储器模型代码,意思是每个数据对象被当作far关键字来处理并生成相应的代码。如果用DATA_SECTION,对象就会被确认为far变量,并且不能更改。这样就保证对不同数据页中的变量进行存取。第6章 基于C语言的DSP芯片开发 2)near和far函数调用 函数调用可采用以下两种方式之一 near关键字编译器假定
25、调用的目的操作数在所调用的224字节内,编译器用PC相对寻址方式调用指令:call _funcfar关键字用户通知编译器调用的不在调用的224字节之内,编译器所用的调用指令为寄存器寻址模式:ldiu cll,rocallu,ro这里cll常量在.bss中为函数的地址。第6章 基于C语言的DSP芯片开发 6.3.3 寄存器变量寄存器变量TMS320C3x/4x C编译器对寄存器变量的处理依靠用户是否用优化器来决定。用优化器的编译编译器忽略寄存器的任何声明,在优化时把所有的变量都当作寄存器变量来处理。不用优化器的编译如果用户用了寄存器关键字,建议把该变量分配到所选的寄存器中。第6章 基于C语言的D
26、SP芯片开发 8个寄存器在每个函数中都用作寄存器变量。寄存器R4和R5保留为整数寄存器变量;寄存器R6和R7保留为浮点数寄存器变量;四个寄存器AR4AR7保留指针或整数变量。任何标量(整数、浮点数或指针)的对象都可以声明为寄存器变量。寄存器标示符被忽略为任何类型的对象。第6章 基于C语言的DSP芯片开发 寄存器保存类是有其意义的参数,或是逻辑变量。如果应用基于栈的调用约定,那么声明作寄存器的参数正常地传入到堆栈并通过函数入口移入到寄存器中。这有利于参数在函数中访问该参数。如果参数不被声明为寄存器,那么参数就会在函数中分配一定的逻辑空间并保存起来,作为函数开始执行的地方。第6章 基于C语言的DS
27、P芯片开发 6.3.4 Pragma 指令指令(预处理指令预处理指令)Pragma 指令指示编译器所对应的处理器如何处理函数。TMS320C3x/C4x C编译器支持下面的pragmas:CODE_SECTIONDATA_SECTIONFUNC_CANNOT_INLINEFUNC_EXT_CALLED第6章 基于C语言的DSP芯片开发 FUNC_IS_PUREFUNC_IS_SYSTEMFUNC_NEVER_RETURNSFUNC_NO_GLOBAL_ASGFUNC_NO_IND_ASGINTERRUPT 这里所指的func和symbol必须在文件内,也就是说,用户不能在函数体内定义和声明它们
28、。即用户必须确定该pragma在函数体外,并且必须声明、定义所用到的func 和symbol操作。如果用户不遵循以下规则,则编译器会发出警告。第6章 基于C语言的DSP芯片开发 1)CODE_SECTION Pragma此pragma为段名section name 分配一个代码空间,语法如下:#pragma CODE_SECTION(symbol,section name);该段在编译器用.sect汇编器指令声明。如果段名的长度大于8个字符,用户需要应用COFF2声时。如果用户想把目标代码与从.bss段分离出来的空间链接,则该pragma是有用的。第6章 基于C语言的DSP芯片开发 例6.5
29、CODE_SECTION 预处理的使用(a)C source file#pragma CODE_SECTION fn,my_sectint fn(int x)return c;(b)Assembly source file.sect my_sect.global _fn第6章 基于C语言的DSP芯片开发 2)DATA_SECTION Pragma此pragma为段名section name 分配一个数据空间,语法如下:#pragma DATA_SECTION(symbol,section name);如果用户想把目标数据与从.bss段分离出来的空间链接,则该pragma是有用的。如果段名的长度
30、大于8个字符,则用户需要应用COFF2。第6章 基于C语言的DSP芯片开发 例6.6 DATA_SECTION预处理的使用。(a)C source file#pragma CODE_SECTION fn,my_sectchar bufferA512;char bufferB512;(b)Assembly source file.global _bufferA.bss _bufferA,512.blobal _bufferB_bufferB:.usect my_sect,512,1 第6章 基于C语言的DSP芯片开发 3)FUNC_CANNOT_INLINE Pragma该pragma指示编译器
31、不能扩展有名称的函数。任何用该pragma 命名的函数优于用户以任何其他方式(例如用内部的关键字)指出inlining。该pragma必须出现在声明或用户参考函数内。此pragma的语法如下:#pragma FUNC_CANNOT_INLINE(func);自变量func 是C函数的名称,此函数不能是内嵌的。第6章 基于C语言的DSP芯片开发 4)FUNC_EXT_CALLED Pragma当用户用-pm选项时,编译器用程序等级最优化。当用户用这种最优化的类型时,编译器将通过主函数直接或间接的去掉没调用的函数。这样,用户可以通过手控代码调用而不是用主函数。该pragma指明了优化器可以控制C函
32、数,并调用这些C函数或其他的函数。这些函数就作为C语言的入口地址。该pragma的定义必须在声明或用户想引用的函数之前。语法如下:#pragma FUNC_EXT_CALLED(func);变量func是用户所定义的C函数的名称。第6章 基于C语言的DSP芯片开发 5)FUNC_IS_PURE Pragma该pragma指明了优化器对有名称函数的影响。编译器按照下面情况处理这种影响:如果函数的值不需要,则删除调用的函数;删除相同的函数。该pragma的定义必须在声明或用到的函数之前,语法如下:#pragma FUNC_IS_PURE(func);变量func是C函数的名称。第6章 基于C语言的
33、DSP芯片开发 6)FUNC_IS_SYSTEM Pragma 该pragma指明优化器可优化有名称的函数的行为,此行为是针对有名称函数的ANSI标准定义的。该pragma的定义必须出现在声明或所用到的函数之前,语法如下:#pragma FUNC_IS_SYSTEM(func);变量func是C函数的名称,此函数被当作ANSI标准函数来处理。第6章 基于C语言的DSP芯片开发 7)FUNC_NEVER_RETURNS Pragma该pragma指明优化器优化从不返回到所调用函数的位置。该pragma的定义必须出现在声明或所用到的函数之前,语法如下:#pragma FUNC_NEVER_RETU
34、RNS(func);变量func是不可返回调用C函数的名称。第6章 基于C语言的DSP芯片开发 8)FUNC_NO_GLOBAL_ASG Pragma该pragma指明优化器优化没有配置有名称的全局变量和不包含asm语句的函数。该pragma的定义必须出现在声明或所用到的函数之前,语法如下:#pragma FUNC_NO_GLOBAL_ASG(func);变量func是没有配置的C函数的名称。第6章 基于C语言的DSP芯片开发 9)FUNC_NO_IND_ASG Pragma该pragma指明优化器优化通过指针没有配置和不包含asm语句的函数。该pragma的定义必须出现在声明或所用到的函数之
35、前,语法如下:#pragma FUNC_NO_IND_ASG(func);变量func是没有配置的C函数的名称。第6章 基于C语言的DSP芯片开发 10)INTERRUPT Pragma该pragma可使用户用C代码直接处理中断。变量func是函数的名称。语法如下:#pragma INTERRUPT(func);除了c_int00需要保留为C程序复位中断名称外,其他中断名称都不需要遵循赋名的规则。第6章 基于C语言的DSP芯片开发 6.3.5 asm语句TMS320C3x/C4x C编译器允许用户嵌套TMS320C3x/C4x汇编语言指令。Asm语句在语法构成上与调用有名称的asm一样,用一个
36、常量串声明:asm (assembler text);编译器直接把声明串拷贝到用户输出文件.assembler text中,并且数据格式必须是双精度的。所有的字符串输出代码保留它们的初始值。例如,用户可以插入一个.string命令,格式如下:asm(STR:.string abc);第6章 基于C语言的DSP芯片开发 所插入的代码必须是合法的汇编语言语句。像所有的汇编语言语句一样,这一行必须以标识符(label)开始,接下来是空格(blank)、标号(tab),或注释(星号或分号开始)。编译器不检查字符串,如果有错,将由汇编器检查。若需要更多的信息,请参考TMS320C3x/C4x汇编语言工具
37、用户手册。这里写asm语句不必严格按照正常的C语言语法:它们将作为语句或声明甚至在块外出现。在编译模块的开始处插入即可。第6章 基于C语言的DSP芯片开发 注意:需要特别小心用asm语句时不要破坏C环境。编译器不检查插入的指令。若插入跳转和标识符到C代码中,则可能引起不可预料的结果;若用到改变段或其他影响汇编环境的指令也可能引起麻烦。在用优化器对汇编语句进行优化时要特别小心。尽管优化器不能删除asm指令,但它可以重新安排asm指令附近的代码顺序,这样就可能引起不可预料的结果。asm命令可使用户访问硬件,而C语言则不可以。第6章 基于C语言的DSP芯片开发 6.3.6 初始化静态和全局变量初始化
38、静态和全局变量ANSI C标准指明在没有初始化的静态和全局变量时,必须在程序执行前明确地初始化(赋初始化值0)。在程序被装载时,初始化的工作就已经完成。因为装载过程依靠系统的环境,TMS320C3x/C4x C编译器本身不能预先初始化变量。如果用户导入的程序不是预先初始化的变量,用户必须用连接器在目标文件中初始化变量(赋初始值0)。在连接器命令文件中,.bss段用0填充,如下:第6章 基于C语言的DSP芯片开发 SECTIONS.bss:=0 x00;.由于链接器向输出COFF文件中写入了一个赋0的.bss段,这种方法可能使输出文件的尺寸急剧增加。用常量可限定类初始化静态和全局变量。限定类的静
39、态常量和全局变量初始化不同于其他类型的静态常量和全局变量。没有明确初始化的静态常量和全局变量可能会在初始化前被赋0。第6章 基于C语言的DSP芯片开发 例如:const int zero;/*may not be initialized to zero*/初始化为全局变量的所有常量被放置在.cinit段中。在表达式中,初始化为局部变量的所有常量或者放置在CONST表或者用立即寻址装载。限定类常量的初始化值不被放在.const段,因为这个值必须用直接寻址对其访问。利用小的存储器模块,如果该值被放在.const段,则要求.bss和.const段必须在同一页中。CONST表和.const段是不一样的
40、。CONST表包括如下内容:第6章 基于C语言的DSP芯片开发 整数常量表达式的宽度多于16位;浮点数常量表达式为指数多于4位或尾数多于11位;整数局部变量初始化值多于16位;浮点数局部变量初始化值为指数多于4位或尾数多于11位;全局变量的地址;字符串常量的地址。.const段内容如下:转变声明表;不许初始化全局变量的字符串常量;用作big模型的CONST表。第6章 基于C语言的DSP芯片开发 6.4 基于基于C语言的语言的DSP芯片开发的运行环境芯片开发的运行环境6.4.1 存储器模式存储器模式C编译器把存储器作为单个线性块处理,这个线性块被分成代码和数据子块。C程序生成的每个代码和数据块都
41、被放置在连续的存储器空间中。编译器假定32位的目标存储器地址空间都是可利用的。第6章 基于C语言的DSP芯片开发 特别需要注意的是由链接器定义的存储器映射。链接器定义的存储器映射把代码和数据分配到目标存储器中。如果编译器不清楚存储器映射的类型,则所有位置都不能装载代码或数据,或任何位置都被保留为I/O段。编译器生成的可重置代码允许链接器把代码和数据分配到合适的存储器空间中。例如,用户可以用链接器分配全局变量到内部快速RAM中或分配可执行代码到内部ROM中。第6章 基于C语言的DSP芯片开发 1C编译器生成的段编译器生成的段C编译器对C语言程序编译后可产生6个重定位的代码和数据块,这些块也就是所
42、谓的段,它们可以用不同的方式分配至存储器中,以符合不同系统配置的需要。这6个段可以分为两种类型,一种是已初始化段,另一种是未初始化段。已初始化的段主要包含数据表和可执行代码。C编译器有3个已初始化的段:.text、.cinit和.const。.text段包含所有可执行代码以及浮点数常量;.cinit段包含初始化变量和常量表;.const段包含浮点数常量和swich表。第6章 基于C语言的DSP芯片开发 未初始化段用于保留存储器空间,程序可以用这些空间在运行时创建和存储变量。编译器支持3个未初始化的段:.bss,.stack和.sysmem。.bss段为全局和静态变量保留空间,在小模型中,.bs
43、s段也为常量表保留空间。在程序运行时,C初始化程序从.cinit段中拷贝数据(可能在ROM中)并把它保存在.bss段中。.stack段为系统堆栈分配空间。这时存储器用作向函数传递变量以及分配局部变量。.system段为存储器组合或存储器组,为动态存储器函数malloc、calloc和realloc分配存储器空间。当然,若C程序没有用到这些函数,编译器就不创建.system段。第6章 基于C语言的DSP芯片开发 链接器从不同的模式下取出的单个段并且把名字相同的段合并在一起生成输出段。需要清楚的是汇编器可产生3个默认的段(.text、.bss和.data);然而C编译器不用.data段。C语言程序
44、是由编译器的输出段和汇编器.data段组成的。连接器从不同的模块取出单个的段并且把名字相同的段合并在一起生成输出段。用户可以把这些输出段放在所需要的任何地址处以满足系统的需要。.text、.cinit、.const和.data段在ROM或RAM中连在一起;.bss、.stack和.system段在RAM中连在一起。需要注意的是,.bss 和.const段必须被分配在同一64K的数据页内。第6章 基于C语言的DSP芯片开发 2C系统堆栈系统堆栈C编译器利用TMS320C3x/C4x内置的堆栈机制实现如下功能:(1)保护函数返回的地址;(2)分配局部变量;(3)传递函数变量;(4)保存临时结果;(
45、5)保护寄存器;(6)保护处理器的状态。第6章 基于C语言的DSP芯片开发 堆栈操作是从低地址向高地址增加的。编译器利用两个辅助寄存器来管理堆栈:SP:堆栈指针(SP),指向当前堆栈的栈顶;AR3:帧指针,指向当前局部帧的开始,每一个函数调用时都要在栈顶建立一个新的局部帧,用来保存局部和临时的变量。C运行环境在调用C函数时自动管理这些寄存器。如果用户在汇编语言使用这些堆栈,一定要确保正确的使用它们。第6章 基于C语言的DSP芯片开发 堆栈尺寸可由链接器利用全局符号_STACK_SIZE来设定,对其赋值即定义堆栈的大小。默认的堆栈大小为1K(400h)字,它适合于片内的任何一RAM块。用户也可在
46、链接器的命令行中利用-stack选项和在选项后用常量指明堆栈的大小,以改变堆栈的大小。在系统初始化后,SP所指定的地址为堆栈的的底部。这个地址为.stack段中的首地址。由于堆栈的位置是由.stack段分配所决定的,而实际的位置是在连接阶段确定的。如果用户配置的堆栈在存储器的最后的段(高地址),则堆栈可在存储器空间中无限增长。第6章 基于C语言的DSP芯片开发 特别注意的是,由于C编译器不提供检查堆栈溢出的方法,因此必须保证有足够的空间用于堆栈;否则将发生溢出现象而破坏程序的运行环境,从而导致程序的瘫痪。所以在编写DSP程序和存储器资源分配时,要注意防止堆栈溢出的发生。第6章 基于C语言的DS
47、P芯片开发 3动态存储器分配动态存储器分配由编译器提供的运行支持库包含了几个允许用户在运行时动态分配存储器函数(例如malloc、calloc和realloc)。动态分配并不是C语言本身的标准,而是由标准的运行支持函数提供的。为全局pool或heap分配的存储器定义在.system段中。.system段的大小在链接时用-heap选项来设定,设置的方法是在-heap选项后面加一个常量来指定。同样,链接器也创建一个全局符号_SYSMEM_SIZE,它的值为heap中的字数目,默认大小为1K字。第6章 基于C语言的DSP芯片开发 动态分配目标不是直接寻址(经常用指针访问),并且存储器区在一个独立的块
48、中,因此动态存储器区甚至在小的存储器模式下也有无限的尺寸。所以,即使在程序中申请了大的数据目标,也可以使用效率更高的小存储器模式。为了在.bss段中保留空间,用户可用heap分配大的数组,以代替声明为全局或静态的。例如如下的声明:struct big table10000;用指针和调用函数来替代:struct big*tabletable=(struct big*)malloc(10000*sizeof(struct big);第6章 基于C语言的DSP芯片开发 4存储器大小模式存储器大小模式编译器支持两个存储器模式:大存储器模式和小存储器模式。这两种模式影响.bss如何分配存储器空间。两种模
49、型下的.text和/或.cinit段的尺寸都不受限制。但两种模型下的单个函数限制在32K字的代码或更少内,这允许编译器在整个函数范围内利用相对条件跳转。第6章 基于C语言的DSP芯片开发 (1)小存储器模式:编译器默认的存储器模式。在这种模式下,要求整个.bss段能匹配一个64KB字的存储器在数据页内。这意味着在程序中的静态和全局数据的空间必须小于64K字,并且.bss也不能超过64K字地址范围。编译器在运行初始化时,将数据页指针寄存器指向.bss的开始。随后,编译器就可以用直接寻址()访问.bss中所有的目标(如全局变量、静态变量和常数表),而不用修改DP寄存器。第6章 基于C语言的DSP芯
50、片开发 (2)大存储器模式:大模式和小模式的区别是它不限制.bss块的大小,因此对于全局和静态变量来说,可用空间是无限的。然而当编译器访问保存在.bss中的任何全局或静态变量时,编译器首先保证DP准确的指向目标所在的存储页。为了做到这一点,编译器在每次访问静态或全局数据时,必须用LDP或LDPK指令设置DP寄存器。由于加了这条指令,不仅增加了一个指令字,而且可能引入3个指令周期或2个指令周期。例如,下面是C编译器生成的汇编语言,它是使用LPD指令在访问全局变量x之前设置DP寄存器的例子:第6章 基于C语言的DSP芯片开发 *_x is a global variable*LDP _x;1 ex