官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师

【案例】电子闹钟设计

发布时间:2023-04-13   作者:admin 浏览量:

案例编号:001600000064

至简设计系列_闹钟


--作者:小黑同学

本文为明德扬原创及录用文章,转载请注明出处!

1.1 总体设计

1.1.1 概述

数字时钟是采用数字电路技术实现时、分、秒计时显示的装置,可以用数字同时显示时,分,秒

的精确时间并实现准确校时,具备体积小、重量轻、抗干扰能力强、对环境要求高、高精确性、容易

开发等特性,在工业控制系统、智能化仪器表、办公自动化系统等诸多领域取得了极为广泛的应用,

诸如自动报警、按时自动打铃、时间程序自动控制、定时广播、自定启闭路灯、定时开关烘箱、通断

动力设备、甚至各种定时电器的自动启用等。与传统表盘式机械时钟相比,数字时钟具有更高的准确

性和直观性,由于没有机械装置,其使用寿命更长。

1.1.2 设计目标

设计一款具有闹钟功能的数字时钟,具体要求如下

1、 用 8 个数码管实现,四个一组,每组有分钟和秒。左边一组是时间显示,右边一组用来

做闹钟时间。

2、 当左边时间等于右边时,蜂鸣器响 5 秒。

3、 闹钟时间和显示时间均可通过 3 个按键设置。设置方法:按下按键 1,时钟暂停,跳到

设置时间状态,再按下按键 1,回到正常状态。通过按键 2,选择要设置的位置,初始

设置秒个位,按一下,设置秒十位,再按下,设置分个位,以此类推,循环设置。通过

按键 3,设置数值,按一下数值加 1,如果溢出则重新变为 0。

1.1.3 系统结构框图

系统结构框图如下所示:

结构图共分两个,如果使用的开发板上是矩阵键盘的时候,对应的结构图是图一。如果使用的开

发板上是普通按键的时候,对应的结构图是图二。


图一


图二


1.1.4 模块功能

➢ 按键检测模块实现功能

1、将外来异步信号打两拍处理,将异步信号同步化。

2、实现 20ms 按键消抖功能,并输出有效按键信号。


➢ 矩阵键盘模块实现功能

1、将外来异步信号打两拍处理,将异步信号同步化。

2、实现 20ms 按键消抖功能。

3、实现矩阵键盘的按键检测功能,并输出有效按键信号。

➢ 时间产生模块实现功能

1、 产生显示时间数据。

2、 产生闹钟时间数据,

3、根据接收到的不同的按键信号,产生暂停、开启、设置时间的功能。

➢ 数码管显示模块实现功能

1、 对接收到的时间数据进行译码。

➢ 蜂鸣器模块实现功能

1、 将接受到的显示时间数据与闹钟时间数据进行比较,控制蜂鸣器的开启。

1.1.5 顶层信号



1.1.6 参考代码

下面是使用普通按键的顶层代码:





1.	module  alarm_clock(  
2.	    clk       ,  
3.	    rst_n     ,  
4.	    key       ,  
5.	    segment   ,  
6.	    seg_sel   ,  
7.	    beep  
8.	);  
9.	input                   clk             ;  
10.	input                   rst_n           ;  
11.	input   [2:0 ]          key             ;  
12.	output  [7:0 ]          segment         ;  
13.	output  [7:0 ]          seg_sel         ;  
14.	output                  beep            ;  
15.	  
16.	wire    [7:0 ]          segment         ;  
17.	wire    [7:0 ]          seg_sel         ;  
18.	wire                    beep            ;  
19.	wire    [3:0 ]          xs_sec_low      ;  
20.	wire    [3:0 ]          xs_sec_high     ;  
21.	wire    [3:0 ]          xs_min_low      ;  
22.	wire    [3:0 ]          xs_min_high     ;  
23.	wire    [3:0 ]          sec_low         ;  
24.	wire    [3:0 ]          sec_high        ;  
25.	wire    [3:0 ]          min_low         ;  
26.	wire    [3:0 ]          min_high        ;  
27.	wire    [25:0]          counter         ;  
28.	wire    [3:0 ]          key_vld         ;  
29.	wire                    flag_set        ;  
30.	  
31.	key_module u0(  
32.	             .clk         (clk         ),  
33.	             .rst_n       (rst_n       ),  
34.	             .key_in      (key         ),  
35.	             .key_vld     (key_vld     )  
36.	             );  
37.	time_data u1(  
38.	             .clk         (clk         ),   
39.	             .rst_n       (rst_n       ),   
40.	             .key_vld     (key_vld     ),   
41.	             .flag_set    (flag_set    ),   
42.	             .counter     (counter     ),  
43.	             .sec_low     (sec_low     ),  
44.	             .sec_high    (sec_high    ),  
45.	             .min_low     (min_low     ),  
46.	             .min_high    (min_high    ),  
47.	             .xs_sec_low  (xs_sec_low  ),   
48.	             .xs_sec_high (xs_sec_high ),   
49.	             .xs_min_low  (xs_min_low  ),   
50.	             .xs_min_high (xs_min_high )   
51.	             );  
52.	beep u2(  
53.	             .clk         (clk         ),   
54.	             .rst_n       (rst_n       ),   
55.	             .flag_set    (flag_set    ),   
56.	             .counter     (counter     ),   
57.	             .beep        (beep        ),  
58.	             .sec_low     (sec_low     ),  
59.	             .sec_high    (sec_high    ),  
60.	             .min_low     (min_low     ),  
61.	             .min_high    (min_high    ),  
62.	             .xs_sec_low  (xs_sec_low  ),  
63.	             .xs_sec_high (xs_sec_high ),  
64.	             .xs_min_low  (xs_min_low  ),  
65.	             .xs_min_high (xs_min_high )   
66.	             );  
67.	seg_disp u3(   
68.	             .clk         (clk         ),   
69.	             .rst_n       (rst_n       ),   
70.	             .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}),   
71.	             .segment     (segment     ),   
72.	             .seg_sel     (seg_sel     )    
73.	);  
74.	  
75.	  
76.	endmodule  


