明德扬论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信扫一扫,快捷登录!

查看: 2419|回复: 2

【FPGA至简设计原理与应用】书籍连载12 第三篇FPGA至简设计项目实践 第三章 PWM呼吸灯

[复制链接]
发表于 2020-5-26 09:19:43 | 显示全部楼层 |阅读模式

马上注册,看完整文章,学更多FPGA知识。

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
本案例的编号为:000800000105,如果有疑问,请按编号在下面贴子查找答案:MDY案例交流【汇总贴】_FPGA-明德扬科教 (mdy-edu.com)
本文为明德扬原创及录用文章,转载请注明出处



大家好,近期我们会连载《FPGA至简设计原理与应用》一书,有兴趣的同学可以学习,也希望大家可以对我们的书提出宝贵的意见和建议。

《FPGA
至简设计原理与应用》书籍连载索引目录

http://www.fpgabbs.cn/forum.php?mod=viewthread&tid=989


读过的朋友可积极在贴后留言,书籍正式出版时,我们会从留言者中挑选20位幸运读者,幸运读者可获潘老师亲笔签名书籍一本。

注:手机浏览可能格式会乱,建议用电脑端进行浏览。






FPGA至简设计项目实践   

     第三章 PWM呼吸灯


本文档编号:000800000015
需要看对应的视频,请点击视频编号:000800000165
1.至简原理与应用配套的案例
2.实现控制LED灯亮度的功能,具体为:上电后,LED灯在前10秒时间内,每隔2秒逐渐变亮。在下一个10秒时间内,每隔2秒,亮度逐渐变暗,如此循环
3. ALTERA入门学习案例文档


第1节 项目背景


随着照明领域需求的不断扩大,LED技术也在迅速发展,其控制方式也越来越多样化,可以产生多样的视觉效果。相较于只具备“开”“关”功能的传统LED照明,能够实现从0到100%灯光亮度调节的LED灯在家装灯饰、舞美灯光等领域的需求更为突出。

这种灯的灯光亮度可以通过调节控制由高到低的逐渐变化,像是人在呼吸一般,因而被称作呼吸灯。呼吸分为两个过程,一个是“呼”,一个是“吸”。而所谓的“呼吸灯”就是将人的呼吸频率通过光的强弱表现出来,其被广泛应用于手机上,并成为各大品牌手机的卖点之一。当手机里有未处理的通知,比如未接来电或未查收的短信时,呼吸灯就会像呼吸一样有节奏的由暗到亮不断变化,从而起到通知提醒的作用。

呼吸灯的设计方法有很多,例如采用555定时器的设计方案,电路利用电容充放电的原理较为简单,也可以用单片机产生脉冲宽度调制来驱动LED。本案例中采用PWM驱动LED灯的方法来进行设计。

脉冲宽度调制技术(Pulse WidthModelation,PWM)是利用微处理器/FPGA的数字输出对模拟电路进行控制的一种有效技术,其广泛应用于测量、通信、功率控制与变换等众多领域。PWM数字信号从处理器到被控系统都采用数字形式,无需进行数模转换。航模中的控制信号大多是PWM信号,比如FUTABA、JR等舵机的控制都采用PWM方式,发射机给接收机输送脉冲后接收机会控制舵机进行旋转。举个例子,假定基础脉宽是100ms,当发射机的脉宽增大(如增加到150ms)时接收机控制舵机进行正向旋转;反之发射机的脉宽减小(如减小到50ms)时,接收机控制舵机进行逆向旋转。

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

通俗来说,PWM是连续的、具有一定比例占空比的脉冲信号,可以通过控制占空比来对其进行改变。简单来说,可以认为PWM就是一种方波,如图3.3-1所示。
                              
1.png
3.3-1PWM波形图
了解PWM之后,下面来学习一下PWM实现呼吸灯的原理。

