当前位置:首页 > FPGA > 正文内容

Tang Nano 4K摄像头驱动例程与原理讲解 解决显示问题

chanra1n11个月前 (01-03)FPGA1558

介绍

Tang Nano 4K是基于高云半导体的小蜜蜂系列 GW1NSR-LV4C 设计的简约型开发板。开发板设计小巧精致,将芯片的所有资源都引出,板载Type-C、USB-JTAG、DVP、HDMI座子及其电路等,并把所有IO资源引出,方便开发者拓展使用,非常适用于小型数字逻辑的设计和实验。

image.png

image.png

原理图分析

由于我们要做的是从摄像头读取数据,然后显示到HDMI上,所以重点关注相关部分原理图

image.png

首先是摄像头部分,看得出来是一个非常标准的Ref设计拉过来的,意味着通用的OV2640等Demo是可以直接移植使用的。

image.png

然后是HDMI部分,电源部分居然额外加了磁珠。

Demo分析

从官方https://github.com/sipeed/TangNano-4K-example 把工程Git了下来,这里安装Git和Gowin的部分省略。

image.png

首先我们需要打开这个摄像头的工程,由于实际测试发现直接使用Demo会无法在显示屏上显示图像。

分析有以下两个原因:
1、烧录后,不掉电复位只是对FPGA进行复位,导致复位不完全。

2、时序问题,或者烧录失败。

image.png

直接编译布局布线是通过了,并且显示是绿色的,分析发现资源也是够用的

image.png

但是仔细一看,存在部分时序是不满足的(PS,我并没有在哪里找到多种工况)

image.png

尤其是这个像素时钟不满足,还是会导致HDMI和摄像头相关逻辑出问题。仔细一看FalsePath,差额还比较大,考虑到资源使用情况不大,应当是这些时钟驱动不动了。

那么怎么解决呢?一般来说,如果需求确定的情况下,无法几种解决方法:1.氪金战士,更换性能更强,工艺更高的器件。2.删除不必要的逻辑。3.打拍。4.回家吃饭

这里我们选择2,把那个测试输出部分删了就行,咱们是要摄像头显示到屏幕的。

我把没用的部分删除了,然后添加了部分注释,帮助读者理解

