锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

【正点原子FPGA连载】第五十五章 双目OV5640摄像头RGB-LCD显示实验 -摘自【正点原子】新起点之FPGA开发指南...

时间:2023-09-08 02:07:02 451集成电路l6传感器

1)实验平台:正点原子新起点V2开发板
2)平台采购地址:https://detail.tmall.com/item.htm?id=609758951113
3)全套实验源码 手册 视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)正点原子FPGA感兴趣的同学可以加群讨论:99424016
5)关注正点原子微信官方账号,获取最新信息更新
在这里插入图片描述

第五十五章 双目OV5640摄像头RGB-LCD显示实验

双目摄像头是将两个摄像头集成到一个模块中,以实现双通道图像采集的功能。双目摄像头一般用于安全监控、三维视觉距离测量、三维重建等领域。这个测试只做最基本的工作OV5640摄像头实时收集的图像分为左右两半。LCD屏幕上。
本章包括以下几个部分:

1.1 简介

1.2 实验任务
1.3 硬件设计
1.4 程序设计
1.5 下载验证?
1.1 简介
摄像头在日常生活中很常见,一般分为单目摄像头、双目摄像头和多目摄像头。目前,单目摄像头应用最广泛;双目摄像头主要用于单目摄像头不称职的场合。例如,在测距领域,人们可以根据两个摄像头的
视差辅以一定的算法来计算物体之间的距离;当然,对于一些特殊,市场上也有多目摄像头来处理更复杂的场景。在“OV5640摄像头LCD显示实验中对OV5640视频传输顺序,SCCB详细介绍了协议和寄存器的配置信息。如果您不熟悉这部分,请参考以前的实验。本实验将在前面单目进行OV在5640摄像头的基础上,学习双目摄像头LCD显示。

1.2 实验任务

本章的实验任务是使用双目OV5640摄像头采集图像,实时显示采集到的图像LCD在屏幕上,两幅图像分别占据LCD屏幕的左右半边。

1.3 硬件设计

新起点开发板上有两个扩展口,即P6和P7.开发板的新起点P6扩展口与LCD屏幕管脚复用,本实验采用P七扩展口连接双目OV5640摄像头。P7扩展口原理图如图所示 55.3.1所示:

图 55.3.1 P7扩展接口原理图
ATK-Dual-OV5640是正点原子推出的双目OV5640摄像头模块的硬件原理图如下图所示:

图 55.3.2 ATK-Dual-OV5640原理图
该模块通过2*20排母(2.54mm间距)同外部连接,连接时将双目摄像头的排母直接插在开发板上的P7.扩展端口,模块实物连接图如图所示 55.3.3所示:

图 55.3.3 双目摄像头模块实物连接图
由于LCD接口和SDRAM它们的管脚列表已经在相应的章节中给出,这里只列出双目摄像头相关管脚的分布,如下表所示:

双目摄像头TCL约束文件如下:

set_location_assignment PIN_L7 -to cam0_data[7] set_location_assignment PIN_L6 -to cam0_data[5] set_location_assignment PIN_K5 -to cam0_data[3] set_location_assignment PIN_K6 -to cam0_data[1] set_location_assignment PIN_F3 -to cam0_rst_n set_location_assignment PIN_G5 -to cam0_href set_location_assignment PIN_M6 -to cam0_vsync set_location_assignment PIN_N3 -to cam0_pclk set_location_assignment PIN_L3 -to cam0_data[6] set_location_assignment PIN_L4 -to cam0_data[4] set_location_assignment PIN_K8 -to cam0_data[2] set_location_assignment PIN_G1 -to cam0_data[0] set_location_assignment PIN_J6 -to cam0_sda set_location_asignment PIN_F1 -to cam0_scl
set_location_assignment PIN_J1 -to cam0_pwdn
set_location_assignment PIN_A2 -to cam1_pwdn
set_location_assignment PIN_B5 -to cam1_data[7]
set_location_assignment PIN_B6 -to cam1_data[5]
set_location_assignment PIN_B7 -to cam1_data[3]
set_location_assignment PIN_A4 -to cam1_data[1]
set_location_assignment PIN_D3 -to cam1_rst_n
set_location_assignment PIN_C6 -to cam1_href
set_location_assignment PIN_E5 -to cam1_vsync
set_location_assignment PIN_A3 -to cam1_pclk
set_location_assignment PIN_A5 -to cam1_data[6]
set_location_assignment PIN_A6 -to cam1_data[4]
set_location_assignment PIN_B3 -to cam1_data[2]
set_location_assignment PIN_B4 -to cam1_data[0]
set_location_assignment PIN_F5 -to cam1_sda
set_location_assignment PIN_D6 -to cam1_scl

