FPGA密码锁(1)
终于,写到了上难度的工程,接下来的工程会比之前有难度,需要的知识更多,我肯定尽力把我对工程的所有理解一字不差的、清清楚楚的写上去。
文章目录
前言
数码管的应用有很多花样,利用开发板中的各种资源来做出各种各样的工程。
现在开始做密码锁,这个密码锁
1、六位密码,且用数码管显示。
2、用按键键入每一位密码,可以加减。
3、密码正确时,led灯以300ms频率闪烁10s,且蜂鸣器播放音乐。
4、密码错误时,led灯以100ms频率闪烁10s,且蜂鸣器报警。
一、设计框架
这就是一个大致的框架图
首先确认所有信号:
系统时钟、复位就不必多说,是代码就都要有;
key是按键键入密码,所以是输入;
led可以这里用来“装饰”,其实是用来显示输入的是第几位密码;
sel、dig就是数码管需要用到的段选位选信号,不懂得回前边;
beep蜂鸣器,密码对了唱歌,错了报警。
这就是顶层所有需要用到的信号,然后开始分模块,分别对这些信号进行定义。
二、模块
1.按键消抖模块
输入密码需要按键,不对按键进行消抖的话密码输入就有可能出错。按键消抖模块就不用说了,直接套用按键消抖的万用代码
/**************************************功能介绍***********************************
Date : 2023年10月1日 11:54:18
Author : Yang.
Project : 密码锁
Require : 用verliog实现密码锁,且具有以下功能:
1、六位密码,且用数码管显示。
2、用按键键入每一位密码,可以加减。
3、密码正确时,led灯以300ms频率闪烁10s,且蜂鸣器播放音乐。
4、密码错误时,led灯以100ms频率闪烁10s,且蜂鸣器报警。
*********************************************************************************/
// key_filter #(.WIDTH(3)
// )key_filter(
// /*input */.clk ( ),
// /*input */.rst_n ( ),
// /*input [WIDTH-1:0] */.key_in ( ),
// /*output reg [WIDTH-1:0] */.key_down ( )
// );
module key_filter #(parameter WIDTH = 4,
DELAY_20MS = 1000_000
)(
input clk ,
input rst_n ,
input [WIDTH-1:0] key_in ,
output reg [WIDTH-1:0] key_down
);
//---------<参数定义>---------------------------------------------------------
//状态机参数定义
localparam IDLE = 4'b0001,//空闲状态
FILETER_DOWN = 4'b0010,//按键按下抖动状态
HOLD_DOWN = 4'b0100,//按下稳定按下状态
FILTER_UP = 4'b1000;//按键释放抖动状态
//延时参数定义
//---------<内部信号定义>-----------------------------------------------------
reg [3:0] state_c ;//现态
reg [3:0] state_n ;//次态
reg [WIDTH-1:0] key_r0 ;//同步打拍
reg [WIDTH-1:0] key_r1 ;
reg [WIDTH-1:0] key_r2 ;
wire [WIDTH-1:0] n_edge ;//下降沿
wire [WIDTH-1:0] p_edge ;//上升沿
reg [19:0] cnt_20ms ;//20ms计数器
wire add_cnt_20ms;
wire end_cnt_20ms;
//状态转移条件定义
wire idle2filter_down ;
wire fiter_down2hold_down ;
wire hold_down2filter_up ;
wire filter_up2idle ;
//****************************************************************
//--state_c
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;//复位的初始状态
end
else begin
state_c <= state_n;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(state_c)
IDLE : begin
if(idle2filter_down)begin
state_n = FILETER_DOWN;
end
else begin
state_n = state_c;
// state_n = IDLE;
end
end
FILETER_DOWN : begin
if(fiter_down2hold_down)begin
state_n = HOLD_DOWN;
end
else begin
state_n = state_c;
end
end
HOLD_DOWN : begin
if(hold_down2filter_up)begin
state_n = FILTER_UP;
end
else begin
state_n = state_c;
end
end
FILTER_UP : begin
if(filter_up2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default : state_n = IDLE;
endcase
end
assign idle2filter_down = state_c == IDLE && n_edge;
assign fiter_down2hold_down = state_c == FILETER_DOWN && end_cnt_20ms;
assign hold_down2filter_up = state_c == HOLD_DOWN && p_edge;
assign filter_up2idle = state_c == FILTER_UP && end_cnt_20ms;
//****************************************************************
//--n_edge、p_edge
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {WIDTH{1'b1}};
key_r1 <= {WIDTH{1'b1}};
key_r2 <= {WIDTH{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
assign n_edge = ~key_r1 & key_r2;
assign p_edge = ~key_r2 & key_r1;
//****************************************************************
//--cnt_20ms
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = state_c == FILETER_DOWN || state_c == FILTER_UP;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == DELAY_20MS - 1;
//****************************************************************
//--key_down
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_down <= 'd0;
end
else if(hold_down2filter_up)begin
key_down <= ~key_r2;
end
else begin
key_down <= 'd0;
end
end
endmodule
2.数码管控制模块
这个数码管控制的代码其实也算得上是万用,每次用只需要小小的改动就行。
/**************************************功能介绍***********************************
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 [23:0] din ,//输入6位数码管显示数据,每位数码管占4位
input [5:0] point_n ,//输入小数点控制位
output reg [5: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 <= 6'b111_110;//循环移位实现时,需要给位选赋初值
end
else if(end_cnt_1ms)begin
sel <= {sel[4:0],sel[5]};//循环左移
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)
6'b111_110 : begin disp_data <= din[3:0] ; point_n_r <= point_n[0]; end//第一位数码管显示的数值
6'b111_101 : begin disp_data <= din[7:4] ; point_n_r <= point_n[1]; end
6'b111_011 : begin disp_data <= din[11:8] ; point_n_r <= point_n[2]; end
6'b110_111 : begin disp_data <= din[15:12]; point_n_r <= point_n[3]; end
6'b101_111 : begin disp_data <= din[19:16]; point_n_r <= point_n[4]; end
6'b011_111 : begin disp_data <= din[23:20]; point_n_r <= point_n[5]; 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
这里的位选信号由cnt_1ms
控制,不过1ms的时间太短,基本等于一直亮。
point_n_r
就是控制小数点的信号。数码管八段led,把小数点那一段led单独拎出来,这里用不到小数点,所以point_n_r
都是1(不亮)。
OFF = 7'b111_1111,//全灭
CROSS = 7'b011_1111,//横杠
代码中的这两个就是让数码管八段全灭,还有显示横杠,这两个用来让键入密码时数码管更好看而已。
3.蜂鸣器报警模块
/**************************************功能介绍***********************************
Date : 2023年10月1日 11:54:18
Author : Yang.
Project : 密码锁
Require : 用verliog实现密码锁,且具有以下功能:
1、六位密码,且用数码管显示。
2、用按键键入每一位密码,可以加减。
3、密码正确时,led灯以300ms频率闪烁10s,且蜂鸣器播放音乐。
4、密码错误时,led灯以100ms频率闪烁10s,且蜂鸣器报警。
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module beep(
input clk ,
input rst_n ,
input [8:0] state ,
output reg beep //蜂鸣器驱动信号,低有效
);
//---------<参数定义>---------------------------------------------------------
parameter BEAT_TIME = 15_000_000;//300ms
parameter TOTAL_NUM_1 = 14 ;//总音符数量
parameter TOTAL_NUM_2 = 14 ;
//音符对应频率周期
localparam L_DO = 18'd190838 /*对应音符周期/20*/,//音符周期 对应音符周期/20ns
L_RE = 18'd170068 /*对应音符周期/20*/,
L_MI = 18'd151515 /*对应音符周期/20*/,
L_FA = 18'd143266 /*对应音符周期/20*/,
L_SO = 18'd127551 /*对应音符周期/20*/,
L_LA = 18'd113636 /*对应音符周期/20*/,
L_SI = 18'd101214 /*对应音符周期/20*/,
M_DO = 17'd95600 /*对应音符周期/20*/,
M_RE = 17'd85150 /*对应音符周期/20*/,
M_MI = 17'd75850 /*对应音符周期/20*/,
M_FA = 17'd71600 /*对应音符周期/20*/,
M_SO = 17'd63750 /*对应音符周期/20*/,
M_LA = 16'd56800 /*对应音符周期/20*/,
M_XI = 16'd51050 /*对应音符周期/20*/,
H_DO = 16'd47750 /*对应音符周期/20*/,
H_RE = 16'd42550 /*对应音符周期/20*/,
H_MI = 16'd37900 /*对应音符周期/20*/,
H_FA = 16'd37550 /*对应音符周期/20*/,
H_SO = 15'd31850 /*对应音符周期/20*/,
H_LA = 15'd28400 /*对应音符周期/20*/,
H_SI = 15'd25400 /*对应音符周期/20*/;
localparam IDLE = 0 ,//空闲状态
S1 = 1 ,//键入第一位
S2 = 2 ,//键入第二位
S3 = 3 ,//键入第三位
S4 = 4 ,//键入第四位
S5 = 5 ,//键入第五位
S6 = 6 ,//键位第六位
SUCESS = 7 ,//密码正确
FAILED = 8 ;//密码错误
//---------<内部信号定义>-----------------------------------------------------
reg [23:0] cnt_beat ;//节拍计数器(单音符持续时间计数器)
wire add_cnt_beat ;
wire end_cnt_beat ;
reg [3:0] cnt_num_1 ;//音符数量计数器
wire add_cnt_num_1 ;
wire end_cnt_num_1 ;
reg [19:0] cnt_freq ;//对应音符频率计数器
wire add_cnt_freq ;
wire end_cnt_freq ;
reg [3:0] cnt_num_2 ;
wire add_cnt_num_2 ;
wire end_cnt_num_2 ;
reg [19:0] frequency1 ;//对应音符频率周期
reg [19:0] frequency2 ;
wire [19:0] duty ;//占空比调节
//****************************************************************
//--cnt_beat
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_beat <= 'd0;
end
else if(add_cnt_beat)begin
if(end_cnt_beat)begin
cnt_beat <= 'd0;
end
else begin
cnt_beat <= cnt_beat + 1'b1;
end
end
end
assign add_cnt_beat = ((state == SUCESS)||(state == FAILED));
assign end_cnt_beat = add_cnt_beat && cnt_beat == BEAT_TIME - 1;
//****************************************************************
//--cnt_num
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_num_1 <= 'd0;
end
else if(add_cnt_num_1)begin
if(end_cnt_num_1)begin
cnt_num_1 <= 'd0;
end
else begin
cnt_num_1 <= cnt_num_1 + 1'b1;
end
end
end
assign add_cnt_num_1 = end_cnt_beat && (state == SUCESS);
assign end_cnt_num_1 = add_cnt_num_1 && cnt_num_1 == TOTAL_NUM_1 - 1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_num_2 <= 'd0 ;
end
else if(add_cnt_num_2)begin
if(end_cnt_num_2)begin
cnt_num_2 <= 'd0 ;
end
else begin
cnt_num_2 <= cnt_num_2 + 1'b1 ;
end
end
end
assign add_cnt_num_2 = end_cnt_beat && (state == FAILED);
assign end_cnt_num_2 = add_cnt_num_2 && cnt_num_2 == TOTAL_NUM_2 - 1 ;
//****************************************************************
//--cnt_freq
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_freq <= 'd0;
end
else if(add_cnt_freq)begin
if(end_cnt_freq)begin
cnt_freq <= 'd0;
end
else begin
cnt_freq <= cnt_freq + 1'b1;
end
end
end
assign add_cnt_freq = ((state == SUCESS)||(state == FAILED));
assign end_cnt_freq = add_cnt_freq && ((cnt_freq == frequency1 - 1) ||(cnt_freq == frequency2 - 1)|| end_cnt_beat);//一个音符300ms计满时,cnt_freq有可能还没计数完,将其置0,开始计数器下一个音符的频率周期
//****************************************************************
//--frequency
//****************************************************************
always @(*)begin
if(state == SUCESS)begin
case (cnt_num_1)
0: frequency1 <= M_XI;
1: frequency1 <= M_SO;
2: frequency1 <= M_FA;
3: frequency1 <= M_DO;
4: frequency1 <= M_XI;
5: frequency1 <= M_SO;
6: frequency1 <= M_FA;
7: frequency1 <= M_DO;
8: frequency1 <= M_XI;
9: frequency1 <= M_SO;
10:frequency1 <= M_FA;
11:frequency1 <= M_DO;
12:frequency1 <= M_XI;
13:frequency1 <= M_SO;
default: frequency1 = M_XI;
endcase
end
end
always @(*)begin
if(state == FAILED)begin
case (cnt_num_2)
0: frequency2 <= H_SO;
1: frequency2 <= M_MI;
2: frequency2 <= L_DO;
3: frequency2 <= H_SO;
4: frequency2 <= M_MI;
5: frequency2 <= L_DO;
6: frequency2 <= H_SO;
7: frequency2 <= M_MI;
8: frequency2 <= L_DO;
9: frequency2 <= H_SO;
10:frequency2 <= M_MI;
11:frequency2 <= L_DO;
12:frequency2 <= H_SO;
13:frequency2 <= M_MI;
default: frequency2 = H_SO;
endcase
end
end
//****************************************************************
//--duty
//****************************************************************
assign duty = (frequency1>>2) ;//50%占空比
//****************************************************************
//--beep_n
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
beep <= 1'b1;
end
else if((cnt_freq < duty)&&(state == SUCESS)||(cnt_freq < duty)&&(state == FAILED))begin
beep <= 1'b0;
end
else if(cnt_freq > duty)begin
beep <= 1'b1;
end
else begin
beep <= 1'b1;
end
end
endmodule
蜂鸣器模块,和之前的模块一样,就是分出了两个频率周期和两个音符数量计数器。
在状态为SUCESS或者FAILED
时分别运行密码正确的音乐密码或者错误的报警音。
4.密码控制模块
这里先不放代码,先引入一下状态机的原理(其实能看这文章的基本都学了状态机,不想看就直接跳到代码就行):
在写工程的时候,总会有一些流程需要去实现
例如在本工程中: 如何依次键入六位密码,如何判断密码正确还是错误 等等问题。
要实现这些流程,我们需要用到状态机。
这些状态机简写为 FSM(Finite State Machine),也称为同步有限状态机,我们一般简称为状态机,之所以说“同步”是因为状态机中所有的状态跳转都是在时钟的作用下进行的,而“有限”则是说状态的个数是有限的。状态机的每一个状态代表一个事件,从执行当前事件到执行另一事件我们称之为状态的跳转或状态的转移,我们需要做的就是执行该事件然后跳转到下一事件,这样我们的系统就可以正常的运转起来了。状态机通过控制各个状态的跳转来控制 流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显。
具体状态机的类型等等问题,会再写一篇文章。
接下来先上代码:
/**************************************功能介绍***********************************
Date : 2023年10月1日 11:54:18
Author : Yang.
Project : 密码锁
Require : 用verliog实现密码锁,且具有以下功能:
1、六位密码,且用数码管显示。
2、用按键键入每一位密码,可以加减。
3、密码正确时,led灯以300ms频率闪烁10s,且蜂鸣器播放音乐。
4、密码错误时,led灯以100ms频率闪烁10s,且蜂鸣器报警。
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module lock(
input clk ,
input rst_n ,
input [2:0] key_in ,
output [8:0] state ,
output reg [23:0] data ,
output reg [3:0] led
);
localparam PASSWORD = 24'h979255 ;
//------------------------<状态机>---------------------------
localparam IDLE = 0 ,//空闲状态
S1 = 1 ,//键入第一位
S2 = 2 ,//键入第二位
S3 = 3 ,//键入第三位
S4 = 4 ,//键入第四位
S5 = 5 ,//键入第五位
S6 = 6 ,//键位第六位
SUCESS = 7 ,//密码正确
FAILED = 8 ;//密码错误
reg [8:0] state_c ;
reg [8:0] state_n ;
//状态转移条件
wire idle_s1 ;
wire s1_s2 ;
wire s2_s3 ;
wire s3_s4 ;
wire s4_s5 ;
wire s5_s6 ;
wire s6_sucess ;
wire s6_failed ;
wire sucess_idle ;
wire failed_idle ;
assign state = state_c ;
//------------------------<键入密码>---------------------------
reg [3:0] value_1 ;//键入的第一位密码
reg [3:0] value_2 ;//键入的第二位密码
reg [3:0] value_3 ;//键入的第三位密码
reg [3:0] value_4 ;//键入的第四位密码
reg [3:0] value_5 ;//键入的第五位密码
reg [3:0] value_6 ;//键位的第六位密码
wire [23:0] key_value ; //六位密码
//------------------------<led闪烁>---------------------------
parameter MAX_10S = 29'd500_000_000 ;
parameter MAX_300MS = 24'd15_000_000 ;
parameter MAX_100MS = 23'd5_000_000 ;
reg [28:0] cnt_10s ;
wire add_cnt_10s ;
wire end_cnt_10s ;
reg [23:0] cnt_300ms ;
wire add_cnt_300ms ;
wire end_cnt_300ms ;
reg [22:0] cnt_100ms ;
wire add_cnt_100ms ;
wire end_cnt_100ms ;
reg [3:0] led_star ;
reg [3:0] led_light ;
//****************************************************************
//--状态机
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
state_c <= IDLE ;
else
state_c <= state_n ;
end
always @(*)begin
case (state_c)
IDLE : if(idle_s1)
state_n <= S1 ;
else
state_n <= state_c ;
S1 : if(s1_s2)
state_n <= S2 ;
else
state_n <= state_c ;
S2 : if(s2_s3)
state_n <= S3 ;
else
state_n <= state_c ;
S3 : if(s3_s4)
state_n <= S4 ;
else
state_n <= state_c ;
S4 : if(s4_s5)
state_n <= S5 ;
else
state_n <= state_c ;
S5 : if(s5_s6)
state_n <= S6 ;
else
state_n <= state_c ;
S6 : if(s6_sucess)
state_n <= SUCESS ;
else if(s6_failed)
state_n <= FAILED ;
else
state_n <= state_c ;
SUCESS : if(sucess_idle)
state_n <= IDLE ;
else
state_n <= state_c ;
FAILED : if(failed_idle)
state_n <= IDLE ;
else
state_n <= state_c ;
default: state_n <= IDLE ;
endcase
end
assign idle_s1 = state_c == IDLE && key_in[2] ;//按下key[2]确认开始输入密码
assign s1_s2 = state_c == S1 && key_in[2] ;//按下key[2]确认密码
assign s2_s3 = state_c == S2 && key_in[2] ;//按下key[2]确认密码
assign s3_s4 = state_c == S3 && key_in[2] ;//按下key[2]确认密码
assign s4_s5 = state_c == S4 && key_in[2] ;//按下key[2]确认密码
assign s5_s6 = state_c == S5 && key_in[2] ;//按下key[2]确认密码
assign s6_sucess = state_c == S6 && key_in[2] && key_value == PASSWORD ;//按下key[2]判断key_value是否等于password,等于进入sucess
assign s6_failed = state_c == S6 && key_in[2] && key_value != PASSWORD ;//按下key[2]判断key_value是否等于password,不等于进入failed
assign sucess_idle = state_c == SUCESS && key_in[2] || end_cnt_10s ;//按下key[2]或者10s后自动返回初始状态
assign failed_idle = state_c == FAILED && key_in[2] || end_cnt_10s ;//按下key[2]或者10s后自动返回初始状态
//****************************************************************
//--键入密码
//****************************************************************
//value_1
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
value_1 <= 0 ;
else if(key_in[0] && value_1 != 9 && state_c == S1)//按下key[0]如果value_1不等于9,则加一
value_1 <= value_1 + 1 ;
else if(key_in[0] && value_1 == 9 && state_c == S1)//如果等于9,则变零
value_1 <= 0 ;
else if(key_in[1] && value_1 != 0 && state_c == S1)//按下key[1]如果value_1不等于0,则减一
value_1 <= value_1 - 1 ;
else if(key_in[1] && value_1 == 0 && state_c == S1)//如果等于零,则变9
value_1 <= 9 ;
end
//value_2
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
value_2 <= 0 ;
else if(key_in[0] && value_2 != 9 && state_c == S2)
value_2 <= value_2 + 1 ;
else if(key_in[0] && value_2 == 9 && state_c == S2)
value_2 <= 0 ;
else if(key_in[1] && value_2 != 0 && state_c == S2)
value_2 <= value_2 - 1 ;
else if(key_in[1] && value_2 == 0 && state_c == S2)
value_2 <= 9 ;
end
//value_3
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
value_3 <= 0 ;
else if(key_in[0] && value_3 != 9 && state_c == S3)
value_3 <= value_3 + 1 ;
else if(key_in[0] && value_3 == 9 && state_c == S3)
value_3 <= 0 ;
else if(key_in[1] && value_3 != 0 && state_c == S3)
value_3 <= value_3 - 1 ;
else if(key_in[1] && value_3 == 0 && state_c == S3)
value_3 <= 9 ;
end
//value_4
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
value_4 <= 0 ;
else if(key_in[0] && value_4 != 9 && state_c == S4)
value_4 <= value_4 + 1 ;
else if(key_in[0] && value_4 == 9 && state_c == S4)
value_4 <= 0 ;
else if(key_in[1] && value_4 != 0 && state_c == S4)
value_4 <= value_4 - 1 ;
else if(key_in[1] && value_4 == 0 && state_c == S4)
value_4 <= 9 ;
end
//value_5
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
value_5 <= 0 ;
else if(key_in[0] && value_5 != 9 && state_c == S5)
value_5 <= value_5 + 1 ;
else if(key_in[0] && value_5 == 9 && state_c == S5)
value_5 <= 0 ;
else if(key_in[1] && value_5 != 0 && state_c == S5)
value_5 <= value_5 - 1 ;
else if(key_in[1] && value_5 == 0 && state_c == S5)
value_5 <= 9 ;
end
//value_6
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
value_6 <= 0 ;
else if(key_in[0] && value_6 != 9 && state_c == S6)
value_6 <= value_6 + 1 ;
else if(key_in[0] && value_6 == 9 && state_c == S6)
value_6 <= 0 ;
else if(key_in[1] && value_6 != 0 && state_c == S6)
value_6 <= value_6 - 1 ;
else if(key_in[1] && value_6 == 0 && state_c == S6)
value_6 <= 9 ;
end
assign key_value = {value_1,value_2,value_3,value_4,value_5,value_6};//由六位密码组成key_value
//****************************************************************
//--led控制
//****************************************************************
//10s计时器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_10s <= 0 ;
end
else if(add_cnt_10s)begin
if(end_cnt_10s)begin
cnt_10s <= 0 ;
end
else begin
cnt_10s <= cnt_10s + 1 ;
end
end
end
assign add_cnt_10s = state_c == SUCESS || state_c == FAILED ; //sucess或者failed状态开始计时10s
assign end_cnt_10s = add_cnt_10s && cnt_10s == MAX_10S - 1 ;
//300ms计时器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_300ms <= 0 ;
end
else if(add_cnt_10s)begin
if(end_cnt_10s)begin
cnt_300ms <= 0 ;
end
else begin
cnt_300ms <= cnt_300ms + 1 ;
end
end
end
assign add_cnt_300ms = state_c == SUCESS ; //sucess状态下计时300ms
assign end_cnt_300ms = add_cnt_300ms && cnt_300ms == MAX_300MS - 1 ;
//100ms计时器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_100ms <= 0 ;
end
else if(add_cnt_100ms)begin
if(end_cnt_100ms)begin
cnt_100ms <= 0 ;
end
else begin
cnt_100ms <= cnt_100ms + 1 ;
end
end
end
assign add_cnt_100ms = state_c == FAILED ; //failed状态下计时100ms
assign end_cnt_100ms = add_cnt_100ms && cnt_100ms == MAX_100MS - 1 ;
//sucess---led
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led_light <= 4'b0000 ;
end
else if(state_c == SUCESS && end_cnt_300ms)begin //sucess状态下每300msLED灯切换亮灭
led_light <= ~led_light ;
end
end
//failed---led
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led_star <= 4'b0000 ;
end
else if(state_c == FAILED && end_cnt_100ms)begin //failed状态下每100msLED灯切换亮灭
led_star <= ~led_star ;
end
end
//****************************************************************
//--数据输出
//****************************************************************
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led <= 4'b1111 ;
data <= 24'd0 ;
end
else begin
case (state_c)
IDLE : begin led <= 4'b0000 ;data <= 24'heeeeeee ;end //数码管驱动模块'he编码为全灭
S1 : begin led <= 4'b0001 ;data <= {8'h11,8'hdd,4'he,value_1} ;end //'hd为横杠 显示aa-- value.
S2 : begin led <= 4'b0010 ;data <= {8'h22,8'hdd,4'he,value_2} ;end //显示bb-- value.
S3 : begin led <= 4'b0100 ;data <= {8'h33,8'hdd,4'he,value_3} ;end //显示cc-- value.
S4 : begin led <= 4'b1000 ;data <= {8'h44,8'hdd,4'he,value_4} ;end //显示dd-- value.
S5 : begin led <= 4'b0101 ;data <= {8'h55,8'hdd,4'he,value_5} ;end //显示ee-- value.
S6 : begin led <= 4'b1010 ;data <= {8'h66,8'hdd,4'he,value_6} ;end //显示ff-- value.
SUCESS : begin led <= led_light ;data <= 24'h111111 ;end //显示111111.
FAILED : begin led <= led_star ;data <= 24'hffffff ;end //显示ffffff.
default: begin led <= 4'b0000 ;data <= 24'd0 ;end
endcase
end
end
endmodule
别看这代码三百多行,理解了以后老简单了,现在一点一点讲解,(可以先把代码复制下来然后一点一点对着看)。
1、端口列表
端口信号列表中
输出state
就是将此模块定义的状态机输出给蜂鸣器模块,上边也说了,蜂鸣器模块也用到了SUCESS和FAILED状态。
data就是把此模块定义的数据发给数码管模块进行显示处理。
led就是上边说的显示键入的第几位密码。
localparam PASSWORD = 24'h979255 ;
就是我定义的6位密码。当输入979255时,密码正确。
2、状态机
然后就是状态机:
图中就是所有的状态,在代码中都有注释
尤其是状态机的跳转条件,这个在代码中写得清清楚楚。符合跳转条件的话,状态机就会跳转状态,实现整个工程的流程。
1、刚上电,要开始键入第一位密码,就按下key2,跳转到S1状态,键入第一位密码;
2、按下key2来确认键入的第一位密码,开始键入第二位密码;
3、以此类推,直到键入第六位密码后,按下key2确认,判断密码是否正确;
4、密码正确进入SUCESS状态;密码错误进入FAILED状态;
5、等待蜂鸣器响十秒后或者手动按key2就能回到初始状态。
3、密码数据
状态机完成后就是密码的输入:
从value_1
到value_6
分别代表六位密码。按下key0就加一,按下key1就减一,
每键入一位密码都会被记录下来。
六位全部键入完毕,就用“{}”符号将六位数字拼接起来变成key_vlaue
。
在s6状态跳转到接下来的状态时让key_vlaue
与PASSWORD = 24'h979255
进行比较,如果一样就密码正确,不一样就密码错误。
4、led控制
这里用了三个计数器,分别是 :
让 计数蜂鸣器响10s,led闪10s的10s计数器;
密码正确:led以300ms的频率闪烁;
密码错误:led以100ms的频率闪烁。
闪烁灯怎么做就看之前的点灯大师就行。
5、数据输出
这里用state写了case语句,data一共有24位,每一个数码管占4位。{8'h11,8'hdd,4'he,value_1}
就是六位数码管显示11-- value_1。
当状态机进入SUCESS状态时,数码管显示111111;
进入FAILED状态时,显示f f f f f f;
5.顶层代码
/**************************************功能介绍***********************************
Date : 2023年10月1日 11:54:18
Author : Yang.
Project : 密码锁
Require : 用verliog实现密码锁,且具有以下功能:
1、六位密码,且用数码管显示。
2、用按键键入每一位密码,可以加减。
3、密码正确时,led灯以300ms频率闪烁10s,且蜂鸣器播放音乐。
4、密码错误时,led灯以100ms频率闪烁10s,且蜂鸣器报警。
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module top(
input clk ,
input rst_n ,
input [2:0] key_in ,
output beep ,
output [5:0] sel ,
output [7:0] dig ,
output [3:0] led
);
wire [2:0] key_down ;
wire [8:0] state ;
wire [23:0] data ;
key_filter #(.WIDTH(3)
)key_filter(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input [WIDTH-1:0] */.key_in (key_in ),
/*output reg [WIDTH-1:0] */.key_down (key_down )
);
lock lock(
/*input */.clk (clk) ,
/*input */.rst_n (rst_n) ,
/*input [2:0] */.key_in (key_down) ,
/*output reg [8:0] */.state (state) ,
/*output reg [23:0] */.data (data) ,
/*output reg [3:0] */.led (led)
);
seg_ctrl seg_ctrl(
/*input wire */.clk (clk) ,
/*input wire */.rst_n (rst_n) ,
/*input wire [23:0] */.din (data) ,
/*input wire [5:0] */.point_n (6'b111_111) ,
/*output reg [5:0] */.sel (sel) ,
/*output reg [7:0] */.dig (dig)
);
beep beep_inst(
/*input */.clk (clk),
/*input */.rst_n (rst_n),
/*input [8:0] */.state (state),
/*output */.beep (beep)
);
endmodule
顶层就是把所有模块的代码例化到一个模块里,把线连好。
一般编写工程的方式都是***自顶向下*** ,从顶层定义出所有需要的信号,再分模块去写。
6.仿真代码
`timescale 1ns/1ns
module top_tb();
//激励信号定义
reg tb_clk ;
reg tb_rst_n ;
reg [2:0] key_in ;
//输出信号定义
wire beep ;
wire [3:0] led ;
wire [5:0] sel ;
wire [7:0] dig ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//参数重新定义
// defparam top_inst.key_filter.DELAY_20MS = 100;
defparam top_inst.lock.MAX_10S = 500;
defparam top_inst.lock.MAX_300MS= 15;
defparam top_inst.lock.MAX_100MS= 5;
//模块例化
top top_inst(
/*input */.clk (tb_clk ),
/*input */.rst_n (tb_rst_n ),
/*input [2:0] */.key_in (key_in ),
/*output [5:0] */.sel (sel ),
/*output [7:0] */.dig (dig ),
/*output [3:0] */.led (led ),
/*output */.beep (beep )
);
//产生时钟
initial tb_clk = 1'b0;
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;
//产生激励
initial begin
tb_rst_n = 1'b0;
key_in = 3'b000;//按键初始化
#(CLOCK_CYCLE*20);
tb_rst_n = 1'b1;
key_in[2] = 1;
#40
key_in[2] = 0;
#100
//s1
key_in[1] = 1;
#40
key_in[1] = 0;
#100
key_in[2] = 1;
#40
key_in[2] = 0;
#100
//s2
key_in[0] = 1;
#40
key_in[0] = 0;
#100
key_in[2] = 1;
#40
key_in[2] = 0;
#100
//s3
key_in[2] = 1;
#40
key_in[2] = 0;
#100
//s4
key_in[1] = 1;
#40
key_in[1] = 0;
#100
key_in[1] = 1;
#40
key_in[1] = 0;
#100
key_in[2] = 1;
#40
key_in[2] = 0;
#100
//s5
key_in[1] = 1;
#40
key_in[1] = 0;
#100
key_in[2] = 1;
#40
key_in[2] = 0;
#100
//s6
key_in[0] = 1;
#40
key_in[0] = 0;
#100
key_in[2] = 1;
#40
key_in[2] = 0;
#5000
$stop;
end
endmodule
这仿真就是模拟运行按下按键,模拟结果。
总结
仿真的时候要把顶层里的按键消抖模块注释掉,而且把lock中的key_down改成key_in才能仿真成功。
仿真正确,去看一看就明白了。重点是上板效果:
FPGA实战--数码管设计数字密码锁+报警
这个视频讲的也挺详细了,代码里lock模块的注释也写的非常清楚,再有不会的评论区+私信。
博客上传的工程都在百度网盘,有压缩包也有整个文件,可以自行下载。
链接:https://pan.baidu.com/s/1lQqqWZXfb3i6XHwkKf52zg
提取码:yang
密码锁整完了,还有售货机,闹钟,马上更。