1.设计规划及波形绘制
1.1 呼吸灯原理
"呼吸灯"是指led灯的亮灭跟呼吸一样,逐渐点亮和逐渐熄灭。
这里可以使用PWM原理来实现呼吸灯。
**PWM:**PWM控制就是对脉冲的宽度进行调制的技术,我们使用占空比来调节脉冲的宽度。
**占空比:**占空比是脉冲处于较高电压的时间占整个脉冲周期的百分比。比如下图分别是占空比为10%,30%,50%,90%占空比的图。

我们通过调节点亮和熄灭过程的占空比,就可以实现像呼吸一样的逐渐点亮和逐渐熄灭。如下图所示,以十个clk周期为一个脉冲周期,从完全熄灭到完全点亮和从完全点亮到完全熄灭都包含十个脉冲周期(T1~T10)。以从完全熄灭到完全点亮这个过程为例,在一个脉周期即一个T内,再分成十小份,T1时高电平占10%, T2高电平占20%,直到T10高电平占100%,这样就可以实现从完全熄灭到完全点亮。
PS:我这里用的板子是高电平点亮。

呼吸灯PWM原理实现
1.2 波形绘制及实验目标
实验目标:实现"呼吸灯"。"呼吸灯"是指led灯的亮灭跟呼吸一样,逐渐点亮和逐渐熄灭。
知道原理后,使得输出波形为上述原理的led输出波形。

模块框图
由于需要计数,则可用计数器实现。(完全熄灭到完全点亮和从完全点亮到完全熄灭两个过程总的clk数是一样的,以下就以完全熄灭到完全点亮这个过程为例。)
计数器cnt_1s: 完全熄灭到完全点亮和从完全点亮到完全熄灭两个过程都是1s。
计数器cnt_1ms: 一个T占1ms(为什么用ms,上面我们的原理图是为了方便理解,1s分成十个T,一个T则0.1s即0.01ms,为了使得呼吸的效果更好更加连贯,把1s分成1000个T,则一个T为1ms。)
计数器cnt_1us: 同样为了使得呼吸的效果更好更加连贯,把一个T再进一步分为1000份,每一份1us, 完全熄灭到完全点亮过程 则每一次逐渐拉高1us的高电平。
**!!!**这里有个点:上一次我们实现流水灯的时候说过,计数器计数到最大值清零且需要进行操作的时候,为了避免时序混乱,可以使用拉高一个周期的flag标志。但我们说过,不增加这个flag标志,直接判断最大值条件也可以实现同样的功能。而这里我们用到了三个计数器,如果要加就加三个对应的flag,比较麻烦,因此我们这里就不加三个flag了(下面使用的flag信号不是避免时序混乱,而是为了区分全熄灭到完全点亮和从完全点亮到完全熄灭两个过程)。

使用一个flag信号:来区分完全熄灭到完全点亮和从完全点亮到完全熄灭两个过程。
综上,信号波形图如下(注意这里没有严格按照一个周期固定几个长度来画,只是画了个大概,画了关键点:led高低电平成倍增加或减少,在对应计数器的最大值跳变等情况):

50MHZ的时钟(一周期为20ns),1us等于50个周期,1ms等于1000个1us,1s等于1000个1ms。
因此三个计数器对应的最大值分别为49,999和999。
2.代码编写
2.1rtl
module breath_led
#
(
parameter cnt_1us_Max = 6'd49,
parameter cnt_1ms_Max = 10'd999,
parameter cnt_1s_Max = 10'd999
)
(
input sys_clk ,
input rst_n ,
output reg led
);
reg [5:0] cnt_1us;
reg [9:0] cnt_1ms;
reg [9:0] cnt_1s;
reg flag;
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
cnt_1us <= 'b0;
else if(cnt_1us == cnt_1us_Max)
cnt_1us <= 'b0;
else
cnt_1us <= cnt_1us + 1;
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
cnt_1ms <= 'b0;
else if(cnt_1ms == cnt_1ms_Max && cnt_1us == cnt_1us_Max )
cnt_1ms <= 'b0;
else if(cnt_1us == cnt_1us_Max )
cnt_1ms <= cnt_1ms + 1;
else
cnt_1ms <= cnt_1ms;
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
cnt_1s <= 'b0;
else if(cnt_1s == cnt_1s_Max && cnt_1ms == cnt_1ms_Max && cnt_1us == cnt_1us_Max)
cnt_1s <= 'b0;
else if(cnt_1ms == cnt_1ms_Max && cnt_1us == cnt_1us_Max)
cnt_1s <= cnt_1s + 1;
else
cnt_1s <= cnt_1s;
end
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
flag <= 1'b0;
else if (cnt_1s == cnt_1s_Max && cnt_1ms == cnt_1ms_Max && cnt_1us == cnt_1us_Max)
flag <= ~flag;
else
flag <= flag;
end
//flag低电平时灯由灭到亮,高电平时由亮到灭。
always @(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
led <= 1'b0;
else if( (flag == 1'b0 && (cnt_1ms <= cnt_1s) ) || (flag == 1'b1 && (cnt_1ms > cnt_1s) ) )
led <= 1'b1;
else
led <= 1'b0;
end
endmodule
写代码时两个错误点:
1(绿色框):为什么要写else if(cnt_1ms == cnt_1ms_Max && cnt_1us == cnt_1us_Max ),else if(cnt_1s == cnt_1s_Max && cnt_1ms == cnt_1ms_Max && cnt_1us == cnt_1us_Max)要同时判断均到达两个计数器或三个计数器的最大值?
因为1.2节画的波形图是个大概,其实1~49真正含50个clk。

如果我们只写cnt_1ms == cnt_1ms_Max从下图可以看到,cnt_1ms有50个clk内都达到了最大值,则这个条件有可能在这50个clk内任一个上升沿跳变,(如图中 ①②③都有可能)。
但我们要的应该是在这50个clk内的最后一个上升沿的下一个周期跳变,即③的下一个周期,则加上cnt_1us == cnt_1us_Max这个条件就能满足。

2(红色框):按照波形图应该写cnt_1ns保持才对。
3: if···else if···语句块内是按顺序执行的,其中一个条件满足就不会在进行往下条件的判断,因此应该先判断清零,再+1,否则计数器会一直加下去达不到清零条件。

错误代码
2.2 tb
`timescale 1ns/1ns
module tb_breath_led();
reg sys_clk ;
reg rst_n ;
wire led ;
initial begin
sys_clk <= 1'b0;
rst_n <= 1'b0;
#22
rst_n <= 1'b1;
end
//50MHZ时钟
always #10 sys_clk <= ~sys_clk;
breath_led #(
.cnt_1us_Max (6'd49 ),
.cnt_1ms_Max (10'd999 ),
.cnt_1s_Max (10'd999 ))
u_breath_led(
.sys_clk (sys_clk ),
.rst_n (rst_n ),
.led (led )
);
endmodule
3 逻辑仿真及波形验证
与上面波形图一致。
(本贴仅是个人经验,如有侵权请联系我~)