简易音乐播放器的实现(FPGA)

一.功能和原理:

本项目主要实现以下功能,当使能en按键按下时开始播放存在rom里的乐谱,当en按键再次按下时暂停播放。

无源蜂鸣器根据输入方波信号频率的不同可以发出不同音调的声音。因此我们可以把乐谱中对应的音符信息存储在ROM里,然后使用一个分频器以一定的速率去取出ROM中的音符频率信息,并将该信息送至另外一个分频器中转换成方波信号输出到蜂鸣器,综上就可以实现按照一定节拍演奏乐曲的功能。更多详细信息请参考该链接

二.代码实现:

根据上述的功能要求,本次项目总共需要一个按键检测模块来判断en按键是否被按下,两个分频模块,一个用来实现那一定速率从ROM中取出音符频率信息,另一个用来将音符信息转换成特定频率的方波输出到无源蜂鸣器,此外还需要一个ROM 模块用来存储音符信息。

1.按键检测模块

先将其实的50Mhz时钟分频成1Mhz的时钟clk1,在ckl1的控制下对en信号进行两次采样,采样结果分别放置在寄存器en_buff1和en_buff2里。en_buff2里的数据相较与en_buff1延迟了一个clk1时钟。因此当en出现下降沿后,en_buff1会先与en_buff2跳转到低电平,因此当en_buff1为0,en_buff1为1时就可以认为按键已经被按下。之所以采用1Mhz是时钟进行采样(按键去抖),是因为当按键按下时由于触点之间的接触存在不稳定,这导致了在按键按下后会出现多次的高低电平跳变,如果用50Mhz的时钟直接进行采样操作可能出现误把按键的抖动也检测成按键按下操作从而导致多次错误触发。

当检测到en按下后对start信号进行一次翻转操作。

	reg [4:0]cnt1;
	reg clk1;
	always @(posedge clk or negedge rstn)
		begin
		if(!rstn)
			begin
			cnt1<=0;
			clk1<=0;
			end
		else
			begin
			cnt1<= cnt1==5'd24 ? 1'b0 : cnt1+1'b1;
			clk1<= cnt1==5'd24 ? ~clk1: clk1;
			end
		end
	reg en_buff1,en_buff2;
	always @(posedge clk1 or negedge rstn)
		begin
		 if (!rstn)
			begin
			en_buff1<=1'b1;
			en_buff2<=1'b1;
			end
			else
			begin
			en_buff1<=en;
			en_buff2<=en_buff1;
			end
		end
		wire down_edge;
	assign down_edge= en_buff2&~en_buff1;
		reg start;
			always @(posedge clk1 or negedge rstn )
			begin
			if (!rstn)
				begin
				start<=1'b0;
				end
			else
				begin
				start<= down_edge ? ~start : start;
				end	
			end

仿真结果如下:

2.分频器模块----beat

根据模块传入参数cnt的值来生成计数器,当cnt=1时,代表每秒读出一个音符。模块传入参数len_sheet代表乐曲音符的个数,len_sheet等于48代表总共有48个音符。输出的时钟o_clk作为ROM模块的读出时钟,address为读出数据的地址,由于ROM模块是在读出时钟为上升沿时进行数据读出的,所以地址应该在读出时钟上沿来临之前准备好,因此在clk时钟频率下对o_clk时钟进行采样检测,当检测到o_clk出现下降沿时将地址加1。

module beat #(parameter cnt=1,len_sheet=48)
				(
				input clk,rstn,start,
				output o_clk,
				output [5:0]address);
localparam div_cnt= 50_000_000/2/cnt-1;


reg [24:0]cnt_reg;
reg out;
	always @(posedge clk or negedge rstn)
		begin
			if (!rstn)
				begin
				cnt_reg<=25'd0;
				out<=1'b1;
				end
			else if (start)
				begin
				cnt_reg<=cnt_reg==div_cnt ? 25'd0 : cnt_reg+ 25'd1;
				out<= cnt_reg==div_cnt ? ~out :out;
				end
			else 
				begin
				cnt_reg<=cnt_reg;
				out<=out;
				end
		end
assign o_clk=out;

	reg clk_buff1,clk_buff2;
		always @(posedge clk or negedge rstn)
			begin
				if(!rstn)
					begin
					clk_buff1<=1'b1;
					clk_buff2<=1'b1;
					end
				else
				begin
					clk_buff1<=o_clk;
					clk_buff2<=clk_buff1;
				end
			end
wire flag;
		assign flag=clk_buff2&~clk_buff1;
reg [5:0]addr;
		always @(posedge clk or negedge rstn)
			begin
				if(!rstn)
					begin
					addr<=len_sheet;
					end
			else if (flag)
					begin
					addr<=addr==len_sheet ? 6'd0 : addr+6'd1;
					end
			end
		assign address=addr;
