16基于UDP的网络摄像头方案

发布时间 2023-12-27 19:59:07作者: 米联客(milianke)

软件版本:VIVADO2021.1

操作系统:WIN10 64bit

硬件平台:适用XILINX A7/K7/Z7/ZU/KU系列FPGA

登录米联客(MiLianKe)FPGA社区-www.uisrc.com观看免费视频课程、在线答疑解惑!

1概述

在前面的课程中,我们实现了基于PHY芯片RGMII接口实现的千兆以太网UDP通信。都详细展示了UDP通信的实现方式,而本文是基于之前的demo增加摄像头采集部分,以及通过DDR实现图像数据的缓存。采集到的图像通过以上任何一种方式把图像数据发动到电脑上,并且通过电脑的上位机显示图像。

2系统框图

本系统采用摄像头输入采用OV5640,I2C的寄存器配置采用Milianke uiSensorRGB565 IP配置。通过Milianke uifdma_dbuf将数据写入DDR。上位机使用太网通过AXI Interconnect IP读取存放在DDR中的摄像头数据。

 

3FPGA程序设计

3.1blockdesign设计

这张图的设计和DDR部分的摄像头采集部分一样,主要是配合"米联客"自定义IP FDMA实现数据缓存到DDR,和从DDR读出。在DDR部分的例子数据读出后就在显示器上显示了,而这里我们需要通过以太网发送给电脑。关于FDMA IP的应用可以参考DDR部分例子。

3.2图像数据格式

以下代码是上位机的帧头部分定义,用上位机定义图像的格式很容易理解。由于UDP一包数据最大是1472字节,所以这里一副1280*720数据格式为RGB888的图像,UDP传输需要分720*3次传输完成,每次传输32字节的帧头和1280字节的图像数据。

#define FLAG_HEAD 0xAA0055FF

struct stHeader

{

quint32 flag; //固定为 FLAG_HEAD 对数据合法性判断,防止其他数据干扰

quint32 width; //图片宽度 例如 1280

quint32 height; //图片高度 例如 720

quint32 total; //一张图片总大小 例如 1280*720*3

quint32 offset; //当前数据偏移量 例如,第一帧数据为0 n帧数据为 (n-1)*framesize

quint32 picseq; //图片序号,第几张图片

quint32 frameseq; //一张图片发送的帧序号,当前图片的第n ,1~ 720*3

quint32 framesize; //当前帧图片数据大小 例如每一次都发有效图片数据大小为1280

quint8 data[0]; //有效图片数据

};

有了以上代码的参考设计FPGA的图像帧数据就简单了一些。以下给出master_ch中关键的图像帧定义,以及以FPGA的方式发送帧数据的代码。通过FDMA获取的数据是128bit位宽(4个32bit XRGB格式的数据)而我们上位机需要的是RGB888所以是需要去掉无效的一个字节数据去掉,并且以RGB888 24bit数据紧密排列。详细的代码阅读配套的demo中代码。

