ZYNQ AXI_DMA_UDP以太网传输(三)
一、前言
本次实验跟之前的实验有所不同,不同点在于:
1、前面一个实验的UDP协议,ZYNQ板卡是作为服务端(Server),向PC端一直主动的发数据,但是这一次不同,这一次ZYNQ板卡是作为客户端(Client),需要等待PC端发送相应指令,然后进入UDP协议接收回调函数,再往PC端发送一次数据;
2、另一个不同点在于,本次实验加入了将PC端的参数文件通过UDP协议发送到PS端,然后PS端通过AXI BRAM CTRL下发到PL端的BRAM中。
二、主要参考学习的链接有:
1、FPGADesigner《学会Zynq》系列目录与传送门
2、《【正点原子】领航者ZYNQ之嵌入式Vitis开发指南v1_2》第十九章实验,基于BRAM的PS和PL交互
3、网络通信之大小端模式及字节序转换函数解析
三、框架与代码
3.1 blockdesign 框架
1、该框架的搭建主要参考《【正点原子】领航者ZYNQ之嵌入式Vitis开发指南v1_2》第十九章实验,基于BRAM的PS和PL交互,与之不同的是我并没有用到官方提供的自定义IP核,本来是想将官方IP核加到自己工程里的,但是一直有问题,后来去掉了该IP核后试了试也能实现自己要求的功能,就先暂时放一边了,这个问题以后有机会再研究一下;
2、要注意的是为了添加BRAM CTRL模块,需要额外引出一个GP口,我之前试过只有一个GP口,但是在PS端运行的过程中卡死了,暂时是觉得和DMA传输数据冲突了。
3、跟之前不一样的还有就是,自己在创建VITIS工程的时候用到的是Hello world模板,没用到那个udp server模板,所以在创建工程时候需要将lwip库给勾选起来。
3.2 main 函数
//****************************************Copyright (c)***********************************//
//原子哥在线教学平台:www.yuanzige.com
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2020-2030
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: main.c
// Last modified Date: 2023/08/05 15:59:46
// Last Version: V1.0
// Descriptions: PS端网口传输OV5640摄像头视频在上位机显示
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2023/08/02 15:59:52
// Version: V1.0
// Descriptions: The original version
//server_netif
//----------------------------------------------------------------------------------------
//****************************************************************************************//
/***************************** Include Files *********************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xil_types.h"
#include "xparameters.h"
// #include "emio_sccb_cfg/emio_sccb_cfg.h"
#include "axi_gpio_cfg/axi_gpio_cfg.h"
#include "axi_dma/axi_dma.h"
// #include "ov5640/ov5640_init.h"
#include "sys_intr/sys_intr.h"
#include "netif/xadapter.h"
#include "xil_printf.h"
#include "lwip/tcp.h"
#include "sleep.h"
#include "lwip/priv/tcp_priv.h"
#include "lwip/init.h"
#include "lwip/inet.h"
#include "udp_perf_client.h"
#include "xil_cache.h"
#include "platform.h"
#include "platform_config.h"
#include "xbram.h"
extern volatile int TcpFastTmrFlag;
extern volatile int TcpSlowTmrFlag;
#define DEFAULT_IP_ADDRESS "192.168.1.10"
#define DEFAULT_IP_MASK "255.255.255.0"
#define DEFAULT_GW_ADDRESS "192.168.1.1"
void start_application(void);
void print_app_header(void);
int lwip_udp_init();
#define MAX_PKT_LEN 4096 //发送包长度
//函数声明
struct netif *netif;
extern volatile int rx_done;
extern u8 *rx_buffer_ptr;
u32 fifo_count = 0;
u8 dma_start_flag = 0; //0:启动DMA ,1:关闭DMA
struct netif server_netif;
static XScuGic Intc; //GIC
int main(void)
{
axi_gpio_init(); // 初始AXI-GPIO接口
axi_dma_cfg(); // 配置AXI DMA
Init_Intr_System(&Intc); // 初始DMA中断系统
Setup_Intr_Exception(&Intc); // 启用来自硬件的中断
dma_setup_intr_system(&Intc); // 建立DMA中断系统
lwip_udp_init(); // UDP通信配置
//接收和处理数据包
while (1) {
xemacif_input(netif);
// fifo_count = get_fifo_count(); //PS端读取FIFO中的读数据计数
//FIFO中的读数据计数个数达到发送包长度后,开始启动DMA从FIFO中读取1024个数据存储进DDR中
if((dma_start_flag == 0)){
axi_gpio_out1();
axi_dma_start(MAX_PKT_LEN);
axi_gpio_out0();
dma_start_flag = 1;
}
//DMA搬运1024个数据完成后,网口就可以从DDR中取数据进行发送了
if(rx_done){
// udp_tx_data(rx_buffer_ptr,2048);
rx_done = 0;
dma_start_flag = 0;
}
}
return 0;
}
static void print_ip(char *msg, ip_addr_t *ip)
{
print(msg);
xil_printf("%d.%d.%d.%d\r\n", ip4_addr1(ip), ip4_addr2(ip),
ip4_addr3(ip), ip4_addr4(ip));
}
static void print_ip_settings(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
print_ip("Board IP: ", ip);
print_ip("Netmask : ", mask);
print_ip("Gateway : ", gw);
}
//设置静态IP地址
static void assign_default_ip(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
int err;
xil_printf("Configuring default IP %s \r\n", DEFAULT_IP_ADDRESS);
err = inet_aton(DEFAULT_IP_ADDRESS, ip);
if (!err)
xil_printf("Invalid default IP address: %d\r\n", err);
err = inet_aton(DEFAULT_IP_MASK, mask);
if (!err)
xil_printf("Invalid default IP MASK: %d\r\n", err);
err = inet_aton(DEFAULT_GW_ADDRESS, gw);
if (!err)
xil_printf("Invalid default gateway address: %d\r\n", err);
}
int lwip_udp_init()
{
/*设置领航者开发板的MAC地址 */
unsigned char mac_ethernet_address[] = {
0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };
netif = &server_netif;
xil_printf("\r\n\r\n");
xil_printf("-----lwIP RAW Mode UDP Server Application-----\r\n");
/* 初始化lwIP*/
lwip_init();
/* 将网络接口添加到netif_list,并将其设置为默认网络接口 (用于输出未找到特定路由的所有数据包) */
if (!xemac_add(netif, NULL, NULL, NULL, mac_ethernet_address,
PLATFORM_EMAC_BASEADDR)) {
xil_printf("Error adding N/W interface\r\n");
return -1;
}
netif_set_default(netif);
/* 指定网络是否已启动*/
netif_set_up(netif);
//设置静态IP地址
assign_default_ip(&(netif->ip_addr), &(netif->netmask), &(netif->gw));
//打印IP设置
print_ip_settings(&(netif->ip_addr), &(netif->netmask), &(netif->gw));
xil_printf("\r\n");
/* 打印应用程序标题 */
print_app_header();
/* 启动应用程序*/
start_application();
xil_printf("\r\n");
return 0;
}
这里其实可以跟前面实验对比一下,发现UDP作为Client和Server时候的配置有什么不同,具体参考FPGADesigner《学会Zynq》系列目录与传送门
3.3 udp_perf_client 函数
#include <stdio.h>
#include <string.h>
#include "lwip/err.h"
#include "lwip/udp.h"
#include "xil_printf.h"
#include "lwip/inet.h"
#include "xbram.h"
static struct udp_pcb *pcb;
extern u8 *rx_buffer_ptr;
u32 total_bytes = 0;
u16 udp_send_buf0[1024];
#define SER_PORT 8000
#define START_ADDR 0 //RAM起始地址 范围:0~1023
#define BRAM_DATA_BYTE 4 //BRAM数据字节个数
//短整型大小端互换
u16_t BigLittleSwap16(u16_t A)
{
return ((((u16_t)(A) & 0XFF00) >> 8) | (((u16_t)(A) & 0X00FF) << 8));
}
//打印应用程序
void print_app_header()
{
xil_printf("\r\n-----Network port UDP transmission camera video display on upper computer ------\r\n");
}
//UDP发送功能函数
void udp_tx_data(u8 *buffer_ptr,unsigned int len){
static struct pbuf *ptr;
ptr = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_POOL); /* 申请内存 */
if (ptr)
{
pbuf_take(ptr, buffer_ptr,len); /* 将buffer_ptr中的数据打包进pbuf结构中 */
udp_send(pcb, ptr); /* udp发送数据 */
pbuf_free(ptr); /* 释放内存 */
}
}
//---------------------------------------------------------
// UDP接收回调函数
//---------------------------------------------------------
void udp_recv_callback(void *arg, struct udp_pcb *tpcb,
struct pbuf *p, struct ip4_addr *addr, u16_t port)
{
int i=0,wr_cnt=0;
int read_data=0;
u16_t pos;
u16 udp_send_buf0_temp[1024];
xil_printf("Received from %d.%d.%d.%d port %d\r\n", (addr->addr) & 0xFF,
(addr->addr>>8) & 0xFF, (addr->addr>>16) & 0xFF, (addr->addr>>24) & 0xFF, port);
pos = pbuf_strstr(p, "come");
if (pos == 0xFFFF)
{
total_bytes = 0;
// memset(udp_send_buf0, 0, 5120);
while(p->tot_len != p->len)
{
xil_printf("%d\n %d\n",p->tot_len,p->len);
memcpy(&udp_send_buf0_temp[total_bytes], p->payload, p->len);
total_bytes += p->len/2;//更新总字节数
p = p->next;
}
xil_printf("%d\n %d\n",p->tot_len,p->len);
memcpy(&udp_send_buf0_temp[total_bytes], p->payload, p->len);
for(i=0;i<1024;i++)
{
udp_send_buf0[i] = BigLittleSwap16(udp_send_buf0_temp[i]);
}
// total_bytes += p->len;
xil_printf("udp_send_buf0\n");
// for(i=0;i<1024;i++)
// {
// xil_printf("%d\n",udp_send_buf0[i]);
// }
//每次循环向BRAM中写入1个字符
for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + 1024) ;
i += BRAM_DATA_BYTE){
XBram_WriteReg(XPAR_BRAM_0_BASEADDR,i,udp_send_buf0[wr_cnt]) ;
wr_cnt++;
}
//循环从BRAM中读出数据
for(i = BRAM_DATA_BYTE*START_ADDR ; i < BRAM_DATA_BYTE*(START_ADDR + 1024) ;
i += BRAM_DATA_BYTE){
read_data = XBram_ReadReg(XPAR_BRAM_0_BASEADDR,i) ;
printf("BRAM address is %d\t,Read data is %d\n",i/BRAM_DATA_BYTE ,read_data) ;
}
xil_printf("Havn't found 'come'.\r\n");
}
else
{
udp_tx_data(rx_buffer_ptr,4096);
}
pbuf_free(p); //释放pbuf
return;
}
void start_application()
{
err_t err;
/* 设置客户端的IP地址 */
ip_addr_t DestIPaddr;
IP4_ADDR( &DestIPaddr,192,168,1,100);
//创建新的UDP PCB
pcb = udp_new();
if (!pcb) {
xil_printf("Error creating PCB. Out of Memory\r\n");
return;
}
//绑定端口
err = udp_bind(pcb, IP_ADDR_ANY, SER_PORT);
if (err != ERR_OK) {
xil_printf("Unable to bind to port %d; err %d\r\n",
SER_PORT, err);
udp_remove(pcb);
return;
}
/* 设置客户端的端口 */
udp_connect(pcb, &DestIPaddr, 8000);
udp_recv(pcb, udp_recv_callback, NULL); //设置接收回调函数
}
//---------------------------------------------------------
// UDP发送数据函数
//---------------------------------------------------------
void udp_printf(struct pbuf *send_pbuf, struct udp_pcb *tpcb,
struct ip4_addr *addr, u16_t port)
{
err_t err;
/* 发送字符串 */
err = udp_sendto(tpcb, send_pbuf, addr, port); //echo
if (err != ERR_OK) {
xil_printf("Error on udp send : %d\r\n", err);
return;
}
}
/*
* Copyright (C) 2017 - 2019 Xilinx, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
*/
#ifndef __UDP_PERF_CLIENT_H_
#define __UDP_PERF_CLIENT_H_
#include "lwipopts.h"
#include "lwip/ip_addr.h"
#include "lwip/err.h"
#include "lwip/udp.h"
#include "lwip/inet.h"
#include "xil_printf.h"
//#include "platform.h"
void udp_tx_data(u8 *buffer_ptr,unsigned int len);
void udp_recv_callback(void *arg, struct udp_pcb *tpcb,
struct pbuf *p, struct ip4_addr *addr, u16_t port);
void udp_printf(struct pbuf *send_pbuf, struct udp_pcb *tpcb,
struct ip4_addr *addr, u16_t port);
u16_t BigLittleSwap16(u16_t A);
/* seconds between periodic bandwidth reports */
#define INTERIM_REPORT_INTERVAL 5
/* server port to listen on/connect to */
#define UDP_CONN_PORT 5001
#endif /* __UDP_PERF_SERVER_H_ */
可以看到,我大部分的功能实现都放在,UDP的接收回调函数里面,无论是PC端(网络调试助手)下发come的时候,PS端将DDR里的数据上传到PC端,还是PC端往PS端写入参数文件,然后PS端将1024个参数写入bram中都在这里实现
四、问题记录
1、当输入come指令的时候,PS端DDR的数据并没有上传到网络调试助手中
这个问题是因为在学习UDP协议作为Client的时候,也就是FPGADesigner《学会Zynq》系列目录与传送门
我其实并没有定义server端的地址和端口
“我们的设计任务是对来自任何地址、任何端口的数据echo,因此程序中没有连接和指定任何IP地址和端口。发送时利用接收回调的参数和udp_sendto函数,便可完成echo server的设计”
但是在我们的实际使用过程中,其实并没有用到udp_sendto函数,这似乎就导致了这个问题,我就尝试了一下,定义了下地址和端口,然后数据就可以正常发送到PC了,不过没有深究这个问题。
2、
while(p->tot_len != p->len)
{
xil_printf("%d\n %d\n",p->tot_len,p->len);
memcpy(&udp_send_buf0_temp[total_bytes], p->payload, p->len);
total_bytes += p->len/2;//更新总字节数
p = p->next;
}
这段代码参考也是正点原子的例程第四十二章,基于TCP协议的远程更新QSPI Flash实验
但是源代码是这么写的
else {
while (q->tot_len != q->len) {
memcpy(&rxbuffer[total_bytes], q->payload, q->len);
total_bytes += q->len;
q = q->next;
}
memcpy(&rxbuffer[total_bytes], q->payload, q->len);
total_bytes += q->len;
total_bytes += p->len/2;//更新总字节数
分析这段代码,首先可以发现区别在于memcpy是一个字节一个字节的存进去,而我定义的udp_send_buf0_temp是16位的,也就是两个字节,假设q->len=1472,那么实际上往udp_send_buf0_temp数字的前1472/2,也就是736个数据存入了数据,然后
total_bytes += q->len;
即下一次存取是从第1472个开始存,但其实我们是想接着从第737个开始存的,这就存在小bug,这也就是为什么要除以2的原因
3、大小端转换的问题
我们可以先将大小端转换的代码注释掉,我们想下发的参数是1~1024
观察实验结果如下:
可以发现是存在大小端的问题的,然后参考链接网络通信之大小端模式及字节序转换函数解析将其解决。
暂时先记录到这吧,纯属是想到了写到哪了,如果哪有写的有问题的地方欢迎一起讨论呀。
未解决的问题
如果下发的参数过多,会出现堵塞的情况,但是目前未发现详细原因。