【fpga4fun】Music box

本文最后更新于:2022年12月22日 凌晨

[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 即可。

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 的电平值 ,而且折中之后可以省一位计数器的值~

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,同时加上了复位信号

主程序代码:

// music.v
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 代码

// tb_music.v
`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

仿真结果

0

占空比 $4545450-3409090=1136360$ $3409090-2272730=1136360$ ,可以看出占空比确实为50%

频率 $\frac{1}{4545450-2272730}\times10^9=440Hz$

)因为我莫得喇叭,就先不上板子了……


剩下的等我学完…… 下面的再更新一下

添加参数

添加了一个名字叫 clkdivider 的参数,方便之后的修改。

// music.v
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
// testbench.v
`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的频率交替出现。

// ambulance.v
module music #(
    parameter  clkdivider = 50000000/440/2
)
(
    input       wire    clk,
    input       wire    rst,
    output      reg     speaker
);

// MSB 1.5Hz变动一次
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;
    // 计数器为0,说明当前频率已产生完毕,切换下一个
    else if(counter == 0) 
        // 根据tone的MSB是否有值来确定
        // 如果tone的MSB为0,则切换为220Hz的声音
        // 如果tone的MSB为1,则切换为440Hz的声音
        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切换一次。

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'b00000007'b1111111 。为了得到一个可用的值产生声音,我们在他前面补2位 2'b01 后面补7位 7'b0000000 进行填充。

wire [15:0] clkdivider = {2'b01, ramp, 7'b0000000};

这样, clkdivider 的值从 16'b010000000000000016'b01111110000000 ,在50MHz的时钟下,相当于产生从765Hz到1525Hz的高音调警报器。

// police
module police #(
    parameter  clkdivider = 50000000/440/2
)
(
    input       wire    clk,
    input       wire    rst,
    output      reg     speaker
);

// MSB 3Hz变动一次
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]);
// clkdivider 以3Hz为周期变动
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] 给我们一个缓慢的斜坡。

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};

最终代码

// hign.v
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 

仿真

`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),  //input     clk
    .rst (rst),

    .speaker (speaker)   //output    speaker
);

endmodule

1

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


待续……


【fpga4fun】Music box
https://blog.ksfu.top/posts/f804/
作者
康师傅
发布于
2022年5月9日
许可协议