基于FPGA的网络摄像头图像封装处理

可编程逻辑

1340人已加入

描述

01概述

本文主要是为了验证之前设计的以太网发送模块,确保之前的设计没有问题,或者找到并修改存在的问题。工程的系统框图如下所示,主要包含OV7725初始化模块、像素数据封装处理模块、FIFO、以太网模块、锁相环模块。

上位机

图1 系统框图OV7725最多只能输出640*480的60帧图像数据,即每秒传输640*480*60*16bit=294912000bit=281Mb it数据,千兆以太网即使存在帧头、帧间隙、校验码等数据,传输速率也远大于281Mbit,所以不需要添加DDR3等外部存储器存储数据,只需要一个FIFO暂存小部分数据即可。

本次使用原子的上位机对以太网接收的数据进行显示,实测当上位机点击打开按钮后,上位机会通过以太网向FPGA发送一个长度为1的数据报,报文数据为8’h31,当FPGA接收到数据后,即可向上位机传输数据。上位机对传输的数据有格式要求,一帧数据的开始需要传输固定长度为4字节的帧头数据32’h,然后需要传输一帧图像的水平像素和垂直像素个数。

上位机才能够在接收数据后正确显示图像。所以每帧图像的开始需要多传输8字节数据,一般规定每次传输一行数据,由于OV7725一行有640个像素,每个像素16位,而以太网每个时钟传输8位数据,因此需要1280个时钟才能传输完一次数据。第一行需要1288个时钟才能全部传输。按理说FIFO的深度设置为2048即可,因为有时候上位机可能会通过ARP获取FPGA的MAC地址,导致FIFO中的数据不能及时被读取发送,所以把FIFO的深度设置得稍微大一点,毕竟2048深度能够充分利用构成FIFO的RAM地址线吧。

整体设计思路是当以太网接收到上位机发送的8’h31数据后,当检测到场同步信号的上升沿之后,当FIFO中数据个数大于等于一次传输的数据个数时,且以太网发送模块处于空闲,将UDP发送使能信号拉高,之后读取FIFO中的数据进行发送。OV7725摄像头初始化和以太网发送模块在前文均已经做过详细讲解,本文需要着重设计的其实只有摄像头的数据封装模块。

02图像封装处理模块

上位机是原子开发的,据说点击关闭后会向开发板发送8’h30,但是实测点击关闭后并没有向开发板发出指令,本处就默认会发结束传输的指令吧。上位机通过以太网向开发板发送8’h31后,FPGA开始通过以太网向上位机传输以太网数据,开发板接收到上位机发送的8’h30后,FPGA停止向上位机传输图片数据。上位机在检测到4字节的帧头数据后,才会接收数据并显示图像。上位机还要知道需要显示图像尺寸,因此在传输四字节的帧头后,需要传输2字节的水平像素个数和2字节的垂直像素点个数。下面通过代码讲解具体设计思路,首先当FPGA接收到UDP数据报文长度为1,如果数据为8’h31,则将发送数据标志信号拉高,如果数据为8’h30,则将发送数据标志信号拉低,其余时间保持不变。由于以太网发送时钟和以太网接收时钟在FPGA内部是同一个时钟,因此后文以太网发送时钟可以直接使用该信号,不属于异步信号。

//解析接收以太网传输的指令数据,其实以太网发送时钟和接收端的时钟时同一个时钟;

always@(posedge gmii_rx_clk)begin

if(rst_n==1‘b0)begin//初始值为0;

transfer_flag 《= 1’b0;

end

else if(udp_rx_data_vld && (udp_rx_data_num == 16‘d1))begin

if(udp_rx_data == 8’h31)//开始传输;

transfer_flag 《= 1‘b1;

else if(udp_rx_data == 8’h30)//停止传输;

transfer_flag 《= 1‘b0;

end

end

首先考虑FIFO的复位,因为xilinx的FIFO复位需要多个时钟周期,因此使用了一个计数器,将复位脉冲拉高多个时钟周期,调节计数器的位宽就可更改复位脉冲长度。当上位机不接收数据时,FIFO一直处于复位状态。每次检测到场同步信号的上升沿,也会对FIFO进行一次复位,清除FIFO中残留数据,保证上次传输过程可能出现的错误不会影响下次传输,对应代码如下:

//后文主要思路:首先为了确保每帧数据的正确显示,当检测到场同步信号的上升沿时,表示后面就是下一帧图像数据了,此时把FIFO复位。

//xilinx的FIFO复位一般需要多个时钟周期,当FIFO复位完成之后,就需要把帧头和水平垂直的像素个数数据写入FIFO中;

//过段时间就会出现图像数据,就把图像数据写入FIFO中。

//读FIFO数据时需要注意,第一行数据包含帧头等信息,会多8字节数据,从第二行开始就是正常的数据个数。

//把场同步信号打两拍,用于检测场同步信号上升沿;

always@(posedge cam_pclk)begin

cam_vsync_r 《= {cam_vsync_r[0],cam_vsync};

fifo_wrrst_busy_r 《= fifo_wrrst_busy;

end

assign cam_vsync_pos = cam_vsync_r[0] & (~cam_vsync_r[1]);//检测场同步信号上升沿;

assign fifo_wrrst_busy_neg = fifo_wrrst_busy_r & (~fifo_wrrst_busy);//检测FIFO复位完成信号的下降沿;

//因为xilinx FIFO复位需要持续多个时钟周期才能有效,所以需要一个计数器来辅助复位;

always@(posedge cam_pclk)begin

if(rst_n==1’b0)begin//初始值为0;

vsync_rst 《= 1‘b0;

end

else if(&rst_cnt)begin//复位计数器所有位均为高电平时拉低复位信号;

vsync_rst 《= 1’b0;

end//当检测到场同步信号上升沿时把FIFO复位信号拉高;

else if(cam_vsync_pos)begin

vsync_rst 《= 1‘b1;

end

end

//复位计数器,对复位脉冲进行计数,改变该计数器位宽即可更改复位脉冲持续时间;

always@(posedge cam_pclk)begin

if(rst_n==1’b0)begin//初始值为0;

rst_cnt 《= 3‘d0;

end//对复位脉冲宽度进行计数。

else if(vsync_rst)begin

rst_cnt 《= rst_cnt + 1;

end

end

assign fifo_rst = (~transfer_flag) || vsync_rst;//FIFO复位信号;

当FIFO复位完成之后,就将4字节帧头数据、2字节水平和垂直像素数据写入FIFO中。因此需要一个标志信号和一个计数器,对应代码如下。为了保证确保数据正确,标志信号和计数器增加了清零的逻辑。

//写入帧头数据标志信号,初始值为0,当FIFO复位或者写入全部帧头后清零,当FIFO复位完成后拉高;

always@(posedge cam_pclk)begin

if(rst_n==1’b0)begin//初始值为0;

head_flag 《= 1‘b0;

end//当FIFO复位或者写入全部帧头后清零;

else if(vsync_rst || (&head_cnt))begin

head_flag 《= 1’b0;

end

else if(fifo_wrrst_busy_neg)begin//FIFO复位完成,开始向FIFO中写入帧头数据;

head_flag 《= 1‘b1;

end

end

//帧头计数器,对帧头标志信号进行计数,因为需要8个数据,所以计数到7之后可以通过溢出清零;

always@(posedge cam_pclk)begin

if(rst_n==1’b0)begin//初始值为0;

head_cnt 《= 3‘d0;

end

else if(fifo_rst)begin//FIFO复位的时候将帧头计数器清零;

head_cnt 《= 3’d0;

end

else if(head_flag)begin

head_cnt 《= head_cnt + 1;

end

end

//FIFO写使能信号。

always@(posedge cam_pclk)begin

if(rst_n==1‘b0)begin//初始值为0;

fifo_wr_en 《= 1’b0;

end//当帧头写入标志有效或者输入有效数据时拉高,其余时间均为低电平;

else begin

fifo_wr_en 《= (head_flag || cam_href) && (~fifo_wrrst_busy);

end

end

//FIFO写数据信号,向FIFO中写入有效数据;

always@(posedge cam_pclk)begin

