基于FPGA的售货机

一、要求

  1. 默认只接收0.5元、1元投币。
  2. 货物为2.5元。
  3. 满足2.5元后自动出货,出货动作用4个LED同时闪烁(闪烁2s)表示。
  4. 满足3元之后,自动出货并找零,动作用4个LED做流水灯表示(同样闪烁2s)。

二、效果

  1. 默认只接收0.5元、1元投币。
  2. 货物有4种可以选择,价格分别为0.5,1.5,2.4,3元。
  3. 满足当前选择的商品价格后自动出货,自动出货并找零,出货动作用4个LED做跑马灯(持续2s)表示。
  4. 显示当前投币的总额、当前选择的商品的价格以及找零的数目。
  5. 复位时播放音乐并显示彩灯。
  6. 投币不足目标价格时可以取消,动作用灯闪烁表示(2s)。

三、设计思路

1.按键

在这里插入图片描述

2.数码管

数码管6位,从左往右,依次是两位的投币输入金额,两位的商品价格
以及两位的找零金额。初始显示1位,两位,1位。
位选总共六种显示状态,段选10种状态。

3.LED

在这里插入图片描述

4.蜂鸣器

在这里插入图片描述

四、程序流程图

在这里插入图片描述

五、项目RTL视图

在这里插入图片描述

六、代码实现

1.按键消抖

module key_drive (
    input   wire clk,
    input   wire rst_n,
    input   wire [2:0] key, //按键
    output  reg [19:0] value //输出到数码管显示的值
);

parameter MAX_VALUE = 20'd1024;     //最大显示数字

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        value <= 20'd0;
    end

    else if (value >= MAX_VALUE) begin
        value <= 20'd0;
    end

    // 根据按下的键进行对应的操作
    else begin
        if(key[0]) begin
            value <= 2;
        end
        else if (key[1]) begin
            value <= 3;
        end
        else if (key[2]) begin
            value <= 4;
        end
        else begin
            value <= value;
        end
    end
end
endmodule

2.售货机

module machine_drive(
	input    wire			   clk			,        //时钟信号
	input    wire			   rst_n		   ,			//复位信号
	input    wire	[2:0] 	key			,        //3个按键 KEY4-key[2]:1元 KEY3-key[1]:0.5元 KEY2-key[0]:更换商品价格
	
	output	reg 	[3:0] 	led_value	,			//对应led显示效果的类型
	output 	reg 	[6:0] 	price_put	,			//用户投入的钱
	output 	reg 	[6:0] 	price_need	,			//商品价格
	output 	reg 	[6:0] 	price_out				//找零
);



//四种商品对应价格
parameter	P1 = 7'd5;
parameter	P2 = 7'd15;
parameter	P3 = 7'd24;
parameter	P4 = 7'd30;

parameter MAX_TIME = 28'd100_000_000;	//退款过程持续时间

reg 	[1 :0] 	price_tmp			  	;	//当前商品价格
reg 	[27:0] 	cnt_time 			 	;   //用作退款过程计时
reg 			flag_can_operation  	;	//可以继续投币和切换商品
reg 			flag_is_retreat_end 	;	//结算完毕


