DSP开发基础实验
实验目的
-
了解DSP开发系统的基本配置;
-
熟悉DSP集成开发环境(CCS);
-
掌握C语言开发的基本流程;
-
熟悉代码调试的基本方法。
实验内容
新建工程,对工程进行编译、链接,下载到目标板上后进行实时调试。利用代码调试工具跟踪程序的运行。根据.map文件,指出各段在存储器空间的地址,体会Memory
Map的设计思想。
实验原理
使用CCS进行DSP开发至少需要以下3种文件:1、.c或者.asm程序文件,这是用户写的程序;2、.cmd文件,即配置命令文件,其指定了链接器在链接时存放各段的物理地址;3、.lib文件,是由芯片厂商提供的运行支持库。TSM320C28x系列对应rts2800_ml.lib.当然除了这3类外,建立项目工程后还会产生.pjt文件,这其实就是一个文本文件,记录了项目中芯片的类型,程序目录,输出目录以及编译器设置等信息。
使用C语言在CCS下开发时,C编译器(Complier)首先将C语言翻译成汇编代码,这一步也会对程序进行一些可选的优化。然后汇编器(Assembler)将汇编代码转换符合公共目标格式COFF的机器码。连接器(Linker)对.obj文件重定位,根据配置命令文件.cmd指定各段的物理地址,输出DSP可执行文件。
DSP的仿真调试可以使用Simulator和Emulator方式。前者使用计算机的CPU进行仿真,可以实现程序功能的验证,但不能进行外设的模拟和实时性验证。后者是利用仿真器硬件,通过JTAG接口与DSP相连,使用边界扫描技术实时获得芯片的状态,程序是全速运行在DSP上的,因此所见即所得。
实验步骤
-
设备上电,连接计算机。
-
设置开发环境。点击"C2000 Setup"图标,设置硬件仿真器的型号,导入配置文件。
-
新建工程。打开CCS C2000,新建工程,填写工程名,选择对应的DSP芯片TMS320C28xx,选择工程路径和输出文件格式。点击"确定"即建立了工程。
-
添加工程文件。将实验9例程中的.c文件、.h文件、.cmd文件和.lib文件复制到工程目录下。在project窗口中工程名上右键单击,Add File to Project,添加所有上述文件。
-
编译链接。
-
下载程序到目标板,进行调试。完成实验内容所要求的工作。
程序设计
第一个实验旨在熟悉CCS的开发环境和DSP开发流程,故而实验程序已经提供,无需改动。
通过对源代码的分析,程序主要干了三件事:1、关闭看门狗;2、在CCS的output窗口输出字符串;3、在存储器中,从一片连续地址取数据,乘以一个增益后存入另一片连续地址。
具体实现如下:
1、关闭看门狗:
图1看门模块
图2 WDCR寄存器定义
直接对WDCR寄存器的WDDIS位写1,关闭看门狗。在对寄存器操作前还要先执行汇编语句eallow,操作完后再执行汇编语句edis.
2、在output窗口输出字符串:
puts语句即可实现:
puts("SineWave example started
");
3.数据的复制:
首先在头文件中声明了一个结构体IOBuffer,结构体内包含两个长度同为BUFFSIZE的数组input[]和output[],类型为16位int. 然后在main函数中定义了1个该类型的结构体,使用下标遍历整个数组,将input[]中的数值乘以增益系数gian,存入output[]中。这些操作在函数processing()中实现。
这个程序已经足够简单,无需使用框图进一步解释。
实验结果
子程序和数据的地址
子程序入口地址:
装载程序到DSP后,将鼠标停留在C语言编辑器的函数上,会显示函数的入口地址。如图6-1(a)和6-1(b),显示了dataIO()和processing()的入口地址,分别是0x003F81F5和0x003F81D8.
图3 (a). dataIO()的入口地址 (b). processing()的入口地址
从中还可以看到,dataIO()函数的入口地址比processing()函数大了29,说明processing()的汇编指令至多有29条。而且还说明,链接器在存储器中分配程序空间时,函数的物理地址不一定按照C语言编写时的顺序。实际上对于汇编指令调用函数来说,程序入口地址的顺序并不重要。
存储器地址:
通过菜单上的View – Watch,打开变量查看窗口。输入变量名currentBuffer.input和currentBuffer.output, 查看变量的值。结果如图6-2(a)和(b),两个变量的地址分别是0x00008480和0x00008500, 他们相差了128,也就是一个数组的长度。结构体中的变量是连续存放的。
图4 (a). currentBuffer.input的地址 (b). currentBuffer.output的地址
初始时的内存数据
程序载入到DSP后,直接运行整个程序。使用Graph工具,以图像方式显示数组currentBuffer.input和currentBuffer.output中的数据。由于数组的类型是int型,长度为128,所以在GraphView中设置类型为"16bit
singned int",长度为128,变量地址填6.1节中所述的物理地址,或者直接填变量名。运行一段时间后,两个数组的内容如图6-3所示:
图5 (a).input数组中的数据 (b).output数组中的数据
从整体来看,input数组中的数据是随机无需的,这是因为DSP在上电后,RAM中的数据是随机的。再看output中数据和input数据的关系,将光标都设置在第10点处,input中的数据是3430,而output中的数据是-13720,是前者的-4倍。证明output中的数据是从input中复制的,而且乘以了增益-4.
用探针导入数据后运行
在processsing()函数之前的位置放置探针,向currentBuffer.input[]中导入正弦波形。程序中的空函数dataIO()正好提供了这样的位置。因此将探针点设置在dataIO()函数上,然后在File-File
I/O中对探针进行设置,包括数据文件、地址、长度等。数据文件就是目录下的sine.dat,地址设为input[]数组的物理地址0x00008480,长度为128d.
图6探针位置
图7探针设置图8导入数据
导入数据后,程序复位,重新运行。刷新Graph窗口中的内容,结果图6-7.
图9 (a).从探针导入数据后的input[]数据
(b).运行之后的output[]数据
这时,input数组中的数据已经是正弦波了,而且运行完processing()函数后,数据也复制到了output中。为了进一步验证复制结果,与上节一样,用光标选取两个数组中相同的位置(本来想都取第35点,写报告时才发现手滑取错了TAT'),input中数据是98,output数据大致是其-4倍。由于乘以了负增益,output中整个波形关于x轴反转了。这些现象都验证了程序功能的正确性。
.map文件与.cmd文件
打开debug目录下编译链接后生成的.map文件,查看其内容。.text、.data、.bss段的起始地址和长度分别如下:
图10 .map文件中各段的地址和长度
其中.text段起始地址为0x003f81c5, 长度为0x0adb;.data没有被分配空间,地址和长度都为0;.bss起始地址为0x00000400,但长度为0.
根据TMS320F2812芯片的memory map(图6-9),可知text位于H0 SARAM中,bss段位于M1 SARAM中。
打开.cmd文件,确实没有定义.data段;.text分配在0x003f8080起始的存储器中,与.print、.cinit段在一起;.bss段为分配在起始地址为0x0400起始的M1 RAM中。
可见,根据芯片的memory map,.cmd文件指定了各段的存储地址,而编译链接后.map文件详细报告了各段的地址分配。这三者是统一的。
图 11 Memory map
图12 cmd文件中指定的段位置
小结
第一个实验的主要目的是为了熟悉CCS开发环境,了解开发流程。由于之前在CCS上开发过430单片机和5000系列
DSP,何况所有的IDE都大同小异,所以这个实验很快就做完了。作为TI自己的软件,CCS的特点在于对芯片的调试支持非常到位,比如可以通过设置探针,导入和导出数据,可以用Graph工具对内存数据作图。而一般的开发工具似乎只能设置断点慢慢调试。
虽然这个实验很简单,但可以分析的问题还是很多的。实验时,我们记录了在用探针导入数据之前和导入之后数组内的数据,以此说明探针的作用;分析了processing()执行后,input[]和output[]中数据的关系,证明这个函数实现了数据的复制,并乘以了增益-4;通过比对.map和.cmd中各段的地址和长度,将数据对应到了Memory
map中的物理存储器,从而对各段数据的存储位置有了直观的认识。
-
任意信号发生器
实验目的
-
熟悉DSP硬件开发平台
-
熟悉DSP集成开发环境(CCS)
-
掌握TMS320F2812的存储器配置表
-
学习TMS320F2812的编程开发
-
熟悉代码调试的基本方法
实验内容
分析实验例程,学习DDS的原理,在此基础上对实验程序进行改写,产生线性调频信号:
采样时间内包含1024点离散数值。
实验原理
DDS的结构框图如下所示,其中频率控制字控制相位累加器的步进量,根据相位累加器中的数值,从波形查找表中取对应的数值,送入DAC,最后经过低通滤波器滤除高频,就得到了波形的模拟输出。
图 1 DDS结构框图
实验箱上的DAC1信号为AD768,位宽16bit,以无符号数表示,0x8000表示0电位。DAC映射到了DSP的地址0x2900, 因此向DAC写数据只要写地址0x2900即可。
实验箱上8个LED数码管共阴,地址从0x2000开始,0x100递增。写入相应的码段之后,在0x2C00写任意数值,刷新锁存器即可。
实验设计
在这个实验中,没有用到任何外设,因此可以将所有外设时钟关闭。除此之外,设定主时钟锁相环5倍频。这样,外部时钟为30M,CPU工作时钟为150M。实验程序的流程图表示如下:
图 2程序流程图
其中,波形查找表存储在0x0010 0000起始的内存中,这个是外部的SRAM,使用指针直接操作地址,实现数据存取。
计算波形数据时,使用math.h中的cos()函数,得到的是有符号数,乘以幅度后转换为int型。而DAC接收的是无符号类型,0x0000为-2v,0x8000为0v,0xFFFF为+2v,相当于将一个补码向上整体搬移了0x8000。因此将int型数据转换为DAC的数据,方法如下:
*(DAC1Addr)=(unsignedint)((*(RamAddr+1*i))<<2)+0x8000;
在将int型转换为unsigned int之前,还要先左移几位,防止向上搬移0x8000时溢出。
实验过程
运行实验例程
建立工程后,添加实验所给的例程,编译链接下载到DSP中。例程中实现了正弦波信号的产生,但很不幸的是,运行实验例程,通过示波器观察发现,实验箱上的DAC坏了..
图 3损坏的DAC输出
如图3,表现为:幅度大于一定值时,输出突然下降一个台阶。为了确认是DAC的问题,在别的实验箱上运行同样的程序,是没有问题的。
所以我们的实验,包括后面的2个实验,用到DAC时,都没有用到满幅度,这样,实际可用的DAC位数是小于16bit的,最大输出幅值也变小了。将正弦波幅度减小4倍后,输出正常。如图4所示,是减小正弦波幅度后的输出,波形正常,但此时峰峰值只有1v了。
图 4减小幅度,避开DAC故障后的输出
运行例程的价值在于:除了发现DAC故障外,还可以测出1024点输出所需的时间。在例程中,一个正弦波波形由1024点数据组成,测得正弦波频率为1.04626kHz,说明遍历输出一个正弦查找表需要1/1.04626k=9.5579e-4 s, 所以平均输出每个点需要时间:9.5579e-4/ 1024=9.33e-7 s. 无论输出什么波形,这个时间是不会变的。
生成线性调频信号
按照实验要求,1024点的线性调频信号为:
分析可知,在2×0.0128=0.0256s时间内,有1024个离散点,因此每个点代表的时间为0.0256 / 1024 = 1/40000s. 换算成离散坐标,表达式如下:
从而,生成查找表的C代码就是:
for(i=0;i<1024;i++)
*(RamAddr+i)=(int)((cos(K*Pi*(-512+i)*(-512+i)/N/N)*2048));
使用Python对上述表达式进行了计算并作图,得到的波形如图5(a).
编译链接程序,下载到DSP上,然后运行程序。在Graph工具中查看起始地址0x0010 0000,长度1024点的数据图形,结果如图5(b)
图 5 (a)仿真结果 (b)实际运行结果
将输出连到示波器上观察,波形仍然正确。图6显示了示波器测量结果,从右边测量结果看,此时的频率是1.042kHz, 周期为960us.
图 6频率为1.042kHz的线性调频信号
调节线性调频信号周期
在上一节中,已经正确产生了频率为1.042kHz,周期为960us的线性调频信号,但题目要求周期是0.0128×2=0.0256s,即25600us. 增加周期只需要增加每个点的输出时间即可,因此可以对每一个点重复输出数次,调整到所需的周期。25600是960的26.6倍,所以对每个点重复输出27次左右即可。通过实验,发现重复28次即可以调整到所需周期。这样,在while(1)内,程序代码就是如下的形式:
while(--j)
{
*(DAC1Addr)=(unsignedint)((*(RamAddr+1*i))<<2)+0x8000;
}
j=29;
经过周期调整的输出波形如图7,右边的测量结果显示,此时周期是25.4us,已经很接近要求了。
LED数码管显示
在DSP初始化设置后,操作LED数码管显示字符。数码管的操作方法在第3节原理中已经叙述,直接向特定的地址写数据就行了。我们让数码管显示了NJUST.Lys字样,"Lys"是我们指导老师名字的首字母,感谢老师的教导。
图 7 LED数码管显示
子程序入口地址与.map文件
下载DSP程序后,鼠标悬停可以看到函数的地址。本程序中除了主函数mian()外,还有dispLED()函数。它们的地址分别是:0x0000 0000 和 0x0000002C.
图 8子程序的入口地址
根据memory map,它们都位于M0 Vector RAM 中。查看.map文件,.text段确实从0x0000 0000开始。这里本来是用于存放中断向量的,但程序没有用的中断,存放函数也无妨。
section page origin length input sections
-------- ---- ---------- ---------- ----------------
.text 0 00000000 000002eb
除了.text、.data、.bss段,实际占据内存长度的还有:
.cinit 0 003f8002 0000002d
.reset 0 003fffc0 00000002 DSECT
.stack 1 00000400 00000400 UNINITIALIZED
其中.cinit段是全局变量和静态变量的C初始化记录,.stack是运行时堆栈。
小结
本实验在第1个实验做完后剩下的时间内接着做的,也比较简单。由于实验箱上的DAC损坏,电压最大幅度只能用到1/4,但这影响不大。首先通过运行实验例程,记录了1024点遍历所需的时间,为后面调整信号周期提供参考(5.1节)。第二步是计算出了线性调频信号的离散表达式(5.2节),并使用python进行了简单的仿真。波形仿真正确后下载到DSP运行,使用graph工具查看波形,与仿真结果相同,示波器也观察到了输出波形。根据题目要求的0.0256s周期,我们通过一个样点多次输出的方法,加大了信号周期,并认为每个点重复28次,可以得到比较接近0.0256s的周期。c程序和调整后的波形在(5.3)节中展示了。但由于对波形查找表的取值是在while(1)中循环的,延时由指令数决定,因而不能做到精确的频率控制。更好的方法是在定时器中断函数内读取查找表,这样就可以通过定时器计数值任意设置波形周期了。此外,还操作了LED数码管,正确显示了预设的字符(5.4节)。最后,查看.map文件,指出了子程序在物理存储器中的位置。
-
DSP数据采集
实验目的
1. 熟悉DSP的软硬件开发平台;
2. 掌握TMS320F2812的ADC外设的使用;
3. 熟悉TMS320F2812的中断的设置;
4. 掌握代码调试的基本方法;
实验内容
建立工程,根据实验例程,使用ADC进行数据的采集,存储以及模拟还原,验证ADC采样频率的设置,查看存储内容。
实验原理
实验的ADC为TMS320F2821内部集成的12bitADC,输入范围为0-3V,最快转换时间80ns。虽然器件是0-3V的,但外围电路对输入电压进行了转换,可以输入负电压。DAC是外部器件AD768,16bit.
ADC的转换触发由定时器控制,因此本实验涉及许多寄存器的操作,主要包括系统时钟和外设两大类,此外,还需要设置相应的中断。
与时钟有关的寄存器有:PLLCR,设置锁相环倍频数;PCLKCR,控制外设时钟的使能;HISPCP和LOSPCP,控制高速外设和低速外设的时钟分配。ADC和定时器都挂在高速外设上。
ADC的功能设置主要用到3个设置寄存器ADCTRL1,ADCTRL2和ADCTRL3,控制转换通道涉及ADCMAC CONV(转换次数设置),ADCHSELSEQn(输入通道选择)。
ADC的时钟链路如下:
图 1 ADC的时钟链
F2812具有两个事件管理器(EV),分别为EVA和EVB,事件管理器的结构如图2所示。在本实验中,只用到了通用定时器。
通用定时器有4种工作模式,本实验使用连续增计数模式。这样,定时器的中断周期时间就为(TxPR+1)个定时器输入时钟周期。
图 2事件管理器框图
之前叙述过,实验程序通过EVA中断触发ADC采样,所以需要用到ADC中断。TMS320F2812使用外设中断扩展单元(PIE),将外设中断信号分组复用到CPU级中断,可以支持96个中断源。PIE的分组结构如图3,EVA中定时器的中断源对应INT1.6.
图 3 PIE单元分组结构
程序设计
寄存器头文件的三级结构实现
本实验涉及大量寄存器,因而使用了官方的库进行开发。在库中,每个外设或者模块作为一个头文件,寄存器都被抽象为联合体,可以操作整个寄存器,也可以操作特定的位。以外设使能寄存器寄PCLKCR为例,结构体定义如下。首先在PCLKCR_BITS中用位带(bit band)特性,构造了一个成员变量位数不定,内存连续的结构体。然后定义了一个联合体PCLKCR_REG,成员变量是PCLKCR_BITS和全部位all,联合体的成员占据同一内存。这样,在程序中只要使用联合体就可以描述一个寄存器,既可以读写特定的位,也可以整体一次操作。最后,头文件中又把所有寄存器的联合体包含到了一个结构体中。
例1 PCLKCR_REG寄存器的联合体描述
// Peripheral clock control register bit definitions:
struct PCLKCR_BITS{//
bits description
Uint16 EVAENCLK:1;//
0 Enable high speed clk to EV-A
Uint16 EVBENCLK:1;//
1 Enable high speed clk to EV-B
Uint16 rsvd1:1;//
2
Uint16 ADCENCLK:1;//
3 Enable high speed clk to ADC
Uint16 rsvd2:4;//
7:4 reserved
Uint16 SPIENCLK:1;//
8 Enable low speed clk to SPI
Uint16 rsvd3:1;//
9 reserved
Uint16 SCIAENCLK:1;//
10 Enable low speed clk to SCI-A
Uint16 SCIBENCLK:1;//
11 Enable low speed clk to SCI-B
Uint16 MCBSPENCLK:1;//
12 Enable low speed clk to McBSP
Uint16 rsvd4:1;//
13 reserved
Uint16 ECANENCLK:1;//
14 Enable system clk to eCAN
};
union PCLKCR_REG{
Uint16 all;
struct PCLKCR_BITS bit;
};
以系统控制相关寄存器为例,寄存器在头文件中的三级组织结构如下所示:
图 4库的寄存器组织结构(以系统控制相关寄存器为例)
程序框图与寄存器配置
本实验的程序框图如下:
其中,在初始化设置中,使用到了以下函数进行设置:
InitSystem(); //初始化DSP内核寄存器
InitPieCtrl(); //调用PIE控制单元初始化函数
InitPieVectTable(); // 调用PIE向量表初始化函数
InitAdc(); //ADC初始化设置
而设置ADC、定时器、PIE到工作状态的代码在main()中实现,直接设置寄存器相关的位。上述设置完成的功能叙述如下:
初始化函数:
InitSystem();
禁用看门狗,
PLLCR DIV为10(d)(PLL5倍频)
HISPCP=0x01(高速外设时钟除2)
LOSPCP=0x02(低速外设时钟除4)
PCLKCR外设时钟使能寄存器设置为:0x0009,即EVAEN位和ADCEN位置1,打开EVA和ADC时钟
InitPieCtrl();
初始化PIECRTL的ENPIE为0,PIEIERx为0, PIEIFRx为0.
InitPieVectTable();
初始化中断向量表并将ENPIE置1
InitAdc();
ADCCRTL3的ADCBGRFDN设为0x03,打开参考电源;ADCPWDN位设置为1,打开ADC其他部分的电源,其余寄存器都为初始值。
在main()中:
PIE设置:
PieCtrlRegs.PIEIER1.bit.INTx6=1,将PIEIER1设置为0x0020;IER寄存器的INT1位写1,IER=0x0001, 打开INT1中断;用EINT汇编指令开全局中断,INTM设0。
ADC设置:
ADC设置为单通道转换,输入通道为ADCINA1。允许EVA触发SEQ1,并打开SEQ1中断。
EVA设置:
T1禁止比较输出,通过T1TOADC启动ADC周期中断方式;设为连续递增计数模式,T1CON的TPS设7,预分频2^7=128,T1PR设置为62,这样中断周期为30M×5/2/128/(62+1)=9.3kHz 而ADC的采样保持时钟根据图1可以算出来为30M×5/2/1/4=18.75M,远大于定时器中断周期。
LED显示切换
按照题目要求,交替显示实验者学号。设计方法为:在ADC中断中计数,一旦计数值到达预设,就切换显示LED。为了不占用中断时间,LED的显示在main函数的while(1)中进行。为此,设计了如下的LED显示函数DispLed(), 使用switch语句切换不同的显示,函数示意如下:
void DispLed(void)
{
staticunsignedchar
ca=1;//静态变量,点睛之笔
switch(ca)
{
case1:
// LED8
*LED8=
CHAR_NULL;
// …………重复代码省略
//显示第一个学号
*(LEDWR)=0XFF;
ca++;
break;
case2:
*LED8=
CHAR_NULL;
//显示第二个学号
*(LEDWR)=0XFF;
ca++;
break;
case3:
*LED8=
CHAR_NULL;
*(LEDWR)=0XFF;
ca++;
break;
case4:
*LED8=
CHAR_NULL;
//显示第二个学号
*(LEDWR)=0XFF;
ca=1;
break;
default:
ca=1;
}
这个函数本身具有静态变量,是完全封装的,无需输入参数和全局变量,每调用一次,就会将LED内容切换为下一个学号。
在while(1)中,判断定时器中断计数达到阀值后,就调用LED显示切换函数DispLed().这样就实现了LED内容的交替显示。
采集数据存储
实验还要求存储ADC采集到的数据。我们设定存储数量为1024点,使用
队列数据结构实现。队列是先进先出的,队列满了之后,每进入一个新数值,最早的数据就会被踢出。考虑到性能,使用
循环数组实现队列功能。
图 5 循环数组实现队列的示意图(以长度12为例)
上图展示了本程序中所用到的队列结果,其原理是一个数组和一个游标index. 数组元素在内存中是按顺序排列的