S03_CH08_DMA_LWIP以太網傳輸


S03_CH08_DMA_LWIP以太網傳輸

8.1概述

本例程詳細創建過程和本季課程第一課《S03_CH01_AXI_DMA_LOOP 環路測試》非常類似,因此如果讀者不清楚如何創建工程,請仔細閱讀本季第一課時。

本例程的基本原理如下。

PS通過AXI GPIO IP核啟動PL不間斷循環構造16bit位寬的0~1023的數據,利用AXI DMA IP核,通過PS的Slave AXI GP接口傳輸至PS DDR3的乒乓緩存中。PL每發完一次0~1023,AXI DMA IP核便會產生一個中斷信號,PS得到中斷信號后將DDR3緩存的數據通過乒乓操作的方式由TCP協議發送至PC機。

8.2搭建硬件系統

8.2.1系統構架

wps2B39.tmp

這個系統實際上就是在前面章節DMA的基礎上去掉FPGA讀DMA通道,只有FPGA往DMA寫輸入數據,當DMA接收中斷產生后,在通過LWIP協議,把數據通過網口發送出去。網口是接在PS的ARM端口的因此,ARM部分也是要把網口配置好。如下圖所示。

8.2.1 啟用HP接口

雙擊ZYNQ的IP之后啟動HP接口,這里只要用到1個HP接口通道

wps2B4A.tmp

8.2.2啟用PL到PS的中斷資源

wps2B4B.tmp

8.2.3啟動PS部分的以太網接口

wps2B4C.tmp

8.2.4 時鍾的設置

將FCLK_CLK0和FCLK_CLK1均設為100Mhz,其中CLK1為PL構造數據部分邏輯的時鍾源,在實際應用中可自由調節時鍾頻率,故與CLK0分開使用。設置如下圖所示。

wps2B4D.tmp

8.2.5 DMA IP 配置

由於只用到了寫通道,因次,只要把勾選寫DMA通道既可以,如下圖所示:

wps2B5E.tmp

8.2.6 GPIO的配置

雙擊axi_gpio_0。設置如下圖所示

wps2B5F.tmp

8.2.7配置axi_ data_fifo _0

雙擊axis_data_fifo_0,設置如下圖所示。fifo在本例程中作為axi_dma_0的S_AXIS_S2MM接口所在的FCLK0時鍾域與外部數據生成邏輯stream接口所在的FCLK1時鍾域之間的轉換媒介。

wps2B60.tmp

8.2.8設置S_AXIS接口

wps2B70.tmp

雙擊S_AXIS端口,進行如下圖設置

wps2B71.tmp

8.2.9地址空間映射

打開Address Editor,設置如下圖所示

wps2B72.tmp

8.3 FPGA的發送代碼

表8-3-1

always@(posedge FCLK_CLK1)

begin

     if(!peripheral_aresetn) begin //系統復位

         S_AXIS_tvalid <= 1'b0;

         S_AXIS_tdata <= 32'd0;

         S_AXIS_tlast <= 1'b0;

         state <=0;

     end

     else begin

        case(state) //狀態機

          0: begin

              if(gpio_tri_o_0&& S_AXIS_tready) begin //當FIFO非滿的時候

                 S_AXIS_tvalid <= 1'b1;//設置寫FIFO數據有效標志為1

                 state <= 1;//轉入狀態1

              end

              else begin

                 S_AXIS_tvalid <= 1'b0;

                 state <= 0;

              end

            end

          1:begin

               if(S_AXIS_tready) begin //當FIFO非滿

                   S_AXIS_tdata <= S_AXIS_tdata + 1'b1;

                   if(S_AXIS_tdata == 16'd1022) begin //從0-1022一共寫入1023個字節

                      S_AXIS_tlast <= 1'b1;//發送最后一個數據

                      state <= 2;

                   end

                   else begin

                      S_AXIS_tlast <= 1'b0;

                      state <= 1;

                   end

               end

               else begin

                  S_AXIS_tdata <= S_AXIS_tdata;                   

                  state <= 1;

               end

            end       

          2:begin

               if(!S_AXIS_tready) begin //如果FIFO滿,等待

                  S_AXIS_tvalid <= 1'b1;

                  S_AXIS_tlast <= 1'b1;

                  S_AXIS_tdata <= S_AXIS_tdata;

                  state <= 2;

               end

               else begin //寫入結束

                  S_AXIS_tvalid <= 1'b0;

                  S_AXIS_tlast <= 1'b0;

                  S_AXIS_tdata <= 16'd0;

                  state <= 0;

               end

            end

         default: state <=0;

         endcase

     end              

end

發送代碼和本季第一課程的代碼一樣,每次發送1024X16bit的數據到通過DMA到DDR內存。代碼比較簡單,如果verilog語法基礎不好的,無法對發送時序精確把我的FPGA初學者可以多思考思考,如果還是無法理解可以找我們FPGA技術支持。

8.4 PS部分BSP設置

8.4.1 SDK工程BSP設置

進入sdk后,新建空工程,命名為AXI_DMA_PL_PS_Test,同時創建相應的bsp。

修改AXI_DMA_PL_PS_Test_bsp的設置,使能lwip 1.4.1函數庫。如下圖所示。

wps2B83.tmp

8.4.2 lwip函數庫設置

本例程使用RAW API,即函數調用不依賴操作系統。傳輸效率也比SOCKET API高,(具體可參考xapp1026)。將use_axieth_on_zynq和use_emaclite_on_zynq設為0。如下圖所示。

wps2B84.tmp

修改lwip_memory_options設置,將mem_size,memp_n_pbuf,mem_n_tcp_pcb,memp_n_tcp_seg這4個參數值設大,這樣會提高TCP傳輸效率。如下圖所示。

wps2B85.tmp

修改pbuf_options設置,將pbuf_pool_size設大,增加可用的pbuf數量,這樣同樣會提高TCP傳輸效率。如下圖所示。

wps2B95.tmp

修改tcp_options設置,將tcp_snd_buf,tcp_wnd參數設大,這樣同樣會提高TCP傳輸效率。如下圖所示。

wps2B96.tmp

修改temac_adapter_options設置,將n_rx_descriptors和n_tx_descriptors參數設大。這樣可以提高zynq內部emac dma的數據遷移效率,同樣能提高TCP傳輸效率。如下圖所示。

wps2B97.tmp

其余選項的參數默認即可,不用修改。點擊OK,重建bsp。

8.5 PS部分程序分析

8.5.1 main.c分析

main函數的主要流程為:

1):初始化並配置PL側的AXI GPIO

2):初始化並配置PL側的AXI DMA

3):初始化並配置PS的中斷控制器

4):初始化lwip協議棧和PS的以太網控制器

5):配置TCP傳輸所需的相關參數,並與服務器建立TCP連接

6):通過AXI GPIO啟動PL進行數據生成和傳輸

7):通過AXI DMA接收PL傳輸的數據,通過TCP發送至PC機,並不斷循環該過程。

表8-5-1

/*

*

* www.osrc.cn

* www.milinker.com

* copyright by nan jin mi lian dian zi www.osrc.cn

* axi dma test

*

*/

#include "dma_intr.h"

#include "timer_intr.h"

#include "sys_intr.h"

#include "xgpio.h"

#include "OLED.h"

#include "lwip/err.h"

#include "lwip/tcp.h"

#include "lwipopts.h"

#include "netif/xadapter.h"

#include "lwipopts.h"

static  XScuGic Intc; //GIC

static  XScuTimer Timer;//timer

XAxiDma AxiDma;

u16 *RxBufferPtr[2];  /* ping pong buffers*/

volatile u32 RX_success;

volatile u32 TX_success;

volatile u32 RX_ready=1;

volatile u32 TX_ready=1;

#define TIMER_LOAD_VALUE    XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 8 //0.25S

extern void send_received_data(void);

extern unsigned tcp_client_connected;

char oled_str[17]="";

static XGpio Gpio;

#define AXI_GPIO_DEV_ID         XPAR_AXI_GPIO_0_DEVICE_ID

int init_intr_sys(void)

{

DMA_Intr_Init(&AxiDma,0);//initial interrupt system

Timer_init(&Timer,TIMER_LOAD_VALUE,0);

Init_Intr_System(&Intc); // initial DMA interrupt system

Setup_Intr_Exception(&Intc);

DMA_Setup_Intr_System(&Intc,&AxiDma,0,RX_INTR_ID);//setup dma interrpt system

Timer_Setup_Intr_System(&Intc,&Timer,TIMER_IRPT_INTR);

DMA_Intr_Enable(&Intc,&AxiDma);

}

int main(void)

{

int Status;

struct netif *netif, server_netif;

struct ip_addr ipaddr, netmask, gw;

/* the mac address of the board. this should be unique per board */

unsigned char mac_ethernet_address[] = { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };

/* Initialize the ping pong buffers for the data received from axidma */

RxBufferPtr[0] = (u16 *)RX_BUFFER0_BASE;

RxBufferPtr[1] = (u16 *)RX_BUFFER1_BASE;

XGpio_Initialize(&Gpio, AXI_GPIO_DEV_ID);

XGpio_SetDataDirection(&Gpio, 1, 0);

init_intr_sys();

TcpTmrFlag = 0;

netif = &server_netif;

IP4_ADDR(&ipaddr,  192, 168,   1,  10);

IP4_ADDR(&netmask, 255, 255, 255,  0);

IP4_ADDR(&gw,      192, 168,   1,  1);

/*lwip library init*/

lwip_init();

/* Add network interface to the netif_list, and set it as default */

if (!xemac_add(netif, &ipaddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR)) {

xil_printf("Error adding N/W interface\r\n");

return -1;

}

netif_set_default(netif);

/* specify that the network if is up */

netif_set_up(netif);

/* initialize tcp pcb */

tcp_send_init();

XGpio_DiscreteWrite(&Gpio, 1, 1);

oled_fresh_en();// enable oled

Timer_start(&Timer);

while (1)

{

/* call tcp timer every 250ms */

if(TcpTmrFlag)

{

tcp_tmr();

TcpTmrFlag = 0;

}

/*receive input packet from emac*/

xemacif_input(netif);//將MAC隊列里的packets傳輸到你的LwIP/IP stack里

/* if connected to the server, start receive data from PL through axidma, then transmit the data to the PC software by TCP*/

if(tcp_client_connected)

send_received_data();

}

return 0;

}

8.5.2 AXI DMA數據傳輸過程

例程中axi dma采用了simple transfer方式,通過XAxiDma_SimpleTransfer函數完成。每次dma傳輸都需要PS主動發起,PS通過AXI總線配置PL側axi dma內部寄存器,發起一次dma傳輸。dma傳輸發起后,axi dma開始通過S_AXIS_S2MM接口接收數據,當其中的tlast信號被拉高,則代表當次傳輸所需要的數據發送完畢,當該次dma傳輸結束,axi dma通過s2mm_introut產生中斷信號,觸發PS中斷控制器產生中斷,PS通過中斷服務函數Dma_RxIsr清除axi dma的中斷狀態,在DM中斷函數中,拉高dma完成指示信號packet_trans_done,一次完整的simple transfer的dma傳輸結束。下表為dma中斷接收函數,接收來自PL的中斷信號,並且設置packet_trans_done。

表:8-5-2-1 DMA_RxIntrHandler DMA中斷接收函數

/*****************************************************************************/

/*

*

* This is the DMA RX interrupt handler function

*

* It gets the interrupt status from the hardware, acknowledges it, and if any

* error happens, it resets the hardware. Otherwise, if a completion interrupt

* is present, then it sets the RxDone flag.

*

* @param Callback is a pointer to RX channel of the DMA engine.

*

* @return None.

*

* @note None.

*

******************************************************************************/

static void DMA_RxIntrHandler(void *Callback)

{

u32 IrqStatus;

int TimeOut;

XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

/* Read pending interrupts */

IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);

/* Acknowledge pending interrupts */

XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);

/*

* If no interrupt is asserted, we do not do anything

*/

if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {

return;

}

/*

* If error interrupt is asserted, raise error flag, reset the

* hardware to recover from the error, and return with no further

* processing.

*/

if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {

xil_printf("rx error! \r\n");

return;

}

/*

* If completion interrupt is asserted, then set RxDone flag

*/

if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

if(packet_trans_done)

xil_printf("last transmission has not finished!\r\n");

else

/*set the axidma done flag*/

    packet_trans_done = 1;

}

}

PS的dma數據接收采用了乒乓操作的模式,兩個緩沖區交替進行數據接收。

需要注意的是, XAxiDma_SimpleTransfer函數中Length,以字節為單位,每次發起dma時,所設置的Length的值必須大於或等於PL實際傳輸的數據長度,否則會出現錯誤。本例程中設置的長度為2048字節。

first_trans_start是為了進行第一次先進行一次DMA中斷傳輸,這樣完成后設置first_trans_start為0。以后每次完成網絡傳輸后,再啟動DMA接受。

TCP數據包的發送主要依賴於tcp_write和tcp_output兩個函數,tcp_write將所需要發送的數據寫入tcp發送緩沖區等待發送,tcp_output函數則將緩存區內數據包發送出去。在發送TCP數據包時,這兩個函數往往要同時配合使用。

