本文的文档编号:000800000015
需要看对应的视频,请点击视频编号:00800000165
1.至简原理与应用配套的案例
2.实现控制LED灯亮度的功能,具体为:上电后,LED灯在前10秒时间内,每隔2秒逐渐变亮。在下一个10秒时间内,每隔2秒,亮度逐渐变暗,如此循环
3. 这是ALTERA入门学习案例文档
第二阶段 项目实践
第四章 PWM呼吸灯
1 项目背景
随着LED在照明领域的不断发展,其控制方式也越来越多样化,形成不同的视觉效果。相比较的只具备“开”“关”功能的传统LED照明,能够实现从0到100%光的亮度的调节的LED灯,在家装灯饰、舞美灯光等领域的需求更为突出。
呼吸灯是指灯光由高到暗的逐渐变化,感觉好像是人在呼吸。所谓的“呼吸灯”就是根据人的呼吸频率通过光的强弱表现出来:呼吸分为两个过程,一个是“呼”的过程,一个是“吸”的过程。其广泛应用于手机之上,并成为各大品牌手机的卖点之一。如果你的手机里面有未处理的通知,比如说未接来电,未查收的短信等,呼吸灯就会由暗到亮的变化,像呼吸一样那么有节奏,起到一个通知提醒的作用。
呼吸灯的设计方法有很多,有的是用单片机产生PWM(脉冲宽度调制)来驱动LED,也有采用555定时器来做。电路利用电容充放电原理,较为简单。
脉宽调制(Pulse Width Modelation,PWM),是利用微处理器/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%,那我们看到的是将是LED暗1秒、亮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秒,占空比变小。
由于需求没有说明具体的占空比是多少,那我们就自行制定一下占空比,读者可以在上板时,根据视觉效果,调整占空比的大小。自行制定的占空比如下:
第1个2秒内,占空比为95%;
第2个2秒内,占空比为85%;
第3个2秒内,占空比为70%;
第4个2秒内,占空比为50%;
第5个2秒内,占空比为20%;
第6个2秒内,占空比为20%;
第7个2秒内,占空比为50%;
第8个2秒内,占空比为70%;
第9个2秒内,占空比为85%;
第10个2秒内,占空比为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=200个10毫秒时间,就能知道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个时,led变0。变1则是由于10毫秒计数时间到了,也就是end_cnt0时,led变1 。综上所述,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 |
最后我们再来思考变量x。x是led变0的时间。这个时间在不同的次数时,值会不相同。例如第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产生的信号,因此类型为reg。cnt0计数的最大值为500_000,需要用19根线表示,即位宽是19位。因此代码如下:
1 |
reg[18:0] cnt0 ; |
add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:
1 2 |
wire add_cnt0; wire end_cnt0; |
cnt1是用always产生的信号,因此类型为reg。cnt1计数的最大值为200,需要用8根线表示,即位宽是8位。因此代码如下:
1 |
reg[7:0] cnt1 ; |
add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下:
1 2 |
wire add_cnt1; wire end_cnt1; |
cnt2是用always产生的信号,因此类型为reg。cnt2计数的最大值为9,需要用4根线表示,即位宽是4位。因此代码如下:
1 |
reg[ 3:0] cnt2 ; |
add_cnt2和end_cnt2都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下:
1 2 |
wire add_cnt2; wire end_cnt2; |
led是用always方式设计的,因此类型为reg。并且其值是0或者1,1根线表示即可。因此代码如下:
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.进度条中提示成功后,即可在开发板上观察到相应的现象。