|
本帖最后由 正点原子 于 2023-2-24 09:34 编辑
1)实验平台:正点原子紫光PGL22G开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340253-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:435699340
第四十章 OV7725摄像头HDMI显示实验
在OV7725摄像头RGB-LCD显示实验中,成功地在LCD屏上实时显示出了摄像头采集的图像。本章将使用FPGA开发板实现对OV7725的数字图像采集并在HDMI显示器上实时显示。
本章包括以下几个部分:
40.1简介
40.2实验任务
40.3硬件设计
40.4程序设计
40.5下载验证
40.1简介
在“OV7725摄像头RGB-LCD显示实验”中对OV7725的视频传输时序、SCCB协议以及寄存器的配置信息等内容作了详细的介绍,如果大家对这部分内容不是很熟悉的话,请参考“OV7725摄像头RGB-LCD显示实验”中的简介部分。
40.2实验任务
本节实验任务是使用ATK-DFPGL22G开发板及OV7725摄像头实现图像采集,通过HDMI接口驱动HDMI显示器,并实时显示出图像。
40.3硬件设计
摄像头扩展接口原理图及OV7725模块说明与“OV7725摄像头RGB-LCD显示实验”完全相同,请参考“OV7725摄像头RGB-LCD显示实验”硬件设计部分。HDMI接口部分的硬件设计请参考“HDMI彩条显示实验”中的硬件设计部分。
由于OV7725、HDMI接口和DDR3引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里不再列出管脚分配。
40.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显示器需要填充黑色的像素点。
图 40.4.1顶层系统框图
由上图可知,本节实验中时钟模块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 //例化PLL IP核
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 ), //output 50M
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_data u_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彩条显示实验”都有过详细的讲解,我们在本节实验只不过是将这些模块拿过来重新拼接一下,所以就不再重复的去详细讲解代码了,大家有不懂的地方可以翻看前面的例程。
40.5下载验证
编译完工程之后就可以开始下载程序了。将OV7725摄像头模块插在ATK-DFPGL22G开发板的“OLED/CAMERA”插座上,并将HDMI电缆一端连接到开发板上的HDMI插座、另一端连接到显示器。将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,验证OV7725 HDMI实时显示功能。下载完成后观察HDMI显示器显示的图案如下图所示,说明OV7725 HDMI实时显示程序下载验证成功。
图 40.5.1 HDMI实时显示图像
|
|