呼吸灯 verilog FPGA 基础练习8
发现问题,用技术解决问题。兴趣是自己的源动力 !
前言
呼吸灯的练习的主要目的是对计数器使用的进阶,理解计数器计数使用的基础时间单位的变化,对计数器的影响。
一、呼吸灯
1.1 呼吸灯原理
我们知道同一时间段内,如果供给led灯一个脉冲信号的低电平持续的时间越长(高电平持续的时间越短)led灯就越亮,我们就是通过调整PWM实现高低电平的占空来调控led灯的亮度,我们取n个相同的时间段,然后让低电平的持续时间按照相等的时间间隔逐渐增多,这样子我们看上去 的led灯就会越来越亮了。
总的来说就是控制亮和灭的占空比
1.2 实现方案
实现呼吸灯的主要思想和步骤:
1.呼吸灯的原理:亮灭占空比的变化。假设低电平为亮,那么低电平的占空比就会不断升高
2.设定灭到亮、亮到灭分别占用1秒,那么如何调整这一秒中的亮灭占空比
3.假设:想实现灭到亮,那么就会有不同情况的占空比
4.要设置多少个不同情况的占空比呢?假定设置1000个不同情况的占空比(对于1s来说,就是1000ms)
5.有了1000个占空比的情况,接下来就去设置每一个占空比的具体占空比数值
6.此时,从1ms看(1000个占空比中的一个),假设1ms是一个时钟周期
(这里要理解占空比,占空比主要用于描述脉冲信号中高电平(或低电平)持续时间与整个周期时间的比例)
7.低电平的占空比逐渐增大。
8.这是就需要考虑去计数一个比1ms还要小的时间。本设计中用1us表示(也可以用其他数值)
小结:要实现以上,那么就要考虑到如何设计计数器和如何使用计数器了
实现波形图如下:
1.2.1 功能代码
注意:下面代码中的计数器1s和1ms的理解,会在1.2.4给出。
module breath_led
#(
parameter CNT_1US_MAX = 6'd49 ,
parameter CNT_1MS_MAX = 10'd999 ,
parameter CNT_1S_MAX = 10'd999
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
output reg led_out //输出信号,控制led灯
);
// *******************************************
// 实现呼吸灯的主要思想和步骤:
// 1.呼吸灯的原理:亮灭占空比的变化。假设低电平为亮,那么低电平的占空比就会不断升高
// 2.设定灭到亮、亮到灭分别占用1秒,那么如何调整这一秒中的亮灭占空比
// 3.假设:想实现灭到亮,那么就会有不同情况的占空比
// 4.要设置多少个不同情况的占空比呢?假定设置1000个不同情况的占空比(对于1s来说,就是1000ms)
// 5.有了1000个占空比的情况,接下来就去设置每一个占空比的具体占空比数值
// 6.此时,从1ms看(1000个占空比中的一个),假设1ms是一个时钟周期
// (这里要理解占空比,占空比主要用于描述脉冲信号中高电平(或低电平)持续时间与整个周期时间的比例)
// 7.低电平的占空比逐渐增大。
// 8.这是就需要考虑去计数一个比1ms还要小的时间。本设计中用1us表示(也可以用其他数值)
// 小结:要实现以上,那么就要考虑到如何设计计数器和如何使用计数器了
// *******************************************
// 实现1s的计数器,注意理解cnt_1s,这个不是代表该值为2就是2s,而是表示达到1s的过程和达到1s为目的,1s中包含的ms值
reg [9:0]cnt_1s;
reg [9:0]cnt_1ms;
reg [6:0]cnt_1us;
reg led_en;
// 另外需要理解时间怎么计数到1us:
// 系统时钟是50MHZ,也就是说50_000_000次对上升沿的计数就是1s,那么1us就是50次时钟上升沿的计数
// 为了将1s分成1000个不同占空比的情况,因此计数器递增间隔为1ms
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_1s <= 10'b0;
else if(cnt_1s == CNT_1S_MAX & cnt_1ms == CNT_1MS_MAX & cnt_1us == CNT_1US_MAX )
cnt_1s <= 10'b0;
else if(cnt_1ms == CNT_1US_MAX & cnt_1us == CNT_1US_MAX)
cnt_1s <= cnt_1s +10'b1;
end
// 考虑1ms内的占空比变化,如果将1ms分成1000份,就是1us,那么也是递增1us
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_1ms <= 10'b0;
else if(cnt_1ms == CNT_1MS_MAX & cnt_1us == CNT_1US_MAX) // 考虑归0,这里思考cnt_1ms刚刚等于10'd999就是达到1ms了吗?
cnt_1ms <= 10'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1ms <= cnt_1ms +10'b1;
else
cnt_1ms <= cnt_1ms;
end
// 计数1us,用于计数的基础时间单位
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_1us <= 10'b0;
else if(cnt_1us == CNT_1US_MAX) // 考虑归0
cnt_1us <= 10'b0;
else
cnt_1us <= cnt_1us + 6'b1;
end
// 设定 亮到灭和灭到亮的使能
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
led_en <= 1'b0; // 0是灭到亮
else if(cnt_1s == CNT_1S_MAX & cnt_1ms == CNT_1MS_MAX & cnt_1us == CNT_1US_MAX ) // 一秒切换一次
led_en <= ~led_en;
else
led_en <= led_en;
end
// 通过计数器来控制灯的亮灭占空比
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
led_out <= 1'b1; // 低是亮
else if( (led_en == 1'b0 & cnt_1ms <= cnt_1s) || (led_en == 1'b1 & cnt_1ms > cnt_1s) ) // 由ms控制us,达到亮的时间
led_out <= 1'b0;
else
led_out <= 1'b1;
end
endmodule
1.2.2 仿真代码
`timescale 1ns/1ns
module tb_top();
//\* Parameter and Internal Signal \//
//wire define
wire led_out ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
///
//\* Main Code \//
//初始化系统时钟、全局复位
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always #10 sys_clk = ~sys_clk;
//\* Instantiation \//
//-------------------- breath_led_inst --------------------
breath_led
#(
.CNT_1US_MAX(6'd4 ),
.CNT_1MS_MAX(10'd9 ),
.CNT_1S_MAX (10'd9 )
)
breath_led_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.led_out (led_out ) //output led_out
);
endmodule
1.2.3 仿真结果
1.2.4 计数器的基本时间单位
下面代码中,为了计数1us,计数器的基本时间单位是一个时钟周期。当一个时钟clk的上升沿采集到cnt_1us == CNT_1US_MAX,那么就是1us。
这里需要理解,如果以一个clk的一个时钟周期作为1us的计数单位,那么下面的时间是不可再分的)。如果没有理解,再往下看。
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_1us <= 10'b0;
else if(cnt_1us == CNT_1US_MAX) // 考虑归0
cnt_1us <= 10'b0;
else
cnt_1us <= cnt_1us + 6'b1;
end
下面代码中,为了计数1ms,使用的基本时间单位是1us。很多人以为(包括我),只要cnt_1ms == 10’d999,就是1ms了,其实不是。计数的最基本的时间单位看,计数器的基本时间单位是一个时钟周期。如果当前cnt_1ms == 10’d999,clk的上升沿采样到cnt_1ms == 10’d999,你可以思考以下,这个第10’d999ms的时间,真的完全过去了吗?
注意:时间是一个过程量,之所以在计数cnt_1us的时候,直接用clk上升沿采样到了当前值,是因为我们使用的是一个clk周期就是最基本的时间单位,当采样到了,那么采样到的这个数值对应的时间已经完成了。
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_1ms <= 10'b0;
else if(cnt_1ms == CNT_1MS_MAX & cnt_1us == CNT_1US_MAX) // 考虑归0,这里思考cnt_1ms刚刚等于10'd999就是达到1ms了吗?
cnt_1ms <= 10'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1ms <= cnt_1ms +10'b1;
else
cnt_1ms <= cnt_1ms;
end
可以结合时钟来理解
对于1s,其基本时间单位是ms,ms的基本时间单位是us(us最基本的时间单位了),所以当清零或自加时要考虑下面的基本时间单位。
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_1s <= 10'b0;
else if(cnt_1s == CNT_1S_MAX & cnt_1ms == CNT_1MS_MAX & cnt_1us == CNT_1US_MAX )
cnt_1s <= 10'b0;
else if(cnt_1ms == CNT_1US_MAX & cnt_1us == CNT_1US_MAX)
cnt_1s <= cnt_1s +10'b1;
end
小结:计数器的计数,对于清零和自增,需要考虑一层一层基本时间单位的划分,要找到最小的那个基本时间单位
总结
- 核心思想:要理解计数的基本时间单位是什么,明白时间的过程量的性质,是否真正达到这个时间了吗?
- 知识点总结:
- 如何实现1us、1ms、1s?
- 基本时间单位是什么,会对计数器有哪些影响
- 呼吸灯产生的原理是什么?
- 欢迎一起交流学习,如有错误之处,还请各位指正。
参考资料
[1] FPGA系列教学