GBA探索日记(5)(DMA)
这一篇的内容其实是为后一篇播放声音的部分作预备.播放声音的部分一共有三个预备篇DMA,Timer,V-Blank中断.这是第一个预备篇(DMA).
声音和中断一直是困惑GBA开发者的两大难题.GBA中播放声音需要使用中断,而GBA的中断设置又很复杂.这篇日记我从简单的几个方面来讲解播放声音的时候需要的预备知识.播放声音的方法其实是很简单的,但是里面涉及到另外的一些东西,如果不把这一节的东西搞清楚,那么后面播放声音的时候就会感到迷惑.
在讲解播放声音之前我需要讲解三个预备的东西:
DMA传输,Timer定时器,V-Blank中断.
最后我还会给出使用中断服务程序的方法
DMA传输
前面的日记里已经提到过一个AgbLib里函数(更确切地说应该是宏)DmaArrayCopy.
它就是使用GBA里面的DMA通道来传输数据的.
DMA通道不仅在GBA里面有,在我们平时使用的计算机里面更是普遍.
它是一种专门传输数据的线路,它传输的数据可以不经过CPU的访问,所以它传输数据的速度是比一般的传输方式快得多.但对于一般的程序员来说,DmaArrayCopy的使用方式和memcpy没有多少区别,只是效率更高.
现在让我们具体来看看GBA里面的DMA.
GBA里面有4条DMA通道
其优先权从高到底如下:DMA0,DMA1和DMA2,DMA3
如果较高优先级的DMA通道开始运行的时候,有较低优先级的DMA已经在运行,那么较地的DMA就回停止运行,而先运行较高的优先权的DMA.当优先级较高的DMA运行完后,原先优先级较低的DMA从它停止的地方的继续开发运行.
DMA0
通常我们并不使用它,它有最高的优先级,不会被其它DMA通道打断,所以它通常是用来传输一些可靠性很高的数据,而当成horizontal-blanking
DMA来用.
DMA1,DMA2
就是我们播放声音的时候需要用到的两个DMA通道.
DMA3
可以用来传输任何数据,完全可以被当成memcpy一样的函数来用,而AgbLib中的DmaArrayCopy中就是使用DMA3.
我们以使用最多的DMA3为例,来看看如何使用DMA传输数据.
我们大致需要做4件事情就可以使用DMA通道了
1.设置要传输的数据的地址到DMA的数据源地址寄存器
2.设置数据要传输到目的地址到DMA的数据目的地址寄存器
3.设置数据量到DMA的数据量寄存器
4.设置DMA传输属性的寄存器
如果你看过前面几篇探索日记,你会发现GBA里面很多硬件方法的使用都是通过设置寄存器来完成的,通过简单的写内存(有些地方不应该叫内存)就可以完成了.
让我们来看看DMA3的寄存器
1) Source Address 数据源地址寄存器
这里一共用了28位来保存数据的源地址,源地址的范围是00000000h-0FFFFFFFh(几乎是全部)
2) Destination Address 数据目的地址寄存器
同样,一共使用了28位来记录数据传输的目的地址,目的地址的范围是00000000h-0FFFFFFFh.
3) Word Count 数据量寄存器
一共用了16位,数据量的范围是1-10000h 字节.
单位是字,两个字节.
如果设置的是16位传输,总共有10000h * 2 =20000h字节传输
如果设置的是32位传输,总共有1000h * 4 =40000h字节可以传输
特别指出的是,通常这个寄存器的值就是要传输数据的字节数.
但是当这个寄存器的值是0的时候,表示要传输的数据为10000h或40000h字节数.
4) DMA Control DMA属性寄存器
下面的属性说明是从官方开发包里翻译而来,其实也没有多大必要仔细看.
DM3CNT_H 第15位: DMA开关
设置成0,表示不能使用DMA
设置成1,表示开始使用DMA,并且当DMA传输完毕数据后,数据源地址寄存器和数据目的寄存器回复到上次的设置.
需要注意的是,一旦设置成1后,DMA马上就开始根据前面三个寄存器的设置开始传输数据.所以你应该想设置好上面三个寄存器和其它DMA属性后,最后设置这个开关.
DM3CNT_H 第14位: 中断要求开关
设置成0,表示不使用中断
设置成1,表示使用中断
这里的中断是在数据全部传输完毕后产生.
DM3CNT_H 第13-12位 传输时间控制
DM3CNT_H 第10位 传输方式
设置成0,表示以16位的方式传输
设置成1,表示以32位的方式传输
好!理论部分就讲完了,下面让我们看看实例代码
首先是寄存器的定义
#define
REG_DMA0SAD
*(u32*)0x40000B0
#define
REG_DMA0DAD
*(u32*)0x40000B4
#define
REG_DMA0CNT
*(u32*)0x40000B8
#define
REG_DMA1SAD
*(u32*)0x40000BC
#define
REG_DMA1DAD
*(u32*)0x40000C0
#define
REG_DMA1CNT
*(u32*)0x40000C4
#define
REG_DMA2SAD
*(u32*)0x40000C8
#define
REG_DMA2DAD
*(u32*)0x40000CC
#define
REG_DMA2CNT
*(u32*)0x40000D0
#define
REG_DMA3SAD *(u32*)0x40000D4
#define
REG_DMA3DAD
*(u32*)0x40000D8
#define
REG_DMA3CNT
*(u32*)0x40000DC
然后属性的定义
#define
DMA_ENABLE 0x80000000
#define
DMA_INTERUPT_ENABLE 0x40000000
#define
DMA_TIMEING_IMMEDIATE 0x00000000
#define
DMA_TIMEING_VBLANK 0x10000000
#define
DMA_TIMEING_HBLANK 0x20000000
#define
DMA_TIMEING_SYNC_TO_DISPLAY 0x30000000
#define
DMA_16 0x00000000
#define
DMA_32 0x04000000
#define
DMA_REPEATE 0x02000000
#define
DMA_SOURCE_INCREMENT 0x00000000
#define
DMA_SOURCE_DECREMENT 0x00800000
#define
DMA_SOURCE_FIXED 0x01000000
#define
DMA_DEST_INCREMENT 0x00000000
#define
DMA_DEST_DECREMENT 0x00200000
#define
DMA_DEST_FIXED 0x00400000
#define
DMA_DEST_RELOAD 0x00600000
#define
DMA_32NOW DMA_ENABLE
| DMA_TIMEING_IMMEDIATE |DMA_32
#define
DMA_16NOW DMA_ENABLE
| DMA_TIMEING_IMMEDIATE |DMA_16
里面有很多我们用不着.
下面直接看看这个函数就知道了
void
DMA_Copy(u8 channel, void* source, void* dest, u32 WordCount, u32 mode)
{
switch (channel)
{
case 0:
REG_DMA0SAD
= (u32)source;
REG_DMA0DAD
= (u32)dest;
REG_DMA0CNT
= WordCount | mode;
break;
case 1:
REG_DMA1SAD
= (u32)source;
REG_DMA1DAD
= (u32)dest;
REG_DMA1CNT
= WordCount | mode;
break;
case 2:
REG_DMA2SAD
= (u32)source;
REG_DMA2DAD
= (u32)dest;
REG_DMA2CNT
= WordCount | mode;
break;
case 3:
REG_DMA3SAD
= (u32)source;
REG_DMA3DAD
= (u32)dest;
REG_DMA3CNT
= WordCount | mode;
break;
}
}
看起来很简单吧.
我们使用的时候也很简单,比如我要把的图片数据传输到VRAM中显示:
DMA_Copy(3,(void*)landbmp_gfx,(void*)VideoBuffer,19200/2,DMA_32NOW);