搜索
bottom↓
回复: 0

《新起点V2之FPGA开发指南》第四十九章 OV5640摄像头HDMI灰度显示

[复制链接]

出0入234汤圆

发表于 2021-10-28 16:05:50 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2021-10-30 10:53 编辑

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摄像头HDMI灰度显示实验

       前面的实验介绍了OV5640摄像头的HDMI显示实验,而在数字图像处理领域YUV是一种常用的图像格式,其特点是将亮度和色度进行分离,更适合运用于图像处理领域。在本次实验中,将摄像头采集的RGB565格式数据到转换为YUV格式的数据,转换后的灰度数据送到HDMI显示器显示。
       本章包括以下几个部分:
       1.1简介
       1.2实验任务
       1.3硬件设计
       1.4程序设计
       1.5下载验证

1.1简介
       人眼中的锥状细胞是负责彩色视觉的传感器,可分为三个主要的感知类别,分别对应红色、绿色、蓝色,而人眼看到的彩色实际上是红、绿、蓝三原色的各种组合。之前所有的图像显示实验用到的RGB就是以红、绿、蓝为三原色的颜色空间模型,通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。
       YUV(YCbCr)是欧洲电视系统所采用的一种颜色编码方法。‘Y’表示明亮度(Luminance或Luma),也就是灰阶值;‘U’和‘V’表示色度,用于描述影像的饱和度和色调。RGB与YUV的转换实际上是色彩空间的转换,即将RGB的三原色色彩空间转换为YUV所表示的亮度与色度的色彩空间模型。YUV 主要应用在模拟系统中,而 YCbCr 是通过 YUV 信号的发展,并通过校正的主要应用在数字视频中的一种编码方法。YUV适用于PAL和SECAM彩色电视制式,而YCrCb适用于计算机用的显示器。 一般意义上 YCbCr 即为 YUV 信号,没有严格的划分。 CbCr 分别为蓝色色度分量、红色色度分量。
       RGB着重于人眼对色彩的感应,YUV则着重于视觉对于亮度的敏感程度。使用YUV描述图像的好处在于以下两个方面:
     (1)亮度(Y)与色度(U、V)是独立的;
     (2)人眼能够识别数千种不同的色彩,但只能识别20多种灰阶值,采用YUV标准可以降低数字彩色图像所需的储存容量。因而YUV在数字图像处理中是一种很常用的颜色标准。
       YUV格式运用最多是以下两种格式:
     (1)YUV4:4:4
       在YUV4:4:4格式中,YUV三个信道的采样率相同。因此在生成的图像里,每个像素都有各自独立的三个分量,每个分量通常为8bit,故每个像素占用3个字节。下图为YUV444单个像素的模型图,可以看出,每个Y都对应一组U、V数据,共同组成一个像素。
第四十九章 OV5640摄像头HDMI灰度显示实验1017.png

图 49.1.1 单个V444的素

      (2)YUV4:2:2
        在YUV4:2:2格式中,U和V的采样率是Y的一半(两个相邻的像素共用一对U、V数据)。如下图所示,图中包含两个相邻的像素。第一个像素的三个YUV分量分别是Y1、U1、V1,第二个像素的三个YUV分量分别是Y2、U1、V1,两个像素共用一组U1、V1。
第四十九章 OV5640摄像头HDMI灰度显示实验1219.png

图 49.1.2 两个相邻的YUV422像素

        YUV4:4:4格式和YUV4:2:2格式的数据流也是不同的。如一组连续的四个像素P1、P2、P3、P4,采用YUV444的采样格式时,数据流为Y0 U0 V0、Y1 U1 V1、Y2 U2 V2、Y3 U3 V3,每组数据代表一个像素点。而用YUV422的采样格式时,数据流为Y0 U0 Y1 V1、Y2 U2 Y3 V3。其中,Y0 U0 Y1 V1表示P1、P2两个像素,Y2 U2 Y3 V3表示P3、P4两个像素。本次实验运用的是YUV4:4:4格式。
       一般意义上 YCbCr 即为 YUV 信号,没有严格的划分。大家通常说的YUV就是指Ycbcr。 CbCr 分别为蓝色色度分量、红色色度分量。下面为 RGB 与 YCbCr 色彩空间转换的算法公式, RGB 转 YCbCr 的公式如下所示:
第四十九章 OV5640摄像头HDMI灰度显示实验1640.png

图 49.1.3 RGB 转 YcbCr算法

       由于 Verilog HDL 无法进行浮点运算,因此使用扩大 256 倍,再向右移 8Bit的方式,来转换公式,如下所示:
第四十九章 OV5640摄像头HDMI灰度显示实验1770.png

图 49.1.4 RGB 转 YcbCr算法

       为了防止运算过程中出现负数,对上述公式进行进一步变换,得到如下公式:
第四十九章 OV5640摄像头HDMI灰度显示实验1873.png

图 49.1.5 RGB 转 YcbCr算法

       实际上OV5640本身支持输出RGB、YUV格式的数据,本章节实验是着重于RGB转YUV的HDL算法实现,因此把摄像头设置为RGB565格式。当需要显示器显示灰度图时,只需要将转换后的Y值作为R、G、B三原色通道的输入就可以实现了。
1.2实验任务
       本节实验任务是使用新起点开发板达到OV5640摄像头采集RGB565格式的数据,RGB565格式的数据通过算法转换,将数据格式转换为YCbCr格式,然后通过HDMI显示器实时显示灰度图的目的。
1.3硬件设计
       本章节中硬件设计与OV5640的HDMI显示实验完全相同,此处不再赘述。
1.4程序设计
       根据实验任务,首先设计如图 49.4.1所示的系统框图,本章实验的系统框架延续了“OV5640摄像头HDMI显示实验”的整体架构。本次实验包括以下模块:时钟模块、SDRAM控制器模块、IIC驱动模块、IIC配置模块、摄像头采集模块、图像处理模块和HDMI顶层模块。其中时钟模块、SDRAM控制器模块、IIC驱动模块、IIC配置模块、摄像头采集模块和HDMI顶层模块本次实验没有做任何修改,这些模块在“OV5640摄像头HDMI显示实验”中已经说明过,这里不再详述,本次实验只是添加了图像处理模块。
       OV5640摄像头HDMI灰度显示系统框图如下图所示:
第四十九章 OV5640摄像头HDMI灰度显示实验2534.png

图 49.4.1 顶层系统框图

       由上图可知,时钟模块(pll和pll_hdmi)为HDMI顶层模块、SDRAM控制模块以及IIC驱动模块提供驱动时钟。IIC驱动模块和IIC配置模块控制着传感器初始化的开始与结束,传感器初始化完成后将采集到的数据写入摄像头采集模块。数据在摄像头采集模块处理完成后写入图像处理模块,图像处理模块将摄像头数据进行处理后存入SDRAM控制模块。顶层模块从SDRAM控制模块中读出数据并驱动显示器显示,这时整个系统才完成了数据的采集、缓存与显示。需要注意的是图像数据采集模块是在SDRAM和传感器都初始化完成之后才开始输出数据的,避免了在SDRAM初始化过程中向里面写入数据。
       顶层模块代码如下所示:
  1. 1   module ov5640_hdmi_yuv(   
  2. 2       input         sys_clk    ,  //系统时钟
  3. 3       input         sys_rst_n  ,  //系统复位,低电平有效
  4. 4       //摄像头
  5. 5       input         cam_pclk   ,  //cmos 数据像素时钟
  6. 6       input         cam_vsync  ,  //cmos 场同步信号
  7. 7       input         cam_href   ,  //cmos 行同步信号
  8. 8       input  [7:0]  cam_data   ,  //cmos 数据  
  9. 9       output        cam_rst_n  ,  //cmos 复位信号,低电平有效
  10. 10      output        cam_pwdn   ,  //cmos 电源休眠模式选择信号
  11. 11      output        cam_scl    ,  //cmos SCCB_SCL线
  12. 12      inout         cam_sda    ,  //cmos SCCB_SDA线
  13. 13      //SDRAM
  14. 14      output        sdram_clk  ,  //SDRAM 时钟
  15. 15      output        sdram_cke  ,  //SDRAM 时钟有效
  16. 16      output        sdram_cs_n ,  //SDRAM 片选
  17. 17      output        sdram_ras_n,  //SDRAM 行有效
  18. 18      output        sdram_cas_n,  //SDRAM 列有效
  19. 19      output        sdram_we_n ,  //SDRAM 写有效
  20. 20      output [1:0]  sdram_ba   ,  //SDRAM Bank地址
  21. 21      output [1:0]  sdram_dqm  ,  //SDRAM 数据掩码
  22. 22      output [12:0] sdram_addr ,  //SDRAM 地址
  23. 23      inout  [15:0] sdram_data ,  //SDRAM 数据   
  24. 24      //HDMI接口
  25. 25      output       tmds_clk_p,    // TMDS 时钟通道
  26. 26      output       tmds_clk_n,
  27. 27      output [2:0] tmds_data_p,   // TMDS 数据通道
  28. 28      output [2:0] tmds_data_n
  29. 29      );
  30. 30  
  31. 31  //parameter define
  32. 32  parameter SLAVE_ADDR    = 7'h3c          ; //OV5640的器件地址7'h3c
  33. 33  parameter BIT_CTRL      = 1'b1           ; //OV5640的字节地址为16位  0:8位 1:16位
  34. 34  parameter CLK_FREQ      = 27'd50_000_000 ; //i2c_dri模块的驱动时钟频率
  35. 35  parameter I2C_FREQ      = 18'd250_000    ; //I2C的SCL时钟频率,不超过400KHz
  36. 36  parameter V_CMOS_DISP   = 11'd800        ; //CMOS分辨率--行
  37. 37  parameter H_CMOS_DISP   = 11'd1280       ; //CMOS分辨率--列
  38. 38  parameter TOTAL_H_PIXEL = 13'd2570       ; //CMOS分辨率--行
  39. 39  parameter TOTAL_V_PIXEL = 13'd980        ;
  40. 40  
  41. 41  //wire define
  42. 42  wire        clk_100m           ;  //100mhz时钟,SDRAM操作时钟
  43. 43  wire        clk_100m_shift     ;  //100mhz时钟,SDRAM相位偏移时钟
  44. 44  wire        clk_50m            ;
  45. 45  wire        hdmi_clk           ;  
  46. 46  wire        hdmi_clk_5         ;  
  47. 47  wire        locked             ;
  48. 48  wire        locked_hdmi        ;
  49. 49  wire        rst_n              ;
  50. 50  wire        sys_init_done      ;  //系统初始化完成(sdram初始化+摄像头初始化)
  51. 51  wire        i2c_exec           ;  //I2C触发执行信号
  52. 52  wire [23:0] i2c_data           ;  //I2C要配置的地址与数据(高8位地址,低8位数据)         
  53. 53  wire        i2c_done           ;  //I2C寄存器配置完成信号
  54. 54  wire        i2c_dri_clk        ;  //I2C操作时钟
  55. 55  wire [ 7:0] i2c_data_r         ;  //I2C读出的数据
  56. 56  wire        i2c_rh_wl          ;  //I2C读写控制信号
  57. 57  wire        cam_init_done      ;  //摄像头初始化完成                        
  58. 58  wire        wr_en              ;  //sdram_ctrl模块写使能
  59. 59  wire [15:0] wr_data            ;  //sdram_ctrl模块写数据
  60. 60  wire        rd_en              ;  //sdram_ctrl模块读使能
  61. 61  wire [15:0] rd_data            ;  //sdram_ctrl模块读数据
  62. 62  wire        sdram_init_done    ;  //SDRAM初始化完成
  63. 63  wire [10:0] pixel_xpos_w       ;  //HDMI横坐标
  64. 64  wire [10:0] pixel_ypos_w       ;  //HDMI纵坐标
  65. 65  wire        post_frame_vsync   ;  //处理后的场信号
  66. 66  wire        post_frame_hsync   ;  //处理后的行信号
  67. 67  wire        post_frame_de      ;  //处理后的数据使能  
  68. 68  wire [15:0] post_rgb           ;  //处理后的数据
  69. 69  
  70. 70  //*****************************************************
  71. 71  //**                    main code
  72. 72  //*****************************************************
  73. 73  
  74. 74  assign  rst_n = sys_rst_n & locked & locked_hdmi;
  75. 75  //系统初始化完成:SDRAM和摄像头都初始化完成
  76. 76  //避免了在SDRAM初始化过程中向里面写入数据
  77. 77  assign  sys_init_done = sdram_init_done & cam_init_done;
  78. 78  //电源休眠模式选择 0:正常模式 1:电源休眠模式
  79. 79  assign  cam_pwdn  = 1'b0;
  80. 80  assign  cam_rst_n = 1'b1;
  81. 81  
  82. 82  //锁相环
  83. 83  pll u_pll(
  84. 84      .areset             (~sys_rst_n),
  85. 85      .inclk0             (sys_clk),            
  86. 86      .c0                 (clk_100m),
  87. 87      .c1                 (clk_100m_shift),  
  88. 88      .c2                 (clk_50m),   
  89. 89      .locked             (locked)
  90. 90      );
  91. 91  
  92. 92  pll_hdmi    pll_hdmi_inst (
  93. 93      .areset             ( ~sys_rst_n  ),
  94. 94      .inclk0             ( sys_clk     ),
  95. 95      .c0                 ( hdmi_clk    ),//hdmi pixel clock 71Mhz
  96. 96      .c1                 ( hdmi_clk_5  ),//hdmi pixel clock*5 355Mhz
  97. 97      .locked             ( locked_hdmi )
  98. 98      );
  99. 99      
  100. 100 //I2C配置模块
  101. 101 i2c_ov5640_rgb565_cfg u_i2c_cfg(
  102. 102     .clk                (i2c_dri_clk),
  103. 103     .rst_n              (rst_n      ),
  104. 104            
  105. 105     .i2c_exec           (i2c_exec   ),
  106. 106     .i2c_data           (i2c_data   ),
  107. 107     .i2c_rh_wl          (i2c_rh_wl  ),      //I2C读写控制信号
  108. 108     .i2c_done           (i2c_done   ),
  109. 109     .i2c_data_r         (i2c_data_r ),   
  110. 110                 
  111. 111     .cmos_h_pixel       (H_CMOS_DISP  ),    //CMOS水平方向像素个数
  112. 112     .cmos_v_pixel       (V_CMOS_DISP  ) ,   //CMOS垂直方向像素个数
  113. 113     .total_h_pixel      (TOTAL_H_PIXEL),    //水平总像素大小
  114. 114     .total_v_pixel      (TOTAL_V_PIXEL),    //垂直总像素大小
  115. 115         
  116. 116     .init_done          (cam_init_done)
  117. 117     );   
  118. 118
  119. 119 //I2C驱动模块
  120. 120 i2c_dri #(
  121. 121     .SLAVE_ADDR         (SLAVE_ADDR    ),    //参数传递
  122. 122     .CLK_FREQ           (CLK_FREQ      ),              
  123. 123     .I2C_FREQ           (I2C_FREQ      )
  124. 124     )
  125. 125 u_i2c_dr(
  126. 126     .clk                (clk_50m       ),
  127. 127     .rst_n              (rst_n         ),
  128. 128
  129. 129     .i2c_exec           (i2c_exec      ),   
  130. 130     .bit_ctrl           (BIT_CTRL      ),   
  131. 131     .i2c_rh_wl          (i2c_rh_wl     ),     //固定为0,只用到了IIC驱动的写操作   
  132. 132     .i2c_addr           (i2c_data[23:8]),   
  133. 133     .i2c_data_w         (i2c_data[7:0] ),   
  134. 134     .i2c_data_r         (i2c_data_r    ),   
  135. 135     .i2c_done           (i2c_done      ),   
  136. 136     .scl                (cam_scl       ),   
  137. 137     .sda                (cam_sda       ),
  138. 138     .dri_clk            (i2c_dri_clk   )       //I2C操作时钟
  139. 139     );
  140. 140
  141. 141 //CMOS图像数据采集模块
  142. 142 cmos_capture_data u_cmos_capture_data(         //系统初始化完成之后再开始采集数据
  143. 143     .rst_n              (rst_n & sys_init_done),
  144. 144     
  145. 145     .cam_pclk           (cam_pclk ),
  146. 146     .cam_vsync          (cam_vsync),
  147. 147     .cam_href           (cam_href ),
  148. 148     .cam_data           (cam_data ),         
  149. 149     
  150. 150     .cmos_frame_vsync   (cmos_frame_vsync),
  151. 151     .cmos_frame_href    (cmos_frame_href),
  152. 152     .cmos_frame_valid   (wr_en    ),      //数据有效使能信号
  153. 153     .cmos_frame_data    (wr_data  )       //有效数据
  154. 154     );
  155. 155      
  156. 156  //图像处理模块
  157. 157 vip u_vip(
  158. 158     //module clock
  159. 159     .clk              (cam_pclk),           // 时钟信号
  160. 160     .rst_n            (rst_n    ),          // 复位信号(低有效)
  161. 161     //图像处理前的数据接口
  162. 162     .pre_frame_vsync  (cmos_frame_vsync   ),
  163. 163     .pre_frame_hsync  (cmos_frame_href   ),
  164. 164     .pre_frame_de     (wr_en   ),
  165. 165     .pre_rgb          (wr_data),
  166. 166     .xpos             (pixel_xpos_w   ),
  167. 167     .ypos             (pixel_ypos_w   ),
  168. 168     //图像处理后的数据接口
  169. 169     .post_frame_vsync (post_frame_vsync ),  // 场同步信号
  170. 170     .post_frame_hsync ( ),                  // 行同步信号
  171. 171     .post_frame_de    (post_frame_de ),     // 数据输入使能
  172. 172     .post_rgb         (post_rgb)            // RGB565颜色数据
  173. 173
  174. 174 );   
  175. 175
  176. 176 //SDRAM 控制器顶层模块,封装成FIFO接口
  177. 177 //SDRAM 控制器地址组成: {bank_addr[1:0],row_addr[12:0],col_addr[8:0]}
  178. 178 sdram_top u_sdram_top(
  179. 179     .ref_clk            (clk_100m),         //sdram 控制器参考时钟
  180. 180     .out_clk            (clk_100m_shift),   //用于输出的相位偏移时钟
  181. 181     .rst_n              (rst_n),            //系统复位
  182. 182                                             
  183. 183     //用户写端口                              
  184. 184     .wr_clk             (cam_pclk),         //写端口FIFO: 写时钟
  185. 185     .wr_en              (post_frame_de),    //写端口FIFO: 写使能
  186. 186     .wr_data            (post_rgb),         //写端口FIFO: 写数据   
  187. 187      
  188. 188     .wr_min_addr        (24'd0),            //写SDRAM的起始地址
  189. 189     .wr_max_addr        (V_CMOS_DISP*H_CMOS_DISP-1),   //写SDRAM的结束地址
  190. 190     .wr_len             (10'd512),          //写SDRAM时的数据突发长度
  191. 191     .wr_load            (~rst_n),           //写端口复位: 复位写地址,清空写FIFO
  192. 192                                             
  193. 193     //用户读端口                              
  194. 194     .rd_clk             (hdmi_clk),         //读端口FIFO: 读时钟
  195. 195     .rd_en              (rd_en),            //读端口FIFO: 读使能
  196. 196     .rd_data            (rd_data),          //读端口FIFO: 读数据
  197. 197     .rd_min_addr        (24'd0),            //读SDRAM的起始地址
  198. 198     .rd_max_addr        (V_CMOS_DISP*H_CMOS_DISP-1),   //读SDRAM的结束地址
  199. 199     .rd_len             (10'd512),          //从SDRAM中读数据时的突发长度
  200. 200     .rd_load            (~rst_n),           //读端口复位: 复位读地址,清空读FIFO
  201. 201                                                
  202. 202     //用户控制端口                                
  203. 203     .sdram_read_valid   (1'b1),             //SDRAM 读使能
  204. 204     .sdram_pingpang_en  (1'b1),             //SDRAM 乒乓操作使能
  205. 205     .sdram_init_done    (sdram_init_done),  //SDRAM 初始化完成标志
  206. 206                                             
  207. 207     //SDRAM 芯片接口                                
  208. 208     .sdram_clk          (sdram_clk),        //SDRAM 芯片时钟
  209. 209     .sdram_cke          (sdram_cke),        //SDRAM 时钟有效
  210. 210     .sdram_cs_n         (sdram_cs_n),       //SDRAM 片选
  211. 211     .sdram_ras_n        (sdram_ras_n),      //SDRAM 行有效
  212. 212     .sdram_cas_n        (sdram_cas_n),      //SDRAM 列有效
  213. 213     .sdram_we_n         (sdram_we_n),       //SDRAM 写有效
  214. 214     .sdram_ba           (sdram_ba),         //SDRAM Bank地址
  215. 215     .sdram_addr         (sdram_addr),       //SDRAM 行/列地址
  216. 216     .sdram_data         (sdram_data),       //SDRAM 数据
  217. 217     .sdram_dqm          (sdram_dqm)         //SDRAM 数据掩码
  218. 218     );
  219. 219
  220. 220 //例化HDMI顶层模块
  221. 221 hdmi_top u_hdmi_top(
  222. 222     .hdmi_clk       (hdmi_clk   ),
  223. 223     .hdmi_clk_5     (hdmi_clk_5 ),
  224. 224     .rst_n          (rst_n      ),
  225. 225                 
  226. 226     .rd_data        (rd_data    ),
  227. 227     .rd_en          (rd_en      ),
  228. 228     .h_disp         (),  
  229. 229     .v_disp         (),
  230. 230     .pixel_xpos     (pixel_xpos_w),
  231. 231     .pixel_ypos     (pixel_ypos_w),
  232. 232     .video_vs       (),  
  233. 233     .tmds_clk_p     (tmds_clk_p ),
  234. 234     .tmds_clk_n     (tmds_clk_n ),
  235. 235     .tmds_data_p    (tmds_data_p),
  236. 236     .tmds_data_n    (tmds_data_n)
  237. 237     );   
  238. 238
  239. 239 endmodule  
复制代码

       FPGA顶层模块(ov5640_hdmi_yuv)例化了以下八个模块:时钟模块1(pll)、时钟模块2(pll_hdmi)、I2C驱动模块(i2c_dri)、I2C配置模块(i2c_ov5640_rgb565_cfg)、图像采集模块(cmos_capture_data)、图像处理模块(vip)、SDRAM控制模块(sdram_top)和HDMI顶层模块(hdmi_top)。
      时钟模块:时钟模块通过调用PLL IP核实现,共输出5个时钟,频率分别为100M时钟、100M偏移-75度时钟、50M时钟、71Mhz时钟和355M时钟(HDMI像素时钟的5倍频)。其中pll 产生了50M时钟、100M时钟和100M偏移-75度时钟,pll_hdmi 产生了71Mhz时钟和355M时钟,这里之所以用两个锁相环是因为HDMI所用的时钟71Mhz与SDRAM控制模块使用的100M时钟不是整数倍,使用一个锁相环不符合设计要求。100Mhz时钟作为SDRAM控制模块的驱动时钟,100M偏移-75度时钟用来输出给外部SDRAM芯片使用,50Mhz时钟作为I2C驱动模块的驱动时钟,71Mhz时钟和355M时钟(HDMI像素时钟的5倍频)负责驱动HDMI顶层模块。
       I2C驱动模块(i2c_dri):I2C驱动模块负责驱动OV5640 SCCB接口总线,用户可根据该模块提供的用户接口可以很方便的对OV5640的寄存器进行配置,该模块和“EEPROM读写实验”章节中用到的I2C驱动模块为同一个模块,有关该模块的详细介绍请大家参考“EEPROM读写实验”章节。
       I2C配置模块(i2c_ov5640_rgb565_cfg):I2C配置模块的驱动时钟是由I2C驱动模块输出的时钟提供的,这样方便了I2C驱动模块和I2C配置模块之间的数据交互。该模块寄存需要配置的寄存器地址、数据以及控制初始化的开始与结束,同时该模块输出OV5640的寄存器地址和数据以及控制I2C驱动模块开始执行的控制信号,直接连接到I2C驱动模块的用户接口,从而完成对OV5640传感器的初始化。
       图像采集模块(cmos_capture_data):摄像头采集模块在像素时钟的驱动下将传感器输出的场同步信号、行同步信号以及8位数据转换成写使能信号和16位写数据信号,完成对OV5640传感器图像的采集。OV5640和OV7725图像输出时序非常相似,有关该模块的详细介绍请大家参考“OV7725摄像头LCD显示实验”章节。
       图像处理模块(vip):对采集后的图像数据进行处理,并将处理后的数据存入SDRAM控制模块。
       SDRAM控制模块(sdram_top):SDRAM读写控制器模块负责驱动SDRAM片外存储器,缓存图像传感器输出的图像数据。有关该模块的详细介绍请大家参考“SDRAM读写测试实验”章节。
       HDMI顶层模块(hdmi_top):HDMI顶层模块负责驱动HDMI显示器的驱动信号的输出,同时为其他模块提供显示器参数、场同步信号和数据请求信号。关HDMI顶层模块的详细介绍请大家参考“OV5640摄像头HDMI显示实验”章节。
       图像处理模块负责图像数据的格式转换,代码如下:
  1. 1  module vip(
  2. 2      //module clock
  3. 3      input           clk            ,   // 时钟信号
  4. 4      input           rst_n          ,   // 复位信号(低有效)
  5. 5  
  6. 6      //图像处理前的数据接口
  7. 7      input           pre_frame_vsync,
  8. 8      input           pre_frame_hsync,
  9. 9      input           pre_frame_de   ,
  10. 10     input    [15:0] pre_rgb        ,
  11. 11     input    [10:0] xpos           ,
  12. 12     input    [10:0] ypos           ,
  13. 13
  14. 14     //图像处理后的数据接口
  15. 15     output          post_frame_vsync,  // 场同步信号
  16. 16     output          post_frame_hsync,  // 行同步信号
  17. 17     output          post_frame_de   ,  // 数据输入使能
  18. 18     output   [15:0] post_rgb           // RGB565颜色数据
  19. 19 );
  20. 20
  21. 21 //wire define
  22. 22 wire   [ 7:0]         img_y;
  23. 23
  24. 24 //*****************************************************
  25. 25 //**                    main code
  26. 26 //*****************************************************
  27. 27
  28. 28 assign  post_rgb = {img_y[7:3],img_y[7:2],img_y[7:3]};
  29. 29
  30. 30 //RGB转YCbCr模块
  31. 31 rgb2ycbcr u_rgb2ycbcr(
  32. 32     //module clock
  33. 33     .clk             (clk    ),            // 时钟信号
  34. 34     .rst_n           (rst_n  ),            // 复位信号(低有效)
  35. 35     //图像处理前的数据接口
  36. 36     .pre_frame_vsync (pre_frame_vsync),    // vsync信号
  37. 37     .pre_frame_hsync (pre_frame_hsync),    // href信号
  38. 38     .pre_frame_de    (pre_frame_de   ),    // data enable信号
  39. 39     .img_red         (pre_rgb[15:11] ),
  40. 40     .img_green       (pre_rgb[10:5 ] ),
  41. 41     .img_blue        (pre_rgb[ 4:0 ] ),
  42. 42     //图像处理后的数据接口
  43. 43     .post_frame_vsync(post_frame_vsync),   // vsync信号
  44. 44     .post_frame_hsync(post_frame_hsync),   // href信号
  45. 45     .post_frame_de   (post_frame_de),      // data enable信号
  46. 46     .img_y           (img_y),
  47. 47     .img_cb          (),
  48. 48     .img_cr          ()
  49. 49 );
  50. 50
  51. 51 endmodule
复制代码

       代码的第28行表示对转换后的8bit灰度数据进行位拼接,形成16bit的RGB565格式的数据输出。
       代码的第31行至49行是对灰度转换模块的例化,在该模块以摄像头采集的16位RGB565红、绿、蓝三原色数据作为输入数据,通过算法实现RGB到YCbCr的转换,并输出8位灰度数据,并输出数据输出使能信号。
       灰度转换模块负责将RGB565格式的图像数据转换为YCbCr格式数据,代码如下:
  1. 1   module rgb2ycbcr
  2. 2   (
  3. 3       //module clock
  4. 4       input               clk             ,   // 模块驱动时钟
  5. 5       input               rst_n           ,   // 复位信号
  6. 6   
  7. 7       //图像处理前的数据接口
  8. 8       input               pre_frame_vsync ,   // vsync信号
  9. 9       input               pre_frame_hsync ,   // hsync信号
  10. 10      input               pre_frame_de    ,   // data enable信号
  11. 11      input       [4:0]   img_red         ,   // 输入图像数据R
  12. 12      input       [5:0]   img_green       ,   // 输入图像数据G
  13. 13      input       [4:0]   img_blue        ,   // 输入图像数据B
  14. 14  
  15. 15      //图像处理后的数据接口
  16. 16      output              post_frame_vsync,   // vsync信号
  17. 17      output              post_frame_hsync,   // hsync信号
  18. 18      output              post_frame_de   ,   // data enable信号
  19. 19      output      [7:0]   img_y           ,   // 输出图像Y数据
  20. 20      output      [7:0]   img_cb          ,   // 输出图像Cb数据
  21. 21      output      [7:0]   img_cr              // 输出图像Cr数据
  22. 22  );
  23. 23  
  24. 24  //reg define
  25. 25  reg  [15:0]   rgb_r_m0, rgb_r_m1, rgb_r_m2;
  26. 26  reg  [15:0]   rgb_g_m0, rgb_g_m1, rgb_g_m2;
  27. 27  reg  [15:0]   rgb_b_m0, rgb_b_m1, rgb_b_m2;
  28. 28  reg  [15:0]   img_y0 ;
  29. 29  reg  [15:0]   img_cb0;
  30. 30  reg  [15:0]   img_cr0;
  31. 31  reg  [ 7:0]   img_y1 ;
  32. 32  reg  [ 7:0]   img_cb1;
  33. 33  reg  [ 7:0]   img_cr1;
  34. 34  reg  [ 2:0]   pre_frame_vsync_d;
  35. 35  reg  [ 2:0]   pre_frame_hsync_d;
  36. 36  reg  [ 2:0]   pre_frame_de_d   ;
  37. 37  
  38. 38  //wire define
  39. 39  wire [ 7:0]   rgb888_r;
  40. 40  wire [ 7:0]   rgb888_g;
  41. 41  wire [ 7:0]   rgb888_b;
  42. 42  
  43. 43  //*****************************************************
  44. 44  //**                    main code
  45. 45  //*****************************************************
  46. 46  
  47. 47  //RGB565 to RGB 888
  48. 48  assign rgb888_r         = {img_red  , img_red[4:2]  };
  49. 49  assign rgb888_g         = {img_green, img_green[5:4]};
  50. 50  assign rgb888_b         = {img_blue , img_blue[4:2] };
  51. 51  //同步输出数据接口信号
  52. 52  assign post_frame_vsync = pre_frame_vsync_d[2]      ;
  53. 53  assign post_frame_hsync = pre_frame_hsync_d[2]      ;
  54. 54  assign post_frame_de    = pre_frame_de_d[2]         ;
  55. 55  assign img_y            = post_frame_hsync ? img_y1 : 8'd0;
  56. 56  assign img_cb           = post_frame_hsync ? img_cb1: 8'd0;
  57. 57  assign img_cr           = post_frame_hsync ? img_cr1: 8'd0;
  58. 58  
  59. 59  //--------------------------------------------
  60. 60  //RGB 888 to YCbCr
  61. 61  
  62. 62  /********************************************************
  63. 63              RGB888 to YCbCr
  64. 64   Y  = 0.299R +0.587G + 0.114B
  65. 65   Cb = 0.568(B-Y) + 128 = -0.172R-0.339G + 0.511B + 128
  66. 66   CR = 0.713(R-Y) + 128 = 0.511R-0.428G -0.083B + 128
  67. 67  
  68. 68   Y  = (77 *R    +    150*G    +    29 *B)>>8
  69. 69   Cb = (-43*R    -    85 *G    +    128*B)>>8 + 128
  70. 70   Cr = (128*R    -    107*G    -    21 *B)>>8 + 128
  71. 71  
  72. 72   Y  = (77 *R    +    150*G    +    29 *B        )>>8
  73. 73   Cb = (-43*R    -    85 *G    +    128*B + 32768)>>8
  74. 74   Cr = (128*R    -    107*G    -    21 *B + 32768)>>8
  75. 75  *********************************************************/
  76. 76  
  77. 77  //step1 pipeline mult
  78. 78  always @(posedge clk or negedge rst_n) begin
  79. 79      if(!rst_n) begin
  80. 80          rgb_r_m0 <= 16'd0;
  81. 81          rgb_r_m1 <= 16'd0;
  82. 82          rgb_r_m2 <= 16'd0;
  83. 83          rgb_g_m0 <= 16'd0;
  84. 84          rgb_g_m1 <= 16'd0;
  85. 85          rgb_g_m2 <= 16'd0;
  86. 86          rgb_b_m0 <= 16'd0;
  87. 87          rgb_b_m1 <= 16'd0;
  88. 88          rgb_b_m2 <= 16'd0;
  89. 89      end
  90. 90      else begin
  91. 91          rgb_r_m0 <= rgb888_r * 8'd77 ;
  92. 92          rgb_r_m1 <= rgb888_r * 8'd43 ;
  93. 93          rgb_r_m2 <= rgb888_r << 3'd7 ;
  94. 94          rgb_g_m0 <= rgb888_g * 8'd150;
  95. 95          rgb_g_m1 <= rgb888_g * 8'd85 ;
  96. 96          rgb_g_m2 <= rgb888_g * 8'd107;
  97. 97          rgb_b_m0 <= rgb888_b * 8'd29 ;
  98. 98          rgb_b_m1 <= rgb888_b << 3'd7 ;
  99. 99          rgb_b_m2 <= rgb888_b * 8'd21 ;
  100. 100     end
  101. 101 end
  102. 102
  103. 103 //step2 pipeline add
  104. 104 always @(posedge clk or negedge rst_n) begin
  105. 105     if(!rst_n) begin
  106. 106         img_y0  <= 16'd0;
  107. 107         img_cb0 <= 16'd0;
  108. 108         img_cr0 <= 16'd0;
  109. 109     end
  110. 110     else begin
  111. 111         img_y0  <= rgb_r_m0 + rgb_g_m0 + rgb_b_m0;
  112. 112         img_cb0 <= rgb_b_m1 - rgb_r_m1 - rgb_g_m1 + 16'd32768;
  113. 113         img_cr0 <= rgb_r_m2 - rgb_g_m2 - rgb_b_m2 + 16'd32768;
  114. 114     end
  115. 115
  116. 116 end
  117. 117
  118. 118 //step3 pipeline div
  119. 119 always @(posedge clk or negedge rst_n) begin
  120. 120     if(!rst_n) begin
  121. 121         img_y1  <= 8'd0;
  122. 122         img_cb1 <= 8'd0;
  123. 123         img_cr1 <= 8'd0;
  124. 124     end
  125. 125     else begin
  126. 126         img_y1  <= img_y0 [15:8];
  127. 127         img_cb1 <= img_cb0[15:8];
  128. 128         img_cr1 <= img_cr0[15:8];
  129. 129     end
  130. 130 end
  131. 131
  132. 132 //延时3拍以同步数据信号
  133. 133 always@(posedge clk or negedge rst_n) begin
  134. 134     if(!rst_n) begin
  135. 135         pre_frame_vsync_d <= 3'd0;
  136. 136         pre_frame_hsync_d <= 3'd0;
  137. 137         pre_frame_de_d    <= 3'd0;
  138. 138     end
  139. 139     else begin
  140. 140         pre_frame_vsync_d <= {pre_frame_vsync_d[1:0], pre_frame_vsync};
  141. 141         pre_frame_hsync_d <= {pre_frame_hsync_d[1:0], pre_frame_hsync};
  142. 142         pre_frame_de_d    <= {pre_frame_de_d[1:0]   , pre_frame_de   };
  143. 143     end
  144. 144 end
  145. 145
  146. 146 endmodule
复制代码

        在RGB转成YUV格式的算法换算过程中数据都是以8位的数据进行的,因而我们需要将RGB565格式的数据转换成RGB888的格式,如代码第48至50行所示,此处采用的是高位补充低位的方法;下面就是进行RGB565转YCbCr算法的HDL实现:第一步,先计算出前面公式中括号里每一个乘法的乘积,如代码第78至101行所示;第二步,计算出Y、Cb、Cr括号内的值,代码104至116行;第三步,右移 8bit,由于 Step2 计算结果为 16bit,因此直接提取高8位即可,代码如119至130行所示。
       实际上从第一步到第三步的运算,均直接通过寄存器描述,没有顾虑行场有效时序等。但在实际电路中会有一个数据流上的先后顺序,这三步操作同时对连续数据进行处理,通过这种方式实现硬件加速的方法称为流水线设计。前面计算出 Y、Cb、Cr 我们消耗了step1、step2、step3这三个时钟,因此需要将输入的行场信号、使能信号同步移动 3 个时钟,如代码第133至144行。
1.5下载验证
       编译完工程之后就可以开始下载程序了。将OV5640摄像头模块插在新起点开发板的“OLED/CAMERA”插座上,并将HDMI电缆一端连接到开发板上的HDMI插座、另一端连接到显示器。将下载器一端连电脑,另一端与开发板上的JTAG端口连接,连接电源线并打开电源开关。接下来下载程序,验证OV5640 HDMI灰度显示功能。下载完成后观察HDMI显示器显示的灰度图案如下图所示,说明OV5640 HDMI灰度显示程序下载验证成功。
第四十九章 OV5640摄像头HDMI灰度显示实验22079.png

图 49.5.1 HDMI实时显示灰度图像


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

如果天空是黑暗的,那就摸黑生存;
如果发出声音是危险的,那就保持沉默;
如果自觉无力发光,那就蜷伏于牆角。
但是,不要习惯了黑暗就为黑暗辩护;
也不要为自己的苟且而得意;
不要嘲讽那些比自己更勇敢的人。
我们可以卑微如尘土,但不可扭曲如蛆虫。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-9-27 07:04

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

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