FPGA开发:音乐播放器

        FPGA开发板上的蜂鸣器可以用来播放音乐,只需要控制蜂鸣器信号的方波频率、占空比和持续时间即可。

1、简谱原理

        简谱上的4/4表示该简谱以4分音符为一拍,每小节4拍,简谱上应该也会标注每分钟多少拍。音符时值对照表如下图所示,这表示了每个音符的演奏时长。

FPGA开发:音乐播放器_第1张图片        音符是记录音的高低和长短的符号,简谱中的音符是七个阿拉伯数字,它们是:1(Do)、2(Re)、3(Mi)、4(Fa)、5(Sol)、6(La)、7(Ti),为了标记更高或更低的音,则在基本符号的上面或下面加上小圆点。在简谱中,不带点的基本符号叫中音。记在简谱基本音符号下面的小圆点,叫低音点,它表示将基本音符降低一个音组,即降低一个纯八度。在基本符号下面加一个点叫低音,加两个点叫倍低音,加三个点叫超低音。记在简谱基本音符号上面的小圆点,叫高音点,它表示将基本音符升高一个音组,即升高一个纯八度。在基本符号上面加一个点叫高音,加两个点叫倍高音,加三个点叫超高音

        音符所对应的频率如下表所示。

音符 频率
低音1 261Hz
低音2 293Hz
低音3 329Hz
低音4 349Hz
低音5 392Hz
低音6 440Hz
低音7 499Hz
中音1 523Hz
中音2 587Hz
中音3 659Hz
中音4 698Hz
中音5 784Hz
中音6 880Hz
中音7 998Hz
高音1 1046Hz
高音2 1174Hz
高音3 1318Hz
高音4 1396Hz
高音5 1568Hz
高音6 1760Hz
高音7 1976Hz

2、结构设计

2.1、按键消抖模块

        由于要是用按键控制音乐开始播放,所以需要一个按键消抖模块,具体可以在FPGA开发:按键消抖一文中找到。

Debounce debounce_0
(
    .clk             (clk),
    .rst             (rst_n),
    .button_in       (button_in),
    .button_out      (button_out)
);

        同时我们还需要一个边沿检测的机制来保证一次按下只触发一次按键操作。

