首页 > 技术文章 > FPGA——图像采集系统摄像头传以太网实现及验证

cnlntr 2021-02-25 16:01 原文

一、设计思路

  • IMAGE_SEQUENCE模块的作用是:给输入的每一行像素RGB565数据编号,并将编号的2字节数据写入FIFO,标志图像每一行像素的开始
  • FIFO_CTR信号状态机实现
  • 上位机软件是大端模式,所有输入以太网MAC模块的数据应该也是大端模式

二、IMAGE_SEQUENCE模块

module camera_image_sequence(
   clk         ,
   rst_n       ,
   data_vld_in ,
   data_in     ,
   h_begin_in  ,
   h_end_in    ,
   haddr_in    ,
   vaddr_in    ,
   data_out    ,
   wr_en       
);

localparam DATA_W    =  16;
localparam HADDR_W   =  12;
localparam VADDR_W   =  12;

input                   clk;
input                   rst_n;
input                   data_vld_in;
input    [DATA_W-1:0]   data_in;
input                   h_begin_in;
input                   h_end_in;
input    [HADDR_W-1:0]  haddr_in;
input    [VADDR_W-1:0]  vaddr_in;


output   [DATA_W-1:0]   data_out;
output                  wr_en;

reg      [DATA_W-1:0]   data_out;
reg                     wr_en;