if(rst_n==1‘b0)begin//初始值为0;

fifo_wdata 《= 8’d0;

end

else if(head_flag)begin

case (head_cnt)//在输出帧头数据时,根据计数器的值输出对应的数据;

3‘d0 : fifo_wdata 《= IMG_FRAME_HEAD[31:24];//帧头;

3’d1 : fifo_wdata 《= IMG_FRAME_HEAD[23:16];//帧头;

3‘d2 : fifo_wdata 《= IMG_FRAME_HEAD[15: 8];//帧头;

3’d3 : fifo_wdata 《= IMG_FRAME_HEAD[ 7: 0];//帧头;

3‘d4 : fifo_wdata 《= {6’d0,CMOS_H_PIXEL[9: 8]};//水平方向分辨率;

3‘d5 : fifo_wdata 《= CMOS_H_PIXEL[7: 0];//水平方向分辨率;

3’d6 : fifo_wdata 《= {7‘d0,CMOS_V_PIXEL[8]};//垂直方向分辨率;

3’d7 : fifo_wdata 《= CMOS_V_PIXEL[7: 0];//垂直方向分辨率;

default : ;

endcase

end//像素数据有效时,将像素数据输出;

else if(cam_href)begin

fifo_wdata 《= cam_data;

end

end

之后就是将接收的摄像头数据写入FIFO中,当写入帧头标志信号或者输入图像数据有效且FIFO不处于空闲状态时,写使能拉高。写数据根据帧头计数器写入对应帧头数据,否则如果输入像素数据有效,则将对应数据写入FIFO。之后再来查看FIFO读侧逻辑,由于每帧数据开头需要多传输8字节数据,所以也需要检测一帧的开始。因此把场同步信号同步到千兆网发送时钟域下,并且检测其上升沿。

//把场同步信号同步到以太网发送时钟域下,然后检测其上升沿,所以需要将场同步信号延迟三个时钟周期。

//延迟的前两个时钟周期用于同步,后一个时钟周期用于检测上升沿;

always@(posedge gmii_tx_clk)begin

cam_vsync_txc_r 《= {cam_vsync_txc_r[1:0],cam_vsync};

end

//在以太网发送时钟域下检测cam_vsync信号上升沿;

assign cam_vsync_txc_pos = cam_vsync_txc_r[1] & (~cam_vsync_txc_r[2]);

如果检测到场同步信号上升沿,表示一帧图像传输的开始,那么需要发送的数据个数为水平像素点*2+8,乘2是因为一个水平像素点包含16位数据,而千兆网一个时钟只能发送8位。当检测到以太网发送使能信号有效时,表示已经发送过一次数据了,即帧头被发送,那么后续发送的数据就不包含帧头,会少8字节数据。

//以太网每次发送数据的报文长度,单位字节。

always@(posedge gmii_tx_clk)begin

if(rst_n==1‘b0)begin//初始值为0;

udp_tx_data_num 《= {CMOS_H_PIXEL,1’b0};

end

else if(cam_vsync_txc_pos)begin//第一行数据需要多传输8个字节的帧头数据;

udp_tx_data_num 《= {CMOS_H_PIXEL,1‘b0} + 16’d8;

end

else if(udp_tx_en)//其余行正常传输数据,由于像素点为16位数据,以太网每次传输8位数据,所以实际发送数据是水平像素点的2倍;

udp_tx_data_num 《= {CMOS_H_PIXEL,1‘b0};

end

最后是以太网发送使能信号,当以太网发送模块处于空闲且FIFO不处于复位状态且FIFO中的数据个数大于一次传输的数据个数且发送标志信号有效时才能进行发送。

//生成UDP发送使能信号;

always@(posedge gmii_tx_clk)begin

if(rst_n==1’b0)begin//初始值为0;

udp_tx_en 《= 1‘b0;

end

else begin//当UDP发送模块处于空闲且FIFO中的数据大于一次发送所需数据且FIFO不处于复位状态,则将使能信号拉高,其余时间使能信号均为低电平;

udp_tx_en 《= (udp_tx_rdy && (fifo_rdusedw 》= udp_tx_data_num) && (~fifo_rdrst_busy) && transfer_flag);

