打印

DSP(TMS320C6713)入门教程

[复制链接]
18355|21
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
jxmzzr|  楼主 | 2014-6-11 16:55 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
一、LED的点亮
最近很多朋友开始学习DSP了(小双同志也加入这个团伙),本人基本上入门。在此给他家分享一下DSP的入门经验。其实DSP和我们本科所使用的单片机基本上架构一致,只是在它的内部集成了一系列的运算单元和逻辑移位单元,并且安排了指令流水,这样在运算性能上大大的提高,可以完成一系列的复杂计算。当然DSP内部也集成了一系列外设,我这儿使用的是TMS320C6713 DSP,这块DSP主频可以达到450M,可以安排8级指令流水,在同一时刻可以同时执行8条指令,当然这要求的是CPU内部的运算单元不能冲突!好了,在此我就不多介绍了,免得把大家说得晕呼呼的!我们刚才是入门了解这些基本上没用,我们得一步一步的按着简单的东西一步一步的做实验。所以我们今天开始一个最简单的实验—LED灯的点亮!
我们一般学习是要买一块开发板,在此我不做推销,其实每一块开发板都基本上差不多,很多就是按照TI公司的Demo板,而设计的。如果没有学习板,自己看书看了半年,还不如我拿到板子调试一个月的效果,因为很多东西是要在实际中才知道他的作用。我们用一个板子一般要几样东西:一、原理图(知道每一根信号线的走向,比如我们的LED就连接到DSP的GPIO的13脚)
二、芯片资料(芯片的总的芯片Datasheet和子模块的Datasheet,一般在芯片资料中总的芯片资料会告诉大家芯片的整体规划,比如内存分布,特殊寄存器的分布和具体的地址,而子模块资料会把这个模块的功能和使用介绍得更为详细)

三、电路板和仿真器(这个是投入较大的一笔了)
四、编译软件和计算机(希望在做实验之前大家用过编译器,不一定是CCS,因为所有基于windows上的编译软件都是一个样)
我们来开始第一步,查看我们的电路板上的LED灯的位置和DSP的信号线的连接



从左边的几个原理图的截图我们可以清晰的看到LED灯接到了GPIO的13脚,中间用了一个缓冲器过度了一下,实际的控制信号还是来自DSP的GPIO13。LED上面上拉了一个150的电阻,这个主要是分压,让LED上面的电压不是很到这样不至于LED因为电流过大而损坏了。当然如果有人觉得LED不够亮就可以把这个电阻换成更小一点的。那么我们要点亮这个灯,我们只要把GPIO13那个管脚拉成低电平就可以了,熄灭呢只要拉成高电平。可能跟我们一般逻辑想象不大一样,不过电路是这样做的,那么我们只能这样服从!
我们开始第二步,查看我们的芯片资料使用我们的GPIO模块
打开《TMS320C6713 Data Sheet (Rev. I)》这个pdf文件,这个里面介绍了我们这个DSP的整体状况和内存的映射地址,当然我们现在不用关心那么多,只看看我们今天要用的GPIO模块所在的内存和他的特殊寄存器的功能

这是我们在内存映射中看到了我们的GPIO模块映射到内存的地址,为0x01B0_0000到0x01B0_3FFF的地址。在此我多说一点,一般的CPU分成统一编址和IO编址,统一编址的意思就是把外围的每一个模块当成内存单元,读写操作和读写内存操作一样的指令集,比如我这个DSP就是这样的。这样我就很方便的使用外设,但是他占用了我们的内存地址空间。大家已经看到了GPIO模块占用了我们的内存地址16K。那么IO编址的哈,就是把外设单元和我们的内存单元分开编址。比如我同样的地址只要指令集不一样,那么读写操作的对象就不一样了!就啰嗦这么多吧,看看我们的寄存器的功能吧。首先看看使能寄存器GPEN,这个寄存器的功能是选择GPIO对应的管脚为GPIO功能,细心的读者也许已经注意到了DSP管脚一般有复用的,比如我们要用的GPIO13就和HD13复用了

