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的使用与区别文章来源:https://www.toymoban.com/news/detail-846265.html
over文章来源地址https://www.toymoban.com/news/detail-846265.html
到了这里,关于FPGA拾忆_(10):按键控制蜂鸣器_边沿检测_按键消抖的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!