SWJTU 数电实验报告——按键防抖动

实验内容:

一、 实验目的

1. 学习有限状态机的设计。 2. 学习信号边沿抖动的消除方法。

二、 基本实验内容

由于金属弹性形变的原因,按键/开关在状态切换 过程中总是会有或多或少的抖动情况,有时这种抖动 会导致电路误动作,甚至无法正常工作。比如在设置 参数时,按一下“加 1”可能会加 4~5 个数甚至更多; 生活中常见的情况是鼠标单击变成了双击等。因此, 在很多时候,输入电路的信号需要经过防抖动处理之 后才会送到后级电路。

按键抖动的电压波形如图 1 如示,tjitter是抖动时间,通常在 1ms~30ms 之间。

按键抖动消除有多种方法: 1. 积分法(模拟电路); 2. 施密特触发器(模拟电路); 3. 延迟再次判断法; 4. 持续稳定判断法。

实验要求:

用 Verilog HDL 设计一个按键防抖动电路,要求用有限状态机实现。防抖动电路的输入 接实验箱的按键/开关(SW0),输出接实验五 1 位计数译码显示电路的时钟输入,实现每按 一次按键(或拨一次开关)计数器加 1,多次测试不出现抖动乱加现象,电路对按键的响应 无明显滞后。

三、 提高性实验

自拟实验方法,测试实验箱的按键抖动时间,精确到 0.1ms。

四、 预习要求

1. 简要写出电路设计思路,列出电路所需状态,画出状态转移图。 2. 分析、计算电路需要多高的时钟频率。 3. 自行查阅 Verilog HDL 编写有限状态机资料,完成实验程序代码编写。 4. 用 ModelSim 对实验电路进行功能仿真,并将仿真结果截图、打印。 5. 列出引脚锁定分配表(信号名->主板器件名->引脚号)。

五、 实验报告要求

1. 列出程序代码(有详细注释)。 2. 附 Quartus 生成的状态图 3. 附仿真测试波形。 4. 列出实验过程出现的问题及解决措施。

设计思路:

本次实验要求实现了一个用于防抖动的按键状态机,可以处理由于按键在状态切换时产生的抖动情况,并输出稳定的按键状态信号和状态机状态。以下是代码的主要设计思路和功能:

1.状态定义:定义了四个状态:`key0`、`key1`、`key2`、`key3`,分别代表按键的不同状态。这些状态用于表示按键的松开和按下过程中的不同状态情况。

2. 计数器和状态机更新:使用 `always` 块结合时钟信号和复位信号,在时钟的上升沿和复位信号变化时,更新状态机的状态和计数器的值。根据状态的变化和计数器的值,控制状态机状态的转换和计数器的计数过程。

3. 按键状态机逻辑: 在 `always` 块中通过 `case` 语句实现按键状态机的逻辑。根据当前状态和输入信号(按键是否按下),切换状态以及控制按键状态的输出。通过状态转换和计数器的变化,实现对按键状态的稳定检测和切换,避免了按键抖动带来的误触发问题。

4. 输出:输出了防抖后的按键状态 `key_state` 和进位信号 `CO`。`key_state` 表示稳定的按键状态,`CO` 则用于其他逻辑或模块的触发或控制。

状态机在按键输入的过程中会经历松开状态、松开毛刺状态、按下毛刺状态和按下状态,并根据输入信号和计数器的状态来稳定地输出按键状态,并在稳定状态下输出进位信号。整个设计的核心思想是通过状态机的状态切换和计数器的计数,实现对按键状态的稳定检测和输出。

代码:

顶层文件:
module top(clk,reset_n,key,key_state,Q,codeout,CO,state,cnt);
	input clk,reset_n,key;// 输入时钟信号、复位信号和按键信号
	output [3:0] Q	; // 计数器
	output [6:0] codeout; // 七段数码管
	output CO; // 进位信号
	output key_state;  // 防抖状态
	output [3:0]state; // 临时状态输出
	output [20:0] cnt; // 计数器状态输出
	
	son1 fun1(clk,reset_n,key,key_state,state,cnt);
	son2 fun2(key_state,reset_n,Q,CO);
	son3 fun3(Q,codeout);
