一.任务
功能完整描述:
KEY4:开关机按键,复位时,默认是关机状态,数码管和LED灯均不亮,同时蜂鸣器响,其余按键按下无效。
KEY3:在开机状态时,投币1元
KEY2:在开机状态时,投币为0.5元
KEY1:当投币但少于货物的价格时,取消订单,数码管显示为0.0,同时LED灯实现跑马灯2s然后熄灭
当投币为2.5元时,刚好能够购买货物,4个LED灯同时闪烁2s然后熄灭,同时数码管数字清零
当投币为3元时,购买货物还需找零,4个LED灯实现流水灯2s然后熄灭,同时数码管数字清零
思路:
位选信号:
两种状态 6’b111_110,6’b111_101让这两个状态频繁切换,看起来像是一直亮着一样。
售货机总共有8个状态:
1:用户投币总数为0,也是初始状态,此时数码管显示0.0
①当用户选择投币0.5,跳转到第2个状态
②当用户选择投币1,跳转到第3个状态
2:用户投币总数为0.5,数码管显示0.5
①当用户选择投币0.5,跳转到第3个状态
②当用户选择投币1,跳转到第4个状态
③当用户取消,跳转到第8个状态,同时开始计时
3:用户投币总数为1,数码管显示1.0
①当用户选择投币0.5,跳转到第4个状态
②当用户选择投币1,跳转到第5个状态
③当用户取消,跳转到第8个状态,同时开始计时
4:用户投币总数为1.5,数码管显示1.5
①当用户选择投币0.5,跳转到第5个状态
②当用户选择投币1,跳转到第6个状态,同时开始计时
③当用户取消,跳转到第8个状态,同时开始计时
5:用户投币总数为2.0,数码管显示2.0
①当用户选择投币0.5,跳转到第6个状态,同时开始计时
②当用户选择投币1,跳转到第7个状态,同时开始计时
③当用户取消,跳转到第8个状态,同时开始计时
6:用户投币总数为2.5,数码管显示2.5
①该状态保持2s后结束计时
7:用户投币总数为3,数码管显示3.0
①该状态保持2s后结束计时
8:用户取消订,数码管显示0.0
①该状态保持2s后结束计时
二.工程项目
Verilog HDL编写
①设计按键消抖模块
key_debounce.v
module key_debounce(
input wire clk,
input wire rst_n,
input wire key,
output reg flag, //判断抖动是否消除的标志信号,0为抖动,1为抖动结束
output reg key_value //消抖后稳定的按键值给到蜂鸣器模块
);
//定义20ms延迟计数器,0.2s,1_000_000次
reg [19:0] delay_cnt;
//寄存依次key的值用来判断按键是否消抖成功
reg key_reg;
//20ms延时计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
begin
key_reg <= 1'b1; //复位信号,设置按键无效
delay_cnt <= 1'b0; //计数器设置为0
end
else
begin
key_reg <= key;
if(key_reg == 1 && key == 0) //当这一次key值和上一次key值不一样,证明正在抖动
delay_cnt <= 20'd1_000_000; //延迟时间20ms
else if(delay_cnt > 0)
delay_cnt <= delay_cnt - 1; //没有抖动,开始20ms倒计时
else
delay_cnt <= 1'b0;
end
end
//根据延时计数器获取按键状态以及按键值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
begin
flag <= 1'b0; //复位信号,设置信号标志为抖动
key_value <= 1'b1; //设置抽样值为1
end
else
begin
if(delay_cnt == 20'd1) //倒计时1_000_000到1
begin
flag <= 1'b1;
key_value <= key; //稳定20ms后将key值给到key_value
end
else
begin
flag <= 1'b0;
key_value <= key_value; //20ms内先不取样
end
end
end
endmodule
②设计数码管位选驱动
sel_drive.v
module sel_drive(
input wire clk,
input wire rst_n,
input wire boot_flag,
output wire [5:0] sel
);
parameter MAX_NUM = 10'd999;//20us
reg [5:0] sel_r;
reg [9:0] cnt;
reg sel_flag;
//20us计时器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 10'd0;
end
else if(cnt == MAX_NUM)begin
cnt <= 10'd0;
end
else begin
cnt <= cnt + 1'd1;
end
end
//状态切换计数器模块
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
sel_flag <= 1'b0;
else if(cnt == MAX_NUM)
sel_flag <= ~sel_flag;
else
sel_flag <= sel_flag;
end
//位选信号切换功能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sel_r <= 6'b111_111;
end
else begin
case(boot_flag)
1'b0:begin //关机状态
sel_r <= 6'b111_111;
end
1'b1:begin //开机状态
case(sel_flag)
1'b0: sel_r <= 6'b111_110;
1'b1: sel_r <= 6'b111_101;
default:sel_r <= sel_r;
endcase
end
endcase
end
end
assign sel = sel_r;
endmodule
③设计数码管显示模块
seg_scan.v
module seg_scan(
input wire clk,
input wire rst_n,
input wire [3:0] money_flag,//用户投币标识
input wire [5:0] sel,//位选信号
output wire [7:0] seg//段选信号
);
reg [3:0] number;
reg [7:0] seg_r;
reg flag;//标识当前显示的数字是否是小数点后面的值 1:是 0:不是
//根据用户投币标识的不同显示不同的数字
always@(*)begin
case(money_flag)
4'd0:begin//显示0.0
case(sel)
6'b111_110: begin
number = 4'd0;
flag = 1'b0;//第二个零不带小数点
end
6'b111_101: begin
number = 4'd0;
flag = 1'b1;//第一个零带小数点
end
default : begin
number = 4'd0;
flag = 1'b0;
end
endcase
end
4'd1:begin//显示0.5
case(sel)
6'b111_110: begin
number = 4'd5;
flag = 1'b0;
end
6'b111_101: begin
number = 4'd0;
flag = 1'b1;
end
default : begin
number = 4'd0;
flag = 1'b0;
end
endcase
end
4'd2:begin//显示1.0
case(sel)
6'b111_110: begin
number = 4'd0;
flag = 1'b0;
end
6'b111_101: begin
number = 4'd1;
flag = 1'b1;
end
default : begin
number = 4'd0;
flag = 1'b0;
end
endcase
end
4'd3:begin//显示1.5
case(sel)
6'b111_110: begin
number = 4'd5;
flag = 1'b0;
end
6'b111_101: begin
number = 4'd1;
flag = 1'b1;
end
default : begin
number = 4'd0;
flag = 1'b0;
end
endcase
end
4'd4:begin//显示2.0
case(sel)
6'b111_110: begin
number = 4'd0;
flag = 1'b0;
end
6'b111_101: begin
number = 4'd2;
flag = 1'b1;
end
default : begin
number = 4'd0;
flag = 1'b0;
end
endcase
end
4'd5:begin//显示2.5
case(sel)
6'b111_110: begin
number = 4'd5;
flag = 1'b0;
end
6'b111_101: begin
number = 4'd2;
flag = 1'b1;
end
default : begin
number = 4'd0;
flag = 1'b0;
end
endcase
end
4'd6:begin//显示3.0
case(sel)
6'b111_110: begin
number = 4'd0;
flag = 1'b0;
end
6'b111_101: begin
number = 4'd3;
flag = 1'b1;
end
default : begin
number = 4'd0;
flag = 1'b0;
end
endcase
end
4'd7:begin//显示0.0
case(sel)
6'b111_110: begin
number = 4'd0;
flag = 1'b0;
end
6'b111_101: begin
number = 4'd0;
flag = 1'b1;
end
default : begin
number = 4'd0;
flag = 1'b0;
end
endcase
end
default:begin//显示0.0
case(sel)
6'b111_110: begin
number = 4'd0;
flag = 1'b0;
end
6'b111_101: begin
number = 4'd0;
flag = 1'b1;
end
default : begin
number = 4'd0;
flag = 1'b0;
end
endcase
end
endcase
end
always@(*)begin
case(number)
4'd0 : begin
if(flag == 1'b1)begin//显示小数点
seg_r = 8'b0100_0000;//数码管显示0 带小数点
end
else begin
seg_r = 8'b1100_0000;//数码管显示0 不带小数点
end
end
4'd1 : begin
if(flag == 1'b1)begin//显示小数点
seg_r = 8'b0111_1001;//数码管显示1 带小数点
end
else begin
seg_r = 8'b1111_1001;//数码管显示1 不带小数点
end
end
4'd2 : begin
if(flag == 1'b1)begin//显示小数点
seg_r = 8'b0010_0100;//数码管显示2 带小数点
end
else begin
seg_r = 8'b1010_0100;//数码管显示2 不带小数点
end
end
4'd3 : begin
if(flag == 1'b1)begin//显示小数点
seg_r = 8'b0011_0000;//数码管显示3 带小数点
end
else begin
seg_r = 8'b1011_0000;//数码管显示3 不带小数点
end
end
4'd5 : begin
if(flag == 1'b1)begin//显示小数点
seg_r = 8'b0001_0010;//数码管显示5 带小数点
end
else begin
seg_r = 8'b1001_0010;//数码管显示5 不带小数点
end
end
default: begin
if(flag == 1'b1)begin//显示小数点
seg_r = 8'b0100_0000;//数码管显示0 带小数点
end
else begin
seg_r = 8'b1100_0000;//数码管显示0 不带小数点
end
end
endcase
end
assign seg = seg_r;
endmodule
④设计售货机购物模块
vending_machine.v
module vending_machine(
input wire clk,//时钟
input wire rst_n,//复位
input [3:0] key_value,//按键值
input [3:0] flag,
output reg beep,
output reg [3:0] led,
output wire [3:0] money_flag_w,
output wire boot_flag_w
);
parameter MAX_NUM = 9_999_999;//定义最大计时0.2s LED灯闪烁间隔
parameter MAX_NUM2 = 10;//2s计数基于0.2s
//定义状态计数器,4个灯4个状态
reg [1:0] led_flag;
reg boot_flag; //标识售货机是否处于开机状态 0:关机 1:开机
reg [3:0] money_flag;//用户投币的状态有8种 分别是0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 退货的0
reg [23:0] cnt = 0; //计时器赋初值为0
reg [26:0] cnt_1 = 0; //计时器赋初值为0
reg time_flag;//开始计时标记 1:开始计时 0:不开始
//0.2s计数器模块
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 1'b0; //按下复位键,清零
else if(cnt == MAX_NUM) //计时器达到最大值,清零重新计数
cnt <= 1'b0;
else
cnt <= cnt + 1'b1;
end
//2s计数器模块
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_1 <= 1'b0; //按下复位键,清零
else if(time_flag == 1)begin//开始计时
if(cnt == MAX_NUM)begin
if(cnt_1 <MAX_NUM2)begin
cnt_1 <= cnt_1 + 1'b1;
end
else begin
cnt_1 <= 1'b0;//cnt_1计数达到最大就清空
end
end
else begin
cnt_1 <= cnt_1;//其余时间保持
end
end
else begin
cnt_1 <= 1'b0;//不计时,cnt_1清空
end
end
//状态切换计数器模块 led_flag的状态基于0.2s
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
led_flag <= 1'b0;
else if(cnt == MAX_NUM)
led_flag <= led_flag + 1'b1; //超出宽度截取低两位
else
led_flag <= led_flag;
end
//售货机开关机状态控制 key4
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
boot_flag <= 1'b0; //初始状态关机中
beep <= 1'b0;//关机状态,蜂鸣器响
end
else if(flag[3] == 1'b1 && key_value[3] == 1'b0)begin//当按键key4按下时,售货机开关机状态取反
boot_flag <= ~boot_flag;
beep <= ~beep;//当开机状态时蜂鸣器停止
end
else begin
boot_flag <= boot_flag;//其余情况售货机开关机状态保持
beep <= beep;//蜂鸣器状态保持
end
end
assign boot_flag_w = boot_flag;
//当投入人民币购买功能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
money_flag <= 4'd0;//初始用户投币为0
end
else if(boot_flag)begin//只有当售货机是开机状态才可以购买
case(money_flag)
4'd0:begin//用户投币为0的情况下
if(flag[1] == 1'b1 && key_value[1] == 1'b0)begin//用户投币0.5
money_flag <= 4'd1;//记下第二个状态
end
else if(flag[2] == 1'b1 && key_value[2] == 1'b0)begin//用户投币1
money_flag <= 4'd2;//记下第三个状态
end
// else if(flag[0] == 1'b1 && key_value[0] == 1'b0)begin//用户退货
// money_flag <= 4'd7;//记下第八个状态
// time_flag <= 1'b1;//开始计时
// end
else begin//用户没操作,状态保持
money_flag <= money_flag;
end
end
4'd1:begin//用户投币为0.5的情况下
if(flag[1] == 1'b1 && key_value[1] == 1'b0)begin//用户投币0.5
money_flag <= 4'd2;//记下第三个状态
end
else if(flag[2] == 1'b1 && key_value[2] == 1'b0)begin//用户投币1
money_flag <= 4'd3;//记下第四个状态
end
else if(flag[0] == 1'b1 && key_value[0] == 1'b0)begin//用户退货
money_flag <= 4'd7;//记下第八个状态
time_flag <= 1'b1;//开始计时
end
else begin//用户没操作,状态保持
money_flag <= money_flag;
end
end
4'd2:begin//用户投币为1的情况下
if(flag[1] == 1'b1 && key_value[1] == 1'b0)begin//用户投币0.5
money_flag <= 4'd3;//记下第四个状态
end
else if(flag[2] == 1'b1 && key_value[2] == 1'b0)begin//用户投币1
money_flag <= 4'd4;//记下第五个状态
end
else if(flag[0] == 1'b1 && key_value[0] == 1'b0)begin//用户退货
money_flag <= 4'd7;//记下第八个状态
time_flag <= 1'b1;//开始计时
end
else begin//用户没操作,状态保持
money_flag <= money_flag;
end
end
4'd3:begin//用户投币为1.5的情况下
if(flag[1] == 1'b1 && key_value[1] == 1'b0)begin//用户投币0.5
money_flag <= 4'd4;//记下第五个状态
end
else if(flag[2] == 1'b1 && key_value[2] == 1'b0)begin//用户投币1
money_flag <= 4'd5;//记下第六个状态
time_flag <= 1'b1;//开始计时
end
else if(flag[0] == 1'b1 && key_value[0] == 1'b0)begin//用户退货
money_flag <= 4'd7;//记下第八个状态
time_flag <= 1'b1;//开始计时
end
else begin//用户没操作,状态保持
money_flag <= money_flag;
end
end
4'd4:begin//用户投币为2的情况下
if(flag[1] == 1'b1 && key_value[1] == 1'b0)begin//用户投币0.5
money_flag <= 4'd5;//记下第六个状态
time_flag <= 1'b1;//开始计时
end
else if(flag[2] == 1'b1 && key_value[2] == 1'b0)begin//用户投币1
money_flag <= 4'd6;//记下第七个状态
time_flag <= 1'b1;//开始计时
end
else if(flag[0] == 1'b1 && key_value[0] == 1'b0)begin//用户退货
money_flag <= 4'd7;//记下第八个状态
time_flag <= 1'b1;//开始计时
end
else begin//用户没操作,状态保持
money_flag <= money_flag;
end
end
4'd5:begin//用户投币为2.5的情况下 保持2s
if(cnt_1 == MAX_NUM2)begin
money_flag <= 4'd0;//返回第一种状态
time_flag <= 1'b0;//结束计时
end
else begin
money_flag <= money_flag;
end
end
4'd6:begin//用户投币为3的情况下 保持2s
if(cnt_1 == MAX_NUM2)begin
money_flag <= 4'd0;//返回第一种状态
time_flag <= 1'b0;//结束计时
end
else begin
money_flag <= money_flag;
end
end
4'd7:begin//用户退货的情况下 保持2s
if(cnt_1 == MAX_NUM2)begin
money_flag <= 4'd0;//返回第一种状态
time_flag <= 1'b0;//结束计时
end
else begin
money_flag <= money_flag;
end
end
endcase
end
else begin
money_flag <= 4'd0;
end
end
//led控制模块
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
led <= 4'b0000;
else if(money_flag == 4'd5)begin//2.5块购买成功不找零 闪烁
case(led_flag)
2'b00 :led <= 4'b1111;
2'b01 :led <= 4'b0000;
2'b10 :led <= 4'b1111;
2'b11 :led <= 4'b0000;
default:led <= 4'b1111;
endcase
end
else if(money_flag == 4'd6)begin//3块购买成功找零 流水灯
case(led_flag)
2'b00 :led <= 4'b1000;
2'b01 :led <= 4'b0100;
2'b10 :led <= 4'b0010;
2'b11 :led <= 4'b0001;
default:led <= 4'b1000;
endcase
end
else if(money_flag == 4'd7)begin//退货 跑马灯(蜂鸣器)
case(led_flag)
2'b00 :led <= 4'b1000;
2'b01 :led <= 4'b1100;
2'b10 :led <= 4'b1110;
2'b11 :led <= 4'b1111;
default:led <= 4'b1000;
endcase
end
else
led <= 4'b0000;//其余情况led不亮
end
assign money_flag_w = money_flag;
endmodule
⑤设计顶层模块
top_vending_machine.v
module top_vending_machine(
input wire clk,//时钟
input wire rst_n,//复位
input wire [3:0] key,//三个按键
output wire beep,
output wire [3:0] led,//led灯
output wire [7:0] seg,//段选信号
output wire [5:0] sel//位选信号
);
wire [3:0] money_flag;
wire [3:0] key_value;
wire [3:0] flag;
wire boot_flag;
//例化按键key1消抖模块
key_debounce inst_key_debounce1(
.clk (clk ),
.rst_n (rst_n ),
.key (key[0] ),
.flag (flag[0] ),
.key_value (key_value[0])
);
//例化按键key2消抖模块
key_debounce inst_key_debounce2(
.clk (clk ),
.rst_n (rst_n ),
.key (key[1] ),
.flag (flag[1] ),
.key_value (key_value[1])
);
//例化按键key3消抖模块
key_debounce inst_key_debounce3(
.clk (clk ),
.rst_n (rst_n ),
.key (key[2] ),
.flag (flag[2] ),
.key_value (key_value[2])
);
//例化按键key4消抖模块
key_debounce inst_key_debounce4(
.clk (clk ),
.rst_n (rst_n ),
.key (key[3] ),
.flag (flag[3] ),
.key_value (key_value[3])
);
sel_drive inst_sel_drive(
.clk (clk ),
.rst_n (rst_n ),
.boot_flag (boot_flag),
.sel (sel )
);
seg_scan inst_seg_scan(
.clk (clk ),
.rst_n (rst_n ),
.money_flag(money_flag),
.sel (sel ),//位选信号
.seg (seg )//段选信号
);
vending_machine inst_vending_machine(
.clk (clk ),//时钟
.rst_n (rst_n ),//复位
.key_value ({key_value[3],key_value[2],key_value[1],key_value[0]}),//按键值
.flag ({flag[3],flag[2],flag[1],flag[0]}),
.beep (beep) ,
.led (led ),
.money_flag_w(money_flag),
.boot_flag_w(boot_flag)
);
endmodule
编译:
查看RTL门级电路:
引脚绑定:
硬件测试:
文章来源:https://www.toymoban.com/news/detail-482420.html
三.总结
售货机的思路和电子锁类似,主要是找出所有状态及切换条件。文章来源地址https://www.toymoban.com/news/detail-482420.html
到了这里,关于【FPGA入门八】自动售货机的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!