售货机模拟


前言

  在没有扫码支付的年代,售货机是需要投币完成商品的购买,现在的售货机功能变得越来越多样,不仅可以纸币购买、硬币购买、还可以扫码支付,但是今天让我们回归本源,使用FPGA模拟自动售货机最原始的模样。

在这里插入图片描述

一、状态机回顾

  状态机结构:

  • 状态寄存器:当前状态机所处状态
  • 状态切换:根据输入信号、当前状态,决定下一个状态
  • 输出:每个状态对应的逻辑输出

  状态机推荐采用二段或三段式设计。三段式状态机:

  • 状态机的状态
  • 状态切换条件
  • 逻辑输出

在这里插入图片描述

  第一个always块采用同步时序逻辑描述状态转移。第二个always块采用组合逻辑方式描述状态转移规律。第三个always块描述电路的输出,在时序允许的情况下,通常让输出信号经过一个寄存器再输出,保证信号中没有毛刺。

二、设计规范

  1. 默认只接收0.5元、1元投币。
  2. 货物为2.5元。
  3. 满足2.5元后自动出货,出货动作用4个LED同时闪烁(状态维持2s)表示。
  4. 满足3元之后,自动出货并找零,动作用4个LED做跑马灯(状态维持2s)表示。

三、设计输入

  1. 创建finit_shift.v文件,编写有限状态机,状态切换模块
module finit_shift(
   input wire clk,
   input wire rst_n,
   input wire [1:0] key,

   output wire [4:0] seg_value,
   output wire [3:0] led

);
parameter TIME_500MS = 25'd24_999_999;
parameter TIME_250MS = 24'd12_499_999;
//状态空间
parameter IDLE = 3'd0;
parameter S1   = 3'd1;
parameter S2   = 3'd2;
parameter S3   = 3'd3;
parameter S4   = 3'd4;
parameter S5   = 3'd5;
parameter S6   = 3'd6;

reg [3:0] led_r;
reg [4:0] seg_value_r;
reg [23:0] cnt_250ms;
wire add_cnt_250ms;
wire end_cnt_250ms;
reg [2:0] cstate;
reg [2:0] nstate;
reg [2:0] cnt_2s;
//250ms计时器
always @(posedge clk or negedge rst_n)begin 
  if(!rst_n)begin
       cnt_250ms <= 24'd0;
   end 
   else if(add_cnt_250ms)begin 
           if(end_cnt_250ms)begin 
               cnt_250ms <= 24'd0;
           end
           else begin 
               cnt_250ms <= cnt_250ms + 1'd1;
           end 
   end
  else  begin
      cnt_250ms <= 24'd0;
   end
end 

assign add_cnt_250ms = (cstate == S5) || (cstate == S6);//S5和S6状态计数
assign end_cnt_250ms = add_cnt_250ms && cnt_250ms == TIME_250MS;

