FPGA综合设计实验:基于PWM脉宽调制的呼吸流水灯设计

目录

一、引言

二、项目准备

1.项目预期目标

2.项目原理及总体实现思路

三、项目模块设计

1.顶层模块

2.按键控制模块

3.呼吸灯模块

4.数码管显示模块

5.二进制转BCD码模块

四、项目测试

1.仿真测试

2.实物测试

五、项目总结

1.选题思考与过程反思

2.设计的具体完成情况详细描述

3.项目可改进之处

4.项目设计心得

参考文献


引言

近年来,FPGA技术的快速发展使得其在数字化系统的设计中扮演着越来越重要的角色,尤其是在嵌入式系统、通信系统和图像处理等领域有广泛的应用。PWM脉宽调制技术是一种常用的电子控制技术,通过调整周期不变的脉冲波形的占空比实现对电路的控制,从而达到精准、稳定的控制效果。呼吸流水灯作为一种极具艺术效果的LED灯光效果,可以产生一种逐渐由暗变明再由明变暗的渐进式变化,能够为现代家居和公共场所提供更加温馨、舒适的氛围。

本次综合设计旨在将FPGA技术和PWM脉宽调制技术相结合,在实验六的基础上扩展,以实现呼吸流水灯灯光效果的控制。通过该课程的学习,我们将学习如何使用FPGA技术实现基于PWM脉宽调制控制LED灯的电路设计,以及如何使用Verilog语言编写控制程序,实现呼吸流水灯灯效的渐变控制。同时,我们还将学习到如何使用Xilinx VIVADO软件进行FPGA硬件电路设计和Verilog代码编写,以及在实际中如何进行调试和优化。

本课程的目标是深入了解FPGA技术和PWM脉宽调制技术的原理和应用,提高其在数字化系统设计中的实践能力和创新能力。

项目准备

1.项目预期目标

以10Hz的频率,点亮实验开发板上的发光二极管LED9~LED0,显示过程中各个点亮的发光二极管的亮度呈现出明暗变化,形似呼吸。SW0 向下系统复位,LED灯全灭。SW0向上,LED呈现呼吸状显示。

分组显示两组不同渐变周期的呼吸灯,Key0控制LED4~LED0显示, KEY1 控制LED9~LED5显示,渐变周期由SW1~SW9输入,同时数码管分别显示2组呼吸灯对应周期数。

完成呼吸灯的仿真测试,对仿真结果进行分析说明。

2.项目原理及总体实现思路

上电后不显示,在按下SW0向上时以10Hz的频率,点亮实验开发板上的发光二极管LED9~LED0,显示过程中各个点亮的发光二极管的亮度呈现出明暗变化,形似呼吸。SW0 向下系统复位,LED灯全灭。SW0向上,LED呈现呼吸状显示,分组显示两组不同渐变周期的呼吸灯,Key0控制LED4~LED0显示, KEY1 控制LED9~LED5显示,渐变周期由SW1~SW9输入二进制编码,同时数码管分别显示2组呼吸灯对应周期数。

为了让“呼吸”的效果表达的比较完美,我们把从灭到亮的时间初始设置为 1s,也就是 led 灯在 1s 的时间内完成从灭到亮的效果,同理从亮到灭也是一样的时间。通过控制 PWM 的占空比来实现 led 灯越亮的效果。我们知道同一时间段内,如果供给led灯一个脉冲信号的低电平持续的时间越长(高电平持续的时间越短),led灯就越亮,我们就是通过调整 PWM(如图1—1—1) 实现高低电平的占 空来调控 led 灯的亮度,我们取 n 个相同的时间段,然后让低电平的持续时间按照相等的 时间间隔逐渐增多,这样子我们看上去的 led 灯就会越来越亮了。

项目模块设计

1.顶层模块

(1)我给模块取名led,设置为顶层模块用于调用其他模块,所以时钟和复位信号也是必须有的,且输入有时钟和复位信号和开关0~9,以及和两个按键。

(2)模块原理图

 

(3)模块代码

受报告篇幅限制仅展示部分关键代码

//数据的输入

    input   wire            sys_clk     ,

    input   wire            sys_rst_n   ,  

    input   wire    [9:0]   sw_in       ,  

    input   wire            key_in1     ,  

    input   wire            key_in2     , 

//led的输出

output  wire   [9:0]   led_out      ;



//key1和key2 控制输入的频率

ctrl ctrl_inst

(

    .sys_clk   (sys_clk)  ,

    .sys_rst_n (sys_rst_n)  ,  

    .sw_in     (sw_in)  ,  

    .key_in1   (key_in1)  ,  

    .key_in2   (key_in2)  ,

    .data_in1  (data_in1) ,

    .data_in2  (data_in2) 

    

);

 

//开关输入的二进制数,转换为BCD码用于数码管显示

binTobcd binTobcd_inst1

( 

    .bin (data_in1),   // binary

    .bcd (bcd1)

); 

//led的输出

breath_led breath_led_inst1

(

    .sys_clk     (sys_clk  ),

    .sys_rst_n   (sys_rst_n),

    .data      (data_in1),

    .led_out     (led_out[4:0]  )

);

 

//数码管显示

seg seg_int1

(

    .sys_clk   (sys_clk)  ,

    .sys_rst_n (sys_rst_n)  ,

    .sw0       (sw_in[0])  ,

    .bcd       (bcd1)  ,

    .ge        (ge1) ,

    .shi       (shi1) ,

    .bai       (bai1) 

);

 

 

2.按键控制模块

(1)我给模块取名ctrl,设置为按键控制模块用于通过输入的开关选择控制前后的灯模块和把输入的拨码开关的数值转换和寄存,所以时钟和复位信号也是必须有的,且输入有时钟和复位信号和开关0~9,以及和两个按键。

(2)模块输入输出设计

(3)模块源代码核心代码

reg              begin_reg        ;//开始的标志,开始时10hz的频率

reg              sw0_reg          ;//sw[0]的延时一拍

reg              key_in1_reg      ;//按键1控制后面的数据

reg              key_in2_reg      ;  //按键2控制前面的数据

 

wire             sw0_flag          ; //SW[0]拨动了的标志信号

    

//最开SW0拨上去就赋值给数据初始值10hz,按下按键后跳转

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        sw0_reg <= 1'd0;

    else

        sw0_reg <= sw_in[0];

        

