zoukankan      html  css  js  c++  java
  • [FPGA]Verilog 60s秒表计时器(最大可计时间长达9min)

    [FPGA]Verilog 60s秒表计时器

    1.引述

        这次的实验来自于本人本科课程数电结课时的自选题目。由于这次上传是后知后觉,学校已将小脚丫板子回收,所以在这篇文章中没法贴出代码结果的效果图了,但最终效果已经过测试,可放心食用。那么下面就贴上代码并略加讲解供大家参考。

    2.分频模块

    我们要实现一个秒表,自然要将实验板中的时钟脉冲clk分频为一个周期为1s的脉冲,已知小脚丫板子的晶振为12MHz。下面贴上分频模块的代码。

    module divide #
    (                            //parameter是verilog里参数定义
    parameter WIDTH    =    24,    //计数器的位数,计数的最大值为 2**(WIDTH-1)
    parameter N        =    12_000_000 //分频系数,请确保 N<2**(WIDTH-1),否则计数会溢出
    )
    (
    input clk, //clk频率为12MHz
    input rst_n, //复位信号,低有效,
    output clkout //输出信号,可以连接到LED观察分频的时钟
    );
    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)    
        begin        
            if(!rst_n)
                cnt_p <= 1'b0;
            else if(cnt_p == (N-1))
                cnt_p <= 1'b0;
            else 
                cnt_p <= cnt_p + 1'b1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器
        end
    //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                clk_p <= 1'b0;
            else if(cnt_p < (N>>1)) //N>>1表示右移一位,相当于除以2取商
                clk_p <= 1'b0;
            else 
                clk_p <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟
        end
    //下降沿触发时计数器的控制            
    always @(negedge clk or negedge rst_n)
        begin
            if(!rst_n)
                cnt_n <= 1'b0;
            else if(cnt_n == (N-1))
                cnt_n <= 1'b0;
            else 
                cnt_n <= cnt_n + 1'b1;
        end
    //下降沿触发的分频时钟输出,和clk_p相差半个clk时钟
    always @(negedge clk or negedge rst_n)
        begin
            if(!rst_n)
                clk_n <= 1'b0;
            else if(cnt_n < (N>>1))  
                clk_n <= 1'b0;
            else 
                clk_n <= 1'b1;    //得到的分频时钟正周期比负周期多一个clk时钟
        end
    wire    clk1 = clk; //当N=1时,直接输出clk
    wire    clk2 = clk_p; //当N为偶数也就是N的最低位为0,N[0]=0,输出clk_p
    wire    clk3 = clk_p & clk_n; //当N为奇数也就是N最低位为1,N[0]=1,输出clk_p&clk_n。正周期多所以是相与
    assign clkout = (N==1)? clk1:(N[0]? clk3:clk2);    //条件判断表达式
    endmodule

    3.八位数码管显示模块 

    小脚丫板子上有两个八位数码管显示,本实验中用来显示从00s到59s的显示。下面贴上数码管显示模块的代码。

    module segment
    (
    input  wire [3:0] seg_data_1, //四位输入数据信号
    input  wire [3:0] seg_data_2, //四位输入数据信号
    output wire [8:0] segment_led_1, //数码管1,MSB~LSB = SEG,DP,G,F,E,D,C,B,A
    output wire [8:0] segment_led_2  //数码管2,MSB~LSB = SEG,DP,G,F,E,D,C,B,A
    );
    reg[8:0] seg [15:0]; //存储7段数码管译码数据
    initial 
        begin
            seg[0] = 9'h3f;   //  0
            seg[1] = 9'h06;   //  1
            seg[2] = 9'h5b;   //  2
            seg[3] = 9'h4f;   //  3
            seg[4] = 9'h66;   //  4
            seg[5] = 9'h6d;   //  5
            seg[6] = 9'h7d;   //  6
            seg[7] = 9'h07;   //  7
            seg[8] = 9'h7f;   //  8
            seg[9] = 9'h6f;   //  9
            seg[10]= 9'h77;   //  A
            seg[11]= 9'h7C;   //  b
            seg[12]= 9'h39;   //  C
            seg[13]= 9'h5e;   //  d
            seg[14]= 9'h79;   //  E
            seg[15]= 9'h71;   //  F
        end
       assign segment_led_1 = seg[seg_data_1];
       assign segment_led_2 = seg[seg_data_2];
    endmodule

    4.功能讲解

        在主模块中除了要例化上述的两个模块之外,还需给这个秒表添砖加瓦一下!标题中提到这是一个60s秒表,而我们数码管显示只从00到59,但最大计时量程却达到了9min,这是怎么办到的呢?这里我们就用到了小脚丫上的一排八位LED灯,每当计到59s时,下一秒数码管显示回到00,点亮八位LED灯中的一个,达到表示已计过了1min的作用。一共有八位LED灯,当八个灯都被点亮后,数码管还有一次从00到59的显示机会,这样我们就的得到了一个最大计时量程为9min的秒表啦!下面贴上八位LED灯显示部分的代码。

    always@(posedge clk)
         if(cnt1==4'b0)
             LED[7:0]<=8'b11111111;
         else if(cnt1==4'b0001)
             LED[7:0]<=8'b11111110;
         else if(cnt1==4'b0010)
             LED[7:0]<=8'b11111100;
         else if(cnt1==4'b0011)
             LED[7:0]<=8'b11111000;
         else if(cnt1==4'b0100)
             LED[7:0]<=8'b11110000;
         else if(cnt1==4'b0101)
             LED[7:0]<=8'b11100000;
         else if(cnt1==4'b0110)
             LED[7:0]<=8'b11000000;
         else if(cnt1<=4'b0111)
             LED[7:0]<=8'b10000000;
         else if(cnt1<=4'b1000)
             LED[7:0]<=8'b00000000;

            此外作为一个秒表自然就要有暂停和开始计时的功能(当然清零功能也是有哒!主模块中就用rst复位键来实现,这里不多赘述。)暂停和开始计时这里我就用同一个按键实现。小脚丫板子上还有两个RGB三色灯,既然有这么好的资源存在,我们就要物尽其用!按下开始计时键时,秒表开始计时,数码管显示开始变化,此处我们让RGB三色灯中的一个等亮绿灯,表示处于正常计时状态中;当再次按键开启键时,秒表暂停,我们让另一个RGB三色灯亮红色,表示处于暂停状态。下面贴上包含三色灯点亮的部分代码。

    always @(posedge clk1h or negedge rst) //产生60进制计数器
        begin    //数码管显示要按照十进制的方式显示
            if(!rst)
            begin
                cnt <= 8'h00; //复位初值显示00
                cnt1<=4'b0;
            end
            else if(flag)
                begin
                G_LED2<=1'b0;
                R_LED1<=1'b1;
                    if(cnt[3:0] == 4'd9) //个位满九?
                        begin
                            cnt[3:0] <= 4'd0; //个位清零
                            if(cnt[7:4] == 4'd5 ) //十位满五?
                            begin
                                cnt[7:4] <= 4'd0; //十位清零
                                cnt1<=cnt1+1;
                            end
                            else
                            begin
                                cnt[7:4] <= cnt[7:4] + 1'b1; //十位加一
                                cnt1<=cnt1;
                            end
                        end
                    else cnt[3:0] <= cnt[3:0] + 1'b1; //个位加一
                end
            else
            begin
                cnt <= cnt;
                G_LED2<=1'b1;
                R_LED1<=1'b0;
            end
        end

    5.主模块

    最后贴上主模块的代码,完成整个秒表的实现。

    module counter60 
    (
    input clk,rst, //时钟和复位输入
    input key, //启动暂停按键
    output wire [8:0] segment_led_1,segment_led_2, //数码管输出
    output reg [7:0] LED, //八位LED灯
    output reg R_LED1,G_LED2 //RGB三色灯,此处用红色表示处于暂停状态中,绿色表示处于正常计时中
    );
    wire clk1h;    //1秒时钟
    reg    [7:0] cnt;    //计时计数器
    reg [3:0] cnt1; //分钟计数器
    reg    flag; //启动暂停标志
     
    divide #  //例化分频器产生1秒时钟信号
    (
    .WIDTH(24),
    .N(12_000_000)
    ) u1
    (
    .clk(clk),
    .rst_n(rst),
    .clkout(clk1h)
    );
    segment u2 //例化数码管显示模块
    (
    .seg_data_1        (cnt[7:4]),  //seg_data input
    .seg_data_2        (cnt[3:0]),  //seg_data input
    .segment_led_1    (segment_led_1),  //MSB~LSB = SEG,DP,G,F,E,D,C,B,A
    .segment_led_2    (segment_led_2)   //MSB~LSB = SEG,DP,G,F,E,D,C,B,A
    );
    always @(posedge clk or negedge rst) //产生标志信号
    begin
    if(!rst)
            flag = 1'b0;
    else if(!key)
         begin
            flag = ~flag;
            end
        else
        begin
            flag = flag;
            end
    end
    always @(posedge clk1h or negedge rst) //产生60进制计数器
        begin    //数码管显示要按照十进制的方式显示
            if(!rst)
            begin
                cnt <= 8'h00; //复位初值显示00
                cnt1<=4'b0;
            end
            else if(flag)
                begin
                G_LED2<=1'b0;
                R_LED1<=1'b1;
                    if(cnt[3:0] == 4'd9) //个位满九?
                        begin
                            cnt[3:0] <= 4'd0; //个位清零
                            if(cnt[7:4] == 4'd5 ) //十位满五?
                            begin
                                cnt[7:4] <= 4'd0; //十位清零
                                cnt1<=cnt1+1;
                            end
                            else
                            begin
                                cnt[7:4] <= cnt[7:4] + 1'b1; //十位加一
                                cnt1<=cnt1;
                            end
                        end
                    else cnt[3:0] <= cnt[3:0] + 1'b1; //个位加一
                end
            else
            begin
                cnt <= cnt;
                G_LED2<=1'b1;
                R_LED1<=1'b0;
            end
        end
    always@(posedge clk)
         if(cnt1==4'b0)
             LED[7:0]<=8'b11111111;
         else if(cnt1==4'b0001)
             LED[7:0]<=8'b11111110;
         else if(cnt1==4'b0010)
             LED[7:0]<=8'b11111100;
         else if(cnt1==4'b0011)
             LED[7:0]<=8'b11111000;
         else if(cnt1==4'b0100)
             LED[7:0]<=8'b11110000;
         else if(cnt1==4'b0101)
             LED[7:0]<=8'b11100000;
         else if(cnt1==4'b0110)
             LED[7:0]<=8'b11000000;
         else if(cnt1<=4'b0111)
             LED[7:0]<=8'b10000000;
         else if(cnt1<=4'b1000)
             LED[7:0]<=8'b00000000;
    endmodule
    
    module divide #
    (                            //parameter是verilog里参数定义
    parameter WIDTH    =    24,    //计数器的位数,计数的最大值为 2**(WIDTH-1)
    parameter N        =    12_000_000 //分频系数,请确保 N<2**(WIDTH-1),否则计数会溢出
    )
    (
    input clk, //clk频率为12MHz
    input rst_n, //复位信号,低有效,
    output clkout //输出信号,可以连接到LED观察分频的时钟
    );
    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)    
        begin        
            if(!rst_n)
                cnt_p <= 1'b0;
            else if(cnt_p == (N-1))
                cnt_p <= 1'b0;
            else 
                cnt_p <= cnt_p + 1'b1; //计数器一直计数,当计数到N-1的时候清零,这是一个模N的计数器
        end
    //上升沿触发的分频时钟输出,如果N为奇数得到的时钟占空比不是50%;如果N为偶数得到的时钟占空比为50%
    always @(posedge clk or negedge rst_n)
        begin
            if(!rst_n)
                clk_p <= 1'b0;
            else if(cnt_p < (N>>1)) //N>>1表示右移一位,相当于除以2取商
                clk_p <= 1'b0;
            else 
                clk_p <= 1'b1; //得到的分频时钟正周期比负周期多一个clk时钟
        end
    //下降沿触发时计数器的控制            
    always @(negedge clk or negedge rst_n)
        begin
            if(!rst_n)
                cnt_n <= 1'b0;
            else if(cnt_n == (N-1))
                cnt_n <= 1'b0;
            else 
                cnt_n <= cnt_n + 1'b1;
        end
    //下降沿触发的分频时钟输出,和clk_p相差半个clk时钟
    always @(negedge clk or negedge rst_n)
        begin
            if(!rst_n)
                clk_n <= 1'b0;
            else if(cnt_n < (N>>1))  
                clk_n <= 1'b0;
            else 
                clk_n <= 1'b1;    //得到的分频时钟正周期比负周期多一个clk时钟
        end
    wire    clk1 = clk; //当N=1时,直接输出clk
    wire    clk2 = clk_p; //当N为偶数也就是N的最低位为0,N[0]=0,输出clk_p
    wire    clk3 = clk_p & clk_n; //当N为奇数也就是N最低位为1,N[0]=1,输出clk_p&clk_n。正周期多所以是相与
    assign clkout = (N==1)? clk1:(N[0]? clk3:clk2);    //条件判断表达式
    endmodule
    
    module segment
    (
    input  wire [3:0] seg_data_1, //四位输入数据信号
    input  wire [3:0] seg_data_2, //四位输入数据信号
    output wire [8:0] segment_led_1, //数码管1,MSB~LSB = SEG,DP,G,F,E,D,C,B,A
    output wire [8:0] segment_led_2  //数码管2,MSB~LSB = SEG,DP,G,F,E,D,C,B,A
    );
    reg[8:0] seg [15:0]; //存储7段数码管译码数据
    initial 
        begin
            seg[0] = 9'h3f;   //  0
            seg[1] = 9'h06;   //  1
            seg[2] = 9'h5b;   //  2
            seg[3] = 9'h4f;   //  3
            seg[4] = 9'h66;   //  4
            seg[5] = 9'h6d;   //  5
            seg[6] = 9'h7d;   //  6
            seg[7] = 9'h07;   //  7
            seg[8] = 9'h7f;   //  8
            seg[9] = 9'h6f;   //  9
            seg[10]= 9'h77;   //  A
            seg[11]= 9'h7C;   //  b
            seg[12]= 9'h39;   //  C
            seg[13]= 9'h5e;   //  d
            seg[14]= 9'h79;   //  E
            seg[15]= 9'h71;   //  F
        end
       assign segment_led_1 = seg[seg_data_1];
       assign segment_led_2 = seg[seg_data_2];
    endmodule

    6.总结

           到这里整个秒表就完成啦。最后再次向读者们道歉,不能贴上实验效果图了。身边有实验板的读者们可以将代码烧录进板子观察现象。本人编程水平、时间有限,这篇文章到这里就要结束啦,欢迎广大读者评论留言,更欢迎大家指出本人的不足,希望能通过交流自身得到提高。最后感谢大家的耐心阅读!

  • 相关阅读:
    单例模式
    Curator Zookeeper分布式锁
    LruCache算法原理及实现
    lombok 简化java代码注解
    Oracle客户端工具出现“Cannot access NLS data files or invalid environment specified”错误的解决办法
    解决mysql Table ‘xxx’ is marked as crashed and should be repaired的问题。
    Redis 3.0 Cluster集群配置
    分布式锁的三种实现方式
    maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令
    How to Use Convolutional Neural Networks for Time Series Classification
  • 原文地址:https://www.cnblogs.com/RDJLM/p/12044385.html
Copyright © 2011-2022 走看看