FPGA学习——数字密码锁(上)

一、简介

本文篇幅可能有些长,请耐心阅读
本数字密码锁采用矩阵键盘作为十六位按键输入,其中,十位数字键,六位功能按键。密码锁具有清零,密码重置,错误警报等多项功能,采用数码管动态显示的方法显示信息。

1.1设备要求

1、FPGA开发板一块(需要有至少四位数码管)
2、矩阵键盘一块
3、下载线,电源线等以及杜邦线若干

1.2 功能要求

六位功能按键:

  1. on:密码锁开关,打开初始值显示“0000”
  2. off:密码锁关闭按键
  3. clear:清零按键,输入数字错误后按下此键清零
  4. lock:密码上锁,输入想要的密码按下此键,即可锁定
  5. reset:密码重置,变更前需核对密码是否正确,在开锁状态下,按下此键后输入新密码,再按下lock键上锁
  6. check:输入密码按下此键,如正确即可开锁
    其他要求:
    1.当连续三次输入错误密码时,系统在第四次输入密码时发出警报
    2.输入新数字显示在数码管最右端,前一个数字依次往左移动

二、工程分析:

首先,作为一个功能较多的工程而言,其内在逻辑必然十分复杂。所以在建立工程时首先考虑进行模块划分,将整个工程分为几个模块,尽量降低每个模块之间的耦合性,提高模块的独立性,这样才会使逻辑更加清晰,程序的可读性更好,以及更加便于修改。
本密码锁具体分为四个模块:
1、矩阵键盘扫描模块:要求将矩阵键盘的十六个值分别进行输出
2、按键功能模块:对于矩阵键盘输出的十六个值进行划分,分别为十位数字键以及六位功能按键并对下一个模块进行输出
3、中心处理模块:对于各个按键值接受以后,按照各个按键功能进行相应的信号处理,最后将其结果输出给下一个模块
4、结果显示模块:接收上一个模块的结果,并对结果进行数码管的显示

三、具体程序:

顶层模块:

按照划分的几个模块进行顶层模块的编写,顶层模块的编写放到最后,需要根据各个模块的信号接口进行连接。


module top_lock(clk,dxuan,wxuan,led,beep,row,col);
input            clk;
input   [3:0]    row;
output        beep;
output  [3:0]   col;
output        led;
output  [5:0] wxuan;
output  [7:0] dxuan;

wire [4:0] key_out;
wire [8:0] number_key;
wire [3:0] row;
wire [3:0] cow;
wire lock;
wire check;
wire reset;
wire clear_flag;
wire state_lock;
wire [3:0]rece_cnt;
wire       rece_flag;

wire [3:0] disp_in1;
wire [3:0] disp_in2;
wire [3:0] disp_in3;
wire [3:0] disp_in4;

key_board key_board1(
          .clk     (clk    ),
          .row   (row  ),
          .col   (col  ),
          .key_out (key_out)
			 );
			 
key_function key_function1(
             .clk         (  clk  ),
			 .key_out     (key_out),
			 
			 .state_lock  (state_lock),
			 .clear_flag  (clear_flag),
			 .number_key  (number_key),
			 .lock      (lock),
			 .reset       (reset),
			 .check     (check),
			 .rece_cnt    (rece_cnt),
			 .rece_flag   (rece_flag)
			 );
			 
lock_ctrl   lock_ctrl1(
              .clk         (clk),
				  .check     (check),
				  .state_lock   (state_lock),
			      .clear_flag       (clear_flag),
			      .lock      (lock),
				  .reset       (reset),
				  .rece_cnt    (rece_cnt),
				  .rece_flag   (rece_flag),
				  .number_key  (number_key),
				  
				  .led         (led),
				  .beep        (beep),
				  .disp_in1  (disp_in1),
				  .disp_in2  (disp_in2),
				  .disp_in3  (disp_in3),
				  .disp_in4  (disp_in4)
				  );
				  
display   display1(
               .clk     (clk),
					.rece_cnt  (rece_cnt),
					.disp_in1  (disp_in1),
					.disp_in2  (disp_in2),
					.disp_in3  (disp_in3),
					.disp_in4  (disp_in4)
					.state_lock   (state_lock),
					.dxuan    (dxuan),
					.wxuan    (wxuan)
					);
 
 endmodule

1、矩阵键盘扫描模块

关于矩阵键盘的原理,在上一篇中详细写过,在这里直接调用。上一篇的地址:
https://blog.csdn.net/zsisinterested/article/details/117195234

module key_board(
  input            clk,
  input      [3:0] row,                 // 矩阵键盘 行
  output reg [3:0] col,                 // 矩阵键盘 列
  output reg [3:0] key_out
);
 
reg [19:0] cnt;    //计数器
reg       key_pressed_flag;             // 键盘按下标志
reg [3:0] col_val, row_val;             // 列行值寄存器

reg key_clk;  //分频脉冲 

//***************分频*******************// 
always @ (posedge clk)
 begin 
	if(cnt<=20'd1048_500)  //分频21ms
    cnt <= cnt + 1'b1;
	else begin
		cnt<=0;
		key_clk<=~key_clk;
		end
 end
//*******************状态机定义*********************//

parameter IDLE				 = 6'b000_001;  // 没有按键按下  
parameter SCAN_COL0      = 6'b000_010;  // 扫描第0列 
parameter SCAN_COL1      = 6'b000_100;  // 扫描第1列 
parameter SCAN_COL2      = 6'b001_000;  // 扫描第2列 
parameter SCAN_COL3      = 6'b010_000;  // 扫描第3列 
parameter KEY_PRESSED    = 6'b100_000;  // 有按键按下
 
reg [5:0] current_state, next_state;    // 现态、次态
 
always @ (posedge key_clk)
    current_state <= next_state;
 
//************状态机切换************************//
always @ *
  case (current_state)
    IDLE :                    // 没有按键按下
        if (row != 4'hF)
          next_state = SCAN_COL0;
        else
          next_state = IDLE;
    SCAN_COL0 :                         // 扫描第0列 
        if (row != 4'hF)
          next_state = KEY_PRESSED;
        else
          next_state = SCAN_COL1;
    SCAN_COL1 :                         // 扫描第1列 
        if (row != 4'hF)
          next_state = KEY_PRESSED;
        else
          next_state = SCAN_COL2;    
    SCAN_COL2 :                         // 扫描第2列
        if (row != 4'hF)
          next_state = KEY_PRESSED;
        else
          next_state = SCAN_COL3;
    SCAN_COL3 :                         // 扫描第3列
        if (row != 4'hF)
          next_state = KEY_PRESSED;
        else
          next_state = IDLE;
    KEY_PRESSED :                       // 有按键按下
        if (row != 4'hF)
          next_state = KEY_PRESSED;
        else
          next_state = IDLE;                      
  endcase
  
//***************赋列值***********************//
always @ (posedge key_clk)
    case (next_state)
      IDLE		 :                  // 没有按键按下
      begin
        col              <= 4'h0;
        key_pressed_flag <=    0;       // 清键盘按下标志
      end
      SCAN_COL0 :                       // 扫描第0列
        col <= 4'b1110;
      SCAN_COL1 :                       // 扫描第1列
        col <= 4'b1101;
      SCAN_COL2 :                       // 扫描第2列
        col <= 4'b1011;
      SCAN_COL3 :                       // 扫描第3列
        col <= 4'b0111;
      KEY_PRESSED :                     // 有按键按下
      begin
        col_val          <= col;        // 锁存列值
        row_val          <= row;        // 锁存行值
        key_pressed_flag <= 1;          // 置键盘按下标志  
      end
    endcase
 
//********************键值输出*************************//
always @ (posedge key_clk)
    if (key_pressed_flag)
      case ({
    
    col_val, row_val})
        8'b1110_1110 : key_out <= 4'h0;
        8'b1110_1101 : key_out <= 4'h4;
        8'b1110_1011 : key_out <= 4'h8;
        8'b1110_0111 : key_out <= 4'hC;
        
        8'b1101_1110 : key_out <= 4'h1;
        8'b1101_1101 : key_out <= 4'h5;
        8'b1101_1011 : key_out <= 4'h9;
        8'b1101_0111 : key_out <= 4'hD;
        
        8'b1011_1110 : key_out <= 4'h2;
        8'b1011_1101 : key_out <= 4'h6;
        8'b1011_1011 : key_out <= 4'hA;
        8'b1011_0111 : key_out <= 4'hE;
        
        8'b0111_1110 : key_out <= 4'h3; 
        8'b0111_1101 : key_out <= 4'h7;
        8'b0111_1011 : key_out <= 4'hB;
        8'b0111_0111 : key_out <= 4'hF;        
      endcase
		
endmodule

2、功能划分模块

首先是根据模块功能来确定该模块的输入输出口。本模块用于对按键的功能进行划分,那么输入一定是来自矩阵键盘模块。
输入:

input        clk;
input  [4:0] key_out;

对十六位输入进行功能的划分,最终的输出为六位功能键以及一位数字键。
输出:

output reg [3:0] number_key; //密码数字(可在0-9切换)
output reg  state_lock;  //密码锁状态(开机或关机,包括了on和off)
output  reg  lock;  //密码锁定
output  reg  reset;  //密码重置
output  reg  check;   //密码确认
output  reg  clear_flag;  //清除输入

除了正常的功能输出外,还需要输出其他信号辅助其他模块进行处理:

output  reg [3:0] rece_cnt;  //接收密码位数
output  reg rece_flag;   //接收标志

完成了接口上的定义以后,接着就是定义在该模块中用到的各种变量。待变量定义结束,开始做逻辑上的编写。对于接收到的十六位按键数值,用case语句将数值进行划分,按照键盘的排列顺序将左上角的九位按键分别定义为数值按键,最后一列第二行的按键定义为数值位0的按键,其余按键为功能按键
当按键被松开,即键值输出不在复制范围,就将所有的功能按键复位,即使用功能键的上升沿进行触发后续信号

always@(posedge clk)
  begin
    case(key_out)
	 5'd 0:off<=1;
	 5'd 1: on<=1;
	 5'd 2:begin number_key<=4'd0;rece_flag<=1; end
	 5'd 3:clear<=1;
	 5'd 4:check<=1;
	 5'd 5:begin number_key<=4'd9;rece_flag<=1; end
	 5'd 6:begin number_key<=4'd8;rece_flag<=1; end
	 5'd 7:begin number_key<=4'd7;rece_flag<=1; end
	 5'd 8:lock<=1;
	 5'd 9:begin number_key<=4'd6;rece_flag<=1; end
	 5'd10:begin number_key<=4'd5;rece_flag<=1; end
	 5'd11:begin number_key<=4'd4;rece_flag<=1; end
	 5'd12:reset<=1;
	 5'd13:begin number_key<=4'd3;rece_flag<=1; end
	 5'd14:begin number_key<=4'd2;rece_flag<=1; end
	 5'd15:begin number_key<=4'd1;rece_flag<=1; end
	 default:begin
	       rece_flag=0;
			 if(clear_flag==1)  number_key<=4'd0;
	       else number_key<=number_key;
			 off<=0;
			 on<=0;
			 clear<=0;
			 check<=0;
			 lock<=1;
			 reset<=0;
			 end
	 endcase
  end

键值按功能划分共可分出七个变量,其中包括六个功能变量以及一个数值变量。但是可以看出来几个变量中有些是相关的,比如on与off,当on有效时off就无效,所以我们考虑用一个变量来代替几个互相关联的变量:
on与off用state_lock来代替,state_lock=1时密码锁打开,否则关闭

//开关控制
always@(posedge clk)       
  begin
    if(on==1) state_lock<=1;
	 else if(off==1) state_lock<=0;
	 else state_lock<=state_lock;
  end			

清零信号的控制
由于清零信号收到清零按键clear以及开关机的控制,所以需要对清零信号进行进一步的设置

//清零标志控制
always@(posedge clk)   
  begin
    if(state_lock==0)   //当关机时,清零
	            clear_flag<=1;
    else if(clear==1)  
	           clear_flag<=1;  //按下清零按键时,清零
    else 
	           clear_flag<=0;  //其余时间不清零
  end

密码锁存在一个隐性要求就是,当输入四位数字以后再输入第五位数字时密码不在发生变化,因此要规定输入数字的位数(输入数字位数也会受到清零信号以及开关机的制约)

 //***********输入密码位数变换控制*************//
always@(posedge rece_flag or posedge clear_flag)
 begin
	   if(clear_flag==1)  rece_cnt<=4'd0;
	   else begin
           if(state_lock==1) begin 
	         if(rece_cnt==4'd5)  rece_cnt<=rece_cnt;
		      else if(rece_cnt==4'd4) rece_cnt<=4'd5;
		      else rece_cnt<=rece_cnt+4'd1;
		      end
	   else rece_cnt<=4'd0;
		end
 end	

下面为该模块完整代码:

module key_function(clk,key_out,state_lock,number_key,
                    lock,reset,rece_flag,
						  check,rece_cnt,clear_flag);
input        clk;
input  [4:0] key_out;

output reg [3:0] number_key;
output reg  state_lock;
output  reg  clear_flag;
output  reg  lock;
output  reg  reset;
output  reg  check;
output  reg [3:0] rece_cnt;
output  reg rece_flag;

reg on;
reg off;
reg clear;

initial 
begin
rece_cnt=4'd0;
end

always@(posedge clk)
  begin
    case(key_out)
	 5'd 0:off<=1;
	 5'd 1: on<=1;
	 5'd 2:begin number_key<=4'd0;rece_flag<=1; end
	 5'd 3:clear<=1;
	 5'd 4:confirm<=1;
	 5'd 5:begin number_key<=4'd9;rece_flag<=1; end
	 5'd 6:begin number_key<=4'd8;rece_flag<=1; end
	 5'd 7:begin number_key<=4'd7;rece_flag<=1; end
	 5'd 8:c_lock<=1;
	 5'd 9:begin number_key<=4'd6;rece_flag<=1; end
	 5'd10:begin number_key<=4'd5;rece_flag<=1; end
	 5'd11:begin number_key<=4'd4;rece_flag<=1; end
	 5'd12:reset<=1;
	 5'd13:begin number_key<=4'd3;rece_flag<=1; end
	 5'd14:begin number_key<=4'd2;rece_flag<=1; end
	 5'd15:begin number_key<=4'd1;rece_flag<=1; end
	 default:begin
	       rece_flag=0;
			 if(clear_flag==1)  number_key<=4'd0;
	       else number_key<=number_key;
			 off<=0;
			 on<=0;
			 clear<=0;
			 confirm<=0;
			 c_lock<=1;
			 reset<=0;
			 end
	 endcase
  end
  
//开关控制
always@(posedge clk)       
  begin
    if(on==1) state_lock<=1;
	 else if(off==1) state_lock<=0;
	 else state_lock<=state_lock;
  end			
//清零标志控制
always@(posedge clk)   
  begin
    if(state_lock==0)  
	            clear_flag<=1;
    else if(clear==1)  
	           clear_flag<=1;
    else 
	           clear_flag<=0;
  end
 
 //***********输入密码位数变换控制*************//
always@(posedge rece_flag or posedge clear_flag)
 begin
	   if(clear_flag==1) begin
		     rece_cnt<=4'd0;
			  end
		else begin
        if(state_lock==1) begin 
	         if(rece_cnt==4'd5)  rece_cnt<=rece_cnt;
		      else if(rece_cnt==4'd4) rece_cnt<=4'd5;
		      else rece_cnt<=rece_cnt+4'd1;
		      end
	     else rece_cnt<=4'd0;
		end
 end	


endmodule

第三、第四模块在下篇中

四、总结:

由于该工程涉及到的逻辑信号太多了,脑子一时转不过来就可能会出现很多的BUG,所以一旦发现有错误会及时更改,也请各位不吝赐教!
剩余模块在下一篇中写出!
地址:数字密码锁(下)

猜你喜欢

转载自blog.csdn.net/zsisinterested/article/details/117558812