FPGA实战-----数码管售货机

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值