|
FD216 DSP开发指南
摘要:
FD216是一款智原科技(Faraday Technology)公司出品的结构和AD公司的ADSP-2181类似,指令集兼容的高性价比16位定点DSP芯片。利用智原科技提供的开发工具包,用户可以方便的将C算法移植到FD216中。本文详细介绍了怎样在FD216评估板上用嵌入式C、汇编语言混合编程的方法进行开发的整个流程及设计要点。
关键词:DSP 嵌入式C语言 汇编语言
1. 介绍
1.1 DSP概述
数字信号处理器(Digital Signal Processor,DSP)是一种具有特殊结构的微处理器。DSP芯片的内部采用程序和数据分开的哈佛结构,具有专门的硬件乘法器,广泛采用流水线操作,提供特殊的DSP 指令,可以用来快速地实现各种数字信号处理算法。自从20世纪70年代末80年代初DSP芯片诞生以来,DSP芯片得到了飞速的发展。在近20年时间里,DSP芯片已经在信号处理、通信、雷达、消费类电子等许多领域得到广泛的应用。
1.2 FD216 16位定点DSP特点介绍
FD216是一颗高效能而易于使用的16位嵌入式数字信号处理器,包含DSP内核、芯片内建数据存储器、程序内存、两个串行接口、一个定时器、12组可编程I/O、内部直接内存存取(IDMA)接口、外部内存接口和串行式在线仿真器(ICE)接口。其特点如下:
1.2.1 指令周期为7ns/5ns,运行速度最大为190MIPS。
1.2.2 独立的算术/逻辑单元(ALU)、乘法/累加器(MAC)、桶型移位器(Barrel Shifter)。
1.2.3 单周期指令和多功能指令。
1.2.4 具有两个串行接口。
1.2.5 兼容ADSP-218X系列汇编代码。
1.2.6 24K*24位word片内程序代码存储器;8K*16位word片内程序数据存储器;8K*16位word片内数据存储器。
1.2.7 2个独立的数据地址产生器DAG。
1.2.8 具有内部直接内存存取(IDMA)接口和外部内存接口。
1.2.9 提供串行式在线仿真器(SICE)接口进行在线系统仿真调试。
2. FD216 开发环境
目标系统为智原科技提供的FD216DSP评估板,系统环境为WIN2K,软件环境为FD2XX工具包(fd2xx_toolset)。
图一为FD216开发系统框图:
2.1 FD216与ADSP2181有几处不同,在此简单说明一下:
2.1.1 FD216与ADSP2181的存储器容量不同。
2.1.2 FD216的CM、PM和DM是严格区分的,在程序设计时要将放在PM区和DM区的数据分别说明。在移植ADSP2181汇编代码时,也要做相应修改。
2.1.3 FD216与ADSP2181的某些寄存器含义或地址不同。
2.2 开发工具及开发流程
fd2xx_toolset是智原科技的FD2XX(FD216/FD220/FD230)系列DSP开发工具,主要由可执行文件、库文件和各种帮助文档组成。fd2xx_toolset的可执行文件包括汇编、编译、链接工具以及调试工具等,见表1。
表1 fd2xx_toolset的可执行文件及用途
可执行文件名 | 用途 |
cpp .exe* | 汇编预处理程序 |
g2xx .exe | 基于GNU的FD2XX系列C编译器 |
fd2asm.exe | FD2XX系列命令行汇编程序 |
fd2ln.exe | FD2XX系列链接工具 |
fd2osplt.exe | PROM分割工具 |
fdb.exe | FD2XX系列仿真调试工具 |
注:“*”代表该程序一般不单独使用,而是由g2xx.exe调用。
fd2xx_toolset工具包中的软件仿真调试工具fdb.exe,采用MS-DOS命令行界面,使用方便。它的主要特点是:可以仿真调试FD2XX系列的DSP;支持断点、单步、全速运行等各种常见调试方法;可以随时查询和修改DSP的程序代码RAM(CM)、程序数据RAM(PM)、数据RAM(DM)和各寄存器的内容;可以仿真中断,进行可执行程序的性能评估(Profile)。fdb.exe是调试程序和验证复杂算法的极好工具。用嵌入式C语言开发FD216 DSP 的典型流程如图二。
3. 系统设计
3.1 C运行时间库
C语言运行时间库(C Run Time Library)是位于LIB目录下的lib*.a文件,是整个C开发工具的核心之一,提供了大量的可以直接调用的库函数。这些库函数的函数原型包含在INCLUDE目录下的头文件中。这些头文件有的还包含一些宏定义。另外,在实际开发过程中,为了减少代码开销,提升程序运行效率,可从库函数的汇编语言源代码中提取有用的代码生成自己的用户库文件reallibrary.dsp,代替系统自带的C运行库。
3.2 设置内存映射寄存器的访问方式,可将其放在define.h头文件中。
例如:
#define Sport0_CLK_Ctrl *(short *) 0x3ffa
调用时,在C程序中直接对寄存器名赋值。
Sport0_CLK_Ctrl = 0x500; /* 给地址0x3ffa赋值0x500 */
3.3 在FD216评估板上进行正确的硬件配置。
TMODE1:程序存储器片内、片外选择模式。
MMAP:复位后程序自动引导使能。
3.4 串口0配置及使能。
FD216评估板可以进行音频应用的实验,评估板所采用的Audio Codec是Philips公司的UDA1345TS,该立体声编解码器用于完成2路16位A/D和D/A转换,为串行输入输出方式。它与FD216的串口0相连,要输出正常的16位或8位PCM线性数据,必须对FD216串口0相关寄存器进行正确的编程设置。这些寄存器的配置必须保证数据格式符合数字音频总线(I2S_Bus)规范。
注意:配置时应先进行串口0初始化,然后使能,再在音频应用程序中根据不同的采样频率对时钟控制寄存器(0x3ffa)CLK_Ctrl和串口0串行时钟分频寄存器(0x3ff5)Sport0_Sclkdiv进行不同的参数设置。
例如:
假设DSP系统工作频率fdsp为147.456MHz,采样频率fs为32KHz,在评估板上系统时钟为采样频率的384倍。
则:
CLK_Ctrl[14:8] = 147.456/(2*384*fs) – 1 = 0x5,CLK_Ctrl = 0x500;
Sport0_Sclkdiv = 147.456/(2*32*fs) – 1 = 71;
3.5 Serial ICE(SICE)仿真调试接口
FD216有一个串行ICE接口用于片上仿真调试,通过ICK(异步ICE时钟)、IMS(ICE模式选择),ID(双向ICE数据传送)三个管脚和外部SICE命令解析器联系,可在仿真时访问目标板上的资源和DSP内部的寄存器和存储器。
4. 程序设计
4.1 在程序的头部建立正确的运行头(RunTimeHeader)文件。
该文件可以单独写成一个.DSP汇编文件,也可用嵌入(inline)的方式将ASM语句嵌到C程序文件的头部。
例如in-line的方式:
asm(".external ___lib_setup_everything;");
/* ------------------------------------中断向量表--------------------------------------------*/
asm("jump start; rti; rti; rti;"); /* Reset */
asm("jump ir2_get_data; rti; rti; rti;"); /* IRQ2 中断服务子程序——从idma接口取数据的控制程序 */
asm("rti; rti; rti; rti;"); /* IRQL1 */
asm("rti; rti; rti; rti;"); /* IRQL0 */
asm("jump intsp0; rti; rti; rti;"); /* SPORT0 tx 中断服务子程序——串口0发送数据 */
asm("rti; rti; rti; rti;"); /*SPORT0 rx */
asm("rti; rti; rti; rti;"); /* IRQE */
asm("rti; rti; rti; rti;"); /* BDMA */
asm("rti; rti; rti; rti;"); /* SPORT1 tx or IRQ1 */
asm("rti; rti; rti; rti;"); /* SPORT1 rx or IRQ0 */
asm("rti; rti; rti; rti;"); /* Timer */
asm("rti; rti; rti; rti;"); /* Power down */
/* -----------------------------------------------------------------------------------------------*/
asm("start: "); /* Begin DSP Program */
asm("call ___lib_setup_everything;"); /* 调用C库函数中的___lib_setup_everything函数作程序启动时的初始化工作 */
asm("call main_;"); /* 开始执行 C 程序 */
asm("ir2_get_data:
… … … /* ir2_get_data 程序代码 */
");
asm("intsp0:
… … … /* intsp0 程序代码 */
");
4.2、内存分页覆盖
4.2.1 FD216的内存结构。
FD216的整个内存由 Program-Code Memory(CM)、Program-Data Memory(PM)、Data Memory (DM)和I/O存储空间四部分组成。
4.2.1.1 Program-Code Memory(CM):存储DSP程序代码。
片内是24k*24 的CM内存,其中分为两部份:1个8k字不可覆盖页(0x0000~0x1fff);4个4k字覆盖(overlay)页(0x2000~0x2fff),使用pmovlay寄存器的第3位~第0位来选择使用哪一页CM覆盖页。
4.2.1.2 Program-Data Memory( PM):存储数据变量。
片内是8k*16的 PM内存,分为2页覆盖页,另外4个4k*16片外PM字覆盖页,地址空间均为0x3000~0x3fff,使用pmovlay寄存器的第7位~第4位来选择使用哪一页PM覆盖页。
4.2.1.3 Data Memory (DM):存储数据变量和内存映射寄存器。
片内有1个8k*16的DM内存,另外有4个8k*16片外DM字覆盖页,地址空间都为0x0000~0x1fff。另外0x2000~0x3fdf的地址空间为DM保留区域,0x3fe0~0x3fff的地址空间为32个内存映射寄存器的放置区域。
4.2.1.4 IO存储空间:用于DSP和外围器件的连接。
FD216的内存结构见图三:
4.2.2 FD216系统说明文件fd216.sys
该文件说明了FD216 DSP的内存分配方式,是支持DSP程序正确运行的系统文件。
.SYSTEM FD216_standard_arch;
.FD216;
.MMAP0;
.SEG/CM/RAM/ABS=0/CODE cm_noovlay[0x2000]; /* CM不可覆盖段 */
.SEG/CM/RAM/ABS=0x2000/CODE cm_ovlay[0x1000]; /* CM覆盖段 */
.SEG/PM/RAM/ABS=0x3000/DATA pm_ovlay[0x1000]; /* PM覆盖段 */
.SEG/DM/RAM/ABS=0/DATA dm_ovlay[0x2000]; /* DM覆盖段 */
.SEG/DM/RAM/ABS=0x2000/DATA dm_noovlay[0x1FE0]; /* DM不可覆盖段 */
.SEG/DM/RAM/ABS=0x3FE0/DATA mmio[0x20]; /* 内存映射寄存器 */
.ENDSYS;
4.2.3 数据、代码分页
4.2.3.1考虑程序需不需要分页,需要分为几页,哪些程序应放在不可覆盖代码cm_noovlay段,哪些程序应放在覆盖代码cm_ovlay段0页、1页等问题。注意不要在频繁调用的子函数中进行内存切换,而要将内存切换放在循环的外层。另外要把公共函数都放在cm_noovlay不可覆盖段,而不要放在cm_ovlay可覆盖段。
4.2.3.2 考虑数据需不需要分页,需要分为几页,哪些数据应放在pm_ovlay程序数据覆盖段0页、1页,哪些数据应放在dm_ovlay数据覆盖段0页(片内)、1页等片外页。
4.2.3.3 注意DM中dm_noovlay段(0x2000~0x3fdf)应保留不用,并且DM数据不要超过8k字空间,否则链接工具会自动将多出的数据分配到dm_noovlay段中,而导致.exe执行文件装载失败。解决办法是分一部分数据到pm_ovlay段中,并且如非必要,尽量不要使用dm_ovlay段1页等片外页。
4.2.4使用开发工具包完成分页操作。
在程序中添加了分页语句后,接着使用开发工具包完成分页操作。下面用实例来说明怎样使用开发工具包进行分页。
假如某个应用程序由file_page0.c、file_page1.c和file_page2.c三个C源程序组成,我们欲将file_page0.c放在不可覆盖代码cm_noovlay段,file_page2.c放在覆盖代码cm_ovlay段0页,file_page1.c放在覆盖代码cm_ovlay段1页。具体分页操作过程如下:
4.2.4.1 写一个compile.bat批处理程序,进行嵌入式C程序编译操作,生成.dsp汇编文件。
compile.bat参考文件如下:
cls /* 清屏 */
g2xx file_page0.c file_page1.c file_page2.c
-mcpu=fd216 /*编译器选中FD216 DSP */
-c /* 只编译生成目标文件,不链接 */
-O2 /* 编译时进行代码优化 */
-Wall /* 打开告警选项 */
4.2.4.2 接着给.dsp文件增加适当的代码段空间的起始abs绝对地址。
例如:将file_page1.dsp
.MODULE/RAM _file_page1_;
修改为:
.MODULE/RAM/abs=0x2000 _file_page1_;
注意:file_page1.dsp和file_page2.dsp代码段空间的起始绝对地址应都设为0x2000,这样才能把程序分配到CM覆盖段。
4.2.4.3 接着对修改后的.dsp文件进行汇编,生成*.obj目标文件。用asm.bat批处理程序来完成。asm.bat参考文件如下:
cls
fd2asm file_page0.dsp file_page1.dsp file_page2.dsp reallibrary.dsp
-fd216 /* 选择目标系统为FD216 */
-g /* 在目标文件中包含调试信息 */
-l /* 产生.lst清单文件 */
说明:reallibrary.dsp是从库函数的汇编语言源代码中提取有用的代码生成的用户库文件,代替系统自带的C运行时间库。
4.2.4.4 用链接工具的“fd2ln -group filegroup”命令行语句进行目标文件链接,链接中自动进行分页并生成可烧录到EEPROM中的.exe文件。filegroup参考文件如下:
file_page0 /* 需要链接的第0页程序文件 */
reallibrary /* 用户库文件 */
-a fd216 /* 指定系统结构文件:fd216.sys */
-e page0 /* 生成page0.exe可执行文件 */
-g /* 产生后缀.sym的符号表文件 */
-x /* 产生后缀.map的内存映射文件 */
-end
file_page1 /* 需要链接的第1页程序文件 */
-e page1 /* 生成page1.exe可执行文件 */
-end
file_page2 /* 需要链接的第2页程序文件 */
-e page2 /* 生成page2.exe可执行文件 */
-end
链接无误后,系统生成page0.exe和page1.exe、page2.exe三个可执行文件和其它相关文件。
4.2.4.5 用PROM分割工具“fd2osplt -v -m page”命令行语句查看程序、数据在内存中的分页放置情况。也可通过链接后生成的page0.map、page1.map和page2.map三个内存映射文件来查看每个C源文件所占据的代码空间和每个DM/PM变量所占据的数据空间等相关信息。
4.2.4.6 用fdb工具来装载(
4.3 中断服务子程序
FD216中断控制器允许处理器响应12个中断,例如,在本文的音频实验中,用到了如下三个中断服务子程序。
4.3.1 reset中断服务子程序:
程序运行时,首先进入reset中断服务子程序,即从start程序开始运行,其优先级最高。
4.3.2 ir2_get_data外部中断服务子程序:
通过内部直接存储器访问(IDMA)接口从HOST(PC)硬盘取数据的控制程序。当该中断发生时,ir2_get_data函数就会控制DSP跳转到相应的指针位置。
4.3.3 intsp0中断服务子程序:向串口0发送数据的子程序,每向串口0发送一次数据,进入intsp0中断服务子程序一次。为了加快程序运行速度,使用第二套寄存器中来执行intsp0中断服务子程序。
4.4 代码移植
在实际开发工作中需要将C算法程序移植为可在DSP系统中运行的嵌入式C程序。
注意事项:
4.4.1 DSP系统中没有文件I/O函数,将C程序中的文件I/O函数改成嵌入式系统中的内存直接读、写方式。
例如:将C程序
for(i = 0; i < size ; i ++)
{
fread (&b,sizeof(char),1,fp_in);
}
改成
for (i = 0; i < size; i++)
{
*b++ = *data_pointer++; /* data_pointer指向RAM具体地址 */
}
4.4.2 在DSP程序中去掉所有的printf()函数。
4.4.3 注意数据类型的处理。
将C程序中的int类型改成short或long类型,unsigned int类型改成unsigned short或unsigned long类型,并且为了提高程序运行效率注意尽量少用(unsigned)long型,算法验证无误后再移植到DSP中。
4.4.4 在保证程序正确性、稳定性的前提下,通过修改C算法和数据结构,尽量缩减C程序中表数据的尺寸,使表在DSP内存中占据的空间尽量小。
4.4.5 合并一些简单函数,尽量减少C调用次数,提高DSP程序的运行效率。
例如:将
void exit_c()
{
printf(“error exit\n”);
exit(1);
}
删掉,在调用exit_c()的其它函数中直接用“exit(1);”代替。
4.5 代码优化
对于实时性要求较高的应用,必须使用汇编语言。fd2xx_toolset为用户提供了两种嵌入式C语言与汇编语言的接口方法:用ASM(“;”)方法,直接嵌入汇编语言语句;用汇编语言编写子程序,供C语言程序调用。
4.5.1 用asm(“;”)方法,直接嵌入汇编语言语句。
例如
asm("imask = b#0000000000;"); /* 清屏蔽中断 */
4.5.2 用汇编语言编写子程序,供C语言程序调用。
直接用汇编语句来替代调用频繁的C函数,而这常常就是系统运行瓶颈之处。这其实就是C、汇编的混合编程,其关键是传递参数和函数的返回值。
注意:
4.5.2.1 在汇编程序中不要使用C编译器g2xx保留的寄存器:M1、M2、M6、I4、M4、L4。
4.5.2.2 L0、L1、L4、L5、L6、L7这些寄存器可以在汇编程序中临时使用,但使用前必须保存,使用后必须恢复。
4.5.2.3 AF、I1、I6、SB、M3、M5、MF、SE、MR、MY1、PX、SI这些寄存器可以在汇编程序中临时使用,使用后不必恢复。
4.5.2.4 C、汇编混合编程的参数传递和函数的返回值要符合完整的约定:
4.5.2.4.1 用AR寄存器传递第1个单字参数;
4.5.2.4.2 用AY1寄存器传递第2个单字参数;
4.5.2.4.3 用堆栈传递第3个及以后的单字参数;
4.5.2.4.4 用堆栈传递多字参数及以后的参数;
4.5.2.4.5 用AR寄存器传递单字或单字结构体的函数返回值;
4.5.2.4.6 用SR寄存器传递长整数、浮点数及双字结构体的函数返回值。
例如乘法运算函数MUL()原C程序为:
short MUL(short a, short b)
{
#define X_FRACTIONAL_BITS 13
return ((short)(((long)(a) * (long)(b) +
(1 << (X_FRACTIONAL_BITS - 1))) >> X_FRACTIONAL_BITS));
#undef X_FRACTIONAL_BITS
};
用汇编代替为:
extern short MUL(short a, short b); /* 说明MUL()为C调用的外部函数——汇编函数*/
asm("
.entry MUL_; /* 在汇编语言中操作C语言中定义的函数时,要在函数名后加下划线 */
MUL_:
my1 = ay1; /* ay1传递第2个参数short b */
mr1 = 0;
mr0 = 0x1000; /* 1 << 12 */
mr = mr + ar*my1 (ss); /* ar传递第1个参数short a,利用MAC进行32位乘加运算 */
sr = lshift mr0 by -13 (lo);
sr = sr or ashift mr1 by -13 (hi);
ar = sr0; /* ar传递MUL()函数返回值 */
rts; /* 返回到C */
");
这样处理后,MUL()函数效率得到极大提升。
4.5.3 有规律的乘除法运算改成查表法实现。
例如:
将main_data_slots()函数里的“n_slots = ((short)1440 * bitrate[(short)fr_ps->bitrate_index]) / s_freq[(short)fr_ps->sampling_frequency];”
乘除法运算改为:
/* t_slots [3][15]为乘除法运算的固定值 */
n_slots = t_slots[fr_ps->sampling_frequency][fr_ps->bitrate_index];
来实现。
4.5.4 尽可能将数组运算方式改为指针运算方式。特别是对于2维、多维数组,数组内的下标有乘除法运算的情况,更应仔细考虑用简单的指针运算代替复杂的数组运算。
4.5.5 优化中断处理子程序,减少中断占用的处理器资源。如合理利用第二套寄存器,进行快速中断处理和返回;优化中断汇编程序代码,提高中断程序的质量;采用并行指令,快速判断跳转等方式,使中断程序尽可能少占用处理器的资源。
4.5.6 尽量使用宏#define代替函数,提高实时系统的效率。
5. 总结
FD216是一颗高效能而易于使用的16位嵌入式数字信号处理器,客户可迅速将FD216整合至他们的系统级芯片(SoC)设计中。本应用笔记详细说明了FD216定点DSP的开发流程和用嵌入式C、汇编语言混合编程的设计方法。本应用笔记完稿之时,智原科技新推出了Windows界面的FD2XX系列DSP仿真、调试工具,可以更好的促进开发进度和进行程序性能评估。