文章目录
- 一、蜂鸣器简介
- 1.1 蜂鸣器分类
- 1.2 PWM
- 二、C4开发板原理图
- 三、如何产生不同的音调
- 四、代码实现及分析
- 五、总结
一、蜂鸣器简介
1.1 蜂鸣器分类
蜂鸣器一般分为有源蜂鸣器和无源蜂鸣器。二者的区别在于,有源蜂鸣器内部含有振动源和功放电路,只需上电便可发出鸣叫。而无源蜂鸣器内部不含振动源和功放电路,因此我们需要给予其PWM方波,才能驱动无源蜂鸣器正常工作。
1.2 PWM
PWM即脉冲脉宽调制,PWM的占空比是指在一个完整的时钟周期内,高电平所占时间占整个时钟周期的比例(50%占空比即高低电平各占一半时钟周期),为方便代码编写,本项目产生的PWM均为50%占空比。
二、C4开发板原理图
博主所用开发板为Cyclone Ⅳ开发板,开发板芯片为:EP4CE6F17C8,由开发板原理图可以看出,本开发板蜂鸣器低电平有效。
三、如何产生不同的音调
我们可以通过给予无源蜂鸣器不同频率的PWM方波信号从而实现不同的音调音符。
相关数据参考可见下图:
举个例子:博主所用开发板晶振为50MHz,而低音Do的频率为262,因此我们需要在一秒内产生50MHz/262次PWM方波信号,此时蜂鸣器变会发出低音Do(如有错误请指正,博主是这样理解的,乐理知识匮乏,请见谅)。
两只老虎的乐谱如下:
四、代码实现及分析
本次项目较为简单,仅有一个蜂鸣器模块。
源码分析:
- 本次项目首先需要一个音符计数器,用来存放歌曲中的音符数目,由乐谱可知两只老虎共含有34个音符,因此我们需要一个6位宽的计数器用来计数到34(后续发现好像乐谱中间隔比较大的是空拍?各位可以自行调整)
- 除此之外,本次项目需要一个节拍计数器,不过这方面也是乐理知识,博主不太懂,两只老虎好像是一秒四拍,所以我们需要设计一个250ms计数器用来记一拍(也就是一个音符播放的时间)
- 同时我们自然需要一个计数音符频率的计数器。由于不同音符的频率不尽相同,因此我们可以很自然地想到需要引入一个中间信号,通过case第几个音符,给予中间信号不同的频率值。
- 另外由于本项目生成的PWM均为50%占空比,因此需要一个中间信号duty,duty为音符频率的一半,音符频率计数器小于duty时蜂鸣器输出0,否则输出1.
module beep (
input wire clk ,
input wire rst_n ,
input wire beep_en ,
output reg beep //输出蜂鸣器
);
//内部参数定义
parameter CYCLE = 26'd50_000_000 ;
parameter TIME = 24'd12_500_000 ;
parameter NUM = 6'd34 ;
parameter DOL = CYCLE/262 ,
REL = CYCLE/294 ,
MIL = CYCLE/330 ,
FAL = CYCLE/349 ,
SOL = CYCLE/392 ,
LAL = CYCLE/440 ,
XIL = CYCLE/494 ,
DOM = CYCLE/523 ,
REM = CYCLE/587 ,
MIM = CYCLE/659 ,
FAM = CYCLE/698 ,
SOM = CYCLE/784 ,
LAM = CYCLE/880 ,
XIM = CYCLE/988 ,
DOH = CYCLE/1047 ,
REH = CYCLE/1175 ,
MIH = CYCLE/1319 ,
FAH = CYCLE/1397 ,
SOH = CYCLE/1568 ,
LAH = CYCLE/1760 ,
XIH = CYCLE/1967 ;
//内部信号定义
reg [5:0] cnt_num ;//音符个数寄存器
wire add_cnt_num ;
wire end_cnt_num ;
reg [23:0] cnt_250 ;//一拍时间寄存器
wire add_cnt_250 ;
wire end_cnt_250 ;
reg [17:0] cnt_frq ;//音符频率寄存器
wire add_cnt_frq ;
wire end_cnt_frq ;
reg [17:0] frq ;//中间信号,存储音符频率
wire [16:0] duty ;//中间信号,用于比较产生50%PWM
//250ms计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_250 <= 1'b0;
end
else if(add_cnt_250)begin
if(end_cnt_250)begin
cnt_250 <= 1'b0;
end
else begin
cnt_250 <= cnt_250 + 1'b1;
end
end
else begin
cnt_250 <= cnt_250;
end
end
assign add_cnt_250 = beep_en;
assign end_cnt_250 = add_cnt_250 && cnt_250 == TIME - 1'b1;
//音符个数寄存器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_num <= 1'b0;
end
else if(add_cnt_num)begin
if(end_cnt_num)begin
cnt_num <= 1'b0;
end
else begin
cnt_num <= cnt_num + 1'b1;
end
end
else begin
cnt_num <= cnt_num;
end
end
assign add_cnt_num = end_cnt_250;
assign end_cnt_num = add_cnt_num && cnt_num == NUM - 1'b1;
//音符频率计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_frq <= 1'b0;
end
else if(add_cnt_frq)begin
if(end_cnt_frq || end_cnt_250)begin
cnt_frq <= 1'b0;
end
else begin
cnt_frq <= cnt_frq + 1'b1;
end
end
else begin
cnt_frq <= cnt_frq;
end
end
assign add_cnt_frq = beep_en;
assign end_cnt_frq = add_cnt_frq && cnt_frq == frq - 1'b1;
//音频赋值
always@(*)begin
case(cnt_num)
6'd0 : frq = DOM;
6'd1 : frq = REM;
6'd2 : frq = MIM;
6'd3 : frq = DOM;
6'd4 : frq = DOM;
6'd5 : frq = REM;
6'd6 : frq = MIM;
6'd7 : frq = DOM;
6'd8 : frq = MIM;
6'd9 : frq = FAM;
6'd10 : frq = SOM;
6'd11 : frq = MIM;
6'd12 : frq = FAM;
6'd13 : frq = SOM;
6'd14 : frq = SOM;
6'd15 : frq = LAM;
6'd16 : frq = SOM;
6'd17 : frq = FAM;
6'd18 : frq = MIM;
6'd19 : frq = DOM;
6'd20 : frq = SOM;
6'd21 : frq = LAM;
6'd22 : frq = SOM;
6'd23 : frq = FAM;
6'd24 : frq = MIM;
6'd25 : frq = DOM;
6'd26 : frq = REM;
6'd27 : frq = SOL;
6'd28 : frq = DOM;
6'd29 : frq = 0 ;
6'd30 : frq = REM;
6'd31 : frq = SOL;
6'd32 : frq = DOM;
6'd33 : frq = 0 ;
default : frq = 0 ;
endcase
end
//beep输出赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
beep <= 1'b1;
end
else if(cnt_frq < duty && beep_en)begin
beep <= 1'b0;
end
else begin
beep <= 1'b1;
end
end
//生成占空比为50%的音频PWM方波的比较信号
assign duty = frq >> 1;
endmodule
五、总结
本项目较为简单,基本是在做计数器的练习和PWM方波信号产生练习,希望大家能够掌握。
如有没有讲清楚的地方还请大家指正。