FPGA开发:音乐播放器
相关阅读FPGA开发专栏https://blog.csdn.net/weixin_45791458/category_12388695.html?spm1001.2014.3001.5482FPGA开发板上的蜂鸣器可以用来播放音乐只需要控制蜂鸣器信号的方波频率、占空比和持续时间即可。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高音71976Hz2、结构设计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 1b1; button_negedge 1b0; end else begin button_out_d0 button_out; button_negedge button_out_d0 ~button_out; end end2.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) 8h01 : cycle CLK_FRE*1000000/261 ; //low 1 261Hz 8h02 : cycle CLK_FRE*1000000/293 ; //low 2 293Hz 8h03 : cycle CLK_FRE*1000000/329 ; //low 3 329Hz 8h04 : cycle CLK_FRE*1000000/349 ; //low 4 349Hz 8h05 : cycle CLK_FRE*1000000/392 ; //low 5 392Hz 8h06 : cycle CLK_FRE*1000000/440 ; //low 6 440Hz 8h07 : cycle CLK_FRE*1000000/499 ; //low 7 499Hz 8h11 : cycle CLK_FRE*1000000/523 ; //middle 1 523Hz 8h12 : cycle CLK_FRE*1000000/587 ; //middle 2 587Hz 8h13 : cycle CLK_FRE*1000000/659 ; //middle 3 659Hz 8h14 : cycle CLK_FRE*1000000/698 ; //middle 4 698Hz 8h15 : cycle CLK_FRE*1000000/784 ; //middle 5 784Hz 8h16 : cycle CLK_FRE*1000000/880 ; //middle 6 880Hz 8h17 : cycle CLK_FRE*1000000/998 ; //middle 7 998Hz 8h21 : cycle CLK_FRE*1000000/1046 ; //high 1 1046Hz 8h22 : cycle CLK_FRE*1000000/1174 ; //high 2 1174Hz 8h23 : cycle CLK_FRE*1000000/1318 ; //high 3 1318Hz 8h24 : cycle CLK_FRE*1000000/1396 ; //high 4 1396Hz 8h25 : cycle CLK_FRE*1000000/1568 ; //high 5 1568Hz 8h26 : cycle CLK_FRE*1000000/1760 ; //high 6 1760Hz 8h27 : cycle CLK_FRE*1000000/1976 ; //high 7 1976Hz default : cycle 20d0 ; endcase end endmodule2.4、状态机演奏模块状态机设有四个状态IDLEPLAYPLAY_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 20d0; else if (state PLAY || state PLAY_WAIT)begin if (hz_cnt cycle - 1) hz_cnt 20d0; else hz_cnt hz_cnt 1b1; end else hz_cnt 20d0; end输出信号模块根据计数值输出信号其中还可以控制占空比。always (posedge clk or negedge rst_n)begin if (~rst_n) buzzer 1b1; else if (state PLAY || state PLAY_WAIT)begin if (hz_cnt cycle/32) //控制占空比 buzzer 1b0; else buzzer 1b1; end else if (state IDLE || state PLAY_END) buzzer 1b1; end演奏时长计数器用于对每个音符的演奏时间计数。always (posedge clk or negedge rst_n)begin if (~rst_n) play_cnt 32d0; else if (state PLAY) play_cnt play_cnt 1b1; else play_cnt 32d0; end演奏个数计数器用于对演奏的音符数计数。always (posedge clk or negedge rst_n)begin if (~rst_n) music_cnt 32d0; else if (state PLAY_WAIT) music_cnt music_cnt 1b1; else if (state IDLE || state PLAY_END) music_cnt 32d0; 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 32hffff_ffff; else music_time rom_time_data*(CLK_FRE*1000000*60/85/8); end