4 流水灯实验

4.1 LED简介

LED是什么?全称:Light Emitting Diode,发光二极管。

4.2 硬件设计

下面来看一下本次实验的硬件设计部分,也就是LED硬件电路,我们这里是将FPGA引脚直接连接到了发光二极管的阳极,然后LED的阴极接了一个电阻,电阻另一端连接到GND。所以引脚给高电平则LED亮,反之则熄灭。

注意,上面两个是PL的引脚,下面这两个PS的引脚。那由于底板上面只有两个灯,所以说我们是用两个灯来实现流水灯效果。

可以发现它这里都串联了一个电阻,其主要是起到一个限流的作用,就说限制流过发光二极管的电流。

4.3 实验任务

本节的实验任务是使用开发板上的两个/四个LED灯顺序点亮并熄灭循环往复产生流水灯的效果,流水的间隔时间为0.5s。

4.4 程序设计

首先,按照2.2小节,构建好项目的4大文件夹。

然后,使用思维导图软件绘制系统框图:

接着,使用Visio绘制模块框图,来确定最终的设计:

新手在编写代码之前,可以先画一下波形图,看着波形图写代码思路会更加清晰。

编写代码

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
module flow_led(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位信号,低电平有效
output reg [3:0] led // 4个Led灯
);

reg [24:0] cnt; // 计数器寄存器

// 计数器计时0.5s
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 25'd0;
else if(cnt < (25'd25000000 - 25'd1))
cnt <= cnt + 25'd1;
else
cnt<= 25'd0;
end

// led移位控制
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
led <= 4'b0001;
else if(cnt == (25'd25000000 - 25'd1))
led <= {led[2:0], led[3]}; // 特殊方式实现循环移位
else
; // 保持不变
end

endmodule

仿真验证

上面编写好了代码,但是这个代码编写的对不对,有没有语法错误,包括它的功能正不正确,还不知道。接下来可以通过仿真对代码进行验证,通过对比仿真的波形图,查看与前面绘制的波形图是否一致,来判断这个代码的编写的对不对。

对模块进行仿真,需要先编写tb测试文件。

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
`timescale 1ns/1ns	// 仿真的单位/仿真的精度
module tb_flow_led();

parameter CLK_PERIOD = 20; // 参数,时钟周期20ns

reg sys_clk; // 时钟周期20ns
reg sys_rst_n;
wire [3:0] led;

initial begin
sys_clk <= 1'b0; // 时钟信号初始值
sys_rst_n <= 1'b0; // 复位信号初始值
#200 // 延迟200ns
sys_rst_n <= 1'b1; // 停止复位
end

always #(CLK_PERIOD/2) sys_clk = ~sys_clk; // 每隔10ns,时钟反转一次

flow_led u_flow_led(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.led (led)
);

endmodule

打开Modelsim软件进行仿真。

4.5 下载验证

打开Vivado,创建新的工程,起名为flow_led。根据之前的步骤一步一步创建好新工程。

首先,看一下RTL原理图,如下图所示:

然后,约束管脚。这里需要结合板子的PCB原理图,约束完成后,点击ctrl+s保存。保存完成之后,可以打开打开约束文件查看约束代码程序。

  • IO引脚约束

  • 时序约束

本实验比较简单,即使不加时序约束应该问题也不大,这里示例一下如何添加时序约束。这里需要对输入时钟做一个周期约束,也就是告诉Vivado软件输入的系统时钟频率是多少,然后Vivado软件就会基于这个时钟进行布局布线。

时序约束代码:

1
2
3
# 时序约束
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
# 简单解释:创建一个时钟,周期是20ns,对应的端口是系统时钟sys_clk,也即对系统时钟的周期进行了约束

接下来,就可以生成比特流文件(是不是二进制文件???),等待编译完成,产生下载文件,在下载之前可以打开综合后的原理图看一下:

最后,连接开发板的下载口,下载程序。

5 按键控制LED灯亮灭实验

基本流程和上面的“流水灯”实验一致,这里就简写了。

实验任务:本节的实验任务是使用开发板上的两个/四个按键控制两个/四个LED灯的亮灭。按下不同的按键,LED灯呈现不同的效果。

按键状态 LED显示效果
无按键按下 四个LED灯全灭
按下KEY0 自左向右的流水灯
按下KEY1 自右向左的流水灯
按下KEY2 四个灯同时闪烁
按下KEY3 四个灯全亮

使用思维导图,绘制系统框图

接着,使用Visio绘制模块框图,来确定最终的设计:

再来画一下波形图:

编写程序:

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
module flow_led(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位信号,低电平有效
input [1:0] key, // 按键
output reg [3:0] led // 4个Led灯
);
parameter CNT_MAX = 25'd25000000; // 计数器计时0.5s
// localparam CNT_MAX = 25'd25000000; // 计数器计时0.5s
reg [24:0] cnt; // 计数器寄存器
reg led_flag; // led状态标志

// 计数器计时0.5s
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 25'd0;
else if(cnt < (CNT_MAX - 25'd1))
cnt <= cnt + 25'd1;
else
cnt<= 25'd0;
end

// led状态切换标志位
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
led_flag <= 1'b0;
else if(cnt == (CNT_MAX - 25'd1))
led_flag <= ~led_flag
else
; // led_flag保持不变
end

//led控制(根据KEY和led_flag的值,对led进行控制)
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
led <= 2'b00;
else begin
case (key)
2'b11 : led <= 2'b00;
2'b10 :
if(led_flag == 1'b0)
led <= 2'b01;
else
led <= 2'b10;
2'b01 :
if(led_flag == 1'b0)
led <= 2'b01;
else
led <= 2'b10;
2'b00 :
; // 状态不变
default :
; // 状态不变
endcase
end
end

endmodule

创建仿真文件(tench bench文件)进行仿真

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
`timescale 1ns/1ns	// 仿真的单位/仿真的精度
module tb_key_led();

parameter CLK_PERIOD = 20; // 参数,时钟周期20ns
parameter TB_CNT_MAX = 25'd25000000;

reg sys_clk; // 时钟周期20ns
reg sys_rst_n;
reg [1:0] key;
wire [3:0] led;

initial begin
sys_clk <= 1'b0; // 时钟信号初始值
sys_rst_n <= 1'b0; // 复位信号初始值
key <= 2b'11; // 按键初始没有被按下
#200 // 延迟200ns
sys_rst_n <= 1'b1; // 停止复位
#2000 // 延迟2000ns
key <= 2b'10; // 模拟按键KEY0被按下
#2000 // 延迟2000ns
key <= 2b'00; // 模拟按键KEY0松开
#2000 // 延迟2000ns
key <= 2b'01; // 模拟按键KEY1被按下
#2000 // 延迟2000ns
key <= 2b'00; // 模拟按键KEY1松开
end

always #(CLK_PERIOD/2) sys_clk = ~sys_clk; // 每隔10ns,时钟反转一次

// 例化参数
key_led #(
.CNT_MAX (TB_CNT_MAX)
)

key_led u_key_led(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.key (key),
.led (led)
);

endmodule

使用Modelsim软件进行仿真。

打开Vivado,创建新的工程,起名为key_led。根据之前的步骤一步一步创建好新工程。

添加约束文件:

综合后生成比特流文件,下载到板卡中。

6 触摸按键控制LED灯亮灭实验

6.1 触摸按键简介

6.1.1 触摸按键初识

  • 触摸按键:通过轻触的形式,实现传统意义机械式按键的功能。
  • 分类:电阻式、电容式、红外感应式和表面声波式。
  • 电容式触摸按键的优点与缺点:
    • 优点
      • 无机械装置,使用寿命长
      • 面板不需要开孔
      • 产品更加美观简洁
      • 防水可以做到很好
      • 不需要按键消抖
      • 灵活性高,多种输出模式
    • 缺点
      • 要单独的触摸芯片(触摸IC),实现对电容变化检测以及对电容按键的配置(什么情况输出高低电平)
      • 没有传统机械式控层以反馈感
      • 按键较多时,占用空间大,成本高

6.1.2 触摸IC简介

触摸IC:电容式触摸芯片是为实现人体触摸而设计的集成电路,可替代传统机械式轻触按键,且触摸界面防水防尘、自由定制、美观耐用。

常用的触摸IC有:AR101、JL223B和JL523B等。

触摸IC的引脚:

触摸IC模式配置说明:

6.2 硬件设计

触摸按键原理图

6.3 实验任务

本节的实验任务是使用触摸按键控制LED灯的亮灭,开发板上电后LED为点亮状态,手指触摸后LED熄灭;当再次触摸时,LED点亮。(自锁模式

6.4 程序设计

6.4.1 思维导图分析模块

6.4.2 系统框图和波形图

6.4.3 编写程序

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
module touch_led(
input sYs_clk,
input sys_rst_n,
input touch_key,
output led
);
reg touch_key_d0;
reg touch_key_d1,

wire pos_touch_key;

assign pos_touch_key = ~touch_key_d1 & touch_key_d0; // 采上升沿

// 打两拍
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
touch_key_d0 <= 1'b0;
touch_key_d1 <= 1'b0;
end
else begin
touch_key_d0 <= touch_key;
touch_key_d1 <= touch_key_d0;
end
end

//LED信号翻转
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
led <= 1'b1;
else if (pos_touch_key)
led <= ~led;
else
led <= led;
end
endmodule

6.4.4 仿真

  • 编写tb文件
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
`timescale 1ns/1ns	// 仿真的单位/仿真的精度
module tb_touch_led();

parameter CLK_PERIOD = 20; // 参数,时钟周期20ns
parameter TB_CNT_MAX = 25'd25000000;

reg sys_clk; // 时钟周期20ns
reg sys_rst_n;
reg touch_key;
wire led;

initial begin
sys_clk <= 1'b0; // 时钟信号初始值
sys_rst_n <= 1'b0; // 复位信号初始值
touch_key <= 1b'0; // 按键初始没有被按下
#200 // 延迟200ns
sys_rst_n <= 1'b1; // 停止复位
#1000 // 延迟1000ns
touch_key <= 2b'1; // 模拟触摸按键被按下
#2000 // 延迟2000ns
touch_key <= 2b'0; // 模拟触摸按键松开
#1000 // 延迟1000ns
touch_key <= 2b'1; // 模拟触摸按键被按下
#2000 // 延迟2000ns
touch_key <= 2b'0; // 模拟触摸按键松开
end

always #(CLK_PERIOD/2) sys_clk = ~sys_clk; // 每隔10ns,时钟反转一次

// 例化参数
key_led #(
.CNT_MAX (TB_CNT_MAX)
)

touch_led u_touch_led(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.touch_key (touch_key),
.led (led)
);

endmodule
  • 使用Modelsim 仿真

6.5 下载验证

打开Vivado,创建新的工程,起名为touch_led。根据之前的步骤一步一步创建好新工程。

添加约束文件:

生成比特流文件,下载到板子中验证。

补充:异步信号同步

touch_key是一个异步信号,对于这个异步信号的一个同步呢一般都是通过打拍的方式,比方说打两拍,打两拍之后这个信号就是同步到时钟域了。

要注意的是,就是说打一拍的时候,其实有的时候会出现一个问题——比方要采这个异步信号,如果说时钟刚好采到这个异步信号的一个边缘的位置,就说它变化的位置,这个时候会出现有可能踩不准确的情况,它其实就是一种亚稳态了,亚稳态就是指这个触发器的输出在规定的时间之内不能达到一个稳定的状态,其实就是指这里的_d0,它就是对这个异步信号打一拍,如果说你刚好打的位置就是变化的位置,那么_d0它会出现一个亚稳态,它采到的值有可能是零、有可能是一。所以说这就容易出现问题。

打一拍它会出现亚稳态,那么可以采用打2拍、打3拍的方式,来得到一个上升沿。

7 按键控制蜂鸣器实验

8 呼吸灯实验

8.1 呼吸灯简介

呼吸灯:由灭渐亮,然后再由亮渐灭,模仿人呼吸方式的LED灯。

呼吸灯原理:PWM(Pulse Width Modulation),脉冲宽度调制。

8.2 实验任务

本节实验任务是使用正点原子FPGA开发板上的LED,实现呼吸灯的效果,即由灭渐亮,然后再由亮渐灭(渐亮和渐灭的时间为2s)。

8.3 程序设计

8.3.1 思维导图分析模块

8.3.2 系统框图和波形图

8.3.3 编写程序

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
module breath_led(
input sys_clk,
input sys_rst_n,
output reg led // 输出设置为reg类型
);

parameter CNT_2US_MAX = 7'd100;
parameter CNT_2MS_MAX = 10'd1000;
parameter CNT_2S_MAX = 10'd1000;

reg [6:0] cnt_2us;
reg [9:0] cnt_2ms;
reg [9:0] cnt_2s;
reg inc_dec_flag; // 亮度递增或递减标志

// 计数器计时2us
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_2us <= 7'b0;
else if(cnt_2us == (CNT_2US_MAX - 7'b1))
cnt_2us <= 7'b0;
else
cnt_2us <= cnt_2us + 7'b1;
end

// 计数器计时2ms
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_2ms <= 10'b0;
else if(cnt_2us == (CNT_2US_MAX - 7'b1) && cnt_2ms == (CNT_2MS_MAX = 10'b1)
cnt_2ms <= 10'b0;
else if(cnt_2us == (CNT_2US_MAX - 7'b1))
cnt_2ms <= cnt_2ms + 10'b1;
else
cnt_2ms <= cnt_2ms;
end

// 计数器计时2s
always @(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n)
cnt_2s <= 10'b0;
else if(cnt_2us == (CNT_2US_MAX - 7'b1) && cnt_2ms = (CNT_2MS_MAX = 10'b1) && cnt_2s = (CNT_2S_MAX - 10'b1))
cnt_2s <= 10'b0;
else if(cnt_2us == (CNT_2US MAX - 7'b1) && cnt_2ms == (CNT_2MS_MAX = 10'b1))
cnt_2s <= cnt_2s + 10'b1;
else
cnt_2s <= cnt_2s;
end

// 亮度递增/递减的标志
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
inc_dec_flag <= 1'b0;
else if(cnt_2us == (CNT_2US_MAX - 7'b1) && cnt_2ms == (CNT_2MS MAX = 10b1) && cnt_2s == (CNT_2S_MAX - 10'b1))
inc_dec_flag <= ~inc_dec_flag;
else
inc_dec_flag <= inc_dec_flag;
end

// 控制LED灯PWM输出
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
led <= 1'b0;
else if((inc_dec_flag == 1'b0 && cnt_2ms <= cnt_2s) || (inc_dec_flag == 1'b1 && cnt_2ms >= cnt_2s))
led <= 1'b1;
else
led <= 1'b0;
end

endmodule

8.3.4 仿真

  • 编写tb文件
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
`timescale 1ns/1ns    //仿真的单位/仿真的精度

module tb_breath_led();

parameter CLK_PERIOD = 20;

parameter CNT_2US_MAX = 7'd1;
parameter CNT_2MS_MAX = 10'd10;
parameter CNT_2S_MAX = 10'd10;

reg sys_clk; //周期20ns
reg sys_rst_n;

wire [3:0] led;

initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
#200
sys_rst_n <= 1'b1;
end

always #(CLK_PERIOD/2) sys_clk = ~sys_clk;

breath_led #(
.CNT_2US_MAX (CNT_2US_MAX)
.CNT_2MS_MAX (CNT_2MS_MAX)
.CNT_2S_MAX (CNT_2S_MAX)
)
u_breath_led(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.led (led )
);

endmodule
  • 使用Modelsim 仿真

8.4 下载验证

打开Vivado,创建新的工程,起名为breath_led。根据之前的步骤一步一步创建好新工程。