ZYNQ调试——OV2640的HDMI显示

本文围绕FPGA开发中OV2640摄像头系统展开。介绍了OV2640模块及管脚,阐述第三方IP核移植,如OV2640和HDMI驱动IP核。详细说明block design设计、连线、管脚约束、SDK工程创建及工程下载,还给出遇到问题的解决办法,如SCCB无法读ID等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

OV260简介

摄像头采用正点原子的OV2640模块,该模块采用OmniVision公司生产的OV2640图像传感器,分辨率最高为1600*1200,向下兼容多种分辨率(SXGA、SVGA 以及按比例缩小到从 SXGA 到 40*30 的任何尺寸),图像输出格式有JPEG 、RGB565、RGB555、YUV(422/420)等多种格式。

模块管脚

在这里插入图片描述

SDA和SCL管脚组成SCCB总线,用于控制OV2640的寄存器。
D0-D7为OV2640的图像数据输出管脚,通过PCLK、VSYN核HERF控制时序,PCLK下降沿时更新图像数据,PCLK为上升沿时读取数据,VSYNC输出的是帧时序,HERF输出的是行时序。
RST管脚控制模块的硬件复位,当RST为低电平时复位模块。
PWDN管脚控制模块的电源,当PWDN为低电平时,模块正常工作,当PWDN为高电平时,模块进入低功耗模式。

第三方IP核

在这个系统中需要使用第三方核自定义的IP核,所以要提前准备。

OV2640 IP核

与OV7725和OV5640相比,OV2640的资料相当少,也没有相关的例程和IP核可以直接使用,所以需要自己手动移植。

  1. 不难发现OV7725和OV5640采用的都是DVP并口传输,并且OV2640的管脚在一定程度上还兼容OV5640,所以可以直接将其代码移植过来。
module  ov2640_capture_data(
    input                 rst_n           ,  

    input                 cam_pclk        ,  //pclk输入 
    input                 cam_vsync       ,  //vsync输入
    input                 cam_href        ,  //herf输入
    input        [7:0]    cam_data        ,  //像素数据输入
    output                cam_pwdn        ,  //pwdn输出

	//RGB888接口
    output                cmos_frame_clk,    
    output                cmos_frame_ce,     
	output                cmos_frame_vsync,  
    output                cmos_frame_href ,  
    output                cmos_frame_de ,    
    output       [23:0]   cmos_frame_data    
);
    //等待10帧之后才输出
    localparam  WAIT_FRAME = 4'd10  ;            
    reg          rst_n_d0 =1;
    reg          rst_n_syn =1;
    reg          cam_vsync_d0 ;
    reg          cam_vsync_d1 ;
    reg          cam_href_d0 ;
    reg          cam_href_d1 ;
    reg   [3:0]  cmos_ps_cnt ;                  
    reg          wait_done ;
    reg          byte_flag ;
    reg   [7:0]  cam_data_d0 ;
    reg  [15:0]  cmos_data_16b ;                
    reg          byte_flag_d0 ;

    //wire define
    wire  pos_vsync ;
    assign  pos_vsync  = (~cam_vsync_d1) & cam_vsync_d0 ;
    assign  cam_pwdn  = 1'b0;
    assign  cmos_frame_clk   = cam_pclk;
    assign  cmos_frame_ce    = wait_done  ?  (byte_flag_d0 & cmos_frame_href) || (!cmos_frame_href) : 1'b0;
    assign  cmos_frame_vsync = wait_done  ?  cam_vsync_d1  :  1'b0;
    assign  cmos_frame_href  = wait_done  ?  cam_href_d1   :  1'b0;
    assign  cmos_frame_de    = cmos_frame_href ;
    assign  cmos_frame_data  = wait_done  ?
        { cmos_data_16b[15:11],3'd0 , cmos_data_16b[10:5],2'd0 , cmos_data_16b[4:0],3'd0 }
        :  24'd0; 

    always @(posedge cam_pclk or negedge rst_n) begin
        if(!rst_n) begin
            rst_n_d0 <= 1'b0;
            rst_n_d0 <= 1'b0;
        end
        else begin
            rst_n_d0  <= 1'b1;
            rst_n_syn <= rst_n_d0;
        end
    end

    always @(posedge cam_pclk or negedge rst_n_syn) begin
        if(!rst_n_syn) begin
            cam_vsync_d0 <= 1'b0;
            cam_vsync_d1 <= 1'b0;

            cam_href_d0 <= 1'b0;
            cam_href_d1 <= 1'b0;
        end
        else begin
            cam_vsync_d0 <= cam_vsync;
            cam_vsync_d1 <= cam_vsync_d0;

            cam_href_d0 <= cam_href;
            cam_href_d1 <= cam_href_d0;
        end
    end

    always @(posedge cam_pclk or negedge rst_n_syn) begin
        if(!rst_n_syn)
            cmos_ps_cnt <= 4'd0;
        else if(pos_vsync && (cmos_ps_cnt < WAIT_FRAME))
            cmos_ps_cnt <= cmos_ps_cnt + 4'd1;  
    end

    always @(posedge cam_pclk or negedge rst_n_syn) begin
        if(!rst_n_syn)
            wait_done <= 1'b0;
        else if((cmos_ps_cnt == WAIT_FRAME) && pos_vsync)
            wait_done <= 1'b1;
    end

    always @(posedge cam_pclk or negedge rst_n_syn) begin
        if(!rst_n_syn) begin
            cmos_data_16b <= 16'd0;
            cam_data_d0 <= 8'd0;
            byte_flag <= 1'b0;
        end
        else if( cam_href ) begin 
            byte_flag   <= ~byte_flag;
            cam_data_d0 <= cam_data;
            if(byte_flag)
                cmos_data_16b <= {cam_data_d0,cam_data};
        end
        else begin
            byte_flag <= 1'b0;
            cam_data_d0 <= 8'b0;
        end
    end

    always @(posedge cam_pclk or negedge rst_n_syn) begin
        if(!rst_n_syn)
            byte_flag_d0 <= 1'b0;
        else
            byte_flag_d0 <= byte_flag;
    end
endmodule

可以参考正点原子的领航者的OV5640_HDMI实验,但是需要注意的是,这里将cam_rst_n接口移除,让PS端直接控制,因为不进行硬件复位的话,发现SCCB很难读取摄像头的ID,所以通过PS端控制,可以简单地控制复位时间。
这个模块是为了采集摄像头数据,并且将RGB565数据转换为RGB888数据,对于RGB565转RGB888,其实有两种方法:

  • 末位补0,就和当前代码一致:
    {R[5:0],{0,0,0},G[6:0],{0,0},B[5:0],{0,0,0}}
  • 用各自的末位补末位,即:
    {R[5:0],R[2:0],G[6:0],R[1:0],B[5:0],B[2:0]}
  1. 管脚配置
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    这个配置可有可无,这里进行管脚配置的意思是将多个管脚组合成某一种类型的接口,这里组合成vid_io_rtl接口,将该接口所需的管脚与实际管脚相映射,当有其它有相同类型接口的IP核时,可以直接接口相连,接口下的实际管脚会自动相连,所以也可以不做这一步,只需记得这些管脚对应的映射关系,当遇到那些IP核时,自己手动将一个个管脚连接起来即可。
    需要修改的就上述两个地方了,其余可以参考IP核封装的教程。
    在这里插入图片描述
    生成IP核即可。

HDMI驱动IP核

这个IP核可以直接去找别人写好的,正点原子的领航者源码中有提供,或者从这里下载
这个IP核实现将RGB888数据转换为HDMI所需要的DVI编码数据,然后通过HDMI接口输出视频信号,需要注意的是,它无需实现时序控制。
因为我所使用的板子是使用PL端资源实现DVI编码的,所以需要这个IP核,有一些开发板使用ADV7511等芯片实现HDMI编码,所以那些就需要另外移植相应的驱动了。

block design设计

此时需要搞清楚自己需要什么IP核

ARM核

在这里插入图片描述

  1. 设置DDR内存,这个根据对应的硬件进行设置即可

  2. 找到Clock Configuration , 在PL Fabric Clocks 设置自己需要的时钟频率,设置50M时钟,然后增加FCLK_CLK1 150M(用于AXI4 HP高速接口用)
    在这里插入图片描述

  3. 在PS-PL接口部分,增加 AXI HP 高速接口
    在这里插入图片描述

  4. (可选)增加对应的SD卡,只是为了后面的固化程序,可以不选,这个也建议参考对应的硬件设计

  5. (可选)增加对应的UART,只是方便调试,建议选上。(我的板子的UART是连接到PL端的,所以只能使用EMIO)
    在这里插入图片描述

  6. 引出三个EMIO,分别用于SCCB的SCL和SDA,以及被我从OV2640 IP核移除的cam_rst_n。
    在这里插入图片描述

HDMI IP核

将上述HDMI驱动IP核(DVI编码器)添加进来,添加方法参考网上教程。

时钟 IP核

DVI编码需要两个时钟,正点原子为了兼容性,使用动态时钟的方法兼容更多的分辨率,需要引入另外一个第三方IP核,如果有需要建议下载其领航者源码查看,而此处已经明确是800*600分辨率,所以直接使用一个MMCM(PLL)模块即可,输出时钟为40M和200M,后者必须是前者的5倍,前者需要根据输出分辨率决定。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于此时输入改为了FCLK0,所以时钟输入应该与此频率一致,但一般此模块都是接到板载晶振上。

video out IP核

由于图像数据在DDR内存中是以AXI数据流的形式存储,而DVI编码需要的是RGB888的数据,因此需要一个模块实现数据格式的转换。
在这里插入图片描述
在这里插入图片描述

唯一需要更改的是将将其时钟源独立出来,Clock Mode代表aclk与vid_io_out_clk两个时钟是否同源。因为HDMI 的显示和aclk的输入这里的时钟域并不同(HDMI显示的像素时钟和ACLK的数据流时钟不同),所以这里选择independent。让两个时钟相互独立。
Timing Mode:因为后面要用video timing controller 模块来提供视频时序,所以这里选择slave模式。

video timing controller IP核

由于DVI编码器没有实现时序控制,因此此处需要一个时序控制模块。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果比较过正点原子的教程,这里会有差别,首先是这里没有勾选include AXI-Lite Interface以及这里需要将video mode选定为800x600p,这是因为正点原子采用了动态时钟的方法,在软件上检测和修改分辨率,然后通过AXI-Lite Interface将结果传输给这个IP核,然后这个IP核再根据传输的结果更改分辨率,由于我的时钟已经固定,不能在软件上进行更改,所以直接取消该接口,并且固定了时序。

VDMA模块

这个模块是负责搬运图像数据的,既可以将摄像头的图像数据搬运到DDR内存,也可以将DDR内存中的图像数据搬运出去,从而减轻ARM处理器的负担。
在这里插入图片描述
在这里插入图片描述
Frame Buffer是图像缓存帧数,大一点可以减轻图像撕裂。
Write channel和Read channel一个负责写入,一个负责读取,因此假如只是为了显示PS端的数据(从DDR读取数据进行显示),则只需要一个Read channel即可。因为我们显示的内容是RGB888,所以将系统默认的stream Data Width 从32位改成24位。 Line Buffer Depth默认512(只要AXI总线的速率大于显示的数据流的速率,缓存可以改更小),Read Burst Size 修改成64。

反相器

因为video out IP核的复位是高电平,与其它模块的低电平复位不一样,所以需要增加一个反相器。
在这里插入图片描述
在这里插入图片描述

video in IP核

与video out IP核相反,因为摄像头IP核产生的是RGB888的数据,需要一个模块将这些数据转换为AXI Stream数据,让VDMA可以进行搬运。
在这里插入图片描述
在这里插入图片描述

OV2640 IP核

直接增加进来即可,不需要修改,增加方式可以查看vivado添加自定义IP核的教程。

连线

这一步建议搞清楚为什么要这样连。

  1. 数据流连线
    在这里插入图片描述
    引出摄像头IP核的相应的接口,点击对应的管脚,选择Make External即可,如果OV2640的IP核封装处做了RGB接口的管脚映射,则此处可以直接将RGB接口与video in IP核的对应接口连接即可,否则需要展开管脚一一对应连接。
    在这里插入图片描述

    RGB888数据被Video in模块转换为AXI Stream数据后,交由VDMA模块将其搬运至内存之中,
    在这里插入图片描述

    图中的两根红线代表的就是VDMA模块读写DDR内存的数据通道,但是这两根线无需手动连接,当其它模块连接好之后,点击Run connection automation 就可以自动生成,现在只需要关注图中的绿线。
    在这里插入图片描述

    至此就大致演示了数据的连线了。

  2. 时钟连线
    在这里插入图片描述

    首先连接好摄像头IP核输出的帧时钟,让Video in 模块将每一帧都转换为AXI数据流。
    在这里插入图片描述
    这些aclk信号是用来同步的,并且都同属于高速的FCLK1(150M)
    在这里插入图片描述

    而这一部分则是低速的。
    在这里插入图片描述

    这个40M输出是给视频时序控制的。
    在这里插入图片描述

    这个200M是给DVI编码的。

  3. 复位连线
    在这里插入图片描述

    由于时钟模块的locked信号表示时钟模块是否稳定输出,所以以这个信号作为其它模块的复位信号,只有当时钟稳定输出之后,其它模块结束复位状态。
    在这里插入图片描述
    上述说了video out的复位信号是高电平复位,所以需要再与locked信号相连之前要经过一个反相器。

  4. 余下需要连线的信号
    此时剩下的都是一些数据使能、时钟使能、时序控制信号了。
    在这里插入图片描述

在这里插入图片描述

  1. 自动连线
    点 Run connection automation 来自动连接剩下的走线,并且将一些必要的端口引出。

  2. 总结
    在这里插入图片描述

  3. 生成代码
    首先验证一下,点击validate design
    在这里插入图片描述

    验证通过后生成hdl代码,create hdl wrapper
    在这里插入图片描述

管脚约束

这里有两个需要注意的事项,第一点是SCCB管脚的约束,需要对SDA管脚进行上拉配置,否则无法读取到摄像头的ID,也无法进行寄存器配置。
在这里插入图片描述

对于上图,记住你的管脚分配,因为[0]对应的是第54,[1]对应的是第55,[2]对应的是第56,在SDK编写C语言的时候需要用到,所以此处注意分配的管脚。
然后就是一个cam_pclk的时序约束,在对应的xdc文件里面填入下面的语句。将使用Make External将cam_pclk引出时的网络名字填入,我这里是cam_pclk_0。

create_clock -period 13.888 -name cam_pclk_0 [get_ports cam_pclk_0]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_0_IBUF]

其它管脚的约束可以参考HDMI、UART的约束方法。
最后生成bit流,并生成硬件平台给SDK使用。

SDK工程

工程就是简单的空项目,然后新建一个

#include"emio_sccb_cfg.h"
#define  GPIOPS_ID  XPAR_XGPIOPS_0_DEVICE_ID  //PS 端 GPIO 器件 ID
#define sccb_delay_utime 50
static  XGpioPs  gpiops_inst; //PS 端 GPIO 驱动实例
//EMIO初始化
void emio_init(void)
{
	XGpioPs_Config *gpiops_cfg_ptr; //PS 端 GPIO 配置信息
	//根据器件 ID 查找配置信息
	gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID);
	//初始化器件驱动
	XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr);
	//设置 sccb端口 为输出
	XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_SCL_NUM, 1);
	XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_SDA_NUM, 1);
	//RST
	XGpioPs_SetDirectionPin(&gpiops_inst, 56, 1);
	XGpioPs_SetOutputEnablePin(&gpiops_inst, 56, 1);
	XGpioPs_WritePin(&gpiops_inst, 56, 0);
	usleep(200000);
	//使能sccb端口 输出
	XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_SCL_NUM, 1);
	XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_SDA_NUM, 1);
	//将sccb的SCLK和SDA都拉高
	XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 1);
	XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, 1);
	XGpioPs_WritePin(&gpiops_inst, 56, 1);
	usleep(1000000);
}
//产生sccb起始信号
void sccb_start(void)
{
	XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 1);
	XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, 1);
	usleep(sccb_delay_utime);
 	XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, 0);  //START:when        CLK is high,DATA change form high to low
 	usleep(sccb_delay_utime);
 	XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 0);  //钳住I2C总线,准备发送或接收数据
}
//产生sccb停止信号
void sccb_stop(void)
{
	XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 0);
	XGpioPs_WritePin(&gpiops_inst,EMIO_SDA_NUM, 0);  //STOP:when CLK is high DATA change form low to high
 	usleep(sccb_delay_utime);
	XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 1);
	usleep(sccb_delay_utime);
	XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, 1);  //发送I2C总线结束信号
}
//sccb发送一个字节
void sccb_send_byte(u8 txd)
{
    u8 t;
    XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 0);  //拉低时钟开始数据传输
    for(t=0; t<8; t++)
    {
        XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, (txd&0x80)>>7);
        txd<<=1;
        usleep(sccb_delay_utime);
        XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 1);
        usleep(sccb_delay_utime);
        XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 0);
        usleep(sccb_delay_utime);
    }
}
//SCCB接收一个字节
u8  sccb_rece_byte(void)
{
	unsigned char i=0 , rxd=0;
	XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_SDA_NUM, 0);
	XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_SDA_NUM, 0);
	XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 0);
	usleep(sccb_delay_utime);
	for(i=0;i<8;i++ )
	{
        XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 1);
        usleep(sccb_delay_utime);
        rxd <<= 1;
        if( XGpioPs_ReadPin(&gpiops_inst, EMIO_SDA_NUM) ) {
        	rxd = rxd | 0x01;
        }
		usleep(2);
		XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 0);
		usleep(sccb_delay_utime);
    }
	XGpioPs_SetDirectionPin(&gpiops_inst, EMIO_SDA_NUM, 1);  //SDA设置为输出
	XGpioPs_SetOutputEnablePin(&gpiops_inst, EMIO_SDA_NUM, 1);
    return rxd;
}
//产生ACK应答
void sccb_ack(void)
{
	XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 0);
	XGpioPs_WritePin(&gpiops_inst, EMIO_SDA_NUM, 0);
	usleep(sccb_delay_utime);
	XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 1);
	usleep(sccb_delay_utime);
	XGpioPs_WritePin(&gpiops_inst, EMIO_SCL_NUM, 0);
	usleep(sccb_delay_utime);
}
//SCCB写寄存器
void sccb_write_reg8(u8 addr , u8 data)
{
	sccb_start();
	sccb_send_byte(OV2640_DEV_ID);
	sccb_ack();
	sccb_send_byte(addr);
	sccb_ack();
	sccb_send_byte(data);
	sccb_ack();
  	sccb_stop();
  	usleep(1000);
}
//SCCB读寄存器
u8 sccb_read_reg8(u8 addr )
{
	u8 rxd;
	sccb_start();
	sccb_send_byte(OV2640_DEV_ID);
	sccb_ack();
	sccb_send_byte(addr);
	sccb_ack();
  	sccb_stop();
  	sccb_start();
	sccb_send_byte(OV2640_DEV_ID | 0x01);
	sccb_ack();
	rxd = sccb_rece_byte();
	sccb_ack();
  	sccb_stop();
  	return  rxd ;
}

对应的.h头文件

#include"xgpiops.h"
#include"sleep.h"
#include "xil_printf.h"
#ifndef sccb_EMIO_CFG_
#define sccb_EMIO_CFG_
#define EMIO_SCL_NUM 54//这里的数字与管脚约束有关
#define EMIO_SDA_NUM 55
#define  OV2640_DEV_ID    0X60   //OV2640 SCCB器件地址
void emio_init(void);
void sccb_start(void);
void sccb_stop(void);
void sccb_ack(void);
void sccb_send_byte(u8 txd);
u8  sccb_rece_byte(void);
u8 sccb_read_reg8(u8 addr );
void sccb_write_reg8(u8 addr , u8 data);
#endif /* sccb_EMIO_CFG_ */

以上两个头文件实现了SCCB通信,以及对应的管脚初始化,其中也包含了RST管脚的初始化。
下面就是摄像头的初始化(ov2640_init.c):

#include "../emio_sccb_cfg/emio_sccb_cfg.h"
#include "../ov2640/ov2640_init.h"
#include "../ov2640/reg_init_cfg.h"
//OV7725初始化
u8 ov2640_init(void)
{
    u16 cam_id = 0;
    //读OV2640摄像头ID
    cam_id = sccb_read_reg8(0X1c);
	cam_id<<=8;
	cam_id |= sccb_read_reg8(0x1d);
	xil_printf("cam_id:%x\r\n",cam_id);
    cam_id = sccb_read_reg8(0x0A);
	cam_id<<=8;
    cam_id |= sccb_read_reg8(0x0B);
    xil_printf("cam_id:%x\r\n",cam_id);
    if(cam_id != 0X2642)  //获取到正确的OV2640 ID
        return 1;
    else{
		reg_init();
		return 0;
    }    
}

reg_init_cfg.c文件的内容,为什么会出现这个文件呢?因为这些寄存器配置文件是在OV2640软件手册里面直接复制出来的,所以为了方便,直接粘贴过来,然后使用define将对应的写寄存器函数替换为已有的。

#include "../ov2640/reg_init_cfg.h"
#define write_SCCB sccb_write_reg8
void reg_init(void)
{
	write_SCCB(0xff, 0x01);
	write_SCCB(0x12, 0x80);
	usleep(5000);
	write_SCCB(0xff, 0x00);
	write_SCCB(0x2c, 0xff);
	write_SCCB(0x2e, 0xdf);
	write_SCCB(0xff, 0x01);
	write_SCCB(0x3c, 0x32);
	//
	write_SCCB(0x11, 0x00);
	write_SCCB(0x09, 0x02);
	write_SCCB(0x04, 0xd8);
	write_SCCB(0x13, 0xe5);
	write_SCCB(0x14, 0x48);
	write_SCCB(0x2c, 0x0c);
	write_SCCB(0x33, 0x78);
	write_SCCB(0x3a, 0x33);
	write_SCCB(0x3b, 0xfB);
	//
	write_SCCB(0x3e, 0x00);
	write_SCCB(0x43, 0x11);
	write_SCCB(0x16, 0x10);
	//
	write_SCCB(0x39, 0x92);
	//
	write_SCCB(0x35, 0xda);
	write_SCCB(0x22, 0x1a);
	write_SCCB(0x37, 0xc3);
	write_SCCB(0x23, 0x00);
	write_SCCB(0x34, 0xc0);
	write_SCCB(0x36, 0x1a);
	write_SCCB(0x06, 0x88);
	write_SCCB(0x07, 0xc0);
	write_SCCB(0x0d, 0x87);
	write_SCCB(0x0e, 0x41);
	write_SCCB(0x4c, 0x00);
	write_SCCB(0x48, 0x00);
	write_SCCB(0x5B, 0x00);
	write_SCCB(0x42, 0x03);
	//
	write_SCCB(0x4a, 0x81);
	write_SCCB(0x21, 0x99);
	//
	write_SCCB(0x24, 0x40);
	write_SCCB(0x25, 0x38);
	write_SCCB(0x26, 0x82);
	write_SCCB(0x5c, 0x00);
	write_SCCB(0x63, 0x00);
	write_SCCB(0x46, 0x22);
	write_SCCB(0x0c, 0x3c);
	//
	write_SCCB(0x61, 0x70);
	write_SCCB(0x62, 0x80);
	write_SCCB(0x7c, 0x05);
	//
	write_SCCB(0x20, 0x80);
	write_SCCB(0x28, 0x30);
	write_SCCB(0x6c, 0x00);
	write_SCCB(0x6d, 0x80);
	write_SCCB(0x6e, 0x00);
	write_SCCB(0x70, 0x02);
	write_SCCB(0x71, 0x94);

	write_SCCB(0x73, 0xc1);
	//
	write_SCCB(0x12, 0x40);
	write_SCCB(0x17, 0x11);
	write_SCCB(0x18, 0x43);
	write_SCCB(0x19, 0x00);
	write_SCCB(0x1a, 0x4b);
	write_SCCB(0x32, 0x09);
	write_SCCB(0x37, 0xc0);
	write_SCCB(0x4f, 0xca);
	write_SCCB(0x50, 0xa8);
	write_SCCB(0x5a, 0x23);
	write_SCCB(0x6d, 0x00);
	write_SCCB(0x3d, 0x38);
	//
	write_SCCB(0xff, 0x00);
	write_SCCB(0xe5, 0x7f);
	write_SCCB(0xf9, 0xc0);
	write_SCCB(0x41, 0x24);
	write_SCCB(0xe0, 0x14);
	write_SCCB(0x76, 0xff);
	write_SCCB(0x33, 0xa0);
	write_SCCB(0x42, 0x20);
	write_SCCB(0x43, 0x18);
	write_SCCB(0x4c, 0x00);
	write_SCCB(0x87, 0xd5);
	write_SCCB(0x88, 0x3f);
	write_SCCB(0xd7, 0x03);
	write_SCCB(0xd9, 0x10);
	write_SCCB(0xd3, 0x82);
	//
	write_SCCB(0xc8, 0x08);
	write_SCCB(0xc9, 0x80);
	//色彩饱和度
	write_SCCB(0x7c, 0x00);
	write_SCCB(0x7d, 0x00);
	write_SCCB(0x7c, 0x03);
	write_SCCB(0x7d, 0x48);//此处原本是48
	write_SCCB(0x7d, 0x28);
	write_SCCB(0x7c, 0x08);
	write_SCCB(0x7d, 0x20);
	write_SCCB(0x7d, 0x10);
	write_SCCB(0x7d, 0x0e);
	//
	write_SCCB(0x90, 0x00);
	write_SCCB(0x91, 0x0e);
	write_SCCB(0x91, 0x1a);
	write_SCCB(0x91, 0x31);
	write_SCCB(0x91, 0x5a);
	write_SCCB(0x91, 0x69);
	write_SCCB(0x91, 0x75);
	write_SCCB(0x91, 0x7e);
	write_SCCB(0x91, 0x88);
	write_SCCB(0x91, 0x8f);
	write_SCCB(0x91, 0x96);
	write_SCCB(0x91, 0xa3);
	write_SCCB(0x91, 0xaf);
	write_SCCB(0x91, 0xc4);
	write_SCCB(0x91, 0xd7);
	write_SCCB(0x91, 0xe8);
	write_SCCB(0x91, 0x20);
	//
	write_SCCB(0x92, 0x00);
	write_SCCB(0x93, 0x06);
	write_SCCB(0x93, 0xe3);
	write_SCCB(0x93, 0x05);
	write_SCCB(0x93, 0x05);
	write_SCCB(0x93, 0x00);
	write_SCCB(0x93, 0x04);
	write_SCCB(0x93, 0x00);
	write_SCCB(0x93, 0x00);
	write_SCCB(0x93, 0x00);
	write_SCCB(0x93, 0x00);
	write_SCCB(0x93, 0x00);
	write_SCCB(0x93, 0x00);
	write_SCCB(0x93, 0x00);
	//
	write_SCCB(0x96, 0x00);
	write_SCCB(0x97, 0x08);
	write_SCCB(0x97, 0x19);
	write_SCCB(0x97, 0x02);
	write_SCCB(0x97, 0x0c);
	write_SCCB(0x97, 0x24);
	write_SCCB(0x97, 0x30);
	write_SCCB(0x97, 0x28);
	write_SCCB(0x97, 0x26);
	write_SCCB(0x97, 0x02);
	write_SCCB(0x97, 0x98);
	write_SCCB(0x97, 0x80);
	write_SCCB(0x97, 0x00);
	write_SCCB(0x97, 0x00);
	//
	write_SCCB(0xc3, 0xed);
	write_SCCB(0xa4, 0x00);
	write_SCCB(0xa8, 0x00);
	write_SCCB(0xc5, 0x11);
	write_SCCB(0xc6, 0x51);
	write_SCCB(0xbf, 0x80);
	write_SCCB(0xc7, 0x10);
	write_SCCB(0xb6, 0x66);
	write_SCCB(0xb8, 0xA5);
	write_SCCB(0xb7, 0x64);
	write_SCCB(0xb9, 0x7C);
	write_SCCB(0xb3, 0xaf);
	write_SCCB(0xb4, 0x97);
	write_SCCB(0xb5, 0xFF);
	write_SCCB(0xb0, 0xC5);
	write_SCCB(0xb1, 0x94);
	write_SCCB(0xb2, 0x0f);
	write_SCCB(0xc4, 0x5c);
	//
	write_SCCB(0xc0, 0x64);
	write_SCCB(0xc1, 0x4B);
	write_SCCB(0x8c, 0x00);
	write_SCCB(0x86, 0x3D);
	write_SCCB(0x50, 0x00);
	write_SCCB(0x51, 0xC8);
	write_SCCB(0x52, 0x96);
	write_SCCB(0x53, 0x00);
	write_SCCB(0x54, 0x00);
	write_SCCB(0x55, 0x00);
	write_SCCB(0x5a, 0xC8);
	write_SCCB(0x5b, 0x96);
	write_SCCB(0x5c, 0x00);
	write_SCCB(0xd3, 0x82);
	//
	write_SCCB(0xc3, 0xed);
	write_SCCB(0x7f, 0x00);
	//
	write_SCCB(0xda, 0x08);
	//
	write_SCCB(0xe5, 0x1f);
	write_SCCB(0xe1, 0x67);
	write_SCCB(0xe0, 0x00);
	write_SCCB(0xdd, 0x7f);
	write_SCCB(0x05, 0x00);
}

然后就是对应的两个.h文件了:
ov2640_init.h

#include "xil_types.h"
#include"sleep.h"
#ifndef _OV2640_INIT_H_
#define _OV2640_INIT_H_
u8 ov2640_init(void);
#endif /* OV5640_INIT_H_ */

reg_init_cfg.h

#ifndef SRC_OV2640_REG_INIT_CFG_H_
#define SRC_OV2640_REG_INIT_CFG_H_
#include "../emio_sccb_cfg/emio_sccb_cfg.h"
void reg_init(void);
#endif /* SRC_OV2640_REG_INIT_CFG_H_ */

然后就剩下main函数了

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xil_types.h"
#include "xil_cache.h"
#include "xparameters.h"
#include "xaxivdma.h"
#include "xaxivdma_i.h"
#include "emio_sccb_cfg/emio_sccb_cfg.h"
#include "ov2640/ov2640_init.h"

#define VDMA_ID          XPAR_AXIVDMA_0_DEVICE_ID    //VDMA器件ID
unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR + 0x1000000);
XAxiVdma     vdma;
#define  GPIOPS_ID  XPAR_XGPIOPS_0_DEVICE_ID  //PS 端 GPIO 器件 ID
#define WIDTH	800
#define DEPTH	600
int run_triple_frame_buffer(XAxiVdma* InstancePtr, int DeviceId, int hsize,
		int vsize, int buf_base_addr, int number_frame_count,
		int enable_frm_cnt_intr);
