PotatoPie 4.0 实验教程(18) —— FPGA实现OV5640摄像头采集以SDRAM作为显存进行HDMI输出显示-Anlogic-安路论坛-FPGA CPLD-ChipDebug

PotatoPie 4.0 实验教程(18) —— FPGA实现OV5640摄像头采集以SDRAM作为显存进行HDMI输出显示

手机扫码

20240416075513933-1713225291635

链接直达

https://item.taobao.com/item.htm?ft=t&id=776516984361

上一教程讲完了SDRAM作为缓存HDMI显示,因为摄像头与HDMI TX的时钟不同频帧率也不一样,所以也需要SDRAM缓存一下,只是把上一课的测试pattern换成video_tpg_24b,但是为了工程的完整性我们没有直接替换掉video_tpg_24b而是用了一个宏TEST_PATTERN来进行测试pattern和摄像头的切换,TEST_PATTERN默认不使能。

20240406154413466-image

工程解析

工程层次图

20240406154614690-image

图中其它模块都跟上一教程相同不再介绍,只是增加了红框中的三个模块。这三个模块主要功能如下:

  • u_i2c_master —— 由于ov5640需要通过IIC配置其寄存器才能输出我们所需格式的图像,该模块就是一个IIC Master控制器。
  • u_ov5640_rgb565_cfg——这个模块驱动u_i2c_master配置OV5640,它控制配置寄存器的流程,并输出配置完成信号。
  • u_cmos_data_cvt——这个模块主要是将OV5640输出的两个8bit的RGB信号拼接成RGB565信号

代码分析

u_i2c_master 

module i2c_master
    #(// slave address(器件地址),放此处方便参数传递
      parameter   SLAVE_ADDR =  7'b1010000   ,
      parameter   CLK_FREQ   = 27'd50_000_000,   // i2c_dri模块的驱动时钟频率(CLK_FREQ)
      parameter   I2C_FREQ   = 18'd250_000       // I2C的SCL时钟频率
     )(
          //global clock
          input                clk        ,      // i2c_dri模块的驱动时钟(CLK_FREQ)
          input                rst_n      ,      // 复位信号

          //i2c interface
          input                i2c_exec   ,      // I2C触发执行信号
          input                bit_ctrl   ,      // 字地址位控制(16b/8b)
          input                i2c_rh_wl  ,      // I2C读写控制信号
          input        [15:0]  i2c_addr   ,      // I2C器件内地址
          input        [ 7:0]  i2c_data_w ,      // I2C要写的数据
          output  reg  [ 7:0]  i2c_data_r ,      // I2C读出的数据
          output  reg          i2c_done   ,      // I2C一次操作完成
          output  reg          scl        ,      // I2C的SCL时钟信号
          inout                sda        ,      // I2C的SDA信号

          //user interface
          output  reg          dri_clk           // 驱动I2C操作的驱动时钟
     );

...
endmodule

模块的端口在代码中有比较详细的注释,主要注意以下几点:

  • bit_ctrl是设置地址位是8位还是16位,OV5640是16位因此需要设置为1。
  • i2c_exec是用来发起一次I2C操作,相当于读或者写一个OV5640的寄存器,这个信号由u_ov5640_rgb565_cfg。
  • i2c_done是告诉一次I2C操作已完成,相当于完成一次OV5640的寄存器读或者写操作,告诉u_ov5640_rgb565_cfg可以进行下一次操作了。

整个代码就是一个大状态机,代码中注释很详细就不过多解释了。

u_ov5640_rgb565_cfg

这个模块的代码的逻辑就是通地init_reg_cnt计数器,将250个寄存器的值通过u_i2c_master 挨个输出。

u_cmos_data_cvt

这个模块首先是对OV5640输出的VS信号进行然行边沿检测,并对VS的边沿计数,目的是扔掉OV5640前面不稳定数据帧。

然后就是将8位数据转16位RGB565数据,注意为了让数据有效信号cmos_frame_valid跟数据的中间对齐,将这个信号的寄存器byte_flag 多延长了一拍。

管脚约束

管脚约束部分主要添加了摄像头的管脚,都是LVCOM33即可。

摄像头的管脚分布在原理图这个地方,coms_x这个脚是预留的,摄像头上是NC的,所以可以不管。

20240406161559978-image

set_pin_assignment	{ cam_data[0] }	{ LOCATION = P70; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_data[1] }	{ LOCATION = P71; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_data[2] }	{ LOCATION = P72; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_data[3] }	{ LOCATION = P74; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_data[4] }	{ LOCATION = P75; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_data[5] }	{ LOCATION = P76; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_data[6] }	{ LOCATION = P77; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_data[7] }	{ LOCATION = P78; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_hsync }	{ LOCATION = P59; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_pclk }	{ LOCATION = P79; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_pwdn }	{ LOCATION = P87; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_rst_n }	{ LOCATION = P66; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_scl }	{ LOCATION = P57; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ cam_sda }	{ LOCATION = P62; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }
set_pin_assignment	{ cam_vsync }	{ LOCATION = P52; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }


set_pin_assignment	{ clk_10m }	{ LOCATION = P11; IOSTANDARD = LVCMOS33; PULLTYPE = NONE; }
set_pin_assignment	{ i_rst_n }	{ LOCATION = P83; IOSTANDARD = LVCMOS33; PULLTYPE = PULLUP; }

#HDMI TX
set_pin_assignment	{ HDMI_CLK_P }	{ LOCATION = P47; IOSTANDARD = LVDS33; }
set_pin_assignment	{ HDMI_D0_P }	{ LOCATION = P55; IOSTANDARD = LVDS33; }
set_pin_assignment	{ HDMI_D1_P }	{ LOCATION = P61; IOSTANDARD = LVDS33; }
set_pin_assignment	{ HDMI_D2_P }	{ LOCATION = P64; IOSTANDARD = LVDS33; }

时序约束

#Created Clock
create_clock -name clk_10m -period 100 [get_ports {clk_10m}]

# Create camera clock
create_clock -name cam_pclk -period 20.8333 [get_ports {cam_pclk}]

# Automatically constrain PLL and other generated clocks
derive_pll_clocks
rename_clock -name {clk_sdr_ref} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[0]}]
rename_clock -name {clk_sdr_out} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[1]}]
rename_clock -name {clk_pixel} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[2]}]
rename_clock -name {clk_pixel_5x} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[3]}]
rename_clock -name {clk_i2c_36m} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[4]}]

# 
rename_clock -name {clk_pll4cam} -source [get_ports {cam_pclk}] -master_clock {cam_pclk} [get_pins {u_pll4cam/pll_inst.clkc[0]}]


# Group constrain
set_clock_groups -exclusive -group [get_clocks {clk_10m}] -group [get_clocks {clk_i2c_36m}] -group [get_clocks {cam_pclk}] -group [get_clocks {clk_pll4cam}] -group [get_clocks {clk_sdr_out}] -group [get_clocks {clk_sdr_ref}] -group [get_clocks {clk_pixel clk_pixel_5x}]

创建系统时钟,即板载的10M时钟:

create_clock -name clk_10m -period 100 [get_ports {clk_10m}]

创建摄像头的时钟,OV5640当前配置为48M:

create_clock -name cam_pclk -period 20.8333 [get_ports {cam_pclk}]

让TD工具自动推导PLL的约束:

derive_pll_clocks

对PLL生成的钟进行重命名,方便后面的约束。

rename_clock -name {clk_sdr_ref} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[0]}]
rename_clock -name {clk_sdr_out} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[1]}]
rename_clock -name {clk_pixel} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[2]}]
rename_clock -name {clk_pixel_5x} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[3]}]
rename_clock -name {clk_i2c_36m} -source [get_ports {clk_10m}] -master_clock {clk_10m} [get_pins {u_system_ctrl_pll/u_sys_pll/pll_inst.clkc[4]}]

对摄像头进来的时钟进PLL之后进行重命名处理

rename_clock -name {clk_pll4cam} -source [get_ports {cam_pclk}] -master_clock {cam_pclk} [get_pins {u_pll4cam/pll_inst.clkc[0]}]

设置工程中的时钟全部为异步,因为跨时钟的数据都做了跨时钟域处理,不需要工具分析。

set_clock_groups -exclusive -group [get_clocks {clk_10m}] -group [get_clocks {clk_i2c_36m}] -group [get_clocks {cam_pclk}] -group [get_clocks {clk_pll4cam}] -group [get_clocks {clk_sdr_out}] -group [get_clocks {clk_sdr_ref}] -group [get_clocks {clk_pixel clk_pixel_5x}]

实验效果

20240323174147122-微信图片_20240323153258

20240406221809287-1712412889201

请登录后发表评论

    没有回复内容