本帖最后由 正点原子运营 于 2023-12-20 15:57 编辑
第四十章 OV7725摄像头HDMI显示实验
1)实验平台:正点原子 ATK-DFPGL22G开发板
2) 章节摘自【正点原子】ATK-DFPGL22G之FPGA开发指南_V1.0
6)FPGA技术交流QQ群:435699340
在OV7725摄像头RGB-LCD显示实验中,成功地在LCD屏上实时显示出了摄像头采集的图像。本章将使用FPGA开发板实现对OV7725的数字图像采集并在HDMI显示器上实时显示。 本章包括以下几个部分: 1.1 简介 1.2 实验任务 1.3 硬件设计 1.4 程序设计 1.5 下载验证
1.1 简介在“OV7725摄像头RGB-LCD显示实验”中对OV7725的视频传输时序、SCCB协议以及寄存器的配置信息等内容作了详细的介绍,如果大家对这部分内容不是很熟悉的话,请参考“OV7725摄像头RGB-LCD显示实验”中的简介部分。
1.2 实验任务本节实验任务是使用ATK-DFPGL22G开发板及OV7725摄像头实现图像采集,通过HDMI接口驱动HDMI显示器,并实时显示出图像。
1.3 硬件设计摄像头扩展接口原理图及OV7725模块说明与“OV7725摄像头RGB-LCD显示实验”完全相同,请参考“OV7725摄像头RGB-LCD显示实验”硬件设计部分。HDMI接口部分的硬件设计请参考“HDMI彩条显示实验”中的硬件设计部分。 由于OV7725、HDMI接口和DDR3引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里不再列出管脚分配。
1.4 程序设计图 40.4.1是根据本章实验任务画出的系统框图。对比“OV7725摄像头RGB-LCD显示实验”的系统框图可以发现,本次实验只是把LCD顶层模块替换成了HDMI顶层模块,将图像采集顶层模块中的图像裁剪模块去掉了,其余模块(除时钟模块外)完全相同。之所以在本节实验中把裁剪模块去掉,是因为本次实验所用的HDMI显示器的分辨率大于摄像头的分辨率,所以只需要在HDMI视频显示模块稍微改一下读数据请求即可,改动部分下文会给大家讲解。时钟模块用于为I2C驱动模块、HDMI顶层模块以及DDR3控制模块提供驱动时钟;I2C驱动模块和I2C配置模块用于初始化OV7725图像传感器;摄像头采集模块负责采集摄像头图像数据,并把图像数据写入DDR3控制模块中;DDR3控制模块负责将用户数据写入和读出片外DDR3存储器;HDMI顶层模块负责驱动HDMI显示器。本章使用的HDMI显示器分辨率为1280*720,而OV7725传感器的最大分辨率为640*480,所以HDMI显示器需要填充黑色的像素点。 由上图可知,本节实验中时钟模块pll_clk为DDR3控制模块、I2C驱动模块提供驱动时钟以及为HDMI模块提供像素时钟和5倍像素时钟。I2C配置模块和I2C驱动模块控制着传感器初始化的开始与结束,传感器初始化完成后图像采集模块将采集到的数据写入DDR3控制模块,HDMI顶层模块从DDR3控制模块中读出数据并驱动显示器显示,这时整个系统才完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在DDR3和传感器都初始化完成之后才开始输出数据的,避免了在DDR3初始化过程中向里面写入数据。 顶层模块代码如下所示: - 1 module ov7725_hdmi(
- 2 input sys_clk ,
- 3 input sys_rst_n ,
- 4
- 5 output tmds_clk_p , // TMDS 时钟通道
- 6 output tmds_clk_n ,
- 7 output [2:0 tmds_data_p , // TMDS 数据通道
- 8 output [2:0 tmds_data_n ,
- 9 //摄像头接口
- 10 input cam_pclk , //cmos 数据像素时钟
- 11 input cam_vsync , //cmos 场同步信号
- 12 input cam_href , //cmos 行同步信号
- 13 input [7:0 cam_data , //cmos 数据
- 14 output cam_rst_n , //cmos 复位信号,低电平有效
- 15 output cam_sgm_ctrl , //cmos 时钟选择信号, 1:使用摄像头自带的晶振
- 16 output cam_scl , //cmos SCCB_SCL线
- 17 inout cam_sda , //cmos SCCB_SDA线
- 18 //DDR3接口
- 19 input pad_loop_in , //低位温度补偿输入
- 20 input pad_loop_in_h , //高位温度补偿输入
- 21 output pad_rstn_ch0 , //Memory复位
- 22 output pad_ddr_clk_w , //Memory差分时钟正
- 23 output pad_ddr_clkn_w , //Memory差分时钟负
- 24 output pad_csn_ch0 , //Memory片选
- 25 output [15:0 pad_addr_ch0 , //Memory地址总线
- 26 inout [16-1:0 pad_dq_ch0 , //数据总线
- 27 inout [16/8-1:0 pad_dqs_ch0 , //数据时钟正端
- 28 inout [16/8-1:0 pad_dqsn_ch0 , //数据时钟负端
- 29 output [16/8-1:0 pad_dm_rdqs_ch0 , //数据Mask
- 30 output pad_cke_ch0 , //Memory差分时钟使
- 31 output pad_odt_ch0 , //On Die Terminati
- 32 output pad_rasn_ch0 , //行地址strobe
- 33 output pad_casn_ch0 , //列地址strobe
- 34 output pad_wen_ch0 , //写使能
- 35 output [2:0 pad_ba_ch0 , //Bank地址总线
- 36 output pad_loop_out , //低位温度补偿输出
- 37 output pad_loop_out_h //高位温度补偿输出
- 38 );
- 39
- 40 //parameter define
- 41 parameter SLAVE_ADDR = 7'h21 ; //OV7725的器件地址7'h21
- 42 parameter BIT_CTRL =1'b0 ; //OV7725的字节地址为8位 0:8位 1:16位
- 43 parameter CLK_FREQ =27'd50_000_000; //i2c_dri模块的驱动时钟频率
- 44 parameter I2C_FREQ =18'd250_000 ;//I2C的SCL时钟频率,不超过400KHz
- 45 parameter APP_ADDR_MIN = 28'd0 ; //ddr3读写起始地址,以一个16bit的数据为一个单位
- 46 parameter APP_ADDR_MAX = 28'd307200 ; //ddr3读写结束地址,以一个16bit的数据为一个单位
- 47 parameter BURST_LENGTH = 8'd80 ; //ddr3读写突发长度,80个128bit的数据
- 48
- 49 //wire define
- 50 //PLL
- 51 wire pixel_clk ; //像素时钟75M
- 52 wire pixel_clk_5x ; //5倍像素时钟375M
- 53 wire clk_50m ; //output 50M
- 54 wire clk_locked ;
- 55 //OV7725
- 56 wire i2c_dri_clk ; //I2C操作时钟
- 57 wire i2c_done ; //I2C寄存器配置完成信号
- 58 wire i2c_exec ; //I2C触发执行信号
- 59 wire [15:0 i2c_data ; //I2C要配置的地址与数据(高8位地址,低8位数据)
- 60 wire cam_init_done ; //摄像头初始化完成
- 61 wire cmos_frame_vsync; //帧有效信号
- 62 wire cmos_frame_href ; //行有效信号
- 63 wire cmos_frame_valid; //数据有效使能信号
- 64 wire [15:0 wr_data ; //OV7725写入DDR3控制器模块的数据
- 65 //HDMI
- 66 wire video_vs ; //场同步信号
- 67 wire [15:0 rd_data ; //DDR3控制器模块读数据给HDMI
- 68 wire rdata_req ; //DDR3控制器模块读使能
- 69 //DDR3
- 70 wire fram_done ; //DDR中已经存入一帧画面标志
- 71 wire ddr_init_done ; //ddr3初始化完成
- 72
- 73 //*****************************************************
- 74 //** main code
- 75 //*****************************************************
- 76 assign cam_sgm_ctrl = 1'b1;
- 77 assign cam_rst_n = 1'b1;
- 78
复制代码第76行代码是选择使用摄像头自带的晶振,第77行代码是不复位摄像头。 - 79 //例化PLLIP核
- 80 pll_clk u_pll_clk(
- 81 .pll_rst (~sys_rst_n ),
- 82 .clkin1 (sys_clk ),
- 83 .clkout0 (pixel_clk ), //像素时钟75M
- 84 .clkout1 (pixel_clk_5x), //5倍像素时钟375M
- 85 .clkout2 (clk_50m ), //output50M
- 86 .pll_lock (clk_locked )
- 87 );
- 88
- 89 //I2C配置模块
- 90 i2c_ov7725_rgb565_cfg u_i2c_cfg(
- 91 .clk (i2c_dri_clk ), //in
- 92 .rst_n (sys_rst_n ), //in
- 93 .i2c_done (i2c_done ), //in
- 94 .i2c_exec (i2c_exec ), //out
- 95 .i2c_data (i2c_data ), //out
- 96 .init_done (cam_init_done) //out
- 97 );
- 98
- 99 //I2C驱动模块
- 100 i2c_dri
- 101 #(
- 102 .SLAVE_ADDR (SLAVE_ADDR), //参数传递
- 103 .CLK_FREQ (CLK_FREQ ),
- 104 .I2C_FREQ (I2C_FREQ )
- 105 )
- 106 u_i2c_dri(
- 107 .clk (clk_50m ),
- 108 .rst_n (sys_rst_n ),
- 109 //i2c interface
- 110 .i2c_exec (i2c_exec ),
- 111 .bit_ctrl (BIT_CTRL ),
- 112 .i2c_rh_wl (1'b0 ), //固定为0,只用到了IIC驱动的写操作
- 113 .i2c_addr (i2c_data[15:8]),
- 114 .i2c_data_w (i2c_data[7:0 ),
- 115 .i2c_data_r ( ), //out
- 116 .i2c_done (i2c_done ), //out
- 117 .i2c_ack ( ), //out
- 118 .scl (cam_scl ), //out
- 119 .sda (cam_sda ), //inout
- 120 //user interface
- 121 .dri_clk (i2c_dri_clk) //I2C操作时钟//out
- 122 );
- 123
- 124 //摄像头数据采集模块
- 125 cmos_capture_datau_cmos_capture_data(
- 126 .rst_n (sys_rst_n & ddr_init_done),
- 127 .cam_pclk (cam_pclk ),
- 128 .cam_vsync (cam_vsync ),
- 129 .cam_href (cam_href ),
- 130 .cam_data (cam_data ),
- 131 .cmos_frame_vsync (cmos_frame_vsync ),//out
- 132 .cmos_frame_href (cmos_frame_href ),//out
- 133 .cmos_frame_valid (cmos_frame_valid ),//out
- 134 .cmos_frame_data (wr_data ) //out
- 135 );
- 136
- 137 //ddr3
- 138 ddr3_top u_ddr3_top(
- 139 .refclk_in (clk_50m ),
- 140 .rst_n (sys_rst_n ),
- 141 .app_addr_rd_min (APP_ADDR_MIN ),
- 142 .app_addr_rd_max (APP_ADDR_MAX ),
- 143 .rd_bust_len (BURST_LENGTH ),
- 144 .app_addr_wr_min (APP_ADDR_MIN ),
- 145 .app_addr_wr_max (APP_ADDR_MAX ),
- 146 .wr_bust_len (BURST_LENGTH ),
- 147 .ddr3_read_valid (1'b1 ),
- 148 .ddr3_pingpang_en (1'b1 ),
- 149 .wr_clk (cam_pclk ),
- 150 .rd_clk (pixel_clk ),
- 151 .datain_valid (cmos_frame_valid),
- 152 .datain (wr_data ),
- 153 .rdata_req (rdata_req ),
- 154 .rd_load (video_vs ),
- 155 .wr_load (cmos_frame_vsync),
- 156 .fram_done (fram_done ),
- 157 .dataout (rd_data ),
- 158 .pll_lock (pll_lock ),
- 159 .ddr_init_done (ddr_init_done ),
- 160 .ddrphy_rst_done ( ),
- 161 .pad_loop_in (pad_loop_in ),
- 162 .pad_loop_in_h (pad_loop_in_h ),
- 163 .pad_rstn_ch0 (pad_rstn_ch0 ),
- 164 .pad_ddr_clk_w (pad_ddr_clk_w ),
- 165 .pad_ddr_clkn_w (pad_ddr_clkn_w ),
- 166 .pad_csn_ch0 (pad_csn_ch0 ),
- 167 .pad_addr_ch0 (pad_addr_ch0 ),
- 168 .pad_dq_ch0 (pad_dq_ch0 ),
- 169 .pad_dqs_ch0 (pad_dqs_ch0 ),
- 170 .pad_dqsn_ch0 (pad_dqsn_ch0 ),
- 171 .pad_dm_rdqs_ch0 (pad_dm_rdqs_ch0 ),
- 172 .pad_cke_ch0 (pad_cke_ch0 ),
- 173 .pad_odt_ch0 (pad_odt_ch0 ),
- 174 .pad_rasn_ch0 (pad_rasn_ch0 ),
- 175 .pad_casn_ch0 (pad_casn_ch0 ),
- 176 .pad_wen_ch0 (pad_wen_ch0 ),
- 177 .pad_ba_ch0 (pad_ba_ch0 ),
- 178 .pad_loop_out (pad_loop_out ),
- 179 .pad_loop_out_h (pad_loop_out_h )
- 180 );
- 181
- 182 //HDMI顶层模块
- 183 hdmi_top u_hdmi_top(
- 184 .hdmi_clk (pixel_clk ),
- 185 .hdmi_clk_5 (pixel_clk_5x ),
- 186 .sys_rst_n (sys_rst_n ),
- 187 .fram_done (fram_done ),
- 188 .clk_locked (clk_locked ),
- 189 .rd_data (rd_data ),
- 190 .rd_en (rdata_req ),
- 191 .video_vs (video_vs ),
- 192 .h_disp ( ),
- 193 .v_disp ( ),
- 194 .pixel_xpos ( ),
- 195 .pixel_ypos ( ),
- 196 .tmds_clk_p (tmds_clk_p ),
- 197 .tmds_clk_n (tmds_clk_n ),
- 198 .tmds_data_p (tmds_data_p ),
- 199 .tmds_data_n (tmds_data_n )
- 200 );
- 201
- 202 endmodule
复制代码从上面顶层模块代码中可以看出本节实验就是在原本的“OV7725摄像头RGB-LCD显示实验”的基础上修改过来的,这里主要就是将原本的LCD顶层模块替换成了HDMI顶层模块,时钟模块为DDR3控制模块、I2C驱动模块提供驱动时钟以及为HDMI模块提供像素时钟和5倍像素时钟。 HDMI顶层模块代码如下: - 1 module hdmi_top(
- 2 input hdmi_clk ,
- 3 input hdmi_clk_5 ,
- 4 input sys_rst_n ,
- 5 input fram_done ,
- 6 input clk_locked ,
- 7
- 8 input [15:0 rd_data ,
- 9 output rd_en ,
- 10 output video_vs , //场同步信号
- 11 output [10:0 pixel_xpos , //像素点横坐标
- 12 output [10:0 pixel_ypos , //像素点纵坐标
- 13 output [12:0 h_disp , //HDMI屏水平分辨率
- 14 output [12:0 v_disp , //HDMI屏垂直分辨率
- 15 //HDMI接口
- 16 output tmds_clk_p , //TMDS 时钟通道
- 17 output tmds_clk_n ,
- 18 output [2:0 tmds_data_p , //TMDS 数据通道
- 19 output [2:0 tmds_data_n
- 20
- 21 );
- 22
- 23 //wire define
- 24 wire video_hs ; //行同步信号
- 25 wire video_de ; //数据使能
- 26 wire [15:0 video_rgb_565 ; //RGB565颜色数据
- 27 wire [15:0 pixel_data_w ; //像素点数据
- 28 wire [23:0 video_rgb ; ////RGB888颜色数据
- 29
- 30 //reg define
- 31 reg fram_done_t0 ;
- 32 reg fram_done_t1 ;
- 33 reg fram_done_t2 ;
- 34
- 35 //*****************************************************
- 36 //** main code
- 37 //*****************************************************
- 38
- 39 assign video_rgb = {video_rgb_565[15:11],3'b000,video_rgb_565[10:5],2'b00,
- 40 video_rgb_565[4:0],3'b000};
- 41
- 第39~40行代码是将采集到的RGB565格式的数据转换为HDMI显示的RGB888格式的数据。
- 42 always @(posedge hdmi_clk) begin
- 43 fram_done_t0 <= fram_done ;
- 44 fram_done_t1 <= fram_done_t0 ;
- 45 fram_done_t2 <= fram_done_t1 ;
- 46 end
- 47
- 48 //例化视频显示驱动模块
- 49 video_driver u_video_driver(
- 50 .pixel_clk (hdmi_clk ),
- 51 .sys_rst_n (sys_rst_n&&fram_done_t2),
- 52 .video_hs (video_hs ),
- 53 .video_vs (video_vs ),
- 54 .video_de (video_de ),
- 55 .video_rgb (video_rgb_565 ),
- 56 .data_req ( ),
- 57 .h_disp (h_disp ),
- 58 .v_disp (v_disp ),
- 59 .pixel_xpos (pixel_xpos ),
- 60 .pixel_ypos (pixel_ypos ),
- 61 .pixel_data (pixel_data_w )
- 62 );
- 63
- 64 //例化视频显示模块
- 65 video_display u_video_display(
- 66 .pixel_clk (hdmi_clk ),
- 67 .sys_rst_n (sys_rst_n&&fram_done_t2),
- 68 .pixel_xpos (pixel_xpos ),
- 69 .pixel_ypos (pixel_ypos ),
- 70 .h_disp (h_disp ),
- 71 .v_disp (v_disp ),
- 72 .cmos_data (rd_data ),
- 73 .video_data (pixel_data_w ),
- 74 .data_req (rd_en )
- 75 );
- 76
- 77 //例化HDMI驱动模块
- 78 dvi_transmitter_top u_rgb2dvi_0(
- 79 .pclk (hdmi_clk ),
- 80 .pclk_x5 (hdmi_clk_5 ),
- 81 .reset_n (sys_rst_n&clk_locked),
- 82 .video_din (video_rgb ),
- 83 .video_hsync (video_hs ),
- 84 .video_vsync (video_vs ),
- 85 .video_de (video_de ),
- 86 .tmds_clk_p (tmds_clk_p ),
- 87 .tmds_clk_n (tmds_clk_n ),
- 88 .tmds_data_p (tmds_data_p ),
- 89 .tmds_data_n (tmds_data_n )
- 90 );
- 91
- 92 endmodule
复制代码HDMI顶层模块例化了三个模块,分别是视频显示驱动模块(video_driver)、视频显示模块(video_display)和HDMI驱动模块(dvi_transmitter_top)。 HDMI驱动模块(dvi_transmitter_top):HDMI驱动模块主要就是进行串并转换和TMDS编码,这部分内容在前面的HDMI彩条实验中已经给大家讲解过了,这里不再重复赘述了。 视频显示驱动模块(video_driver):显示驱动模块的作用就是生成HDMI显示器的驱动时序,用来驱动HDMI显示器,其中包括行场同步信号和DE像素有效信号。这里的显示驱动模块(video_driver)跟HDMI彩条显示实验的驱动模块几乎是一模一样的,唯一的区别就是将HDMI的分辨率信息也作为了模块的输出,代码是没有改动的,详细的讲解可以查看前面的HDMI彩条实验,这里不再重复赘述了。 视频显示模块(video_display):该模块用于控制HDMI显示器显示的像素值,即摄像头采集的图像及黑边填充。因为OV7725摄像头的分辨率小于显示器的分辨率,所以我们需要配合当前的像素点坐标来进行判断是否向DDR3控制器发出读数据请求信号。在摄像头有效显示范围内,将摄像头采集的数据赋值给hdmi像素点数据,否则将黑色所对应的数据赋值给hdmi像素点数据(即填充黑边),其代码如下: - 1 module video_display(
- 2 input pixel_clk , //驱动时钟
- 3 input sys_rst_n , //复位信号
- 4
- 5 input [10:0 pixel_xpos, //像素点横坐标
- 6 input [10:0 pixel_ypos, //像素点纵坐标
- 7 input [15:0 cmos_data , //CMOS传感器像素点数据
- 8 input [10:0 h_disp , //水平分辨率
- 9 input [10:0 v_disp , //垂直分辨率
- 10
- 11 output [15:0 video_data, //像素点数据
- 12 output data_req //请求像素点颜色数据输入
- 13 );
- 14
- 15 //parameter define
- 16 parameter V_CMOS_DISP = 11'd480; //CMOS分辨率——行
- 17 parameter H_CMOS_DISP = 11'd640; //CMOS分辨率——列
- 18 localparam BLACK = 16'b00000_000000_00000; //RGB565 黑色
- 19
- 20 //reg define
- 21 reg data_val ; //数据有效信号
- 22
- 23 //wire define
- 24 wire [10:0 display_border_pos_l; //左侧边界的横坐标
- 25 wire [10:0 display_border_pos_r; //右侧边界的横坐标
- 26 wire [10:0 display_border_pos_t; //上端边界的纵坐标
- 27 wire [10:0 display_border_pos_b; //下端边界的纵坐标
- 28
- 29 //*****************************************************
- 30 //** main code
- 31 //*****************************************************
- 32
- 33 //左侧边界的横坐标计算
- 34 assign display_border_pos_l = (h_disp - H_CMOS_DISP)/2-1;
- 35 //右侧边界的横坐标计算
- 36 assign display_border_pos_r = H_CMOS_DISP + (h_disp - H_CMOS_DISP)/2-1;
- 37
- 38 //上端边界的纵坐标计算
- 39 assign display_border_pos_t = (v_disp - V_CMOS_DISP)/2;
- 40
- 41 //下端边界的纵坐标计算
- 42 assign display_border_pos_b = V_CMOS_DISP + (v_disp - V_CMOS_DISP)/2;
- 43
- 44 //请求像素点颜色数据输入 范围:79~718,共640个时钟周期
- 45 assign data_req = ((pixel_xpos >= display_border_pos_l ) &&
- 46 (pixel_xpos < display_border_pos_r) &&
- 47 (pixel_ypos > display_border_pos_t) &&
- 48 (pixel_ypos <= display_border_pos_b)
- 49 ) ? 1'b1 : 1'b0;
- 50
- 51 //在数据有效范围内,将摄像头采集的数据赋值给hdmi像素点数据
- 52 assign video_data = data_val ? cmos_data : BLACK;
- 53
- 54 //有效数据滞后于请求信号一个时钟周期,所以数据有效信号在此延时一拍
- 55 always @(posedge pixel_clk or negedge sys_rst_n) begin
- 56 if(!sys_rst_n)
- 57 data_val <= 1'b0;
- 58 else
- 59 data_val <= data_req;
- 60 end
- 61
- 62 endmodule
复制代码在代码的第33至42行,计算了摄像头存入DDR3的边界坐标,上边界坐标(display_border_pos_t)是HDMI屏幕的场分辨率(720)减去摄像头的场分辨率(480)再除以2得到的,其他的边界也是以类似的计算方法得到,裁剪原理与lcd屏裁剪相同(可参考图 39.4.5)。 这里还有一点需要提醒大家注意的是我们读数据请求发送给DDR3控制器后实际上是发送给读FIFO,而读FIFO接收到数据请求到数据出来是需要一个时钟周期的延迟的,也就是说有效数据实际上比读数据请求晚了一个时钟(如果FIFO用的是另外一种数据提前模式则有效数据和读数据请求是同步的,本节实验采用的是普通模式),因此代码的55~60行对读数据请求作了一个打拍处理,生成数据有效使能信号(data_val)。如代码第52行所示,当data_val信号拉高代表数据有效,data_val信号为低时代表数据无效,此时给HDMI显示屏填充黑边,到这里OV7725 HDMI显示实验的代码就讲完了。 因为本节实验例程的所有代码在“DDR3读写实验”、“ov7725_lcd显示实验”和“HDMI彩条显示实验”都有过详细的讲解,我们在本节实验只不过是将这些模块拿过来重新拼接一下,所以就不再重复的去详细讲解代码了,大家有不懂的地方可以翻看前面的例程。
1.5 下载验证 编译完工程之后就可以开始下载程序了。将OV7725摄像头模块插在ATK-DFPGL22G开发板的“OLED/CAMERA”插座上,并将HDMI电缆一端连接到开发板上的HDMI插座、另一端连接到显示器。将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,验证OV7725 HDMI实时显示功能。下载完成后观察HDMI显示器显示的图案如下图所示,说明OV7725 HDMI实时显示程序下载验证成功。 |