用FPGA制作一个简单的自动售货机
这篇博客讲了如何用FPGA模拟实现自动售卖机的功能。
文章目录
1. 程序功能和总体框架详解
程序功能:
- 按键K3为复位信号,led0,led1控制led等,siren控制蜂鸣器,seg_sel和seg_led分别控制数码管的片选和位选信号
- 有两个商品cnt_40和cnt_50,分别价值40元和50元,K1,K2连接按键,每按一下,代表要购买的cnt_40和cnt_50加1,例如:买2个cnt_40,1个cnt_50,则按2下K1和1下K2,选好商品后,按K4确认;
- 确认后开始投币,投币时K1,K2分别代表投入10元硬币和20元硬币,按K4确认;
- 用4个数码管分别显示2个商品购买个数和2种硬币投入个数
- 若硬币价钱和商品价钱相等,则出货:两个led灯的闪烁次数表示两个商品的出货个数
- 若不相等,蜂鸣器报警
整体框架的代码如下所示:
module top(input k1,input k2,input k4,input k3,input clk,output led1,output led2,output [5:0] seg_sel,output [7:0] seg_led,output siren);
wire key1,key2,key4,le,sir,f1hz;
wire [3:0] cnt_40;
wire [3:0] cnt_50;
wire [3:0] coin_10;
wire [3:0] coin_20;
wire [2:0] cnt;
wire [15:0] data;
assign data={cnt_40[3:0],cnt_50[3:0],coin_10[3:0],coin_20[3:0]};
debounce d1 (.rst(k3),.clock(clk),.noisy(k1),.clean(key1)); //Debounce模块
debounce d2 (.rst(k3),.clock(clk),.noisy(k2),.clean(key2));
debounce d3 (.rst(k3),.clock(clk),.noisy(k4),.clean(key4));
fsm f1(.clock(f1hz),.k1(key1),.k2(key2),.k3(k3),.k4(key4),.led(le),.siren(sir),.cnt_40(cnt_40),.cnt_50(cnt_50),.coin_10(coin_10),.coin_20(coin_20));
divider di1(.clock(clk),.rst(k3),.wave(f1hz)); //对应图中FSM模块
led l1(.clk(clk),.cnt_40(cnt_40),.cnt_50(cnt_50),.led(le),.rst(k3),.wave(f1hz),.led1(led2),.led0(led1));
siren si(.clk(clk),.rst(k3),.start(sir),.sir(siren));
display dx(.clk(clk),.rst(k3), .data(data),.seg_sel(seg_sel),.seg_led(seg_led));//对应图中Seg模块
endmodule
clk为时钟信号,下面是各个模块的解析和代码
实物效果图如下:
2.Divider分频模块
我使用的FPGA开发板的时钟频率为25Mhz,通过这个程序分频,得到一个1hz的时钟信号,led闪烁时,闪太快我们人眼看不出来,所以需要一个1hz的时钟信号,来让它闪慢一点。
module divider(input clock,input rst,output reg wave);
reg [30:0] cnt;
always@(posedge clock or negedge rst)
if(!rst)
begin
cnt<=0;
wave<=0;
end
else if(cnt==25000000)
begin
cnt<=0;
wave<=!wave;
end
else
begin
wave<=wave;
cnt<=cnt+1;
end
endmodule
3.Debounce模块,按键去抖
确保按键按一次,只会产生一个下降沿,因此需要一个去抖动模块
module debounce #(parameter DELAY=500000)
(input rst, clock, noisy,
output reg clean);
reg [19:0] count;
reg new;
always @(posedge clock)
if (!rst)
begin
count <= 0;
new <= noisy;
clean <= noisy;
end
else if (noisy != new)
begin
new <= noisy;
count <= 0;
end
else if (count == DELAY)
clean <= new;
else
count <= count+1;
endmodule
4.FSM状态机
有4种状态,
- Pay状态读取购物信息
- BIJIAO状态来比较货物价值和投入硬币价值是否相等
- SIREN_ON状态控制蜂鸣器报警
- CHUHUO控制出货(led闪烁)
module fsm
(input clock,
input k1,input k2,input k3,input k4,
output reg led,
output reg siren,
output reg [3:0] cnt_40,
output reg [3:0] cnt_50,
output reg [3:0] coin_10,
output reg [3:0] coin_20
);
parameter PRICE = 0;
parameter PAY = 1;//jie shou zhi fu shu liang
parameter BIJIAO = 2;//pan duan
parameter SIREN_ON = 3;//bao jing
parameter CHUHUO = 4;//chu huo
reg [2:0] state;
always @ (posedge clock or negedge k3 )
if (!k3) //复位
begin
state <= PRICE;
cnt_40<=0;
cnt_50<=0;
coin_20<=0;
coin_10<=0;
end
else
begin
case (state)
PRICE: begin //K1和K2按键决定cnt_40和cnt_50的购买数量,并输出到数码管显示;
led <= 0; //k4按键确认时,进入投币付费状态PAY
siren <= 0;
if(!k1)
begin
cnt_40 <= cnt_40 + 1;
end
if(!k2)
begin
cnt_50 <= cnt_50 + 1;
end
if(!k4)
begin
state <= PAY;
end
else
state<=state;
end
PAY: begin //投币付费状态,K1,K2按键决定10和20的硬币投入数量,并输出到数码管显示
if(!k1) //K4按键确认后,进入比较状态BIJIAO
begin
coin_10 <= coin_10 + 1;
end
else if(!k2)
begin
coin_20 <= coin_20 + 1;
end
else if(!k4)
state<=BIJIAO;
else
state<=state;
end
BIJIAO: begin //比较输入的货物价值和投入硬币价值是否相等,若相等,出货CHUHUO状态,否则报警状态SIREN_ON
if((4 * cnt_40+ 5 * cnt_50) == (2*coin_20 + coin_10))
begin
state <= CHUHUO;
end
else
begin
state <= SIREN_ON;
end
end
CHUHUO: begin //出货,led=1使能LED模块,LED模块后面介绍,开启两个led闪烁,闪烁次数等于两个货物的数量
led <= 1;
siren <= 0;
end
SIREN_ON: begin //报警,siren控制SIREN模块,使蜂鸣器报警
led <= 0;
siren <= 1;
end
default: state <= PRICE;
endcase
end
endmodule
5.Seg显示模块
接受来自状态机FSM的货物数量信号cnt_40,cnt_50,和投入10元20元硬币数量coin_10和coin_20;并使用4个数码管将其显示出来;
数码管显示的程序讲解已经有很多了,这里就不赘述了
module display(
input clk,
input rst,
input [15:0] data,
output reg [5:0] seg_sel,
output reg [7:0] seg_led
);
localparam CLK_DIVIDE = 4'd10 ;
localparam MAX_NUM = 13'd5000 ;
reg [3:0] clk_cnt ;
reg dri_clk ;
reg [12:0] cnt0 ;
reg flag ;
reg [2:0] cnt_sel ;
reg [3:0] num_disp ;
always @(posedge clk or negedge rst) begin
if(!rst) begin
clk_cnt <= 4'd0;
dri_clk <= 1'b1;
end
else if(clk_cnt >= CLK_DIVIDE/2 - 1'd1) begin
clk_cnt <= 4'd0;
dri_clk <= ~dri_clk;
end
else begin
clk_cnt <= clk_cnt + 1'b1;
dri_clk <= dri_clk;
end
end
always @ (posedge dri_clk or negedge rst) begin
if (rst == 1'b0) begin
cnt0 <= 13'b0;
flag <= 1'b0;
end
else if (cnt0 < MAX_NUM - 1'b1) begin
cnt0 <= cnt0 + 1'b1;
flag <= 1'b0;
end
else begin
cnt0 <= 13'b0;
flag <= 1'b1;
end
end
always @ (posedge dri_clk or negedge rst) begin
if (rst == 1'b0)
cnt_sel <= 3'b0;
else if(flag) begin
if(cnt_sel < 3'd4)
cnt_sel <= cnt_sel + 1'b1;
else
cnt_sel <= 3'b0;
end
else
cnt_sel <= cnt_sel;
end
always @ (posedge dri_clk or negedge rst) begin
if(!rst) begin
seg_sel <= 6'b111111;
num_disp <= 4'b0;
end
else begin
case (cnt_sel)
3'd0 :begin
seg_sel <= 6'b111101;
num_disp <= data[3:0] ;
end
3'd1 :begin
seg_sel <= 6'b111011;
num_disp <= data[7:4] ;
end
3'd2 :begin
seg_sel <= 6'b110111;
num_disp <= data[11:8];
end
3'd3 :begin
seg_sel <= 6'b101111;
num_disp <= data[15:12];
end
default :begin
seg_sel <= 6'b111111;
num_disp <= 4'b0;
end
endcase
end
end
always @ (posedge clk or negedge rst) begin
if (!rst)
seg_led <= 8'b0;
else begin
case (num_disp)
4'h0 : seg_led <= 8'b1100_0000;
4'h1 : seg_led <= 8'b1111_1001;
4'h2 : seg_led <= 8'b1010_0100;
4'h3 : seg_led <= 8'b1011_0000;
4'h4 : seg_led <= 8'b1001_1001;
4'h5 : seg_led <= 8'b1001_0010;
4'h6 : seg_led <= 8'b1000_0010;
4'h7 : seg_led <= 8'b1111_1000;
4'h8 : seg_led <= 8'b1000_0000;
4'h9 : seg_led <= 8'b1001_0000;
4'ha : seg_led <= 8'b1000_1000;
4'hb : seg_led <= 8'b1000_0011;
4'hc : seg_led <= 8'b1100_0110;
4'hd : seg_led <= 8'b1010_0001;
4'he : seg_led <= 8'b1000_0110;
4'hf : seg_led <= 8'b1000_1110;
default : seg_led <= 8'b1100_0000;
endcase
end
end
endmodule
6.Siren蜂鸣器报警模块
蜂鸣器报警没什么好讲的,低电平就不响,高电平就响,以一定的频率开关蜂鸣器,能产生不同的声音,该蜂鸣器模块功能:
- start信号为1时,开启蜂鸣器报警
- 开启蜂鸣器报警时,蜂鸣器以一定的频率发出声音
module siren(input clk,input rst,input start,output reg sir);
reg [24:0] T;
reg [24:0] cnt;
reg [24:0] cnt1;
reg clk1;
always@(posedge clk)
if(cnt1>1000_0000)
begin
cnt1<=0;
clk1<=!clk1;
end
else
cnt1<=cnt1+1;
always@(posedge clk1)
if(!rst)
T<=0;
else if(T<50000 || T>100000)
begin
T<=50000;
end
else begin
T<=T+10000;
end
always@(posedge clk or negedge rst)
if(!rst)
begin
sir<=0;
cnt<=0;
end
else if(start==1 && cnt<T)
cnt<=cnt+10;
else if(start==1)
begin
cnt<=0;
sir<=!sir;
end
else begin
sir<=0;
cnt<=0;
end
endmodule
7. Led灯闪烁模块
该模块功能:
- 控制信号为1时,控制两个led灯闪烁
- 闪烁的次数等于输入的两个信号代表的数量
module led(input clk,input [3:0] cnt_40,input [3:0] cnt_50,input led,input rst,input wave,output reg led1,output reg led0);
reg led0_twinkle;
reg led1_twinkle;
reg [3:0] num1;
reg [3:0] num2;
always@(posedge clk or negedge rst)
if(!rst)
begin
led0 <=1'b0;
led1 <=1'b0;
end
else if(!led)
begin
led0 <=1'b0;
led1 <=1'b0;
end
else
begin
led0 <=led0_twinkle;
led1 <=led1_twinkle;
end
always@(posedge wave)
if(!rst)
begin
led0_twinkle <=0;
num1<=0;
end
else if (led==1 && num1<2*cnt_40)
begin
led0_twinkle <=!led0_twinkle;
num1<=num1+1;
end
else
begin
led0_twinkle <=0;
num1<=num1;
end
always@(posedge wave)
if(!rst)
begin
led1_twinkle <=0;
num2<=0;
end
else if (led==1 && num2<2*cnt_50)
begin
led1_twinkle <=!led1_twinkle;
num2<=num2+1;
end
else
begin
led1_twinkle <=0;
num2<=num2;
end
endmodule
总结
这里对文章进行总结,这篇博客用FPGA的状态机的概念,做了一个自动售货机,并详细介绍了各个模块的功能及代码,仅供参考。