ZYNQ开发板LED灯的闪烁呼吸以及按键控制(基于FPGA)

1 新建工程

1、新建一个工程,芯片类型选择如下,ZYNQ7010。
在这里插入图片描述
2、源文件已经在原子哥代码的基础上改好,新建工程时直接勾选增加源文件,分两次分别增加源文件和约束文件。
在这里插入图片描述3、程序文件结构如下图,其中srcs文件夹包含程序的源码.v以及约束文件。jou与log为日志文件,每次都会新生成一个,可以进行删除。
在这里插入图片描述

4 编译完成后打开硬件管理器在这里插入图片描述
5 连接单板烧录程序,进行连接下载操作,此时程序并未固化到单板上,上电或者复位会导致程序丢失。ZYNQ是通过PS端启动PL端,因此如果需要固化有更加复杂的操作。
在这里插入图片描述

2 程序源码

文件层次结构如下图所示,包含一个顶层文件和4个子文件,这种结构化的层次是通过对模块进行例化自动进行的。第一个顶层文件定义了主模块的输入输出IO,第二个为例化后的按键检测模块,第三个为例化后的LED控制模块,第四个为例化后的LED闪烁模块,第五个为例化后的呼吸灯模块。子模块的名字为例化的模块名字。有点类似C语言编程的结构,在C语言中是写一个main文件,然后定义各种模块的子文件,通过include头文件还有函数引用进行包含调用。FPGA是通过例化进行调用。
在这里插入图片描述

2.1 LED顶层模块

top_key_led模块定义了一个model,包含全部的输入输出信号,完整的源代码如下:


module top_key_led(
    input          sys_clk ,
    input          sys_rst_n ,
    input          key ,
    output  [1:0]  led12,         //LED灯    
    output         led4 ,         //按键灯
    output         led3           //呼吸灯
);

//wire define
wire key_value ;
wire key_flag ;

//*****************************************************
//**                    main code
//*****************************************************

//例化按键消抖模块
key_debounce  u_key_debounce(
    .sys_clk    (sys_clk),
    .sys_rst_n  (sys_rst_n),

    .key        (key),
    .key_value  (key_value),
    .key_flag   (key_flag)
    );

//例化蜂鸣器控制模块
led4_control  u_led4_control(
    .sys_clk    (sys_clk),
    .sys_rst_n  (sys_rst_n),

    .key_value  (key_value),
    .key_flag   (key_flag),
    .led4       (led4)
    );

//呼吸灯模块
breath_led u_breath_led(
    .sys_clk   (sys_clk),    
    .sys_rst_n (sys_rst_n),  
    .led3       (led3)         
);

//跑马灯模块
led_twinkle u_led_twinkle(
    .sys_clk     (sys_clk),    
    .sys_rst_n   (sys_rst_n),  
    .led12       (led12)         
);


endmodule

2.2 按键检测模块

第一个子模块按键检测模块代码如下,程序设计了一个20mS的消抖操作。

module key_debounce(
    input        sys_clk ,
    input        sys_rst_n ,

    input        key ,         //外部输入的按键值
    output  reg  key_value ,   //消抖后的按键值
    output  reg  key_flag      //消抖后的按键值的效标志
);

//reg define
reg [19:0] cnt ;
reg        key_reg ;
parameter  DELAYTIME=20'd100_0000;//定义消抖时间
//*****************************************************
//**                    main code
//*****************************************************