收發送函數的具體源碼如下表。

8-5-2:send_received_data() 發送函數源碼

void send_received_data()

{

#if __arm__

int copy = 3;

#else

int copy = 0;

#endif

err_t err;

int Status;

struct tcp_pcb *tpcb = connected_pcb;

/*initial the first axdma transmission, only excuse once*/

if(!first_trans_start)

{

Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)RxBufferPtr[0],

(u32)(PAKET_LENGTH), XAXIDMA_DEVICE_TO_DMA);

if (Status != XST_SUCCESS)

{

xil_printf("axi dma failed! 0 %d\r\n", Status);

return;

}

/*set the flag, so this part of code will not excuse again*/

first_trans_start = 1;

}

/*if the last axidma transmission is done, the interrupt triggered, then start TCP transmission*/

if(packet_trans_done)

{

if (!connected_pcb)

return;

/* if tcp send buffer has enough space to hold the data we want to transmit from PL, then start tcp transmission*/

if (tcp_sndbuf(tpcb) > SEND_SIZE)

{

/*transmit received data through TCP*/

err = tcp_write(tpcb, RxBufferPtr[packet_index & 1], SEND_SIZE, copy);

if (err != ERR_OK) {

xil_printf("txperf: Error on tcp_write: %d\r\n", err);

connected_pcb = NULL;

return;

}

err = tcp_output(tpcb);

if (err != ERR_OK) {

xil_printf("txperf: Error on tcp_output: %d\r\n",err);

return;

}

packet_index++;

/*clear the axidma done flag*/

packet_trans_done = 0;

/*initial the other axidma transmission when the current transmission is done*/

Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)RxBufferPtr[(packet_index + 1)&1],

(u32)(PAKET_LENGTH), XAXIDMA_DEVICE_TO_DMA);

if (Status != XST_SUCCESS)

{

xil_printf("axi dma %d failed! %d \r\n", (packet_index + 1), Status);

return;

}

}

}

}

3.3 TCP發送流程

3.3.1 TCP連接建立

在本例程中,zynq作為客戶端,PC作為服務器。由zynq向PC主動發起TCP連接請求,通過tcp_connect函數便可以完成這個過程。該函數的參數包含了一個回調函數指針tcp_connected_fn,該回調函數將在TCP連接請求三次握手完成后被自動調用。該回調函數被調用時代表客戶端和服務器之間的TCP連接建立完成。在本例程中,該回調函數被定義為tcp_connected_callback,在該函數中,拉高連接建立完成信號tcp_client_connected,並通過tcp_sent函數配置另一個TCP發送完成的回調函數。該回調函數在每個TCP包發送完成后會被自動調用,代表TCP包發送完成。該回調函數在本例程中被定義為tcp_sent_callback,僅作發送完成數據包的計數。

表tcp_connected_callback 回調函數

static err_t

tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err)

{

xil_printf("txperf: Connected to iperf server\r\n");

/* store state */

connected_pcb = tpcb;

/* set callback values & functions */

tcp_arg(tpcb, NULL);

tcp_sent(tpcb, tcp_sent_callback);

tcp_client_connected = 1;

/* initiate data transfer */

return ERR_OK;

}

表 tcp_sent_callback 回調函數

/*this fuction just used to count the tcp transmission times*/

static err_t

tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len)

{

tcp_trans_done ++;

return ERR_OK;

}

8.6 連接測試

把開發板網卡通過網線接到PC網口上,修改 IP地址如下圖

wps2BA8.tmp

打開網絡調試助手,第一次用的時候windows會提示你是否允許訪問網絡一定要選擇是,否則你就無法通信了。設置電腦為TCP Server 本機IP為剛才設置的192.168.1.209 端口號為7.

wps2BA9.tmp

在SDK里面打開串口,並且啟動SDK調試(調試方法前面已經講過),板子是client可以看到成功連接到了PCB上。

wps2BBA.tmp

這個時候可以看到網絡調試助手接收到數據了,由於數據量大,刷新數據顯示,會導致電腦變慢,這里把勾選暫停顯示。右下角是接收數據的計數器,可以看到計數器在飛奔中。

wps2BBB.tmp

現在檢測下網速,可以看到時間速度在500Mbps/s左右查看網速,大概是在62MB/S-70MB/S的速度,當然我們也可以通過優化實現更高速度的傳輸。

wps2BBC.tmp

在SDK里面設定內存空間的查看地址,查看內存中的數據,可以看到正是PL 發出的數據。

wps2BCC.tmp


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM