文章目录
- 前言
- 一、系统设计
- 1、模块框图
- 2、状态机框图
- 3、RTL视图
- 二、源码
- 1.蜂鸣器驱动模块
- 2.按键消抖模块
- 3、PWM模块
- 4、sale_goods模块(状态机部分)
- 5、数码管驱动模块
- 6、Sales(顶层模块)
- 三、效果
- 四、总结
- 五、参考资料
前言
环境:
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