//2s计时器
always@(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
       cnt_2s <= 3'd0;
   end 
   else if(end_cnt_250ms) begin
       cnt_2s <= cnt_2s + 1'd1;
   end 
   else if(cnt_2s == 3'd7)begin
       cnt_2s <= 3'd0;
   end 
   else begin
       cnt_2s <= cnt_2s;
   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)
       IDLE: begin
           if(key[0])begin
               nstate = S1;
           end
           else if(key[1])begin
               nstate = S2;
           end 
           else begin
               nstate = IDLE;
           end 
       end 
       S1: begin
           if(key[0])begin
               nstate = S2;
           end
           else if(key[1])begin
               nstate = S3;
           end 
           else begin
               nstate = S1;
           end 
       end 
       S2: begin
           if(key[0])begin
               nstate = S3;
           end
           else if(key[1])begin
               nstate = S4;
           end 
           else begin
               nstate = S2;
           end 
       end 
       S3: begin
           if(key[0])begin
               nstate = S4;
           end
           else if(key[1])begin
               nstate = S5;
           end 
           else begin
               nstate = S3;
           end 
       end 
       S4: begin
           if(key[0])begin
               nstate = S5;
           end
           else if(key[1])begin
               nstate = S6;
           end 
           else begin
               nstate = S4;
           end 
       end 
       S5: begin
           if(cnt_2s == 3'd7)begin
               nstate = IDLE;
           end 
           else begin
               nstate = S5;
           end 
       end 
       S6: begin
           if(cnt_2s == 3'd7)begin
               nstate = IDLE;
           end 
           else begin
               nstate = S6;
           end 
       end
       default:nstate = IDLE;
   endcase
end
//三段式,第三段led灯动作
always@(posedge clk or negedge rst_n)begin
   if(!rst_n)begin
       led_r <= 4'b0000;
   end 
   else begin
       case(cstate)
           IDLE,S1,S2,S3,S4: led_r <= 4'b0000;
           S5:begin
               if(end_cnt_250ms)begin
                   led_r <= ~led_r;
               end 
               else begin
                   led_r <= led_r;
               end 
           end 
           S6:begin
               if(end_cnt_250ms)begin
                   led_r <= {~led_r[0],led_r[3:1]};
               end 
               else begin
                   led_r <= led_r;
               end 
              
           end 
           default:led_r <= 4'b0000;
       endcase
   end
end 
assign led = led_r;
//三段式,第三段数码管动作
always@(*)begin
   case(cstate)
       IDLE:   seg_value_r = 5'd0 ;
         S1:   seg_value_r = 5'd5 ;   
         S2:   seg_value_r = 5'd10;
         S3:   seg_value_r = 5'd15;
         S4:   seg_value_r = 5'd20;
         S5:   seg_value_r = 5'd25;
         S6:   seg_value_r = 5'd30;
       default:seg_value_r = 5'd0 ;
   endcase
end 
assign seg_value = seg_value_r;
endmodule 
  1. 创建key_debounce.v文件,编写按键消抖模块
module key_debounce(
    input wire clk,
    input wire rst_n,
    input wire key,

    output wire flag,
    output wire key_value
);
parameter TIME_20MS = 20'd1_000_000;
reg key_reg;
reg key_value_r;
reg flag_r;
reg [19:0] cnt_delay;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        key_reg <= 1'b1;
        cnt_delay <= 20'd0;
    end 
    else begin
        key_reg <= key;
        if(key_reg == 1'b1 && key == 1'b0)begin
            cnt_delay <= TIME_20MS;
        end 
        else if(cnt_delay > 20'd0)begin
            cnt_delay <= cnt_delay - 1'd1;
        end 
        else begin
            cnt_delay <= 20'd0;
        end 
    end 
end

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        flag_r <= 1'b0;
    end 
    else if(cnt_delay == 1'b1)begin
        flag_r <= 1'b1;
    end 
    else begin
        flag_r <= 1'b0;
    end 
end 

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        key_value_r <= 1'b0;
    end 
    else if(cnt_delay == 1'b1)begin
        key_value_r <= ~key_value_r;
    end 
    else begin
        key_value_r <= key_value_r;
    end 
end 
assign key_value = key_value_r;
assign flag = flag_r;
endmodule 
  1. 创建sel_drive.v文件,编写数码管驱动模块
module sel_drive(
   input wire clk,
   input wire rst_n,
   
   output wire [1:0] sel_2
);
parameter CNT_20US = 10'd999;
reg [9:0] cnt_20us;
reg [1:0] sel_2_r;
wire add_cnt;
wire end_cnt;
always @(posedge clk or negedge rst_n)begin 
  if(!rst_n)begin
       cnt_20us <= 10'd0;
   end 
   else if(add_cnt)begin 
           if(end_cnt)begin 
               cnt_20us <= 10'd0;
           end
           else begin 
               cnt_20us <= cnt_20us + 1'd1;
           end 
   end
  else  begin
      cnt_20us <= 10'd0;
   end
end 

assign add_cnt = 1;
assign end_cnt = add_cnt && cnt_20us == CNT_20US;

always @(posedge clk or negedge rst_n) begin
   if(!rst_n)begin
       sel_2_r <= 2'b10;
   end 
   else if(end_cnt)begin
       sel_2_r <= {sel_2_r[0],sel_2_r[1]};
   end 
   else begin
       sel_2_r <= sel_2_r;
   end 
end
assign sel_2 = sel_2_r;
endmodule 
  1. 创建seg_decode.v文件,编写数码管译码模块
module seg_decode(
   input wire clk,
   input wire rst_n,
   input wire [4:0] seg_value,
   input wire [5:0] sel,

   output wire [7:0] seg


);
reg [2:0] number;
reg [7:0] seg_r;
always @(posedge clk or negedge rst_n) begin
   if(!rst_n)begin
       number <= 3'd0;
   end 
   else begin
       case(sel)
           6'b111110: number <= seg_value/10;
           6'b111101: number <= seg_value%10;
           default:number <= 3'd0;
       endcase
   end
end
always @(posedge clk or negedge rst_n) begin
   if(!rst_n)begin
       seg_r <= 8'b1100_0000;
   end 
   else begin
       case(number)
           3'd0:   seg_r <= 8'b1100_0000;
           3'd1:   seg_r <= 8'b0111_1001;
           3'd2:   seg_r <= 8'b0010_0100;
           3'd3:   seg_r <= 8'b0011_0000;
           3'd4:   seg_r <= 8'b1001_1001;
           3'd5:   seg_r <= 8'b1001_0010;
           default:seg_r <= 8'b1100_0000;
       endcase
   end
end
assign seg = seg_r;
endmodule 
  1. 创建vendor_top.v文件,编写售货机顶层模块
module vendor_top(
  input wire clk,
  input wire rst_n,
  input wire [1:0] key,

  output wire [3:0] led,
  output wire [5:0] sel,
  output wire [7:0] seg
);

wire [1:0] flag;
wire [1:0] key_value;
wire [1:0] sel_w;
wire [4:0] seg_value;
key_debounce    key_debounce_u1(
.clk         (clk),
.rst_n       (rst_n),
.key         (key[0]),

.flag        (flag[0]),
.key_value   (key_value[0])
);
key_debounce    key_debounce_u2(
.clk         (clk),
.rst_n       (rst_n),
.key         (key[1]),

.flag        (flag[1]),
.key_value   (key_value[1])
);

sel_drive   sel_drive_u(
.clk    (clk),
.rst_n  (rst_n),
  
.sel_2    (sel_w)
);



finit_shift   finit_shift_u(
.clk        (clk),
.rst_n      (rst_n),
.key        ({(flag[1]&&key_value[1]),(flag[0]&&key_value[0])}),

.seg_value  (seg_value),
.led        (led)

);
seg_decode  seg_decode_u(
.clk        (clk),
.rst_n      (rst_n),
.seg_value  (seg_value),
.sel        ({4'b1111,sel_w}),

.seg        (seg)

);
assign sel = {4'b1111,sel_w};
endmodule

四、运行效果

售货机模拟

总结

  上述代码只是实现了售货机最基本的功能,并没有实现更复杂的售货机。当然,代码量是和需求成正比的,如果你有更好的想法,可以造一个搭载FPGA芯片的售货机,让你的售货机起飞。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值