赞
踩
那么在上一章节当中,我们使用板载的 LED 灯设计并实现了一个流水灯的实验效果。那么在本章节当中,我们将带领各位朋友设计并实现一个呼吸灯的实验效果。
那么呼吸灯在我们的日常生活中很常见,比如说我们手机上。在手机上一般作为消息提醒指示灯被广泛地使用,那么它的效果是小灯在一段时间内从完全熄灭慢慢到完全点亮,同样的时间内再由完全点亮到完全熄灭这样一个过程;这个过程循环往复,感觉就像呼吸一样一张一弛,那么人们看了特别舒服。
说到这里,有的朋友肯定会说:那么呼吸灯应该怎么实现呢?
那么下面我们就通过实验工程来讲解一下呼吸灯的实现方法。
使用板载 LED 灯 D6 实现呼吸灯效果,第一个 1s 内 LED 由熄灭慢慢点亮直到最亮,第二个 1s 内 LED 由最亮的状态慢慢变暗直到熄灭,如此循环往复。
我们让开发板板载的 D6 LED 实现呼吸灯
那么首先,先来搭建一个文件体系
然后打开我们的 doc 文件夹,建立一个 Visio 文件用来绘制我们的框图和波形图
那么首先是框图的绘制
然后是端口信号。首先是输入信号,那么输入信号一定有时钟信号和复位信号,因为后面我们要用到我们的计数器
那么输入信号除了我们的时钟信号和复位信号之外,再没有其他的输入信号了。
下面就是输出信号。那么输出信号,要有一路输出信号连接到我们的板载的 LED 灯,只有这样才能进行实验效果的检验
那么到了这里,我们的模块框图绘制完成。
下面可以开始波形图的绘制。
在波形图的绘制之前,我们要进行一下分析,分析一下呼吸灯这个效果应该怎么实现。
首先,我们可以将呼吸灯的呼吸效果分为两个过程:第一个过程是由完全熄灭到完全点亮,然后第二个过程是完全点亮到完全熄灭。其实这两个过程可以看作一个过程,因为如果是完全点亮到完全熄灭,它就是完全熄灭到完全点亮的一个逆过程。
那么下面就要考虑:怎样实现完全熄灭到完全点亮?
各位学员如果有电路常识就应该知道:如果说在一个安全范围内给我们的 LED 灯供的电压越大,灯越亮。难道这表示我们要控制电压的高低吗?
显然这是很不现实的。所以说我们要采用另外一种方法,通过控制 PWM 的占空比来控制我们灯的亮灭程度。
那么接下来就引入了一个新问题:如何使用 PWM 来实现呼吸灯的一个效果?下面我们结合图形来讲解一下实现的方法。
那么经过前面学习我们已经知道:我们开发板板载的 LED 灯它是低电平点亮,高电平是熄灭状态。那么初始状态既然是完全熄灭,那么我们就给它一段时间的高电平,那么这段时间我们定义为一个周期,用 T 来表示它
在第一个 T 周期内给我们的输出信号是一个高电平,那么这样就对应完全熄灭。那么接下来在第二个周期内,让我们的信号保持十分之一个周期的低电平;这就表示在第一个周期内,我们的灯是完全熄灭,第二个周期内,前面点亮了一点点,后面又处于熄灭状态
那么在第三个周期内让它保持十分之二个周期的低电平,就比上一次增加了一倍;然后剩下的十分之八个周期都是高电平
那么第三个周期内也点亮了一点点,后面又是熄灭;但点亮的这个时间是前一个 T 周期的两倍。
那么接下来在第四个周期内再延长十分之一个低电平保持时间
那么这样每个周期增加十分之一 T 个低电平的保持时间,那么到了后面也就是第十个周期,它就变成什么样子呢?我们来看一下
第十个周期应该是保持了十分之九个周期的低电平,然后高电平只保持了十分之一个周期。
到了第十一个周期,它就应该是完全的一个周期的低电平
那么这样就实现了我们的 LED 灯从一个完全熄灭的状态到一个完全点亮的状态,我们通过 PWM 这个方法来实现的。那么完全点亮到完全熄灭是它的一个逆过程。第一个周期内也是让它保持全为低电平,然后在下一个周期,首先让它保持十分之一个周期的高电平,然后是十分之九个周期的低电平。然后中间我们就省略了
那么最后保持一个周期的高电平
那么这样,这个波形就展示出了我们的呼吸灯的一个效果:由完全熄灭到完全点亮,再由完全点亮一步步到完全熄灭。
那么这样我们就通过 PWM 展示了一个完整的一个呼吸灯的一个效果,那么这个波形其实就是我们最后要输出的一个波形。那么后面我们就需要考虑:如何通过输入的时钟信号和复位信号来产生这个波形。
下面开始波形图的绘制。那么首先是我们的输入信号,输入的信号有时钟和复位,那么输入信号要填充为绿色;然后是复位信号,复位信号上电后保持一段时间的低电平,起到复位作用,然后让它保持一段时间的高电平,这个高电平是一直保持,这样系统才能够正常工作
那么下面就要考虑:如何使用输入信号这两个波形产生我们的输出信号这个波形。
第一点我们要考虑到从完全熄灭状态到完全点亮状态,就是 ❶ 到 ❷ 它的时间是多少;知道了这个时间,❷ 到 ❸ 这个时间就确定了,我们让这两个时间相等就可以了
我们这儿把 ❶ 到 ❷ 这个时间定义为 1s(或者说 ❷ 到 ❸ 这个时间段也是 1s)
那么既然是时间,肯定需要我们的计数器。我们就先声明一个 1s 的计数器
那么这个完全点亮到完全熄灭这个时间是 1s 我们确定了,那么确定了这个时间之后,就可以确定周期 T 是多长时间。那么怎么确定呢?
那么 ❶ 到 ❷ 这段时间有多少个 T 我们做个除法就可以确定了,我们这儿把 1s 分成 1000 份,那么每个 T 就是 1ms。那么为什么要分成 1000 份呢?因为这个份数分的越大,我们的呼吸效果就越细腻,就看起来越舒服
那么这个时间 T 确定之后,我们就要确定一下每个周期增加的低电平保持时间。那么有的朋友肯定会说:你这儿不是增加了十分之一 T 吗?那就是十分之一 ms 啊
那么这里我们不以这个时间长度来进行增加,我们把我们的 T 分成 1000 份,那么每次增加一小份儿。为什么这样呢?同样也是为了让我们的呼吸效果更加的细腻
那么 1ms 的千分之一就是 1us
那么接下来我们就可以通过声明的这三个计数器变量来实现我们输出的波形。
下面开始它们波形的绘制,那么首先我们先绘制 1us 计数器的波形。为什么先绘制它呢?
因为它们三个计数器之间有一个倍数的关系:那么 1ms 是 1us 的 1000 倍,那么 1s 是 1ms 的 1000 倍。也就是说,cnt_1us 记一个周期 cnt_1ms 自加一,cnt_1ms 自加 1000 次就是 1ms;那么 cnt_1ms 记一个周期我们的 cnt_1s 自加一,那么 cnt_1s 自加 1000 次,计数就是 1s。为什么要这样做呢?
这样可以节省我们的逻辑资源,什么意思呢?如果说都使用时钟信号来进行计数,那么 cnt_1s 的计数值会很大,是最大的,cnt_1ms 是第二大,cnt_1us 是第三大。
那么现在如果说 cnt_1ms 针对 cnt_1us 的周期进行计数,那么 cnt_1s 针对 cnt_1ms 的周期进行计数;那么 cnt_1s、cnt_1ms 它们两个的计数值最大值就是 999 那么这样会节省很多的逻辑资源。
那么首先我们来绘制 cnt_1us 的波形。首先给它一个初值,初值是 0;然后我们的 1us 计数器每个时钟周期自加一,最大值是多少呢?我们的系统时钟频率是 50MHz 那么一个时钟周期就是 20ns 那么 1us 就是 50 个时钟周期。那么最大值就是 49,因为是从 0 开始计数;所以说最大值是 49 我们这儿采用一个省略的画法,因为波形太长了;然后计数到最大值进行清零,清零之后开始下一个周期的计数
那么这样 1us 计数器的波形我们绘制完成。
下面开始 1ms 计数器的波形绘制。那么它的初值同样是 0 当我们的 1us 计数器完成一个周期的计数它自加一,那么最大值是 999,计数了 1000 次;那么当其计数到最大值,归零;然后每个时钟周期自加一,然后开始下一个周期的计数,我们这儿采用一个省略线,要不然太长了,我们画不开,那么最大值是 999 前面是 998 到这儿是最大值 999 然后归零
那么这样 1ms 的波形绘制完成。
下面开始 1s 的波形绘制,那么它的初值也设为 0 当我们的 1ms 计数器完成一个周期的计数,我们的 1s 计数器加一;然后这儿就是 2 那么这儿同样省略一下,因为太长了我们画不开,这儿就是 998 这儿就是 999 那么到后面这儿就归零了
到了这里,三个计数器的波形就绘制完成。
接下来就考虑输出信号的波形绘制,那么输出信号的波形和这个差不多
它的初值为高电平,就是熄灭。那么输出信号的初值赋值完成之后,这儿引入了一个新的问题:在每一个时间间隔 T 内,怎么让低电平保持的时间成倍的增长,这儿要怎么实现呢?我们来观察一下我们的波形图,那么经过观察我们发现,我们找到了一个条件,什么条件呢?当我们的 cnt_1ms 它的计数值小于等于我们的 cnt_1s 计数值,让输出信号保持低电平,那么其他状态保持高电平,就可以实现我们想要的效果。什么意思呢?我们来看一下
我们的输出信号初值是高电平,就是熄灭状态(完全熄灭);当我们的 cnt_1ms 它的计数值小于等于 1s 计数器的时候,让输出信号保持低电平。那么在这个周期内就这样:我们的 cnt_1ms 计数值为 0 那么 cnt_1s 的计数值也是为 0 那么 cnt_1ms 的计数值小于等于 cnt_1s 的计数值,那么这个时钟周期保持一个低电平;在这个 T 周期内其他时刻为高电平
那么就到这个位置。那么在下一个周期内,我们 cnt_1s 的计数值为 1 那么 cnt_1ms 的计数值为 0 或 1 时,小于等于我们 cnt_1s 的计数值,那么这两个时钟周期保持低电平,那么其他时刻保持高电平
那么按照这个规律,我们继续波形图的绘制。1s 内的最后一个 T 周期,我们的输出信号就已经变为了完全的低电平
那么这个过程就实现了完全熄灭到完全点亮的一个状态,对这个过程进行一个取反就能得到我们的 LED 灯由完全点亮到完全熄灭这么一个状态,我们来绘制一下
那么这样整个实验工程的完整的一个波形图已经绘制完成了,那么下面可以开始代码的编写
那么首先是模块开始、模块名称、端口列表,然后是模块结束;然后输入信号有两路:时钟信号和复位信号,然后是输出信号
那么这样,端口列表编写完成。
然后声明三个变量,就是我们三个计数器的变量。那么前面已经提到了,1us 计数器它的计数最大值是 49,我们来看一下它需要多少位宽,这儿需要 6 位宽,也就是 [5:0];我们 1ms 和 1s 的计数器最大值都是 999 它们需要 10 个位宽
那么下面开始变量的赋值,我们使用 always 语句,使用异步复位;当我们的复位信号有效时给 1us 的计数器赋一个初值,初值是 0 当它计数到最大值 49 时让它归零,那么写到这里,我们在这儿增加几个参数的定义
我们这儿定义了三个参数,分别是三个计数器的最大值;那么目的是方便代码的编写、实例化以及仿真。
当我们 1us 计数器计数到最大值,也就是 CNT_1US_MAX 这个值,我们就让它归零;当上面所有的条件都不满足时,也就是说其他的位置那么让它不断的自加一
那么这样 1us 计数器的代码我们参照着它的波形已经编写完成了。
下面开始 1ms 计数器的代码编写,同样使用 always 语句。然后给它一个初值,初值一样是 0 和这个位置是对应的;那么初值赋值完成之后,下面是它的归零条件,它在什么时候归零呢?
有的学员肯定会说:计到最大值 999 就归零了,不完全对。它要计数到最大值 999 就是这个位置,而且要等到我们 1us 计数器也计数到最大值 49 时,才能进行归零,只有这两个条件都满足时才能进行清零,这儿大家一定要注意:那么只有这两个条件都满足时,才能对这个计数器进行清零
那么下面就是它的计数条件,它的计数条件是什么呢?
当我们 1us 的计数器计数到最大值 49 也就是我们的 1us 计数器完成了 1us 的计数,我们的 1ms 计数器才能自加一,那么其他时刻就保持原来的值不变
那么下面就是我们 1s 计数器的赋值,我们同样使用 always 语句;然后它的初值同样是 0,下面是它的清零条件,它什么时候进行清零呢?
那么它的清零条件是最复杂的,它必须要等到它自己计数到最大值 999 而且要等到 1ms 计数器同时计数到最大值 999,1us 计数器也要计数到最大值 49 才能进行清零;只有满足这三个条件,才能对它进行清零;如果上述条件都不满足,那么其他时刻它就保持原来的值不变
到这里,三个计数器变量的代码已经编写完成。代码编写到这儿我们发现一个问题,什么问题呢?我们回到我们的波形图
我们这里发现一个问题:我们的呼吸灯它的呼吸效果是由两个过程组成,第一个过程是完全熄灭到完全点亮、第二个过程是完全点亮到完全熄灭,那么我们怎么来区分这两个过程呢?我们的波形图并没有画出。那么怎么解决这个问题呢?
这个位置我们需要增加一个新的变量信号,我们把它定义为使能信号,它的初值为低电平;当我们的 1s 计数器计数到最大值,对它进行取反,那么其他时刻保持原有电平不变。那么这儿就应该是一个高电平,到了 1s 计数器计数到最大值对它进行取反,就由高电平变为了低电平
当这个使能信号为低电平时,表示的就是完全熄灭到完全点亮这样一个过程;当它为高电平时,表示的就是完全点亮到完全熄灭这么一个过程。那么这样就区分出了这两个过程,
修改完成之后我们继续代码的编写,那么这里就要声明一个新的信号,我们的使能信号,同样使用 always 语句进行赋值,它的初值是低电平;如果说我们 1s 计数器计数到最大值,对它进行取反;那么其他时刻让它保持原来的电平
那么有了这个条件,我们开始输出信号代码的编写。同样使用 always 语句,它的初值是高电平,与这个位置是对应的;那么我们的输出信号什么时候变为低电平呢?我们看一下波形图
当我们的使能信号为低电平,而且我们 1ms 计数器的计数值小于 1s 计数器的计数值,这个时候保持低电平,那么其他时刻都是高电平。那么仅仅有这么一个条件吗?我们看一下第二个波形图
当我们的使能信号为低电平时,并且我们 1ms 计数器大于我们 1s 计数器的计数值时,我们的输出信号同样保持低电平。那么这儿就增加一下我们的条件,我们的输出信号就变为低电平,就点亮了我们的 LED 灯。如果说这几种情况都不满足,让它保持为高电平
这样参照着我们的波形图我们的代码编写完成,保存一下
breath_led.v
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 , input wire sys_rst_n , output wire led_out ); reg [5:0] cnt_1us; reg [9:0] cnt_1ms; reg [9:0] cnt_1s; reg cnt_en; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) cnt_1us <= 6'd0; else if (cnt_1us == CNT_1US_MAX) cnt_1us <= 6'd0; else cnt_1us <= cnt_1us + 6'd1; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) cnt_1ms <= 10'd0; else if ((cnt_1us == CNT_1US_MAX) &&(cnt_1ms == CNT_1MS_MAX)) cnt_1ms <= 10'd0; else if (cnt_1us == CNT_1US_MAX) cnt_1ms <= cnt_1ms + 10'd1; else cnt_1ms <= cnt_1ms; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) cnt_1s <= 10'd0; else if ((cnt_1s == CNT_1S_MAX) &&(cnt_1us == CNT_1US_MAX) &&(cnt_1ms == CNT_1MS_MAX)) cnt_1s <= 10'd0; else if ((cnt_1us == CNT_1US_MAX) &&(cnt_1ms == CNT_1MS_MAX)) cnt_1s <= cnt_1s + 10'd1; else cnt_1s <= cnt_1s; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) cnt_en <= 1'b0; else if ((cnt_1s == CNT_1S_MAX) &&(cnt_1us == CNT_1US_MAX) &&(cnt_1ms == CNT_1MS_MAX)) cnt_en <= ~cnt_en; else cnt_en <= cnt_en; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) led_out <= 1'b1; else if ((cnt_en==1'b0) && (cnt_1ms<=cnt_1s) ||(cnt_en==1'b0) && (cnt_1ms>cnt_1s)) led_out <= 1'b0; else led_out <= 1'b1; endmodule
回到我们的桌面,新建一个实验工程,对代码进行编译,查找我们的语法错误
然后加载我们的代码,然后进行全编译,出现报错信息,发现是 led_out 信号类型编写错误,修改保存。重新编译,编译完成,点击 OK
breath_led.v
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 , input wire sys_rst_n , output reg led_out ); reg [5:0] cnt_1us; reg [9:0] cnt_1ms; reg [9:0] cnt_1s; reg cnt_en; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) cnt_1us <= 6'd0; else if (cnt_1us == CNT_1US_MAX) cnt_1us <= 6'd0; else cnt_1us <= cnt_1us + 6'd1; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) cnt_1ms <= 10'd0; else if ((cnt_1us == CNT_1US_MAX) &&(cnt_1ms == CNT_1MS_MAX)) cnt_1ms <= 10'd0; else if (cnt_1us == CNT_1US_MAX) cnt_1ms <= cnt_1ms + 10'd1; else cnt_1ms <= cnt_1ms; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) cnt_1s <= 10'd0; else if ((cnt_1s == CNT_1S_MAX) &&(cnt_1us == CNT_1US_MAX) &&(cnt_1ms == CNT_1MS_MAX)) cnt_1s <= 10'd0; else if ((cnt_1us == CNT_1US_MAX) &&(cnt_1ms == CNT_1MS_MAX)) cnt_1s <= cnt_1s + 10'd1; else cnt_1s <= cnt_1s; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) cnt_en <= 1'b0; else if ((cnt_1s == CNT_1S_MAX) &&(cnt_1us == CNT_1US_MAX) &&(cnt_1ms == CNT_1MS_MAX)) cnt_en <= ~cnt_en; else cnt_en <= cnt_en; always@(posedge sys_clk or negedge sys_rst_n) if (sys_rst_n == 1'b0) led_out <= 1'b1; else if ((cnt_en==1'b0) && (cnt_1ms<=cnt_1s) ||(cnt_en==1'b0) && (cnt_1ms>cnt_1s)) led_out <= 1'b0; else led_out <= 1'b1; endmodule
那么下面开始仿真代码的编写。那么首先是时间参数,然后模块开始、模块名称,还有端口列表为空,模块结束
然后声明时钟和复位,然后引出我们的输出信号
然后使用 initial 语句赋初值,然后是生成时钟信号
下面是模块的实例化,那么这里为了方便仿真,我们缩小参数
那么到了这里仿真代码编写完成,保存
tb_breath_led.v
`timescale 1ns/1ns module tb_breath_led(); reg sys_clk; reg sys_rst_n; wire led_out; initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; #20 sys_rst_n <= 1'b1; end always #10 sys_clk = ~sys_clk; breath_led #( .CNT_1US_MAX(6'd4 ), .CNT_1MS_MAX(10'd9), .CNT_1S_MAX (10'd9) ) breath_led_inst ( .sys_clk (sys_clk ), .sys_rst_n(sys_rst_n), .led_out (led_out ) ); endmodule
回到我们的实验工程,添加我们的仿真代码,然后进行一次全编译。全编译通过,点击 OK
然后进行仿真设置
然后开始仿真
仿真编译完成,打开我们的 sim 窗口添加模块波形;回到波形界面,全选、分组、清除前缀,点击 Restart 然后这儿时间参数先改为 50us 我们来试一下,那么运行一次、全局视图;那么这样就能明显的看出输出信号的波形变化了
我们参照着波形图看一下仿真波形。首先是 1us 的计数器,那么初值是 0 最大值是 49 不断循环计数,我们在仿真代码当中把它改为了 4,我们来看一下
那么 1us 计数器它的波形是正确的,没有问题。
那么下面看一下 ms 计数器的波形。我们的 ms 计数器它的初值应该是 0 这儿也是 0 没有问题,当我们 us 计数器计数到最大值,它自加一;那么这儿我们看一下,这儿也是没有问题;当它计数到最大值 999 清零,开始下一周期计数,我们来看一下;那么这儿计数到最大值 9 和这个位置的修改是相对应的,那么计数到最大值 9 进行清零,开始下一周期计数,那么这个也是没有问题的
下面看一下 1s 的计数器,它的初值也是 0,当 1ms 计数器和 1us 计数器都计数到最大值时,对它进行一个自加一,我们来看一下;看一下这个位置,那么两个计数器都计数到最大值 9 和 4,1s 计数器自加一,这儿没有问题;然后看一下它的清零部分,这儿,那么当三个计数器都计数到最大值,对它进行清零 9、9、4 这儿是没有问题的
那么三个计数器的波形是正确的。
我们来看一下使能信号的波形。它的初值是低电平,没有问题,当我们三个计数器都计数到最大值,对它进行一个取反,那么低电平变为了高电平,这儿也是没有问题的;我们来看一下下一个周期,这儿也是,三个计数器都计数到最大值,对它进行取反,由高电平变为低电平,这儿也是没有问题的
最后看一下我们的输出信号。我们的输出信号初值是高电平,是没有问题的;当我们的使能信号为低电平,我们 us 计数器的计数值小于等于 s 计数器的计数值,那么就让它保持一个低电平,仿真波形有问题
修改完代码,保存
回到 ModelSim,打开 Library 重新编译 breath_led 模块。重新查看输出信号
那么这儿是没有问题的
我们来看一下当我们的使能信号为高电平,我们的 ms 计数器它的计数值大于我们的 s 计数器就让它保持一个低电平,那么这儿也是没有问题的
我们仿真的波形图与绘制的波形图是一致的,那么仿真验证通过。
我们回到我们的实验工程,绑定我们的管脚。输出信号与 LED 灯绑定,我们绑定为 L7 就是我们的 D6 LED 灯;我们的时钟是 E1;复位信号是 M15。然后回到实验工程进行一次全编译,编译完成,点击 OK
如图所示连接我们的下载器与电源,那么下载器的另一端连接到电脑,为开发板进行上电
回到我们的实验工程,点击 Programmer 这个位置打开程序下载界面,然后找到我们的 SOF 文件,点击 Start 进行程序的下载
那么程序下载完成
那么在这里我们可以看到:我们的 LED 灯呈现了一个呼吸灯的一个状态。
那么这样就表示上板验证成功,那么以上的内容就是本章节的全部内容。
参考资料:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。