首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

速成DSP

速成DSP

实验一 新手上路]

初学者编写的第一个程序通常是控制XF引脚的变化,然后用示波器测量XF脚波形或观察与相接的LED。这个程序也常常用来测度一下DSP能否正常工作。

实验1.1 最简单的程序:控制XF引脚周期性变化
实验目的:通过简单的程序了解DSP程序的结构,熟悉CCS开发环境。
*************************************************************
*
最简单的程序:TestXF1.asm
*
循环对XF位置1和清0,用示波器可以在XF脚检测到电平高低周期性变化
*
常用于检测DSP是否工作。
*************************************************************
.mmregs ;
预定义的寄存器
.def CodeStart ;
定义程序入口标记

.text ;程序区
CodeStart: ;
程序入口
SSBX XF ;XF
1
RPT #999 ;
重复执行1000次空指令产生延时
NOP
RSBX XF ;XF
0
RPT #999 ;
重复执行1000次空指令产生延时
NOP
B CodeStart ;
跳转到程序开头循环执行
.end

NOP指令执行时间为一个时钟周期,设DSP工作频率是50MHz,可以估算出XF引脚电平的变化频率约为:50M/2000=25kHz
在没有示波器的情况下,就要将程序1.1稍作改进,增加延时,用一个延时子程序将XF脚电平变化频率降到肉眼可分辨的程度,就可以用LED来显示电平的变化,程序如下:

实验1.2 子程序调用
实验目的:学习子程序的调用
*************************************************************
*TestXF2.asm
*
TestXF1.asm稍作改进,用延时子程序设置较长的延时,
*
可以用试验板上的LED看到XF引脚电平的变化
*************************************************************
.mmregs ;
预定义的寄存器
.def CodeStart ;
定义程序入口标记

.text ;程序区
CodeStart: ;
程序入口
SSBX XF ;XF
1
CALL Delay ;
调用延时程序
RSBX XF ;XF
0
CALL Delay ;
调用延时程序
B CodeStart ;
跳转到程序开头循环执行

**************************************************************
*
延时子程序:Delay
*
用两级减一计数器来延时。调整AR1AR2的大小LED闪烁的频率不同
**************************************************************
Delay:
STM #999,AR1 ;
循环次数1000
LOOP1: STM #4999, AR2 ;
循环次数5000
LOOP2: BANZ LOOP2,*AR2- ;
如果AR2不等于0AR21,再判断
BANZ LOOP1,*AR1- ;
如果AR1不等于0AR11,跳转到LOOP1
RET
.end

**************************************************************
*
注意这种延时方法并不精确,需要精确定时必须用定时器。
*
按此法延时的近似公式为:4*(AR2+1)*(AR1+1)*时钟周期
*
DSP工作在50MHz(时钟周期20ns),AR1=999, AR2=4999
*
延时约为400ms,则LED闪烁的周期为800ms,频率1.25Hz
**************************************************************

设计指导:
1
.源代码书写格式
源代码的书写有一定的格式,初学者往往容易忽视。简单归纳如下:
1
.每一行代码分为三个区:标号区、指令区和注释区。标号区必须顶格写,主要是定义变量、常量、程序标签时的名称。指令区位于标号区之后,以空格或TAB格开。如果没有标号,也必须在指令前面加上空格或TAB,不能顶格。注释区在标号区、程序区之后,以分号开始。注释区前面可以没有标号区或程序区。另外还有专门的注释行,以*打头,必须顶格开始。
2
.一般区分大小写,除非加编译参数忽略大小写。
3
.标点符号有时不注意会打成中文全角字符导致错误。
书写格式的要求在很多DSP书里都没有提,初学者往往只把书上的代码输入进去,编译时得到错误的提示,而不知所措。其中最容易犯的错误指令顶格写,不过一般经提示后不会犯第二次。
有些格式CCS并没有做要求,但注意养成良好的代码书写风格,增加代码的可读性。以上两个例子的书写风格可作参考,但不是硬性规定:
1
.标号区占3TAB的间隔,即12个字符
2
.指令中的指令码占两个TAB间隔,然后是操作数。
3
.每一行的尾注能对齐的尽量对齐
4
.标明一段程序功能的注释以*号打头顶格写,如果功能说明的注释较多,用分格线框起来。
此外其它编程语言的编程风格也可以借用过来,比如标示符命名规则、程序说明的要求等。如果项目组有规定,则按规定执行。
本书的代码尽量保持一定的风格,不过读者可以发现前面的代码注释较多,后面随着学习的深入,一般不会对每一条指令加注释,只注明程序段的功能。另外代码贴到word里后,格式有些错位,无法一一纠正。

2.链接配置文件
一个完整的DSP程序至少包含三个部分:程序代码、中断向量表、链接配置文件(*.cmd)。这里介绍一下链接配置文件文件,对本次试验影响不大的中断向量表将在后文介绍。
连接配置文件的确定了程序链接成最终可执行代码时的选项,其中有很多条目,实现不同方面的选项,其中最常用的也是必须的有两条:1.存贮器的分配
2.
标明程序入口。以本次实验为例,下面的简单的链接配置文件就够用了:
/* TestXF.cmd */
-e CodeStart /*
程序入口,必须在程序中定义相应的标号*/

MEMORY {
page 0:
PRAM: org=0100h len=0F00h /*
定义程序存贮区,起始0100H,长度0F00H*/
}

SECTIONS{
.text:>RAM page 0 /*
.text段映射到page0param*/
}

由于每个程序都需要一个链接配置文件,可以编写一个满足通常需要的链接配置文件。作为本手册通用的链接配置文件如下,可以满足本书大部分程序的需要。在未特别指明的情况下使用这个通用的链接配置文件:

/* 5402.cmd */
-e CodeStart /*
程序入口,必须在程序中定义相应的标号*/
-m map.map /*
生成存储器映射报告文件 */

MEMORY {
PAGE 0:
VECT: org=0080h len=0080h /*
中断向量表*/
PARAM: org=100h len=0F00h /*
代码区*/
PAGE 1:
DARAM: org=1000h len=1000h /*
数据区*/
}

SECTIONS {
.text :> PARAM PAGE 0 /*
代码段*/
.vectors :> VECT PAGE 0 /*
中断向量表*/
STACK :> DARAM PAGE 1 /*
堆栈*/
.bss :> DARAM PAGE 1 /*
未命名段*/
.data :> DARAM PAGE 1 /*
数据段*/
}

更多参考:
1
.关于代码书写格式:SPRU102: TMS320C54x Assembly Language Tools User's Guide3.5
Source Statement Format
2
.关于链接配置文件:SPRU102: TMS320C54x Assembly Language Tools User's Guide7.5
Linker Command Files
7.7 The MEMORY Directive7.8 The SECTIONS Directive

练习:
1
、试一下不按规定格式书写代码会产生什么样的编译错误。
2
、试一下将链接配置文件中的MEMORYSECTIONS改成小写会出现什么样的编译错误。
3
.修改程序1.2AR1,AR2的值,观察LED闪烁频率

实验二 基本运算
<本节选自为HK-DSP实验箱写的实验指导书,有待整理>
DSP
指令数量最多的是:算术指令、逻辑指令和数据加载与传送指令。数据加载与传送指令由于处处要用,所以不单独列为实验。算术与逻辑指令也是数量繁多,无法一一举例,这里简单举一个加法和除法的例子,乘法和乘加指令在FIR用得比较多,稍后一并介绍。其它指令有兴趣可以对照指令表的说明,试验一下各指令运行的结果。
实验2.1 加减法计算
************************************************
*
计算z=x+y-w
************************************************
.mmregs
.def CodeStart
Data_DP: ;
数据段指针
x: .word 10 ;
初始化变量
y: .word 26
w: .word 23
z: .word 0

.text
CodeStart:
LD #Data_DP,DP ;
装载数据指针DP
STM #STACK+10H,SP
SUMB: LD x,A ;A=x
ADD y,A ;A=A+y
SUB w,A ;A=A-w
STL A,z ;z=A
END: B END

计算结果数据存储器地址存储内容十进制
x1010H000aH10
y1011H001aH26
w1012H0017H23
z1013H000dH13


技巧提示:试验算术指令由于不需要外部资源,可以不需要仿真器和实验箱。同学们可以平时自己用软件

仿真,多多实验。但是复杂的算法最好还是在线仿真,因为程序是流水线执行,软件仿真有时与实际硬件执行结果有所不同。

实验2.2 除法计算
DSP
并没有除法指令,回想一下我们用在稿纸上演算除法列的竖式,实际是一种移位减法,DSP中也是通过做多次减法的办法来做除法。下面例子是把用除以10的办法二进制数转成BCD码例子:
*********************************
*16
进制转BCD
*********************************
.mmregs
.global CodeStart

.data
x: .word 1234 ;
待转换的数字
y: .word 10 ;
除数
z: .word 0Fh,0Fh,0Fh,0Fh,0Fh;
结果区,每位BCD存一个字,
;
初始化为F因为实验板的数码管不显示F

.text
 
CodeStart:
LD #x,DP ;
设置DP
LD x,A ;
被除数
STM #z,AR1 ;
结果区指针
loop:
RPT #15 ;
执行完16次减法后,A的高16位是余数
SUBC y,A ;
16位是商
STH A,*AR1+ ;
余数保存到Z
AND #0FFFFH,A ;
掩盖掉高16位,保留商值
BC loop,ANEQ ;
继续做除法直到商为0
end: B end

练习:
练习其他算术指令

其它参考:
spru172c
TMS320C54x DSP Reference Set Volume 2: Mnemonic Instruction
Set
2.1 Arithmetic Operations
这个资料对每个指令都有详细说明。也可以在Help中的TMS320C54x DSP Mnemonic Instruction
Set
中查询或搜索相关指令。

实验三 中断
中断的概念应该不陌生,指的是当某个事件发生时,暂停当前的操作,转向中断服务程序,执行完后再返回继续原来的操作。这使得DSP能够处理多个任务。
DSP
有许多中断源,可以设置中断控制寄存器来确定响应哪些中断而不理会哪些中断。本实验介绍最常用的定时器中断和外部中断的使用方法,并介绍中断向量表和中断向量指针。

实验3.1 定时器中断:方波发生器
实验目的:学习定时器中断的设计方法

回顾一下实验一控制LED的闪烁实际就是一个简单的方波发生器。但不足的是延时的方法定时不精确,另外还有一个缺点是在执行延时的过程中DSP就无法执行其它指令,这时就可以用定时器来改进。
使用定时器首先要对它初始化,基本步骤如下:
1
.关掉中断
2
.停止定时器运行。
3
.设定时器的定时长度
4
.允许定时器中断
5
.运行定时器
6
.打开中断

现以简单的方波程序为例:
;==============================================
; fangbo1.asm
;
利用定时器Timer0XF脚产生周期2ms的的方波
;==============================================
.title "fangbo1.asm"
.mmregs
.def codestart ;
程序入口
.def TINT0_ISR ;Timer0
中断服务程序

STACK .usect "STACK",10H ;分配堆栈空间

;设定定时器0控制寄存器的内容
K_TCR_SOFT .set 0B<<11 ;TCR
11soft=0
K_TCR_FREE .set 0B<<10 ;TCR
10free=0
 
K_TCR_PSC .set 0B<<6 ;TCR
9-6位,可设TDDR一样,也可不设自动加载
K_TCR_TRB .set 1B<<5 ;TCR
5TRB=1此位置1,PSC会自动加载的
K_TCR_TSS .set 0B<<4 ;TCR
4TSS=0
K_TCR_TDDR .set 1001B<<0 ;TCR
3-0TDDR=1001B
K_TCR .set
K_TCR_SOFT|K_TCR_FREE|K_TCR_PSC|K_TCR_TRB|K_TCR_TSS|K_TCR_TDDR
K_TCR_STOP .set 1B<<4 ;TSS=1
时计数器停止

.data
DATA_DP: ;
数据区指针
XF_Flag: .word 1 ;
当前XF的电平标志,如果XF_Flag=1,XF=1

;================================================

;主程序:
;================================================
.text
CodeStart:
STM #STACK+10H,SP ;
设堆栈指针SP
LD #DATA_DP,DP ;
设数据地址DP
STM #XF_Flag,AR2 ;AR
指向XF标志

;改变中断向量表位置
K_IPTR .set 0080h ;
指向0080H,默认是FF80
LDM PMST,A
AND #7FH,A ;
保留低7位,清掉高位
OR #K_IPTR,A ;
STLM A,PMST
*
初始化定时器0
*f=50MHz
,定时2ms时:
*
根据定时器长度计算公式:Tt=T*(1+TDDR)*(1+PRD)
*
给定TDDR=9PRD=9999CLKOUT主频f=40MHz,T=25ns
*Tt=20ns*(1+9)*(1+9999)=2000us=2ms
*f=100Mhz
,定时最大是:10ns*2^4*2^16=10ms

PERIOD .set 9999 ;定义计数周期
STM K_TCR_STOP,TCR ;
停止计数器0
; STM #PERIOD,TIM ;
可设成跟PRD一样,也可不设自动加载
STM #PERIOD,PRD ;
设定计数周期
STM #K_TCR,TCR ;
开始Timer0
stm #0008h,IMR ;
允许Timer0中断
STM #0008h,IFR ;
清除挂起的中断
rsbx intm ;
开中断
end: nop
B end

;================================================
;Timer0
中断服务程序:TIN0_ISR
;================================================
TINT0_ISR:
PSHM ST0 ;
本中断程序影响TC,位于ST0
;
判断当前XF状态并作电平变化
BITF *AR2,#1 ;IF XF_Flag=1 then TC=1 else TC=0
BC ResetXF,TC ;IF TC=1 then XF=0 else XF=1
setXF:
SSBX XF ;
XF为高电平
ST #1,*AR2 ;
相应修改标志
B Next
ResetXF:
RSBX XF ;;
XF为高电平
ST #0,*AR2 ;
相应修改标志
Next:
POPM ST0
RETE
.end

有时定时的长度不能满足需要,比如DSP工作频率50Mhz时,定时最大值是:20ns*2^4*2^16=20ms。如果需要更长的定时,就要在定时器中断子程序中再加一个计数器,直到产生一定次数的定时中断后再执行相应的操作。程序只需要稍作修改,见附盘的fangbo2.asm
技巧提示:寄存器的不同位通常有不同的含义,初始化时单独设定寄存器的每一位可以增加程序可读性,容易让其它人看懂具体每一位设置的含义,并且易于修改。如果代码太长可以自己写一个初始化的子程序,需要时修改一下调定时再调用。当然如果对寄存器各个位的含义相当熟悉,直接整个初始化也行。可以自行选择这些不同的编程风格。

设计指导:
1
.中断向量表
中断向量表是DSP程序的重要组成部分,当有中断发生并且处于允许状态时,程序指针跳转到中断向量表中对应的中断地址。由于中断服务程序一般较长,通常中断向量表存放的是一个跳转指令,指向实际的中断服务程序。下面是5402中断向量表的一个范例,可以作为模板,使用时稍作修改就行:
*****************************************************************
*5402Vectors.asm
*
完整的5402中断向量表示例
*5402
共有30个中断向量,每个向量占4个字的空间。
*
使用向量一般用一条跳转指令转到相应中断服务子程序,其余空位用NOP填充
*
未使用的向量直接用RETE返回,是为了防止意外进入未用中断。
*****************************************************************
.sect ".vectors" ;
开始命名段.vecotrs
.global CodeStart ;
引用程序入口的全局符号定义
;
。。。引用其它中断程序入口的全局符号定义
.align 0x80 ;
中断向量表必须对齐128字的页边界
RESET: B CodeStart ; Reset
中断向量,跳转到程序入口
NOP ;
NOP填充表中其余空字
NOP ;B
指令占了两个字,所以要填两个NOP
NMI: RETE ;
不可屏蔽中断
NOP
NOP
NOP
;
软件中断
SINT17 .space 4*16 ;
软件中断使用较少,简单起见用0填充
SINT18 .space 4*16
SINT19 .space 4*16
SINT20 .space 4*16
SINT21 .space 4*16
SINT22 .space 4*16
SINT23 .space 4*16
SINT24 .space 4*16
SINT25 .space 4*16
SINT26 .space 4*16
SINT27 .space 4*16
SINT28 .space 4*16
SINT29 .space 4*16
SINT30 .space 4*16

INT0: RETE ;外部中断INT0
NOP
NOP
NOP
INT1: RETE ;
外部中断INT1
NOP
NOP
NOP
INT2: RETE ;
外部中断INT2
NOP
NOP
NOP
TINT: RETE ;Timer0
中断
NOP
NOP
NOP
BRINT0: RETE ;McBSP #0
接收中断
NOP
NOP
NOP
BXINT0: RETE ;McBSP #0
发送中断
NOP
NOP
NOP
DMAC0: RETE ;
无定义(默认)DMA0中断
NOP
NOP
NOP
TINT1: RETE ;Timer1
中断(默认)DMA1中断.
NOP
NOP
NOP
INT3: RETE ;
外部中断3
NOP
NOP
NOP
HPINT: RETE ;HPI
中断
NOP
NOP
NOP
BRINT1: RETE ;McBSP #1
接收中断(默认)DMA2中断
NOP
NOP
NOP
BXINT1: RETE ;McBSP #1
发送中断(默认)DMA3中断
NOP
NOP
NOP
DMAC4: RETE ;DMA4
中断
NOP
NOP
NOP
DMAC5: RETE ;DMA5
中断
.end

在本实验中只要把在开头加上中断子程序标号的引用,并在中断表的TINT部分换成跳转指令就行了:

*******************************************************
*vectors.asm for
方波发生器
*******************************************************
.sect ".vectors" ;
开始命名段.vecotrs
.global CodeStart ;
引用程序入口的全局符号定义
.global TINT0_ISR ;
引用Timer0中断子程序
<节省篇幅,中间省略>
TINT: B TINT0_ISR ;Timer0
中断
NOP
NOP
BRINT0: RETE ;McBSP #0 receive interrupt
<
节省篇幅,下略>

技巧提示:只有第一个中断(Reset中断)是每个程序都应该有的,在不需要其它中断的情况下,可以只用这一部分,后面可以省略。如果只需要部分中断也可以按需设置,但必须保证所用中断在中断向量表的位置不变。不熟悉中断向量表的情况下最好还是用这个完整中断向量表样例。
另外C5400系列中不同型号DSP的中断向量数量和在中断向量表中的位置有所不同,程序移植时需要查相应datasheet确认。

2.中断向量指针
中断向量表的位置并没有强制的位置,可以在内部存贮器,也可以在外部存贮器。但有一个要求:中断量表必须放在80H字长存贮块的起始处,即中断向量表的首地址的低7位必须全为0DSP
的寄存器PMST的高9位是中断向量表的指针IPTR。其上电时默认是在FF80H处,这是为了运行固化在内部ROM的上电加载程序(见实验八的程序加载部分)。由于FF80H是只读的,加载用户自定义的中断向量表时会报错。这样需要重新设置IPTR的值,本书一般把它重定义到0080H(也可以用自定义的地址),并在程序开头重新设置一下IPTR的值:
;
改变中断向量表位置
K_IPTR .set 0080h ;
指向0080H,默认是FF80
LDM PMST,A
AND #7FH,A ;
保留低7位,清掉高位
OR #K_IPTR,A ;
将新值传到高9
STLM A,PMST ;
修改PMST寄存器

技巧指示:由于这段代码几乎每个程序都需要,可以单独存成一个文件:IPTR0080H.asm,然后在程序需要的地方用.copy.include指令:
.copy
IPTR0080H.asm
或: .include IPTR0080H.asm
编译时就会自动把这段代码嵌到相应位置。稍微要注意的是由于这一小段代码要用到累加器A,所以最好保证执行这段代码之前不要使用累加器A
其它还有一些经常重复的代码,如初始化SPDPIPTR的代码都可以写在一个文件里include/copy进来。
1.copy.inlucde指令效果是一样的,只是在生成程序列表时,.copy会把代码复制过来,而.include不会。
2:文件名可以用路径,如果不用,则编译器会按下面的循序搜索:当前目录、编译选项指定的目录、环境变量指定的目录。

更多参考:
1
.关于中断:SPRU131 TMS320C54x DSP Reference Set, Volume 1: CPU and
Peripherals
6.10 Interrupts
2
.关于定时器:SPRU131 TMS320C54x DSP Reference Set, Volume 1: CPU and
Peripherals
8.4 Timer

实验3.2 外部中断:频率计
DSP
4个外部中断INT0-INT3,下降沿触发,实验箱的频率计使用的是INT3
频率计的设计原理是:在设定时间下计外部中断INT3的次数,除以定时器的定时周期(也就是乘以定时器中断的触发频率),就得到外部脉冲频率。实验箱上配有1.024k-262.144k8档频率源,也可以外接频率源。用跳线冒选择频率源,并接到INT3上。下面的例程是定时器定时1s,在INT3中断服务子程序中计脉冲个数,到时则关闭中断。脉冲计数结果显示到数码管上,即为以单位为Hz的频率值

**********************************************
*
频率计
**********************************************
.mmregs
.global CodeStart
.global TINT1_ISR
.global INT3_ISR

.include "../DefineIO.asm"