下面是使用矩阵键盘的顶层代码:






1.	module  alarm_clock_jvzhen(  
2.	    clk       ,  
3.	    rst_n     ,  
4.	    key_col   ,  
5.	    key_row   ,  
6.	    segment   ,  
7.	    seg_sel   ,  
8.	    beep  
9.	);  
10.	input                   clk             ;  
11.	input                   rst_n           ;  
12.	input   [3:0 ]          key_col         ;  
13.	output  [3:0 ]          key_row         ;  
14.	output  [7:0 ]          segment         ;  
15.	output  [7:0 ]          seg_sel         ;  
16.	output                  beep            ;  
17.	  
18.	wire    [7:0 ]          segment         ;  
19.	wire    [7:0 ]          seg_sel         ;  
20.	wire                    beep            ;  
21.	wire    [3:0 ]          xs_sec_low      ;  
22.	wire    [3:0 ]          xs_sec_high     ;  
23.	wire    [3:0 ]          xs_min_low      ;  
24.	wire    [3:0 ]          xs_min_high     ;  
25.	wire    [3:0 ]          sec_low         ;  
26.	wire    [3:0 ]          sec_high        ;  
27.	wire    [3:0 ]          min_low         ;  
28.	wire    [3:0 ]          min_high        ;  
29.	wire    [25:0]          counter         ;  
30.	wire    [3:0 ]          key_vld         ;  
31.	wire                    flag_set        ;  
32.	wire    [15:0]          key_out         ;  
33.	  
34.	key_scan  u0(  
35.	             .clk         (clk         ),  
36.	             .rst_n       (rst_n       ),  
37.	             .key_col     (key_col     ),  
38.	             .key_row     (key_row     ),  
39.	             .key_en      (key_vld     )  
40.	             );  
41.	time_data u1(  
42.	             .clk         (clk         ),   
43.	             .rst_n       (rst_n       ),   
44.	             .key_vld     (key_vld     ),   
45.	             .flag_set    (flag_set    ),   
46.	             .counter     (counter     ),  
47.	             .sec_low     (sec_low     ),  
48.	             .sec_high    (sec_high    ),  
49.	             .min_low     (min_low     ),  
50.	             .min_high    (min_high    ),  
51.	             .xs_sec_low  (xs_sec_low  ),   
52.	             .xs_sec_high (xs_sec_high ),   
53.	             .xs_min_low  (xs_min_low  ),   
54.	             .xs_min_high (xs_min_high )   
55.	             );  
56.	beep u2(  
57.	             .clk         (clk         ),   
58.	             .rst_n       (rst_n       ),   
59.	             .flag_set    (flag_set    ),   
60.	             .counter     (counter     ),   
61.	             .beep        (beep        ),  
62.	             .sec_low     (sec_low     ),  
63.	             .sec_high    (sec_high    ),  
64.	             .min_low     (min_low     ),  
65.	             .min_high    (min_high    ),  
66.	             .xs_sec_low  (xs_sec_low  ),  
67.	             .xs_sec_high (xs_sec_high ),  
68.	             .xs_min_low  (xs_min_low  ),  
69.	             .xs_min_high (xs_min_high )   
70.	             );  
71.	seg_disp u3(   
72.	             .clk         (clk         ),   
73.	             .rst_n       (rst_n       ),   
74.	             .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}),   
75.	             .segment     (segment     ),   
76.	             .seg_sel     (seg_sel     )    
77.	);  
78.	  
79.	  
80.	endmodule  


1.2 按键检测模块设计

1.2.1 接口信号



1.2.2 设计思路

在前面的案例中已经有按键检测的介绍,所以这里不在过多介绍,详细介绍请看下方链接:

