1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html 3)对正点原子FPGA感兴趣的同学可以加群讨论:994244016 4)关注正点原子公众号,获取最新资料更新
第四十章OV7725摄像头HDMI显示实验
在OV7725摄像头RGB-LCD显示实验中,成功地在LCD屏上实时显示出了摄像头采集的图像。本章将使用FPGA开发板实现对OV7725的数字图像采集并在HDMI显示器上实时显示。 本章包括以下几个部分: 3939.1简介 39.2实验任务 39.3硬件设计 39.4程序设计 39.5下载验证
40.1简介
在“OV7725摄像头RGB-LCD显示实验”中对OV7725的视频传输时序、SCCB协议以及寄存器的配置信息等内容作了详细的介绍,如果大家对这部分内容不是很熟悉的话,请参考“OV7725摄像头RGB-LCD显示实验”中的OV7725简介部分。 40.2实验任务 本节实验任务是使用新起点开发板及OV7725摄像头实现图像采集,通过HDMI接口驱动HDMI显示器,并实时显示出图像。 40.3硬件设计 摄像头扩展接口原理图及OV7725模块说明与“OV7725摄像头RGB-LCD显示实验”完全相同,请参考“OV7725摄像头RGB-LCD显示实验”硬件设计部分。HDMI接口部分的硬件设计请参考“HDMI彩条显示实验”中的硬件设计部分。 由于OV7725、HDMI接口和SDRAM引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里不再列出管脚分配。
40.4程序设计
图 40.4.1是根据本章实验任务画出的系统框图。对比“OV7725摄像头RGB-LCD显示实验”的系统框图可以发现,本次实验只是把LCD顶层模块替换成了HDMI顶层模块,将图像采集顶层模块中的图像裁剪模块去掉了,其余模块(除时钟模块外)完全相同。之所以在本节实验中把裁剪模块去掉了是因为本次实验所用的HDMI显示器的分辨率大于摄像头的分辨率相同,所以只需要在HDMI驱动模块稍微改一下读数据请求即可,改动部分下文会给大家讲解。时钟模块(由两个pll锁相环组成,一个负责提供LCD、摄像头和IIC模块的参考时钟;另一个负责提供HDMI模块的像素时钟与5倍频时钟。这里之所以用两个锁相环是因为HDMI所用的时钟71Mhz与SDRAM模块使用的时钟不是整数倍,使用一个锁相环不符合设计要求)用于为I2C驱动模块、HDMI顶层模块以及SDRAM控制模块提供驱动时钟;I2C驱动模块和I2C配置模块用于初始化OV7725图像传感器;摄像头采集模块负责采集摄像头图像数据,并且把图像数据写入SDRAM控制模块中;SDRAM控制模块负责将用户数据写入和读出片外SDRAM存储器;HDMI顶层模块负责驱动HDMI显示器。本章使用的HDMI显示器其分辨率为1280800,而OV7725传感器的最大分辨率为640480,所以HDMI显示器需要填充黑色的像素点。
图 40.4.1 顶层系统框图 由上图可知,本节实验使用了两个时钟模块(pll_clk、pll_hdmi),其中pll_clk为SDRAM控制模块以及I2C驱动模块提供驱动时钟,pll_hdmi 主要是为HDMI模块提供像素时钟和5倍像素时钟。I2C配置模块和I2C驱动模块控制着传感器初始化的开始与结束,传感器初始化完成后图像采集模块将采集到的数据写入SDRAM控制模块,HDMI顶层模块从SDRAM控制模块中读出数据并驱动显示器显示,这时整个系统才完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在SDRAM和传感器都初始化完成之后才开始输出数据的,避免了在SDRAM初始化过程中向里面写入数据。 顶层模块的代码如下所示:
1 module ov7725_hdmi(
2 input sys_clk ,
3 input sys_rst_n ,
4
5 input cam_pclk ,
6 input cam_vsync ,
7 input cam_href ,
8 input [7:0] cam_data ,
9 output cam_rst_n ,
10 output cam_sgm_ctrl,
11 output cam_scl ,
12 inout cam_sda ,
13
14 output sdram_clk ,
15 output sdram_cke ,
16 output sdram_cs_n ,
17 output sdram_ras_n ,
18 output sdram_cas_n ,
19 output sdram_we_n ,
20 output [1:0] sdram_ba ,
21 output [1:0] sdram_dqm ,
22 output [12:0] sdram_addr ,
23 inout [15:0] sdram_data ,
24
25 output tmds_clk_p,
26 output tmds_clk_n,
27 output [2:0] tmds_data_p,
28 output [2:0] tmds_data_n
29 );
30
31
32 parameter SLAVE_ADDR = 7'h21 ; //OV7725的器件地址7'h21
33 parameter BIT_CTRL = 1'b0 ;
34 parameter CLK_FREQ = 26'd50_000_000;
35 parameter I2C_FREQ = 18'd250_000 ;
36 parameter V_CMOS_DISP = 11'd480 ;
37 parameter H_CMOS_DISP = 11'd640 ;
38
39
40 wire clk_100m ;
41 wire clk_100m_shift ;
42 wire clk_50m ;
43 wire hdmi_clk ;
44 wire hdmi_clk_5 ;
45 wire locked ;
46 wire locked_hdmi ;
47 wire rst_n ;
48
49 wire i2c_exec ;
50 wire [15:0] i2c_data ;
51 wire cam_init_done ;
52 wire i2c_done ;
53 wire i2c_dri_clk ;
54 wire wr_en ;
55 wire [15:0] wr_data ;
56 wire rd_en ;
57 wire sdram_init_done ;
58 wire rdata_req ;
59 wire [15:0] rd_data ;
60 wire cmos_frame_valid ;
61 wire init_calib_complete ;
62 wire sys_init_done ;
63 wire clk_200m ;
64 wire cmos_frame_vsync ;
65 wire cmos_frame_href ;
66 wire [23:0] video_rgb ;
67 wire video_hs ;
68 wire video_vs ;
69 wire video_de ;
70 wire [23:0] pixel_data_w ;
71 wire [10:0] h_disp ;
72 wire [10:0] v_disp ;
73
74
75
76
77
78
79 assign rst_n = sys_rst_n & locked & locked_hdmi;
80
81
82 assign sys_init_done = sdram_init_done & cam_init_done;
83
84 assign cam_rst_n = 1'b1;
85
86 assign cam_sgm_ctrl = 1'b1;
87
88
89 pll_clk u_pll_clk(
90 .areset (~sys_rst_n),
91 .inclk0 (sys_clk),
92 .c0 (clk_100m),
93 .c1 (clk_100m_shift),
94 .c2 (clk_50m),
95 .locked (locked)
96 );
97 pll_hdmi pll_hdmi_inst (
98 .areset ( ~sys_rst_n ),
99 .inclk0 ( clk_50m ),
100 .c0 ( hdmi_clk ),
101 .c1 ( hdmi_clk_5 ),
102 .locked ( locked_hdmi )
103 );
104
105
106 i2c_ov7725_rgb565_cfg u_i2c_cfg(
107 .clk (i2c_dri_clk),
108 .rst_n (rst_n),
109 .i2c_done (i2c_done),
110 .i2c_exec (i2c_exec),
111 .i2c_data (i2c_data),
112 .init_done (cam_init_done)
113 );
114
115
116 i2c_dri
117 #(
118 .SLAVE_ADDR (SLAVE_ADDR),
119 .CLK_FREQ (CLK_FREQ ),
120 .I2C_FREQ (I2C_FREQ )
121 )
122 u_i2c_dri(
123 .clk (clk_50m ),
124 .rst_n (rst_n ),
125
126 .i2c_exec (i2c_exec ),
127 .bit_ctrl (BIT_CTRL ),
128 .i2c_rh_wl (1'b0),
129 .i2c_addr (i2c_data[15:8]),
130 .i2c_data_w (i2c_data[7:0]),
131 .i2c_data_r (),
132 .i2c_done (i2c_done ),
133 .scl (cam_scl ),
134 .sda (cam_sda ),
135
136 .dri_clk (i2c_dri_clk)
137 );
138
139
140 cmos_capture_data u_cmos_capture_data(
141 .rst_n (rst_n & sys_init_done),
142 .cam_pclk (cam_pclk),
143 .cam_vsync (cam_vsync),
144 .cam_href (cam_href),
145 .cam_data (cam_data),
146 .cmos_frame_vsync (cmos_frame_vsync),
147 .cmos_frame_href (cmos_frame_href),
148 .cmos_frame_valid (cmos_frame_valid),
149 .cmos_frame_data (wr_data)
150 );
151
152
153
154 sdram_top u_sdram_top(
155 .ref_clk (clk_100m),
156 .out_clk (clk_100m_shift),
157 .rst_n (rst_n),
158
159
160 .wr_clk (cam_pclk),
161 .wr_en (cmos_frame_valid),
162 .wr_data (wr_data),
163 .wr_min_addr (24'd0),
164 .wr_max_addr (V_CMOS_DISP*H_CMOS_DISP-1),
165 .wr_len (10'd512),
166 .wr_load (~rst_n),
167
168
169 .rd_clk (hdmi_clk),
170 .rd_en (rdata_req),
171 .rd_data (rd_data),
172 .rd_min_addr (24'd0),
173 .rd_max_addr (V_CMOS_DISP*H_CMOS_DISP-1),
174 .rd_len (10'd512),
175 .rd_load (~rst_n),
176
177
178 .sdram_read_valid (1'b1),
179 .sdram_pingpang_en (1'b1),
180 .sdram_init_done (sdram_init_done),
181
182
183 .sdram_clk (sdram_clk),
184 .sdram_cke (sdram_cke),
185 .sdram_cs_n (sdram_cs_n),
186 .sdram_ras_n (sdram_ras_n),
187 .sdram_cas_n (sdram_cas_n),
188 .sdram_we_n (sdram_we_n),
189 .sdram_ba (sdram_ba),
190 .sdram_addr (sdram_addr),
191 .sdram_data (sdram_data),
192 .sdram_dqm (sdram_dqm)
193 );
194
195
196 hdmi_top u_hdmi_top(
197 .hdmi_clk (hdmi_clk ),
198 .hdmi_clk_5 (hdmi_clk_5 ),
199 .rst_n (rst_n ),
200
201 .rd_data (rd_data ),
202 .rd_en (rdata_req),
203 .h_disp (h_disp ),
204 .v_disp (v_disp ),
205 .pixel_xpos (),
206 .pixel_ypos (),
207 .video_vs (),
208 .tmds_clk_p (tmds_clk_p ),
209 .tmds_clk_n (tmds_clk_n ),
210 .tmds_data_p (tmds_data_p ),
211 .tmds_data_n (tmds_data_n )
212 );
213 endmodule
从上面顶层模块代码中可以看出本节实验就是在原本的“OV7725摄像头RGB-LCD显示实验”的基础上修改过来的,这里主要就是将原本的LCD顶层模块替换成了HDMI显示模块,然后时钟模块又添加了一个锁相环(之所以又添加一个锁相环是因为HDMI使用的71Mhz像素时钟与IIC、SDRAM模块的时钟不是整数倍,只使用一个锁相环不符合设计要求)。锁相环没什么好介绍的,下面我们来看看HDMI模块。 HDMI模块一共分成两个子模块,分别是显示驱动模块(video_driver)和HDMI驱动模块(dvi_transmitter_top),其中HDMI驱动模块(dvi_transmitter_top)主要就是进行串并转换和TMDS编码,这部分内容在前面的HDMI彩条实验中已经给大家讲解过了,这里不再重复赘述了,下面我们来看看显示驱动模块。 显示驱动模块(video_driver):显示驱动模块的作用就是生成HDMI显示器的驱动时序,用来驱动HDMI显示器,其中包括行场同步信号和DE像素有效信号。这里的显示驱动模块(video_driver)跟HDMI彩条显示实验的驱动模块还是有区别的,一个是分辨率不同,在彩条实验当中用的是720P,而本节实验用的是1280*800的分辨率。另一个就是读数据请求作了修改,因为摄像头采集数据模块我们将图片裁剪模块删掉了,但是OV7725摄像头的分辨率又小于显示器的分辨率,所以我们需要配合读数据请求信号来给显示器填充黑边,其代码如下:
1 module video_driver(
2 input pixel_clk ,
3 input sys_rst_n ,
4
5
6 output video_hs ,
7 output video_vs ,
8 output video_de ,
9 output [23:0] video_rgb ,
10 output data_req ,
11
12 input [15:0] video_rgb_565 ,
13 output [10:0] pixel_xpos ,
14 output [10:0] pixel_ypos ,
15 output [10:0] h_disp ,
16 output [10:0] v_disp
17 );
18
19
20
21
22 parameter H_SYNC = 11'd32;
23 parameter H_BACK = 11'd80;
24 parameter H_DISP = 11'd1280;
25 parameter H_FRONT = 11'd48;
26 parameter H_TOTAL = 11'd1440;
27
28 parameter V_SYNC = 11'd6;
29 parameter V_BACK = 11'd14;
30 parameter V_DISP = 11'd800;
31 parameter V_FRONT = 11'd3;
32 parameter V_TOTAL = 11'd823;
33
34
35 reg [10:0] cnt_h;
36 reg [10:0] cnt_v;
37
38
39 wire video_en ;
40 reg data_en ;
41 wire [23:0]pixel_data;
42
43
44
45
46 assign video_de = video_en;
47
48 assign video_hs = ( cnt_h < H_SYNC ) ? 1'b0 : 1'b1;
49 assign video_vs = ( cnt_v < V_SYNC ) ? 1'b0 : 1'b1;
50
51
52 assign video_en = (((cnt_h >= H_SYNC+H_BACK) && (cnt_h < H_SYNC+H_BACK+H_DISP))
53 &&((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
54 ? 1'b1 : 1'b0;
55
56
57 assign video_rgb = data_en ? pixel_data : 24'd0;
58
59
60 assign data_req = (((cnt_h >= H_SYNC+H_BACK+320) &&
61 (cnt_h < H_SYNC+H_BACK+H_DISP-320))
62 && ((cnt_v >= V_SYNC+V_BACK+160) && (cnt_v < V_SYNC+V_BACK+V_DISP-160)))
63 ? 1'b1 : 1'b0;
64
65 always@(posedge pixel_clk or negedge sys_rst_n)begin
66 if(!sys_rst_n)
67 data_en <= 0;
68 else
69 data_en <= data_req;
70 end
71
72
73 assign pixel_xpos = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 11'd0;
74 assign pixel_ypos = data_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 11'd0;
75
76 assign pixel_data = {video_rgb_565[15:11],3'b000,video_rgb_565[10:5],2'b00,
77 video_rgb_565[4:0],3'b000};
78
79
80 assign h_disp = H_DISP;
81 assign v_disp = V_DISP;
82
83
84 always @(posedge pixel_clk ) begin
85 if (!sys_rst_n)
86 cnt_h <= 11'd0;
87 else begin
88 if(cnt_h < H_TOTAL - 1'b1)
89 cnt_h <= cnt_h + 1'b1;
90 else
91 cnt_h <= 11'd0;
92 end
93 end
94
95
96 always @(posedge pixel_clk ) begin
97 if (!sys_rst_n)
98 cnt_v <= 11'd0;
99 else if(cnt_h == H_TOTAL - 1'b1) begin
100 if(cnt_v < V_TOTAL - 1'b1)
101 cnt_v <= cnt_v + 1'b1;
102 else
103 cnt_v <= 11'd0;
104 end
105 end
106
107 endmodule
代码的60~63行可以看到读数据请求不再像HDMI彩条实验那样从行同步后延之后就拉高而是延迟了320个像素点,同样保持时间也不再是一个完整的行显示时间(H_DISP)了,而是提前320个像素点就拉低。之所以这样做是因为,摄像头的行分辨率是640,而显示器的分辨率是1280,我们想把图片显示在显示器的正中间,所以就要从显示区域的第320个像素点开始读取摄像头数据,然后保持640个时钟周期后拉低读数据请求(这个显示区域也可以不再正中间,根据自己需求可以调整位置)。同理场区域也可以像行区域这样处理,不过摄像头场分辨率是480,而显示器是800,那么想让图片在列方向显示在显示器的正中间那么读数据请求就要延迟160行拉高(显示区域根据需求可以自行调整,不是有效显示区域部分我们填充黑边),然后持续480个时钟周期后拉低。 这里还有一点需要提醒大家注意的是我们读数据请求发送给SDRAM控制器后实际上是发送给读FIFO,而读FIFO接收到数据请求到数据出来是需要一个时钟周期的延迟的,也就是说有效数据实际上比读数据请求晚了一个时钟(如果FIFO用的是另外一种数据提前模式则有效数据和读数据请求是同步的,本节实验采用的是普通模式),因此代码的65~70行对读数据请求作了一个打拍处理,生成数据有效使能信号(data_en),当data_en信号拉高代表数据有效如代码第57行所示,data_en信号为低时代表数据无效,此时给HDMI显示屏填充黑边,到这里OV7725 HDMI显示实验的代码就讲完了。 因为本节实验例程的所有代码在“SDRAM读写实验”、“ov7725_lcd显示实验”和“HDMI彩条显示实验”都有过详细的讲解,我们在本节实验只不过是将这些模块拿过来重新拼接一下,所以就不再重复的去详细讲解代码了,大家有不懂的地方可以翻看前面的例程。 40.5下载验证 编译完工程之后就可以开始下载程序了。将OV7725摄像头模块插在新起点开发板的“OLED/CAMERA”插座上,并将HDMI电缆一端连接到开发板上的HDMI插座、另一端连接到显示器。将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来我们下载程序,验证OV7725 HDMI实时显示功能。下载完成后观察HDMI显示器显示的图案如下图所示,说明OV7725 HDMI实时显示程序下载验证成功。
图 40.5.1 HDMI实时显示图像
|