基于EP4CE6F17C8的FPGA双数码管六十进制秒计数实例

一、电路模块

本例的电路模块与“基于EP4CE6F17C8的FPGA数码管动态显示实例”中的完全一样,此处就不再给出了。

二、实验代码

本例实现2个数码管循环显示00~59,显示间隔为1秒,代码使用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

接下来编写秒计数程序,共有两个模块,名称分别为count_m10和count_m6。先看count_m10的模块,文件名称为count_m10.v,代码如下。

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

always@(posedge clk or negedge rst_n)        //敏感信号为时钟上沿或复位下沿 
begin
    if(rst_n==0)                             //低电平复位
    begin
        data <= 4'd0;                        //复位时计数值及进位位清零
        t <= 1'd0;
    end
    else if(en)                              //如果计数使能,则执行计数,否则保持上一次的值不变
    begin
        if(data==4'd9)                       //如果计数到达9时
        begin
            t<= 1'b1;                        //进位位置1
            data <= 4'd0;                    //计数值清零
        end
        else
        begin
            t <= 1'b0;                       //否则进位位清零,计数值加1
            data <= data + 4'd1;
        end
    end
    else                                     //如果计数不使能,进位位置0
        t <= 1'b0;
end
endmodule

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

module count_m6(
      input          clk,                    //板载50HMz系统时钟
      input          rst_n,                  //复位按键
      input          en,                     //计数使能位  
      output reg[3:0]data,                   //计数值,从0~9共10位,所以用4位
      output reg     t                       //进位位
);

always@(posedge clk or negedge rst_n)        //敏感信号为时钟上沿或复位下沿 
begin
    if(rst_n==0)                             //低电平复位
    begin
        data <= 4'd0;                        //复位时计数值及进位位清零
        t <= 1'd0;
    end
    else if(en)                              //如果计数使能,则执行计数,否则保持上一次的值不变
    begin
        if(data==4'd5)                       //如果计数到达5时
        begin
            t<= 1'b1;                        //进位位置1
            data <= 4'd0;                    //计数值清零
        end
        else
        begin
            t <= 1'b0;                       //否则进位位清零,计数值加1
            data <= data + 4'd1;
        end
    end
    else                                     //如果计数不使能,进位位置0
        t <= 1'b0;
end
endmodule

最后编写数码管显示程序,并设置为顶层模块,模块名称为seg_count,文件名称为seg_count.v,代码如下。

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

reg [25:0] cnt;                            //定义26位时钟计数器
reg sec;                                   //定义秒信号

always@(posedge clk or negedge rst)        //敏感信号为时钟上沿或复位下沿
begin
    if(rst == 0)                           //低电平复位时秒计数清零
    begin
        sec <= 1'b0;
    end
    else 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

wire t0;                                   //定义个位的进位位
wire [3:0] count_data0,count_data1;        //定义显示的值(个位、十位共两个)
wire [7:0] seg_0,seg_1;                    //定义显示字形码的值(个位、十位共两个)
//下面例化秒的个位计数单元(十进制)
count_m10 u0(.clk(clk), .rst_n(rst), .en(sec), .data(count_data0), .t(t0));
//下面例化秒的十位计数单元(六进制)
count_m6 u1(.clk(clk), .rst_n(rst), .en(t0), .data(count_data1), .t());
//下面例化秒的个位字形解码单元
seg_decode seg0(.data(count_data0), .seg(seg_0));
//下面例化秒的十位字形解码单元
seg_decode seg1(.data(count_data1), .seg(seg_1));

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'd3)               //如果扫描位置计数器已经到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显示秒的个位
            begin
                bit <= 6'b111110;
                seg7 <= seg_0;
            end
            4'd1:                          //数码管1显示秒的十位
            begin
                bit <= 6'b111101;
                seg7 <= seg_1;
            end
            default:                       //数码管全部熄灭
            begin
                bit <= 6'b111111;
                seg7 <= 8'hff;
            end
        endcase
end
endmodule

三、代码说明

1、要实现60进制的秒计数,可以拆分为个位上的10进制计数和十位上的6进制计数。个位通过计秒信号,直到9后,再计一次数清零,同时产生一个进位信号,十位通过计个位的进位信号,直到5后,再计一次数清零。如此就可实现从00~59的计数了。
2、count_m10模块主要负责10进制计数,在系统时钟的同步下,来一个en脉冲计一次数,计满后回零并产生进位信号t。计数的快慢取决于en端口输入的脉冲周期,产生的计数值则通过data端口向外输出,产生的进位信号通过t端口向外输出。
3、count_m6模块主要负责6进制计数,在系统时钟的同步下,来一个进位信号t计一次数,计满后回零并产生分的进位信号t。计数的快慢取决于个位进位信号t的周期,产生的计数值则通过data端口向外输出,产生的进位信号通过t端口向外输出。
4、seg_count模块为顶层模块,负责产生秒时钟信号(即en脉冲),通过例化10进制计数单元,把秒信号en传入,之后获得按秒变化的计数值count_data0。同时通过例化数码管字形解码单元,把计数值count_data0传入,之后获得个位上的字形编码seg7。同时,通过例化6进制计数单元,把个位上来的进位信号t传入,之后获得按十秒变化的计数值count_data1。同时通过例化数码管字形解码单元,把计数值count_data1传入,之后获得十位上的字形编码seg7。
5、数码管的扫描方式请参考“基于EP4CE6F17C8的FPGA数码管动态显示实例”一文,这里就不再赘述了。

四、实验步骤

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

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

接下来看管脚约束,本例中只用到了两个数码管,一共有10个引脚,再加上时钟晶振和复位按钮,一共12个。具体的端口分配如下图所示。

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

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

另外,还可以点击菜单Tools->Netlist Viewers->RTL Viewer,查看一下生成的RTL电路图,如下图所示。 

最后进行程序下载,并查看结果。下面是两位数码管动态显示秒计数图片的其中几张。

当按下复位键后,所有数码管熄灭,如下图所示。

posted @ 2024-03-25 23:38  fxzq  阅读(165)  评论(0编辑  收藏  举报