2024年寒假练 - 基于小脚丫FPGA核心板 - 实现秒表
基于Lattice MXO2的小脚丫FPGA核心板 - Type C接口实现制作具有启动、停止、递增和清除功能的秒表,过小脚丫FPGA核心板上的2个数码管和轻触按键制作一个秒表,通过按键来控制秒表的功能,并在数码管上显示数值。
标签
FPGA
levitate
更新2024-04-03
北京理工大学
189

1. 项目需求

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

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

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

 

2. 完成的功能及达到的性能

2.1 计时器的计时功能实现

基本实现了计时器的基本功能,可以实现从0.0到9.9的计时功能,并且在0.0时单色LED闪亮

2.2 四种按键功能的实现

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

2.3 功能框图

image.png

3. 实现思路

1.首先,需要一个时钟信号来驱动计数器。可以使用FPGA上的时钟模块或外部时钟源来提供稳定的时钟信号。

2.设计一个计数器模块,该模块可以根据时钟信号进行递增操作。可以使用FPGA上的计数器资源或者自己设计一个计数器电路。

3.实现启动功能:可以通过一个按钮或者开关来触发启动功能。当启动信号触发时,计数器开始递增。

4.实现停止功能:同样可以通过一个按钮或者开关来触发停止功能。当停止信号触发时,计数器停止递增。

5.实现递增功能:在计数器模块中,每次接收到时钟信号时,根据启动和停止信号的状态来决定是否进行递增操作。

6.实现清除功能:可以通过一个按钮或者开关来触发清除功能。当清除信号触发时,将计数器的值重置为初始值。

通过以上步骤,可以实现一个具有启动、停止、递增和清除功能的秒表。

 

4. 实现过程

(1)代码主体部分:

module counter

(

clk ,    //时钟

rst ,    //复位

hold ,    //启动暂停按键

add             ,

seg_led_1 ,    //数码管1

seg_led_2 ,    //数码管2

led                  //led

);

 

input clk,rst;

input hold;

    input   add;

    

output [8:0] seg_led_2,seg_led_1;

//    wire  [8:0]     seg_led_1;

//    wire  [8:0]     seg_led_2;

output reg [7:0] led;

 

wire clk1h;        //1Hz时钟

wire hold_pulse;   //按键消抖后信号

wire        add_pulse;

reg hold_flag = 1'b0;    //按键标志位

reg         add_flag = 1'b0;

reg back_to_zero_flag ; //计时完成信号

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

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

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

 

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

 

    wire add_pulse2;

    reg  add_pulse3;

    reg  [23:0] add_pulse3_cnt;

    reg  add_pulse3_cnt_en;

 

//启动/暂停按键进行消抖

debounce  U2 (

.clk(clk),

.rst(rst),

.key(hold),

.key_pulse(hold_pulse)

);



//启动/暂停按键进行消抖

debounce2  U3 (

.clk(clk),

.rst(rst),

.key(add),

.key_sec(add_pulse),

.key_pulse(add_pulse2)

);



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

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

.clk(clk),

.rst_n(rst),      

.clkout(clk1h)

);

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

// always @ (posedge hold_pulse)

// if(!rst==1)

// hold_flag <= 0;

// else

// hold_flag <= ~hold_flag;



reg hold_pulse_dly;

always @ (posedge clk or negedge rst)begin

if(!rst==1)begin

hold_flag <= 1;

hold_pulse_dly <= 1'b0;

    end

else begin

    if(hold_pulse && ~hold_pulse_dly)begin

    hold_flag <= ~hold_flag;

end

else begin

    hold_flag <= hold_flag;

end

hold_pulse_dly <= hold_pulse;

    end

end





always @ (posedge add_pulse)

if(!rst==1)

add_flag <= 0;

else begin

