FPGA实战-----数码管设置数字密码锁+报警(1)

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_1value_6分别代表六位密码。按下key0就加一,按下key1就减一,
每键入一位密码都会被记录下来。
六位全部键入完毕,就用“{}”符号将六位数字拼接起来变成key_vlaue
在s6状态跳转到接下来的状态时让key_vlauePASSWORD = 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

密码锁整完了,还有售货机,闹钟,马上更。

、状态显示功能: 锁定状态时系统用3位数码管显示OFF,用3位数码管显示成功开锁次数;成功开锁时用3位数码管显示888,用3位数码管显示成功开锁次数。 2、密码设定功能: 通过一个4×4的矩阵式键盘可以任意设置用户密码(1-16位长度),同时系统掉电后能自动记忆和存储密码在系统中。 3、报警和加锁功能: 密码的输入时间超过12秒或者连续3次输入失败,声音报警同时锁定系统,不让再次输入密码。此时只有管理员方能对系统解锁。 设计电路思路描述:本电路分为四部分组成:主程序部分,4×4矩阵键盘部分,6位数码管串口静态显示部分,24c02读写部分。 主程序部分主要分两方面:一、用户模式密码输入,密码比较,开锁,报警,修改密码;二、管理员模式密码比较,取消锁定键盘,报警,修改密码,清除开锁次数。 电路操作描述:上电时6位数码管前三位显示0FF,后三位显示开锁成功次数。 指示灯L1亮,等待输入用户密码或者按下管理员模式键输入管理员密码。如果输入用户密码正确,成功开锁,6位数码管前三位显示888,后三位显示成功开锁次数,指示灯L1灭,L2亮,并且开锁信号输出,用于控制电路开锁电路,成功开锁后,如果开锁次数到100此时,将锁定电路,如果按下密码修改键那么进入密码修改模式,输入0到16位密码,确认后等待退出键按下。 如果用户输入密码错误或12秒未完成输入,那么系统进入第一次报警,6位数码管显示NONONO,声光报警,三秒后,从新回到开锁前状态,如果输入错误次数到3次,那么锁定键盘,只有按下管理员模式键,输入正确的管理员密码打开键盘。 在管理员模式下,按下修改键可以修改管理员密码,按下清零键可以清除成功开锁次数。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值