当前位置: 首页 > news >正文

基于FPGA的图像边缘检测(OV5640)

一、简介

1.应用范围

边缘主要存在于图像中目标与目标之间,目标与背景之间,区域与区域之间。

边缘检测的目的就是找到图像中亮度变化剧烈的像素点构成的集合,表现出来往往是轮廓。如果图像中边缘能够精确的测量和定位,那么,就意味着实际的物体能够被定位和测量,包括物体的面积,物体的直径,物体的形状等就能被测量。

基于此,边缘检测技术在许多场景下被应用,如:车牌检测与提取,物体识别等。

2.边缘检测背景介绍

数字图像处理是指将图像信号转换成数字信号并利用计算机对其进行处理的过程。图像处理最早出现于20世纪50年代,当时的电了计算机已经发展到一定水平,人们开始利用计算机来处理图形和图像信息。数字图像处理作为一门学科人约形成于20世纪60年代初期。早期的图像处理的目的是改善图像的质量,它以人为对象,以改善人的视觉效果为日的。图像处理中,输入的是质量低的图像,输出的是改善质量后的图像,常用的图像处理方法有图像增强,复原,编码,压缩等。

边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。

边缘检测的实质是采用某种算法来提取出图像中对象与背景问的交界线。我们将边缘定义为图像中灰度发生急刷变化的区域边界。图像灰度的变化情况可以川图像灰度分布的梯度来反唤,因此我们可以用局部图像微分技术来获得边缘检测算子。经典的边缘检测方法,是通过对原始图像中像素的某小邻域构造边缘检测算子来达到检测边缘这一目的。

3.工程实践

3.1需求分析

3.2 系统模块说明

3.3系统架构

模块名称

模块功能

摄像头驱动

初始化配置ov5640摄像头

摄像头数据采集

采集ov5640摄像头输出的图像数据

图像处理单元

实现图像处理功能--灰度化,二值化,边缘检测等

sdram控制器

视频数据缓存

vga显示驱动

vga显示器驱动时序实现

4、摄像头简介

二、程序设计

1.摄像头配置

1.1 摄像头配置原理

本次设计使用的摄像头是OV5640,摄像头配置的详细原理,请参考:OV5640手册解读

1.2 程序设计
1.2.1 接口模块程序设计

本次设计中,接口模块采用的是IIC协议。因为IIC向下兼容SCCB协议,只是在写时序时,SCCB第九位传输的是Don't care,而IIC传输的是ACK响应,故IIC接口模块直接拿来使用时,要将写时序的ACK响应废除。

module i2c_intf (input           clk         ,input           rst_n       ,input   [3:0]   cmd         ,input           req         ,input   [7:0]   wr_data     ,output  [7:0]   dout        ,output          done        ,output  reg     m_scl       ,inout           m_sda                
);parameter   CMD_START   = 4'b0001,CMD_WITER   = 4'b0010,CMD_READ    = 4'b0100,CMD_STOP    = 4'b1000;parameter   SCL_MAX     = 50_000_000 / 100_000 ,// 500分频-->100k的时钟频率SCL_LOW     = 125,//低电平的中间时刻,发送 1/4SCL_HIGHT   = 375;//高电平的中间时刻,采样 3/4//wr_data
reg     [7:0]   wr_data_r;
reg     [7:0]   rd_data  ;
//cmd寄存
reg     [3:0]   cmd_r;
//ack响应
reg             rx_ack;   
//scl计数器
reg	    [8:0]   cnt_scl     ;
wire		    add_cnt_scl ;
wire            end_cnt_scl ;
//bit计数器
reg     [3:0]   cnt_bit     ;
wire		    add_cnt_bit ;
wire            end_cnt_bit ;
reg     [3:0]   bit_max     ;//bit最大计数复用//状态转移条件
reg [3:0]   state_c;
reg [3:0]   state_n;
wire        idle2start  ;
wire        idle2witer  ;
wire        idle2read   ;
wire        start2witer ;
wire        witer2rack  ;
wire        rack2idle   ;
wire        rack2stop   ;
wire        read2sack   ;
wire        sack2idle   ;
wire        sack2stop   ;
wire        stop2idle   ;
//三态门
reg         m_sda_en        ; // 设置SDA模式,1位输出,0为输入
reg         m_sda_out       ; // SDA寄存器
wire        m_sda_in;
/**************************************************************状态机        
**************************************************************/
parameter   IDLE    =   0,START   =   1,WITER   =   2,RACK    =   3,READ    =   4,SACK    =   5,STOP    =   6;//第一段状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)beginstate_c <= IDLE;
end
else beginstate_c <= state_n;
end
end//第二段状态机
always @(*)begin
case(state_c)IDLE    :	if(idle2start)state_n = START;else if(idle2witer)state_n = WITER;else if(idle2read)state_n = READ;else state_n = state_c;START    :	if(start2witer)state_n = WITER;else state_n = state_c;WITER    :	if(witer2rack)state_n = RACK;else state_n = state_c;                                   RACK    :	if(rack2idle)state_n = IDLE;else if(rack2stop)state_n = STOP;else state_n = state_c;READ    :	if(read2sack)state_n = SACK;else state_n = state_c;SACK    :	if(sack2idle)state_n = IDLE;else if(sack2stop)state_n = STOP;else state_n = state_c;                                                                         STOP    :   if(stop2idle)state_n = IDLE;elsestate_n = state_c;            default : state_n = state_c;endcase
end	//状态跳转条件
assign 	   idle2start  = state_c == IDLE  && req && (cmd & CMD_START)	; 
assign 	   idle2witer  = state_c == IDLE  && req && (cmd & CMD_WITER)	;
assign 	   idle2read   = state_c == IDLE  && req && (cmd & CMD_READ )	;
assign 	   start2witer = state_c == START && end_cnt_bit ;
assign 	   witer2rack  = state_c == WITER && end_cnt_bit ;
assign 	   rack2idle   = state_c == RACK  && end_cnt_bit && (!(cmd_r & CMD_STOP));
assign 	   rack2stop   = state_c == RACK  && end_cnt_bit && ((cmd_r & CMD_STOP) /* || rx_ack */);
assign 	   read2sack   = state_c == READ  && end_cnt_bit ;
assign 	   sack2idle   = state_c == SACK  && end_cnt_bit && (!(cmd_r & CMD_STOP));
assign 	   sack2stop   = state_c == SACK  && end_cnt_bit && (cmd_r & CMD_STOP);
assign     stop2idle   = state_c == STOP  && end_cnt_bit ;
/**************************************************************时序约束            
**************************************************************/
//cmd寄存
always@(posedge clk or negedge rst_n)if(!rst_n)cmd_r <= 4'b0000;else if(req)cmd_r <= cmd;//接收从机回应的ackalways @(posedge clk or negedge rst_n)begin if(!rst_n)beginrx_ack <= 1'b1;end else if(state_c == RACK && cnt_scl == SCL_HIGHT)begin rx_ack <= m_sda_in;end end//写入的数据 always @(posedge clk or negedge rst_n)begin if(!rst_n)beginwr_data_r <= 0;end else if(req)begin wr_data_r <= wr_data;end end
//接收读取的数据
//rd_data       接收读入的数据always  @(posedge clk or negedge rst_n)beginif(~rst_n)beginrd_data <= 0;endelse if(state_c == READ && cnt_scl == SCL_HIGHT)beginrd_data[7-cnt_bit] <= m_sda_in;    //将接收到的SDA信号串并转换发送到eeprom_rw模块endend
/**************************************************************双向端口m_sda的使用方式                   
**************************************************************/
assign	m_sda_in = m_sda;					 //高阻态的话,则把总线上的数据赋给m_sda_in
assign	m_sda =  m_sda_en ? m_sda_out : 1'bz;//使能1则输出,0则高阻态                    //m_sda_en
always@(posedge clk or negedge rst_n)if(!rst_n)m_sda_en <= 1'b0;else if(state_c == READ | state_c == RACK)   m_sda_en <= 1'b0;     else m_sda_en <= 1'b1; //m_sda_outalways @(posedge clk or negedge rst_n)begin if(!rst_n)beginm_sda_out <= 1;end else begin case (state_c)START : if(cnt_scl == SCL_LOW)m_sda_out <= 1'b1;else if(cnt_scl == SCL_HIGHT)m_sda_out <= 1'b0;WITER : if(cnt_scl == SCL_LOW)m_sda_out <= wr_data_r[7-cnt_bit];//MSBSTOP  : if(cnt_scl == SCL_LOW)m_sda_out <= 1'b0;else if(cnt_scl == SCL_HIGHT)m_sda_out <= 1'b1;SACK  : if(cnt_scl == SCL_LOW)m_sda_out <= (cmd & CMD_STOP)?1'b1:1'b0;default: m_sda_out <= 1'bz;endcaseend end
/**************************************************************系统时钟降频模块             
**************************************************************/
//cnt_sclalways @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_scl <= 0;end else if(add_cnt_scl)begin if(end_cnt_scl)begin cnt_scl <= 0;endelse begin cnt_scl <= cnt_scl + 1;end endelse begincnt_scl <= cnt_scl;endend assign add_cnt_scl = state_c != IDLE ;assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_MAX - 1;//m_sclalways @(posedge clk or negedge rst_n)begin if(!rst_n)beginm_scl <= 1'b1;end else if(cnt_scl <= (SCL_MAX>>1))begin m_scl <= 1'b0;end else begin m_scl <= 1'b1;end end/**************************************************************bit计数器              
**************************************************************/always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_bit <= 0;end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 0;endelse begin cnt_bit <= cnt_bit + 1'b1;end endelse begincnt_bit <= cnt_bit;endend assign add_cnt_bit = end_cnt_scl ;assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1;//bit_maxalways@(*)if(state_c == WITER || state_c == READ)bit_max = 8; else bit_max = 1; 
/**************************************************************输出信号              
**************************************************************/
assign  dout = rd_data;
assign  done = rack2idle | sack2idle | stop2idle; endmodule
1.2.2 OV5640配置模块程序设计

