前言
在没有扫码支付的年代,售货机是需要投币完成商品的购买,现在的售货机功能变得越来越多样,不仅可以纸币购买、硬币购买、还可以扫码支付,但是今天让我们回归本源,使用FPGA模拟自动售货机最原始的模样。
一、状态机回顾
状态机结构:
- 状态寄存器:当前状态机所处状态
- 状态切换:根据输入信号、当前状态,决定下一个状态
- 输出:每个状态对应的逻辑输出
状态机推荐采用二段或三段式设计。三段式状态机:
- 状态机的状态
- 状态切换条件
- 逻辑输出
第一个always块采用同步时序逻辑描述状态转移。第二个always块采用组合逻辑方式描述状态转移规律。第三个always块描述电路的输出,在时序允许的情况下,通常让输出信号经过一个寄存器再输出,保证信号中没有毛刺。
二、设计规范
- 默认只接收0.5元、1元投币。
- 货物为2.5元。
- 满足2.5元后自动出货,出货动作用4个LED同时闪烁(状态维持2s)表示。
- 满足3元之后,自动出货并找零,动作用4个LED做跑马灯(状态维持2s)表示。
三、设计输入
- 创建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
- 创建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
- 创建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
- 创建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
- 创建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芯片的售货机,让你的售货机起飞。