基于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读写控制模块仿真

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/43303.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GaussDB关键技术原理:高性能(四)

GaussDB关键技术原理&#xff1a;高性能&#xff08;三&#xff09;从查询重写RBO、物理优化CBO、分布式优化器、布式执行框架、轻量全局事务管理GTM-lite等五方面对高性能关键技术进行了解读&#xff0c;本篇将从USTORE存储引擎、计划缓存计划技术、数据分区与分区剪枝、列式存…

Redis 7.x 系列【19】管道

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 往返时间2. 管道技术3. 代码演示4. 其他批处理4.1 原生批处理命令4.2 事务4.3 脚本…

240708_昇思学习打卡-Day20-MindNLP ChatGLM-6B StreamChat

240708_昇思学习打卡-Day20-MindNLP ChatGLM-6B StreamChat 基于MindNLP和ChatGLM-6B实现一个聊天应用&#xff0c;本文进行简单记录。 环境配置 %%capture captured_output # 实验环境已经预装了mindspore2.2.14&#xff0c;如需更换mindspore版本&#xff0c;可更改下面mi…

Java | Leetcode Java题解之第224题基本计算器

题目&#xff1a; 题解&#xff1a; class Solution {public int calculate(String s) {Deque<Integer> ops new LinkedList<Integer>();ops.push(1);int sign 1;int ret 0;int n s.length();int i 0;while (i < n) {if (s.charAt(i) ) {i;} else if (s…

实施OPC UA网关以加速设备与MES系统之间的连接

在现代工业自动化中&#xff0c;信息化和智能化已成为企业提升竞争力的关键因素&#xff0c;为了实现生产过程的自动化和管理的高效化&#xff0c;工业自动化系统&#xff08;如OPC UA&#xff09;与制造执行系统&#xff08;MES&#xff09;的集成变得尤为重要。OPC UA&#x…

Pycharm 出现sdk is not defined for run configuration解决办法

第一步&#xff1a;运行->编辑配置 第二部&#xff1a;重新选择一下脚本路径和Python解释器 第三步&#xff1a;保存。重新运行

WebKit简介及其神秘的工作流程

在信息时代的巨浪中&#xff0c;互联网已经深深地渗透到了我们生活的每一个角落。作为连接我们与这个庞大网络世界的桥梁&#xff0c;网页浏览器无疑成为了我们生活中不可或缺的一部分。而在这些浏览器的背后&#xff0c;往往隐藏着一些强大而神秘的引擎&#xff0c;它们为浏览…

鸿蒙系统:未来智能生态的引领者

在当今这个日新月异的互联网领域&#xff0c;操作系统作为连接硬件与软件的桥梁&#xff0c;其重要性不言而喻。随着华为鸿蒙系统&#xff08;HarmonyOS&#xff09;的崛起&#xff0c;一场关于操作系统未来的讨论再次被推向高潮。 鸿蒙OS&#xff0c;华为的全新力作&#xff…

K8S篇之Ingress详解以及用法说明

一、Ingress简介 Ingress 是 Kubernetes 中用于管理和配置从集群外部访问集群内部服务的资源对象。它通过定义路由规则来控制外部流量的访问方式&#xff0c;支持基于 HTTP 和 HTTPS 的高级路由功能和安全性配置。 Ingress是一种HTTP方式的路由转发机制&#xff0c;为K8S服务配…

AGAST (角点检测)

AGAST检测原理 AGAST(Adaptive and Generic Accelerated Segment Test)算法是Elmar于2010年提出的特征检测算法,改进了FAST(Features from Accelerated Segment Test)特征检测方法,使其具有更快的速度和更好的鲁棒性。AGAST算法提供了比FAST算法更详细的特征标记方式和判断依…

【Python_GUI】tkinter模块、创建空白窗口

tkinter是使用Python进行窗口视觉设计的模块&#xff0c;它是Python的标准Tk GUI工具包的接口&#xff0c;在安装Python时&#xff0c;就自动安装了该模块。 使用tkinter模块开发时&#xff0c;最核心的就是各种组件的使用。生活中玩积木时&#xff0c;通过将不同形状的木板进…

A股本周在3000点以下继续筑底,本周依然继续探底?

夜已深&#xff0c;市场传来了3个浓烈的消息&#xff0c;炸锅了&#xff0c;恐有大事发生&#xff0c;马上告诉所有人&#xff1a; 消息面&#xff1a; 1、中国经济周刊首席评论员钮文新称&#xff1a;不要等中小投资者都彻底希望&#xff0c;销户离场了&#xff0c;才发现该…

【APK】Unity出android包,报错 Gradle build failed.See the Console for details

参考大佬的博客&#xff1a;报错&#xff1a;Gradle build failed.See the Console for details.&#xff08;已解决&#xff09;_starting a gradle daemon, 1 incompatible daemon co-CSDN博客 本地出Android包&#xff0c;Build失败 解决办法&#xff1a; 1.下载一个低版本…

c++语法之缺省参数

缺省参数通俗来说就是一个函数里面有初值的参数。有初值那么就可以不传参。 基础语法 缺省分为全缺省和半缺省 全缺省 我们来看它的基础语法&#xff0c;我们以add函数为例: 语法基础就是在给要规定成缺省参数的变量后面一个值 我们可以看到给add传参就会使用默认的数据。…

uniapp版即时通讯软件 IM社交交友聊天系统 语音视频通话双端APP 聊天交友APP源码 (含搭建教程)

修复音视频&#xff08;官方团队插件&#xff0c;无二次费用&#xff09;&#xff0c;文件发送&#xff0c;公告&#xff0c;签到&#xff0c;发现页&#xff0c;朋友圈删除&#xff0c;轮询客服&#xff0c;马甲等 可内嵌第三方网页连接&#xff0c;后台添加&#xff0c;带完…

从零开始做题:好怪哦

题目 给出一个压缩文件 解题 方法1 01Edit打开&#xff0c;发现是个反着的压缩包&#xff08;末尾倒着的PK头&#xff09; import os# 目标目录路径 # target_directory /home/ai001/alpaca-lora# 切换到目标目录 # os.chdir(target_directory)# 打印当前工作目录以确认…

如何监控和优化 PostgreSQL 中的连接池使用?

文章目录 一、连接池的基本概念二、监控 PostgreSQL 连接池使用的重要性&#xff08;一&#xff09;性能优化&#xff08;二&#xff09;资源管理&#xff08;三&#xff09;故障排查 三、PostgreSQL 连接池监控指标&#xff08;一&#xff09;活跃连接数&#xff08;二&#x…

适合宠物饮水机的光电传感器有哪些

如今&#xff0c;随着越来越多的人选择养宠物&#xff0c;宠物饮水机作为一种便捷的饮水解决方案日益受到欢迎。为了确保宠物随时能够获得足够的水源&#xff0c;宠物饮水机通常配备了先进的光电液位传感器技术。 光电液位传感器在宠物饮水机中起着关键作用&#xff0c;主要用…

C++初阶学习第一弹——入门学习C++

目录 1.什么是C 2.C关键字 3.命名空间 3.1命名空间的定义 3.2命名空间的使用 1、加命名空间名称及作用域限定符 2、使用 using 将命名空间中某个成员引入 3.使用using namespace 命名空间名称 引入 4.C输入&输出 5.缺省参数 5.1 缺省参数概念 5.2缺省参数分类 6. …

23.状态机设计——可乐机设计(2.5元1瓶可乐,有找零功能)

&#xff08;1&#xff09;Visio视图&#xff1a; &#xff08;2&#xff09;Verilog代码&#xff1a; module fsm_cola_plus(clk,reset_n,pi_money_one,pi_money_half,po_cola,po_money);input clk;input reset_n;input pi_money_one;input pi_money_half;output po_cola;out…