16基于UDP的网络摄像头方案
软件版本: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;//4个64bit 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
本文来米联客(milianke),作者:米联客(milianke),转载请注明原文链接:https://www.cnblogs.com/milianke/p/17931308.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· 【全网最全教程】使用最强DeepSeekR1+联网的火山引擎,没有生成长度限制,DeepSeek本体