明德扬吴老师 发表于 2020-9-21 10:20:27

【每周FPGA案例】电子密码锁

【上板现象】

电子密码锁的在MP801的上板现象
https://www.bilibili.com/video/BV1Af4y117H4?p=43

电子密码锁的在点拨开发板的上板现象

https://www.bilibili.com/video/BV1Af4y117H4?p=42

电子密码锁的在实验箱的上板现象

https://www.bilibili.com/video/BV1Af4y117H4?p=44


【设计教程】


至简设计系列_电子密码锁
--作者:肖肖肖--案例作者:WB_Yih
本文为明德扬原创及录用文章,转载请注明出处!1.1 总体设计


1.1.1 概述随着生活质量的不断提高,加强家庭防盗安全变得非常重要,但传统机械锁的构造过于简单,很容易被打开,从而降低了安全性。数字密码锁因为它的保密性很高,安全系数也非常高,再加上其不需要携带避免了丢失的可能,省去了因钥匙丢失而需要换锁的麻烦,受到了越来越多的人的欢迎。随看人们对高科技产品也越来越推崇,在当今社会科技的高度集中和创新,人们对日常生活中保护自身及财产安全的物品非常追捧,对其安全性的要求也非常的高。为了达到人们对锁具安全性的高要求,加强锁具的安全保密性,用密码锁来取代传统机械锁的锁具是必然趋势。数字密码锁比传统机械锁具更加的安全。在本案例的设计过程中,应用了至简设计法、状态机模板应用等,在经过逐步改进、调试等一系列工作之后,最终达到了设计目标。
基于明德扬至简设计法和明德扬设计规范,设计一个基于FPGA的密码锁、并将数值显示在数码管上,然后根据输入的键值判断密码是否正确。
1.1.2 设计目标
实现电子密码锁的功能,具体功能要求如下:1.       密码4位,初始密码2345。2.       密码锁状态:LOCKED和OPEN,初始状态为LOCKED。1)       当在LOCKED状态时,连续两次输入正确密码,状态变为OPEN状态。当输入错误密码时(包括第一次就输入错误;或者第一次输入正确,第二次输入错误的情况),数码管显示ERROR2秒后重新显示原来的状态(LOCKED)。2)       当在OPEN状态时,一次输入错误密码,状态变为LOCKED状态。当输入正确密码时,数码管无显示,10秒后重新显示原来的状态(OPEN)。3)       不管在何状态,当输入4位密码或者某几位密码,但未按下确认键,并超过10S时,返回原来的状态。(即输入密码超时,返回原状态)
对于点拨开发板,使用矩阵按键输入(本文以点拨603开发板为例)。对于Mp801开发板,密码显示及确认:无论在OPEN,还是LOCKED状态下,均可以通过拨码开关输入密码。当有拨码开关拨动时,数码管当前显示的OPEN或LOCKED消失,并显示当前输入的密码,暂未输入的密码位不显示。4位密码输入完毕后,再拨动拨码开关时视为无效输入,当前显示的密码不改变。4位密码输入完毕后,按下确认键后,系统判断密码是否正确。
拨码开关及按键:初始状态下,拨码开关全部往下拨。当拨码开关向上拨后,再向下拨(回到初始状态),表示一个数字的有效输入。按键每按下一次(会自动弹起),为一次有效输入(复位/确认)。


1.1.3 系统结构框图
系统结构框图如下图一所示:
图一1.1.4模块功能按键检测模块实现功能
1、检测按键的数值

控制模块实现功能
1、对接收到的按键数值进行判断和控制对应的密码锁状态,实现对输入密码的正误判断和对密码锁的开启和闭合控制。
数码管显示模块实现功能
1、显示输入的密码数值;2、显示当前密码锁的状态(开启状态或者闭锁状态);3、提示密码输入错误的状态。
1.1.5顶层信号

信号名I/O位宽定义
clkI1系统工作时钟 50M
rst_nI1系统复位信号,低电平有效
key_colI4矩阵键盘列信号
key_rowO4矩阵键盘行信号
seg_selO66位数码管位选信号
segmentO88位数码管段选信号


1.1.6参考代码
下面是使用工程的顶层代码:module top_mdyPwdlock_keyscan(
    clk             ,   
    rst_n         ,   

    key_col         ,
    key_row         ,

    seg_sel         ,
    segment            
   
    );

    input               clk               ;
    input               rst_n               ;
    input          key_col             ;

    output         seg_sel             ;
    output         segment             ;
    output         key_row             ;

    wire         seg_sel             ;
    wire         segment             ;
    wire         key_row             ;

    wire         key_out             ;
    wire                key_vld             ;
    wire   seg_dout            ;
    wire         seg_dout_vld      ;

   
   
    key_scan u_key_scan(
      .clk                (clk         ),      
      .rst_n            (rst_n         ),   
      .key_col            (key_col       ),
      .key_row            (key_row       ),
      .key_out            (key_out       ),
      .key_vld            (key_vld       )
    );


    control u_ctrl(
      .clk                (clk            ),      
      .rst_n            (rst_n          ),      
                                    
      .key_num            (key_out      ),      
      .key_vld            (key_vld      ),      
                                    
      .seg_dout         (seg_dout       ),      
      .seg_dout_vld       (seg_dout_vld   )      
    );

    seg_display u_segment(
      .clk                (clk            ),      
      .rst_n            (rst_n          ),      

      .din                (seg_dout       ),      
      .din_vld            (seg_dout_vld   ),      

      .segment            (segment      ),      
      .seg_sel            (seg_sel      )      
    );

    endmodule


1.2 按键检测模块设计1.2.1接口信号
信号名I/O位宽定义
clkI1系统工作时钟 50M
rst_nI1系统复位信号,低电平有效
key_colI4矩阵按键列信号
key_rowO4矩阵按键行信号
key_outO4输出的按键有效数值
key_vldO1按键有效指示信号

1.2.2 设计思路
在前面的案例中已经有矩阵按键检测模块的介绍,所以这里不在过多介绍,详细介绍请看下方链接:http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=310&highlight=%BE%D8%D5%F3其中,按键的功能面板如下图所示:

1.2.3参考代码modulekey_scan(
               clk    ,
               rst_n,
               key_col,
               key_row,
               key_out,
               key_vld   
               );

    parameter      KEY_W=         4 ;
    parameter      CHK_COL=   0 ;
    parameter      CHK_ROW=   1 ;
    parameter      DELAY    =   2 ;
    parameter      WAIT_END =   3 ;
    parameter      COL_CNT=   16;
    parameter      TIME_20MS=   1000000;

    input               clk    ;
    input               rst_n;
    input      key_col;

    output            key_vld;
    output         key_out;
    output   key_row;

    reg            key_out;
    reg      key_row;
    reg               key_vld;


    reg          key_col_ff0;
    reg          key_col_ff1;
    reg          key_col_get;
    wire                shake_flag ;
    reg               shake_flag_ff0;
    reg            state_c;
    reg           shake_cnt;
    reg            state_n;
    reg          row_index;
    reg         row_cnt;
    wire                chk_col2chk_row ;
    wire                chk_row2delay   ;
    wire                delay2wait_end;
    wire                wait_end2chk_col;


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_col_ff0 <= 4'b1111;
      key_col_ff1 <= 4'b1111;
    end
    else begin
      key_col_ff0 <= key_col    ;
      key_col_ff1 <= key_col_ff0;
    end
end


wire      add_shake_cnt ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      shake_cnt <= 0;
    end
    else if(add_shake_cnt) begin
      if(shake_flag)
            shake_cnt <= 0;
      else
            shake_cnt <= shake_cnt+1 ;
   end
