一,混合编程的基本环境
在进行混合编程控制之前, 首先要为其创造一个运行的基本环境。这个基本环境包括存储空间的分配、DSP寄存器映射地址的定义以及中断向量的定义等方面。
1 存储空间的分配
对TM S320LF240x DSP 混合编程设计而言, 首先必须对其存储空间组成及如何分配有深入地了解。TM S320LF240x DSP 的存储空间分为程序存储空间、数据存储空间和I/O存储空间,并在各个存储空间中存储着相应的变量或指令。后缀名为.CMD 的命令文件实现了对程序存储器空间和数据存储器空间的分配, 该文件中常使用的伪指令有MEMORY和SECTIONS。MEMORY 伪指令用来标示实际存在目标系统中且可以被使用的存储器范围; SECTIONS 伪指令的作用则是: 描述输入段怎样被组合到输出段内;在可执行程序内定义输出段; 规定在存储器内何处放置输出段; 允许重命名输出段。下面给出了命令文件的示例。
MEMORY (存储器的分配)
{
PAGE 0: vector: origin= 0, length=0x50
(第0 页为中断向量区, 起始地址= 0, 长度= 50h)
PAGE 0: PROG: origin= 0x1000, length= 0x0F000
( 第0 页为外部程序存储器, 起始地址= 1000h, 长度=F000h)
PAGE1: DATA: origin= 0x8000, length= 0x8000
( 第1 页为外部数据存储器, 起始地址= 8000h, 长度=8000h)
}
SECTION S
{
. vectors: {}> vector PAGE 0 (中断向量在第0 页)
. text: {}> PROG PAGE 0 (程序区段在第0 页)
. data: {}> PROG PAGE 0 (. data 数据区在第0 页)
. cinit: {}> PROG PAGE 0 (复位定义在第0 页)
. bss: {}> DATA PAGE 1 (. bss 数据区段在第1 页)
}
2 DSP 寄存器映射地址的定义
TM S320LF240x DSP 的各个模块中都有相应功能的寄存器, 虽然各个寄存器的地址已经指定, 但其所需空间并没有分配, 因此需要在后缀名为.H 的头文件中, 按照各寄存器给定地址严格分配空间。寄存器位于不同的存储空间, 其地址声明方法以及写控制字的操作也是不同的。例如定时器1 比较寄存器(T1CMPR) 是处于数据存储器空间的寄存器, 其地址为7402h。在头文件中的定义如下:
“# define T1CMPR (volatile unsigned int* ) 0x7402”
如果要将控制字“0x4000”放入T 1CM PR 中时, 在C 语言程序中只需写入“*T1CMPR =0x4000”即可。而处于I/O空间FFFFh 的等待状态发生器控制寄存器(WSGR) , 其定义方式如下:
“# define WSGR portFFFF
ioport unsigned portFFFF; ”
对于这种类型的寄存器, 只需使用语句“WSGR= 0x0000”将控制字写入即可对其进行控制。
3 中断向量的定义
在TM S320LF240x DSP 的各个模块中, 提供了许多相应的中断功能。利用这些中断, 可以更好更有效地控制DSP, 令程序的运行效率更高。复位中断向量(c_int0) 是在实时运行支持库(rts2xx.lib)中定义的一种特殊的中断, 它的作用是先进行软件堆栈, 然后初始化全局变量, 最后调用主程序main()。所以当程序复位(Reset) 时, 应跳转到c_int0 进行相应的处理。中断向量表则是将主程序中用到的中断子程序和相应中断级别类型连接起来的一个简单的跳转指令表。它是后缀名为ASM 的汇编语言文件, 需要在命令文件中将其声明到程序存储地址空间0000h~0040h
二, 混合编程的关键环节
对TM S320LF240x DSP 采用混合编程, 关键的问题在于如何在程序中将C 语言和汇编语言结合起来。主要使用两种方法: 直接内嵌式和调用子程序式。
1 直接内嵌式
在C 程序中直接内嵌汇编语句是一种最为简单的结合方式。DSP 当中有一些C 语言无法操作的控制位, 需采用这种方式来实现, 例如清中断标志位“CLRC INTM ”。要嵌入汇编, 只需在汇编语句两边加上双引号并用小括号括起来, 前面再加上asm 关键字, 即“ asm (" 汇编语句" ) ; ”。注意汇编语句不能顶着前一个双引号写, 它们之间必须用空格、tab 或标号开头。这种方式虽然操作简单, 但是破坏了C 语言的完整性, 因此只提倡在程序开始的系统初始化部分少量使用, 而在C 语言中嵌入实现某一完整功能的多句汇编语言时,不提倡采用这种方式。
2 调用子程序式
在C 语言中调用汇编子程序是另一种混合编程的方式。与内嵌式相比, 这种方式虽然复杂,但是其应用范围更为广泛。在C语言中调用子程序有一定的规范性, 其主要内容如下。
2. 1 主程序操作
主程序调用另外一个子程序时需要进行如下的操作(特别注意ARP 已经由编译器自动设置为AR1)。
1) 主程序把需要向子程序传递的参数按反序压入堆栈, 即最右边的参数最先被压入堆栈, 最左边的参数最后被压入堆栈。
2) 主程序调用子程序。
3) 在子程序返回前, ARP 已经被设置为AR1。
4) 当子程序调用完毕后, 主程序要弹出先前压入堆栈的传递参数。这个操作通过下面的命令语句实现:SBRK n,n 是主程序向子程序传递的参数的个数。
2. 2 子程序操作
一个子程序被调用时, 需要进行下面的一些操作(在子程序的入口处, 假设ARP已经被设置为AR1, 这是由C编译器自动完成的)。
1) 从硬件堆栈中弹出返回地址, 然后把它压入软件堆栈。
2) 把主程序的数据结构指针FP压入堆栈。
3) 如果子程序改变了AR6或AR7, 也需要把它们压入堆栈。
4) 分配局部数据结构。
5) 执行子程序的实际任务代码。
6) 如果子程序有返回值, 则把这个返回值放入累加器中。
7) 设置ARP为AR1。
8) 解除分配的局部数据结构。
9) 如果AR6 和AR7 曾经被保存过, 则从软件堆栈恢复它们的值。
10) 从软件堆栈恢复FP。
11) 把软件堆栈中存储的返回地址压入硬件堆栈。
12) 返回。
如果是C 语言主程序调用C 语言子程序, 则上面所述的调用规范是C 编译器在生成汇编语言代码时自动完成的。但如果是C 语言主程序调用汇编语言子程序时, 汇编子程序中必须遵循前面所述“子程序需要进行的操作”规范进行编写,而“主函数需要进行的操作”则由C 编译器自动完成。
三,混合编程中经常出现的问题及解决方法
由于混合编程存在一定的难度, 在其过程中经常会出现一系列的问题, 这些问题涉及到编程规范等各方面。本文将从以下几个方面进行介绍。
1 头文件定义
头文件的定义有两种方式。第一种是以指针变量的形式定义了各种寄存器, 即采用如下形式:
“volatile unsigned int T1CMPR (volatile unsigned int * )0x7402”;
第二种则是用宏定义的方式定义各种寄存器的名称为
“# define T1CMPR (volatile unsigned int* ) 0x7402”。
第一种方式采用的是变量的形式, 如果使用这种方式来定义头文件的话, 则会在数据存储器中为这些寄存器重复分配空间, 这样就浪费很多的存储空间, 所以一般不使用这种方法。而第二种方法是宏定义方式, 不会给寄存器重复分配空间,因此常采用这种方法来定义头文件。此外, 需要注意的是, 在定义处于I鯫 地址空间的寄存器时, 如:
“# define WSGR portFFFF
ioport unsigned portFFFF; ”
第二行结尾处的分号是不可缺少的, 否则系统编译时不通过。
2 字母大小写问题
在C 语言中, 对于字母大小写的区分是很严格的, 因此在混合编程的过程中也应该严格遵守这一点。例如, 在写命令文件时, 误将“.bss”写成“.bSS”, 此时系统将无法给变量分配存储空间,导致程序错误, 无法执行。
3 C 语言库函数应用
在T I 的C 编译器中内置了很多的函数, 包含于rts2xx.lib 的函数库中。库函数并不是C 语言的一部分, 它是由人们根据需要编制并提供用户可直接使用的。每一种C 编译系统都提供了一批库函数, 不同的编译系统所提供的库函数的数目和函数名及函数功能是不完全相同的。关于库函数的使用, 则只需在源文件中添加语句“#include“函数名.h””, 就可使用相应的库函数了。
4 变量定义
在定义变量类型的时候, 要注意所定义的变量的长度及其类型是否相符合。如果不符合, 则数据的赋值内容将会被改变。另外, 定义变量的语句要在程序一开始就给出。如果是全局变量或静态变量, 则在主程序之前定义; 如果是局部变量, 需在子程序开始处定义。否则系统将不能正确定义变量。
5 汇编子程序调用
在C 语言主程序中调用汇编子程序时有许多需要注意的地方。首先, 在C 语言主程序中要定义子程序外部模块的变量格式, 例如:
“extern int PID ( int, int) ; ”
在汇编子程序的开始需采用以下方式定义:
“. def_ PID
. text
_ PID: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ret”
C 语言程序调用汇编模块, 则该汇编模块的名称必须要声明成. global 或. def, 且模块的名称必须要用下划线“_ ”为起始字。如果遇到在汇编中要使用C 语言中定义的变量时, 则在汇编语言中需要将变量用“. ref”声明为外部定义的变量,并在使用的变量名前加下划线“_ ”。然后进行编译连接时, 将汇编子程序也包含到工程中去就可以了。另外, 在汇编程序中, 需要在开头和结尾处编写程序进行现场保护和现场恢复。这段语句的编写是根据不同的程序需要而改变的。
6 堆栈长度问题
在C 语言中, 子程序的编写也需要考虑一些问题。例如要注意定义的局部变量的个数。因为在调用子程序的时候, 局部变量是暂时放在堆栈中的, 所以如果变量过多, 超过堆栈的长度, 则程序不能通过。例如, 用子程序算正弦表时
void scaculate ()
{
double Q [ 1025 ];
for ( i= 0; i< 1025; i+ + )
{
Q [ i]= 0. 00613592315154253 i;
stable[ i]= sin (n) ;
}
}
这样Q [1025]数组太长, 变量太多, 超过堆栈的长度, 应改为
void scaculate ()
{
double n;
for ( i= 0; i< 1025; i+ + )
{
n= 0. 00613592315154253 i;
stable[ i]= sin (n) ;
}
}
7 计算简化
在编写程序的时候, 应该将计算次数降到最低并对其尽量简化。这样在将C 语言程序编译成汇编语句时, 可以减少汇编语句的条数, 以此来减少程序运行的时间, 提高执行效率.