【干货】基于FPGA的网络摄像头(含源码)

本文详细描述了一种设计,通过FPGA实现OV7725摄像头的图像数据打包和以太网发送,确保数据正确传输到上位机,并处理了帧同步、数据格式转换和FIFO管理。文章重点介绍了图像封装处理模块,包括帧头数据的插入、FIFO复位策略和数据发送控制逻辑。
摘要由CSDN通过智能技术生成

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 修改摄像头芯片锁相环倍频系数

60帧测试结果如下所示,没有什么问题,只是存在很小的波动。

视频

图13 60帧网络视频测试

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

05 总结

本文将OV7725图像数据通过以太网传输到PC端上位机进行显示,由于前文实现了摄像头数据采集和以太网收发模块的设计,本文只需要将摄像头采集的图像封装成上位机显示图像的格式即可,总体比较简单。

为了确保上一帧图像的错误传输不会影响下一帧数据,每次检测到场同步信号之后,需要复位FIFO,将FIFO清空,然后写入帧头数据,之后将图像数据写入FIFO。

每当FIFO中的数据超过一行图像数据时,向以太网发送模块的使能信号拉高,然后读取FIFO中的数据通过以太网传输给上位机。

需要本工程的在公众号后台回复“基于FPGA的网络摄像头”(不包括引号)即可。

----------------------------------------------------------

如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!

如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!

FPGA摄像头获取数据的过程如下:首先,摄像头通过初始化设置进行配置。然后,FPGA摄像头获取一帧一帧的图像数据,并将ov7670数据流转换成所需的RGB565数据流。接下来,数据被存入写FIFO模块。当写FIFO模块中存储的数据达到一定数量(例如8)时,发出SDRAM写请求。一旦SDRAM写请求通过,数据将被读取并存储起来。同时,读FIFO模块会读取SDRAM中的数据,并经过读FIFO缓存后送入VGA显示模块进行显示。写控制模块和读控制模块会控制SDRAM读写地址的增加,以实现数据的连续读写。\[1\] #### 引用[.reference_title] - *1* *3* [基于FPGA的OV7670摄像头实时检测](https://blog.csdn.net/mxh3600/article/details/126733682)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [FPGA之OV7725摄像头采集与VGA显示实验--4--摄像头数据输出VAG协议分析](https://blog.csdn.net/weixin_54358182/article/details/126681942)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值