1.4 程序设计

根据实验任务,首先设计如图 55.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头LCD显示实验”的整体架构,但是做出了一定的修改,在本节实验中我们将寄存器配置模块、IIC驱动模块以及图像采集模块封装成了一个模块(OV5640驱动模块),并且对OV5640驱动模块例化了两次(因为是双目摄像头,所以需要例化两次来分别驱动两个摄像头)。整个工程包含以下5个模块:时钟模块、图像分辨率设置模块、SDRAM控制器模块、摄像头驱动模块(例化两次)和LCD顶层模块。其中时钟模块、图像分辨率设置模块和摄像头驱动模块没有做任何修改,这些模块在单目OV5640摄像头LCD显示实验中已经说明过,这里不再详述,本次实验对SDRAM控制模块和LCD顶层模块做了修改。

图 55.4.1 顶层系统框图
时钟模块(pll):时钟模块通过调用
相环 IP核实现,共输出3路时钟,分别是SDRAM参考时钟(100Mhz)、SDRAM相位偏移时钟(100Mhz偏移120度,这个偏移角度可以适当调整。)和LCD驱动时钟(50Mhz)。其中SDRAM参考时钟不仅仅用来驱动SDRAM顶层模块还作为摄像头驱动模块的工作时钟来用。
图像分辨率设置模块(picture_size):图像尺寸配置模块用于配置摄像头输出图像尺寸的大小,此外还完成了SDRAM的读写结束地址设置。有关图像分辨率设置模块的详细介绍请大家参考单目OV5640摄像头LCD显示实验章节。
LCD顶层模块(lcd_rgb_top):LCD顶层模块负责驱动LCD屏的驱动信号的输出,同时为其他模块提供屏体参数、场同步信号和数据请求信号。
摄像头驱动模块(ov5640_dri):本模块由原先的IIC驱动模块、OV5640 寄存器配置模块和摄像头图像采集模块封装而成,这样做是为了减少顶层模块的代码量,增强可读性,同时有利于代码的维护和管理。由于本次实验连接了两个相同的摄像头,因此我们会对摄像头驱动模块例化两次,将两个驱动模块输出的数据和数据有效使能全部连接到SDRAM控制模块。有关摄像头驱动模块的详细介绍请大家参考单目OV5640摄像头LCD显示实验章节。
SDRAM控制模块(sdram_top):SDRAM读写控制器模块负责驱动SDRAM片外存储器,缓存图像传感器输出的图像数据。该模块将SDRAM复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。
下面是顶层模块的原理图:

图 55.4.2 顶层模块原理图
本次实验是在单目OV5640摄像头LCD显示实验的基础上作修改的,主要修改了SDRAM控制模块和LCD顶层模块,而其余模块基本相同,因此接下来只介绍修改部分的内容。前面的实验,使用读写各一个FIFO,并在SDRAM中开辟了两个缓冲区,而本实验有两个摄像头,所以读写需要各增加一个FIFO,总共四个,同时在SDRAM中再开辟两个缓冲区,达到四个缓冲区,而满足两组摄像头数据的处理需要。
下面我们就一起来看看本节实验在单目OV5640摄像头LCD显示实验的基础上作了哪些修改。首先我们先看下顶层模块,
顶层模块最大的改变就是摄像头驱动模块了,代码如下(只贴出修改部分):