wire			flag_is_retreat     	;	//开始结算
wire			flag_price_is_enough	;	//可以买下商品
reg		[6:0] 	price_put_last      	;	//结算前的投币数


	
//结算倒计时模块
always @(posedge clk,negedge rst_n) begin
	if(!rst_n)begin
		cnt_time <= 28'd0;          //初始设置计数器为0
		flag_can_operation <= 1'b1 ;//设置KEY键可操作
		flag_is_retreat_end <= 1'b0;//设置结算完毕标志为0
	end
	else begin
		if (flag_is_retreat) begin     //开始结算
			cnt_time <= MAX_TIME;       //计数器设置最大值
			flag_can_operation <= 1'b0 ;//当前处于结算状态,设置KEY键不可用
			flag_is_retreat_end <= 1'b0;//设置结算未完毕
		end
		else if(cnt_time > 28'd1) begin //计数器数值大于1
			cnt_time <= cnt_time - 28'd1;//倒计时
			flag_can_operation <= 1'b0;  //当前处于结算状态,设置KEY键不可操作
			flag_is_retreat_end <= 1'b0; //设置结算未完毕
		end

		else if(cnt_time == 28'd1) begin//计数器倒计时数到1
			cnt_time <= 28'd0;           //计数器清零
			flag_can_operation <= 1'b1;  //结算完毕,设置KEY键可操作
			flag_is_retreat_end <= 1'b1; //设置结算完毕
		end
		else begin
			cnt_time <= cnt_time;
			flag_can_operation <= flag_can_operation;
			flag_is_retreat_end <= 1'b0;
		end
			
	end
	 
end

//按下KEY2-key[0]切换商品价格 
always@(posedge clk,negedge rst_n)begin
	if(!rst_n)begin
		price_tmp<= 2'd0;  //初始设置商品价格为1号商品
	end
	else if(flag_can_operation) begin//当前按键可操作
		// 当没有投币的时候按下 KEY2-key[0] 为切换商品 
		if(key[0] && ! price_put) begin
			price_tmp <= (price_tmp + 2'd1) % 4;//%操作,循环切换
		end
		else begin
			price_tmp <= price_tmp;	//没有按下切换按键,当前商品价格保持
		end
	end

	else begin
		price_tmp <= price_tmp;	
	end 
end

// 切换商品价格
always@(posedge clk,negedge rst_n)begin
	if(!rst_n)
		price_need<=P1;
	else
		case(price_tmp)
			2'b00 : price_need <= P1;
			2'b01 : price_need <= P2;
			2'b10 : price_need <= P3;
			2'b11 : price_need <= P4;
			default:price_need <= P1;
		endcase
end

//切换商品价格时led灯光效果
always @(posedge clk,negedge rst_n) begin
	if(!rst_n)
		led_value <= 4'd1;
	//退款的时候判断是 补差价 或者 全额退款
	//补差价为流水灯效果
	//全额退款为闪烁效果	
	else if(flag_is_retreat)begin//开始结算
		led_value <= price_put_last >= price_need ? 4'd6 : 4'd7 ;//当前投币数大于等于商品价格,LED状态为6,否则状态为7
	end

	//正常操作状态根据当前选择商品亮起对应商品led
	else if(flag_can_operation)begin
		case(price_tmp)
			2'b00 : led_value <= 4'd2;
			2'b01 : led_value <= 4'd3;
			2'b10 : led_value <= 4'd4;
			2'b11 : led_value <= 4'd5;
			default:led_value <= 4'd2;
		endcase
	end
	
	else 
		led_value <= led_value;
end

//用户通过按键进行投币
always@(posedge clk,negedge rst_n)begin
	if(!rst_n)begin
		price_put_last<=7'd0;//累计投币数初始化为0
	end
	else if(flag_can_operation) begin//当前按键可操作
		//超过100 或者 
		if(price_put_last>=7'd100 || flag_is_retreat) begin//当前投币大于10超过可显示数字或开始结算,投币清零
			price_put_last<=7'd0;
		end
		/*
			按下 key[2] 投币 + 10
			按下 key[1] 投币 + 5
		*/	
		else begin
			if(key[2])//KEY4-key[2]投币1元
				price_put_last<=price_put_last+7'd10;
			else if(key[1])//KEY3-key[1]投币0.5元
				price_put_last<=price_put_last+7'd5;
			else	
				price_put_last<=price_put_last;
		end
	end
	else begin
		price_put<=price_put;
	end
end

//投币数码显示保持2s
always @(posedge clk,negedge rst_n) begin
	if(!rst_n)begin
		price_put<=7'd0;
	end

	else if(!flag_can_operation)//按键不可操作
		price_put <= price_put;//输出投币保持

	else 
		price_put <= price_put_last;//否则将当前投币值赋值给输出投币寄存器
end

// 输出找零
always @(posedge clk,negedge rst_n) begin
	if(!rst_n)begin
		price_out <= 7'd0;//初始找零0元
	end
	//结算完毕,归零
	else if(flag_is_retreat_end) begin
	 	price_out =7'd0 ;
	end	
	//当退款标志到来,计算退款金额为 补差价 或者 全额退款
	else if(flag_is_retreat) begin//开始结算
		price_out <= price_put_last >= price_need ? price_put_last - price_need : price_put_last ;//当前投币大于商品价格则找零差值,否则找零为0
	end

	else begin
		price_out <= price_out;
	end
end

//当投币可以买下商品
assign flag_price_is_enough = price_put_last >= price_need;//当前投币数大于等于商品价格,投币足够标志置1
//为了保证在结算前得到最后一次投币数量
// assign price_put_last = price_put;
// 当币足够 或者 在投币过程选择切换商品则开始退款
assign flag_is_retreat = flag_price_is_enough || (price_put && key[0]);//投币足够或退款,结算处理标志置1
endmodule

3.LED

module led_drive (
    input   wire          clk  ,//时钟信号
    input   wire          rst_n,//复位信号
    input   wire    [3:0] value,//LED显示状态

    output  reg     [3:0] led   //4个LED输出
);
/*
    value       效果

    0           全灭
    1           全亮
    2           只亮led[0]
    3           只亮led[1]
    4           只亮led[2]
    5           只亮led[3]
    6           流水灯
    7           闪烁
*/
parameter MAX_TIME_RUNNING = 28'd4_000_000;     //流水灯频率0.08s
parameter MAX_TIME_FLASH =  28'd10_000_000;     //闪烁频率0.2s


reg [27:0] cnt_time_running ;       //流水灯计时器
reg [27:0] cnt_time_flash;          //闪烁灯计时器

reg [7:0] led_running;              //流水灯状态寄存器
reg [3:0] led_flash;                //闪烁灯状态寄存器

//流水灯计数器0.08s
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt_time_running <=28'd1;
    else if(value == 4'd6) begin
        if(cnt_time_running == MAX_TIME_RUNNING)
            cnt_time_running <=28'd1;
        else
            cnt_time_running <= cnt_time_running+28'd1;
    end
    else 
        cnt_time_running <= 28'd1;
end

//闪烁灯计数器0.2s
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt_time_flash <=28'd1;
    else if (value == 4'd7) begin
        if(cnt_time_flash == MAX_TIME_FLASH )
            cnt_time_flash <=28'd1;
        else
            cnt_time_flash <= cnt_time_flash+28'd1;
    end
    else 
        cnt_time_flash <= 28'd1;
end

//流水灯状态切换 间隔0.08s
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        led_running <= 8'b00001111;
    else if(cnt_time_running == MAX_TIME_RUNNING)begin
        led_running <= {led_running[0],led_running[7:1]};
    end
    else 
        led_running <=led_running;
	
end

//闪烁状态切换 间隔0.2s
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        led_flash <= 4'b0000;
    else if(cnt_time_flash == MAX_TIME_FLASH)begin
        led_flash <= ~led_flash;
    end
    else 
        led_flash <=led_flash;
	
end

//根据value值输出对应灯效果
always @(*) begin
    case(value)
        4'd0: begin
            led = 4'b0000;//默认状态LED全灭
        end

        4'd1:begin
            led = 4'b1111;//
        end

        4'd2:begin
            led = 4'b0001;//选择第一种商品
        end

        4'd3:begin
            led = 4'b0010;//选择第二种商品
        end

        4'd4:begin
            led = 4'b0100;//选择第三种商品
        end

        4'd5:begin
            led = 4'b1000;//选择第四种商品
        end

        4'd6: begin
		      led = led_running[3:0];//购买成功找零不找零,流水灯
        end

        4'd7:begin
            led = led_flash;//取消订单,闪烁
        end
        default : led = 4'b0000;
    endcase
end
endmodule 

4.数码管

位选

module sel_drive(
	input    wire             clk			   ,
	input    wire             rst_n			,
	input    wire    [6:0]    price_put		,	//投入的钱
	input    wire    [6:0]    price_need	,	//商品的价格
	input    wire    [6:0]    price_out		,	//找零的钱

	output    reg    [5:0]    sel				//数码管位选
);
//状态
localparam state0 = 3'd0;
localparam state1 = 3'd1;
localparam state2 = 3'd2;
localparam state3 = 3'd3;
localparam state4 = 3'd4;
localparam state5 = 3'd5;

parameter	 MAX_NUM = 1_000;//计数器最大计数值 刷新频率20微秒

reg    [2 :0]    current_state;
reg    [2 :0]    next_state;
reg    [20:0]	  cnt; //时钟分频计数器
reg              flag;

//计数器
always @(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		flag <= 1'b0;
		cnt <= 0;
	end
	else if(cnt == 0)begin//一轮计数完毕
		flag <= 1'b1;
		cnt <= 1;
	end
	else	begin 
		flag <= 1'b0;
		cnt <= (cnt + 1'b1) % MAX_NUM;//循环+1
	end
end
// 状态跳转
always @(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		current_state <= state0;
	end
	
	else if(flag) begin
		current_state <= next_state;
	end
	
	else 
		current_state <= current_state;
end


//状态判断
always @(*) begin
	if(!rst_n) begin
		next_state <= state0;
	end
	
	else if(flag) begin
		case(current_state)
			state0: begin
				next_state <= state1;
			end
			state1: begin
				next_state <= state2;
			end
			state2: begin
				next_state <= state3;
			end
			state3: begin
				next_state <= state4;
			end
			state4: begin
				next_state <= state5;
			end
			state5: begin
				next_state <= state0;
			end
			
			default:begin
				next_state <= state0;
			end
		endcase
	end
	
	else begin
		next_state <= next_state;
	end
end


//根据value的值确定需要亮几位
always@(current_state) begin
	case (current_state)
		state0: begin                 //右第一位数码管显示
			sel <= 6'b011111;
		end
		state1: begin                 //右第二位数码管显示
			if (price_out >=1) begin
				sel <= 6'b101111;
			end
			else begin
				sel <= 6'b111111;
			end
		end
		state2: begin                 //右第三位数码管显示
			if (price_need >=0) begin
				sel <= 6'b110111;
			end
			else begin
				sel <= 6'b111111;
			end
		end
		state3: begin                 //右第四位数码管显示
			if (price_need >=1) begin
				sel <= 6'b111011;
			end
			else begin
				sel <= 6'b111111;
			end
		end
		state4: begin                 //右第五位数码管显示
			if (price_put >=0) begin
				sel <= 6'b111101;
			end
			else begin
				sel <= 6'b111111;
			end
		end
		state5: begin                 //右第五位数码管显示
			if (price_put >=1) begin
				sel <= 6'b111110;
			end
			else begin
				sel <= 6'b111111;
			end
		end
		default:begin
			sel <= 6'b111111;
		end
	endcase
end
endmodule 

段选

module seg_drive(
    input   wire    			 clk         ,
    input   wire    			 rst_n       ,       //复位
    input   wire    [5:0]   sel         ,       //数码管位选
    input   wire    [6:0]   price_put   ,       //投入的钱
    input   wire    [6:0]   price_need  ,       //商品的价格
    input   wire    [6:0]   price_out   ,       //找零的钱
    output   reg    [7:0]   seg                 //数码管段选
   
);

reg [3:0] num;
always@(*) begin
    case(sel)
        //投入的钱
		6'b111_110: num = (price_put % 100) / 10;       //十位  
		6'b111_101: num = price_put % 10;               //个位
        //需要的钱
		6'b111_011: num = (price_need % 100) / 10;      //十位
		6'b110_111: num = price_need % 10;              //个位
        //找回的钱
		6'b101_111: num = (price_out % 100) / 10;       //十位
		6'b011_111: num = price_out % 10;               //个位
		default:num = 4'd0;
   endcase
end

always @ (*) begin
    //需要显示小数点
    if(!sel[1] || !sel[3] || !sel[5]) begin
        case(num)
            4'd0:    seg = 8'b1100_0000; //匹配到后参考共阳极真值表
            4'd1:    seg = 8'b1111_1001;
            4'd2:    seg = 8'b1010_0100;
            4'd3:    seg = 8'b1011_0000;
            4'd4:    seg = 8'b1001_1001;
            4'd5:    seg = 8'b1001_0010;
            4'd6:    seg = 8'b1000_0010;
            4'd7:    seg = 8'b1111_1000;
            4'd8:    seg = 8'b1000_0000;
            4'd9:    seg = 8'b1001_0000;
            default : seg = 8'b1100_0000;
        endcase
    end
       
    else begin
        case(num)
            4'd0:    seg = 8'b0100_0000; //匹配到后参考共阳极真值表
            4'd1:    seg = 8'b0111_1001;
            4'd2:    seg = 8'b0010_0100;
            4'd3:    seg = 8'b0011_0000;
            4'd4:    seg = 8'b0001_1001;
            4'd5:    seg = 8'b0001_0010;
            4'd6:    seg = 8'b0000_0010;
            4'd7:    seg = 8'b0111_1000;
            4'd8:    seg = 8'b0000_0000;
            4'd9:    seg = 8'b0001_0000;
            default : seg = 8'b0100_0000;
        endcase
    end

end
endmodule

5.音乐播放

module freq_select
(
	input   wire 		clk,
	input   wire		rst_n,
		
	output  reg 		status, //蜂鸣器1/0
	output  reg [2:0]	spec_flag//音符

);

parameter NOTE_NUM=6'd50;  //50个音符
//中
parameter   DO  	= 20'd95600		;//1
parameter   RE  	= 20'd83150		;//2
parameter   MI  	= 20'd75850		;//3
parameter   FA  	= 20'd71600		;//4
parameter   SO  	= 20'd63750		;//5
parameter   LA    = 20'd56800		;//6
parameter   XI    = 20'd50600		;//7
//高
parameter   HDO  	= 16'd47750		;//1
parameter   HRE  	= 16'd42250		;//2
parameter   HMI  	= 16'd37900		;//3
parameter   HFA  	= 16'd37550		;//4
parameter   HSO  	= 16'd31850		;//5
parameter   HLA   = 16'd28400		;//6
parameter   HXI   = 16'd25400		;//7
//低
parameter   LDO  	= 20'd190800	;//1
parameter   LRE  	= 20'd170050	;//2
parameter   LMI  	= 20'd151500	;//3
parameter   LFA  	= 20'd143250	;//4
parameter   LSO  	= 20'd127550	;//5
parameter   LLA    = 20'd113600	;//6
parameter   LXI    = 20'd101200	;//7


reg [25:0] 	inte_cnt;  		//300ms,间隔
reg [19:0] 	note_cnt;		//音符持续时间计时
reg [5:0] 	spec_cnt;		//音谱个数计数
reg [19:0] 	spec_data;		//音符频率
reg [25:0] 	continue_time;	//持续时间
reg [27:0] 	blank_time; 		//空白时间 

wire[18:0] 	duty_data;		//占空比数据
wire 			end_note; 		//音符结束时间
wire 			end_spectrum;	//音谱结束时间

//音符之间间隔时间计数
always@(posedge clk,negedge rst_n)begin
	if(!rst_n)
		inte_cnt<=26'b0;
	else if(inte_cnt==continue_time+blank_time)
		inte_cnt<=26'b0;
	else begin
		inte_cnt<=inte_cnt+1'b1;
	end
end

//单个音符频率计数
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		note_cnt <= 20'd0;//20
	end 
	else if(end_note)begin
		note_cnt <= 20'd0;
	end 
	else begin
		note_cnt <= note_cnt + 1'd1;
	end 
end

//音符数计时
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		spec_cnt <= 6'd0;
	end 
	else if(end_spectrum)begin
		spec_cnt <= 6'd0;
	end 
	else if(inte_cnt == continue_time+blank_time)begin
		spec_cnt <= spec_cnt + 1'd1;
	end 
	else begin
		spec_cnt <= spec_cnt;
	end 
end 

always@(posedge clk or negedge rst_n)begin
	case(spec_cnt)
			6'd0:	continue_time<=26'd10_000_000;//你爱我
			6'd1:	continue_time<=26'd_000_000;
			6'd2:	continue_time<=26'd10_000_000;						
			6'd3:	continue_time<=26'd20_000_000;
		
			6'd4:	continue_time<=26'd10_000_000;//我爱你蜜雪					
			6'd5:	continue_time<=26'd10_000_000;					
			6'd6:	continue_time<=26'd20_000_000;					
			6'd7:	continue_time<=26'd10_000_000;
			6'd8:	continue_time<=26'd10_000_000;
			
			6'd9:	continue_time<=26'd10_000_000;//冰城甜蜜				
			6'd10:continue_time<=26'd15_000_000;
			6'd11:continue_time<=26'd10_000_000;
			6'd12:continue_time<=26'd9_000_000;
			
			6'd13:continue_time<=26'd25_000_000;//蜜
			
			6'd14:continue_time<=26'd10_000_000;//你爱我
			6'd15:continue_time<=26'd10_000_000;
			6'd16:continue_time<=26'd10_000_000;
			6'd17:continue_time<=26'd20_000_000;
			
			6'd18:continue_time<=26'd10_000_000;//我爱你蜜雪
			6'd19:continue_time<=26'd10_000_000;
			6'd20:continue_time<=26'd20_000_000;
			6'd21:continue_time<=26'd10_000_000;
			6'd22:continue_time<=26'd10_000_000;

			6'd23:continue_time<=26'd10_000_000;//冰城甜蜜
			6'd24:continue_time<=26'd15_000_000;
			6'd25:continue_time<=26'd10_000_000;
			6'd26:continue_time<=26'd9_000_000;

			6'd27:continue_time<=26'd25_000_000;//蜜
			
			6'd28:continue_time<=26'd20_000_000;//你爱
			6'd29:continue_time<=26'd20_000_000;

			6'd30:continue_time<=26'd20_000_000;//我呀
			6'd31:continue_time<=26'd10_000_000;
			6'd32:continue_time<=26'd10_000_000;
			
			6'd33:continue_time<=26'd20_000_000;//我爱
			6'd34:continue_time<=26'd10_000_000;
			6'd35:continue_time<=26'd10_000_000;

			6'd36:continue_time<=26'd50_000_000;//你
			
		   6'd37:continue_time<=26'd10_000_000;//你爱我
			6'd38:continue_time<=26'd10_000_000;
			6'd39:continue_time<=26'd10_000_000;						
			6'd40:continue_time<=26'd20_000_000;
		
			6'd41:continue_time<=26'd10_000_000;//我爱你蜜雪					
			6'd42:continue_time<=26'd10_000_000;					
			6'd43:continue_time<=26'd20_000_000;					
			6'd44:continue_time<=26'd10_000_000;
			6'd45:continue_time<=26'd10_000_000;
			
			6'd46:continue_time<=26'd10_000_000;//冰城甜蜜				
			6'd47:continue_time<=26'd25_000_000;
			6'd48:continue_time<=26'd10_000_000;
			6'd49:continue_time<=26'd9_000_000;
			
			6'd50:continue_time<=26'd25_000_000;//蜜
			default:	continue_time<=26'd24_000_000;
		endcase
end
//空白时间
always@(spec_cnt)begin
	case(spec_cnt)
			6'd0:	blank_time<=26'd2_000_000;//你爱我
			6'd1:	blank_time<=26'd2_000_000;
			6'd2:	blank_time<=26'd2_000_000;						
			6'd3:	blank_time<=26'd5_000_000;
	
			6'd4:	blank_time<=26'd2_000_000;	//我爱你蜜雪					
			6'd5:	blank_time<=26'd2_000_000;					
			6'd6:	blank_time<=26'd5_000_000;					
			6'd7:	blank_time<=26'd2_000_000;
			6'd8:	blank_time<=26'd2_000_000;	
			
			6'd9:	blank_time<=26'd2_000_000;	//冰城甜蜜					
			6'd10:blank_time<=26'd5_000_000;
			6'd11:blank_time<=26'd2_000_000;
			6'd12:blank_time<=26'd2_000_000;
			
			6'd13:blank_time<=26'd5_000_000;//蜜
			
			6'd14:blank_time<=26'd2_000_000;//你爱我
			6'd15:blank_time<=26'd2_000_000;
			6'd16:blank_time<=26'd2_000_000;
			6'd17:blank_time<=26'd2_000_000;
			
			6'd18:blank_time<=26'd2_000_000;//我爱你蜜雪
			6'd19:blank_time<=26'd2_000_000;
			6'd20:blank_time<=26'd5_000_000;
			6'd21:blank_time<=26'd2_000_000;
			6'd22:blank_time<=26'd2_000_000;
		
			6'd23:blank_time<=26'd2_000_000;//冰城甜蜜
			6'd24:blank_time<=26'd5_000_000;
			6'd25:blank_time<=26'd2_000_000;
			6'd26:blank_time<=26'd2_000_000;

			6'd27:blank_time<=26'd5_000_000;//蜜
			
			6'd28:blank_time<=26'd2_000_000;//你爱
			6'd29:blank_time<=26'd5_000_000;
			
			6'd30:blank_time<=26'd2_000_000;//我呀
			6'd31:blank_time<=26'd2_000_000;
			6'd32:blank_time<=26'd5_000_000;
			
			6'd33:blank_time<=26'd2_000_000;//我爱
			6'd34:blank_time<=26'd2_000_000;
			6'd35:blank_time<=26'd5_000_000;

			6'd36:blank_time<=26'd10_000_000;//你
			
			6'd37:blank_time<=26'd2_000_000;//你爱我
			6'd38:blank_time<=26'd2_000_000;
			6'd49:blank_time<=26'd2_000_000;						
			6'd40:blank_time<=26'd5_000_000;
	
			6'd41:blank_time<=26'd2_000_000;	//我爱你蜜雪					
			6'd42:blank_time<=26'd2_000_000;					
			6'd43:blank_time<=26'd5_000_000;					
			6'd44:blank_time<=26'd2_000_000;
			6'd45:blank_time<=26'd2_000_000;	
			
			6'd46:blank_time<=26'd2_000_000;	//冰城甜蜜					
			6'd47:blank_time<=26'd5_000_000;
			6'd48:blank_time<=26'd2_000_000;
			6'd49:blank_time<=26'd2_000_000;
			
			6'd50:blank_time<=26'd5_000_000;//蜜
			default:blank_time<=26'd1_000_000;
		endcase
end
always@(posedge clk,negedge rst_n)begin
	if(!rst_n)
		spec_data<=DO;
	else
		case(spec_cnt)
			6'd0:	spec_data <= MI;//你爱我
			6'd1:	spec_data <= SO;
			6'd2:	spec_data <= SO;						
			6'd3:	spec_data <= LA;
	
			6'd4:	spec_data <= SO;	//我爱你蜜雪				
			6'd5:	spec_data <= MI;					
			6'd6:	spec_data <= DO;					
			6'd7:	spec_data <= DO;
			6'd8:	spec_data <= RE;

			6'd9:	spec_data <= MI;	//冰城甜蜜				
			6'd10:spec_data <= MI;
			6'd11:spec_data <= RE;
			6'd12:spec_data <= DO;
	
			6'd13:spec_data <= RE;  //蜜

			6'd14:spec_data <= MI;  //你爱我
			6'd15:spec_data <= SO;
			6'd16:spec_data <= SO;
			6'd17:spec_data <= LA;

			6'd18:spec_data <= SO;  //我爱你蜜雪	
			6'd19:spec_data <= MI;
			6'd20:spec_data <= DO;
			6'd21:spec_data <= DO;
			6'd22:spec_data <= RE;

			6'd23:spec_data <= MI;  //冰城甜蜜
			6'd24:spec_data <= MI;
			6'd25:spec_data <= RE;
			6'd26:spec_data <= RE;

			6'd27:spec_data <= DO;  //蜜

			6'd28:spec_data <= FA;  //你爱
			6'd29:spec_data <= FA;

			6'd30:spec_data <= FA;  //我呀
			6'd31:spec_data <= LA;
			6'd32:spec_data <= LA;
			
			6'd33:spec_data <= SO;  //我爱
			6'd34:spec_data <= SO;
			6'd35:spec_data <= MI;
			
			6'd36:spec_data <= RE;  //你
			
			6'd37:spec_data <= MI;  //你爱我
			6'd38:spec_data <= SO;
			6'd39:spec_data <= SO;
			6'd40:spec_data <= LA;
			
			6'd41:spec_data <= SO;  //我爱你蜜雪	
			6'd42:spec_data <= MI;
			6'd43:spec_data <= DO;
			6'd44:spec_data <= DO;
			6'd45:spec_data <= RE;

			6'd46:spec_data <= MI;  //冰城甜蜜
			6'd47:spec_data <= MI;
			6'd48:spec_data <= RE;
			6'd49:spec_data <= RE;

			6'd50:spec_data <= DO;  //蜜
			default:spec_data <= DO;

		endcase
end
//当前音符spec_flag
always@(posedge clk,negedge rst_n)begin
	if(!rst_n)
		spec_flag<=3'd0;
	else
		case(spec_data)
			DO:spec_flag<=3'd1;
			RE:spec_flag<=3'd2;
			MI:spec_flag<=3'd3;
			FA:spec_flag<=3'd4;
			SO:spec_flag<=3'd5;
			LA:spec_flag<=3'd6;
			XI:spec_flag<=3'd7;
			default:spec_flag<=3'd0;
		endcase
end

assign duty_data = spec_data >> 4;

assign end_note = note_cnt== spec_data; //spec_dara对音谱计数
assign end_spectrum = spec_cnt == NOTE_NUM && inte_cnt == continue_time;

//pwm信号产生模块
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		status <= 1'b0;
	end 

	else	
		status <= (note_cnt >= duty_data) ? 1'b1 : 1'b0; 
end         


endmodule

6.蜂鸣器

module beep_drive (
    input   wire    clk,
    input   wire    rst_n,
    input   wire    flag,       //蜂鸣器开始鸣叫
    input   wire    status,
    output  reg     beep
);

parameter MAX_TIME = 24'd10_000_000;        //鸣叫时间
parameter MAX_TIME_MUSIC = 28'd250_000_000; //音乐播放时间

reg [23:0] cnt_time;        //计时
reg [27:0] cnt_time_music;  //音乐播放计时器
reg flag_beep_time_out;     // 计时是否结束


//音乐播放计时
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt_time_music <= 28'd0;
    end

    else if(cnt_time_music < MAX_TIME_MUSIC) begin
        cnt_time_music <= cnt_time_music + 28'd1;
    end

    else 
        cnt_time_music <= cnt_time_music;
end

//蜂鸣器输出
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        cnt_time <= 0;
        beep <= 1;
        flag_beep_time_out <= 1;
    end

    else if(!status && cnt_time_music < MAX_TIME_MUSIC) begin
        beep <= 0;
    end

    else if(status && cnt_time_music < MAX_TIME_MUSIC) begin
        beep <= 1;
    end

    else if(flag && flag_beep_time_out) begin //开始鸣叫
        cnt_time <= MAX_TIME;
        flag_beep_time_out <= 0;
    end

    else if(cnt_time >=1 && !flag_beep_time_out) begin
        cnt_time <= cnt_time -24'd1;
        beep <= 0;
    end

    else if(cnt_time == 0) begin//计时结束
		beep <= 1;
		flag_beep_time_out <= 1;
	end
	else begin
		cnt_time <= cnt_time ;
		beep <= beep;
        flag_beep_time_out <= flag_beep_time_out;
	end
end


endmodule

7.顶层模块

module automatic_machine_top(
    input    wire             clk       ,   //时钟 50M
    input    wire             rst_n     ,   //复位
    input    wire    [2:0]    key       ,   //按键

    output    wire             beep     ,   //蜂鸣器
    output    wire    [3:0]    led      ,   //售货机状态灯效
    output    wire    [5:0]    sel      ,   //数码管位选
    output 	  wire    [7:0]    seg      ,   //数码管段选
    output    wire    [6:0]    lan_led      //音乐播放灯效

);

wire            status;         //音乐播放驱使蜂鸣器标志
wire    [2:0]   key_flag;       //按键消抖完成标志
wire    [2:0]   key_value;      //按键消抖完成后的按键值
wire    [4:0]   led_value;      //售货机驱使led模块效果的值
wire    [6:0]   price_put;      //售货机输出到数码管的投币值
wire    [6:0]   price_need;     //售货机输出到数码管的商品价格
wire    [6:0]   price_out;      //售货机输出到数码管的退款
wire    [2:0]   spec_flag;      //音乐模块输出的音符,用于音乐灯效




//数码管位选模块
sel_drive inst_sel_drive(
.clk            (clk)           ,
.rst_n          (rst_n)         ,
.price_put      (price_put)     ,      
.price_need     (price_need)    ,     
.price_out      (price_out)     ,  
.sel            (sel)
);

//数码管段选模块
seg_drive inst_seg_drive(
.clk            (clk)           ,	
.rst_n          (rst_n)         ,

.price_put      (price_put)     ,      
.price_need     (price_need)    ,     
.price_out      (price_out)     ,   
.sel            (sel)           ,          
.seg            (seg)
	
);

//售货机模块
machine_drive inst_machine_drive(
.clk        (clk)               ,
.rst_n      (rst_n)             ,
.key        ({key_value[2] && key_flag[2], key_value[1] && key_flag[1], key_value[0] && key_flag[0] }),

.led_value  (led_value)         ,
.price_put  (price_put)         ,
.price_need (price_need)        ,
.price_out  (price_out)			 
);

//led模块
led_drive inst_led(
.clk        (clk)               ,
.rst_n      (rst_n)             ,
.value      (led_value)         ,

.led        (led)
);

//音乐模块
freq_select inst_freq_select
(
.clk        (clk   )            ,
.rst_n      (rst_n )            ,
	     	
.status     (status)            , 
.spec_flag  (spec_flag)
);

//音乐灯效模块
lanterns inst_lanterns(
.clk        (clk   )            ,
.rst_n      (rst_n )            ,
.spec_flag  (spec_flag)         ,
        
.lan_led    (lan_led)
);


//蜂鸣器
beep_drive inst_beep_drive(
.clk        (clk)               ,
.rst_n      (rst_n)             ,
.flag       ((key_value[2] && key_flag[2]) || ( key_value[1] && key_flag[1]) || (key_value[0] && key_flag[0])),    
.status     (status)            ,
.beep       (beep)
);

//按键消抖
key_debounce inst_key_debounce_key0(
.clk        (clk)               ,
.rst_n      (rst_n)             ,
.key        (key[0])            ,
          
.flag       (key_flag[0])       ,
.key_value  (key_value[0])
);

key_debounce inst_key_debounce_key1(
.clk        (clk)               ,
.rst_n      (rst_n)             ,
.key        (key[1])            ,
          
.flag       (key_flag[1])       ,
.key_value  (key_value[1])
);

key_debounce inst_key_debounce_key2(
.clk        (clk)               ,
.rst_n      (rst_n)             ,
.key        (key[2])            ,
          
.flag       (key_flag[2])       ,
.key_value  (key_value[2])
);

endmodule

七、仿真

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 7
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值