官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师

2.4 PWM呼吸灯

发布时间:2021-08-22   作者:admin 浏览量:

本文的文档编号:000800000015

需要看对应的视频,请点击视频编号:00800000165

1.至简原理与应用配套的案例
2.实现控制LED灯亮度的功能,具体为:上电后,LED灯在前10秒时间内,每隔2秒逐渐变亮。在下一个10秒时间内,每隔2秒,亮度逐渐变暗,如此循环
3. 这是ALTERA入门学习案例文档

第二阶段  项目实践




第四章  PWM呼吸灯




1 项目背景

  随着LED在照明领域的不断发展,其控制方式也越来越多样化,形成不同的视觉效果。相比较的只具备“开”“关”功能的传统LED照明,能够实现从0100%光的亮度的调节的LED灯,在家装灯饰、舞美灯光等领域的需求更为突出。

  呼吸灯是指灯光由高到暗的逐渐变化,感觉好像是人在呼吸。所谓的“呼吸灯”就是根据人的呼吸频率通过光的强弱表现出来:呼吸分为两个过程,一个是“呼”的过程,一个是“吸”的过程。其广泛应用于手机之上,并成为各大品牌手机的卖点之一。如果你的手机里面有未处理的通知,比如说未接来电,未查收的短信等,呼吸灯就会由暗到亮的变化,像呼吸一样那么有节奏,起到一个通知提醒的作用。

呼吸灯的设计方法有很多,有的是用单片机产生PWM(脉冲宽度调制)来驱动LED,也有采用555定时器来做。电路利用电容充放电原理,较为简单。

脉宽调制(Pulse Width ModelationPWM),是利用微处理器/FPGA的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在测量、通信、功率控制与变换等许多领域。PWM数字信号从处理器到被控系统都是数字形式,无需数模转换。

航模中的控制信号大多是PWM信号,比如FUTABA,JR等舵机的控制都采用PWM方式。

发射机给接收机一串脉冲,比如基础脉宽是100ms,那么发射机的脉宽变大时,比如增大为150ms,那么接收机就控制舵机正向旋转,发射的脉宽减小时,比如减小为50ms,那么接收机就控制舵机逆向旋转。

  PWM是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用PWM进行编码。

通俗来说,PWM就是连续的、一定比例占空比的脉冲信号。通过控制占空比来实现不同的控制。简单地,我们可以认为PWM就是一种方波。如图所示:

176

PWM波形图

PWM实现呼吸灯的原理

当输出为低电平时控制LED灯,当输出低电平时,灯亮,当输出高电平时,灯灭。如果一直输出低电平,则灯一直亮;如果一直输出高电平,则灯一直暗;如果50%时间输出低电平,50%时间输出高电平,则灯会暗一些。所以占空比会影响到LED灯的明暗程度。

另一个影响LED亮度的是PWM波形的周期。试想一下,如果PWM的周期是2秒,占空比为50%,那我们看到的是将是LED1秒、亮1秒,而不是半亮的状态。只是我们提高PWM的周期,才能看到半亮的状态。根据经验值,PWM的周期是10毫秒为宜。

也就是说,控制高低电平的时间,也就是占空比(高电平占周期的百分比,例如上面是60%),以及控制PWM的周期,就可以控制灯的亮暗程度。


2 设计目标

本工程实现一个控制LED灯亮度的功能,具体要求:上电后,LED灯显示接近于灭,然后在10秒内,每隔2秒,亮度变化一次,逐渐变亮。在下一个10秒内,每隔2秒,亮度变化一次,逐渐变暗。总而言之,就是20秒一次循环,每隔2秒变化一次,前10秒亮度增大,后10秒,亮度减小。

开发板的硬件原理图

177

上板效果图如下图所示。

178

3 设计实现

3.1 顶层信号

新建目录:D:mdy_bookpwmled在该目录中,新建一个名为pwmled.v的文件,并用GVIM打开,开始编写代码。

我们先分析一下板子上的LED灯。板子上的每个LED灯,都有一个信号与之相连,这个信号一端连着LED,另一端连着FPGA。FPGA控制这根线,就可以控制LED灯的亮、灭以及亮度。当FPGA将这根线输出为0时,LED要亮;当FPGA将这根线输出为1时,这个LED灯就灭;FPGA通过输出一个PWM波形,并且控制占空比,就可以实现LED的亮度控制。

下面表格表示了硬件电路图的连接关系。

器件

原理图信号

FPGA管脚

FPGA工程信号

LED6

LED1_NET

AA4

led

X1

SYS_CLK

G1

clk

K1

SYS_RST

AB12

rst_n

综上所述,我们这个工程需要三个信号,时钟clk,复位rst_n和输出信号led。将module的名称定义为pwmled。为此,代码如下:

1

2

3

4

5

module pwmled(

clk    ,

rst_n  ,

led

);

其中clk、rst_n是输入信号,led是输出信号,并且三个信号都是1比特的,根据这些信息,我们补充输入输出端口定义。代码如下:

1

2

3

input               clk     ;

input               rst_n   ;

output              led     ;


3.2 信号设计

我们首先分析一下需求:上电后,LED灯显示接近于灭,然后在10秒内,每隔2秒,亮度变化一次,逐渐变亮。在下一个10秒内,每隔2秒,亮度变化一次,逐渐变暗。总而言之,就是20秒一次循环,每隔2秒变化一次,前10秒亮度增大,后10秒,亮度减小。

另外,根据PWM的原理,通过控制PWM的占空比就可以实现亮度控制。占空比越大(高电平时间越长,低电平时间越低),灯的亮度越暗。

根据这个原理,可以翻译成:FPGA控制led信号,输出PWM波形,并调整占空比。调整方法:20秒一次循环,每隔2秒变化led的占空比一次,前10秒占空比变大,后10秒,占空比变小。

由于需求没有说明具体的占空比是多少,那我们就自行制定一下占空比,读者可以在上板时,根据视觉效果,调整占空比的大小。自行制定的占空比如下:

12秒内,占空比为95%

22秒内,占空比为85%

32秒内,占空比为70%

42秒内,占空比为50%

52秒内,占空比为20%

62秒内,占空比为20%

72秒内,占空比为50%

82秒内,占空比为70%

92秒内,占空比为85%

102秒内,占空比为95%

然后按上面的过程循环。

PWM的波的周期,根据经验值可以设为10毫秒。

根据上述分析,这个led信号的变化情况如下:

179

1次持续时间2秒,每10毫秒输出一个PWM波(9.5毫秒时变低);

2次持续时间2秒,每10毫秒输出一个PWM波(8.5毫秒时变低);

3次持续时间2秒,每10毫秒输出一个PWM波(7.0毫秒时变低);

4次持续时间2秒,每10毫秒输出一个PWM波(5.0毫秒时变低);

5次持续时间2秒,每10毫秒输出一个PWM波(2.0毫秒时变低);

6次持续时间2秒,每10毫秒输出一个PWM波(2.0毫秒时变低);

7次持续时间2秒,每10毫秒输出一个PWM波(5.0毫秒时变低);

8次持续时间2秒,每10毫秒输出一个PWM波(7.0毫秒时变低);

9次持续时间2秒,每10毫秒输出一个PWM波(8.5毫秒时变低);

10次持续时间2秒,每10毫秒输出一个PWM波(9.5毫秒时变低);

从中我们可以得到有以下几个计数器:计数10毫秒时间的计数器;计数2秒时间的计数器以及计数第1~10次的计数器。

计数10毫秒的计数器的设计思路。本工程的工作时钟是50MHz,即周期为20ns,计数器计数到10_000_000/20=500_000个,我们就能知道10毫秒时间到了。另外,由于该计数器是不停地计数,永远不停止的,可以认为加1条件一直有效,可写成:assign add_cnt==1。该计数器一共要数500_000个。综上所述,该计数器的代码如下。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

cnt0 <= 0;

end

else if(add_cnt0)begin

if(end_cnt0)

cnt0 <= 0;

else

cnt0 <= cnt0 + 1;

end

end

assign add_cnt0 = 1;

assign end_cnt0 = add_cnt0 && cnt0== 500_000-1;

2秒时间计数器的设计思路。本工程的工作时钟是50MHz,即周期为20ns,计数器计数到2_000_000_000/20=100_000_000个,我们就能知道2秒时间到了。这是一种设计思路。但我们也可以以10毫秒为基础,通过数有2_000_000_000/10_000_000=20010毫秒时间,就能知道2秒时间到了。所以该计数器的加1条件是end_cnt0,一共有数200个。综上所述,该计数器的代码如下。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

cnt1 <= 0;

end

else if(add_cnt1)begin

if(end_cnt1)

cnt1 <= 0;

else

cnt1 <= cnt1 + 1;

end

end

assign add_cnt1 = end_cnt0;

assign end_cnt1 = add_cnt1 && cnt1==200-1 ;

1~10次计数器的设计思路。该计数器是每隔2秒就会加1,也就是end_cnt1的时候,就会加1;该计数器一共要数10个。综上所述,该计数器的代码如下。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

always @(posedge clk or negedge rst_n)begin

if(!rst_n)begin

cnt2 <= 0;

end

else if(add_cnt2)begin

if(end_cnt2)

cnt2 <= 0;

else

cnt2 <= cnt2 + 1;

end

end

assign add_cnt2 = end_cnt1;

assign end_cnt2 = add_cnt2 && cnt2==10-1 ;

有了这三个计数器,我们来思考输出信号led的变化。概括起来,led有两个变化点:变0和变1。变0的原因都是在10毫秒计数器数到一定个数时变0,但这个计数是会变的,那么我们可以假设为x,也就是数到x个时,led0。变1则是由于10毫秒计数时间到了,也就是end_cnt0时,led1 。综上所述,led信号的代码如下:

1

2

3

4

5

6

7

8

9

10

11

always  @(posedge clk or negedge rst_n)begin

