本节的实验任务是使用开发板上的KEY0按键来控制蜂鸣器发声。初始状态为蜂鸣器鸣叫,按下按键后蜂鸣器停止鸣叫,再次按下开关,蜂鸣器重新鸣叫。
一、项目总体思路的构建
首先我们要知道蜂鸣器是如何工作的?就拿我这个板子的蜂鸣器来说,BEEP处接入IO口,图中有个NPN型三极管, 基极Q1为低电平时截止,也就是低电平的发射极无法导通到集电极使蜂鸣器发声;当基极Q1为高电平时导通,此时发射极可以导通到GND形成回路,使蜂鸣器发声。
通常我们所使用的开关为机械弹性开关,当我们按下或松开按键时,由于弹片的物理特性,不能立即闭合或断开,往往会在断开或闭合的短时间内产生机械抖动,可能会产生短暂的电平波动,即按键抖动。
我们使用机械按键进行触发蜂鸣器工作,如果不进行按键消抖处理,可能会导致系统误触发或多次触发事件。因此,蜂鸣器的鸣叫控制需要考虑按键消抖算法,以确保只有在按键状态稳定时才产生鸣叫信号,避免误触发。这时候你可能会想之前的实验中我们也使用了机械按键来控制LED,为什么就不用按键消抖呢?是因为LED 的状态变化对于人眼来说相对较慢,不会像按键一样频繁地发生状态变化。
按键消抖可分为硬件消抖和软件消抖。硬件消抖主要使用 RS触发器或电容等方法实现消抖,一般在按键较少时使用。软件消抖的原理主要为按键按下或松开后延时 5ms—20ms采样,也可以在检测到按键状态稳定后采样,即避开抖动区域后再采样。
了解完这些必要的知识后,我们构建一个思维导图来确定我们的实验流程:
我们根据上述的思路绘制图像来模拟按键控制蜂鸣器功能的实现,架构的设计图如下:
波形图如下:
二、编写代码
2.1 按键消抖模块
进入rtl文件夹,在此文件夹内建立key_debounce.v文件开始编写按键消抖模块程序。
为了实现按键消抖,我们知道抖动的时长为20ms左右,因此我们取时钟cnt最大值为1000000(1000000=20ms/20ns),我们通过对key进行打两拍,当key_d0和key_d1电平不同时,时钟cnt开始计时,且取最大值,当电平相同时,递减1直至为0。
♾️ verilog 代码:module key_debounce(
input sys_clk,
input sys_rst_n,
input key,
output reg key_filter //滤除消抖后的KEY
);
parameter CNT_MAX = 20'd1000000;
reg key_d0;
reg key_d1;
reg [19:0] cnt;
//对KEY打两拍
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
//如果按键有变化,重新计时20ms
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 20'd0;
else if(key_d0 != key_d1)
cnt <= CNT_MAX;
else if(cnt > 20'd0)
cnt <= cnt - 20'd1;
else
cnt <= 20'd0;
end
//滤除抖动后的KEY赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
key_filter <= 1'b1;
else if(cnt == 20'd1)
key_filter <= key_d1;
else
key_filter <= key_filter;
end
endmodule
2.2 按键控制蜂鸣器模块
进入rtl文件夹,在此文件夹内建立key_beep.v文件开始编写按键控制蜂鸣器模块程序。
我们对滤除消抖后的key打一拍与取反后的滤除消抖后的key相与检测出key_filter的下降沿,以此作为控制蜂鸣器的标志,当识别到下降沿时,蜂鸣器电平取反,即停止鸣叫。
♾️ verilog 代码:module key_beep(
input sys_clk,
input sys_rst_n,
input key_filter, //滤除消抖后的KEY
output reg beep
);
parameter CNT_MAX = 20'd1000000;
reg key_filter_d0;
wire neg_key_filter;
assign neg_key_filter = key_filter_d0 & (~key_filter);
//对key_filter打一拍
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
2.3 例化整合模块
进入rtl文件夹,在此文件夹内建立top_key_beep.v文件开始编写例化整合模块程序。例化模块类似tb代码,将前面两个模块进行整合形成一个大模块。
♾️ verilog 代码:module top_key_beep(
input sys_clk,
input sys_rst_n,
input key,
output beep
);
parameter CNT_MAX = 20'd1000000;
wire key_filter;
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
使用vivado软件进行系统分析的得到例化后的系统框架如下:
由此可以看出我们的RTL代码逻辑没有错误,接下来进行波形图仿真验证RTL代码是否正确。
三、验证仿真
在sim文件夹建立tb_top_key_beep.v文件,用来编辑仿真文件。这里注意自定的CNT_MAX例化代码,完整的tb代码如下:
♾️ verilog 代码:`timescale 1ns/1ns //仿真单位/仿真精度
module tb_top_key_beep();
parameter CLK_PERIOD = 20;
parameter CNT_MAX = 20'd10;
reg sys_clk;
reg sys_rst_n;
reg key;
wire beep;
initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
key <= 1'b1;
#200
sys_rst_n <= 1'b1;
#100
key <= 1'b0; //按键被按下
#40
key <= 1'b1; //按键抖动
#30
key <= 1'b0; //按键抖动结束
#1000
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
仿真图像如下,由仿真图像我们可以看出与我们想要实现的目的相符合,起初蜂鸣器是鸣叫的,接受到滤除按键消抖的key后产生低电平停止鸣叫。
四、上板验证
查询开发板资料,设置好IO口,并烧录,最终结果如下: