基于0v2640的光口视频传输

本文详细介绍了基于FPGA的光口视频传输系统设计,涉及OV2640摄像头数据采集、8b10b编码、光口传输、数据对齐及解码等关键模块。通过摄像头采集图像,经过编码、光口传输、解码后在HDMI显示器上正常显示,验证了设计的有效性。
摘要由CSDN通过智能技术生成


前言

本节实验任务是使用开发板上的两个光纤接口实现视频图像的传输,视频图像由 OV2640 摄像头模块采集数据, 再通过开发板上的其中两路光模块进行视频信号的光纤发送和接收,然后在 HDMI 显示器上显示出来。


提示:任何文章不要过度深思!万事万物都经不起审视,因为世上没有同样的成长环境,也没有同样的认知水平,更「没有适用于所有人的解决方案」 ;不要急着评判文章列出的观点,只需代入其中,适度审视一番自己即可,能「跳脱出来从外人的角度看看现在的自己处在什么样的阶段」才不为俗人 。怎么想、怎么做,全在乎自己「不断实践中寻找适合自己的大道」

一、整体模块设计

在这里插入图片描述
对于上图中各个模块的功能介绍如下:
(1)camera_init 模块:摄像头初始化模块,完成对 OV2640 众多模式设置寄存器的写入操作(camera_init 摄像头初始化模块);
(2)DVP_Capture 模块:实现每两个数据拼接为一个 16 位的数据(DVP 接口时序逻辑设计);
(3)光口传输顶层模块:该模块将摄像头采集到的数据通过光口完成数据的发送和接收,该模块主要包含四部分:一个是 sfp_encoder模块主要负责将上游传递过来的 16 位数据转换成 32 位数据;一个是sfp_data_align 模 块 主 要 是 将 接 收 到 的 数 据 进 行 对 齐 ; 一 个 是sfp_decode 模块主要负责将接收到的 32 位数据转换为 16 位数据,再将解码后的数据送入下游;还有一个是 gt_aurora_exdes 模块(用于将sfp_encoder模块输入进行8b_10b编码输出给sfp_data_align 模 块,同时控制光口),主模块主要作用就是例化了 GTP IP;
(4)fifo_axi4_adapter 模块:该模块主要包含三部分:一个是写fifo 主要负责将上游外设传递过来的数据写进 DDR3 控制器,一个读fifo 主要负责人将 DDR3 控制器的内部数据读出到下游,一个是读写fifo 与 DDR3 存储器数据交互控制模块(参考fifo_mig_axi_fifo);
(5)dvi_encoder 模块:该模块就是将输出的图像数据与时序转换为符合 HDMI 接口发送时序的编码格式(基于 FPGA 的 HDMI/DVI 显示);
(6)disp_driver 模块:该模块就是将 tft 屏显示驱动控制,对缓存在 DDR3 中的图像数据进行显示(VGA成像原理与简单实现);

于是本次设计主要是解决
光口传输顶层模块(sfp_8b10b_top):光口传输顶层模块负责将摄像头采集的数据先转化为 32bit 数据,然后按照 8b10b 的通信编码格式将数据发送出去,然后将光口接收到的数据按照 8b10b 的通信解码格式将数据解析出来,最后将 32bit 数据转化为 16bit 数据写入到 ddr3 中。

二、光口传输顶层模块设计

(1)光口编码模块(sfp_encoder):光口编码模块负责将摄像头输入进来的 16bit 数据转化为 32bit 数据,再按照 8b10b 的编码格式将数据发送到 GTP 顶层模块。
(2)GTP 顶层模块(gt_aurora_exdes):GTP 顶层模块一方面负责与用户(FPGA)进行数据交互,另一方面还产生控制光口传输的各种时序,并实现对光口的通信操作。用户可以把该 IP 看作是 FPGA 与光口交流信息的桥梁,当然这个“桥梁”的实现代码不需要用户自己写,Xilinx 官方已帮大家写好,并封装成了一个 IP 供用户调用,用户只需要根据实际应用配置该 IP 即可。
(3)光口字对齐模块(sfp_data_align):光口字对齐模块是将接收回来不对齐的数据转化为对齐的数据,然后再输出到光口解码模块。
(4)光口解码模块(sfp_decode):光口解码模块将对齐后的数据按照 8b10b 的解码格式转化为 32bit 的像素数据,再通过 fifo 将 32bit 的数据转化为 16bit 的数据写入到 ddr3 中。