所以在此我们要用GPIO13那么我就要使能这个管脚,在我框住的红色哪一位置“1”,那么我们怎么写语句呢。一个好友(小双)写成这样*(unsigned int *)0x01B00000 =1<<13;我觉得这个语句是达到目的了,但不大好,因为在对13位置“1”的同时就把其他的清零了,那么我们就影响了别人的功能了。所以我改成*(unsigned int *)0x01B00000 |=1<<13;把以前的读出来了然后通过或操作达到只影响13位置“1”的目的。OK,我们这就把13脚弄成GPIO功能了。那么我们一般的GPIO脚有输入输出的功能。输入就是让DSP采样外部管脚电压值,而输出就是DSP驱动外部电路。当然我们是要用LED的点亮和熄灭,那么我们就要把13脚弄成输出。那么我就要使用 GPIO Direction Register (GPDIR) [Hex Address: 01B0 0004]GPIO方向寄存器。

相关帖子

沙发
jxmzzr|  楼主 | 2014-6-11 16:57 | 只看该作者




这个寄存器就是控制GPIO管脚的输入输出功能,通过原理图大家可以看得很清楚。那么我只要把13位置“1”就是输出功能。语句为:*(unsigned int *)0x01B00004|=1<<13;下一步我们就是输出高低电平来点亮和熄灭我们的LED那么改变我们管脚的电平值的寄存器就是管脚的值.“0”输出低电平,“1”输出高电平。GPIO Value Register (GPVAL)控制这个功能.



那么我点亮LED的语句为:*(unsigned int *)0x01B00008 &=~(1<<13);熄灭LED的语句为:*(unsigned int *)0x01B00008|=1<<13;
整体工程源代码:
void delay(unsigned int i)
{
while(i–);
}
void main()
{
*(unsigned int *)0x01B00000 |=1<<13;
*(unsigned int *)0x01B00004|=1<<13;
while(1)
{
*(unsigned int *)0x01B00008 &=~(1<<13);
delay(0xffff);
*(unsigned int *)0x01B00008|=1<<13;
delay(0xffff);
}
}

使用特权

评论回复
板凳
jxmzzr|  楼主 | 2014-6-11 17:00 | 只看该作者
二、定时器的使用
接着我们上一章(LED灯的点亮)的旅行!上一章我们使用了DSP的GPIO去点亮我们的LED灯,我们为了让人眼能够直接的感受LED的闪烁我们用到了一个延迟函数。但是这样的延迟是让CPU空转,while内部的。当然这样我们不好精确的计算出延迟了多长时间,幸好DSP内部用一个32bit的定时计数器(其实最简单的单片机里面都会有集成的8bit或者16bit定时计数器),我们可以使用它来精确的定时。一般定时计数器有两个功能,一是定时:时钟源一般来源于DSP内部,当然也可以选择来自于外部。二是计数:我们可以利用它的功能来计算外部脉冲在一段时间内到来的次数,所以叫做计数器(记录外部脉冲的次数)。因为内部有一个计数的count,如果用作定时就是先输入一个数字,然后在来了一个时钟之后count自动减一。如果用成计数就是在一个脉冲到来之后count自动加一!DSP6713内部有两个32bit的定时计数器(timer0和timer1),两个基本上是一样的。我们先来看看定时器的内部构造:

大家通过这样的原理框图就可以看出哪些bit影响哪些功能,比如CLKSRC bit就是选择时钟源,为“0”选择TINP脚的和INVINP的异或信号作为时钟源,“1”选择的是CPU内部时钟源,为CPU主频的1/4.我们的DSP主频可以达到450MHz,那么我们的定时器使用的时钟就是112.5MHz。GO bit就是使能定时器,让定时器开始计数,也就是定时开始信号。HLD bit就是暂停和继续计数器控制位。我们来看看Timer Control Register (CTL)寄存器的的实际用法

我们这儿使用的是定时功能,所以我们就把红色标记的几个bit置成“1”,我们先分析一下这几个bit的意义。
一、设置CLKSRC bit位为“1”,这样就使用CPU内部的时钟源,CPU的主频的4分频。
二、设置CP bit位为“1”,这样我时钟源的占空比就为50%了。这样就把timer的时钟源选定了!
三、设置PWID bit为“1”这样我们的定时器到时时,产生一个脉冲信号,这个信号的有效电平的时钟长度为两个时钟源周期。如果为“0”那么就是一个时钟源周期。
四、设置GO bit为“0”这样定时器暂时不计数!
看看timer1模块在DSP内部的地址映射

