DSP

DSP实验——TSM320F2812

2019-07-13 10:32发布


DSP开发基础实验 实验目的
  1. 了解DSP开发系统的基本配置;
  2. 熟悉DSP集成开发环境(CCS);
  3. 掌握C语言开发的基本流程;
  4. 熟悉代码调试的基本方法。

    实验内容

新建工程,对工程进行编译、链接,下载到目标板上后进行实时调试。利用代码调试工具跟踪程序的运行。根据.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的仿真调试可以使用SimulatorEmulator方式。前者使用计算机的CPU进行仿真,可以实现程序功能的验证,但不能进行外设的模拟和实时性验证。后者是利用仿真器硬件,通过JTAG接口与DSP相连,使用边界扫描技术实时获得芯片的状态,程序是全速运行在DSP上的,因此所见即所得。

实验步骤

  1. 设备上电,连接计算机。
  2. 设置开发环境。点击"C2000 Setup"图标,设置硬件仿真器的型号,导入配置文件。
  3. 新建工程。打开CCS C2000,新建工程,填写工程名,选择对应的DSP芯片TMS320C28xx,选择工程路径和输出文件格式。点击"确定"即建立了工程。
  4. 添加工程文件。将实验9例程中的.c文件、.h文件、.cmd文件和.lib文件复制到工程目录下。在project窗口中工程名上右键单击,Add File to Project,添加所有上述文件。
  5. 编译链接。
  6. 下载程序到目标板,进行调试。完成实验内容所要求的工作。
 

程序设计

第一个实验旨在熟悉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()的入口地址,分别是0x003F81F50x003F81D8.   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.inputcurrentBuffer.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中的物理存储器,从而对各段数据的存储位置有了直观的认识。    
  1. 任意信号发生器

实验目的

  1. 熟悉DSP硬件开发平台
  2. 熟悉DSP集成开发环境(CCS)
  3. 掌握TMS320F2812的存储器配置表
  4. 学习TMS320F2812的编程开发
  5. 熟悉代码调试的基本方法

    实验内容

分析实验例程,学习DDS的原理,在此基础上对实验程序进行改写,产生线性调频信号:   采样时间内包含1024点离散数值。

实验原理

DDS的结构框图如下所示,其中频率控制字控制相位累加器的步进量,根据相位累加器中的数值,从波形查找表中取对应的数值,送入DAC,最后经过低通滤波器滤除高频,就得到了波形的模拟输出。   1 DDS结构框图     实验箱上的DAC1信号为AD768,位宽16bit,以无符号数表示,0x8000表示0电位。DAC映射到了DSP的地址0x2900, 因此向DAC写数据只要写地址0x2900即可。     实验箱上8个LED数码管共阴,地址从0x2000开始,0x100递增。写入相应的码段之后,在0x2C00写任意数值,刷新锁存器即可。

实验设计

在这个实验中,没有用到任何外设,因此可以将所有外设时钟关闭。除此之外,设定主时钟锁相环5倍频。这样,外部时钟为30MCPU工作时钟为150M。实验程序的流程图表示如下:   2程序流程图     其中,波形查找表存储在0x0010 0000起始的内存中,这个是外部的SRAM,使用指针直接操作地址,实现数据存取。 计算波形数据时,使用math.h中的cos()函数,得到的是有符号数,乘以幅度后转换为int型。而DAC接收的是无符号类型,0x0000-2v0x80000v0xFFFF+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文件,指出了子程序在物理存储器中的位置。  
  1. 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. 数组元素在内存中是按顺序排列的