assign  sw0_flag = ((sw_in[0])&&(~ sw0_reg));

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        begin_reg <= 1'd0;

    else    if(key_in1 == 1'd0 ||key_in2 == 1'd0)

        begin_reg <= 1'd0;

    else    if(sw0_flag == 1'd1)

        begin_reg <= ~ begin_reg;

    else

        begin_reg <=  begin_reg;

        

// 按下按键获得对前后数据的控制权

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        key_in2_reg <= 1'd0;

    else    if(sw_in[0] == 1'd0)

        key_in2_reg <= 1'd0;

    else    if(key_in1 == 1'd0)

        key_in2_reg <= 1'd0;

    else    if(sw_in[0] == 1'd1 && key_in2 == 1'd0)

        key_in2_reg <= 1'd1;

    else

        key_in2_reg <= key_in2_reg;

        

//把数据传输给寄存器

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        data_in1 <= 9'd0;

    else    if(sw_in[0] == 1'd0)

        data_in1 <= 9'd0;

    else    if(sw_in[0] == 1'd1 && begin_reg == 1'd1)

        data_in1 <= 9'd10;

    else    if(key_in1_reg == 1'd1)

        data_in1 <= sw_in[9:1] ;

    else

        data_in1 <= data_in1;   

endmodule

3.呼吸灯模块

(1)我们给模块取名为 breath_led,同样本例我们也会用到计数器,所以时钟和复位信号也 是必须有的,且输入只有时钟和复位信号以及拨码开关的数据。我们需要控制分别两组的 led 灯进行“呼吸”就可以了,所以输出是一个名为 led_out 的输出信号。根据上面的分析设计出的 Visio 框图如图所示。

(2模块输入输出设计

 

(3)模块部分关键代码

//cnt_1ms:1ms计数器

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        cnt_1ms <=  10'd0;

    else    if((cnt_1ms == CNT_1MS_MAX) && (cnt_1us == CNT_1US_MAX))

        cnt_1ms <=  10'd0;

    else    if(cnt_1us == CNT_1US_MAX)

        cnt_1ms <=  cnt_1ms + 10'd1;

    else

        cnt_1ms <=  cnt_1ms;

//cnt_1s:1s计数器以及用1S来作为基础,通过1/data来获得新的频率让led的闪亮

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        cnt_1s  <=  10'd0;

    else    if(data == 9'd0)

        cnt_1s  <=  10'd0;

    else    if((cnt_1s == CNT_1S_MAX / data) && 

                (cnt_1ms == CNT_1MS_MAX) && (cnt_1us == CNT_1US_MAX))

        cnt_1s  <=  10'd0;

    else    if((cnt_1ms == CNT_1MS_MAX) && (cnt_1us == CNT_1US_MAX))

        cnt_1s  <=  cnt_1s + 10'd1;

    else

        cnt_1s  <=  cnt_1s;

//cnt_1s:1s计数器以及用1S来作为基础,通过1/data来获得新的频率让led的闪亮的使能信号

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        cnt_en  <=  1'b0;

    else    if(data == 9'd0)

        cnt_en  <=  1'b0;

    else    if((cnt_1s == CNT_1S_MAX / data) && 

                (cnt_1ms == CNT_1MS_MAX) && (cnt_1us == CNT_1US_MAX))

        cnt_en  <=  ~cnt_en;

    else

        cnt_en  <=  cnt_en;

//利用PWM脉宽调制呼吸流水

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        led_out <=  5'b00000;

    else    if(data == 9'd0)

        led_out <=  5'b00000;

    else    if(((cnt_en == 1'b0) && (cnt_1ms <= cnt_1s)) 

                || ((cnt_en == 1'b1) && (cnt_1ms > cnt_1s)))

        led_out <=  5'b00000;

    else

        led_out <=  5'b11111;

4.数码管显示模块

(1)我们给模块取名为 seg,同样本例我们也会用到数据手册的数码管模块的使用,所以时钟和复位信号也是必须有的,且输入只有时钟和复位信号以及转换成BCD码的拨码开关的寄存数据。我们把BCD码作为条件使用case语句,所以输出是一个名为ge,shi,bai三个数码位的输出信号。根据上面的分析设计出的 Visio 框图如图所示。

(2)模块输入输出

(3)模块核心代码:

module  seg

(

    input   wire            sys_clk     ,//系统时钟50Mhz

    input   wire            sys_rst_n   ,//key3全局复位

    input   wire            sw0         ,//sw0的拨码输入

    input   wire    [10:0]  bcd         ,//转换后的BCd码

    

    output  reg    [6:0]    ge         ,//个位的输出

    output  reg    [6:0]    shi        ,//十位的输出

    output  reg    [6:0]    bai        //百位的输出

);

//个位的输出

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        ge <= 7'b1_111_111;

    else    if(sw0 == 1'd0)

        ge <= 7'b1_111_111;

    else    case(bcd[3:0])

4'b0000:

    ge <= 7'b1_000_000;

4'b0001:   

    ge <= 7'b1_111_001;

4'b0010:   

    ge <= 7'b0_100_100;

4'b0011:   

    ge <= 7'b0_110_000;

4'b0100:   

    ge <= 7'b0_011_001;

4'b0101:   

    ge <= 7'b0_010_010;

4'b0110:   

    ge <= 7'b0_000_010;

4'b0111:   

    ge <= 7'b1_111_000;

4'b1000:   

    ge <= 7'b0_000_000;

4'b1001:   

    ge <= 7'b0_010_000;

default:    ge <= 7'b1_000_000;

endcase

 

endmodule


module  seg

(

    input   wire            sys_clk     ,//系统时钟50Mhz

    input   wire            sys_rst_n   ,//key3全局复位

    input   wire            sw0         ,//sw0的拨码输入

    input   wire    [10:0]  bcd         ,//转换后的BCd码

    

    output  reg    [6:0]    ge         ,//个位的输出

    output  reg    [6:0]    shi        ,//十位的输出

    output  reg    [6:0]    bai        //百位的输出

);

//个位的输出

always@(posedge sys_clk or negedge sys_rst_n)

    if(sys_rst_n == 1'b0)

        ge <= 7'b1_111_111;

    else    if(sw0 == 1'd0)

        ge <= 7'b1_111_111;

    else    case(bcd[3:0])

4'b0000:

    ge <= 7'b1_000_000;

4'b0001:   

    ge <= 7'b1_111_001;

4'b0010:   

    ge <= 7'b0_100_100;

4'b0011:   

    ge <= 7'b0_110_000;

4'b0100:   

    ge <= 7'b0_011_001;

4'b0101:   

    ge <= 7'b0_010_010;

4'b0110:   

    ge <= 7'b0_000_010;

4'b0111:   

    ge <= 7'b1_111_000;

4'b1000:   

    ge <= 7'b0_000_000;

4'b1001:   

    ge <= 7'b0_010_000;

default:    ge <= 7'b1_000_000;

endcase

 

endmodule



5.二进制转BCD码模块

(1)利用移三位加法原理设计一个二进制转换BCD码的模块,以输入为8位二进制数10100101(十进制165)为例,8位二进制数表示范围为0~255,BCD码需要表示百位、十位、个位:
送入最高位1;
送入第二位0得到10;
送入第三位1得到101 ,因为101 > 100,修正:101 + 011 = 1000;
送入第四位0得到1_0000;
送入第五位0得到10_0000;
送入第六位1得到100_0001;
送入第七位0得到1000_0010,1000 > 100,修正:1000 + 011 = 1011;
送入第八位1得到1_0110_0101,得到输出结果为0001_0110_0101(十进制165)。

我们给模块取名binTobcd,输入寄存的二进制数据,输出BCD码,如图所示

(2)模块核心代码如下

//移3进位的算法

always @(*) begin

ones = 4'd0;

tens = 4'd0;

hundreds = 3'd0;



for(i = 8; i >= 0; i = i - 1) begin

if (ones >= 4'd5) ones = ones + 4'd3;

if (tens >= 4'd5) tens = tens + 4'd3;

if (hundreds >= 4'd5) hundreds = hundreds + 4'd3;

hundreds = {hundreds[1:0],tens[3]};

tens  = {tens[2:0],ones[3]};

ones  = {ones[2:0],bin[i]};

end

end

 

项目测试

1.仿真测试

仿真部分不显示数码管状态波形如下

​​​​​​​

信号分析

sys_clk:时钟信号        sys_rst_n:使能端

sw_in:拨码控制输出信号频率

Key_in1、Key_in2:分别控制led0~led4和led5~led9

led_out:显示led的波形信息

 

观察波形2

Key_in1出现置0led_out波形发生了频率和占空比的变化且led[9]~led[5]不显示波形

 

观察波形图3,在黄线处,sw_in[8]和sw_in[6]由低电平变为高电平,led_out波形发生了频率和占空比的变化体现了拨码信号对输出波形的控制占空比的变化也使其呈现呼吸状变化

2.实物测试

开发板:bassy3

按下key2,控制输入7hz的频率,使右边5个led灯亮此时灯亮的频率由拨码开关控制

 

 

 ​​​​​​​

受图片形式限制,无法展示亮灯频率的变化

五、项目总结

1.选题思考与过程反思

此次综合实验设计,最初是打算使用VGA进行贪吃蛇游戏的设计,但在实现老师的要求“在VGA上显示姓名学号及校徽”时遇到的了困难,虽然可以很好的实现图片的显示,但在文字的显示这一问题上一直未能解决。于是在验收前几天选择了一个更简单易通过的呼吸灯设计。

回望整个综合实验的设计过程,时间大部分花在代码的调试上,无论是最初的VGA图片和文字显示还是呼吸灯中如何更新颖的呈现亮灯过程。其次便是查阅资料的过程,任何一个环节都需要自己去主动的查找有关的资料并进行知识的检索和学习,然后运用到自己的项目设计上。总之,此次综合设计实验让我收获很大,虽然最终是以难度较低的呼吸灯进行验收,但在课程结束后的课余时间,我依旧会进行VGA项目的学习和设计。

2.设计的具体完成情况详细描述

开始上电后全部都进入待机状态,然后按上开关0过后,然后显示10hz并呼吸灯以10hz频率开始闪烁。然后按下key 0的时候获得后面三位的控制权,输入频率可以改变呼吸灯的闪亮频率和数码管上的具体数值,然后按下key1的时候可以获得前面的控制权。可以对前面的呼吸灯的频率改变和改变数码管的具体数值,然后拨上。开关临时全部又进入了待机状态。完成了所有要求的实验步骤和功能。

3.项目可改进之处

经过自己在验收后的思考总结,发现一些可以改进的地方。

(1)实验难度的提升:最初我认为自己无法在最后一次课上完成综合实验设计的验收(VGA相关设计),于是换了一个更简单的实验。在验收才觉得自己完全可以结合VGA再次进行功能的拓展。

(2)针对本项目呼吸灯的设置,频率的调节方法,用了一个比较复杂的数据直接控制最大计数范围的方法,但是还应该可以有更简便的方法。在代码的过程中,使用了比较多的寄存量。在后面代码的简化和优化过程中,都可以对寄存量以及代码的减少。

(3)本功能使用了较多的模块,目前也只想到了这个方法如果有更好的方法,可以学习加以改进。

   

4.项目设计心得

(1)本次FPGA综合实验设计“基于PWM脉宽调制的呼吸流水灯设计”让我收获了很多。在整个项目的过程中,我主要从以下几个方面得到了很大的提升和收获。

首先是理论知识方面。在本次项目中,我学习了PWM脉宽调制技术和FPGA硬件设计的原理和应用,包括FPGA电路设计和Verilog语言编程等方面的知识。通过这些理论知识的学习,同时前期对于VGA相关实验的探索对FPGA开发有了进一步的理解不断的探索也让我更加深入地理解了数字电路设计和控制的原理和方法,并且对FPGA技术的实际应用有了更加清晰的认识。

其次是实践能力方面。在项目的过程中,我亲手设计和实现了一个基于PWM脉宽调制的呼吸流水灯控制系统,从FPGA电路设计到Verilog语言程序编写、仿真调试和最终实际硬件测试,每一个环节都亲身参与了并且深刻理解了每一个步骤的重要性以及设计中需要注意的细节。这些实践过程让我更加自信,并且增强了我解决实际问题的能力。

最后是和沟通能力方面。在这个项目中,我和班上的同学进行有效的合作和沟通,进行了很多有效的探索。通过交流和合作,我们更好地理解了各自的专业领域和思路,相互帮助,一起攻克问题和难题,使得整个项目可以有条不紊地完成。虽然最终未能完成使用VGA进行文字的显示这对我的团队合作和沟通能力提升非常有帮助。

总结来说,本次FPGA综合实验设计“基于PWM脉宽调制的呼吸流水灯设计”让我受益匪浅,虽然最后难度相对VGA相关开发偏低但依旧加深了我的理论知识和实践经验,让我更加熟悉了团队合作和沟通的重要性和方法。这是我成长和提升的重要机会,也是一个非常有意义的项目体验。

参考文献

  1. 黄继业潘松.EDA技术实用教程--Verilog HDL [M]:第六版.北京科学出版社,2018.8.
  2. 何宾.Xilinx FPGA权威设计指南[M].北京电子工业出版社,2015.2.
  3. 王明.脉冲宽度调制的原理和实现[EB].http://www.doc88.com/p-54859547918569.html,2023-04-23.
  4. 张亮.数字电路设计与Verilog HDL[M].北京:人民邮电出版社,2000.

2023-06-15

猜你喜欢

转载自blog.csdn.net/m0_64198455/article/details/131548504