搜索
bottom↓
回复: 0

《新起点V2之FPGA开发指南》第五十五章 双目OV5640摄像头RGB-LCD

[复制链接]

出0入234汤圆

发表于 2021-11-1 15:05:30 | 显示全部楼层 |阅读模式
1)实验平台:正点原子新起点V2FPGA开发板
2)  章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-328002-1-1.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122
1.png


2.jpg


3.png



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

       双目摄像头是在一个模组上集成了两个摄像头,实现双通道图像采集的功能。双目摄像头一般应用于安防监控、立体视觉测距、三维重建等领域。本试验只做最基础的工作,把双目OV5640摄像头实时采集到的图像分左右两半显示在LCD屏幕上。
       本章包括以下几个部分:
       1.1简介
       1.2实验任务
       1.3硬件设计
       1.4程序设计
       1.5下载验证

1.1简介
       摄像头在日常生活中非常常见,一般分为单目摄像头、双目摄像头和多目摄像头。单目摄像头目前使用最为广泛;双目摄像头主要应用于单目摄像头无法胜任的场合,如测距领域,根据两个摄像头的视差,辅以一定的算法,人们可以计算物体的距离;当然针对一些特殊的应用,目前市场上也出现了多目摄像头,以应对更加复杂的场景。在“OV5640摄像头LCD显示实验”中对OV5640的视频传输时序、SCCB协议以及寄存器的配置信息等内容作了详细的介绍,如果大家对这部分内容不是很熟悉的话,请参考之前的实验。本次实验将在前面单目OV5640摄像头的基础上学习双目摄像头的LCD显示。
1.2实验任务
       本章实验任务是利用双目OV5640摄像头采集图像,将采集到的图像实时显示在LCD屏幕上,两幅图像分别占据LCD屏的左右半边。
1.3硬件设计
       新起点开发板上有两个扩展口,分别是P6和P7。新起点开发板的P6扩展口与LCD屏的管脚复用,故本次实验采用P7扩展口来连接双目OV5640摄像头。P7扩展口原理图如图 55.3.1所示:
第五十五章 双目OV5640摄像头RGB664.png

图 55.3.1 P7扩展接口原理图

       ATK-Dual-OV5640是正点原子推出的一款双目OV5640摄像头模块,其硬件原理图如下图所示:
第五十五章 双目OV5640摄像头RGB780.png

图 55.3.2 ATK-Dual-OV5640原理图

       该模块通过2*20排母(2.54mm间距)同外部连接,连接时将双目摄像头的排母直接插在开发板上的P7扩展口即可,模块实物连接图如图 55.3.3所示:
第五十五章 双目OV5640摄像头RGB953.png

图 55.3.3 双目摄像头模块实物连接图

       由于LCD接口和SDRAM引脚数目较多且在前面相应的章节中已经给出它们的管脚列表,这里只列出双目摄像头相关管脚分配,如下表所示:
1.png

2.png

表 55.3.1 OV5640摄像头管脚分配


       双目摄像头TCL约束文件如下:
  1. set_location_assignment PIN_L7 -to cam0_data[7]
  2. set_location_assignment PIN_L6 -to cam0_data[5]
  3. set_location_assignment PIN_K5 -to cam0_data[3]
  4. set_location_assignment PIN_K6 -to cam0_data[1]
  5. set_location_assignment PIN_F3 -to cam0_rst_n
  6. set_location_assignment PIN_G5 -to cam0_href
  7. set_location_assignment PIN_M6 -to cam0_vsync
  8. set_location_assignment PIN_N3 -to cam0_pclk
  9. set_location_assignment PIN_L3 -to cam0_data[6]
  10. set_location_assignment PIN_L4 -to cam0_data[4]
  11. set_location_assignment PIN_K8 -to cam0_data[2]
  12. set_location_assignment PIN_G1 -to cam0_data[0]
  13. set_location_assignment PIN_J6 -to cam0_sda
  14. set_location_assignment PIN_F1 -to cam0_scl
  15. set_location_assignment PIN_J1 -to cam0_pwdn
  16. set_location_assignment PIN_A2 -to cam1_pwdn
  17. set_location_assignment PIN_B5 -to cam1_data[7]
  18. set_location_assignment PIN_B6 -to cam1_data[5]
  19. set_location_assignment PIN_B7 -to cam1_data[3]
  20. set_location_assignment PIN_A4 -to cam1_data[1]
  21. set_location_assignment PIN_D3 -to cam1_rst_n
  22. set_location_assignment PIN_C6 -to cam1_href
  23. set_location_assignment PIN_E5 -to cam1_vsync
  24. set_location_assignment PIN_A3 -to cam1_pclk
  25. set_location_assignment PIN_A5 -to cam1_data[6]
  26. set_location_assignment PIN_A6 -to cam1_data[4]
  27. set_location_assignment PIN_B3 -to cam1_data[2]
  28. set_location_assignment PIN_B4 -to cam1_data[0]
  29. set_location_assignment PIN_F5 -to cam1_sda
  30. 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顶层模块做了修改。