always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      data_out <= 0;
   else if(h_begin_in)
      data_out <= {vaddr_in[7:0],4'b0,vaddr_in[11:8]};
   else if(data_vld_in)
      data_out <= {data_in[7:0],data_in[15:8]};
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      wr_en <= 0;
   else if(h_begin_in)
      wr_en <= 1;
   else if(data_vld_in)
      wr_en <= 1;
   else
      wr_en <= 0;
end

endmodule

三、FIFO_CTR模块

module fifo_ctr(
   rst_n       ,
   wr_clk      ,
   rd_clk      ,
   data_in     ,
   wr_en       ,
   eth_data_vld,
   eth_tx_done ,
   eth_tx_en   ,
   dout        
);
parameter IMAGE_WIDTH   = 800;
parameter IMAGE_HEIGHT  = 640;

//状态机参数
localparam IDLE         =  3'b001;
localparam EN_ETH       =  3'b010;
localparam WAIT_TX_DONE =  3'b100;
localparam STATE_W      =  3;


localparam DIN_W        =  16;
localparam DOUT_CNT_W   =  14;
localparam DOUT_W       =  8;

input                      rst_n;
input                      wr_clk;
input                      rd_clk;
input    [DIN_W-1:0]       data_in;
input                      wr_en;
input                      eth_data_vld;
input                      eth_tx_done;

output                     eth_tx_en;
output                     dout;

reg                        eth_tx_en;

//模块变量
wire                       rd_en;
wire                       full;
wire                       empty;
wire     [DOUT_CNT_W-1:0]  rd_data_count;
wire     [DOUT_W-1:0]      dout;

//状态机变量
reg      [STATE_W-1:0]     state_c;
reg      [STATE_W-1:0]     state_n;
wire                       idle2en_eth;
wire                       en_eth2wait_tx_done;
wire                       wait_tx_done2idle;


camera2eth_fifo camera2eth_fifo (
  .wr_rst((!rst_n)),                 // input wire wr_rst
  .rd_rst((!rst_n)),                 // input wire rd_rst
  .wr_clk(wr_clk),                // input wire wr_clk
  .rd_clk(rd_clk),                // input wire rd_clk
  .din(data_in),                  // input wire [15 : 0] din
  .wr_en(wr_en),                  // input wire wr_en
  .rd_en(rd_en),                  // input wire rd_en
  .dout(dout),                    // output wire [7 : 0] dout
  .full(full),                    // output wire full
  .empty(empty),                  // output wire empty
  .rd_data_count(rd_data_count)  // output wire [13 : 0] rd_data_count
);


/****************** FIFO输出端 ******************/
always @(posedge rd_clk or negedge rst_n)begin
   if(!rst_n)
      state_c <= IDLE;
   else
      state_c <= state_n;
end

always @(*)begin
   case(state_c)
      IDLE:begin
         if(idle2en_eth)
            state_n = EN_ETH;
         else
            state_n = state_c;
      end
      EN_ETH:begin
         if(en_eth2wait_tx_done)
            state_n = WAIT_TX_DONE;
         else
            state_n = state_c;
      end
      WAIT_TX_DONE:begin
         if(wait_tx_done2idle)
            state_n = IDLE;
         else
            state_n = state_c;
      end
      default:state_n = IDLE;
   endcase
end

assign idle2en_eth               =  state_c == IDLE && (rd_data_count >= (IMAGE_WIDTH<<1) + 2);
assign en_eth2wait_tx_done       =  state_c == EN_ETH && eth_tx_en;
assign wait_tx_done2idle         =  state_c == WAIT_TX_DONE && eth_tx_done;

always @(posedge rd_clk or negedge rst_n)begin
   if(!rst_n)
      eth_tx_en <= 0;
   else if(state_c == EN_ETH)
      eth_tx_en <= 1;
   else
      eth_tx_en <= 0;
end

assign rd_en = eth_data_vld;

endmodule

四、摄像头捕获数据模块

module dvp_capture(
   rst_n       ,
   clk         ,
   vsync       ,
   hsync       ,
   data        ,
   pix_data    ,
   image_state ,
   haddr       ,
   vaddr       ,
   h_begin     ,
   h_end       ,
   data_vld    
);

parameter IMAGE_WIDTH   =  800;
parameter IMAGE_HEIGHT  =  480;

localparam DATA_W    =  8;
localparam DATA_PIX  =  16;
localparam HADDR_W   =  12;
localparam VADDR_W   =  12;

input                   rst_n;
input                   clk;
input                   vsync;        //场同步信号
input                   hsync;        //行同步信号
input    [DATA_W-1:0]   data;

output   [DATA_PIX-1:0] pix_data;     //摄像头输出的565像素数据
output                  image_state;  //摄像头初始化完成开始输出数据标志信号
output   [HADDR_W-1:0]  haddr;        //行地址
output   [VADDR_W-1:0]  vaddr;        //场地址
output                  data_vld;     //输出数据有效信号
output                  h_begin;      //行同步开始,标志信号
output                  h_end;        //行同步接收,标志信号

reg      [DATA_PIX-1:0] pix_data;     //摄像头输出的565像素数据
reg                     image_state;  //摄像头初始化完成开始输出数据标志信号
reg      [HADDR_W-1:0]  haddr;        //行地址
reg      [VADDR_W-1:0]  vaddr;        //场地址
reg                     data_vld;     //输出数据有效信号
reg                     h_begin;
reg                     h_end;

//中间变量
reg                     pre_vsync;
reg                     pre_hsync;
reg      [DATA_W-1:0]   pre_data;

//计数器变量
reg      [HADDR_W-1:0]  cnt_hsync;
wire                    add_cnt_hsync;
wire                    end_cnt_hsync;

reg      [VADDR_W-1:0]  cnt_vsync;
wire                    add_cnt_vsync;
wire                    end_cnt_vsync;

reg      [4-1:0]        cnt_frame;
wire                    add_cnt_frame;
wire                    end_cnt_frame;
reg                     output_frame;


//打一拍,快速IO,优化时序,边沿检测
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
      pre_vsync <= 0;
      pre_hsync <= 0;
      pre_data  <= 0;
   end
   else begin
      pre_vsync <= vsync;
      pre_hsync <= hsync;
      pre_data  <= data;
   end
end

//行同步计数器,行同步有效时加1,行同步信号失效时结束
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_hsync <= 0;
   else if(add_cnt_hsync)begin
      if(end_cnt_hsync)
         cnt_hsync <= 0;
      else
         cnt_hsync <= cnt_hsync + 1'b1;
   end
end
assign add_cnt_hsync = pre_hsync;
assign end_cnt_hsync = add_cnt_hsync && {pre_hsync,hsync} == 2'b10;

//场同步信号计数器,行同步计数完成时加1,场同步达到要求的像素值结束,当输出图像不稳定在场同步拉高时清0,
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_vsync <= 0;
   else if(add_cnt_vsync)begin
      if(end_cnt_vsync)
         cnt_vsync <= 0;
      else
         cnt_vsync <= cnt_vsync + 1'b1;
   end
   else if({pre_vsync,vsync} == 2'b01)
      cnt_vsync <= 0;
end
assign add_cnt_vsync = end_cnt_hsync;
assign end_cnt_vsync = add_cnt_vsync && cnt_vsync == IMAGE_HEIGHT - 1;

//图像帧数计数器,出现场同步信号脉冲时加一,数到十帧图像结束
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_frame <= 0;
   else if(add_cnt_frame)begin
      if(end_cnt_frame)
         cnt_frame <= 0;
      else
         cnt_frame <= cnt_frame + 1'b1;
   end
end
assign add_cnt_frame = {pre_vsync,vsync} == 2'b10 && !output_frame;
assign end_cnt_frame = add_cnt_frame && cnt_frame == 10 - 1;

//舍弃每次系统开始运行后的前 10 帧图像的数据,以确保输出图像稳定
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      output_frame <= 0;
   else if(end_cnt_frame)
      output_frame <= 1;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      pix_data <= 0;
   else
      pix_data <= {pix_data[7:0],pre_data};
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      data_vld <= 0;
   else if(cnt_hsync[0] == 1 && add_cnt_hsync && output_frame)
      data_vld <= 1;
   else
      data_vld <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      image_state <= 0;
   else if(pre_vsync)
      image_state <= 1;
   else if(end_cnt_vsync)
      image_state <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      vaddr <= 0;
   else
      vaddr <= cnt_vsync;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      haddr <= 0;
   else
      haddr = cnt_hsync[7:1];
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      h_begin <= 0;
   else if({pre_hsync,hsync} == 2'b01 && output_frame)
      h_begin <= 1;
   else
      h_begin <= 0;
end

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      h_end <= 0;
   else if({pre_hsync,hsync} == 2'b10)
      h_end <= 1;
   else
      h_end <= 0;
end

endmodule

五、摄像头初始化模块

上一个随笔写了
https://www.cnblogs.com/cnlntr/p/14412096.html

六、以太网发送模块

上一个随笔写了
https://www.cnblogs.com/cnlntr/p/14431114.html

七、顶层模块

module ov5640_udp_gmii(
   rst_n          ,
   clk            ,
   camera_rst_n   ,
   camera_scl     ,
   camera_sda     ,
   camera_clk_in  ,
   camera_vsync   ,
   camera_hsync   ,
   camera_data    ,
   gmii_clk       ,
   gmii_en        ,
   gmii_tx_data   ,
   camera_clk24M  ,
   eth_rst_n      ,
   led            
);

parameter IMAGE_WIDTH   =  1280;
parameter IMAGE_HEIGHT  =  720;

parameter DST_MAC   = 48'hB0_6E_BF_02_11_C8;
parameter SRC_MAC   = 48'h00_0a_35_01_fe_c0;
parameter DST_IP    = 32'hc0_a8_00_03;
parameter SRC_IP    = 32'hc0_a8_00_02;
parameter DST_PORT  = 16'd6000;
parameter SRC_PORT  = 16'd5000;

localparam CAM_DATA_W   =  8;
localparam PIX_DATA_W   =  16;
localparam VADDR_W      =  12;
localparam DOUT_W       =  8;
localparam GMII_DATA_W  =  8;
localparam LED_W        =  2;

input                      rst_n;
input                      clk;
input                      camera_clk_in;
input                      camera_vsync;
input                      camera_hsync;
input    [CAM_DATA_W-1:0]  camera_data;

output                     camera_rst_n;
output                     camera_scl;
output                     gmii_clk;
output                     gmii_en;
output   [GMII_DATA_W-1:0] gmii_tx_data;
output                     camera_clk24M;
output                     eth_rst_n;
output   [LED_W-1:0]       led;

inout                      camera_sda;

wire                       camera_rst_n;
wire                       camera_scl;
wire                       gmii_clk;
wire                       gmii_en;
wire     [GMII_DATA_W-1:0] gmii_tx_data;
wire                       camera_clk24M;
wire                       eth_rst_n;
wire     [2-1:0]           led;

wire                       camera_sda;

//中间变量
wire                       rst_p;

//模块连线
wire                       clk50M;
wire                       clk125M;
wire                       clk24M;
wire                       locked;
wire     [PIX_DATA_W-1:0]  pix_data;
wire     [VADDR_W-1:0]     vaddr;
wire                       h_begin;
wire                       data_vld;
wire     [PIX_DATA_W-1:0]  camera_data_out;
wire                       wr_en;
wire                       eth_data_vld;
wire                       eth_tx_done;
wire                       eth_tx_en;
wire     [DOUT_W-1:0]      fifo_dout;
wire                       init_done;

assign rst_p         = locked;
assign camera_clk24M = clk24M;
assign eth_rst_n     = locked;

assign led = {locked,init_done};

// wire clk300M;
// ila ila (
// 	.clk(clk300M), // input wire clk


// 	.probe0(camera_clk_in), // input wire [0:0]  probe0  
// 	.probe1(1'b0), // input wire [0:0]  probe1 
// 	.probe2(1'b0), // input wire [0:0]  probe2 
// 	.probe3(1'b0), // input wire [0:0]  probe3 
// 	.probe4(16'b0), // input wire [15:0]  probe4 
// 	.probe5(1'b0), // input wire [0:0]  probe5 
// 	.probe6(1'b0), // input wire [0:0]  probe6 
// 	.probe7(1'b0), // input wire [0:0]  probe7 
// 	.probe8(1'b0), // input wire [0:0]  probe8 
// 	.probe9(1'b0) // input wire [0:0]  probe9
// );


pll pll(
   .clk_out1(clk50M) , 
   .clk_out2(clk125M),  
   .clk_out3(clk24M) ,
   // .clk_out4(clk300M),
   .resetn(rst_n)    ,
   .locked(locked)   ,
   .clk_in1(clk)
);

camera_init #(
   .IMAGE_WIDTH   (IMAGE_WIDTH)  ,
   .IMAGE_HEIGHT  (IMAGE_HEIGHT)
)
camera_init(
   .clk         (clk50M),
   .rst_n       (rst_p),
   .init_done   (init_done),     //摄像头初始化完成标志信号
   .camera_rst_n(camera_rst_n),
   .camera_pwdn (),              //ov5640掉电控制信号
   .scl         (camera_scl),    //sccb 时钟信号
   .sda         (camera_sda)     //sccb 串行数据信号
);

BUFG BUFG_inst(
   .O(camera_clk_out),
   .I(camera_clk_in)
);

dvp_capture  #(
   .IMAGE_WIDTH   (IMAGE_WIDTH)  ,
   .IMAGE_HEIGHT  (IMAGE_HEIGHT)
)
dvp_capture(
   .rst_n       (rst_p),
   .clk         (camera_clk_out),
   .vsync       (camera_vsync),
   .hsync       (camera_hsync),
   .data        (camera_data),
   .pix_data    (pix_data),
   .image_state (),
   .haddr       (),
   .vaddr       (vaddr),
   .h_begin     (h_begin),
   .h_end       (),
   .data_vld    (data_vld)
);


camera_image_sequence camera_image_sequence(
   .clk         (camera_clk_out),
   .rst_n       (rst_p),
   .data_vld_in (data_vld),
   .data_in     (pix_data),
   .h_begin_in  (h_begin),
   .vaddr_in    (vaddr),
   .data_out    (camera_data_out),
   .wr_en       (wr_en)
);

fifo_ctr #(
   .IMAGE_WIDTH   (IMAGE_WIDTH)  ,
   .IMAGE_HEIGHT  (IMAGE_HEIGHT)
)
fifo_ctr(
   .rst_n       (rst_p),
   .wr_clk      (camera_clk_out),
   .rd_clk      (clk125M),
   .data_in     (camera_data_out),
   .wr_en       (wr_en),
   .eth_data_vld(eth_data_vld),
   .eth_tx_done (eth_tx_done),
   .eth_tx_en   (eth_tx_en),
   .dout        (fifo_dout)
);

eth_udp_tx_gmii eth_udp_tx_gmii(
   .clk      (clk125M),
   .rst_n    (rst_p),
   .tx_en    (eth_tx_en),
   .tx_done  (eth_tx_done),
   .dst_mac  (DST_MAC),
   .src_mac  (SRC_MAC),
   .dst_ip   (DST_IP),
   .src_ip   (SRC_IP),
   .dst_port (DST_PORT),
   .src_port (SRC_PORT),
   .gmii_clk (gmii_clk),
   .data_len ((IMAGE_WIDTH<<1) + 2),
   .data_vld (eth_data_vld),
   .gmii_en  (gmii_en),
   .gmii_tx  (gmii_tx_data),
   .data_in  (fifo_dout)
);

endmodule

八、仿真验证文件

`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2021/02/24 19:03:39
// Design Name: 
// Module Name: ov5640_udp_gmii_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module ov5640_udp_gmii_tb();

parameter WIDTH      =  1280;
parameter HEIGHT     =  720;

localparam CAM_DATA_W   =  8;
localparam PIX_DATA_W   =  16;
localparam HADDR_W      =  12;
localparam VADDR_W      =  12;
localparam DOUT_W       =  8;
localparam GMII_DATA_W  =  8;
localparam LED_W        =  2;


reg                      rst_n;
reg                      clk;
reg                      camera_clk_in;
reg                      camera_vsync;
reg                      camera_hsync;
reg    [CAM_DATA_W-1:0]  camera_data;

wire                     camera_rst_n;
wire                     camera_scl;
wire                     gmii_clk;
wire                     gmii_en;
wire   [GMII_DATA_W-1:0] gmii_tx_data;
wire                     camera_clk24M;
wire                     eth_rst_n;
wire   [LED_W-1:0]       led;

wire                     camera_sda;

ov5640_udp_gmii #(
   .IMAGE_WIDTH(WIDTH)  ,
   .IMAGE_HEIGHT(HEIGHT)
)
ov5640_udp_gmii(
   .rst_n          (rst_n),
   .clk            (clk),
   .camera_rst_n   (camera_rst_n),
   .camera_scl     (camera_scl),
   .camera_sda     (camera_sda),
   .camera_clk_in  (camera_clk_in),
   .camera_vsync   (camera_vsync),
   .camera_hsync   (camera_hsync),
   .camera_data    (camera_data),
   .gmii_clk       (gmii_clk),
   .gmii_en        (gmii_en),
   .gmii_tx_data   (gmii_tx_data),
   .camera_clk24M  (camera_clk24M),
   .eth_rst_n      (eth_rst_n),
   .led            (led)
);

//时钟周期,单位ns,在这里修改时钟周期
parameter CYCLE = 20;
parameter CAM_CYCLE = 26;

//复位时间,此时表示复位3个时钟周期的时间
parameter RST_TIME = 3;

//生成本地时钟50M
initial begin
   clk = 0;
   forever
   #(CYCLE/2)
   clk=~clk;
end

initial begin
   camera_clk_in = 0;
   forever
   #(CAM_CYCLE/2)
   camera_clk_in=~camera_clk_in;
end

//产生复位信号
initial begin
   rst_n = 1;
   #2;
   rst_n = 0;
   #(CYCLE * RST_TIME);
   rst_n = 1;
end

integer i = 0;
integer j = 0;
//输入信号din0赋值方式
initial begin
   #1;
   camera_data = 8'hff;
   camera_vsync = 0;
   camera_hsync = 0;
   #(10*CYCLE);
   //开始赋值,模拟前10帧图像不稳定
   repeat(3)begin
      camera_vsync = 1;
      #(10*CYCLE)
      camera_vsync = 0;
      #(20*CYCLE)
      for(i = 0;i < 6;i = i + 1)begin
         for(j = 0;j < (5<<1);j = j + 1)begin
            camera_hsync = 1;
            camera_data = camera_data - 1;
            #80;
         end
         camera_hsync = 0;
         #(20*CYCLE);
      end
   end
   repeat(3)begin
      camera_vsync = 1;
      #(10*CYCLE)
      camera_vsync = 0;
      #(20*CYCLE)
      for(i = 0;i < 2;i = i + 1)begin
         for(j = 0;j < (5<<1);j = j + 1)begin
            camera_hsync = 1;
            camera_data = camera_data - 1;
            #80;
         end
         camera_hsync = 0;
         #(20*CYCLE);
      end
   end

   repeat(15)begin
      camera_vsync = 1;
      #(10*CYCLE)
      camera_vsync = 0;
      #(20*CYCLE)
      for(i = 0;i < HEIGHT;i = i + 1)begin
         for(j = 0;j < (WIDTH<<1);j = j + 1)begin
            camera_hsync = 1;
            camera_data = camera_data - 1;
            #80;
         end
         camera_hsync = 0;
         #(20*CYCLE);
      end
   end
   #1000;
   $stop;
end
endmodule

推荐阅读