在iCE40UP5K的FPGA学习平台下利用蜂鸣器制作音乐播放器
我选择的项目为利用基于iCE40UP5K的FPGA学习平台制作一款PWM制作一个音乐播放器,要求有音乐播放、按键防抖切换、OLED屏幕显示音乐汉字名称。
标签
FPGA
PWM
2022寒假在家练
LED12864
WS2812
蜂鸣器音乐
神经娃
更新2022-03-04
兰州大学
779

一、使用平台

      本设计基于Lattice的ICE40UP5K FPGA,板载LPC11U35下载器,可以通过USB-C接口进行FPGA的配置,支持在ICE40UP5K上对RISC-V软核的移植以及开源的FPGA开发工具链,板上RGB三色LED灯用于简单的调试,总计28个IO用于扩展使用。

二、项目要求

项目2 - 利用PWM制作一个音乐播放器

  1. 通过PWM产生不同的音调,并驱动板上蜂鸣器将音调输出
  2. 能够播放三首不同的曲子,每个曲子的时间长度为1分钟,可以切换播放
  3. 曲子的切换使用扩展板的按键,需要有按键消抖的功能
  4. 播放的曲子的名字在OLED屏幕上显示出来(汉字显示)

三、主要思路

      本项目中原理框图,如图1所示,其中包括按键消抖、音乐播放、蜂鸣器驱动、OLED显示驱动、LED驱动、分频器、ws2817驱动等模块,K1键用以暂停和播放音乐,k2键切换音乐曲目。

FmtcpkmxhROBiQVP2HowuMASjJ0T      图1 原理框图

     本项目中播放音乐主要用到的元器件是蜂鸣器,从平台电路图可以知道蜂鸣器使用NPN三极管驱动,三极管当开关用,当基极电压拉高时,蜂鸣器通电,当基极电压拉低时,蜂鸣器断电,FPGA控制GPIO口给三极管的基极输出不同频率的脉冲信号,蜂鸣器就可以发出不同的音节。下图为各种音节对于的频率。

Fjp5x1k3DmjvotagMmxJBJwzzSrP

      FPGA要驱动蜂鸣器就需要给蜂鸣器模块输出《音调频率对照表》中不同频率的脉冲信号就可以了,那么下一步根据上图所示音调产生对于频率的pwm波,假设需要产生低音1,则脉冲信号频率控制为261.6Hz,平台电路中系统时钟采用12MHz,计数器计数终值就等于12M / 261.6 = 45872,即当我们给PWM模块中cycle信号值为45872时,得到低音1的音节输出。以此类推可以计算出所有音节的PWM模块中cycle信号值。

代码如下:

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

     音乐播放模块,主要作用是将选择的音乐曲目按照曲目音节以此传输给蜂鸣器驱动模块,主要是按曲目音节编辑曲目,同时响应K2按键,根据按键情况切换音乐,代码如下:

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

localparam nummax1 = 6'd214;  
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'd1: begin nummax <= nummax1;  end
		2'd2: begin nummax <= nummax2;  end
		2'd3: 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'd1: tone <= mem1[num];  //奇迹之光
	         	2'd2: tone <= mem2[num];  //东方红
		        2'd3: 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'd 0;
	//篇幅原因省略
	mem1[214] <= 5'd 7;
	 
	//东方红
	mem2[0]<=5'd12;
        //篇幅原因省略    
        mem2[63]<=5'd5;
	
	//我和我的祖国
	mem3[0]<=5'd12;
	//篇幅原因省略
	mem3[95]<=5'd10;
	
end
	
endmodule

      LED12864显示驱动,按照k2键选择的曲目中文显示曲目及曲目编号,代码主要参考电子森林源码,修改中文显示和刷新程序,并制作了对应曲目的字模,部分代码如下:

