FPGA学习——FPGA利用状态机实现电子锁模拟

文章目录

  • 一、本次实验简介
  • 二、源码及分析
  • 三、总结

一、本次实验简介

本次是实验是为了利用状态机模拟电子锁,相关要求如下:

  • 顺序输入4位密码,密码为1234,用按键来键入密码
  • 用led灯指示键入第几位密码,(博主IDLE状态亮1个LED,输入第一位密码后亮2个,以此类推),若密码输入正确,让4个led闪烁(每间隔0.3s)
  • 用3个按键,按下key1,对应位的数值加1
  • 按下key2,对应位的数值减1
  • 按下按键key3,表示确认键入该数值

二、源码及分析

fsm_lock模块:

源码分析:

  • 博主为状态机设计了五个状态并采用独热码编码的方式(一般均建议状态机状态编码方式采用独热码):
    IDLE 空闲状态
    P1密码1输入正确状态
    P2 密码2输入正确状态
    P3密码3输入正确状态
    P4密码4输入正确后LED闪烁状态
  • 有题目可以看出,我们有四位的密码,博主的想法是设置四个密码寄存器,KEY1按下后计数值加1,KEY2按下时计数值减一,以此来存储键入密码值(当然每个密码寄存器的加1条件有些许不同,详细请看代码)。
  • 而除了最后一个状态,其他状态博主设置的跳转下一状态的条件均为状态机处于当前状态并且按下了KEY3(确认键)并且此时键入的值为密码值。
  • 对于最后一个状态,由于需要在P4闪烁LED灯,因此跳转状态设置为了LED闪烁结束。至于如何控制LED闪烁结束,博主设置了一个八位的次数寄存器,LED每完成一次闪烁,该计数器左移一位(初始值为8’d1),当闪烁完成时,该计数器的最高位为1,此时便可以将最高位作为跳转的控制条件。
/****
状态机模拟电子锁

- 顺序输入4位密码,密码为1234,用按键来键入密码
- 用led灯指示键入第几位密码,(博主IDLE状态亮1个LED,输入第一位密码后亮2个,以此类推),若密码输入正确,让4个led闪烁(每间隔0.3s)
- 用3个按键,按下key1,对应位的数值加1
- 按下key2,对应位的数值减1
- 按下按键key3,表示确认键入该数值

****/
module fsm_lock (
    input       wire                clk     ,
    input       wire                rst_n   ,
    input       wire    [2:0]       key_in  , //按键输入信号

    output      reg     [3:0]       led       //输出led
);

//参数定义
parameter MAX = 15_000_000  ;//300ms计数器
parameter IDLE = 5'b00001   ,//空闲状态
          P1   = 5'b00010   ,//密码1输入正确状态 
          P2   = 5'b00100   ,//密码2输入正确状态
          P3   = 5'b01000   ,//密码3输入正确状态
          P4   = 5'b10000   ;//密码4输入正确LED闪烁状态

//状态转移条件定义
wire            idle2p1     ;
wire            p12p2       ;
wire            p22p3       ;
wire            p32p4       ;
wire            p42idle     ;


//内部信号定义
reg     [23:0]  cnt      ;//300ms计数寄存器
reg     [7:0]   cnt_8    ;//解锁成功后让LED闪烁4次

reg     [4:0]   cstate   ;//现态
reg     [4:0]   nstate   ;//次态

reg     [3:0]   cnt_key1 ;//密码计数器 计数当前按下的密码值
reg     [3:0]   cnt_key2 ;
reg     [3:0]   cnt_key3 ;
reg     [3:0]   cnt_key4 ;

reg             led_light;

//三段式状态机
//第一段时序逻辑,描述状态转移
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cstate <= IDLE;
    end
    else begin
        cstate <= nstate;
    end
end

//第二段组合逻辑,描述状态转移规律及状态转移条件
always@(*)begin
    case(cstate)
        IDLE    :   begin
                        if(idle2p1)begin
                            nstate = P1;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        P1      :   begin
                        if(p12p2)begin
                            nstate = P2;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        P2      :   begin
                        if(p22p3)begin
                            nstate = P3;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        P3      :   begin
                        if(p32p4)begin
                            nstate = P4;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        P4      :   begin
                        if(p42idle)begin
                            nstate = IDLE;
                        end
                        else begin
                            nstate = cstate;
                        end
                    end
        default :   nstate = IDLE;
    endcase
end