int main(void)
{
	u32 status;
	emio_init();               //初始化EMIO
	status = ov2640_init();    //初始化ov7725
	if(status == 0)
		xil_printf("OV2640 detected successful!\r\n");
	else
		xil_printf("OV2640 detected failed!\r\n");
	//配置VDMA
	run_triple_frame_buffer(&vdma, VDMA_ID,WIDTH,DEPTH,frame_buffer_addr,0,0);
    return 0;
}

需要注意的是run_triple_frame_buffer这个函数需要导入一个例程才能使用,导入方法:
在这里插入图片描述

找到对应的system.mss文件,双击。
在这里插入图片描述

选择对应的工程。
在这里插入图片描述

然后在工程管理里面找到vdma_api.c,将其复制到原来工程的src文件夹下面。
在这里插入图片描述

最后将刚刚导入的例程删除即可,因为只是需要其中一个vdma_api.c而已。
在这里插入图片描述

工程的文件结构如下:
在这里插入图片描述

由于main函数核ov2640初始化函数里面调用了xil_printf函数,建议开启一个串口给该函数进行输出,否则建议删除相关语句。

工程下载

记得设置RUN AS的配置,在烧录的时候初始化FPGA部分。
在这里插入图片描述

总结

  1. 如果遇到SCCB无法读取摄像头ID的情况,记得检查对应的管脚约束,是否上拉了SDA管脚、是否设置对了管脚。
  2. 如果综合时对xdc报错,可以检查管脚名字是否正确,以及记得及时将没有的、多的管脚移除,还有加上cam_pclk的时序约束,需要注意的是,只要填入给出的两条语句即可,有时候vivado会自己生成多一条相似的语句,如果造成报错,请认真仔细检查。
  3. 正点原子对于OV7725和OV5640的硬件初始化都不进行,直接给高电平,但是在我的测试中,OV2640如果不进行初始化就无法读取摄像头ID,所以才将复位管脚通过EMIO的方式输出。
  4. 需要注意摄像头的SCCB器件地址,如果摄像头ID读取失败,可以检查一下器件地址是否正确。
  5. 如果遇到画面偏某一种颜色,可以考虑使用ILA抓取对应的信号,看看是后级模块出错还是它就是这样的输入,从而定位问题。
  6. 没事少用OV2640,一个豪威科技自己都放弃的摄像头模组,咱们就少掺和了。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值