信号的高低电平变化可以控制LED灯的明灭状态。当输出信号为低电平时LED灯亮,反之,当输出信号为高电平时LED灯灭。当输出电平持续为低,则灯一直保持亮的状态;而当输出电平持续为高,则灯一直保持灭的状态。如果输出信号50%为低电平,50%为高电平,在PWM适宜的周期下LED灯光会变暗,这也说明占空比对LED灯的明暗程度会产生影响。

另一个影响LED亮度的因素则是PWM波形的周期。试想一下,如果PWM的周期是2秒,占空比为50%,此时观察到LED的状态是暗1秒、亮1秒,而不是半亮的状态。只有缩短PWM的周期,才能看到LED半亮的状态。根据经验可知,如果要达到改变亮度的效果,PWM的周期以10毫秒为宜。
综上所述,通过控制高低电平的时间,即占空比(高电平占周期的百分比),以及控制PWM的周期,就可以改变灯的明暗程度。

第2节 设计目标

了解PWM呼吸灯的运行原理后来明确本次设计的功能目标。确定功能目标是本书以及至简设计法的特别之处,只有对设计的功能目标有一定的理解和预期,才能更好地进一步讨论如何逐步进行代码设计和实现。后续设计中的每一个步骤都是围绕着设计目标的实现来针对性的展开,如果对于设计目标一知半解,那么在后续的设计思路中可能难以形成体系化的思考模式,只能碎片式的接收知识。设计目标就像大楼的设计图,如果连最终想要实现的目标都无法确定,那么后续的讨论就没有任何意义。因此在学习完整设计案例的过程中,静下心来从明确设计目标开始,一步步细细咀嚼,逐步掌握,才可以起到事半功倍的效果。

本工程旨在实现控制LED灯亮度的功能,具体要求为:上电后,LED灯显示接近于灭,随后每隔2秒亮度发生一次变化,在前10秒时间内,每隔2秒逐渐变亮。在下一个10秒时间内,每隔2秒,亮度逐渐变暗。简单来说即是以20秒为一次循环,每隔2秒变化一次,前10秒亮度逐渐增大,后10秒亮度逐渐减小。

选用开发板的硬件原理图如下图所示。
2.png
3.3-2开发板LED原理图
本设计的上板效果如3.3-3所示。
  
3.png
3.3-3PWM呼吸灯效果图
如果想要观看上板后的演示视频,可以登录至简设计法官方网站进行观看:www.mdy-edu.com/xxxx

第3节 设计实现

3.1 顶层信号

新建目录:D:\mdy_book\pwmled,并在此目录中新建一个名为pwmled.v的文件。用GVIM打开该文件后开始编写代码。在这里再次强调,初学者按照本书提供的路径名和文件名创建文件,不要对路径名或文件名进行任何更改。因为对于初学者来说,如果贸然按照自己的意愿更名,后续操作中可能会出现中文路径、空格路径等非法路径的问题,或者有些文件名更改后会出现报错的现象。因此建议先按照要求更名保存,在多次进行工程练习熟悉了各个步骤后再进行自主更名操作。在操作中不要想当然,细心操作以避免不必要的错误。

首先对板子上的LED灯进行分析,每个LED灯都与一个信号相连。这个信号一端连接LED,另一端连接FPGAFPGA通过控制这一信号的输出就可以控制LED灯的亮、灭以及亮度。当FPGA输出信号为0时,LED灯点亮,反之,当FPGA输出信号为1时,LED灯熄灭。通过输出PWM波形并控制其占空比,FPGA可以实现对LED灯的亮度进行控制。

硬件电路图的连接关系如下表所示。本工程中LED1灯连接的FPGA管脚为AA4,对应FPGA工程信号为led;工程的时钟管脚为G1,对应FPGA工程信号为clk;复位管脚为G1,对应FPGA工程信号为rst_n

3.3- 1信号和管脚关系
  
器件
  
原理图信号
FPGA管脚
FPGA工程信号
LED1
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         
  
    );

