一、摘要
本篇博文实现了DMA的3种传输方式。
二、实验平台
1、Quartus II9.0 + Nios II9.0
2、USB_Board
三、实验内容
1、存储器到存储器
这种情况下需要同时打开发送通道和接收通道,而且源地址和目标地址都是自增的。
tx = alt_dma_txchan_open("/dev/dma_0");//打开发送通道
dma_res = alt_dma_txchan_send(tx, tx_buf, 32, NULL, NULL); // tx_buf是源地址
rx = alt_dma_rxchan_open("/dev/dma_0");//打开接收通道
dma_res = alt_dma_rxchan_prepare(rx, rx_buf, 32, dma_done, NULL); // rx_buf是目标地址,dma_done()是DMA完成后被调用的回调函数。
2、存储器到外设
这种情况下只要打开发送通道,而且源地址是自增的,目标地址是固定的。
tx = alt_dma_txchan_open("/dev/dma_0"); // 打开发送通道
alt_dma_txchan_ioctl(tx, ALT_DMA_TX_ONLY_ON, (void *)dst_addr); // dst_addr是目标地址
dma_res = alt_dma_txchan_send(tx, tx_buf, 32, dma_done, NULL); // tx_buf是源地址
3、外设到存储器
这种情况下只要打开接收通道,而且源地址是固定的,目标地址是自增的。
rx = alt_dma_rxchan_open("/dev/dma_0"); // 打开接收通道
alt_dma_rxchan_ioctl(rx, ALT_DMA_RX_ONLY_ON, (void *)source_addr); // source_addr是源地址
dma_res = alt_dma_rxchan_prepare(rx, rx_buf, 32, dma_done, NULL); // rx_buf是目标地址
其中通过alt_dma_txchan_ioctl,alt_dma_rxchan_ioctl还可以设置每次发送和接收的字节数。
四、实验步骤
1、建立一个sopc系统
其中dma模块、uart模块、jtag_uart都是默认设置,有时添加设置(如dma的突发传输)可能实验就会出现异常。onchip_memery,设置为8位1024字节,sdram是一个添加的存储器接口模块,这个用于运行程序,也可以使用自己板上其他的存储器如sram、flash等。
2、建立原理图
3、Nios II 程序
依次在Nios II下建立3个工程,分别是:mem_to_mem、mem_to_uart和uart_to_mem
(1)第一个工程:存储器到存储器mem_to_mem
这里用dma将buff中的数据传输到mem_1中,通过观察dma工作前mem_1中的数据和工作后mem_1中的数据就可看出实验的结果。试验中使用了dma_tx_done标志,以便检查中断是否成功,因为有时数据传输正确了,中断上可能还有问题,而实际中高效率往往需要中断。
- #include
- #include
- #include
- #include "altera_avalon_uart_regs.h"
- #include "sys/alt_irq.h"
- #include "system.h"
- #include "altera_avalon_dma_regs.h"
- static volatile int tx_done = 0;
- static char buff[16];
- /*这里是在SDRAM中开辟了一个buff,也可以直接用FPGA内部的RAM
- 例如
- void* source = (void*) 0x01002000; //假设0x01002000为另外一个onchip-memory的地址
- */
- void* source = (void*) buff; /* 源地址*/
- void* dest = (void*) 0x01001000; /* 目标地址,从SOPC看到x01801000为onchip-memory的地址*/
- //DMA传输结束回调函数
- static void dma_done(void* handle, void* data)
- {
- IOWR_ALTERA_AVALON_DMA_STATUS(DMA_0_BASE,0x0);
- tx_done++;
- }
- int main (int argc, char* argv[], char* envp[])
- {
- int ret,i;
- alt_dma_txchan txchan;
- alt_dma_rxchan rxchan;
- //向buff写入16个递增数据
- for(i = 0;i < 16;i = i + 1)
- {
- IOWR_8DIRECT(source,i,i);
- }
- //打印buff中写入的16个数据
- for(i = 0;i < 16;i = i + 1)
- {
- printf("mem0[%4x] is %4x\n",i,IORD_8DIRECT(source,i));
- }
- //向ONCHIP_MEMORY2_1写入16个0
- for(i = 0;i < 16;i = i + 1)
- {
- IOWR_8DIRECT(dest,i,0);
- }
- //打印ONCHIP_MEMORY2_1中写入的16个0
- for(i = 0;i < 16;i = i + 1)
- {
- printf("mem1[%4x] is %4x\n",i,IORD_8DIRECT(dest,i));
- }
- // mem to mem
- //打开DMA发送通道,并设置为位传输模式,开始发送数据
- if ((txchan=alt_dma_txchan_open("/dev/dma_0"))!=NULL)
- {
- if((ret=alt_dma_txchan_ioctl(txchan,ALT_DMA_SET_MODE_16,NULL))>=0)
- {
- if((ret=alt_dma_txchan_send(txchan,source,16,NULL,NULL))<0)
- {
- printf("Fail to send,error %d\n",ret);
- }
- }
- else
- {
- printf("Fail to send,error %d\n",ret);
- }
- }
- else
- {
- printf("Fail to open DMA txchan\n");
- }
- //打开DMA接收通道,开始接收数据
- if((rxchan=alt_dma_rxchan_open("/dev/dma_0"))!=NULL)
- {
- if((ret=alt_dma_rxchan_prepare(rxchan,dest,16,dma_done,NULL))<0)
- {
- printf("Fail to receive ,error %d\n",ret);
- }
- }
- else
- {
- printf("Fail to open dma rxchan\n");
- }
- //等待传输结束
- while (!tx_done);
- printf ("Transfer successful!\n");
- //打印ONCHIP_MEMORY2_1中的数据
- for(i = 0;i < 16;i = i + 1)
- {
- ret=IORD_8DIRECT(dest,i);
- printf("mem1[%4x] is %4x\n",i,ret);
- }
- while(1);
- return 0;
- }
(2)第二个工程:存储器到uart 232
这里将mem_1中的数据发送到串口,在电脑上通过串口调试器观察接收到的数据。有的调试器可能有字符显示模式和16进制模式,注意发送的数据和字符的对应关系,注意设置模式和波特率等。串口调试器上观察到以前写入mem_1中的数据,以及在nios调试软件中打印出Transfer success即可。nios调试软件中stdin、stdout都设置的是jtag_uart。
- #include
- #include
- #include
- #include "altera_avalon_uart_regs.h"
- #include "sys/alt_irq.h"
- #include "system.h"
- #include "altera_avalon_dma_regs.h"
- static volatile int tx_done = 0;
- static char buff[16];
- void* source = (void*) buff; /* 源地址*/
- void* dest = (void*) 0x01001000; /* 目标地址,从SOPC看到x01801000为onchip-memory的地址*/
- //DMA传输结束回调函数
- static void dma_done(void* handle, void* data)
- {
- IOWR_ALTERA_AVALON_DMA_STATUS(DMA_0_BASE,0x0);
- tx_done++;
- }
- int main (int argc, char* argv[], char* envp[])
- {
- int ret,i;
- alt_dma_txchan txchan;
- alt_dma_rxchan rxchan;
- //向ONCHIP_MEMORY2_1写入个
- for(i = 0;i < 16;i = i + 1)
- {
- IOWR_8DIRECT(dest,i,0);
- }
- //打印ONCHIP_MEMORY2_1中写入的个
- for(i = 0;i < 16;i = i + 1)
- {
- printf("mem1[%4x] is %4x\n",i,IORD_8DIRECT(dest,i));
- }
- //mem to uart
- if((txchan=alt_dma_txchan_open(DMA_0_NAME))!=NULL)
- {
- if((ret=alt_dma_txchan_ioctl(txchan,ALT_DMA_SET_MODE_8,NULL))>=0)
- {
- if((ret=alt_dma_txchan_ioctl(txchan,ALT_DMA_TX_ONLY_ON,UART_0_BASE+2))>=0)
- {
- if((ret=alt_dma_txchan_send(txchan,source,16,dma_done,NULL))<0)
- {
- printf("Fail to send,error %d\n",ret);
- }
- }
- else
- {
- printf("Fail to set tx only,error %d\n",ret);
- }
- }
- else
- {
- printf("Fail to set TX MODE 8 ,error %d\n",ret);
- }
- }
- else
- {
- printf("Fail to open DMA txchan\n");
- }
- //等待传输结束
- while (!tx_done);
- printf ("Transfer successful!\n");
- while(1);
- return 0;
- }
(3)第三个工程:uart 232 到存储器
这里dma从uart 232接收数据并保存到mem_1中。在电脑上通过串口调试器发送数据到串口,当发送的数据达到程序中设定的长度后,会打印Transfer success,并打印出mem_1中接收到数据,通过与初始化的数据比较即可看出结果,这里nios调试软件中stdin设置为uart_232 ,stdout设置为jtag_uart,也可设置为uart_232。
- #include
- #include
- #include
- #include "altera_avalon_uart_regs.h"
- #include "sys/alt_irq.h"
- #include "system.h"
- #include "altera_avalon_dma_regs.h"
- static volatile int tx_done = 0;
- static char buff[16];
- void* source = (void*) buff; /* 源地址*/
- void* dest = (void*) 0x01001000; /* 目标地址,从SOPC看到x01801000为onchip-memory的地址*/
- //DMA传输结束回调函数
- static void dma_done(void* handle, void* data)
- {
- IOWR_ALTERA_AVALON_DMA_STATUS(DMA_0_BASE,0x0);
- tx_done++;
- }
- int main (int argc, char* argv[], char* envp[])
- {
- int ret,i;
- alt_dma_txchan txchan;
- alt_dma_rxchan rxchan;
- //向ONCHIP_MEMORY2_1写入个
- for(i = 0;i < 16;i = i + 1)
- {
- IOWR_8DIRECT(dest,i,0);
- }
- //打印ONCHIP_MEMORY2_1中写入的个
- for(i = 0;i < 16;i = i + 1)
- {
- printf("mem1[%4x] is %4x\n",i,IORD_8DIRECT(dest,i));
- }
- IOWR_ALTERA_AVALON_UART_CONTROL(UART_0_BASE,0x0); //close uart irq
- //uart to mem
- if((rxchan=alt_dma_rxchan_open(DMA_0_NAME))!=NULL)
- {
- if((ret=alt_dma_rxchan_ioctl(rxchan,ALT_DMA_SET_MODE_8,NULL))>=0)
- {
- if((ret=alt_dma_rxchan_ioctl(rxchan,ALT_DMA_RX_ONLY_ON,UART_0_BASE))>=0)
- {
- if((ret=alt_dma_rxchan_prepare(rxchan,dest,16,dma_done,NULL))<0)
- {
- printf("Fail to receive ,error %d\n",ret);
- }
- }
- else
- {
- printf("Fail to set rx only,error %d\n",ret);
- }
- }
- else
- {
- printf("Fail to set mod 8,error %d\n",ret);
- }
- }
- else
- {
- printf("Fail to open dma rxchan\n");
- }
- //等待传输结束
- while (!tx_done);
- printf ("Transfer successful!\n");
- //打印ONCHIP_MEMORY2_1中的数据
- for(i = 0;i < 16;i = i + 1)
- {
- ret=IORD_8DIRECT(dest,i);
- printf("mem1[%4x] is %4x\n",i,ret);
- }
- while(1);
- return 0;
- }
五、NIOS II程序的几点注意事项
1、注意一
IOWR_ALTERA_AVALON_UART_CONTROL(UART_0_BASE,0x0); //close uart irq
这一句在uart to mem中需要用到,要不然不能顺利调用中断程序,当然如果采用的是一个dma接收,一个dma发送,不使用中断模式,就不会遇到这样的问题,很多dma uart的接收程序没通过的就是没做这一步处理。
2、注意二
if((ret=alt_dma_txchan_ioctl(txchan,ALT_DMA_TX_ONLY_ON,UART_0_BASE+2))>=0)
这句中UART_0_BASE+2容易被设置错,当做存储器到uart的时候,使用alt_dma_txchan_ioctl设置发送地址时要设置为UART_0_BASE+2,因为寄存器为16位,不是UART_0_BASE或者UART_0_BASE+4或者IOADDR_ALTERA_AVALON_UART_TXDATA(UART_0_BASE)。
3、注意三
dma中断程序中最好添加 IOWR_ALTERA_AVALON_DMA_STATUS(DMA_0_BASE, 0x0);语句,这是用于清除中断标志的,避免反复中断。因为寄存器中的done位不能自动清零,需要写0到status寄存器才能清零。