FPGA实现简易的自动售货机模型


前言

环境:
1、Quartus18.1
2、vscode
3、板子型号:EP4CE6F17C8N
要求:
自动售货机功能:
1.两个按键用于投币,1个1元,1个5角
2.一个按键用于商品选择,农夫山泉2块,干脆面1块5
3.找零
设计:
我们选择使用四个按键,分别用于投币一元、五角、商品选择、结算四个功能。用六位数码管进行展示,高两位表示投币金额,中间两位表示所选商品类型('10’表示干脆面、'01’表示矿泉水),低两位表示找零数目。交易成功蜂鸣器响起5s。


一、系统设计

1、模块框图

在这里插入图片描述

2、状态机框图

在这里插入图片描述

3、RTL视图

在这里插入图片描述

二、源码

1.蜂鸣器驱动模块

module beep_ctrl(
    input wire clk,
    input wire rst_n,
    input wire pwm,

    output wire beep
);
reg beep_r;
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        beep_r<=1'b1;
    end
    else if(pwm)begin
        beep_r<=1'b0;
    end
    else begin
        beep_r<=1'b1;
    end
end
assign beep=beep_r;
endmodule

2.按键消抖模块

module key_debounce (
    input   wire    clk     ,
    input   wire    rst_n   ,
    input   wire    [3:0] key,
      
    output  wire    [3:0] key_out
);

    localparam      MAX20       = 20'd100_0000; 
    reg [19:0]      cnt_20ms;
    reg             start   ;//稳定信号 开始
    reg [3:0]       key_r0  ;//按键信号寄存器0
    reg [3:0]       key_r1  ;//按键信号寄存器1
    wire            nedge   ;//下降沿
    reg [3:0]       key_r   ;//开启流水

//倒计时计数器20ms
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_20ms <= 20'd0;
    end
    else if(nedge)begin
        cnt_20ms <= MAX20;
    end
    else if(start) begin
        if(cnt_20ms == 1'd1)begin
            cnt_20ms <= 20'd0;  
        end
        else begin
            cnt_20ms <= cnt_20ms - 1'd1;
        end
    end
    else begin
        cnt_20ms <= cnt_20ms;
    end
end

//下降沿检测
always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            key_r0 <= 4'b1111;//非阻塞赋值,同时赋值
            key_r1 <= 4'b1111;
        end
        else begin
            key_r0 <= key; //打一拍,同步时钟域
            key_r1 <= key_r0; //打一拍,检测按键下降沿
        end
    end

assign nedge = (~key_r0[0] && key_r1[0]) || (~key_r0[1] && key_r1[1]) || (~key_r0[2] && key_r1[2]) || (~key_r0[3] && key_r1[3]);//检测到下降沿

//约束start信号
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        start <= 1'b0;
    end
    else if(nedge)begin
        start <= 1'b1;
    end
    else if(cnt_20ms == 1'b1)begin
        start <= 1'b0;
    end
    else begin
        start <= start;
    end
end

//约束flag信号
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
       key_r <= 4'b0000;
    end
    else if(cnt_20ms == 1'b1)begin
       key_r <= ~key_r0;
    end
    else begin
        key_r <= 4'b0000;//修改
    end
end

assign key_out =key_r;
endmodule

3、PWM模块

module pwm (
    input wire clk,
    input wire rst_n,
    input wire flag_beep,

    output wire pwm
);
parameter  CNT_300MS= 24'd15_000_000;
parameter  NUM_NOTE =6'd34;
parameter  DO = 16'd47749,
           RE = 16'd42549,
           MI = 16'd37899,
           FA = 16'd37549,
           SO = 16'd31849,
           LA = 16'd28399,
           XI = 16'd25399;
reg [23:0]  cnt_300ms;
wire add_cnt_300ms;
wire end_cnt_300ms;

reg [5:0]   cnt_note;  
wire add_cnt_note;
wire end_cnt_note;

reg [15:0] cnt_freq;
wire add_cnt_freq;
wire end_cnt_freq;

reg [15:0] freq_data;
wire [15:0] duty_data;//占空比
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        cnt_300ms<=24'd0;
    end
    else if(add_cnt_300ms)begin
        if(end_cnt_300ms)begin
            cnt_300ms<=24'd0;
        end
        else begin
            cnt_300ms<=cnt_300ms+1'd1;
        end
    end
    else begin
        cnt_300ms<=cnt_300ms;
    end
end
assign add_cnt_300ms=1'd1;
assign end_cnt_300ms=add_cnt_300ms&&(cnt_300ms==CNT_300MS-1'd1);
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_note<=6'd0;
    end
    else if(add_cnt_note)begin
        if(end_cnt_note)begin
            cnt_note<=6'd0;
        end
        else begin
            cnt_note<=cnt_note+1'd1;
        end
    end
    else begin
        cnt_note<=cnt_note;
    end
end
assign add_cnt_note=end_cnt_300ms;
assign end_cnt_note=add_cnt_note&&cnt_note==NUM_NOTE-1'd1;
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_freq<=16'd0;
    end
    else if(add_cnt_freq)begin
        if(end_cnt_freq)begin
            cnt_freq<=16'd0;
        end
        else begin
            cnt_freq<=cnt_freq+1'd1;
        end
    end
    else begin
        cnt_freq<=cnt_freq;
    end
end
assign add_cnt_freq=1'b1;
assign end_cnt_freq=add_cnt_freq&&cnt_freq==freq_data;

//音符查找表
always @(*)begin
    case (cnt_note)
        6'd0:   freq_data=DO;
        6'd1:   freq_data=RE;
        6'd2:   freq_data=MI;
        6'd3:   freq_data=DO;
        6'd4:   freq_data=DO;
        6'd5:   freq_data=RE;
        6'd6:   freq_data=MI;
        6'd7:   freq_data=DO;
        6'd8:   freq_data=MI;
        6'd9:   freq_data=FA;
        6'd10:  freq_data=SO;
        6'd11:  freq_data=MI;
        6'd12:  freq_data=FA;
        6'd13:  freq_data=SO;
        6'd14:  freq_data=SO;
        6'd15:  freq_data=LA;
        6'd16:  freq_data=SO;
        6'd17:  freq_data=FA;
        6'd18:  freq_data=MI;
        6'd19:  freq_data=DO;
        6'd20:  freq_data=SO;
        6'd21:  freq_data=LA;
        6'd22:  freq_data=SO;
        6'd23:  freq_data=FA;
        6'd24:  freq_data=MI;
        6'd25:  freq_data=DO;
        6'd26:  freq_data=RE;
        6'd27:  freq_data=SO;
        6'd28:   freq_data=DO;
        6'd29:   freq_data=DO;
        6'd30:   freq_data=RE;
        6'd31:   freq_data=SO;
        6'd32:   freq_data=DO;
        6'd33:   freq_data=DO;
        default: freq_data=DO;
    endcase
end
assign duty_data=freq_data>>1;
assign pwm = (cnt_freq>duty_data&&flag_beep==1)?1'b1:1'b0;
endmodule

4、sale_goods模块(状态机部分)

module sale_goods (
    input  wire             clk      ,
    input  wire             rst_n    ,
    input  wire  [3:0]      key_in   ,

    output wire  [11:0]     dout,
    output wire             beep  
);
//状态空间
parameter IDLE       = 3'd0,
          Half       = 3'd1,
          ONE        = 3'd2,
          ONE_Half   = 3'd3,
          TWO        = 3'd4;

parameter IDLE1       = 3'd0,
          shui       =  3'd1, 
          mian       =  3'd2;
parameter MAX1S = 26'd50_000_000;
parameter MAX_5 =3'd5;
reg [25:0] cnt_1s;
reg [2:0] cstate;//现态
reg [2:0] nstate;//次态
reg [2:0] cstate1;//现态
reg [2:0] nstate1;//次态
reg [4:0] pay_r;
reg [3:0] choose_r;
reg [2:0] pay_back;
reg beep_r;
reg beep_r1;
reg [2: 0]  cnt_5s;
//1s计数器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_1s <= 26'd0;
    end 
    else if(cnt_1s == MAX1S - 1'd1)begin
        cnt_1s <= 26'd0;
    end   
    else begin
        cnt_1s <= cnt_1s + 1'd1;
    end   
end 
//五秒计数器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_5s <= 3'd0;
    end 
    else if(cnt_5s == MAX_5 - 1'd1 && cnt_1s == MAX1S - 1'd1)begin
        cnt_5s <= 3'd0;
    end
    else if(cnt_1s == MAX1S - 1'd1&&beep_r==1)begin
        cnt_5s <= cnt_5s + 1'd1;
    end 
    else begin
        cnt_5s <= cnt_5s;
    end 
end 

//第一段
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)                                  //key[0]0.5元,key[1]1  ,key[2]选择 ,key[3]下单
        IDLE:   begin
                    if(key_in[0])begin
                        nstate = Half;
                    end
                    else if(key_in[1])begin
                        nstate=ONE;
                    end
                    else begin
                        nstate=cstate;
                    end
                end
        Half:    begin
                    if(key_in[0])begin
                        nstate = ONE;
                    end
                    else if(key_in[1])begin
                        nstate=ONE_Half;
                    end
                    else begin
                        nstate=cstate;
                    end
                end
        ONE:     begin
                    if(key_in[0])begin
                        nstate = ONE_Half;
                    end
                    else if(key_in[1])begin
                        nstate=TWO;
                    end
                    else begin
                        nstate=cstate;
                    end
                end
        ONE_Half:     begin
                    if(key_in[0])begin
                        nstate = TWO;
                    end
                    else if(key_in[3])begin
                        nstate =IDLE;
                    end
                    else begin
                        nstate=cstate;
                    end
                end
        TWO:    begin
                    if(key_in[3])begin
                        nstate = IDLE;
                    end
                    else begin
                        nstate=cstate;
                    end
                end
        default:   nstate = IDLE;
    endcase
end
//第三段
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        pay_r<=5'd0;
    end
    else begin
        case(cstate)
            IDLE:         pay_r <=5'd0;
            Half    :     pay_r <=5'd5;
            ONE     :     pay_r <=5'd10;
            ONE_Half:     pay_r <=5'd15;
            TWO     :     pay_r <=5'd20;
            default:      pay_r <=5'd0;
        endcase
    end
end

/*商品选择状态机*/
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cstate1<=IDLE1;
    end
    else begin
        cstate1<=nstate1;
    end
end

//第二段
always @(*)begin
    case(cstate1)                                  //00未选择,10mian,01shui
        IDLE1:   begin
                    if(key_in[2])begin
                        nstate1 = shui;
                    end
                    else begin
                        nstate1=cstate1;
                    end
                end
        shui:     begin
                    if(key_in[2])begin
                        nstate1 = mian;
                    end
                    else begin
                        nstate1=cstate1;
                    end
                end
        mian:     begin
                    if(key_in[2])begin
                        nstate1 = IDLE1;
                    end
                    else begin
                        nstate1=cstate1;
                    end
                end
        default:   nstate1 = IDLE1;
    endcase
end
//第三段
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        choose_r<=4'd0;
    end
    else begin
        case(cstate1)
           IDLE1:         choose_r <=4'd0;
           shui     :     choose_r <=4'd1;
           mian     :     choose_r <=4'd10;
           default:       choose_r <=4'd0;
        endcase
    end
end

//找零
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        pay_back <=3'd0;
        beep_r1<=0;
    end
    else if(key_in[3]&&cstate1==shui)begin
        if(pay_r>=20)begin
            pay_back<=pay_r-4'd20;
            beep_r1<=1;
        end
        else
            pay_back<=4'd33;
    end
    else if(key_in[3]&&cstate1==mian)begin
        if(pay_r>=15)begin
            pay_back<=pay_r-4'd15;
            beep_r1<=1;
        end
        else
            pay_back<=4'd33;
    end
    else begin
        beep_r1<=0;
    end
end
//五秒关蜂鸣器
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        beep_r<=1'b0;
    end
    else if(cnt_5s == MAX_5 - 1'd1 && cnt_1s == MAX1S - 1'd1)begin
        beep_r<=1'b0;
    end
    else if(beep_r1)begin
        beep_r<=1'b1;
    end
    else begin
        beep_r<=beep_r;
    end
end
assign dout={
    
    pay_r,choose_r,pay_back};
assign beep=beep_r;
endmodule

5、数码管驱动模块

module seg_driver(  //数码管驱动
    input               clk     ,
    input               rst_n   ,
    input       [14:0]  din     ,

    output reg  [5:0]   sel     , //片选
    output reg  [7:0]   dig       //段选     
);
parameter TIME_SCAN = 50_000;
    // parameter TIME_SCAN = 50_000 ; // 1MS 让片选一直扫描的移动

    parameter   ZER = 7'b100_0000,  // 0亮 1灭
                ONE = 7'b111_1001,
                TWO = 7'b010_0100,
                THR = 7'b011_0000,
                FOR = 7'b001_1001,
                FIV = 7'b001_0010,
                SIX = 7'b000_0010,
                SEV = 7'b111_1000,
                EIG = 7'b000_0000,
                NIN = 7'b001_0000;

    reg    [15:0]   cnt_scan    ; //扫描计数器
    wire            add_cnt_scan;
    wire            end_cnt_scan;

    reg    [3:0]    data        ; //寄存器 缓存数据
    reg             dot         ; //小数点

    wire   [3:0]    sec_l       ;  //秒低位
    wire   [3:0]    sec_h       ;  //秒 高位
    wire   [3:0]    min_l       ;  // 分地位
    wire   [3:0]    min_h       ;  //分高位
    wire   [3:0]    hou_l       ; 
    wire   [3:0]    hou_h       ;

    assign  sec_l = din[2:0]   % 10 ;  // 59 % 10 = 9 
    assign  sec_h = din[2:0]   / 10 ;  // 59 / 10 = 5 
    assign  min_l = din[6:3]  % 10 ;
    assign  min_h = din[6:3]  / 10 ;
    assign  hou_l = din[11:7] % 10 ;
    assign  hou_h = din[11:7] / 10 ;

    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_scan <= 16'b0;
        end
        else if(add_cnt_scan)begin
            if(end_cnt_scan)begin
                cnt_scan <= 16'b0;
            end
            else begin
                cnt_scan <= cnt_scan + 1'b1;
            end
        end
        else begin
            cnt_scan <= cnt_scan ;
        end
    end

    assign add_cnt_scan = 1'b1;
    assign end_cnt_scan = add_cnt_scan && cnt_scan == TIME_SCAN - 1;

    always @(posedge clk or negedge rst_n)begin   // 片选
        if(!rst_n)begin
            sel <= 6'b011_111;
        end
        else if(end_cnt_scan)begin
            sel <= {
    
    sel[0],sel[5:1]};//循环向右移动  
        end
        else begin
            sel <= sel;
        end
    end 

    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            data <= 3'b0;
            dot  <= 1'b1;
        end
        else begin
            case(sel)
                6'b011_111 : begin data <= sec_l; dot <= 1'b1; end
                6'b101_111 : begin data <= sec_h; dot <= 1'b0; end
                6'b110_111 : begin data <= min_l; dot <= 1'b1; end
                6'b111_011 : begin data <= min_h; dot <= 1'b1; end
                6'b111_101 : begin data <= hou_l; dot <= 1'b1; end
                6'b111_110 : begin data <= hou_h; dot <= 1'b0; end
                default : begin data <= 3'b0; dot <= 1'b1;end
            endcase
        end
    end
    
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            dig <= 8'b0;
        end
        else begin
            case(data)
                0 : dig <= {
    
    dot,ZER};
                1 : dig <= {
    
    dot,ONE};
                2 : dig <= {
    
    dot,TWO};
                3 : dig <= {
    
    dot,THR};
                4 : dig <= {
    
    dot,FOR};
                5 : dig <= {
    
    dot,FIV};
                6 : dig <= {
    
    dot,SIX};
                7 : dig <= {
    
    dot,SEV};
                8 : dig <= {
    
    dot,EIG};
                9 : dig <= {
    
    dot,NIN};
                default : dig <= 8'b0;
            endcase
        end
    end
endmodule

6、Sales(顶层模块)

module Sales(
    input clk,
    input rst_n,
    input [3:0] key_in,

    output [5:0] sel,
    output [7:0] seg,
    output wire  beep
);
wire [3:0] key_in1;
wire [11:0] dout;
wire beep1;
wire pwm1;
key_debounce key_debounce_inst(
    .clk       (clk)     ,
    .rst_n     (rst_n)     ,
    .key       (key_in)     ,

    .key_out   (key_in1)
);
sale_goods sale_goods_inst(
    .clk      (clk),
    .rst_n    (rst_n),
    .key_in   (key_in1),

    .dout     (dout),
    .beep     (beep1)
);
seg_driver seg_driver_inst(  //数码管驱动
    .clk   (clk)  ,
    .rst_n (rst_n)  ,
    .din   (dout)  ,

    .sel   (sel)  , //片选
    .dig   (seg)    //段选     
);
pwm pwm_inst(
    .clk         (clk)    ,
    .rst_n       (rst_n)    ,
    .flag_beep   (beep1)    ,
    .pwm         (pwm1) 
);
beep_ctrl beep_ctrl_inst(
    .clk        (clk)    ,
    .rst_n      (rst_n)    ,
    .pwm        (pwm1)    ,

    .beep       (beep)
);
endmodule

三、效果

FPGA模拟自动售货机


四、总结

此次的售货机模型只是一个简单的模拟,将之前所学习的各个小的模块进行一个综合的运用,使得对一些基本操作:数码管驱动、按键消抖、蜂鸣器、状态机等更加熟练,理解上也更加深刻。

五、参考资料

https://blog.csdn.net/weixin_43828944/article/details/124231997?spm=1001.2014.3001.5502

猜你喜欢

转载自blog.csdn.net/qq_52215423/article/details/130854256