设置控制器CTL *(unsigned int *)0×01980000 &=~(1<<7|1<<6) ;停止定时器,*(unsigned int *)0×01980000 |=1<<9|1<<8|1<<4;设置定时器的运行模式。*(unsigned int *)0×01980004=0x0000ffff;设置定时器周期为0xffff,这个值会在定时器开始时把这个值装入count中,然后在每一个时钟源周期自动将这个值减一。当这个count减成“0”时就在TSTAT位置“1”这样就产生一个timer事件,这个事件可以中断CPU和使能EDMA。最后在打开定时器计数,开始运行。*(unsigned int *)0×01980000 |=1<<6;置GO bit为“1”。我们可以查询一下cnt寄存器的值,这个是实时的定时器的计数值。为“0”的时候就是定时器到时了我们可以用语句:while((*(unsigned int *)0×01980008)!=0);这样CPU也是空循环等待定时器到时!
主要的程序代码为:
void main()
{
*(unsigned int *)0×01980000 &=~(1<<7|1<<6) ;
*(unsigned int *)0×01980000 |=1<<9|1<<8|1<<4;
*(unsigned int *)0×01980004=0x0000ffff;
while(1)
{
while((*(unsigned int *)0×01980008)!=0);
*(unsigned int *)0x01B00008 &=~(1<<13);//点亮LED
while((*(unsigned int *)0×01980008)!=0);
*(unsigned int *)0x01B00008|=1<<13; //熄灭LED
}
}

使用特权

评论回复
地板
jxmzzr|  楼主 | 2014-6-11 17:03 | 只看该作者
三、中断的理解和使用
学习一个芯片的功能时,我的建议是先学会如何用C语言点亮一个LED灯,然后就是学习一下使用他的中断,因为在做芯片的时候,各个厂家有自己的一套自己的方法。所以使用中断的就必须了解很多概念,比如如何打开中断,如何安装自己的中断子服务程序,等等!先介绍一下什么叫中断:你在下象棋,突然电话响了,你回屋接电话,然后回来继续下象棋,这个过程就叫做中断响应过程(中断过程)。
CPU执行正常任务———————下象棋
保护现场———————————-你已经想好要“将军”,先在脑海中记下来。
中断发生———————————-电话响-
中断服务程序—————————-接电话
恢复现场———————————-回来后恢复刚才想法
中断返回———————————-你回来继续下象棋
中断屏蔽———————————-Boss 正在训话,要求所有电话关机,你不能接电话了。
非屏蔽中断——————————-你内急,即使是 Boss 在训话,你还是得到外面去嘘嘘。
可屏蔽中断——————————-你在“闭关修炼”,可以不受外界干扰
所以我们在使用中断之前先得告诉CPU,我们要使用那个中断,当中断发生的时候,你的执行程序的去向(也就是中断服务子程序),最后在返回我们被中断的函数。这样就完成了我们的中断历程


看看6713执行中断的流程:
一、使能了全局中断和子中断,那么CPU每执行一条指令之前就去查询一下有没有中断被置位,如果有产生的,那么CPU就要跳转!
二、软件把CPU内部的寄存器A0~A15,B0~B15,等等这些寄存器的值推入堆栈保存,把当前PC寄存器的值放入IRP寄存器中以备中断返回能找到当前被打断的位置(保存现场,中断函数前面得加interrupt关键字)
三、CPU的PC指针读出中断向量表的地址,也就是把(ISTP寄存器的值+子中断向量偏移量)装入PC寄存器,这样就执行跳转。
四、在中断向量表里一般有就用跳转指令,这样就可以跳转到我们用C语言编写的中断服务子程序中。由于我们在一般的中断服务子程序函数前面加入了interrupt关键字。那么编译器就会在函数返回的语句改成B IRP,这样就把IRP的值送给PC寄存器。(中断服务)
五、CPU在执行跳转之前会把堆栈中以前保存的那些值出栈到自己的相应的寄存器中。(恢复现场)
OK,明白了中断执行的一个大概流程,我们就来看看6713或者说是C6000系列的中断如何安装和使用(以上一章的timer1中断为实例)。
由于本来6713有32个中断源,也就是中断CPU的信号可以来自这个32信号源。但是DSP却安排了在同一时刻置最多服务16个中断号,但是有一个reset,nmi和2个保留的,这些我们不能使用其他的中断源占用,所以我们外设能占用的中断号为4~15。那么怎么办呢,TI在次想了一个办法,就是使用多路选择开关来决定哪个中断源连接到我们的某一中断号上。MUXL和MUXH两个寄存器,就是这个选择开关!

