FPGA拾忆_(10):按键控制蜂鸣器_边沿检测_按键消抖

1.硬件特征:

轻触式(回弹式)按键

        略

蜂鸣器:

分为蜂鸣器按照结构原理不同可分为压电式蜂鸣器和电磁式蜂鸣器。 压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、 阻抗匹配器及共鸣箱、外壳等组成; 电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。

其他资料:1
 

发声原理:压电式蜂鸣器是利用压电效应原理工作的,当对其施加交变电压时它会产生机械振动发声; 电磁式蜂鸣器是接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场, 振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
压电式蜂鸣器和电磁式蜂鸣器由于发音原理不同, 产生的声音信号也不一样。 压电式结构简单耐用但音调单一,适用于报警器等设备; 而电磁式由于音质好,所以多用于语音、音乐等设备。 本次实验使用的蜂鸣器为电磁式蜂鸣器。
蜂鸣器按照驱动方式不同又可分为有源蜂鸣器和无源蜂鸣器,其主要区别为蜂鸣器内部是否含有震荡源。一般的有源蜂鸣器内部自带了震荡源,只要通电就会发声。而无源蜂鸣器由于不含内部震荡源,需要外接震荡信号才能发声

2.原理图:
3.端口以及模块结构:

其中,key_debounce是将输入的key信号消抖后输出,这个模块的输出信号是key_filter,key_beep则是简单的控制逻辑,检测到有下降沿则判断为一次按键按下,beep信号翻转,最后是顶层模块将两个模块例化到一块,顶层模块中连接的信号key_filter为wire型,这样的结构清晰且富有条例,而我自己写的模块的结构应该如下面这个一样的 

关键模块:

消抖处理模块:电平跳变就进行计数,计数时间超过20ms说明此信号已经平稳,抖动信号已经过滤掉,将key信号直接赋值给key_filter即可。(我写的较为麻烦)

边沿检测模块:key信号是异步信号,所以要有个同步打拍子过程,然后进行边沿检测,

buf_res != buffer2 ,就说明有边沿出现。

4.代码技巧: 

人家的代码和我的代码对比:

module top_key_beep(
    input        sys_clk   ,    //系统时钟
    input        sys_rst_n ,    //系统复位,低电平有效

    input        key       ,    //按键    
    output       beep           //蜂鸣器
);

//parameter define
parameter  CNT_MAX = 20'd100_0000;   //消抖时间20ms

//wire define
wire key_filter ;                    //按键消抖后的值

//*****************************************************
//**                    main code
//*****************************************************

//例化按键消抖模块
key_debounce #(
    .CNT_MAX    (CNT_MAX)  
)u_key_debounce(
    .sys_clk       (sys_clk),
    .sys_rst_n     (sys_rst_n),
    .key           (key),
    .key_filter    (key_filter)
    );

//例化按键控制蜂鸣器模块
key_beep  u_key_beep(
    .sys_clk       (sys_clk),
    .sys_rst_n     (sys_rst_n),
    .key_filter    (key_filter),
    .beep          (beep)
    );

endmodule

/

按键消抖模块


module key_debounce(
    input        sys_clk   ,
    input        sys_rst_n ,

    input        key       ,   //外部输入的按键值
    output  reg  key_filter    //按键消抖后的值
);

//parameter define
parameter  CNT_MAX = 20'd100_0000;    //消抖时间20ms

//reg define
reg [19:0] cnt ;
reg        key_d0;            //将按键信号延迟一个时钟周期
reg        key_d1;            //将按键信号延迟两个时钟周期

//*****************************************************
//**                    main code
//*****************************************************

//对按键端口的数据延迟两个时钟周期
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        key_d0 <= 1'b1;
        key_d1 <= 1'b1;
    end
    else begin
        key_d0 <= key;
        key_d1 <= key_d0;
    end 
end

//按键值消抖
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) 
        cnt <= 20'd0;
    else begin
        if(key_d1 != key_d0)    //检测到按键状态发生变化
            cnt <= CNT_MAX;     //则将计数器置为20'd100_0000,
                                //即延时100_0000 * 20ns(1s/50MHz) = 20ms
        else begin              //如果当前按键值和前一个按键值一样,即按键没有发生变化
            if(cnt > 20'd0)     //则计数器递减到0
                cnt <= cnt - 1'b1;  
            else
                cnt <= 20'd0;
        end
    end
end

//将消抖后的最终的按键值送出去
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        key_filter <= 1'b1;
	//在计数器递减到1时送出按键值
    else if(cnt == 20'd1) 
		key_filter <= key_d1;
    else
		key_filter <= key_filter;
end

endmodule


按键控制逻辑

module key_beep(
    input        sys_clk,
    input        sys_rst_n,

    input        key_filter,   //消抖后按键值
    output  reg  beep          //蜂鸣器
    );

//reg define
reg    key_filter_d0;          //将消抖后的按键值延迟一个时钟周期

//wire define
wire   neg_key_filter;         //按键有效脉信号
 
//*****************************************************
//**                    main code
//*****************************************************

//捕获按键端口的下降沿,得到一个时钟周期的脉冲信号
assign  neg_key_filter = (~key_filter) & key_filter_d0;

//对按键端口的数据延迟一个时钟周期
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) 
        key_filter_d0 <= 1'b1;
    else 
        key_filter_d0 <= key_filter;
end

//每次按键按下时,就翻转蜂鸣器的状态
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        beep <= 1'b1;
    else if(neg_key_filter)  //有效的一次按键被按下
        beep <= ~beep;
    else
        beep <= beep;
end

endmodule

我的代码:

module key_beep(
    input           sys_clk,
    input           sys_rst_n,
    input           key,
    output  reg     beep
);
    wire key_filter;
    reg buffer2, beep_flag;
    //例化按键消抖模块
key_filter   
			key_filter_1(
                        .key(key),
                        .sys_clk(sys_clk),
                        .sys_rst_n(sys_rst_n),          
                        .key_filter(key_filter)
);
   
	
 //边沿检测电路
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(!sys_rst_n)
            buffer2 <= 1'b0;
        else
            buffer2 <= key_filter;   
    end
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            beep_flag <= 1'b0;
        else if((key_filter == 1'b1)&&(buffer2 == 1'b0))
            beep_flag <= 1'b1;  //一个脉冲的按键标志信号
        else
            beep_flag <= 1'b0;
    end

    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(!sys_rst_n)
            beep <= 1'b0;
        else if(beep_flag == 1'b1) //检测到一次按键
            beep <= ~beep;
        else
            beep <= beep; //保持
    end

endmodule



按键消抖

module key_filter (
    input           key,
    input           sys_clk,
    input           sys_rst_n,          
    output   reg    key_filter
);
    parameter CNT_MAX = 20'd1_000_000; //20ms
    reg [19:0] cnt;
    reg buffer1,buf_res;
    reg pose_flag,nege_flag;

    //ĺć­Ľććĺ­?
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            buffer1 <= 1'b0;
        else
            buffer1 <= key;
    end
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            buf_res <= 1'b0;
        else
            buf_res <= buffer1;
    end

    //ä¸čžšć˛żćŁćľçľčˇ?
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)begin
            pose_flag <= 1'b0;
            nege_flag <= 1'b0;
        end
        else if((buf_res == 1'b1)&&(buffer1 == 1'b0))begin
            pose_flag <= 1'b0;
            nege_flag <= 1'b1;
        end
        else if((buf_res == 1'b0)&&(buffer1 == 1'b1))begin
            pose_flag <= 1'b1;
            nege_flag <= 1'b0;
        end
        else
        begin
            pose_flag <= pose_flag;
            nege_flag <= nege_flag;
        end
    end



    //ćŁ?ćľĺ°čžšć˛żĺ°ąĺźĺ§čŽĄć?
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            cnt <= 10'd0;
        //ćŁ?ćľĺ°čžšć˛żďźĺźĺ§ć¸éśčŽĄć?
        else if(((buf_res == 1'b1)&&(buffer1 == 1'b0))||((buf_res == 1'b0)&&(buffer1 == 1'b1)))
            cnt <= 10'd0;
        //else if((pose_flag == 1'b1)||(nege_flag == 1'b1)) //ĺŞćć螚沿觌ĺďźĺ°ąĺźĺ§čŽĄć°ďźčŽĄć°čśčż20msďźćĺłçćŻä¸ćŹĄćé?
        else if(cnt == CNT_MAX - 1'b1)
            cnt <= 10'd0;
        else 
            cnt <= cnt + 1'd1;
    end
    //ĺ¤ć­čŽĄć°ćśé´ćŻĺŚč˝čžžĺ?20ms
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            key_filter <= 1'b0;
        else if((cnt == CNT_MAX - 1'b1)&&(nege_flag == 1'b1)) //莥ć°čžžĺ°1000ďźĺł20ms
            key_filter <= 1'b1; //ćä¸ćŹĄćéŽćä¸äş
        else if((cnt == CNT_MAX - 1'b1)&&(pose_flag == 1'b1))
            key_filter <= 1'b0;//ćéŽĺˇ˛çťćžĺźäş?
        else    
            key_filter <= key_filter;
    end


endmodule
5.仿真与验证:

功能验证正确,但是我引入了较多的中间变量,而且输出的key_filter是与key信号消抖后的信号相反的,不过功能都验证正确了,也说明实现功能的方法可以有很多。

6.总结与易错点:

(1)几个重要且常用的模块:

        按键消抖模块,边沿检测模块,同步打拍子操作(解决亚稳态问题)

(2)加强对于自锁与非自锁概念的理解:

        自锁:一直输出有效电平。

        非自锁:仅输出一个周期的有效电平,需要自己对有效电平进行采集和保持。

(3)这次写testbench代码时遇到了一个从没遇到的问题:

        参数重定义,我的CNT_MAX是在key_filter模块定义的,然后key_beep例化了此模块,然后我在这个key_beep模块加入parameter语句or采用参数传递的模式(#(.CNT_MAX(CNT_MAX)))都不行,编译不报错,但是仿真的时候,也就是例化key_beep模块的时候,一直给我提示了CNT_MAX 不能被overwrite这个错误,但是正点原子的代码也参数重定义了,他的没有错误,我暂时还不知道问题出在哪,待定,等那天知道答案了来补充吧。贴一个将参数传递的文字:

verilog中参数传递与参数定义

待补充.....

(4)$stop(n)  和  $finish(n)的区别:可以参考一下

Verilog中$finish、$stop的使用与区别

over

  • 23
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值