随后对信号的输入输出属性进行声明,指出对于FPGA来说这一信号属于输入还是输出,若为输入声明则为input,若为输出声明则为output。在本设计中,由于clk是外部晶振输送给FPGA的,因此在FPGAclk为输入信号input;同样地,rst_n是外部按键给FPGA的,在FPGArst_n也为输入信号input;同时可知ledFPGA输出控制LED1显示状态的,因此led为输出信号output。根据三个信号的属性将输入输出端口定义补充完整,其代码如下:
1
  
2
  
3
input               clk  ;
  
input               rst_n  ;
  
output              led     ;
3.2 信号设计

在进行信号设计之前先按照至简设计法的思路来进行架构设计。回顾一下设计需求:通电后,LED灯显示接近于灭,在之后的10秒内,每隔2秒钟亮度变化一次,逐渐变亮;在下一个10秒内,依旧是每隔2秒亮度变化一次,但是会逐渐变暗。可以将其总结为:本设计以20秒为一次循环,每隔2秒变化一次,前10秒亮度逐渐增大,后10秒亮度逐渐减小。

前文中详细的介绍过PWM的原理,可以得知通过控制PWM的占空比可以实现LED灯的亮度控制。PWM占空比越大(高电平时间越长,低电平时间越低),灯的亮度越暗。可以这样理解:FPGA控制led信号的输出,可以输出为PWM波形并通过调整占比来达到改变LED灯亮度的效果。根据设计目标可以得出设计方案:每20秒一次循环,每隔2秒改变一次led的占空比,前10秒占空比逐渐变大,LED灯逐渐变亮,后10秒占空比逐渐变小,LED灯也随之逐渐变暗。

由于设计目标只对亮度改变进行要求,并没有说明具体的占空比是多少,因此本书自行设定占空比。可以在上板时根据观察到的视觉效果,调整占空比的大小。本书设定占空比如下:
第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%。
之后以此为规律循环往复。

经过多次的实验和测试后发现:PWM10毫秒刷新一次,LED灯显示的亮度效果是最好的。因此根据经验值,本书将PWM波周期设为10毫秒。建议初学者按照书中提供经验值来进行操作,在完全掌握了设计原理可以独立完成设计后,可以再进行不同波型以及不同占空比的尝试。
根据前文分析,得到led信号的变化波形图如下图所示。
4.png
3.3-4PWM呼吸灯波形图
第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毫秒时变低);

此处PWM波变低的时间是根据10毫秒PWM波的占空比算出来的,例如占空比为95%时可以得出10ms×95%=9.5ms,以此类推,可以得出所有PWM波的变化值。

根据至简设计法原理,总结需求可知,这次设计需要以下三个计数器:计算输出10毫秒PWM波的计数器;计算每一个持续2秒时间的计数器;计算次数(1-10次)的计数器。

来思考一下:既然设计目标为2秒改变一次状态,那么为什么除了计算2秒的计数器之外还要增加次数的计数器,这样岂不是更麻烦吗?实际上增加计数器的操作正是采用了至简设计法的设计思路,让信号代码更加有条理并便于设计师确定位置。

举个生活中常见的例子,如下图所示,可以把每一次20秒的循环看做楼层,把每2秒一次的状态改变看做门牌号,循环中的2秒、4秒、6秒时间即在对应门牌123号,以此类推。如果只用一个计数器的话,那么一楼门牌号为12345678,二楼门牌号为9101112131415161718,三楼以此类推。随着楼层的变高,这种计数方式的弊端就会显露出来。比如在这种情况下想要寻找76号,就可能需要很久才能找到。
5.png
3.3-5单一门牌号计数模式

如果在一个计数单位的基础上再加一个计数单位,即采用两种技术模式,一个记楼层,一个记门牌号,如下图所示。在同样的门牌号计数中,可以记为一楼的12345678号,二楼的12345678910号,以此类推,每一层都有对应的房间号。在这种计数模式下,如果想要找到七层的6号房间,不需要多做思考就可以一下定位到正确位置。
6.png
3.3-6两种门牌号复合计数模式

此外,如果想要定位到每一层固定位置的房间,同样可以使用两种计数单位复合的模式:用cnt0来表示房间号,其范围是0-9,用cnt1来表示楼层号,其范围是0-1,通过cnt0cnt1两个计数器可以找到任何一个房间。如果想找同一个位置的房间,也可以直接用cnt0来表示。例如cnt0==4可以统一表示每层楼的四号房间。但如果只有房间号这一计数模式而没有楼层的话,想表示每层楼的四号房间,则表示方式为“cnt0==4”、“cnt0=12”,更高楼层以此类推。两种表现形式的难易程度显而易见。

通过案例分析可以发现复合计数并不是多此一举,反而是最简单的计数方式。因此,除计算输出10毫秒PWM波的计数器外,本设计会使用一个计数2秒的计数器和一个表示次数的计数器。这是最适合本设计的计数器方案,在后续遇到问题时可以快速的定位到相应位置,从而避免很多麻烦。
不论是简单设计还是复杂设计,至简设计法都会全面的考虑设计需求,在每一个环节采用最适合的设计方案,尽量为后续的步骤减少不必要的麻烦。

确定了三个计数器后来讨论一下每个计数器的实现。至简设计法的设计规则中有讲过,计数器的设计只考虑两个因素:加1条件和计数数量。只要确定好相应逻辑,就能完成计数器代码设计。

首先来讨论计数10毫秒PWM波的计数器cnt0,由于cnt0始终不停地进行计数,因此可以认为其加1条件是一直有效的,可写成:assign add_cnt0==1

可能会有同学提出疑问:加1条件的概念是什么?这里以停车位来进行比喻,一般情况下对每个停车位置会进行对应编号,但是如果某个位置上放置了一块石头无法作为停车位时,该位置就不能获得对应的编号。反之则可以认为停车位编号的加1条件就是:对应位置上没有石头,其可以继续的进行编号,即assign add_cnt0 = “没有石头。因此如果在设计中计数器一直没有阻碍地进行计数工作,则可以认为加1条件是一直有效的。

接着讨论计数器cnt0的计数数量:本工程的工作时钟是50MHz,即周期为20ns,当计数器计数到第10_000_000/20=500_000个时,就代表10毫秒时间到了,因此cnt0的计数数量为500_000

确定好cnt0的加1条件和计数数量后开始进行代码编写,以往都是一行行的输入相应代码。但是至简设计法有一个小技巧,可以节省代码编写时间的同时在一定程度上降低了代码的出错率。至简设计法将日常代码中常用到的固定部分制作成模板,进行代码编程时可以调用相应模板后根据逻辑输入对应设计的变量将代码补充完整。这处就可以用模板编写计数器代码,感受一下这个炫酷的功能。

打开GVIM工具,在命令模式下输入“:Mdyjsq”后点击回车就调出了对应模板,如下图所示。之后再将本案例中的变量填到模板里面,就可以得到完整正确的计数器代码。

7.png
3.3-7至简设计法调用计数器代码模板
按照上文方法,得出计数器cnt0的代码设计如下:

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秒时间的计数器cnt1。本工程的工作时钟是50MHz,即周期为20ns,因此当计数器计数到第2_000_000_000/20=100_000_000个时,就代表2秒时间计时结束。这是第一种设计思路,至简设计法在这里也提供另外一种设计思路:与cnt0复合使用,以10毫秒为小周期,通过数2_000_000_000/10_000_000=20010毫秒时间,就能确定2秒时间计时结束。在这种思路下可以得出计数器cnt1的加1条件是end_cnt0,计数数量为200。此时继续调用至简设计法模板,在GVIM命令模式下输入“:Mdyjsq”,点击回车后调出对应模板,将“add_cnt1”和“end_cnt1”补充完整,得到计数器cnt1的代码如下:


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 ;