配置流程:

主要采用状态机加计数器的方式来设计配置模块;

当上电之后计数20ms,之后就可以进行摄像头的配置,有一个配置完成信号,当配置完254(测试模式254,实际显示模式252)个寄存器后,配置信号有效。

配置模块主要就是通过IIC_master模块向摄像头里面写入数据,完成配置。

发送数据是以任务的方式发请求、命令和数据

注意: OV5640的寄存器地址是 16 位的,加上数据,sccb_data 的值为 24 位 ,写数据传输时要传输4个字节:1字节写命令+2字节地址+1字节数据

`include "param.v"
module cmos_config(input               clk         ,input               rst_n       ,//i2c_masteroutput              req         ,output      [3:0]   cmd         ,output      [7:0]   dout        ,input               done        ,output              config_done 
);//定义参数localparam  WAIT   = 4'b0001,//上电等待20msIDLE   = 4'b0010,WREQ   = 4'b0100,//发写请求WRITE  = 4'b1000;//等待一个字节写完parameter   DELAY  = 1000_000;//上电延时20ms开始配置
//信号定义reg     [3:0]       state_c     ;reg     [3:0]       state_n     ;reg     [19:0]      cnt0        ;wire                add_cnt0/* synthesis syn_keep*/    ;wire                end_cnt0/* synthesis syn_keep*/    ;reg     [1:0]       cnt1        ;wire                add_cnt1/* synthesis syn_keep*/    ;wire                end_cnt1/* synthesis syn_keep*/    ;reg                 config_flag ;	//1:表示在配置摄像头 0:表示配置完成reg     [23:0]      lut_data    ;reg                 tran_req    ; 	//发送请求命令和数据reg      [3:0]      tran_cmd    ; reg      [7:0]      tran_dout   ; wire                wait2idle   ; 	//状态转移条件wire                idle2wreq   ; wire                write2wreq  ; wire                write2idle  ; //状态机always  @(posedge clk or negedge rst_n)beginif(~rst_n)begin        state_c <= WAIT;endelse beginstate_c <= state_n;endendalways  @(*)begincase(state_c)WAIT :begin if(wait2idle)state_n = IDLE;else state_n = state_c; end IDLE :begin if(idle2wreq)state_n = WREQ; else state_n = state_c; end  WREQ  :state_n = WRITE;WRITE :begin if(write2wreq)state_n = WREQ; else if(write2idle)state_n = IDLE;else state_n = state_c; end default:state_n = IDLE; endcase endassign wait2idle  = state_c == WAIT  && end_cnt0; assign idle2wreq  = state_c == IDLE  && config_flag; assign write2wreq = state_c == WRITE && done && ~end_cnt1; assign write2idle = state_c == WRITE && end_cnt1; //计数器always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt0 <= 0;endelse if(add_cnt0)beginif(end_cnt0)cnt0 <= 0;elsecnt0 <= cnt0 + 1;endendassign add_cnt0 = state_c == WAIT || state_c == WRITE && end_cnt1;assign end_cnt0 = add_cnt0 && cnt0 == ((state_c == WAIT)?(DELAY-1):(`REG_NUM-1));always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt1 <= 0;endelse if(add_cnt1)beginif(end_cnt1)cnt1 <= 0;elsecnt1 <= cnt1 + 1;endendassign add_cnt1 = state_c == WRITE && done;assign end_cnt1 = add_cnt1 && cnt1 == 4-1;//config_flagalways  @(posedge clk or negedge rst_n)beginif(~rst_n)beginconfig_flag <= 1'b1;endelse if(config_flag & end_cnt0 & state_c != WAIT)begin    //所有寄存器配置完,flag拉低config_flag <= 1'b0;endend//输出寄存器always  @(posedge clk or negedge rst_n)beginif(~rst_n)begin				tran_req <= 0;tran_cmd <= 0;tran_dout <= 0;endelse if(state_c == WREQ)begincase(cnt1)0:begin tran_req <= 1;tran_cmd <= {`CMD_START | `CMD_WRITE};tran_dout <= `WR_ID;end 1:begin tran_req <= 1;tran_cmd <= `CMD_WRITE;tran_dout <= lut_data[23:16];end2:begin tran_req <= 1;tran_cmd <= `CMD_WRITE;tran_dout <= lut_data[15:8];end3:begin tran_req <= 1;tran_cmd <= {`CMD_STOP | `CMD_WRITE};tran_dout <= lut_data[7:0];enddefault:tran_req <= 0;endcase endelse begintran_req  <= 0;tran_cmd  <= 0;tran_dout <= 0;end end//输出assign config_done = ~config_flag;	//配置好为0,否则就是在配置中assign req = tran_req;assign cmd = tran_cmd;assign dout = tran_dout;//lut_data   always@(*)begincase(cnt0)			  //15fps VGA YUV output// 24MHz input clock, 84MHz PCLK0  :lut_data	= 	{24'h3103_11}; // system clock from pad, bit[1]1  :lut_data	= 	{24'h3008_82}; // software reset, bit[7]2  :lut_data	= 	{24'h3008_42}; // software power down, bit[6]3  :lut_data	= 	{24'h3103_03}; // system clock from PLL, bit[1]4  :lut_data	= 	{24'h3017_ff}; // FREX, Vsync, HREF, PCLK, D[9:6] output enable5  :lut_data	= 	{24'h3018_ff}; // D[5:0], GPIO[1:0] output enable6  :lut_data	= 	{24'h3034_1a}; // MIPI 10-bit7  :lut_data	= 	{24'h3037_13}; // PLL root divider, bit[4], PLL pre-divider, bit[3:0]8  :lut_data	= 	{24'h3108_01}; // PCLK root divider, bit[5:4], SCLK2x root divider, bit[3:2]9  :lut_data	= 	{24'h3630_36};//SCLK root divider, bit[1:0]10 :lut_data	= 	{24'h3631_0e};11 :lut_data	= 	{24'h3632_e2};12 :lut_data	= 	{24'h3633_12};13 :lut_data	= 	{24'h3621_e0};14 :lut_data	= 	{24'h3704_a0};15 :lut_data	= 	{24'h3703_5a};16 :lut_data	= 	{24'h3715_78};17 :lut_data	= 	{24'h3717_01};18 :lut_data	= 	{24'h370b_60};19 :lut_data	= 	{24'h3705_1a};20 :lut_data	= 	{24'h3905_02};21 :lut_data	= 	{24'h3906_10};22 :lut_data	= 	{24'h3901_0a};23 :lut_data	= 	{24'h3731_12};24 :lut_data	= 	{24'h3600_08}; // VCM control25 :lut_data	= 	{24'h3601_33}; // VCM control26 :lut_data	= 	{24'h302d_60}; // system control27 :lut_data	= 	{24'h3620_52};28 :lut_data	= 	{24'h371b_20};29 :lut_data	= 	{24'h471c_50};30 :lut_data	= 	{24'h3a13_43}; // pre-gain = 1.047x31 :lut_data	= 	{24'h3a18_00}; // gain ceiling32 :lut_data	= 	{24'h3a19_f8}; // gain ceiling = 15.5x33 :lut_data	= 	{24'h3635_13};34 :lut_data	= 	{24'h3636_03};35 :lut_data	= 	{24'h3634_40};36 :lut_data	= 	{24'h3622_01};// 50/60Hz detection 50/60Hz 灯光条纹过滤37 :lut_data	= 	{24'h3c01_34}; // Band auto, bit[7]38 :lut_data	= 	{24'h3c04_28}; // threshold low sum39 :lut_data	= 	{24'h3c05_98}; // threshold high sum40 :lut_data	= 	{24'h3c06_00}; // light meter 1 threshold[15:8]41 :lut_data	= 	{24'h3c07_08}; // light meter 1 threshold[7:0]42 :lut_data	= 	{24'h3c08_00}; // light meter 2 threshold[15:8]43 :lut_data	= 	{24'h3c09_1c}; // light meter 2 threshold[7:0]44 :lut_data	= 	{24'h3c0a_9c}; // sample number[15:8]45 :lut_data	= 	{24'h3c0b_40}; // sample number[7:0]46 :lut_data	= 	{24'h3810_00}; // Timing Hoffset[11:8]47 :lut_data	= 	{24'h3811_10}; // Timing Hoffset[7:0]48 :lut_data	= 	{24'h3812_00}; // Timing Voffset[10:8]49 :lut_data	= 	{24'h3708_64};50 :lut_data	= 	{24'h4001_02}; // BLC start from line 251 :lut_data	= 	{24'h4005_1a}; // BLC always update52 :lut_data	= 	{24'h3000_00}; // enable blocks53 :lut_data	= 	{24'h3004_ff}; // enable clocks54 :lut_data	= 	{24'h300e_58}; //MIPI power down,DVP enable55 :lut_data	= 	{24'h302e_00};56 :lut_data	= 	{24'h4300_61}; // RGB,57 :lut_data	= 	{24'h501f_01}; // ISP RGB58 :lut_data	= 	{24'h440e_00};59 :lut_data	= 	{24'h5000_a7}; // Lenc on, raw gamma on, BPC on, WPC on, CIP on// AEC target 自动曝光控制60 :lut_data	= 	{24'h3a0f_30}; // stable range in high61 :lut_data	= 	{24'h3a10_28}; // stable range in low62 :lut_data	= 	{24'h3a1b_30}; // stable range out high63 :lut_data	= 	{24'h3a1e_26}; // stable range out low64 :lut_data	= 	{24'h3a11_60}; // fast zone high65 :lut_data	= 	{24'h3a1f_14}; // fast zone low// Lens correction for ? 镜头补偿66 :lut_data	= 	{24'h5800_23};67 :lut_data	= 	{24'h5801_14};68 :lut_data	= 	{24'h5802_0f};69 :lut_data	= 	{24'h5803_0f};70 :lut_data	= 	{24'h5804_12};71 :lut_data	= 	{24'h5805_26};72 :lut_data	= 	{24'h5806_0c};73 :lut_data	= 	{24'h5807_08};74 :lut_data	= 	{24'h5808_05};75 :lut_data	= 	{24'h5809_05};76 :lut_data	= 	{24'h580a_08};77 :lut_data	= 	{24'h580b_0d};78 :lut_data	= 	{24'h580c_08};79 :lut_data	= 	{24'h580d_03};80 :lut_data	= 	{24'h580e_00};81 :lut_data	= 	{24'h580f_00};82 :lut_data	= 	{24'h5810_03};83 :lut_data	= 	{24'h5811_09};84 :lut_data	= 	{24'h5812_07};85 :lut_data	= 	{24'h5813_03};86 :lut_data	= 	{24'h5814_00};87 :lut_data	= 	{24'h5815_01};88 :lut_data	= 	{24'h5816_03};89 :lut_data	= 	{24'h5817_08};90 :lut_data	= 	{24'h5818_0d};91 :lut_data	= 	{24'h5819_08};92 :lut_data	= 	{24'h581a_05};93 :lut_data	= 	{24'h581b_06};94 :lut_data	= 	{24'h581c_08};95 :lut_data	= 	{24'h581d_0e};96 :lut_data	= 	{24'h581e_29};97 :lut_data	= 	{24'h581f_17};98 :lut_data	= 	{24'h5820_11};99 :lut_data	= 	{24'h5821_11};100:lut_data	= 	{24'h5822_15};101:lut_data	= 	{24'h5823_28};102:lut_data	= 	{24'h5824_46};103:lut_data	= 	{24'h5825_26};104:lut_data	= 	{24'h5826_08};105:lut_data	= 	{24'h5827_26};106:lut_data	= 	{24'h5828_64};107:lut_data	= 	{24'h5829_26};108:lut_data	= 	{24'h582a_24};109:lut_data	= 	{24'h582b_22};110:lut_data	= 	{24'h582c_24};111:lut_data	= 	{24'h582d_24};112:lut_data	= 	{24'h582e_06};113:lut_data	= 	{24'h582f_22};114:lut_data	= 	{24'h5830_40};115:lut_data	= 	{24'h5831_42};116:lut_data	= 	{24'h5832_24};117:lut_data	= 	{24'h5833_26};118:lut_data	= 	{24'h5834_24};119:lut_data	= 	{24'h5835_22};120:lut_data	= 	{24'h5836_22};121:lut_data	= 	{24'h5837_26};122:lut_data	= 	{24'h5838_44};123:lut_data	= 	{24'h5839_24};124:lut_data	= 	{24'h583a_26};125:lut_data	= 	{24'h583b_28};126:lut_data	= 	{24'h583c_42};127:lut_data	= 	{24'h583d_ce}; // lenc BR offset// AWB 自动白平衡128:lut_data	= 	{24'h5180_ff}; // AWB B block129:lut_data	= 	{24'h5181_f2}; // AWB control130:lut_data	= 	{24'h5182_00}; // [7:4] max local counter, [3:0] max fast counter131:lut_data	= 	{24'h5183_14}; // AWB advanced132:lut_data	= 	{24'h5184_25};133:lut_data	= 	{24'h5185_24};134:lut_data	= 	{24'h5186_09};135:lut_data	= 	{24'h5187_09};136:lut_data	= 	{24'h5188_09};137:lut_data	= 	{24'h5189_75};138:lut_data	= 	{24'h518a_54};139:lut_data	= 	{24'h518b_e0};140:lut_data	= 	{24'h518c_b2};141:lut_data	= 	{24'h518d_42};142:lut_data	= 	{24'h518e_3d};143:lut_data	= 	{24'h518f_56};144:lut_data	= 	{24'h5190_46};145:lut_data	= 	{24'h5191_f8}; // AWB top limit146:lut_data	= 	{24'h5192_04}; // AWB bottom limit147:lut_data	= 	{24'h5193_70}; // red limit148:lut_data	= 	{24'h5194_f0}; // green limit149:lut_data	= 	{24'h5195_f0}; // blue limit150:lut_data	= 	{24'h5196_03}; // AWB control151:lut_data	= 	{24'h5197_01}; // local limit152:lut_data	= 	{24'h5198_04};153:lut_data	= 	{24'h5199_12};154:lut_data	= 	{24'h519a_04};155:lut_data	= 	{24'h519b_00};156:lut_data	= 	{24'h519c_06};157:lut_data	= 	{24'h519d_82};158:lut_data	= 	{24'h519e_38}; // AWB control// Gamma 伽玛曲线159:lut_data	= 	{24'h5480_01}; //Gamma bias plus on, bit[0]160:lut_data	= 	{24'h5481_08};161:lut_data	= 	{24'h5482_14};162:lut_data	= 	{24'h5483_28};163:lut_data	= 	{24'h5484_51};164:lut_data	= 	{24'h5485_65};165:lut_data	= 	{24'h5486_71};166:lut_data	= 	{24'h5487_7d};167:lut_data	= 	{24'h5488_87};168:lut_data	= 	{24'h5489_91};169:lut_data	= 	{24'h548a_9a};170:lut_data	= 	{24'h548b_aa};171:lut_data	= 	{24'h548c_b8};172:lut_data	= 	{24'h548d_cd};173:lut_data	= 	{24'h548e_dd};174:lut_data	= 	{24'h548f_ea};175:lut_data	= 	{24'h5490_1d};// color matrix 色彩矩阵176:lut_data	= 	{24'h5381_1e}; // CMX1 for Y177:lut_data	= 	{24'h5382_5b}; // CMX2 for Y178:lut_data	= 	{24'h5383_08}; // CMX3 for Y179:lut_data	= 	{24'h5384_0a}; // CMX4 for U180:lut_data	= 	{24'h5385_7e}; // CMX5 for U181:lut_data	= 	{24'h5386_88}; // CMX6 for U182:lut_data	= 	{24'h5387_7c}; // CMX7 for V183:lut_data	= 	{24'h5388_6c}; // CMX8 for V184:lut_data	= 	{24'h5389_10}; // CMX9 for V185:lut_data	= 	{24'h538a_01}; // sign[9]186:lut_data	= 	{24'h538b_98}; // sign[8:1]// UV adjust UV 色彩饱和度调整187:lut_data	= 	{24'h5580_06}; // saturation on, bit[1]188:lut_data	= 	{24'h5583_40};189:lut_data	= 	{24'h5584_10};190:lut_data	= 	{24'h5589_10};191:lut_data	= 	{24'h558a_00};192:lut_data	= 	{24'h558b_f8};193:lut_data	= 	{24'h501d_40}; // enable manual offset of contrast// CIP 锐化和降噪194:lut_data	= 	{24'h5300_08}; //CIP sharpen MT threshold 1195:lut_data	= 	{24'h5301_30}; //CIP sharpen MT threshold 2196:lut_data	= 	{24'h5302_10}; // CIP sharpen MT offset 1197:lut_data	= 	{24'h5303_00}; // CIP sharpen MT offset 2198:lut_data	= 	{24'h5304_08}; // CIP DNS threshold 1199:lut_data	= 	{24'h5305_30}; // CIP DNS threshold 2200:lut_data	= 	{24'h5306_08}; // CIP DNS offset 1201:lut_data	= 	{24'h5307_16}; // CIP DNS offset 2202:lut_data	= 	{24'h5309_08}; //CIP sharpen TH threshold 1203:lut_data	= 	{24'h530a_30}; //CIP sharpen TH threshold 2204:lut_data	= 	{24'h530b_04}; //CIP sharpen TH offset 1205:lut_data	= 	{24'h530c_06}; //CIP sharpen TH offset 2206:lut_data	= 	{24'h5025_00};207:lut_data	= 	{24'h3008_02}; //wake up from standby,bit[6]// input clock 24Mhz, PCLK 84Mhz208:lut_data	= 	{24'h3035_21}; // PLL209:lut_data	= 	{24'h3036_69}; // PLL210:lut_data	= 	{24'h3c07_07}; // lightmeter 1 threshold[7:0]211:lut_data	= 	{24'h3820_47}; // flip212:lut_data	= 	{24'h3821_01}; // no mirror213:lut_data	= 	{24'h3814_31}; // timing X inc214:lut_data	= 	{24'h3815_31}; // timing Y inc215:lut_data	= 	{24'h3800_00}; // HS216:lut_data	= 	{24'h3801_00}; // HS217:lut_data	= 	{24'h3802_00}; // VS218:lut_data	= 	{24'h3803_fa}; // VS219:lut_data	= 	{24'h3804_0a}; // HW  :   	 220:lut_data	= 	{24'h3805_3f}; // HW  :   	221:lut_data	= 	{24'h3806_06}; // VH  :   	222:lut_data	= 	{24'h3807_a9}; // VH  :   	223:lut_data	= 	{24'h3808_05}; // DVPHO 1280224:lut_data	= 	{24'h3809_00}; // DVPHO225:lut_data	= 	{24'h380a_02}; // DVPVO 720226:lut_data	= 	{24'h380b_d0}; // DVPVO227:lut_data	= 	{24'h380c_07}; // HTS228:lut_data	= 	{24'h380d_64}; // HTS229:lut_data	= 	{24'h380e_02}; // VTS230:lut_data	= 	{24'h380f_e4}; // VTS231:lut_data	= 	{24'h3813_04}; // timing V offset232:lut_data	= 	{24'h3618_00};233:lut_data	= 	{24'h3612_29};234:lut_data	= 	{24'h3709_52};235:lut_data	= 	{24'h370c_03};236:lut_data	= 	{24'h3a02_02}; // 60Hz max exposure237:lut_data	= 	{24'h3a03_e0}; // 60Hz max exposure238:lut_data	= 	{24'h3a14_02}; // 50Hz max exposure239:lut_data	= 	{24'h3a15_e0}; // 50Hz max exposure240:lut_data	= 	{24'h4004_02}; // BLC line number241:lut_data	= 	{24'h3002_1c}; // reset JFIFO, SFIFO, JPG242:lut_data	= 	{24'h3006_c3}; // disable clock of JPEG2x, JPEG243:lut_data	= 	{24'h4713_03}; // JPEG mode 3244:lut_data	= 	{24'h4407_04}; // Quantization scale245:lut_data	= 	{24'h460b_37};246:lut_data	= 	{24'h460c_20};247:lut_data	= 	{24'h4837_16}; // MIPI global timing248:lut_data	= 	{24'h3824_04}; // PCLK manual divider249:lut_data	= 	{24'h5001_83}; // SDE on, CMX on, AWB on250:lut_data	= 	{24'h3503_00}; // AEC/AGC on             251:lut_data	= 	{24'h4740_20}; // VS 1252:lut_data	= 	{24'h503d_80}; // color bar253:lut_data	= 	{24'h4741_00}; //default:lut_data	=	0;endcaseendendmodule 

