基于FPGA 的4位密码锁 矩阵键盘 报警仿真
- 题目描述
- 一、整体框架
- 二、Verilog代码
-
- 1.top
- [2.key_scan 键盘](#2.key_scan 键盘)
- [3.check_mima 模块](#3.check_mima 模块)
- [4.change_mima 模块](#4.change_mima 模块)
- [5.seg_ctrl 模块](#5.seg_ctrl 模块)
- 仿真
题目描述
4位数字密码锁
1、通过键盘模拟4位密码输入 矩阵键盘
正确 错误输出对应的led信号 连续错误3次输出led锁定信号
3、密码可通过管理员按键进行修改
一、整体框架
二、Verilog代码
1.top
c
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/12/19 12:36:54
// Design Name:
// Module Name: top
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module top(
input sys_clk , //外部100MHz时钟
input rst_n, //系外部复位信号,低有效
input key_check,
input key_change,
input [3:0] key_col,
output led_lock,
output led_check,
output led_change,
output led_change_right,
output led_check_error,
output led_check_right,
output wire [3:0] key_row,
output wire [3:0] sel,
output wire [7:0] dig,
output wire [3:0] key_out,
output wire key_vld
);
parameter TCLK = 20 ;
parameter TIME_20MS = 2_000;
wire [3:0] mima_1;
wire [3:0] mima_2;
wire [3:0] mima_3;
wire [3:0] mima_4;
wire [15:0] correct_password;
wire [15:0] mima;
wire [15:0] display_data;
assign display_data=(led_check==1'b1)?mima:(led_change==1'b1)?correct_password:16'hDDDD;
assign mima={mima_1,mima_2,mima_3,mima_4};
//例化需要测试的模块;
key_scan #(
.TCLK ( TCLK ),
.TIME_20MS ( TIME_20MS )
)
u_key_scan (
.clk ( sys_clk ),
.rst_n ( rst_n ),
.key_col ( key_col ),
.key_row ( key_row ),
.key_out ( key_out ),
.key_vld ( key_vld )
);
// four_four_key u_four_four_key(
// .clk(sys_clk),//时钟
// .rst(rst_n),//复位
// .c_pin(key_row),//行引脚
// .r_pin(key_col),//列引脚
// .key_out(key_out),//按键编号输出
// .key_pulse(key_vld)//按键编号输出
//);
check_mima u_check_mima(
.sys_clk(sys_clk), // 时钟信号
.rst_n(rst_n), // 异步复位信号,低有效
.key4(key_check), // 启动密码输入的按键
.key_vld(key_vld),
.key_out(key_out), // 键盘输出的具体数字
.mima_1 (mima_1),
.mima_2 (mima_2),
.mima_3 (mima_3),
.mima_4 (mima_4),
.correct_password(correct_password),
.led9(led_lock),
.led6(led_check),
.led5(led_check_error), // 密码验证标志
.led4(led_check_right) // 密码验证标志
);
change_mima u_change_mima(
.sys_clk(sys_clk), // 时钟信号
.rst_n(rst_n), // 异步复位信号,低有效
.key2(key_change), // 启动密码输入的按键
.key_vld(key_vld),
.key_out(key_out), // 键盘输出的具体数字
.correct_password(correct_password), // 16位密码存储寄存器
.led3(led_change),//进入change状态
.led4(led_change_right) //密码修改正确
);
seg_ctrl u_seg_ctrl(
.clk(sys_clk) ,
.rst_n(rst_n) ,
.din(display_data) ,//输入4位数码管显示数据,每位数码管占4位
.point_n(4'b1111) ,//输入小数点控制位
.sel(sel) ,//输出位选
.dig(dig) //输出段选
);
endmodule
2.key_scan 键盘
c
module key_scan #(
parameter TCLK = 20 ,//系统时钟周期,单位ns。
parameter TIME_20MS = 200 //按键消抖时间,单位为ns。
)(
input clk ,//系统时钟信号,默认50MHz。
input rst_n ,//系统复位,低电平有效;
input [3 : 0] key_col ,//矩阵键盘的列号;
output reg [3 : 0] key_row ,//矩阵键盘的行号;
output reg [3 : 0] key_out ,//矩阵键盘被按下按键的数值;
output reg key_vld //矩阵键盘被按下按键数据输出有效指示信号;
);
//自定义参数;
localparam CHK_COL = 4'b0001 ;//状态机的列扫描状态;
localparam CHK_ROW = 4'b0010 ;//状态机的的行扫描状态;
localparam DELAY = 4'b0100 ;//状态机的延时状态;
localparam WAIT_END = 4'b1000 ;//状态机的等待状态;
localparam TIME_20MS_NUM = TIME_20MS / TCLK ;//计算出TIME_20MS对应的系统时钟个数;
localparam TIME_20MS_W = clogb2(TIME_20MS_NUM-1) ;//利用函数计算出TIME_20MS_NUM对应的寄存器位宽;
reg [3 : 0] key_col_ff0 ;
reg [3 : 0] key_col_ff1 ;
reg [1 : 0] key_col_get ;
reg [3 : 0] state_c ;
reg [TIME_20MS_W - 1 : 0] shake_cnt ;
reg [3 : 0] state_n ;
reg [1 : 0] row_index ;
reg [3 : 0] row_cnt ;
wire end_shake_cnt ;
wire col2row_start ;
wire row2del_start ;
wire del2wait_start ;
wire wait2col_start ;
wire add_row_cnt ;
wire end_row_cnt ;
wire add_shake_cnt ;
wire add_row_index ;
wire end_row_index ;
//自动计算位宽函数;
function integer clogb2(input integer depth);begin
if(depth == 0)
clogb2 = 1;
else if(depth != 0)
for(clogb2=0 ; depth>0 ; clogb2=clogb2+1)
depth=depth >> 1;
end
endfunction
//将输入的列信号打两拍,降低亚稳态出现的机率。
always@(posedge clk)begin
{key_col_ff1,key_col_ff0} <= {key_col_ff0,key_col};
end
//计数器shake_cnt,如果有按键被按下,则key_col_ff1!=4'hf,此时计数器计数。
always@(posedge clk or negedge rst_n)begin
if(rst_n==0)begin
shake_cnt <= 0;
end
else if(add_shake_cnt)begin
if(end_shake_cnt)//按键被按下20ms时,计数器清零;
shake_cnt <= 0;
else//否则当按键被按下时,计数器进行计数;
shake_cnt <= shake_cnt + 1;
end
else begin//没有按键被按下时,计数器清零;
shake_cnt <= 0;
end
end
assign add_shake_cnt = (key_col_ff1!=4'hf) && (state_c == CHK_COL);
assign end_shake_cnt = add_shake_cnt && shake_cnt == TIME_20MS_NUM-1 ;
//当列检查结束时,将被按下按键所在列寄存;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin//初始为0,没有按键被按下;
key_col_get <= 0;
end
else if(col2row_start)begin//当状态机从列检查跳转到行检查时,将按键对应列保存;
if(key_col_ff1==4'b1110)//最低位为0,则表示第0列按键被按下;
key_col_get <= 0;
else if(key_col_ff1==4'b1101)//第1位位0,则表示第1列按键被按下;
key_col_get <= 1;
else if(key_col_ff1==4'b1011)//第2位位0,则表示第2列按键被按下;
key_col_get <= 2;
else//否则表示第3列按键被按下;
key_col_get <= 3;
end
end
//状态机的第一段;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_c <= CHK_COL;
end
else begin
state_c <= state_n;
end
end
always@(*)begin
case(state_c)
CHK_COL: begin//检查列触发;
if(col2row_start)begin
state_n = CHK_ROW;
end
else begin
state_n = CHK_COL;
end
end
CHK_ROW: begin//检查行触发;
if(row2del_start)begin
state_n = DELAY;
end
else begin
state_n = CHK_ROW;
end
end
DELAY : begin//这个状态的存在是为了等待行扫描结束后,计算结果输出。
if(del2wait_start)begin
state_n = WAIT_END;
end
else begin
state_n = DELAY;
end
end
WAIT_END: begin//此时四行全部输出低电平,如果按键被按下,没有松开,那么会持续之前的状态,就需要一致等待按键松开;
if(wait2col_start)begin
state_n = CHK_COL;
end
else begin
state_n = WAIT_END;
end
end
default: state_n = CHK_COL;
endcase
end
//状态机第三段,描述
assign col2row_start = (state_c==CHK_COL ) && end_shake_cnt;//检查到有对应列持续20MS被按下。
assign row2del_start = (state_c==CHK_ROW ) && end_row_index;//行扫描完成;
assign del2wait_start= (state_c==DELAY ) && end_row_cnt;
assign wait2col_start= (state_c==WAIT_END) && key_col_ff1==4'hf;//4'hf表示前面的按键已经被松开,状态机重新回到列检测状态。
//控制行数据的输出,在检查被按下按键所在行时,进行行循环扫描。
//从第一行开始一次拉低,其余行拉高,其余时刻所有行全部拉低。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_row <= 4'b0;
end
else if(state_c==CHK_ROW)begin//行扫描,依次将每行的电平拉低。
key_row <= ~(1'b1 << row_index);
end
else begin
key_row <= 4'b0;
end
end
//行扫描的计数器,对行进行扫描。
//每行扫描持续时间为行计数器row_cnt的计数值,目前为16个时钟周期。
//当4行全部扫面完毕时,计数器清零;
always@(posedge clk or negedge rst_n)begin
if(rst_n==0)begin
row_index <= 0;
end
else if(add_row_index) begin
if(end_row_index)
row_index <= 0;
else
row_index <= row_index + 1;
end
else if(state_c!=CHK_ROW)begin
row_index <= 0;
end
end
assign add_row_index = state_c==CHK_ROW && end_row_cnt;
assign end_row_index = add_row_index && row_index == 4-1 ;
//每行扫描持续时间,初始值为0,此处设置每行扫面16个时钟周期;
//状态机位于行扫描或者等待状态时进行计数,当计数到最大值16时清零。
always@(posedge clk or negedge rst_n)begin
if(rst_n==0)begin
row_cnt <= 0;
end
else if(add_row_cnt)begin
if(end_row_cnt)
row_cnt <= 0;
else
row_cnt <= row_cnt + 1;
end
end
assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
assign end_row_cnt = add_row_cnt && row_cnt == 16-1;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_out <= 0;
end//计算被按下按键的数值;
else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin
key_out <= {row_index,key_col_get};
end
end
//按键数值有效指示信号,高电平时表示key_out输出的值是有效的。
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_vld <= 1'b0;
end
else begin//当没扫描一行,前面暂存的列为低电平的时候,表示这一行,这一列的按键被按下。
key_vld <= (state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0);
end
end
endmodule
3.check_mima 模块
c
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/12/16 20:53:29
// Design Name:
// Module Name: check_mima
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module check_mima(
input sys_clk , //外部100MHz时钟
input rst_n, //系外部复位信号,低有效
input key4,
input key_vld,
output led4,
output led5,
output led6,
output led9,
output reg [3:0] mima_1,
output reg [3:0] mima_2,
output reg [3:0] mima_3,
output reg [3:0] mima_4,
input [15:0] correct_password,
input [3:0] key_out, // 键盘输出的具体数字
output wire [15:0] mima // 24位密码存储寄存器
);
parameter IDLE = 6'd0; // 初始状态,等待key3按下
parameter STORE = 6'd1; // 密码输入存储状态
parameter CHECK = 6'd2; // 密码校验状态
parameter RIGHT = 6'd3; // 密码校验状态
parameter ERROR = 6'd4; // 密码校验状态
parameter LOCK = 6'd5; // 密码校验状态
reg [2:0] error_count;
reg [5:0] state;
reg [5:0] mima_count;
reg [44:0] wait_count;
// reg [23:0] correct_password=24'h123456;
assign mima={mima_1,mima_2,mima_3,mima_4};
assign led4=(state==RIGHT)?1'b1:1'b0;
assign led5=(state==ERROR)?1'b1:1'b0;
assign led6=(state>IDLE)?1'b1:1'b0;
assign led9=(state==LOCK)?1'b1:1'b0;
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
error_count <= 0;
end
else if(state==CHECK &&mima!=correct_password && error_count<3)begin
error_count <= error_count+1;
end
else if(state==LOCK )begin
error_count <= 0;
end
else if(state==CHECK &&mima==correct_password)begin
error_count <= 0;
end
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
wait_count <= 0;
state<=IDLE;
end
else begin
case(state)
IDLE: begin
wait_count <= 0;
if (key4==1'b1) // 按下key3启动密码输入
state <= STORE;
else
state <= IDLE;
end
STORE: begin
if(mima_count<4)begin
state <= STORE;
end
else begin
state <= CHECK;
end
end
CHECK: begin
if(mima==correct_password)begin
state <= RIGHT;
end
else begin
state <= ERROR;
end
end
RIGHT: begin
if(wait_count<100)begin
wait_count<=wait_count+1;
state <= RIGHT;
end else if(wait_count==100) begin
wait_count<=0;
state <= IDLE;
end
end
ERROR: begin
if(error_count==3)begin
state <= LOCK;
end
else begin
if(wait_count<100)begin
wait_count<=wait_count+1;
state <= ERROR;
end else if(wait_count==100) begin
wait_count<=0;
state <= IDLE;
end
end
end
LOCK: begin
if(wait_count<100)begin
wait_count<=wait_count+1;
state <= LOCK;
end else if(wait_count==100) begin
wait_count<=0;
state <= IDLE;
end
end
default: state <= IDLE;
endcase
end
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
mima_count<=0;
end
else if(state==STORE && key_vld==1'b1)begin
mima_count<=mima_count+1;
end
else if(state==CHECK)begin
mima_count<=0;
end
else begin
mima_count<=mima_count;
end
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
mima_1<=0;
mima_2<=0;
mima_3<=0;
mima_4<=0;
end
else if(state == IDLE)begin
mima_1<=0;
mima_2<=0;
mima_3<=0;
mima_4<=0;
end
else if(state == STORE && mima_count==0 && key_vld==1'b1)begin
mima_1<=key_out;
end
else if(state == STORE && mima_count==1 && key_vld==1'b1)begin
mima_2<=key_out;
end
else if(state == STORE && mima_count==2 && key_vld==1'b1)begin
mima_3<=key_out;
end
else if(state == STORE && mima_count==3 && key_vld==1'b1)begin
mima_4<=key_out;
end
else begin
mima_1<=mima_1;
mima_2<=mima_2;
mima_3<=mima_3;
mima_4<=mima_4;
end
end
endmodule
4.change_mima 模块
c
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/12/16 20:53:29
// Design Name:
// Module Name: check_mima
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module change_mima(
input sys_clk , //外部100MHz时钟
input rst_n, //系外部复位信号,低有效
input key2,
input key_vld,
output led4,
output led3,
output reg [3:0] mima_1,
output reg [3:0] mima_2,
output reg [3:0] mima_3,
output reg [3:0] mima_4,
input [3:0] key_out, // 键盘输出的具体数字
output wire [15:0] correct_password // 24位密码存储寄存器
);
parameter IDLE = 6'd0; // 初始状态,等待key3按下
parameter STORE = 6'd1; // 密码输入存储状态
parameter CHECK = 6'd2; // 密码校验状态
parameter RIGHT = 6'd3; // 密码校验状态
parameter ERROR = 6'd4; // 密码校验状态
reg [5:0] state;
reg [5:0] mima_count;
reg [44:0] wait_count;
// reg [23:0] correct_password=24'h123456;
assign correct_password={mima_1,mima_2,mima_3,mima_4};
assign led3=(state>IDLE)?1'b1:1'b0;
assign led4=(state==RIGHT)?1'b1:1'b0;
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
wait_count <= 0;
state<=IDLE;
end
else begin
case(state)
IDLE: begin
wait_count <= 0;
if (key2==1'b1) // 按下key3启动密码输入
state <= STORE;
else
state <= IDLE;
end
STORE: begin
if(mima_count<4)begin
state <= STORE;
end
else begin
state <= RIGHT;
end
end
RIGHT: begin
if(wait_count<100)begin
wait_count<=wait_count+1;
state <= RIGHT;
end else if(wait_count==100) begin
wait_count<=0;
state <= IDLE;
end
end
default: state <= IDLE;
endcase
end
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
mima_count<=0;
end
else if(state==STORE && key_vld==1'b1)begin
mima_count<=mima_count+1;
end
else if(state==RIGHT)begin
mima_count<=0;
end
else begin
mima_count<=mima_count;
end
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)begin
mima_1<=1;
mima_2<=2;
mima_3<=3;
mima_4<=4;
end
else if(state == STORE && mima_count==0 && key_vld==1'b1)begin
mima_1<=key_out;
end
else if(state == STORE && mima_count==1 && key_vld==1'b1)begin
mima_2<=key_out;
end
else if(state == STORE && mima_count==2 && key_vld==1'b1)begin
mima_3<=key_out;
end
else if(state == STORE && mima_count==3 && key_vld==1'b1)begin
mima_4<=key_out;
end
else begin
mima_1<=mima_1;
mima_2<=mima_2;
mima_3<=mima_3;
mima_4<=mima_4;
end
end
endmodule
5.seg_ctrl 模块
c
/**************************************功能介绍***********************************
Date : 2023年10月1日 11:54:18
Author : Yang.
Project : 密码锁
Require : 用verliog实现密码锁,且具有以下功能:
1、六位密码,且用数码管显示。
2、用按键键入每一位密码,可以加减。
3、密码正确时,led灯以300ms频率闪烁10s,且蜂鸣器播放音乐。
4、密码错误时,led灯以100ms频率闪烁10s,且蜂鸣器报警。
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module seg_ctrl(
input clk ,
input rst_n ,
input [15:0] din ,//输入6位数码管显示数据,每位数码管占4位
input [3:0] point_n ,//输入小数点控制位
output reg [3:0] sel ,//输出位选
output reg [7:0] dig //输出段选
);
//---------<参数定义>---------------------------------------------------------
parameter TIME_1MS = 50_000;//1ms
//数码管显示字符编码
localparam NUM_0 = 7'b100_0000,//0
NUM_1 = 7'b111_1001,//1
NUM_2 = 7'b010_0100,//
NUM_3 = 7'b011_0000,//
NUM_4 = 7'b001_1001,//
NUM_5 = 7'b001_0010,//
NUM_6 = 7'b000_0010,//
NUM_7 = 7'b111_1000,//
NUM_8 = 7'b000_0000,//
NUM_9 = 7'b001_0000,//
A = 7'b000_1000,//
B = 7'b000_0011,//b
C = 7'b100_0110,//
OFF = 7'b111_1111,//全灭
CROSS = 7'b011_1111,//横杠
//D = 7'b010_0001,//d
//E = 7'b000_0110,//
F = 7'b000_1110;//
//---------<内部信号定义>-----------------------------------------------------
reg [15:0] cnt_1ms ;//1ms计数器(扫描间隔计数器)
wire add_cnt_1ms ;
wire end_cnt_1ms ;
reg [3:0] disp_data ;//每一位数码管显示的数值
reg point_n_r ;//每一位数码管显示的小数点
//****************************************************************
//--cnt_1ms
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1ms <= 'd0;
end
else if(add_cnt_1ms)begin
if(end_cnt_1ms)begin
cnt_1ms <= 'd0;
end
else begin
cnt_1ms <= cnt_1ms + 1'b1;
end
end
end
assign add_cnt_1ms = 1'b1;//数码管一直亮
assign end_cnt_1ms = add_cnt_1ms && cnt_1ms == TIME_1MS - 1;
//****************************************************************
//--seg_sel
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sel <= 4'b1_110;//循环移位实现时,需要给位选赋初值
end
else if(end_cnt_1ms)begin
sel <= {sel[2:0],sel[3]};//循环左移
end
end
//****************************************************************
//--disp_data
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
disp_data <= 'd0;
point_n_r <= 1'b1;
end
else begin
case (sel)
4'b1_110 : begin disp_data <= din[3:0] ; point_n_r <= point_n[0]; end//第一位数码管显示的数值
4'b1_101 : begin disp_data <= din[7:4] ; point_n_r <= point_n[1]; end
4'b1_011 : begin disp_data <= din[11:8] ; point_n_r <= point_n[2]; end
4'b0_111 : begin disp_data <= din[15:12]; point_n_r <= point_n[3]; end
default: disp_data <= 'd0;
endcase
end
end
//****************************************************************
//--seg_dig
//****************************************************************
always @(*)begin
case (disp_data)
0 : dig = {point_n_r,NUM_0};
1 : dig = {point_n_r,NUM_1};
2 : dig = {point_n_r,NUM_2};
3 : dig = {point_n_r,NUM_3};
4 : dig = {point_n_r,NUM_4};
5 : dig = {point_n_r,NUM_5};
6 : dig = {point_n_r,NUM_6};
7 : dig = {point_n_r,NUM_7};
8 : dig = {point_n_r,NUM_8};
9 : dig = {point_n_r,NUM_9};
10 : dig = {point_n_r,A };
11 : dig = {point_n_r,B };
12 : dig = {point_n_r,C };
13 : dig = {point_n_r,CROSS};
14 : dig = {point_n_r,OFF };
15 : dig = {point_n_r,F };
default: dig = 8'hff;
endcase
end
endmodule