AXI_DMA_UDP以太网传输

开发环境:Vivado2020.2;硬件设备:ZYNQ7010

数据传输流程

PS端下发控制指令,开启DMA传输的同时下发一个上升沿信号(后文将说为什么要加一个上升沿)PL端接收到指令开始产生2048个32bit的数据,通过AXI DMA将数据传输到PS端的DDR3中;PS端将PS端的DDR的数据通过UDP传输给PC,用网口传输助手查看传输的数据。

PL端Block Design设计

ZYNQ配置

参考正点原子的教程《领航者ZYNQ之嵌入式Vitis开发指南v1_2》中的实验《 基于 OV5640 的 PS 以太网视频传输实验》配置,配置以太网、串口、中断等。

数据产生模块——data_gen

参考博客: 【JokerのZYNQ7020】AXI_DMA_PL_PS ZYNQ通过AXI DMA实现PL发送连续大量数据到PS DDR

`timescale 1ns / 1ps

//

// Company:

// Engineer:

//

// Create Date: 2024/02/27 16:35:14

// Design Name:

// Module Name: data_gen

// Project Name:

// Target Devices:

// Tool Versions:

// Description:

//

// Dependencies:

//

// Revision:

// Revision 0.01 - File Created

// Additional Comments:

//

//

module data_gen #(

parameter TRANS_NUM = 32'd2047 //1514*1024

)

(

input clk ,

input i_rstn ,

input trans_start,

input M_AXIS_tready ,

output [31 : 0] M_AXIS_tdata ,

output M_AXIS_tvalid ,

output M_AXIS_tlast ,

output [3 : 0] M_AXIS_tkeep

);

reg [31 : 0] r_M_AXIS_tdata ;

reg r_M_AXIS_tvalid ;

reg r_M_AXIS_tlast ;

reg [3 : 0] r_M_AXIS_tkeep ;

reg [1 : 0] r_current_state ;

reg [1 : 0] r_next_state;

reg trans_start_0, trans_start_1;

wire pos_trans_start;

assign pos_trans_start = trans_start_0 & (~trans_start_1);

always @(posedge clk) begin

if(!i_rstn) begin

trans_start_0 <= 1'd0;

trans_start_1 <= 1'd0;

end

else begin

trans_start_0 <= trans_start;

trans_start_1 <= trans_start_0;

end

end

localparam IDLE = 2'd0;

localparam TRAN = 2'd1;

localparam LAST = 2'd2;

always @(posedge clk ) begin

if(!i_rstn)

r_current_state <= IDLE;

else

r_current_state <= r_next_state;

end

always @(*) begin

case(r_current_state)

IDLE : r_next_state = (pos_trans_start && M_AXIS_tready) ? TRAN : IDLE;

TRAN : r_next_state = (r_M_AXIS_tdata == TRANS_NUM) ? LAST : TRAN;

LAST : r_next_state = M_AXIS_tready ? IDLE : LAST;

default : r_next_state = IDLE;

endcase

end

always @(posedge clk ) begin

case(r_current_state)

IDLE : begin

r_M_AXIS_tdata <= 32'd0;

r_M_AXIS_tvalid <= 1'd0;

r_M_AXIS_tlast <= 1'd0;

r_M_AXIS_tkeep <= 4'b1111;

end

TRAN : begin

r_M_AXIS_tvalid <= 1'd1;

if(M_AXIS_tready)begin

r_M_AXIS_tdata <= r_M_AXIS_tdata + 32'd1;

if(r_M_AXIS_tdata == TRANS_NUM)

r_M_AXIS_tlast <= 1'd1;

else

r_M_AXIS_tlast <= 1'd0;

end

else

r_M_AXIS_tdata <= r_M_AXIS_tdata;

end

LAST : begin

if(!M_AXIS_tready)begin

r_M_AXIS_tvalid <= 1'd1;

r_M_AXIS_tlast <= 1'd1;

r_M_AXIS_tdata <= r_M_AXIS_tdata;

end

else begin

r_M_AXIS_tvalid <= 1'd0;

r_M_AXIS_tlast <= 1'd0;

r_M_AXIS_tdata <= 32'd0;

end

end

default : begin

r_M_AXIS_tdata <= 32'd0;

r_M_AXIS_tvalid <= 1'd0;

r_M_AXIS_tlast <= 1'd0;

r_M_AXIS_tkeep <= 4'b1111;

end

endcase

end

assign M_AXIS_tdata = r_M_AXIS_tdata ;

assign M_AXIS_tvalid = r_M_AXIS_tvalid ;

assign M_AXIS_tlast = r_M_AXIS_tlast ;

assign M_AXIS_tkeep = r_M_AXIS_tkeep ;

endmodule

模块写好后直接添加进block design即可。

axis data fifo配置

虽然引出了读数据计数引脚,但其实并未用到,可以关掉。

axi dma 配置

axi gpio配置

按上述配置完之后自动布线,然后valite design、generate output products、create HDL wrapper,一切无误之后综合布线,生成bit流

PS端配置

参考:正点原子的教程《领航者ZYNQ之嵌入式Vitis开发指南v1_2》中的实验《 基于 OV5640 的 PS 以太网视频传输实验》软件设计部分,下面贴出修改的部分

main.c

//****************************************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

#include

#include

#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 "udp_perf_server.h"

#include "netif/xadapter.h"

#include "platform.h"

#include "platform_config.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 "xil_cache.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 2048 //发送包长度

//函数声明

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)

{

// u32 status;

u16 cmos_h_pixel; //ov5640 DVP 输出水平像素点数

u16 cmos_v_pixel; //ov5640 DVP 输出垂直像素点数

u16 total_h_pixel; //ov5640 水平总像素大小

u16 total_v_pixel; //ov5640 垂直总像素大小

cmos_h_pixel = 640;

cmos_v_pixel = 480;

total_h_pixel = 2844;

total_v_pixel = 1968;

//

// emio_init(); //初始化EMIO

//

// status = ov5640_init( cmos_h_pixel, //初始化ov5640

// cmos_v_pixel,

// total_h_pixel,

// total_v_pixel);

//

// if(status == 0)

// xil_printf("OV5640 detected successful!\r\n");

// else

// xil_printf("OV5640 detected failed!\r\n");

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,8192);

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;

}

axi_gpio_cfg.c

//****************************************Copyright (c)***********************************//

//原子哥在线教学平台:www.yuanzige.com

//技术支持:www.openedv.com

//淘宝店铺:http://openedv.taobao.com

//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。

//版权所有,盗版必究。

//Copyright(C) 正点原子 2018-2028

//All rights reserved

//----------------------------------------------------------------------------------------

// File name: axi_gpio_cfg.c

// Last modified Date: 2020/04/15 10:59:46

// Last Version: V1.0

// Descriptions: AXI GPIO配置

//----------------------------------------------------------------------------------------

// Created by: 正点原子

// Created date: 2023/06/30 15:59:52

// Version: V1.0

// Descriptions: The original version

//

//----------------------------------------------------------------------------------------

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

#include"axi_gpio_cfg.h"

#define AXI_GPIO_0_ID XPAR_AXI_GPIO_0_DEVICE_ID //AXI GPIO 0器件 ID

#define AXI_GPIO_0_CHANEL1 1

XGpio axi_gpio_inst0; //AXI GPIO 0 驱动实例

//AXI GPIO初始化

void axi_gpio_init(void)

{

//AXI GPIO 0驱动

XGpio_Initialize(&axi_gpio_inst0, AXI_GPIO_0_ID);

//配置AXI GPIO 0 通道1为输入

XGpio_SetDataDirection(&axi_gpio_inst0, AXI_GPIO_0_CHANEL1,0);

}

//通过AXI GPIO获取FIFO数据个数

//u32 get_fifo_count(void)

//{

// u32 fifo_count = 0;

// fifo_count = XGpio_DiscreteRead(&axi_gpio_inst0, AXI_GPIO_0_CHANEL1);

// return fifo_count;

//}

void axi_gpio_out1(void)

{

XGpio_DiscreteWrite(&axi_gpio_inst0, 1, 0x01);

}

void axi_gpio_out0(void)

{

XGpio_DiscreteWrite(&axi_gpio_inst0, 1, 0x00);

}

axi_gpio_cfg.h

//****************************************Copyright (c)***********************************//

//原子哥在线教学平台:www.yuanzige.com

//技术支持:www.openedv.com

//淘宝店铺:http://openedv.taobao.com

//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。

//版权所有,盗版必究。

//Copyright(C) 正点原子 2018-2028

//All rights reserved

//----------------------------------------------------------------------------------------

// File name: emio_sccb_cfg.h

// Last modified Date: 2019/06/30 15:59:46

// Last Version: V1.0

// Descriptions: SCCB驱动

//----------------------------------------------------------------------------------------

// Created by: 正点原子

// Created date: 2019/06/30 15:59:52

// Version: V1.0

// Descriptions: The original version

//

//----------------------------------------------------------------------------------------

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

#include "xil_types.h"

#include "xgpio.h"

#ifndef AXI_GPIO_CFG_

#define AXI_GPIO_CFG_

void axi_gpio_init(void);

void axi_gpio_out0(void);

void axi_gpio_out1(void);

//u32 get_fifo_count(void);

#endif /* sccb_EMIO_CFG_ */

axi_dma.c

//****************************************Copyright (c)***********************************//

//原子哥在线教学平台:www.yuanzige.com

//技术支持:www.openedv.com

//淘宝店铺:http://openedv.taobao.com

//关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。

//版权所有,盗版必究。

//Copyright(C) 正点原子 2018-2028

//All rights reserved

//----------------------------------------------------------------------------------------

// File name: axi_dma.c

// Last modified Date: 2023/08/9 10:59:46

// Last Version: V1.0

// Descriptions: Axi dma驱动程序在中断模式下接收数据包

//----------------------------------------------------------------------------------------

// Created by: 正点原子

// Created date: 2019/06/30 15:59:52

// Version: V1.0

// Descriptions: The original version

//

//----------------------------------------------------------------------------------------

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

/***************************** Include Files *********************************/

#include "axi_dma.h"

/************************** Constant Definitions *****************************/

#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID

#define RX_INTR_ID XPAR_FABRIC_AXIDMA_0_VEC_ID

#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID

#define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR //0x00100000

#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1000000) //0x01100000

#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) //0x01400000

#define RESET_TIMEOUT_COUNTER 10000 //复位时间

/************************** Variable Definitions *****************************/

static XAxiDma axidma; //XAxiDma实例

volatile int rx_done=0; //接收完成标志

volatile int error; //传输出错标志

u32 *rx_buffer_ptr;

/************************** Function Definitions *****************************/

int axi_dma_cfg(void)

{

int status;

XAxiDma_Config *config;

rx_buffer_ptr = (u32 *) RX_BUFFER_BASE;

xil_printf("\r\n--- Entering axi_dma_cfg --- \r\n");

config = XAxiDma_LookupConfig(DMA_DEV_ID);

if (!config) {

xil_printf("No config found for %d\r\n", DMA_DEV_ID);

return XST_FAILURE;

}

//初始化DMA引擎

status = XAxiDma_CfgInitialize(&axidma, config);

if (status != XST_SUCCESS) {

xil_printf("Initialization failed %d\r\n", status);

return XST_FAILURE;

}

if (XAxiDma_HasSg(&axidma)) {

xil_printf("Device configured as SG mode \r\n");

return XST_FAILURE;

}

xil_printf("AXI DMA CFG Success\r\n");

return XST_SUCCESS;

}

//启用AXI DMA

int axi_dma_start(u32 pkt_len)

{

int status;

u32 a;

//初始化标志信号

error = 0;

a = (u32)(pkt_len*sizeof(u32));

xil_printf("%d",a);

status = XAxiDma_SimpleTransfer(&axidma, (u32) rx_buffer_ptr,

(u32)(pkt_len*sizeof(u32)), XAXIDMA_DEVICE_TO_DMA);

if (status != XST_SUCCESS) {

xil_printf("AXI DMA Start FAILURE\r\n");

return XST_FAILURE;

}

Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, pkt_len); //刷新Data Cache

return XST_SUCCESS;

}

//DMA RX中断处理函数

void rx_intr_handler(void *callback)

{

u32 irq_status;

int timeout;

XAxiDma *axidma_inst = (XAxiDma *) callback;

irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);

XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);

//Rx出错

if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {

error = 1;

xil_printf("XAxiDma error");

XAxiDma_Reset(axidma_inst);

timeout = RESET_TIMEOUT_COUNTER;

while (timeout) {

if (XAxiDma_ResetIsDone(axidma_inst))

break;

timeout -= 1;

}

return;

}

//Rx完成

if ((irq_status & XAXIDMA_IRQ_IOC_MASK))

rx_done = 1;

irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);

}

//建立DMA中断系统

// @param int_ins_ptr是指向XScuGic实例的指针

// @param AxiDmaPtr是指向DMA引擎实例的指针

// @param rx_intr_id是RX通道中断ID

// @return:成功返回XST_SUCCESS,否则返回XST_FAILURE

int dma_setup_intr_system(XScuGic * int_ins_ptr)

{

int status;

//设置优先级和触发类型

XScuGic_SetPriorityTriggerType(int_ins_ptr, RX_INTR_ID, 0xA0, 0x3);

//为中断设置中断处理函数

status = XScuGic_Connect(int_ins_ptr, RX_INTR_ID,

(Xil_InterruptHandler) rx_intr_handler, &axidma);

if (status != XST_SUCCESS) {

return status;

}

XScuGic_Enable(int_ins_ptr, RX_INTR_ID);

//使能DMA中断

XAxiDma_IntrEnable(&axidma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);

return XST_SUCCESS;

}

udp_perf_server.c

#include

#include

#include "lwip/err.h"

#include "lwip/udp.h"

#include "xil_printf.h"

#include "lwip/inet.h"

static struct udp_pcb *pcb;

#define SER_PORT 8000

//打印应用程序

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); /* 释放内存 */

}

}

void start_application()

{

err_t err;

/* 设置客户端的IP地址 */

ip_addr_t DestIPaddr;

IP4_ADDR( &DestIPaddr,192,168,1,102);

//创建新的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);

xil_printf("UDP server started @ port %d\n\r", SER_PORT);

}

调试结果

网口调试助手

PS的memory monitor

串口端口

下篇文章主要说下本次项目在调试的过程中遇见的问题

推荐链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: