前言
本系列文章来源于FPGA学习开源网站fpga4fun,网站由浅入深的介绍了26个FPGA工程,通过工程的学习,可以了解什么是FPGA以及它是如何工作的。本人仅用于学习和记录,如有侵权,速删。
一、工程介绍
工程使用25MHz晶振的FPGA驱动蜂鸣器,具体连接电路如下:
示意图如下:
晶振给FPGA提供固定的频率,FPGA通过分频以驱动IO口,IO通过1KΩ电阻连接蜂鸣器,通过改变IO口的频率,使FPGA产生不同的声音。
根据代码的复杂程度,可以分成以下两个功能:
1.简单蜂鸣器
2.呼叫器
二、简单蜂鸣器
2.1 16位计数器的蜂鸣器
16位计数器范围为0-65535,使用25MHz晶振可以实现16位自由运行计数器。其中,计数器最高位MSB的开关频率为25_000_000 ÷ 65536 = 381Hz,381Hz可以看成是蜂鸣器的音调,计数器在0-32767之间,counter[15]为0,在32768-65535之间,conter[15]值为1,具体verilog代码如下:
module music(
input clk_i,
output speaker_o
);
reg [15:0] counter;
always@(posedge clk_i)begin
counter <= counter + 1;
end
//use the most significant bit(MSB) of the counter to drive the speaker.
assign speaker_o = counter[15];
endmodule
2.2 特定字符蜂鸣器
2.1节设计的蜂鸣器,计数器的值是人为设定的,如果想让蜂鸣器发出特定的音调,比如‘A’字符,音调为440Hz,那么计数器的值应为25_000_000 ÷ 440 = 56818,具体verilog代码如下:
module music(
input clk_i,
output speaker_o
);
reg [15:0] counter;
always@(posedge clk_i)begin
if(counter == 56817)begin
counter <= 0;
end else begin
counter <= counter + 1;
end
end
assign speaker_o = counter[15];
endmodule
2.3 调整蜂鸣器占空比为50%
从2.1和2.2节得知,我们是将16位计数器的最高位counter[15]做为蜂鸣器的开关,2.1节计数器从0计数到65535期间,counter[15]的值在0-32767时的值为0,在32768-65535时的值为1,可见发出381Hz音调的占空比为50%;而2.2节计数器从0计数到56817期间,counter[15]的值在0-32767时的值为0,在32768-56817时的值为1,可见发出440Hz音调的占空比为42%。现在调整蜂鸣器占空比为50%,只需将计数器的值除以2,并且在计数器满值的时候翻转输出状态,具体verilog代码如下:
module music(
input clk_i,
output reg speaker_o
);
reg [14:0] counter;
always@(posedge clk_i)begin
if(counter == 28408)begin
counter <= 0;
end else begin
counter <= counter + 1;
end
end
always@(posedge clk_i)begin
if(counter == 28408) begin
speaker_o <= ~ speaker_o;
end
end
endmodule
2.4 参数化蜂鸣器
对2.3节代码进行参数化设计,并使用倒计时计数器,具体verilog代码如下:
module music(
input clk_i,
output reg speaker_o
);
parameter CLK_DIV = 25_000_000/440/2;
reg [14:0] counter;
always@(posedge clk_i)begin
if(counter == 0)begin
counter <= CLK_DIV - 1;
end else begin
counter <= counter - 1;
end
end
always@(posedge clk_i)begin
if(counter == 0) begin
speaker_o <= ~ speaker_o;
end
end
endmodule
三、呼叫器
3.1 救护车
救护车的声音通过切换两种不同的音调即可实现,我们使用24位计数器的最高位tone[23]作为蜂鸣器的开关,先给tone[23]一个计数值,然后将tone[23]里的值切换到另一个计数值,即可实现不同音调的切换,注意tone[23]切换频率约为1.5Hz,具体verilog代码如下所示:
module music(
input clk_i,
output reg speaker_o
);
parameter CLK_DIV = 25_000_000/440/2;
reg [23:0] tone;
reg [14:0] counter;
always@(posedge clk_i) begin
tone <= tone + 1;
end
always@(posedge clk_i)begin
if(counter == 0)begin
counter <= (tone[23] ? CLK_DIV-1 : CLK_DIV/2-1);
end else begin
counter <= counter - 1;
end
end
always@(posedge clk_i)begin
if(counter == 0) begin
speaker_o <= ~ speaker_o;
end
end
endmodule
3.2 警车
相比于救护车,警车的音调切换的频率比较快,我们使用23位计数器的最高位tone[22]作为蜂鸣器开关,其切换频率为3Hz,为救护车频率的2倍。简单警车的声音是从低到高 再 从高到低这样一个循环的声音。因此要设计一个从低到高的计数器和一个从高到低的计数器。这里有一个思想是从23位计数器中取7位作为从低到高计数器,再取7位作为从高到低计数器。先设计从低到高计数器,取计数器的[15-21]位,计数器从0计数到127后,重新从0计数;再设计从高到低计数器,取计数器的[15-21]位取反即可实现127计数到0;最后使用最高位tone[22]作为音调切换开关,当从低到高计数器计数到127,切换到从高到低计数器,计数到0,再回到从低到高计数器,如此循环,具体代码片段如下所示:
wire [6:0] ramp = (tone[23] ? tone[21:15] : ~tone[21:15]);
上述得到的只是一个从低到高和从高到低的计数器,并不能实际使用,需要设计一个有用的计数值去产生声音,设计一个15位的计数值,高两位为01,中间7位计数器ramp,后面6位为0,这样做是为了产生一个765Hz-1525Hz的报警器,当然你也可以设定你自己想要的值,具体代码片段如下所示:
wire [14:0] ramp = {2'b01, ramp, 6'b000_000};
整体verilog代码如下所示:
module music(
input clk_i,
output reg speaker_o
);
reg [22:0] tone;
reg [14:0] counter;
wire [ 6:0] ramp = (tone[22] ? tone[21:15] : ~tone[21:15]);
wire [14:0] clkdivider = {2'b01, ramp, 6'b000_000};
always@(posedge clk_i) begin
tone <= tone + 1;
end
always@(posedge clk_i)begin
if(counter == 0)begin
counter <= clkdivider;
end else begin
counter <= counter - 1;
end
end
always@(posedge clk_i)begin
if(counter == 0) begin
speaker_o <= ~ speaker_o;
end
end
endmodule
3.3 追击警报
在实现3.2警车的基础上,我们再实现一个追击警报,让警报声时快时慢,取tone[21:15]作为快音调,tone[24:18]作为慢音调,具体代码片段如下所示:
wire [ 6:0] fastsweep = (tone[22] ? tone[21:15] : ~tone[21:15]);
wire [ 6:0] slowsweep = (tone[25] ? tone[24:18] : ~tone[24:18]);
wire [14:0] clkdivider = {2'b01, (tone[27] ? slowsweep : fastsweep), 6'b000_000};
整体verilog代码如下所示:
module music(
input clk_i,
output reg speaker_o
);
reg [27:0] tone;
reg [14:0] counter;
wire [ 6:0] fastsweep = (tone[22] ? tone[21:15] : ~tone[21:15]);
wire [ 6:0] slowsweep = (tone[25] ? tone[24:18] : ~tone[24:18]);
wire [14:0] clkdivider = {2'b01, (tone[27] ? slowsweep : fastsweep), 6'b000_000};
always@(posedge clk_i) begin
tone <= tone + 1;
end
always@(posedge clk_i)begin
if(counter == 0)begin
counter <= clkdivider;
end else begin
counter <= counter - 1;
end
end
always@(posedge clk_i)begin
if(counter == 0) begin
speaker_o <= ~ speaker_o;
end
end
endmodule