自动售货机中有价值为1元,3.5元,5元的三种饮料,能识别的金额为0.5元,1元,5元,10元。在购买饮料时,当输入金额小于饮料价值时,LED灯100ms闪烁;输入金额与饮料价值相等时,LED灯常亮;输入金额大于饮料价值时,显示多出的钱数,LED灯2s闪烁。饮料与输入金额须在数码管显示,饮料选择与金额输入可通过按键或者拨码开关进行。
这个自动售货机采用top-down思想,主要分为5个模块,分别是:消抖模块、计算模块、状态机控制模块、led模块、显示模块;
这个自动售货机根据·功能分析,它的输入有:系统时钟、复位信号、[2:0]drink(表示所选货物,拨码开关控制 分别为1元,3.5元,5元)、四种钱币的信号(0.5元,1元,5元,10元 由按键控制);它的输出有:数码管的位选和段选(sel[3:0],seg[7:0])和一个led的控制信号;
本模块的实现主要是各个端口的例化,想要清晰的的实现本模块,画图是最简单的方式,所以,这个自动售货机的结构图如下:
根据上图所示,本模块的代码为:
module top(
input clk, //系统时钟
input rst, //复位信号
input [2:0] drink,//货物
input money_one, //0.5
input money_two, //1
input money_three, //5
input money_four, //10
output wire [3:0] sel,//数码管位选
output wire [7:0] seg,//数码管段选
output wire led
);
wire [7:0] money_1;
wire [7:0] money_2;
wire [2:0] flag;
wire [11:0] money_in;
wire [11:0] money_drink;
wire [11:0] money_out;
wire [2:0] ctrl;
//消抖部分
xiaodou s0(
.clk(clk),
.rst(rst),
.key_in(money_one),
.key_flag(key1)
);
xiaodou s1(
.clk(clk),
.rst(rst),
.key_in(money_two),
.key_flag(key2)
);
xiaodou s2(
.clk(clk),
.rst(rst),
.key_in(money_three),
.key_flag(key3)
);
xiaodou s3(
.clk(clk),
.rst(rst),
.key_in(money_four),
.key_flag(key4)
);
//计算部分
money s4(
.clk(clk),
.rst(rst),
.key1(key1),
.key2(key2),
.key3(key3),
.key4(key4),
.drink(drink),
.money_1(money_1),
.money_2(money_2),
.flag(flag),
.money_in(money_in),
.money_drink(money_drink),
.money_out(money_out)
);
//状态机
zhaungtai s5(
.clk(clk),
.rst(rst),
.flag(flag),
.ctrl(ctrl)
);
//数码管显示部分
xianshi s6(
.clk(clk),
.rst(rst),
.ctrl(ctrl),
.date1(money_drink),
.date2(money_in),
.date3(money_out),
.sel(sel),
.seg(seg)
);
//led灯控制部分
led s7(
.clk(clk),
.rst(rst),
.flag(flag),
.money_1(money_1),
.money_2(money_2),
.led(led)
);
endmodule
本模块的主要功能是对四个有按键输入的信号进行消抖,并输出一个脉冲信号;本模块的实现方法与上个文章的实现方法一致,在此不再赘述;
本模块的主要功能是计算所选的饮料的价格、输入铅笔的价格和所要找零的钱数并将其转化为BCD码(方便在数码管上显示);
本模块的的输入有:系统时钟、复位信号、drink[2:0]、四个消抖后的钱币信号;输出有: money_1所选货物的价格(二进制)、money_2/所支付的钱数(二进制)flag状态机状态转化信号、money_in所支付的钱数(BCD)、 money_drink所选货物的价格(BCD)、 money_out 找零的钱数(BCD)。
本模块的实现困难之处是在数据转化为BCD码上,对于本模块我采用了三种实现方法:
money_drink转化为BCD码:
这个信号的实现较为简单、直接通过一个译码器来实现:
always@(posedge clk or negedge rst) begin
if(rst==0)
money_1<=0;
else begin
case(drink)
3'd000:begin money_1<='d0 ;money_drink<=12'b0000_0000_0000; end
3'd001:begin money_1<='d10;money_drink<=12'b0000_0001_0000; end
3'd010:begin money_1<='d35;money_drink<=12'b0000_0011_0101; end
3'd011:begin money_1<='d45;money_drink<=12'b0000_0100_0101; end
3'd100:begin money_1<='d50;money_drink<=12'b0000_0101_0000; end
3'd101:begin money_1<='d60;money_drink<=12'b0000_0110_0000; end
3'd110:begin money_1<='d85;money_drink<=12'b0000_1000_0101; end
3'd111:begin money_1<='d95;money_drink<=12'b0000_1001_0101; end
endcase
end
end
money_in转化为BCD码:
这个信号的处理采用直接赋值的方式,直接对数据的[3:0]、[7:4]进行赋值来实现;
always@(posedge clk or negedge rst)begin
if(!rst) begin
money_in<=12'd0;
money_2<='d0;
end
else if(money_in[3:0]>4'b1001)begin
money_in[3:0]<=money_in[3:0]-4'b1010;
money_in[7:4]<=money_in[7:4]+1'b1;
end
else if(money_in[7:4]>4'b1001)begin
money_in[7:4]<=money_in[3:0]-4'b1010;
money_in[11:8]<=money_in[7:0]+1'b1;
end
else if(key1) begin
money_in[3:0]<=money_in[3:0]+4'b0101;
money_2<=money_2+'d5;
end
else if(key2) begin
money_in[7:4]<=money_in[7:4]+4'b0001;
money_2<=money_2+'d10;
end
else if(key3) begin
money_in[7:4]<=money_in[7:4]+4'b0101;
money_2<=money_2+'d50;
end
else if(key4) begin
money_in[11:8]<=money_in[11:8]+4'b0001;
money_2<=money_2+'d100;
end
else
money_in<=money_in;
end
money_out转化为BCD码 :
对这个部分的操作是一个比较通用的方法:由于BCD码只对0到9的数据有效,所以当我们这个数据:x
10=
20=
...以此类推;
always@(posedge clk or negedge rst) begin
if(rst==0)
money_3<=0;
else if(money_2>=money_1&&flag[2]==0)
money_3<=money_2-money_1;
else
money_3<=money_3;
end
always@(posedge clk or negedge rst) begin
if(rst==0)
money_out<=0;
else if(money_out[3:0]>4'b1001)begin
money_out[3:0]<=money_out[3:0]-4'b1010;
money_out[7:4]<=money_out[7:4]+1'b1;
end
else if(money_out[7:4]>4'b1001)begin
money_out[7:4]<=money_out[3:0]-4'b1010;
money_out[11:8]<=money_out[7:0]+1'b1;
end
else if(money_3>='d90&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b1001;
money_out[3:0]<=money_3-'d90;
end
else if(money_3>='d80&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b1000;
money_out[3:0]<=money_3-'d80;
end
else if(money_3>='d70&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0111;
money_out[3:0]<=money_3-'d70;
end
else if(money_3>='d60&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0110;
money_out[3:0]<=money_3-'d60;
end
else if(money_3>='d50&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0101;
money_out[3:0]<=money_3-'d50;
end
else if(money_3>='d40&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0100;
money_out[3:0]<=money_3-'d40;
end
else if(money_3>='d30&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0011;
money_out[3:0]<=money_3-'d30;
end
else if(money_3>='d20&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0010;
money_out[3:0]<=money_3-'d20;
end
else if(money_3>='d10&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0001;
money_out[3:0]<=money_3-'d10;
end
本模块还有一个对状态机进行控制的转换信号:
由于key1,key2,key3,key4这四个信号都是脉冲信号,要将对其首先判断;
本模块的完整代码为:
module money(
input clk,
input rst,
input key1, //消抖后的信号
input key2, //消抖后的信号
input key3, //消抖后的信号
input key4, //消抖后的信号
input [2:0] drink,//货物
output reg [7:0] money_1, //所选货物的价格(二进制)
output reg [7:0] money_2, //所支付的钱数(二进制)
output reg[2:0] flag, //状态机状态转化信号
output reg[11:0] money_in, //所支付的钱数(BCD)
output reg[11:0] money_drink, //所选货物的价格(BCD)
output reg[11:0] money_out //找零的钱数(BCD)
);
reg [7:0] money_3; //找零的钱数(二进制)
//所负的总钱数并转化为bcd码
always@(posedge clk or negedge rst)begin
if(!rst) begin
money_in<=12'd0;
money_2<='d0;
end
else if(money_in[3:0]>4'b1001)begin
money_in[3:0]<=money_in[3:0]-4'b1010;
money_in[7:4]<=money_in[7:4]+1'b1;
end
else if(money_in[7:4]>4'b1001)begin
money_in[7:4]<=money_in[3:0]-4'b1010;
money_in[11:8]<=money_in[7:0]+1'b1;
end
else if(key1) begin
money_in[3:0]<=money_in[3:0]+4'b0101;
money_2<=money_2+'d5;
end
else if(key2) begin
money_in[7:4]<=money_in[7:4]+4'b0001;
money_2<=money_2+'d10;
end
else if(key3) begin
money_in[7:4]<=money_in[7:4]+4'b0101;
money_2<=money_2+'d50;
end
else if(key4) begin
money_in[11:8]<=money_in[11:8]+4'b0001;
money_2<=money_2+'d100;
end
else
money_in<=money_in;
end
//所选饮料的钱数并将其转化为bcd码
always@(posedge clk or negedge rst) begin
if(rst==0)
money_1<=0;
else begin
case(drink)
3'd000:begin money_1<='d0 ;money_drink<=12'b0000_0000_0000; end
3'd001:begin money_1<='d10;money_drink<=12'b0000_0001_0000; end
3'd010:begin money_1<='d35;money_drink<=12'b0000_0011_0101; end
3'd011:begin money_1<='d45;money_drink<=12'b0000_0100_0101; end
3'd100:begin money_1<='d50;money_drink<=12'b0000_0101_0000; end
3'd101:begin money_1<='d60;money_drink<=12'b0000_0110_0000; end
3'd110:begin money_1<='d85;money_drink<=12'b0000_1000_0101; end
3'd111:begin money_1<='d95;money_drink<=12'b0000_1001_0101; end
endcase
end
end
//计算所要找的钱并将其转化为BCD码
always@(posedge clk or negedge rst) begin
if(rst==0)
money_3<=0;
else if(money_2>=money_1&&flag[2]==0)
money_3<=money_2-money_1;
else
money_3<=money_3;
end
always@(posedge clk or negedge rst) begin
if(rst==0)
money_out<=0;
else if(money_out[3:0]>4'b1001)begin
money_out[3:0]<=money_out[3:0]-4'b1010;
money_out[7:4]<=money_out[7:4]+1'b1;
end
else if(money_out[7:4]>4'b1001)begin
money_out[7:4]<=money_out[3:0]-4'b1010;
money_out[11:8]<=money_out[7:0]+1'b1;
end
else if(money_3>='d90&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b1001;
money_out[3:0]<=money_3-'d90;
end
else if(money_3>='d80&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b1000;
money_out[3:0]<=money_3-'d80;
end
else if(money_3>='d70&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0111;
money_out[3:0]<=money_3-'d70;
end
else if(money_3>='d60&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0110;
money_out[3:0]<=money_3-'d60;
end
else if(money_3>='d50&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0101;
money_out[3:0]<=money_3-'d50;
end
else if(money_3>='d40&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0100;
money_out[3:0]<=money_3-'d40;
end
else if(money_3>='d30&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0011;
money_out[3:0]<=money_3-'d30;
end
else if(money_3>='d20&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0010;
money_out[3:0]<=money_3-'d20;
end
else if(money_3>='d10&&flag[2]==0) begin
money_out[7:4]<=money_out[7:4]+4'b0001;
money_out[3:0]<=money_3-'d10;
end
end
//状态机状态转化信号
always@(posedge clk or negedge rst) begin
if(rst==0)
flag<=0;
else if({key1,key2,key3,key4}>0&&flag[0]<=1)
flag[1]<=1;
else if(money_3>0&&flag[1]<=1)
flag[2]<=1;
else if(drink>0)
flag[0]<=1;
else
flag<=0;
end
endmodule
本模块的主要功能是控制各个状态的转换,将会有一下状态:IDLE,选货,付钱,找钱;本模块的的输入有:系统时钟、复位信号、flag信号;输出有:一个控制信号来控制数码管的显示;
状态机的工作为:当有人选货时也就是drink>0时flag[0]=1,进入选货状态;当有钱币投入时,也就是{key1,key2,key3,key4}>0(flag[1]=1),进入付钱状态;当所支付的钱数大于等于所选货的钱数时(我在这里加了一个并且本状态至少保持1s,原因是如果一次性所支付的钱数大于或者等于货物的价格时,数码管上只会显示一个时钟周期(20ns)的所支付钱数,时间太短,我们肉眼无法察觉),进入找钱状态;当进入找钱状态五秒后回到IDLE;
状态机的状态转移图为:
本模块的代码为:
module zhaungtai(
input clk,
input rst,
input [2:0] flag,
output reg [2:0] ctrl
);
localparam
IDLE = 4'b0001,
XUANHUO = 4'b0010,
FUQIAN = 4'b0100,
ZHAOQIAN = 4'b1000;
reg [3:0] c_stats;
reg [3:0] n_stats;
reg en_1;
reg en_2;
localparam TIME_1S='d49_999_999;
/* localparam TIME_1S='d4; */
reg [25:0] cnt_1s;
reg [25:0] cnt_1s2;
reg [2:0] cnt_1;
//三段式状态机
always@(posedge clk or negedge rst) begin
if(rst==0)
c_stats<= IDLE;
else
c_stats<= n_stats;
end
always@(*)begin
case(c_stats)
IDLE: begin
if(flag[0])
n_stats<=XUANHUO;
else
n_stats<=IDLE;
end
XUANHUO: begin
if(flag[1])
n_stats<=FUQIAN;
else
n_stats<=XUANHUO;
end
FUQIAN: begin
if(flag[2]&&cnt_1s2==TIME_1S)
n_stats<=ZHAOQIAN;
else
n_stats<=FUQIAN;
end
ZHAOQIAN: begin
if(cnt_1=='d5)
n_stats<=IDLE;
else
n_stats<=ZHAOQIAN;
end
endcase
end
always@(posedge clk or negedge rst) begin
if(rst==0) begin
ctrl<=0;
en_1<=0;
en_2<=0;
end
else if(c_stats==IDLE) begin
ctrl<=3'b000;
en_1<=0;
en_2<=0;
end
else if(c_stats==ZHAOQIAN) begin
ctrl<=3'b100;
en_1<=1;
en_2<=0;
end
else if(c_stats==FUQIAN) begin
ctrl<=3'b010;
en_1<=0;
en_2<=1;
end
else if(c_stats==XUANHUO) begin
ctrl<=3'b001;
en_1<=0;
en_2<=0;
end
end
//
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt_1s<='d0;
else if(cnt_1s == TIME_1S)
cnt_1s<='d0;
else if(en_1==1)
cnt_1s<= cnt_1s + 1'd1;
end
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt_1<=0;
else if(cnt_1=='d4)
cnt_1<=0;
else if(cnt_1s==TIME_1S)
cnt_1<=cnt_1+1'd1;
else
cnt_1<=cnt_1;
end
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt_1s2<='d0;
else if(cnt_1s2 == TIME_1S)
cnt_1s2<='d0;
else if(en_2==1)
cnt_1s2<= cnt_1s2 + 1'd1;
end
endmodule
本模块的主要功能是实现当输入金额小于饮料价值时,LED灯100ms闪烁;输入金额与饮料价值相等时,LED灯常亮;输入金额大于饮料价值时,显示多出的钱数,LED灯2s闪烁。
本模块的的输入有:系统时钟、复位信号、flag信号、money_1、money_2;输出:led信号;
本模块实现较为简单,具体代码如下:
module led(
input clk,
input rst,
input [2:0] flag, //状态转化信号
input [7:0] money_1,//所选货物的价格(二进制)
input [7:0] money_2,//所支付的钱数(二进制)
output reg led
);
localparam
TIME_1S ='d49_999_999,
TIME_100MS ='d4999_999;
reg [22:0] cnt_100ms;
reg [25:0] cnt_1s;
always@(posedge clk or negedge rst)begin
if(rst==0)
cnt_1s<=0;
else if(cnt_1s==TIME_1S)
cnt_1s<=0;
else
cnt_1s<=cnt_1s+1'd1;
end
always@(posedge clk or negedge rst)begin
if(rst==0)
cnt_100ms<=0;
else if(cnt_100ms==TIME_100MS)
cnt_100ms<=0;
else
cnt_100ms<=cnt_100ms+1'd1;
end
always@(posedge clk or negedge rst)begin
if(rst==0)
led<=0;
else if(money_1>money_2 && cnt_100ms==TIME_100MS)
led<=~led;
else if(money_1==money_2 && flag>0)
led<=1;
else if(money_1
本模块的主要功能是将此状态下所要展示的数据显示在数码管上;本模块的的输入有:系统时钟、复位信号、ctrl信号、date1,date2,date3;输出:数码管的段选和位选;
看到这里,我们一直所没有注意到问题:无论是产品的价格和所支付的钱数亦或是所要找零的钱数都会出现浮点数,我们的解决办法是给所有数据乘以10,在数据的第二位让其小数点常亮,也就是当动态扫描到sel[1]时;seg[7]为0;其余位置seg[7]都为1;
本模块的代码为:
module xianshi(
input clk,
input rst,
input [2:0] ctrl,
input [11:0] date1,//数据信号
input [11:0] date2,//数据信号
input [11:0] date3,//数据信号
output reg [3:0] sel, // 数码管位选(选择当前要显示的数码管)
output reg [7:0] seg // 数码管段选(选择当前要显示的内容)
);
reg [14:0] cnt_1; //分频记数的计数器
reg clk_out; //分频后的时钟信号
/* reg [3:0] sel_r; //表示那个数码管亮 */
reg [3:0] date_tmp;
reg [11:0] disp_data;
localparam TIME='d24999;
/* localparam TIME='d4; */
always@(posedge clk or negedge rst) begin
if(rst==0)
disp_data<=0;
else
case(ctrl)
3'b001:disp_data<=date1;
3'b010:disp_data<=date2;
3'b100:disp_data<=date3;
default:disp_data<=0;
endcase
end
//cnt_1的记数模块
always@(posedge clk or negedge rst) begin
if(rst==0)
cnt_1 <= 15'd0;
else if(cnt_1 ==TIME)
cnt_1 <= 15'd0;
else
cnt_1 <= cnt_1+1'd1;
end
//时钟分频模块
always@(posedge clk or negedge rst) begin
if(rst==0)
clk_out<=1'b0;
else if(cnt_1== TIME)
clk_out<=~clk_out;
else
clk_out<=clk_out;
end
//移位寄存器
always@(posedge clk_out or negedge rst) begin
if(rst==0)
sel<=4'b1110;
else
sel[2:0]<={sel[1:0],sel[2]};
/* if(rst==0)
sel_r<=4'b0001;
else
sel_r<={sel_r[2:0],sel_r[3]}; */
end
//四选一多路器
always@(*) begin
case(sel)
4'b1110 : date_tmp <= disp_data[3:0];
4'b1101 : date_tmp <= disp_data[7:4];
4'b1011 : date_tmp <= disp_data[11:8];
default:date_tmp<=4'b0000;
endcase
end
//字典(译码器)
always@(*) begin
if(sel[2:0]==3'b101)begin
case(date_tmp)
4'h0:seg<=8'b01000000;
4'h1:seg<=8'b01111001;
4'h2:seg<=8'b00100100;
4'h3:seg<=8'b00110000;
4'h4:seg<=8'b00011001;
4'h5:seg<=8'b00010010;
4'h6:seg<=8'b00000010;
4'h7:seg<=8'b01111000;
4'h8:seg<=8'b00000000;
4'h9:seg<=8'b00010000;
endcase
end
else begin
case(date_tmp)
4'h0:seg<=8'b11000000;
4'h1:seg<=8'b11111001;
4'h2:seg<=8'b10100100;
4'h3:seg<=8'b10110000;
4'h4:seg<=8'b10011001;
4'h5:seg<=8'b10010010;
4'h6:seg<=8'b10000010;
4'h7:seg<=8'b11111000;
4'h8:seg<=8'b10000000;
4'h9:seg<=8'b10010000;
endcase
end
end
endmodule