2.图像数据采集

2.1 图像数据采集模块原理

图像数据采集模块,参考的是OV5640手册中的DVP时序部分

2.2 图像数据采集模块程序设计

1)先对场同步信号进行同步打拍,然后检测下降沿

2)检测到下降沿,且接收到摄像头配置完成信号,采集数据标志拉高(开始采集图像数据),采集完一帧图像,标志拉低。之后进行下一帧图像的采集。

3)改变输出的图像分辨率,两种方法:一是配置寄存器,二是用简单计数器裁剪分辨率。

本次设计采用的是用计数器进行的简单分辨率裁剪。(行、场信号有效时,数据计数;其余无效数据丢弃)。

采集数据:采集数据标志拉高且行参考信号有效时,进行数据采集.

4)数据拼接:摄像头的数据是把16位RGB拆分为高八位和低八位发送的,我们需要通过移位+位拼接的方式把两个8bit数据合并成16bit数据输出。

`include "param.v"
module capture(input           clk     ,//像素时钟 摄像头输出的pclkinput           rst_n   ,input           enable  ,//采集使能 配置完成input           vsync   ,//摄像头场同步信号input           href    ,//摄像头行参考信号input   [7:0]   din     ,//摄像头像素字节output  [15:0]  dout    ,//像素数据output          dout_sop,//包文头 一帧图像第一个像素点output          dout_eop,//包文尾 一帧图像最后一个像素点output          dout_vld //像素数据有效
);//信号定义reg     [11:0]      cnt_h       ;wire                add_cnt_h   ;wire                end_cnt_h   ;reg     [9:0]       cnt_v       ;wire                add_cnt_v   ;wire                end_cnt_v   ;reg     [1:0]       vsync_r     ;//同步打拍wire                vsync_nedge ;//下降沿reg                 flag        ;//串并转换标志reg     [15:0]      data        ;reg                 data_vld    ;reg                 data_sop    ;reg                 data_eop    ;//vsync同步打拍always  @(posedge clk or negedge rst_n)beginif(~rst_n)beginvsync_r <= 2'b00;endelse beginvsync_r <= {vsync_r[0],vsync};endendassign vsync_nedge = vsync_r[1] & ~vsync_r[0];  //检测下降沿always  @(posedge clk or negedge rst_n)beginif(~rst_n)beginflag <= 1'b0;endelse if(enable & vsync_nedge)begin  //摄像头配置完成且场同步信号拉低之后开始采集有效数据flag <= 1'b1;endelse if(end_cnt_v)begin     //一帧数据采集完拉低flag <= 1'b0;   endend//计数器always @(posedge clk or negedge rst_n) begin if (rst_n==0) begincnt_h <= 0; endelse if(add_cnt_h) beginif(end_cnt_h)cnt_h <= 0; elsecnt_h <= cnt_h+1 ;endendassign add_cnt_h = flag & href;     //摄像头配置完成且场同步信号拉低且行参考信号有效assign end_cnt_h = add_cnt_h  && cnt_h == (`H_AP << 1)-1;always @(posedge clk or negedge rst_n) begin if (rst_n==0) begincnt_v <= 0; endelse if(add_cnt_v) beginif(end_cnt_v)cnt_v <= 0; elsecnt_v <= cnt_v+1 ;endendassign add_cnt_v = end_cnt_h;assign end_cnt_v = add_cnt_v  && cnt_v == `V_AP-1 ;//dataalways  @(posedge clk or negedge rst_n)beginif(~rst_n)begindata <= 0;endelse begindata <= {data[7:0],din};//左移//data <= 16'b1101_1010_1111_0111;//16'hdaf7endend//data_sopalways  @(posedge clk or negedge rst_n)beginif(~rst_n)begindata_sop <= 1'b0;data_eop <= 1'b0;data_vld <= 1'b0;endelse begindata_sop <= add_cnt_h && cnt_h == 2-1 && cnt_v == 0;data_eop <= end_cnt_v;data_vld <= add_cnt_h && cnt_h[0] == 1'b1;endendassign dout = data;assign dout_sop = data_sop;assign dout_eop = data_eop;assign dout_vld = data_vld;endmodule 

3.图像灰度转化

3.1 灰度转化算法

对于彩色转灰度,有一个很著名的心理学公式:Gray = R0.299 + G0.587 + B*0.114

RGB888 转 Ycbcr 算法:

因为FPGA无法进行浮点运算,所以我们采取将整个式子右端先都扩大256倍,然后再右移8位,这样就得到了FPGA擅长的乘法运算和加法运算了。

Y = ((77*R+150*G+29*B)>>8);

Cb = ((-43*R - 85*G + 128*B)>>8) + 128;

Cr = ((128*R - 107*G - 21*B)>>8) + 128;

3.2 灰度化代码设计
module rgb2gary (input           clk     ,input           rst_n   ,input   [15:0]  din     ,   //rgb565input           din_sop ,input           din_eop ,input           din_vld ,output  [7:0]   dout    ,   //灰度输出output          dout_sop,output          dout_eop,output          dout_vld  
);reg     [7:0]   RGB_R;
reg     [7:0]   RGB_G;
reg     [7:0]   RGB_B;reg     [15:0]  RGB_R_mult;
reg     [15:0]  RGB_G_mult;
reg     [15:0]  RGB_B_mult;reg     [16:0]  out_gary;
reg     [1:0]   vld     ;
reg     [1:0]   sop     ;
reg     [1:0]   eop     ;
/**************************************************************RGB564 -> RGB88           
**************************************************************/
always@(posedge clk or negedge rst_n)if(!rst_n)beginRGB_R <= 8'b0;RGB_G <= 8'b0;RGB_B <= 8'b0;endelse if(din_vld)beginRGB_R <= {din[15:11],din[13:11]};    //r5-r1,r3-r1;低位补偿3位RGB_G <= {din[10:5 ],din[ 6:5 ]};RGB_B <= {din[ 4:0 ],din[ 2:0 ]};end/**************************************************************rgb -> gary            
**************************************************************/
always@(posedge clk or negedge rst_n)if(!rst_n)  beginRGB_R_mult <= 16'b0; RGB_G_mult <= 16'b0;RGB_B_mult <= 16'b0;   endelse if(vld[0])    beginRGB_R_mult <= RGB_R * 76; RGB_G_mult <= RGB_G * 150;RGB_B_mult <= RGB_B * 29;  end//
always@(posedge clk or negedge rst_n)if(!rst_n)out_gary <= 17'b0;else if(vld[1])out_gary <= RGB_R_mult + RGB_G_mult + RGB_B_mult;always  @(posedge clk or negedge rst_n)beginif(~rst_n)beginsop <= 0;  eop <= 0;  vld <= 0; endelse beginsop <= {sop[0],din_sop};  eop <= {eop[0],din_eop};  vld <= {vld[0],din_vld};end
end//输出
assign dout = out_gary[16:9];    //取平均
assign dout_sop = sop[1];
assign dout_eop = eop[1];
assign dout_vld = vld[1];endmodule

4.高斯滤波

4.1 高斯滤波原理

高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。通俗的讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。

高斯滤波后图像被平滑的程度取决于标准差。它的输出是临域像素的加权平均,同时离中心越近的像素权重越高。因此,相对于均值滤波(mean filter)它的平滑效果更柔和,而且边缘保留的也更好。

高斯滤波被用作为平滑滤波器的本质原因是因为它是一个低通滤波器,而且大部份基于卷积平滑滤波器都是低通滤波器。

GAUSS滤波算法克服了边界效应,因而滤波后的图像较好。

4.2 高斯滤波算法实现步骤

详细见FPGA丨高斯滤波算法实现

高斯滤波3x3算子:

高斯滤波5x5算子:

用经过shift_ram缓存后的数据,分别乘以高斯滤波算子,在加权求和算出总的高斯滤波输出,如下图:

4.3 高斯滤波代码设计
module gauss_filter (input           clk     ,input           rst_n   ,input   [7:0]   din     ,   //灰度输入input           din_sop ,input           din_eop ,input           din_vld ,output  [7:0]   dout    ,   //高斯滤波输出output          dout_sop,output          dout_eop,output          dout_vld  
);wire    [7:0]   taps0 ;
wire    [7:0]   taps1 ;
wire    [7:0]   taps2 ;
//行同步
reg     [7:0]   row0_0;
reg     [7:0]   row0_1;
reg     [7:0]   row0_2;
reg     [7:0]   row1_0;
reg     [7:0]   row1_1;
reg     [7:0]   row1_2;
reg     [7:0]   row2_0;
reg     [7:0]   row2_1;
reg     [7:0]   row2_2;
//
reg     [10:0]  sum0;
reg     [10:0]  sum1;
reg     [10:0]  sum2;
//
reg     [7:0]   out_gauss;
reg     [2:0]   vld      ;
reg     [2:0]   sop      ;
reg     [2:0]   eop      ;/**************************************************************shift_ram(3x3)模块            
**************************************************************/    
gs_shift_ram	gs_shift_ram_inst (.aclr       ( ~rst_n     ),.clken      ( din_vld    ),.clock      ( clk        ),.shiftin    ( din        ),.shiftout   (            ),.taps0x     ( taps0      ),.taps1x     ( taps1      ),.taps2x     ( taps2      ));/**************************************************************第一级流水       
**************************************************************///缓存3行数据always@(posedge clk or negedge rst_n)if(!rst_n) beginrow0_0 <= 'd0; row0_1 <= 'd0; row0_2 <= 'd0;row1_0 <= 'd0; row1_1 <= 'd0; row1_2 <= 'd0;row2_0 <= 'd0; row2_1 <= 'd0; row2_2 <= 'd0;endelse if(vld[0]) beginrow0_0 <= taps0; row0_1 <= row0_0; row0_2 <= row0_1;row1_0 <= taps1; row1_1 <= row1_0; row1_2 <= row1_1;row2_0 <= taps2; row2_1 <= row2_0; row2_2 <= row2_1;end/**************************************************************第二级流水           
**************************************************************/   
//对三行数分别进行加权求和
always@(posedge clk or negedge rst_n)if(!rst_n) beginsum0 <= 'd0;sum1 <= 'd0;sum2 <= 'd0;endelse if(vld[1]) begin     //将每个数分别乘以对应的3x3高斯算子sum0 <= {2'b0,row0_0} + {1'b0,row0_1,1'b0} + {2'b0,row0_2 };sum1 <= {1'b0,row1_0,1'b0} + {row1_1,2'b0} + {1'b0,row1_2,1'b0};sum2 <= {2'b0,row2_0} + {1'b0,row2_1,1'b0} + {2'b0,row2_2 };end/**************************************************************第三级流水          
**************************************************************/
//
always@(posedge clk or negedge rst_n)if(!rst_n)out_gauss <= 'd0;else if(vld[2])   beginout_gauss <= (sum0 + sum1 + sum2) >> 4;endalways  @(posedge clk or negedge rst_n)beginif(~rst_n)beginsop <= 'd0;  eop <= 'd0;  vld <= 'd0; endelse beginsop <= {sop[1:0],din_sop};  eop <= {eop[1:0],din_eop};  vld <= {vld[1:0],din_vld};end
end//输出端口
assign dout = out_gauss;   
assign dout_sop = sop[2];
assign dout_eop = eop[2];
assign dout_vld = vld[2];
endmodule

5.图像二值化

5.1 图像二值化原理

对于一个灰度图像来说,如果指定的像素点大于某一个数值,那么该点设置为255;反之则设置为0。这就是图像二值化的由来 。

5.2 二值化代码设计
module binarization (input           clk     ,input           rst_n   ,input   [7:0]   din     ,   //高斯滤波输入input           din_sop ,input           din_eop ,input           din_vld ,output          dout    ,   //图像二值化输出output          dout_sop,output          dout_eop,output          dout_vld 
);reg             dout_r    ;
reg             dout_sop_r;
reg             dout_eop_r;
reg             dout_vld_r;always@(posedge clk or negedge rst_n)if(!rst_n)begindout_r     <= 'd0;dout_sop_r <= 'd0;dout_eop_r <= 'd0;dout_vld_r <= 'd0;endelse    begindout_sop_r <= din_sop; dout_eop_r <= din_eop; dout_vld_r <= din_vld;if(din > 120)           //二值化阈值dout_r <= 1'b1;elsedout_r <= 1'b0;end//输出端口
assign  dout     = dout_r    ;
assign  dout_sop = dout_sop_r;
assign  dout_eop = dout_eop_r;
assign  dout_vld = dout_vld_r;        endmodule

6.sobel边沿检测

6.1 sobel算子简介

关于sobel算子,详细可见:OpenCV(十五)边缘检测1 -- Sobel算子

sobel算子是一个离散的一阶差分算子,广泛应用于边缘检测等领域。算法的应用原理比较简单,可以完成对水平方向和垂直方向的边缘检测。分别用图中的两个卷积模板对图像进行滑动窗口的卷积计算,将卷积模板和图像3*3窗口对应的数据相乘,相乘的结果相加得到

,通过

计算的得到G,再通过阈值比较得到二值图像。有时为了提高计算效率,通过

来近似得到G。

运用sobel算子进行边缘检测时,可以直接乘其模板的绝对值,再将相乘结果相减,以便于运算

6.2 sobel算法使用步骤

1.先求x,y方向的梯度dx,dy。

2.然后求出近似梯度

然后开根号,也可以为了分别计算近似为

3.最后提取G的值,来判断该点是不是边缘点,是的话,就将该点的像素复制为255,否则为0;阈值G可以自己随意指定,阈值的设定通常在0-255之间,没有标准值。阈值设定过高,会导致边缘被过滤掉;阈值设定过低,会导致边缘过多的被保留,造成边缘检测的结果混乱。

6.3 sobel代码设计
module sobel (input           clk     ,input           rst_n   ,input           din     , input           din_sop ,input           din_eop ,input           din_vld ,output          dout    , output          dout_sop,output          dout_eop,output          dout_vld     
);wire            taps0 ;
wire            taps1 ;
wire            taps2 ;
//行同步
reg             row0_0;
reg             row0_1;
reg             row0_2;
reg             row1_0;
reg             row1_1;
reg             row1_2;
reg             row2_0;
reg             row2_1;
reg             row2_2;  
reg     [2:0]   sumx_0;
reg     [2:0]   sumx_2;
reg     [2:0]   sumy_0;
reg     [2:0]   sumy_2;reg     [3:0]   x_abs;
reg     [3:0]   y_abs;
reg     [3:0]   g       ;  
reg     [3:0]   sop     ;
reg     [3:0]   eop     ;
reg     [3:0]   vld     ;
/**************************************************************shift_ram(3x3)模块       
**************************************************************/
sobel_shift_ram	sobel_shift_ram_inst (.aclr       ( ~rst_n     ),.clken      ( din_vld    ),.clock      ( clk        ),.shiftin    ( din        ),.shiftout   (            ),.taps0x     ( taps0      ),.taps1x     ( taps1      ),.taps2x     ( taps2      ));//缓存3行数据,第一级流水
always@(posedge clk or negedge rst_n)if(!rst_n) beginrow0_0 <= 'd0; row0_1 <= 'd0; row0_2 <= 'd0;row1_0 <= 'd0; row1_1 <= 'd0; row1_2 <= 'd0;row2_0 <= 'd0; row2_1 <= 'd0; row2_2 <= 'd0;endelse if(vld[0]) beginrow0_0 <= taps0; row0_1 <= row0_0; row0_2 <= row0_1;row1_0 <= taps1; row1_1 <= row1_0; row1_2 <= row1_1;row2_0 <= taps2; row2_1 <= row2_0; row2_2 <= row2_1;end//将缓存后的数据乘以sobel算子模板,第二级流水
always@(posedge clk or negedge rst_n)if(!rst_n) beginsumx_0 <= 'd0;sumx_2 <= 'd0; sumy_0 <= 'd0;sumy_2 <= 'd0;       endelse if(vld[1]) beginsumx_0 <= {2'b0,row0_0} + {1'b0,row1_0,1'b0} + {2'b0,row2_0};sumx_2 <= {2'b0,row0_2} + {1'b0,row1_2,1'b0} + {2'b0,row2_2};sumy_0 <= {2'b0,row0_0} + {1'b0,row0_1,1'b1} + {2'b0,row0_2};sumy_2 <= {2'b0,row2_0} + {1'b0,row2_1,1'b1} + {2'b0,row2_2};end//计算x、y梯度绝对值,第3级流水
always@(posedge clk or negedge rst_n)if(!rst_n)  beginx_abs <= 'd0;y_abs <= 'd0;endelse if(vld[2]) beginx_abs <= (sumx_0 > sumx_2) ? (sumx_0 - sumx_2) : (sumx_2 - sumx_0);y_abs <= (sumy_0 > sumy_2) ? (sumy_0 - sumy_2) : (sumy_2 - sumy_0);end//计算最终的g值,第4级流水
always@(posedge clk or negedge rst_n)if(!rst_n)g <= 'd0;else if(vld[3]) beging <= x_abs + y_abs;end//打拍
always  @(posedge clk or negedge rst_n)beginif(~rst_n)beginsop <= 0;eop <= 0;vld <= 0;endelse beginsop <= {sop[2:0],din_sop};eop <= {eop[2:0],din_eop};vld <= {vld[2:0],din_vld};end
end
assign  dout     = g >= 3;//阈值假设为3 当某一个像素点的梯度值大于3,认为其是一个边缘点
assign  dout_sop = sop[3];
assign  dout_eop = eop[3];
assign  dout_vld = vld[3];endmodule

7.sdram模块

通过乒乓缓存操作向SDRAM中读写图像数据,接口通过调用IP,主要是SDRAM读写控制逻辑(rw_control),使用两个异步FIFO跨时钟域数据处理,使用读写仲裁机制产生读写传输请求、地址等
为什么要用pp(乒乓)缓存?
如果不采用乒乓缓存,OV5640 帧率 30fps,VGA 帧率 60fps,如果摄像头输入的数据和VGA输出的数据都是连续不断的,那么刚好可以写一帧读两帧。但是一帧图像实际情况是一行行的生成和读取的,所以会出现 VGA 从SDRAM处读的上半帧是新帧,而由于SDRAM缓存的下半帧还没有被 OV5640写完,VGA 从SDRAM处读的下半帧还是旧帧,会出现错帧现象。

采用乒乓缓存机制时,使用两个缓存区,写缓存区 1 时读缓存区 2,写缓存区 2 时读缓存区 1,每个缓存区存储完整的数据帧,读写隔离并且读写交替则不会出现错帧现象。具体乒乓缓存操作如下图:


为什么要读写仲裁?
在FPGA中,当多个操作同时发出请求,容易导致操作冲突,因此我们需要根据相应的优先级来响应哪一个操作,这个过程就叫仲裁。在SDRAM中,初始化完成后,主要的功能就是突发写、突发读和自动刷新。如果同时发起写、读和刷新请求,就会出现操作冲突,从而导致SDRAM工作出错,因此这里就需要引入仲裁机制。为了简化设计,考虑将刷新与读写请求的仲裁分开考虑。由于刷新的优先级一定高于读写,因此,在底层接口中,只对读/写请求与刷新请求进行仲裁,即刷新请求的优先级一定高于读/写请求。在控制逻辑中,对读/写请求进行仲裁,保证底层接口不会同时收到读请求与写请求,从而避免底层接口中出现复杂控制。

7.1 sdram读写控制模块设计思路
7.1.1 整体分析数据从哪儿输入,输出到哪儿去?

(1).跨时钟域数据传输,读写FIFO。
(2).涉及到的时钟信号3个:pclkclk_75m(vga),sdram控制器时钟clk_100m

sdram控制器时钟为什么取100m?

数据吞吐量计算:由于sdram地址总线、命令总线和数据总线是共用的,所以读写操作不能同时进行,要考虑1s钟能否成功接收摄像头传输过来的数据和vga正常显示需要的数据:

摄像头1s传输数据量

1280*720*30 ≈ 30m

vga接口1s需要传输的数据量

1280*720*60 ≈ 60m

7.1.2 如何控制sdram控制器的数据读写策略,能避免数据拥塞?

(1).问题分析:由于摄像头数据输出的像素数据量大且速度较快,vga显示所需要的数据量大且速度较快,若不合理控制读写,则有可能会导致写fifo中的数据量溢出或者读fifo中数据读空。
(2).解决方案:动态调整读写操作的优先 -- 根据与缓冲区与读缓冲区中的剩余数据量。动态仲裁读操作和写操作的先后顺序,保证写缓冲区不溢出,读缓冲区不空。
(3).思考:读写速度过快的情况下,无法手动控制读写请求,由控制器内部去产生控制请求。可以做一个读写仲裁机制。

只读 (满足可读条件)

读fifo剩余数据可供vga显示

只写 (满足可写条件)

写fifo有多少剩余数据量可向sdram写入

读写同时存在?同时满足读写条件

上一次操作是读操作

这次就是读操作

上一次操作是写操作

这次就是读操作

注意:sdram不能同时执行读写操作,所以控制器不能同时给接口模块读写请求。
多久仲裁1次?每完成一次突发读或写操作仲裁1次(突发长度建议512)。
(4).sdram的读写请求怎么产生?利用读写fifo的剩余数据量。

①.写请求:写fifo的usedw足够sdram完成一次突发读时,即wr_usedw > 512,sdram的写请求拉高;反之,拉低。
②.读请求:读fifo的数据余量低于一个下限值(下限值大于突发长度)时,拉高
读请求:读fifo的数据余量高于一个上限值(上限值大于2倍突发长度)时,拉低读请求,能保证低于上限是也能完成一次突发读;保证读fifo中有足够数据量传输到 VGA端.

即,当rd_wrusedw <= 下限值(本次设计为600)时,读请求拉高,开启突发读;当rd_usedw >上限值(本次设计为1500)时,读请求拉低。

7.1.3 如何保证显示器显示的是一帧完整的图像?

通过双bank乒乓缓存实现写入和读出图像帧的完整性 -- 对每个bank的读写都是以完整的数据帧为单位操作,通过sop与eop信号确定数据帧的范围。

注意:代码中SDRAM无法进行同时读写,我们只能在写完且读完一帧数据时去切换存储区域,便于操控选择两个不同的bank进行切换。

乒乓操作主要⽤于控制数据流,在此项⽬中主要体现为先写SDRAM bank1的数据,同时读SDRAM bank3的数据,当两块bank的数据读写完毕后,切换操作为读bank1的数据,写bank3的数据,这样可以保持数据为完整的⼀帧,使显⽰屏帧与帧之间切换瞬间完成。

具体步骤如下:

在第一个缓冲周期,输入数据流写入数据缓冲模块1,写完后进入第二个缓冲周期。

在第二个缓冲周期,输入数据流写入数据缓冲模块2,同时将数据缓冲模块1中的数据读出。

在第三个缓冲周期,输入数据流再次写入数据缓冲模块 1,同时将数据缓冲模块 2 中的数据读出。

7.1.4 丢帧处理.

(1)为什么要丢帧?

乒乓操作中有若出现一帧数据写完但是还没读完的情况,又来一帧新的图像数据,此时就不能再向sdram中写入数据,否则会出现帧错位的情况,此时则需要丢到当前帧,等待读操作完成后,下一次sop的到来。

2.SDRAM模块代码设计
`include "param.v"
module sdram_drive (input               clk        ,    //clk_100minput               clk_in     ,input               clk_out    ,input               rst_n      ,//image_processinput       [15:0]  din        ,input               din_sop    ,input               din_eop    ,input               din_vld    , //vgainput               req        ,    //vga读数据请求output      [15:0]  dout       ,output              dout_vld   ,//avalon_portoutput      [23:0]  addr       ,    //访问sdram的地址output              wr_n       ,    //访问sdram的写使能信号output      [15:0]  wr_data    ,    //访问sdram的写数据output              rd_n       ,    //访问sdram的读使能信号input       [15:0]  rd_data    ,    //访问sdram的读出数据input               rd_data_vld,    //访问sdram的读出数据有效信号input               waitrequest     //sdram等待请求信号    
);reg         [1:0]   state_c       ; 
reg         [1:0]   state_n       ;
wire                idle2write    ;
wire                idle2read     ;
wire                read2done     ;
wire                write2done    ;
wire                done2idle     ;
//avalon_r
reg         [15:0]  rd_data_r     ;
reg                 rd_data_vld_r ;
reg                 waitrequest_r ;
//vga_r
reg         [15:0]  vga_data      ;
reg                 vga_data_vld  ;
//cnt_BL  
reg 	    [9:0]	cnt_bl	      ;
wire	    	  	add_cnt_bl    ;
wire	    	  	end_cnt_bl    ;
//wraddr
reg 	    [21:0]	cnt_wraddr	  ;
wire	    	  	add_cnt_wraddr;
wire	    	  	end_cnt_wraddr;
//rdaddr
reg 	    [21:0]	cnt_rdaddr	  ;
wire	    	  	add_cnt_rdaddr;	
wire	    	  	end_cnt_rdaddr;	
//wrfifo
wire		[17:0]	wrfifo_din	  ;
wire		[17:0]	wrfifo_dout	  ;
wire				wrfifo_wrreq  ;
wire				wrfifo_rdreq  ;
wire				wrfifo_rdempty;
wire				wrfifo_rdfull ;
wire		[10:0]	wrfifo_rdusedw;
wire				wrfifo_wrempty;
wire				wrfifo_wrfull ;
wire		[10:0]	wrfifo_wrusedw;
reg                 wr_data_flag  ;
//rdfifo
wire		[15:0]	rdfifo_din	  ;
wire		[15:0]	rdfifo_dout	  ;
wire				rdfifo_wrreq  ;
wire				rdfifo_rdreq  ;
wire				rdfifo_rdempty;
wire				rdfifo_rdfull ;
wire		[10:0]	rdfifo_rdusedw;
wire				rdfifo_wrempty;
wire				rdfifo_wrfull ;
wire		[10:0]	rdfifo_wrusedw;//读写优先级仲裁标志
reg                 rd_flag       ;
reg                 wr_flag       ;
reg                 flag_r        ;
reg                 priority_flag ;
//乒乓操作
reg         [1:0]   wr_bank       ;
reg         [1:0]   rd_bank       ;
reg                 change_bank   ;
reg                 wr_finish     ;
//打拍 同步到写侧
reg         [1:0]   wr_finish_r   ;
/**************************************************************状态机         
**************************************************************/
parameter   IDLE    =   0,READ    =   1,WRITE   =   2,DONE    =   3;//第一段状态机
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginstate_c <= IDLE;endelse beginstate_c <= state_n;end
end//第二段状态机
always @(*)begin
case(state_c)IDLE    :	if(idle2write)state_n = WRITE;else if(idle2read)state_n = READ;else state_n = state_c;READ    :	if(read2done)state_n = DONE;else state_n = state_c;                   WRITE    :	if(write2done)state_n = DONE;else state_n = state_c;                DONE    :	if(done2idle)state_n = IDLE;else state_n = state_c;                           default : state_n = state_c;endcase
end	//状态跳转条件
assign 	  idle2write  = state_c == IDLE  && (~priority_flag && wrfifo_rdusedw > `BURST_LENTH); 
assign 	  idle2read   = state_c == IDLE  && priority_flag && rdfifo_wrusedw <= `RD_UT;                
assign 	  read2done   = state_c == READ  && end_cnt_rdaddr; 
assign 	  write2done  = state_c == WRITE && end_cnt_wraddr; 
assign 	  done2idle   = state_c == DONE  && 1'b1;                /**************************************************************突发长度计数器           
**************************************************************/		
always@(posedge clk or negedge rst_n)	if(!rst_n)								cnt_bl <= 'd0;						else    if(add_cnt_bl) begin				if(end_cnt_bl)						cnt_bl <= 'd0;  				else									cnt_bl <= cnt_bl + 1'b1;		end											
assign add_cnt_bl =  (state_c == READ | state_c == WRITE) && !waitrequest_r;
assign end_cnt_bl = add_cnt_bl && cnt_bl ==  `BURST_LENTH - 1;/**************************************************************读写优先级仲裁            
**************************************************************/
//rd_falg
always@(posedge clk or negedge rst_n)if(!rst_n)rd_flag <= 1'b0;else if(rdfifo_wrusedw <= `RD_LT)rd_flag  <= 1'b1;else if(rdfifo_wrusedw >  `RD_UT)rd_flag <= 1'b0;elserd_flag <= rd_flag;//wr_flag
always@(posedge clk or negedge rst_n)if(!rst_n)wr_flag <= 1'b0;else if(wrfifo_rdusedw > `BURST_LENTH)  wr_flag <= 1'b1;elsewr_flag <= 1'b0;//flag_r 判断上一次为读/写操作?flag_r=1,为读操作;flag_r=0,为写操作
always@(posedge clk or negedge rst_n)if(!rst_n)flag_r <= 1'b0;else if(read2done)      flag_r <= 1'b1;else if(write2done)flag_r <= 1'b0;//priority_flag 优先级标志  0:写优先级高; 1:读优先级高
always@(posedge clk or negedge rst_n)if(!rst_n)priority_flag <= 1'b0;else if(wr_flag && (flag_r || (~flag_r && ~rd_flag)))priority_flag <= 1'b0;else if(rd_flag && (~flag_r || (flag_r && ~wr_flag)))priority_flag <= 1'b1;/**************************************************************地址计数器            
**************************************************************///wraddr		
always@(posedge clk or negedge rst_n)	if(!rst_n)								cnt_wraddr <= 'd0;						else    if(add_cnt_wraddr) begin				if(end_cnt_wraddr)						cnt_wraddr <= 'd0;  				else									cnt_wraddr <= cnt_wraddr + `BURST_LENGTH;		end											
assign add_cnt_wraddr =  state_c == WRITE && !waitrequest_r;
assign end_cnt_wraddr = add_cnt_wraddr && cnt_wraddr == `BURST_MAX - `BURST_LENGTH;//rdaddr
always@(posedge clk or negedge rst_n)	if(!rst_n)								cnt_rdaddr <= 'd0;						else    if(add_cnt_rdaddr) begin				if(end_cnt_rdaddr)						cnt_rdaddr <= 'd0;  				else									cnt_rdaddr <= cnt_rdaddr + `BURST_LENGTH;		end											
assign add_cnt_rdaddr =  state_c == READ  && !waitrequest_r;
assign end_cnt_rdaddr = add_cnt_rdaddr && cnt_rdaddr == `BURST_MAX - `BURST_LENGTH;//wr_bank  rd_bankalways  @(posedge clk or negedge rst_n)beginif(~rst_n)beginwr_bank <= 2'b00;rd_bank <= 2'b11;endelse if(change_bank)beginwr_bank <= ~wr_bank;rd_bank <= ~rd_bank;endend//change bank
always  @(posedge clk or negedge rst_n)beginif(~rst_n)beginwr_bank <= 2'b00;rd_bank <= 2'b11;endelse if(change_bank)beginwr_bank <= ~wr_bank;rd_bank <= ~rd_bank;end
end//wr_finish     一帧数据全部写到SDRAM
always  @(posedge clk or negedge rst_n)beginif(~rst_n)beginwr_finish <= 1'b0;endelse if(~wr_finish & wrfifo_dout[17])begin  //写完  从wrfifo读出eopwr_finish <= 1'b1;endelse if(wr_finish && end_cnt_rdaddr)begin  //读完wr_finish <= 1'b0;end
end//change_bank ;//切换bank 
always  @(posedge clk or negedge rst_n)beginif(~rst_n)beginchange_bank <= 1'b0;endelse beginchange_bank <= wr_finish && end_cnt_rdaddr;end
end/**************************************************************wrfifo 写数据        
**************************************************************/
//控制像素数据帧 写入 或 丢帧 
always  @(posedge clk_in or negedge rst_n)beginif(~rst_n)beginwr_data_flag <= 1'b0;end else if(~wr_data_flag & ~wr_finish_r[1] & din_sop)begin//可以向wrfifo写数据wr_data_flag <= 1'b1;endelse if(wr_data_flag & din_eop)begin//不可以向wrfifo写入数据wr_data_flag <= 1'b0;end
endalways  @(posedge clk_in or negedge rst_n)begin //把wr_finish从wrfifo的读侧同步到写侧if(~rst_n)beginwr_finish_r <= 0;endelse beginwr_finish_r <= {wr_finish_r[0],wr_finish};end
end/**************************************************************sdram输入寄存             
**************************************************************/
//由于主从机时钟相位不同,所以从机发送来的信号需要同步寄存
always@(posedge clk_out or negedge rst_n)if(!rst_n)  beginrd_data_r       <= 16'b0;rd_data_vld_r   <= 1'b0;waitrequest_r   <= 1'b0;    endelse    beginrd_data_r     <= rd_data    ;rd_data_vld_r <= rd_data_vld;waitrequest_r <= waitrequest;end/**************************************************************vga输出寄存            
**************************************************************/
always  @(posedge clk_out or negedge rst_n)beginif(~rst_n)beginvga_data     <= 0;vga_data_vld <= 1'b0;endelse beginvga_data     <= rdfifo_dout;vga_data_vld <= rdfifo_rdreq;end
end/**************************************************************FIFO模块				 
**************************************************************/    
//wrfifowrfifo	wrfifo_inst (.aclr    ( ~rst_n         ),.data  	 ( wrfifo_din     ),.rdclk 	 ( clk            ),.rdreq 	 ( wrfifo_rdreq   ),.wrclk 	 ( clk_in	  	  ),.wrreq 	 ( wrfifo_wrreq   ),.q 	   	 ( wrfifo_dout 	  ),.rdempty ( wrfifo_rdempty ),.rdfull  ( wrfifo_rdfull  ),.rdusedw ( wrfifo_rdusedw ),.wrempty ( wrfifo_wrempty ),.wrfull  ( wrfifo_wrfull  ),.wrusedw ( wrfifo_wrusedw ));
assign	wrfifo_wrreq = din_vld && ~wrfifo_wrfull && ((wr_finish_r[1] && din_sop) || wr_data_flag);
assign	wrfifo_rdreq = ~wrfifo_rdempty && (state_c == WRITE) && !waitrequest_r ;
assign	wrfifo_din	 = {din_eop,din_sop,din};//rdfifo   rdfifo	rdfifo_inst (.aclr    ( ~rst_n         ),.data 	 ( rdfifo_din  	  ),.rdclk 	 ( clk_out		  ),.rdreq 	 ( rdfifo_rdreq   ),.wrclk 	 ( clk 	          ),.wrreq 	 ( rdfifo_wrreq   ),.q 		 ( rdfifo_dout 	  ),.rdempty ( rdfifo_rdempty ),.rdfull  ( rdfifo_rdfull  ),.rdusedw ( rdfifo_rdusedw ),.wrempty ( rdfifo_wrempty ),.wrfull  ( rdfifo_wrfull  ),.wrusedw ( rdfifo_wrusedw ));
assign	rdfifo_wrreq = rd_data_vld_r && !rdfifo_wrfull && !waitrequest_r;
assign	rdfifo_rdreq = !rdfifo_rdempty && req;
assign	rdfifo_din	 = rd_data_r;/**************************************************************输出端口             
**************************************************************/
//avalon
assign	wr_data     = wrfifo_dout[15:0]	 ;
assign  addr        = (state_c == WRITE) ? {wr_bank[1],cnt_wraddr[21:9],wr_bank[0],cnt_wraddr[8:0]} :((state_c == READ) ? {rd_bank[1],cnt_rdaddr[21:9],rd_bank[0],cnt_rdaddr[8:0]} : 0) ;
assign  wr_n        = !(state_c == WRITE);
assign  rd_n        = !(state_c == READ);      
//vga
assign  dout        = vga_data;
assign  dout_vld    = vga_data_vld;endmodule

三、仿真测试

1.摄像头配置模块仿真

2.图像采集模块仿真

3.图像处理模块仿真

3.1 灰度化仿真

3.2 高斯滤波仿真

3.3 二值化仿真

3.4 sobel仿真

4.sdram读写控制模块仿真

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 4. kvm存储虚拟化
  • PHP框架详解-symfony框架
  • 基于考研题库小程序V2.0实现倒计时功能板块和超时判错功能
  • html5——CSS3_文本样式属性
  • Oracle 19c 统一审计表清理
  • 使用YOLO5进行模型训练机器学习【教程篇】
  • 秋招Java后端开发冲刺——Mybatis使用总结
  • 阅读笔记——《Fuzz4All: Universal Fuzzing with Large Language Models》
  • GPS北斗标准时钟同步服务器结构是什么?安徽京准
  • 在自定义总线下注册驱动
  • 最优雅的PHP框架 Laravel
  • docker(一)
  • 《Windows API每日一练》8.5 listbox控件
  • spark 中hint使用总结
  • Python:正则表达式相关整理
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • iOS 系统授权开发
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • opencv python Meanshift 和 Camshift
  • Phpstorm怎样批量删除空行?
  • spring学习第二天
  • ------- 计算机网络基础
  • 近期前端发展计划
  • 嵌入式文件系统
  • 我是如何设计 Upload 上传组件的
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • 原生js练习题---第五课
  • ​Python 3 新特性:类型注解
  • #mysql 8.0 踩坑日记
  • #每日一题合集#牛客JZ23-JZ33
  • (160)时序收敛--->(10)时序收敛十
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (附源码)计算机毕业设计高校学生选课系统
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (详细文档!)javaswing图书管理系统+mysql数据库
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • **python多态
  • *++p:p先自+,然后*p,最终为3 ++*p:先*p,即arr[0]=1,然后再++,最终为2 *p++:值为arr[0],即1,该语句执行完毕后,p指向arr[1]
  • .gitignore文件忽略的内容不生效问题解决
  • .net安装_还在用第三方安装.NET?Win10自带.NET3.5安装
  • .NET开发者必备的11款免费工具
  • .NET中的Event与Delegates,从Publisher到Subscriber的衔接!
  • @media screen 针对不同移动设备
  • @RestController注解的使用
  • [AIR] NativeExtension在IOS下的开发实例 --- IOS项目的创建 (一)
  • [C++提高编程](三):STL初识
  • [CareerCup] 6.1 Find Heavy Bottle 寻找重瓶子
  • [codevs 2822] 爱在心中 【tarjan 算法】
  • [FxCop.设计规则]8. 也许参数类型应该是基类型