一、摘要
本篇博文實現了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 <stdio.h>
- #include <stdlib.h>
- #include <sys/alt_dma.h>
- #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 <stdio.h>
- #include <stdlib.h>
- #include <sys/alt_dma.h>
- #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 <stdio.h>
- #include <stdlib.h>
- #include <sys/alt_dma.h>
- #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寄存器才能清零。