必威体育Betway必威体育官网
当前位置:首页 > IT技术

数字时钟设计

时间:2019-09-25 21:44:30来源:IT技术作者:seo实验室小编阅读:73次「手机版」
 

数字时钟

一、功能描述:

设计一个多功能数字时钟,有基本的数字时钟、秒表、闹钟三个功能。其中,数字时钟可设置,秒表可以暂停和清零,闹钟可设置,到达设定时间后LED灯亮。

二、具体说明

1,数字时钟:

当拨码开关为“00”状态时,对应功能为数字时钟状态。当按下一次设置键时,可以使用加减键设置小时,再次按下设置键时,可以使用加减键设置分钟。第三次按下设置键,返回正常数字时钟状态。

2,秒表功能:

当拨码开关为“01”状态时,对应功能为秒表。对应显示状态为“SS.0D”,这里,SS代表秒,范围0~59。D代表毫秒,范围0~9。两个按键,按键一代表暂停与继续,按键二代表清零。

3,闹钟功能:

当拨码开关为“10”状态时,对应功能为闹钟。默认进入闹钟状态时,不可以设置闹钟时间。当按下一次设置键时,可以使用加减键设置闹钟小时,再次按下设置键时,可以使用加减键设置闹钟分钟。第三次按下设置键,返回正常闹钟状态。

三、框图

这里写图片描述

还没有总线的概念,因此各个模块和状态机的连线比较复杂。

主要模块:数字时钟、秒表、闹钟设置、按键驱动、拨码开关驱动、数码管显示以及转换电路、LED显示、状态机。

顶层模块:拨码开关选择功能,对应数码管显示不同内容。实际上就是动态地连接输入输出。

四、各个模块的IO说明

1,按键扫描模块

module key_scan(
    input clk,
    input rst_n,
    input [4:0]button,  //Buttons
    output button_flag, //press button flag
    output [4:0]db_button   //debounced button,High valid
);

借鉴了CrazyBingo设计的按键消抖模块,这里button flag为指示有按键按下的标志,当db_button之中有任何一个按下时,button_flag维持一个时钟的高电平。

2,拨码开关消抖模块

module debounce_slide_switch(
    input clk,
    input rst_n,
    input [1:0]slide_switch,//slide switch
    output [1:0]db_slide_switch //debounced slide switch
);

利用《FPGA prototyping by verilog examples》一书中P146页的拨码开关消抖例子来实现的,主要思路就是间隔一段时间去读取相应电平高低。

3,数码管显示模块

module data_disp_convert(
    input clk,
    input rst_n,
    input [5:0] bin_data1,      //input data to display in binary
    input [5:0] bin_data2,      //    
    input [3:0]dp_in,           //4 decimal points
    input start,        //start to convet binary to BCD
    output [7:0]seg_display,//7 segment display
    output [3:0]an_seg      //anode 7 segment display select
    );

主要就是将输入的二进制数据转换为BCD码,然后将十位和个位分别显示在数码管上,dp_in为各个位的小数点。bin_data1和bin_data2为要显示的两个二进制数据。start为启动bin2bcd转换的信号。输出则是数码管8位段选信号和4个片选信号。

下面来看一下内部构成,主要由‘bin2bcd’的数据格式转换模块和‘four_segment_display’十六进制数码管显示电路组成。

bin2bcd模块的接口如下,主要完成二进制编码到BCD编码的转换。

module bin2bcd(
    input clk,
    input rst_n,
    input start,
    input [12:0] bin,   //0000-8191
    output reg ready,   //ready to receive data to display
    output reg done_tick,//transform done trick signal
    output [3:0] bcd3,bcd2,bcd1,bcd0 //binary coded decimal
    );

这部分利用《FPGA prototyping by Verilog examples》一书中P160页的Binary-to-BCD conversion的例子来实现的,需要注意的是start为启动转换信号,ready为可以转换信号(模块不忙),done_tick为转换结束标志位,当转换完成时,持续一个时钟周期,提醒显示模块读取数据。

four_segment_display模块的接口如下:

module four_segment_display(
    input clk,
    input rst_n,
    input load_data,    //load input signal
    input [3:0]data1,data2,data3,data4, //input data to display, in binary mode
    input [3:0]dp_in,   //4 decimal points
    output reg[7:0]seg_display,//7 segment display
    output reg[3:0]an_seg       //anode 7 segment display select
    );

这部分很简单,就是将输入的四个数据按照预先编定的查找表,然后将显示数据依次送到数码管的段选上。这部分代码参考《FPGA prototyping by Verilog examples》一书中P99页的LED time-multiplesing circuit的例子。

显示部分需要注意的就是,只有当bin2bcd模块的done_tick为高电平时,four_segment_display模块的load_data信号才为高,这时候才载入新的显示数据,以防在bin2bcd模块进行数据转换时,错误的数据被送给four_segment_display。

4,正常时钟模块

module normal_clock(
    input clk,
    input rst_n,
    input button_flag,      //button pressed flag
    input [4:0]db_button,
    output reg [3:0]dp_in,          //data points
    output [5:0]hh_data,mm_data //hour,minute in binary of 6 bits
    );

输入按键信号,输出小时分钟(二进制格式)和小数点。这部分后面单独一个标题来说明。

5,秒表模块

module stopwatch(
    input clk,
    input rst_n,
    input button_flag,      //button pressed flag
    input [4:0]db_button,       //debounced button
    output reg [3:0]dp_in,          //data points
    output reg [5:0]stopwatch_ss,stopwatch_ms   //stopwatch's second and millisecond
    );

这部分代码参考《FPGA prototyping by Verilog examples》一书中P107页的stopwatch的例子。

6,闹钟模块

module alarm_clock(
    input clk,
    input rst_n,
    input button_flag,
    input [4:0]db_button,       //debounced button
    input [5:0]hh_data,mm_data, //get the current time
    output reg led_alarm,           //led for alarming
    output reg [3:0]dp_in,          //data points
    output [5:0]alarm_hh_data,  // the setted alarm's hour
    output [5:0]alarm_mm_data   // the setted alarm's minute
    );

主要就是获取当前的的时钟数据,以便和设置的闹钟数据进行对比。

五,正常时钟模块

这里主要讲一下正常时钟模块的设计思路,其他的闹钟或者秒表实际思路差不多。

先来看一下需要实现的功能:

1)显示格式为HH.MM,秒数利用小数点来显示。随着时钟运行,小数点闪烁。

2)key[0]:在正常运行、设置HH、设置MM三个状态之间进行切换。利用小数点指示当前是设置小时还是分钟。

3)Key[2] “++”。

4)Key[4]“–”。

状态转换图如下:

这里写图片描述

在这里设置三个状态,分别为normal、set_hh、set_mm,对应正常运行、设置小时、设置分钟三个状态。

1) Normal状态,ss_reg正常累加,hh_reg和mm_reg根据进位累加。dp_in[2]跟随秒嘀嗒闪烁。

2)Set_hh状态,ss_reg清零,hh_reg根据按键相应加减。Dp_in[3]亮起。

3)Set_mm状态,ss_reg清零,mm_reg根据按键相应加减。Dp_in[1]亮起。

需要操纵的数据主要是小时数,分钟数,秒数。数据路径需要执行的操作:

1)秒数的累加与清零。

2)hh_reg和mm_reg的进位累加和累减。

为了状态机写起来直观,将控制路径和数据路径分开。根据以上的抽象,FSM对data path的control signal利用以下几个:

1)ss_go来实现秒数的运行和暂停,ss_clr实现秒数的清零。

2)hh_up_down来实现小时数的加减。

3) mm_up_down来实现分钟的加减。

部分代码如下:

信号声明:

//symbolic state declaration
localparam [1:0]
    NORMAL = 2'b00,
    SET_HH = 2'b01,
    SET_MM = 2'b10;
//signal declaration
//clk=100Mhz,(100^6/100_000_000=1Hz)
localparam FREQ_DV=32'd100_000_000; 
//localparam FREQ_DV=32'd20;        //only for test
//signal declaration
reg [1:0] state_reg,state_next;
reg [31:0] ss_tick_reg; //second tick register
wire [31:0]ss_tick_next;
wire ss_tick;           //second tick

reg [7:0] ss_reg,mm_reg,hh_reg; //second,minute,hour register
wire [7:0] ss_next,mm_next,hh_next;
reg ss_go;  //second go or stop signal
            //1:go
            //0:stay at current value
reg ss_clr; //second claer signal
            //1:clear
           //0:other
reg ss_tick_clr;

reg [1:0]hh_up_down;    //hour up or down signal
                        //00:keep current data;
                        //01:counter up
                        //10:counter down
                        //11:clear to zero
reg [1:0]mm_up_down;    //minute up or down signal

状态转换:

//fsmd state & data register
always @(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin           
            state_reg<=NORMAL;  // reset
            ss_tick_reg<=0;
            ss_reg<=0;
            mm_reg<=0;
            hh_reg<=0;
        end
    else 
        begin
            state_reg<=state_next;
            ss_tick_reg<=ss_tick_next;
            ss_reg<=ss_next;
            mm_reg<=mm_next;
            hh_reg<=hh_next;    
        end
end

数据流路径:

//generate the second tick
assign ss_tick_next = (ss_tick_clr || (ss_tick_reg==FREQ_DV-1'b1)) ? 31'd0 : ss_tick_reg + 1'b1;

assign ss_tick = (ss_tick_reg==FREQ_DV-1'b1) ? 1'b1 : 1'b0;

//FSMD data path next-state logic
assign ss_next = (ss_clr)? 0 :
                 (ss_go)? (ss_reg + 1'b1) :
                           ss_reg;
//assign ss_next = (ss_go_clr)? (ss_reg + 1'b1) : 0;

assign hh_next = (hh_up_down == 2'b00)? hh_reg :
                 (hh_up_down == 2'b01)? hh_reg + 1'b1 :
                 (hh_up_down == 2'b10)? hh_reg - 1'b1 : 0;
assign mm_next = (mm_up_down == 2'b00)? mm_reg :
                 (mm_up_down == 2'b01)? mm_reg + 1'b1 :
                 (mm_up_down == 2'b10)? mm_reg - 1'b1 : 0;

状态转换机:

//FSMD control path next-state logic
always @*
begin
    state_next=state_reg;   //default state: the same

    ss_clr=1'b1;            //ss_next=0
    ss_go=1'b0; 
    ss_tick_clr=1'b1;       //second tick clear
    hh_up_down=2'b00;       //hh_next=hh_reg
    mm_up_down=2'b00;       //mm_next=mm_reg

    case(state_reg)
        NORMAL:
            begin
            ss_tick_clr = 1'b0;
            ss_clr = 1'b0;
                if(button_flag)
                    begin
                        if(db_button[0])
                            state_next=SET_HH;
                    end
                else 
                    begin
                        if(ss_tick)
                            begin                               
                                if(ss_reg!=8'd59)
                                    ss_go=1'b1;////////////////////////////////////
                                else
                                    begin
                                        ss_clr = 1'b1;/////////////////////////////
                                        if(mm_reg!=8'd59)
                                            mm_up_down = 2'b01;
                                        else 
                                            begin
                                                mm_up_down = 2'b11;
                                                if(hh_reg !=8'd23)
                                                    hh_up_down = 2'b01;
                                                else
                                                    hh_up_down = 2'b11;
                                            end
                                    end
                            end
                    end
            end
        SET_HH:
            begin
                ss_tick_clr=1'b1;       //second tick clear
                ss_clr=1'b1;            //ss_next=0             
                if(button_flag)
                    begin
                        if(db_button[0])
                            state_next=SET_MM;
                        else if(db_button[2])   //hour ++
                            begin
                                if(hh_reg !=8'd23)
                                    hh_up_down = 2'b01;                                 
                                else 
                                    hh_up_down = 2'b11;
                            end                          
                        else if(db_button[4])   //hour --
                            begin
                                if(hh_reg !=8'd0)
                                    hh_up_down = 2'b10;                                 
                                else 
                                    hh_up_down = 2'b11;
                            end 
                    end
            end
        SET_MM:
            begin
                ss_tick_clr=1'b1;       //second tick clear
                ss_clr=1'b1;            //ss_next=0                     
                if(button_flag)
                    begin
                        if(db_button[0])
                            state_next=NORMAL;
                        else if(db_button[2])   //minute ++
                            begin
                                if(mm_reg !=8'd59)
                                    mm_up_down = 2'b01;
                                else 
                                    mm_up_down = 2'b11;
                            end
                        else if(db_button[4])   //minute --
                            begin
                                if(mm_reg !=8'd0)
                                    mm_up_down = 2'b10;
                                else 
                                    mm_up_down = 2'b11;
                            end
                    end
            end
        default: state_next=NORMAL;
        endcase
end

同步化输出:

//output data point signal
always @(posedge clk or negedge rst_n)
begin
    if(!rst_n)
        begin           
            dp_in <=4'b1111;    //reset,data point off
        end
    else
        begin
            case(state_reg)
                NORMAL:
                    if(ss_tick)
                    dp_in <={1'b1,~dp_in[2],1'b1,1'b1};    
                SET_HH:
                    dp_in <= 4'b0111;
                SET_MM:
                    dp_in <= 4'b1101;
                default:
                    dp_in <= 4'b1111; 
            endcase
        end
end
assign hh_data = hh_reg[5:0];
assign mm_data = mm_reg[5:0];

七,总结

学到了什么,

1)学到了状态机写法,control path和data path的抽象分立和通信。

2)模块化划分,各个模块之间的通信。

待学习:

1)四段式状态机写法。

2)常用的模块间通信控制信号。

相关阅读

移位寄存器专题(verilog HDL设计)

目录 移位寄存器简介 分类 4位右移位寄存器工作原理 1、 16位右移位寄存器 2、 16位左移寄存器 3、 串行输入并行输出寄存器 4

Logo“撞衫”?logo设计网让你找到属于自己的 企业 logo

标志是企业与社会公众之间的视觉桥梁,它必须要向公众传达出企业的行业特性。--奥利弗&middot;沃克Logo是树立品牌的基础,小到应用软

设计师如何管理自己的文档

随着项目的积累,我们的文件目录会变得十分的盘大,如果不好好管理,将会变得一团糟,有时会影响到我们工作的效率与心情,好的文档管理方式

2B SaaS 产品用户系统设计

无论是2B产品,还是2C产品,用户系统都是基础。对于非互联网产品从业者,2C用户系统的场景和功能通过日常各类APP的使用,大家都非常熟悉

用了很多年的 QQ 小黄脸表情要换了,新版是我设计的

作为新版本表情的主设计师,来回答这个问题似乎有点让人 o(*////▽////*)q首先,QQ 默认表情其实已经经历了许多次的更新换代,每一次的

分享到:

栏目导航

推荐阅读

热门阅读