FPGA实战售货机
密码锁做过了,接下来就是售货机。
前言
自动售货机的目标:
1.售货机里有A.B.C三种商品,分别是3.5.8元.
2.按下key[1]选择购买哪种商品,key[3]确认购买此种商品;
3.按下key[1]选择购买该商品的数量,key[3]确认数量;
4.按下key[1]回到选择商品,按下key[2]直接结算并支付;
5.按下key[1]表示支付成功,led全亮.
一、设计框架
这里用到了三个模块,分别是数码管控制模块、按键消抖模块、售货机主控模块。
信号过于平常,不再赘述。
不过框架图里有一个seg_mask
信号。这个信号是掩码,大致的用处就是覆盖六个数码管,哪位为1可以让对应的数码管亮,为0时遮住对应的数码管不让它亮。
二、模块分讲
1.按键消抖
/**************************************功能介绍***********************************
Date : 2023年10月1日 19:56:42
Author : Yang.
Project : 自动售货机
Require : 1.售货机里有A.B.C三种商品,分别是3.5.8元.
2.按下key[1]选择购买哪种商品,key[3]确认购买此种商品;
3.按下key[1]选择购买该商品的数量,key[3]确认数量;
4.按下key[1]回到选择商品,按下key[2]直接结算并支付;
5.按下key[1]表示支付成功,led全亮.
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module key#(
parameter KEY_WIDTH = 3'd3
)(
input sys_clk ,
input sys_rst_n ,
input [KEY_WIDTH - 1:0] key_in ,
output reg [KEY_WIDTH - 1:0] key_flag
);
//---------<参数定义>---------------------------------------------------------
parameter CNT_MAX = 20'd1000_000; //20ms消抖时间
localparam IDLE = 4'b0001,
DONE = 4'b0010,
HOLD = 4'b0100,
UP = 4'b1000;
//---------<内部信号定义>-----------------------------------------------------
reg [3:0] state;
reg [KEY_WIDTH - 1:0] key_in_reg0,
key_in_reg1,
key_in_reg2;
reg [KEY_WIDTH - 1:0] key_reg;
wire key_in_negedge,
key_in_posedge;
/* 打拍 */
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n) begin
key_in_reg0 <= {KEY_WIDTH{1'd1}}; /* 同步 */
key_in_reg1 <= {KEY_WIDTH{1'd1}}; /* 同步 */
key_in_reg2 <= {KEY_WIDTH{1'd1}}; /* 边沿 */
end
else begin
key_in_reg0 <= key_in;
key_in_reg1 <= key_in_reg0;
key_in_reg2 <= key_in_reg1;
end
/* 按位与,只要有一位出现了下降沿/上升沿,就会将该信号拉高 */
assign key_in_negedge = ((key_in_reg2 & ~key_in_reg1) != 'd0)? 1'b1 : 1'b0;
assign key_in_posedge = ((~key_in_reg2 & key_in_reg1) != 'd0)? 1'b1 : 1'b0;
reg start_cnt;
reg cnt_flag;
reg [19:0] cnt;
wire add_cnt,end_cnt;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
start_cnt <= 1'b0;
else if(end_cnt)
start_cnt <= 1'b0;
else if(key_in_negedge || key_in_posedge)
start_cnt <= 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
cnt <= 20'd0;
else if(add_cnt) begin
if(end_cnt)
cnt <= 20'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 20'd0;
assign add_cnt = start_cnt;
assign end_cnt = add_cnt && ((cnt == CNT_MAX - 1) || (key_in_negedge));
/* 触发缓存 */
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
key_reg <= 'd0;
else if(key_in_negedge)
key_reg <= key_in_reg1;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
state <= IDLE;
else case(state)
IDLE : if(key_in_negedge)
state <= DONE;
DONE : if(end_cnt) begin
if(key_in == key_reg)
state <= HOLD;
else
state <= IDLE;
end
HOLD : if(key_in_posedge)
state <= UP;
UP : if(end_cnt)
state <= IDLE;
default : state <= IDLE;
endcase
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
key_flag <= 'd0;
else if(state == HOLD && key_in_posedge)
key_flag <= ~key_in_reg2;
else
key_flag <= 'd0;
endmodule
不多说,看前边的文章就行。
2.数码管驱动
/**************************************功能介绍***********************************
Date : 2023年10月1日 19:56:46
Author : Yang.
Project : 自动售货机
Require : 1.售货机里有A.B.C三种商品,分别是3.5.8元.
2.按下key[1]选择购买哪种商品,key[3]确认购买此种商品;
3.按下key[1]选择购买该商品的数量,key[3]确认数量;
4.按下key[1]回到选择商品,按下key[2]直接结算并支付;
5.按下key[1]表示支付成功,led全亮.
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module seg_dirver(
input clk ,
input rst_n ,
input [5:0] seg_mask ,//掩码
input [23:0] seg_data ,//数码管显示的数据
input [5:0] seg_point ,//
小数点
output reg [5:0] sel ,
output [7:0] dig
);
//---------<参数定义>---------------------------------------------------------
parameter NUM_0 = 7'b100_0000 ,
NUM_1 = 7'b111_1001 ,
NUM_2 = 7'b010_0100 ,
NUM_3 = 7'b011_0000 ,
NUM_4 = 7'b001_1001 ,
NUM_5 = 7'b001_0010 ,
NUM_6 = 7'b000_0010 ,
NUM_7 = 7'b111_1000 ,
NUM_8 = 7'b000_0000 ,
NUM_9 = 7'b001_0000 ,
NUM_A = 7'b000_1000 ,
NUM_B = 7'b000_0011 ,
NUM_C = 7'b100_0110 ,
NUM_D = 7'b010_0001 ,
NUM_E = 7'b000_0110 ,
NUM_F = 7'b000_1110 ,
NUM_H = 7'b000_1001 ,
NUM_L = 7'b100_0111 ,
NUM_O = 7'b010_0011 ,
NUM_P = 7'b000_1100 ,
NUM_Q = 7'b001_1000 ,
NUM_U = 7'b100_0001 ;
//---------<内部信号定义>-----------------------------------------------------
//2ms
parameter CNT_2MS = 20'd1_000_00;
reg [19:0] cnt_2ms;
wire add_2ms_cnt,end_2ms_cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_2ms <= 'd0;
else if(add_2ms_cnt) begin
if(end_2ms_cnt)
cnt_2ms <= 'd0;
else
cnt_2ms <= cnt_2ms + 1'b1;
end
end
assign add_2ms_cnt = 1'b1;
assign end_2ms_cnt = add_2ms_cnt && cnt_2ms == CNT_2MS - 1;
//位选
reg [5:0] cnt_sel;
wire add_sel_cnt,end_sel_cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_sel <= 'd0;
else if(add_sel_cnt) begin
if(end_sel_cnt)
cnt_sel <= 'd0;
else
cnt_sel <= cnt_sel + 1'b1;
end
end
assign add_sel_cnt = end_2ms_cnt;
assign end_sel_cnt = add_sel_cnt && cnt_sel == 6 - 1;
reg [3:0] display_reg;
reg [6:0] seg_data_reg;
reg seg_point_reg;
//数据输出
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
display_reg <= 4'd0; /* 默认选择第0个数码管 */
else case(cnt_sel)
6'd0 : display_reg <= seg_data[3:0];
6'd1 : display_reg <= seg_data[7:4];
6'd2 : display_reg <= seg_data[11:8];
6'd3 : display_reg <= seg_data[15:12];
6'd4 : display_reg <= seg_data[19:16];
6'd5 : display_reg <= seg_data[23:20];
default : display_reg <= 4'd0;
endcase
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
seg_data_reg <= 7'h7F; //不亮
else case(display_reg)
6'd0 : seg_data_reg <= NUM_0;
6'd1 : seg_data_reg <= NUM_1;
6'd2 : seg_data_reg <= NUM_2;
6'd3 : seg_data_reg <= NUM_3;
6'd4 : seg_data_reg <= NUM_4;
6'd5 : seg_data_reg <= NUM_5;
6'd6 : seg_data_reg <= NUM_6;
6'd7 : seg_data_reg <= NUM_7;
6'd8 : seg_data_reg <= NUM_8;
6'd9 : seg_data_reg <= NUM_9;
6'd10 : seg_data_reg <= NUM_A;
6'd11 : seg_data_reg <= NUM_B;
6'd12 : seg_data_reg <= NUM_C;
6'd13 : seg_data_reg <= NUM_D;
6'd14 : seg_data_reg <= NUM_E;
6'd15 : seg_data_reg <= NUM_F;
default : seg_data_reg <= 7'h7F;
endcase
end
//小数点输出
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
seg_point_reg <= 1'b1; //小数点不亮
else case(cnt_sel)
6'd0 : seg_point_reg <= !(seg_point[0] && seg_mask[0]);
6'd1 : seg_point_reg <= !(seg_point[1] && seg_mask[1]);
6'd2 : seg_point_reg <= !(seg_point[2] && seg_mask[2]);
6'd3 : seg_point_reg <= !(seg_point[3] && seg_mask[3]);
6'd4 : seg_point_reg <= !(seg_point[4] && seg_mask[4]);
6'd5 : seg_point_reg <= !(seg_point[5] && seg_mask[5]);
default : seg_point_reg <= 1'b1;
endcase
end
assign dig = {seg_point_reg,seg_data_reg[6:0]}; /* 拼接小数点和数据 */
//位选输出
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
sel <= 6'b11_1110; //默认选择第一个数码管
else case(cnt_sel)
6'd0 : sel <= ~(6'b00_0001 & seg_mask);
6'd1 : sel <= ~(6'b00_0010 & seg_mask);
6'd2 : sel <= ~(6'b00_0100 & seg_mask);
6'd3 : sel <= ~(6'b00_1000 & seg_mask);
6'd4 : sel <= ~(6'b01_0000 & seg_mask);
6'd5 : sel <= ~(6'b10_0000 & seg_mask);
default : sel <= ~(6'b00_0000 & seg_mask); //不选择任何的数码管
endcase
end
endmodule
3.售货机主控
idle:初始状态,按下key1进入选择状态;
sort: 选择状态,选择购买哪种商品,按下key3,确认购买此商品;
num:选择购买此商品的数量,按key1加数量,key3确认;
decide:按下key2继续购买,按下key3直接支付。
/**************************************功能介绍***********************************
Date : 2023年10月1日 19:56:31
Author : Yang.
Project : 自动售货机
Require : 1.售货机里有A.B.C三种商品,分别是3.5.8元.
且好久没人上货了,每个商品存货不多,都只剩四个.希望没过期-_-
2.按下key[1]选择购买哪种商品,key[3]确认购买此种商品;
3.按下key[1]选择购买该商品的数量,key[3]确认数量;
4.按下key[1]回到选择商品,按下key[2]直接结算并支付;
5.按下key[1]表示支付成功,led全亮.
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module ctrl(
input clk ,
input rst_n ,
input [4:1] key_in ,
output [3:0] led ,
output reg [5:0] seg_mask ,
output reg [23:0] seg_data
);
//------------------------<参数定义>---------------------------
localparam A = 3 ,//A价格三元
B = 5 ,//B价格五元
C = 8 ;//C价格八元
reg [1:0] cnt_sel ; //a.b.c选择哪个商品
wire add_cnt_sel ;
wire end_cnt_sel ;
reg [3:0] cnt_a ; //选择a商品的数量
wire add_cnt_a ;
wire end_cnt_a ;
reg [3:0] cnt_b ; //选择b商品的数量
wire add_cnt_b ;
wire end_cnt_b ;
reg [3:0] cnt_c ; //选择c商品的数量
wire add_cnt_c ;
wire end_cnt_c ;
wire [6:0] sum ; //总价
wire [3:0] sum_s,sum_g ; //总价十位/个位,方便在数码管显示
//------------------------<状态机>---------------------------
parameter IDLE = 0 ,//初始
SORT = 1 ,//选择购买哪个商品
NUM = 2 ,//购买此商品的数量
DECIDE = 3 ,//选择继续购买还是直接支付
PAY = 4 ; //支付
reg [2:0] state ;
wire idle2sort ;
wire sort2num ;
wire num2decide ;
wire decide2sort ;
wire decide2pay ;
wire pay2idle ;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state <= IDLE ;
end
else begin
case (state)
IDLE : if(idle2sort)
state <= SORT ;
SORT : if(sort2num)
state <= NUM ;
NUM : if(num2decide)
state <= DECIDE ;
DECIDE : if(decide2sort)
state <= SORT ;
else if(decide2pay)
state <= PAY ;
PAY : if(pay2idle)
state <= IDLE ;
default: state <= IDLE ;
endcase
end
end
assign idle2sort = state == IDLE && key_in[1] ;//初始状态按下key[1]进入选商品阶段
assign sort2num = state == SORT && key_in[3] ;//选商品完成按下key[3]进入商品数量
assign num2decide = state == NUM && key_in[3] ;//数量选择完毕按下key[3]进入选择阶段
assign decide2sort = state == DECIDE && key_in[2] ;//按下key[2]继续购买
assign decide2pay = state == DECIDE && key_in[3] ;//按下key[2]直接支付
assign pay2idle = state == PAY && key_in[1] ;//按下key[1]支付完成交易结束
//------------------------<计数器>---------------------------
//进入sort状态后,按下key[1]选择商品种类
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_sel <= 0 ;
end
else if(add_cnt_sel)begin
if(end_cnt_sel)begin
cnt_sel <= 0 ;
end
else begin
cnt_sel <= cnt_sel + 1 ;
end
end
end
assign add_cnt_sel = state == SORT && key_in[1] ;
assign end_cnt_sel = add_cnt_sel && cnt_sel == 3 - 1 ;
//a商品数量选择
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_a <= 0 ;
end
else if(add_cnt_a)begin
if(end_cnt_a)begin
cnt_a <= 0 ;
end
else begin
cnt_a <= cnt_a + 1 ;
end
end
end
assign add_cnt_a = state == NUM && cnt_sel == 0 && key_in[1] ;//cnt_sel==0时表示选择a商品
assign end_cnt_a = add_cnt_a && cnt_a == 5 - 1 ;
//b商品数量
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_b <= 0 ;
end
else if(add_cnt_b)begin
if(end_cnt_b)begin
cnt_b <= 0 ;
end
else begin
cnt_b <= cnt_b + 1 ;
end
end
end
assign add_cnt_b = state == NUM && cnt_sel == 1 && key_in[1] ;//cnt_sel==1时表示选择b商品
assign end_cnt_b = add_cnt_b && cnt_b == 5 - 1 ;
//c商品数量
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_c <= 0 ;
end
else if(add_cnt_c)begin
if(end_cnt_c)begin
cnt_c <= 0 ;
end
else begin
cnt_c <= cnt_c + 1 ;
end
end
end
assign add_cnt_c = state == NUM && cnt_sel == 2 && key_in[1] ;//cnt_sel==2时表示选择c商品
assign end_cnt_c = add_cnt_c && cnt_c == 5 - 1 ;
//------------------------<结账>---------------------------
assign sum = 3*cnt_a + 5*cnt_b + 8*cnt_c ;//计算所有商品数量的价格
assign sum_s = sum/10 ;
assign sum_g = sum%10 ;
always@(*)begin
case (state)
IDLE : seg_data = {4'd10,cnt_a,4'd11,cnt_b,4'd12,cnt_c};//显示abc三种商品选择的数量
SORT,NUM : case (cnt_sel)
0 : seg_data = {4'd0,4'd10,4'd0,4'd3,4'd0,cnt_a};//选择购买a商品的话显示 a(种类) 3(单价) cnt_a(数量)
1 : seg_data = {4'd0,4'd11,4'd0,4'd5,4'd0,cnt_b};//选择购买b商品的话显示 b(种类) 5(单价) cnt_b(数量)
2 : seg_data = {4'd0,4'd12,4'd0,4'd8,4'd0,cnt_c}; //选择购买c商品的话显示 c(种类) 8(单价) cnt_c(数量)
default: seg_data = {4'd1,4'd0,4'd0,4'd0,4'd0,4'd1} ;
endcase
DECIDE : seg_data = {4'd10,cnt_a,4'd11,cnt_b,4'd12,cnt_c};//显示a/cnt_a/b/cnt_b/c/cnt_c;
PAY : seg_data = {4'd0,4'd0,4'd0,4'd0,sum_s,sum_g}; //显示总价
default : seg_data = {4'd10,cnt_a,4'd11,cnt_b,4'd12,cnt_c};
endcase
end
always@(*)begin
case (state)
IDLE : seg_mask = 6'b111111;
SORT,NUM : seg_mask = 6'b010101;
DECIDE : seg_mask = 6'b111111;
PAY : seg_mask = 6'b000011;
default: seg_mask = 6'b111111;
endcase
end
assign led = (state == PAY)?4'b1111 : 4'b0000;
endmodule
四个计数器,分别是在sort状态选择购买哪种商品;num状态a、b、c三种商品的数量计数。每种商品最大值四个。
assign sum = 3*cnt_a + 5*cnt_b + 8*cnt_c ;//计算所有商品数量的价格
assign sum_s = sum/10 ;
assign sum_g = sum%10 ;
这里就是计算商品的总价值。sum_s就是总价的十位,g就是总价的个位。一共就两位数,在数码管的后两位显示。
其他的代码里写注释挺全的,不会就评论区+私信。
总结
仿真可以参考上一个密码锁的仿真来写,要改的也不多,就是模仿按键按下,模拟出结果。
上板效果:
FPGA实战-----数码管售货机
博客上传的工程都在百度网盘,有压缩包也有整个文件,可以自行下载。
链接:https://pan.baidu.com/s/1lQqqWZXfb3i6XHwkKf52zg
提取码:yang