【每周 FPGA 案例】至简设计系列_按键控制数字时钟

1.2.3 参考代码

使用明德扬的计数器模板,可以很快速很熟练地写出按键消抖模块。





1.	module  alarm_clock_jvzhen(  
2.	    clk       ,  
3.	    rst_n     ,  
4.	    key_col   ,  
5.	    key_row   ,  
6.	    segment   ,  
7.	    seg_sel   ,  
8.	    beep  
9.	);  
10.	input                   clk             ;  
11.	input                   rst_n           ;  
12.	input   [3:0 ]          key_col         ;  
13.	output  [3:0 ]          key_row         ;  
14.	output  [7:0 ]          segment         ;  
15.	output  [7:0 ]          seg_sel         ;  
16.	output                  beep            ;  
17.	  
18.	wire    [7:0 ]          segment         ;  
19.	wire    [7:0 ]          seg_sel         ;  
20.	wire                    beep            ;  
21.	wire    [3:0 ]          xs_sec_low      ;  
22.	wire    [3:0 ]          xs_sec_high     ;  
23.	wire    [3:0 ]          xs_min_low      ;  
24.	wire    [3:0 ]          xs_min_high     ;  
25.	wire    [3:0 ]          sec_low         ;  
26.	wire    [3:0 ]          sec_high        ;  
27.	wire    [3:0 ]          min_low         ;  
28.	wire    [3:0 ]          min_high        ;  
29.	wire    [25:0]          counter         ;  
30.	wire    [3:0 ]          key_vld         ;  
31.	wire                    flag_set        ;  
32.	wire    [15:0]          key_out         ;  
33.	  
34.	key_scan  u0(  
35.	             .clk         (clk         ),  
36.	             .rst_n       (rst_n       ),  
37.	             .key_col     (key_col     ),  
38.	             .key_row     (key_row     ),  
39.	             .key_en      (key_vld     )  
40.	             );  
41.	time_data u1(  
42.	             .clk         (clk         ),   
43.	             .rst_n       (rst_n       ),   
44.	             .key_vld     (key_vld     ),   
45.	             .flag_set    (flag_set    ),   
46.	             .counter     (counter     ),  
47.	             .sec_low     (sec_low     ),  
48.	             .sec_high    (sec_high    ),  
49.	             .min_low     (min_low     ),  
50.	             .min_high    (min_high    ),  
51.	             .xs_sec_low  (xs_sec_low  ),   
52.	             .xs_sec_high (xs_sec_high ),   
53.	             .xs_min_low  (xs_min_low  ),   
54.	             .xs_min_high (xs_min_high )   
55.	             );  
56.	beep u2(  
57.	             .clk         (clk         ),   
58.	             .rst_n       (rst_n       ),   
59.	             .flag_set    (flag_set    ),   
60.	             .counter     (counter     ),   
61.	             .beep        (beep        ),  
62.	             .sec_low     (sec_low     ),  
63.	             .sec_high    (sec_high    ),  
64.	             .min_low     (min_low     ),  
65.	             .min_high    (min_high    ),  
66.	             .xs_sec_low  (xs_sec_low  ),  
67.	             .xs_sec_high (xs_sec_high ),  
68.	             .xs_min_low  (xs_min_low  ),  
69.	             .xs_min_high (xs_min_high )   
70.	             );  
71.	seg_disp u3(   
72.	             .clk         (clk         ),   
73.	             .rst_n       (rst_n       ),   
74.	             .segment_data({xs_min_high,xs_min_low,xs_sec_high,xs_sec_low,min_high,min_low,sec_high,sec_low}),   
75.	             .segment     (segment     ),   
76.	             .seg_sel     (seg_sel     )    
77.	);  
78.	  
79.	  
80.	endmodule  

1.3 矩阵键盘模块设计

1.3.1 接口信号



1.3.2 设计思路

在前面的案例中已经有矩阵键盘的介绍,所以这里不在过多介绍,详细介绍请看下方链接:

http://fpgabbs.com/forum.php?mod=viewthread&tid=310

1.3.3 参考代码





