当前位置:   article > 正文

FPGA项目(12)——基于FPGA的万年历设计_fpga数字万年历

fpga数字万年历

        首先称述一下所实现的功能:可以显示年、月、日、时、分、秒,有闹钟设置功能,闹钟时间到时,蜂鸣器响,报警。用6位数码管进行显示,分三个显示页面,第一个页面显示年月日,第二个界面显示时分秒,第三个页面显示闹钟时间。可以用按键进行翻页,按键进行时间、日期设置、闹钟设置。

        本次做的设计,使用了正点原子的开拓者FPGA开发板,并且在开发板上验证了功能,通过了实物测试。实物图片如下:

        对于本次设计,我还拍了实物演示视频,视频播放链接如下:

基于FPGA的万年历设计_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1FT4y1i7YJ/?spm_id_from=333.999.list.card_archive.click&vd_source=392747917e381eaafdf7756cf4b87612

         看完实物视频后,接下来就是思路及代码的讲解了。

        还是使用自顶层向下的设计思想,将整个设计分为许多个模块,分别进行设计,最后将它们全部综合起来,形成一个整体。先给出系统的rtl视图如下,因为从rtl视图可以清楚的看到整个设计由哪几个模块组成,各个模块都有哪些端口,各端口之间是如何连接的。

        

        从上图可以看出,整个系统首先使用了按键消抖模块,因为开拓者板子上的按键为机械按键,抖动非常严重,如果不使用按键消抖模块,那么按下一次按键就会被程序识别为按下n多次,就导致程序无法正常运行。(关于按键消抖模块的具体思路及其代码,可以参考我的另一篇文章)

 FPGA项目(3)--按键消抖_嵌入式小李的博客-CSDN博客https://blog.csdn.net/guangali/article/details/130674206?spm=1001.2014.3001.5501

        经过消抖的按键信号直接输出给了按键处理模块,在这个模块里面,设置了很多加信号,以及状态标志位。比如:

        这里面的flag_turn就是页面标志位,因为总共分为三个页面,这个标志就是用于记录当前处于哪个页面,数码管根据当前所处的页面,控制输出的显示信息。此外还有一系列的加信号(年月日的加1信号,时分秒的加1信号,闹钟的加1信号等等)如下所示:

        这些信号分别输出到时钟处理模块和闹钟处理模块。

        按键模块的具体代码如下:

         

  1. //按键驱动模块
  2. //实现功能:检测按键并转换状态。按键按下为0
  3. //输入信号:系统时钟、复位、翻页按键、数码管闪烁选择按键、加一按键
  4. //输出信号:翻页选择状态、数码管闪烁选择状态、时分秒和年月日和闹钟时分秒的加一脉冲信号
  5. //当按下翻页按键时,转换翻页状态、当按下数码管闪烁选择按键时转换当前数码管闪烁选择状态、当按下加一按键时生成每个部分的专属增一脉冲信号(秒分时....的增一信号都分开)
  6. //显示状态有三个(一共有三页)状态转换为:00-->01-->10
  7. //数码管显示状态有四个(一个没选中和三个不同得选择)状态转换:00-->01-->10-->11(00是谁也没选的状态)
  8. module key_drive_module(system_clk,reset,key_turn,key_switch,key_add,flag_switch,flag_turn,second_add,minute_add,hour_add,day_add,month_add,year_add,alarm_second_add,alarm_minute_add,alarm_hour_add,select_sign);
  9. input key_turn,key_switch,key_add,system_clk,reset;
  10. output [1:0] flag_switch,flag_turn;
  11. output select_sign;
  12. output second_add,minute_add,hour_add,day_add,month_add,year_add,alarm_second_add,alarm_minute_add,alarm_hour_add;
  13. reg [3:0] flag_add=0;
  14. reg second_add,minute_add,hour_add,day_add,month_add,year_add,alarm_second_add,alarm_minute_add,alarm_hour_add;
  15. reg [1:0] flag_turn=0,turn_state=0,turn_next_state=0; //翻页状态机(分别是输出、现态、次态)
  16. reg [1:0] flag_switch=0,switch_state=0,switch_next_state=0;//当前选择数码管状态机
  17. assign select_sign=(flag_add==4'b0000);
  18. //页面切换的状态机
  19. //次态电路
  20. always@(negedge key_turn or negedge reset) //敏感信号为页面切换按键(按下按键切换状态) 页面切换状态:00-->01-->10
  21. begin
  22. if(!reset) turn_next_state=2'b00;
  23. else
  24. case(turn_state)
  25. 2'b00:turn_next_state=2'b01;
  26. 2'b01:turn_next_state=2'b10;
  27. 2'b10:turn_next_state=2'b00;
  28. default:turn_next_state=2'b00;
  29. endcase
  30. end
  31. //次态到现态转换
  32. always@(posedge system_clk or negedge reset)
  33. begin
  34. if(!reset) turn_state<=2'b00;
  35. else turn_state<=turn_next_state;
  36. end
  37. //输出电路
  38. always@(reset or turn_state)
  39. begin
  40. if(!reset) flag_turn= 2'b00;
  41. else
  42. case(turn_state)
  43. 2'b00:flag_turn=2'b00;
  44. 2'b01:flag_turn=2'b01;
  45. 2'b10:flag_turn=2'b10;
  46. default:flag_turn=2'b00;
  47. endcase
  48. end
  49. //数码管闪烁选择的状态机
  50. //次态电路
  51. always@(negedge key_switch or negedge reset) //敏感信号为选项切换按键(按下按键切换状态) 页面切换状态:00-->01-->10-->11
  52. begin
  53. if(!reset) switch_next_state=2'b00;
  54. else
  55. case(switch_state)
  56. 2'b00:switch_next_state=2'b01;
  57. 2'b01:switch_next_state=2'b10;
  58. 2'b10:switch_next_state=2'b11;
  59. 2'b11:switch_next_state=2'b00;
  60. default:switch_next_state=2'b00;
  61. endcase
  62. end
  63. //次态-->现态转换
  64. always@(posedge system_clk or negedge reset)
  65. begin
  66. if(!reset) switch_state<=2'b00;
  67. else switch_state<=switch_next_state;
  68. end
  69. //输出电路
  70. always@(reset or switch_state)
  71. begin
  72. if(!reset) flag_switch= 2'b00;
  73. else
  74. case(switch_state)
  75. 2'b00:flag_switch=2'b00;
  76. 2'b01:flag_switch=2'b01;
  77. 2'b10:flag_switch=2'b10;
  78. 2'b11:flag_switch=2'b11;
  79. default:switch_next_state=2'b00;
  80. endcase
  81. end
  82. //增一选择项目
  83. always@(turn_state or switch_state or reset) //敏感信号为当前页面状态、数码管闪烁选择状态(当选择项目变动时,增一选择项目也随之变化)
  84. begin
  85. if(!reset) flag_add=4'b0000;//0000代表空状态(都没选)
  86. else
  87. case(turn_state)//当前页面
  88. 2'b00: //第一页(时分秒)
  89. case(switch_state)//当前数码管闪烁选择
  90. 2'b00:flag_add=4'b0000;
  91. 2'b01:flag_add=4'b0001;//选中秒
  92. 2'b10:flag_add=4'b0010;//选中分
  93. 2'b11:flag_add=4'b0011;//选中时
  94. endcase
  95. 2'b01: //第二页(年月日)
  96. case(switch_state)
  97. 2'b00:flag_add=4'b0000;
  98. 2'b01:flag_add=4'b0101;//选中日
  99. 2'b10:flag_add=4'b0110;//选中月
  100. 2'b11:flag_add=4'b0111;//选中年
  101. endcase
  102. 2'b10: //第三页(闹钟)
  103. case(switch_state)
  104. 2'b00:flag_add=4'b0000;
  105. 2'b01:flag_add=4'b1001;//选中闹钟秒
  106. 2'b10:flag_add=4'b1010;//选中闹钟分
  107. 2'b11:flag_add=4'b1011;//选中闹钟时
  108. endcase
  109. default:flag_add=4'b0000;
  110. endcase
  111. end
  112. //生成增一的专属信号
  113. always@(key_add)//敏感信号为增一按键,当按键按下时,专属增一脉冲为0;当按键抬起时,专属增一脉冲为1
  114. begin
  115. case(flag_add)
  116. 4'b0001:second_add=key_add;
  117. 4'b0010:minute_add=key_add;
  118. 4'b0011:hour_add=key_add;
  119. 4'b0101:day_add=key_add;
  120. 4'b0110:month_add=key_add;
  121. 4'b0111:year_add=key_add;
  122. 4'b1001:alarm_second_add=key_add;
  123. 4'b1010:alarm_minute_add=key_add;
  124. 4'b1011:alarm_hour_add=key_add;
  125. default:; //其它的都什么也不执行
  126. endcase
  127. end
  128. endmodule

         接下来就是时钟处理模块,这个模块里面的内容并不是很难。首先就是对系统时钟进行分频,产生1S的脉冲信号,然后在这个脉冲的驱动下,驱使时、分、秒、年、月、日的正常逻辑运转。部分示例代码如下:

        但是,时、分、秒、年、月、日不仅能够正常的运转(在秒脉冲的驱动下,做正常的时钟运行) ,还能够在设置信号(second_add,minute_add,hour_add,day_add,month_add,year_add)等的驱使下,随着按键按下一次,数值增加一次,起到一个按键调节时钟的效果。

        这部分的所有代码如下:

  1. //时钟模块
  2. //实现功能:计时功能及其时钟设置功能
  3. //输入信号:系统时钟、复位按键、时分秒年月日的加一信号
  4. //输出信号:年月日和时分秒、秒脉冲
  5. module clock(system_clk,reset,select_sign,second_add,minute_add,hour_add,day_add,month_add,year_add,second,minute,hour,day,month,year);
  6. input system_clk,reset,second_add,minute_add,hour_add,day_add,month_add,year_add;
  7. input select_sign;
  8. output [5:0] second;//最大59
  9. output [5:0] minute;//最大59
  10. output [4:0] hour;//最大23
  11. output [4:0] day;//最大31
  12. output [3:0] month;//最大12
  13. output [6:0] year;//最大99
  14. reg [31:0]p;//最大24999999
  15. wire pulse_second;//秒脉冲
  16. reg [5:0] second;
  17. reg [5:0] second_set;
  18. wire pulse_minute; //分脉冲
  19. reg [5:0] minute;
  20. reg [5:0] minute_set;
  21. wire pulse_hour;//小时脉冲
  22. reg [4:0] hour;
  23. reg [4:0] hour_set;
  24. wire pulse_day;//日脉冲
  25. reg [4:0] day=5'd8;
  26. reg [4:0] day_set;
  27. reg [3:0] day_month;
  28. reg [6:0] day_year;
  29. wire pulse_month;//月脉冲
  30. reg [3:0] month;
  31. reg [3:0] month_set;
  32. wire pulse_year;//年脉冲
  33. reg [6:0] year;
  34. reg [6:0] year_set;
  35. always@(posedge system_clk or negedge reset)//敏感信号:系统时钟和复位
  36. begin
  37. if(!reset)
  38. p<=0;
  39. else
  40. if(p==49999999)
  41. p<=0;
  42. else
  43. p<=p+1;
  44. end
  45. assign pulse_second=(p==49999999 && (select_sign==1'b1)); //秒脉冲
  46. //秒部分
  47. always@(posedge system_clk)
  48. begin
  49. if(!reset)
  50. second<=6'd35;
  51. else
  52. if(pulse_second) //秒脉冲
  53. if(second>=59)
  54. second<=0;
  55. else
  56. second<=second+1;
  57. else if(!second_add) //增一信号
  58. second<=second_set;
  59. else
  60. second<=second;
  61. end
  62. //分脉冲信号的生成
  63. assign pulse_minute=(second==6'd59 && pulse_second==1'b1); //分脉冲
  64. //秒设置
  65. always@(negedge second_add)
  66. begin
  67. second_set=second; //先读取当前秒
  68. if(second_set>=59)
  69. second_set=0;
  70. else
  71. second_set=second_set+1;
  72. end
  73. //分部分
  74. always@(posedge system_clk)
  75. begin
  76. if(!reset)
  77. minute<=6'd24;
  78. else
  79. if(pulse_minute)
  80. if(minute>=59)
  81. minute<=0;
  82. else
  83. minute<=minute+1;
  84. else if(!minute_add)
  85. minute<=minute_set;
  86. else
  87. minute<=minute;
  88. end
  89. //小时脉冲的生成
  90. assign pulse_hour=(minute==6'd59 && pulse_minute==1'b1); //小时脉冲
  91. //分设置
  92. always@(negedge minute_add)
  93. begin
  94. minute_set=minute;
  95. if(minute_set>=59)
  96. minute_set=0;
  97. else
  98. minute_set=minute_set+1;
  99. end
  100. //小时部分
  101. always@(posedge system_clk)
  102. begin
  103. if(!reset)
  104. hour<=5'd16;
  105. else
  106. if(pulse_hour)
  107. if(hour>=23)
  108. hour<=0;
  109. else
  110. hour<=hour+1;
  111. else if(!hour_add)
  112. hour<=hour_set;
  113. else
  114. hour<=hour;
  115. end
  116. //天脉冲的生成
  117. assign pulse_day=(hour==5'd23 && pulse_hour==1'b1);
  118. //小时的设置
  119. always@(negedge hour_add)
  120. begin
  121. hour_set=hour;
  122. if(hour_set>=23)
  123. hour_set=0;
  124. else
  125. hour_set=hour_set+1;
  126. end
  127. //天部分
  128. always@(posedge system_clk)
  129. begin
  130. if(!reset)
  131. day<=5'd8;
  132. else
  133. if(pulse_day) //天脉冲
  134. if(month==1 || month==3 ||month==5 || month==7 || month==8 || month==10 || month==12)
  135. if(day>=31)
  136. day<=1;
  137. else
  138. day<=day+1;
  139. else if(month==4 || month==6 ||month==9 || month==11)
  140. if(day>=30)
  141. day<=1;
  142. else
  143. day<=day+1;
  144. else if(month==2 && (year%4==0))
  145. if(day>=29)
  146. day<=1;
  147. else
  148. day<=day+1;
  149. else
  150. if(day>=28)
  151. day<=1;
  152. else
  153. day<=day+1;
  154. else if(!day_add)
  155. day<=day_set;
  156. else
  157. day<=day;
  158. end
  159. //月脉冲的生成
  160. assign pulse_month=((day==5'd28 && month==4'd2 && (year%4!=0) && pulse_day==1'b1)
  161. ||(day==5'd29 && month==4'd2 && (year%4==0) && pulse_day==1'b1)
  162. ||(day==5'd30 && (month==4'd4 || month==4'd6 ||month==4'd9 || month==4'd11) && pulse_day==1'b1)
  163. ||(day==5'd31 && (month==4'd1 || month==4'd3 ||month==4'd5 || month==4'd7 || month==4'd8 || month==4'd10 || month==4'd12) && pulse_day==1'b1));
  164. //天的设置
  165. always@(negedge day_add)
  166. begin
  167. day_set=day;
  168. day_month=month;
  169. day_year=year;
  170. if(day_month==1 || day_month==3 ||day_month==5 || day_month==7 || day_month==8 || day_month==10 || day_month==12)
  171. if(day_set>=31)
  172. day_set<=1;
  173. else
  174. day_set<=day_set+1;
  175. else if(day_month==4 || day_month==6 ||day_month==9 || day_month==11)
  176. if(day_set>=30)
  177. day_set<=1;
  178. else
  179. day_set<=day_set+1;
  180. else if(day_month==2 && (day_year%4==0)) //闰年
  181. if(day_set>=29)
  182. day_set<=1;
  183. else
  184. day_set<=day_set+1;
  185. else
  186. if(day_set>=28)
  187. day_set<=1;
  188. else
  189. day_set<=day_set+1;
  190. end
  191. //月部分
  192. always@(posedge system_clk)
  193. begin
  194. if(!reset)
  195. month<=4'd12;
  196. else
  197. if(pulse_month)
  198. if(month>=12)
  199. month<=1;
  200. else
  201. month<=month+1;
  202. else if(!month_add)
  203. month<=month_set;
  204. else
  205. month<=month;
  206. end
  207. //年脉冲的生成
  208. assign pulse_year=(month==4'd12 && pulse_month==1'b1);
  209. //月设置
  210. always@(negedge month_add)
  211. begin
  212. month_set=month;
  213. if(month_set>=12)
  214. month_set=1;
  215. else
  216. month_set=month_set+1;
  217. end
  218. //年部分
  219. always@(posedge system_clk)
  220. begin
  221. if(!reset)
  222. year<=7'd21;
  223. else
  224. if(pulse_year)
  225. if(year>=99)
  226. year<=0;
  227. else
  228. year<=year+1;
  229. else if(!year_add)
  230. year<=year_set;
  231. else
  232. year<=year;
  233. end
  234. //年设置
  235. always@(negedge year_add)
  236. begin
  237. year_set=year;
  238. if(year_set>=99)
  239. year_set=0;
  240. else
  241. year_set=year_set+1;
  242. end
  243. endmodule

        接着就是闹钟模块。这个模块更为简单,就是在闹钟设置信号(alarm_second_add,alarm_minute_add,alarm_hour_add)的驱动下,控制闹钟时分秒的增加而已。然后判断当前时间与闹钟时间是否相等,如果相等了,那么蜂鸣器响,进行报警。此外,报警功能还收一个按键控制,这个按键控制闹钟功能是否使能。只有使能了,闹钟功能才起作用,否则不起作用。

        这里的 switch_alarm就是控制闹钟正常工作的按键,高电平有效。如果它为低电平,那么闹钟功能不使能,无效。

        这里的LED灯用于指示,是否启用了闹钟功能。

        该模块所有代码如下:

        

  1. //闹钟模块
  2. //实现功能:设置闹钟,闹钟时间到时闹钟响1分钟
  3. //输入信号:拨码开关(使能信号)、时钟模块的时分秒、按键驱动模块的时分秒专属增一脉冲信号
  4. //输出信号:LED灯、蜂鸣器、当前的设置时间(给显示模块来显示)
  5. module alarm(sys_clk,reset,second,minute,hour,switch_alarm,alarm_second_add,alarm_minute_add,alarm_hour_add,led,beep,alarm_second,alarm_minute,alarm_hour);
  6. input sys_clk,reset,switch_alarm,alarm_second_add,alarm_minute_add,alarm_hour_add;
  7. input [5:0] second;
  8. input [5:0] minute;
  9. input [4:0] hour;
  10. output led,beep;
  11. output [5:0] alarm_second;
  12. output [5:0] alarm_minute;
  13. output [4:0] alarm_hour;
  14. reg [5:0] alarm_second;
  15. reg [5:0] alarm_minute;
  16. reg [4:0] alarm_hour;
  17. assign led=switch_alarm; //拨码开关开时亮
  18. assign beep=((minute==alarm_minute&&hour==alarm_hour&&switch_alarm==1'b1)||(minute==6'd59 && second==6'd59));
  19. //设置秒
  20. always@(negedge alarm_second_add or negedge reset)
  21. begin
  22. if(!reset)
  23. alarm_second<=6'd30;
  24. else
  25. if(alarm_second>=59)
  26. alarm_second<=0;
  27. else
  28. alarm_second<=alarm_second+1;
  29. end
  30. //设置分
  31. always@(negedge alarm_minute_add or negedge reset)
  32. begin
  33. if(!reset)
  34. alarm_minute<=6'd30;
  35. else
  36. if(alarm_minute>=59)
  37. alarm_minute<=0;
  38. else
  39. alarm_minute<=alarm_minute+1;
  40. end
  41. //设置时
  42. always@(negedge alarm_hour_add or negedge reset)
  43. begin
  44. if(!reset)
  45. alarm_hour<=6'd12;
  46. else
  47. if(alarm_hour>=23)
  48. alarm_hour<=0;
  49. else
  50. alarm_hour<=alarm_hour+1;
  51. end
  52. endmodule

         最后就是显示模块了。FPGA驱动数码管进行动态显示的代码,可以参考我的这篇博客:

        FPGA项目(5)--FPGA控制数码管动态显示的原理_fpga数码管显示实验原理_嵌入式小李的博客-CSDN博客https://blog.csdn.net/guangali/article/details/130754726?spm=1001.2014.3001.5501        

        虽然不是一模一样的,但是稍加修改,即可完整使用。

        

        最后说明,如果您需要完整项目工程,请私信并评论!

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

闽ICP备14008679号