FPGA播放声音和音乐
2012-06-02
标签: FPGA

弹奏曲子

现在我们希望通过FPGA来弹奏曲子。首先我们需要一个类似与键盘的东西来弹奏音符。如果我们使用6位去编码一个音符,那么我们可以得到64个音符。每个音阶有12个音符,所以64个音符可以包括比5个还要多的音阶,这对与弹奏一小曲子来说已经足够了。

第一步

为了实现一升调的方式依次64个音符,我们使用一个28位的计数器,使用它的最高6位来作为我们希望弹奏的音符。

代码

  • reg [27:0] tone;
  • always @(posedge clk) tone <= tone+1;
  • wire [5:0] fullnote = tone[27:22];

当输入时钟频率为25MHz时,每个音符持续167ms,总共需要10.6s才能播放完全部64个音符。

第二步.

我们将“fullnote”除以12,从而得到八度音阶(五个音阶(0-4),所以3位就足够了)和12个音符(0-11,所以4位就够了)。

代码

  • wire [2:0] octave;
  • wire [3:0] note;
  • divide_by12 divby12(.numer(fullnote[5:0]), .quotient(octave), .remain(note));

可以看到,这里我们使用了除法模块divby12来完成除法,具体的细节将在后面谈到。

第三步.

当从一个音阶跳到下一个音阶,频率需要乘以2时,这个在硬件上很容易实现,具体将在第四步中讨论。

但是当需要乘以“1.0594”时,这个在硬件上很难实现。因此我们使用一个存储了预先计算好的值的查找表来实现。

我们将主时钟除以512得到A调,除以483得到A#调,除以456得到B调。除以一个越小的值,得到的音调越高。

代码

  • always @(note)
  • case(note)
  • 0: clkdivider = 512-1; // A
  • 1: clkdivider = 483-1; // A#/Bb
  • 2: clkdivider = 456-1; // B
  • 3: clkdivider = 431-1; // C
  • 4: clkdivider = 406-1; // C#/Db
  • 5: clkdivider = 384-1; // D
  • 6: clkdivider = 362-1; // D#/Eb
  • 7: clkdivider = 342-1; // E
  • 8: clkdivider = 323-1; // F
  • 9: clkdivider = 304-1; // F#/Gb
  • 10: clkdivider = 287-1; // G
  • 11: clkdivider = 271-1; // G#/Ab
  • 12: clkdivider = 0; // 永远不会发生
  • 13: clkdivider = 0; // 永远不会发生
  • 14: clkdivider = 0; // 永远不会发生
  • 15: clkdivider = 0; // 永远不会发生
  • endcase
  • always @(posedge clk) if(counter_note==0) counter_note <= clkdivider; else counter_note <= counter_note-1;

每次"counter_note"等于0,都意味着将要转到下一个音阶,对应到程序中就是counter_octave除以2。

第四步.

好了。现在我们来处理一下音阶。

对于最低的音阶,我们将"counter_note"除以256。对于音阶1,除以128... 以此类推...

代码

  • reg [7:0] counter_octave;
  • always @(posedge clk)
  • if(counter_note==0)
  • begin
  • if(counter_octave==0)
  • counter_octave <= (octave==0?255:octave==1?127:octave==2?63:octave==331:octave==4?15:7);
  • else
  • counter_octave <= counter_octave-1;
  • end
  • reg speaker;
  • always @(posedge clk) if(counter_note==0 && counter_octave==0) speaker <= ~speaker;

完整的代码如下所示:

代码

  • module music(clk, speaker);
  • input clk;
  • output speaker;
  • reg [27:0] tone;
  • always @(posedge clk) tone <= tone+1;
  • wire [5:0] fullnote = tone[27:22];
  • wire [2:0] octave;
  • wire [3:0] note;
  • divide_by12 divby12(.numer(fullnote[5:0]), .quotient(octave), .remain(note));
  • reg [8:0] clkdivider;
  • always @(note)
  • case(note)
  • 0: clkdivider = 512-1; // A
  • 1: clkdivider = 483-1; // A#/Bb
  • 2: clkdivider = 456-1; // B
  • 3: clkdivider = 431-1; // C
  • 4: clkdivider = 406-1; // C#/Db
  • 5: clkdivider = 384-1; // D
  • 6: clkdivider = 362-1; // D#/Eb
  • 7: clkdivider = 342-1; // E
  • 8: clkdivider = 323-1; // F
  • 9: clkdivider = 304-1; // F#/Gb
  • 10: clkdivider = 287-1; // G
  • 11: clkdivider = 271-1; // G#/Ab
  • 12: clkdivider = 0; // 永远不会发生
  • 13: clkdivider = 0; // 永远不会发生
  • 14: clkdivider = 0; // 永远不会发生
  • 15: clkdivider = 0; // 永远不会发生
  • endcase
  • reg [8:0] counter_note;
  • always @(posedge clk) if(counter_note==0) counter_note <= clkdivider; else counter_note <= counter_note-1;
  • reg [7:0] counter_octave;
  • always @(posedge clk)
  • if(counter_note==0)
  • begin
  • if(counter_octave==0)
  • counter_octave <= (octave==0255:octave==1127:octave==263:octave==331:octave==415:7);
  • else
  • counter_octave <= counter_octave-1;
  • end
  • reg speaker;
  • always @(posedge clk) if(counter_note==0 && counter_octave==0) speaker <= ~speaker;
  • endmodule