add_flag <= 1;

    end

    

    always@(posedge clk or negedge rst)begin

        if(rst == 1'b0)begin

            add_pulse3 <= 1'b0;

            add_pulse3_cnt <= 24'd0;

            add_pulse3_cnt_en <= 1'b0;

        end

        else begin

            if(add_pulse2)begin

                add_pulse3_cnt_en <= 1'b1;

                add_pulse3_cnt <= add_pulse3_cnt + 1'b1;

                add_pulse3 <= 1'b1;

            end

            else begin

                if(add_pulse3_cnt_en)begin

                    add_pulse3 <= 1'b1;

                    if(add_pulse3_cnt == 24'd12_000_00)begin

                        add_pulse3_cnt <= 24'd0;

                        add_pulse3_cnt_en <= 1'b0;

                    end

                    else begin

                        add_pulse3_cnt <= add_pulse3_cnt + 1'b1;

                        add_pulse3_cnt_en <= add_pulse3_cnt_en;

                    end

                end

                else begin

                    add_pulse3_cnt <= add_pulse3_cnt;

                    add_pulse3 <= 1'b0;

                end

            end    

        end

    end

    

//计时完成标志信号产生

always @ (*)

if(!rst == 1)

back_to_zero_flag <= 0;

else if(cnt_shi==0 &&

cnt_ge==0)

back_to_zero_flag <= 1;

else

back_to_zero_flag <= 0;

always @ (posedge clk1h or negedge rst) begin

if (!rst == 1) begin

cnt_ge <= 4'd0;

cnt_shi <= 4'd0;

end

else begin

    if(hold_flag)begin

        if(add_pulse3)begin

                    if(cnt_ge == 4'd9)begin

                 cnt_ge <= 4'd1;

                 if(cnt_shi == 4'd9)begin

                     cnt_shi <= 4'd0;

                 end

                 else begin

                     cnt_shi <= cnt_shi + 1'b1;

                 end

            end

            else begin

                cnt_ge <= cnt_ge + 1'b1;

                cnt_shi <= cnt_shi;

            end

        end

        else begin

             cnt_shi <= cnt_shi;

         cnt_ge <= cnt_ge;

    end

    end

    else begin

        if(cnt_ge == 4'd9)begin

            cnt_ge <= 4'd1;

            if(cnt_shi == 4'd9)begin

                cnt_shi <= 4'd0;

            end

            else begin

                cnt_shi <= cnt_shi + 1'b1;

            end

        end

        else begin

            cnt_ge <= cnt_ge + 1'b1;

        end

    end

end

end

//计时完成点亮led

always @ ( back_to_zero_flag)begin

if (back_to_zero_flag==1)

led = 8'b0;

else

led = 8'b11111111;

end

 

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

 

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

 

endmodule

(2)按键消抖模块:

module debounce (clk,rst,key,key_pulse);
 parameter       N  =  1;                      //要消除的按键的数量

 

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;                   //检测到按键由高到低变化是产生一个高脉冲

        reg     [N-1:0]   key_sec;

 

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

        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;                //延时后检测电平寄存器变量

                            

 

 

        //延时后检测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

 

       

 

 

(3)分频模块

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

 

 

(4)增量按钮的实现

module debounce2 (clk,rst,key,key_sec,key_pulse);
 
        parameter       N  =  1;                      //要消除的按键的数量
 
input             clk;
        input             rst;
        input [N-1:0]   key;                        //输入的按键
output  [N-1:0]   key_sec;                  //按键动作产生的脉冲
output    [N-1:0]   key_pulse;
 
        reg     [N-1:0]   key_rst_pre;                //定义一个寄存器型变量存储上一个触发时的按键值
        reg     [N-1:0]   key_rst;                    //定义一个寄存器变量储存储当前时刻触发的按键值
        reg     [N-1:0]   key_sec;
        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;                //延时后检测电平寄存器变量
                       
 
 
        //延时后检测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

 

 

5. 遇到的主要难题

利用时钟分频时需要利用clk分出时钟信号,并确保分频系数N < 2**WIDTH-1,否则计数会溢出,cnt_p为上升沿触发时的计数器,cnt_n为下降沿触发时的计数器

条件判断表达式当N=1时,直接输出clk;当N为偶数也就是N的最低位为0,N(0)=0,输出clk_p;当N为奇数也就是N最低位为1,N(0)=1,输出clk_p&clk_n。

在增量按钮的设计中,也需要利用按键消抖功能,因此做好按键消抖功能也是十分重要的,

按键抖动:按键抖动通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。当按下一次按键,可能在A点检测到一次低电平,在B点检测到一次高电平,在C点又检测到一次低电平。同时抖动是随机,不可测的。那么按下一次按键,抖动可能会误以为按下多次按键。因此我们需要消除按键抖动对我们的影响。

我们利用的是debounce模块,实现了按键消抖。

6. 未来的计划建议

 该项目已基本实现了具有启动、停止、递增和清除功能的秒表设计,不过在代码编写时偏冗余,并且占用的fpga资源较多,仍具有较大的改善空间,在之后的学习过程中不断对这些方面进行完善和调整。

附件下载
寒假在家练.jed
团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号