16. 呼吸灯¶
16.1. 章节导读¶
在上一章节,我们使用板载LED等设计并实现了流水灯效果;在本章节,我们将带领读者设计并实现呼吸灯效果。
16.2. 理论学习¶
呼吸灯在我们的生活中很常见,在手机上多作为消息提醒指示灯而被广泛使用,其效果是小灯在一段时间内从完全熄灭的状态逐渐变到最亮,再在同样的时间段内逐渐达到完全熄灭的状态,并循环往复。这种效果就像“呼吸”一样,有张有弛,而且给人一种很舒服的感觉。其工作原理是利用PWM来控制小灯在相同时间段内的不同占空比, 即在同样小时间段内,小灯亮的时间依次增加到最大后再依次减小,从而实现渐亮到渐灭的“呼吸”效果。
16.3. 实战演练¶
16.3.1. 实验目标¶
使用板载LED灯D6实现呼吸灯效果。
16.3.2. 硬件资源¶
我们使用开发板上的一个LED灯去进行该实验的验证,如图 22‑1所示。
图 22‑1 硬件资源
由原理图可知,征途Pro开发板的led灯为低电平时点亮。如图 22‑2所示。
图 22‑2 led灯原理图
16.3.3. 程序设计¶
16.3.3.1. 模块框图¶
我们给模块取名为breath_led,同样本例我们也会用到计数器,所以时钟和复位信号也是必须有的,且输入只有时钟和复位信号。我们只需要控制一个led灯进行“呼吸”就可以了,所以输出是一个名为led_out的输出信号。根据上面的分析设计出的Visio框图如图 22‑3所示。
图 22‑3 呼吸灯模块框图
端口列表与功能总结如表格 22‑1所示。
表格 22‑1 呼吸灯模块输入输出信号描述
信号 |
位宽 |
类型 |
功能描述 |
---|---|---|---|
sys_clk |
1Bit |
Input |
工作时钟,频率50MHz |
sys_rst_n |
1Bit |
Input |
复位信号,低电平有效 |
led_out |
1Bit |
Output |
输出控制led灯 |
16.3.3.2. 波形图绘制¶
在画波形之前,我们先分析一下该如何才能让 led灯实现“呼吸”的效果。整个呼吸灯“呼吸”的效果分为两部分,一个过程是从灭到亮,另一个过程是从亮到灭。为了把复杂的问题简单化,我们把led整个“呼吸”的动作进行分解,先分析从灭到亮的过程,而从亮到灭则是与之相反的一个过程。
为了让“呼吸”的效果表达的比较完美,我们把从灭到亮的时间设置为1s,也就是led灯在1s的时间内完成从灭到亮的效果,同理从亮到灭也是一样的时间。我们知道在安全范围内给led灯供的电压越大led灯也就越亮,难道这里我们要控制led灯的电压渐渐变大吗?显然这是不现实的,所以我们用另一种方法,通过控制PW M的占空比来实现led灯越亮的效果。我们知道同一时间段内,如果供给led灯一个脉冲信号的低电平持续的时间越长(高电平持续的时间越短)led灯就越亮,我们就是通过调整PWM实现高低电平的占空来调控led灯的亮度,我们取n个相同的时间段,然后让低电平的持续时间按照相等的时间间隔逐渐增多,这样子我们看上去 的led灯就会越来越亮了。
知道了原理后我们就要进一步分析该如何具体实现,首先我们需要在1s的时间内产生很多个时间相等的小段,然后控制每个时间小段中低电平持续的时间渐渐增加就可以了。为了让“呼吸”效果显得更加细腻,我们把1s的时间分成1000个时间相等的时间小段,每个小段的时间就是1ms,这1000个1ms的时间小段中低电平持 续的时间还不能相等,要有一个从少到多渐渐递增的关系,为了让1000个1ms的时间小段都不一样,我们就需要将1ms再分成1000个时间小段,那么每一个时间小段就是1us, 在逐渐变亮的过程中我们可以让led灯在第1个1ms的时间小段内亮0us,即全灭;第2个1ms的时间小段亮1us;第3个1ms的时间 小段亮2us,……,第998个1ms的时间小段亮997us,第999个1ms的时间小段亮998us,第1000个1ms的时间小段亮999us。在逐渐变灭的过程中同理,我们划分的等级越多,“呼吸”的就越顺畅,看上去的效果就越好,但是人眼的分辨率的有限的,当大于这个分辨率人眼就看不出来这种差异了。
下面我们开始进行波形的绘制,首先画出输入时钟和复位的波形,根据分析我们至少需要设计三个计数器,分别为用于计数从灭到亮1s时间的cnt_1s计数器、用于划分1000个1ms时间小段的cnt_1ms计数器和用于划分1000个1us时间小段的cnt_1us计数器。然后就是要确定每个计数器计数开始和清零的条 件。我们先画出cnt_1us计数器的波形,根据50MHz的时钟计算得需要计50个数,即计数器从0计数到49,只要不复位则计数器一直计数,cnt_1us计数器计数满即可清零。接下来就是产生cnt_1ms计数器的波形,我们想是不是可以利用已经产生好的cnt_1us计数器来产生cnt_1ms计数器呢?答案 是当然可以的,我们完全没有必要再重新产生一个计数1ms的计数器,而是利用已经做好的1us计数器,根据cnt_1us计数器的时间计算得每当cnt_1us计数器计数到49时cnt_1ms计数器加1,cnt_1ms计数器计数到999且cnt_1us计数器同时也计数到49时cnt_1ms计数器才能清零,如果 少了cnt_1us计数器计数到49这个条件,仿真时会发现最后一个1ms的时间小段是不够1ms时间的。我们也用同样的方法产生cnt_1s计数器,每当cnt_1ms计数器计数到999且cnt_1us计数器计数到49时cnt_1s计数器加1,cnt_1s计数器计数到999且cnt_1ms计数器计数到999 且cnt_1us计数器计数到49三个条件同时满足时cnt_1s计数器清零(条件如此多的原因和cnt_1us计数器的分析是相同的,编写代码仿真时可以观察效果)。设计好的三个计数器的波形如图 22‑4所示。
图 22‑4 计数器产生波形图
我们产生了所需要的三个计数器后新的问题来了,我们该如何才能让每一个1ms的时间小段的低电平持续时间都不一样且渐渐增加呢?这是本实验的一个难点所在,之所以说是难点在于需要我们仔细观察、联想才能找到答案,而且还涉及到一个小小的技巧问题。如图 22‑5所示,我们先画出呼吸灯从灭到亮时间段预期的led_ou t波形图,结合三个计数器我们发现led_out为低电平时间时cnt_1s计数器的计数值总是小于cnt_1ms计数器的计数值,这简直就是太完美的发现了,我们让cnt_1s计数器的计数值大于cnt_1ms计数器的计数值就可以实现每一个1ms的时间小段的低电平持续时间都不一样且渐渐增加的效果。同理呼吸灯从 亮到灭正好是相反的过程,如图 22‑6所示,要使cnt_1s计数器的计数值总是大于cnt_1ms计数器的计数值才能实现每一个1ms的时间小段的低电平持续时间都不一样且渐渐减小的效果。
图 22‑5 呼吸灯波形图(一)
图 22‑6 呼吸灯波形图(二)
为了区分从灭到亮和从亮到灭两个过程我们使用一个标志信号,这个标志信号不是脉冲,而是一个电平,更准确的来讲应该叫使能信号,这里我们取名为cnt_1s_en,如图 22‑7和图 22‑8所示,cnt_1s_en使能信号为低电平时实现呼吸灯从灭到亮的过程,cnt_1s_en使能信号为高的时候实现呼吸灯从亮到灭的过程,cnt_1s_en使能信号就这样来回取反就可以了,取反的条件和cnt_1s清零的条件相同即可,这样才能够表示完整的1s时间。
图 22‑7 呼吸灯波形图(三)
图 22‑8 呼吸灯波形图(四)
16.3.3.3. 代码编写¶
为了后面仿真和修改呼吸灯“呼吸”的时间,我们将计数器的计数个数用parameter进行定义。呼吸灯模块参考代码如下。
代码清单 22‑1 呼吸灯模块参考代码(breath_led.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | 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灯
);
////
//\* Parameter and Internal Signal \//
////
//reg define
reg [5:0] cnt_1us ;
reg [9:0] cnt_1ms ;
reg [9:0] cnt_1s ;
reg cnt_1s_en ;
///
//\* Main Code \//
////
//cnt_1us:1us计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1us <= 6'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1us <= 6'b0;
else
cnt_1us <= cnt_1us + 1'b1;
//cnt_1ms:1ms计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1ms <= 10'b0;
else if(cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
cnt_1ms <= 10'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1ms <= cnt_1ms + 1'b1;
//cnt_1s:1s计数器
always@(posedge sys_clk or negedge sys_rst_n)
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_1MS_MAX && cnt_1us == CNT_1US_MAX)
cnt_1s <= cnt_1s + 1'b1;
//cnt_1s_en:1s计数器标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1s_en <= 1'b0;
else if(cnt_1s == CNT_1S_MAX && cnt_1ms == CNT_1MS_MAX
&& cnt_1us == CNT_1US_MAX)
cnt_1s_en <= ~cnt_1s_en;
//led_out:输出信号连接到外部的led灯
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led_out <= 1'b0;
else if((cnt_1s_en == 1'b1 && cnt_1ms < cnt_1s) \|\|
(cnt_1s_en == 1'b0 && cnt_1ms > cnt_1s))
led_out <= 1'b0;
else
led_out <= 1'b1;
endmodule
|
16.3.3.4. 仿真验证¶
仿真文件编写
编写仿真文件对呼吸灯模块进行仿真验证,呼吸灯模块仿真参考代码如下。
代码清单 22‑2 呼吸灯模块仿真参考代码(tb_breath_led.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | \`timescale 1ns/1ns
module tb_breath_led();
////
//\* 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
|
仿真波形分析
打开ModelSim执行仿真,仿真出来的波形如图 22‑9所示,我们在仿真的时候通过修改参数的方式将计数器的值同比例缩小,所以让仿真运行了50us后就可以比较明显的观察到led_out的变化了,可以发现led_out信号在cnt_1s_en脉冲标志信号为低时高电平时序的时间是渐渐增加的,对应的led 灯会渐渐变亮;而在cnt_1s_en脉冲标志信号为低时高电平时序的时间是渐渐减小的,对应的led灯会渐渐变灭。是我们想要的结果,可以进行下板验证结果了。
图 22‑9 呼吸灯仿真波形图(一)
如图 22‑10所示,我们将三个计数器计数的关键位置处的波形放大,可以发现cnt_1us计数器计数到4时,cnt_1ms计数器加1,同时cnt_1us计数器清零;cnt_1us计数器计数到4时cnt_1ms计数器加1,cnt_1ms计数器计数到9且cnt_1us计数器计数到4时cnt_1ms计数器清 零;cnt_1us计数器计数到4且cnt_1ms计数器计数到9时cnt_1s计数器加1,cnt_1s计数器计数到9且cnt_1ms计数器计数到9且cnt_1us计数器计数到4时cnt_1s计数器清零,符合预先的设计思想。
图 22‑10 呼吸灯仿真波形图(二)
仿真验证通过后我们可以修改RTL代码中的条件来深刻体会计数器缺少条件带来的后果,可能在最后上板观察led灯“呼吸”的效果时差别不是很明显,但是仿真时可以观察到的很清晰,为了严谨一定不能缺少条件。
16.3.4. 上板验证¶
16.3.4.1. 引脚约束¶
仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 22‑2所示。
表格 22‑2 引脚分配表
信号名 |
信号类型 |
对应引脚 |
备注 |
---|---|---|---|
sys_clk |
input |
E1 |
时钟 |
sys_rst_n |
input |
M15 |
复位 |
led |
output |
L7 |
LED灯 |
下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 22‑11所示。
图 22‑11 管脚分配
16.3.4.2. 结果验证¶
管脚配置完成之后重新进行编译,编译完之后就可以进行下载验证了,在下载之前首先将电源与下载线与开发板连接好,如图 22‑12所示。
图 22‑12 下载连线图
打开下载界面后,当检测到下载器(USB-Blaster)已连接之后,即可点击“Add File…”添加sof文件,添加好后点击“start”开始下载,随后界面会显示下载成功,如图 22‑13所示。
图 22‑13 下载成功图
下载成功后即可以开始验证了,板卡上电后LED灯D6出现呼吸灯效果;则说明验证成功。
16.4. 章末总结¶
本章我们实现的是一个呼吸灯的例子,很多学习者感觉呼吸灯的例子是一个跨越式的内容,它虽然是一个简单的基础内容,没有新语法也没有太复杂的功能,但却给初学者带来不小的困难,让初学者一下子没有了思路,感觉很难实现。很多其他的教程都是采用的上帝手法,没有从细节上带着大家分析如果思考实现呼吸灯的例子,导致大家不 会去分析,以为呼吸灯是一个很难的例子,其实并不是这样的,我相信大家在看了前面详细的分析思考过程,一定能够感觉到呼吸灯的实现思路是非常清晰且简单的。
知识点总结
1、深刻体会呼吸灯的分析过程,如何把一个复杂的问题简单化,然后顺着解决问题的思路思考下去;
2、继续深入体会parameter参数化常数所带来的好处,以及用法;
3、多体会计数器如何控制,什么时候开始计数、什么时候清零,千万要考虑全面,不要忘记条件。
16.5. 拓展训练¶
本例中我们实现了一个led灯的“呼吸”,而板子上有4个led灯,我们可以修改代码让4个led灯的呼吸频率都不同步,大家可以尝试一下。