end

end

03顶层模块

顶层模块跟以前一样,主要就是对各个模块的引脚进行连接,对应的RTL视图如下所示。

上位机

图2 顶层模块RTL视图注意power_en信号只是控制模块开关电源工作的信号,只与固定模块有关,不使用该模块可以不考虑。04上板实测

将上述工程的ILA注释取消,然后进行综合,下载到开发板上,开发板环境如下所示,连接摄像头和网线。

上位机

图3 硬件开发环境之后打开Wireshark工具,打开原子的上位机软件,进行如下设置。接收图像格式为RGB565的图像数据进行显示,目的IP、UDP端口地址等设置需要与工程顶层模块的对应参数保持一致。上位机

上位机

图4 上位机设置然后点击上位机的打开按钮,通过Wireshark软件抓取数据如下图所示,首先上位机会先向FPGA开发板发送2个长度为1的UDP报文。如果上位机没有绑定开发板的MAC地址和IP地址,还会发送ARP请求,由于筛选的关系,此处看不见ARP请求数据报文。

上位机

图5 wireshark抓取数据之后FPGA开始向上位机发送图像数据,第一帧数据长度为1288,之后数据长度均为1280。如下图所示,第一帧虽然包含帧头,但是数据其实是错误的。错误的原因在于上位机可能在任何时候发起开始信号,有可能在一帧图像的中间开始传输数据,这样导致FIFO中的数据其实是溢出了很多的,最终导致错误。

上位机

图6 wireshark抓取错误数据但是第二帧开始时FIFO会复位清空,然后就能正常传输数据了,因此没有去做修改。在wireshark中可以通过frame.len》=1330去筛选报文长度,进而可以查看全部长度为1288的报文,点击第二帧数据的第一个报文。发现帧头和分辨率都显示正确,没有问题。

上位机

图7 wireshark第二帧第一行报文之后将ILA设置抓取第一行数据报文的时序,当FIFO中数据等于1288时,产生UDP使能信号,下个时钟周期需要发送报文长度变为1280。

上位机

图8 ILA抓取第一行数据读出数据如下图所示,帧头数据和像素尺寸均与设置保持一致,传输的数据没有问题。上位机

上位机

图9 ILA抓取帧头数据还可以抓一下FIFO复位之后的时序,如下图所示,当检测到场同步信号上升沿后,将FIFO复位信号拉高几个时钟周期,等FIFO复位结束之后,将第一行需要发送的帧头数据写入FIFO中,之后就等待需要写入FIFO的像素数据到来即可。

上位机

图10 ILA抓取复位时序最后来查看一下摄像头传输的效果吧,如下面视频所示,由上位机显示结果可知,帧率维持在30左右,与前文的计算能够对应。

上位机

上位机图11 上板结果及帧率如果想要提高帧率,只需要修改PCLK时钟频率就行,目前为24MHz,如果将PCLK频率更改为48MHz,那么帧率可以达到60帧。修改方式如下图所示,在初始化OV7725摄像头寄存器时,只需要将地址为8’h0d的高两位改为2’b11即可。

上位机

图12 修改摄像头芯片锁相环倍频系数

只是需要注意一个问题,就是原子的这个上位机软件点击“关闭”之后,并不会向开发板发送以太网报文,这个可以通过wireshrak软件去抓,实际上抓不到任何报文。只是上位机软件不会接收图像数据了而已,可能是开发者忘记了这个功能吧。05总结

本文将OV7725图像数据通过以太网传输到PC端上位机进行显示,由于前文实现了摄像头数据采集和以太网收发模块的设计,本文只需要将摄像头采集的图像封装成上位机显示图像的格式即可,总体比较简单。为了确保上一帧图像的错误传输不会影响下一帧数据,每次检测到场同步信号之后,需要复位FIFO,将FIFO清空,然后写入帧头数据,之后将图像数据写入FIFO。每当FIFO中的数据超过一行图像数据时,向以太网发送模块的使能信号拉高,然后读取FIFO中的数据通过以太网传输给上位机。

审核编辑:黄飞

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分