基于ICE40UP5K利用PWM制作一个音乐播放器
2022寒假在家练,基于ICE40UP5K的FPGA学习平台,项目二-基于PWM实现一个音乐播放器
标签
FPGA
数字逻辑
显示
2022寒假在家练
梦比优斯
更新2022-03-03
北京理工大学
676

内容介绍

一.功能描述

1.通过PWM产生不同的音调,并驱动板上蜂鸣器将音调输出
2.能够播放三首不同的曲子,每个曲子的时间长度为1分钟,可以切换播放
3.曲子的切换使用扩展板的按键,需要有按键消抖的功能
4.播放的曲子的名字在OLED屏幕上显示出来(汉字显示)
 
二.硬件介绍
本设计基于Lattice的ICE40UP5K FPGA,板载LPC11U35下载器,可以通过USB-C接口进行FPGA的配置,支持在ICE40UP5K上对RISC-V软核的移植以及开源的FPGA开发工具链,板上RGB三色LED灯用于简单的调试,总计28个IO用于扩展使用。ICE40 UltraPlus - 增强互连,拥抱智能。构建即时量产、低功耗,接口灵活的ML/AI解决方案
  • 低功耗互连与计算 —— 运用于智能家居、智能工厂和智慧城市的各类系统正变得日趋复杂,而iCE40 UltraPlus则能有效解决互连难题,通过各类广泛的接口和协议,提供低功耗的计算资源实现更高级别的智能。
  • 网络边缘智能FPGA —— 拥有5K LUT的iCE40 UltraPlus FPGA可实现网络边缘实时在线的智能应用所需的神经网络模式匹配。其功耗优化遥遥领先,并且设计人员消除了云端智能应用带来的延迟,降低了整个系统解决方案的成本。
  • 灵活的封装选择 —— 为满足各类应用的需求,可提供多种封装选项,从专为电子消费品和IoT设备优化的超小尺寸2.15 mm x 2.50 mm x 0.45 mm WLCSP封装到低成本应用的0.5mm间距7x7mm QFN封装,不一而足。

特性

  • 灵活的逻辑架构,拥有2800或5280个4输入LUT、自定义I/O、多达80 Kb和1Mb的嵌入式存储器
  • 超低功耗的先进工艺,睡眠电流低至75 uA,工作电流仅为1-10mA
  • 使用DSP模块实现高性能信号处理,支持乘法和累加功能
  • 神经网络软IP和编译器实现灵活的机器学习/人工智能应用

三.设计思路

设计框图:

FhxRPnVpXmdsi5apW1CX1ifScj42

实物展示图:

Fu8BBu9ZF4nJo9DDkO684nvW1_1d

资源报告:

FkB8NyoKzMa1xGdRm6hCGBXtQveH

本次设计是利用PWM来制作一个音乐播放器实现要求的相关功能,设计过程中主要参考了卞维智同学之前的设计及相关思路,设计主要可分为五大模块来对其设计:top模块、oled模块、beeper模块、key(按键消抖)模块、music模块。每个模块的都有其独特的作用,从而实现所要求的音乐播放器功能。下面对每个模块进行一一分析:

1.top模块

顶层top模块的设计和应用的编写方式就像C++语言中的头文件和函数一样,它使得很多开源的代码能够直接应用到一个很大的项目中去。该top模块包含了music音乐模块、beeper模块、key模块和oled模块。该模块代码参考卞维智同学案例中使用的代码。

module top(
  	input                   clk_in,		//12MHz时钟
	input				    rst_n_in,	
    input                   k2,        

    output                  beep,      //蜂鸣器
	output				    oled_csn,	//OLED_CS
	output				    oled_rst,	//OLED_reset
	output				    oled_dcn,	//OLED_D/C
	output				    oled_clk,	//OLED_CLK
	output				    oled_dat	//OLCD_DATA
);

wire [1:0] key_value;
wire tone_en;

Music music(  //音乐模块,其中调用了Beeper模块
    .clk_in(clk_in),		
    .rst_n_in(rst_n_in),	
    .beep(beep),	
    .key_value(key_value),    
	.tone_en(tone_en)    
);

key filter(  //按键消抖模块
    .clk(clk_in),
	.rst(rst_n_in),
	.key(k2),
	.key_value(key_value),   
	.tone_en(tone_en)    
);

OLED12832 OLED(  //OLED显示模块
    .clk_in(clk_in),
	.rst_n_in(rst_n_in),
	.key_value(key_value), 
	.oled_csn(oled_csn),
	.oled_rst(oled_rst),
	.oled_dcn(oled_dcn),
	.oled_clk(oled_clk),
	.oled_dat(oled_dat)
);

endmodule

2.OLED模块

oled模块主要就是用来显示播放曲目的名字,该部分主要参考了硬禾学堂提供在github的开源代码。