1.	always  @(posedge clk or negedge rst_n)begin  
2.	    if(rst_n==1'b0)begin  
3.	        key_col_ff0 <= 4'b1111;  
4.	        key_col_ff1 <= 4'b1111;  
5.	    end  
6.	    else begin  
7.	        key_col_ff0 <= key_col    ;  
8.	        key_col_ff1 <= key_col_ff0;  
9.	    end  
10.	end  
11.	  
12.	  
13.	always @(posedge clk or negedge rst_n) begin   
14.	    if (rst_n==0) begin  
15.	        shake_cnt <= 0;   
16.	    end  
17.	    else if(add_shake_cnt) begin  
18.	        if(end_shake_cnt)  
19.	            shake_cnt <= 0;   
20.	        else  
21.	            shake_cnt <= shake_cnt+1 ;  
22.	   end  
23.	end  
24.	assign add_shake_cnt = key_col_ff1!=4'hf;  
25.	assign end_shake_cnt = add_shake_cnt  && shake_cnt == TIME_20MS-1 ;  
26.	  
27.	  
28.	always  @(posedge clk or negedge rst_n)begin  
29.	    if(rst_n==1'b0)begin  
30.	        state_c <= CHK_COL;  
31.	    end  
32.	    else begin  
33.	        state_c <= state_n;  
34.	    end  
35.	end  
36.	  
37.	always  @(*)begin  
38.	    case(state_c)  
39.	        CHK_COL: begin  
40.	                     if(col2row_start )begin  
41.	                         state_n = CHK_ROW;  
42.	                     end  
43.	                     else begin  
44.	                         state_n = CHK_COL;  
45.	                     end  
46.	                 end  
47.	        CHK_ROW: begin  
48.	                     if(row2del_start)begin  
49.	                         state_n = DELAY;  
50.	                     end  
51.	                     else begin  
52.	                         state_n = CHK_ROW;  
53.	                     end  
54.	                 end  
55.	        DELAY :  begin  
56.	                     if(del2wait_start)begin  
57.	                         state_n = WAIT_END;  
58.	                     end  
59.	                     else begin  
60.	                         state_n = DELAY;  
61.	                     end  
62.	                 end  
63.	        WAIT_END: begin  
64.	                     if(wait2col_start)begin  
65.	                         state_n = CHK_COL;  
66.	                     end  
67.	                     else begin  
68.	                         state_n = WAIT_END;  
69.	                     end  
70.	                  end  
71.	       default: state_n = CHK_COL;  
72.	    endcase  
73.	end  
74.	assign col2row_start = state_c==CHK_COL  && end_shake_cnt;  
75.	assign row2del_start = state_c==CHK_ROW  && row_index==3 && end_row_cnt;  
76.	assign del2wait_start= state_c==DELAY    && end_row_cnt;  
77.	assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;  
78.	  
79.	always  @(posedge clk or negedge rst_n)begin  
80.	    if(rst_n==1'b0)begin  
81.	        key_row <= 4'b0;  
82.	    end  
83.	    else if(state_c==CHK_ROW)begin  
84.	        key_row <= ~(1'b1 << row_index);  
85.	    end  
86.	    else begin  
87.	        key_row <= 4'b0;  
88.	    end  
89.	end  
90.	  
91.	  
92.	always @(posedge clk or negedge rst_n) begin   
93.	    if (rst_n==0) begin  
94.	        row_index <= 0;   
95.	    end  
96.	    else if(add_row_index) begin  
97.	        if(end_row_index)  
98.	            row_index <= 0;   
99.	        else  
100.	            row_index <= row_index+1 ;  
101.	   end  
102.	   else if(state_c!=CHK_ROW)begin  
103.	       row_index <= 0;  
104.	   end  
105.	end  
106.	assign add_row_index = state_c==CHK_ROW && end_row_cnt;  
107.	assign end_row_index = add_row_index  && row_index == 4-1 ;  
108.	  
109.	  
110.	always @(posedge clk or negedge rst_n) begin   
111.	    if (rst_n==0) begin  
112.	        row_cnt <= 0;   
113.	    end  
114.	    else if(add_row_cnt) begin  
115.	        if(end_row_cnt)  
116.	            row_cnt <= 0;   
117.	        else  
118.	            row_cnt <= row_cnt+1 ;  
119.	   end  
120.	end  
121.	assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;  
122.	assign end_row_cnt = add_row_cnt  && row_cnt == 16-1 ;  
123.	  
124.	  
125.	always  @(posedge clk or negedge rst_n)begin  
126.	    if(rst_n==1'b0)begin  
127.	        key_col_get <= 0;  
128.	    end  
129.	    else if(state_c==CHK_COL && end_shake_cnt ) begin  
130.	        if(key_col_ff1==4'b1110)  
131.	            key_col_get <= 0;  
132.	        else if(key_col_ff1==4'b1101)  
133.	            key_col_get <= 1;  
134.	        else if(key_col_ff1==4'b1011)  
135.	            key_col_get <= 2;  
136.	        else   
137.	            key_col_get <= 3;  
138.	    end  
139.	end  
140.	  
141.	  
142.	always  @(posedge clk or negedge rst_n)begin  
143.	    if(rst_n==1'b0)begin  
144.	        key_out <= 0;  
145.	    end  
146.	    else if(state_c==CHK_ROW && end_row_cnt)begin  
147.	        key_out <= {row_index,key_col_get};  
148.	    end  
149.	    else begin  
150.	        key_out <= 0;  
151.	    end  
152.	end  
153.	  
154.	always  @(posedge clk or negedge rst_n)begin  
155.	    if(rst_n==1'b0)begin  
156.	        key_vld <= 1'b0;  
157.	    end  
158.	    else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin  
159.	        key_vld <= 1'b1;  
160.	    end  
161.	    else begin  
162.	        key_vld <= 1'b0;  
163.	    end  
164.	end  
165.	  
166.	  
167.	always  @(*)begin  
168.	    if(rst_n==1'b0)begin  
169.	        key_en = 0;  
170.	    end  
171.	    else if(key_vld && key_out==0)begin  
172.	        key_en = 4'b0001;  
173.	    end  
174.	    else if(key_vld && key_out==1)begin  
175.	        key_en = 4'b0010;  
176.	    end  
177.	    else if(key_vld && key_out==2)begin  
178.	        key_en = 4'b0100;  
179.	    end  
180.	    else begin  
181.	        key_en = 0;  
182.	    end  
183.	end  

1.4 时间产生模块设计

1.4.1 接口信号



1.4.2 设计思路

根据题目功能要求可知,要显示的时间就是在完整的数字时钟的基础上,减少了时的高位和低位

的显示,再介绍架构之前,先了解一下本模块其他几个信号的作用。

设置状态指示信号 flag_set:该信号初始状态为低电平,表示模块处于正常工作状态,当按下按

键 key1 时,设置状态指示信号进行翻转,变为高电平,表示进入到设置状态。

设置位计数器 sel_cnt:该计数器表示要设置的位,初始状态为 0,表示可以设置闹钟的秒低位,

当其为 1 时表示可以设置闹钟的秒的高位,按照这样的顺序依次类推,当其为 7 的时候,表示可以设

置显示时间的分高位。加一条件为 key_vld[1]==1'b1,表示按下按键 key2 的时候加一;结束条件为 8,

显示时间的四个数码管加上闹钟的四个数码管共 8 个,所以数 8 个就清零。

由此可提出 5 个计数器的架构,如下图所示:



该架构由 5 个计数器组成:时钟计数器 counter、秒低位计数器 xs_sec_low、秒高位计数器 xs

_sec_high、分低位计数器 xs_min_low、分高位计数器 xs_min_high。

时钟计数器 counter:用于计算 1 秒的时钟个数,加一条件为 flag_set==1'b0,表示刚上电时开

始计数,key1 按下之后,进入设置模式,停止计数,再按下又重新开始计数;结束条件为 5000000

0,表示数到 1 秒就清零。

秒低位计数器 xs_sec_low:用于对 1 秒进行计数,加一条件为(sel_cnt==5-1 && set_en) || e

nd_counter,表示在设置状态下可通过按键 key3 来控制加一,或者在正常状态时数到 1 秒就加 1;

结束条件为 10,表示数到 10 秒就清零。

秒高位计数器 xs_sec_high:用于对 10 秒进行计数,加一条件为(sel_cnt==6-1 && set_en) ||

end_xs_sec_low,表示在设置状态下可通过按键 key3 来控制加一,或者在正常状态时数到 10 秒就

加 1;结束条件为 6,表示数到 60 秒就清零。

分低位计数器 xs_min_low:用于对 1 分进行计数,加一条件为(sel_cnt==7-1 && set_en) || e

nd_xs_sec_high,表示在设置状态下可通过按键 key3 来控制加一,或者在正常状态时数到 1 分就加

1;结束条件为 10,表示数到 10 分就清零。

分高位计数器 xs_min_high:用于对 10 分进行计数,加一条件为(sel_cnt==8-1 && set_en) ||

end_xs_min_low,表示在设置状态下可通过按键 key3 来控制加一,或者在正常状态时数到 10 分就

加 1;结束条件为 6,表示数到 60 分就清零。

上面介绍了显示时间的计数器架构,下面我们来思考一下闹钟部分的架构。

我们都知道闹钟的工作原理,它本身不会自动计数,需要我们手动设置。根据本设计的功能要求,

有四个数码管来显示设置的闹钟秒的高低位和分的高低位,因此我们提出四个计数器组成的架构,这


四个计数器相互独立,互不干涉,结构图如下:

该架构由 4 个计数器组成:秒低位计数器 sec_low、秒高位计数器 sec_high、分低位计数器 mi

n_low、分高位计数器 min_high。

秒低位计数器 sec_low:用于对闹钟秒的低位进行计数,加一条件为 sel_cnt==1-1 && set_en,

表示在设置状态下通过按键 key3 来控制加一 ;结束条件为 10,表示最大能设置为 9,超过之后便

清零。

秒高位计数器 sec_high:用于对闹钟秒的高位进行计数,加一条件为 sel_cnt==2-1 && set_en,

表示在设置状态下可通过按键 key3 来控制加一;结束条件为 6,表示最大能设置为 5,超过之后便

清零。

分低位计数器 min_low:用于对闹钟分的低位进行计数,加一条件为 sel_cnt==3-1 && set_en,

表示在设置状态下可通过按键 key3 来控制加一;结束条件为 10,表示最大能设置为 9,超过之后便

清零。

分高位计数器 min_high:用于对闹钟分高位进行计数,加一条件为 sel_cnt==4-1 && set_en,

表示在设置状态下可通过按键 key3 来控制加一;结束条件为 6,表示最大能设置为 5,超过之后便

清零。


1.4.3 参考代码

使用明德扬的计数器模板,可以很快速很熟练地写出时间产生模块。





always  @(posedge clk or negedge rst_n)begin
2.	    if(rst_n==1'b0)begin
3.	        flag_set<=1'b0;
4.	    end
5.	    else if(key_vld[0]==1'b1)begin
6.	        flag_set<=~flag_set;
7.	    end
8.	    else begin
9.	        flag_set<=flag_set;
10.	    end
11.	end
12.	
13.	
14.	always @(posedge clk or negedge rst_n) begin 
15.	    if (rst_n==0) begin
16.	        sel_cnt <= 0; 
17.	    end
18.	    else if(add_sel_cnt) begin
19.	        if(end_sel_cnt)
20.	            sel_cnt <= 0; 
21.	        else
22.	            sel_cnt <= sel_cnt+1 ;
23.	   end
24.	end
25.	assign add_sel_cnt = key_vld[1]==1'b1;
26.	assign end_sel_cnt = add_sel_cnt  && sel_cnt == 8-1 ;
27.	
28.	
29.	always  @(posedge clk or negedge rst_n)begin
30.	    if(rst_n==1'b0)begin
31.	        set_en<=1'b0;
32.	    end
33.	    else if(flag_set==1'b1 && key_vld[2]==1'b1)begin
34.	        set_en<=1'b1;
35.	    end
36.	    else begin
37.	        set_en<=1'b0;
38.	    end
39.	end
40.	
41.	
42.	always @(posedge clk or negedge rst_n) begin 
43.	    if (rst_n==0) begin
44.	        counter <= 0; 
45.	    end
46.	    else if(add_counter) begin
47.	        if(end_counter)
48.	            counter <= 0; 
49.	        else
50.	            counter <= counter+1 ;
51.	   end
52.	end
53.	assign add_counter = flag_set==1'b0;
54.	assign end_counter = add_counter  && counter == 26'd5000_0000-1;
55.	
56.	
57.	always @(posedge clk or negedge rst_n) begin 
58.	    if (rst_n==0) begin
59.	        sec_low <= 0; 
60.	    end
61.	    else if(add_sec_low) begin
62.	        if(end_sec_low)
63.	            sec_low <= 0; 
64.	        else
65.	            sec_low <= sec_low+1 ;
66.	   end
67.	end
68.	assign add_sec_low = sel_cnt==1-1 && set_en;
69.	assign end_sec_low = add_sec_low  && sec_low == 10-1 ;
70.	
71.	
72.	always @(posedge clk or negedge rst_n) begin 
73.	    if (rst_n==0) begin
74.	        sec_high <= 0; 
75.	    end
76.	    else if(add_sec_high) begin
77.	        if(end_sec_high)
78.	            sec_high <= 0; 
79.	        else
80.	            sec_high <= sec_high+1 ;
81.	   end
82.	end
83.	assign add_sec_high = sel_cnt==2-1 && set_en;
84.	assign end_sec_high = add_sec_high  && sec_high == 6-1 ;
85.	
86.	
87.	always @(posedge clk or negedge rst_n) begin 
88.	    if (rst_n==0) begin
89.	        min_low <= 0; 
90.	    end
91.	    else if(add_min_low) begin
92.	        if(end_min_low)
93.	            min_low <= 0; 
94.	        else
95.	            min_low <= min_low+1 ;
96.	   end
97.	end
98.	assign add_min_low = sel_cnt==3-1 && set_en;
99.	assign end_min_low = add_min_low  && min_low == 10-1 ;
100.	
101.	always @(posedge clk or negedge rst_n) begin 
102.	    if (rst_n==0) begin
103.	        min_high <= 0; 
104.	    end
105.	    else if(add_min_high) begin
106.	        if(end_min_high)
107.	            min_high <= 0; 
108.	        else
109.	            min_high <= min_high+1 ;
110.	   end
111.	end
112.	assign add_min_high = sel_cnt==4-1 && set_en;
113.	assign end_min_high = add_min_high  && min_high == 6-1 ;
114.	
115.	
116.	always @(posedge clk or negedge rst_n) begin 
117.	    if (rst_n==0) begin
118.	        xs_sec_low <= 0; 
119.	    end
120.	    else if(add_xs_sec_low) begin
121.	        if(end_xs_sec_low)
122.	            xs_sec_low <= 0; 
123.	        else
124.	            xs_sec_low <= xs_sec_low+1 ;
125.	   end
126.	end
127.	assign add_xs_sec_low = (sel_cnt==5-1 && set_en) || end_counter;
128.	assign end_xs_sec_low = add_xs_sec_low && xs_sec_low == 10-1 ;
129.	
130.	
131.	always @(posedge clk or negedge rst_n) begin 
132.	    if (rst_n==0) begin
133.	        xs_sec_high <= 0; 
134.	    end
135.	    else if(add_xs_sec_high) begin
136.	        if(end_xs_sec_high)
137.	            xs_sec_high <= 0; 
138.	        else
139.	            xs_sec_high <= xs_sec_high+1 ;
140.	   end
141.	end
142.	assign add_xs_sec_high = (sel_cnt==6-1 && set_en) || end_xs_sec_low;
143.	assign end_xs_sec_high = add_xs_sec_high  && xs_sec_high == 6-1 ;
144.	
145.	
146.	always @(posedge clk or negedge rst_n) begin 
147.	    if (rst_n==0) begin
148.	        xs_min_low <= 0; 
149.	    end
150.	    else if(add_xs_min_low) begin
151.	        if(end_xs_min_low)
152.	            xs_min_low <= 0; 
153.	        else
154.	            xs_min_low <= xs_min_low+1 ;
155.	   end
156.	end
157.	assign add_xs_min_low = (sel_cnt==7-1 && set_en) || end_xs_sec_high;
158.	assign end_xs_min_low = add_xs_min_low  && xs_min_low == 10-1 ;
159.	
160.	
161.	always @(posedge clk or negedge rst_n) begin 
162.	    if (rst_n==0) begin
163.	        xs_min_high <= 0; 
164.	    end
165.	    else if(add_xs_min_high) begin
166.	        if(end_xs_min_high)
167.	            xs_min_high <= 0; 
168.	        else
169.	            xs_min_high <= xs_min_high+1 ;
170.	   end
171.	end
172.	assign add_xs_min_high = (sel_cnt==8-1 && set_en) || end_xs_min_low;
173.	assign end_xs_min_high = add_xs_min_high  && xs_min_high == 6-1 ; 

1.5 数码管显示模块设计


1.5.1 接口信号



1.5.2 设计思路

在前面的案例中已经有数码管显示的介绍,所以这里不在过多介绍,详细介绍请看下方链接:

http://fpgabbs.com/forum.php?mod=viewthread&tid=399

1.5.3 参考代码





174.	always @(posedge clk or negedge rst_n) begin 
175.	    if (rst_n==0) begin
176.	        delay <= 0; 
177.	    end
178.	    else if(add_delay) begin
179.	        if(end_delay)
180.	            delay <= 0; 
181.	        else
182.	            delay <= delay+1 ;
183.	   end
184.	end
185.	assign add_delay = 1;
186.	assign end_delay = add_delay  && delay == 2000-1 ;
187.	
188.	
189.	
190.	
191.	always @(posedge clk or negedge rst_n) begin 
192.	    if (rst_n==0) begin
193.	        delay_time <= 0; 
194.	    end
195.	    else if(add_delay_time) begin
196.	        if(end_delay_time)
197.	            delay_time <= 0; 
198.	        else
199.	            delay_time <= delay_time+1 ;
200.	   end
201.	end
202.	assign add_delay_time = end_delay;
203.	assign end_delay_time = add_delay_time  && delay_time == 8-1 ;
204.	
205.	
206.	assign segment_tmp  = segment_data[(1+delay_time)*4-1 -:4];
207.	always  @(posedge clk or negedge rst_n)begin
208.	    if(rst_n==1'b0)begin
209.	        segment <= ZERO;
210.	    end
211.	    else begin
212.	        case(segment_tmp)
213.	            4'd0:segment <= ZERO;
214.	            4'd1:segment <= ONE  ;
215.	            4'd2:segment <= TWO  ;
216.	            4'd3:segment <= THREE;
217.	            4'd4:segment <= FOUR ;
218.	            4'd5:segment <= FIVE ;
219.	            4'd6:segment <= SIX  ;
220.	            4'd7:segment <= SEVEN;
221.	            4'd8:segment <= EIGHT;
222.	            4'd9:segment <= NINE ;
223.	            default:begin
224.	                segment <= segment;
225.	            end
226.	        endcase
227.	    end
228.	end
229.	
230.	
231.	always  @(posedge clk or negedge rst_n)begin
232.	    if(rst_n==1'b0)begin
233.	        seg_sel <= 8'b1111_1111;
234.	    end
235.	    else begin
236.	        seg_sel <= ~(8'b1<<delay_time);
237.	    end
238.	end

1.6 蜂鸣器模块设计

1.6.1 接口信号



1.6.2 设计思路

本模块主要通过将显示时间与设置的闹钟时间进行比较,如果相同的话,就控制 beep 拉低,持

续时间为 5 秒。由此提出一个计数器的架构,如下图所示。



该架构由蜂鸣器控制信号 beep、秒计数器 miao 和闹钟触发指示信号 flag_add 组成。

秒计数器秒:用于对 5 秒的时间进行计数,加一条件为 flag_add && end_counter,表示当闹

钟被触发,并且经过 1 秒的时间就加一;结束条件为 5,表示数完 5 秒就清零。

闹钟触发指示信号 flag:当其为高电平时表示闹钟被触发,低电平表示没有被触发。初始状态为

低电平,从低变高的条件为 sec_low==xs_sec_low&&sec_high==xs_sec_high&&min_low==xs_min

_low&&min_high==xs_min_high&&init&&!key1_func,表示当显示时间的秒高低位、分高低位和闹钟

设置的秒高低位、分高低位相等,同时不处于刚上电的初始状态和设置状态时,闹钟被触发;从高变

低的条件为 end_miao,表示当 5 秒数完之后,就拉低。

蜂鸣器控制信号 beep:当其为低电平时,控制蜂鸣器响,为高电平时不响。初始状态为高电平,

从高变低的条件为 flag_add,表示计数器开始计数之后便将其拉低,当检测到 flag_add=0 的时候,

便将其拉高。

1.6.3 参考代码





239.	always  @(posedge clk or negedge rst_n)begin
240.	    if(rst_n==1'b0)begin
241.	        flag_add <= 0;
242.	    end
243.	    else if(sec_low==xs_sec_low&&sec_high==xs_sec_high&&min_low==xs_min_low&&min_high==xs_min_high&&init&&flag_set==0)begin
244.	        flag_add <= 1;
245.	    end
246.	    else if(end_miao)begin
247.	        flag_add <= 0;
248.	    end
249.	end
250.	
251.	
252.	always@(*)begin
253.	    if(!sec_low&&!sec_high&&!min_low&&!min_high)begin
254.	        init=0;
255.	    end
256.	    else begin
257.	        init=1;
258.	    end
259.	end
260.	
261.	
262.	always @(posedge clk or negedge rst_n) begin 
263.	    if (rst_n==0) begin
264.	        miao <= 0; 
265.	    end
266.	    else if(add_miao) begin
267.	        if(end_miao)
268.	            miao <= 0; 
269.	        else
270.	            miao <= miao+1 ;
271.	   end
272.	end
273.	assign add_miao = flag_add && end_counter;
274.	assign end_miao = add_miao  && miao == 5-1 ;
275.	
276.	
277.	always@(posedge clk or negedge rst_n)begin
278.	     if(rst_n==1'b0)begin
279.	        beep<=1'b1;
280.	    end
281.	    else if(flag_add)begin
282.	        beep<=1'b0;
283.	    end
284.	    else
285.	        beep<=1'b1;
286.	end 


1.7 效果和总结

➢ 下图是该工程在 mp801 开发板上的现象

其中按键 s4 控制数字时钟的暂停与开始,按键 s3 来选择需要设置的位,按键 s2 设置数值。左

边四个数码管显示的是时钟的时间,右边四个数码管显示的是闹钟设置的时间。



➢ 下图是该工程在 db603 开发板上的现象

其中按键 s1 控制数字时钟的暂停与开始,按键 s2 来选择需要设置的位,按键 s3 设置数值。左

边四个数码管显示的是时钟的时间,右边四个数码管显示的是闹钟设置的时间。



➢ 下图是该工程在 ms980 试验箱上的现象

其中按键 s1 控制数字时钟的暂停与开始,按键 s2 来选择需要设置的位,按键 s3 设置数值。左

边四个数码管显示的是时钟的时间,右边四个数码管显示的是闹钟设置的时间。



由于该项目的上板现象是动态的,开始、暂停、时间设置等现象无法通过图片表现出来,想观看

完整现象的朋友可以看一下现象演示的视频。


感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行 FPGA 相关工程设计学习,

也可以看一下我们往期的文章:

《基于 FPGA 的密码锁设计》

《波形相位频率可调 DDS 信号发生器》

《基于 FPGA 的曼彻斯特编码解码设计》

《基于 FPGA 的出租车计费系统》

《数电基础与 Verilog 设计》

《基于 FPGA 的频率、电压测量》

《基于 FPGA 的汉明码编码解码设计》

《关于锁存器问题的讨论》

《阻塞赋值与非阻塞赋值》

《参数例化时自动计算位宽的解决办法》


明德扬是一家专注于 FPGA 领域的专业性公司,公司主要业务包括开发板、教育培训、项目承

接、人才服务等多个方向。

点拨开发板——学习 FPGA 的入门之选。

MP801 开发板——千兆网、ADDA、大容量 SDRAM 等,学习和项目需求一步到位。

网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习 FPGA。

周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。

就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。

专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO 架构

设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。

项目承接——承接企业 FPGA 研发项目。

人才服务——提供人才推荐、人才代培、人才派遣等服务。


   拓展阅读