DMA基本知识
计算机系统中各种常用的数据输入/输出方法有查询方式(包括无条件及条件传送方式)和中断方式,这些方式适用于CPU与慢速及中速外设之间的数据交换。但当高速外设要与系统内存或者要在系统内存的不同区域之间进行大量数据的快速传送时,就在一定程度上限制了数据传送的速率。直接存储器存取(DMA)就是为解决这个问题提出的,采用DMA方式,在一定时间段内,由DMA控制器取代CPU,获得总线控制权,来实现内存与外设或者内存的不同区域之间大量数据的快速传送。
典型DMA控制器(以下简称DMAC ),其数据传送工作过程如下:
1.外设向 DMAC发出DMA传送请求;
2. DMAC通过连接到CPU的HOLD信号向CPU提出DMA请求;
3.CPU在完成当前总线操作后会立即对DMA请求做出响应。CPU的响应包括两个方面:一方面,CPU将控制总线、数据总线和地址总线浮空,即放弃对这些总线的控制权;另一方面,CPU将有效的HLDA信号加到DMAC上,以通知DMAC CPU己经放弃了总线的控制权;
4.CPU将总线浮空,即放弃了总线控制权后,由DMAC接管系统总线的控制权,并向外设送出DMA的应答信号;
5.DMAC送出地址信号和控制信号,实现外设与内存或内存之间大量数据的快速传送。
6.DMAC将规定的数据字节传送完之后,通过向CPU发HOLD信号,撤消对CPU的DMA请求。CPU收到此信号,一方面使HLDA无效,另一方面又重新开始控制总线,实现正常取指令、分析指令、执行指令的操作。
s3c2440提供了4个通道的DMA,它们不仅可以实现内存之间的数据交换,还可以实现内存与外设,以及外设与外设之间的数据交换。要用好s3c2440的DMA,关键是配置好它的源、目的寄存器,和必要的控制寄存器。寄存器DISRCn是初始DMA源寄存器,它是用于设置DMA数据传输的源基址,而寄存器DIDSTn是初始DMA目的寄存器,它是用于设置DMA数据传输的目的基址。初始DMA源控制寄存器DISRCCn的第1位用于选择源的总线(系统总线AHB还是外设总线APB),第0位用于设置源基址在数据传输过程中是递增还是固定不变。初始DMA目的控制寄存器DIDSTCn的低两位与寄存器DISRCCn相识,但它是用来设置目的基址,而第2位用于设置是在传输完数据之后中断还是在自动重载后中断。DMA控制寄存器DCONn用于控制数据的DMA传输,第31位用于设置传输协议是需求模式还是握手模式,第30位用于选择同步时钟是PCLK还是HCLK,第29位用于设置DMA中断是否发生,第28位用于选择传输大小是单元传输还是突发传输,第27位用于选择服务模式是单步模式还是完全模式,第24位到第26位用于设置DMA的请求源,第23位用于设置DMA的源是软件还是硬件,第22位用于设置是否需要重载传输的目的和源基址,第20位和第21位用于设置数据传输的数据大小(字节、半字还是字),低20位用于初始化传输数据的个数。而通过读取DMA状态寄存器DSTATn的低20位可以获知当前的传输的计数。DMA掩码触发寄存器DMASKTRIGn的第2位可以终止当前DMA操作,第1位可以用于开启DMA通道,第0位则表示在软件请求模式下触发DMA通道。
下面我们就用DMA的方式来实现音频的播放。由于是用DMA的方式,因此在播放的过程中不占用系统资源,我们可以很容易的实现声音的各种操作而丝毫不影响播放的效果,如音量的提高和降低、静音、暂停等。在这里,还需要强调一点,利用DMA传输数据,一次最多可以传输的字节大小为:DSZ×TSZ×TC,DSZ表示的是数据大小(字节、半字还是字,即是1、2还是4),TSZ表示的是传输大小(单元传输还是突发传输,即1还是4),TC表示传输计数值(即寄存器DCONn的低20位存放的数据),因此如果需要传输的字节大小超出了这三个参数乘积的大小,则还要进一步处理,在我们给出的程序中,我们就考虑了这方面的问题。下面就是具体的程序,其中我们是利用UART来实现音频信号的播出、停止、暂停、静音、音量的提高和降低的。
程序分析:
#include"2440addr.h"
#include"What_are_words.h"//音频文件
#include"def.h"
//L3接口
#define L3C (1<<4) //GPB4 = L3CLOCK
#define L3D (1<<3) //GPB3 = L3DATA
#define L3M (1<<2) //GPB2 = L3MODE
int result;
int remainder;
char flag;
char cmd;
char play_state;
void __irq uartISR(void)//串口中断
{
char ch;
rSUBSRCPND |= 0x1;
rSRCPND |= 0x1<<28;
rINTPND |= 0x1<<28;
ch=rURXH0;
switch(ch)
{
case 'p'://0x55: //播放
cmd = 1;
break;
case 'm'://0x1: //静音
cmd = 0x11;
break;
case '+'://0x2: //音量提高
cmd = 0x12;
break;
case '-'://0x3: //音量降低
cmd = 0x13;
break;
case 's'://0x66: //停止
cmd = 0x2;
break;
case ' '://0x77: //空格暂停
cmd = 0x3;
break;
}
rUTXH0=ch;
}
//L3总线接口的写函数
//输入参数data为要写入的数据
//输入参数address,为1表示地址模式,为0表示数据传输模式
static void WriteL3(U8 data,U8 address)
{
int i,j;
if(address == 1)
rGPBDAT = rGPBDAT & ~(L3D | L3M | L3C) | L3C; //L3D=L, L3M=L(地址模式), L3C=H
else
rGPBDAT = rGPBDAT & ~(L3D | L3M | L3C) | (L3C | L3M); //L3M=H(数据传输模式)
for(i=0;i<10;i++)
; //等待一段时间
//并行数据转串行数据输出,以低位在前、高位在后的顺序
for(i=0;i<8;i++)
{
if(data & 0x1) // H
{
rGPBDAT &= ~L3C; //L3C=L
rGPBDAT |= L3D; //L3D=H
for(j=0;j<5;j++)
; //等待一段时间
rGPBDAT |= L3C; //L3C=H
rGPBDAT |= L3D; //L3D=H
for(j=0;j<5;j++)
; //等待一段时间
}
else // L
{
rGPBDAT &= ~L3C; //L3C=L
rGPBDAT &= ~L3D; //L3D=L
for(j=0;j<5;j++)
; //等待一段时间
rGPBDAT |= L3C; //L3C=H
rGPBDAT &= ~L3D; //L3D=L
for(j=0;j<5;j++)
; //等待一段时间
}
data >>= 1;
}
rGPBDAT = rGPBDAT & ~(L3D | L3M | L3C) | (L3C | L3M); //L3M=H,L3C=H
}
//放音子程序
void playsound(unsigned char *buffer,int length)
{
//用于计算音频数据的长度是否超过DMA所能传输的字节数范围
//这里音频数据的通道位数为16位,因此需要length除以2
remainder = (length>>1) & 0xfffff; //余数
result = (length>>1) / 0x100000; //商
play_state = 1; //置播放标志
rGPBDAT = rGPBDAT & ~(L3M|L3C|L3D) |(L3M|L3C);
//配置1341,详细讲解请看上一篇**
WriteL3(0x14 + 2,1);
WriteL3(0x60,0);
WriteL3(0x14 + 2,1);
WriteL3(0x10,0);
WriteL3(0x14 + 2,1);
WriteL3(0xc1,0);
//配置IIS
rIISPSR = 3<<5|3;
rIISCON = (1<<5)|(0<<4)|(0<<3)|(1<<2)|(1<<1); //发送IIS的DMA使能
rIISMOD = (0<<9)|(0<<8)|(2<<6)|(0<<5)|(0<<4)|(1<<3)|(1<<2)|(1<<0);
rIISFCON = (1<<15)|(1<<13); //发送FIFO为DMA
//配置DMA
rDISRC2 = (U32)buffer; //DMA的源基址为音频数据数组的首地址
rDISRCC2 = (0<<1)|(0<<0); //AHB,源地址递增
rDIDST2 = (U32)IISFIFO; //DMA的目的基址为IIS的FIFO
rDIDSTC2 = (0<<2)| (1<<1)|(1<<0); //当传输计数值为0时中断,APB,目的地址不变
if (result == 0) //所传输的字节数没有超出DMA的最大传输范围
{
flag = 0; //清标志,表示没有超出范围,进入DMA中断后结束DMA操作
//握手模式,PCLK同步,传输计数中断,单元传输,单步服务模式,IISSDO,
//硬件请求模式,非自动重载,半字,
rDCON2 = (1<<31) | (0<<30) | (1<<29) | (0<<28) | (0<<27) | (0<<24) | (1<<23) | (1<<22) | (1<<20) | (remainder);
}
else //所传输的字节数超出了DMA的最大传输范围
{
flag = 1; //置标志,表示超出范围
rDCON2 = (1<<31) | (0<<30) | (1<<29) | (0<<28) | (0<<27) | (0<<24) | (1<<23) | (1<<22) | (1<<20) | (0xfffff);
}
rDMASKTRIG2=(0<<2)|(1<<1)|0; //不停止DMA,DMA通道开启,非软件触发
//启动IIS
rIISCON |= 0x1;
}
void __irq DMA_end(void)
{
rSRCPND |= 0x1<<19;
rINTPND |= 0x1<<19;
if (flag == 0) //DMA传输完毕
{
rIISCON = 0x0; //关闭IIS
rIISFCON = 0x0; //清IIS的FIFO
rDMASKTRIG2=1<<2; //停止DMA
play_state = 0; //清播放标志
}
else //DMA没有传输完毕,继续传输
{
result --; //商递减
rDISRC2 += 0x200000; //DMA源基址递增。因为传输的数据是半字,所以这里递增0x200000
if (result == 0 ) //只剩下余数部分需要传输
{
rDCON2=(rDCON2&(~0xfffff))|(remainder); //需要重新设置传输计数值
flag=0; //清标志
}
rDMASKTRIG2=(0<<2)|(1<<1)|0; //需要重新设置DMA通道的开启
}
}
void Main(void)
{
char mute;
char volume;
//unsigned char* music=WindowsXP_Wav;
//配置MPLL
//fs=44.1kHz,CODECLK=384fs=16.9344MHz
//不改变CLKDIVN,所以PCLK=FCLK/8
//MPLLCON:MDIV=150,PDIV=5,SDIV=0,所以FCLK=541.7143MHz,PCLK=67.714MHz
rMPLLCON = (150<<12) | (5<<4) | 0;
Uart_Init(67714000,115200);
//配置L3接口总线,GPB2:L3MODE, GPB3:L3DATA, GPB4:L3CLOCK
rGPBCON = 0x015550; //输出
rGPBUP = 0x7ff; //上拉无效
rGPBDAT = 0x1e4;
//配置IIS接口
rGPEUP = rGPEUP & ~(0x1f) | 0x1f; //上拉无效,GPE[4:0] 1 1111
rGPECON = rGPECON & ~(0x3ff) | 0x2aa;
Uart_Init(33857000*2,115200);
rSRCPND = (0x1<<19)|(0x1<<28);
rSUBSRCPND = 0x1;
rINTPND = (0x1<<19)|(0x1<<28);
rINTSUBMSK = ~(0x1);
rINTMSK = ~((0x1<<19)|(0x1<<28)); //开启DMA2中断屏蔽,串口0接收中断
pISR_UART0 = (U32)uartISR;
pISR_DMA2=(U32)DMA_end;
result=0;
remainder=0;
flag=0;
cmd=0;
play_state =0;
Uart_Printf("\n'p':Play,'s':Stop,Space:Pause,'m':mute,'+':Volume Up,'-'Volume Down\n");
while(1)
{
switch(cmd)
{
case 0x1: //播放
if (play_state==0)
{
volume = 0; //音量清零
mute=0xa0; //初始化静音
playsound(What_are_words+44,sizeof(What_are_words)-44);
}
else
{
;
Uart_Printf("Error\n");
}
cmd = 0;
break;
case 0x2: //停止
if (play_state==1)
{
rIISCON = 0x0; //停止IIS
rIISFCON = 0x0; //清IIS的FIFO
rDMASKTRIG2=1<<2; //终止DMA2
flag = 0;
play_state = 0;
}
else
{
;
Uart_Printf("Error\n");//
}
cmd = 0;
break;
case 0x3: //暂停,
if(play_state == 1)
{
rIISCON ^= 0x1; //异或,
}
else
{
;
Uart_Printf("Error\n");
}
cmd = 0;
break;
case 0x11: //静音
if (play_state==1)
{
mute ^= 0x4;
WriteL3(0x14 + 0,1); //DATA0 (000101xx+00)
WriteL3(mute,0); //10,1,00,x,00:x,静音
}
else
{
;
Uart_Printf("Error\n");
}
cmd = 0;
break;
case 0x12: //音量递增
if (play_state==1)
{
if(volume>0)
{
volume --;
WriteL3(0x14 + 0,1); //DATA0 (000101xx+00)
WriteL3(volume,0); //音量提高
}
}
else
{
;
Uart_Printf("Error\n");//rUTXH0=0xff;
}
cmd = 0;
break;
case 0x13: //音量递减
if (play_state==1)
{
if(volume<61)
{
volume++;
WriteL3(0x14 + 0,1); //DATA0 (000101xx+00)
WriteL3(volume,0); //音量降低
}
}
else
{
;
Uart_Printf("Error\n");//rUTXH0=0xff;
}
cmd = 0;
break;
}
}
} |