汇编语言程序执行速度快,但是开发周期长、移植性和可读性较差。C语言程序设计开发周期短、移植性和可读性较好,执行速度通常可满足要求。
DSP支持通过汇编、C/C++语言开发其软件。一般来说C编译器较C++编译器具有更高的编译效率,同时随着C编译器的发展,利用C编译器和C语言源文件所生成的目标代码,其执行的效率已经十分接近汇编语言程序。因此,相对于庞大、复杂的汇编语言系统来说,C语言具有不可比拟的优势。在大多数应用场合下,使用C语言开发DSP软件程序更为适宜。TMS320x28x的C/C++编译器支持由美国国家标准学会定义的ANSI C语言标准。采用C语言编程具有代码的可读性与可移植性强、开发效率高的特点。
在C语言中,每个变量在使用之前必须定义其数据类型,而每个数据类型都有与之对应的类型名,这些类型名都是编译器的保留字。各种数据类型的长度、描述及范围见表2-1。
表2-1 C语言常用数据类型
由于C2000中数据的最小长度为16位,因此所有的字符(char)型数据,包括有符号型(signed char)和无符号字符型(unsigned char),长度均为16位,即用一个字的长度表示。
数据类型的其他特点有:
1)所有的整型(char, short, int以及对应的无符号类型)都是等效的,用16位二进制值表示。
2)长整型和无符号长整型用32位二进制值表示。
3)有符号数用补码符号表示。
4)char是有符号数,等效于int。
5)枚举类型enum代表16位值,在表达式中enum与int等效。
6)所有的浮点类型(float,double及long double)等效,表示成IEEE单精度格式。
头文件(扩展名为.h)是C语言不可缺少的组成部分,是用户程序和函数库之间的纽带,它本身不含程序代码,只是起描述性作用,是一种包含功能函数、数据接口声明的载体文件,用户程序只要按照头文件中的接口声明来调用库功能,编译器就会从库中提取相应的代码。TI公司提供头文件供用户使用,其中定义了DSP系统用到的寄存器映射地址,寄存器位定义和寄存器结构等内容。DSP2833x头文件主要包含DSP2833x.h和各个外设头文件。
在每个主程序中一般都会出现头文件DSP2833x_Device.h,这个头文件中包括了所有其他外设头文件以及对一些常量的定义等内容。例如,外设头文件有:
对常量的定义有
为了增加可移植性,头文件还重新定义了16位和32位有符号或者无符号整数的基本类型,例如
除此之外,还定义了中断标志寄存器和中断使能寄存器,以及一些汇编指令在C语言中的重新定义,例如
由于在DSP283x_Device.h头文件中已经包括了所有外设头文件,所以在主程序中不需要预定义外设头文件,但是在程序运行时,外设头文件也必须加载。在外设头文件中对外设寄存器进行了定义,使得程序既可以对整个寄存器进行读写操作,也可以对其中的每一位进行操作。以下是CPU定时器控制寄存器TCR的位域定义。
结构定义中对每个成员进行类型说明,例如“Uint16 TIE:1;”表示TIE是一个无符号整型变量,冒号表示成员是不满16位的整型数据,这样的成员称作字段,冒号后面的数字1表示该字段占用的二进制长度为1。编译器可将各个字段按顺序合并成一个字,当一个结构中的有效字段长度不足16位时,可以加入一些保留字段,以保证数据的完整性,如结构成员rsvd1~rsvd3为保留位。
位域定义方法允许用户直接对寄存器的某些位进行操作,通过联合声明允许对各个位域或整个寄存器进行访问,例如:
头文件根据定义的联合重新定义了CPU定时器中所有的寄存器结构,例如:
外设寄存器按照其占用的存储器地址依次排列;保留的结构成员(如rsvd1)仅用于占用存储器中的相应空间;Uint16和Uint32是指无符号16位和32位数的类型定义。对于C2000芯片,Uint16和Uint32分别等效于unsigned int和unsigned long。
在外设头文件中还包含对支持的变量、函数原型、外部定义和常用操作的定义。例如:
在一个C源程序中,除了变量和函数的定义、声明以及表达式等基本程序语句外,还包括一些#号开始的编译预处理命令(Preprocessor Directive)。这些预处理命令由编译器正式编译之前调用相应的预编译函数来解释和执行。主要的编译预处理功能有宏定义、文件包含及条件编译命令等。
宏(Macro)定义是指用一个指定的名字来代表一个常量表达式或字符串,其复杂形式是带参数的宏。宏定义的一般格式为
#define标识符常量表达式或字符串
例如:
通常#define出现在源程序的首部,使用宏名之前一定要用#define进行宏定义。宏定义不是C语句,不必在行末尾加分号。也可以将常用的宏定义放到头文件中。
文件包含是指一个程序文件将另一个指定文件的内容全部包含进来。一般格式为#include“被包含文件名”或#include<被包含文件名>
其中“被包含文件名”是一个已经存在于系统中的文件名字。被包含文件通常称为头文件,通常.h以作为后缀,例如:
其功能是将头文件“math.h”的内容嵌入该命令行处,使它成为源程序的一部分。当用一对尖括号时,编译系统按设定的标准目录搜索头文件。当用一对双引号时,编译系统先在源文件所在的目录中搜索,搜索不到,再按设定的标准目录搜索头文件。
文件包含预处理命令行通常放在文件的开头,被包含的文件内容通常是一些公用的宏定义,如外设寄存器定义或外部变量说明等。例如
使用包含文件应注意以下几点:
1)调用标准库函数,例如数学函数时,一定要包含所要用到的库文件。
2)头文件只能是ASCII文件,不能是目标代码文件。
3)一个#include命令只能包含一个头文件,若要包含多个头文件,需要用多个#include命令。
4)头文件包含可以嵌套,即被包含的头文件可以再包含其他的头文件。
条件编译是在编译C文件之前,根据条件决定编译的范围。其格式有
(1)编译格式1
其功能是若标识符已被定义过,则对程序段1进行编译;否则对程序段2进行编译。可简化为
(2)编译格式2
其功能是若标识符未被定义过,则对程序段1进行编译;否则对程序段2进行编译。
例如:
C语言与汇编语言混合编程通常有如下3种方法:①在C程序中直接嵌入汇编语句;②独立的C模块和汇编模块接口;③C程序中访问汇编程序变量。
在C程序中嵌入汇编语句是一种直接的C模块和汇编模块接口方法。这种方法一方面可以在C程序中实现用C语言难以实现的一些硬件控制功能;另一方面也可以在C程序中的关键部分用汇编语句代替C语句以优化程序。这种方法的一个缺点是它比较容易破坏C环境,因为C编译器在编译嵌入了汇编语句的C程序时并不检查或分析所嵌入的汇编语句。直接在C语言程序中相应位置嵌入汇编语句,只需在汇编语句加上双引号和小括号,前面加asm标识符号,称为ASM语句(ASM Statement),一般格式为
asm("汇编语句")
例如:
注意双引号内第一个字符必须是空格,这与汇编语言程序的要求是一样的。
独立编写C程序与汇编程序,分别编译、汇编生成目标代码模块,然后用链接器连接起来。C程序可以调用汇编子程序,也可以访问汇编程序中定义的变量。同样汇编程序可以调用C函数或访问C程序中定义的变量。
在编写独立的汇编程序时,必须注意以下几点。
1)不论是用C语言编写的函数还是用汇编语言编写的函数,都必须遵循寄存器使用规则。
2)必须保护C函数要用到的几个特定寄存器(XAR1、XAR2、XAR3、SP)。
3)中断程序必须保护所有用到的寄存器。
4)从汇编程序调用C函数时,第一个参数(最左边)必须放入累加器中,剩下的参数按自右向左的顺序压入堆栈。
5)调用C函数时,注意C函数只保护了几个特定的寄存器,而其他的可以自由使用。
6)长整型和浮点数在存储器中存放的顺序是低位字在高地址,高位字在低地址。
7)如果函数有返回值,返回值存放在累加器中。
8)汇编语言模块不能改变由C模块产生的.cinit段,如果改变其内容将会引起不可预测的后果。
9)编译器在所有标识符(函数名、变量名等)前加下画线“_”,因此,在编写汇编程序时,必须在C程序可以访问的标识符前加“_”。
10)任何在汇编程序中定义的对象或函数,如果需要在C程序中访问或调用,则必须用汇编命令。
从C程序中访问在汇编程序中定义的变量或常数,可以分为访问在或不在.bss段中定义的变量两种情况。
对于访问在.bss段中定义的变量,可以采用如下方法实现:①采用.bss命令定义变量;②采用.global命令将命令声明为全局变量;③在汇编程序变量名加下画线“_”;④在C程序中将变量声明为外部变量,然后进行正常的访问。
【例2-1】 在C程序中访问在.bss段中定义的变量。
汇编程序:
对于访问不在.bss段中定义的变量,例如访问汇编程序的常数表,可以定义一个指向该变量的指针,然后在程序中间接访问该变量。
【例2-2】 在C程序中访问不在.bss段中定义的变量。
汇编程序:
C程序:
TMS320F2833x的C/C++编译器除了支持标准的const、volatile关键字外,还支持cregister、interrupt等关键字。
C/C++编译器支持ANSI/ISO标准的关键字const,通过该关键字可以优化和控制存储空间的分配。const关键字用来表明变量或数组的值是不变的。例如
编译用户程序时优化器会分析数据流,尽可能避免对存储器的直接读/写操作。因此,对存储器或外设寄存器进行访问时,需要使用volatile关键字,来说明所定义的变量可以被DSP系统中的其他硬件修改,而不是只能被C语言本身修改。用volatile关键字的变量被分配到未初始化块,编译器不会在优化时修改引用volatile变量的语句。例如以下语句循环地对一个外设寄存器的地址进行读写操作,直到读出的值等于0xFF。
while( * ctrl != 0xFF);
* ctrl指针所指向的地址内容在循环过程中不会发生变化,该循环语句会被编译器优化,对存储器执行一次读操作。如果定义 * ctrl指针为volatile类型变量,即
则 * ctr指针指向一个硬件地址,比如PIE中断标志寄存器,该地址单元的内容可以被其他硬件修改。
cregister关键字允许采用高级语言直接访问控制寄存器。在TMS320F2833x的C语言中,cregister仅限于中断使能寄存器IER和中断标志寄存器IFR,程序中应有如下声明。
可以用运算符|(位或)和&(位与)进行操作,例如
IER = 0x100;
IER |= 0x100;
IFR |= 0x0004;
IFR &= 0x0800;
interrupt关键字用来声明一个函数是中断服务程序。CPU响应中断服务程序时需要遵守特定的规则,如函数调用前依次对相关寄存器进行入栈保护,返回时恢复寄存器的值。当一个函数采用interrupt声明后,编译器会自动为中断函数产生保护现场和恢复现场所需执行的操作。对于采用interrupt声明的函数,其返回值应定义为void类型,且无参数调用。在中断函数内可以定义局部变量,并可以自由使用堆栈和全局变量,例如:
有一个特殊的名为c_int00的中断程序(汇编语言中名称为_c_int00),用于DSP复位中断的处理。它完成系统初始化并调用主函数main(),是用户C程序的入口。