always @ (posedge clk or posedge rst)begin
	if(rst == 1'b1)begin
		button_out_d0 <= 1'b1;
		button_negedge <= 1'b0;
	end
	else begin
		button_out_d0 <= button_out;
		button_negedge <= button_out_d0 & ~button_out;
	end	
end

2.2、ROM模块

        使用ROM保存音符时长和音调,创建ROM的过程可以根据不同的FPGA开发环境而定,如果是Quartus的话步骤如下:

        首先新建两个个MIF文件,它们是用来初始化ROM的,如下图所示。

FPGA开发:音乐播放器_第2张图片

         根据你的简谱长度,设置深度,如下图所示。

FPGA开发:音乐播放器_第3张图片

        随后根据简谱填入对应信息并保存,如下图所示。 

FPGA开发:音乐播放器_第4张图片

        接着在IP窗口搜索ROM IP,如下图所示。

FPGA开发:音乐播放器_第5张图片

         选好模块名和HDL类型并保存,这里选择Verilog HDL,如下图所示。

FPGA开发:音乐播放器_第6张图片

        在ROM创建菜单中选择创建的ROM大小(这里应该要和刚才的MIF文件一致),如下图所示。

FPGA开发:音乐播放器_第7张图片

        在初始化界面,选择使用刚才创建的MIF文件并Finish即可完成ROM的创建,如下图所示。

FPGA开发:音乐播放器_第8张图片2.3、频率译码模块

         规定中音1使用十进制数11表示,而低音1使用01表示,中音2使用12表示。译码模块根据对应的音符频率,输出相应的周期,其中CLK_FRE根据开发板的频率而定。

module music_hz(
input  [7:0]  hz_sel,
output reg [19:0] cycle
);

parameter CLK_FRE = 50 ;

  always @(*)begin
    case(hz_sel)
      8'h01   : cycle = CLK_FRE*1000000/261  ;  //low 1         261Hz
      8'h02   : cycle = CLK_FRE*1000000/293  ;  //low 2         293Hz
      8'h03   : cycle = CLK_FRE*1000000/329  ;  //low 3         329Hz
      8'h04   : cycle = CLK_FRE*1000000/349  ;  //low 4         349Hz
      8'h05   : cycle = CLK_FRE*1000000/392  ;  //low 5         392Hz
      8'h06   : cycle = CLK_FRE*1000000/440  ;  //low 6         440Hz
      8'h07   : cycle = CLK_FRE*1000000/499  ;  //low 7         499Hz
      8'h11   : cycle = CLK_FRE*1000000/523  ;  //middle 1      523Hz
      8'h12   : cycle = CLK_FRE*1000000/587  ;  //middle 2      587Hz
      8'h13   : cycle = CLK_FRE*1000000/659  ;  //middle 3      659Hz
      8'h14   : cycle = CLK_FRE*1000000/698  ;  //middle 4      698Hz
      8'h15   : cycle = CLK_FRE*1000000/784  ;  //middle 5      784Hz
      8'h16   : cycle = CLK_FRE*1000000/880  ;  //middle 6      880Hz
      8'h17   : cycle = CLK_FRE*1000000/998  ;  //middle 7      998Hz
      8'h21   : cycle = CLK_FRE*1000000/1046 ;  //high 1        1046Hz
      8'h22   : cycle = CLK_FRE*1000000/1174 ;  //high 2        1174Hz
      8'h23   : cycle = CLK_FRE*1000000/1318 ;  //high 3        1318Hz
      8'h24   : cycle = CLK_FRE*1000000/1396 ;  //high 4        1396Hz
      8'h25   : cycle = CLK_FRE*1000000/1568 ;  //high 5        1568Hz
      8'h26   : cycle = CLK_FRE*1000000/1760 ;  //high 6        1760Hz
      8'h27   : cycle = CLK_FRE*1000000/1976 ;  //high 7        1976Hz
      default : cycle = 20'd0 ;
    endcase
  end
endmodule

2.4、状态机演奏模块

        状态机设有四个状态,IDLE,PLAY,PLAY_WAIT和PLAY_END,其中PLAY状态使用一个计数器对每个音符的演奏时长进行计数,PLAY_WAIT用于检查是否全部音符演奏完毕,如果否,则会对演奏时长计数器清零并再次进入PLAY状态。

always @(*)begin
  case(state)
    IDLE:begin
      if (button_negedge)
        next_state = PLAY;
      else
        next_state = IDLE; 
    end
    PLAY:begin
      if (play_cnt == music_time)  
        next_state = PLAY_WAIT;
      else
        next_state = PLAY;
    end
    PLAY_WAIT:begin
      if (music_cnt == music_len - 1)
        next_state = PLAY_END;
      else
        next_state = PLAY;
    end
    PLAY_END:next_state = IDLE;
    default:next_state = IDLE;
  endcase
end

        周期计数器用于对音符的每个周期进行计数,并提供计数值给输出信号模块。

always @(posedge clk or negedge rst_n)begin
  if (~rst_n)
    hz_cnt <= 20'd0;  
  else if (state == PLAY || state == PLAY_WAIT)begin
    if (hz_cnt == cycle - 1)
	    hz_cnt <= 20'd0;
	  else
      hz_cnt <= hz_cnt + 1'b1;
  end
  else 
    hz_cnt <= 20'd0;
end	

        输出信号模块根据计数值输出信号,其中还可以控制占空比。

always @(posedge clk or negedge rst_n)begin
  if (~rst_n)
    buzzer <= 1'b1;  
  else if (state == PLAY || state == PLAY_WAIT)begin
    if (hz_cnt < cycle/32) //控制占空比
      buzzer <= 1'b0;
	else
	  buzzer <= 1'b1;
  end
  else if (state == IDLE || state == PLAY_END)
    buzzer <= 1'b1;
end

        演奏时长计数器用于对每个音符的演奏时间计数。

always @(posedge clk or negedge rst_n)begin
  if (~rst_n)
    play_cnt <= 32'd0;  
  else if (state == PLAY)
    play_cnt <= play_cnt + 1'b1;
  else 
    play_cnt <= 32'd0;
end

        演奏个数计数器用于对演奏的音符数计数。

always @(posedge clk or negedge rst_n)begin
  if (~rst_n)
    music_cnt <= 32'd0;  
  else if (state == PLAY_WAIT)
    music_cnt <= music_cnt + 1'b1;
  else if (state == IDLE || state == PLAY_END)
    music_cnt <= 32'd0;
end

        最后实例化ROM,并且注意,这里规定演奏时长rom值以8为一拍,所以读取rom值后需要进行转换,假设一分钟85拍。

music_hz hz0
(
 .hz_sel(rom_hz_data),
 .cycle(cycle) 
) ;

music_rom hz_rom
(
	.address(music_cnt[8:0]),
	.clock(clk),
	.q(rom_hz_data)
	);


music_time_rom time_rom
(
	.address(music_cnt[8:0]),
	.clock(clk),
	.q(rom_time_data)
	);
	
always @(posedge clk or negedge rst_n)begin
  if (~rst_n)
    music_time <= 32'hffff_ffff;  
  else
    music_time <= rom_time_data*(CLK_FRE*1000000*60/85/8);
end

你可能感兴趣的:(FPGA开发,fpga开发)