module video_top (
    input        I_clk,            // 输入时钟信号 27MHz
    input        I_rst_n,          // 输入复位信号
    output [1:0] O_led,            // 输出LED信号
    inout        SDA,              // SCCB接口数据线
    inout        SCL,              // SCCB接口时钟线
    input        VSYNC,            // 输入垂直同步信号
    input        HREF,             // 输入水平参考信号
    input  [9:0] PIXDATA,          // 输入像素数据
    input        PIXCLK,           // 输入像素时钟
    output       XCLK,             // 输出时钟信号
    output [0:0] O_hpram_ck,       // 输出Hyperram时钟
    output [0:0] O_hpram_ck_n,     // 输出Hyperram时钟取反
    output [0:0] O_hpram_cs_n,     // 输出Hyperram片选信号取反
    output [0:0] O_hpram_reset_n,  // 输出Hyperram复位信号取反
    inout  [7:0] IO_hpram_dq,      // 输入/输出Hyperram数据线
    inout  [0:0] IO_hpram_rwds,    // 输入/输出Hyperram读写使能信号
    output       O_tmds_clk_p,     // 输出TMDS时钟正极性信号
    output       O_tmds_clk_n,     // 输出TMDS时钟负极性信号
    output [2:0] O_tmds_data_p,    // 输出TMDS数据正极性信号({r,g,b})
    output [2:0] O_tmds_data_n,    // 输出TMDS数据负极性信号({r,g,b})

    input key  // 输入按键信号
);

  //==================================================
  reg  [31:0] run_cnt;  // 运行计数器
  wire        running;   // 运行标志位

  //--------------------------
  wire        tp0_vs_in;  // TP0垂直同步输入
  wire        tp0_hs_in;  // TP0水平同步输入
  wire        tp0_de_in;  // TP0数据使能输入

  //--------------------------
  reg  [ 9:0] pixdata_d1;  // 上一个像素数据
  reg         hcnt;        // 水平计数器
  wire [15:0] cam_data;    // 摄像头数据

  //-------------------------
  // 帧缓冲器输入
  wire        ch0_vfb_clk_in;   // 通道0帧缓冲器时钟输入
  wire        ch0_vfb_vs_in;    // 通道0帧缓冲器垂直同步输入
  wire        ch0_vfb_de_in;    // 通道0帧缓冲器数据使能输入
  wire [15:0] ch0_vfb_data_in;  // 通道0帧缓冲器数据输入

  //-------------------
  // 同步码
  wire        syn_off0_re;   // 偏移同步码读使能信号
  wire        syn_off0_vs;   // 偏移同步码垂直同步信号
  wire        syn_off0_hs;   // 偏移同步码水平同步信号

  wire        off0_syn_de;   // 偏移同步码数据使能信号
  wire [15:0] off0_syn_data;  // 偏移同步码数据

  //-------------------------------------
  // Hyperram
  wire        dma_clk;       // DMA时钟信号

  wire        memory_clk;    // 存储器时钟信号
  wire        mem_pll_lock;  // 存储器PLL锁定信号

  //-------------------------------------------------
  // 存储器接口
  wire        cmd;           // 存储器命令信号
  wire        cmd_en;        // 存储器命令使能信号
  wire [21:0] addr;          // 存储器地址
  wire [31:0] wr_data;       // 存储器写数据
  wire [ 3:0] data_mask;     // 存储器数据掩码
  wire        rd_data_valid;  // 存储器读数据有效信号
  wire [31:0] rd_data;       // 存储器读数据
  wire        init_calib;    // 初始化校准信号

  //------------------------------------------
  // RGB数据
  wire        rgb_vs;        // RGB垂直同步信号
  wire        rgb_hs;        // RGB水平同步信号
  wire        rgb_de;        // RGB数据使能信号
  wire [23:0] rgb_data;      // RGB数据

  //------------------------------------
  // HDMI TX
  wire        serial_clk;    // 串行时钟信号
  wire        pll_lock;      // PLL锁定信号

  wire        hdmi_rst_n;    // HDMI复位信号

  wire        pix_clk;       // 像素时钟信号

  wire        clk_12M;       // 12MHz时钟信号

  //===================================================
  // LED测试
  always @(posedge I_clk or negedge sys_resetn)
  begin
    if (!sys_resetn) run_cnt <= 32'd0;
    else if (run_cnt >= 32'd27_000_000) run_cnt <= 32'd0;
    else run_cnt <= run_cnt + 1'b1;
  end

  assign running = (run_cnt < 32'd13_500_000) ? 1'b1 : 1'b0;  // 如果运行计数小于一半,设置运行标志位

  assign O_led[0] = running;        // LED0显示运行状态
  assign O_led[1] = ~init_calib;    // LED1显示初始化校准状态

  assign XCLK = clk_12M;            // 设置输出时钟信号

  // 相机复位
  Reset_Sync u_Reset_Sync (
      .resetn(sys_resetn),
      .ext_reset(I_rst_n & pll_lock),
      .clk(I_clk)
  );

  //==============================================================================
  OV2640_Controller u_OV2640_Controller (
      .clk            (clk_12M),  // 24Mhz时钟信号
      .resend         (1'b0),     // 复位信号
      .config_finished(),         // 配置完成标志
      .sioc           (SCL),      // SCCB接口 - 时钟信号
      .siod           (SDA),      // SCCB接口 - 数据信号
      .reset          (),         // OV7670复位信号
      .pwdn           ()          // OV7670电源控制信号
  );

  always @(posedge PIXCLK or negedge sys_resetn)
  begin
    if (!sys_resetn) pixdata_d1 <= 10'd0;
    else pixdata_d1 <= PIXDATA;
  end

  always @(posedge PIXCLK or negedge sys_resetn)
  begin
    if (!sys_resetn) hcnt <= 1'd0;
    else if (HREF) hcnt <= ~hcnt;
    else hcnt <= 1'd0;
  end

  // assign cam_data = {pixdata_d1[9:5],pixdata_d1[4:2],PIXDATA[9:7],PIXDATA[6:2]}; //RGB565
  // assign cam_data = {PIXDATA[9:5],PIXDATA[4:2],pixdata_d1[9:7],pixdata_d1[6:2]}; //RGB565

  assign cam_data = {PIXDATA[9:5], PIXDATA[9:4], PIXDATA[9:5]};  // RAW10

  //==============================================
  // 数据宽度16位
  assign ch0_vfb_clk_in = key_flag ? I_clk : PIXCLK;
  assign ch0_vfb_vs_in = key_flag ? ~tp0_vs_in : VSYNC;  // 取反
  assign ch0_vfb_de_in = key_flag ? tp0_de_in : HREF;   // hcnt;
  assign ch0_vfb_data_in = cam_data;  // RGB565

  key_flag key_flag_inst (
      .clk(I_clk),
      .rst_n(I_rst_n),
      .key(key),
      .key_flag(key_flag)
  );

  //=====================================================
  // SRAM 控制模块
  Video_Frame_Buffer_Top Video_Frame_Buffer_Top_inst (
      .I_rst_n           (init_calib),       // 复位信号
      .I_dma_clk         (dma_clk),          // 存储器时钟信号
      .I_wr_halt         (1'd0),             // 写入暂停,0表示不暂停
      .I_rd_halt         (1'd0),             // 读取暂停,0表示不暂停
      // 视频数据输入
      .I_vin0_clk        (ch0_vfb_clk_in),
      .I_vin0_vs_n       (ch0_vfb_vs_in),
      .I_vin0_de         (ch0_vfb_de_in),
      .I_vin0_data       (ch0_vfb_data_in),
      .O_vin0_fifo_full  (),
      // 视频数据输出
      .I_vout0_clk       (pix_clk),
      .I_vout0_vs_n      (~syn_off0_vs),
      .I_vout0_de        (syn_off0_re),
      .O_vout0_den       (off0_syn_de),
      .O_vout0_data      (off0_syn_data),
      .O_vout0_fifo_empty(),
      // DDR写请求
      .O_cmd             (cmd),
      .O_cmd_en          (cmd_en),
      .O_addr            (addr),             //[ADDR_WIDTH-1:0]
      .O_wr_data         (wr_data),          //[DATA_WIDTH-1:0]
      .O_data_mask       (data_mask),
      .I_rd_data_valid   (rd_data_valid),
      .I_rd_data         (rd_data),          //[DATA_WIDTH-1:0]
      .I_init_calib      (init_calib)
  );

  //================================================
  // HyperRAM ip
  GW_PLLVR GW_PLLVR_inst (
      .clkout(memory_clk),    // 输出clkout
      .lock  (mem_pll_lock),  // 输出锁定信号
      .clkin (I_clk)          // 输入时钟信号
  );

  HyperRAM_Memory_Interface_Top HyperRAM_Memory_Interface_Top_inst (
      .clk            (I_clk),
      .memory_clk     (memory_clk),
      .pll_lock       (mem_pll_lock),
      .rst_n          (sys_resetn),       // 复位信号
      .O_hpram_ck     (O_hpram_ck),
      .O_hpram_ck_n   (O_hpram_ck_n),
      .IO_hpram_rwds  (IO_hpram_rwds),
      .IO_hpram_dq    (IO_hpram_dq),
      .O_hpram_reset_n(O_hpram_reset_n),
      .O_hpram_cs_n   (O_hpram_cs_n),
      .wr_data        (wr_data),
      .rd_data        (rd_data),
      .rd_data_valid  (rd_data_valid),
      .addr           (addr),
      .cmd            (cmd),
      .cmd_en         (cmd_en),
      .clk_out        (dma_clk),
      .data_mask      (data_mask),
      .init_calib     (init_calib)
  );
  //================================================
  wire out_de;
  syn_gen syn_gen_inst (
      .I_pxl_clk (pix_clk),      //40MHz      //65MHz      //74.25MHz
      .I_rst_n   (hdmi_rst_n),   //800x600    //1024x768   //1280x720
      .I_h_total (16'd1650),     // 16'd1056  // 16'd1344  // 16'd1650
      .I_h_sync  (16'd40),       // 16'd128   // 16'd136   // 16'd40
      .I_h_bporch(16'd220),      // 16'd88    // 16'd160   // 16'd220
      .I_h_res   (16'd1280),     // 16'd800   // 16'd1024  // 16'd1280
      .I_v_total (16'd750),      // 16'd628   // 16'd806   // 16'd750
      .I_v_sync  (16'd5),        // 16'd4     // 16'd6     // 16'd5
      .I_v_bporch(16'd20),       // 16'd23    // 16'd29    // 16'd20
      .I_v_res   (16'd720),      // 16'd600   // 16'd768   // 16'd720
      .I_rd_hres (16'd640),
      .I_rd_vres (16'd480),
      .I_hs_pol  (1'b1),         //HS polarity , 0:负极性,1:正极性
      .I_vs_pol  (1'b1),         //VS polarity , 0:负极性,1:正极性
      .O_rden    (syn_off0_re),
      .O_de      (out_de),
      .O_hs      (syn_off0_hs),
      .O_vs      (syn_off0_vs)
  );

  localparam N = 2;  //delay N clocks

  reg [N-1:0] Pout_hs_dn;
  reg [N-1:0] Pout_vs_dn;
  reg [N-1:0] Pout_de_dn;

  always @(posedge pix_clk or negedge hdmi_rst_n) begin
    if (!hdmi_rst_n) begin
      Pout_hs_dn <= {N{1'b1}};
      Pout_vs_dn <= {N{1'b1}};
      Pout_de_dn <= {N{1'b0}};
    end else begin
      Pout_hs_dn <= {Pout_hs_dn[N-2:0], syn_off0_hs};
      Pout_vs_dn <= {Pout_vs_dn[N-2:0], syn_off0_vs};
      Pout_de_dn <= {Pout_de_dn[N-2:0], out_de};
    end
  end

  //==============================================================================
  //TMDS TX
  assign rgb_data    = off0_syn_de ? {off0_syn_data[15:11],3'd0,off0_syn_data[10:5],2'd0,off0_syn_data[4:0],3'd0} : 24'h0000ff;//{r,g,b}
  assign rgb_vs = Pout_vs_dn[N-1];  //syn_off0_vs;
  assign rgb_hs = Pout_hs_dn[N-1];  //syn_off0_hs;
  assign rgb_de = Pout_de_dn[N-1];  //off0_syn_de;


  TMDS_PLLVR TMDS_PLLVR_inst (
        .clkin  (I_clk)       //input clk
      , .clkout (serial_clk)  //output clk
      , .clkoutd(clk_12M)     //output clkoutd
      , .lock   (pll_lock)    //output lock
  );

  assign hdmi_rst_n = sys_resetn & pll_lock;

  CLKDIV u_clkdiv (
      .RESETN(hdmi_rst_n),
      .HCLKIN(serial_clk)  //clk  x5
      , .CLKOUT(pix_clk)  //clk  x1
      , .CALIB(1'b1)
  );
  defparam u_clkdiv.DIV_MODE = "5";

  DVI_TX_Top DVI_TX_Top_inst (
      .I_rst_n      (hdmi_rst_n),       //asynchronous reset, low active
      .I_serial_clk (serial_clk),
      .I_rgb_clk    (pix_clk),          //pixel clock
      .I_rgb_vs     (rgb_vs),
      .I_rgb_hs     (rgb_hs),
      .I_rgb_de     (rgb_de),
      .I_rgb_r      (rgb_data[23:16]),
      .I_rgb_g      (rgb_data[15:8]),
      .I_rgb_b      (rgb_data[7:0]),
      .O_tmds_clk_p (O_tmds_clk_p),
      .O_tmds_clk_n (O_tmds_clk_n),
      .O_tmds_data_p(O_tmds_data_p),    //{r,g,b}
      .O_tmds_data_n(O_tmds_data_n)
  );

endmodule

module Reset_Sync (
    input  clk,
    input  ext_reset,
    output resetn
);

  reg [3:0] reset_cnt = 0;

  always @(posedge clk or negedge ext_reset) begin
    if (~ext_reset) reset_cnt <= 4'b0;
    else reset_cnt <= reset_cnt + !resetn;
  end

  assign resetn = &reset_cnt;

endmodule

module key_flag #(
    parameter clk_frequency = 27_000_000,
    parameter io_num        = 1
) (
    input  clk,      // Clock in
    input  rst_n,
    input  key,
    output key_flag
);

  parameter count_ms = clk_frequency / 1000;

  parameter count_20ms = count_ms * 20 - 1;
  parameter count_500ms = count_ms * 500 - 1;

  reg [($clog2(count_20ms)-1)+10:0] count_20ms_reg;
  reg [$clog2(count_500ms)-1:0] count_500ms_reg;

  // key flag

  reg key_input = 1'd1;

  always @(posedge clk) begin
    key_input <= ~key;
  end

  reg key_flag;

  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) count_20ms_reg <= 'd0;
    else if (key_input) count_20ms_reg <= count_20ms_reg + 'd1;
    else if (count_20ms_reg >= count_20ms) begin
      key_flag = ~key_flag;
      count_20ms_reg <= 'd0;
      ;
    end else count_20ms_reg <= 'd0;
    ;
  end

endmodule

然后就是约束文件也得修改,参考高云官方的设计时序约束手册,说真的,连示例都没有,搞起来真费劲,易用性和兼容性有待提高。

image.png

我写了多年的新思和赛灵思系列产品的约束,如PT和Vivado这些,写高云这个动不动就莫名其妙的报错。。。确实让人头疼和莫名其妙。

修改之后重新编译,资源使用也少了,时序也够用了↓

image.png

image.png

image.png

最后烧录上板测试,通过!接下来我们将在这块开发板上介绍图像处理相关应用。

image.png

为了方便大家进行测试,可以直接使用本压缩包的工程进行烧录:camera_hdmi_mdf.zip


扫描二维码推送至手机访问。

版权声明:本文由我的FPGA发布,如需转载请注明出处。

本文链接:https://myfpga.cn/index.php/post/345.html

分享给朋友:

“Tang Nano 4K摄像头驱动例程与原理讲解 解决显示问题” 的相关文章

ALGO C4MB V11引脚参照表(持续更新)

ALGO C4MB V11引脚参照表(持续更新)

功能:常用引脚CLKPIN_E1LED0PIN_G15LED1PIN_F16LED2PIN_F15LED3PIN_D16KEY1PIN_E15KEY2PIN_E16KEY3PIN_M15KEY4PIN_M16RXDPIN_M2TXDPIN_G1功能:VGA引脚VGA_BLUE[0]PIN_C15VG...

SOC 在线修改设备树和FPGA配置文件 并在线配置FPGA

SOC 在线修改设备树和FPGA配置文件 并在线配置FPGA

测试过的平台:     1、DE-10 Cyclone V开发板              ...

Verilog实现时钟分频(奇数分频,偶数分频)二分频 三分频 四分频 五分频

Verilog实现时钟分频(奇数分频,偶数分频)二分频 三分频 四分频 五分频

完整工程文件:clkdiv.zip//------------------------------------------------------// File Name        : clkdiv.v// Author     &nb...

Xilinx FIFO和ILA学习

Xilinx FIFO和ILA学习

`timescale 1ns / 1ps//-------------------------------------------------------//Filename       ﹕ FIFO_TOP.v//Author      ...

Verilog实现串并转换

Verilog实现串并转换

项目文件:SIPO.zip//------------------------------------------------------// File Name        : SIPO.v// Author       &n...