由于我们使用的是Timer1中断,从图中可以看出15号中断号默认为Timer1的中断服务号,但是我这儿想用14号来服务我们的中断,这样以后大家想用其他的就依样画葫芦了。那么我这儿就得把MUXH[26:21]=0×02;这样就把14号中断和我们的timer1中断源接上了。当定时器到时时就CPU就会跳转到中断向量表的14号向量那儿。接下来我就来安装和使用定时器中断吧。
1、安装中断向量表,一般用汇编编写:取名为vector.asm

*——————————————————————————
* Global symbols defined here and exported out of this file
*——————————————————————————
.global _vectors//全局标号,可以在别处使用.
.global _c_int00
.global _vector1
.global _vector2
.global _vector3
.global _vector4
.global _vector5
.global _vector6
.global _vector7
.global _vector8
.global _vector9
.global _vector10
.global _vector11
.global _vector12
.global _vector13
.global _TimerHandler ; Hookup the TimerHandler ISR in main() 汇编中的C语言函数要加“_”
.global _vector15

*——————————————————————————
* Global symbols referenced in this file but defined somewhere else.
* Remember that your interrupt service routines need to be referenced here.
*——————————————————————————
.ref _c_int00//相当于 extern,在这里引用,在别处定义
*——————————————————————————
* This is a macro that instantiates one entry in the interrupt service table.
*——————————————————————————
VEC_ENTRY .macro addr//定义中断向量入口地址

STW B0,*–B15 ;保存B0 内容,中断产生后执行的第一条指令
MVKL addr,B0;
MVKH addr,B0 ;把地址装入 B0
B B0 ;跳转至 B0 中存储的地址
LDW *B15++,B0 ;恢复B0内容; 由于C6000流水线的原因,跳转后仍然可以执行多条指令
NOP 2
NOP
NOP
.endm
*——————————————————————————
* This is a dummy interrupt service routine used to initialize the IST.
*——————————————————————————
_vec_dummy:未定义中断服务程序
B B3;其他没有定义的中断跳转至 B3 存储的地址
NOP 5

*——————————————————————————
* This is the actual interrupt service table (IST). It is properly aligned and
* is located in the subsection .text:vecs. This means if you don’t explicitly
* specify this section in your linker command file, it will default and link
* into the .text section. Remember to set the ISTP register to point to this
* table.
*——————————————————————————
.sect “.text:vecs”;定义段
.align 1024;1024 字节对边界对齐

_vectors:
_vector0: VEC_ENTRY _c_int00 ;RESET 跳转到_c_int00 ,_c_int00是 c语言程序的入口
_vector1: VEC_ENTRY _vec_dummy ;NMI
_vector2: VEC_ENTRY _vec_dummy ;RSVD
_vector3: VEC_ENTRY _vec_dummy ; 所有未定义中断均跳转到同一地址
_vector4: VEC_ENTRY _vec_dummy
_vector5: VEC_ENTRY _vec_dummy
_vector6: VEC_ENTRY _vec_dummy
_vector7: VEC_ENTRY _vec_dummy
_vector8: VEC_ENTRY _vec_dummy
_vector9: VEC_ENTRY _vec_dummy
_vector10: VEC_ENTRY _vec_dummy
_vector11: VEC_ENTRY _vec_dummy
_vector12: VEC_ENTRY _vec_dummy
_vector13: VEC_ENTRY _vec_dummy
_vector14: VEC_ENTRY _TimerHandler ; Hookup the TimerHandler ISR in main() 定时中断中断向量
_vector15: VEC_ENTRY _vec_dummy
2、初始化中断:先关闭全局中断(即使有新中断也不相应),然后在清除所有中断号的中断标志位,把timer1中断映射到14号中断,设置14号中断为使能,安装中断向量表,最后打开全局中断。
TimerEventId = TIMER_getEventId(Htimer); //取得定时事件 ID 返回为0×02
IRQ_setVecs(vectors); //重新设置中断向量 ISTP= (unsigned int)vectors
IRQ_globalEnable(); //全局中断使能 IER|=1<<14
IRQ_nmiEnable();
IRQ_map(TimerEventId,14); //把定时中断重新映射到 14 MUXH[25:21]=0×02
IRQ_reset(TimerEventId); /
IRQ_enable(TimerEventId);
3、编写中断服务子程序:
interrupt void TimerEventHandler(void)
{
/* process timer event here */
cnt++;
/* Exit from the program when certain count is reached */
if (cnt > TIMER_CNT)
{
TIMER_pause(hTimer1);
TIMER_close(hTimer1);
printf(“\nDone…”);
exit(0);
}
printf(“\n Count : %3d “,cnt);
}




  

