[fpga4fun] Music box
还没整完。
UPDATE1 AT: 2022-12-22 继续完成了Music box 2 这一部分
因为在学校收了一块野火征途mini FPGA开发板,在看的是配套的视频教程。群里学长推荐我做一做 fpga4fun 上的东西,那就整一整 ~
简介
这个项目是用 FPGA 驱动喇叭发出声音和音乐。
硬件连接为 FPGA 开发板,一个喇叭,一个 1kΩ 电阻。


Music box 1 -Simple beep
计数与频率
一个16位的计数器,范围位 0 ~ 65535 共65536个值,如果板子的主频为 25M ,如果在上升沿计数(时钟是个方波),那么这个16位计数器最高位对应的频率便为 $25\times10^6\div65536=381Hz$ (就是加到对应那一位变化之后算一次)
以此类推第15位为 $25\times10^6\div32768=762Hz$ , 第14位为 $25\times10^6\div16384=1525Hz$ …… 第2位为$25\times10^6\div4=6.25\times10^6Hz$ , 第1位为$25\times10^6\div2=15.5\times10^6Hz$
产生 A 的声音
A 的频率为 440Hz
以 25M 主频为例,产生440Hz可以先使用16位计数器,将 25M 分为 65536 份,此时计算出产生 440Hz 只需要计数到 56818 即可。
1 2 3 4 5 6 7
| reg [15:0] counter; always @(posedge clk) if(counter==56817) counter < = 0; else counter < = counter+1; assign speaker = counter[15];
|
但是这样的话,占空比就不是 50% 了,$b’1000\ 0000\ 0000\ 0000=d’32768$ ,所以低电平为 032767,高电平为 3276856818,占空比为42%
所以可以折中一下,然后每次翻转 speaker
的电平值 ,而且折中之后可以省一位计数器的值~
1 2 3 4 5 6 7 8 9 10 11
| reg [14:0] counter; always @(posedge clk) if(counter==28408) counter< =0; else counter < = counter+1;
reg speaker; always @(posedge clk) if(counter==28408) speaker < = ~speaker;
|
实践
因为征途mini的主频为50M,所以用17位可以产生的时钟为 $(2\times25\times10^6)\div(2\times65536)=381Hz$
要产生440Hz,计数到 $56818\times2=113636$ 即可。
使用翻转的方式,使用16位计数器,折中计数至 56818,同时加上了复位信号
主程序代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| module music ( input wire clk, input wire rst, output reg speaker );
reg [15:0] counter;
always @(posedge clk or negedge rst) if(rst == 1'b0) counter < = 16'b0; else if(counter == 16'd56817) counter < = 16'b0; else counter < = counter + 1'b1;
always @(posedge clk or negedge rst) if(rst == 1'b0) speaker < = 1'b0; else if(counter == 16'd56817 ) speaker < = ~speaker; else speaker < = speaker;
endmodule
|
仿真验证,Testbench
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| `timescale 1ns/1ns module tb_music();
reg clk; reg rst; wire speaker;
initial begin clk = 1'b0; rst < = 1'b0; #20 rst < = 1'b1; end
always #10 clk = ~clk;
music music_inst ( .clk (clk), .rst (rst),
.speaker (speaker) );
endmodule
|
仿真结果

占空比 $4545450-3409090=1136360$ $3409090-2272730=1136360$ ,可以看出占空比确实为50%
频率 $\frac{1}{4545450-2272730}\times10^9=440Hz$
)因为我莫得喇叭,就先不上板子了……
剩下的等我学完…… 下面的再更新一下
添加参数
添加了一个名字叫 clkdivider
的参数,方便之后的修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| module music #( parameter clkdivider = 50000000/440/2 ) ( input wire clk, input wire rst, output reg speaker );
reg [15:0] counter;
always @(posedge clk or negedge rst) if(rst == 1'b0) counter <= 16'b0; else if(counter == clkdivider-1) counter <= 16'b0; else counter <= counter + 1'b1;
always @(posedge clk or negedge rst) if(rst == 1'b0) speaker <= 1'b0; else if(counter == clkdivider-1 ) speaker <= ~speaker; else speaker <= speaker;
endmodule
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| `timescale 1ns/1ns module tb_music();
reg clk; reg rst; wire speaker; parameter clkdivider = 50000000/440/2;
initial begin clk = 1'b0; rst <= 1'b0; #20 rst <= 1'b1; end
always #10 clk = ~clk;
music #( .clkdivider(clkdivider) ) music_inst ( .clk (clk), .rst (rst),
.speaker (speaker) );
endmodule
|
Music box 2 - Ambulance siren
这次在两种音调之间交替。首先使用24位计数器 tone
来产生一个缓慢的方波,其最高位(MSB)以1.5Hz的频率进行切换。然后我们在这两个频率之间切换另一个计数器。这样就发出了类似救护车警报一类的声音。
先产生一个1.5Hz的信号 tone
,此时计数器 counter
为向下计数,当 counter
为0时,如果 tone
为1,继续产生440Hz的信号,当 tone
为0时,产生220Hz。这两个频率的信号以1.5Hz的频率交替出现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| module music #( parameter clkdivider = 50000000/440/2 ) ( input wire clk, input wire rst, output reg speaker );
reg [24:0] tone; always @(posedge clk or negedge rst) if(rst == 1'b0) tone <= 25'b0; else tone <= tone+1;
reg [15:0] counter; always @(posedge clk or negedge rst) if(rst == 1'b0) counter <= 16'b0; else if(counter == 0) counter <= (tone[24]?clkdivider-1 : clkdivider/2-1); else counter <= counter - 1;
always @(posedge clk or negedge rst) if(rst == 1'b0) speaker <= 1'b0; else if(counter == 0) speaker <= ~speaker;
endmodule
|
Police siren
接下来是生成一个听起来像警笛的声音。这个声音听着是又小逐渐变大的这种,之前声音像跳楼梯,这次的像上坡。
我们将音调计数器 tone
的速度提高一倍,变为3Hz。
然后,就开始升频操作。我们提取音调计数器的16位到第22位:tone[22:16]
。这样我们得到了7bit,并且以一个中等速度从0~127,到达127后,再从127~0。
为了得到一个向下的斜坡,将其进行翻转(~tone[22:16]
),此时从127~0。
为了切换上下这两个通道,同上面一样,我们根据 MSB tone[23]
的值进行选择,3Hz切换一次。
1
| wire [6:0] ramp = (tone[23] ? tone[22:16] : ~tone[22:16]);
|
wire表示直通,即只要输入有变化,输出马上无条件地反映;reg表示一定要有触发,输出才会反映输入。(【Verilog HDL】Verilog中wire与reg类型学习 - 知乎 (zhihu.com))
这样做的意义:fpga4fun.com - Music box siren ramp 。
这个 ramp
的值从 7'b0000000
到 7'b1111111
。为了得到一个可用的值产生声音,我们在他前面补2位 2'b01
后面补7位 7'b0000000
进行填充。
1
| wire [15:0] clkdivider = {2'b01, ramp, 7'b0000000};
|
这样, clkdivider
的值从 16'b0100000000000000
到16'b01111110000000
,在50MHz的时钟下,相当于产生从765Hz到1525Hz的高音调警报器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| module police #( parameter clkdivider = 50000000/440/2 ) ( input wire clk, input wire rst, output reg speaker );
reg [23:0] tone; always @(posedge clk or negedge rst) if(rst == 1'b0) tone <= 24'b0; else tone <= tone+1;
wire [6:0] ramp = (tone[23] ? tone[22:16] : ~tone[22:16]);
wire [15:0] clkdivider = {2'b01, ramp, 7'b0000000};
reg [15:0] counter; always @(posedge clk or negedge rst) if(rst == 1'b0) counter <= 16'b0; else if(counter == 0) counter <= clkdivider; else counter <= counter - 1;
always @(posedge clk or negedge rst) if(rst == 1'b0) speaker <= 1'b0; else if(counter == 0) speaker <= ~speaker;
endmodule
|
High-speed pursuit
要进行高速追击,警报声音有快有慢。
所以 tone[22:16]
给我们一个快速的斜坡,而 tone[25:19]
给我们一个缓慢的斜坡。
1 2 3
| wire [6:0] fastsweep = (tone[23] ? tone[22:16] : ~tone[22:16]); wire [6:0] slowsweep = (tone[26] ? tone[25:19] : ~tone[25:19]); wire [15:0] clkdivider = {2'b01, (tone[28] ? slowsweep : fastsweep), 7'b0000000};
|
最终代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| module hign ( input wire clk, input wire rst, output reg speaker );
reg [28:0] tone; always @(posedge clk or negedge rst) if(rst == 1'b0) tone <= 25'b0; else tone <= tone+1;
wire [6:0] fastsweep = (tone[23] ? tone[22:16] : ~tone[22:16]); wire [6:0] slowsweep = (tone[26] ? tone[25:19] : ~tone[25:19]); wire [15:0] clkdivider = {2'b01, (tone[28] ? slowsweep : fastsweep), 7'b0000000};
reg [15:0] counter; always @(posedge clk or negedge rst) if(rst == 1'b0) counter <= 16'b0; else if(counter == 0) counter <= clkdivider; else counter <= counter - 1;
always @(posedge clk or negedge rst) if(rst == 1'b0) speaker <= 1'b0; else if(counter == 0) speaker <= ~speaker;
endmodule
|
仿真
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| `timescale 1ns/1ns module tb_hign();
reg clk; reg rst; wire speaker;
initial begin clk = 1'b0; rst <= 1'b0; #20 rst <= 1'b1; end
always #10 clk = ~clk;
hign hign_inst ( .clk (clk), .rst (rst),
.speaker (speaker) );
endmodule
|

可以看出间隔确实是变化的,仿真波形图里第一个频率经过计算为912Hz,符合要求。
待续……