2024年寒假练 - 用attice MXO2的小脚丫FPGA核心板制作秒表
该项目使用了Lattice MXO2的小脚丫FPGA核心板,实现了秒表的设计,它的主要功能为:实现开始暂停增量的秒表,以10hz从0.0-9.9的秒表实现。
标签
FPGA
数字逻辑
开发板
冲向天空的猪
更新2024-04-01
92

2024年寒假练 - 小脚丫FPGA核心板制作具有启动、停止、递增和清除功能的秒表

一、任务内容

通过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。

使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。

秒表使用四个按钮输入:开始、停止、增量和清除(重置)。 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。


二、内容分析

配置时钟,以适当的10HZ速率驱动秒表的计数。

编写代码来处理按键输入。使用适当的逻辑来检测按键按下和释放事件,并根据按键类型执行相应的操作。按键做消抖处理

实现一个计数器,用于记录秒表的当前值。计数器的值应以0.1秒为单位递增,并在达到9.9秒时从0开始计数。

在数码管上显示秒表的值。将计数器的值转换为适当的七段显示器编码,并将其输出到数码管引脚上。

根据按键输入,控制秒表的功能。当按下开始按钮时,启动计数器并开始递增。当按下停止按钮时,停止计数器递增但保持显示当前计数值。按下增量按钮时,增加显示值。按下复位按钮时,将计数器重置为零。

下面几点我们要考虑的:

确保控制时钟的稳定性和准确性,以保证秒表的计时精度。

•      处理按键输入的反弹问题,以避免意外的多次触发。

•      适当地处理按钮长按的情况,以确保增量按钮的功能按需增加。

•      考虑并处理计数器溢出的情况,以确保秒表能够正确地翻转。

代码编写

这里首先我们先通过电子森林,关于小脚丫的一些底层代码进行使用。

1.按键消抖

module debounce (clk,rst,key,key_pulse);

 

        parameter       N  =  3;                      //要消除的按键的数量

 

       input             clk;

        input             rst;

        input      [N-1:0]   key;                        //输入的按键                                  

       output  [N-1:0]   key_pulse;                  //按键动作产生的脉冲      

 

        reg     [N-1:0]   key_rst_pre;                //定义一个寄存器型变量存储上一个触发时的按键值

        reg     [N-1:0]   key_rst;                    //定义一个寄存器变量储存储当前时刻触发的按键值

 

        wire    [N-1:0]   key_edge;                   //检测到按键由高到低变化是产生一个高脉冲

 

        //利用非阻塞赋值特点,将两个时钟触发时按键状态存储在两个寄存器变量中

        always @(posedge clk  or  negedge rst)

          begin

             if (!rst) begin

                 key_rst <= {N{1'b1}};                //初始化时给key_rst赋值全为1,{}中表示N个1

                 key_rst_pre <= {N{1'b1}};

             end

             else begin

                 key_rst <= key;                     //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre

                 key_rst_pre <= key_rst;             //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值

             end   

           end

 

        assign  key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平

 

        reg  [17:0]        cnt;                       //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器    

 

        //产生20ms延时,当检测到key_edge有效是计数器清零开始计数

        always @(posedge clk or negedge rst)

           begin

             if(!rst)

                cnt <= 18'h0;

             else if(key_edge)

                cnt <= 18'h0;

             else

                cnt <= cnt + 1'h1;

             end 

 

        reg     [N-1:0]   key_sec_pre;                //延时后检测电平寄存器变量

        reg     [N-1:0]   key_sec;                   

 

 

        //延时后检测key,如果按键状态变低产生一个时钟的高脉冲。如果按键状态是高的话说明按键无效

        always @(posedge clk  or  negedge rst)

          begin

             if (!rst)

                 key_sec <= {N{1'b1}};               

             else if (cnt==18'h3ffff)

                 key_sec <= key; 

          end

       always @(posedge clk  or  negedge rst)

          begin

             if (!rst)

                 key_sec_pre <= {N{1'b1}};

             else                  

                 key_sec_pre <= key_sec;            

         end     

       assign  key_pulse = key_sec_pre & (~key_sec);    

 

endmodule


这里关于按键消除的数量,我是通过询问GPT将N改为3,减少了我们主程序代码的数量。

根据GPT的回答,我很快的将实例代码进行修改,让代码更符合自己使用。

3.12MHZ分频代码

这里也是根据电子森林的任意整数分频,来实现我们对10hz的分频。

module divide (     clk,rst_n,clkout);

 

        input      clk,rst_n;                       //输入信号,其中clk连接到FPGA的C1脚,频率为12MHz

        output    clkout;                          //输出信号,可以连接到LED观察分频的时钟

 

        //parameter是verilog里常数语句

       parameter     WIDTH   = 24;             //计数器的位数,计数的最大值为 2**WIDTH-1

       parameter     N     = 12_000_000;             //分频系数,请确保 N < 2**WIDTH-1,否则计数会溢出

 

       reg [WIDTH-1:0]  cnt_p,cnt_n;     //cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器

       reg                clk_p,clk_n;     //clk_p为上升沿触发时分频时钟,clk_n为下降沿触发时分频时钟

 

       //上升沿触发时计数器的控制

       always @ (posedge clk or negedge rst_n )         //posedge和negedge是verilog表示信号上升沿和下降沿

                                                         //当clk上升沿来临或者rst_n变低的时候执行一次always里的语句

              begin

                     if(!rst_n)

                            cnt_p<=0;

                     else if (cnt_p==(N-1))

                            cnt_p<=0;

                     else cnt_p<=cnt_p+1;             //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器

              end

 

         //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%

         always @ (posedge clk or negedge rst_n)

              begin

                     if(!rst_n)

                            clk_p<=0;

                     else if (cnt_p<(N>>1))          //N>>1表示右移一位,相当于除以2去掉余数

                            clk_p<=0;

                     else

                            clk_p<=1;               //得到的分频时钟正周期比负周期多一个clk时钟

              end

 

        //下降沿触发时计数器的控制           

       always @ (negedge clk or negedge rst_n)

              begin

                     if(!rst_n)

                            cnt_n<=0;

                     else if (cnt_n==(N-1))

                            cnt_n<=0;

                     else cnt_n<=cnt_n+1;

              end

 

        //下降沿触发的分频时钟输出,和clk_p相差半个时钟

       always @ (negedge clk)

              begin

                     if(!rst_n)

                            clk_n<=0;

                     else if (cnt_n<(N>>1)) 

                            clk_n<=0;

                     else

                            clk_n<=1;                //得到的分频时钟正周期比负周期多一个clk时钟

              end

 

        assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p;      //条件判断表达式

                                                                    //当N=1时,直接输出clk

                                                                    //当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p

                                                                    //当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。正周期多所以是相与

Endmodule

       这里我们直接传入时间变量,和分频得出的时间,就能得到我们想要的10hz的频率。

4.主程序代码

这里根据电子森林的数码管显示代码进行修改。

module counter

(

    clk,       // 时钟

    rst,       // 复位

    increase,  // 增量

    start,     // 开始

    stop,      // 停止

    seg_led_1, // 数码管1

    seg_led_2  // 数码管2

);

 

    input clk, rst;

    input start, stop, increase;

    output [8:0] seg_led_1, seg_led_2;

 

    wire clk10h;             // 10Hz 时钟

    wire start_pulse, stop_pulse, increase_pulse; // 按键消抖后信号

    reg start_flag, stop_flag, increase_flag;       // 按键标志位

    reg [6:0] seg [9:0];

    reg [3:0] cnt_ge;        // 个位

    reg [3:0] cnt_shi;       // 十位

    reg increased;

 

    initial begin

        seg[0] = 7'h3f; // 0

        seg[1] = 7'h06; // 1

        seg[2] = 7'h5b; // 2

        seg[3] = 7'h4f; // 3

        seg[4] = 7'h66; // 4

        seg[5] = 7'h6d; // 5

        seg[6] = 7'h7d; // 6

        seg[7] = 7'h07; // 7

        seg[8] = 7'h7f; // 8

        seg[9] = 7'h6f; // 9

    end

 

    // 开始按键进行消抖

    debounce U2 (

        .clk(clk),

        .rst(rst),

        .key({start, stop, increase}),

        .key_pulse({start_pulse, stop_pulse, increase_pulse})

    );

 

    // 用于分出一个 10Hz 的频率

    divide #(.WIDTH(32), .N(1200000)) U1 (

        .clk(clk),

        .rst_n(rst),

        .clkout(clk10h)

    );

 

    // 按键动作标志信号产生

    always @ (posedge clk10h or posedge start_pulse or posedge stop_pulse or posedge increase_pulse or negedge rst) begin

        if(increase_flag==1)begin

         increase_flag<=0;

         end

        if (!rst) begin

            start_flag <= 1'b0;

            stop_flag <= 1'b0;

            increase_flag <= 1'b0;

        end else if (start_pulse) begin

                start_flag <= 1'b1;

                stop_flag <= 1'b0;

                increase_flag  <= 1'b0;

            end else if (stop_pulse) begin

                stop_flag <= 1'b1;

                start_flag <= 0;

                increase_flag  <= 1'b0;

            end else if (increase_pulse) begin

                increase_flag <= 1'b1;

            end

    end

 

    // 0.0-99.9 秒的计时开始

    always @ (posedge clk10h or negedge rst) begin

        if (!rst) begin

            cnt_ge <= 4'd0;

            cnt_shi <= 4'd0;

        end

            else if (stop_flag == 1 && increase_flag==1) begin

                cnt_ge <= cnt_ge + 1;

             if (cnt_shi == 9 && cnt_ge == 9) begin

                cnt_shi <= 4'd0;

                cnt_ge <= 4'd0;

            end else if (cnt_ge == 9) begin

                cnt_ge <= 4'd0;

                cnt_shi <= cnt_shi + 1;

            end

            end else if (start_flag == 1) begin

                 cnt_ge <= cnt_ge + 1;  // 在开始和增加标志位为 1 时增加

                if (cnt_shi == 9 && cnt_ge == 9) begin

                    cnt_shi <= 4'd0;

                    cnt_ge <= 4'd0;

                end else if (cnt_ge == 9) begin

                    cnt_ge <= 4'd0;

                    cnt_shi <= cnt_shi + 1;

                end

            end

        else begin

       

        end

    end

 

    assign seg_led_1[8:0] = {2'b00, seg[cnt_shi]} | 8'b10000000;

    assign seg_led_2[8:0] = {2'b00, seg[cnt_ge]};

endmodule

在原有的基础上,询问了GPT添加小数点显示。

GPT提供了思路,发现直接与上小数点的位数,我们就能实现小数点的点亮。

 

这里我们设计思路是,当开始按键按下的时候,我们就将我们的cnt每10hz加一次,实现我们的小数点的变化,之后当我们暂停按下时,我们不执行任何语句,程序就会保持不动,通过我们按键的边沿检测,实现我们对暂停情况的操作。这里的增量的操作的本质也是跟开始的cnt操作是一样的,我们这里设置为在暂停的情况下才能够执行,这里我们增量开始还需要判断暂停的标志位,再通过清零标志位,实现我们对小数的改变。

 

 


效果图如上。

5.资源利用率情况

  • 设备型号:LCMXO2-4000HC
  • 封装类型:CSBGA132
  • 时钟频率约为154.44MHz和133.209MHz
  • 设计使用了3%的SLICE资源
  • 设计使用了2%的寄存器资源

6.遇到的主要问题

1.一开始根据实例代码,将hold的程序,修改我们的暂停程序,导致我们的程序,一直与增量的冲突,因为增量要改变数码管,暂停是保持数值不变。而FPGA是并行任务进行,导致我们程序在10hz的前提下同时进行,导致我们的数据无法刷

新。

解决方法:在暂停标志位时,什么语句都不执行,这样的话,就不会与我们的增量冲突。

2.增量只能一直增加,因为在一开始的程序里,按键判断函数,只有在按键按下的上升沿才会执行,改变我们的标志位。之后我想通过在数码管显示函数中,修改增量的标志位,一直失败,因为在always语句下,俩个变量不能同时出现在不同的always中。

解决方法:在按键扫描中也加10hz的扫描,这样的话每当我们增量按下的时候,将我们的增量标志位清除,等待下一个增量。

7.最后总结

在这一次的寒假训练营,我第一次接触到FPGA,在此过程中,不断询问GPT关于FPGA的语句的使用,也得知FPGA的运行逻辑与单片机是不同的,通过并行的逻辑,在代码编写和时序问题上,我们需要考虑的更多。我遇到的问题都是在时序和并行,都是学习FPGA的要点,在处理这些问题上,我总会有一些空缺。也感谢硬禾学堂的寒假训练营,让我体验到FPGA的魅力,在这个寒假也学习到关于FPGA的相关语法,在经历了俩次耗费许久的问题,让我深刻知道了FPGA时序和并行的逻辑的重要性。

附件下载
implement.jed
秒表
团队介绍
喜爱电子物联网
团队成员
冲向天空的猪
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号