最后是次数计数器cnt2的设计。根据设计目标可知,每隔2秒为1次改变,计数器的值应加1,即cnt2的加1条件为end_cnt1;每个周期该计数器应计数10次,即cnt2的计数数量为10。继续调用至简设计法模板,在命令模式下输入“:Mdyjsq”,点击回车,调出对应模板后将“add_cnt1”和“end_cnt1”补充完整,得到计数器cnt2的代码如下:


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。当10毫秒计数器计数到一定个数时led信号值变为0,但由于占空比不断进行变化,该计数值也会发生变化。可以假设该值为x,也就是当计数器数到第x个时,led的值变0led值变为1则是由于10毫秒计数时间到了,也就是当end_cnt0时,led的值变1 。依旧调用至简设计法模板,在编辑模式下输入“Shixu2”回车,调出模板并将代码补充完整,得出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

最后再来思考一下变量xx代表led信号值变为0的条件,即PWM波变低的时刻。由于不同次数中PWM波的占空比是不断变化的,其对应的x值会有所变化。也就是说x的值与闪烁次数有关,即与计数器cnt2有关。在第1次闪烁(cnt2=0)时,led信号在9.5毫秒时刻变为0,即在第一次闪烁中,当cnt0数到第9_500_000/20=475_000个时led信号变为0。因此当cnt2=0时,x的值为475_000。同样的,第2次闪烁(cnt2=1)时,led信号在8.5毫秒时刻变为08_500_000/20=425_000,因此cnt2=1时,x=425_000。同理可得第三次x=350_000,第四次x=250_000,第五次x=100_000,第六次x=100_000,第七次x=250_000,第八次x=350_000,第九次x=425_000,第十次x=475_000

综上所述,可得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

至此,主体程序已经完成。回顾一下思考过程会发现设计的每一步都要按照设计目标逐步展开,看似在讨论一个个小问题,但实际上每个问题都是围绕设计目标来进行讨论的,这也是本书最开始强调制定和理解设计目标的重要性的原因。

3.3 信号定义

下来将module补充完整,首先来定义信号类型。对类型regwire的判断总会有多余的联想,比如认为reg是寄存器,wire是线;或者认为reg类型会综合成寄存器,wire类型不会综合成寄存器。但是这些其实和reg型还是wire型都并无关系。实际上对信号类型的判断不需要做任何的联想,只要记住一个规则“用always实现的是reg型,其他都是wire型”就可以了。

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

关于信号位宽的获取,至简设计法在此分享一个非常实用的技巧:打开计算器,点击“查看”,选择“程序员”模式,在“十进制”下将信号值输入,就会获得对应的信号位宽。如下图所示,将cnt0的值500_000输入,可以看出其位宽为19

8.png
3.3-8通过计算器获取信号位宽
综上所述,cnt0的信号定义代码如下:
1
reg[18:0]           cnt0     ;
cnt1也是用always产生的信号,因此类型为regcnt1计数的最大值为200,需要用8根线表示,即位宽是8位。编辑模式下输入“Reg8”调用模板,得到代码表示如下:
1
reg[7:0]           cnt1    ;
同理,cnt2信号也由always产生,其类型为regcnt2计数的最大值为9,需要用4根线表示,即位宽是4位。编辑模式下输入“Reg4”调用模板,得到代码表示如下:
1
reg[  3:0]           cnt2    ;
add_cnt0end_cnt0都是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可,位宽为1。编辑模式下输入“Wire1”调用模板,得到代码表示如下:
1
  
2
wire                add_cnt0;
  
wire                end_cnt0;
add_cnt1end_cnt1也是用assign方式设计的,因此类型为wire。其值是0或者1,用1根线表示即可,位宽为1。编辑模式下输入“Wire1”调用模板,得到代码表示如下:
1
  
2
wire                add_cnt1;
  
wire                end_cnt1;
同样,add_cnt2end_cnt2是用assign方式设计,类型为wire。其值是0或者1,用1根线表示即可,位宽为1。编辑模式下输入“Wire1”调用模板,得到代码表示如下:
1
  
2
wire                add_cnt2;
  
wire                end_cnt2;
led信号是用always方式设计的,因此类型为reg。其值是0或者1,用1根线表示即可,位宽为1。编辑模式下输入“Reg1”调用模板,得到代码表示如下:
1
reg                 led     ;
x是用always方式设计的,因此类型为reg。其值是最大是475_000,需要19根线表示,即位宽为19,其代码表示如下:
1
reg[18:0]           x;
至此,整个代码的设计工作已经完成。完整版的工程代码如下:
1
module  pwmled(
  
clk  ,
  
rst_n  ,
  
    led         
  
);
  
  
input       clk   ;
  
input       rst_n ;
  
output      led    ;
  
  
reg  [18:0]   cnt0     ;
  
wire         add_cnt0 ;
  
wire         end_cnt0 ;
  
reg  [ 7:0]   cnt1     ;
  
wire         add_cnt1 ;
  
wire         end_cnt1 ;
  
reg  [ 3:0]   cnt2     ;
  
wire         add_cnt2 ;
  
wire         end_cnt2 ;
  
reg          led      ;
  
reg  [18:0]   x        ;
  
  
  
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 ;
  
  
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 ;
  
  
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 ;
  
  
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
  
  
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
  
  
endmodule
第4节 综合与上板

4.1 新建工程

打开软件“Quartus Ⅱ”,点击File下拉列表中的New Project Wzard...新建工程选项,如下图所示。
9.png
3.3-9Quartus新建工程

随后会出现Quartus新建工程介绍,如下图所示,直接点击“Next”。
10.png
3.3-10Quartus新建工程介绍

此时会出现工程文件夹、工程名、顶层模块名设置界面,如图3.3- 11所示。设置目录为:D:/mdy_book/pwmled,工程名和顶层名为pwmled。这里再次进行强调,为了避免初学者使用过程中出现报错情况,强烈建议按照本书的工程名和文件名进行设置,设置完成后点击“Next”。
11.png
3.3-11QUARTUS新建工程设置名称

新建工程类型设置选择“Empty project”,如下图所示,然后点击“Next”。
12.png
3.3-12QUARTUS新建工程类型

文件添加界面如图3.3- 13所示,点击右侧的“Add”按钮,添加之前写好的“pwmled.v”文件,可以看到界面下方会显示出文件,随后点击“Next”。
13.png
3.3-13QUARTUS添加文件

芯片型号选择界面如图3.3- 14所示,“Device Family”选择“Cyclone E”,在芯片型号选择处选择“EP4CE15F23C8”,之后点击“Next”。
14.png
3.3-14QUARTUS选择芯片型号

3.3- 15QUARTUS设置工具界面,不必做任何修改,直接点击“Next”即可。
15.png
3.3-15QUARTUS设置工具界面

新建工程的汇总情况如下图所示,点击“Finish”,完成新建工程。
16.png
3.3-16QUARTUS新建工程汇总界面

4.2 综合

新建工程步骤完成后,会出现如下所示的QUARTUS界面。
17.png
3.3-17QUARTUS新建工程后界面

点击编译按钮可以对整个工程进行编译。编译成功的界面如图3.3- 18所示。
18.png
3.3-18QUARTUS编译后界面

4.3 配置管脚

下面需要对相应管脚进行配置。如下图所示,在菜单栏中选中“Assignments”,然后选择“Pin Planner”,随后会弹出配置管脚的窗口。
19.png
3.3-19QUARTUS配置管脚选项

在配置窗口最下方中的“location”一列,参考信号和管脚的关系,按照表3.3-1中最右两列配置好FPGA管脚,最终配置结果如图3.3- 20。配置完成后,关闭“PinPlanner”,软件自动会保存管脚配置信息。

3.3- 1 信号和管脚关系
  
器件
  
原理图信号
FPGA管脚
FPGA工程信号
LED6
LED1_NET
AA4
led
X1
SYS_CLK
G1
clk
K1
SYS_RST
AB12
rst_n

