15. 流水灯

15.1. 章节导读

前面章节我们完成了触摸按键控制LED灯的小实验,本章节我们来一次LED灯小实验的进阶,利用板载的4个LED灯,完成流水灯实验。

15.2. 理论学习

如果大家之前玩过单片机肯定知道,流水灯实验绝对是一个经典的例程,其效果是让排成一排的led灯依次闪亮,像“流水”一样循环不止,看上去很舒服,其原理就是依次控制每个连接到led灯的I/O电平的高低,我们本次的实验是让led灯依次闪亮的间隔为0.5s,也就是让led灯每次只亮一个,每次亮的时间为0.5s ,这样就速度就比较快了,更像“流水”的效果且肉眼还能够分辨出,本章我们还会涉及到让led灯依次闪亮的新语法。

15.3. 实战演练

15.3.1. 实验目标

依次点亮板载的4个LED灯,实现流水灯的效果,两灯之间点亮间隔为0.5s,LED灯一次点亮持续时间0.5s。

15.3.2. 硬件资源

我们使用开发板上的四个LED灯去进行该实验的验证,如图 21‑1所示。

flowle002

图 21‑1 硬件资源

由原理图可知,征途Pro开发板的led灯为低电平时点亮。如图 21‑2所示。

flowle003

图 21‑2 led灯原理图

15.3.3. 程序设计

15.3.3.1. 模块框图

我们给模块取名为water_led,因为我们要让led灯亮的时间为0.5s,肯定少不了计数器,既然用到计数器,那时钟和复位信号也是必须有的,所以输入为时钟和复位信号。我们只需要控制led灯亮的时间和哪一个led灯亮,流水的过程会无限循环下去,所以不需要额外的其他输入信号,而输出则为4bit的led_ out,用于控制板子上的4个小灯,使它们依次闪亮产生流水的效果。根据上面的分析设计出的Visio框图如图 21‑3所示。

flowle004

图 21‑3 流水灯模块框图

端口列表与功能总结如表格 21‑1所示。

表格 21‑1 流水灯输入输出信号描述

信号

位宽

类型

功能描述

sys_clk

1Bit

Input

工作时钟,频率50MHz

sys_rst_n

1Bit

Input

复位信号,低电平有效

led_out

4Bit

Input

输出控制led灯

15.3.3.2. 波形图绘制

其实流水灯和前面的计数器、分频器很像,都需要进行计数,但是除了计数,我们还要实现让led灯产生流水的效果,如何产生流水的效果是我们本例和以往不同的地方。

首先我们还是先将时钟和复位的波形画出,如图 21‑4所示,这两个是输入信号,所以我们用绿色标注。因为led灯依次闪亮的间隔时间为0.5s,所以肯定需要一个计数器计数0.5s的时间,我们现在应该已经掌握如何根据时钟的频率来计算需要计数多少个数了,可以很快的计算出在系统时钟50MHz的频率下计0.5s的 时间需要计数的个数为25_000_000个,即计数器需要从0计数到24_999_999,我们产生一个名为cnt(如果计数器需要多个,取名时可以以时间进行区分)的计数器,然后还产生一个cnt_flag脉冲标志信号作为流水切换的标志,cnt_flag脉冲标志信号每当计数器每计数到24_999_998时拉 高并只产生一个时钟的高电平。流水的效果对应于led灯是一个怎样的状态呢?如图 21‑5所示,每次只亮一个led灯,且亮0.5s后熄灭,下一个邻近的led灯亮,然后循环往复,在波形上我们也很容易表达,led_out控制的4个小灯初始状态为最右边的亮,其余的都处于熄灭状态,管脚电平状态为4’b1110, 每当cnt_flag脉冲标志信号为高电平时亮的led左移一个,其余的led灯熄灭,管脚电平状态变为4’b1101。

flowle005

图 21‑4 流水灯波形图

flowle006

图 21‑5 led灯状态图

15.3.3.3. 代码编写

上面的波形是很容易表达移位效果的,那代码该如何来实现led的移动呢?此时我们应该想到前面语法部分讲过的左移(<<)和右移(>>)操作符,这个很容易记住,即往哪个方向移动箭头就指向哪里。这里我们根据图示知道我们需要实现左移操作,且每次只移动1位。模块的参考代码如下所示:

代码清单 21‑1 流水灯参考代码(water_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
module water_led
#(
parameter CNT_MAX = 25'd24_999_999
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位

output wire [3:0] led_out //输出控制led灯

 );

 ////
 //\* Parameter and Internal Signal \//
 ////
 //reg define
 reg [24:0] cnt ;
 reg cnt_flag ;
 reg [3:0] led_out_reg ;

 ////
 //\* Main Code \//
 ////
 //cnt:计数器计数500ms
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 cnt <= 25'b0;
 else if(cnt == CNT_MAX)
 cnt <= 25'b0;
 else
 cnt <= cnt + 1'b1;

 //cnt_flag:计数器计数满500ms标志信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 cnt_flag <= 1'b0;
 else if(cnt == CNT_MAX - 1)
 cnt_flag <= 1'b1;
 else
 cnt_flag <= 1'b0;

 //led_out_reg:led循环流水
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 led_out_reg <= 4'b0001;
 else if(led_out_reg == 4'b1000 && cnt_flag == 1'b1)
 led_out_reg <= 4'b0001;
 else if(cnt_flag == 1'b1)
 led_out_reg <= led_out_reg << 1'b1; //左移

 assign led_out = ~led_out_reg;

 endmodule

如果代码第46和47行去掉,上板后会发现在一轮流水后就全部熄灭了,不会循环流水,因为左移溢出后就全为0了。

15.3.3.4. 仿真验证

仿真文件编写

代码清单 21‑2 流水灯仿真参考代码(tb_water_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
\`timescale 1ns/1ns
module tb_water_led();

////
//\* Parameter and Internal Signal \//
////
//wire define
wire [3:0] 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 \//
 ////
 //-------------------- water_led_inst --------------------
 water_led
 #(
 .CNT_MAX (25'd24)
 )
 water_led_inst
 (
 .sys_clk (sys_clk ), //input sys_clk
 .sys_rst_n (sys_rst_n ), //input sys_rst_n

 .led_out (led_out ) //output [3:0] led_out
 );

 endmodule

仿真波形分析

打开ModelSim执行仿真,仿真出来的波形如图 21‑6所示,我们让仿真运行了5us,可以发现led_out信号可以实现循环左移的功能,且每次左移的位置都是在cnt_flag脉冲标志信号为高电平的时候进行的,而图 21‑7则显示了cnt计数器计数的个数,我们进行了同比例缩小,且cnt_flag脉冲标志信号拉高的位置都是正确的,然后我们可以放心下板验证了。

flowle007

图 21‑6 仿真波形图(一)

flowle008

图 21‑7 仿真波形图(二)

15.3.4. 上板验证

15.3.4.1. 引脚约束

仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 21‑2所示。

表格 21‑2 引脚分配表

信号名

信号类型

对应引脚

备注

sys_clk

input

E1

时钟

sys_rst_n

input

M15

复位

led[0]

output

N3

LED灯

led[1]

output

P3

LED灯

led[2]

output

M6

LED灯

led[3]

output

L7

LED灯

下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 21‑8所示。

flowle009

图 21‑8 管脚分配

15.3.4.2. 结果验证

管脚配置完成之后重新进行编译,编译完之后就可以进行下载验证了,在下载之前首先将电源与下载线与开发板连接好,如图 21‑9所示。

flowle010

图 21‑9 下载连线图

打开下载界面后,当检测到下载器(USB-Blaster)已连接之后,即可点击“Add File…”添加sof文件,添加好后点击“start”开始下载,随后界面会显示下载成功,如图 21‑10所示。

flowle011

图 21‑10 下载成功图

下载成功后即可以开始验证了,若看到LED灯在以流水灯的形式在显示,则说明验证成功。

15.4. 章末总结

本章通过一个最经典的流水灯例子重点引入了移位这个新语法,移位操作符是很常用的运算符,在串并转换、移位寄存器的设计中都很常用,希望大家一定掌握它的用法。

新语法总结

重点掌握

1、<<(实现左移位功能的移位运算符)

知识点总结

  1. 能够熟练根据时钟计算出任意精确计时所需要计数的个数;

  2. 掌握移位运算符的使用。

15.5. 拓展训练

1、本次思考给大家布置一个学习的任务,自行查阅相关资料,用位拼接({})的方法实现同样的左移功能,并与使用移位操作符进行的移位方式进行对比,总结各自的特点与优点。

2、本例我们实现的是单向循环左移,我们尝试将其改为双向往复循环移动,自己画波形图进行修改并下板验证。