endmodule		

3.ROM模块 sheet

使用quartus13.1的rom IP核生成。rom ip核使用方法可以查看该贴进行了解:quartus ii ROM ip的使用

4.分频器模块---tone

当en信号有效时模块开始工作。首先在50Mhz的时钟下对从ROM模块传来的音符信息进行两次寄存。当两次寄存的值不同时将目前对应的信息存入tone_reg寄存器,然后根据tone_reg寄存器的值进行分频器计数值的选择,从而实现根据输入音符信息输出不同方波信号给无源蜂鸣器的功能。

module tone (
				input clk,rstn,en,
				input [2:0]tone_num,
				output bee
			);

reg [2:0] buff1,buff2;

	always @(posedge clk or negedge rstn)
		begin
			if(!rstn)
				begin
				buff1<=3'd0;
				buff2<=3'd0;
				end
			else
				begin
				buff1<=tone_num;
				buff2<=buff1;
				end
		end
		wire en_flag;
		assign en_flag=buff1 != buff2;
reg[2:0] tone_reg;
	always @(posedge clk or negedge rstn)
		begin
			if(!rstn)
				begin
				tone_reg<=3'd0;
				end
			else if (en_flag)
				begin
				tone_reg<=buff1;
				end
			else
				begin
				tone_reg<=tone_reg;
				end
		end
reg en_cnt;
reg [16:0]cnt_buff;
	always @(posedge clk or negedge rstn)
		begin
			if(!rstn)
				begin
				en_cnt<=1'b0;
				cnt_buff<=17'd0;
				end
			else
			begin
				case (tone_reg)
					0:begin
					  en_cnt<=1'b0;
					  end
					1:begin
					  en_cnt<=1'b1;
					  cnt_buff<=17'd47801;
					  end			
					2:begin
					  en_cnt<=1'b1;
					  cnt_buff<=17'd43253;
					  end			
					3:begin
					  en_cnt<=1'b1;
					  cnt_buff<=17'd37936;
					  end
					4:begin
					  en_cnt<=1'b1;
					  cnt_buff<=17'd35817;
					  end	
					5:begin
					  en_cnt<=1'b1;
					  cnt_buff<=17'd31888;
					  end
					6:begin
					  en_cnt<=1'b1;
					  cnt_buff<=17'd28409;
					  end 
					7:begin
					  en_cnt<=1'b1;
					  cnt_buff<=17'd25050;
					  end 
				endcase
			end
		end
	reg out;
	reg [16:0]cnt;
	always @(posedge clk or negedge rstn)
		begin
			if (!rstn)
				begin
				out<=1'b0;
				cnt<=17'd0;
				end
			else if(en_cnt&en)
				begin
				cnt<=cnt==cnt_buff ? 17'd0 : cnt+17'd1;
				out<=cnt==cnt_buff ? ~out : out;
				end
			else
			begin
			out<=1'b0;
			cnt<=17'd0;
			end
		end
assign bee=out;
endmodule 

5.例化代码: 

工程文件已经上传至https://download.csdn.net/download/qq_54843159/88788475?spm=1001.2014.3001.5503

module music (
					input clk,
					input rstn,
					input en,
					output bee);

	reg [4:0]cnt1;
	reg clk1;
	always @(posedge clk or negedge rstn)
		begin
		if(!rstn)
			begin
			cnt1<=0;
			clk1<=0;
			end
		else
			begin
			cnt1<= cnt1==5'd24 ? 1'b0 : cnt1+1'b1;
			clk1<= cnt1==5'd24 ? ~clk1: clk1;
			end
		end
	reg en_buff1,en_buff2;
	always @(posedge clk1 or negedge rstn)
		begin
		 if (!rstn)
			begin
			en_buff1<=1'b1;
			en_buff2<=1'b1;
			end
			else
			begin
			en_buff1<=en;
			en_buff2<=en_buff1;
			end
		end
		wire down_edge;
	assign down_edge= en_buff2&~en_buff1;
		reg start;
			always @(posedge clk1 or negedge rstn )
			begin
			if (!rstn)
				begin
				start<=1'b0;
				end
			else
				begin
				start<= down_edge ? ~start : start;
				end	
			end
			wire clk_sheet;
			wire [5:0] addr;
	 beat #(.cnt(4),.len_sheet(48))
			u0(
				.clk(clk),.rstn(rstn),.start(start),
				.o_clk(clk_sheet),
				.address(addr));
			wire [2:0]data;
   Sheet	u1
			   (
				.address (addr),
				.clock (clk_sheet),
				.q (data)
				);
   tone  u2(
				.clk(clk),.rstn(rstn),.en(start),
				.tone_num(data),
				.bee(bee)
			  );
	
endmodule

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值