基于ZYNQ-7000系列的FPGA学习笔记7——按键控制蜂鸣器(模块化编写)

发布于:2024-12-06 ⋅ 阅读:(60) ⋅ 点赞:(0)

在上期的内容中,我们通过模拟按键控制led,这一期学习的内容是:按键控制蜂鸣器

1. 实验要求

在这里插入图片描述

通过按键控制领航者底板上的蜂鸣器,要求如下:

  • 初始状态蜂鸣器响起
  • 按下一次按键,改变一次按键的状态
  • 需要对输入的按键信号进行消抖,消抖的时间为20ms
  • 按下复位按键,蜂鸣器恢复默认状态

2. 功能分析

首先我们需要看一下蜂鸣器相关的原理图:
在这里插入图片描述
可以看到蜂鸣器通过一个8050的三极管驱动,连接到FPGA芯片的Bank35 ,引脚为L23。需要让蜂鸣器叫的话,给高电平就可以。

关于按键部分的内容,可以参考本系列的前面几篇,这里不做介绍。

3. 模块设计

在这里插入图片描述
由于本次的实验内容中需要我们设计多个模块,所以我设计了上述的功能框图,可以分为下面三个子模块:

  1. 按键消抖模块:作用是对输入的按键信号进行一个20ms的消抖
  2. 按键控制蜂鸣器模块:作用是对消抖之后的数据边沿采集,通过采集到的信号沿控制蜂鸣器
  3. 按键控制蜂鸣器顶层模块:分别例化了按键消抖模块和按键控制蜂鸣器模块,完成整个实验要求。

4. 波形图

4.1 按键消抖模块

在这里插入图片描述
为了防止按键的输入信号在采集的过程中出现亚稳态,我这里进行了打两拍的操作。同时为了实现按键的消抖,我也定义了一个cnt,用来记录电平保持稳定的时间,当这个时间大于20ms时才任务是按键按下了,这样就实现了按键的消抖

4.2 按键控制蜂鸣器模块

在这里插入图片描述
由于按键控制蜂鸣器模块的按键输入是消抖的后的按键信息(已经打过两拍了),所以我这里只打一个拍,用来计算是否产生了下降沿,从而判断按键是否按下

【注】:由于顶层按键控制蜂鸣器只是例化了上述两个模块,因此这里就不绘制波形图了,之间看后面仿真结果就可以。

5.代码编写

5.1 rtl代码

  1. 按键消抖模块:key_filter.v
//模块的端口定义
module key_filter(
        input  sys_clk,
        input  sys_rst_n,
        input  key,
        output reg key_filter
    );
    
reg key_d0;     // 打两拍的变量定义
reg key_d1;

reg change;     //这个是用来确定的是否有电平变化的,为1表示有电平变化,为0表示没有

//定义计数的变量,用来判断电平是否持续了20ms
reg [19:0] cnt;
//CNT_MAX用来确定按键消抖的时间, CNT_MAX =  20ms /  (1s / 50Mhz ) =  1000_000 ,其中20ms是消抖的时间,50Mhz是系统时钟频率
parameter CNT_MAX = 20'd1000_000;   

//对输入的key信号,打两拍操作
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)   begin   //初始状态下,d0和d1都是高电平,和按键未按下时一致
    key_d0 <= 1'b1;
    key_d1 <= 1'b1;
    end
    else begin       //运行状态下,开始打拍
    key_d0 <= key;
    key_d1 <= key_d0;
    end
end

//检测是否有电平变化,如果有的话,把change置1,否则保持0
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
    change <= 1'b0;
    else begin 
        if ( key_d0 != key_d1 )
        change <= 1'b1;
        else 
        change <= 1'b0;
    end
end

//如果change为1,说明电平有变化,重新开始计数,否则cnt减一,减到0停止
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) 
    cnt <= 20'd0;
    else begin
        if(change == 1'b1)
        cnt <= CNT_MAX;
        else begin
            if(cnt >20'd0)
            cnt <= cnt - 20'd1;
            else
            cnt <= 20'd0;
        end
     end
end

//如果cnt顺利的减到了1,说明电平保持了20ms,可以输出
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) 
    key_filter <= 1'b1;
    else begin
        if(cnt == 20'd1)
        key_filter <= key_d1; 
        else
        key_filter <= key_filter;
    end
end

endmodule

大致流程如下:首先对输入的按键信号大量拍,判断是否按键的电平是否变化,如果变化就把cnt置为最大值,如果没有变化,cnt每个系统时钟减1,直到减到1,表示抖动结束,输出当前的按键信息,否则输出上一次的按键信息。

  1. 按键控制蜂鸣器模块:key_beep.v
    module key_beep(
    input sys_clk,
    input sys_rst_n,
    input key_filter, //消抖后按键值
    output reg beep //蜂鸣器
);

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

wire neg_key_filter; //按键有效脉信号

//捕获按键端口的下降沿,得到一个时钟周期的脉冲信号
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

大致流程如下:对消抖之后的按键输入打一个拍,用来判断是否产生下降沿,如果产生下降沿,说明有按键按下,改变一次蜂鸣器的状态。

  1. 顶层按键控制蜂鸣器模块:top_key_beep.v
module top_key_beep(
    input sys_clk , //系统时钟
    input sys_rst_n , //系统复位,低电平有效
    input key , //按键
    output beep //蜂鸣器
);

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

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

//例化按键消抖模块
key_filter #( .CNT_MAX (CNT_MAX) ) u_key_filter(
    .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

5.2 测试代码

`timescale 1ns/1ns  //仿真单位/精度

module tb_top_key_beep();

parameter CLK_PERIOD = 20;      //时钟周期20ms
parameter CNT_MAX = 20'd10;     //消抖时间 200ns


//定义输入和输出的变量
reg     sys_clk;
reg     sys_rst_n;
reg     key;

wire beep;

//这里用来产生按键按下的信息
initial begin
    sys_clk <=1'b0;
    sys_rst_n <=1'b0;
    key <= 1'b0;    
    #190
    sys_rst_n <=1'b1;
    key <= 1'b1;    //抖动开始
    #20
    key <= 1'b0;
    #30
    key <= 1'b1;
    #40
    key <= 1'b0;
    #30
    key <= 1'b1;
    #40
    key <= 1'b0;    //抖动结束
    #300
    key <= 1'b1;    //松开按键,抖动开始
    #40
    key <= 1'b0;
    #30
    key <= 1'b1;    //抖动结束   

end

//产生时钟
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;

//例化测试设计
top_key_beep  #(.CNT_MAX (CNT_MAX) )  u_top_key_beep(
    .sys_clk    (sys_clk),
    .sys_rst_n  (sys_rst_n),
    .key        (key),
    .beep       (beep)
);

endmodule

    

【注】:这里的测试代码只针对顶层文件,底层的模块测试代码,暂时不需要。

6. 代码仿真

如下是仿真的结果:
在这里插入图片描述
可以看到仿真的结果和我们话的波形图是基本一致的,并且小于200ns的抖动都已经被滤除了,很不错。

7. 添加约束文件并分析综合

首先我们添加对应的约束文件

#时序约束
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
#IO 管脚约束
set_property -dict {PACKAGE_PIN M14 IOSTANDARD LVCMOS33} [get_ports beep]
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports key]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

完成之后,分析综合,得到如下结果:

  1. 顶层模块:
    在这里插入图片描述
    可以看到里面分别由两个子模块连接,下面是子模块的内部结构。

  2. 按键消抖模块

在这里插入图片描述

  1. 按键控制蜂鸣器模块
    在这里插入图片描述
    下一步就是生成比特流文件,然后上板调试了,这里结果我就还是不展示了。

以上就是本期的所有内容,创造不易,点个关注再走呗。

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到

热门文章