.data
DATA_DP:
PulseCounter: .word 0 ;
脉冲计数器
Display: .word 0FH,0FH,0FH,0FH,0FH,0FH;
存放数据管显示值,F在数码管上不显示
DotData: .word 000000B ;
数码管的dot point
Number10: .word 10 ;
十六进制转BCD所除的10

.text
CodeStart:
.copy "../SP_DP_IPTR.asm" ;
初始化SPDPIPTR的代码段
STM #99,AR1 ;10ms
计数后再100分频
STM #Display,AR3 ;
定义数据管显示存贮区指针
LD #0,A ;A
用来计脉冲数

SSBX INTM ;关中断
CALL Timer1Init ;
初始化Timer1
STM #110000000B,IMR ;
允许Timer1INT3中断
STM #0FFH,IFR ;
清除挂起的中断
RSBX INTM ;
开中断

wait:
B wait;

***************************************
*
外部中断子程序
***************************************
INT3_ISR:
ADD #1,A ;
计中断次数
RETE
***************************************
*
定时器中断子程序
***************************************
TINT1_ISR:
BANZ GoOnCount,*AR1- ;
测量次数计数器减1,次数为0就中止计数数,
;
结束计数
STM #0,IMR ;
取消所有中断

HEX2BCD: ;把计数结果转成BCD
RPT #15
SUBC Number10,A
STH A,*AR3+
AND #0FFFFH,A
BC HEX2BCD,ANEQ
;
在数码管上显示结果
STM #Display,AR3
PORTW *AR3+,Digital0
PORTW *AR3+,Digital1
PORTW *AR3+,Digital2
PORTW *AR3+,Digital3
PORTW *AR3+,Digital4
PORTW *AR3+,Digital5
PORTW DotData,DotPoint
RETE

GoOnCount: ;继续计数
STM #1100001B,IFR ;
清除挂起的中断
RETE
***************************************
*
定时器初始化
***************************************
Timer1Init:
;
定时器1的寄存器地址
TIM1 .set 0030h ;
减1计数器
PRD1 .set 0031h ;
存放定时时间常数
TCR1 .set 0032h ;
定时器状态及控制寄存器

;F=50MHz, T=20ns*(1+15)*(1+3124)=20ns*16*31250=10ms
STM #010,TCR1 ;TSS
置位停止Timer
STM #31249,PRD1
STM #2FH,TCR1
RET
.end

简单起见本例只能测一次,可以做一些改进,比如每隔1-2S自动重新测量,或者用按键来触发测量。

实验4.1 数码管及LED显示接口实验
实验箱说明部分已经介绍了数码管的控制原理,下面的程序DigitalLED.asm简单的演示了对数码管和LED控制的指令,可以在显示预设的数字和LED状态。复杂的程序可以见附盘的流水灯程序,DigitalLED2.asm
;=========================================================
;DigitalLED.asm
;
实验用DSP控制实验板数码管
;DSP
I/O指令对CPLD地址1000-10005写数据,分别对应Digtal0-5
;=========================================================
.mmregs
.def main ;
主程序入口
.ref Timer0Init ;Timer0
初始化子程序

;数据管地址
Digital0 .set 1000H ;
数据管1
Digital1 .set 1001H ;
数据管2
Digital2 .set 1002H ;
数据管3
Digital3 .set 1003H ;
数据管4
Digital4 .set 1004H ;
数据管5
Digital5 .set 1005H ;
数据管6
DotPoint .set 1006H ;
小数点
LED .set 1007H ;LED

STACK .usect "STACK",10H ;分配堆栈空间

.data
DATA: .word 1,2,3,4,5,6 ;
测试数据
Dot_DATA: .word 010101b;
LED_DATA: .word 0101010b

.text
main:
STM #STACK+10H,SP ;
设堆栈指针SP
STM #K_SWWSR,SWWSR

SSBX INTM ;关中断

LD #DATA,DP ;设数据地址DP
STM #DATA,AR1
*
写数据
PORTW *AR1+,Digital0
PORTW *AR1+,Digital1
PORTW *AR1+,Digital2
PORTW *AR1+,Digital3
PORTW *AR1+,Digital4
PORTW *AR1+,Digital5
PORTW Dot_DATA,DotPoint
PORTW LED_DATA,LED

END: B END
.end

技巧提示:数码管、LEDIO地址的定义也可以单独存到一个文件中,在需要它的程序中用.include/.copy指令。
练习:修改预设值重新运行观察结果。

实验4.2 键盘接口实验
实验板上有四个按键,当有键按下时,会触发DSPINT1中断,在INT1的中断服务程序中读入键码,判断哪一个键被按下,然后执行相应的操作。各键对应的二进制和十六进制键码分别为:
按键1 0001B 1H
按键2 0010B 2H
按键3 0100B 4H
按键4 1000B 8H

下面有一个小例子:
******************************************************
*keyboardTest.asm
*
测试按键的功能,响应按键中断,读取键值,
*
并对不同键按键次数计数
******************************************************
.mmregs
.global CodeStart
.global INT1_ISR
.include "../DefineIO.asm"


.data
DATA_DP:
Counter1: .word 0 ;
按键1计数器
Counter2: .word 0 ;
按键2计数器
Counter3: .word 0 ;
按键3计数器
Counter4: .word 0 ;
按键4计数器
Keyvalue: .space 30H*16 ;
按键历史缓冲区

.text
CodeStart:
.copy "../SP_DP_IPTR.asm" ;
初始化SPDPIPTR代码段
;
初始化变量
STM #Keyvalue,AR2
ST #0,Counter1
ST #0,Counter2
ST #0,Counter3
ST #0,Counter4

SSBX INTM ;关中断
STM #00000010B,IMR ;
允许INT1中断
STM #0FFH,IFR ;
清除挂起的中断
RSBX INTM ;
开中断

wait:
B wait;
*******************************************************
*
键盘中断子程序
*******************************************************
INT1_ISR:
PORTR #Keyboard,*AR2 ;
读取键码
ANDM #0FH,*AR2 ;Keyvalue
只有低四位有效
BITF *AR2,#01H ;
如果键码为1,跳转到FuncKey1
BC FuncKey1,TC
BITF *AR2,#02H ;
如果键码为2,跳转到FuncKey2
BC FuncKey2,TC
BITF *AR2,#04H ;
如果键码为3,跳转到FuncKey3
BC FuncKey3,TC
BITF *AR2,#08H ;
如果键码为4,跳转到FuncKey4
BC FuncKey4,TC
B FuncKeyEnd ;

FuncKey1:
ADDM #1,Counter1 ;
按键1计数器+1
B FuncKeyEnd
FuncKey2:
ADDM #1,Counter2 ;
按键2计数器+1
B FuncKeyEnd
FuncKey3:
ADDM #1,Counter3 ;
按键3计数器+1
B FuncKeyEnd
FuncKey4:
ADDM #1,Counter4 ;
按键4计数器+1
B FuncKeyEnd
FuncKeyEnd:
PORTW *AR2+,Digital0 ;
当前键码显示到数码管上
STM #0FFH,IFR ;
清除挂起的中断
RETE

实验六 DMA实验
实验目的:学习DMA的原理的使用方法
实验内容:用DMA方法接收McBSP接口语音芯片的数据

DMA是直接存储器存取,是一种传送不占用CPU处理时间的大批量数据传送的有效方式。我们用以下实例来说明它的应用:

如果我们要做一个音频处理系统,需要连续用McBSP接口的语音芯片采集若干个样本进行处理,比如频谱分析、音频压缩等。本例假设要每采集256个样本进行一次处理。上例用的是查询方式,占用了所有CPU资源。可以用中断方式,结合前面的实验不难做到,请同学们自行完成。在这个实验中我们将介绍一个更有效的DMA传送方式。我们比较一下用中断方式和DMA方式的效率有何不同:
一、中断方式:每当中缓冲串口接收一个16bit样本的数据,触发一次串口接收中断,将数据转移到一个256
word
的数据接收缓冲区并计数。当计数达到256个,即缓冲区满时,将256个数据转移到数据处理存储区,并通知主程序进行处理。

二、DMA方式:我们使用一个通道自动接收McBSP传来的数据并存入接收缓冲区,当缓冲区满时触发DMA中断,将256个数据传送到数据处理存储区,传送完毕触发通知主程序进行处理。

由上比较可见,每接收一批样本,用中断方式将触发256次中断,也就是主程被打断256次去接收数据。而用DMA方式,只在全部256个样本全部接收完毕时发生一次中断,这时主程序应该已经处理完上一批的数据。
进一步考虑,当数据处理完毕后还需要将数据送走,这时又可以采用另一个DMA通道完成这个任务,将CPU释放出来等待进行下一批样本的处理。

事实上DMA传送并非比用CPU直接处理快,例如在内部存贮器之间传送时,用CPU需要2cycle/word,而用DMA4cycle/wordDMA的优势在是把CPU解放出来做其它的事。
以下是两个DMA通道与CPU协调工作的情况(阴影部分表示空闲)。
DMA0
①①①
CPU
②③ ②③
DMA1

          

①从McBSP接收数据
DMA中断,将数据从接收缓冲区转移到数据处理存储区
③对对数据进行处理
④将处理完的数据送走
估计一下各步的时间,设采样频率是8kHzCPU时钟频率100MHz。因此一个处理周期为1/8kHz*256=32ms
②传送256个点至少需要256word*2cycle/word=512cycle=5.12us
假设处理完后数据量不变,④需要256word*4cycle/word=1024 cycle=10.24us
③所需要的时钟周期取决于算法的复杂度了。

计算好各步所需要的时钟周期,就可以根据情况灵活选择如何使用DMA,例如如果CPU有足够的空闲时间送走数据就不必要④;如果CPU仍然不足,就需要再增加个一个DMA来做②的任务。如果数据的输出也是从McBSP输出,还要用一个DMA通道进行McBSP的发送。
总之要合适地使用DMA通道,使用不当也会使程序变得更加复杂,例如多个DMA通道优先级的问题等等。

C54x系列有6DMA通道,但不同型号C54x系列DSP
DMA
通道的使用不全相同,如C5402只能将DMA通道用于内部数据存贮器之间传送、McBSPHPI接口,而C5410可用于内部、外部数据、程序存贮器之间传送。详细介绍请参阅SPRU302
TMS320C54x DSP Reference Set, Volume 5: Enhanced
Peripherals
和各DSP的数据手册。