20.png
3.3-20QUARTUS配置管脚

4.4 再次综合

再次打开“QUARTUS”软件,在菜单栏中选中“Processing”,然后选择“StartCompilation”,再次对整个工程进行编译和综合,如下图所示。
21.png
3.3-21QUARTUS编译选项

当出现如下所示的编译成功标志时,说明已经成功完成编译综合。
22.png
3.3-22QUARTUS编译成功标志
4.5 连接开发板
完成编译后开始进行上板调试操作,按照下图的方式,将下载器接入电脑USB接口,接上开发板电源后按下开发板下方蓝色开关,硬件连接完毕。
23.png
3.3-23开发板连接图
4.6 上板
打开QUARTUS界面,双击“Tasks”一栏中的”Program Device”
24.png
3.3-24QUARTUS界面

出现下载程序界面后点击“add file”添加“.sof”文件,点击“Start”,会在上方的“Progress”处显示进度,当进度条到100%时提示成功,此时即可在开发板上观察相应现象。
25.png
3.3-25配置程序界面

如果操作步骤正确,此时可以在板子上看到LED灯在十秒内逐渐变亮,接下来的十秒又逐渐变暗,随后继续变亮,不断循环。如果观察到这一现象,可以判断此次设计成功。反之,如果LED灯没有正常显示或者按照设计目标的规律变暗变亮,就需要从头开始进行错误排查。如果无法自己完成错误排查的话,可以重新按照步骤操作一遍,相信一定可以达到想要的效果。

第5节 简化版步骤分享

同样这里也会分享简化版的步骤,方便复习以及反复实操。

5.1 设计实现

5.1.1 顶层信号

新建目录:D:\mdy_book\pwmled。在该目录中新建一个名为pwmled.v的文件,并用GVIM打开,开始编写代码。
确定顶层信号。工程信号和管脚关系如下图:
3.3 -1信号和管脚关系
  
器件
  
原理图信号
FPGA管脚
FPGA工程信号
LED6
LED1_NET
AA4
led
X1
SYS_CLK
G1
clk
K1
SYS_RST
AB12
rst_n
写出顶层信号代码
1
  
2
  
3
  
4
  
5
module  pwmled(
  
clk  ,
  
rst_n  ,
  
    led         
  
    );
声明输入输出属性。
1
  
2
  
3
input               clk  ;
  
input               rst_n  ;
  
output              led     ;
5.1.2 信号设计
首先进行架构设计,分析设计目标得到如下所示的波形图。

26.png
3.3 -4 PWM呼吸灯波形图
设计计数器架构,表示10毫秒的计数器代码cnt0如下:


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秒时间的计数器代码cnt1如下:


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次)的计数器cnt2代码如下:


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信号的代码如下:
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
设计表示led信号值变0条件的变量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补充完整。

5.1.3 信号定义
对信号进行类型定义,其中cnt0 的信号定义如下:
1
reg[18:0]           cnt0    ;
add_cnt0end_cnt0的信号定义如下:
1
  
2
wire                add_cnt0;
  
wire                end_cnt0;
cnt1的信号定义如下:
1
reg[7:0]           cnt1    ;
add_cnt1end_cnt1的信号定义如下:
1
  
2
wire                add_cnt1;
  
wire                end_cnt1;
cnt2的信号定义如下:
1
reg[  3:0]           cnt2    ;
add_cnt2end_cnt2的信号定义如下:
1
  
2
wire                add_cnt2;
  
wire                end_cnt2;
led的信号定义如下:
1
reg                 led     ;
变量x的信号定义如下:
1
reg[18:0]           x;
至此,整个代码的设计工作已经完成,后续应对代码进行编译综合以及上板查看现象。

5.2 综合与上板

5.2.1 新建工程

打开软件“Quartus Ⅱ”,点击File下拉列表中的New Project Wzard...新建工程选项。
27.png
3.3-9Quartus新建工程

直接点击“Next”。
30.png
3.3-10Quartus新建工程介绍

此时会出现工程文件夹、工程名、顶层模块名设置界面,设置目录为:“D:/mdy_book/pwmled”,工程名和顶层名为“pwmled”,完成设置后点击“Next”。
31.png
3.3-11QUARTUS新建工程设置名称

选择“Empty project”后点击“Next”。
32.png
3.3-12QUARTUS新建工程类型

点击“Add”按钮后添加“pwmled.v”文件,点击“Next”。
33.png
3.3-13QUARTUS添加文件

对芯片型号进行选择:“Device Family”选择“Cyclone E”,“Available devices”选项下选择“EP4CE15F23C8”,随后点击“Next”。
34.png
图3.3-14QUARTUS选择芯片型号

直接点击“Next”。
35.png
图3.3-15QUARTUS设置工具界面

点击“Finish”,完成新建工程。
36.png
3.3-16QUARTUS新建工程汇总界面

5.2.2 综合

新建工程后界面如下图所示,点击“编译”按钮。
37.png
3.3-17QUARTUS新建工程后界面

编译成功界面如下图。
38.png
3.3-18QUARTUS编译后界面

5.2.3 配置管脚

在菜单栏点击“Assignments”后点击“Pin Planner”,随后会弹出配置管脚的窗口。
39.png
3.3-19QUARTUS配置管脚选项

在配置窗口“location”根据信号和管脚关系配置管脚,配置完成关闭“Pin Planner”即可自动保存配置信息。
40.png
3.3-20QUARTUS配置管脚

3.3 - 1信号和管脚关系
  
器件
  
原理图信号
FPGA管脚
FPGA工程信号
LED6
LED1_NET
AA4
led
X1
SYS_CLK
G1
clk
K1
SYS_RST
AB12
rst_n
5.2.4 再次综合

打开“QUARTUS”软件,在菜单栏中选择“Processing”,点击“StartCompilation”再次进行综合。
41.png
3.3-21QUARTUS编译选项

出现 QUARTUS 编译成功标志,表示此次编译成功。
42.png
3.3-22QUARTUS编译成功标志

5.2.5 连接开发板

下载器接入电脑 USB 接口,开发板接上电源后按下蓝色开关。
43.png
3.3-23开发板连接图

5.2.6 上板

打开QUARTUS界面,双击“Tasks”一栏中的“Program Device”。
44.png
3.3-24QUARTUS界面

点击“add file”,添加.sof文件,随后点击“Start”。“Progress”会显示进度,当进度条显示“100%”时代表添加成功,可在开发板上观察此时现象。
45.png
3.3-25配置程序界面

第6节 扩展练习

至此整个PWM呼吸灯设计就分享完毕了。在学会整个设计之后可以展开思考,尝试改变灯亮与暗状态的维持时间以及改变灯的亮度。在基于原理不变的情况下多做一些尝试,这样可以更深刻的掌握案例,也欢迎有更好思路和想法的同学前往至简设计法论坛进行讨论。








上一篇:【FPGA至简设计原理与应用】书籍连载11 第三篇FPGA至简设计项目实践 第二章 4位闪烁灯
下一篇:【FPGA至简设计原理与应用】书籍连载13 第三篇FPGA至简设计项目实践 第四章 串口通信
MP801开发板 网络培训班 就业培训班 FPGA学习资料
吴老师18022857217

0

主题

8

帖子

53

积分

注册会员

Rank: 2

积分
53
发表于 2020-5-27 10:48:50 | 显示全部楼层
明德杨的案例都很用心,很有参考价值,有利于像我这样的初学者学习
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


QQ|手机版|小黑屋|MDYBBS ( 粤ICP备16061416号-1

GMT+8, 2021-10-28 19:54 , Processed in 0.539719 second(s), 16 queries , File On.

Powered by Discuz! X3.4

本论坛由广州健飞通信有限公司所有

© 2001-2019 Comsenz Inc.

快速回复 返回顶部 返回列表