最近忙于驱动这块,于是我自己整理了有关S3C2410的DMA的基本编程及DMA的相关寄存器操作。
一.DMA访问的原理
1.DMA(Driect Memory Access)访问的概述
当系统内存想要与高速外设或者内存的不同区域之间进行大数据的快速传送时,查询和中断这两种方式不能满足要求:DMA就是为解决这样的问题提出来的。
(中断方式较之查询方式来说,可以提高CPU的利用率和保证对外设响应的实时性,但对于高速外设,中断方式不能满足数据传输速度的要求。因为,中断方式下,每次中断均需保存断点和现场;中断返回时,要恢复断点和现场。同时,进入中断和从中断返回均使CPU指令队列被清除。这些原因,使得中断方式难以满足高速外设对传输速度的要求。)
采用DMA方式,在一段时间内,由DMA控制器取代CPU,获得总线控制权,从而实现内存与外设或者内存不同区域间的大量数据快速传递。
2.DMA的工作过程
1.当外设准备就绪,希望进行DMA传送时,向DMA控制器(DMAC)发出DMA请求信号(DREQ)。DMAC收到此信号后,向CPU发出总线请求信号(HOLD)。
2.CPU在完成当前总线操作后立即对DMA请求信号做出响应:先放弃对总线的控制(包括控制总线、数据总线、地址总线),然后将有效的HLDA信号加到DMAC上。此时,CPU便放弃对总线的控制,DMAC获得总线的控制权。
3.DMAC获得总线的控制权后,向地址总线发出地址信号,指出传送过程需使用的内存地址(DMAC内部设有“地址寄存器”,在DMA操作过程中,每传送一字节数据,DMAC自动修改地址寄存器的值,以指向下一个内存地址)。同时,向外设发出DMA应答信号(DACK),实现该外设与内存之间进行DMA传送。
4.在DMA传送期间,DMAC发出内存和外设的读/写信号。
5.为了决定数据块传输的字节数,在DMAC内部必须有一个“字节计数器”。在开始时,由软件设置数据块的长度,在DMA传送过程中,每传送一字节,字节计数器减1,减为0时,该次DMA传输结束。
6.DMA过程结束时,DMAC向CPU发出结束信号(撤消HOLD请求),将总线控制权交还CPU。
二.DMA传送的方式
DMA传送的方式分为三种:I/O接口到存储器、存储器到I/O接口、存储器到存储器
1.I/O接口到存储器方式
当想要由I/O接口到存储器的数据传送时,来自I/O接口的数据利用DMAC送出控制信号,将数据输送到数据总线D0-D7上,同时DMAC送出存储器单元地址及控制信号,将存于D0-D7上的数据写入选中的存储单元中。这样就完成了I/O接口到存储器的一个字节的传送。同时DMAC修改“地址寄存器”和“字节计数器”的内容。
2.存储器到I/O接口方式
与上面情况类似,在进行传送时,DMAC送出存储器地址和控制信号,将选中的存储器单元的内容读入数据总线的D0-D7上,然后,DMAC送出控制信号,将数据写到指定的端口中去,再DMAC修改“地址寄存器”和“字节计数器”的内容。
3.存储器到存储器方式
这种方式的DMA数据传送是用“数据块”方式传送(连续进行多个字节的传输,只有当“字节计数器”减为0,才完成一次DMA传送)。首先,送出存储器源的地址和控制信号,将选中内存单元的数据暂存,然后修改“地址寄存器”和“字节计数器”的值,接着,送出存储器目标的地址和控制信号,将暂存的数据通过数据总线写入到存储器的目标区域中,最后修改“地址寄存器”和“字节计数器”的内容,当“字节计数器”的值减少到零时可结束一次DMA。
三.S3C2410的DMA访问相关操作
1.(本文采用S3C2410开发板)S3C2410支持4通道DMA控制器,在系统总线和外围总线之间。每个通道都能实现快速数据传送。每个通道能处理下面4种情况:
1.源和目标器件都在系统总线AHB上。
2.源器件在系统总线AHB上,目标器件在外围总线APB上。
3.源器件在外围总线APB上,目标器件在系统总线AHB上。
4.源和目标器件都在外围总线APB上。
DMA操作可以通过软件或者硬件来初始化。如果DCON寄存器选择采用硬件(H/W)DMA请求模式,那么DMA控制器可以从对应通道的DMA请求源中选择一个。如果DCON寄存器选择采用软件(S/W)DMA请求,那么这些DMA请求源将没有任何意义。DMA请求源如下表所列。
nXDREQ0和nXDREQ1代表两个外部源,I2SSDO和I2SSDI分别代表IIS的传输和发送。
2.DMA工作模式
DMA的工作模式共有两种:单一服务模式和整体服务模式。
在单一服务模式下,一次DMA请求完成一项原子操作(操作不可间断) 。
在整体服务模式下,一次DMA请求完成一批原子操作,直到CURR_TC等于0,表示完成一次整体服务。
3.DMA状态机描述
DMA可以有3种FSM(Finite State Machine,有限状态机)进行操作。
状态1:初始状态,DMA等待一个DMA请求,若请求到达,进入状态2。此阶段,DMA ACK和INT REQ 为0。
状态2:在此状态,DMA ACK 变为1,计数器CURR_TC的值从DCONn[19:0 ]装载。DMA ACK保持1,直到它被清0。
状态3:在此状态,对DMA进行原子操作的sub-FSM(子状态机)进行初始化。sub-FSM 从源地址读取数据后将数据写入目标地址。对于这种操作方式,数据大小和传输大小应给予考虑。在整体服务方式中,这种操作重复直到计数器CURR_TC变为0,然而在单一模式中只进行一次。当子FSM完成每个原子操作,主FSM倒计CURR_TC。另外,当CURR_TC为0 和中断设置DCONn[29]为1时,主FSM发出INT REQ(中断请求信号),假如以下任一种情况发生,要清除DMA ACK 。
(1)在单一模式下,主FSM的3种状态执行完后就停止,并等待下一个DMA请求。对于每个原子操作,DMA ACK先置1,完成后清0。
(2)在整体服务模式下,主FSM一直在状态3等待,直到CURR_TC变为0。因此,DMA ACK在整个传送过程中置1,CURR_TC变为0时则清0。
4.DMA基本时序
DMA服务意味着在DMA操作中执行一次读写周期,形成一个DMA操作。下图为S3C2410的DMA操作的基本时序。
nXDREQ请求生效并经过2CLK周期同步后,nXDACK响应并开始生效,但至少还要经过3CLK的周期延迟,DMA控制器才可获得总线的控制权,并开始数据传输。当DMA操作完成后,XnXDACK被设无效。
5.请求和答应的协议有两种:请求(Demand)和握手(Handshake)模式。(区别:在一个传输末尾,DMA检测两次同步的XnXDREQ的状态)
请求模式:如果XnXDREQ保持有效,则下一个传输开始马上开始,否则它会一直等到XnXDREQ有效。
握手模式:如果XnXDREQ无效,则DMA在两个同步周期内将XnXDACK设无效,否则它一直等到XnXDREQ无效。
6.传输大小
DMA的一次原子操作中,可以是Unit模式(传输一个Data Size)和brust模式(传输4个Data Size)。在DCON[28]中设置。
7.Data Size:指一次原子操作的数据位宽,可以为8,16,32。在DCON[21:20]中设置。
四.DMA控制器
S3C2410每个DMA通道有9个控制寄存器,4个通道,共有36个寄存器。每个DMA通道有6个用于控制DMA传输,3个用于监控DMA控制器的状态。要进行DMA操作,首先需要对这些寄存器进行正确配置。下面介绍相关寄存器。(由于图太多,这里没有贴出来,大家可以在三星的S3C2401 Data Sheet里面找到对应的表)
1、DMA初始化数据源(DISRC)寄存器组:分别对应着4个相关寄存器,主要用于初始化传输的数据源的基地址。
2、DMA初始化数据源控制(DISRCC)寄存器:主要用于选择数据源的位置(在APB还是在AHB上)以及决定地址递增还是固定。
3、DMA初始化目标(DIDST)寄存器:主要用于初始化传输目标开始地址。
4、DMA初始化目标控制(DIDSTC)寄存器:主要用于选择目标的位置(在APB还是在AHB上)以及决定地址递增还是固定。
5、DMA控制(DCON)寄存器:主要功能选择请求模式或者握手模式、选择DREQ/DACK同步、使能/禁止CURR_TC中断设置、选择Data Size、选择单一服务或整体服务模式、选择DMA请求源及初始化传输计数器等。
6、DMA状态(DSTAT)寄存器:主要用于读取DMA控制器的状态及传输计数器的当前值。
7、DMA当前源(DCSRC)寄存器:主要用于读取DMA控制器当前源地址的值。
8、DMA当前目标(DCDST)寄存器:主要用于读取DMA控制器当前目标地址的值。
9、中断屏蔽触发(DMASKTRIG)寄存器:用于屏蔽PCI设备的中断请求。
四.DMA控制器
S3C2410每个DMA通道有9个控制寄存器,4个通道,共有36个寄存器。每个DMA通道有6个用于控制DMA传输,3个用于监控DMA控制器的状态。要进行DMA操作,首先需要对这些寄存器进行正确配置。下面介绍相关寄存器。(由于图太多,这里没有贴出来,大家可以在三星的S3C2401 Data Sheet里面找到对应的表)
1、DMA初始化数据源(DISRC)寄存器组:分别对应着4个相关寄存器,主要用于初始化传输的数据源的基地址。
2、DMA初始化数据源控制(DISRCC)寄存器:主要用于选择数据源的位置(在APB还是在AHB上)以及决定地址递增还是固定。
3、DMA初始化目标(DIDST)寄存器:主要用于初始化传输目标开始地址。
4、DMA初始化目标控制(DIDSTC)寄存器:主要用于选择目标的位置(在APB还是在AHB上)以及决定地址递增还是固定。
5、DMA控制(DCON)寄存器:主要功能选择请求模式或者握手模式、选择DREQ/DACK同步、使能/禁止CURR_TC中断设置、选择Data Size、选择单一服务或整体服务模式、选择DMA请求源及初始化传输计数器等。
6、DMA状态(DSTAT)寄存器:主要用于读取DMA控制器的状态及传输计数器的当前值。
7、DMA当前源(DCSRC)寄存器:主要用于读取DMA控制器当前源地址的值。
8、DMA当前目标(DCDST)寄存器:主要用于读取DMA控制器当前目标地址的值。
9、中断屏蔽触发(DMASKTRIG)寄存器:用于屏蔽PCI设备的中断请求。
==================================================================================
详细设计S3C2410数据手册里可以找到有关DMA的寄存器的配置信息。根据实验目的,要想让实现内存间DMA传输,步骤如下:
1.首先当然是选择DMA通道;
2.设置DMA数据源地址(对DISRC操作);
3.设置DMA数据源地址控制寄存器(对DISRCC操作);
4.设置DMA目标地址(对DIDST操作);
5.设置DMA数据源地址控制寄存器(对DIDSTC操作);
6.初始化DMA控制寄存器(对DCON操作);
7.打开DMA通道(DMASKTRIG操作)。
具体寄存器设置如下(假设选择DMA0通道):
rDISRC0=srcAddr; //设置DMA数据源地址
rDISRCC0=(0<<1) | (0<<0); //地址递增,数据源地址在AHB上
rDIDST0=dstAdr;
rDIDSTC0=(0<<1) | (0<<0);
rDCON0=tc |(1UL<<31) | (1<<30) | (1<<29) | (burst<<28) | (1<<27) | (0<<23) | (1<<22) | (dsz<<20) | (tc);
//定义DCON寄存器HS , AHB ,TC interrupt , whole, SW request mode , relaod off
rDMASKTRIG0=(1<<1)|1; //DMA on(打开DMA通道), SW_TRIG
编码设计:
1.DMA传输函数void DMA_M2M(int ch, int srcAddr, int dstAddr, int tc, int dsz, int burst)
2.DMA测试函数void Test_DMA(void)
3.测试主函数void Main(void)
void DMA_M2M(int ch,int srcAddr,int dstAddr,int tc,int dsz,int burst)
{
int i,time;
volatile U32 memSum0=0,memSum1=0;
DMA *pDMA;
int length=tc*(burst ? 4:1)*((dsz==0)+(dsz==1)*2+(dsz==2)*4);
Uart_Printf("[DMA%d MEM2MEM Test]/n",ch);
switch(ch)
{
case 0:
pISR_DMA0 =(int)Dma0Done;//DMA0的中断服务程序
rINTMSK &=~(BIT_DMA0); //允许DMA0中断
pDMA = (void *)0x4b000000;
break;
case 1:
pISR_DMA1 = (int)Dma1Done;
rINTMSK &= ~(BIT_DMA1);
pDMA = (void *)0x4b000040;break;
case 2:
pISR_DMA2 = (int)Dma2Done;
rINTMSK &= ~(BIT_DMA2);
pDMA = (void *)0x4b000080;break;
case 3:
pISR_DMA3 = (int)Dma3Done;
rINTMSK &= ~(BIT_DMA3);
pDMA = (void *)0x4b0000c0; break;
}
Uart_Printf("DMA%d %8xh->%8xh,size=%xh(tc=%xh),dsz=%d,burst=%d/n",ch,srcAddr,dstAddr,length,tc,dsz,burst);
Uart_Printf("Initialize the src./n");
for(i=srcAddr;i<(srcAddr+length);i+=4)
{
*((U32 *)i)=i^0x55aa5aa5;
memSum0+=i^0x55aa5aa5;
}
Uart_Printf("DMA%d start/n",ch);
dmaDone=0;
rDISRC0=srcAddr;
rDISRCC0=(0<<1)|(0<<0); // inc,AHB源来自于AHB
rDIDST0=dstAddr;
rDIDSTC0=(0<<1)|(0<<0); // inc,AHB目的为AHB
rDCON0=tc|(1UL<<31)|(1<<30)|(1<<29)|(burst<<28)|(1<<27)|/
(0<<23)|(1<<22)|(dsz<<20)|(tc);
//HS,AHB,TC interrupt,whole, SW request mode,relaod off
// rINTMSK=~(BIT_DMA0);
rDMASKTRIG0=(1<<1)|1; //DMA on, SW_TRIG
// Timer_Start(3);//128us resolution
while(dmaDone==0);
// time=Timer_Stop();
printf("DMA%d end/n", ch);
// Uart_Printf("DMA transfer done. time=%f, %fMB/S/n",(float)time/ONESEC3,
// length/((float)time/ONESEC3)/1000000.);
rINTMSK |= (BIT_DMA0 | BIT_DMA1 | BIT_DMA2 | BIT_DMA3);
for(i=dstAddr;i<dstAddr+length;i+=4)
{
memSum1+=*((U32 *)i)=i^0x55aa5aa5;
}
Uart_Printf("memSum0=%x,memSum1=%x/n",memSum0,memSum1);
if(memSum0==memSum1)
Uart_Printf("DMA test result--------O.K./n");
else
Uart_Printf("DMA test result--------ERROR!!!/n");
}
void Test_DMA(void)
{
//DMA Ch 0
DMA_M2M(0,0x31020000,0x31020000+0x80000,0x80000,0,0); //byte,single
}
void Main(void)
{
int i;
U8 key;
U32 mpll_val = 0 ;
Port_Init(); //端口初始化
Isr_Init(); //中断初始化
Uart_Init(0,115200); //串口初始化 波特率为115200
Uart_Select(0); //选者串口0
ChangeClockDivider(1,1); // 1:2:4
ChangeMPllValue(0xa1,0x3,0x1); // FCLK=202.8MHz
i=0;
Uart_Printf("/nBegin to start UART test,OK? (Y/N)/n");
key = Uart_Getch();
if(key=='y'||key=='Y')
Test_DMA();
else
Uart_Printf("/nOh! You quit the test!/n");
Uart_Printf("/n====== UART Test End ======/n");
}