实验7.1 FIR
;=============================================================
; fir4.asm
;
用用循环缓冲区和双操作数寻址方法实现FIR滤波器
;
先用matlab,选择80点汉明窗设计一个截止频率为0.2π的低通滤波器
;
本例与前不同的是系数直接引用程序存储器的系数表
;N=5 y(n)=h0*x(n)+h1*x(n-1)+h2*x(n-2)+h3*x(n-3)+h4*x(n-4)
;=============================================================
.title "fir4.asm"
.mmregs
.def start
;
分配数据存储区
.bss y,1 ;y
xn .usect "xn",80 ;xn
h .usect "h",80 ;h
PA0 .set 0000H ;
数据输出端口
PA1 .set 0001H ;
数据输入端口
;
参数表
.data
table: .word -7,-18,-24,-22,-9,11,33,48
;
已在Matlab中转成十六进制的小数
.word 46,20,-24,-73,-104,-97,-43,49
.word 146,204,187,81,-91,-268,-371,-337
.word -144,162,476,661,603,261,-297,-894
.word -1283,-1222,-562,697,2373,4142,5618,6456
.word 6456,5618,4142,2373,697,-562,-1222,-1283
.word -894,-297,261,603,661,476,162,-144
.word -337,-371,-268,-91,81,187,204,146
.word 49,-43,-97,-104,-73,-24,20,46
.word 48,33,11,-9,-22,-24,-18,-7

start: SSBX FRCT ;小数乘法
;
把参数表复制到数据存储区
STM #h,AR1
RPT #79
MVPD #table,*AR1+
;
x(n)-x(n-79)赋始值0
STM #xn,AR1
RPT #79
ST #0,*AR1+

STM #xn+79,AR3 ;x(n-79)---AR3
STM #h+79,AR4 ;h(n-79)---AR4
STM #80,BK ;
循环缓冲区大小80
STM #-1,AR0 ;
指针调整值-1
LD #xn,DP ;DP
指向xn所在页
PORTR PA1,@xn ;
输入数据
LD #y,DP ;DP
指向y所在页
FIR: RPTZ A,#79 ;
进行一次FIR运算
MAC *AR3+0%,*AR4+0%,A;A=(AR3)*(AR4)+A,
AR3=AR3+AR0,AR4=AR4+AR0
STH A,@y ;
保存计算结果
PORTW @y,PA0 ;
输出结果
BD FIR ;
读入下一个数据并进行下一次计算
PORTR PA1,*AR3+0% ;
新数据覆盖了最旧的数据
.end

实验7.2 IIR
.mmregs
.global codestart
K_DATA_SIZE .set 256 ;
输入数据个数
K_BUFFER_SIZE .set 8 ;
缓冲大小,需是2的整数次幂,并大于ab的个数
K_STACK_SIZE .set 256 ;
堆栈大小
K_A .set 3 ;a
向量个数
K_B .set 4 ;b
向量的个数
K_CIR .set 4 ;>=a
b的长度,也可以设为K_BUFFER_SIZE-1

STACK .usect "stack",K_STACK_SIZE
SYSTEM_STACK .set K_STACK_SIZE+STACK

.data
DATA_DP:
.align K_BUFFER_SIZE
bufferdatax: .space K_BUFFER_SIZE*16 ;size in bits
bufferdatay: .space K_BUFFER_SIZE*16 ;size in bits
inputdata: .word 0
filterdata: .word 0

.text
.asg AR2, ORIGIN
.asg AR3, INPUT
.asg AR4, FILTER
.asg AR5, OUTPUT


codestart:
SSBX FRCT
SSBX INTM
LD #DATA_DP,DP
STM #SYSTEM_STACK, SP
CALL filter_start
NOP
NOP
NOP

LOOP:
B LOOP

.def b0,b1,b2,b3,a1,a2,a3;
.def filter_start
b0 .set 1456H ;b1=0.1589 *2^15
b1 .set 3D07H ;b2=0.4768
b2 .set 3D07H ;b3=0.4768
b3 .set 1456H ;b4=0.1589
a1 .set -103AH ;a1=-0.1268
a2 .set 430FH ;a2=0.5239
a3 .set -1016H ;a3=-0.1257

;=================================================================
;
滤波子程序:filter_start
;=================================================================
.text
filter_start:
STM #K_CIR,BK ;
设置环形buffer的大小
STM #1,AR0 ;
和步长
STM #inputdata,ORIGIN ;AR2
STM #bufferdatax,INPUT ;AR3
STM #bufferdatay,FILTER ;AR4
STM #filterdata,OUTPUT ;AR5
;
初始化
RPT #K_B-1-1 ;
ST #0,*INPUT+0% ;x(-1)
x(-2)x(-3)设为0
RPT #K_A-1
ST 0,*FILTER+% ;y(-1)
y(-2)y(-3)设为0
STM #bufferdatay,FILTER

STM #K_DATA_SIZE-1,BRC ;块循环次数,头三个值已经直接通过了
RPTB filter_end-1 ;
块循环结束位置
;
可以把块循环改成中断调用,有新数据就中断一次。
nop ;
数据从件导入点,加nop保证数据在使用前导入
nop
MVDD *ORIGIN,*INPUT ;
新数据
MAR *+INPUT(-K_B+1)%
MPY *INPUT+0%,#b3,B ;B=x(n-3)*b3, i=i+1
LD B,A
MPY *INPUT+0%,#b2,B ;B=x(n-2)*b2, i=i+1
ADD B,A
MPY *INPUT+0%,#b1,B ;B=x(n-1)*b1, i=i+1


ADD B,A
MPY *INPUT+0%,#b0,B ;B=x(n)*b0, i=i+1


ADD B,A
MPY *FILTER+0%,#a3,B ;B=y(n-3)*a3, j=j+1 j=n-3
y的指针