assign idle2p1 = (cstate == IDLE) && (key_in[2]) && (cnt_key1 == 4'd1);
assign p12p2   = (cstate == P1  ) && (key_in[2]) && (cnt_key2 == 4'd2);
assign p22p3   = (cstate == P2  ) && (key_in[2]) && (cnt_key3 == 4'd3);
assign p32p4   = (cstate == P3  ) && (key_in[2]) && (cnt_key4 == 4'd4);
assign p42idle = (cstate == P4  ) && (cnt_8[7]);//led闪烁完成后跳转


//第三段,时序逻辑,描述不同状态下的输出
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        led <= 4'b0000;
    end
    else begin
        case(cstate)
        IDLE    :   led <= 4'b0001      ;
        P1      :   led <= 4'b0011      ;
        P2      :   led <= 4'b0111      ;
        P3      :   led <= 4'b1111      ;
        P4      :   led <= led_light    ;
        default :   led <= 4'b0000      ;
        endcase
    end
end


//密码计数器1
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_key1 <= 1'b0;
    end
    else if(key_in[0] && cstate == IDLE)begin
        cnt_key1 <= cnt_key1 + 1'b1;
    end
    else if(key_in[1] && (cnt_key1 != 1'b0))begin
        cnt_key1 <= cnt_key1 - 1'b1;
    end
    else if(cnt_8[7])begin
        cnt_key1 <= 1'b0;
    end
    else begin
        cnt_key1 <= cnt_key1;
    end
end

//密码计数器2
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_key2 <= 1'b0;
    end
    else if(key_in[0] && cstate == P1)begin
        cnt_key2 <= cnt_key2 + 1'b1;
    end
    else if(key_in[1] && (cnt_key2 != 1'b0))begin
        cnt_key2 <= cnt_key2 - 1'b1;
    end
    else if(cnt_8[7])begin
        cnt_key2 <= 1'b0;
    end
    else begin
        cnt_key2 <= cnt_key2;
    end
end

//密码计数器3
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_key3 <= 1'b0;
    end
    else if(key_in[0] && cstate == P2)begin
        cnt_key3 <= cnt_key3 + 1'b1;
    end
    else if(key_in[1] && (cnt_key3 != 1'b0))begin
        cnt_key3 <= cnt_key3 - 1'b1;
    end
    else if(cnt_8[7])begin
        cnt_key3 <= 1'b0;
    end
    else begin
        cnt_key3 <= cnt_key3;
    end
end

//密码计数器4
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_key4 <= 1'b0;
    end
    else if(key_in[0] && cstate == P3)begin
        cnt_key4 <= cnt_key4 + 1'b1;
    end
    else if(key_in[1] && (cnt_key4 != 1'b0))begin
        cnt_key4 <= cnt_key4 - 1'b1;
    end
    else if(cnt_8[7])begin
        cnt_key4 <= 1'b0;
    end
    else begin
        cnt_key4 <= cnt_key4;
    end
end

//300ms计数器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt <= 1'b0;
    end
    else if(cnt == MAX - 1'b1)begin
        cnt <= 1'b0;
    end
    else if(cstate == P4)begin
        cnt <= cnt + 1'b1;
    end
    else begin
        cnt <= 1'b0;
    end
end

//闪烁次数寄存器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_8 <= 8'b00000001;
    end    
    else if(cnt == MAX -1'd1)begin
        cnt_8 <= {cnt_8[6:0],cnt_8[7]};
    end
	 else if(cstate == IDLE)begin
    cnt_8 <= 8'b00000001;
    end
    else begin
        cnt_8 <= cnt_8;
    end
end

//led闪烁
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        led_light <= 4'b1111;
    end
    else if(cstate == P4 && cnt == MAX - 1'b1)begin
        led_light <= ~led_light;
    end
    else begin
        led_light <= led_light;
    end
end

endmodule

fsm_key模块(按键消抖模块):

如果有读者不了解按键消抖原理与代码细节的可以查看博主此篇博文,博主在此就不再赘述了

module fsm_key#(parameter WIDTH = 3) (
    input       wire                    clk     ,
    input       wire                    rst_n   ,
    input       wire   [WIDTH - 1:0]    key_in  ,//按键信号输入

    output      reg    [WIDTH - 1:0]    key_out  //消抖后稳定的脉冲信号输出
);

//参数定义
parameter       IDLE        = 4'b0001;//空闲状态
parameter       FILTER_DOWN = 4'b0010;//按键按下抖动状态
parameter       HOLD_DOWN   = 4'b0100;//按键稳定状态
parameter       FILTER_UP   = 4'b1000;//按键释放抖动状态


parameter MAX = 20'd1_000_000 ;//延时20ms采样

//内部信号定义
reg         [19:0]               cnt_20ms    ;//20ms计数寄存器
reg         [WIDTH - 1:0]        key_r0      ;//同步
reg         [WIDTH - 1:0]        key_r1      ;//打一拍
reg         [WIDTH - 1:0]        key_r2      ;//打两拍
 
reg         [3:0]                cstate      ;//现态寄存器
reg         [3:0]                nstate      ;//次态寄存器


//状态转移条件定义
wire                             idle2down   ;
wire                             down2hold   ;
wire                             hold2up     ;
wire                             up2idle     ;  

//计数器信号定义
wire                             add_cnt_20ms;//开始计时的标志
wire                             end_cnt_20ms;//结束计时的标志

wire        [WIDTH - 1:0]        nedge       ;//下降沿寄存器
wire        [WIDTH - 1:0]        pedge       ;//上升沿寄存器


//同步打拍
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

//20ms计数器
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_20ms <= 1'b0;
    end
    else if(add_cnt_20ms)begin
        if(end_cnt_20ms)begin
            cnt_20ms <= 1'b0;
        end
        else begin
            cnt_20ms <= cnt_20ms + 1'b1;
        end
    end
    else begin
        cnt_20ms <= 1'b0;
    end
end

assign add_cnt_20ms = cstate == FILTER_DOWN || cstate == FILTER_UP;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == MAX - 1'b1;


//下降沿 上升沿检测
assign nedge = ~key_r1 & key_r2;
assign pedge = key_r1 & ~key_r2;


//三段式状态机

//第一段,时序逻辑,描述状态转移
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cstate <= IDLE;
    end
    else begin
        cstate <= nstate;
    end
end

//第二段,组合逻辑,状态转移规律及状态转移条件
always @(*)begin
    case (cstate)
        IDLE         : begin
            if(idle2down)begin
                nstate <= FILTER_DOWN;
            end
            else begin
                nstate <= cstate;
            end
        end
        FILTER_DOWN  : begin
            if(down2hold)begin
                nstate <= HOLD_DOWN;
            end
            else begin
                nstate <= cstate;
            end
        end
        HOLD_DOWN    : begin
            if(hold2up)begin
                nstate <= FILTER_UP;
            end
            else begin
                nstate <= cstate;
            end
        end
        FILTER_UP    : begin
            if(up2idle)begin
                nstate <= IDLE;
            end
            else begin
                nstate <= cstate;
            end
        end
        default     : nstate <= IDLE;
    endcase
end

assign idle2down   = cstate == IDLE         && nedge        ;
assign down2hold   = cstate == FILTER_DOWN  && end_cnt_20ms ;
assign hold2up     = cstate == HOLD_DOWN    && pedge        ;
assign up2idle     = cstate == FILTER_UP    && end_cnt_20ms ;

//第三段 时序逻辑或组合逻辑 描述输出
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        key_out <= {WIDTH{1'b0}};
    end
    else if(down2hold)begin
        key_out <= ~key_in;
    end
    else begin
        key_out <= {WIDTH{1'b0}};
    end
end
            
endmodule

顶层模块:

module top_lock(
    input   wire            clk     ,
    input   wire            rst_n   ,
    input   wire    [2:0]   key_in  ,

    output  wire    [3:0]   led
);

wire   [2:0]     key_out;

fsm_lock u_fsm_lock(
   .clk   (clk    )  ,
   .rst_n (rst_n  )  ,
   .key_in(key_out)  ,

   .led   (led)   
);

fsm_key u_fsm_key(
    .clk    (clk    ) ,
    .rst_n  (rst_n  ) ,
    .key_in (key_in ) ,

    .key_out(key_out)  
);



endmodule

三、总结

本次实验较为简单,博主没有编写仿真文件跑仿真(其实是因为懒毕竟我觉得TB文件写起来真的很麻烦),功能实现也较为简单,感兴趣的读者可以自主添加其他功能,如:输入错误蜂鸣器鸣叫并自动跳转回到IDLE状态,或者利用数码管显示正在输入的密码位数与密码值等。

你可能感兴趣的:(fpga开发,学习)