使用特权

评论回复
5
zhangmangui| | 2014-6-11 22:07 | 只看该作者
好详细的分享啊

使用特权

评论回复
6
wushaofei168| | 2014-6-12 19:27 | 只看该作者
好贴,楼主原理图有没有?上传个pdf!谢谢哈!

使用特权

评论回复
7
icekoor| | 2014-7-24 14:57 | 只看该作者
最近正接触TMS320C6713,仅仅用作较大规模计算,仍就有些疑问,谢谢分享。

使用特权

评论回复
8
shenmu2012| | 2014-7-24 20:52 | 只看该作者
不管啥都是从简单的学起的。

使用特权

评论回复
9
zhangmangui| | 2014-7-26 09:38 | 只看该作者
icekoor 发表于 2014-7-24 14:57
最近正接触TMS320C6713,仅仅用作较大规模计算,仍就有些疑问,谢谢分享。

多多指点  啊

使用特权

评论回复
10
firstblood| | 2014-7-26 16:37 | 只看该作者
不论什么的开始学习的都是从最简单的点亮LED灯开始的啊。

使用特权

评论回复
11
firstblood| | 2014-7-26 16:38 | 只看该作者
使能了全局中断和子中断,那么CPU每执行一条指令之前就去查询一下有没有中断被置位,如果有产生的,那么CPU就要跳转!这是中断的机制的。。

使用特权

评论回复
12
firstblood| | 2014-7-26 16:39 | 只看该作者
在中断向量表里一般有就用跳转指令,这样就可以跳转到我们用C语言编写的中断服务子程序中。由于我们在一般的中断服务子程序函数前面加入了interrupt关键字。那么编译器就会在函数返回的语句改成B IRP,这样就把IRP的值送给PC寄存器。

使用特权

评论回复
13
xuer250| | 2014-12-2 16:03 | 只看该作者
看了这个LED灯的点亮,还是无从下手程序的编译额

使用特权

评论回复
14
xuer250| | 2014-12-3 15:46 | 只看该作者
firstblood 发表于 2014-7-26 16:38
使能了全局中断和子中断,那么CPU每执行一条指令之前就去查询一下有没有中断被置位,如果有产生的,那么CPU ...

LED灯点亮那一节,楼主附上的主程序是加在6713_GPIO.c主程序里,却报错,那几个数字无定义,请大神帮忙解决

使用特权

评论回复
15
天师猫神| | 2014-12-3 16:23 | 只看该作者
好详细的分享啊好详细的分享啊

使用特权

评论回复
16
xuer250| | 2014-12-3 17:53 | 只看该作者
天师猫神 发表于 2014-12-3 16:23
好详细的分享啊好详细的分享啊

你好哦,请问我把第一节的那个LED点亮加到GPIO例程中,报错,地址那个数字没有定义

使用特权

评论回复
17
好好学习啊| | 2015-8-5 10:45 | 只看该作者
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊

使用特权

评论回复
18
tichysu| | 2015-11-12 20:41 | 只看该作者
太有用了!!!

使用特权

评论回复
19
我是老法海| | 2016-5-5 10:01 | 只看该作者
太用用了。。。。。。。。。

使用特权

评论回复
20
江小黑| | 2017-2-20 15:04 | 只看该作者
谢谢你

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

460

主题

2188

帖子

12

粉丝