光口传输顶层模块下面有 4 个子模块,顶层模块是对子模块的例化。

module sfp_8b10b_top(
    //光口接口
    input           Q0_CLK1_GTREFCLK_PAD_N_IN, //差分参考时钟
    input           Q0_CLK1_GTREFCLK_PAD_P_IN,
    input  [1:0]    RXN_IN,                    //差分接收数据
    input  [1:0]    RXP_IN,
    output [1:0]    TXN_OUT,                   //差分发送数据
    output [1:0]    TXP_OUT,
    output [1:0]    tx_disable,	               //tx端发送禁止使能
	//用户接口
	input           drp_clk,                   //光口输入时钟  
	input           clk_in,                    //输入时钟
	input           rst_n,                     //输入复位
	input           vs_in,	                   //输入场信号
	input           data_valid_in,	           //输入数据有效信号
	input [15:0]    data_in,	               //输入数据                                        
	output          vs_out,	                   //输出场信号
	output          data_valid_out,	           //输出数据有效信号
	output [15:0]   data_out	               //输出数据
    );
    
    
//parameter define     
	parameter vs_data0	    =	32'h550001bc ;
	parameter vs_data1	    =	32'h550002bc ;
	parameter data_begin0	=	32'h550003bc ;
	parameter data_begin1	=	32'h550004bc ;
	parameter data_end0		=	32'h550005bc ;
	parameter data_end1		=	32'h550006bc ;
	parameter unuse_data	=	32'h550007bc ;


//wire define    
wire		 tx0_user_clk; 
wire		 tx0_rst_n; 
wire [3:0]  tx0_gt_txcharisk;
wire [31:0] tx0_gt_txdata;
wire		 rx1_user_clk; 
wire		 rx1_rst_n; 
wire [3:0]  rx1_gt_rxcharisk;
wire [31:0] rx1_gt_rxdata;  
wire [3:0]  rx1_charisk_align;
wire [31:0] rx1_data_align; 


sfp_encode#(
	.vs_data0	(vs_data0	),
	.vs_data1	(vs_data1	),	
	.data_begin0(data_begin0),
	.data_begin1(data_begin1),
	.data_end0  (data_end0  ),
	.data_end1	(data_end1	),
	.unuse_data	(unuse_data	)
)
    sfp_encoder(
	.clk           (clk_in          ),            //输入时钟
	.rst_n         (rst_n           ),             //输入复位
	.vs_in         (vs_in           ),             //输入场信号 
	.code_valid    (data_valid_in   ),     //输入数据有效信号
	.code_data     (data_in         ),	        //输入数据
	.tx_code_clk   (tx0_user_clk    ),      //光口tx端时钟
	. tx_code_rst_n(tx0_rst_n       ),         //光口tx端复位
	.tx_charrisk   (tx0_gt_txcharisk),  //光口tx端K码发送信号
	.tx_data	   (tx0_gt_txdata   )      //光口tx端发送数据
    ); 
 
  

    
//光口控制及8b_10

   
   gt_aurora_exdes  u_gt_aurora_exdes