MAIN:begin
					 case(key_value)
						 2'd0:begin
						if(cnt_main >= 5'd19) cnt_main <= 5'd13;//接下来执行空操作,实现数据只刷新一次
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)	//MAIN状态							
							5'd0 :	begin state <= INIT; end
																														
							5'd1 :	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;  char <= "                ";state <= SCAN; end
							5'd2 :	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;  char <= "                ";state <= SCAN; end												
							5'd3 :	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd4 :	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd5 :	begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd6 :	begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd7 :	begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd8 :	begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end							
							
							5'd9 :	begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd0; state <= CHINESE; end
							5'd10:	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd2; state <= CHINESE; end
							5'd11 :	begin y_p <= 8'hb0; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd4; state <= CHINESE; end
							5'd12:	begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd6; state <= CHINESE; end
						
							5'd13:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd8; state <= CHINESE; end
							5'd14:	begin y_p <= 8'hb3; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd10; state <= CHINESE; end
							5'd15:	begin y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd12; state <= CHINESE; end
							5'd16:	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd14; state <= CHINESE; end	
							5'd17:	begin y_p <= 8'hb3; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd16; state <= CHINESE; end
							5'd18:	begin y_p <= 8'hb3; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd18; state <= CHINESE; end
							5'd19:	begin y_p <= 8'hb6; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd2;  char <= key_value;state <= SCAN; end
							default: state <= IDLE;   //如果你需要动态刷新一些信息,此行应该取消注释
						endcase
						end
						2'd1:begin
						if(cnt_main >= 5'd19) cnt_main <= 5'd13;//接下来执行空操作,实现数据只刷新一次
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)	//MAIN状态							
							5'd0 :	begin state <= INIT; end
																														
							5'd1 :	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;  char <= "                ";state <= SCAN; end
							5'd2 :	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;  char <= "                ";state <= SCAN; end												
							5'd3 :	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd4 :	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd5 :	begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd6 :	begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd7 :	begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd8 :	begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end							
							
							5'd9 :	begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd0; state <= CHINESE; end
							5'd10:	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd2; state <= CHINESE; end
							5'd11 :	begin y_p <= 8'hb0; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd4; state <= CHINESE; end
							5'd12:	begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd6; state <= CHINESE; end
						
							5'd13:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd8; state <= CHINESE; end
							5'd14:	begin y_p <= 8'hb3; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd10; state <= CHINESE; end
							5'd15:	begin y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd12; state <= CHINESE; end
							5'd16:	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd14; state <= CHINESE; end	
							5'd17:	begin y_p <= 8'hb3; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd16; state <= CHINESE; end
							5'd18:	begin y_p <= 8'hb3; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd18; state <= CHINESE; end
							5'd19:	begin y_p <= 8'hb6; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd2;  char <= key_value;state <= SCAN; end
							default: state <= IDLE;   //如果你需要动态刷新一些信息,此行应该取消注释
						endcase
						end
						2'd2:begin
						if(cnt_main >= 5'd19) cnt_main <= 5'd13;
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)	//MAIN状态							
							5'd0 :	begin state <= INIT; end
																														
							5'd1 :	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;  char <= "                ";state <= SCAN; end
							5'd2 :	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;  char <= "                ";state <= SCAN; end												
							5'd3 :	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd4 :	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd5 :	begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd6 :	begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd7 :	begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd8 :	begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end							
							
							5'd9 :	begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd0; state <= CHINESE; end
							5'd10:	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd2; state <= CHINESE; end
							5'd11 :	begin y_p <= 8'hb0; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd4; state <= CHINESE; end
							5'd12:	begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd6; state <= CHINESE; end
							
							5'd13:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd8; state <= CHINESE; end
							5'd14:	begin y_p <= 8'hb3; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd10; state <= CHINESE; end
							5'd15:	begin y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd12; state <= CHINESE; end
							5'd16:	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd14; state <= CHINESE; end	
							5'd17:	begin y_p <= 8'hb3; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd16; state <= CHINESE; end
							5'd18:	begin y_p <= 8'hb3; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd18; state <= CHINESE; end
							5'd19:	begin y_p <= 8'hb6; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd2;  char <= key_value;state <= SCAN; end
							default: state <= IDLE;   //如果你需要动态刷新一些信息,此行应该取消注释
						endcase
						end
						2'd3:begin
						if(cnt_main >= 5'd19) cnt_main <= 5'd13;
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)	//MAIN状态							
							5'd0 :	begin state <= INIT; end
																														
							5'd1 :	begin y_p <= 8'hb0; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;  char <= "                ";state <= SCAN; end
							5'd2 :	begin y_p <= 8'hb1; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16;  char <= "                ";state <= SCAN; end												
							5'd3 :	begin y_p <= 8'hb2; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd4 :	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd5 :	begin y_p <= 8'hb4; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd6 :	begin y_p <= 8'hb5; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd7 :	begin y_p <= 8'hb6; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end
							5'd8 :	begin y_p <= 8'hb7; x_ph <= 8'h10; x_pl <= 8'h00; num <= 5'd16; char <= "                ";state <= SCAN; end							
							
							5'd9 :	begin y_p <= 8'hb0; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd0; state <= CHINESE; end
							5'd10:	begin y_p <= 8'hb0; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd2; state <= CHINESE; end
							5'd11 :	begin y_p <= 8'hb0; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd4; state <= CHINESE; end
							5'd12:	begin y_p <= 8'hb0; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd6; state <= CHINESE; end
			
							5'd13:	begin y_p <= 8'hb3; x_ph <= 8'h10; x_pl <= 8'h00; mem_hanzi_num <= 8'd8; state <= CHINESE; end
							5'd14:	begin y_p <= 8'hb3; x_ph <= 8'h11; x_pl <= 8'h00; mem_hanzi_num <= 8'd10; state <= CHINESE; end
							5'd15:	begin y_p <= 8'hb3; x_ph <= 8'h12; x_pl <= 8'h00; mem_hanzi_num <= 8'd8; state <= CHINESE; end
							5'd16:	begin y_p <= 8'hb3; x_ph <= 8'h13; x_pl <= 8'h00; mem_hanzi_num <= 8'd12; state <= CHINESE; end	
							5'd17:	begin y_p <= 8'hb3; x_ph <= 8'h14; x_pl <= 8'h00; mem_hanzi_num <= 8'd14; state <= CHINESE; end
							5'd18:	begin y_p <= 8'hb3; x_ph <= 8'h15; x_pl <= 8'h00; mem_hanzi_num <= 8'd18; state <= CHINESE; end
							5'd19:	begin y_p <= 8'hb6; x_ph <= 8'h12; x_pl <= 8'h08; num <= 5'd2;  char <= key_value;state <= SCAN; end
							default: state <= IDLE;   //如果你需要动态刷新一些信息,此行应该取消注释
						endcase
						end
						default: state <= IDLE;   //如果你需要动态刷新一些信息,此行应该取消注释
						endcase
					end

        为了利用扩展板上的四色LED灯,并随音乐变换闪烁,主要是利用了驱动蜂鸣器的高低电平值,经过分频器产生1s延时后,驱动LED灯。部分代码如下:

module LED (
 
	input clk,rst,led_b,				
	//output led_1,led_2,led_3,led_4
	output [3:0] led_out
);
	wire clk1h;                                     //定义一个中间变量,表示分频得到的时钟,用作计数器的触发        

	//例化分频器模块,产生一个1Hz时钟信号		
	divide #(.WIDTH(32),.N(4000000)) u2 (         //传递参数
		.clk(clk),
		.rst_n(rst),                   //例化的端口信号都连接到定义好的信号
		.clkout(clk1h)
		);                             

	//1Hz时钟上升沿触发循环赋值
	reg [3:0] led;	
	always@(posedge clk1h or negedge rst)
		begin
			if(!rst)
				led <= 4'b1110;            // <=为非阻塞赋值
			else 
				led <= {led_b,led[3:1]};      //当时钟上升沿来一次,执行一次赋值,赋值内容是led[0]与led[7:1]重新拼接成8位赋给led,相当于循环右移
		end
assign led_out = led;
 endmodule

 k1,k2两个按键采用了电子森林提供的消抖模块进行了按键消抖,其中k1按下和弹起输出0/1控制音乐播放和暂停,k2在记录按下次数选择播放音乐的曲目,部分代码如下:

// 监测两个按键的按下与释放
module key_out(
	input					clk_in,		//系统时钟
	input					rst_n_in,	//系统复位,低有效
	input				    key_on,//蜂鸣器开启关闭
	input				    key_sl,//歌曲选择
	output                 key_on_down,//key_on是按下还是弹起
	output  	[1:0]	    key_sl_no//key_sl的按下次数 0-3循环
);

wire key_pulse_1;
wire key_pulse_2;

debounce debounce_u1(
	.clk(clk_in),
	.rst(rst_n_in),
	.key(key_on),
	.key_pulse(key_pulse_1)
);

debounce debounce_u2(
	.clk(clk_in),
	.rst(rst_n_in),
	.key(key_sl),
	.key_pulse(key_pulse_2)
);

reg tone_en ;
reg [1:0] key_value;

always @(posedge clk_in  or  negedge rst_n_in)
        begin
             if (!rst_n_in) 
				tone_en <= 1'b1;
			 else if (key_pulse_1)
					tone_en <= ~tone_en;
			 else
                tone_en <= tone_en;
	   end 

always @(posedge clk_in  or  negedge rst_n_in)
        begin
             if (!rst_n_in) 
				key_value <= 2'd0;
			 else if (key_pulse_2)
					if (key_value==2'd3)
						key_value <= 2'd0;
					else
						key_value <= key_value + 1;
			 else
				 key_value <= key_value;
	   end 


assign key_on_down = tone_en;
assign key_sl_no = key_value;

endmodule

为了使平台播放音乐时不单调,使用了ws2812闪烁制造氛围,驱动代码采用了电子森林示例代码,此处不在赘述。

以上所述使用的硬件资源如下图所示:

FsXPQ2LzQ1DpomO6tGX6_ig8-m33Fkd_aSwVnZwzG2u_LIHKa1ebVNkU

四、FPGA资源占用报告

FoQsFZX-ojKEZ5xpCzjpIeAawMZU

 

 

四、主要的收获和遇到的困难

      通过这次参加活动,我学习了Verilog语言和FPGA应用开发方法,特别掌握了一些硬件基础知识和驱动方法,开阔了眼界且打开了思路,我感觉非常有成就感也对FPGA开发产生了浓厚的兴趣。

      同时,我在代码编写过程中也遇到了不少困难,刚开始对FPGA并行编程思想掌握不清楚导致学习进度缓慢,特别是OLED显示方面有些摸不着头脑,后来在不断解决问题中不断摸索,虽然遇到了一些问题,我也从处理问题中学到了很多知识。

附件下载
music_player.rar
整个工程文件
music_player.rbt
编译后的二进制文件
FPGA占用资源报告.docx
FPGA占用资源报告
团队介绍
电子制作爱好者
团队成员
神经娃
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号