fpga实操训练(按键消抖)

时间:2022-12-17 10:54:39

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        之前我们谈到按键输入,其中涉及的功能就是,当按键按下的时候,led灯亮起来。等到按键弹起来的时候,led灯灭掉。这个功能实现起来是比较简单的。只需要把key和led之间做绑定就可以了。

        今天,我们实现一个复杂点的功能。这就是,当第一次按键按下、弹出来的时候,led灯亮起;第二次按键按下、弹出来的时候,led灯灭掉。这个时候应该怎么做呢?其实应该做到这两点,第一,做到按键消抖;第二,实现边沿检测。

1、按键消抖

        要做好按键消抖,那么就要设计一个简单的状态机。一个是idle状态,一个是消抖过程中的延时状态。在做好状态切换前,先锁存一下输入的数据,这个之前在按键输入那一章讲到过,

// delay to get signal from in		
always@(posedge clk or negedge rst)
	if(!rst)
		tmp0 <= 1'b0;
	else
		tmp0 <= in;


always @(posedge clk or negedge rst)
	if(!rst)
		tmp1 <= 1'b0;
	else
		tmp1 <= tmp0;

        有了tmp0和tmp1这两个信号,就可以判断信号什么时候发生了改变,

wire diff;
assign diff = tmp0 ^ tmp1;

        接着,当diff产生的时候,就可以产生一个状态机和计数器,其中状态机的代码如下,

// state machine
always @(posedge clk or negedge rst)
	if(!rst)
		state <= 2'b00;
	else
		state <= next_state;

always@(*)
	if(!rst)
		next_state <= 2'b00;
	else begin
		case (state)
			2'b00:
				if(diff)
					next_state <= 2'b01;
				else
					next_state <= 2'b00;
			
			2'b01:
				if(count == 32'd199_9999)
					next_state <= 2'b00;
				else
					next_state <= 2'b01;
				
			default:
				next_state <= 2'b00;
		endcase
	end

        这是一个经典的三段式状态机写法。没有diff发生的时候,next_state是2'b00。如果diff发生,那么next_state切换成2’b01。等到count变成32’d199_9999的时候,next_state重新变成2'b00。在状态机中谈到了count,接着可以看看count是怎么写的,

always@(posedge clk or negedge rst)
	if(!rst)
		count <= 32'b0;
	else if(state == 2'b01) begin
		if(diff)
			count <= 32'b0;
		else if(count != 32'd199_9999)
			count <= count + 1;
		else
			count <= 32'b0;
	end 

        从上面的代码可以看出,rst复位的时候,count为0。等到state切换为2'b01的时候,count准备递增。如果在这过程中发生了二次抖动,则count重新计数,否则在计数到32'd199_9999之前一直在递增。等递增到32'd199_9999的时候,恢复为0。在state处于其他状态的时候,count都是设置为0。

2、边沿触发检测

        要做到边沿触发检测,一般需要两级寄存器进行检测。等到发现两个寄存器的数值不同的时候,这个时候其实就发生了边沿触发,

// delay one clock for out2
always@(posedge clk or negedge rst)
	if(!rst)
		out1 <= 1'b0;
	else if(count == 32'd199_9999)
		out1 <= tmp1;
		
always@(posedge clk or negedge rst)
	if(!rst)
		out2 <= 1'b0;
	else 
		out2 <= out1;		

// output signal here		
always@(posedge clk or negedge rst)
	if(!rst)
		led <= 1'b0;
	else if(out2 && ~out1)
		led <= ~led;

        这里当count为32'd199_9999的时候,把tmp1的信号传递给out1,下一个时钟,再从out1传递给out2。在这过程当中,如果某一个时刻发现out2和out1不相等的时候,其实边沿触发就发生了。out2&~out1代表的是下降沿,~out2&out1代表的是上升沿。

3、开始实验

        等到代码都编写好之后,最重要的就是把pin脚配置好,烧入到fpga当中。

fpga实操训练(按键消抖)

注:

        最后给出完整的代码,有需要的同学可以好好看下。

//
// led_test
// author: feixiaoxing
// 2022.12.13
//

module led_test(clk, rst, in, led);

input clk;
input rst;
input in;
output led;

wire clk;
wire rst;
wire in;

reg led;
reg out1;
reg out2;

reg[31:0] count;
reg tmp0;
reg tmp1;

reg[1:0] state;
reg[1:0] next_state;

wire diff;
assign diff = tmp0 ^ tmp1;

always @(posedge clk or negedge rst)
	if(!rst)
		state <= 2'b00;
	else
		state <= next_state;

always@(*)
	if(!rst)
		next_state <= 2'b00;
	else begin
		case (state)
			2'b00:
				if(diff)
					next_state <= 2'b01;
				else
					next_state <= 2'b00;
			
			2'b01:
				if(count == 32'd499_9999)
					next_state <= 2'b00;
				else
					next_state <= 2'b01;
				
			default:
				next_state <= 2'b00;
		endcase
	end
		
always@(posedge clk or negedge rst)
	if(!rst)
		count <= 32'b0;
	else if(state == 2'b01) begin
		if(diff)
			count <= 32'b0;
		else if(count != 32'd499_9999)
			count <= count + 1;
		else
			count <= 32'b0;
	end else
		count <= 32'b0;

// delay to get signal from in		
always@(posedge clk or negedge rst)
	if(!rst)
		tmp0 <= 1'b0;
	else
		tmp0 <= in;


always @(posedge clk or negedge rst)
	if(!rst)
		tmp1 <= 1'b0;
	else
		tmp1 <= tmp0;

// delay one clock for out2
always@(posedge clk or negedge rst)
	if(!rst)
		out1 <= 1'b0;
	else if(count == 32'd499_9999)
		out1 <= tmp1;
		
always@(posedge clk or negedge rst)
	if(!rst)
		out2 <= 1'b0;
	else 
		out2 <= out1;		

// output signal here		
always@(posedge clk or negedge rst)
	if(!rst)
		led <= 1'b0;
	else if(out2 && ~out1)
		led <= ~led;

endmodule