endmodule 
子文件:
状态机代码:
module son1(clk,reset_n,key,key_state,state,cnt);
	input clk,reset_n,key;// 输入时钟信号、复位信号和按键信号
	output reg key_state;  // 防抖状态

    reg cnt_en;                      // 计数使能信号
    output reg [20:0] cnt;                  // 计数器,用于计算时间
    output reg [3:0] state;                 // 状态寄存器,用于记录按键状态机的状态

    parameter key0 = 4'b0001; //松开状态       // 按键状态机的状态参数
    parameter key1 = 4'b0010; //松开毛刺状态
    parameter key2 = 4'b0100;	//按下毛刺状态
    parameter key3 = 4'b1000; //按下状态
    
    // 计数器和状态机的更新
    always @ (posedge clk, negedge reset_n) begin
        if (!reset_n)
            cnt <= 0;               // 复位时将计数器清零
        else if (cnt_en)
            cnt <= cnt + 1'b1;      // 根据计数使能信号更新计数器值
        else
            cnt <= 0;               // 当计数使能信号为0时,将计数器清零
    end

    // 按键状态机逻辑
    always @ (posedge clk, negedge reset_n) begin
        if (!reset_n) begin
            state <= key0;          // 复位时,将状态设置为初始状态key0
            cnt_en <= 0;            // 复位时,将计数使能信号清零
            key_state <= 0;         // 复位时,将按键状态清零
        end
        else begin
            case(state)
                key0: begin 
                    key_state <= 1'b0;  // 设置按键状态为低电平
                    if (key == 1 && cnt_en == 0) begin
                        state <= key1;   // 如果按键被按下且计数使能信号为0,则切换到状态key1
                        cnt_en <= 1'b1;  // 启动计数使能信号
                    end
                    else state <= key0;   // 否则保持当前状态
                end
                key1: begin
                    key_state <= 1'b0;  // 设置按键状态为低电平
                    if (cnt == 21'd100000 && key == 1) begin
                        state <= key2;   // 如果计数器达到阈值且按键仍被按下,则切换到状态key2
                        cnt_en <= 1'b0;  // 关闭计数使能信号
                    end
                    else if (cnt == 21'd100000 && key == 0) begin
									state <= key0;
									cnt_en <= 1'b0;
								end
							else state <= key1;   // 否则保持当前状态
                end
                key2: begin
                    key_state <= 1'b1;  // 设置按键状态为高电平
                    if (key == 0 && cnt_en == 0) begin
                        state <= key3;   // 如果按键被释放且计数使能信号为0,则切换到状态key3
                        cnt_en <= 1'b1;  // 启动计数使能信号
                    end
                    else state <= key2;   // 否则保持当前状态
                end
                key3: begin
                    key_state <= 1'b1; // 按键状态返回初始状态key0
                    if (cnt == 21'd100000 && key == 0) begin
                        state <= key0;      // 如果计数器达到阈值且按键已释放,则切换到空闲状态
                        cnt_en <= 1'b0;  // 关闭计数使能信号
                    end
                    else if (cnt == 21'd100000 && key == 1) begin
								state <= key2;
								cnt_en = 1'b0;
							end
							else state <= key3;   // 否则保持当前状态
                end
                default: state <= key0; // 默认情况下将状态设置为初始状态key0
            endcase
        end
    end
endmodule 
计数器代码: 
module son2(clk, clr, Q, CO);
    input clk, clr;           // 输入时钟信号和清零信号
    output reg [3:0] Q;       // 输出4位计数值
    output CO;                // 输出进位信号

    always @ (posedge clk, negedge clr) begin
        if (!clr)               // 当清零信号为低电平时
            Q <= 4'd0;          // 将计数器值清零
        else begin
            if (Q == 4'd9)      // 如果计数器值为9
                Q <= 4'd0;      // 将计数器值重置为0
            else
                Q <= Q + 1'd1;  // 否则计数器值加1
        end
    end
    assign CO = (clk & Q == 4'd9); // 判断是否达到9,输出进位信号
endmodule
控制LED灯数值显示代码: 
module son3(indec,codeout);
	input[3: 0] indec; // 说明输入信号的数组宽度为4
	output[6: 0] codeout; // 说明输出信号的数组宽度为7
	reg[6: 0] codeout; // 定义为寄存器
	always@(indec)
	begin
		case(indec) // indec作为判别常量
		// 根据输入信号indec,输出相应的codeout
		4'd0: codeout = 7'b1111110;
		4'd1: codeout = 7'b0110000;
		4'd2: codeout = 7'b1101101;
		4'd3: codeout = 7'b1111001;
		4'd4: codeout = 7'b0110011;
		4'd5: codeout = 7'b1011011;
		4'd6: codeout = 7'b1011111;
		4'd7: codeout = 7'b1110000;
		4'd8: codeout = 7'b1111111;
		4'd9: codeout = 7'b1111011; 
		default: codeout = 7'b0000001; // 将大于9的数显示为-负号
		endcase
	end
endmodule
modelsim代码:
`timescale 1ns / 1ns
module test_7();
	reg clk;
	reg reset_n;
	reg key;
	wire key_state;
	wire [3:0] Q;
	wire [6:0] codeout;
	wire CO;
	wire [3:0] state;
	wire [20:0] cnt;
	top fun(
		.clk(clk),
		.reset_n(reset_n),
		.key(key),
		.key_state(key_state),
		.Q(Q),
		.codeout(codeout),
		.CO(CO),
		.state(state),
		.cnt(cnt)
	);
	
	initial begin
		clk = 1'b0;
		reset_n = 1'b0;
		key = 1'b1;
		
		#200 reset_n = 1'b1; // 在 200 时间单位后,将复位信号置为高电平
	end
	
	always begin  // 无限循环块
		// 1ms变化一次 一共变化10ms
		key = 1'b1;
		#100000 key = 1'b0;
		#100000 key = 1'b1;
		#100000 key = 1'b0;
		#100000 key = 1'b1;
		#100000 key = 1'b0;
		#100000 key = 1'b1;
		#100000 key = 1'b0;
		#100000 key = 1'b1;
		#100000 key = 1'b0;
		// 保持按键信号为高电平,然后再置为低电平
		#(50*100000) key = 1'b1;
		// 继续10ms变化
		#100000 key = 1'b0;
		#100000 key = 1'b1;
		#100000 key = 1'b0;
		#100000 key = 1'b1;
		#100000 key = 1'b0;
		#100000 key = 1'b1;
		#100000 key = 1'b0;
		#100000 key = 1'b1;
		#100000 key = 1'b0;
		#100000 key = 1'b1;
		// 保持低电平50ms
		#(50*100000) key = 1'b0;
	end
	
	always #10 clk = ~clk; // 模拟50MHZ时钟
	
endmodule 

  • 27
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值