本次实验我们实现PWM驱动一个蜂鸣器,通过按键改变PWM的占空比,改变蜂鸣器的音调。
首先我们来了解一下PWM的原理,用一个N位的计数器,最大值可表示成2的N次方,最小值为0,计数器以“period”为步进值进行累加,加到最大值之后重新进行下一次的增加。当计数值大于“duty”值的时候,脉冲输出为低,否则输出为高,这样就可以实现脉冲占空比可调的PWM可视,同时“period”可以调节脉冲频率。示意图如下。
我们来分析一下这PWM输出模块,该模块输入为“period”和“duty”。首先我们给计数器period_cnt设置一个位数,本实验中直接设置为32位,PWM的频率为100HZ,时钟频率为50_000_000HZ,那么每Hz计数为50_000_000/100=500_000,每hz计数为2^32=4294967296,那么每一次需要增加的数为4294967296/500_000=8590,即period的值为8590,当计数器period_cnt大于duty值时,输出为1,否则输出为0即可。相应的我们写出以下代码
/*
Author:Alawyssun
Time:2020/03/27
实现功能:输入PWM的周期和占空比 输出PWM波
同时可以接收一个采样率的参数本次试验默认为32位
*/
module ax_pwm
#(
parameter N = 32
)
(
input clk,
input rst,
input[N - 1:0]period,//每一个step的值 比如100hz
//那么每一个周期的长度为50_000_000/100=500_000
//即每一个周期计数到500000为止 但我们计数器是32位 也就是将500000分成2^32份。
//用2^32/500000即为每一个时钟周期对应的 计数值
//500000/2^32为每一个计数值对应的 时间
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;//计数器 相当每次在上一次值的基础上增加一个period的值,当该值大于duty输出为低(即蜂鸣器输出为高电平)
reg pwm_r;
assign pwm_out = pwm_r;
always@(posedge clk or posedge rst)
begin
if(rst==1)
begin
period_r <= { N {1'b0} };
duty_r <= { N {1'b0} };
end
else
begin
period_r <= period;
duty_r <= duty;
end
end
always@(posedge clk or posedge rst)
begin
if(rst==1)
period_cnt <= { N {1'b0} };
else
period_cnt <= period_cnt + period_r;
end
always@(posedge clk or posedge rst)
begin
if(rst==1)
begin
pwm_r <= 1'b0;
end
else
begin
if(period_cnt >= duty_r)
pwm_r <= 1'b1;
else
pwm_r <= 1'b0;
end
end
endmodule
再分析一下主模块, 我们本次实验设置buzzer响铃时间为250ms,当我们检测到按键按下我们将duty改变,同时开启响铃时间计时器,将period和duty传入到PWM模块中,当计时器到达250ms,停止发声,为了达到该目的,我们将PWM输出与开启响铃时间计时器的状态相与即可。相应的代码如下:
/*
Author:Alawyssun
Time:2020/03/27
采样率:32位
实现功能:实现蜂鸣器输出一定频率的PWM波
并通过按键实现占空比的切换 每次实现响250ms
占空比每次增加2^32/10
*/
module buzzer_pwm_test(
input clk,
input rst_n,
input key1,
output buzzer
);
parameter IDLE = 0;
parameter BUZZER = 1;
wire button_negedge;
wire pwm_out;
reg[31:0] period;
reg[31:0] duty;
reg[3:0] state;
reg[31:0] timer;
assign buzzer = ~(pwm_out & (state == BUZZER));//当输出为高电平 同时是响铃状态的时候响铃
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
period <= 32'd0;
timer <= 32'd0;
duty <= 32'd429496729;
state <= IDLE;
end
else
case(state)
IDLE:
begin
if(button_negedge)
begin
period <= 32'd8590; // 2^32/(50_000_000/100)
state <= BUZZER;
duty <= duty + 429496729;//2^32=4294967296
end
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
default:
begin
state <= IDLE;
end
endcase
end
key_scan key_scan_my
(
.clk (clk),
.rst (rst_n),
.key1 (key1),
.button_out_reg (button_negedge),
);
ax_pwm ax_pwm_m0(
.clk (clk),
.rst (~rst_n),
.period (period),
.duty (duty),
.pwm_out (pwm_out)
);
endmodule
按键模块仍然保持上次的实验代码即可。
本次实验结果为按下按键,蜂鸣器响250ms,同时随着按键次数不同,蜂鸣器的频率不断改变。
本实验的工程文件下载地址为: