Zynq与PC间的以太网通信实验(二)——PS侧代码分析

主函数:

 1 int main()
 2 {
 3     uint cycle = 0;
 4     ip_addr_t ipaddr, netmask, gw;
 5     RxBufNo = 0;
 6     RxBufferPtr[0] = (u16 *)RX_BUFFER0_BASE;
 7     RxBufferPtr[1] = (u16 *)RX_BUFFER1_BASE;
 8 
 9     /* the mac address of the board. this should be unique per board */
10     unsigned char mac_ethernet_address[] = { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };
11 
12     XGpio_Initialize(&Gpio, XPAR_AXI_GPIO_0_DEVICE_ID);//初始化AXI-GPIO
13     XGpio_SetDataDirection(&Gpio, 1, 0);
14 
15 
16 /* Define this board specific macro in order perform PHY reset on ZCU102 */
17 
18     // dma configuration
19     int ret;
20     ret = dma_init(&AxiDma);
21     if(ret!=XST_SUCCESS)
22         xil_printf("dma init error!");
23     ret = Timer_init(&Timer, TIMER_LOAD_VALUE, 0);
24     if(ret!=1)
25         xil_printf("timer init failed!\n");
26     ret = dma_intr_init(&AxiDma);
27     if(ret!=XST_SUCCESS){
28         xil_printf("dma interrup init failed!\n");
29     }
30     Timer_Setup_Intr_System(&Intc,&Timer, XPAR_SCUTIMER_INTR);
31 
32     echo_netif = &server_netif;
33     /* initliaze IP addresses to be used */
34     IP4_ADDR(&ipaddr,  192, 168,   1, 10);
35     IP4_ADDR(&netmask, 255, 255, 255,  0);
36     IP4_ADDR(&gw,      192, 168,   1,  1);
37 
38     lwip_init();
39 
40       /* Add network interface to the netif_list, and set it as default */
41     if (!xemac_add(echo_netif, &ipaddr, &netmask,
42                         &gw, mac_ethernet_address,
43                         PLATFORM_EMAC_BASEADDR)) {
44         xil_printf("Error adding N/W interface\n\r");
45         return -1;
46     }
47     netif_set_default(echo_netif);
48 
49     /* specify that the network if is up */
50     netif_set_up(echo_netif);
51 
52     /* start the application (web server, rxtest, txtest, etc..) */
53     client_application();
54     // start data generation progress in PL side by AXI4-Lite bus
55     XGpio_DiscreteWrite(&Gpio, 1, 1);
56     Timer_start(&Timer);
57 
58     /* receive and process packets */
59     while (1) {
60         if (TcpFastTmrFlag) {
61             tcp_fasttmr();
62             TcpFastTmrFlag = 0;
63         }
64         if (TcpSlowTmrFlag) {
65             tcp_slowtmr();
66             TcpSlowTmrFlag = 0;
67         }
68         xemacif_input(echo_netif);
69         if(connected_pcb!=NULL){
70             send_data();
71         }
72     }
73     return 0;
74 }
main函数

首先是各种初始化,包括GPIO初始化,DMA初始化,定时器初始化;其次是DMA中断和定时器中断的初始化;然后是LWIP的初始化(IP地址、端口号、网关),添加PS侧的MAC,设置回调函数;通过GPIO开启PL侧数据的产生。

主循环中先判断是否已经建立好连接,如果已经建立了连接,那么就启动send_data函数:

 1 int send_data()
 2 {
 3     err_t err;
 4     int ret;
 5     struct tcp_pcb *tpcb = connected_pcb;
 6 
 7     if (!tpcb){
 8         xil_printf("tpcb created error!\r\n");
 9         return -1;
10     }
11     if(!first_dma_start){
12         ret = XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)RxBufferPtr[RxBufNo&1],MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
13         if(ret!=XST_SUCCESS)
14             xil_printf("first dma start failed!\n");
15         first_dma_start = 1;
16     }
17     if(dma_com_flag){
18         //判断发送数据长度是否小于发送缓冲区剩余可用长度
19         if (TX_SIZE < tcp_sndbuf(tpcb)) {
20             //Write data for sending (but does not send it immediately).
21         //        err = tcp_write(tpcb, data, TX_SIZE, 1);
22             err = tcp_write(tpcb, RxBufferPtr[RxBufNo&1], TX_SIZE, 1);
23             if (err != ERR_OK) {
24                 xil_printf("txperf: Error on tcp_write: %d\r\n", err);
25                 connected_pcb = NULL;
26                 return -1;
27             }
28 
29             //Find out what we can send and send it
30             err = tcp_output(tpcb);
31             if (err != ERR_OK) {
32                 xil_printf("txperf: Error on tcp_output: %d\r\n",err);
33                 return -1;
34             }
35             dma_com_flag = 0;
36             RxBufNo++;
37             XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)RxBufferPtr[(RxBufNo)&1], MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
38         }
39     }
40     return 0;
41 }
send_data函数

首先判断是否已经启动了第一次DMA传输,如果还没有,就先启动一次DMA传输;如果已经启动了,那么就判断DMA传输是否已经完成,如果已经完成一次DAM传输,那么就启动TCP传输对应缓冲的数据,同时再开启另一块缓冲的DMA传输。其中DMA传输完成的标志是在DMA传输中断处理函数RxIntrHandler中设置的:

 1 static void RxIntrHandler(void *Callback)
 2 {
 3     u32 IrqStatus;
 4     int TimeOut;
 5     XAxiDma *AxiDmaInst = (XAxiDma *)Callback;
 6 
 7     /* Read pending interrupts */
 8     IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);
 9 
10     /* Acknowledge pending interrupts */
11     XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);
12 
13     /*
14      * If no interrupt is asserted, we do not do anything
15      */
16     if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
17         return;
18     }
19 
20     /*
21      * If error interrupt is asserted, raise error flag, reset the
22      * hardware to recover from the error, and return with no further
23      * processing.
24      */
25     if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
26 
27         Error = 1;
28 
29         /* Reset could fail and hang
30          * NEED a way to handle this or do not call it??
31          */
32         XAxiDma_Reset(AxiDmaInst);
33 
34         TimeOut = RESET_TIMEOUT_COUNTER;
35 
36         while (TimeOut) {
37             if(XAxiDma_ResetIsDone(AxiDmaInst)) {
38                 break;
39             }
40 
41             TimeOut -= 1;
42         }
43 
44         return;
45     }
46 
47     /*
48      * If completion interrupt is asserted, then set RxDone flag
49      */
50     if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
51         if(!dma_com_flag)
52             dma_com_flag = 1;
53     }
54 }
RxIntrHandler函数

send_data函数中的tcp_write函数和tcp_output函数是真正实现TCP数据传输功能的函数。若处于连接状态,且发送缓冲区容量比带发送数据量大,则调用tcp_write将待发送数据写入发送缓冲区,之后调用tcp_output函数立即传输发送缓冲区内容。如果不调用tcp_output,LWIP会等待数据量达到一定值时一起发送来提高效率,是否调用tcp_output函数可根据具体需求而定。

client_application函数:

 1 int client_application()
 2 {
 3     struct tcp_pcb *pcb;
 4     ip_addr_t ipaddr;
 5     err_t err;
 6     unsigned port = 7;
 7 
 8     /* create new TCP PCB structure */
 9     pcb = tcp_new();
10     if (!pcb) {
11         xil_printf("Error creating PCB. Out of Memory\n\r");
12         return -1;
13     }
14 
15     /* connect to iperf tcp server */
16     IP4_ADDR(&ipaddr,  192, 168,   1, 209);//设置要连接的主机的地址
17 
18     //当连接到主机时,调用tcp_connected_callback
19     err = tcp_connect(pcb, &ipaddr, port, tcp_connected_callback);
20     if (err != ERR_OK) {
21         xil_printf("txperf: tcp_connect returned error: %d\r\n", err);
22         return err;
23     }
24 
25     return 0;
26 }
client_application函数

在本例程中, zynq 作为客户端, PC 作为服务器。 由 zynq 向 PC 主动发起 TCP 连接请求,通过 tcp_connect 函数便可以完成这个过程。 该函数的参数包含了一个回调函数指针tcp_connected_fn, 该回调函数将在 TCP 连接请求三次握手完成后被自动调用。 该回调函数被调用时代表客户端和服务器之间的 TCP 连接建立完成。 在本例程中, 该回调函数被定义为 tcp_connected_callback。

 1 static err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err)
 2 {
 3     /* store state */
 4     connected_pcb = tpcb;
 5 
 6     /* set callback values & functions */
 7     tcp_arg(tpcb, NULL);
 8 
 9     //发送到远程主机后调用tcp_sent_callback
10     tcp_sent(tpcb, tcp_sent_callback);
11 
12     client_connected = 1;
13 
14     /* initiate data transfer */
15     return ERR_OK;
16 }
tcp_connected_callback函数

在该函数中,通过 tcp_sent 函数配置另一个 TCP 发送完成的回调函数tcp_sent_callback。

1 static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb,u16_t len)
2 {
3     tcp_trans_done ++;
4     return ERR_OK;
5 }
tcp_sent_callback函数

该回调函数在每个 TCP 包发送完成后会被自动调用, 代表 TCP 包发送完成。 该回调函数在本例程中仅作发送完成数据包的计数。

 

参考:

1. 米联客视频教程及《Zynq SOC修炼秘籍》

2. https://www.cnblogs.com/moluoqishi/p/9865480.html

posted @ 2021-09-22 21:11  何时忘却营营  阅读(2291)  评论(0编辑  收藏  举报