当前位置:   article > 正文

基于高云FPGA开发板的多功能数字时钟_高云fpga的多功能tft屏数字钟

高云fpga的多功能tft屏数字钟

目录

一、数字时钟作品的功能

二、数字时钟作品的主体设计原理和功能说明

三、数字时钟的各设计模块说明及模块程序代码

1) 时钟分频模块time_div、freq_div

2)按键消抖模块key_db

3)控制模块control

4)时间正常计数模块time_count

5)时间设置模块time_set

6)时间动态扫描位选模块time_display_sel

7)显示模块display

8)秒表模块stop_watch

9)闹钟模块alarm_clock

10)多功能数字钟的顶层设计clock_demo

四、模块调试和硬件下载测试

本程序进行硬件下载测试的流程:

模块调试:

1.时间正常显示模块调试:

2.时间设置模块调试:

3.秒表模块调试:

4、闹钟模块调试

五、遇到的问题及解决方法

六、课程学习总结


一、数字时钟作品的功能

本作品是一款基于FPGA开发设计的多功能数字时钟,在结合利用高云开发板的相关硬件资源的情况下,设计了这样的一款多功能数字时钟。该数字时钟具备有以下的相关功能:

1、具有基本走时功能,时间范围是00:00:00~23:59:59。

2、具有暂停计时和清零功能。

3、具有调节时间功能。

4、闹钟功能,定时时间可调,可以发出提示信号。

5、秒表功能,具有清0和暂停。

二、数字时钟作品的主体设计原理和功能说明

数字时钟作品设计的基础语言是Verilog,在高云软件(Gowin V1.9.8.11 Education)上面完成程序的编写,并进行系统的仿真,最后将代码下载到高云FPGA实物开发板上面测试,验证数字时钟的功能,最终完成本次的课程设计。 

根据项目的设计要求,多功能数字时钟主要完成了基本的时间显示功能、时间设置功能、闹钟功能及秒表功能。在各种功能之间,需要进行界面的转换,因此需要设定外部功能按键,用来控制界面跳转和设置参数。同时,在不同的界面下,需要完成各种小的功能实现,以实现对时间的设置、秒表、闹钟等等功能的设置和实现。

1)时间正常显示、设置时间的状态信息、秒表信息等功能的显示   

采用四段共阳极数码管来完成界面信息显示,在按键的控制下,实现时间的设置,以及在设置过程中的时间显示、秒表计时显示、闹钟时间设置与查看。

2)四个按键开关的控制,对展示的界面进行调节

(1)按键1(key1):

功能1,进入时间正常显示功能模式;

功能2,进入时间设置功能模式;

功能3,进入秒表功能模式;

功能4,进入闹钟时间查看与设置功能模式。

(2)按键2(key2):

按键2主要实现时间设置、闹钟设置、秒表的暂停与开始计时,与按键1配合使用。在功能2模式时,用做时、分、秒数码管的移位,按一下,就会出现“时-分-秒”的依次移位,便于在特定的数码管位置进行时间的设置,在退出功能2模式时,功能1的正常时间显示会马上更新为新的时间。在功能3中,当按下按键时,秒表将开始计时,同时,在按下按键时,停下计时。在功能4模式时,用做时、分、秒的移位,按一下,就会实现“时-分-秒”的依次移位,便于在特定的数码管位置进行时间的设置。

(3)按键3(key3):

按键3主要用于闹钟设置、秒表和时间设置中的调整按键。在功能2模式时,用做时、分、秒的数字调整,按一下,会使当前按键2选择的位置的数字加1。在功能4模式时,用做时、分、秒的数字调整,按一下,会使当前按键2选择的位置的数字加1。

(4)按键4(key4):

主要与按键3配合使用,当按键4按下时,此时的相关标志位打开,再次按下按键3,当前的数码管数字清0,再次按下按键4,便可以继续实现按键3的+1操作,主要用于模式2的时间设置和模式3的秒表模式。同时,在模式1中,当按下按键4时,此时将会暂停当前的时间显示,时钟不会跳动,实现暂停。

3)相关提示信号和闹钟时间到达时的提醒信号

在模式1的正常时间显示中,在数码管的中间位置设置两个小点的闪烁信号,每隔1s的时钟,该指示灯便会闪一下,用于提醒用户此时正在进行时间走时。在模式3中,秒表在正常计时的时候,对从左往右第1位数码管和第3位数码管设置小数点,用于区分此时的分、秒、十分秒。同时,在模式4中,当正常时间与闹钟设置的之间相等时,会发送一个提醒信号LED,信号的持续时长为60s,起到闹钟的功能。

多功能数字钟总体设计框图如下:

三、数字时钟的各设计模块说明及模块程序代码

1) 时钟分频模块time_divfreq_div

时钟分频模块time_div主要利用模块freq_div模块来完成基准时钟clk的分频,得到计时模块clk1、数码管动态扫描模块clk1k、秒表模块clk10、设置时间时,单独显示一个数码管所需要的时钟信号clk200,此时的时钟分别为1Hz 、1kHz、100Hz和200Hz

其中,在time_div模块中,将分频系数1、1000、100、200通过下面的模块进行例化,并传入freq_div模块,便可以得到相应的时钟信号输出。

程序如下所示:

  1. //clk:系统基准时钟  27MHZ
  2. //clk1k:动态扫描时钟信号  1kHZ
  3. //clk200:闪烁时钟信号  200HZ
  4. //clk10:秒表--时钟信号  10HZ
  5. //clk1:秒--时钟信号  1HZ=1s
  6. module time_div(
  7.   input   clk,     //系统的自带时钟 27MHz=27 000 000
  8.   output   clk1k,
  9.   output   clk200,
  10.   output   clk100,
  11.   output   clk1    
  12. );
  13. localparam CLK_IN_FREQ = 27000000;   //设备的时钟频率
  14. freq_div #( //1000HZ分频 --硬件不同在此处的分频随时调整
  15.      .DIV_RATE_2N  (CLK_IN_FREQ/1000)
  16. ) freq_div_i_clk1k(
  17.     .clk_in_i    (clk),
  18.     .rst_i       (rst),
  19.     .clk_out_o   (clk1k)  
  20. );
  21. freq_div #(      //200HZ分频
  22.      .DIV_RATE_2N  (CLK_IN_FREQ/200)
  23. ) freq_div_i_clk200(
  24.     .clk_in_i    (clk),
  25.     .rst_i       (rst),
  26.     .clk_out_o   (clk200)  
  27. );
  28. freq_div #(     //100HZ分频
  29.      .DIV_RATE_2N  (CLK_IN_FREQ/10)
  30. ) freq_div_i_clk10(
  31.     .clk_in_i    (clk),
  32.     .rst_i       (rst),
  33.     .clk_out_o   (clk10)  
  34. );
  35. freq_div #(     //1HZ分频
  36.      .DIV_RATE_2N  (CLK_IN_FREQ/1)
  37. ) freq_div_i_clk1(
  38.     .clk_in_i    (clk),
  39.     .rst_i       (rst),
  40.     .clk_out_o   (clk1)  
  41. );
  42. endmodule

2)按键消抖模块key_db

按键消抖模块,主要实现外部的四个按键信号输入时,将输入的按键信号进行消抖,防止抖动的误操作。

其中,在下面的模块例化中,将输入的按键信号key1key2key3key4送入key_db模块中,便可以得到4个已经消抖之后的按键信号key_db1key_db2key_db3key_db4

程序如下所示:

  1. key_db    key_db_i1( // 按键去抖动信1
  2.    .clk    (clk),
  3.    .key_i    (key1),
  4.    .key_db_o     (key_db1)    
  5. );
  6. key_db    key_db_i2( // 按键去抖动信号2
  7.    .clk    (clk),
  8.    .key_i    (key2),
  9.    .key_db_o     (key_db2)    
  10. );
  11. key_db    key_db_i3( // 按键去抖动信号 3
  12.    .clk    (clk),
  13.    .key_i    (key3),
  14.    .key_db_o     (key_db3)    
  15. );
  16. key_db    key_db_i4( // 按键去抖动信号 4
  17.    .clk    (clk),
  18.    .key_i    (key4),
  19.    .key_db_o     (key_db4)    
  20. );
  21. //按键消抖模块 外部按键信号输入,实现消抖输出
  22. `timescale 1ns/1ns
  23. module key_db #(
  24. parameter RST_VALUE = 1'b0,
  25. parameter CLK_FREQ_Hz = 27000000
  26. )(
  27. input clk,
  28. input rst,
  29. input key_i,
  30. output key_db_o
  31. );
  32. wire clk_100Hz;
  33. freq_div #(
  34. .DIV_RATE_2N (CLK_FREQ_Hz/100)
  35. ) freq_div_i (
  36. .clk_in_i (clk),
  37. .rst_i (rst),
  38. .clk_out_o (clk_100Hz)
  39. );
  40. wire [2:0] samples;
  41. shift_reg #(
  42. .WIDTH (3),
  43. .INIT_VALUE ({3{RST_VALUE [0]}})
  44. )shift_reg_i(
  45. .clk (clk_100Hz),
  46. .rst (rst),
  47. .load_i (1'b0),
  48. .load_data_i (3'h0),
  49. .reg_in_i (key_i),
  50. .reg_out_o (samples[2:0])
  51. );
  52. assign key_db_o = RST_VALUE ? |samples[2:0] : &samples[2:0];
  53. endmodule

3)控制模块control

控制模块主要是对四个功能模块进行整体控制,包括对时间正常显示、时间设置、秒表和闹钟显示及调整的控制,其中,四个功能模块的转换是通过按键key1实现的。

按键key1按下一次,此时在程序中控制的标志位set_en便会+1,再在程序的下面设置合适索引值的case语句,就使下一个功能的使能端处于高电平状态(表示选中),此时便会显示该界面,再按下一次,就会转入下一个功能,使下一个功能的使能端有效,依次类推,便可以实现四个功能time_entime_set_enstopwatch_enalarm_clock_en的切换。

程序如下所示:

  1. module control(
  2.   input key1,
  3.   output reg time_en,   //时间正常工作使能
  4.   output reg time_set_en,   //时间设置使能
  5.   output reg stopwatch_en,  //秒表使能
  6.   output reg alarm_clock_en );   //闹钟设置使能
  7. reg [2:0] set_en;    //用于功能选择
  8. always @(posedge key1)    begin
  9.     if (set_en < 3)    //各种功能信号的产生,通过自增进行叠加。
  10.       set_en <= set_en + 1;
  11.     else
  12.       set_en <= 0;
  13.     case (set_en)      //根据各种功能信号产生相对应的控制信号,高电平有效
  14.  3'b000: begin   //时间正常工作使能
  15.           time_en <= 1;
  16.           time_set_en <= 0;
  17.           stopwatch_en <= 0;
  18.           alarm_clock_en <= 0;
  19.         end
  20. 3'b001: begin //时间设置使能
  21.           time_en <= 0;
  22.           time_set_en <= 1;
  23.           stopwatch_en <= 0;
  24.           alarm_clock_en <= 0;
  25.         end
  26.     3'b010: begin //秒表使能
  27.           time_en <= 0;
  28.           time_set_en <= 0;
  29.           stopwatch_en <= 1;
  30.           alarm_clock_en <= 0;
  31.         end
  32.     3'b011:begin //闹钟使能
  33.           time_en <= 0;
  34.           time_set_en <= 0;
  35.           stopwatch_en <= 0;
  36.           alarm_clock_en <= 1;
  37.         end
  38.       default: begin      //全部关闭
  39.           time_en <= 0;
  40.           time_set_en <= 0;
  41.           stopwatch_en <= 0;
  42.           alarm_clock_en <= 0;
  43.         end
  44.     endcase
  45.   end
  46. endmodule

4)时间正常计数模块time_count

时间正常计数模块主要实现六位数字时间的正常运行,并将生成的数字情况发送给数码管进行显示。

其中,秒和分本质是为0~59的六十进制计数器,小时为0~23的二十四进制计数器。在具体进行计数的过程中,秒的低位进行依次+1,当达到9时,秒的低位将会清0,此时秒的高位便会+1。之后,当秒的高位达到5,秒的低位达到9时,此时便会产生相应的秒向分的进位信号cout1,分的低位便会实现+1,同样,在相同的逻辑之下,分也会产生向小时的进位信号cout2,小时低位也会+1,其中需要注意的是,小时高位只有2,再次进位便会到达天的计数。

同时,在时间计数模块中,还引入时间设置的情况,用于在二者的模式进行转换的时候,可以将原先的计数情况进行更新,以达到设置时间的功能。

在进行秒的计数过程中,将秒的低位计数设置标志位flag1,当按键key4按下的时候,此时秒的低位停止计数,此时正常时间显示可以实现暂停的功能。

程序如下所示:

  1. //clk1:秒功能的时钟信号,为1Hz的脉冲信号
  2. //time_en:时间正常工作的使能信号    time_set_en:时间设置使能信号
  3. //hourh_set,hourl_set, minh_set,minl_set, sech_set,secl_set:设置后的小时、分和秒(分别两位)
  4. //hourh,hourl:小时的高、低位显示         minh,minl:分的高、低位显示
  5. //sech,secl:秒的高、低位显示      cout:进位输出,即计满24小时,向下一天产生的进位输出信号
  6. module time_count(
  7.   input clk1,
  8.   input clk,
  9.   input key4,    // 添加按键信号
  10.   input time_en,  //control模块--时间正常工作使能
  11.   input time_set_en,  //时间设置使能
  12.   input alarm_clock_en,
  13.   input [3:0]  hourh_set,   //小时、分和秒(分别两位)设置
  14.   input [3:0]  hourl_set,
  15.   input [3:0]  minh_set,
  16.   input [3:0]  minl_set,
  17.   input [3:0]  sech_set,
  18.   input [3:0]  secl_set,
  19.   output reg [3:0]  hourh,  //小时、分和秒(分别两位)显示
  20.   output reg [3:0]  hourl,
  21.   output reg [3:0]  minh,
  22.   output reg [3:0]  minl,
  23.   output reg [3:0]  sech,
  24.   output reg [3:0]  secl,
  25.   output reg cout,  //进位输出
  26.   output reg two_points  );    //输出数码管中间的两个点                        
  27. reg flag1;
  28. reg cout1,cout2;    //cout1和cout2分别为秒向分、分向小时的进位
  29. reg [31:0] counter = 0;
  30. always @(posedge key4)   begin  //每次检测key4的按下,进行标志位翻转
  31.     flag1 = ~flag1;                          
  32. end
  33. always @(posedge clk )  begin   //计数器,实现对数码管的中间小点的闪烁控制--基于系统时钟27Mhz
  34.   if (time_en)
  35.     if (flag1) begin
  36.       if (counter == 13500000)  begin
  37.           two_points <= ~two_points;  // 反转小点状态
  38.           counter <= 0; // 重置计数器
  39.         end
  40.       else
  41.         begin
  42.           counter <= counter + 1;
  43.         end
  44.     end
  45.   else  if (alarm_clock_en)  begin
  46.             two_points <= 0;
  47.           end
  48.         else
  49.           begin
  50.             two_points <= 1;
  51.           end
  52. end
  53. always@(posedge clk1 or posedge time_set_en)  begin
  54.     if (time_set_en) begin   //秒的设置
  55.         sech<=sech_set;     //设置给秒的高低两位
  56.         secl<=secl_set;
  57.       end
  58.     else  if (time_en)    begin
  59.            //秒的计数情况
  60.         if (secl==9)  //秒的低位为9时
  61.           begin
  62.           secl<=0;     //秒低位清0
  63.           if (sech==5)   //秒高位为5时
  64.             begin
  65.               sech<=0;   //秒的低位为9,秒的高位为5时,秒的高位清0
  66.               cout1<=1;  //秒向分进1
  67.             end
  68.           else
  69.             begin
  70.               sech<=sech+1;  //秒高位不为5,继续+1
  71.             end
  72.           end
  73.         else if (flag1)   //当标志位翻转时,秒的低位不再变化,实现暂停
  74.           begin
  75.           secl<=secl+1;  //秒低位不为9,继续+1
  76.           cout1<=0;
  77.           end                
  78.       end
  79.   end
  80. always@(posedge cout1 or posedge time_set_en)  //检测秒向分的进位
  81.   begin  
  82.     if (time_set_en)  begin    //分的设置
  83.         minh<=minh_set; //设置给分的高低两位
  84.         minl<=minl_set;
  85.       end  
  86.     else if (minl==9)  begin   //分的计数情况
  87.         minl<=0;     //分低位清0
  88.         if(minh==5)   begin //分高位为5时
  89.             minh<=0;  //分的低位为9,分的高位为5时,分的高位清0
  90.             cout2<=1;  //分向时进1
  91.           end
  92.         else
  93.           begin
  94.             minh<=minh+1;  //分高位不为5,继续+1
  95.           end
  96.       end
  97.       else
  98.           begin
  99.           minl<=minl+1;
  100.           cout2<=0;
  101.           end
  102.   end
  103. always@(posedge cout2 or posedge time_set_en)   begin    //检测分向时的进位
  104.     if(time_set_en)    //小时的设置
  105.       begin
  106.         hourh<=hourh_set;
  107.         hourl<=hourl_set;
  108.       end
  109.     else if ((hourh==2)&&(hourl==3))  begin //小时的计数情况
  110.         hourh<=0;  //时高位清0
  111.         hourl<=0;  //时低位清0
  112.         cout<=1;   //向天进位
  113.       end
  114.     else if (hourl==9)  begin  //时的低位为9
  115.         hourl<=0;   //时的低位清0
  116.         if(hourh==2)  //时高位为2时
  117.           hourh<=0;   //时的高位清0
  118.         else
  119.           hourh<=hourh+1;  //时的高位不为2,高位继续+1
  120.       end
  121.     else
  122.       begin
  123.       hourl<=hourl+1;  //都不满足,时的低位继续+1
  124.       cout<=0;
  125.       end
  126.    end
  127. endmodule

5)时间设置模块time_set

时间设置模块time_set的主要功能是对当前时间的时、分和秒进行调整,可以实现指定数码管时间的更改,并将更改后的时间用于正常时间的更新。

time_set_en高电平有效时,可以对当前时间进行调整,每按一下key2,位选择信号time_display就会进行+1,改变此时的值,当time_display=O时,对小时高位进行调整;当time_display=1时,对小时低位进行调整。依次类推,直到对时、分、秒的高位和低位都调整完毕为止。

   其中,当前数字的+1操作,是通过按键key3实现的,每按一次key3,就会在小时高位上+1,选择不同的数码管时,就会在对应的数码管上+1。 在time_set模块中,

加入了当前数字的清0操作,在进行数字+1时,首先判断标志位flag2,当标志位为真时,清空当前的数码管。

程序如下所示:

  1. //key2:在某种功能的条件下,各种设置位的选择信号
  2. //key3:在设置位有效的条件下,对设置位进行加1操作
  3. //time_set_en:时间设置使能信号
  4. //hourh_set,hourl_set,minh_set,minl_set,sech_set,secl_set:设置后的小时、分和秒
  5. //time_display:设置中的数码管选择   6个
  6. module time_set(
  7.       input key2,      //各种设置位的选择信号
  8.       input key3,      //+1
  9.       input key4,      //当前数码管,清0
  10.       input time_set_en,  //时间设置使能信号
  11.       output reg [3:0] hourh_set,   //设置后的小时、分和秒
  12.       output reg [3:0] hourl_set,
  13.       output reg [3:0] minh_set,
  14.       output reg [3:0] minl_set,
  15.       output reg [3:0] sech_set,
  16.       output reg [3:0] secl_set,
  17.       output reg [2:0] time_display  //设置中的数码管选择 6
  18. );
  19. reg flag2;   //通过标志位清0
  20. always @(posedge key4)    //每次检测key4的按下,进行标志位翻转
  21. begin
  22.     flag2 = ~flag2;
  23. end
  24. always@(posedge key2)
  25.       begin
  26.       if(time_set_en)
  27.             begin         //6个数码管选择
  28.             if(time_display<5)   //时、分、秒6位分别选择
  29.                   time_display<=time_display+1;
  30.             else
  31.                   time_display<=0;
  32.             end
  33.       end
  34. always@(posedge key3)   //按下按键key3
  35.       begin
  36.             if (time_set_en)
  37.             begin
  38.                   case(time_display)
  39.                   3'b000:   //设置小时高位
  40.                   begin
  41.                         if (flag2)   //通过标志位,清0小时的高位
  42.                         begin
  43.                               hourh_set<=0;
  44.                         end
  45.                         else if(hourh_set<2)  //小时的高位最大为2
  46.                                     hourh_set<=hourh_set+1;  //+1
  47.                               else
  48.                                     hourh_set<=0;
  49.                   end
  50.                   3'b001:   //设置小时低位
  51.                   begin
  52.                         if (flag2)   //通过标志位,清0小时的低位
  53.                         begin
  54.                               hourl_set<=0;
  55.                         end
  56.                         else if(hourl_set<9)  //小时的低位最大为9
  57.                               hourl_set<=hourl_set+1;
  58.                         else
  59.                             hourl_set<=0;
  60.                   end
  61.                   3'b010:   //设置分高位
  62.                   begin
  63.                         if (flag2)   //通过标志位,清0分的高位
  64.                         begin
  65.                              minh_set<=0;
  66.                         end
  67.                         else if(minh_set<5)  //分的高位最大为5
  68.                  minh_set<=minh_set+1;
  69.                         else
  70.                         minh_set<=0;
  71.                   end
  72.                   3'b011:   //设置分低位
  73.                   begin
  74.                         if (flag2)   //通过标志位,清0分的低位
  75.                         begin
  76.                               minl_set<=0;
  77.                         end
  78.                         else if(minl_set<9)  //分的低位最大为9
  79.                               minl_set<=minl_set+1;
  80.                         else
  81.                               minl_set<=0;
  82.                   end
  83.                   3'b100:   //设置秒高位
  84.                   begin
  85.                         if (flag2)   //通过标志位,清0秒的高位
  86.                         begin
  87.                               sech_set<=0;
  88.                         end
  89.                         else if(sech_set<5)  //秒的高位最大为5
  90.                               sech_set<=sech_set+1;
  91.                         else
  92.                               sech_set<=0;
  93.                   end
  94.                   3'b101:     //设置秒低位
  95.                   begin
  96.                         if (flag2)   //通过标志位,清0秒的低位
  97.                         begin
  98.                               secl_set<=0;
  99.                         end
  100.                         else if(secl_set<9)  //秒的低位最大为9
  101.                               secl_set<=secl_set+1;
  102.                         else
  103.                               secl_set<=0;
  104.                   end
  105.                   endcase
  106.             end
  107.       end
  108. endmodule

6)时间动态扫描位选模块time_display_sel

时间动态扫描位选模块time_display_sel主要实现分时显示时间数据,通过一个高频的时钟信号clk1k对数据进行分时传送,即将时、分、秒的高位和低位6个数据传送给对应时、分、秒的高位或低位的数码管进行显示。同时,在设置时间时,设置一个较低的时钟信号clk200,用于扫描单个数码管显示,以示区分。

在此处,应该注意的是,正常显示时间信息时,为了能够同时看到时、分、秒,要求高频的时钟频率大于人眼的分辨率。但是也不能太高,时钟频率太高,会使数码管虚亮,这时看到数码管显示的信息均为“8”,不能正确显示信息;同时也不能太低,时钟频率太低,会产生闪烁现象,甚至不能同时看到时、分、秒的信息。

所以,高频采用的是1kHz的时钟信号。在进行时间设置时,要求时、分、秒是单个数码管显示的,因此采用的时钟信号频率是200Hz。在不同的使能信号之下,进入不同的模式时,便会使用不同的时钟信号进行显示。

程序如下所示:

  1. //clk1k:对时、分、秒的数据进行分时传送的时钟信号
  2. //clk200:用于闪烁的时钟信号
  3. //time_en:时间正常工作使能
  4. //time_set_en:时间设置使能
  5. //time_display:时间设置时的同步信号,对位进行闪烁显示控制
  6. //time_display_sel:动态扫描位选输出信号
  7. module time_display_sel(      
  8.       input clk1k,  //对时、分、秒的数据进行分时传送的时钟信号
  9.       input clk200,   //闪烁的时钟信号
  10.       input time_en,  //时间正常工作使能
  11.       input time_set_en,  //时间设置使能
  12.       input stopwatch_en,
  13.       input alarm_clock_en,
  14.       input [2:0] alarm_clock_display,
  15.       input [2:0] time_display,  //根据数码管的选择位置,进行闪烁显示控制
  16.       output reg [5:0] time_display_sel  ); //动态扫描位选输出信号(6位)
  17. reg clk0;   reg [2:0] sel;     reg [2:0] time_sel;
  18. always@(posedge clk1k)   begin  //分时传送扫描
  19.             if(time_sel<5)   //6位数码管  
  20.                   time_sel<=time_sel+1;
  21.             else
  22.                   time_sel<=0;
  23.       end
  24. //扫描时钟的选择
  25. always@(time_en or time_set_en or clk1k or clk200
  26.                         or stopwatch_en or alarm_clock_en) begin
  27.             if(time_en)   begin   //正常显示时间信息时,选择1kHz的扫描时钟  快一些
  28.                         clk0<=clk1k;
  29.                         sel<=time_sel;
  30.                   end
  31. //设置调整时间信息时,选择200Hz的扫描时钟   慢一些
  32.             else  if(time_set_en)   begin                    
  33.                         clk0<=clk200;
  34.                         sel<=time_display;   //选择的单个数码管
  35.                   end
  36.             else if(stopwatch_en) begin                  
  37.                         clk0<=clk1k;
  38.                         sel<=time_sel;
  39.                   end
  40.             else if(alarm_clock_en)  begin                  
  41.                         clk0<=clk200;
  42.                         sel<=alarm_clock_display;
  43.                   end            
  44.       end
  45. //为时、分、秒的高位和低位数据进行动态位选择
  46. always@(posedge clk0)  begin  
  47.             case(sel)     //6位分别传送给位控信号
  48.                   3'b000: time_display_sel<=6'b100000;
  49.                   3'b001: time_display_sel<=6'b010000;
  50.                   3'b010: time_display_sel<=6'b001000;
  51.                   3'b011: time_display_sel<=6'b000100;
  52.                   3'b100: time_display_sel<=6'b000010;
  53.                   3'b101: time_display_sel<=6'b000001;
  54.                   default: time_display_sel<=6'b000000;
  55.           endcase
  56.       end
  57. endmodule

7)显示模块display

显示模块display主要完成正常时间、时间设置、秒表功能和闹钟设置的时间信息用数码管进行显示。 

其中,在显示不同模式的信息时,采用的方法是取不同的使能信号,当使能信号为真的时候,索引当前的位控信息,分别发送不同的段码信息和位控信息,以实现在指定的数码管上面显示不同的时间信息。

需要注意的是,在显示的时候,由于采用分模块编写的思想,因此要引入各模块的当前数字时间信息,以实现不同的段码信息输出。

程序如下所示:

  1. //display_data:译码后的数据,送给数码管显示
  2. //display_sel:数码管的动态扫描位选择信号
  3. module display (
  4.   input time_en,      //时间正常工作使能
  5.   input time_set_en,    //时间设置使能信号
  6.   input stopwatch_en,    //秒表使能信号
  7.   input alarm_clock_en,    //闹钟的使能信号
  8.   input [5:0] time_display_sel,    //动态扫描位选输出信号(6位)
  9.   input [3:0] hourh,    //显示正常时间的 时、分、秒、百分秒的高低位
  10.   input [3:0] hourl,
  11.   input [3:0] minh,
  12.   input [3:0] minl,
  13.   input [3:0] sech,
  14.   input [3:0] secl,
  15.   input [3:0] s100minh,   //显示秒表的分,秒,百分秒
  16.   input [3:0] s100minl,
  17.   input [3:0] s100sech,
  18.   input [3:0] s100secl,
  19.   input [3:0] s100h,
  20.   input [3:0] s100l,
  21.   input  [3:0] alarm_hourh_set,   //设置后的小时、分和秒
  22.   input  [3:0] alarm_hourl_set,
  23.   input  [3:0] alarm_minh_set,
  24.   input  [3:0] alarm_minl_set,
  25.   input  [3:0] alarm_sech_set,
  26.   input  [3:0] alarm_secl_set,
  27.   output reg  [5:0] display_sel,   //数码管的动态扫描位选择信号
  28.   output reg  [6:0] display_data  //译码后的数据,送给数码管显示
  29. );
  30. reg [3:0] data;   //4位二进制,保存时钟每一位的数字情况
  31. always@(time_en or time_set_en or stopwatch_en or alarm_clock_en or
  32.           time_display_sel   or display_sel or
  33.            hourh or hourl or minh or minl or sech or secl or
  34.           s100minh or s100minl  or s100sech or s100secl or s100h or s100l ) begin
  35.     if((time_en==1) || (time_set_en==1))   begin  //正常时间和设定时间显示
  36.         display_sel<=time_display_sel;
  37.         case(time_display_sel)      //6位数码管
  38.             6'b100000:data<=hourh;
  39.             6'b010000:data<=hourl;
  40.             6'b001000:data<=minh;
  41.             6'b000100:data<=minl;
  42.             6'b000010:data<=sech;
  43.             6'b000001:data<=secl;
  44.            default:data<=4'b0;
  45.         endcase
  46.       end
  47.     else if(stopwatch_en==1)       //秒表的时间显示
  48.       begin
  49.         display_sel<=time_display_sel;
  50.         case(time_display_sel)
  51.             6'b100000:data<=s100minh;
  52.             6'b010000:data<=s100minl;
  53.             6'b001000:data<=s100sech;
  54.             6'b000100:data<=s100secl;
  55.             6'b000010:data<=s100h;
  56.             6'b000001:data<=s100l;
  57.             default:data<=4'b0;
  58.         endcase
  59.       end
  60.     else if(alarm_clock_en)        //闹钟的设置时间显示
  61.       begin
  62.         display_sel <= time_display_sel;
  63.         case(time_display_sel)
  64.             6'b100000:data<=alarm_hourh_set;
  65.             6'b010000:data<=alarm_hourl_set;
  66.             6'b001000:data<=alarm_minh_set;
  67.             6'b000100:data<=alarm_minl_set;
  68.             6'b000010:data<=alarm_sech_set;
  69.             6'b000001:data<=alarm_secl_set;
  70.             default:data<=4'b0;
  71.         endcase
  72.       end
  73.     case(data)          //数码管的8位译码情况  ,采用共阳极输出  
  74.          4'b0000:display_data<= ~7'b1111110; //数字0
  75.          4'b0001:display_data<= ~7'b0110000; //数字1
  76.          4'b0010:display_data<= ~7'b1101101; //数字2
  77.          4'b0011:display_data<= ~7'b1111001; //数字3
  78.          4'b0100:display_data<= ~7'b0110011; //数字4
  79.          4'b0101:display_data<= ~7'b1011011; //数字5
  80.          4'b0110:display_data<= ~7'b1011111; //数字6
  81.          4'b0111:display_data<= ~7'b1110000; //数字7
  82.          4'b1000:display_data<= ~7'b1111111; //数字8
  83.          4'b1001:display_data<= ~7'b1111011; //数字9
  84.          default:display_data<= ~7'b0; //数字0
  85.     endcase
  86.   end
  87. endmodule

8)秒表模块stop_watch

在实际中,在此假设秒表功能需要显示分、秒和十分秒,所以这里就需要有实现百分秒功能的时钟信号clk10,用与时间正常显示模块相同的计数方法,分别实现十分秒向秒的进位计数,秒向分的进位计数,进而完成秒表功能。

在秒表功能实现中,加入标志位flag3,并与按键key2进行绑定,当按键按下的时候,此时的十分秒将会停止计数,实现暂停的效果,再次按下按键,会继续进行计数。并且在每一个计数中,加入标志位flag2并与按键key4进行绑定,当按键按下的时候,此时所有的数码管的计数值将会清0

另外,在此说明,由于在原开发板上面进行秒表功能实现的时候,时钟信号clk10会与其他的时钟信号产生同步时序混叠的情况,始终无法解决这个问题。因此,将秒表的功能移到另一块开发板上面实现,其他的数码管扫描模块和显示模块均与原开发板相同,具体说明可见下文详述。

程序如下所示:

  1. //秒表显   分、秒、十分秒。
  2. //clk10:秒表时钟信号
  3. // key2,      //暂停和开始计数按键    key4,      //当前数码管,清0
  4. // s10minl,  //秒表的分的低位   s10sech,  //秒表的秒的高位
  5. // s10secl,  //秒表的秒的低位
  6. //  s10       //秒表的十分秒
  7. module stop_watch(
  8.       input clk10,   //秒表时钟信号--0.1s
  9.       input key2,      //暂停和开始计数按键
  10.       input key4,      //当前数码管,清0
  11.       output reg [3:0]  s10minl,  //秒表的分的低位
  12.       output reg [3:0]  s10sech,  //秒表的秒的高位
  13.       output reg [3:0]  s10secl,  //秒表的秒的低位
  14.       output reg [3:0]  s10       //秒表的十分秒
  15. );
  16. reg count1,cout2;   //进位信号
  17. reg flag3;   //两个按键的标志位
  18. reg flag2;  
  19. always @(posedge key2)    //每次检测key3的按下,暂停和开始
  20. begin
  21.     flag3 = ~flag3;
  22. end
  23. always @(posedge key4)    //每次检测key4,用于进行清0操作
  24. begin
  25.     flag2 = ~flag2;
  26. end
  27. always@(posedge clk10  or posedge flag2) //十分秒的计数  
  28. begin
  29.       if (flag2)
  30.             begin
  31.             s10<=0;
  32.             end
  33.       else if (s10==9)   //十分秒到9时
  34.                   begin
  35.                   count1<=1;  //十分秒进位到1s
  36.                   s10<=0;  
  37.                   end
  38.             else if (flag3)    //检测按键的标志位,实现暂停
  39.                   begin
  40.                   s10<=s10+1;  //高位没到时,继续+1
  41.                   count1<=0;
  42.                   end
  43. end
  44. always@(posedge count1 or posedge flag2)   //秒的计数情况
  45. begin    
  46.       if (flag2)
  47.       begin
  48.             s10secl<=0;
  49.             s10sech<=0;
  50.       end
  51.       else if (s10secl==9)  //秒的低位为9时
  52.             begin
  53.             s10secl<=0;     //秒低位清0
  54.             if (s10sech==5)   //秒高位为5时
  55.                   begin
  56.                   s10sech<=0;   //秒的低位为9,秒的高位为5时,秒的高位清0
  57.                   cout2<=1;  //秒向分进1
  58.                   end
  59.             else
  60.                   begin
  61.                   s10sech<=s10sech+1;  //秒高位不为5,继续+1
  62.                   end
  63.             end
  64.       else    
  65.             begin
  66.             s10secl<=s10secl+1;  //秒低位不为9,继续+1
  67.             cout2<=0;
  68.             end          
  69. end    
  70. always@(posedge cout2 or posedge flag2)  //检测秒向分的进位
  71. begin  
  72.       if (flag2)
  73.             begin
  74.             s10minl<=0;
  75.             end
  76.       else  if (s10minl==9)       //分低位计数情况
  77.             begin
  78.             s10minl<=0;          //分低位清0
  79.             end
  80.       else
  81.             begin
  82.             s10minl<=s10minl+1;
  83.             end
  84. end
  85. endmodule

9)闹钟模块alarm_clock

闹钟模块主要完成闹钟的时间设置及闹钟时间到后的提示。闹钟模块具体的设置时间的方法与时间设置模块time_set相同,均采用key2的按键叠加检测到不同的数码管,然后当key3按下的时候,当前数码管的数字将会+1,实现可以调节时间的功能。同时,闹钟设置时间与正常时间相等时会产生一个提示信号alarm,此信号持续60s的时间,可以将这个信号接到蜂鸣器上,用于声音提示。在本实验的开发板中,我们利用灯的亮起来近似替代。

程序如下所示:

  1. //alarm_clock_en:闹钟的使能信号
  2. //alarm:闹钟时间到的提示信号
  3. //alarm_clock_display_sel:闹钟显示的同步信号
  4. module alarm_clock(
  5.       input clk1k,    //计时60s的时钟信号
  6.       input alarm_clock_en,   //闹钟的使能信号
  7.       input key2,   //时、分、秒高低位依次选择
  8.       input key3,   //+1
  9.       input [3:0] hourh,   //闹钟的时、分、秒高低位
  10.       input [3:0] hourl,
  11.       input [3:0] minh,
  12.       input [3:0] minl,
  13.       input [3:0] sech,
  14.       input [3:0] secl,
  15.       output reg [3:0] alarm_hourh_set,   //设置后的小时、分和秒
  16.       output reg [3:0] alarm_hourl_set,
  17.       output reg [3:0] alarm_minh_set,
  18.       output reg [3:0] alarm_minl_set,
  19.       output reg [3:0] alarm_sech_set,
  20.       output reg [3:0] alarm_secl_set,
  21.       output reg [2:0] alarm_clock_display,   //位选的控制信号
  22.       output reg alarm  );    //闹钟时间到的提示信号
  23. //闹钟信号提醒---计时60s     不进行秒的比对,只比对一分钟   //只比较时、分四位
  24. always@(hourh or hourl or minh or minl or sech or secl or alarm_hourh_set or
  25.       alarm_hourl_set or alarm_minh_set or
  26.       alarm_minl_set or alarm_sech_set or alarm_secl_set)  begin
  27.       if((alarm_minl_set==minl)&&(alarm_hourh_set==hourh)
  28.          &&(alarm_hourl_set==hourl)&&(alarm_minh_set==minh))  
  29.                   alarm<=0;         //当前时间与设定的闹钟时间相等,发送高电平    
  30.             else
  31.                   alarm<=1;            
  32. end
  33. always@(posedge key2)     //对时、分、秒的高位和低位进行选择
  34.       begin
  35.             if(alarm_clock_en)
  36.                   begin
  37.                   if(alarm_clock_display<5)  //6位选择
  38.                         alarm_clock_display<=alarm_clock_display+1;
  39.                   else
  40.                         alarm_clock_display<=0;
  41.                   end
  42.       end
  43. always@(posedge key3)     //对时、分、秒的高位和低位进行调整
  44.       begin
  45.          if (alarm_clock_en)
  46.             begin
  47.                   case(alarm_clock_display)  
  48.                         3'b000:  //调整小时高位
  49.                         begin
  50.                         if(alarm_hourh_set<2)  
  51.                               alarm_hourh_set<=alarm_hourh_set+1;
  52.                         else
  53.                               alarm_hourh_set<=0;
  54.                         end
  55.                         3'b001:  //调整小时低位
  56.                         begin
  57.                         if(alarm_hourl_set<9)
  58.                               alarm_hourl_set<=alarm_hourl_set+1;
  59.                         else
  60.                               alarm_hourl_set<=0;
  61.                         end
  62.                         3'b010:  //调整分高位
  63.                         begin
  64.                         if(alarm_minh_set<5)
  65.                               alarm_minh_set<=alarm_minh_set+1;
  66.                         else
  67.                               alarm_minh_set<=0;
  68.                         end
  69.                         3'b011:  //调整分低位
  70.                         begin
  71.                         if(alarm_minl_set<9)
  72.                               alarm_minl_set<=alarm_minl_set+1;
  73.                         else
  74.                               alarm_minl_set<=0;
  75.                         end
  76.                         3'b100:  //调整秒高位
  77.                         begin
  78.                         if(alarm_sech_set<5)
  79.                               alarm_sech_set<=alarm_sech_set+1;
  80.                         else
  81.                               alarm_sech_set<=0;
  82.                         end
  83.                         3'b101:  //调整秒低位
  84.                         begin
  85.                         if(alarm_secl_set<9)
  86.                               alarm_secl_set<=alarm_secl_set+1;                      
  87.                         else
  88.                               alarm_secl_set<=0;
  89.                         end
  90.                   endcase
  91.             end
  92.       end
  93. endmodule

10)多功能数字钟的顶层设计clock_demo

多功能数字钟的顶层设计主要的目的是将上述的所有功能模块进行例化,连接所有的功能模块,保证其模块之间的正常工作。

clock_demo模块中,首先将四个按键进行例化和输出,用于下列的各个模块按键使用。之后,时钟例化time_div,导入开发板硬件时钟clk,完成四种其他时钟的生成。之后,功能控制例化control,导入key1按键,用于控制四种功能模式。之后,进行时间设置例化time_timeset,对于此模块,我将时间计数和时间设置模块进行合并,以最初实现正常的数字时钟功能,输出此时的计数数字值。之后,数码管动态扫描例化time_display_sel显示模块例化display,对应于四种功能模式下,数码管的显示扫描输出相应的控制信息,与后续的display模块配合,将对应的数字段码值进行输出。其间,在display模块中,需要导入正常显时、秒表时间、闹钟时间,实现段码值display_data输出到数码管。例化正常时间计数模块time_count主要完成数码管中间两个小点的输出。最后,秒表例化和闹钟模块例化stopwatchalarm_clock,秒表导出此时的计时情况,闹钟模块需要导入正常的时间,输出闹钟设置的时间,二者比较,输出提醒信号alarm

在整个模块例化的过程中,可以看到需要很多信号的输入和输出,在FPGA中,实现各个模块的信号连接的方式是布线wire,通过将所需要的输入和输出信号进行其他名称的布线,在连接输出信号时,此时布线其他的信号,可以实现输入。此外,如何判断信号是输入还是输出,与各个模块在编写时的inputoutput信号相关,应该特别注意分辨。

程序如下所示:

  1. //1、具有基本走时功能,时间范围是00:00:00~23:59:59。
  2. //2、具有暂停计时和清零功能。
  3. //3、具有调节时间功能。
  4. //4、闹钟功能,定时时间可调,并具有到时1min的提醒功能。
  5. //5、秒表功能,由于同步时钟的问题,在另一块开发板上面完成。
  6. module clock_demo(
  7.     input clk,
  8.     input key1,  //时间正常显示、时间设置、秒表、闹钟功能四种功能模式
  9.     input key2,  //时间设置和闹钟时间设置的时候,选择不同的数码管
  10.     input key3,  //时间设置时的数字+1操作
  11.     input key4,  //暂停正常显示  当前数码管清0
  12.     output alarm,   //闹钟到时间的提醒信号
  13.     output [6:0] display_data,   //7段数码管的译码
  14.     output [5:0] display_sel,   //扫描6位数码管
  15.     output two_points      //输出数码管中间的两个小点,1s闪烁
  16.     //output day_clk,  //天数+1的信号
  17. );
  18. wire clk1k_1,clk200_1,clk100_1,clk1_1;   //时钟信号布线
  19. wire t_en,t_s_en,sw_en,ac_en;   //四种功能控制布线
  20. wire [3:0] hh,hl,mh,ml,sh,sl,      
  21.         hourh,hourl,minh,minl,sech,secl,  
  22.         s100mh,s100ml,s100sh,s100sl,s100h_1,s100l_1,
  23.         aahh,aahl,aamh,aaml,aash,aasl;  //时间设置、秒表布线
  24. wire [2:0] t_d;  
  25. wire [5:0] a_d_s,t_d_s;
  26. key_db    key_db_i1(  // 按键去抖动信号 1  
  27.    .clk    (clk),
  28.    .key_i    (key1),
  29.    .key_db_o     (key_db1)    
  30. );
  31. key_db    key_db_i2(  // 按键去抖动信号 2  
  32.    .clk    (clk),
  33.    .key_i    (key2),
  34.    .key_db_o     (key_db2)    
  35. );
  36. key_db    key_db_i3(  // 按键去抖动信号 3
  37.    .clk    (clk),
  38.    .key_i    (key3),
  39.    .key_db_o     (key_db3)    
  40. );
  41. key_db    key_db_i4(  // 按键去抖动信号 4
  42.    .clk    (clk),
  43.    .key_i    (key4),
  44.    .key_db_o     (key_db4)    
  45. );
  46. //时钟例化,为各选择、显示等等显示时钟
  47. time_div #( )
  48. time_div(  
  49.     .clk(clk),  //clk:系统基准时钟  27MHZ
  50.     .clk1k(clk1k_1),    //clk1k:动态扫描时钟信号  1kHZ
  51.     .clk200(clk200_1),   //clk200:闪烁时钟信号  200HZ
  52.     .clk100(clk100_1),  //clk100:秒表--时钟信号  100HZ,百分秒
  53.     .clk1(clk1_1)  );      //clk1:秒--时钟信号  1HZ=1s
  54. //功能控制例化
  55. control #( )
  56. control (
  57.     .key1(key_db1),   //key1:功能选择按键,有4种功能
  58.     .time_en(t_en),   //time_en:时间正常工作使能
  59.     .time_set_en(t_s_en),  //time_set_en:时间设置使能
  60.     .stopwatch_en(sw_en),   //stopwatch_en:秒表使能
  61.     .alarm_clock_en(ac_en)  );    //alarm_clock_en:闹钟显示及调整使能
  62. //时间设置例化
  63. time_timeset #( )
  64. time_timeset (
  65.     .clk(clk),
  66.     .clk1(clk1_1),   //1s
  67.     .key2(key_db2),    //设置的时候,进行移位操作,选择不同的数码管
  68.     .key3(key_db3),    //数字+1操作
  69.     .key4(key_db4),    //暂停时间正常显示
  70.     .time_en(t_en),    //control模块--时间正常工作使能
  71.     .time_set_en(t_s_en),   //时间设置使能
  72.     .alarm_clock_en(ac_en),
  73.     .hourh(hh),    //小时、分和秒(分别两位)设置
  74.     .hourl(hl),
  75.     .minh(mh),
  76.     .minl(ml),
  77.     .sech(sh),
  78.     .secl(sl),
  79.     .day_clk(day_clk),      //天数+1的信号
  80.     .time_display(t_d),    //闪烁显示控制
  81.     .two_points(two_points)  );
  82. //数码管动态扫描例化
  83. time_display_sel #( )
  84. time_display_sel (
  85.     .clk1k(clk1k_1),      
  86.     .clk200(clk200_1),  
  87.     .time_en(t_en),
  88.     .time_set_en(t_s_en),
  89.     .stopwatch_en(sw_en),
  90.     .alarm_clock_en(ac_en),
  91.     .alarm_clock_display(a_d_s),
  92.     .time_display(t_d),
  93.     .time_display_sel(t_d_s)    );
  94. //显示模块例化
  95. display #( )
  96. display (
  97.     .time_en(t_en),     //四个使能信号
  98.     .time_set_en(t_s_en),
  99.     .stopwatch_en(sw_en),
  100.     .alarm_clock_en(ac_en),
  101.     .time_display_sel(t_d_s),   //动态扫描
  102.     .hourh(hh),    //正常时间的时、分、秒
  103.     .hourl(hl),
  104.     .minh(mh),
  105.     .minl(ml),
  106.     .sech(sh),
  107.     .secl(sl),
  108.     .s100minh(s100mh),  //秒表的分的高位
  109.     .s100minl(s100ml),  //秒表的分的低位
  110.     .s100sech(s100sh),  //秒表的秒的高位
  111.     .s100secl(s100sl),  //秒表的秒的低位
  112.     .s100h(s100h_1),
  113.     .s100l(s100l_1),
  114.     .alarm_hourh_set(aahh),   //闹钟的时、分、秒高低位
  115.     .alarm_hourl_set(aahl),
  116.     .alarm_minh_set(aamh),
  117.     .alarm_minl_set(aaml),
  118.     .alarm_sech_set(aash),
  119.     .alarm_secl_set(aasl),
  120.     .display_sel(display_sel),
  121.     .display_data(display_data)    );
  122. //对于此处的秒表功能,由于同步时钟的问题,在另一块开发板上面完成。
  123. stopwatch #( )
  124. stopwatch (
  125.     .clk100(clk100_1),   //秒表时钟信号--0.01s
  126.     .stopwatch_en(sw_en),   //stopwatch_en:秒表使能信号
  127.     .key2(key_db2),    
  128.     .key3(key_db3),    
  129.     .key4(key_db4),    
  130.     .s100minh(s100mh),  //秒表的分的高位
  131.     .s100minl(s100ml),  //秒表的分的低位
  132.     .s100sech(s100sh),  //秒表的秒的高位
  133.     .s100secl(s100sl),  //秒表的秒的低位
  134.     .s100h(s100h_1),  //秒表的百分秒的高位
  135.     .s100l(s100l_1)  );    //秒表的百分秒的低位
  136. //时间正常显示--直接引用time_count模块即可
  137. time_count #( )
  138. time_count(
  139.     .hourh(hourh),    //正常时间的时、分、秒
  140.     .hourl(hourl),
  141.     .minh(minh),
  142.     .minl(minl),
  143.     .sech(sech),
  144.     .secl(secl),
  145.     .alarm_clock_en(ac_en)   ); //闹钟使能,用于显示两个数码管小点
  146. //闹钟模块例化
  147. alarm_clock #( )
  148. alarm_clock (
  149.     .clk1k(clk1k_1),   //扫描显示同步时钟
  150.     .key2(key_db2),   //移位数码管
  151.     .key3(key_db3),   //数字+1
  152.     .alarm_clock_en(ac_en),   //闹钟的使能信号
  153.     .hourh(hh),    //小时、分和秒(分别两位)设置
  154.     .hourl(hl),
  155.     .minh(mh),
  156.     .minl(ml),
  157.     .sech(sh),
  158.     .secl(sl),
  159.     .alarm_hourh_set(aahh),   //闹钟的时、分、秒高低位
  160.     .alarm_hourl_set(aahl),
  161.     .alarm_minh_set(aamh),
  162.     .alarm_minl_set(aaml),
  163.     .alarm_sech_set(aash),
  164.     .alarm_secl_set(aasl),
  165.     .alarm_clock_display(a_d_s),
  166.     .alarm(alarm)    );     //闹钟时间到的提示信号
  167. endmodule

四、模块调试和硬件下载测试

本程序进行硬件下载测试的流程:

1、在高云软件(Gowin V1.9.8.11 Education上面完成程序的编写,同时,双击按钮Synthesize,保证此时在Message界面无错误出现,如果有错误,需要参照错误信息进行更改。

2、在FloorPlanner界面完成布线,主要是将FPGA的管脚连接按键、数码管、LED灯等外设。之后,双击按钮Place &Route,保证此时在Message界面无错误出现,如果有错误,需要参照错误信息进行更改。

具体的布线情况如下:

端口

FPGA管脚

外设管脚

端口

FPGA管脚

外设管脚

display_sel[0]

29

DIG1

display_data[4]

39

E

display_sel[1]

30

DIG2

display_data[5]

40

F

display_sel[2]

31

DIG3

display_data[6]

41

G

display_sel[3]

32

DIG4

display_data[7]

42

DP

display_sel[4]

34

DIG1

key1

8

KEY1

display_sel[5]

35

DIG2

key2

9

KEY2

display_data[0]

31

A

key3

27

KEY3

display_data[1]

32

B

key4

28

KEY4

display_data[2]

34

C

clk

22

-

display_data[3]

35

D

alarm

47

LED1

two_points

48

L1/L2

3、双击Program Device,选择Operation,将程序初步烧录到SRAM区,有时候在调试完成之后,为了避免程序掉电消失,可以烧录到flash区。

 

4、在开发板上面进行观察,查看具体的实物测试的结果,再根据具体的情况进行调整。

模块调试:

在本程序的调试中,分别对四个小模块进行调试和功能检查,同时,对整体的功能进行联调,完成数字时钟的设计。

1.时间正常显示模块调试:

在下面的数码管显示中,可以看到正常的秒计数,在秒满59之后,向分进位,分满59之后,向小时进位,正常显示时-分-秒,并且六位数码管工作正常。同时,在本功能中,当按下按键key4时,此时的计时停止,实现时间暂停。此模块功能全部达到要求。

2.时间设置模块调试:

在下面的数码管显示中,当选中第一位数码管的时候,按下按键key3可以实现当前的数码管+1,同时,按下按键key4,再按下按键key3可以实现当前的数码管清0,按下按键key2可以实现6位数码管移位,直至对时-分-秒的高低位均可以实现时间设置。同时,在时间设置完成之后,再次更换功能,将会更新时间显示。此模块功能全部达到要求。在此举例调试,其他的数码管均照此逻辑调试

3.秒表模块调试:

在下面的数码管显示中,当按下按键key2时,此时会进行分-秒-十分秒的秒表计数,按照正常的时间进行走时,同时在分数码管和秒低位数码管下面有小数点显示,用于区分不同的时间单位。同时,再次按下按键key2,将会暂停秒表计时。按下按键key4,将会秒表清0,此时可以进行下一次的计时。此模块功能全部达到要求。

4、闹钟模块调试

在下面的数码管显示中,同时间设置模块调试,按下按键key3可以对当前的数码管进行+1,按下按键key2可以实现6位数码管移位,用于设置闹钟的时间。当闹钟的时-分与正常显示的时间相同时,此时闹钟回复发出一个提醒信号,在开发板上,可以看到LED1灯泡亮起,在60s之后,LED1灯泡熄灭。

注意,闹钟时钟比较只涉及时-分。此模块功能全部达到要求。

5、数字时钟四个功能模块联合调试,通过按键key1可以实现四种功能的分别显示,并且,在不同的功能模式下,对应的数码管显示的数字信息也不同,通过其他按键的使用,可以具体的调控数码管和输出对应的信号。数字时钟所有模块调试完成,所有模块功能全部达到要求。

五、遇到的问题及解决方法

在本次的课程设计中,我总共遇到了4个问题,在查阅资料和询问老师的情况下,得到了很好的解决,完成了本次的数字时钟课程设计。

问题1:正常时间显示时、分、秒的计数和进位,时间设置的代码设计中遇到了问题。

解决方法1:在正常时间的显示逻辑中,小时的最大应该为24,分钟的最大应该为59,秒的最大应该为59(在60 的时候应该直接进位,不予显示)。由此,可以在模块中以秒的低位作为最底层进行时钟沿的触发,因为只有秒进行计数和进位后,才有分钟和小时的显示。

在if判断语句中,在秒低位首先进行是否为9 的判断,进而再次比较秒高位是否为5,当达到59时,进位到分,否则秒的低位时钟+1,继续计数。针对其他模块,可以采用相同的设计方法,但是由此不同的是,对于分钟和小时的时钟沿触发,应该使用进位标志触发,只有存在进位出现时,才有计数+1

  1. always@(posedge clk1 or posedge time_set_en)  begin
  2.     if (time_set_en) begin   //秒的设置
  3.         sech<=sech_set;     //设置给秒的高低两位
  4.         secl<=secl_set;
  5.       end
  6.     else  if (time_en)    begin
  7.            //秒的计数情况
  8.         if (secl==9)  //秒的低位为9时
  9.           begin
  10.           secl<=0;     //秒低位清0
  11.           if (sech==5)   //秒高位为5时
  12.             begin
  13.               sech<=0;   //秒的低位为9,秒的高位为5时,秒的高位清0
  14.               cout1<=1;  //秒向分进1
  15.             end
  16.           else
  17.             begin
  18.               sech<=sech+1;  //秒高位不为5,继续+1
  19.             end
  20.           end
  21.         else if (flag1)   //当标志位翻转时,秒的低位不再变化,实现暂停
  22.           begin
  23.           secl<=secl+1;  //秒低位不为9,继续+1
  24.           cout1<=0;
  25.           end                
  26.       end
  27.   end

针对时间的设置,在此处主要的问题是,需要联合时间的显示和设置,在完成时间的设置之后,应该将设置的时间用于替代原本的时间显示,实现时间的设置和覆盖。因此,我的设计逻辑在于将时钟计数和设置模块进行联合,当需要时间正常计时模式时,利用时间设置的数字值进行现有覆盖。此外,在时间设置模式时,时间计数正常进行,不起冲突。同时,通过外部的按键,利用时钟沿去检测按键的按下,可以实现时间设置现有的值加1。

问题2由于采用多种时钟,在功能切换和数码管扫描显示逻辑中,遇到了问题。

解决方法2:在实现功能模式的跳转时,其对应的数码管显示也需要发生对应的改变,例如在时间设置时,需要的时钟逻辑与时间计数和数码管扫描时钟不一致,针对此问题的解决,我想到的是,设置两组always语句,分别判断此时的模式使能的情况,在不同的模式下,传输不同的扫描时间clk0,再利用此扫描时间,分别扫描6个数码管即可。

  1. //扫描时钟的选择
  2. always@(time_en or time_set_en or clk1k or clk200
  3.                         or stopwatch_en or alarm_clock_en) begin
  4.             if(time_en)   begin   //正常显示时间信息时,选择1kHz的扫描时钟  快一些
  5.                         clk0<=clk1k;
  6.                         sel<=time_sel;
  7.                   end
  8. //设置调整时间信息时,选择200Hz的扫描时钟   慢一些
  9.             else  if(time_set_en)   begin                    
  10.                         clk0<=clk200;
  11.                         sel<=time_display;   //选择的单个数码管
  12.                   end
  13.             else if(stopwatch_en) begin                  
  14.                         clk0<=clk1k;
  15.                         sel<=time_sel;
  16.                   end
  17.             else if(alarm_clock_en)  begin                  
  18.                         clk0<=clk200;
  19.                         sel<=alarm_clock_display;
  20.                   end            
  21.       end
  22. //为时、分、秒的高位和低位数据进行动态位选择
  23. always@(posedge clk0)  begin  
  24.             case(sel)     //6位分别传送给位控信号
  25.                   3'b000: time_display_sel<=6'b100000;
  26.                   3'b001: time_display_sel<=6'b010000;
  27.                   3'b010: time_display_sel<=6'b001000;
  28.                   3'b011: time_display_sel<=6'b000100;
  29.                   3'b100: time_display_sel<=6'b000010;
  30.                   3'b101: time_display_sel<=6'b000001;
  31.                   default: time_display_sel<=6'b000000;
  32.           endcase
  33.       end

问题3闹钟时间设置和闹钟的时间到达时的提醒信号,遇到了问题。

解决方法3:在初步解决该问题时,我设想的方法是设置一个可以进行60s计数的计数器,当计数器的值满60时,关闭此时输出的闹钟信号,但是我在设计的过程中,我一直无法解决当前的问题,因为在计时的过程中,我既需要在always语句中导入当前的时钟信号,又需要1s的计数信号clk1,此时的时序控制非常困难。

在询问老师具体的解决方法之后,我想到,其实在进行程序的比较的时候,不需要进行秒的比较,只需要比较时、分就行,因为在时、分不相等时,自然完成了60s的时钟比较,这时,基于此分析的逻辑,便可以很好的实现。

  1. //闹钟信号提醒---计时60s     不进行秒的比对,只比对一分钟   //只比较时、分四位
  2. always@(hourh or hourl or minh or minl or sech or secl or alarm_hourh_set or
  3.       alarm_hourl_set or alarm_minh_set or
  4.       alarm_minl_set or alarm_sech_set or alarm_secl_set)  begin
  5.       if((alarm_minl_set==minl)&&(alarm_hourh_set==hourh)
  6.          &&(alarm_hourl_set==hourl)&&(alarm_minh_set==minh))  
  7.                   alarm<=0;       //当前时间与设定的闹钟时间相等,发送高电平    
  8.             else
  9.                   alarm<=1;
  10. end

问题4秒表的时钟控制和数码管的显示,由于在本模块中,采用多种时钟跳转和数码管扫描,出现同步时钟混乱的情况,导致秒表的显示不一致。

解决方法4:首先在此处具体分析一下出现问题的具体原因,在进行秒表计时的过程中,我既需要发送此时的秒表计时的情况,又需要在数码管上面进行数字扫描输出,同时,在前面的时间正常计数时,还有一个1s的计数时钟,三种时钟信号在同时进行,在进行具体的时钟输出的时候,我认为在当前的时序输出上会出现混乱的情况。在程序的实现中,我采用的具体方法是分时发送时钟信号,在下面的四种情况下,分别对应不同的扫描时钟。

在进行初步解决时,我采用的方法是,提高数码管的扫描频率,先修改秒表的计时信号为1s,此时秒表的计时信号恢复正常,但是在设定为0.01s时,此时显示又出现问题,因此,我想的是再次提高数码管的扫描频率,但是此时便会影响其他模块的功能显示,并且,我多次检查各模块之间调用的代码逻辑和程序的实现,无法找到问题所在。

 最后,我的解决方法就是,将秒表的功能移动到另一块开发板上面,只有秒表一项功能,同时也保留数码显示的代码,可以很好的实现秒表的功能,同时,为了更好的区分当前的计时情况,我在特定的数码管显示下面加了小数点,用与区分例如6.32.3时间。当然,将秒表的功能移动到一块开发板上面,我认为还是可以实现,同样在control模块中,控制功能调用即可。

  1.   if(time_en)   begin      //正常显示时间信息时,选择1kHz的扫描时钟  快一些
  2.                         clk0<=clk1k;
  3.                         sel<=time_sel;
  4.                   end
  5.         else  if(time_set_en)   begin //设置调整时间信息时,选择200Hz的扫描时钟   慢一些       
  6.                         clk0<=clk200;
  7.                         sel<=time_display;   //选择的单个数码管
  8.                   end
  9.             else if(stopwatch_en) begin                  
  10.                         clk0<=clk1k;
  11.                         sel<=time_sel;
  12.                   end
  13.             else if(alarm_clock_en)  begin                  
  14.                         clk0<=clk200;
  15.                         sel<=alarm_clock_display;
  16.                   end            
  17.       end

六、课程学习总结

在本次基于FPGA的多功能数字时钟项目的设计中,需要熟练掌握时序逻辑的代码编写以及基于时序的不同而产生的问题调试。在编写代码的过程中,需要详细了解模块的工作原理和实现的基本逻辑。在实现的过程中,我的步骤是首先在纸上大致对每个模块进行功能分类,大致进行功能的构思和初步设计,同时,画出各个模块的连接方式,可以等同于先一步完成了RTL原理图的设计。之后,我再针对每一个模块进行代码编写,同时,完成一个模块编写,调试一个模块,逐步便能完成整体的设计。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/569198
推荐阅读
相关标签
  

闽ICP备14008679号