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 }
首先是各种初始化,包括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 }
首先判断是否已经启动了第一次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 }
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 }
在本例程中, 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_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 包发送完成后会被自动调用, 代表 TCP 包发送完成。 该回调函数在本例程中仅作发送完成数据包的计数。
参考:
1. 米联客视频教程及《Zynq SOC修炼秘籍》
2. https://www.cnblogs.com/moluoqishi/p/9865480.html