FPGA实战篇——【3】按键控制蜂鸣器

FPGA实战——按键控制蜂鸣器

实验任务:

复位后蜂鸣器发声,按下按键后停止发声,再次按下继续发声。

蜂鸣器

有源无源的判断:
1.将蜂鸣器引脚朝上,可以看出有绿色电路板的一种是无源蜂鸣器,没有电路板而用黑胶封闭的一种是有源蜂鸣器。
2.万用表电阻档Rxl档: 用黑表笔接蜂鸣器 "+"引脚,红表笔在另一引脚上来回碰触,如果触发出咔、咔声的且电阻只有8Ω(或16Ω)的是无源蜂鸣器;如果能发出持续声音的,且电阻在几百欧以上的,是有源蜂鸣器。
本次实验使用有源蜂鸣器

硬件设计

在这里插入图片描述
当BUZZER引脚输出低电平时,PNP导通,蜂鸣器发声工作。

程序设计

rtl文件

这次我们用到了按键消抖,需要单独写一个模块比较方便,所以这节开始,我们就开始用多个.V文件,然后写顶层模块调用例化的底层模块这种写法了。回顾一下例化的知识(伪代码):

比如我已经定义了
<底层led模块>
module pled(
	input   sys_clk,
	output  led_value
);
那么我在顶层模块例化他的时候:
<底层模块>
首先定义顶层模块:
module top_flow_led(
	input sys_clk,
	output led		
);
例化led模块时:
pled pled_u(
	.sysclk(sys_clk),
	.led_value(led)
);
 在例化模块中,.sysclk后的()表示的是输入,也就是说,
 因为在原来的pled模块中,sysclk是input,所以()中的变量为一个输入,
 现在,只需要将开发板上的晶振连接到顶层文件的input sysclk,
 然后这个顶层文件的sysclk再输入到led 的sysclk就实现了对底层模块的时钟的赋值。
 ***********************
 而底层模块pled中 led_value是个output,
 所以这里.led_value(led)中led是作为led_value的输出接收信号,
 接收底层模块的输出,然后通过顶层模块,连接到开发板的LED灯上。
 ***********************
 这就是通过例化连接到顶层模块的写法。具体后面的例子变量多,看起来可能更容易理解。
按键消抖

在这里插入图片描述

思想:当检测到按键值发生变化,也就是被按下时,将按键值保存,保存之后,每隔一段时间取按键值与原来值进行比较,如果持续20ms不变,就认为按键确实按下了。
(实际上,只需要检测到按键按下之后,启动计数器(不管怎么抖,都以变化后为计时起点重新计时),只要计数器在计数到20ms之前 ,按键值没有发生变化,就认为按键被按下)
那么我们写一个按键消抖模块,输入key(只用一个按键)输出一个key_flag表示按键被按下的标志,再输出一个key_value来存储key的值。
(一些疑问在最后的 下载与debug解决部分解决)

**************按键消抖模块**************
module key_debounce(           //消抖模块
	input       sys_clk,
	input       sys_rst_n,
		  
	input       key,
	output reg  key_value,
	output reg  key_flag

);

reg 		key_reg;           //存储key的值,相当于temp的作用
reg [19:0]  delay_cnt;         //计数器,计数20ms内key未变即按键被按下  20ms / 20ns = 100_0000,20位

//按键延时计数器
always @ (posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		key_reg   <= 1'b1;       		     //1为未按下
		delay_cnt <= 20'd0;    			     //计数器值清零
	end
	else begin
		key_reg <= key;  				     //把key的值存储到寄存器中去
		if(key_reg != key)			         //说明按键值有变化!!!!!(不管怎么抖,都以变化后为计时起点重新计时)	
			delay_cnt <= 20'd100_0000;       //一旦检测到key值变化,就把计数器设到倒计时最大值。
		else if(key_reg == key)begin	     //说明此时按键还是按下的状态
				if(delay_cnt > 20'd0)
					delay_cnt <= delay_cnt - 1'b1;
				else                         //else 它要等于自己!!!!!
					delay_cnt <= delay_cnt;
		end
		
	end
end

//按键抖动判断
always @ (posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		key_value  <= 1'b1;
        key_flag   <= 1'b0;
	end
	else if(delay_cnt == 20'd1) begin         //从100_0000到了1,说明持续了20ms
		key_flag   <= 1'b1;
		key_value  <= key;                    //寄存此时的按键值
	end
	else begin                                //计数器减到0
		key_flag   <= 1'b0;					  //这是个flag 标志位,不需要一直为1,只需要一个周期即可。
		key_value  <= key_value;
	end
end
endmodule

这里有一点需要讲解一下:
在这里插入图片描述
我们在key_reg <= key;之后,马上就判断key_reg和key是否相等,而且FPGA是并行的,这又是非阻塞赋值,那这不是肯定相等吗,其实不然。
always语句块是在posedge sys_clk的时候执行,其他时候不执行,而key_reg <= key; 这句虽然执行了,但是并不是马上把key的值给key_reg,而是需要等下一个clk上升沿才真正赋值,此时key_reg是上一clk的值,key就是实时的按键值,此时可以进行比较。

**************蜂鸣器模块**************
module beep_ctrl(
	input       sys_clk,
	input       sys_rst_n,
		  
	input       key_value,
	input       key_flag,
	output reg  beep
);

always @ (posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		beep <= 1'b0;
	else if(key_flag && (~key_value))
		beep <= ~beep;
end

endmodule
**************顶层模块**************
module top_key_beep(
	input       sys_clk,
	input       sys_rst_n,
		  
	input       key,
	output      beep

);

//只需要作为导线把两个模块连接到一起即可,不需要是reg型
wire key_value;
wire key_flag;     

key_debounce key_debounce_u(
	.sys_clk      (sys_clk)  ,
	.sys_rst_n    (sys_rst_n), 
				  
    .key          (key)      ,
    .key_value    (key_value),    
    .key_flag     (key_flag)    
	);
	
beep_ctrl beep_ctrl_u(
	.sys_clk     (sys_clk)   ,
    .sys_rst_n   (sys_rst_n) ,
				 
    .key_value   (key_value) ,
    .key_flag    (key_flag)  ,
    .beep        (beep)       
	
	); 
	
endmodule

在这里插入图片描述
发现顶层模块并不是top_key_beep,所以我们设置一下,在这里插入图片描述
如果设置完之后没有变化,说明我们的代码有错误,一定要好好检查。比如endmodule会忘记加。(如果代码添加进ise之后发现全编译的绿色按钮灰色,下方工具栏很多没有显示,也可能使endmodule未加)
在这里插入图片描述
这样就OK了。

ucf文件

NET sys_clk            TNM_NET = sys_clk_pin;
TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 20ns HIGH 50%;

NET sys_clk     LOC = T8  | IOSTANDARD = LVCMOS33;
NET sys_rst_n   LOC = L3  | IOSTANDARD = LVCMOS33;

##################   KEY    ############################
NET key   	    LOC = C3  | IOSTANDARD = LVCMOS33;

##################   BEEP   ############################
NET beep        LOC = J11 | IOSTANDARD = LVCMOS33;

编译

编译报错:
Line 29: Target of concurrent assignment or output port connection should be a net type.
原因是:在这里插入图片描述
例化时输出必须为wire类型的!!!!!

RTL图

上次我们用了第一种,是需要我们手动添加的,这次我们用第二个,就不需要手动添加了。
在这里插入图片描述
首先是这样的,但是我们还想看内部的图,那么我们可以双击这个rtl图,就可以看到内部
在这里插入图片描述
在这里插入图片描述

补充——例化模块 的软件操作:

首先两个底层模块加入进ISE,然后创建一个顶层模块。接下来,对于底层模块的例化,我们可以在这里插入图片描述
在这里插入图片描述
将这段代码复制进顶层文件即可。

下载及debug

生成bit流文件,就可以下载进开发板了。
发现不能按一下就一直响,响了按一下不能停,问题出在下图那
在这里插入图片描述
我们先分析下逻辑,如果计数器从1百万减到1了,就可以把flag变成1,此时让key_value等于0,就是按下了。但是我们忽略了一点,就是按键松开的时候,也需要消抖!!!!那这时候,是不是消抖完还让他等于0呢,不是应该是等于稳定后的key值也就是把这里改成:key_value <=key;在这里插入图片描述
如果按我们刚才那种,那没错,按下去是正常的,但是松开的时候,因为我们flag =1 ,但是按键值key_vlaue却还是0,此时,在下图的逻辑中相当于又按下了一次!!!!在这里插入图片描述
另外,这个bug 也解决了了另外一个问题,就是为什么key_flag只持续了一个clk周期,不能一直等于1,这样对于后面的按键松开消抖不利,实验证明,这样按键会及其不灵敏!!

仿真

tb很简单,思想就是模拟按键抖动即可。

`timescale 1ns / 1ns

module tb_top_key_beep;

	// Inputs
	reg sys_clk;
	reg sys_rst_n;
	reg key;
	
	// Outputs
	wire beep;

	// Instantiate the Unit Under Test (UUT)
	top_key_beep u_top_key_beep (
		.sys_clk(sys_clk), 
		.sys_rst_n(sys_rst_n), 
		.key(key), 
		.beep(beep)
	);

	initial begin
		// Initialize Inputs
		sys_clk   <= 1'b0;
		sys_rst_n <= 1'b0;
		key       <= 1'b1;
		
		// Wait 100 ns for global reset to finish
		#100;
        sys_rst_n <= 1'b1;
		// Add stimulus here
		#150         key  <= 1'b0;  	// 在第250ns按下按键
		#1_000_000   key  <= 1'b1;      //模拟按键抖动1ms
		#1_000_000   key  <= 1'b0;
		#1_000_000   key  <= 1'b1;
		#1_000_000   key  <= 1'b0;
		#21_000_000  key  <= 1'b1;      //20ms之后松开按键
		#1_000_000   key  <= 1'b0;
		#1_000_000   key  <= 1'b1;
		#1_000_000   key  <= 1'b0;
	    #1_000_000   key  <= 1'b1;
		#17_000_000  key  <= 1'b0;      //开始按下
		#1_000_000   key  <= 1'b1;
		#1_000_000   key  <= 1'b0;
		#1_000_000   key  <= 1'b1;
		#1_000_000   key  <= 1'b0;
		#17_000_000  key  <= 1'b1;      //松开
		#1_000_000   key  <= 1'b0;
		#1_000_000   key  <= 1'b1;
		#1_000_000   key  <= 1'b0;
		#1_000_000   key  <= 1'b1;
		#20_000_000  key  <= 1'b0;      //松开
	end
    //生成时钟  
	always #10 sys_clk = ~sys_clk;
endmodule

模拟了100ms之后的结果,我设置的是按键按下的延迟先20ms,后两个17ms,再一个20ms,可以看到符合我们的预期,之后前后两个才改变了beep的状态。
在这里插入图片描述

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值