除以12:

“除以12”这么模块完成将一个6位的数(number)除以12这个功能。结果我们将得到一个3位的商(0..5)和一个 4位的余数(0..11)。我们尝试使用厂商提供的除法模块,但是它提供的是一个针对通用除法优化的模块。而这里,除数是固定不变的。所以需要设计一个定制的除法模块。

为了完成处理12,我们采用以下技巧,先将数除以4,然后再除以3。

除以4只需要将数据右移2位即可,移出的2位作为余数。这样我们只剩下6-2=4位数据,只要将他们除以3即可。除以3的操作是用查找表的方法实现的。(为什么这么做:避免使用除法器,可以获得更高的速度,并节省器件资源)

代码

  • module divide_by12(numer, quotient, remain);
  • input [5:0] numer;
  • output [2:0] quotient;
  • output [3:0] remain;
  • reg [2:0] quotient;
  • reg [3:0] remain_bit3_bit2;
  • assign remain = {remain_bit3_bit2, numer[1:0]}; // the first 2 bits are copied through
  • always @(numer[5:2]) // 查找表实现除以3的供呢个
  • case(numer[5:2])
  • 0: begin quotient = 0; remain_bit3_bit2 = 0; end
  • 1: begin quotient = 0; remain_bit3_bit2 = 1; end
  • 2: begin quotient = 0; remain_bit3_bit2 = 2; end
  • 3: begin quotient = 1; remain_bit3_bit2 = 0; end
  • 4: begin quotient = 1; remain_bit3_bit2 = 1; end
  • 5: begin quotient = 1; remain_bit3_bit2 = 2; end
  • 6: begin quotient = 2; remain_bit3_bit2 = 0; end
  • 7: begin quotient = 2; remain_bit3_bit2 = 1; end
  • 8: begin quotient = 2; remain_bit3_bit2 = 2; end
  • 9: begin quotient = 3; remain_bit3_bit2 = 0; end
  • 10: begin quotient = 3; remain_bit3_bit2 = 1; end
  • 11: begin quotient = 3; remain_bit3_bit2 = 2; end
  • 12: begin quotient = 4; remain_bit3_bit2 = 0; end
  • 13: begin quotient = 4; remain_bit3_bit2 = 1; end
  • 14: begin quotient = 4; remain_bit3_bit2 = 2; end
  • 15: begin quotient = 5; remain_bit3_bit2 = 0; end
  • endcase
  • endmodule

音乐

现在让我们来让FPGA播放一段音乐。这个很容易实现,只要再用一块ROM来存放希望播放的音乐的乐谱就可以了。

代码

  • reg [30:0] tone;
  • always @(posedge clk) tone <= tone+1;
  • wire [7:0] fullnote;
  • music_rom rom(.inclock(clk), .outclock(clk), .address(tone[29:22]), .q(fullnote));

其中“music_rom”使用FPGA厂商提供的工具生成的,如Altera的Quartus II 及其宏功能生成的这些文件。

另外,我们还希望实现以下功能:

* 播放音乐时,在ROM的结尾处暂停

* "fullnote"值为0时,表示静音

所以我们将原来的程序的最后一行从

代码

  • always @(posedge clk) if(counter_note==0 && counter_octave==0) speaker <= ~speaker;

修改为:

代码

  • always @(posedge clk) if(counter_note==0 && counter_octave==0 && tone[30]==0 && fullnote!=0) speaker <= ~speaker;

设计的剩下部分是一样的。你能认得这首音乐吗?:)

共 3 页   上一页123
可能会用到的工具/仪表
本站简介 | 意见建议 | 免责声明 | 版权声明 | 联系我们
CopyRight@2024-2039 嵌入式资源网
蜀ICP备2021025729号