end
assign add_shake_cnt = key_col_ff1!=4'hf;
assign shake_flag = add_shake_cnt&& shake_cnt == TIME_20MS-1 ;

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(shake_flag && shake_flag_ff0==1'b0)begin
                         state_n = CHK_ROW;
                     end
                     else begin
                         state_n = CHK_COL;
                     end
               end
      CHK_ROW: begin
                     if(row_index==3 && row_cnt==0)begin
                         state_n = DELAY;
                     end
                     else begin
                         state_n = CHK_ROW;
                     end
               end
      DELAY :begin
                     if(row_cnt==0)begin
                         state_n = WAIT_END;
                     end
                     else begin
                         state_n = DELAY;
                     end
               end
      WAIT_END: begin
                     if(key_col_ff1==4'hf)begin
                         state_n = CHK_COL;
                     end
                     else begin
                         state_n = WAIT_END;
                     end
                  end
       default: state_n = CHK_COL;
    endcase
end

assign chk_col2chk_row = shake_flag && shake_flag_ff0 ==1'b0;
assign chk_row2delay   = row_index==3 && row_cnt==0;
assign delay2wait_end= row_cnt==0;
assign wait_end2chk_col= key_col_ff1==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

always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      row_index <= 0;
    end
    else if(state_c==CHK_ROW)begin
       if(row_cnt==0)begin
         if(row_index==3)
               row_index <= 0;
         else
               row_index <= row_index + 1;
       end
    end
    else begin
      row_index <= 0;
    end
end


wire      add_row_cnt ;
wire      end_row_cnt ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      row_cnt <= COL_CNT;
    end
    else if(add_row_cnt) begin
      if(end_row_cnt)
            row_cnt <= COL_CNT;
      else
            row_cnt <= row_cnt-1 ;
   end
   else begin
       row_cnt <= COL_CNT;
   end
end
assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
assign end_row_cnt = add_row_cnt&& row_cnt == 0 ;



always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      shake_flag_ff0 <= 1'b0;
    end
    else begin
      shake_flag_ff0 <= shake_flag;
    end
end

always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_col_get <= 0;
    end
    else if(state_c==CHK_COL && shake_flag==1'b1 && shake_flag_ff0==1'b0) begin
      if(key_col_ff1==4'b1110)
            key_col_get <= 0;
      else if(key_col_ff1==4'b1101)
            key_col_get <= 1;
      else if(key_col_ff1==4'b1011)
            key_col_get <= 2;
      else
            key_col_get <= 3;
    end
end


always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_out <= 0;
    end
    else if(state_c==CHK_ROW && row_cnt==0)begin
      key_out <= {row_index,key_col_get};
    end
    else begin
      key_out <= 0;
    end
end

always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      key_vld <= 1'b0;
    end
    else if(state_c==CHK_ROW && row_cnt==0 && key_col_ff1==1'b0)begin
      key_vld <= 1'b1;
    end
    else begin
      key_vld <= 1'b0;
    end
end

endmodue





1.3 控制模块设计1.3.1接口信号
信号名I/O位宽定义
clkI1系统工作时钟 50M
rst_nI1系统复位信号,低电平有效
key_numI4输入的按键号
key_vldI1按键有效指示信号
seg_doutO3030bit的数码管显示数据,每5bit为一个字符(对应一个数码管),一共表示6个数码管的显示数据。
seg_dout_vldO6数码管显示数据有效指示信号,seg_dout_vld 为1时,seg_dout有效;seg_dout_vld 为1时,seg_dout有效,以此为推。


1.3.2设计思路
Ø状态机架构本模块的主要功能是根据输入的按键信息进行不同状态的判断和切换当前工作状态。根据项目功能要求,一共有四种工作状态:密码锁开启状态(open)、密码锁闭合状态(clocked)、输入密码状态(password)和提示输入错误状态(error)。以下为本模块的状态跳转图:
复位后,状态机进入LOCKED的状态,即初始状态为LOCKED;在LOCKED状态下:A.有按键按下,跳到PASSWORD状态;B.否则,保持LOCKED状态不变;在OPEN状态下:A.有按键按下,跳到PASSWORD状态;B.否则,保持OPEN状态不变;在PASSWORD状态下:A.有密码输入但超过10秒没有确认,跳到原来的LOCKED状态或者OPEN状态;B.密码正确输入并确认两次,跳到OPEN状态;C.密码错误输入并确认,跳到ERROR状态;D.否则,保持PASSWORD状态不变;在ERROR状态下:A.提示输入错误2秒,跳到LOCKED状态;B.否则,保持ERROR状态不变;无论当前处于什么状态,只要不满足状态间的跳转条件就跳到LOCKED状态。Ø计数器架构本模块的某些状态跳转之间存在一定的时间间隔,根据项目功能要求,一共有两种时间的间隔:10秒的等待输入时间间隔和2秒的显示提示时间间隔。以下为计数器的架构示意图:


10秒计数器cnt_10s_nvld:用于计算10秒的时间。加一条件为state_c==PASSWORD,表示进入密码输入状态就开始计数。结束条件为数500_000_000个,系统时钟为50M,一个时钟周期为20ns,500_000_000个时钟周期就是10秒。2秒计数器cnt_2s:用于计算2秒的时间。加一条件为state_c==ERROR,表示进入提示输入错误状态就开始计数。结束条件为数100_000_000个,系统时钟为50M,一个时钟周期为20ns,100_000_000个时钟周期就是2秒。



1.3.3参考代码
module control(
    clk             ,
    rst_n         ,

    key_num         ,
    key_vld         ,

    seg_dout      ,
    seg_dout_vld   
   
    );

    parameter PASSWORD_INI   = 16'h2345    ;
    parameter CHAR_O         = 5'h10       ;
    parameter CHAR_P         = 5'h11       ;
    parameter CHAR_E         = 5'h12       ;
    parameter CHAR_N         = 5'h13       ;
    parameter CHAR_L         = 5'h14       ;
    parameter CHAR_C         = 5'h15       ;
    parameter CHAR_K         = 5'h16       ;
    parameter CHAR_D         = 5'h17       ;
    parameter CHAR_R         = 5'h18       ;
    parameter NONE_DIS         = 5'h1F       ;

    parameter C_10S_WID      = 29          ;
    parameter C_10S_NUM      = 500_000_000 ;
    parameter C_2S_WID         = 27          ;
    parameter C_2S_NUM         = 100_000_000 ;
    parameter C_PWD_WID      = 3         ;

    input               clk               ;
    input               rst_n               ;
    input          key_num             ;
    input               key_vld             ;

    output   seg_dout            ;
    output         seg_dout_vld      ;

    reg      seg_dout            ;
    wire         seg_dout_vld      ;

    reg            state_c             ;
    reg            state_n             ;
    reg               lock_stata_flag   ;
    reg               password_correct_twice;
   
    reg       cnt_2s          ;
    reg      cnt_10s_nvld    ;
    reg      cnt_password    ;

    reg               password      ;

   
    parameter LOCKED    = 2'b00             ;
    parameter OPEN      = 2'b01             ;
    parameter PASSWORD= 2'b10             ;
    parameter ERROR   = 2'b11             ;

    //current state
    always@(posedge clk or negedge rst_n)begin
      if(!rst_n)begin
            state_c <= LOCKED;
      end
      else begin
            state_c <= state_n;
      end
    end

    //next state and the condition of state LOCKEDtransition
    always@(*)begin
      case(state_c)
            LOCKED:begin
                if(locked2password_switch)begin
                  state_n = PASSWORD;
                end
                else begin
                  state_n = state_c;
                end
            end
            OPEN:begin
                if(open2password_switch)begin
                  state_n = PASSWORD;
                end
                else begin
                  state_n = state_c;
                end
            end            
            PASSWORD:begin
                if(password2locked_switch0)begin
                  state_n = LOCKED;
                end
                else if(password2open_switch0 || password2open_switch1)begin
                  state_n = OPEN;
                end
                else if(password2error_switch || password2locked_switch1)begin
                  state_n = ERROR;
                end
                else begin
                  state_n = state_c;
                end
            end
            ERROR:begin
                if(error2locked_switch0 )begin
                  state_n = LOCKED;
                end
                else begin
                  state_n = state_c;
                end
            end
            default:begin
                state_n = LOCKED;
            end
      endcase
    end
    assign locked2password_switch    = state_c==LOCKED   &&lock_stata_flag && key_num<10 && key_vld;
    assign open2password_switch      = state_c==OPEN   && !lock_stata_flag && key_num<10 && key_vld;
    assign password2locked_switch0   = state_c==PASSWORD &&lock_stata_flag && end_cnt_10s_nvld;
    assign password2locked_switch1   = state_c==PASSWORD &&lock_stata_flag && confirm && password!=PASSWORD_INI ;//TO ERROR
    assign password2open_switch0   = state_c==PASSWORD &&lock_stata_flag && confirm && password==PASSWORD_INI &&password_correct_twice;
    assign password2open_switch1   = state_c==PASSWORD && !lock_stata_flag && end_cnt_10s_nvld;
    assign password2error_switch   = state_c==PASSWORD && !lock_stata_flag && confirm && password!=PASSWORD_INI;
    assign error2locked_switch0      = state_c==ERROR    &&end_cnt_2s;


    //lock_stata_flag
    always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
            lock_stata_flag <= 1;
      end
      else if(password2locked_switch0 || password2locked_switch1 || error2locked_switch0)begin
            lock_stata_flag <= 1;
      end
      else if(password2open_switch0 || password2open_switch1 )begin
            lock_stata_flag <= 0;
      end
    end

    //cnt_10s_nvld
    always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
            cnt_10s_nvld <= 0;
      end
      else if(end_cnt_10s_nvld)begin
            cnt_10s_nvld <= 0;
      end
      else if(add_cnt_10s_nvld)begin
            cnt_10s_nvld <= cnt_10s_nvld + 1;
      end
    end
    assign add_cnt_10s_nvld = state_c==PASSWORD;
    assign end_cnt_10s_nvld = add_cnt_10s_nvld && cnt_10s_nvld==C_10S_NUM-1;

    //confirm
    assign confirm = key_num==10 && key_vld;

    //password_correct_twice   
    always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
            password_correct_twice <= 0;
      end
      else if(state_c==PASSWORD && lock_stata_flag && confirm && password==PASSWORD_INI && !password_correct_twice)begin
            password_correct_twice <= 1;
      end
      else if(password2locked_switch0 || password2locked_switch1 || password2open_switch0 || password2open_switch1 || password2error_switch)begin
            password_correct_twice <= 0;
      end
    end

    //cnt_2s
    always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
            cnt_2s <= 0;
      end
      else if(end_cnt_2s )begin
            cnt_2s <= 0;
      end
      else if(add_cnt_2s )begin
            cnt_2s <= cnt_2s + 1;
      end
    end
    assign add_cnt_2s = state_c==ERROR;
    assign end_cnt_2s = add_cnt_2s && cnt_2s==C_2S_NUM-1;



    //seg_dout
    always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
            seg_dout <= 0;
      end
      else if(state_c==OPEN)begin
            seg_dout <= {NONE_DIS,NONE_DIS,CHAR_O,CHAR_P,CHAR_E,CHAR_N};
      end
      else if(state_c==LOCKED)begin
            seg_dout <= {CHAR_L,CHAR_O,CHAR_C,CHAR_K,CHAR_E,CHAR_D};
      end
      else if(state_c==ERROR)begin
            seg_dout <= {NONE_DIS,CHAR_E,CHAR_R,CHAR_R,CHAR_O,CHAR_R};
      end
      else if(state_c==PASSWORD)begin
            if(cnt_password==0)
                seg_dout <= {NONE_DIS,NONE_DIS,NONE_DIS,NONE_DIS,NONE_DIS,NONE_DIS};
            else if(cnt_password==1)
                seg_dout <= {NONE_DIS,NONE_DIS,NONE_DIS,NONE_DIS,NONE_DIS,{1'b0,password}};
            else if(cnt_password==2)
                seg_dout <= {NONE_DIS,NONE_DIS,NONE_DIS,NONE_DIS,{1'b0,password},{1'b0,password}};
            else if(cnt_password==3)
                seg_dout <= {NONE_DIS,NONE_DIS,NONE_DIS,{1'b0,password},{1'b0,password},{1'b0,password}};
            else if(cnt_password==4)
                seg_dout <= {NONE_DIS,NONE_DIS,{1'b0,password},{1'b0,password},{1'b0,password},{1'b0,password}};
      end
    end
   
    //seg_dout_vld
    assign seg_dout_vld = 6'b11_1111;

    //cnt_password
    always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
            cnt_password <= 0;
      end
      else if(end_cnt_password)begin
            cnt_password <= 0;
      end
      else if(add_cnt_password)begin
            cnt_password <= cnt_password + 1;
      end
    end
    assign add_cnt_password = state_c!=ERROR && key_num<10 && key_vld && cnt_password<4;
    assign end_cnt_password = confirm || end_cnt_10s_nvld;

    //password
    always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
            password <= 16'h0000;
      end
      else if(add_cnt_password)begin
            password <= {password,key_num};
      end
    end



    endmodule

1.4 数码管显示模块设计1.4.1接口信号

信号名I/O位宽定义
clkI1系统工作时钟 50M
rst_nI1系统复位信号,低电平有效
dinI3030位的输入数码管显示数据。每5bit一个字符(对应一个数码管),6个数码管则一共30bit。
din_vldI6输入数据有效指示信号,din_vld为1时,din有效;din_vld为1时,din有效,以此类推。
segmentO88位数码管段选信号
seg_selO66位数码管位选信号


1.4.2设计思路
在前面的案例中已经有数码管显示的介绍,所以这里不在过多介绍,详细介绍请看下方链接:http://fpgabbs.com/forum.php?mod=viewthread&tid=1085&fromuid=100105其中,数码管显示的数值和英文字母对应图像如下图所示:

file:///C:/Users/27657/AppData/Local/Temp/msohtmlclip1/01/clip_image010.gif1.4.3参考代码
module seg_display(
    clk   ,      
    rst_n   ,      
    din   ,      
    din_vld ,      
    segment ,      
    seg_sel         
    );

    parameter SEGMENT_NUM   = 6             ;   
    parameter W_DATA      = 5             ;   

    parameter SEGMENT_WID   = 8             ;   
    parameter TIME_300US    = 15_000         ;

    parameter SEG_DATA_0    = 7'b100_0000   ;
    parameter SEG_DATA_1    = 7'b111_1001   ;
    parameter SEG_DATA_2    = 7'b010_0100   ;
    parameter SEG_DATA_3    = 7'b011_0000   ;
    parameter SEG_DATA_4    = 7'b001_1001   ;
    parameter SEG_DATA_5    = 7'b001_0010   ;
    parameter SEG_DATA_6    = 7'b000_0010   ;
    parameter SEG_DATA_7    = 7'b111_1000   ;
    parameter SEG_DATA_8    = 7'b000_0000   ;
    parameter SEG_DATA_9    = 7'b001_0000   ;

    parameter SEG_CHAR_O    = 7'b010_0011   ;
    parameter SEG_CHAR_P    = 7'b000_1100   ;
    parameter SEG_CHAR_E    = 7'b000_0110   ;
    parameter SEG_CHAR_N    = 7'b010_1011   ;
    parameter SEG_CHAR_L    = 7'b100_0111   ;
    parameter SEG_CHAR_C    = 7'b100_0110   ;
    parameter SEG_CHAR_K    = 7'b000_0101   ;
    parameter SEG_CHAR_D    = 7'b010_0001   ;
    parameter SEG_CHAR_R    = 7'b010_1111   ;
    parameter SEG_NONE_DIS= 7'b111_1111   ;

    input                           clk         ;
    input                           rst_n       ;
    input din         ;
    input          din_vld   ;

    output         segment   ;
    output         seg_sel   ;

    reg            segment   ;
    reg            seg_sel   ;

    reg               segment_pre ;
    reg   din_get   ;
    reg                     cnt_300us   ;
    reg                        cnt_sel   ;
    wire                            dot         ;


   
wire      add_cnt_300us ;
wire      end_cnt_300us ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      cnt_300us <= 0;
    end
    else if(add_cnt_300us) begin
      if(end_cnt_300us)
            cnt_300us <= 0;
      else
            cnt_300us <= cnt_300us+1 ;
   end
end
assign add_cnt_300us =1;
assign end_cnt_300us = add_cnt_300us&& cnt_300us == TIME_300US-1 ;

   


wire      add_cnt_sel ;
wire      end_cnt_sel ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      cnt_sel <= 0;
    end
    else if(add_cnt_sel) begin
      if(end_cnt_sel)
            cnt_sel <= 0;
      else
            cnt_sel <= cnt_sel+1 ;
   end
end
assign add_cnt_sel = end_cnt_300us;
assign end_cnt_sel = add_cnt_sel&& cnt_sel == SEGMENT_NUM-1 ;


reg      din_vvld;
always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      din_vvld <= 0 ;
    end
    else begin
      din_vvld <= din_vld ;
    end
end


reg [ 2:0]cnt   ;
wire      add_cnt ;
wire      end_cnt ;
always @(posedge clk or negedge rst_n) begin
    if (rst_n==0) begin
      cnt <= 0;
    end
    else if(add_cnt) begin
      if(end_cnt)
            cnt <= 0;
      else
            cnt <= cnt+1 ;
   end
end
assign add_cnt = 1;
assign end_cnt = add_cnt&& cnt == SEGMENT_NUM-1 ;

always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      din_get <= 0;
    end
    else if(din_vvld)begin
      din_get <= din;
    end
end


    always@(*)begin
      segment_pre = din_get;
    end


    always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
            segment <= {dot,SEG_NONE_DIS};
      end
      else if(add_cnt_300us&& cnt_300us ==10-1)begin
            case(segment_pre)
                5'h00: segment <= {dot,SEG_DATA_0};
                5'h01: segment <= {dot,SEG_DATA_1};
                5'h02: segment <= {dot,SEG_DATA_2};
                5'h03: segment <= {dot,SEG_DATA_3};
                5'h04: segment <= {dot,SEG_DATA_4};
                5'h05: segment <= {dot,SEG_DATA_5};
                5'h06: segment <= {dot,SEG_DATA_6};
                5'h07: segment <= {dot,SEG_DATA_7};
                5'h08: segment <= {dot,SEG_DATA_8};
                5'h09: segment <= {dot,SEG_DATA_9};
                5'h10: segment <= {dot,SEG_CHAR_O};
                5'h11: segment <= {dot,SEG_CHAR_P};
                5'h12: segment <= {dot,SEG_CHAR_E};
                5'h13: segment <= {dot,SEG_CHAR_N};
                5'h14: segment <= {dot,SEG_CHAR_L};
                5'h15: segment <= {dot,SEG_CHAR_C};
                5'h16: segment <= {dot,SEG_CHAR_K};
                5'h17: segment <= {dot,SEG_CHAR_D};
                5'h18: segment <= {dot,SEG_CHAR_R};
                5'h1F: segment <= {dot,SEG_NONE_DIS};
                default:segment <= {dot,SEG_NONE_DIS};
            endcase
      end
    end
    assign dot = 1'b1;

    always@(posedge clk or negedge rst_n)begin
      if(rst_n==1'b0)begin
            seg_sel <= {SEGMENT_NUM{1'b0}};
      end
      else begin
            seg_sel <= ~(1'b1<<cnt_sel);
      end
    end


    endmodule






1.5 效果和总结


下图是该工程在db603开发板上的现象——密码锁初始状态和闭合状态



下图是该工程在db603开发板上的现象——提示输入错误状态





下图是该工程在db603开发板上的现象——密码锁开启状态





下图是该工程在db603开发板上的现象——输入密码状态





下图是该工程在mp801开发板上的现象——密码锁初始状态和闭合状态





下图是该工程在mp801开发板上的现象——提示输入错误状态



下图是该工程在mp801开发板上的现象——密码锁开启状态




下图是该工程在mp801开发板上的现象——输入密码状态
下图是该工程在ms980开发板上的现象——密码锁初始状态和闭合状态





下图是该工程在ms980开发板上的现象——提示输入错误状态





下图是该工程在ms980开发板上的现象——密码锁开启状态





下图是该工程在ms980开发板上的现象——输入密码状态




由于该项目的上板现象是在数码管上显示输入的密码,并且判断密码是否正确:正确则在数码管上显示OPEN,错误则在数码管上显示ERROR并提示输入错误2秒,然后数码管显示LOCKED。想观看完整现象的朋友可以看一下上板演示的视频。感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行FPGA相关工程设计学习,也可以看一下我们往期的文章:《波形相位频率可调DDS信号发生器》《基于FPGA的曼彻斯特编码解码设计》《基于FPGA的出租车计费系统》《数电基础与Verilog设计》《基于FPGA的频率、电压测量》《基于FPGA的汉明码编码解码设计》《关于锁存器问题的讨论》《阻塞赋值与非阻塞赋值》《参数例化时自动计算位宽的解决办法》
1.6 公司简介
明德扬是一家专注于FPGA领域的专业性公司,公司主要业务包括开发板、教育培训、项目承接、人才服务等多个方向。点拨开发板——学习FPGA的入门之选。
MP801开发板——千兆网、ADDA、大容量SDRAM等,学习和项目需求一步到位。网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习FPGA。周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO架构设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。项目承接——承接企业FPGA研发项目。人才服务——提供人才推荐、人才代培、人才派遣等服务。



【设计教程下载】



【设计视频教程】

https://www.bilibili.com/video/BV1Af4y117H4?p=41

【工程源码】


【答疑】
【问题1】
答:在顶层里,可以看到红框里的信号的是相连的,

那么去control模块看,这里的CHAR_O的参数的数值是随便定的,只要可以与其它区分

再到seg_display模块看,当得到数值为5'h10时则表示要显示的是“O”,那么对应的数码管段选信号segment的数值就取:信号dot的值和参数SIG_CHAR_O的值拼接得到的数据。

min_x91 发表于 2021-6-11 21:49:49

感谢分享,太好了
页: [1]
查看完整版本: 【每周FPGA案例】电子密码锁