(
    .Q0_CLK1_GTREFCLK_PAD_N_IN  (Q0_CLK1_GTREFCLK_PAD_N_IN),
    .Q0_CLK1_GTREFCLK_PAD_P_IN  (Q0_CLK1_GTREFCLK_PAD_P_IN),
    .RXN_IN                     (RXN_IN                   ),
    .RXP_IN                     (RXP_IN                   ),
    .TXN_OUT                    (TXN_OUT                  ),
    .TXP_OUT                    (TXP_OUT                  ),
    .tx_disable                 (tx_disable               ),
    .drp_clk                    (drp_clk                  ),     
    .rx0_user_clk               (rx0_user_clk             ), 
    .rx0_rst_n                  (rx0_rst_n                ), 
    .tx0_user_clk               (tx0_user_clk             ), 
    .tx0_rst_n                  (tx0_rst_n                ),   
    .rx0_gt_rxdata              (                         ), 
    .rx0_gt_rxcharisk           (                         ),  
    .tx0_gt_txdata              (tx0_gt_txdata            ), 
    .tx0_gt_txcharisk           (tx0_gt_txcharisk         ),
    .rx1_user_clk               (rx1_user_clk             ), 
    .rx1_rst_n                  (rx1_rst_n                ), 
    .tx1_user_clk               (tx1_user_clk             ), 
    .tx1_rst_n                  (tx1_rst_n                ),   
    .rx1_gt_rxdata              (rx1_gt_rxdata            ), 
    .rx1_gt_rxcharisk           (rx1_gt_rxcharisk         ),  
    .tx1_gt_txdata              (32'd0                    ), 
    .tx1_gt_txcharisk           (4'd0                     )   
    
);    
    

	
	//光口字对齐模块    
  data_align sfp_data_align(
	.clk            (rx1_user_clk         ),       //rx端时钟
	.rst_n			(rx1_rst_n            ),          //rx端复位
	.rx_data		(rx1_gt_rxdata        ),      //rx端K码未校正接收信号
	.rx_charrisk	(rx1_gt_rxcharisk     ),   //rx端未校正发送数据
	.rx_data_align	(rx1_data_align       ),     //rx端K码校正后接收信号
	.rx_charrisk_align	(rx1_charisk_align)   //rx端校正后发送数据
    );     
	
	
	
	ila_8 ila_data_align (
	.clk(rx1_user_clk), // input wire clk
	.probe0(rx1_charisk_align), // input wire [3:0]  probe0  
	.probe1(rx1_data_align) // input wire [31:0]  probe1
);
	
	
	
//光口解码模块    
	sfp_decode#(
		.vs_data0	    (vs_data0	    ),
		.vs_data1	    (vs_data1	    ),	
		.data_begin0	(data_begin0	),
		.data_begin1	(data_begin1	),
		.data_end0   	(data_end0   	),
		.data_end1	    (data_end1	    ),
		.unuse_data	    (unuse_data		)
	)
	   sfp_decoder(
		.clk              (clk_in           ),
		.rst_n            (rst_n            ),
		.vs_out           (vs_out           ),
		.valid_out        (data_valid_out   ),
		.data_out         (data_out         ),                
		.rx_code_clk      (rx1_user_clk     ),
		.rx_code_rst_n    (rx1_rst_n        ),
		.rx_code_charrisk (rx1_charisk_align),
		.rx_code          (rx1_data_align   )
		);   
	
	
	
	
endmodule


代码//parameter define 是对编解码时所需的一些特定字符进行定义。因为不加这些特定字符,那么当接收端接收到数据时就不知道什么时候是有效数据,什么时候是无效数据,也不知道什么时候恢复场信号,所以添加这些特定字符是很有必要的。

数据流向为sfp_encode →gt_aurora_exdes(TX0 – RX1)→data_align→sfp_decode

三、 光口编码模块设计

1、光口编码模块设计

光口编码模块负责将摄像头输入进来的 16bit 数据转化为 32bit 数据,再按照 8b10b 的编码格式将数据发送到 GTP 顶层模块。
状态图的设计思想是当场信号的上升沿到来时,GTP 会发送帧同步信号。然后再判断这个 FIFO
中的数据量,如果 FIFO 内的数据还没有一行的视频数据,GTP 则发送无用的数据,当 FIFO 内已经有一行视频的数据时,GTP 会先发数据开始信号的特殊字符,然后再把这一行视频的数据通过 GTP 发送出去。一行数据发送完成时 GTP 会发送数据发送结束信号的特殊字符。最后再重新判断 FIFO 内的数据量,FIFO 数据量达到一行视频数据时,接着发送第二行视频图像。(注:状态图中使用的名称与实际代码名称可能不符,但不影响理解)

在这里插入图片描述

首先对光口编码(sfp_encode)模块进行讲解,由于 GTP IP 的外部接口是32 位,我们在光口编码模块中将摄像头采集的 16 位位宽的数据先经过异步 fifo转为 32 位位宽的数据,再将 fifo 输出的数据按照 8b/10b 的方式进行编码。首先,使用独热码对各个状态进行定义。

parameter tx_unuse_data = 8'b0000_0001,
tx_vs_data0 = 8'b0000_0010,
tx_vs_data1 = 8'b0000_0100,
tx_data_begin0 = 8'b0000_1000,
tx_data_begin1 = 8'b0001_0000,
tx_send_data = 8'b0010_0000,
tx_data_end0 = 8'b0100_0000,
tx_data_end1 = 8'b1000_0000;

参数化定义特殊字符,我们使用的是 K28.5,对应的十六进制为 0xbc,而且 0xbc 放在数据的最低字节,其余的高 24 位自定义即可。我们对特殊字符进行参数化定义,而且特殊字符的最低字节必须为 0xbc。

parameter vs_data0 = 32'h550001bc,
parameter vs_data1 = 32'h550002bc,
parameter data_begin0 = 32'h550003bc,
parameter data_begin1 = 32'h550004bc,
parameter data_end0 = 32'h550005bc,
parameter data_end1 = 32'h550006bc,
parameter unuse_data = 32'h550007bc

下面我们接着对光口编码(sfp_encoder)模块中发送端的状态转移进行讲解。首先产生一个数据发送的标志信号,当 fifo 中读计数大于本次实验设置的阈值 390 时(80016位 = 40032位,选取390防止读写速度不一致,造成数据读空)就将数据发送的标志信号拉高;当 fifo 为空或者场信号 vs_pose 到来时就将数据发送的标志信号拉低。

//-----------------------------------------------------  
	//产生一个数据发送的标志信号,当 fifo 中读计数大于本次实验设置的阈值时就将数据发送的标志信号拉高
    //当 fifo 为空或者场信号 vs_pose 到来时就将数据发送的标志信号拉低。   
	always@(posedge tx_code_clk or negedge tx_code_rst_n)
	   if(!tx_code_rst_n)
	     data_begin<=1'b0;
	   else if(empty||vs_pose)
	     data_begin<=1'b0;
	   else if(rd_data_count >= fifo_threshold_value) 
	     data_begin<=1'b1;
	   else
	      data_begin<=data_begin;
	//-----------------------------------------------------

状态机在进入 tx_unuse_data 状态时,在该状态里判断 fifo 中数据的数量,如果 fifo 中数据的数量还没有达到定值 255 时 TX 端就发送无用的数据;如果fifo 中的数据到达定值 255 时,TX 端就开始发送特殊字符。当检测到场信号vs_pose 时就跳转到 tx_vs_data0 状态; tx_data_begin0 状态,tx_data_begin1状态,tx_data_end0 以及 tx_data_end1 都是 TX 端发送 k 码以及发送无用数据,只有在 tx_send_data 发送有用数据。代码中还有一个 tx_charrisk 信号,该信号位宽为 4 位,该信号的每个 bit 都依次对应着 tx_data 中的每个 byte,用 tx_charrisk信号的高电平来表示 K 码位于 tx_data 中的哪一个字节。

//-----------------------------------------------------
	//产生 tx 端 K 码发送信号和发送数据
	always@(posedge tx_code_clk or negedge tx_code_rst_n)
	   if(!tx_code_rst_n)begin
          tx_charrisk <= 4'd0;
          tx_data <= 32'd0;
		  state   <= tx_unuse_data;
		  rd_en   <=1'b0;
		  data_cnt<=8'd0;
        end	   
		else     begin
		    if(vs_pose)    begin
		       tx_charrisk <= 4'd0;
		       tx_data  <= 32'h55000102;
		       state   <= tx_vs_data0;
			   data_cnt<=8'd0;
		    end
		    else if(data_begin_flag==2'b10)begin
			   tx_charrisk <= 4'd0;
		       tx_data  <= 32'h55000102;
		       state    <= tx_data_begin0;
			   data_cnt <= 8'd0;
		    end
		    else  begin
			    case(state)
				   tx_unuse_data :begin
				      rd_en<=1'b0;
				      data_cnt<=data_cnt+1'b1;
					  if(data_cnt==8'd255)begin
					     tx_charrisk <= 4'b0001;
					     tx_data <= unuse_data;  //unuse_data=32'h550007bc
						 state   <=tx_unuse_data;
				        end
				      else  begin
					    tx_charrisk <= 4'b0000;
					    tx_data <= 32'h55000102;
					    state   <=tx_unuse_data;
				        end
				    end
			   
			       tx_vs_data0 :begin
				        tx_charrisk <= 4'b0001;
					    tx_data <= vs_data0;    //vs_data0=32'h550001bc
					    state   <=tx_vs_data1;
				        rd_en<=1'b0;
				    end
			
			       tx_vs_data1 :begin
				        tx_charrisk <= 4'b0001;
					    tx_data <= vs_data1;   //vs_data0=32'h550002bc
					    state   <=tx_unuse_data;
				   
				    end
			
			       tx_data_begin0:begin
				        tx_charrisk <= 4'b0001;
					    tx_data <= data_begin0;//data_begin0=32'h550003bc
					    state   <= tx_data_begin1;
				        rd_en<=1'b1;  
				    end
			        tx_data_begin1:begin
				        tx_charrisk <= 4'b0001;
					    tx_data <= data_begin1;//data_begin0=32'h550004bc
					    state   <= tx_send_data;
				        rd_en<=1'b1; 
				    end
			
			       tx_send_data:begin
				         if(almost_empty==1'b0) begin
						 tx_charrisk <= 4'b0000;
					     tx_data <= dout;
					     state   <= tx_send_data;
				          rd_en<=1'b1;  
						end
						else if(almost_empty==1'b1&&empty==1'b0)begin
						 tx_charrisk <= 4'b0000;
					     tx_data <= dout;
					     state   <= tx_send_data;
				         rd_en<=1'b1;  //0
						end
					    else begin
						 tx_charrisk <= 4'b0000;
					     tx_data <= dout;
					     state   <= tx_data_end0;
				         rd_en<=1'b0;  
                        end						
				    end
			
			      tx_data_end0:begin
				         tx_charrisk <= 4'b0001;
					     tx_data <= data_end0;   //data_end0=32'h550005bc
					     state   <= tx_data_end1;
				         rd_en   <=1'b0; 
				    end
			
			       tx_data_end1:begin
				         tx_charrisk <= 4'b0001;
					     tx_data <= data_end1;   //data_end0=32'h550006bc
					     state   <= tx_unuse_data ;
				         rd_en   <=1'b0; 
				    end
			       default:begin
				         data_cnt <=data_cnt +1'b1;
						 if(data_cnt==8'd255)begin
					     tx_charrisk <= 4'b0001;
					     tx_data <= unuse_data;
						 state   <=tx_unuse_data;
				        end
				      else  begin
					    tx_charrisk <= 4'b0000;
					    tx_data <= 32'h55000102;
					    state   <=tx_unuse_data;
				        end
				    end
			       
			    endcase
			
			end
		end

当场信号到来时,对 fifo 进行复位,至于 fifo 的复位信号为什么要持续那么长时间,这是为了保证 fifo 完全复位成功。

//产生 fifo 复位信号  
  always@(posedge tx_code_clk or negedge tx_code_rst_n)
	   if(!tx_code_rst_n)begin
		fifo_cnt <= 8'd0;	
		fifo_rst <= 1'b1;
    end
 	else if(vs_pose)begin
		fifo_cnt <= 8'd0;
		fifo_rst <= 1'b1;
    end	   
	else if(fifo_cnt >=100
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C.V-Pupil

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值