125 //OV5640 0摄像头驱动
126 ov5640_dri u0_ov5640_dri(
127     .clk               (clk_100m),
128     .rst_n             (rst_n),
129 
130     .cam_pclk          (cam0_pclk ),
131     .cam_vsync         (cam0_vsync),
132     .cam_href          (cam0_href ),
133     .cam_data          (cam0_data ),
134     .cam_rst_n         (cam0_rst_n),
135     .cam_pwdn          (cam0_pwdn ),
136     .cam_scl           (cam0_scl  ),
137     .cam_sda           (cam0_sda  ),
138     
139     .capture_start     (sdram_init_done),
140     .cmos_h_pixel      (cmos_h_pixel[12:1]),
141     .cmos_v_pixel      (cmos_v_pixel),
142     .total_h_pixel     (total_h_pixel),
143     .total_v_pixel     (total_v_pixel),
144     .cam_init_done     (cam_init_done_0),   
145 
146     .cmos_frame_vsync  (),
147     .cmos_frame_href   (),
148     .cmos_frame_valid  (wr0_en),
149     .cmos_frame_data   (wr0_data)
150;

摄像头驱动模块将原本的IIC驱动模块、OV5640 寄存器配置模块和摄像头图像采集模块封装成一个模块,这部分内容没什么好讲的,无非就是把端口封装一下用一个顶层模块去调用三个子模块而已。这里需要注意的是代码第140行,cmos_h_pixel是指LCD显示屏的行分辨率,在前面单目OV5640摄像头LCD显示实验中它是直接作为ov5640摄像头的行分辨率配置参数,但是本节实验不行,因为本节实验是双目,两个摄像头两幅画面需要显示在一个屏幕上,因此一个摄像头所占有的行分辨率只能是LCD显示屏行分辨率的一半,所以cmos_h_pixel这个参数需要除以2作为摄像头的行分辨率配置参数(cmos_h_pixel[12:1]这种写法相当于除以2)。
接下来我们再继续看看SDRAM控制器修改的内容。SDRAM控制器主要是修改了sdram_fifo_ctrl模块,这个模块是控制着整个SDRAM的读和写。在前面单目OV5640摄像头LCD显示实验中我们的读写原理是在SDRAM中开辟两个存储空间,一个空间正在缓存数据另一个空间就可以往外读出数据,这样交替使用。但是本节实验使用的是双目摄像头,有两个数据源,因此原本的两个存储空间肯定是不够用的,所以我们在原本的基础上将两个存储空间都扩大成四个存储空间,这样就可以容纳两个摄像头的数据了代码如下:

199 //sdram写地址0产生模块
200 always @(posedge clk_ref or negedge rst_n) begin
201     if!rst_n)begin
202         sdram_wr_addr0 <= 24'd0;
203         rw_bank_flag0  <= 0;
204         sw_bank_en0    <= 0;
205     end 
206     else if(wr_load_flag)begin              //检测到写端口复位信号时,写地址复位
207         sdram_wr_addr0 <= wr_min_addr;
208         rw_bank_flag0  <= 0;
209         sw_bank_en0    <= 0;
210     end                                     //若突发写SDRAM结束更改写地址
211     else if(write_done_flag && !wr_fifo_flag) begin 
212         if(sdram_pingpang_en) begin         //SDRAM 读写乒乓使能
213                                             //若未到达写SDRAM的结束地址写地址累加 
214             if(sdram_wr_addr0[21:0] < wr_max_addr - wr_length)
215                 sdram_wr_addr0 <= sdram_wr_addr0 + wr_length;
216             else begin                      //切换BANK
217                 rw_bank_flag0 <= ~rw_bank_flag0;   
218                 sw_bank_en0 <= 1'b1;        //拉高切换BANK使能信号
219             end            
220         end                                 //乒乓操作不使能时
221                                             //判断是否到达结束地址 
222         else if(sdram_wr_addr0 < wr_max_addr - wr_length)
223                                             //没达结束地址,地址累加一个突发长度
224             sdram_wr_addr0 <= sdram_wr_addr0 + wr_length;
225         else                                //若已到达结束地址,则回到写起始地址
226             sdram_wr_addr0 <= wr_min_addr;
227     end    
228     else if(sw_bank_en0) begin              //如果bank切换使能信号有效
229         sw_bank_en0 <= 1'b0;                //将使能信号置0,方便下次使用
230         if(rw_bank_flag0 == 1'b0)           //根据bank标志信号切换BANK
231             sdram_wr_addr0 <= { 
        2'b00,wr_min_addr[21:0]};
232         else
233             sdram_wr_addr0 <= { 
        2'b01,wr_min_addr[21:0]};     
234 end
235 end
236 
237 //sdram写地址1产生模块
238 always @(posedge clk_ref or negedge rst_n) begin
239     if!rst_n)begin
240         sdram_wr_addr1 <= 24'd0;
241         rw_bank_flag1  <= 0;
242         sw_bank_en1    <= 0;
243     end 
244     else if(wr_load_flag)begin              //检测到写端口复位信号时,写地址复位
245         rw_bank_flag1  <= 0;
246         sw_bank_en1    <= 0;
247         sdram_wr_addr1 <= wr_max_addr;
248     end                                     //若突发写SDRAM结束,更改写地址
249     else if(write_done_flag && wr_fifo_flag) begin
250         if(sdram_pingpang_en) begin         //判断若SDRAM 读写乒乓使能
251                                             //若未到达写SDRAM的结束地址写地址累加 
252         if(sdram_wr_addr1[21:0] < wr_max_addr*2 - wr_length)
253                 sdram_wr_addr1 <= sdram_wr_addr1 + wr_length;
254             else begin                      //切换BANK
255                 rw_bank_flag1 <= ~rw_bank_flag1;   
256                 sw_bank_en1 <= 1'b1;        //拉高切换BANK使能信号
257             end            
258         end                                 //乒乓操作不使能
259                                             //未到达写SDRAM的结束地址写地址累加
260         else if(sdram_wr_addr1 < wr_max_addr*2 - wr_length)
261             sdram_wr_addr1 <= sdram_wr_addr1 + wr_length;
262             else                            //到达写SDRAM的结束地址回到写起始地址
263             sdram_wr_addr1 <= wr_max_addr;
264     end
265     else if(sw_bank_en1) begin              //如果bank切换使能信号有效
266         sw_bank_en1 <= 1'b0;                //将使能信号置0,方便下次使用
267         if(rw_bank_flag1 == 1'b0)           //切换BANK
268             sdram_wr_addr1 <= { 
        2'b10,wr_max_addr[21:0]};
269         else
270             sdram_wr_addr1 <= { 
        2'b11,wr_max_addr[21:0]};     
271     end
272 end
273 
274 //sdram读地址0产生模块
275 always @(posedge clk_ref or negedge rst_n) begin
276     if!rst_n)
277         sdram_rd_addr0 <= 24'd0;    
278     else if(rd_load_flag)                   //检测到写端口复位信号时,写地址复位
279         sdram_rd_addr0 <= rd_min_addr;      //若突发读SDRAM结束,更改读地址
280     else if(read_done_flag && !rd_fifo_flag ) begin
281         if(sdram_pingpang_en) begin         //判断若SDRAM 读写乒乓使能 
282                                             //若未到达SDRAM的结束地址则地址累加 
283             if(sdram_rd_addr0[21:0] < rd_max_addr - rd_length)
284                 sdram_rd_addr0 <= sdram_rd_addr0 + rd_length;                                                
285             else begin                      //到达读SDRAM的结束地址,回到读起始 
286                 if(rw_bank_flag0 == 1'b0)   //根据rw_bank_flag的值切换读BANK地址
287                     sdram_rd_addr0 <= { 
        2'b01,rd_min_addr[21:0]};
288                 else
289                     sdram_rd_addr0 <= { 
        2'b00,rd_min_addr[21:0]};    
290             end    
291         end                                  //若乒乓操作未使能
292                                             //未到达SDRAM的结束地址地址累加
293         else if(sdram_rd_addr0 < rd_max_addr - rd_length)
294             sdram_rd_addr0 <= sdram_rd_addr0 + rd_length;
295         else                                 //若到达SDRAM的结束地址回到起始地址
296             sdram_rd_addr0 <= rd_min_addr;
297     end
298 end
299 
300 //sdram读地址1产生模块
301 always @(posedge clk_ref or negedge rst_n) begin
302     if!rst_n)
303         sdram_rd_addr1 <= 24'd0;    
304     else if(rd_load_flag)                    //检测到复位信号时地址复位
305         sdram_rd_addr1 <= rd_max_addr;  
306                                             //判断若突发读SDRAM结束
307     else if(read_done_flag && rd_fifo_flag) begin
308         if(sdram_pingpang_en) begin          //若SDRAM 读写乒乓使能 
309                                             //若未到达SDRAM的结束地址则地址累加 
310             if(sdram_rd_addr1[21:0] < rd_max_addr*2 - rd_length)
311                 sdram_rd_addr1 <= sdram_rd_addr1 + rd_length;                                                
312             else begin                       //到达读SDRAM的结束地址 
313                 if(rw_bank_flag1 == 1'b0)    //根据rw_bank_flag的值切换BANK地址
314                     sdram_rd_addr1 <= { 
        2'b11,rd_max_addr[21:0]};
315                 else
316                     sdram_rd_addr1 <= { 
        2'b10,rd_max_addr[21:0]};    
317             end    
318         end                                  //如果乒乓操作没有使能
319                                             //未到达SDRAM的结束地址地址累加
320         else if(sdram_rd_addr1 < rd_max_addr*2 - rd_length)
321             sdram_rd_addr1 <= sdram_rd_addr1 + rd_length;
322         else                                 //若已到达SDRAM的结束地址回到起始地址
323             sdram_rd_addr1 <= rd_max_addr;
324     end
325 end

由于FIFO控制模块代码太长,因此我一段一段的讲解,上面这段代码就是读写地址控制,相比较于单目摄像头LCD显示实验可以看到读写地址都增加了一帧的空间。代码200235行跟之前的单目一样就是在SDRAM中开辟一个空间用来存储一帧图片,写满一帧后BANK地址切换继续写下一帧,但是本节是经验就不一样了,代码237272行又开辟了一帧的存储空间,这一帧的存储空间跟上一帧不在同一BANK上。像这样开辟两帧的空间刚好可以用来分别存储两个摄像头的数据,当两个摄像头的数据都各存一帧后就可以切换BANK继续去存储两个摄像头下一帧的数据了。接下来就进入读地址了,读地址和写地址是相对应的,当写地址完成一个BANK的数据存储切换到下一个BANK后,读地址开始去读这个已经存储了数据的BANK,当写地址写完下一个BANK又切换回来则读地址切换到另一个BANK上去,这样周而复始的交替进行读写就可以实现摄像头数据的缓存了。接下来我们再来看看读写状态是如何切换的,代码如下所示:

326 
327 //读写端四个FIFO的判断逻辑 
328 always@(posedge clk_ref or negedge rst_n) begin
329     if!rst_n) begin                     
330         sdram_wr_req  <= 0;
331         sdram_wr_addr <= sdram_wr_addr0;
332         wr_fifo_flag  <= 0;
333         
334         sdram_rd_req  <= 0;
335         rd_fifo_flag  <= 0;
336         sdram_rd_addr <= sdram_rd_addr0;
337         state         <= idle;          //复位处于空闲状态,不操作任何FIFO
338     end
339     else begin
340         case(state)
341             idle:begin
342                 if(sdram_init_done)
343                     state <=  sdram_done;//SDRAM初始化完成进入sdram_done状态
344             end
345             sdram_done:begin            //在sdram_done状态对四个FIFO的读写操作进行判断
346                 if(wrf_use0 >= wr_length*2) begin //进入写端FIFO0的读状态状态
347                     sdram_wr_req  <= 1;
348                     sdram_wr_addr <= sdram_wr_addr0;
349                     wr_fifo_flag  <= 0;
350                     
351                     sdram_rd_req  <= 0;
352                     sdram_rd_addr <= sdram_rd_addr0;
353                     rd_fifo_flag  <= 0;
354                     state <= wr_keep;
355                     
356                 end
357         
358                 else if(wrf_use1 >= wr_length*2) begin//进入写端FIFO1的读状态状态
359                     sdram_wr_req  <= 1;
360                     sdram_wr_addr <= sdram_wr_addr1;
361                     wr_fifo_flag  <= 1;
362                     
363                     sdram_rd_req  <= 0;
364                     sdram_rd_addr <= sdram_rd_addr0;
365                     rd_fifo_flag  <= 0;
366                     
367                     state <= wr_keep;
368                 end
369                 else if((rdf_use0 < rd_length*2//进入读端FIFO0的写状态状态
370                 ) begin
371                     sdram_wr_req  <= 0;
372                     sdram_wr_addr <= sdram_wr_addr0;
373                     wr_fifo_flag  <= 0;
374                 
375                     sdram_rd_req  <= 1;
376                     sdram_rd_addr <= sdram_rd_addr0;
377                     rd_fifo_flag  <= 0;
378                     state <= rd_keep;
379                 end
380                 else if((rdf_use1 < rd_length*2//进入读端FIFO1的写状态状态
381                 ) begin
382                     sdram_wr_req  <= 0;
383                     sdram_wr_addr <= sdram_wr_addr0;
384                     wr_fifo_flag  <= 0;
385                 
386                     sdram_rd_req  <= 1;
387                     sdram_rd_addr <= sdram_rd_addr1;
388                     rd_fifo_flag  <= 1;
389                     state <= rd_keep;
390                 end  
391             end
392                 wr_keep:begin
393                     if(write_done_flag) begin  //保持写状态
394                     sdram_wr_req  <= 0;
395                     sdram_wr_addr <= sdram_wr_addr0;
396                     wr_fifo_flag  <= 0;
397                     state <= sdram_done;
398                 end    
399                 end
400                 rd_keep:begin
401                     if(read_done_flag) begin  //保持读状态
402                     sdram_rd_req  <= 0;
403                     sdram_rd_addr <= sdram_rd_addr0;
404                     rd_fifo_flag  <= 0;
405                     state <= sdram_done;
406                 end    
407                 end   
408                 default : state <= idle;    //默认停在空闲状态
409         endcase    
410     end
411 end
412  

上面这段代码就是读写切换了,在前文我们说到这次我们在一个BANK上开辟了两帧数据大小的存储空间,用来分别存储两个摄像头的数据,所以在写SDRAM这端我们添加了两个写FIFO,两个摄像头的数据分别往自己的FIFO中写入数据,而当FIFO中存储的数据量大于2倍突发长度时就会开启SDRAM写操作,将数据写入SDRAM中如代码346~368所示。这里尤其要注意的是FIFO的容量一定要大于三倍突发长度,因为两个FIFO开启SDRAM写操作的判断条件是相同的,都是存储的数据量大于2倍突发长度时就会开启,双目摄像头几乎是同时开始工作的,这也就意味着两个写FIFO几乎是同时满足开启SDRAM写操作的判断条件(虽然宏观上几乎同时,但是肯定是有一个先一个后的),这就意味着其中一个FIFO满足条件后先开启SDRAM写操作,开启后状态机跳转到保持状态如代码354行和367行所示,这个时候必须完成一次突发长度的写操作才能跳出保持状态,那么另外一个FIFO此时的数据也已经超过了2倍突发长度,它必须等待当前FIFO完成写操作后,它才能开启SDRAM写操作,所以他要有足够的缓冲容量来等待当前FIFO完成写操作。因此我们将FIFO的容量设置为2048,刚好是四倍突发长度,有足够的缓冲容量来等待。
讲完了写再来看看读操作,如代码369~391行所示,其实读跟写完全是一模一样的机制,都是当读FIFO中存储的数据不满足两倍突发长度就开启SDRAM读操作,同样其中一个开启另一个就需要等待。
明白了SDRAM控制器的运行机制之后我们再来看看FIFO的例化,代码如下:

413 //例化写端口FIFO0
414 wrfifo  u_wrfifo0(
415     //用户接口
416     .wrclk      (clk_write0),             //写时钟
417     .wrreq      (wrf_wrreq0),             //写请求
418     .data       (wrf_din0),               //写数据
419     //sdram接口
420     .rdclk      (clk_ref),                //读时钟
421     .rdreq      (sdram_wr_ack0),          //读请求
422     .q          (sdram_din0),             //读数据
423     
424     .rdusedw    (wrf_use0),               //FIFO中的数据量
425     .aclr       (~rst_n | wr_load_flag)   //异步清零信号
426; 
427     
428 //例化写端口FIFO1
429 wrfifo  u_wrfifo1(
430     //用户接口
431     .wrclk      (clk_write1),             //写时钟
432     .wrreq      (wrf_wrreq1),             //写请求
433     .data       (wrf_din1),               //写数据 
434     //sdram接口
435     .rdclk      (clk_ref),                //读时钟
436     .rdreq      (sdram_wr_ack1),          //读请求
437     .q          (sdram_din1),             //读数据
438 
439     .rdusedw    (wrf_use1),               //FIFO中的数据量
440     .aclr       (~rst_n | wr_load_flag)   //异步清零信号
441;      
442     
443 //例化读端口FIFO0
444 rdfifo  u_rdfifo1(
445     //sdram接口
446     .wrclk      (clk_ref),                //写时钟
447     .wrreq      (sdram_rd_ack1),          //写请求
448     .data       (sdram_dout1),            //写数据
449     
450     //用户接口
451     .rdclk      (clk_read),              //读时钟
452     .rdreq      (rdf_rdreq1),             //读请求
453     .q          (rdf_dout1),              //读数据
454     
455     .wrusedw    (rdf_use1),               //FIFO中的数据量
456     .aclr       (~rst_n | rd_load_flag)   //异步清零信号 
457;
458 //例化读端口FIFO1
459 rdfifo  u_rdfifo0(
460     //sdram接口
461     .wrclk      (clk_ref),                //写时钟
462     .wrreq      (sdram_rd_ack0),          //写请求
463     .data       (sdram_dout0),            //写数据
464     
465     //用户接口
466     .rdclk      (clk_read),              //读时钟
467     .rdreq      (rdf_rdreq0),             //读请求
468     .q          (rdf_dout0),              //读数据
469     
470     .wrusedw    (rdf_use0),               //FIFO中的数据量
471     .aclr       (~rst_n | rd_load_flag)   //异步清零信号 
472;    
473 endmodule 

FIFO例化是一个FIFO IP核被例化两次,当两个FIFO来用。SDRAM控制模块的代码到这里就讲完了,为了方便大家更好的去理解整个状态的跳转,下面给出了FIFO控制模块的原理示意图:

图 55.4.3 FIFO控制模块原理图
从上图中来分析FIFO控制模块的运行机制就简单的多了,双目摄像头分别往两个FIFO中写数据,FIFO数据存满两个突发长度后,其中FIFO1会交替往SDRAM的BANK0和BANK1中写数据,而FIFO2会交替往SDRAM的BANK2和BANK3中写数据,一个FIFO对应一个摄像头。读FIFO也一样,一个FIFO对应一个摄像头,比如“读FIFO1”它对应摄像头1,因此它就不断交替的从BANK0和BANK1的“空间”中读取数据,“读FIFO2”就不断从两个BANK2和BANK3的“空间”中读取数据,读取出来的数据最终传输到LCD显示屏上去显示,一个读FIFO的数据只占半个屏幕,这样两个摄像头的数据就能在同一个屏幕上一左一右的显示出来了。FIFO控制模块的整个运行机制就是这样的。
最后我们再来看看LCD显示模块作了哪些修改,修改代码如下:

1   module lcd_disply(
2       input              lcd_clk,      //lcd模块驱动时钟
3       input              sys_rst_n,  
锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章