基于EP4CE6F17C8的FPGA可调校数码管时钟实例

一、电路模块

1、数码管

开发板板载了6个数码管,全部为共阳型,原理图如下图所示,段码端引脚为DIG[0]~DIG[7]共8位(包含小数点),位选端引脚为SEL[0]~SEL[5]共6位。端口均为低电平有效。

其实物图如下所示。

数码管引脚分配见下表。

2、时钟晶振

开发板板载了一个50MHz的有源晶振,为系统提供时钟。

其实物图如下所示。

时钟输出引脚分配见下表。

3、按键

开发板板载了4个独立按键,其中有3个用户按键(KEY1~KEY3),1个功能按键(RESET)。按键按下为低电平(0),释放为高电平(1),4个按键的原理图如下图所示。

其实物图如下所示。

按键的引脚分配见下表。

 

二、实验代码

本例使用6个数码管显示时钟的时、分、秒,时与分之间及分与秒之间通过小数点来分隔,按键reset为复位,key1为调校,key2为增加,key3为减少,代码使用Verilog编写。本例使用了两种方式来实现。

第一种方式,共有八个文件,具体如下。

先编写数码管实现显示字形解码的程序,有两个,一个为不带小数点显示的,一个是带小数点显示的,先看不带小数点的,模块名称为seg_decode,文件名称为seg_decode.v,代码如下。

module seg_decode(
    input[3:0]      data,            //显示的字形,可显示0~9十个字形,所以需要4位
    output reg[7:0] seg              //字形编码,包含小数点,共8位
);

always@(*)                           //敏感信号为所有输入量
begin
    case(data)
        4'd0:seg <= 8'b1100_0000;    //字形0的编码
        4'd1:seg <= 8'b1111_1001;    //字形1的编码
        4'd2:seg <= 8'b1010_0100;    //字形2的编码
        4'd3:seg <= 8'b1011_0000;    //字形3的编码
        4'd4:seg <= 8'b1001_1001;    //字形4的编码
        4'd5:seg <= 8'b1001_0010;    //字形5的编码
        4'd6:seg <= 8'b1000_0010;    //字形6的编码
        4'd7:seg <= 8'b1111_1000;    //字形7的编码
        4'd8:seg <= 8'b1000_0000;    //字形8的编码
        4'd9:seg <= 8'b1001_0000;    //字形9的编码
        default:seg <= 7'b111_1111;  //默认不显示
    endcase
end
endmodule

再编写一个看带小数点显示的字形解码程序,模块名称为seg_decode_dot,文件名称为seg_decode_dot.v,代码如下。

module seg_decode_dot(
    input[3:0]      data,            //显示的字形,可显示0~9十个字形,所以需要4位
    output reg[7:0] seg              //字形编码,包含小数点,共8位
);

always@(*)                           //敏感信号为所有输入量
begin
    case(data)
        4'd0:seg <= 8'b0100_0000;    //字形0的编码(带小数点)
        4'd1:seg <= 8'b0111_1001;    //字形1的编码(带小数点)
        4'd2:seg <= 8'b0010_0100;    //字形2的编码(带小数点)
        4'd3:seg <= 8'b0011_0000;    //字形3的编码(带小数点)
        4'd4:seg <= 8'b0001_1001;    //字形4的编码(带小数点)
        4'd5:seg <= 8'b0001_0010;    //字形5的编码(带小数点)
        4'd6:seg <= 8'b0000_0010;    //字形6的编码(带小数点)
        4'd7:seg <= 8'b0111_1000;    //字形7的编码(带小数点)
        4'd8:seg <= 8'b0000_0000;    //字形8的编码(带小数点)
        4'd9:seg <= 8'b0001_0000;    //字形9的编码(带小数点)
        default:seg <= 7'b111_1111;  //默认不显示
    endcase
end
endmodule

接下来编写模10和模6的两个带增减调校的计数模块,名称分别为count_m10和count_m6。先看count_m10的模块,文件名称为count_m10.v,代码如下。

module count_m10(
      input          clk,                    //板载50HMz系统时钟
      input          rst_n,                  //复位按键
      input          set,                    //计数增加
      input          clr,                    //计数减少
      input          en,                     //计数使能位  
      output reg[3:0]data_out,               //计数值,从0~9共10位,所以用4位
      output reg     t,                      //进位位
      output reg     c                       //借位位
);

always@(posedge clk or negedge rst_n)        //敏感信号为时钟上沿或复位下沿 
begin
    if(rst_n==0)                             //低电平复位
    begin
        data_out <= 4'd0;                    //复位时计数值及进位位清零
        t <= 1'd0;
          c <= 1'd0;
    end
    else if(en || set)                       //如果计数使能或增加位有效,则执行增计数
    begin
        if(data_out==4'd9)                   //如果计数到达9时
        begin
            t<= 1'b1;                        //进位位置1
            data_out <= 4'd0;                //计数值清零
        end
        else
        begin
            t <= 1'b0;                       //否则进位位清零,计数值加1
            data_out <= data_out + 4'd1;
        end
    end
     else if(clr)                            //如果减少位有效,则执行减计数
     begin
        if(data_out != 4'd0)                 //如果没有减到0,则执行减1
            data_out <= data_out - 1'd1;
        else
       begin    
            c <= 1'b1;                       //否则借位位置1
            data_out <= 4'b1001;             //计数值回到9
        end    
     end
    else                                     //如果计数不使能,进、借位位置0
     begin
        t <= 1'b0;
          c <= 1'b0;
     end
end
endmodule

再来看count_m6的模块,文件名称为count_m6.v,代码如下。

module count_m6(
      input          clk,                    //板载50HMz系统时钟
      input          rst_n,                  //复位按键
      input          clr,                    //计数减少
      input          en,                     //计数使能位  
      output reg[3:0]data_out,               //计数值,从0~5共6位
      output reg     t                       //进位位
);

always@(posedge clk or negedge rst_n)        //敏感信号为时钟上沿或复位下沿 
begin
    if(rst_n==0)                             //低电平复位
    begin
        data_out <= 4'd0;                    //复位时计数值及进位位清零
        t <= 1'd0;
    end
    else if(en)                              //如果计数使能,则执行增计数
    begin
        if(data_out==4'd5)                   //如果计数到达5时
        begin
            t<= 1'b1;                        //进位位置1
            data_out <= 4'd0;                //计数值清零
        end
        else
        begin
            t <= 1'b0;                       //否则进位位清零,计数值加1
            data_out <= data_out + 4'd1;
        end
    end
     else if(clr)                            //如果减少位有效,则执行减计数
     begin
        if(data_out != 4'd0)
            data_out <= data_out - 1'd1;     //如果没有减到0,则执行减1
        else
       begin    
            data_out <= 4'b0101;             //否则计数值回到5
        end    
     end
    else                                     //如果计数不使能,进位位置0
        t <= 1'b0;
end
endmodule

接下来编写一个模60的带增减调校的计数模块,但它是通过例化前模10和模6模块来实现的,模块名称为count_m60,文件名称为count_m60.v,代码如下。

module count_m60(
      input          clk,                    //板载50HMz系统时钟
      input          rst,                    //复位按键
      input          set,                    //计数增加
      input          clr,                    //计数减少
      input          en,                     //计数使能位  
      output [7:0]   data,                   //计数值从00~59共,使用BCD方式
      output         t                       //进位位
);

wire [3:0] count_data0,count_data1;          //定义计数值的个位和十位
wire t0,t1,c0;                               //定义个位和十位的进位位及个位的借位
 
//下面例化个位计数单元(十进制)
count_m10 u0(.clk(clk), .rst_n(rst), .set(set), .clr(clr), .en(en), .data_out(count_data0), .t(t0), .c(c0));
//下面例化十位计数单元(六进制)
count_m6 u1(.clk(clk), .rst_n(rst), .clr(c0), .en(t0), .data_out(count_data1), .t(t1));

assign t = t1;                               //连接输出高位进位位
assign data = {count_data1, count_data0};    //并位连接输出60进制计数值
endmodule

接下来是模24的带增减调校的计数模块,模块名称为count_m24,文件名称为count_m24.v,代码如下。

module count_m24(
     input          clk,                        //板载50HMz系统时钟
     input          rst_n,                      //复位按键
     input          set,                        //计数增加
     input          clr,                        //计数减少
     input          en,                         //计数使能位
     output reg[7:0]data_out                    //计数值从00~23共,使用BCD方式
);

always@(posedge clk or negedge rst_n)           //敏感信号为时钟上沿或复位下沿    
begin
    if(rst_n==0)                                //低电平复位
    begin
        data_out <= 8'd0;                       //复位时计数值清零
    end
    else if(en || set)                          //如果计数使能或增加位有效,则执行增计数
    begin
        if(data_out == 8'b00100011)             //如果计数值BCD码为23,则计数值清零
        begin
            data_out <= 8'd0;
        end
        else if(data_out[3:0] == 4'b1001)       //如果计数值低位等于9,则低位清零,高位加1
          begin
                data_out[3:0] <= 4'd0;
                data_out[7:4] <= data_out[7:4] + 1'd1;
          end
          else 
        begin
            data_out <= data_out + 1'd1;         //否则计数值加1
        end
    end
     else if(clr)                                //如果减少位有效,则执行减计数
     begin
        if(data_out[3:0] != 4'd0)                //如果个位没有减到0,则个位减1
            data_out[3:0] <= data_out[3:0] - 1'd1;
        else if(data_out[7:4] != 4'd0)           //如果十位没减到0,则十位减1,个位回到9
        begin
            data_out[3:0] <= 4'b1001;
            data_out[7:4] <= data_out[7:4] - 1'd1;
        end
        else
        begin
            data_out <= 8'b00100011;             //个位十位都减到0,再来一个时钟回到23
        end
     end
end
endmodule

接下来编写调校按键模块,模块名称为key,文件名称为key.v,代码如下。

module key(
        input           clk,                    //板载50HMz系统时钟
        input           rst,                    //复位按键
        input [2:0]     key_h,                  //按键输入
        output [2:0]    key_press               //按键键值
);

wire key = key_h[0] & key_h[1] & key_h[2];      //定义三个按键相与

reg[3:0] keyr;                                  //定义按键存储变量
always@(posedge clk or negedge rst)             //敏感信号为时钟上沿或复位下沿
begin
    if(!rst)                                    //低电平复位
        keyr <= 4'b1111;                        //复位时按键值为全1
    else
        keyr <= {keyr[2:0], key};               //相当于每个时钟之后用key值向左填充keyr
end

wire    key_neg = ~keyr[2] & keyr[3];           //按键按下时钟打三拍之后判定有下降沿
wire    key_pos = keyr[2] & ~keyr[3];           //按键释放时钟打三拍之后判定有上升沿

//定时计数20ms时间,用于对按键的消抖判断
reg[19:0]    cnt;                                        
always@(posedge clk or negedge rst)             //敏感信号为时钟上沿或复位下沿
begin
    if(!rst)                                    //低电平复位时计数值清零
        cnt <= 20'd0;
    else if(key_pos || key_neg)                 //如果有上升沿或下降沿发生,计数值清零
        cnt <= 20'd0;
    else if(cnt < 20'd999_999)                  //如果未计到20ms,则继续加1计数
        cnt <= cnt + 20'd1;
    else
        cnt <= 20'd0;                           //到20ms,计数值清零
        
end

//定时采集按键值
reg[2:0]    key_halue[1:0];                     //定义两个健值存储变量
always@(posedge clk or negedge rst)             //敏感信号为时钟上沿或复位下沿
begin
    if(!rst)                                    //低电平复位时健值变量全部置1
    begin
        key_halue[0] <= 3'b111;
        key_halue[1] <= 3'b111;
    end
    else
    begin
    key_halue[1] <= key_halue[0];               //两次键值相差一个时钟节拍,用于在不相同时产生一个变化脉冲
        if(cnt == 20'd999_999)
            key_halue[0] <= key_h;              //到20ms后,获取外部按键值
    end
end

assign    key_press = key_halue[1] & ~key_halue[0];        //按键值按下时产生一个变化脉冲
//wire[3:0]    key_press = ~key_halue[1] & key_halue[0];   //按键值释放时产生一个变化脉冲
endmodule

最后编写时钟显示模块,并设置为顶层模块,模块名称为seg_clock,文件名称为seg_clock.v,代码如下。

module seg_clock(
    input               clk,               //板载50HMz系统时钟
    input               rst,               //复位按键
    input [2:0]         key_n,             //三个按键
    output reg[7:0]     seg7,              //段码端口
    output reg[5:0]     bit                //位选端口
);

wire t0,t1,t2;                             //定义进位信号
reg [25:0] cnt;                            //定义26位时钟计数器
reg sec;                                   //定义秒信号
reg start_sec;                             //定义开始走时信号

always@(posedge clk or negedge rst)        //敏感信号为时钟上沿或复位下沿
begin
    if(rst == 0)                           //低电平复位
    begin
        sec <= 1'b0;                       //秒计数清零    
    end
    else if(start_sec)
     begin
     if(cnt == 26'd49_999_999)             //时钟计数器到达1秒时
    begin
        cnt <= 26'd0;                      //时钟计数器清零
        sec <= 1'b1;                       //产生秒信号
    end
    else
    begin
        sec <= 1'b0;                       //否则秒信号清零
        cnt <= cnt + 26'd1;                //时钟计数器加1,即来一次时钟脉冲加一次
    end
     end
end
//下面定义6个数码管显示数值的存储变量
wire [3:0] count_data0,count_data1,count_data2,count_data3,count_data4,count_data5;
//下面定义时、分、秒的存储变量
wire [7:0] count_data_0,count_data_1,count_data_2;
//下面定义6个数码管的字形码存储变量
wire [7:0] seg_0,seg_1,seg_2,seg_3,seg_4,seg_5;
//下面例化秒的计数单元(六十进制)
count_m60 u1(.clk(clk), .rst(rst), .set(set0), .clr(clr0), .en(sec), .data(count_data_0), .t(t0));
//下面例化分的计数单元(六十进制)
count_m60 u2(.clk(clk), .rst(rst), .set(set1), .clr(clr1), .en(t0), .data(count_data_1), .t(t1));
//下面例化时的计数单元(二十四进制)
count_m24 u3(.clk(clk), .rst_n(rst), .set(set2), .clr(clr2), .en(t1), .data_out(count_data_2));
//下面分别取出6个数码管的显示值
assign count_data0 = count_data_0[3:0];
assign count_data1 = count_data_0[7:4];
assign count_data2 = count_data_1[3:0];
assign count_data3 = count_data_1[7:4];
assign count_data4 = count_data_2[3:0];
assign count_data5 = count_data_2[7:4];
//下面例化秒的个位字形解码单元
seg_decode seg0(.data(count_data0), .seg(seg_0));
//下面例化秒的十位字形解码单元
seg_decode seg1(.data(count_data1), .seg(seg_1));
//下面例化分的个位字形解码单元
seg_decode_dot seg2(.data(count_data2), .seg(seg_2));
//下面例化分的十位字形解码单元
seg_decode seg3(.data(count_data3), .seg(seg_3));
//下面例化时的个位字形解码单元
seg_decode_dot seg4(.data(count_data4), .seg(seg_4));
//下面例化时的十位字形解码单元
seg_decode seg5(.data(count_data5), .seg(seg_5));

reg[17:0]     time_cnt;                   //定义20位时钟计数器
reg[3:0]      scan_sel;                   //定义扫描位置计数器
//3.3毫秒循环计数
always@(posedge clk or negedge rst)       //敏感信号为时钟上沿或复位下沿
begin
    if(rst == 1'b0)                       //低电平复位时计数器全部清零
    begin
        time_cnt <= 18'd0;
        scan_sel <= 4'd0;
    end
    else if(time_cnt >= 18'd166_666)      //时钟计数器到达3.3毫秒时
    begin
        time_cnt <= 18'd0;                //时钟计数器清零
        if(scan_sel == 4'd5)              //如果扫描位置计数器已经到1则恢复0
            scan_sel <= 4'd0;
        else
            scan_sel <= scan_sel + 4'd1;  //否则扫描位置计数器加1,即每3.3ms加一次
    end
    else
        begin
            time_cnt <= time_cnt + 18'd1; //否则时钟计数器加1,即来一次时钟脉冲加一次
        end
end

//数码管扫描显示
always@(posedge clk or negedge rst)       //敏感信号为时钟上沿或复位下沿
begin
    if(!rst)                              //低电平复位时数码管全灭
    begin
        bit <= 6'b111111;
        seg7 <= 8'hff;
    end
    else 
        case(scan_sel)
            4'd0:                         //数码管0显示秒的个位
                if(flash_sec == 1'b0)     //如果秒闪烁标志为0,则正常显示
                     begin
                         bit <= 6'b111110;
                         seg7 <= seg_0;
                     end
                else                      //否则该位显示黑屏
                begin
                         bit <= 6'b111110;
                         seg7 <= 8'hff;
                     end
            4'd1:                         //数码管1显示秒的十位
                if(flash_sec == 1'b0)     //如果秒闪烁标志为0,则正常显示
                     begin
                         bit <= 6'b111101;
                         seg7 <= seg_1;
                     end
                else                      //否则该位显示黑屏
                begin
                         bit <= 6'b111101;
                         seg7 <= 8'hff;
                     end
            4'd2:                         //数码管2显示分的个位
                if(flash_min == 1'b0)     //如果分闪烁标志为0,则正常显示
                     begin
                         bit <= 6'b111011;
                         seg7 <= seg_2;
                     end
                else                      //否则该位显示黑屏
                begin
                         bit <= 6'b111011;
                         seg7 <= 8'hff;
                     end
            4'd3:                         //数码管3显示分的十位
                if(flash_min == 1'b0)     //如果分闪烁标志为0,则正常显示
                     begin
                         bit <= 6'b110111;
                         seg7 <= seg_3;
                     end
                else                      //否则该位显示黑屏
                begin
                         bit <= 6'b110111;
                         seg7 <= 8'hff;
                     end
            4'd4:                         //数码管4显示时的个位
                if(flash_hour == 1'b0)    //如果时闪烁标志为0,则正常显示
                     begin
                         bit <= 6'b101111;
                         seg7 <= seg_4;
                     end
                else                      //否则该位显示黑屏
                begin
                         bit <= 6'b101111;
                         seg7 <= 8'hff;
                     end
            4'd5:                         //数码管5显示时的十位
                if(flash_hour == 1'b0)    //如果时闪烁标志为0,则正常显示
                begin
                        if (count_data5 == 4'd0)
                        begin
                            bit <= 6'b011111;//如果十位为0则不显示
                            seg7 <= 8'hff;
                        end
                        else              //否则正常显示
                        begin
                            bit <= 6'b011111;
                            seg7 <= seg_5;
                        end
                end
                else                      //否则该位显示黑屏
                begin
                if (count_data5 == 4'd0)
                        begin
                            bit <= 6'b011111;//如果十位为0则不显示
                            seg7 <= 8'hff;
                        end
                else
                     begin
                        bit <= 6'b011111; //否则正常显示
                        seg7 <= 8'hff;
                     end
              end
            default:                      //数码管全部熄灭
            begin
                bit <= 6'b111111;
                seg7 <= 8'hff;
            end
        endcase
end

wire [2:0] key_pressed;                   //定义健值存储变量
//下面例化按键扫描单元
key u4(.clk(clk), .rst(rst), .key_h(key_n), .key_press(key_pressed));

reg[2:0] flash;                           //定义时分秒显示闪烁标志
always@(posedge clk or negedge rst)       //敏感信号为时钟上沿或复位下沿
begin
    if(!rst)                              //低电平复位
    begin
        flash <= 3'd0;                    //闪烁标号清零
        start_sec <= 1'b1;                //走时使能
    end
    else if(key_pressed[0])               //如果key1按下
    begin
        flash <= flash + 3'd1;            //闪烁标志加1
        start_sec <= 1'b0;                //走时禁止
    end
    else if(flash == 3'd4)                //如果闪烁标志值加到4
    begin
        flash <= 3'd0;                    //闪烁标志清零
        start_sec <= 1'b1;                //走时使能
    end
end

reg [22:0] flash_cnt;                     //定义23位闪烁计数器
reg    flash_sec, flash_min, flash_hour;
always@(posedge clk or negedge rst)       //敏感信号为时钟上沿或复位下沿
begin
    if(rst == 0)                          //低电平复位全部计数器清零
    begin
        flash_sec <= 1'b0;
          flash_min <= 1'b0;
          flash_hour <= 1'b0;
          flash_cnt <= 23'd0;
    end
    else if(flash_cnt == 23'd7_999_999)   //时钟计数器到达0.16秒时(用于控制闪烁频率)
    begin
        flash_cnt <= 23'd0;               //闪烁计数器清零
          if(flash == 3'd1)               //如果闪烁标志值为1,表示秒闪烁
                flash_sec <= ~flash_sec;  //秒闪烁标志取反,产生闪烁
            else
                flash_sec <= 1'b0;        //否则秒不闪烁,正常显示
            if(flash == 3'd2)             //如果闪烁标志值为2,表示分闪烁
                flash_min <= ~flash_min;  //分闪烁标志取反,产生闪烁
            else
                flash_min <= 1'b0;        //否则分不闪烁,正常显示
            if(flash == 3'd3)             //如果闪烁标志值为3,表示时闪烁
                flash_hour <= ~flash_hour;//时闪烁标志取反,产生闪烁
            else
                flash_hour <= 1'b0;       //否则时不闪烁,正常显示
    end
    else
    begin
        flash_cnt <= flash_cnt + 23'd1;   //闪烁计数器加1,即来一次时钟脉冲加一次
    end
end

reg    set0,set1,set2;                    //定义秒、分、时的计数增加信号变量
always@(posedge clk)                      //敏感量为时钟上升沿
begin
    case(flash)                           //根据闪烁标志的值,区分秒、分、时的增加
        3'd0:                             //值为0时,都不增加
        begin
            set0 <= 1'b0;
            set1 <= 1'b0;
            set2 <= 1'b0;
        end
        3'd1:                             //值为1时,key1按下秒增加
            set0 <= key_pressed[1];
        3'd2:                             //值为2时,key1按下分增加
            set1 <= key_pressed[1];
        3'd3:                             //值为3时,key1按下时增加
            set2 <= key_pressed[1];
        default:                          //默认都不增加
        begin
            set0 <= 1'b0;
            set1 <= 1'b0;
            set2 <= 1'b0;
        end    
    endcase
end

reg    clr0,clr1,clr2;                    //定义秒、分、时的计数减少信号变量
always@(posedge clk)                      //敏感量为时钟上升沿
begin
    case(flash)                           //根据闪烁标志的值,区分秒、分、时的减少
        3'd0:                             //值为0时,都不减少
        begin
            clr0 <= 1'b0;
            clr1 <= 1'b0;
            clr2 <= 1'b0;
        end
        3'd1:                             //值为1时,key2按下秒减少
            clr0 <= key_pressed[2];
        3'd2:                             //值为2时,key2按下分减少
            clr1 <= key_pressed[2];
        3'd3:                             //值为3时,key2按下时减少
            clr2 <= key_pressed[2];
        default:                          //默认都不减少
        begin
            clr0 <= 1'b0;
            clr1 <= 1'b0;
            clr2 <= 1'b0;
        end    
    endcase
end
endmodule

第二种方式,共有六个文件,它没有使用模10和模6的模块,而是直接写了一个模60的模块,模块名称为count_m60,文件名称仍为count_m60.v,代码如下。

module count_m60(
      input          clk,                         //板载50HMz系统时钟
      input          rst,                         //复位按键
      input          set,                         //计数增加
      input          clr,                         //计数减少
      input          en,                          //计数使能位  
      output reg[7:0]data,                        //计数值从00~59共,使用BCD方式
      output reg     t                            //进位位
);

always@(posedge clk or negedge rst)               //敏感信号为时钟上沿或复位下沿
begin
    if(rst==0)                                    //低电平复位
    begin
        data <= 8'd0;                             //复位时计数值及进位位清零
          t <= 1'd0;
    end
    else if(set)                                  //如果增加位有效,则执行增计数
    begin
        if(data == 8'b01011001)                   //如果计数值BCD码为59,则计数值清零,进位位置1
        begin
            data <= 8'd0;
        end
        else if(data[3:0] == 4'b1001)             //如果计数值低位等于9,则低位清零,高位加1
          begin
                data[3:0] <= 4'd0;
                data[7:4] <= data[7:4] + 1'd1;
          end
          else 
        begin
            data <= data + 1'd1;                  //否则计数值加1,进位位清零
        end
    end
     else if(clr)                                 //如果减少位有效,则执行减计数
     begin
        if(data[3:0] != 4'd0)                     //如果个位没有减到0,则个位执行减1    
            data[3:0] <= data[3:0] - 1'd1;
        else if(data[7:4] != 4'd0)                //如果十位没减到0,则十位减1,个位回到9
        begin
            data[3:0] <= 4'b1001;
            data[7:4] <= data[7:4] - 1'd1;
        end
        else
        begin
            data <= 8'b01011001;                  //个位十位都减到0,再来一个时钟回到59
        end
     end
     else if(en)                                  //如果计数使能,则执行计数,否则保持上一次的值不变
    begin
        if(data == 8'b01011001)                   //如果计数值BCD码为59,则计数值清零,进位位置1
        begin
            data <= 8'd0;
                t<= 1'b1;
        end
        else if(data[3:0] == 4'b1001)             //如果计数值低位等于9,则低位清零,高位加1
          begin
                data[3:0] <= 4'd0;
                data[7:4] <= data[7:4] + 1'd1;
          end
          else 
        begin
            data <= data + 1'd1;                  //否则计数值加1,进位位清零
                t<= 1'b0;
        end
    end
     else                                         //如果计数不使能,进位位置0
        t<= 1'b0;                                    
end
endmodule

从上面的代码中可以看到,该模块与第一种方式中的不一样。其余五个文件(seg_clock.v、seg_decode.v、seg_decode_dot.v、count_m24.v、key.v)与第一种方式中的一样。 

三、代码说明

1、本例把前面的数码管扫描、时钟走时和按键消抖等部分结合起来,形成了一个具备调校功能的数码管时钟。其中的详细内容请参考“基于EP4CE6F17C8的FPGA数码管动态显示实例”、“基于EP4CE6F17C8的FPGA数码管时钟显示实例”及“基于EP4CE6F17C8的FPGA键控灯实例”等章节。
2、这里主要讨论如何通过实例化的模块进行计数的增减控制。
3、在第二种方式中,由于模60的模块和模24的模块的计数都是自己实现的(没有例化其他模块),所以对于这两个计数模块,可通过传入增(set)或减(clr)信号来实现模块内部计数值的更新,所以只需要对以前的计数模块稍加修改即可。
4、对于第一种方式,由于模60的模块是由模10和模6两个模块例化来实现的,所以修改起来相对麻烦,必须把相关信号分层传入。在模60的模块中,通过例化传入增(set)或减(clr)信号,在此模块中,再通过例化模10的模块,把增(set)或减(clr)信号传入其中,同时还要考虑当该模块计数值减到0时的借位输出,以此信号提供给例化的模6的模块进行减计数。而增计数就借由模块原来的进位信号来提供。也就是说,模6的模块不用再传入增信号set,而是依靠模10来的进位信号就行了。同理,模6的模块也不用再传入减信号clr,而是依靠模10来的借位信号就行了。这种方式虽然简化了操作,但缺点是在调校到溢出时也会产生进位信号,所以还具备进一步改进的空间。
5、调校时的闪烁控制由时、分、秒各自的flash变量来承担,为节约器件且实现较好的闪烁效果,代码中使用了23位计数器,通过取反产生出约0.16秒的闪烁间隔,实践观察效果还不错。在显示控制中,当时、分、秒各自的闪烁变量(flash_sec、flash_min、flash_hour)为0时,正常显示,当为1时,显示为黑屏(即不显示)。通过每间隔0.16秒对相应的flash变量取反一次,就实现了闪烁效果。
6、对于设置按键key1,第一次按下,对应秒闪烁,第二次按下,对应分闪烁,第三次按下,对应时闪烁,第四次按下,恢复到走时状态(不闪烁)。只有在闪烁状态时,增加键key2和减少键key3才有效。当处于调校状态时,走时停止,调校结束后才开始走时。

四、实验步骤

FPGA开发的详细步骤请参见“基于EP4CE6F17C8的FPGA开发流程(以半加器为例)”一文,本例只对不同之处进行说明。

本例工程放在D:\EDA_FPGA\Exam_8文件夹下,工程名称为Exam_8。有八个模块文件,一个名称为seg_clock.v,设置为顶层实体,另外七个名称分别为seg_decode.v、seg_decode_dot.v、count_m10.v、count_m6.v、count_m60.v、count_m24.v和key.v,用于提供例化。其余步骤与“基于EP4CE6F17C8的FPGA开发流程”中的一样。

接下来看管脚约束,本例中6个数码管一共有14个引脚,再加上时钟晶振和复位以及其他三个按钮,一共19个。具体的端口分配如下图所示。

对于未用到的引脚设置为三态输入方式,多用用途引脚全部做为普通I/O端口,电压设置为3.3-V LVTTL(与”基于EP4CE6F17C8的FPGA开发流程“中的一样)。需要注意,程序中的每个端口都必须为其分配管脚,如果系统中存在未分配的I/O,软件可能会进行随机分配,这将造成不可预料的后果,存在烧坏FPGA芯片的风险。

接下来对工程进行编译,编译完成后,可查看一下逻辑器件的消耗情况,第一种方式的器件消耗量如下图所示。

第二种方式的器件消耗量如下图所示。

可以看到,第二种方式消耗的器件更多一些,但它解决了调校过程中的进位问题,因此还是推荐使用第二种方式。另外,还可以点击菜单Tools->Netlist Viewers->RTL Viewer,查看一下生成的RTL电路图。

最后进行程序下载,并查看结果。下图为时钟正常走时的图片。

下图为按下一次key1键对秒进行调校时的图片,秒显示部分闪烁。

下图为按下key2键,秒计数值增加。

下图为按下key3键,秒计数值减小。

以上为秒调校的情况,秒调校好后,再按一次key1键,分显示部分闪烁,可进行加减调校,完毕后再接一次key1键,调校时部分,全部完成后再按一次key1键,调校结束,进入到走时状态。在走时状态,按key2和key3键均不起作用。当按下reset键时,显示熄灭,释放reset键时,显示0时0分0秒,并开始走时。

posted @ 2024-04-06 15:23  fxzq  阅读(40)  评论(0编辑  收藏  举报