首页 > 其他分享 >基于EP4CE6F17C8的FPGA数码管时钟显示实例

基于EP4CE6F17C8的FPGA数码管时钟显示实例

时间:2024-03-30 14:25:17浏览次数:29  
标签:count FPGA 计数 EP4CE6F17C8 seg 数码管 模块 rst input

一、电路模块

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

二、实验代码

本例使用6个数码管显示时钟的时、分、秒,时与分之间及分与秒之间通过小数点来分隔,代码使用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          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

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

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

wire [3:0] count_data0,count_data1;          //定义计数值的个位和十位
wire t0,t1;                                  //定义个位和十位的进位位 
//下面例化个位计数单元(十进制)
count_m10 u0(.clk(clk), .rst_n(rst), .en(en), .data(count_data0), .t(t0));
//下面例化十位计数单元(六进制)
count_m6 u1(.clk(clk), .rst_n(rst), .en(t0), .data(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          en,                         //计数使能位 
      output reg[7:0]data                       //计数值从00~23共,使用BCD方式
);

always@(posedge clk or negedge rst_n)           //敏感信号为时钟上沿或复位下沿    
begin
    if(rst_n==0)                                //低电平复位
    begin
        data <= 8'd0;                           //复位时计数值清零
    end
    else if(en)                                 //如果计数使能,则执行计数,否则保持上一次的值不变
    begin
        if(data == 8'b00100011)                 //如果计数值BCD码为23,则计数值清零
        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
end
endmodule

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

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

wire t0,t1,t2;                             //定义进位信号
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
//下面定义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), .en(sec), .data(count_data_0), .t(t0));
//下面例化分的计数单元(六十进制)
count_m60 u2(.clk(clk), .rst(rst), .en(t0), .data(count_data_1), .t(t1));
//下面例化时的计数单元(二十四进制)
count_m24 u3(.clk(clk), .rst_n(rst), .en(t1), .data(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显示秒的个位
            begin
                bit <= 6'b111110;
                seg7 <= seg_0;
            end
            4'd1:                         //数码管1显示秒的十位
            begin
                bit <= 6'b111101;
                seg7 <= seg_1;
            end
                4'd2:                     //数码管2显示分的个位
            begin
                bit <= 6'b111011;
                seg7 <= seg_2;
            end
                4'd3:                     //数码管3显示分的十位
            begin
                bit <= 6'b110111;
                seg7 <= seg_3;
            end
                4'd4:                     //数码管4显示时的个位
            begin
                bit <= 6'b101111;
                seg7 <= seg_4;
            end
                4'd5:                     //数码管5显示时的十位
                if (count_data5 == 4'd0)
                begin
                bit <= 6'b011111;         //如果十位为0则不显示
                    seg7 <= 8'hff;
                end
                else
            begin
                bit <= 6'b011111;
                seg7 <= seg_5;
            end
            default:                      //数码管全部熄灭
            begin
                bit <= 6'b111111;
                seg7 <= 8'hff;
            end
        endcase
end
endmodule

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

module count_m60(
      input          clk,                         //板载50HMz系统时钟
      input          rst,                         //复位按键
      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(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)与第一种方式中的一样。 

三、代码说明

1、本例采用了两种方式来实现,第一种方式使用了层层例化的方法,先实现最基本的模10和模6的计数模块,再通过例化方式形成上一层的模60计数模块,然后再实现一个模24的计数模块,共同为顶层的显示模块提供例化。而第二种方式则是独立实现了模60和模24的两个计数模块,为顶层的显示模块提供例化。从编译后的结果来看,第一种方式要节省器件一些。
2、为了提供带小数点的显示效果,额外又编写了一个带小数点的字形解码模块,为时钟分的个位及时的个位提供小数点显示,以作为隔离。
3、在顶层的显示模块中,除了例化需要的模块之外,还承担了产生秒信号和进行数码管扫描的任务,具体可参看“基于EP4CE6F17C8的FPGA双数码管六十进制秒计数实例”一文。
4、在顶层模块中,按时、分、秒的计数只例化出3个实体,即时通过模24的计数单元例化出1个,分通过模60的计数单元例化出1个,秒通过模60的计数单元例化出1个。每个单元的个位和十位的计数规律则由各自的例化元件来实现,这也体现出元件例化的好处。
5、由于显示部分是按位来进行的,所以需要把各个计数单元中的个位和十位分别取出来进行字形解码并通过动态扫描显示。这里规定,每个计数单元使用了BCD编码,所以对应的低4位就是个位部分,高4位就是十位部分,通过位截取的方式分别进行赋值,就可得到六个数码管显示的值了。
6、以秒为例,先产生出秒信号sec,然后通过例化一个模60的计数实体,把sec传入其中,从而得到一个60进制的秒计数值count_data_0。然后把count_data_0的低4位(秒的个位)赋值给秒个位存储变量count_data0,高4位(秒的十位)赋值给秒十位存储变量count_data1。接着通过例化两个字形解码实体,分别把count_data0、count_data1传入,从而得到秒个位的字形编码seg_0和秒十位的字形编码seg_1,最后通过数码管扫描方式把seg_0、seg_1送到数码管对应的位上去显示。分和时的显示也一样,只不过在例化字形解码实体时,个位需要带小数点的,十位的不带。
7、本例中,时显示部分最高位进行了灭零处理,即当时只有个位数时,其最高位不显示(熄灭)。另外,顶层模块中的数码管扫描部分也可独立出来作为一个例化元件,这样顶层文件的内容会更简洁一些。

四、实验步骤

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

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

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

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

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

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

可以看到,第二种方式消耗的器件更多一些,因此推荐使用第一种方式。另外,还可以点击菜单Tools->Netlist Viewers->RTL Viewer,查看一下生成的RTL电路图。

最后进行程序下载,并查看结果。下图为数码管时钟显示的效果,其中第一张的时部分为个位数,所以十位没有点亮(灭零)。

当按下复位键后,时钟清零,所有数码管均熄灭,如下图所示。

 

标签:count,FPGA,计数,EP4CE6F17C8,seg,数码管,模块,rst,input
From: https://www.cnblogs.com/fxzq/p/18096158

相关文章

  • FPGA入门笔记010——UART串口接收模块设计
    1、串口接收模块原理​当对于数据线Rs232_Rx上的每一位进行采样时,一般情况下认为每一位数据的中间点是最稳定的。因此一般应用中,采集中间时刻时的电平即认为是此位数据的电平,如图1所示。图1——串口接收时序图(图中BPS_CLK为采样时钟)​但是在实际工业应......
  • 聊一聊数码管(提高篇第一期)
    数码管数码管内部原理图及其种类如何判别晶体管引脚排列及其内部结构数码管静态显示原理当多位数码管应用于某一系统时,它们的“位选”是可独立控制的,而“段选”是连接在一起的,我们可以通过位选信号控制哪几个数码管亮,而在同一时刻,位选选通的所有数码管上显示的数......
  • FPGA时序约束实战
    改编自8FPGA时序约束实战篇之主时钟约束_checktimingnoclock 以Vivado自带的wave_gen工程为例,该工程的各个模块功能较为明确,如下图所示。为了引入异步时钟域,我们在此程序上由增加了另一个时钟–clkin2,该时钟产生脉冲信号pulse,samp_gen中在pulse为高时才产生信号。下面我......
  • 实测52.4MB/s!全国产ARM+FPGA的CSI通信案例分享!
    CSI总线介绍与优势CSI(CMOSsensorparallelinterfaces)总线是一种用于连接图像传感器和处理器的并行通信接口,应用于工业自动化、能源电力、智慧医疗等领域,CSI总线接口示意图如下所示(以全志科技T3处理器的CSI0为例)。  图1高带宽:CSI总线支持高速数据传输,可以满足多通道高速......
  • FPGA原语
    ODDR代表的是双数据速率输出寄存器(OutputDoubleDataRateRegister)。这种原语用于在一个时钟周期内产生两个数据输出,通常用于高速数据传输和时钟数据恢复等应用。在以下示例VHDL代码中,ODDR原语被用来生成一个双数据速率的输出信号ad9653_1clk。ad1_clk:ODDRgenericmap(......
  • Xilinx ZYNQ 7000+Vivado2015.2系列(八)ARM+FPGA的优势,PS控制PL产生需要的PWM波(基于AXI
    上一节我们观察了AXI总线的信号,了解了基于AXI总线读写的时序,这一节我们继续探索基于AXI总线的设计,来看一看ZYNQ系列开发板的独特优势,PS可以控制PL产生定制化的行为,而不需要去动硬件代码。这次实验是产生频率和占空比可调的PWM(PulseWidthModulation)信号,调用8次,产生8路PWM......
  • 基于FPGA实现的自适应三速以太网
    一、三速以太网千兆以太网PHY芯片是适配百兆和十兆的,十兆就不管了,我们的设计只适应千兆和百兆。根据上图,我们是可以获取当前主机网口的速率信息的。always@(posedgew_rxc_bufr)beginif(w_rec_valid=='d0)beginro_speed<=w_rec_data[2:1];......
  • FPGA学习DDR篇—MIG IP核使用
    文章目录一、MIGIP核配置详解1、第一页2、第二页3、第三页4、第四页5、第五页6、第六页7、第七页8、第八页9、第九页10、第十页二、MIG仿真一、MIGIP核配置详解1、第一页2、第二页3、第三页类型选择DDR34、第四页ClockPeriod:DDR3芯片运行的时钟速率,该数......
  • FPGA与以太网:概念知识
    参考:以太网详解(一)-MAC/PHY/MII/RMII/GMII/RGMII基本介绍-CSDN博客OSI七层模型、TCP/IP四层模型(超详细!!!!!)-CSDN博客TCP/IPLWIPFPGA笔记_rltcpnet和lwip-CSDN博客达芬奇Pro的以太网PHY芯片型号是YT8531(底板);TCP/IP四层模型TCP/IP(TransmissionControlProtocol/InternetProt......
  • FPGA接口系列——UART
    FPGA接口系列——UART一、UART简介UART是一种采用异步串行通信方式的通用异步收发传输器。这里我们主要弄明白两个问题:①什么是串行通信,与并行通信有什么区别②同步串行通信和异步串行通信有什么区别③我们常说的UART和RS232以及RS485又有什么区别?串行通信和并行通信......