基于FPGA的自动售货机

这篇具有很好参考价值的文章主要介绍了基于FPGA的自动售货机。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、项目功能

二、设计思路

按键实现:

数码管

蜂鸣器

LED灯

三、流程图

四、代码实现

1、按键消抖  key_debounce.v

 2、LED状态选择  led_drive.v

3、蜂鸣器模块   beep_drive.v

4、数码管位选信号选择  sel_drive.v

5、数码管段选信号选择     seg_drive.v

6、售货机按键选择处理   machine_drive.v

7、音符彩灯实现   lanterns.v

8、音符选择   freq_select.v

9、顶层文件  top_vending_machine

五、测试

RTL门级电路查看​编辑

硬件测试:

六、总结


一、项目功能

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

二、设计思路

按键按下,KEY输出值为低电平,否则为高电平。

按键实现:

每个按键对应相应的功能:
key0:退货
key1:选择不同的价格(0.5;1.5;2.4,3.0)
key2:投币,按一次个位增加5,即为0.5
key3:投币,按一次十位增加1,即增加1 

数码管

数码管通过位选信号控制指定数码管,给0信号值,表示选中此数码管,给1信号值,表示未选中此应数码管,段选信号控制数码管上对应的led灯,数码管的LED灯共阳极,给低电平亮。 

数码管显示数字真值表

基于FPGA的自动售货机

 数码管实现:

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

蜂鸣器

不同的音符振动频率不同,周期T=1/频率f

基于FPGA的自动售货机

根据上图可以计算出音符振动的周期,单位微秒。Cyclone IV开发板的晶振是50MHz,振动一次是20纳秒,使用周期时间除以20纳秒得出音符振动的次数 。比如高音的DO计算方式如下公式所示。

DO(高)=955*1000/20=47750

按下key0后,蜂鸣器播放5秒的音乐,(音符+每个音符的空白时间),每个音符对应一个外接彩灯闪烁灯
按下key1,key2,key3中任意一个,蜂鸣器会有0.2s的提示音

LED灯

LDE灯对应8种状态:全灭;全亮;只亮led[0];只亮led[1];只亮led[2];只亮led[3];流水灯;闪烁 

三、流程图

基于FPGA的自动售货机

四、代码实现

1、按键消抖  key_debounce.v

module key_debounce(
	input 	wire	clk,
	input 	wire 	rst_n,
	input 	wire 	key,
	
	output 	reg 	flag,// 0抖动, 1抖动结束
	output 	reg	key_value//key抖动结束后的值
);

parameter MAX_NUM = 20'd1_000_000;

reg [19:0] delay_cnt;//1_000_000

reg key_reg;//key上一次的值

always @(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		key_reg <= 1;
		delay_cnt <= 0;
	end
	
	else begin
		key_reg <= key;
		//当key为1 key 为0 表示按下抖动,开始计时
		if(key_reg  != key  ) begin 
		   delay_cnt <= MAX_NUM ;
		end
		else begin
		    if(delay_cnt > 0)
				delay_cnt <= delay_cnt -1;
			else
				delay_cnt <= 0;
		end
	end
end


//当计时完成,获取key的值
always @(posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		flag <= 0;
		key_value <= 1;
	end

	else begin
		
		// 计时完成 处于稳定状态,进行赋值
		if(delay_cnt == 1) begin
			flag <= 1;
			key_value <= key;
		end
		else begin
			flag <= 0;
			key_value <= key_value;
		end
	end
end

endmodule

 2、LED状态选择  led_drive.v

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 

3、蜂鸣器模块   beep_drive.v

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

4、数码管位选信号选择  sel_drive.v

总共有6个数码管,分成了3部分,每一部分包含整数和小数部分,左边一位显示带小数点的整数,右边一位显示小数部分。左边两位显示当前投币数,中间两位显示当前商品价格,右边两位显示当前找零数。
运用三段式状态机写法,六个数码管对应六种状态,每20ms状态切换一次,三段式的好处是可以避免冒险和竞争产生的毛刺。每一种状态数码管是否显示和对应的数值大小有关,当数值小于1时,只显示小数部分那个数码管。

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 

5、数码管段选信号选择     seg_drive.v

该部分数码管显示具体数值,售货机按键处理模块将用户投币的数据处理后传递给该模块,不同位选信号,也就是不同的数码管对应显示的相应值的位置是固定的,将收到的用户投币数,商品价格数,找零数取出十位和个位,在数码管上显示,同时在三部分的整数部分数码管显示小数点,也就是段选信号的左边第一位为0,其余部分根据真值表显示。

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

6、售货机按键选择处理   machine_drive.v

该部分使用三个变量用于存储用户投币数,商品价格,找零。
通过按键可操作标志使处于售货机处理状态下按键不能重复操作影响正常功能。在按键可操作情况下,KEY4键投币加1,KEY3键投币加0.5,在用户投币为零的情况下,按键KEY2用于取消订单,退还投币,在投币非零的情况下,按键KEY2用于修改商品价格,用了一个取余操作,这里设置了4种商品价格,每次按下都加1再取余,数值只会在0-3之间循环。

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

7、音符彩灯实现   lanterns.v

module lanterns(
	input 	wire 			clk,
	input 	wire 			rst_n,
	input 	wire [2:0] 	spec_flag,//音符
	
	output 	wire [6:0] 	lan_led 	//彩灯
);


reg [6:0] led_r;

//led工作
always@(posedge clk,negedge rst_n)begin
	if(!rst_n)
		led_r<=7'b000_0000;
	else
		case(spec_flag)
			3'd1: led_r<=7'b000_0001;
			3'd2: led_r<=7'b000_0010;
			3'd3: led_r<=7'b000_0100;
			3'd4: led_r<=7'b000_1000;
			3'd5: led_r<=7'b001_0000;
			3'd6: led_r<=7'b010_0000;
			3'd7: led_r<=7'b100_0000;
			default:led_r<=7'b000_0000;
		endcase
	
end
assign lan_led=led_r;

endmodule

8、音符选择   freq_select.v

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

9、顶层文件  top_vending_machine

module top_vending_machine(
    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

五、测试

RTL门级电路查看

硬件测试:

在黑金AX301 FPGA开发板上可看到现象

六、总结

以后牛客网刷题可能会遇到自动售货机的题目,可继续完善文章来源地址https://www.toymoban.com/news/detail-457649.html

到了这里,关于基于FPGA的自动售货机的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 自动售货机控制系统的FPGA设计与实现

            采用VHDL语言设计一个自动售货机控制系统,要求能在MaxPlus Ⅱ软件平台进行仿真模拟,技术指标如下: 1)有2元、3元、8元商品;有1元、5元、10元钱币; 2)当投入的总币值大于顾客购买的商品单价时,机器提供商品并将余币退出,回到初始状态;若投入的总币值小

    2024年02月06日
    浏览(41)
  • 基于单片机的自动售货机系统设计

    欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 技术交流认准下方 CSDN 官方提供的联系方式   本文设计基于单片机的自动售货机系统,分为硬件电路设计与软件设计。硬件电路设计,本系统由ST(意法半导体)公司设计和生产的STM32F103C8T6单片

    2024年02月03日
    浏览(50)
  • 售货机基于ego1开发板的自动饮料机verilog代码vivado软件

    名称:售货机基于ego1开发板的自动饮料机verilog代码vivado软件 软件:VIVADO 语言:Verilog 代码功能: 设计一个自动饮料机逻辑电路, 它的投币口只能投入一枚五角或一元硬币; 投入一元五角硬币后机器自动给出一杯饮料,投入两元硬币后再给出饮料的同时找回一枚五角硬币;

    2024年01月18日
    浏览(49)
  • python自动售货机

    分享python实现自动售货一个小作业 输入2进行购买商品,输入商品id和数量,可以选择不同商品,最后进行结账操作。 可以有两种查询方式,全部查询和按时间条件查询 具体操作如下: 全部查询: 时间条件查询: 就按4直接退出23333 其实就是参考路边的自动售货机贩卖饮料啥的,

    2024年02月11日
    浏览(31)
  • 【Python实训】饮品自动售货机

    这段代码定义了三个函数:show_goods()、total()和main()。show_goods()函数用于展示饮品信息,total()函数用于计算总额,main()函数用于控制整个饮品自动售货机的操作流程。 运行程序后,会首先调用show_goods()函数展示饮品信息,然后用户可以根据展示的信息选择饮品和数量,输入

    2024年02月05日
    浏览(43)
  • 如何用Java设计自动售货机?

    如何用Java设计自动售货机?是大多在高级Java开发人员面试中经常被问到的好问题之一。在典型的编码面试中,你会得到一个问题描述来开发一个售货机,在有限的时间内,通常2到3小时内,你需要在Java中编写设计文档、工作代码和单元测试。这种Java面试的一个关键优势是可以一次

    2024年02月05日
    浏览(95)
  • 自动售货机销售数据分析与应用

    本书不仅适用于零基础的读者自学,还适用于教师教学,为了帮助读者更加高效地掌握本书的内容,本书提供了以下10项附加价值: (1)建模平台:提供一站式大数据挖掘建模平台,免配置,包含大量案例工程,边练边学,告别纸上谈兵 (2)视频讲解:提供不少于600分钟

    2024年02月08日
    浏览(43)
  • Verilog设计实例(一):自动售货机设计实例

    本系列为FPGA设计实例,基于Verilog HDL,题目一般是我在网上看到的一些FPGA相关的实验题目,基本会是一个实际场景的系统实现,而不是简单单元的设计,这是为了能更全面的练习,这些实例一般是可以基于FPGA进行实现的,因为正好手里有一块zynq板子,所以想把这个东西用起

    2024年02月05日
    浏览(33)
  • VIVADO自动售票机售货机verilog代码ego1开发板验证

    名称:VIVADO自动售票机售货机verilog代码ego1开发板验证 软件:VIVADO 语言:Verilog 代码功能: 自动售票机  1、自动售票机只出售1角、2角、5角和1元4种车票 2、只接收1角、5角和1元硬币,每次只能出售1张车  3、自动售票机具有累加销售额的功能 实验要求  出售车票用SW0~3分别

    2024年02月05日
    浏览(43)
  • 【Python】python自动售货机销售数据分析

    👉博__主👈:米码收割机 👉技__能👈:C++/Python语言 👉公众号👈:测试开发自动化【获取源码+商业合作】 👉荣__誉👈:阿里云博客专家博主、51CTO技术博主 👉专__注👈:专注主流机器人、人工智能等相关领域的开发、测试技术。 近年来,随着我国经济技术的不断提升,

    2024年02月06日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包