//按键值消抖
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        cnt <= 20'd0;             //系统复位时赋值0
        key_reg <= 1'b1;
    end
    else begin
        key_reg <= key;           //将按键值延迟一拍
        if(key_reg != key) begin  //如果当前按键值和前一拍的按键值不一样,即按键被按下或松开
            cnt <= DELAYTIME;  //则将计数器置为20'd100_0000,
                                  //即延时100_0000 * 20ns(1s/50MHz) = 20ms
        end
        else begin                //如果当前按键值和前一个按键值一样,即按键没有发生变化
            if(cnt > 20'd0)       //则计数器递减到0
                cnt <= cnt - 1'b1;  
            else
                cnt <= 20'd0;
        end
    end
end

//将消抖后的最终的按键值送出去
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        key_value <= 1'b1;
        key_flag  <= 1'b0;
    end
	//在计数器递减到1时送出按键值
    else if(cnt == 20'd1) begin
		key_value <= key;
		key_flag  <= 1'b1;
        end
    else begin
		key_value <= key_value;
		key_flag  <= 1'b0;
    end
end

endmodule

2.3 按键控制LED闪烁模块

第二个子模块控制LED灯闪烁的代码如下,当检测到按键时翻转LED灯的状态。

module led4_control(
    input        sys_clk,
    input        sys_rst_n,

    input        key_value,
    input        key_flag,
    output  reg  led4
    );

//*****************************************************
//**                    main code
//*****************************************************

//每次按键按下时,就翻转蜂鸣器的状态
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        led4 <= 1'b1;
    else if(key_flag && (key_value == 1'b0))
        led4 <= ~led4;
end

endmodule

2.4 呼吸灯子模块

第三个子模块呼吸灯子模块的源代码如下,呼吸灯的频率为1KHz,通过调整占空比来控制LED的呼吸亮灭。

module breath_led(
    input   sys_clk   ,  //时钟信号50Mhz
    input   sys_rst_n ,  //复位信号

    output  led3          //呼吸灯
);

//reg define
reg  [15:0]  period_cnt ;   //周期计数器频率:1khz 周期:1ms  计数值:1ms/20ns=50000
reg  [15:0]  duty_cycle ;   //占空比数值
reg          inc_dec_flag ; //0 递增  1 递减

//*****************************************************
//**                  main code
//*****************************************************

//根据占空比和计数值之间的大小关系来输出LED
assign   led3 = (period_cnt >= duty_cycle) ?  1'b1  :  1'b0;

//周期计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        period_cnt <= 16'd0;
    else if(period_cnt == 16'd50000)
        period_cnt <= 16'd0;
    else
        period_cnt <= period_cnt + 1'b1;
end

//在周期计数器的节拍下递增或递减占空比
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        duty_cycle   <= 16'd0;
        inc_dec_flag <= 1'b0;
    end
    else begin
        if(period_cnt == 16'd50000) begin    //计满1ms
            if(inc_dec_flag == 1'b0) begin   //占空比递增状态
                if(duty_cycle == 16'd50000)  //如果占空比已递增至最大
                    inc_dec_flag <= 1'b1;    //则占空比开始递减
                else                         //否则占空比以25为单位递增
                    duty_cycle <= duty_cycle + 16'd25;
            end
            else begin                       //占空比递减状态
                if(duty_cycle == 16'd0)      //如果占空比已递减至0
                    inc_dec_flag <= 1'b0;    //则占空比开始递增
                else                         //否则占空比以25为单位递减
                    duty_cycle <= duty_cycle - 16'd25;
            end
        end
    end
end

endmodule

2.5 LED闪烁模块

第四个子模块LED闪烁模块每隔0.5S跳转两个LED灯,类似一个跑马灯。源代码如下

module led_twinkle(
    input          sys_clk  ,  //系统时钟
    input          sys_rst_n,  //系统复位,低电平有效

    output  [1:0]  led12         //LED灯
);

//reg define
reg  [25:0]  cnt ;

//*****************************************************
//**                    main code
//*****************************************************

//对计数器的值进行判断,以输出LED的状态
assign led12 = (cnt < 26'd2500_0000) ? 2'b01 : 2'b10 ;

//计数器在0~5000_000之间进行计数
always @ (posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt <= 26'd0;
    else if(cnt < 26'd5000_0000)
        cnt <= cnt + 1'b1;
    else
        cnt <= 26'd0;
end

endmodule

2.6 约束文件

约束文件为管脚的约束,对信号的脚位进行定义电平定义等,源代码如下,移植时需要根据单板的不同进行修改。

set_property -dict {PACKAGE_PIN V12 IOSTANDARD LVCMOS33} [get_ports {led3}]
set_property -dict {PACKAGE_PIN T12 IOSTANDARD LVCMOS33} [get_ports {led12[0]}]
set_property -dict {PACKAGE_PIN U12 IOSTANDARD LVCMOS33} [get_ports {led12[1]}]
set_property -dict {PACKAGE_PIN W13 IOSTANDARD LVCMOS33} [get_ports {led4}]
set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33} [get_ports {key}]
set_property -dict {PACKAGE_PIN K17 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN E17 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

当然也可以直接使用可视化界面对其进行更改,下图为IO planing的配置界面,修改完后软件会自动生成一个约束文件。
在这里插入图片描述

3 生成原理图

生成的RTL原理图如下所示,通过例化后,这种模块间的相互关系就表现非常清晰,点击模块上的+号可以对模块展开看下一层次的原理图。这里也是实际真实的硬件。
在这里插入图片描述

4 实际效果

实际运行效果和程序设计相同,这里需要了解的是这四个模块程序是并行进行的,而ARM程序是串行进行,这是一个很大的差异,也就是使用FPGA可以进行数据的并行处理。对于摄像头摄像视频解码并行数据处理非常有优势。
在这里插入图片描述

5 固化到QSPI Flash中

1、上述程序是掉电丢失,如果要固化到Flash中必须创建IP核,ZYNQ7最小系统
在这里插入图片描述2、勾选SPI Flash,配置内存,其它的都不勾选
在这里插入图片描述
创建输出产品以及HDL wrapper
在这里插入图片描述
此时上文中的top_led_key文件就不需要了,需要在生成的HDL文件中,增加输入输出接口以及增加模块例化ed_flash_wrapper。


`timescale 1 ps / 1 ps

module led_flash_wrapper
   (DDR_addr,
    DDR_ba,
    DDR_cas_n,
    DDR_ck_n,
    DDR_ck_p,
    DDR_cke,
    DDR_cs_n,
    DDR_dm,
    DDR_dq,
    DDR_dqs_n,
    DDR_dqs_p,
    DDR_odt,
    DDR_ras_n,
    DDR_reset_n,
    DDR_we_n,
    FIXED_IO_ddr_vrn,
    FIXED_IO_ddr_vrp,
    FIXED_IO_mio,
    FIXED_IO_ps_clk,
    FIXED_IO_ps_porb,
    FIXED_IO_ps_srstb,
    sys_clk ,  //输入时钟
    sys_rst_n ,//复位信号
    key ,      //按键信号
    led12,     //跑马灯
    led4,      //按键灯
    led3 );	   //呼吸灯
  input          sys_clk;       //输入时钟
  input          sys_rst_n;     //复位信号
  input          key;           //按键信号
  output   [1:0] led12;         //LED灯    
  output         led4;          //按键灯
  output         led3;          //呼吸灯     
  inout [14:0]DDR_addr;
  inout [2:0]DDR_ba;
  inout DDR_cas_n;
  inout DDR_ck_n;
  inout DDR_ck_p;
  inout DDR_cke;
  inout DDR_cs_n;
  inout [3:0]DDR_dm;
  inout [31:0]DDR_dq;
  inout [3:0]DDR_dqs_n;
  inout [3:0]DDR_dqs_p;
  inout DDR_odt;
  inout DDR_ras_n;
  inout DDR_reset_n;
  inout DDR_we_n;
  inout FIXED_IO_ddr_vrn;
  inout FIXED_IO_ddr_vrp;
  inout [53:0]FIXED_IO_mio;
  inout FIXED_IO_ps_clk;
  inout FIXED_IO_ps_porb;
  inout FIXED_IO_ps_srstb;

  wire [14:0]DDR_addr;
  wire [2:0]DDR_ba;
  wire DDR_cas_n;
  wire DDR_ck_n;
  wire DDR_ck_p;
  wire DDR_cke;
  wire DDR_cs_n;
  wire [3:0]DDR_dm;
  wire [31:0]DDR_dq;
  wire [3:0]DDR_dqs_n;
  wire [3:0]DDR_dqs_p;
  wire DDR_odt;
  wire DDR_ras_n;
  wire DDR_reset_n;
  wire DDR_we_n;
  wire FIXED_IO_ddr_vrn;
  wire FIXED_IO_ddr_vrp;
  wire [53:0]FIXED_IO_mio;
  wire FIXED_IO_ps_clk;
  wire FIXED_IO_ps_porb;
  wire FIXED_IO_ps_srstb;
  wire key_value ;    // 按键值
  wire key_flag ;     //按键标志位
  
  led_flash led_flash_i
       (.DDR_addr(DDR_addr),
        .DDR_ba(DDR_ba),
        .DDR_cas_n(DDR_cas_n),
        .DDR_ck_n(DDR_ck_n),
        .DDR_ck_p(DDR_ck_p),
        .DDR_cke(DDR_cke),
        .DDR_cs_n(DDR_cs_n),
        .DDR_dm(DDR_dm),
        .DDR_dq(DDR_dq),
        .DDR_dqs_n(DDR_dqs_n),
        .DDR_dqs_p(DDR_dqs_p),
        .DDR_odt(DDR_odt),
        .DDR_ras_n(DDR_ras_n),
        .DDR_reset_n(DDR_reset_n),
        .DDR_we_n(DDR_we_n),
        .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
        .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
        .FIXED_IO_mio(FIXED_IO_mio),
        .FIXED_IO_ps_clk(FIXED_IO_ps_clk),
        .FIXED_IO_ps_porb(FIXED_IO_ps_porb),
        .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb));
//例化按键检测模块
    key_debounce  u_key_debounce(
        .sys_clk    (sys_clk),
        .sys_rst_n  (sys_rst_n),
    
        .key        (key),
        .key_value  (key_value),
        .key_flag   (key_flag)
        );

//例化LED控制模块
    led4_control  u_led4_control(
        .sys_clk    (sys_clk),
        .sys_rst_n  (sys_rst_n),
    
        .key_value  (key_value),
        .key_flag   (key_flag),
        .led4       (led4)
        );

//例化呼吸灯模块
    breath_led u_breath_led(
        .sys_clk   (sys_clk),    
        .sys_rst_n (sys_rst_n),  
        .led3       (led3)         
    );

//例化跑马灯模块
    led_twinkle u_led_twinkle(
        .sys_clk     (sys_clk),    
        .sys_rst_n   (sys_rst_n),  
        .led12       (led12)         
    );

endmodule

3、生成
的原理图会在原基础上增加ZYNQ最小系统,但这部分和原FPGA的其它部分没有关联。
在这里插入图片描述
4、导出硬件(包含比特流),加载SDK
在这里插入图片描述
5、在打开SDK后,新建应用工程,创建第一阶段BootLoader(FSBL),板级支持包BSP,BSP为PS侧的FSBL与PL侧的沟通桥梁。SDK软件好像是基于eclispe开发的。
在这里插入图片描述
6、创建BIN镜像文件,需要同时添加比特.bit文件和FSBL.elf文件,需要将Boot拨码开关拨到对应正确的位置,这个和ARM相似。
在这里插入图片描述7、对应的原理图如下,MIO4和MIO5可以配置,其余Boot配置脚全部拉地处理了。烧录时配置为JTAG模式,即全0,烧录完后配置为QSPI启动,即为10。在这里插入图片描述
8、选择烧写Flash。在这里插入图片描述
烧录完成后的信息如下,将拨码开关拨到QSPI启动后按复位即可,后续再次上电程序也不会丢失。
在这里插入图片描述
SDK软件的program FPGA还可以烧录bit文件,这样就无需打开Vivado了。
在这里插入图片描述
bit文件默认在led_flash.runs-》impl_1文件夹中

6 固化到SD卡中

在IP核中双击打开配置菜单,勾选SD卡,这里特别注意SD卡的电平,需要和原理图一致,如果不一致会导致无法从SD卡启动。BANK0和BANK1均为PS端端口,也就是ZYNQ内核的配置和PL无关,不涉及FPGA部分端口的占用。
在这里插入图片描述
然后保存,此时前面的Wrapper.v文件会发生变化。需要重新添加IO口和例化模块。这里我有试过,增加QSPI和SD卡,最终的wrapper顶层文件是一样的。
生成.bin文件与QSPI方法相同,最后将生成的BIN文件拷贝到SD卡中(SD卡需要格式化为FAT32)。将BOOT跳到SD卡启动的模式。将SD卡插入后上电。单板即可从SD卡进行启动。

7 遇到的问题

SD卡方式启动时,如果频繁(几秒钟间隔)给开发板上电,存在较大的概率单板不能启动,更换SD卡后也不能解决,但是如果断电10分钟以上时,单板可以100%启动,查阅了相关资料,可能和单板上电时序设计有关,频繁上电会导致电容上的残压不能完全释放,导致上电初始时刻ZYNQ核心已经正常工作,但是SD卡尚未正常工作,导致启动时ZYNQ无法读到SD卡加载程序,启动失败。特别大容量的TF卡不能格式化为FAT32(比如256G),因此也无法被ZYNQ识别。
在这里插入图片描述

8 其它小技巧

配置IP核时点击右侧的summary report 可以生成IP配置报告
在这里插入图片描述
生成的报告(部分)
在这里插入图片描述
在写程序时要多用例化,这样层次和结构会非常好,也方便查找问题和移植。

9 程序仿真

9.1仿真文件

编写仿真所需的tb文件,这里的文件和top文件比较类似,将输入信号定义为reg信号,将输出信号定义为wire。(定义错误会导致软件报错)赋初值,这里定义0.1s翻转一次key。完成后,系统会自动将tb文件识别为仿真顶层文件。如果波形中出现蓝色的线条,说明对应的值是不定的,需要进行初始化。

`timescale 1ns / 1ps

module tb_key_led( );
//输入
reg           sys_clk;
reg           sys_rst_n;
reg           key;   


//输出
wire           key_value;
wire           key_flag;
wire[1:0]      led12;
wire           led3;
wire           led4;

//信号初始化
initial begin
    sys_clk = 1'b0;
    sys_rst_n = 1'b0;
    #200
    sys_rst_n = 1'b1;
    key=1'b1;
end

//生成时钟
always #10 sys_clk = ~sys_clk;
always #100000000 key=~key;//0.1s翻转一次KEY,模拟按键操作

//例化待测设计
key_debounce  u_key_debounce(
    .sys_clk    (sys_clk),
    .sys_rst_n  (sys_rst_n),

    .key        (key),
    .key_value  (key_value),
    .key_flag   (key_flag)
    );

//例化蜂鸣器控制模块
led4_control  u_led4_control(
    .sys_clk    (sys_clk),
    .sys_rst_n  (sys_rst_n),

    .key_value  (key_value),
    .key_flag   (key_flag),
    .led4       (led4)
    );

//呼吸灯模块
breath_led u_breath_led(
    .sys_clk   (sys_clk),    
    .sys_rst_n (sys_rst_n),  
    .led3       (led3)         
);

//跑马灯模块
led_twinkle u_led_twinkle(
    .sys_clk     (sys_clk),    
    .sys_rst_n   (sys_rst_n),  
    .led12       (led12)         
);      
    
endmodule

9.2 仿真结果

如下图所示,0.1s按键key翻转一次,在20ms后key_value值发生改变,说明消抖完成,并产生了相应的key_flag信号。同时led4灯状态发生改变。仿真结果与程序代码相符合。
在这里插入图片描述
初始时刻,在200ns处退出复位,这和程序的设计也是一样的
在这里插入图片描述

10 LED实验

10.1 实验问题

后续我自行画了一块ZYNQ的扩展板,上面含数码管显示等电路。扩展板和ZYNQ主板通过40PIN排线进行连接。在开发过程中数码管显示不正常,当数字为非5和非6的其它数字时,始终会显示小数点,怎么改程序都没用。字符段选的部分代码如下。

//控制数码管段选信号,显示字符
always @ (posedge dri_clk or negedge rst_n) begin
    if (!rst_n)
        seg_led <= 8'hc0;
    else begin
        case (num_disp)
            4'd0 : seg_led <= {8'b00111111}; //显示数字 0
            4'd1 : seg_led <= {8'b00000110}; //显示数字 1
            4'd2 : seg_led <= {8'b01011011}; //显示数字 2
            4'd3 : seg_led <= {8'b01001111}; //显示数字 3
            4'd4 : seg_led <= {8'b01100110}; //显示数字 4
            4'd5 : seg_led <= {8'b01101101}; //显示数字 5
            4'd6 : seg_led <= {8'b01111101}; //显示数字 6
            4'd7 : seg_led <= {8'b00000111}; //显示数字 7
            4'd8 : seg_led <= {8'b01111111}; //显示数字 8
            4'd9 : seg_led <= {8'b01101111}; //显示数字 9
            4'd10: seg_led <= 8'b00000000;   //不显示任何字符
            default: 
                   seg_led <=  8'b00000000;
        endcase
    end
end

endmodule 

我仔细思考了一下,数字5和数字6和其它数字的区别是,B那一竖杠不显示(位置如下图),难道是这个和小数点同亮同灭?
在这里插入图片描述
为了验证猜想,我将所有段码改为了8’b00000010,如果硬件正常,那么只会显示右上角那一竖杠。如果硬件不正常,那么同时会显示小数点。

8'b00000010

烧入程序后,显示如下,验证了我的想法。就是开通B时,同步会点亮DP。
在这里插入图片描述
电路是经过缓冲器再到达数码管的,于是我怀疑是74LVC244缓冲芯片的问题。

在这里插入图片描述
拔掉这个自己绘制打样的扩展板,给ZYNQ主板烧录上电,测量相应的引脚并无异常,B脚有电压而DP点没有电压。插上扩展板后,发现B脚和DP脚均有电压!确定是芯片存在问题。
于是将芯片拆下来,换一个新的芯片,没想到TSOP的芯片是如此的难焊接,我成功的把单板的铜皮给干废了。用半残的单板重新实验,程序正常烧录后,DP不再误显示(DP的铜皮没有被破坏),遗憾的是,铜皮废掉的段位也不显示了。
在这里插入图片描述

10.2 经验总结

1、自己制作的单板,千万不要用类似TSOP这种16脚的小封装,非常难焊接,且容易连锡;

2、尽量不要用烙铁焊接小封装器件,很容易把铜皮搞挂,用热风枪吹上去;

3、多分析实验现象,软件硬件都要考虑,不要再一颗树上吊死;

4、尽量用成熟的单板验证完方案后,再上自制的单板,软件和硬件至少要确认一个是好的。

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值