FPGA开发:音乐播放器

相关阅读 

FPGA开发专栏icon-default.png?t=O83Ahttps://blog.csdn.net/weixin_45791458/category_12388695.html?spm=1001.2014.3001.5482


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

1、简谱原理

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

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

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

音符频率
低音1261Hz
低音2293Hz
低音3329Hz
低音4349Hz
低音5392Hz
低音6440Hz
低音7499Hz
中音1523Hz
中音2587Hz
中音3659Hz
中音4698Hz
中音5784Hz
中音6880Hz
中音7998Hz
高音11046Hz
高音21174Hz
高音31318Hz
高音41396Hz
高音51568Hz
高音61760Hz
高音71976Hz

2、结构设计

2.1、按键消抖模块

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

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

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

always @ (posedge clk or negedge rst_n)begin
	if(~rst_n)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的,如下图所示。

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

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

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

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

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

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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日晨难再

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值