`

localparam IMG_HEADER = 32'hAA0055FF;

localparam IMG_WIDTH = 32'd1280;

localparam IMG_HEIGHT = 32'd720;

localparam IMG_TOTAL = IMG_WIDTH*IMG_HEIGHT*3;

localparam IMG_FRAMSIZE = 32'd1280;

localparam IMG_FRAMTOTAL = IMG_HEIGHT*3;

localparam IMG_HEADER_LEN = 6'd32;//464bit

reg [31 :0] IMG_FRAMSEQ;

reg [31 :0] IMG_PICSEQ;

wire [31 :0] IMG_OFFSET = (IMG_FRAMSEQ-1'b1) * IMG_FRAMSIZE;

wire [255:0] STHEADER_X86 = {IMG_FRAMSIZE,IMG_FRAMSEQ,IMG_PICSEQ,IMG_OFFSET,IMG_TOTAL,IMG_HEIGHT,IMG_WIDTH,IMG_HEADER};

 

always@(posedge clk_15_625 or posedge core_reset)begin

if(core_reset) begin

app_tx_data <= 64'd0;

R0_data_r <= 128'd0;

end

else begin

if(UDP_MS == S_UDP_ACK || UDP_MS == S_IMG_HEADER)begin

app_tx_data <= STHEADER_X86[app_tx_header_cnt*8 +: 64];

end

else if(UDP_MS == S_IMG_DATA)begin

R0_data_r <= R0_data_o;

case(RO_INDEX)

0:begin app_tx_data <={R0_data_o[47 : 40],R0_data_o[55 : 48],R0_data_o[71 : 64],R0_data_o[79 : 72],R0_data_o[87 : 80],R0_data_o[103: 96],R0_data_o[111:104],R0_data_o[119:112]};end

1:begin app_tx_data <={R0_data_o[87 : 80],R0_data_o[103: 96],R0_data_o[111:104],R0_data_o[119:112],R0_data_r[7 : 0],R0_data_r[15 : 8 ],R0_data_r[23 : 16],R0_data_r[39 : 32]};end

2:begin app_tx_data <={R0_data_r[7 : 0],R0_data_r[15 : 8 ],R0_data_r[23 : 16],R0_data_r[39 : 32],R0_data_r[47 : 40],R0_data_r[55 : 48],R0_data_r[71 : 64],R0_data_r[79 : 72]};end

default:begin app_tx_data <= app_tx_data; end

endcase

end

else begin

app_tx_data <= app_tx_data;

end

end

end

 

 

reg [11:0] vstout_cnt;

wire [15:0] PACKET_INTERVAL;

reg [15:0] delay_cnt;

 

always@(posedge clk_15_625 or posedge core_reset)

begin

if(core_reset) begin

delay_cnt <= 16'd0;

end

else begin

if(delay_cnt < PACKET_INTERVAL)

delay_cnt <= delay_cnt + 1'b1;

else

delay_cnt <= 16'd0;

end

end

 

wire pkg_tx_en = (delay_cnt == PACKET_INTERVAL);

vio_0 vio_debug(.clk(clk_15_625),.probe_out0(PACKET_INTERVAL));

 

assign R0_rden_i = (RO_INDEX == 2'd0 || RO_INDEX == 2'd2)&& (UDP_MS == S_IMG_DATA );

 

always@(posedge clk_15_625 or posedge core_reset)

begin

if(core_reset) begin

app_tx_data_valid <= 1'b0;

app_tx_header_cnt <= 8'd0;

app_tx_data_cnt <= 12'd0;

app_tx_data_last <= 1'b0;

R0_FS_i <= 1'b0;

IMG_PICSEQ <= 32'd0;

IMG_FRAMSEQ <= 32'd0;

vstout_cnt <= 12'd0;

UDP_MS <= S_SYNC_1;

end

else begin

case(UDP_MS)

S_SYNC_1:begin

vstout_cnt <= 12'd0;

RO_INDEX <= 2'd0;

R0_FS_i <= 1'b0;

IMG_FRAMSEQ <= 32'd0;

IMG_PICSEQ <= IMG_PICSEQ + 1'b1;

UDP_MS <= S_SYNC_2;

end

S_SYNC_2:begin

UDP_MS <= S_SYNC_3;

end

S_SYNC_3:begin

R0_FS_i <= 1'b1;

vstout_cnt <= vstout_cnt + 1'b1;

if(R0_rdy_o)begin

UDP_MS <= S_UDP_WAIT;

end

else if(vstout_cnt[11])

UDP_MS <= S_SYNC_1;

end

S_UDP_WAIT:begin

app_tx_header_cnt <= 8'd0;

app_tx_data_cnt <= 12'd0;

if(udp_tx_ready&&pkg_tx_en) begin

app_tx_data_req <= 1'b1;UDP_MS <= S_UDP_ACK;

end

else begin

app_tx_data_req <= 1'b0;UDP_MS <= S_UDP_WAIT;

end

end

 

S_UDP_ACK:begin

if(app_tx_ack) begin

app_tx_data_req <= 1'b0;

app_tx_data_valid <= 1'b1;

app_tx_header_cnt <= app_tx_header_cnt + 8;

IMG_FRAMSEQ <= IMG_FRAMSEQ + 1'b1;

UDP_MS <= S_IMG_HEADER;

end

else if(dst_ip_unreachable) begin

app_tx_data_req <= 1'b0;app_tx_data_valid <= 1'b0;UDP_MS <= S_SYNC_1;

end

else begin

app_tx_data_req <= 1'b1;app_tx_data_valid <= 1'b0;UDP_MS <= S_UDP_ACK;

end

end

 

S_IMG_HEADER:begin

app_tx_data_valid <= 1'b1;

app_tx_header_cnt <= app_tx_header_cnt + 8;

if((app_tx_header_cnt + 8) >= IMG_HEADER_LEN )

UDP_MS <= S_IMG_DATA;

else UDP_MS <= S_IMG_HEADER;

end

 

S_IMG_DATA:begin

app_tx_data_valid <= 1'b1;

app_tx_data_cnt <= app_tx_data_cnt + 8;

if(RO_INDEX[1:0] == 2'd2)

RO_INDEX[1:0] <= 2'd0;

else

RO_INDEX[1:0] <= RO_INDEX[1:0] + 1'b1;

if((app_tx_data_cnt + 8) >= IMG_FRAMSIZE) begin

app_tx_data_last <= 1'b1; UDP_MS <= S_IMG_END;

end

else UDP_MS <= S_IMG_DATA;

end

 

S_IMG_END:begin

app_tx_data_valid <= 1'b0;

app_tx_data_last <= 1'b0;

if(IMG_FRAMSEQ >= IMG_FRAMTOTAL)begin

UDP_MS <= S_SYNC_1;

end

else begin

UDP_MS <= S_UDP_WAIT;

end

end

default: UDP_MS <= S_SYNC_1;

endcase

end

end

4硬件连接

4.1 RGMII接口千兆以太网

5程序测试

老版本MA703-100T开发板需要通过设置模式开关输出125M。

 

 

1

2

3

4

5

6

时钟

以太网速度

OFF

OFF

ON

OFF

OFF

OFF

125M

1Gbps

 

新版本F9-100T开发板直接焊接了一个125M固定时钟

5.1千兆以太网上位机采集设置

 

时间间隔参设置300~400大概是10~15fps的帧率

 

 

本地主机以太网IP地址设置

 

上位机主要需要正确设置IP地址以及端口号,目前"米联客"所有的UDP通信方案,开发板端口号为6002,本地主机端口号为6001,对于以太网UDP通信开发板的IP地址(摄像头IP地址)为192.168.137.2