module OLED12832(
	input				    clk_in,		//12MHz
	input				    rst_n_in,		//复位
    input          [1:0]   key_value,     //按键键值,控制显示

	output	reg			    oled_csn,	//OLCD_CS
	output	reg			    oled_rst,	//OLCD_RESET
	output	reg			    oled_dcn,	//OLCD_DC
	output	reg			    oled_clk,	//OLCD_CLK
	output	reg			    oled_dat	//OLCD_DAT
);
    reg [63:0] music_data;
	//通过改变music_data可以改变OLED显示文字,即已封装完成
	localparam INIT_DEPTH = 6'd29; //LCD初始化深度
	localparam IDLE = 6'h1, MAIN = 6'h2, INIT = 6'h4, SCAN = 6'h8, WRITE = 6'h10, DELAY = 6'h20;
	localparam HIGH	= 1'b1, LOW = 1'b0;
	localparam DATA	= 1'b1, CMD = 1'b0;
 
	reg [7:0] cmd [28:0];
	reg [63:0] mem1 [135:0];
	reg	[7:0]  x_ph, x_pl;
    reg [7:0]  y_ph, y_pl;
	reg	[7:0]  char;
	reg	[7:0]  num1, char_reg;
	reg	[4:0]  cnt_main, cnt_init, cnt_scan, cnt_write;
	reg	[15:0] num1_delay, cnt_delay, cnt;
	reg	[5:0]  state, state_back;
  
	always@(posedge clk_in or negedge rst_n_in) begin
		if(!rst_n_in)
			music_data=64'h05_0F_00_00_00_00_00_00;  //我的项目
		else if(key_value == 0)
	        music_data=64'h05_0F_00_00_00_00_00_00;			//我的项目
		else if(key_value == 2'd1)
	        music_data=64'h06_07_08_00_00_00_00_00;  //东方红
	    else if(key_value==2'd2)
			music_data=64'h09_0A_09_0B_0C_0D_00_00;  //我和我的祖国
		else if(key_value==2'd3)
			music_data=64'h01_02_03_04_05_00_00_00;  //森林幻想曲
		else 
			music_data=64'h05_0F_00_00_00_00_00_00;  //项目二
		end

 
	always@(posedge clk_in or negedge rst_n_in) begin
		if(!rst_n_in) begin
			cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 5'b0; cnt_write <= 1'b0;
			x_ph <= 8'h7f; x_pl <= 8'h00;
            y_ph <= 8'h03; y_pl <= 8'h00;
			num1 <= 8'b0; char <= 8'b0; char_reg <= 8'b0;
			num1_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
			oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
			state <= IDLE; state_back <= IDLE;
		end else begin
			case(state)
				IDLE:begin
						cnt_main <= 1'b0; cnt_init <= 1'b0; cnt_scan <= 5'b0; cnt_write <= 1'b0;
						x_ph <= 8'h7f; x_pl <= 8'h00;
                        y_ph <= 8'h03; y_pl <= 8'h00;
						num1 <= 8'b0;  char <= 8'b0; char_reg <= 1'b0;
						num1_delay <= 16'd5; cnt_delay <= 1'b0; cnt <= 1'b0;
						oled_csn <= HIGH; oled_rst <= HIGH; oled_dcn <= CMD; oled_clk <= HIGH; oled_dat <= LOW;
						state <= MAIN; state_back <= MAIN;
					end
				MAIN:begin
						if(cnt_main == 5'd8) cnt_main <= 5'd1;
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)	
							5'd0:	begin state <= INIT; end
								
							5'd1:	begin x_ph <= 8'h7f; x_pl <= 8'h00; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[63:56]<<3;state <= SCAN; end
							5'd2:	begin x_ph <= 8'h7f; x_pl <= 8'h10; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[55:48]<<3;state <= SCAN; end
							5'd3:	begin x_ph <= 8'h7f; x_pl <= 8'h20; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[47:40]<<3;state <= SCAN; end                                                                                        
							5'd4:	begin x_ph <= 8'h7f; x_pl <= 8'h30; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[39:32]<<3;state <= SCAN; end
							5'd5:	begin x_ph <= 8'h7f; x_pl <= 8'h40; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[31:24]<<3;state <= SCAN; end
							5'd6:	begin x_ph <= 8'h7f; x_pl <= 8'h50; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[23:16]<<3;state <= SCAN; end
							5'd7:	begin x_ph <= 8'h7f; x_pl <= 8'h60; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[15:8]<<3;state <= SCAN; end
							5'd8:	begin x_ph <= 8'h7f; x_pl <= 8'h70; y_ph <= 8'h03; y_pl <= 8'h00; num1 <= 8'd8; char <= music_data[7:0]<<3;state <= SCAN; end

							default: state <= IDLE;
						endcase
					end
				INIT:begin	
						case(cnt_init)
							5'd0:	begin oled_rst <= LOW; cnt_init <= cnt_init + 1'b1; end	
							5'd1:	begin num1_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	
							5'd2:	begin oled_rst <= HIGH; cnt_init <= cnt_init + 1'b1; end	
							5'd3:	begin num1_delay <= 16'd25000; state <= DELAY; state_back <= INIT; cnt_init <= cnt_init + 1'b1; end	
							5'd4:	begin 
										if(cnt>=INIT_DEPTH) begin	
											cnt <= 1'b0;
											cnt_init <= cnt_init + 1'b1;
										end else begin	
											cnt <= cnt + 1'b1; num1_delay <= 16'd5;
											oled_dcn <= CMD; char_reg <= cmd[cnt]; state <= WRITE; state_back <= INIT;
										end
									end
							5'd5:	begin cnt_init <= 1'b0; state <= MAIN; end	
							default: state <= IDLE;
						endcase
					end
				SCAN:begin	
					
						if(cnt_scan == 5'd7) begin
							cnt_scan <= cnt_scan + 1'b1;        //32*16
						end
                        else if(cnt_scan == 5'd16) begin
							if(num1) cnt_scan <= 5'd8;
							else cnt_scan <= cnt_scan + 1'b1;
						end
                        else if(cnt_scan == 5'd17) cnt_scan <= 1'b0;
                        else cnt_scan <= cnt_scan + 1'b1;				
				
						case(cnt_scan)
                            5'd0:	begin oled_dcn <= CMD; char_reg <= 8'h20; state <= WRITE; state_back <= SCAN; end
							5'd1:	begin oled_dcn <= CMD; char_reg <= 8'h01; state <= WRITE; state_back <= SCAN; end
							5'd2:	begin oled_dcn <= CMD; char_reg <= 8'h21; state <= WRITE; state_back <= SCAN; end
							5'd3:	begin oled_dcn <= CMD; char_reg <= x_pl; state <= WRITE; state_back <= SCAN; end
							5'd4:	begin oled_dcn <= CMD; char_reg <= x_ph; state <= WRITE; state_back <= SCAN; end
							5'd5:	begin oled_dcn <= CMD; char_reg <= 8'h22; state <= WRITE; state_back <= SCAN; end
							5'd6:	begin oled_dcn <= CMD; char_reg <= y_pl; state <= WRITE; state_back <= SCAN; end
							5'd7:	begin oled_dcn <= CMD; char_reg <= y_ph; state <= WRITE; state_back <= SCAN; end

                            5'd8:	begin num1 <= num1 - 1'b1;end        
							5'd9:	begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][63:56]; state <= WRITE; state_back <= SCAN; end
							5'd10:	begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][55:48]; state <= WRITE; state_back <= SCAN; end
							5'd11:	begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][47:40]; state <= WRITE; state_back <= SCAN; end
							5'd12:	begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][39:32]; state <= WRITE; state_back <= SCAN; end
							5'd13:	begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][31:24]; state <= WRITE; state_back <= SCAN; end
							5'd14:	begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][23:16]; state <= WRITE; state_back <= SCAN; end
							5'd15:	begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][15: 8]; state <= WRITE; state_back <= SCAN; end
							5'd16:	begin oled_dcn <= DATA; char_reg <= mem1[char+7-num1][ 7: 0]; state <= WRITE; state_back <= SCAN; end
                            
                            5'd17:	begin state <= MAIN; end
							default: state <= IDLE;
						endcase
					end
				WRITE:begin	
						if(cnt_write >= 5'd17) cnt_write <= 1'b0;
						else cnt_write <= cnt_write + 1'b1;
						case(cnt_write)
							5'd0:	begin oled_csn <= LOW; end	
							5'd1:	begin oled_clk <= LOW; oled_dat <= char_reg[7]; end
							5'd2:	begin oled_clk <= HIGH; end
							5'd3:	begin oled_clk <= LOW; oled_dat <= char_reg[6]; end
							5'd4:	begin oled_clk <= HIGH; end
							5'd5:	begin oled_clk <= LOW; oled_dat <= char_reg[5]; end
							5'd6:	begin oled_clk <= HIGH; end
							5'd7:	begin oled_clk <= LOW; oled_dat <= char_reg[4]; end
							5'd8:	begin oled_clk <= HIGH; end
							5'd9:	begin oled_clk <= LOW; oled_dat <= char_reg[3]; end
							5'd10:	begin oled_clk <= HIGH; end
							5'd11:	begin oled_clk <= LOW; oled_dat <= char_reg[2]; end
							5'd12:	begin oled_clk <= HIGH; end
							5'd13:	begin oled_clk <= LOW; oled_dat <= char_reg[1]; end
							5'd14:	begin oled_clk <= HIGH; end
							5'd15:	begin oled_clk <= LOW; oled_dat <= char_reg[0]; end
							5'd16:	begin oled_clk <= HIGH; end
							5'd17:	begin oled_csn <= HIGH; state <= DELAY; end	//
							default: state <= IDLE;
						endcase
					end
				DELAY:begin	
						if(cnt_delay >= num1_delay) begin
							cnt_delay <= 16'd0; state <= state_back; 
						end else cnt_delay <= cnt_delay + 1'b1;
					end
				default:state <= IDLE;
			endcase
		end
	end
 
	//OLED初始化命令
	always@(posedge rst_n_in)
		begin
			cmd[ 0] = {8'hae}; 
			cmd[ 1] = {8'h20}; 
			cmd[ 2] = {8'h01}; 
			cmd[ 3] = {8'h21}; 
			cmd[ 4] = {8'h00}; 
			cmd[ 5] = {8'h7f}; 
			cmd[ 6] = {8'h22};
			cmd[ 7] = {8'h00}; 
			cmd[ 8] = {8'h03}; 
			cmd[ 9] = {8'h81}; 
			cmd[10] = {8'hff}; 
			cmd[11] = {8'ha1}; 
			cmd[12] = {8'ha6}; 
			cmd[13] = {8'ha8}; 
			cmd[14] = {8'h1f}; 
			cmd[15] = {8'hc8}; 
			cmd[16] = {8'hd3}; 
			cmd[17] = {8'h00}; 
			cmd[18] = {8'hd5}; 
			cmd[19] = {8'h80}; 
			cmd[20] = {8'hd9}; 
			cmd[21] = {8'h1f}; 
			cmd[22] = {8'hda}; 
			cmd[23] = {8'h00}; 
			cmd[24] = {8'hdb}; 
			cmd[25] = {8'h40}; 
			cmd[26] = {8'h8d}; 
			cmd[27] = {8'h14};
			cmd[28] = {8'haf}; 
		end 
 
	//OLED字库
	always@(posedge rst_n_in)
		begin
			//0		

            mem1[0]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[1]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[2]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[3]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[4]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[5]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[6]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[7]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*空白,0*/
			
            //1
			mem1[8]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h05,8'h0C};
            mem1[9]={8'h40,8'h80,8'h04,8'h03,8'h40,8'h60,8'hE4,8'h00};
            mem1[10]={8'h40,8'h90,8'hFF,8'h7F,8'h40,8'h8C,8'h24,8'h00};
            mem1[11]={8'hC0,8'h03,8'hC6,8'h19,8'hF4,8'hFF,8'h05,8'h04};
            mem1[12]={8'h48,8'h00,8'h84,8'h03,8'hC0,8'h03,8'h74,8'h00};
            mem1[13]={8'h40,8'hCC,8'hFF,8'h7F,8'h40,8'h98,8'h3C,8'h00};
            mem1[14]={8'h60,8'h70,8'hC4,8'h01,8'h60,8'hE0,8'h06,8'h06};
            mem1[15]={8'h00,8'h00,8'h04,8'h04,8'h00,8'h00,8'h00,8'h00};/*"森",1*/



			//2
			mem1[16]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h02,8'hE0,8'h00};
            mem1[17]={8'h00,8'h02,8'h1C,8'h00,8'h00,8'hC2,8'h03,8'h00};
            mem1[18]={8'hFC,8'hFF,8'hFF,8'h7F,8'h00,8'hC2,8'h00,8'h00};
            mem1[19]={8'h00,8'h82,8'hC7,8'h00,8'h00,8'h02,8'h38,8'h00};
            mem1[20]={8'h00,8'h02,8'h07,8'h00,8'h00,8'hFA,8'h00,8'h00};
            mem1[21]={8'hFC,8'hFF,8'hFF,8'h7F,8'h00,8'hE2,8'h01,8'h00};
            mem1[22]={8'h00,8'h02,8'h0E,8'h00,8'h00,8'h03,8'h70,8'h00};
            mem1[23]={8'h00,8'h02,8'hC0,8'h00,8'h00,8'h00,8'h00,8'h00};/*"林",2*/

            //3
            mem1[24]={8'h00,8'h00,8'h00,8'h00,8'h10,8'h10,8'h40,8'h00};
            mem1[25]={8'h20,8'h08,8'h30,8'h10,8'hC0,8'h06,8'h0E,8'h70};
            mem1[26]={8'hC0,8'hFF,8'h01,8'h3C,8'h38,8'hC0,8'hFF,8'h03};
            mem1[27]={8'h50,8'h00,8'h00,8'h18,8'h40,8'h00,8'h01,8'h08};
            mem1[28]={8'h40,8'h00,8'h01,8'h08,8'h40,8'h00,8'h01,8'h08};
            mem1[29]={8'hC0,8'hFF,8'hFF,8'h0F,8'h40,8'h00,8'h01,8'h08};
            mem1[30]={8'h40,8'h00,8'h01,8'h08,8'h60,8'h80,8'h01,8'h08};
            mem1[31]={8'h40,8'h00,8'h00,8'h08,8'h00,8'h00,8'h00,8'h00};/*"狂",3*/
			
			//4
            mem1[32]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h01,8'h06,8'h00};
            mem1[33]={8'h00,8'h81,8'h01,8'h1E,8'h00,8'h71,8'h00,8'h01};
            mem1[34]={8'hFC,8'hFF,8'h0F,8'h00,8'h00,8'h09,8'hC0,8'h3F};
            mem1[35]={8'h80,8'hF1,8'h00,8'h30,8'h00,8'h01,8'h10,8'h30};
            mem1[36]={8'hF0,8'hFF,8'hEF,8'h31,8'h20,8'h22,8'h02,8'h30};
            mem1[37]={8'h20,8'h22,8'h02,8'h30,8'h20,8'h22,8'h82,8'h3F};
            mem1[38]={8'h20,8'h22,8'h42,8'h00,8'hF0,8'hFF,8'h8F,8'h03};
            mem1[39]={8'h00,8'h00,8'h00,8'h0E,8'h00,8'h00,8'h00,8'h00};/*"想",4*/
			
			//5
            mem1[40]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[41]={8'h00,8'hFD,8'hFD,8'h77,8'h00,8'h02,8'h02,8'h08};
            mem1[42]={8'h00,8'h02,8'h02,8'h08,8'h00,8'h02,8'h02,8'h08};
            mem1[43]={8'hFC,8'hFF,8'hFF,8'h0F,8'h00,8'h02,8'h02,8'h08};
            mem1[44]={8'h00,8'h02,8'h02,8'h08,8'hFC,8'hFF,8'hFF,8'h0F};
            mem1[45]={8'h08,8'h02,8'h02,8'h08,8'h00,8'h02,8'h02,8'h08};
            mem1[46]={8'h00,8'h02,8'h02,8'h08,8'h00,8'hFF,8'hFF,8'h1F};
            mem1[47]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"曲",5*/
								
			//6
            mem1[48]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h08};
            mem1[49]={8'h80,8'h00,8'h00,8'h04,8'h80,8'h80,8'h03,8'h03};
            mem1[50]={8'h80,8'hE0,8'hC1,8'h00,8'h80,8'h1C,8'h39,8'h00};
            mem1[51]={8'hE0,8'h03,8'h11,8'h10,8'hBC,8'h00,8'h01,8'h70};
            mem1[52]={8'h80,8'hF8,8'hFF,8'h3F,8'h80,8'h00,8'h01,8'h00};
            mem1[53]={8'h80,8'h00,8'h09,8'h00,8'h80,8'h00,8'h31,8'h00};
            mem1[54]={8'h80,8'hC0,8'hC1,8'h00,8'hC0,8'h00,8'h81,8'h07};
            mem1[55]={8'h00,8'h00,8'h00,8'h04,8'h00,8'h00,8'h00,8'h00};/*"东",6*/

            //7
            mem1[56]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h01,8'h00,8'h40};
            mem1[57]={8'h00,8'h01,8'h00,8'h30,8'h00,8'h01,8'h00,8'h08};
            mem1[58]={8'h00,8'h01,8'h00,8'h07,8'h00,8'h01,8'hE0,8'h00};
            mem1[59]={8'h00,8'hE1,8'h1F,8'h00,8'h18,8'h9F,8'h00,8'h08};
            mem1[60]={8'hF0,8'h81,8'h00,8'h10,8'h00,8'h81,8'h00,8'h70};
            mem1[61]={8'h00,8'h81,8'h00,8'h38,8'h00,8'hC1,8'hFF,8'h0F};
            mem1[62]={8'h00,8'hC1,8'h00,8'h00,8'h00,8'h01,8'h00,8'h00};
            mem1[63]={8'h80,8'h01,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"方",7*/
			
			//8
			mem1[64]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h70,8'h00,8'h08};
            mem1[65]={8'h00,8'h6C,8'h78,8'h18,8'hC0,8'h23,8'h36,8'h0C};
            mem1[66]={8'h38,8'hE0,8'h11,8'h06,8'h00,8'h38,8'h08,8'h02};
            mem1[67]={8'h00,8'h06,8'h08,8'h11,8'h80,8'h00,8'h00,8'h10};
            mem1[68]={8'h80,8'h00,8'h00,8'h10,8'h80,8'h00,8'h00,8'h10};
            mem1[69]={8'h80,8'hFF,8'hFF,8'h0F,8'h80,8'h00,8'h00,8'h10};
            mem1[70]={8'h80,8'h00,8'h00,8'h10,8'hC0,8'h00,8'h00,8'h08};
            mem1[71]={8'h00,8'h00,8'h00,8'h08,8'h00,8'h00,8'h00,8'h00};/*"红",8*/
		
		    //9
            mem1[72]={8'h00,8'h00,8'h00,8'h00,8'h40,8'h10,8'h70,8'h00};
            mem1[73]={8'h40,8'h10,8'h30,8'h00,8'h20,8'h10,8'h18,8'h10};
            mem1[74]={8'hE0,8'hFF,8'hFF,8'h3F,8'h30,8'h10,8'h04,8'h00};
            mem1[75]={8'h18,8'h10,8'h02,8'h08,8'h10,8'h10,8'h02,8'h04};
            mem1[76]={8'h04,8'h10,8'h00,8'h03,8'hF8,8'hFF,8'hBF,8'h01};
            mem1[77]={8'h00,8'h10,8'hF0,8'h01,8'h10,8'h10,8'h0C,8'h0E};
            mem1[78]={8'hE0,8'hD0,8'h03,8'h18,8'h80,8'h8D,8'h00,8'h30};
            mem1[79]={8'h00,8'h08,8'h80,8'h7F,8'h00,8'h00,8'h00,8'h00};/*"我",9*/
			
			//0A
            mem1[80]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h08,8'hC0,8'h00};
            mem1[81]={8'h40,8'h08,8'h30,8'h00,8'h20,8'h08,8'h0E,8'h00};
            mem1[82]={8'h20,8'hF8,8'h01,8'h00,8'hF0,8'hFF,8'hFF,8'h3F};
            mem1[83]={8'h10,8'h08,8'h01,8'h00,8'h18,8'h08,8'h07,8'h00};
            mem1[84]={8'h00,8'h08,8'h00,8'h00,8'hC0,8'hFF,8'hFF,8'h07};
            mem1[85]={8'h80,8'h00,8'h80,8'h00,8'h80,8'h00,8'h80,8'h00};
            mem1[86]={8'h80,8'h00,8'h80,8'h00,8'hC0,8'hFF,8'hFF,8'h03};
            mem1[87]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"和",0A*/
			
			//0B
            mem1[88]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[89]={8'h00,8'hFF,8'hFF,8'h3F,8'h80,8'h01,8'h02,8'h08};
            mem1[90]={8'h7C,8'h01,8'h02,8'h08,8'h00,8'h01,8'h02,8'h08};
            mem1[91]={8'h80,8'hFF,8'hFF,8'h3F,8'h00,8'h60,8'h00,8'h00};
            mem1[92]={8'h00,8'h1C,8'h00,8'h00,8'hE0,8'h83,8'h01,8'h00};
            mem1[93]={8'h38,8'h02,8'h1E,8'h08,8'h00,8'h02,8'h00,8'h10};
            mem1[94]={8'h00,8'h02,8'h00,8'h30,8'h00,8'hFF,8'hFF,8'h1F};
            mem1[95]={8'h00,8'h02,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"的",0B*/
			
			//0C
            mem1[96]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h02,8'h18,8'h00};
            mem1[97]={8'h00,8'h02,8'h06,8'h00,8'h38,8'hC2,8'hFF,8'h7F};
            mem1[98]={8'h60,8'hBE,8'h00,8'h00,8'h80,8'h03,8'h07,8'h10};
            mem1[99]={8'h00,8'h00,8'h00,8'h10,8'hF0,8'hFF,8'hFF,8'h1F};
            mem1[100]={8'h20,8'h10,8'h10,8'h10,8'h20,8'h10,8'h10,8'h10};
            mem1[101]={8'h20,8'h10,8'h10,8'h10,8'h20,8'h10,8'h10,8'h10};
            mem1[102]={8'hF0,8'hFF,8'hFF,8'h1F,8'h00,8'h00,8'h00,8'h10};
            mem1[103]={8'h00,8'h00,8'h00,8'h18,8'h00,8'h00,8'h00,8'h00};/*"祖",0C*/
			
			//0D
            mem1[104]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[105]={8'hFC,8'hFF,8'hFF,8'h3F,8'h08,8'h00,8'h40,8'h08};
            mem1[106]={8'h88,8'h00,8'h80,8'h08,8'h88,8'h40,8'h80,8'h08};
            mem1[107]={8'h88,8'h40,8'h80,8'h08,8'h88,8'hFF,8'hFF,8'h08};
            mem1[108]={8'h88,8'h40,8'h80,8'h08,8'h88,8'h40,8'h81,8'h08};
            mem1[109]={8'h88,8'h60,8'h9E,8'h08,8'hC8,8'h40,8'hE0,8'h08};
            mem1[110]={8'h08,8'h01,8'h80,8'h08,8'hFC,8'hFF,8'hFF,8'h3F};
            mem1[111]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"国",0D*/
          
		    //0E
			mem1[112]={8'h00,8'h00,8'h00,8'h00,8'h80,8'h00,8'h80,8'h01};
            mem1[113]={8'h80,8'h00,8'hC0,8'h00,8'h80,8'hFF,8'h7F,8'h00};
            mem1[114]={8'h80,8'h00,8'h20,8'h00,8'hE0,8'h00,8'h10,8'h40};
            mem1[115]={8'h90,8'h00,8'h08,8'h20,8'h10,8'hFF,8'hFF,8'h30};
            mem1[116]={8'h10,8'h02,8'h00,8'h18,8'h90,8'h03,8'h00,8'h07};
            mem1[117]={8'h70,8'hF2,8'hFF,8'h00,8'h10,8'h22,8'h80,8'h00};
            mem1[118]={8'h10,8'h02,8'h00,8'h03,8'h08,8'hFF,8'h7F,8'h0E};
            mem1[119]={8'h08,8'h00,8'h00,8'h38,8'h00,8'h00,8'h00,8'h00};/*"项",0E*/
			
			//0F
            mem1[120]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[121]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[122]={8'hF0,8'hFF,8'hFF,8'h3F,8'h20,8'h10,8'h08,8'h04};
            mem1[123]={8'h20,8'h10,8'h08,8'h04,8'h20,8'h10,8'h08,8'h04};
            mem1[124]={8'h20,8'h10,8'h08,8'h04,8'h20,8'h10,8'h08,8'h04};
            mem1[125]={8'h20,8'h10,8'h08,8'h04,8'hF0,8'hFF,8'hFF,8'h7F};
            mem1[126]={8'h30,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};
            mem1[127]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00};/*"目",0F*/
			
			//10
            mem1[128]={8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h00,8'h04};
            mem1[129]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
            mem1[130]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
            mem1[131]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
            mem1[132]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
            mem1[133]={8'h00,8'h01,8'h00,8'h04,8'h00,8'h01,8'h00,8'h04};
            mem1[134]={8'h80,8'h01,8'h00,8'h04,8'h00,8'h00,8'h00,8'h07};
            mem1[135]={8'h00,8'h00,8'h00,8'h06,8'h00,8'h00,8'h00,8'h00};/*"二",10*/
		end

endmodule

3.music模块

该模块内部调用了电子森林提供的蜂鸣器模块,该模块的设计也主要参考了卞维智同学之前案例中的思路与代码。当音调传入Beeper模块之后,Beeper模块通过pwm将对应频率的声音输出,从而达到播放音乐的目的。此外,该模块还通过传入的键值不同来播放不同的音乐,以达到切换音乐的目的。

module Music
(
input					clk_in,		//系统时钟
input					rst_n_in,	//系统复位,低有效
output					beep,	//蜂鸣器控制输出
input          [1:0]   key_value,     //按键键值
input                   tone_en
);

localparam nummax1 = 6'd63;  //森林狂想曲音乐的长度
localparam nummax2 = 6'd63;  //东方红音乐的长度
localparam nummax3 = 7'd95;  //我和我的祖国音乐的长度

reg [6:0] nummax;   //当前音乐播放长度
reg [63:0] delay_cnt = 64'd0;  //延时计数器

reg [4:0] mem0 = 5'd0;  //蜂鸣器静止
reg [4:0] mem1 [nummax1:0];  //森林狂想曲乐码
reg [4:0] mem2 [nummax2:0];  //东方红乐码
reg [4:0] mem3 [nummax3:0];  //我和我的祖国乐码 

reg [4:0] tone;   
reg [6:0] num = 7'd0;  //播放音乐索引

Beeper Beep
(
    .clk_in(clk_in),		//系统时钟
	.rst_n_in(rst_n_in),	//系统复位,低有效
	.tone_en(tone_en),	//蜂鸣器使能信号
    .tone(tone),		//蜂鸣器音节控制
    .piano_out(beep)	//蜂鸣器控制输出
);

always@(posedge clk_in or negedge rst_n_in) begin
	case(key_value)
		2'd3: begin nummax <= nummax1;  end
		2'd1: begin nummax <= nummax2;  end
		2'd2: begin nummax <= nummax3;  end
		default:; 
	endcase
	if(!rst_n_in) begin
		num <= 6'd0;
		delay_cnt<=64'd0;
	end
	else if(num <= nummax)	begin  //如果未播放完音乐,继续计数延时
		if(delay_cnt <= 64'd1500000) begin //该音调还未播放完全
			delay_cnt <= delay_cnt + 1'd1;
			case(key_value)
				2'd0: tone <= mem0;  //静止不响
		        2'd3: tone <= mem1[num];  //森林幻想曲
	         	2'd1: tone <= mem2[num];  //东方红
		        2'd2: tone <= mem3[num];  //我和我的祖国
		        default: ;  //默认播放第一首
	        endcase
		end
        else begin
            delay_cnt <= 64'd0;			
		    num <= num + 1'd1;
		end
	end 
	else begin    //音乐播放完,num=0重新播放
		num <= 7'd0;
	end
end


always@(negedge rst_n_in) begin  
//森林狂想曲
    mem1[0]<=5'd6;
	mem1[1]<=5'd8;
	mem1[2]<=5'd10;
	mem1[3]<=5'd12;
	mem1[4]<=5'd10;
	mem1[5]<=5'd10;
	mem1[6]<=5'd10;
	mem1[7]<=5'd9;
	mem1[8]<=5'd10;
	mem1[9]<=5'd10;
	mem1[10]<=5'd10;	
	mem1[11]<=5'd9;
	mem1[12]<=5'd10;
	mem1[13]<=5'd10;
	mem1[14]<=5'd6;
	mem1[15]<=5'd7;
	mem1[16]<=5'd8;
	mem1[17]<=5'd10;
	mem1[18]<=5'd9;
	mem1[19]<=5'd8;
	mem1[20]<=5'd6;	
	mem1[21]<=5'd6;
	mem1[22]<=5'd5;
	mem1[23]<=5'd5;
	mem1[24]<=5'd3;
	mem1[25]<=5'd3;
	mem1[26]<=5'd3;
	mem1[27]<=5'd3;
	mem1[28]<=5'd3;
	mem1[29]<=5'd3;
	mem1[30]<=5'd3;
	mem1[31]<=5'd3;
	mem1[32]<=5'd6;
	mem1[33]<=5'd8;
	mem1[34]<=5'd10;
	mem1[35]<=5'd12;
	mem1[36]<=5'd10;
	mem1[37]<=5'd10;
	mem1[38]<=5'd10;
	mem1[39]<=5'd9;
	mem1[40]<=5'd10;
	mem1[41]<=5'd10;
	mem1[42]<=5'd10;
	mem1[43]<=5'd9;
	mem1[44]<=5'd10;
	mem1[45]<=5'd10;
	mem1[46]<=5'd6;
	mem1[47]<=5'd7;
	mem1[48]<=5'd8;
	mem1[49]<=5'd10;
	mem1[50]<=5'd9;
	mem1[51]<=5'd8;
	mem1[52]<=5'd6;
    mem1[53]<=5'd6;
	mem1[54]<=5'd5;
	mem1[55]<=5'd5;
	mem1[56]<=5'd6;
	mem1[57]<=5'd6;
	mem1[58]<=5'd6;
	mem1[59]<=5'd6;
	mem1[60]<=5'd6;
	mem1[61]<=5'd6;
	mem1[62]<=5'd6;
	mem1[63]<=5'd7;
	 
	//东方红
	mem2[0]<=5'd12;
	mem2[1]<=5'd12;
	mem2[2]<=5'd12;
	mem2[3]<=5'd13;
	mem2[4]<=5'd9;
	mem2[5]<=5'd9;
	mem2[6]<=5'd9;
	mem2[7]<=5'd9;
	mem2[8]<=5'd8;
	mem2[9]<=5'd8;
	mem2[10]<=5'd8;
	mem2[11]<=5'd6;
	mem2[12]<=5'd9;
	mem2[13]<=5'd9;
	mem2[14]<=5'd9;
	mem2[15]<=5'd9;
	mem2[16]<=5'd12;
	mem2[17]<=5'd12;
	mem2[18]<=5'd12;
	mem2[19]<=5'd12;
	mem2[20]<=5'd13;
	mem2[21]<=5'd15;
	mem2[22]<=5'd13;
	mem2[23]<=5'd12;
	mem2[24]<=5'd8;
	mem2[25]<=5'd8;
	mem2[26]<=5'd8;
	mem2[27]<=5'd6;
	mem2[28]<=5'd9;
	mem2[29]<=5'd9;
	mem2[30]<=5'd9;
	mem2[31]<=5'd9;
	mem2[32]<=5'd12;
	mem2[33]<=5'd12;
	mem2[34]<=5'd9;
	mem2[35]<=5'd9;
	mem2[36]<=5'd8;
	mem2[37]<=5'd8;
	mem2[38]<=5'd7;
	mem2[39]<=5'd6;
	mem2[40]<=5'd5;
	mem2[41]<=5'd5;
	mem2[42]<=5'd12;
	mem2[43]<=5'd12;
	mem2[44]<=5'd9;
	mem2[45]<=5'd9;
	mem2[46]<=5'd10;
	mem2[47]<=5'd9;
	mem2[48]<=5'd8;
	mem2[49]<=5'd8;
	mem2[50]<=5'd8;
	mem2[51]<=5'd6;
	mem2[52]<=5'd9;
	mem2[53]<=5'd10;
	mem2[54]<=5'd9;
	mem2[55]<=5'd8;
	mem2[56]<=5'd9;
	mem2[57]<=5'd8;
	mem2[58]<=5'd7;
	mem2[59]<=5'd6;
	mem2[60]<=5'd5;
	mem2[61]<=5'd5;
	mem2[62]<=5'd5;
	mem2[63]<=5'd5;
	
	//我和我的祖国
	mem3[0]<=5'd12;
	mem3[1]<=5'd12;
	mem3[2]<=5'd13;
	mem3[3]<=5'd13;
	mem3[4]<=5'd12;
	mem3[5]<=5'd12;
	mem3[6]<=5'd11;
	mem3[7]<=5'd11;
	mem3[8]<=5'd10;
	mem3[9]<=5'd10;
	mem3[10]<=5'd9;
	mem3[11]<=5'd9;
	mem3[12]<=5'd8;
	mem3[13]<=5'd8;
	mem3[14]<=5'd8;
	mem3[15]<=5'd8;
	mem3[16]<=5'd8;
	mem3[17]<=5'd8;
	mem3[18]<=5'd5;
	mem3[19]<=5'd5;
	mem3[20]<=5'd5;
	mem3[21]<=5'd5;
	mem3[22]<=5'd5;
	mem3[23]<=5'd5;
	mem3[24]<=5'd8;
	mem3[25]<=5'd8;
	mem3[26]<=5'd10;
	mem3[27]<=5'd10;
	mem3[28]<=5'd15;
	mem3[29]<=5'd15;
	mem3[30]<=5'd14;
	mem3[31]<=5'd14;
	mem3[32]<=5'd13;
	mem3[33]<=5'd13;
	mem3[34]<=5'd13;
	mem3[35]<=5'd10;
	mem3[36]<=5'd12;
	mem3[37]<=5'd12;
	mem3[38]<=5'd12;
	mem3[39]<=5'd12;
	mem3[40]<=5'd12;
	mem3[41]<=5'd12;
	mem3[42]<=5'd12;
	mem3[43]<=5'd12;
	mem3[44]<=5'd12;
	mem3[45]<=5'd12;
	mem3[46]<=5'd12;
	mem3[47]<=5'd12;
	mem3[48]<=5'd13;
	mem3[49]<=5'd13;
	mem3[50]<=5'd14;
	mem3[51]<=5'd14;
	mem3[52]<=5'd13;
	mem3[53]<=5'd13;
	mem3[54]<=5'd12;
	mem3[55]<=5'd12;
	mem3[56]<=5'd11;
	mem3[57]<=5'd11;
	mem3[58]<=5'd10;
	mem3[59]<=5'd10;
	mem3[60]<=5'd9;
	mem3[61]<=5'd9;
	mem3[62]<=5'd9;
	mem3[63]<=5'd9;
	mem3[64]<=5'd9;
	mem3[65]<=5'd9;
	mem3[66]<=5'd6;
	mem3[67]<=5'd6;
	mem3[68]<=5'd6;
	mem3[69]<=5'd6;
	mem3[70]<=5'd6;
	mem3[71]<=5'd6;
	mem3[72]<=5'd7;
	mem3[73]<=5'd7;
	mem3[74]<=5'd6;
	mem3[75]<=5'd6;	
	mem3[76]<=5'd5;
	mem3[77]<=5'd5;
	mem3[78]<=5'd12;
	mem3[79]<=5'd12;
	mem3[80]<=5'd8;
	mem3[81]<=5'd8;
	mem3[82]<=5'd8;
	mem3[83]<=5'd9;
	mem3[84]<=5'd10;
	mem3[85]<=5'd10;
	mem3[86]<=5'd10;
	mem3[87]<=5'd10;
	mem3[88]<=5'd10;
	mem3[89]<=5'd10;
	mem3[90]<=5'd10;
	mem3[91]<=5'd10;
	mem3[92]<=5'd10;
	mem3[93]<=5'd10;
	mem3[94]<=5'd10;
	mem3[95]<=5'd10;
	
end
	
endmodule

beeper:

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: Beeper
// 
// Author: Step
// 
// Description: Beeper
// 
// Web: www.stepfapga.com
// 
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.0     |2016/04/20   |Initial ver
// --------------------------------------------------------------------
module Beeper
(
input					clk_in,		//系统时钟
input					rst_n_in,	//系统复位,低有效
input					tone_en,	//蜂鸣器使能信号
input			[4:0]	tone,		//蜂鸣器音节控制
output	reg				piano_out	//蜂鸣器控制输出
);
/*
无源蜂鸣器可以发出不同的音节,与蜂鸣器震动的频率(等于蜂鸣器控制信号的频率)相关,
为了让蜂鸣器控制信号产生不同的频率,我们使用计数器计数(分频)实现,不同的音节控制对应不同的计数终值(分频系数)
计数器根据计数终值计数并分频,产生蜂鸣器控制信号
*/
reg [19:0] time_end;
//根据不同的音节控制,选择对应的计数终值(分频系数)
//低音1的频率为261.6Hz,蜂鸣器控制信号周期应为12MHz/261.6Hz = 45871.5,
//因为本设计中蜂鸣器控制信号是按计数器周期翻转的,所以几种终值 = 45871.5/2 = 22936
//需要计数22936个,计数范围为0 ~ (22936-1),所以time_end = 22935
always@(tone) begin
	case(tone)
		5'd1:	time_end =	16'd22935;	//L1,
		5'd2:	time_end =	16'd20428;	//L2,
		5'd3:	time_end =	16'd18203;	//L3,
		5'd4:	time_end =	16'd17181;	//L4,
		5'd5:	time_end =	16'd15305;	//L5,
		5'd6:	time_end =	16'd13635;	//L6,
		5'd7:	time_end =	16'd12147;	//L7,
		5'd8:	time_end =	16'd11464;	//M1,
		5'd9:	time_end =	16'd10215;	//M2,
		5'd10:	time_end =	16'd9100;	//M3,
		5'd11:	time_end =	16'd8589;	//M4,
		5'd12:	time_end =	16'd7652;	//M5,
		5'd13:	time_end =	16'd6817;	//M6,
		5'd14:	time_end =	16'd6073;	//M7,
		5'd15:	time_end =	16'd5740;	//H1,
		5'd16:	time_end =	16'd5107;	//H2,
		5'd17:	time_end =	16'd4549;	//H3,
		5'd18:	time_end =	16'd4294;	//H4,
		5'd19:	time_end =	16'd3825;	//H5,
		5'd20:	time_end =	16'd3408;	//H6,
		5'd21:	time_end =	16'd3036;	//H7,
		default:time_end =	20'd1048575;	 //让蜂鸣器不响
	endcase
end
 
reg [17:0] time_cnt;
//当蜂鸣器使能时,计数器按照计数终值(分频系数)计数
always@(posedge clk_in or negedge rst_n_in) begin
	if(!rst_n_in) begin
		time_cnt <= 1'b0;
	end else if(!tone_en) begin
		time_cnt <= 1'b0;
	end else if(time_cnt>=time_end) begin
		time_cnt <= 1'b0;
	end else begin
		time_cnt <= time_cnt + 1'b1;
	end
end
 
//根据计数器的周期,翻转蜂鸣器控制信号
always@(posedge clk_in or negedge rst_n_in) begin
	if(!rst_n_in) begin
		piano_out <= 1'b0;
	end else if(time_cnt==time_end) begin
		piano_out <= ~piano_out;	//蜂鸣器控制输出翻转,两次翻转为1Hz
	end else begin
		piano_out <= piano_out;
	end
end
 
endmodule

 4.key模块(按键消抖)

该模块的设计思路与设计方法也主要参考了卞维智同学与电子森林提供的开源代码。

module key(  //按键消抖模块
	input clk,
	input rst,
	
	input key,
	output reg [1:0] key_value,  //键值,即按下几次
	output reg   tone_en  //蜂鸣器使能
 );
 
 reg  key_reg ;  
 reg [22:0] delay_cnt;
 reg [63:0] delay_1s_cnt;
 reg [7:0]  delay_1m_cnt;

 
 always @(posedge clk or negedge rst) begin
	if (!rst) begin
		key_reg <= 1'b1;
	end
	else begin
		key_reg <= key;  //非阻塞赋值
		if (key_reg != key)  //判断按键是否在变化(抖动或按下),此时key_reg还是上一个时钟沿的key_reg
		    delay_cnt <= 23'd2400000;			//设置20ms的延时
		else 
			if(delay_cnt > 23'd0)
			     delay_cnt <= delay_cnt - 1'b1;
			else
			     delay_cnt <= 23'd0;
	end
 end
 
  
 always @(posedge clk or negedge rst) begin
	if (!rst) begin
		tone_en <= 1;  //使能蜂鸣器
		key_value <= 1'b0;
		delay_1s_cnt <= 64'd0;  //重新计时
		delay_1m_cnt <= 6'd0;
	end
	else begin
		    if (delay_cnt == 23'd1)begin
			//若按键发生变化,则延时1分钟后关闭音乐   
		            tone_en <= 1;  //使能蜂鸣器
			        delay_1s_cnt <= 64'd0;  //重新计时
		            delay_1m_cnt <= 6'd0;
					key_value <= key_value + 1'b1;
			        if(key_value >= 3)
				        key_value <= 2'b00;
	        end
			else begin  //按键没有发生变化
			       key_value <= key_value;
		           //1分钟后停止播放
		           delay_1s_cnt <= delay_1s_cnt + 1'd1;
		           if(delay_1s_cnt >= 12000000) begin //计时到达1s
			             delay_1m_cnt <= delay_1m_cnt + 1'd1;  //秒数加1
			             delay_1s_cnt <= 64'd0; 
			             if(delay_1m_cnt >= 60) begin  //但秒数加到60,即计时已到达1分钟
			                 	tone_en <= 1'd0;  //计时到1分钟音乐停止播放
                               delay_1m_cnt <= 6'd0;				
			             end
		           end	
		    end
	end
 end
 endmodule

四.完成功能

参考项目需求部分,满足了全部需求。

五.遇到的难题及解决方法

本次设计是我第一次接触FPGA,在安装完软件后我对项目的实现思路并无思路,这成了完成此次寒假在家练项目的第一个障碍。之后在电子森林中观看了一些相关内容的视频和有关本次设计的相关基础知识后并阅读了卞维智同学在2021年的利用PWM实现音乐播放器的项目后,我开始有了思路,这种分开模块特别是top模块让我对这次项目设计的思路变得格外清晰。

有了设计思路后,下一步便是学习代码。我首先通过电子森林提供的很多基础代码知识学习相关代码的使用与功能,在对相关代码有了一定认识后,我开始阅读之前同学所写的代码以及github上提供的开源代码,这个过程很漫长但提升也是真的大。通过读懂代码然和说明书,我开始了本次的设计。

在设计的过程中也遇到了很多问题,其中oled显示是设计过程中比较突出的一个难题,但通过读电子森林平台所提供的的开源代码并查询相关资料,我解决了这一难题。由于第一次接触FPGA的相关知识,此次设计在学习和读之前同学的过程时效率较低,但卞维智同学的设计思路与相关案例的代码给了我很大的帮助,最后完成了本次设计。

六、未来计划及建议

进一步学习FPGA的相关知识,在之后的学习中多参加像这次寒假在家练类似的活动,通过这样的活动让我学习了很多,也充分利用好了寒假的时间,收获很多。在未来的学习中,我们应该多去参考和理解一些优秀同学和老师提供的开源代码,通过越多的阅读可以更高效地解决很多自己之前迷惑的问题。

附件下载
ice.zip
团队介绍
北京理工大学
团队成员
吴昊
北京理工大学
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号