ADD B,A
MPY *FILTER+0%,#a2,B ;B=y(n-2)*a2, j=j+1


ADD B,A
MPY *FILTER+0%,#a1,B ;B=y(n-1)*a1, j=j+1


ADD B,A
STH A,*FILTER ;
传送y(n)y, ;16位小数相乘得到的是32位小数
STH A,*OUTPUT ;
传送y(n)至结果区 ;取前16位就行了
MAR *+FILTER(-K_A+1)%
nop
nop ;
数据文件导出点,加nop保证数据在导出前已更新
filter_end: NOP ;
循环结束
RET
.end

实验八 程序加载
C5000 DSP
没有内部提供掉电保存程序的ROM/EPROM/Flash,上电时需要从外部加载应用程序。C5000
DSP
提供了多种程序加载方式,满足不同应用的需要:串行加载、并行加载、HPI加载等,实际应用最多的是并行加载,本实验主要介绍8位并行存贮器加载。
加载过程:DSP上电时,如果MP/MC引脚为低电平,则跳转到内部ROMFF80中断向量表的Reset中断,该处有一个跳转指令转到称为Bootloader的加载程序执行,该程序的功能是按照一定顺序查找可用的加载方式,如果找到,则开始加载应用程序,加载完毕转向应用程序执行。
实现并行存贮器加载的关键是建立一个加载表(boot
table)
,该表包括:一个或多个程序代码段、部分需要初始化的寄存器值、程序入口等信息。CCS附带有一个应用程序(C5000系列是hex500.exx)可以把.out程序转成.hex格式的加载表,然后可以烧录到非易失性存贮器中,如OTP/EPPOM/EEPROM/Flash中。
具体步骤:
1.
修改项目的编译选顶,使其生成可以转化成加载表的.out文件格式
2.
hex500.exe建立一个配置文件
3.
hex500.exe*.out转化成加载表*.hex
4.
.hex未尾加上加载表起始地址
5.
烧录到非易失性存贮器中
6.
安到目标板上进行加载实验

各步骤详述如下:
1.
修改项目的编译选顶,使其生成可以转化成加载表的.out文件格式
Project/Build Option/Complier 里面加一个选项:-v548,或在Basic/Process
verson
一栏中填写:548,然后点确定。
注意:如果不加这个选项,用hex500程序转化出来的hex文件无法加载。
2.
hex500.exe建立一个配置文件
这个配置文件包含了hex500程序执行所需要的选顶,下面是一个样板配置文件hex.cmd
sample.out /*
待转化的程序文件*/
-map hex.map /*
生成一个map文件便于查看转化结果*/
-o hex.hex /*
输出文件名*/
-i /*
输出文件为Intel Hex 文件格式 */
-memwidth 8 /*
目标系统的存贮器为8*/
-romwidth 8 /*
存贮器芯片的位宽为8*/
-boot /*
生成加载表*/
-bootorg 0000h /*
加载表在存贮芯片中的起始位置*/
-e 80h /*
程序入口,即加载完毕后跳转执行程序位置 */

初学者容易弄错的是memwidthromwidthbootorg这三个选项。不同存贮器配置下设置不同,并且要注意的是上文注释的(包括TI资料中的解释)是程序脱机烧写的设置,而在系统烧录(EEPROMFlashNVSRAM可以支持)与脱机烧写又会有所不同。
脱机烧写指的是将存贮芯片放置在编程器里烧写,优点是直接可以利用hex500转化出来的hex文件,缺点是普通编程器无法烧写贴片封装的芯片。
在系统烧写相对灵活,但对不同系统,不同芯片需要编写专用的烧录程序,并且需要将hex文件进一步进行格式转换成烧录程序可以识别的程序。
不同情况设置方法如下表:
存贮器配置方案脱机烧写在系统烧写
8
位存贮器-memwidth 8
-romwidth 8
16
位存贮器-memwidth 16
-romwidth 16
两片8位贮器并行组成16位存贮器-memwidth 16
-romwidth 8-memwidth 16
-romwidth 16
bootorg
芯片中的起始地址系统中的起始地址


3.
hex500.exe*.out转化成加载表*.hex
dos窗口下执行:hex500 hex.cmd
如果hex500.exehex.cmd以及待转化的程序文件不在同一目录下,需加上路径或设置path环境变量。

4.FFFFH加上加载表起始地址
当开始并行加载时,Bootloader程序会在外部程序空间的FFFFH(如果是8位系统,同时也会查FFFEH)寻找加载表的地址,如果熟悉hex文件格式可以直接在hex文件末尾加一条纪录,也可以烧录器软件中加。

应用实例:
本实验箱是用于程序加载的是8EPROMEEPROM,可以用脱机方式,如用EEPROM也可以用在系统方式。Hex500程序的配置文件如前hex.cmd文件,只需要把第一行的sample.out换成实际的程序名称。EPROM/EEPROM在程序空间的地址为8000H,并且由于是8位系统,则要在FFFEHFFFFH的值分别设为80H00H。可以在生成的.hex文件倒数第二行加上:
:027FFE00800001
也可以在烧录器软件中修改FFFEH-FFFFH处的值,然后就可以烧写了。
烧写好后,将芯片安置在IC座中,确认MP/MC跳线置为低电平,INT3INT2的跳线置于悬空后,打开电源,就可以看到程序运行的状况。

楼主辛苦了[em01]
返回列表