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
串口端口
下篇文章主要说下本次项目在调试的过程中遇见的问题
推荐链接
发表评论