实验内容:
一、 实验目的
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