第五十五章 双目OV5640摄像头RGB3960.png

图 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的用户接口,非常方便用户的使用。
下面是顶层模块的原理图:
第五十五章 双目OV5640摄像头RGB4696.png

图 55.4.2 顶层模块原理图

       本次实验是在单目OV5640摄像头LCD显示实验的基础上作修改的,主要修改了SDRAM控制模块和LCD顶层模块,而其余模块基本相同,因此接下来只介绍修改部分的内容。前面的实验,使用读写各一个FIFO,并在SDRAM中开辟了两个缓冲区,而本实验有两个摄像头,所以读写需要各增加一个FIFO,总共四个,同时在SDRAM中再开辟两个缓冲区,达到四个缓冲区,而满足两组摄像头数据的处理需要。
       下面我们就一起来看看本节实验在单目OV5640摄像头LCD显示实验的基础上作了哪些修改。首先我们先看下顶层模块,
       顶层模块最大的改变就是摄像头驱动模块了,代码如下(只贴出修改部分):
  1. 125 //OV5640 0摄像头驱动
  2. 126 ov5640_dri u0_ov5640_dri(
  3. 127     .clk               (clk_100m),
  4. 128     .rst_n             (rst_n),
  5. 129
  6. 130     .cam_pclk          (cam0_pclk ),
  7. 131     .cam_vsync         (cam0_vsync),
  8. 132     .cam_href          (cam0_href ),
  9. 133     .cam_data          (cam0_data ),
  10. 134     .cam_rst_n         (cam0_rst_n),
  11. 135     .cam_pwdn          (cam0_pwdn ),
  12. 136     .cam_scl           (cam0_scl  ),
  13. 137     .cam_sda           (cam0_sda  ),
  14. 138     
  15. 139     .capture_start     (sdram_init_done),
  16. 140     .cmos_h_pixel      (cmos_h_pixel[12:1]),
  17. 141     .cmos_v_pixel      (cmos_v_pixel),
  18. 142     .total_h_pixel     (total_h_pixel),
  19. 143     .total_v_pixel     (total_v_pixel),
  20. 144     .cam_init_done     (cam_init_done_0),   
  21. 145
  22. 146     .cmos_frame_vsync  (),
  23. 147     .cmos_frame_href   (),
  24. 148     .cmos_frame_valid  (wr0_en),
  25. 149     .cmos_frame_data   (wr0_data)
  26. 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中开辟两个存储空间,一个空间正在缓存数据另一个空间就可以往外读出数据,这样交替使用。但是本节实验使用的是双目摄像头,有两个数据源,因此原本的两个存储空间肯定是不够用的,所以我们在原本的基础上将两个存储空间都扩大成四个存储空间,这样就可以容纳两个摄像头的数据了代码如下:
  1. 199 //sdram写地址0产生模块
  2. 200 always @(posedge clk_ref or negedge rst_n) begin
  3. 201     if(!rst_n)begin
  4. 202         sdram_wr_addr0 <= 24'd0;
  5. 203         rw_bank_flag0  <= 0;
  6. 204         sw_bank_en0    <= 0;
  7. 205     end
  8. 206     else if(wr_load_flag)begin              //检测到写端口复位信号时,写地址复位
  9. 207         sdram_wr_addr0 <= wr_min_addr;
  10. 208         rw_bank_flag0  <= 0;
  11. 209         sw_bank_en0    <= 0;
  12. 210     end                                     //若突发写SDRAM结束更改写地址
  13. 211     else if(write_done_flag && !wr_fifo_flag) begin
  14. 212         if(sdram_pingpang_en) begin         //SDRAM 读写乒乓使能
  15. 213                                             //若未到达写SDRAM的结束地址写地址累加
  16. 214             if(sdram_wr_addr0[21:0] < wr_max_addr - wr_length)
  17. 215                 sdram_wr_addr0 <= sdram_wr_addr0 + wr_length;
  18. 216             else begin                      //切换BANK
  19. 217                 rw_bank_flag0 <= ~rw_bank_flag0;   
  20. 218                 sw_bank_en0 <= 1'b1;        //拉高切换BANK使能信号
  21. 219             end            
  22. 220         end                                 //乒乓操作不使能时
  23. 221                                             //判断是否到达结束地址                     
  24. 222         else if(sdram_wr_addr0 < wr_max_addr - wr_length)
  25. 223                                             //没达结束地址,地址累加一个突发长度
  26. 224             sdram_wr_addr0 <= sdram_wr_addr0 + wr_length;
  27. 225         else                                //若已到达结束地址,则回到写起始地址
  28. 226             sdram_wr_addr0 <= wr_min_addr;
  29. 227     end   
  30. 228     else if(sw_bank_en0) begin              //如果bank切换使能信号有效
  31. 229         sw_bank_en0 <= 1'b0;                //将使能信号置0,方便下次使用
  32. 230         if(rw_bank_flag0 == 1'b0)           //根据bank标志信号切换BANK
  33. 231             sdram_wr_addr0 <= {2'b00,wr_min_addr[21:0]};
  34. 232         else
  35. 233             sdram_wr_addr0 <= {2'b01,wr_min_addr[21:0]};     
  36. 234 end
  37. 235 end
  38. 236
  39. 237 //sdram写地址1产生模块
  40. 238 always @(posedge clk_ref or negedge rst_n) begin
  41. 239     if(!rst_n)begin
  42. 240         sdram_wr_addr1 <= 24'd0;
  43. 241         rw_bank_flag1  <= 0;
  44. 242         sw_bank_en1    <= 0;
  45. 243     end
  46. 244     else if(wr_load_flag)begin              //检测到写端口复位信号时,写地址复位
  47. 245         rw_bank_flag1  <= 0;
  48. 246         sw_bank_en1    <= 0;
  49. 247         sdram_wr_addr1 <= wr_max_addr;
  50. 248     end                                     //若突发写SDRAM结束,更改写地址
  51. 249     else if(write_done_flag && wr_fifo_flag) begin
  52. 250         if(sdram_pingpang_en) begin         //判断若SDRAM 读写乒乓使能
  53. 251                                             //若未到达写SDRAM的结束地址写地址累加
  54. 252         if(sdram_wr_addr1[21:0] < wr_max_addr*2 - wr_length)
  55. 253                 sdram_wr_addr1 <= sdram_wr_addr1 + wr_length;
  56. 254             else begin                      //切换BANK
  57. 255                 rw_bank_flag1 <= ~rw_bank_flag1;   
  58. 256                 sw_bank_en1 <= 1'b1;        //拉高切换BANK使能信号
  59. 257             end            
  60. 258         end                                 //乒乓操作不使能
  61. 259                                             //未到达写SDRAM的结束地址写地址累加
  62. 260         else if(sdram_wr_addr1 < wr_max_addr*2 - wr_length)
  63. 261             sdram_wr_addr1 <= sdram_wr_addr1 + wr_length;
  64. 262             else                            //到达写SDRAM的结束地址回到写起始地址
  65. 263             sdram_wr_addr1 <= wr_max_addr;
  66. 264     end
  67. 265     else if(sw_bank_en1) begin              //如果bank切换使能信号有效
  68. 266         sw_bank_en1 <= 1'b0;                //将使能信号置0,方便下次使用
  69. 267         if(rw_bank_flag1 == 1'b0)           //切换BANK
  70. 268             sdram_wr_addr1 <= {2'b10,wr_max_addr[21:0]};
  71. 269         else
  72. 270             sdram_wr_addr1 <= {2'b11,wr_max_addr[21:0]};     
  73. 271     end
  74. 272 end
  75. 273
  76. 274 //sdram读地址0产生模块
  77. 275 always @(posedge clk_ref or negedge rst_n) begin
  78. 276     if(!rst_n)
  79. 277         sdram_rd_addr0 <= 24'd0;   
  80. 278     else if(rd_load_flag)                   //检测到写端口复位信号时,写地址复位
  81. 279         sdram_rd_addr0 <= rd_min_addr;      //若突发读SDRAM结束,更改读地址
  82. 280     else if(read_done_flag && !rd_fifo_flag ) begin
  83. 281         if(sdram_pingpang_en) begin         //判断若SDRAM 读写乒乓使能  
  84. 282                                             //若未到达SDRAM的结束地址则地址累加
  85. 283             if(sdram_rd_addr0[21:0] < rd_max_addr - rd_length)
  86. 284                 sdram_rd_addr0 <= sdram_rd_addr0 + rd_length;                                                
  87. 285             else begin                      //到达读SDRAM的结束地址,回到读起始                     
  88. 286                 if(rw_bank_flag0 == 1'b0)   //根据rw_bank_flag的值切换读BANK地址
  89. 287                     sdram_rd_addr0 <= {2'b01,rd_min_addr[21:0]};
  90. 288                 else
  91. 289                     sdram_rd_addr0 <= {2'b00,rd_min_addr[21:0]};   
  92. 290             end   
  93. 291         end                                  //若乒乓操作未使能
  94. 292                                             //未到达SDRAM的结束地址地址累加
  95. 293         else if(sdram_rd_addr0 < rd_max_addr - rd_length)
  96. 294             sdram_rd_addr0 <= sdram_rd_addr0 + rd_length;
  97. 295         else                                 //若到达SDRAM的结束地址回到起始地址
  98. 296             sdram_rd_addr0 <= rd_min_addr;
  99. 297     end
  100. 298 end
  101. 299
  102. 300 //sdram读地址1产生模块
  103. 301 always @(posedge clk_ref or negedge rst_n) begin
  104. 302     if(!rst_n)
  105. 303         sdram_rd_addr1 <= 24'd0;   
  106. 304     else if(rd_load_flag)                    //检测到复位信号时地址复位
  107. 305         sdram_rd_addr1 <= rd_max_addr;  
  108. 306                                             //判断若突发读SDRAM结束
  109. 307     else if(read_done_flag && rd_fifo_flag) begin
  110. 308         if(sdram_pingpang_en) begin          //若SDRAM 读写乒乓使能
  111. 309                                             //若未到达SDRAM的结束地址则地址累加        
  112. 310             if(sdram_rd_addr1[21:0] < rd_max_addr*2 - rd_length)
  113. 311                 sdram_rd_addr1 <= sdram_rd_addr1 + rd_length;                                                
  114. 312             else begin                       //到达读SDRAM的结束地址                    
  115. 313                 if(rw_bank_flag1 == 1'b0)    //根据rw_bank_flag的值切换BANK地址
  116. 314                     sdram_rd_addr1 <= {2'b11,rd_max_addr[21:0]};
  117. 315                 else
  118. 316                     sdram_rd_addr1 <= {2'b10,rd_max_addr[21:0]};   
  119. 317             end   
  120. 318         end                                  //如果乒乓操作没有使能
  121. 319                                             //未到达SDRAM的结束地址地址累加
  122. 320         else if(sdram_rd_addr1 < rd_max_addr*2 - rd_length)
  123. 321             sdram_rd_addr1 <= sdram_rd_addr1 + rd_length;
  124. 322         else                                 //若已到达SDRAM的结束地址回到起始地址
  125. 323             sdram_rd_addr1 <= rd_max_addr;
  126. 324     end
  127. 325 end
复制代码

       由于FIFO控制模块代码太长,因此我一段一段的讲解,上面这段代码就是读写地址控制,相比较于单目摄像头LCD显示实验可以看到读写地址都增加了一帧的空间。代码200~235行跟之前的单目一样就是在SDRAM中开辟一个空间用来存储一帧图片,写满一帧后BANK地址切换继续写下一帧,但是本节是经验就不一样了,代码237~272行又开辟了一帧的存储空间,这一帧的存储空间跟上一帧不在同一BANK上。像这样开辟两帧的空间刚好可以用来分别存储两个摄像头的数据,当两个摄像头的数据都各存一帧后就可以切换BANK继续去存储两个摄像头下一帧的数据了。接下来就进入读地址了,读地址和写地址是相对应的,当写地址完成一个BANK的数据存储切换到下一个BANK后,读地址开始去读这个已经存储了数据的BANK,当写地址写完下一个BANK又切换回来则读地址切换到另一个BANK上去,这样周而复始的交替进行读写就可以实现摄像头数据的缓存了。接下来我们再来看看读写状态是如何切换的,代码如下所示:
  1. 326
  2. 327 //读写端四个FIFO的判断逻辑      
  3. 328 always@(posedge clk_ref or negedge rst_n) begin
  4. 329     if(!rst_n) begin                     
  5. 330         sdram_wr_req  <= 0;
  6. 331         sdram_wr_addr <= sdram_wr_addr0;
  7. 332         wr_fifo_flag  <= 0;
  8. 333         
  9. 334         sdram_rd_req  <= 0;
  10. 335         rd_fifo_flag  <= 0;
  11. 336         sdram_rd_addr <= sdram_rd_addr0;
  12. 337         state         <= idle;          //复位处于空闲状态,不操作任何FIFO
  13. 338     end
  14. 339     else begin
  15. 340         case(state)
  16. 341             idle:begin
  17. 342                 if(sdram_init_done)
  18. 343                     state <=  sdram_done;//SDRAM初始化完成进入sdram_done状态
  19. 344             end
  20. 345             sdram_done:begin            //在sdram_done状态对四个FIFO的读写操作进行判断
  21. 346                 if(wrf_use0 >= wr_length*2) begin //进入写端FIFO0的读状态状态
  22. 347                     sdram_wr_req  <= 1;
  23. 348                     sdram_wr_addr <= sdram_wr_addr0;
  24. 349                     wr_fifo_flag  <= 0;
  25. 350                     
  26. 351                     sdram_rd_req  <= 0;
  27. 352                     sdram_rd_addr <= sdram_rd_addr0;
  28. 353                     rd_fifo_flag  <= 0;
  29. 354                     state <= wr_keep;
  30. 355                     
  31. 356                 end
  32. 357         
  33. 358                 else if(wrf_use1 >= wr_length*2) begin//进入写端FIFO1的读状态状态
  34. 359                     sdram_wr_req  <= 1;
  35. 360                     sdram_wr_addr <= sdram_wr_addr1;
  36. 361                     wr_fifo_flag  <= 1;
  37. 362                     
  38. 363                     sdram_rd_req  <= 0;
  39. 364                     sdram_rd_addr <= sdram_rd_addr0;
  40. 365                     rd_fifo_flag  <= 0;
  41. 366                     
  42. 367                     state <= wr_keep;
  43. 368                 end
  44. 369                 else if((rdf_use0 < rd_length*2)//进入读端FIFO0的写状态状态
  45. 370                 ) begin
  46. 371                     sdram_wr_req  <= 0;
  47. 372                     sdram_wr_addr <= sdram_wr_addr0;
  48. 373                     wr_fifo_flag  <= 0;
  49. 374                 
  50. 375                     sdram_rd_req  <= 1;
  51. 376                     sdram_rd_addr <= sdram_rd_addr0;
  52. 377                     rd_fifo_flag  <= 0;
  53. 378                     state <= rd_keep;
  54. 379                 end
  55. 380                 else if((rdf_use1 < rd_length*2)//进入读端FIFO1的写状态状态
  56. 381                 ) begin
  57. 382                     sdram_wr_req  <= 0;
  58. 383                     sdram_wr_addr <= sdram_wr_addr0;
  59. 384                     wr_fifo_flag  <= 0;
  60. 385                 
  61. 386                     sdram_rd_req  <= 1;
  62. 387                     sdram_rd_addr <= sdram_rd_addr1;
  63. 388                     rd_fifo_flag  <= 1;
  64. 389                     state <= rd_keep;
  65. 390                 end  
  66. 391             end
  67. 392                 wr_keep:begin
  68. 393                     if(write_done_flag) begin  //保持写状态
  69. 394                     sdram_wr_req  <= 0;
  70. 395                     sdram_wr_addr <= sdram_wr_addr0;
  71. 396                     wr_fifo_flag  <= 0;
  72. 397                     state <= sdram_done;
  73. 398                 end   
  74. 399                 end
  75. 400                 rd_keep:begin
  76. 401                     if(read_done_flag) begin  //保持读状态
  77. 402                     sdram_rd_req  <= 0;
  78. 403                     sdram_rd_addr <= sdram_rd_addr0;
  79. 404                     rd_fifo_flag  <= 0;
  80. 405                     state <= sdram_done;
  81. 406                 end   
  82. 407                 end   
  83. 408                 default : state <= idle;    //默认停在空闲状态
  84. 409         endcase   
  85. 410     end
  86. 411 end
  87. 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的例化,代码如下:      
  1. 413 //例化写端口FIFO0
  2. 414 wrfifo  u_wrfifo0(
  3. 415     //用户接口
  4. 416     .wrclk      (clk_write0),             //写时钟
  5. 417     .wrreq      (wrf_wrreq0),             //写请求
  6. 418     .data       (wrf_din0),               //写数据
  7. 419     //sdram接口
  8. 420     .rdclk      (clk_ref),                //读时钟
  9. 421     .rdreq      (sdram_wr_ack0),          //读请求
  10. 422     .q          (sdram_din0),             //读数据
  11. 423     
  12. 424     .rdusedw    (wrf_use0),               //FIFO中的数据量
  13. 425     .aclr       (~rst_n | wr_load_flag)   //异步清零信号
  14. 426     );
  15. 427     
  16. 428 //例化写端口FIFO1
  17. 429 wrfifo  u_wrfifo1(
  18. 430     //用户接口
  19. 431     .wrclk      (clk_write1),             //写时钟
  20. 432     .wrreq      (wrf_wrreq1),             //写请求
  21. 433     .data       (wrf_din1),               //写数据
  22. 434     //sdram接口
  23. 435     .rdclk      (clk_ref),                //读时钟
  24. 436     .rdreq      (sdram_wr_ack1),          //读请求
  25. 437     .q          (sdram_din1),             //读数据
  26. 438
  27. 439     .rdusedw    (wrf_use1),               //FIFO中的数据量
  28. 440     .aclr       (~rst_n | wr_load_flag)   //异步清零信号
  29. 441     );      
  30. 442     
  31. 443 //例化读端口FIFO0
  32. 444 rdfifo  u_rdfifo1(
  33. 445     //sdram接口
  34. 446     .wrclk      (clk_ref),                //写时钟
  35. 447     .wrreq      (sdram_rd_ack1),          //写请求
  36. 448     .data       (sdram_dout1),            //写数据
  37. 449     
  38. 450     //用户接口
  39. 451     .rdclk      (clk_read),              //读时钟
  40. 452     .rdreq      (rdf_rdreq1),             //读请求
  41. 453     .q          (rdf_dout1),              //读数据
  42. 454     
  43. 455     .wrusedw    (rdf_use1),               //FIFO中的数据量
  44. 456     .aclr       (~rst_n | rd_load_flag)   //异步清零信号   
  45. 457     );
  46. 458 //例化读端口FIFO1
  47. 459 rdfifo  u_rdfifo0(
  48. 460     //sdram接口
  49. 461     .wrclk      (clk_ref),                //写时钟
  50. 462     .wrreq      (sdram_rd_ack0),          //写请求
  51. 463     .data       (sdram_dout0),            //写数据
  52. 464     
  53. 465     //用户接口
  54. 466     .rdclk      (clk_read),              //读时钟
  55. 467     .rdreq      (rdf_rdreq0),             //读请求
  56. 468     .q          (rdf_dout0),              //读数据
  57. 469     
  58. 470     .wrusedw    (rdf_use0),               //FIFO中的数据量
  59. 471     .aclr       (~rst_n | rd_load_flag)   //异步清零信号   
  60. 472     );   
  61. 473 endmodule
复制代码

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

图 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. 1   module lcd_disply(
  2. 2       input              lcd_clk,      //lcd模块驱动时钟
  3. 3       input              sys_rst_n,    //复位信号
  4. 4       //RGB LCD接口                             
  5. 5       input      [ 10:0] pixel_xpos,   //像素点横坐标
  6. 6       input      [ 10:0] pixel_ypos,   //像素点纵坐标
  7. 7       input      [15:0]  lcd_id ,      //LCD的ID   
  8. 8       input      [15:0]  rd_data,      //图像像素值
  9. 9       input      [12:0]  rd_h_pixel,   //摄像头输出的水平方向分辨率
  10. 10      output reg [15:0]  pixel_data    //像素点数据,
  11. 11      );
  12. 12  
  13. 13  //LCD的ID
  14. 14  parameter  ID_4342 =   16'h4342;
  15. 15  parameter  ID_7084 =   16'h7084;
  16. 16  parameter  ID_7016 =   16'h7016;
  17. 17  parameter  ID_1018 =   16'h1018;
  18. 18  parameter  ID_4384 =   16'h4384;
  19. 19  //颜色定义
  20. 20  localparam RED    = 16'b11111_000000_00000;     //字符颜色
  21. 21  localparam BLUE   = 16'b00000_000000_11111;     //字符区域背景色
  22. 22  localparam BLACK  = 16'b00000_000000_00000;     //屏幕背景色  
  23. 23  //reg define                                    
  24. 24  reg  [63:0]  char0[15:0];                       //字符数组0
  25. 25  reg  [63:0]  char1[15:0];                       //字符数组1
  26. 26  reg  [127:0] char2[32:0];                       //字符数组2
  27. 27  reg  [127:0] char3[32:0];                       //字符数组3
  28. 28  
  29. 29  //给字符数组0的赋值:OV5640 0 (16*64)
  30. 30  always @(posedge lcd_clk) begin
  31. 31      char0[0]  <= 64'h0000000000000000      ;
  32. 32      char0[1]  <= 64'h0000000000000000      ;
  33. 33      char0[2]  <= 64'h0000000000000000      ;
  34. 34      char0[3]  <= 64'h38E77E1804180008      ;
  35. 35      char0[4]  <= 64'h444240240C240038      ;
  36. 36      char0[5]  <= 64'h824240400C420008      ;
  37. 37      char0[6]  <= 64'h8244404014420008      ;
  38. 38      char0[7]  <= 64'h8224785C24420008      ;
  39. 39      char0[8]  <= 64'h8224446224420008      ;
  40. 40      char0[9]  <= 64'h8228024244420008      ;
  41. 41      char0[10] <= 64'h822802427F420008      ;
  42. 42      char0[11] <= 64'h8218424204420008      ;
  43. 43      char0[12] <= 64'h4410442204240008      ;
  44. 44      char0[13] <= 64'h3810381C1F18003E      ;
  45. 45      char0[14] <= 64'h0000000000000000      ;
  46. 46      char0[15] <= 64'h0000000000000000      ;
  47. 47  end
  48. 48  
  49. 49  //给字符数组1的赋值: OV5640 1 (16*64)
  50. 50  always @(posedge lcd_clk) begin
  51. 51      char1[0]  <= 64'h0000000000000000      ;
  52. 52      char1[1]  <= 64'h0000000000000000      ;
  53. 53      char1[2]  <= 64'h0000000000000000      ;
  54. 54      char1[3]  <= 64'h38E77E180418003C      ;
  55. 55      char1[4]  <= 64'h444240240C240042      ;
  56. 56      char1[5]  <= 64'h824240400C420042      ;
  57. 57      char1[6]  <= 64'h8244404014420042      ;
  58. 58      char1[7]  <= 64'h8224785C24420002      ;
  59. 59      char1[8]  <= 64'h8224446224420004      ;
  60. 60      char1[9]  <= 64'h8228024244420008      ;
  61. 61      char1[10] <= 64'h822802427F420010      ;   
  62. 62      char1[11] <= 64'h8218424204420020      ;
  63. 63      char1[12] <= 64'h4410442204240042      ;
  64. 64      char1[13] <= 64'h3810381C1F18007E      ;
  65. 65      char1[14] <= 64'h0000000000000000      ;
  66. 66      char1[15] <= 64'h0000000000000000      ;
  67. 67  end
  68. 68  
  69. 69  //给字符数组2的赋值: OV5640 0 (32*128)
  70. 70  always @(posedge lcd_clk) begin               
  71. 71      char2[0]  <= 128'h00000000000000000000000000000000;
  72. 72      char2[1]  <= 128'h00000000000000000000000000000000;
  73. 73      char2[2]  <= 128'h00000000000000000000000000000000;
  74. 74      char2[3]  <= 128'h00000000000000000000000000000000;
  75. 75      char2[4]  <= 128'h00000000000000000000000000000000;
  76. 76      char2[5]  <= 128'h00000000000000000000000000000000;
  77. 77      char2[6]  <= 128'h03C07C1E0FFC01E0006003C000000080;
  78. 78      char2[7]  <= 128'h0C30180C0FFC06180060062000000180;
  79. 79      char2[8]  <= 128'h1818180810000C1800E00C3000001F80;
  80. 80      char2[9]  <= 128'h100818081000081800E0181800000180;
  81. 81      char2[10] <= 128'h300C1808100018000160181800000180;
  82. 82      char2[11] <= 128'h300C0C10100010000160180800000180;
  83. 83      char2[12] <= 128'h60040C10100010000260300C00000180;
  84. 84      char2[13] <= 128'h60060C10100030000460300C00000180;
  85. 85      char2[14] <= 128'h60060C1013E033E00460300C00000180;
  86. 86      char2[15] <= 128'h60060C20143036300860300C00000180;
  87. 87      char2[16] <= 128'h60060620181838180860300C00000180;
  88. 88      char2[17] <= 128'h60060620100838081060300C00000180;
  89. 89      char2[18] <= 128'h60060620000C300C3060300C00000180;
  90. 90      char2[19] <= 128'h60060640000C300C2060300C00000180;
  91. 91      char2[20] <= 128'h60060340000C300C4060300C00000180;
  92. 92      char2[21] <= 128'h20060340000C300C7FFC300C00000180;
  93. 93      char2[22] <= 128'h300C0340300C300C0060180800000180;
  94. 94      char2[23] <= 128'h300C0380300C180C0060181800000180;
  95. 95      char2[24] <= 128'h10080180201818080060181800000180;
  96. 96      char2[25] <= 128'h1818018020180C1800600C3000000180;
  97. 97      char2[26] <= 128'h0C30010018300E3000600620000003C0;
  98. 98      char2[27] <= 128'h03C0010007C003E003FC03C000001FF8;
  99. 99      char2[28] <= 128'h00000000000000000000000000000000;
  100. 100     char2[29] <= 128'h00000000000000000000000000000000;
  101. 101     char2[30] <= 128'h00000000000000000000000000000000;
  102. 102     char2[31] <= 128'h00000000000000000000000000000000;
  103. 103 end
  104. 104
  105. 105 //给字符数组3的赋值: OV5640 1 (32*128)
  106. 106 always @(posedge lcd_clk) begin               
  107. 107     char3[0]  <= 128'h00000000000000000000000000000000;
  108. 108     char3[1]  <= 128'h00000000000000000000000000000000;
  109. 109     char3[2]  <= 128'h00000000000000000000000000000000;
  110. 110     char3[3]  <= 128'h00000000000000000000000000000000;
  111. 111     char3[4]  <= 128'h00000000000000000000000000000000;
  112. 112     char3[5]  <= 128'h00000000000000000000000000000000;
  113. 113     char3[6]  <= 128'h03C07C1E0FFC01E0006003C0000007E0;
  114. 114     char3[7]  <= 128'h0C30180C0FFC06180060062000000838;
  115. 115     char3[8]  <= 128'h1818180810000C1800E00C3000001018;
  116. 116     char3[9]  <= 128'h100818081000081800E018180000200C;
  117. 117     char3[10] <= 128'h300C180810001800016018180000200C;
  118. 118     char3[11] <= 128'h300C0C1010001000016018080000300C;
  119. 119     char3[12] <= 128'h60040C10100010000260300C0000300C;
  120. 120     char3[13] <= 128'h60060C10100030000460300C0000000C;
  121. 121     char3[14] <= 128'h60060C1013E033E00460300C00000018;
  122. 122     char3[15] <= 128'h60060C20143036300860300C00000018;
  123. 123     char3[16] <= 128'h60060620181838180860300C00000030;
  124. 124     char3[17] <= 128'h60060620100838081060300C00000060;
  125. 125     char3[18] <= 128'h60060620000C300C3060300C000000C0;
  126. 126     char3[19] <= 128'h60060640000C300C2060300C00000180;
  127. 127     char3[20] <= 128'h60060340000C300C4060300C00000300;
  128. 128     char3[21] <= 128'h20060340000C300C7FFC300C00000200;
  129. 129     char3[22] <= 128'h300C0340300C300C0060180800000404;
  130. 130     char3[23] <= 128'h300C0380300C180C0060181800000804;
  131. 131     char3[24] <= 128'h10080180201818080060181800001004;
  132. 132     char3[25] <= 128'h1818018020180C1800600C300000200C;
  133. 133     char3[26] <= 128'h0C30010018300E300060062000003FF8;
  134. 134     char3[27] <= 128'h03C0010007C003E003FC03C000003FF8;
  135. 135     char3[28] <= 128'h00000000000000000000000000000000;
  136. 136     char3[29] <= 128'h00000000000000000000000000000000;
  137. 137     char3[30] <= 128'h00000000000000000000000000000000;
  138. 138     char3[31] <= 128'h00000000000000000000000000000000;                                 
  139. 139 end
  140. 140
  141. 141 //显示逻辑判断
  142. 142 always@(*) begin
  143. 143     if ( pixel_ypos >= 0 && pixel_ypos < 33)begin
  144. 144         if(pixel_xpos < (rd_h_pixel[12:2]+64)
  145. 145         && pixel_xpos >= (rd_h_pixel[12:2]-64) )begin
  146. 146             if(char2[pixel_ypos][127-(pixel_xpos-rd_h_pixel[12:2]+64)])
  147. 147                 pixel_data =BLUE;
  148. 148             else
  149. 149             pixel_data = rd_data;
  150. 150         end
  151. 151         else if(pixel_xpos < (rd_h_pixel[12:2]*3+64)
  152. 152         && pixel_xpos >= (rd_h_pixel[12:2]*3-64))begin
  153. 153             if(char3[pixel_ypos][63-pixel_xpos+(rd_h_pixel[12:2])*3])
  154. 154                 pixel_data =BLUE;
  155. 155             else
  156. 156                 pixel_data = rd_data;
  157. 157         end
  158. 158         else
  159. 159             pixel_data = rd_data;
  160. 160     end      
  161. 161     else
  162. 162         pixel_data = rd_data;
  163. 163 end
  164. 164
  165. 165 endmodule
复制代码

       其实LCD显示部分主要就是加了一个字符显示,因为我们的两个摄像头是一左一右显示在LCD屏幕上的,为了确定哪个摄像头显示在哪边我们会在显示画面上标注“OV5640 1”和“OV5640 2”字样。所以我们在LCD显示部分中加入了字符显示模块(lcd_disply)。代码30~139行就是生成“OV5640 1”和“OV5640 2”这两个字符串的字模,代码142~163行就是把“OV5640 1”和“OV5640 2”这两个字符串显示到屏幕上去。具体的显示原理这里就不再详细介绍了,如果大家有不懂的可以去看前面LCD字符显示实验和RTC实时时钟显示实验。
       到这里本节双目摄像头LCD显示实验的程序设计部分就讲解完了,接下来就可以把程序下载到开发板上去验证了。
1.5下载验证
       首先我们将双目摄像头插到开发板的P7扩展口,然后连接LCD显示屏,最后打开电源,下载sof文件到开发板上去,硬件连接图如下所示:
第五十五章 双目OV5640摄像头RGB28348.png

图 55.5.1 硬件连接

        显示的效果如上图所示。


阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-27 09:49

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表