一.原理图
由蜂鸣器的原理图可看出,当I/O口(C11)输出低电平时蜂鸣器响!
二.代码和注释
1.key_debounce.v
按键消抖代码,前面介绍过了,此处不再说明。
module key_debounce(
input sys_clk, //外部50M时钟
input sys_rst_n, //外部复位信号,低有效
input [2:0] key, //外部按键输入,按下后为低电平
output reg key_flag, //按键数据有效信号,即表示延时结束,按键已稳定
output reg [2:0] key_value //按键消抖后的数据
);
//reg define
reg [19:0] delay_cnt; //消抖延时的计数器
reg [2:0] key_reg; //按键值存储
//*****************************************************
//** main code
//*****************************************************
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
begin
key_reg <= 3'b111; //按键值复位,全为高电平
delay_cnt <= 19'd0; //计数器清零
end
else
begin
key_reg <= key; //非阻塞赋值,因此下行if判断中的key_reg仍为前一次的数据,而非此次的key
if(key_reg != key) //一旦检测到按键状态发生变化(有按键被按下或释放)
delay_cnt <= 19'd1_000_000; //给延时计数器重新装载初始值(计数时间为20ms)
else if(key_reg == key)
begin //在按键状态稳定时,计数器递减,开始20ms倒计时
if(delay_cnt > 19'd0)
delay_cnt <= delay_cnt - 1'b1;
else
delay_cnt <= delay_cnt;
end
end
end
always @(posedge sys_clk or negedge sys_rst_n)
begin
if (!sys_rst_n)
begin
key_flag <= 1'b0;
key_value <= 3'b111;
end
else
begin
if(delay_cnt == 19'd1) //减到1而不是0的原因是:复位情况和无按键按下时cnt恒为零,则key_flag会一直为1
begin //同时,当计数器递减到1时,说明按键稳定状态维持了20ms
key_flag <= 1'b1; //此时消抖过程结束,给出一个时钟周期的标志信号
key_value <= key; //并寄存此时按键的值
end
else
begin
key_flag <= 1'b0; //延时未到,不给出有效信号
key_value <= key_value;
end
end
end
endmodule
2.buzzer_pwm.v
module buzzer_pwm
#(
parameter N=16 //可给外部调用的常数参数,即在实例化时可传递参数
)
(
input sys_clk,
input sys_rst_n,
input [N-1:0] period, //用于控制计数的速度,从而控制PWM的频率
input [N-1:0] duty, //用于控制比较值,从而控制脉宽(占空比)
output pwm_out
);
reg [N-1:0] period_r;
reg [N-1:0] duty_r;
reg [N-1:0] period_cnt; //PWM计数器
reg pwm_r;
assign pwm_out=pwm_r;
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n==0)
begin
period_r<={ N{1'b0} };
duty_r<={ N{1'b0} };
end
else
begin
period_r<=period; //实时更新PWM频率
duty_r<=duty; //实时更新PWM脉宽
end
end
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n==0)
period_cnt<={ N{1'b0} };
else
period_cnt<=period_cnt+period_r; //PWM计数器,每个时钟上升沿以设定速度(period_r)计数
end
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n==0)
begin
pwm_r<=1'b0;
end
else
begin
if(period_cnt>=duty_r) //输出PWM
pwm_r<=1'b1; //计数器数值大于比较值时输出高电平
else
pwm_r<=1'b0; //计数器数值小于比较值时输出低电平
end
end
endmodule
此图即为PWM产生的原理,通过一个32位的计数器与一个32位的设定值进行比较。当计数器的值小于比较值时,输出高电平;当计数器的值大于比较值时,输出低电平,从而产生PWM波。
计数器的计数速度period_r可以自行设定。
通过上图不难发现:
1.比较值duty越大,高电平持续时间越长,即占空比越大。因此控制比较值的大小就可以控制占空比,从而控制蜂鸣器的音量。
2.由于计数器在每个时钟周期(50MHz->0.02ns)增加period_r。当计数器的位数固定时,计数速度period_r越大,PWM的周期越小,频率越大。因此控制计数速度period_r的大小可以控制PWM频率,从而控制蜂鸣器的音调。
3.key_buzzer_test.v
此代码时实验现象是:按键按下后,蜂鸣器会对应响250ms,KEY1改变蜂鸣器音量,KEY2改变蜂鸣器音调。
module key_buzzer_test
(
input sys_clk,
input sys_rst_n,
input [2:0] key,
output buzzer
);
parameter IDLE=0; //蜂鸣器关闭状态
parameter BUZZER=1; //蜂鸣器打开状态
wire [2:0] key_value; //按键值
wire key_flag; //按键有效标志
wire pwm_out;
reg [31:0] period; //PWM计数速度
reg [31:0] duty; //比较值
reg[31:0] timer; //250ms延时定时器
reg state;
assign buzzer=~(pwm_out&(state==BUZZER)); //PWM为高电平且蜂鸣器处于打开状态时为蜂鸣器的I/O口输出PWM
always @(posedge sys_clk or negedge sys_rst_n)
begin
if(sys_rst_n==0)
begin
period<=32'd8590;
timer<=32'd0;
duty<=32'd429496729;
state<=IDLE;
end
else
begin
case(state)
IDLE:
begin
if(key_flag&&key_value[0]==0) //KEY1控制音量(PWM脉宽)
begin
period<=32'd8590; //PWM计数速度固定
state<=BUZZER; //打开蜂鸣器
duty<=duty+32'd429496729; //比较值增加,
end
else if(key_flag&&key_value[1]==0) //KEY2控制音调(PWM频率)
begin
period<=period+32'd17180; //PWM计数速度增加,PWM频率增加
state<=BUZZER;
duty<=32'd429496729; //比较值固定
end
else;
end
BUZZER:
begin
if(timer >= 32'd12_499_999) //计时250ms后关闭蜂鸣器
begin
state<=IDLE;
timer<=32'd0;
end
else
begin
timer<=timer+32'd1;
end
end
endcase
end
end
key_debounce u_key_debounce
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key (key),
.key_flag (key_flag),
.key_value (key_value),
);
buzzer_pwm#
(
.N(32) //为模块的常量参数进行参数传递
)
u_buzzer_pwm
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.period (period),
.duty (duty),
.pwm_out (pwm_out)
);
endmodule