if(rst_n==1'b0)begin

led <= 1;

end

else if(add_cnt0 && cnt0==x-1) begin

led <= 0;

end

else if(end_cnt0)begin

led <= 1;

end

end

最后我们再来思考变量xxled0的时间。这个时间在不同的次数时,值会不相同。例如第1次是数到9.5毫秒(cnt0数到475_000个),第2次则是在8.5毫秒(cnt0数到425_000个)。也就是说x的值与第几次有关,即与cnt2有关。根据题意,可知x的代码如下:

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

always  @(*)begin

if(cnt2==0)begin

x = 475_000 ;

end

else if(cnt2==1)begin

x = 425_000 ;

end

else if(cnt2==2)begin

x = 350_000 ;

end

else if(cnt2==3)begin

x = 250_000 ;

end

else if(cnt2==4)begin

x = 100_000 ;

end

else if(cnt2==5)begin

x = 100_000 ;

end

else if(cnt2==6)begin

x = 250_000 ;

end

else if(cnt2==7)begin

x = 350_000 ;

end

else if(cnt2==8)begin

x = 425_000 ;

end

else begin

x = 475_000 ;

end

end

此次,主体程序已经完成。接下来是将module补充完整。

3.3 信号定义

接下来定义信号类型。

cnt0是用always产生的信号,因此类型为regcnt0计数的最大值为500_000,需要用19根线表示,即位宽是19位。因此代码如下:

1

reg[18:0]           cnt0    ;

add_cnt0end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是0或者11个线表示即可。因此代码如下:

1

2

wire                add_cnt0;

wire                end_cnt0;

cnt1是用always产生的信号,因此类型为regcnt1计数的最大值为200,需要用8根线表示,即位宽是8位。因此代码如下:

1

reg[7:0]           cnt1    ;

add_cnt1end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者11根线表示即可。因此代码如下:

1

2

wire                add_cnt1;

wire                end_cnt1;

cnt2是用always产生的信号,因此类型为regcnt2计数的最大值为9,需要用4根线表示,即位宽是4位。因此代码如下:

1

reg[ 3:0]           cnt2    ;

add_cnt2end_cnt2都是用assign方式设计的,因此类型为wire。并且其值是0或者11根线表示即可。因此代码如下:

1

2

wire                add_cnt2;

wire                end_cnt2;

led是用always方式设计的,因此类型为reg。并且其值是0或者11根线表示即可。因此代码如下:

1

reg                 led     ;

x是用always方式设计的,因此类型为reg。并且其值是最大是475_000,需要19根线表示即可。因此代码如下:

1

reg[18:0]           x;

至此,整个代码的设计工作已经完成。下一步是新建工程和上板查看现象。


4 综合与上板

4.1 新建工程

首先在d盘中创建名为“pwmled”的工程文件夹,将写的代码命名为“pwmled.v,顶层模块名为“pwmled”。

180

181

然后打开Quartus Ⅱ,点击File下拉列表中的New Project Wzard...新建工程选项。

182

3.在出现的界面中直接点击最下方的“Next”。

183

4.之后出现的是工程文件夹、工程名、顶层模块名设置界面。按照之前的命名进行填写,第一栏选择工程文件夹“pwmled”,第二栏选择工程文件“pwmled.v”,最后一栏选择顶层模块名“pwmled,然后点击”Next”,再出现的界面选择empty project

184

185

5.之后是文件添加界面。在上方一栏中添加之前写的”pwmled.v”文件,点击右侧的“Add”按钮,之后文件还会出现在大方框中,之后点击“Next”。

186

器件型号选择界面。在Device family处选择Cyclone E,在“Available devices”处选择EP4CE15F23C8,然后点击“Next”。

187

EDA工具界面。该页面用默认的就行,直接点击最下方“Next”。

188

8.之后出现的界面是我们前面的设置的总结,确认没有错误后点击“Finish”。

189

4.2 综合

1.新建工程步骤完成后,就会出现以下界面。在“Project Navigator”下选中要编译的文件,点击上方工具栏中“Start Compilation”编译按钮(蓝色三角形)。

190

2.编译成功后会出现以下界面。

191


4.3 配置管脚

192

菜单栏中,选中Assignments,然后选择Pin Planner,就会弹出配置管脚的窗口。

193

在配置窗口最下方中的location一列,参考下表中最右两列配置好FPGA管脚。

器件

原理图信号

FPGA管脚

FPGA工程信号

LED6

LED1_NET

AA4

led

X1

SYS_CLK

G1

clk

K1

SYS_RST

AB12

rst_n

配置完成后,关闭Pin Planner,软件自动会保存管脚配置信息。


4.4 再次综合

194

在菜单栏中,选中Processing,然后选择Start Compilation,再次对整个工程进行编译和综合。

195

出现上面的界面,就说明编译综合成功。


4.5 连接开发板

图中,下载器接入电脑USB接口,电源接入电源,然后摁下下方蓝色开关,看到开发板灯亮。

196


4.6 上板

1.双击Tasks一栏中”Program Device”。

197

2.会出现如下界面,点击add file添加.sof文件,在左侧点击“Start”,会在上方的“Progress”处显示进度。

198

3.进度条中提示成功后,即可在开发板上观察到相应的现象。


   拓展阅读