搜索
bottom↓
回复: 0

【正点原子FPGA连载】第四十八章双目OV5640摄像头RGB-LCD显示实验

[复制链接]

出0入234汤圆

发表于 2020-12-10 11:06:29 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2021-1-23 15:30 编辑

1)实验平台:正点原子达芬奇FPGA开发板
2)  章节摘自【正点原子】达芬奇之FPGA开发指南
3)购买链接:https://detail.tmall.com/item.htm?id=624335496505
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_dafenqi.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)对正点原子FPGA感兴趣的同学可以加群讨论:
905624739 点击加入: QQ群头像.png


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

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



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


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

图 48.3.1 J2扩展接口原理图

ATK-Dual-OV5640是正点原子推出的一款双目OV5640摄像头模块,其硬件原理图如下图所示:
基于OV5640的二值化实验791.png

图 48.3.2 ATK-Dual-OV5640原理图

该模块通过2*20排母(2.54mm间距)同外部连接,连接时将双目摄像头的排母直接插在开发板上的J2扩展口即可,模块实物连接图如图 48.3.3所示:
基于OV5640的二值化实验980.png

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

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

双目摄像头XDC约束文件如下:
#CAMERA
################ J2 ###########################
set_property -dict {PACKAGE_PIN L19 IOSTANDARD LVCMOS33} [get_ports cam_scl_1]
set_property -dict {PACKAGE_PIN U20 IOSTANDARD LVCMOS33} [get_ports cam_sda_1]
set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[0]}]
set_property -dict {PACKAGE_PIN N20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[2]}]
set_property -dict {PACKAGE_PIN J20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[4]}]
set_property -dict {PACKAGE_PIN H20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[6]}]
set_property -dict {PACKAGE_PIN M18 IOSTANDARD LVCMOS33} [get_ports cam_pclk_1]
create_clock -period 40.000 -name cmos_pclk [get_ports cam_pclk_1]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_1_IBUF]
set_property -dict {PACKAGE_PIN L20 IOSTANDARD LVCMOS33} [get_ports cam_vsync_1]
set_property -dict {PACKAGE_PIN T20 IOSTANDARD LVCMOS33} [get_ports cam_href_1]
set_property -dict {PACKAGE_PIN N19 IOSTANDARD LVCMOS33} [get_ports cam_rst_n_1]
set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[1]}]
set_property -dict {PACKAGE_PIN J21 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[3]}]
set_property -dict {PACKAGE_PIN G20 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[5]}]
set_property -dict {PACKAGE_PIN L18 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_1[7]}]
set_property -dict {PACKAGE_PIN F21 IOSTANDARD LVCMOS33} [get_ports cam_pwdn_1]
#摄像头2接口的时钟
set_property -dict {PACKAGE_PIN F18 IOSTANDARD LVCMOS33} [get_ports cam_pwdn_2]
set_property -dict {PACKAGE_PIN D17 IOSTANDARD LVCMOS33} [get_ports cam_scl_2]
set_property -dict {PACKAGE_PIN M22 IOSTANDARD LVCMOS33} [get_ports cam_sda_2]
set_property -dict {PACKAGE_PIN L21 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[0]}]
set_property -dict {PACKAGE_PIN K21 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[2]}]
set_property -dict {PACKAGE_PIN H22 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[4]}]
set_property -dict {PACKAGE_PIN G21 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[6]}]
set_property -dict {PACKAGE_PIN D19 IOSTANDARD LVCMOS33} [get_ports cam_pclk_2]
create_clock -period 40.000 -name cmos_pclk [get_ports cam_pclk_2]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_2_IBUF]
set_property -dict {PACKAGE_PIN E17 IOSTANDARD LVCMOS33} [get_ports cam_vsync_2]
set_property -dict {PACKAGE_PIN N22 IOSTANDARD LVCMOS33} [get_ports cam_href_2]
set_property -dict {PACKAGE_PIN M21 IOSTANDARD LVCMOS33} [get_ports cam_rst_n_2]
set_property -dict {PACKAGE_PIN K22 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[1]}]
set_property -dict {PACKAGE_PIN J22 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[3]}]
set_property -dict {PACKAGE_PIN G22 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[5]}]
set_property -dict {PACKAGE_PIN E19 IOSTANDARD LVCMOS33 IOB TRUE} [get_ports {cam_data_2[7]}]
48.4程序设计
根据实验任务,首先设计如图 48.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头RGB-LCD显示实验”的整体架构。包括以下模块:时钟模块、图像分辨率设置模块、DDR控制器模块、摄像头驱动模块和LCD顶层模块。其中时钟模块、图像分辨率设置模块和摄像头驱动模块没有做任何修改,这些模块在“OV5640摄像头RGB-LCD显示实验”中已经说明过,这里不再详述,本次实验对DDR控制模块和LCD顶层模块做了修改。
基于OV5640的二值化实验5766.png

图 48.4.1 顶层系统框图

时钟模块(clk_wiz_0):时钟模块通过调用MMCM IP核实现,共输出2个时钟,频率分别为200Mhz(DDR3参考时钟)和50Mhz时钟。200Mhz时钟作为DDR控制模块的参考时钟,由MIG IP核产生的ui_clk(本次设计为100Mhz)作为DDR控制模块的驱动时钟,50Mhz时钟作为OV5640驱动模块、摄像头图像分辨率设置模块和LCD顶层模块的驱动时钟。
图像分辨率设置模块(picture_size):图像尺寸配置模块用于配置摄像头输出图像尺寸的大小,此外还完成了DDR3的读写结束地址设置。有关图像分辨率设置模块的详细介绍请大家参考“OV5640摄像头RGB-LCD显示实验”章节。
LCD顶层模块(lcd_rgb_top):LCD顶层模块负责驱动LCD屏的驱动信号的输出,同时为其他模块提供屏体参数、场同步信号和数据请求信号。
摄像头驱动模块:本模块由原先的IIC驱动,OV5640 IIC配置,摄像头图像采集封装而成,这样做是为了减少顶层模块的代码量,增强可读性,同时有利于代码的维护和管理。由于本次实验连接了两个相同的摄像头,因此我们会对摄像头驱动模块例化两次,将两个驱动模块输出的数据和数据有效使能全部连接到DDR控制模块。有关摄像头驱动模块的详细介绍请大家参考“OV5640摄像头RGBT-LCD显示实验”章节。
DDR控制模块(ddr3_top):DDR读写控制器模块负责驱动DDR片外存储器,缓存图像传感器输出的图像数据。该模块将MIG IP核复杂的读写操作封装成类似FIFO的用户接口,非常方便用户的使用。
下面是顶层模块的原理图:
基于OV5640的二值化实验6515.png

图 48.4.2 顶层模块原理图

本次实验是在“OV5640摄像头RGB-LCD显示实验”的基础上作修改,主要修改了DDR控制模块和LCD顶层模块,而其余模块基本相同,因此接下来只介绍DDR控制模块模块和LCD顶层模块。前面的实验,使用读写各一个FIFO,并在DDR3中开辟了两个缓冲区,而本实验有两个摄像头,所以读写需要各增加一个FIFO,总共四个,同时在从DDR3中再开辟了两个缓冲区,达到四个缓冲区,而满足两组摄像头数据的处理需要。
下面就顶层模块与“OV5640摄像头RGB-LCD显示实验”的顶层代码的改动的部分做介绍。
  1. 115  //ov5640 驱动
  2. 116 ov5640_dri u_ov5640_dri_1(
  3. 117     .clk               (clk_50m),
  4. 118     .rst_n             (rst_n),
  5. 119
  6. 120     .cam_pclk          (cam_pclk_1),
  7. 121     .cam_vsync         (cam_vsync_1),
  8. 122     .cam_href          (cam_href_1 ),
  9. 123     .cam_data          (cam_data_1 ),
  10. 124     .cam_rst_n         (cam_rst_n_1),
  11. 125     .cam_pwdn          (cam_pwdn_1),
  12. 126     .cam_scl           (cam_scl_1  ),
  13. 127     .cam_sda           (cam_sda_1  ),
  14. 128     
  15. 129     .capture_start     (init_calib_complete),
  16. 130     .cmos_h_pixel      (h_disp[12:1]),
  17. 131     .cmos_v_pixel      (v_disp),
  18. 132     .total_h_pixel     (total_h_pixel),
  19. 133     .total_v_pixel     (total_v_pixel),
  20. 134     .cmos_frame_vsync  (cmos_frame_vsync_1),
  21. 135     .cmos_frame_href   (),
  22. 136     .cmos_frame_valid  (cmos_frame_valid_1),
  23. 137     .cmos_frame_data   (wr_data_1)
  24. 138     );   
  25. 139     
  26. 140   //ov5640 驱动
  27. 141 ov5640_dri u_ov5640_dri_2(
  28. 142     .clk               (clk_50m),
  29. 143     .rst_n             (rst_n),
  30. 144
  31. 145     .cam_pclk          (cam_pclk_2 ),
  32. 146     .cam_vsync         (cam_vsync_2),
  33. 147     .cam_href          (cam_href_2 ),
  34. 148     .cam_data          (cam_data_2),
  35. 149     .cam_rst_n         (cam_rst_n_2),
  36. 150     .cam_pwdn          (cam_pwdn_2 ),
  37. 151     .cam_scl           (cam_scl_2  ),
  38. 152     .cam_sda           (cam_sda_2 ),
  39. 153     
  40. 154     .capture_start     (init_calib_complete),
  41. 155     .cmos_h_pixel      (h_disp[12:1]),
  42. 156     .cmos_v_pixel      (v_disp),
  43. 157     .total_h_pixel     (total_h_pixel),
  44. 158     .total_v_pixel     (total_v_pixel),
  45. 159     .cmos_frame_vsync  (cmos_frame_vsync_2),
  46. 160     .cmos_frame_href   (),
  47. 161     .cmos_frame_valid  (cmos_frame_valid_2),
  48. 162     .cmos_frame_data   (wr_data_2)
  49. 163     );   
复制代码


这段代码是对OV5640驱动模块的一个顶层例化,负责驱动OV5640外设,采集图像等作用。因为本次实验用的是双目摄像头,所以顶层需要例化两次。
在程序的第120行至第127行和第145至第152行,这几行的信号都是与外设OV5640相连的,负责驱动摄像头。
在程序的第129行和154行,这个信号的作用是保证OV5640驱动模块在DDR3初始化没有完成之前不会对图像进行采集。
在程序的第130行至第133行和第155行至第158行,这四个信号是摄像头配置时需要的参数,使摄像头输出本次实验需要的行场分辨率和计算帧率的相关参数。在130行和155行将LCD屏的行分辨率除以2的原因是本次实验用的是双目摄像头,最终显示的效果是将双目OV5640摄像头实时采集到的图像分左右两半显示在LCD屏幕上,故摄像头需要采集的图像的行分辨率是LCD屏的行分辨率一半,场分辨率两者是相同的。
在程序的第136行至第137行和第161行至第162行,OV5640驱动模块输出的数据有效使能信号和有效数据连接到DDR控制模块,实现了图像数据的缓存。  
  1. 166 ddr3_top u_ddr3_top (
  2. 167     .clk_200               (clk_200),               //系统时钟
  3. 168     .sys_rst_n             (rst_n),                 //复位,低有效
  4. 169     .sys_init_done         (sys_init_done),         //系统初始化完成
  5. 170     .init_calib_complete   (init_calib_complete),   //ddr3初始化完成信号   
  6. 171     //ddr3接口信号         
  7. 172     .app_addr_rd_min       (28'd0),                 //读DDR3的起始地址
  8. 173     .app_addr_rd_max       (ddr3_addr_max[27:1]),   //读DDR3的结束地址
  9. 174     .rd_bust_len           (h_disp[10:3]),          //从DDR3中读数据时的突发长度
  10. 175     .app_addr_wr_min       (28'd0),                 //写DDR3的起始地址
  11. 176     .app_addr_wr_max       (ddr3_addr_max[27:1]),   //写DDR3的结束地址
  12. 177     .wr_bust_len           (h_disp[10:3]),          //从DDR3中写数据时的突发长度
  13. 178     // DDR3 IO接口               
  14. 179     .ddr3_dq               (ddr3_dq),               //DDR3 数据
  15. 180     .ddr3_dqs_n            (ddr3_dqs_n),            //DDR3 dqs负
  16. 181     .ddr3_dqs_p            (ddr3_dqs_p),            //DDR3 dqs正  
  17. 182     .ddr3_addr             (ddr3_addr),             //DDR3 地址   
  18. 183     .ddr3_ba               (ddr3_ba),               //DDR3 banck 选择
  19. 184     .ddr3_ras_n            (ddr3_ras_n),            //DDR3 行选择
  20. 185     .ddr3_cas_n            (ddr3_cas_n),            //DDR3 列选择
  21. 186     .ddr3_we_n             (ddr3_we_n),             //DDR3 读写选择
  22. 187     .ddr3_reset_n          (ddr3_reset_n),          //DDR3 复位
  23. 188     .ddr3_ck_p             (ddr3_ck_p),             //DDR3 时钟正
  24. 189     .ddr3_ck_n             (ddr3_ck_n),             //DDR3 时钟负  
  25. 190     .ddr3_cke              (ddr3_cke),              //DDR3 时钟使能
  26. 191     .ddr3_cs_n             (ddr3_cs_n),             //DDR3 片选
  27. 192     .ddr3_dm               (ddr3_dm),               //DDR3_dm
  28. 193     .ddr3_odt              (ddr3_odt),              //DDR3_odt
  29. 194     //用户
  30. 195     .wr_clk_1              (cam_pclk_1),            //摄像头1时钟
  31. 196     .wr_vsync_1            (cmos_frame_vsync_1),    //摄像头1场信号   
  32. 197     .datain_valid_1        (cmos_frame_valid_1),    //数据1有效使能信号
  33. 198     .datain_1              (wr_data_1),             //有效数据1
  34. 199     .wr_clk_2              (cam_pclk_2),            //摄像头2时钟
  35. 200     .wr_vsync_2            (cmos_frame_vsync_2),    //摄像头2场信号   
  36. 201     .datain_valid_2        (cmos_frame_valid_2),    //数据有效使能信号
  37. 202     .datain_2              (wr_data_2),             //有效数据   
  38. 203
  39. 204     .h_disp                (h_disp),   
  40. 205     .rd_clk                (lcd_clk),               //rfifo的读时钟
  41. 206     .rd_vsync              (rd_vsync),              //lcd场信号   
  42. 207     .dataout               (rd_data),               //rfifo输出数据
  43. 208     .rdata_req             (rdata_req)              //请求像素点颜色数据输入   
  44. 209      );     
复制代码

           
在程序的第173和第176行,本次实验摄像头采集的图像的行分辨率只有LCD屏的一半,场分辨率是相同的,故最大读写地址信号(ddr3_addr_max)必须要除以二才是最后存到DDR的最大读写地址,但代码中将最低一位舍去,相当于信号右移一位,等同于除以二。
在程序的第174和第177行,信号(wr_bust_len)和信号(rd_bust_len)表示一次向DDR3读或写的长度,因为输入DDR3顶层模块的数据和从DDR3顶层模块输出的数据都为16bit,而MIG的数据位宽为128bit,所以一次写两行或读一行的长度是LCD屏水平分辨率除以8(128bit/16bit),这里将wr_bust_len和rd_bust_len的低三位舍去,相当于信号右移三位,等同于除以八。本次实验摄像头采集的图像的行分辨率只有LCD屏的一半,所以这次实验往DDR3一次读写的数据大小是写两行或者读一行。
本次实验的时钟模块、摄像头驱动模块、图像分辨率设置模块和摄像头驱动模块没有做任何修改,这些模块在“OV5640摄像头RGB-LCD显示实验”中已经说明过,这里不再详述。本次实验只对DDR控制模块和LCD顶层模块做了修改,本次实验着重讲解这两个模块。下面是DDR控制模块的系统框图:
基于OV5640的二值化实验12312.png

图 48.4.3 DDR控制模块的系统框图

结合图可以看出相较于“OV5640摄像头RGB-LCD显示实验”,本次实验将FIFO调度模块替换成了FIFO顶层调度模块。本次实验是两个摄像头采集数据并且把数据存入两个写FIFO,两个写FIFO再将数据写入DDR3的两个不同的存储空间中,接着两个读FIFO分别从两个对应的DDR3地址空间中取出数据,最后两组数据一起拼接为完整一帧LCD图像,达到一个屏幕同时显示两幅图像的效果。在DDR3处理数据的时候,两组数据流相当于过独木桥,需要分时“排队”进出;同样在显示端,两组数据也要“排队”显示,所以本次实验的FIFO顶层调度模块的作用就是根据不同的情况对两个FIFO调度模块的信号进行切换。
下面是DDR控制模块的原理图:
基于OV5640的二值化实验12691.png

图 48.4.4 DDR控制模块的原理图

DDR读写模块:该模块负责与MIG模块的命令和地址的交互,根据FIFO顶层调度模块中各个FIFO的剩余数据量来切换DDR3的读写命令和地址。
MIG模块:MIG模块(mig_7series_0)负责连接外设和FPGA,详细说明请看“DDR3读写测试实验”。
FIFO顶层调度模块:负责对输入和输出的数据进行时钟域的切换和位宽的转换,并根据DDR读写模块输出的使能来调度四个FIFO的数据。
下面是DDR控制模块的代码:
  1. 1   module ddr3_top(
  2. 2       input              clk_200m            ,   //ddr3参考时钟
  3. 3       input              sys_rst_n           ,   //复位,低有效
  4. 4       input              sys_init_done       ,   //系统初始化完成               
  5. 5       //DDR3接口信号                           
  6. 6       input   [27:0]     app_addr_rd_min     ,   //读ddr3的起始地址
  7. 7       input   [27:0]     app_addr_rd_max     ,   //读ddr3的结束地址
  8. 8       input   [7:0]      rd_bust_len         ,   //从ddr3中读数据时的突发长度
  9. 9       input   [27:0]     app_addr_wr_min     ,   //读ddr3的起始地址
  10. 10      input   [27:0]     app_addr_wr_max     ,   //读ddr3的结束地址
  11. 11      input   [7:0]      wr_bust_len         ,   //从ddr3中读数据时的突发长度
  12. 12      // DDR3 IO接口
  13. 13      inout   [15:0]     ddr3_dq             ,   //ddr3 数据
  14. 14      inout   [1:0]      ddr3_dqs_n          ,   //ddr3 dqs负
  15. 15      inout   [1:0]      ddr3_dqs_p          ,   //ddr3 dqs正  
  16. 16      output  [13:0]     ddr3_addr           ,   //ddr3 地址   
  17. 17      output  [2:0]      ddr3_ba             ,   //ddr3 banck 选择
  18. 18      output             ddr3_ras_n          ,   //ddr3 行选择
  19. 19      output             ddr3_cas_n          ,   //ddr3 列选择
  20. 20      output             ddr3_we_n           ,   //ddr3 读写选择
  21. 21      output             ddr3_reset_n        ,   //ddr3 复位
  22. 22      output  [0:0]      ddr3_ck_p           ,   //ddr3 时钟正
  23. 23      output  [0:0]      ddr3_ck_n           ,   //ddr3 时钟负
  24. 24      output  [0:0]      ddr3_cke            ,   //ddr3 时钟使能
  25. 25      output  [0:0]      ddr3_cs_n           ,   //ddr3 片选
  26. 26      output  [1:0]      ddr3_dm             ,   //ddr3_dm
  27. 27      output  [0:0]      ddr3_odt            ,   //ddr3_odt      
  28. 28      //用户
  29. 29      input              wr_clk_1            ,   //wfifo时钟
  30. 30      input              datain_valid_1      ,   //数据有效使能信号
  31. 31      input   [15:0]     datain_1            ,   //有效数据
  32. 32      input              wr_load_1           ,   //输入源场信号   
  33. 33      input              wr_clk_2            ,   //wfifo时钟
  34. 34      input              datain_valid_2      ,   //数据有效使能信号
  35. 35      input   [15:0]     datain_2            ,   //有效数据
  36. 36      input              wr_load_2           ,   //输入源场信号
  37. 37      
  38. 38      input   [12:0]     h_disp              ,   //摄像头水平分辨率
  39. 39      input              rd_clk              ,   //rfifo的读时钟      
  40. 40      input              rdata_req           ,   //请求像素点颜色数据输入  
  41. 41      input              rd_load             ,   //输出源场信号
  42. 42      output  [15:0]     dataout             ,   //rfifo输出数据
  43. 43      output             init_calib_complete     //ddr3初始化完成信号
  44. 44  
  45. 45      );               
  46. 46                        
  47. 47   //wire define  
  48. 48  wire                  ui_clk               ;   //用户时钟
  49. 49  wire [27:0]           app_addr             ;   //ddr3 地址
  50. 50  wire [2:0]            app_cmd              ;   //用户读写命令
  51. 51  wire                  app_en               ;   //MIG IP核使能
  52. 52  wire                  app_rdy              ;   //MIG IP核空闲
  53. 53  wire [127:0]          app_rd_data          ;   //用户读数据
  54. 54  wire                  app_rd_data_end      ;   //突发读当前时钟最后一个数据
  55. 55  wire                  app_rd_data_valid    ;   //读数据有效
  56. 56  wire [127:0]          app_wdf_data         ;   //用户写数据
  57. 57  wire                  app_wdf_end          ;   //突发写当前时钟最后一个数据
  58. 58  wire [15:0]           app_wdf_mask         ;   //写数据屏蔽                           
  59. 59  wire                  app_wdf_rdy          ;   //写空闲                              
  60. 60  wire                  app_sr_active        ;   //保留                                 
  61. 61  wire                  app_ref_ack          ;   //刷新请求                             
  62. 62  wire                  app_zq_ack           ;   //ZQ 校准请求                          
  63. 63  wire                  app_wdf_wren         ;   //ddr3 写使能                          
  64. 64  wire                  clk_ref_i            ;   //ddr3参考时钟                        
  65. 65  wire                  sys_clk_i            ;   //MIG IP核输入时钟                     
  66. 66  wire                  ui_clk_sync_rst      ;   //用户复位信号                        
  67. 67  wire [20:0]           rd_cnt               ;   //实际读地址计数                       
  68. 68  wire [3 :0]           state_cnt            ;   //状态计数器                           
  69. 69  wire [23:0]           rd_addr_cnt          ;   //用户读地址计数器                     
  70. 70  wire [23:0]           wr_addr_cnt          ;   //用户写地址计数器                     
  71. 71  wire                  rfifo_wren           ;   //从ddr3读出数据的有效使能              
  72. 72  wire                  init_calib_complete  ;   //ddr3初始化完成信号
  73. 73  wire [127:0]          rfifo_wdata_1        ;   //rfifo1输入数据  
  74. 74  wire [127:0]          rfifo_wdata_2        ;   //rfifo2输入数据                                                                                    
  75. 75  wire [10:0]           wfifo_rcount_1       ;   //wfifo1剩余数据计数
  76. 76  wire [10:0]           wfifo_rcount_2       ;   //wfifo2剩余数据计数
  77. 77  wire [10:0]           rfifo_wcount_1       ;   //rfifo1写进数据计数
  78. 78  wire [10:0]           rfifo_wcount_2       ;   //rfifo2写进数据计数  
  79. 79                                                                                    
  80. 80  //*****************************************************                              
  81. 81  //**                    main code                                                     
  82. 82  //*****************************************************                              
  83. 83                                                                                       
  84. 84  //读写模块                                                                           
  85. 85   ddr3_rw u_ddr3_rw(                                                                  
  86. 86      .ui_clk               (ui_clk)              ,                                    
  87. 87      .ui_clk_sync_rst      (ui_clk_sync_rst)     ,                                      
  88. 88      //MIG 接口                                                                        
  89. 89      .init_calib_complete  (init_calib_complete) ,   //ddr3初始化完成信号                                   
  90. 90      .app_rdy              (app_rdy)             ,   //MIG IP核空闲                                   
  91. 91      .app_wdf_rdy          (app_wdf_rdy)         ,   //写空闲                                   
  92. 92      .app_rd_data_valid    (app_rd_data_valid)   ,   //读数据有效
  93. 93      .app_rd_data          (app_rd_data)         ,   //读数据           
  94. 94      .app_addr             (app_addr)            ,   //ddr3 地址                                   
  95. 95      .app_en               (app_en)              ,   //MIG IP核使能                                   
  96. 96      .app_wdf_wren         (app_wdf_wren)        ,   //ddr3 写使能                                    
  97. 97      .app_wdf_end          (app_wdf_end)         ,   //突发写当前时钟最后一个数据                                   
  98. 98      .app_cmd              (app_cmd)             ,   //用户读写命令                                                                                                                        
  99. 99      //DDR3 地址参数                                                                  
  100. 100     .app_addr_rd_min      (app_addr_rd_min)     ,   //读ddr3的起始地址                                 
  101. 101     .app_addr_rd_max      (app_addr_rd_max)     ,   //读ddr3的结束地址                                 
  102. 102     .rd_bust_len          (rd_bust_len)         ,   //从ddr3中读数据时的突发长度                                 
  103. 103     .app_addr_wr_min      (app_addr_wr_min)     ,   //写ddr3的起始地址                                 
  104. 104     .app_addr_wr_max      (app_addr_wr_max)     ,   //写ddr3的结束地址                                 
  105. 105     .wr_bust_len          (wr_bust_len)         ,   //从ddr3中写数据时的突发长度                                 
  106. 106     //用户接口                                                                        
  107. 107     .rfifo_wren_1         (rfifo_wren_1)        ,   //rfifo写使能
  108. 108     .rfifo_wdata_1        (rfifo_wdata_1)       ,   //rfifo写数据
  109. 109     .rfifo_wren_2         (rfifo_wren_2)        ,   //rfifo写使能  
  110. 110     .rfifo_wdata_2        (rfifo_wdata_2)       ,   //rfifo写数据
  111. 111     .wfifo_rden_1         (wfifo_rden_1)        ,   //写端口FIFO1中的读使能
  112. 112     .wfifo_rden_2         (wfifo_rden_2)        ,   //写端口FIFO2中的读使能  
  113. 113     .rd_load              (rd_load)             ,   //输出源场信号
  114. 114     .wr_load_1            (wr_load_1)           ,   //输入源场信号
  115. 115     .wr_load_2            (wr_load_2)           ,   //输入源场信号   
  116. 116     .wfifo_rcount_1       (wfifo_rcount_1)      ,   //wfifo剩余数据计数                  
  117. 117     .rfifo_wcount_1       (rfifo_wcount_1)      ,   //rfifo写进数据计数
  118. 118     .wfifo_rcount_2       (wfifo_rcount_2)      ,   //wfifo剩余数据计数                  
  119. 119     .rfifo_wcount_2       (rfifo_wcount_2)      ,   //rfifo写进数据计数   
  120. 120     .wr_clk_2             (wr_clk_2)            ,   //wfifo时钟
  121. 121     .wr_clk_1             (wr_clk_1)                //wfifo时钟         
  122. 122     );
  123. 123     
  124. 124 //MIG IP核模块
  125. 125 mig_7series_0 u_mig_7series_0 (
  126. 126     // Memory interface ports
  127. 127     .ddr3_addr           (ddr3_addr)            ,         
  128. 128     .ddr3_ba             (ddr3_ba)              ,            
  129. 129     .ddr3_cas_n          (ddr3_cas_n)           ,         
  130. 130     .ddr3_ck_n           (ddr3_ck_n)            ,        
  131. 131     .ddr3_ck_p           (ddr3_ck_p)            ,         
  132. 132     .ddr3_cke            (ddr3_cke)             ,            
  133. 133     .ddr3_ras_n          (ddr3_ras_n)           ,         
  134. 134     .ddr3_reset_n        (ddr3_reset_n)         ,      
  135. 135     .ddr3_we_n           (ddr3_we_n)            ,        
  136. 136     .ddr3_dq             (ddr3_dq)              ,            
  137. 137     .ddr3_dqs_n          (ddr3_dqs_n)           ,        
  138. 138     .ddr3_dqs_p          (ddr3_dqs_p)           ,                                                      
  139. 139     .ddr3_cs_n           (ddr3_cs_n)            ,                        
  140. 140     .ddr3_dm             (ddr3_dm)              ,   
  141. 141     .ddr3_odt            (ddr3_odt)             ,         
  142. 142     // Application interface ports                                       
  143. 143     .app_addr            (app_addr)             ,         
  144. 144     .app_cmd             (app_cmd)              ,         
  145. 145     .app_en              (app_en)               ,        
  146. 146     .app_wdf_data        (app_wdf_data)         ,      
  147. 147     .app_wdf_end         (app_wdf_end)          ,      
  148. 148     .app_wdf_wren        (app_wdf_wren)         ,           
  149. 149     .app_rd_data         (app_rd_data)          ,      
  150. 150     .app_rd_data_end     (app_rd_data_end)      ,                                       
  151. 151     .app_rd_data_valid   (app_rd_data_valid)    ,     
  152. 152     .init_calib_complete (init_calib_complete)  ,            
  153. 153                                                      
  154. 154     .app_rdy             (app_rdy)              ,      
  155. 155     .app_wdf_rdy         (app_wdf_rdy)          ,         
  156. 156     .app_sr_req          ()                     ,                    
  157. 157     .app_ref_req         ()                     ,              
  158. 158     .app_zq_req          ()                     ,            
  159. 159     .app_sr_active       (app_sr_active)        ,        
  160. 160     .app_ref_ack         (app_ref_ack)          ,         
  161. 161     .app_zq_ack          (app_zq_ack)           ,            
  162. 162     .ui_clk              (ui_clk)               ,               
  163. 163     .ui_clk_sync_rst     (ui_clk_sync_rst)      ,                                               
  164. 164     .app_wdf_mask        (31'b0)                ,   
  165. 165     // System Clock Ports                           
  166. 166     .sys_clk_i           (clk_200m)             ,   
  167. 167     // Reference Clock Ports                        
  168. 168     .clk_ref_i           (clk_200m)             ,   
  169. 169     .sys_rst             (sys_rst_n)                 
  170. 170     );                                               
  171. 171                                                      
  172. 172 ddr3_fifo_ctrl_top u_ddr3_fifo_ctrl_top(
  173. 173     .rst_n             (sys_rst_n &&sys_init_done),  //复位信号   
  174. 174     .rd_clk            (rd_clk)                   ,  //rfifo时钟
  175. 175     .clk_100           (ui_clk)                   ,  //用户时钟
  176. 176     //fifo1接口信号   
  177. 177     .wr_clk_1          (wr_clk_1)                 ,  //wfifo时钟   
  178. 178     .datain_valid_1    (datain_valid_1)           ,  //数据有效使能信号
  179. 179     .datain_1          (datain_1)                 ,  //有效数据
  180. 180     .wr_load_1         (wr_load_1)                ,  //输入源场信号   
  181. 181     .rfifo_din_1       (rfifo_wdata_1)            ,  //rfifo写数据
  182. 182     .rfifo_wren_1      (rfifo_wren_1)             ,  //rfifo写使能
  183. 183     .wfifo_rden_1      (wfifo_rden_1)             ,  //wfifo读使能
  184. 184     .wfifo_rcount_1    (wfifo_rcount_1)           ,  //wfifo剩余数据计数
  185. 185     .rfifo_wcount_1    (rfifo_wcount_1)           ,  //rfifo写进数据计数   
  186. 186     //fifo2接口信号   
  187. 187     .wr_clk_2          (wr_clk_2)                 ,  //wfifo时钟   
  188. 188     .datain_valid_2    (datain_valid_2)           ,  //数据有效使能信号
  189. 189     .datain_2          (datain_2)                 ,  //有效数据   
  190. 190     .wr_load_2         (wr_load_2)                ,  //输入源场信号
  191. 191     .rfifo_din_2       (rfifo_wdata_2)            ,  //rfifo写数据
  192. 192     .rfifo_wren_2      (rfifo_wren_2)             ,  //rfifo写使能
  193. 193     .wfifo_rden_2      (wfifo_rden_2)             ,  //wfifo读使能   
  194. 194     .wfifo_rcount_2    (wfifo_rcount_2)           ,  //wfifo剩余数据计数
  195. 195     .rfifo_wcount_2    (rfifo_wcount_2)           ,  //rfifo写进数据计数
  196. 196                        
  197. 197     .h_disp            (h_disp)                   ,  //摄像头水平分辨率
  198. 198     .rd_load           (rd_load)                  ,  //输出源场信号
  199. 199     .rdata_req         (rdata_req)                ,  //请求像素点颜色数据输入     
  200. 200     .pic_data          (dataout)                  ,  //有效数据
  201. 201     .wfifo_dout        (app_wdf_data)                //用户写数据   
  202. 202
  203. 203     );
  204. 204      
  205. 205 endmodule
复制代码

在“OV5640摄像头RGB-LCD显示实验”的程序中,读写操作地址用了DDR3的两个存储空间,但本次实验开辟了四个存储空间,使得两个摄像头的数据在DDR3的存储中互不影响,也方便调度,所以本次实验需要在“OV5640摄像头RGB-LCD显示实验”的程序方面做些改动。具体框图如下图所示:
基于OV5640的二值化实验26949.png

图 48.4.5 DDR读写控制流程图

图像数据是由两个摄像头分别采集得来的,所以将图像分别存入两个wfifo中。当两个wfifo任意一个fifo剩余的数据量大于本次实验设定的阈值时,DDR读写模块就对其发出读数据请求信号,使fifo中的数据写入DDR3中,保证wfifo不会写满。当两个rfifo任意一个fifo剩余的数据量小于本次实验设定的阈值时,DDR读写模块就向对应的rfifo中写入数据,保证rfifo不会读空。DDR3中开辟了四个存储空间,每个输入源使用两个存储空间,这么做的好处一是对输入源做乒乓操作,防止画面撕裂,另一个是保证两个输入源的数据不会相互干扰。  
本次实验中的DDR读写模块是基于“OV5640摄像头RGB-LCD显示实验”做的修改,本次实验着重对代码的改动部分做讲解。
  1. 40  //localparam
  2. 41  localparam IDLE          = 7'b0000001;   //空闲状态
  3. 42  localparam DDR3_DONE     = 7'b0000010;   //DDR3初始化完成状态
  4. 43  localparam WRITE_1       = 7'b0000100;   //读FIFO保持状态
  5. 44  localparam READ_1        = 7'b0001000;   //写FIFO保持状态
  6. 45  localparam WRITE_2       = 7'b0010000;   //读FIFO保持状态
  7. 46  localparam READ_2        = 7'b0100000;   //写FIFO保持状态
  8. 47  localparam READ_WAIT     = 7'b1000000;   //写FIFO保持状态
  9. 在代码的40行至47行,相比于“OV5640摄像头RGB-LCD显示实验”多添加了三个状态,一个读状态,一个写状态,还有一个读等待状态。
  10. 105 //在写状态,MIG空闲且写有效,此时拉高FIFO写使能
  11. 106 assign wfifo_rden_1 = (state_cnt == WRITE_1  && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
  12. 107
  13. 108 //在写状态,MIG空闲且写有效,此时拉高FIFO写使能
  14. 109 assign wfifo_rden_2 = (state_cnt == WRITE_2 && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
  15. 在代码的105行至109行,根据不同的写状态来给不同的WFIFO发出读数据使能信号。
  16. 117 //读端口FIFO1数据没有写完的使能信号
  17. 118 always @(posedge ui_clk or negedge rst_n)  begin
  18. 119     if(~rst_n || rd_rst)begin
  19. 120         rfifo_data_en_1 <= 0;   
  20. 121     end   
  21. 122     else begin
  22. 123         if(state_cnt == DDR3_DONE  )
  23. 124            rfifo_data_en_1 <= 0;
  24. 125         else if(state_cnt == READ_1 )
  25. 126            rfifo_data_en_1 <= 1;
  26. 127         else
  27. 128            rfifo_data_en_1 <= rfifo_data_en_1;         
  28. 129     end   
  29. 130 end
  30. 131
  31. 132 //读端口FIFO2数据没有写完的使能信号
  32. 133 always @(posedge ui_clk or negedge rst_n)  begin
  33. 134     if(~rst_n || rd_rst)begin
  34. 135         rfifo_data_en_2 <= 0;   
  35. 136     end   
  36. 137     else begin
  37. 138         if(state_cnt == DDR3_DONE)
  38. 139            rfifo_data_en_2 <= 0;
  39. 140         else if(state_cnt == READ_2 )
  40. 141            rfifo_data_en_2 <= 1;
  41. 142         else
  42. 143            rfifo_data_en_2 <= rfifo_data_en_2;         
  43. 144     end   
  44. 145 end
  45. 在代码的117行至145行,对rfifo_data_en_1和rfifo_data_en_2进行了赋值,这两个信号在读操作开始后拉高,进入DDR3空闲状态拉低。信号为高时表示此次读操作所需要的数据没有全部从DDR3读出来,为低时,表示数据已经全部读出来了。
  46. 147  //从ddr3读出的有效数据使能进行计数
  47. 148 always @(posedge ui_clk or negedge rst_n)  begin
  48. 149     if(~rst_n || rd_rst )begin
  49. 150        data_valid_cnt <= 0;   
  50. 151     end   
  51. 152     else begin
  52. 153         if(state_cnt == DDR3_DONE )
  53. 154            data_valid_cnt <= 0;     
  54. 155         else if(app_rd_data_valid)
  55. 156            data_valid_cnt <= data_valid_cnt + 1;
  56. 157         else
  57. 158            data_valid_cnt <= data_valid_cnt;            
  58. 159     end   
  59. 160 end
  60. 在代码的147行至160行,对DDR3读出数据的有效使能(app_rd_data_valid)进行了计数。
  61. 162  //对DDR读数据的输出端进行选择
  62. 163 always @(posedge ui_clk or negedge rst_n)  begin
  63. 164     if(~rst_n || rd_rst)begin
  64. 165         rfifo_wren_1 <= 0;
  65. 166         rfifo_wren_2 <= 0;
  66. 167         rfifo_wdata_1 <= 0;
  67. 168         rfifo_wdata_2 <= 0;        
  68. 169     end   
  69. 170     else begin
  70. 171         if(rfifo_data_en_1)begin
  71. 172             rfifo_wren_1 <= app_rd_data_valid;
  72. 173             rfifo_wdata_1 <= app_rd_data;
  73. 174             rfifo_wren_2 <= 0;
  74. 175             rfifo_wdata_2 <= 0;                        
  75. 176         end
  76. 177         else if(rfifo_data_en_2)begin
  77. 178             rfifo_wren_2 <= app_rd_data_valid;
  78. 179             rfifo_wdata_2 <= app_rd_data;           
  79. 180             rfifo_wren_1 <= 0;
  80. 181             rfifo_wdata_1 <= 0;                        
  81. 182         end        
  82. 183         else begin
  83. 184             rfifo_wren_2 <= 0;
  84. 185             rfifo_wdata_2 <= 0;
  85. 186             rfifo_wren_1 <= 0;
  86. 187             rfifo_wdata_1 <= 0;               
  87. 188         end        
  88. 189         
  89. 190     end   
  90. 191 end
  91. 在代码的162行至191行,根据信号rfifo_data_en_1和rfifo_data_en_2来判断是哪个rfifo将要空了,并把DDR3读出的数据写入这个rfifo。
  92. 193 //将数据读写地址赋给ddr地址
  93. 194 always @(*)  begin
  94. 195     if(~rst_n)
  95. 196         app_addr <= 0;
  96. 197     else if(state_cnt == READ_1 )
  97. 198         app_addr <= {3'b0,raddr_page_1,1'b0,app_addr_rd_1[22:0]};
  98. 199     else if(state_cnt == READ_2 )
  99. 200         app_addr <= {3'b1,raddr_page_2,1'b0,app_addr_rd_2[22:0]};
  100. 201     else if(state_cnt == WRITE_1 )
  101. 202         app_addr <= {3'b0,waddr_page_1,1'b0,app_addr_wr_1[22:0]};        
  102. 203     else
  103. 204         app_addr <= {3'b1,waddr_page_2,1'b0,app_addr_wr_2[22:0]};
  104. 205 end
复制代码

在代码的193行至205行,根据不同的状态写入相对应的地址。在代码的200行和204行,将信号app_addr的高三位赋1,其用意是为了和另一个输入源的存储空间做区分。信号raddr_page_1和waddr_page_1是对同一个输入源的两个存储空间的切换信号,同理信号raddr_page_2和waddr_page_2也是如此。
  1. 322 //DDR3读写逻辑实现
  2. 323 always @(posedge ui_clk or negedge rst_n) begin
  3. 324     if(~rst_n) begin
  4. 325         state_cnt    <= IDLE;              
  5. 326         wr_addr_cnt_1  <= 24'd0;      
  6. 327         rd_addr_cnt_1  <= 24'd0;      
  7. 328         app_addr_wr_1  <= 28'd0;   
  8. 329         app_addr_rd_1  <= 28'd0;
  9. 330         wr_addr_cnt_2  <= 24'd0;      
  10. 331         rd_addr_cnt_2  <= 24'd0;      
  11. 332         app_addr_wr_2  <= 28'd0;   
  12. 333         app_addr_rd_2  <= 28'd0;         
  13. 334     end
  14. 335     else begin
  15. 336         case(state_cnt)
  16. 337             IDLE:begin
  17. 338                 if(init_calib_complete)
  18. 339                     state_cnt <= DDR3_DONE ;
  19. 340                 else
  20. 341                     state_cnt <= IDLE;
  21. 342             end
  22. 343             DDR3_DONE:begin  //当wfifo1存储数据超过一次突发长度时,跳到写操作1
  23. 344                 if(wfifo_rcount_1 >= wr_bust_len - 2 )begin  
  24. 345                     state_cnt <= WRITE_1;                    
  25. 346                 end         //当wfifo2存储数据超过一次突发长度时,跳到写操作2
  26. 347                 else if(wfifo_rcount_2 >= wr_bust_len - 2 )begin
  27. 348                     state_cnt <= WRITE_2;                    
  28. 349                 end               
  29. 350                 else if(raddr_rst_h)begin         //当帧复位到来时,对寄存器进行复位
  30. 351                     if(raddr_rst_h_cnt >= 1000 && star_rd_flag)begin
  31. 352                         state_cnt <= READ_1;      //保证读fifo在复位时不会进行读操作            
  32. 353                     end
  33. 354                     else begin
  34. 355                         state_cnt <= DDR3_DONE;                     
  35. 356                     end                                
  36. 357                 end //当rfifo1存储数据少于设定阈值时,并且输入源1已经写入ddr 1帧数据                                    
  37. 358                 else if(rfifo_wcount_1 < 5 && star_rd_flag )begin  //跳到读操作1
  38. 359                     state_cnt <= READ_1;                                                
  39. 360                 end //当rfifo1存储数据少于设定阈值时,并且输入源1已经写入ddr 1帧数据                                    
  40. 361                 else if(rfifo_wcount_2 < 5 && star_rd_flag )begin  //跳到读操作2
  41. 362                     state_cnt <= READ_2;                                                                                       
  42. 363                 end                                                                                                            
  43. 364                 else begin
  44. 365                     state_cnt <= state_cnt;                     
  45. 366                 end
  46. 367                              
  47. 368                 if(raddr_rst_h)begin        //当帧复位到来时,对信号进行复位        
  48. 369                     rd_addr_cnt_1  <= 24'd0;      
  49. 370                     app_addr_rd_1 <= app_addr_rd_min;
  50. 371                     rd_addr_cnt_2  <= 24'd0;      
  51. 372                     app_addr_rd_2 <= app_addr_rd_min;                                                        
  52. 373                 end //当rfifo1存储数据少于设定阈值时,并且输入源1已经写入ddr 1帧数据
  53. 374                 else if(rfifo_wcount_1 < 5 && star_rd_flag )begin            
  54. 375                     rd_addr_cnt_1 <= 24'd0;            //计数器清零
  55. 376                     app_addr_rd_1 <= app_addr_rd_1;    //读地址保持不变
  56. 377                 end //当rfifo1存储数据少于设定阈值时,并且输入源1已经写入ddr 1帧数据
  57. 378                 else if(rfifo_wcount_2 < 5 && star_rd_flag )begin            
  58. 379                     rd_addr_cnt_2 <= 24'd0;            //计数器清零
  59. 380                     app_addr_rd_2 <= app_addr_rd_2;    //读地址保持不变
  60. 381                 end                                                                                                            
  61. 382                 else begin
  62. 383                     wr_addr_cnt_1  <= 24'd0;      
  63. 384                     rd_addr_cnt_1  <= 24'd0;                     
  64. 385                 end               
  65. 386   
  66. 387                 if(wr_rst_2)begin             //当帧复位到来时,对信号进行复位
  67. 388                     wr_addr_cnt_2  <= 24'd0;   
  68. 389                     app_addr_wr_2 <= app_addr_wr_min;                  
  69. 390                 end                    //当wfifo存储数据超过一次突发长度时
  70. 391                 else if(wfifo_rcount_2 >= wr_bust_len - 2 )begin  
  71. 392                     wr_addr_cnt_2  <= 24'd0;                   //计数器清零   
  72. 393                     app_addr_wr_2 <= app_addr_wr_2;            //写地址保持不变
  73. 394                  end
  74. 395                  else begin
  75. 396                     wr_addr_cnt_2  <= wr_addr_cnt_2;
  76. 397                     app_addr_wr_2  <= app_addr_wr_2;                  
  77. 398                  end
  78. 399   
  79. 400                  if(wr_rst_1)begin               //当帧复位到来时,对信号进行复位
  80. 401                     wr_addr_cnt_1  <= 24'd0;   
  81. 402                     app_addr_wr_1 <= app_addr_wr_min;                  
  82. 403                 end                  //当wfifo存储数据超过一次突发长度时
  83. 404                 else if(wfifo_rcount_1 >= wr_bust_len - 2 )begin  
  84. 405                     wr_addr_cnt_1  <= 24'd0;                   //计数器清零     
  85. 406                     app_addr_wr_1 <= app_addr_wr_1;            //写地址保持不变
  86. 407                  end
  87. 408                  else begin
  88. 409                     wr_addr_cnt_1  <= wr_addr_cnt_1;
  89. 410                     app_addr_wr_1  <= app_addr_wr_1;                  
  90. 411                  end
  91. 412                 
  92. 413             end   
  93. 414             WRITE_1:   begin
  94. 415                 if((wr_addr_cnt_1 == (wr_bust_len - 1)) &&
  95. 416                    (app_rdy && app_wdf_rdy))begin        //写到设定的长度跳到等待状态                  
  96. 417                     state_cnt    <= DDR3_DONE;           //写到设定的长度跳到等待状态               
  97. 418                     app_addr_wr_1 <= app_addr_wr_1 + 8;  //一次性写进8个数,故加8
  98. 419                 end      
  99. 420                 else if(app_rdy && app_wdf_rdy)begin       //写条件满足
  100. 421                     wr_addr_cnt_1  <= wr_addr_cnt_1 + 1'd1;//写地址计数器自加
  101. 422                     app_addr_wr_1  <= app_addr_wr_1 + 8;   //一次性写进8个数,故加8
  102. 423                 end
  103. 424                 else begin                                 //写条件不满足,保持当前值     
  104. 425                     wr_addr_cnt_1  <= wr_addr_cnt_1;
  105. 426                     app_addr_wr_1  <= app_addr_wr_1;
  106. 427                 end
  107. 428             end
  108. 429             WRITE_2:   begin
  109. 430                 if((wr_addr_cnt_2 == (wr_bust_len - 1)) &&
  110. 431                    (app_rdy && app_wdf_rdy))begin         //写到设定的长度跳到等待状态                  
  111. 432                     state_cnt    <= DDR3_DONE;            //写到设定的长度跳到等待状态               
  112. 433                     app_addr_wr_2 <= app_addr_wr_2 + 8;   //一次性写进8个数,故加8
  113. 434                 end      
  114. 435                 else if(app_rdy && app_wdf_rdy)begin      //写条件满足
  115. 436                     wr_addr_cnt_2  <= wr_addr_cnt_2 + 1'd1; //写地址计数器自加
  116. 437                     app_addr_wr_2  <= app_addr_wr_2 + 8; //一次性写进8个数,故加8
  117. 438                 end
  118. 439                 else begin                              //写条件不满足,保持当前值     
  119. 440                     wr_addr_cnt_2  <= wr_addr_cnt_2;
  120. 441                     app_addr_wr_2  <= app_addr_wr_2;
  121. 442                 end
  122. 443             end            
  123. 444             READ_1:begin                                  //读到设定的地址长度   
  124. 445                 if((rd_addr_cnt_1 == (rd_bust_len - 1)) && app_rdy)begin
  125. 446                     state_cnt   <= READ_WAIT;             //则跳到空闲状态
  126. 447                     app_addr_rd_1 <= app_addr_rd_1 + 8;
  127. 448                 end      
  128. 449                 else if(app_rdy)begin                   //若MIG已经准备好,则开始读
  129. 450                     rd_addr_cnt_1 <= rd_addr_cnt_1 + 1'd1; //用户地址计数器每次加一
  130. 451                     app_addr_rd_1 <= app_addr_rd_1 + 8; //一次性读出8个数,DDR3地址加8
  131. 452                 end
  132. 453                 else begin                               //若MIG没准备好,则保持原值
  133. 454                     rd_addr_cnt_1 <= rd_addr_cnt_1;
  134. 455                     app_addr_rd_1 <= app_addr_rd_1;
  135. 456                 end
  136. 457                 
  137. 458                 if(wr_rst_2)begin                    //当帧复位到来时,对信号进行复位
  138. 459                     wr_addr_cnt_2  <= 24'd0;   
  139. 460                     app_addr_wr_2 <= app_addr_wr_min;                  
  140. 461                 end
  141. 462                  else begin
  142. 463                     wr_addr_cnt_2  <= wr_addr_cnt_2;
  143. 464                     app_addr_wr_2  <= app_addr_wr_2;                  
  144. 465                  end
  145. 466  
  146. 467                  if(wr_rst_1)begin                   //当帧复位到来时,对信号进行复位
  147. 468                     wr_addr_cnt_1  <= 24'd0;   
  148. 469                     app_addr_wr_1 <= app_addr_wr_min;                  
  149. 470                 end
  150. 471                  else begin
  151. 472                     wr_addr_cnt_1  <= wr_addr_cnt_1;
  152. 473                     app_addr_wr_1  <= app_addr_wr_1;                  
  153. 474                  end               
  154. 475             end
  155. 476             READ_2:begin                         //读到设定的地址长度   
  156. 477                 if((rd_addr_cnt_2 == (rd_bust_len - 1)) && app_rdy)begin
  157. 478                     state_cnt   <= READ_WAIT;             //则跳到空闲状态
  158. 479                     app_addr_rd_2 <= app_addr_rd_2 + 8;
  159. 480                 end      
  160. 481                 else if(app_rdy)begin                      //若MIG已经准备好,则开始读
  161. 482                     rd_addr_cnt_2 <= rd_addr_cnt_2 + 1'd1; //用户地址计数器每次加一
  162. 483                     app_addr_rd_2 <= app_addr_rd_2 + 8; //一次性读出8个数,DDR3地址加8
  163. 484                 end
  164. 485                 else begin                                 //若MIG没准备好,则保持原值
  165. 486                     rd_addr_cnt_2 <= rd_addr_cnt_2;
  166. 487                     app_addr_rd_2 <= app_addr_rd_2;
  167. 488                 end
  168. 489                 
  169. 490                  if(wr_rst_2)begin                  //当帧复位到来时,对信号进行复位
  170. 491                     wr_addr_cnt_2  <= 24'd0;   
  171. 492                     app_addr_wr_2 <= app_addr_wr_min;                  
  172. 493                 end
  173. 494                  else begin
  174. 495                     wr_addr_cnt_2  <= wr_addr_cnt_2;
  175. 496                     app_addr_wr_2  <= app_addr_wr_2;                  
  176. 497                  end
  177. 498  
  178. 499                  if(wr_rst_1)begin                   //当帧复位到来时,对信号进行复位
  179. 500                     wr_addr_cnt_1  <= 24'd0;   
  180. 501                     app_addr_wr_1 <= app_addr_wr_min;                  
  181. 502                 end
  182. 503                  else begin
  183. 504                     wr_addr_cnt_1  <= wr_addr_cnt_1;
  184. 505                     app_addr_wr_1  <= app_addr_wr_1;                  
  185. 506                  end               
  186. 507             end
  187. 508             READ_WAIT:begin       //计到设定的地址长度   
  188. 509                 if((data_valid_cnt >= rd_bust_len - 1) && app_rd_data_valid)begin
  189. 510                     state_cnt   <= DDR3_DONE;             //则跳到空闲状态
  190. 511                 end      
  191. 512                 else begin                              
  192. 513                     state_cnt   <= READ_WAIT;
  193. 514                 end
  194. 515             end            
  195. 516             default:begin
  196. 517                     state_cnt    <= IDLE;              
  197. 518                     wr_addr_cnt_1  <= 24'd0;      
  198. 519                     rd_addr_cnt_1  <= 24'd0;      
  199. 520                     app_addr_wr_1  <= 28'd0;   
  200. 521                     app_addr_rd_1  <= 28'd0;
  201. 522                     wr_addr_cnt_2  <= 24'd0;      
  202. 523                     rd_addr_cnt_2  <= 24'd0;      
  203. 524                     app_addr_wr_2  <= 28'd0;   
  204. 525                     app_addr_rd_2  <= 28'd0;   
  205. 526             end
  206. 527         endcase
  207. 528     end
  208. 529 end     
复制代码

                     
程序中第322至529行所示,这段代码是DDR3读写逻辑实现,状态跳转图如下图所示:
基于OV5640的二值化实验42934.png

图 48.4.6 状态跳转图

本次实验的状态跳转相对于“OV7725摄像头RGB-LCD显示实验”中的状态跳转只是多了一个读状态、一个写状态和读等待状态。两个实验当中的写状态跳转的条件是没有变的,读状态的跳转出现了变化。本次实验读状态不直接跳到DDR的空闲状态,而是跳到读等待状态。本次实验的实验任务是利用双目OV5640摄像头采集图像,将采集到的图像实时显示在LCD屏幕上,两幅图像分别占据LCD屏的左右半边,所以在输出端存在两个rfifo。在下面的时序图中可以发现当读状态操作完后,而此时的数据没有全部读出来。如果此时直接跳转到DDR的空闲状态,下一刻状态就会跳到另一个读状态,那么DDR3读出的数据就不容易区分是哪个rfifo的数据,容易造成数据错乱。为了保证数据可以准确的写入到对应的rfifo中,必须在数据完全读出后才能跳转到DDR的空闲状态,这是添加读等待状态的原因。时序图如下:
基于OV5640的二值化实验43377.png

图 48.4.7 状态时序图

下面是FIFO顶层调度模块的原理图:
基于OV5640的二值化实验43456.png

图 48.4.8 FIFO顶层调度模块的原理图

本次实验增加了一个rfifo和一个wfifo,所以对FIFO调度模块例化了两次。由原理图可知,在FIFO顶层调度模块,对写fifo的输出数据进行了判断,也对读fifo的输出数据进行判断。
FIFO调度模块:负责对输入和输出的数据进行时钟域的切换和位宽的转换。详细说明请看“OV5640摄像头RGB-LCD显示实验”。
FIFO顶层调度模块的代码如下:
  1. 1   module ddr3_fifo_ctrl_top(
  2. 2       input           rst_n              ,  //复位信号   
  3. 3       input           rd_clk             ,  //rfifo时钟
  4. 4       input           clk_100            ,  //用户时钟
  5. 5       //fifo1接口信号
  6. 6       input           wr_clk_1           ,  //wfifo时钟   
  7. 7       input           datain_valid_1     ,  //数据有效使能信号
  8. 8       input  [15:0]   datain_1           ,  //有效数据
  9. 9       input           wr_load_1          ,  //输入源场信号   
  10. 10      input  [127:0]  rfifo_din_1        ,  //用户读数据
  11. 11      input           rfifo_wren_1       ,  //从ddr3读出数据的有效使能
  12. 12      input           wfifo_rden_1       ,  //wfifo读使能
  13. 13      output [10:0]   wfifo_rcount_1     ,  //wfifo剩余数据计数
  14. 14      output [10:0]   rfifo_wcount_1     ,  //rfifo写进数据计数   
  15. 15      //fifo2接口信号  
  16. 16      input           wr_clk_2           ,  //wfifo时钟   
  17. 17      input           datain_valid_2     ,  //数据有效使能信号
  18. 18      input  [15:0]   datain_2           ,  //有效数据   
  19. 19      input           wr_load_2          ,  //输入源场信号
  20. 20      input  [127:0]  rfifo_din_2        ,  //用户读数据
  21. 21      input           rfifo_wren_2       ,  //从ddr3读出数据的有效使能
  22. 22      input           wfifo_rden_2       ,  //wfifo读使能   
  23. 23      output [10:0]   wfifo_rcount_2     ,  //wfifo剩余数据计数
  24. 24      output [10:0]   rfifo_wcount_2     ,  //rfifo写进数据计数
  25. 25  
  26. 26      input  [12:0]   h_disp             ,
  27. 27      input           rd_load            ,  //输出源场信号
  28. 28      input           rdata_req          ,  //请求像素点颜色数据输入     
  29. 29      output [15:0]   pic_data           ,  //有效数据  
  30. 30      output [127:0]  wfifo_dout            //用户写数据  
  31. 31         
  32. 32      );
  33. 33  
  34. 34  //reg define
  35. 35  reg  [12:0]  rd_cnt;
  36. 36  
  37. 37  //wire define
  38. 38  wire         rdata_req_1;
  39. 39  wire         rdata_req_2;
  40. 40  wire [15:0]  pic_data_1;
  41. 41  wire [15:0]  pic_data_2;
  42. 42  wire [15:0]  pic_data;
  43. 43  wire [127:0] wfifo_dout;
  44. 44  wire [127:0] wfifo_dout_1;
  45. 45  wire [127:0] wfifo_dout_2;
  46. 46  wire [10:0]  wfifo_rcount_1;
  47. 47  wire [10:0]  wfifo_rcount_2;
  48. 48  wire [10:0]  rfifo_wcount_1;
  49. 49  wire [10:0]  rfifo_wcount_2;
  50. 50  
  51. 51   //*****************************************************
  52. 52  //**                    main code
  53. 53  //*****************************************************
  54. 54  
  55. 55  //像素显示请求信号切换,即显示器左侧请求FIFO1显示,右侧请求FIFO2显示
  56. 56  assign rdata_req_1  = (rd_cnt <= h_disp[12:1]-1) ? rdata_req :1'b0;
  57. 57  assign rdata_req_2  = (rd_cnt <= h_disp[12:1]-1) ? 1'b0 :rdata_req;
  58. 58  
  59. 59  //像素在显示器显示位置的切换,即显示器左侧显示FIFO1,右侧显示FIFO2
  60. 60  assign pic_data =     (rd_cnt <= h_disp[12:1]) ? pic_data_1 : pic_data_2;
  61. 61  
  62. 62  //写入DDR3的像素数据切换
  63. 63  assign wfifo_dout = wfifo_rden_1 ? wfifo_dout_1 : wfifo_dout_2;
  64. 64  
  65. 65  //对读请求信号计数
  66. 66  always @(posedge rd_clk or negedge rst_n) begin
  67. 67      if(!rst_n)
  68. 68          rd_cnt <= 13'd0;
  69. 69      else if(rdata_req)
  70. 70          rd_cnt <= rd_cnt + 1'b1;
  71. 71      else
  72. 72          rd_cnt <= 13'd0;
  73. 73  end
  74. 74  
  75. 75  ddr3_fifo_ctrl u_ddr3_fifo_ctrl_1 (
  76. 76  
  77. 77      .rst_n               (rst_n )           ,  
  78. 78      //摄像头接口
  79. 79      .wr_clk              (wr_clk_1)         ,
  80. 80      .rd_clk              (rd_clk)           ,
  81. 81      .clk_100             (clk_100)          ,    //用户时钟
  82. 82      .datain_valid        (datain_valid_1)   ,    //数据有效使能信号
  83. 83      .datain              (datain_1)         ,    //有效数据
  84. 84      .rfifo_din           (rfifo_din_1)      ,    //用户读数据
  85. 85      .rdata_req           (rdata_req_1)      ,    //请求像素点颜色数据输入
  86. 86      .rfifo_wren          (rfifo_wren_1)     ,    //ddr3读出数据的有效使能
  87. 87      .wfifo_rden          (wfifo_rden_1)     ,    //ddr3 写使能         
  88. 88      //用户接口
  89. 89      .wfifo_rcount        (wfifo_rcount_1)   ,    //wfifo剩余数据计数                 
  90. 90      .rfifo_wcount        (rfifo_wcount_1)   ,    //rfifo写进数据计数               
  91. 91      .wfifo_dout          (wfifo_dout_1)     ,    //用户写数据
  92. 92      .rd_load             (rd_load)          ,    //lcd场信号
  93. 93      .wr_load             (wr_load_1)        ,    //摄像头场信号
  94. 94      .pic_data            (pic_data_1)            //rfifo输出数据        
  95. 95      
  96. 96      );
  97. 97      
  98. 98  ddr3_fifo_ctrl u_ddr3_fifo_ctrl_2 (
  99. 99  
  100. 100     .rst_n               (rst_n )           ,  
  101. 101     //摄像头接口                           
  102. 102     .wr_clk              (wr_clk_2)         ,
  103. 103     .rd_clk              (rd_clk)           ,
  104. 104     .clk_100             (clk_100)          ,    //用户时钟
  105. 105     .datain_valid        (datain_valid_2)   ,    //数据有效使能信号
  106. 106     .datain              (datain_2)         ,    //有效数据
  107. 107     .rfifo_din           (rfifo_din_2)      ,    //用户读数据
  108. 108     .rdata_req           (rdata_req_2)      ,    //请求像素点颜色数据输入
  109. 109     .rfifo_wren          (rfifo_wren_2)     ,    //ddr3读出数据的有效使能
  110. 110     .wfifo_rden          (wfifo_rden_2)     ,    //ddr3 写使能         
  111. 111     //用户接口                              
  112. 112     .wfifo_rcount        (wfifo_rcount_2)   ,    //wfifo剩余数据计数                  
  113. 113     .rfifo_wcount        (rfifo_wcount_2)   ,    //rfifo写进数据计数                  
  114. 114     .wfifo_dout          (wfifo_dout_2)     ,    //用户写数据
  115. 115     .rd_load             (rd_load)          ,    //lcd场信号
  116. 116     .wr_load             (wr_load_2)        ,    //摄像头场信号
  117. 117     .pic_data            (pic_data_2)            //rfifo输出数据        
  118. 118     
  119. 119     );   
  120. 120
  121. 121 endmodule
复制代码

在代码56至57行,表示的是像素显示请求信号切换,即LCD屏左侧请求FIFO1显示,右侧请求FIFO2显示。
在代码60行,表示的是像素在LCD屏显示位置的切换,即LCD屏左侧显示FIFO1,右侧显示FIFO2。
在代码63行,表示的是像素数据在写入DDR3前的切换。
在代码66至73行,对LCD顶层模块发出的对读请求信号进行计数。
在代码75至119行是两个FIFO调度模块的例化,我们只给出代码注释,方便大家了解各信号的连接关系,这里不做分析了。
接下来介绍LCD顶层模块(lcd_rgb_top),这里先给出该模块的顶层代码:
  1. 1   module lcd_rgb_top(
  2. 2       input           sys_clk ,       //系统时钟
  3. 3       input           sys_rst_n,      //复位信号  
  4. 4       input           sys_init_done,
  5. 5       //lcd接口  
  6. 6       output          lcd_clk,        //LCD驱动时钟   
  7. 7       output          lcd_hs,         //LCD 行同步信号
  8. 8       output          lcd_vs,         //LCD 场同步信号
  9. 9       output          lcd_de,         //LCD 数据输入使能
  10. 10      inout  [23:0]   lcd_rgb,        //LCD RGB颜色数据
  11. 11      output          lcd_bl,         //LCD 背光控制信号
  12. 12      output          lcd_rst,        //LCD 复位信号
  13. 13      output          lcd_pclk,       //LCD 采样时钟
  14. 14      output  [15:0]  lcd_id,         //LCD屏ID  
  15. 15      output          out_vsync,      //lcd场信号
  16. 16      output  [9:0]   pixel_xpos,     //像素点横坐标
  17. 17      output  [9:0]   pixel_ypos,     //像素点纵坐标        
  18. 18      output  [10:0]  h_disp,         //LCD屏水平分辨率
  19. 19      output  [10:0]  v_disp,         //LCD屏垂直分辨率         
  20. 20      input   [15:0]  data_in,        //数据输入  
  21. 21      output          data_req        //请求像素点颜色数据输入
  22. 22      
  23. 23      );
  24. 24  
  25. 25  //wire define
  26. 26  wire [9:0]   pixel_xpos;            //像素点横坐标
  27. 27  wire [9:0]   pixel_ypos;            //像素点纵坐标   
  28. 28  wire         out_vsync;             //帧复位,高有效
  29. 29  wire [10:0]  h_disp;                //LCD屏水平分辨率
  30. 30  wire [10:0]  v_disp;                //LCD屏垂直分辨率  
  31. 31  wire [15:0]  lcd_data;              //选择屏后的数据
  32. 32  wire [15:0]  lcd_rgb_565;           //输出的16位lcd数据
  33. 33  wire [23:0]  lcd_rgb_o ;            //LCD 输出颜色数据
  34. 34  wire [23:0]  lcd_rgb_i ;            //LCD 输入颜色数据
  35. 35  
  36. 36  //*****************************************************
  37. 37  //**                    main code                     
  38. 38  //*****************************************************
  39. 39  
  40. 40  //将摄像头16bit数据转换为24bit的lcd数据
  41. 41  assign lcd_rgb_o = {lcd_rgb_565[15:11],3'b000,lcd_rgb_565[10:5],2'b00,
  42. 42                      lcd_rgb_565[4:0],3'b000};         
  43. 43  
  44. 44  //像素数据方向切换
  45. 45  assign lcd_rgb = lcd_de ?  lcd_rgb_o :  {24{1'bz}};
  46. 46  assign lcd_rgb_i = lcd_rgb;
  47. 47              
  48. 48  //*****************************************************
  49. 49  //**                    main code
  50. 50  //*****************************************************
  51. 51  
  52. 52  //时钟分频模块   
  53. 53  clk_div u_clk_div(
  54. 54      .clk                    (sys_clk  ),
  55. 55      .rst_n                  (sys_rst_n),
  56. 56      .lcd_id                 (lcd_id   ),
  57. 57      .lcd_pclk               (lcd_clk  )
  58. 58      );  
  59. 59  
  60. 60  //读LCD ID模块
  61. 61  rd_id u_rd_id(
  62. 62      .clk                    (sys_clk  ),
  63. 63      .rst_n                  (sys_rst_n),
  64. 64      .lcd_rgb                (lcd_rgb_i),
  65. 65      .lcd_id                 (lcd_id   )
  66. 66      );  
  67. 67  
  68. 68  //lcd驱动模块
  69. 69  lcd_driver u_lcd_driver(           
  70. 70      .lcd_clk        (lcd_clk),   
  71. 71      .sys_rst_n      (sys_rst_n & sys_init_done),
  72. 72      .lcd_id         (lcd_id),   
  73. 73  
  74. 74      .lcd_hs         (lcd_hs),      
  75. 75      .lcd_vs         (lcd_vs),      
  76. 76      .lcd_de         (lcd_de),      
  77. 77      .lcd_rgb        (),
  78. 78      .lcd_bl         (lcd_bl),
  79. 79      .lcd_rst        (lcd_rst),
  80. 80      .lcd_pclk       (lcd_pclk),
  81. 81      
  82. 82      .pixel_data     (data_in),
  83. 83      .data_req       (data_req),
  84. 84      .out_vsync      (out_vsync),
  85. 85      .h_disp         (h_disp),
  86. 86      .v_disp         (v_disp),
  87. 87      .pixel_xpos     (pixel_xpos),
  88. 88      .pixel_ypos     (pixel_ypos)
  89. 89      );
  90. 90      
  91. 91   lcd_disply u_lcd_disply(
  92. 92  
  93. 93      .lcd_clk          (lcd_clk),                   //lcd模块驱动时钟
  94. 94      .sys_rst_n        (sys_rst_n & sys_init_done), //复位信号
  95. 95      //RGB LCD接口                             
  96. 96      .pixel_xpos       (pixel_xpos),                //像素点横坐标
  97. 97      .pixel_ypos       (pixel_ypos),                //像素点纵坐标
  98. 98      .rd_data          (data_in),                   //图像数据
  99. 99      .rd_h_pixel       (h_disp),                    //图像水平像素大小
  100. 100     .pixel_data       (lcd_rgb_565)                //像素点数据
  101. 101 );   
  102. 102                  
  103. 103 endmodule
复制代码

以上主要是LCD顶层模块的顶层例化,从代码中可以看到LCD顶层模块一共包含四个模块,同时可以看到各信号的定义。代码第45行做了一个判断,lcd_de为高则把像素值赋给RGB,否则RGB为高阻态,即不显示任何图像。
关于四个模块,本次实验只对lcd_disply模块进行讲解。另外三个模块未作改动,具体代码讲解,可以参看“OV7725摄像头RGB-LCD显示”实验。
最后讲解lcd_disply模块,以下是lcd_disply模块的代码:
  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]  rd_data,      //图像像素值
  8. 8       input      [12:0]  rd_h_pixel,   //摄像头输出的水平方向分辨率
  9. 9       output reg [15:0]  pixel_data    //像素点数据,
  10. 10      );
  11. 11  
  12. 12  //颜色定义
  13. 13  localparam RED    = 16'b11111_000000_00000;     //字符颜色
  14. 14  localparam BLUE   = 16'b00000_000000_11111;     //字符区域背景色
  15. 15  localparam BLACK  = 16'b00000_000000_00000;     //屏幕背景色  
  16. 16  //reg define                                    
  17. 17  reg  [63:0]  char0[15:0];                       //字符数组0
  18. 18  reg  [63:0]  char1[15:0];                       //字符数组1
  19. 19  reg  [127:0] char2[32:0];                       //字符数组2
  20. 20  reg  [127:0] char3[32:0];                       //字符数组3
  21. 21  
  22. 22  //给字符数组0的赋值:OV5640 0 (16*64)
  23. 23  always @(posedge lcd_clk) begin
  24. 24      char0[0]  <= 64'h0000000000000000      ;
  25. 25      char0[1]  <= 64'h0000000000000000      ;
  26. 26      char0[2]  <= 64'h0000000000000000      ;
  27. 27      char0[3]  <= 64'h38E77E1804180008      ;
  28. 28      char0[4]  <= 64'h444240240C240038      ;
  29. 29      char0[5]  <= 64'h824240400C420008      ;
  30. 30      char0[6]  <= 64'h8244404014420008      ;
  31. 31      char0[7]  <= 64'h8224785C24420008      ;
  32. 32      char0[8]  <= 64'h8224446224420008      ;
  33. 33      char0[9]  <= 64'h8228024244420008      ;
  34. 34      char0[10] <= 64'h822802427F420008      ;
  35. 35      char0[11] <= 64'h8218424204420008      ;
  36. 36      char0[12] <= 64'h4410442204240008      ;
  37. 37      char0[13] <= 64'h3810381C1F18003E      ;
  38. 38      char0[14] <= 64'h0000000000000000      ;
  39. 39      char0[15] <= 64'h0000000000000000      ;
  40. 40  end
  41. 41  
  42. 42  //给字符数组1的赋值: OV5640 1 (16*64)
  43. 43  always @(posedge lcd_clk) begin
  44. 44      char1[0]  <= 64'h0000000000000000      ;
  45. 45      char1[1]  <= 64'h0000000000000000      ;
  46. 46      char1[2]  <= 64'h0000000000000000      ;
  47. 47      char1[3]  <= 64'h38E77E180418003C      ;
  48. 48      char1[4]  <= 64'h444240240C240042      ;
  49. 49      char1[5]  <= 64'h824240400C420042      ;
  50. 50      char1[6]  <= 64'h8244404014420042      ;
  51. 51      char1[7]  <= 64'h8224785C24420002      ;
  52. 52      char1[8]  <= 64'h8224446224420004      ;
  53. 53      char1[9]  <= 64'h8228024244420008      ;
  54. 54      char1[10] <= 64'h822802427F420010      ;   
  55. 55      char1[11] <= 64'h8218424204420020      ;
  56. 56      char1[12] <= 64'h4410442204240042      ;
  57. 57      char1[13] <= 64'h3810381C1F18007E      ;
  58. 58      char1[14] <= 64'h0000000000000000      ;
  59. 59      char1[15] <= 64'h0000000000000000      ;
  60. 60  end
  61. 61  
  62. 62  //给字符数组2的赋值: OV5640 0 (32*128)
  63. 63  always @(posedge lcd_clk) begin               
  64. 64      char2[0]  <= 128'h00000000000000000000000000000000;
  65. 65      char2[1]  <= 128'h00000000000000000000000000000000;
  66. 66      char2[2]  <= 128'h00000000000000000000000000000000;
  67. 67      char2[3]  <= 128'h00000000000000000000000000000000;
  68. 68      char2[4]  <= 128'h00000000000000000000000000000000;
  69. 69      char2[5]  <= 128'h00000000000000000000000000000000;
  70. 70      char2[6]  <= 128'h03C07C1E0FFC01E0006003C000000080;
  71. 71      char2[7]  <= 128'h0C30180C0FFC06180060062000000180;
  72. 72      char2[8]  <= 128'h1818180810000C1800E00C3000001F80;
  73. 73      char2[9]  <= 128'h100818081000081800E0181800000180;
  74. 74      char2[10] <= 128'h300C1808100018000160181800000180;
  75. 75      char2[11] <= 128'h300C0C10100010000160180800000180;
  76. 76      char2[12] <= 128'h60040C10100010000260300C00000180;
  77. 77      char2[13] <= 128'h60060C10100030000460300C00000180;
  78. 78      char2[14] <= 128'h60060C1013E033E00460300C00000180;
  79. 79      char2[15] <= 128'h60060C20143036300860300C00000180;
  80. 80      char2[16] <= 128'h60060620181838180860300C00000180;
  81. 81      char2[17] <= 128'h60060620100838081060300C00000180;
  82. 82      char2[18] <= 128'h60060620000C300C3060300C00000180;
  83. 83      char2[19] <= 128'h60060640000C300C2060300C00000180;
  84. 84      char2[20] <= 128'h60060340000C300C4060300C00000180;
  85. 85      char2[21] <= 128'h20060340000C300C7FFC300C00000180;
  86. 86      char2[22] <= 128'h300C0340300C300C0060180800000180;
  87. 87      char2[23] <= 128'h300C0380300C180C0060181800000180;
  88. 88      char2[24] <= 128'h10080180201818080060181800000180;
  89. 89      char2[25] <= 128'h1818018020180C1800600C3000000180;
  90. 90      char2[26] <= 128'h0C30010018300E3000600620000003C0;
  91. 91      char2[27] <= 128'h03C0010007C003E003FC03C000001FF8;
  92. 92      char2[28] <= 128'h00000000000000000000000000000000;
  93. 93      char2[29] <= 128'h00000000000000000000000000000000;
  94. 94      char2[30] <= 128'h00000000000000000000000000000000;
  95. 95      char2[31] <= 128'h00000000000000000000000000000000;
  96. 96  end
  97. 97  
  98. 98  //给字符数组3的赋值: OV5640 1 (32*128)
  99. 99  always @(posedge lcd_clk) begin               
  100. 100     char3[0]  <= 128'h00000000000000000000000000000000;
  101. 101     char3[1]  <= 128'h00000000000000000000000000000000;
  102. 102     char3[2]  <= 128'h00000000000000000000000000000000;
  103. 103     char3[3]  <= 128'h00000000000000000000000000000000;
  104. 104     char3[4]  <= 128'h00000000000000000000000000000000;
  105. 105     char3[5]  <= 128'h00000000000000000000000000000000;
  106. 106     char3[6]  <= 128'h03C07C1E0FFC01E0006003C0000007E0;
  107. 107     char3[7]  <= 128'h0C30180C0FFC06180060062000000838;
  108. 108     char3[8]  <= 128'h1818180810000C1800E00C3000001018;
  109. 109     char3[9]  <= 128'h100818081000081800E018180000200C;
  110. 110     char3[10] <= 128'h300C180810001800016018180000200C;
  111. 111     char3[11] <= 128'h300C0C1010001000016018080000300C;
  112. 112     char3[12] <= 128'h60040C10100010000260300C0000300C;
  113. 113     char3[13] <= 128'h60060C10100030000460300C0000000C;
  114. 114     char3[14] <= 128'h60060C1013E033E00460300C00000018;
  115. 115     char3[15] <= 128'h60060C20143036300860300C00000018;
  116. 116     char3[16] <= 128'h60060620181838180860300C00000030;
  117. 117     char3[17] <= 128'h60060620100838081060300C00000060;
  118. 118     char3[18] <= 128'h60060620000C300C3060300C000000C0;
  119. 119     char3[19] <= 128'h60060640000C300C2060300C00000180;
  120. 120     char3[20] <= 128'h60060340000C300C4060300C00000300;
  121. 121     char3[21] <= 128'h20060340000C300C7FFC300C00000200;
  122. 122     char3[22] <= 128'h300C0340300C300C0060180800000404;
  123. 123     char3[23] <= 128'h300C0380300C180C0060181800000804;
  124. 124     char3[24] <= 128'h10080180201818080060181800001004;
  125. 125     char3[25] <= 128'h1818018020180C1800600C300000200C;
  126. 126     char3[26] <= 128'h0C30010018300E300060062000003FF8;
  127. 127     char3[27] <= 128'h03C0010007C003E003FC03C000003FF8;
  128. 128     char3[28] <= 128'h00000000000000000000000000000000;
  129. 129     char3[29] <= 128'h00000000000000000000000000000000;
  130. 130     char3[30] <= 128'h00000000000000000000000000000000;
  131. 131     char3[31] <= 128'h00000000000000000000000000000000;                                 
  132. 132 end
  133. 133
  134. 134 //显示逻辑判断
  135. 135 always@(posedge lcd_clk) begin  
  136. 136     if(pixel_ypos >= 0 && pixel_ypos < 33)begin
  137. 137         //判断像素坐标是否在左半边屏幕中间
  138. 138         if(pixel_xpos < (rd_h_pixel[12:2]+64)
  139. 139         && pixel_xpos >= (rd_h_pixel[12:2]-64) )begin
  140. 140             //读取字模OV5640 1 (32*128)
  141. 141             if(char2[pixel_ypos][127-(pixel_xpos-rd_h_pixel[12:2]+64)])
  142. 142                 pixel_data =BLUE;  //字模数组中的“1”显示蓝色
  143. 143             else                   //字模数组中的“0”显示图像像素值
  144. 144                 pixel_data = rd_data;
  145. 145         end    //判断像素坐标是否在右半边屏幕中间
  146. 146         else if(pixel_xpos < (rd_h_pixel[12:2]*3+64)
  147. 147         && pixel_xpos >= (rd_h_pixel[12:2]*3-64))begin
  148. 148             //读取字模OV5640 2 (32*128)
  149. 149             if(char3[pixel_ypos][63-pixel_xpos+(rd_h_pixel[12:2])*3])
  150. 150                 pixel_data =BLUE;  //字模数组中的“1”显示蓝色
  151. 151             else                    //字模数组中的“0”显示图像像素值
  152. 152                 pixel_data = rd_data;
  153. 153         end
  154. 154         else            //纵坐标位于字符区域内的字符两边区域显示黑色
  155. 155             pixel_data = rd_data;
  156. 156     end      
  157. 157     else                //所有屏幕纵坐标位于字符区域外时显示像素值
  158. 158         pixel_data = rd_data;
  159. 159 end
  160. 160
  161. 161 endmodule
复制代码

本模块实现了五种RGB-LCD屏不同区域显示内容的逻辑判断,同时实现了字符叠加功能。
代码第30行到139行,定义了四个二维数组char,用于存储对英文取模得到的点阵数据,具体代码讲解,可以参看“RGB-LCD字符和图片显示实验”实验。
代码第13行到15行,定义了不同颜色对应的参数。代码第23行到132行给我们要用到的四个字符的模值进行赋值。在本次实验中LCD屏幕上要显示的字符大小为32*128,左右半边分别叠加上的“OV5640 1”和“OV5640 2”。
代码从135行往后是显示逻辑的具体实现,由于代码涉及到了判断条件之间的多重嵌套,为了让我们快速理清思路,我们结合代码制作了如图 48.4.9所示的显示逻辑图。结合代码和图,我们来具体介绍。
首先我们对屏幕类型和图像像素纵坐标进行判断,对于所有屏幕,如果纵坐标值不在字符显示区域内,我们执行代码第144行,让屏幕显示图像像素的值。只有当纵坐标值位于字符显示区域内,我们正式开始字符叠加处理。
代码第136行,通过判断了像素点纵坐标的位置。代码第138行和139行,此时开始判断像素点横坐标位置,如果此时横坐标处于左半边的中间位置,此时执行代码第142行,开始读取“OV5640 1”的字符数组的值。代码第141到144行,将字模数组中为“1”的点的像素值赋值为蓝色,为“0”的点像素值赋值为图像像素的值。这就是LCD屏左半边字符叠加的实现。代码第146到156行,实现的是LCD屏右半边字符叠加,实现的代码形式一模一样,只是字模数组变为了“OV5640 2”,在这里我们不再重复。代码第158行,非字符纵坐标区域,显示图片像素值。
基于OV5640的二值化实验62055.png

图 48.4.9 显示逻辑

48.5下载验证
首先将FPC排线一端与RGB-LCD模块上的J1接口连接,另一端与达芬奇开发板上的RGB-LCD接口连接。连接时,先掀开FPC连接器上的黑色翻盖,将FPC排线蓝色面朝上插入连接器,最后将黑色翻盖压下以固定FPC排线,如图 48.5.1和图 48.5.2所示。
基于OV5640的二值化实验62298.png

图 48.5.1 正点原子RGBLCD模块FPC连接器






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

如果想吃一顿饺子,就得从冰箱里取出肉,剁馅儿,倒面粉、揉面、醒面,擀成皮儿,下锅……
一整个繁琐流程,就是为了出锅时那一嘴滚烫流油的热饺子。

如果这个过程,禁不住饿,零食下肚了,饺子出锅时也就不香了……《非诚勿扰3》
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-3-28 22:10

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

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