题目:
在FPGA中设计实现一个多功能数字钟,具备以下功能:
1.准确计时。能显示时分秒,小时的计时为24进制,分和秒的计时为 60 进制。
2.准点报时。当“时-分-秒” 为“ XX-59-55、 XX-59-56、 XX-59-57、 XX-59-58、XX-59-59” 时, 蜂鸣器发“ 嘀” ; 当“时-分-秒” 为“ XX-00-00” 时,扬声器发“ 嗒” 。
3.校时功能。能够对数字钟的时分秒进行精确的调节。
(本实验基于Altera EP4CE10 征途Pro开发板设计实现)
一.设计思路
该工程主要包括顶层模块Digitalclock和若干个底层模块。计时模块Timer_gen,数码管显示模块display,595控制模块hc595_ctrl,按键消抖模块key_filter,调时模块Adjust以及蜂鸣器控制模块buzzer。
整体的RTL视图如下:
其中display_595模块中包含了对数码管显示模块display,595控制模块hc595_ctrl的例化。
二.FPGA板硬件部分电路原理图
1. 六位八段数码管
其具体使用方法见文章:Altera EP4CE10 征途Pro开发板数码管显示原理(以实现模60计数器为例)-CSDN博客
2. 无源蜂鸣器buzzer
相对于有源蜂鸣器,无源蜂鸣器的成本更低,声音频率可控。而有源蜂鸣器因其内部自带振荡源,只要加上适当的直流电源即可发声,程序控制较为方便。无源蜂鸣器与有源蜂鸣器不同,因其内部不带震荡源,所以其无法向有缘蜂鸣器那样直接用直流信号驱动,这里需要使用PWM方波才能驱动其发声。
其具体使用方法见文章:无源蜂鸣器的驱动实验-CSDN博客
3. 按键key
开发板上使用的机械按键也是按键的一种,特点是:接触电阻小 ,手感好,按键按下或弹起时有“滴答”清脆声;但由于其构造和原理,在按键闭合及断开的瞬间均伴随有一连串的抖动。
具体按键消抖方法的设计请参阅文章:按键消抖模块设计实现-CSDN博客
三.Verilog代码实现各个模块
1. 计时模块Timer_gen
计时模块主要包含对时间数据的生成及其如何计数的设计。本模块内包含一个分频器产生标准秒脉冲。模块中同样包含调时信号输入时数字时间的变化。
调时信号输入时,时间保持不变,由按键进行调时。
核心部分代码如下:
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
cnt_1s <= 1'b0 ;
else if( cnt_1s == CNT_MAX )
cnt_1s <= 1'b0 ;
else
cnt_1s <= cnt_1s + 1'b1 ;
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
clk1 <= 1'b0;
else if( cnt_1s == CNT_MAX )
clk1 <= ~clk1 ;
else
clk1 <= clk1 ;
always@( posedge clk1 or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
begin
timer[23:20] <= 4'd0;
timer[19:16] <= 4'd0;
timer[15:12] <= 4'd0;
timer[11:8] <= 4'd0;
timer[7:4] <= 4'd0;
timer[3:0] <= 4'd0;
end
else if( adjust_h == 1'b0 && adjust_m == 1'b0 && adjust_s == 1'b0)
begin
if( (timer[23:20] == 4'd2) && (timer[19:16] == 4'd3) && (timer[15:12] == 4'd5) && (timer[11:8] == 4'd9) && (timer[7:4] == 4'd5) && (timer[3:0] == 4'd9 ) )
begin
timer[23:20] <= 4'd0;
timer[19:16] <= 4'd0;
timer[15:12] <= 4'd0;
timer[11:8] <= 4'd0;
timer[7:4] <= 4'd0;
timer[3:0] <= 4'd0;
end
else if( (timer[19:16] == 4'd3) && (timer[15:12] == 4'd5) && (timer[11:8] == 4'd9) && (timer[7:4] == 4'd5) && (timer[3:0] == 4'd9 ) )
begin
timer[23:20] <= timer[23:20] + 1'b1 ;
timer[19:16] <= 4'd0;
timer[15:12] <= 4'd0;
timer[11:8] <= 4'd0;
timer[7:4] <= 4'd0;
timer[3:0] <= 4'd0;
end
else if( (timer[15:12] == 4'd5) && (timer[11:8] == 4'd9) && (timer[7:4] == 4'd5) && (timer[3:0] == 4'd9 ) )
begin
timer[19:16] <= timer[19:16] + 1'b1 ;
timer[15:12] <= 4'd0;
timer[11:8] <= 4'd0;
timer[7:4] <= 4'd0;
timer[3:0] <= 4'd0;
end
else if( (timer[11:8] == 4'd9) && (timer[7:4] == 4'd5) && (timer[3:0] == 4'd9 ) )
begin
timer[15:12] <= timer[15:12] + 1'b1 ;
timer[11:8] <= 4'd0;
timer[7:4] <= 4'd0;
timer[3:0] <= 4'd0;
end
else if( (timer[7:4] == 4'd5) && (timer[3:0] == 4'd9 ) )
begin
timer[11:8] <= timer[11:8] + 1'b1 ;
timer[7:4] <= 4'd0;
timer[3:0] <= 4'd0;
end
else if( (timer[3:0] == 4'd9 ) )
begin
timer[7:4] <= timer[7:4] + 1'b1 ;
timer[3:0] <= 4'd0;
end
else
begin
timer[3:0] <= timer[3:0] + 1'b1 ;
end
end
else if( adjust_h == 1'b1 || adjust_m == 1'b1 || adjust_s == 1'b1 )
if( adjust_en == 1'b0 )
begin
timer[23:20] <= timer[23:20] ;
timer[19:16] <= timer[19:16] ;
timer[15:12] <= timer[15:12] ;
timer[11:8] <= timer[11:8] ;
timer[7:4] <= timer[7:4] ;
timer[3:0] <= timer[3:0] ;
end
else if( adjust_en == 1'b1 && adjust_h == 1'b1 )
begin
timer[15:12] <= timer[15:12] ;
timer[11:8] <= timer[11:8] ;
timer[7:4] <= timer[7:4] ;
timer[3:0] <= timer[3:0] ;
if( timer[23:20] == 4'd2 && timer[19:16] == 4'd3 )
timer[23:16] <= 8'b0000_0000;
else if( timer[19:16] == 4'd9 )
begin
timer[23:20] <= timer[23:20] + 1'b1 ;
timer[19:16] <= 4'd0 ;
end
else
timer[19:16] <= timer[19:16] + 1'b1 ;
end
else if( adjust_en == 1'b1 && adjust_m == 1'b1 )
begin
timer[23:20] <= timer[23:20] ;
timer[19:16] <= timer[19:16] ;
timer[7:4] <= timer[7:4] ;
timer[3:0] <= timer[3:0] ;
if( timer[15:12] == 4'd5 && timer[11:8] == 4'd9 )
timer[7:0] <= 8'b0000_0000;
else if( timer[11:8] == 4'd9 )
begin
timer[15:12] <= timer[15:12] + 1'b1 ;
timer[11:8] <= 4'd0 ;
end
else
timer[11:8] <= timer[11:8] + 1'b1 ;
end
else if( adjust_en == 1'b1 && adjust_s == 1'b1 )
begin
timer[23:20] <= timer[23:20] ;
timer[19:16] <= timer[19:16] ;
timer[15:12] <= timer[15:12] ;
timer[11:8] <= timer[11:8] ;
if( timer[7:4] == 4'd5 && timer[3:0] == 4'd9 )
timer[7:0] <= 8'b0000_0000;
else if( timer[3:0] == 4'd9 )
begin
timer[7:4] <= timer[7:4] + 1'b1 ;
timer[3:0] <= 4'd0 ;
end
else
timer[3:0] <= timer[3:0] + 1'b1 ;
end
else
begin
timer[23:20] <= timer[23:20] ;
timer[19:16] <= timer[19:16] ;
timer[15:12] <= timer[15:12] ;
timer[11:8] <= timer[11:8] ;
timer[7:4] <= timer[7:4] ;
timer[3:0] <= timer[3:0] ;
end
2. 数码管显示模块display
通过动态扫描的方式实现数码管的动态显示。详见:Altera EP4CE10 征途Pro开发板数码管显示原理(以实现模60计数器为例)-CSDN博客
本实验中主要依靠其进行时分秒的显示。
当调时信号输入时,调整小时,数码管只显示小时;调整分钟,数码管只显示分钟;调整秒,数码管只显示秒。
核心代码如下:
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1ms <= 16'd0;
else if(cnt_1ms == CNT_MAX)
cnt_1ms <= 16'd0;
else
cnt_1ms <= cnt_1ms + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
flag_1ms <= 1'b0;
else if(cnt_1ms == CNT_MAX - 1'b1)
flag_1ms <= 1'b1;
else
flag_1ms <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sel <= 3'd0;
else if((cnt_sel == 3'd5) && (flag_1ms == 1'b1))
cnt_sel <= 3'd0;
else if(flag_1ms == 1'b1)
cnt_sel <= cnt_sel + 1'b1;
else
cnt_sel <= cnt_sel;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel_reg <= 6'b000_000;
else if((cnt_sel == 3'd0) && (flag_1ms == 1'b1))
sel_reg <= 6'b000_001;
else if(flag_1ms == 1'b1)
sel_reg <= sel_reg << 1;
else
sel_reg <= sel_reg;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_disp <= 4'b0;
else if((seg_en == 1'b1) && (flag_1ms == 1'b1))
begin
if( adjust_h == 1'b1 )
case(cnt_sel)
3'd0: data_disp <= 4'd11 ;
3'd1: data_disp <= 4'd11 ;
3'd2: data_disp <= 4'd11 ;
3'd3: data_disp <= 4'd11 ;
3'd4: data_disp <= timer[19:16];
3'd5: data_disp <= timer[23:20];
default:data_disp <= 4'b0 ;
endcase
else if( adjust_m == 1'b1 )
case(cnt_sel)
3'd0: data_disp <= 4'd11 ;
3'd1: data_disp <= 4'd11 ;
3'd2: data_disp <= timer[11:8] ;
3'd3: data_disp <= timer[15:12];
3'd4: data_disp <= 4'd11 ;
3'd5: data_disp <= 4'd11 ;
default:data_disp <= 4'b0 ;
endcase
else if( adjust_s == 1'b1 )
case(cnt_sel)
3'd0: data_disp <= timer[3:0] ;
3'd1: data_disp <= timer[7:4] ;
3'd2: data_disp <= 4'd11 ;
3'd3: data_disp <= 4'd11 ;
3'd4: data_disp <= 4'd11 ;
3'd5: data_disp <= 4'd11 ;
default:data_disp <= 4'b0 ;
endcase
else
case(cnt_sel)
3'd0: data_disp <= timer[3:0] ;
3'd1: data_disp <= timer[7:4] ;
3'd2: data_disp <= timer[11:8] ;
3'd3: data_disp <= timer[15:12];
3'd4: data_disp <= timer[19:16];
3'd5: data_disp <= timer[23:20];
default:data_disp <= 4'b0 ;
endcase
end
else
data_disp <= data_disp;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
dot_disp <= 1'b1;
else if(flag_1ms == 1'b1)
dot_disp <= ~point[cnt_sel];
else
dot_disp <= dot_disp;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg <= 8'b1111_1111;
else
case(data_disp)
4'd0 : seg <= {dot_disp,7'b100_0000};
4'd1 : seg <= {dot_disp,7'b111_1001};
4'd2 : seg <= {dot_disp,7'b010_0100};
4'd3 : seg <= {dot_disp,7'b011_0000};
4'd4 : seg <= {dot_disp,7'b001_1001};
4'd5 : seg <= {dot_disp,7'b001_0010};
4'd6 : seg <= {dot_disp,7'b000_0010};
4'd7 : seg <= {dot_disp,7'b111_1000};
4'd8 : seg <= {dot_disp,7'b000_0000};
4'd9 : seg <= {dot_disp,7'b001_0000};
4'd10 : seg <= 8'b1011_1111 ;
4'd11 : seg <= 8'b1111_1111 ;
default:seg <= 8'b1100_0000;
endcase
3. display_595模块
(对display模块以及hc595_ctr模块l的例化)
module display_595
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [23:0] timer ,
input wire [5:0] point ,
input wire seg_en ,
input wire sign ,
input wire adjust_h ,
input wire adjust_m ,
input wire adjust_s ,
input wire adjust_en ,
output wire stcp ,
output wire shcp ,
output wire ds ,
output wire oe
);
wire [5:0] sel;
wire [7:0] seg;
display display_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.timer (timer ),
.point (point ),
.seg_en (seg_en ),
.sign (sign ),
.adjust_h (adjust_h ),
.adjust_m (adjust_m ),
.adjust_s (adjust_s ),
.adjust_en (adjust_en),
.sel (sel ),
.seg (seg )
);
hc595_ctrl hc595_ctrl_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.sel (sel ),
.seg (seg ),
.stcp (stcp ),
.shcp (shcp ),
.ds (ds ),
.oe (oe )
);
endmodule
4. 蜂鸣器模块buzzer
准点报时。当“时-分-秒” 为“ XX-59-55、 XX-59-56、 XX-59-57、 XX-59-58、XX-59-59” 时, 蜂鸣器发“ 嘀” ; 当“时-分-秒” 为“ XX-00-00” 时,扬声器发“ 嗒” 。
其中“ 嘀”用频率为262HZ的DO音,“ 嗒”使用频率为494HZ的SI音。
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
en <= 1'b0 ;
else if( timer[15:0] == 16'h5955 || timer[15:0] == 16'h5956 || timer[15:0] == 16'h5957 || timer[15:0] == 16'h5958 || timer[15:0] == 16'h5959 || timer[15:0] == 16'h0000 )
en <= 1'b1 ;
else
en <= 1'b0 ;
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
begin
freq_data1 <= 18'd0;
freq_data2 <= 18'd0;
end
else if( flag && en )
begin
freq_data1 <= DO ;
freq_data2 <= SI ;
end
else
begin
freq_data1 <= 18'd0;
freq_data2 <= 18'd0;
end
assign duty_data = ( timer[15:0] == 16'h0000 ) ? 18'd50607 : 18'd95420 ;
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
freq_cnt <= 18'd0 ;
else
begin
if( timer[15:0] == 16'h0000 )
begin
if( freq_cnt == freq_data2 )
freq_cnt <= 18'd0 ;
else
freq_cnt <= freq_cnt + 1'b1 ;
end
else if( timer[15:0] == 16'h5959 || timer[15:0] == 16'h5958 || timer[15:0] == 16'h5957 || timer[15:0] == 16'h5956 || timer[15:0] == 16'h5955 )
begin
if( freq_cnt == freq_data1 )
freq_cnt <= 18'd0 ;
else
freq_cnt <= freq_cnt + 1'b1 ;
end
else
freq_cnt <= 18'd0 ;
end
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
beepout <= 1'b0 ;
else if( timer[15:0] == 16'h0000 || timer[15:0] == 16'h5959 || timer[15:0] == 16'h5958 || timer[15:0] == 16'h5957 || timer[15:0] == 16'h5956 || timer[15:0] == 16'h5955 )
begin
if ( freq_cnt >= duty_data )
beepout <= 1'b1 ;
else
beepout <= 1'b0 ;
end
else
beepout <= 1'b0 ;
6. 调时模块Adjust
按键key1调时按键,其按下时,LED0亮起,表示调时状态;再次按下退出调时,LED熄灭;
按键key2调分按键,其按下时,LED1亮起,表示调分状态;再次按下退出调分,LED熄灭;
按键key3调秒按键,其按下时,LED2亮起,表示调秒状态;再次按下退出调秒,LED熄灭;
按键key4调节按键,其按下时,LED3亮起,表示调节状态,所选中的时或分或秒进行自增调节,再次按下即确定所调时间。
核心代码如下:
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
begin
adjust_h <= 1'b0 ;
adjust_m <= 1'b0 ;
adjust_s <= 1'b0 ;
end
else if( adjust_m == 1'b0 && adjust_s == 1'b0 && flag_h == 1'b0 )
adjust_h <= ~ adjust_h ;
else if( adjust_h == 1'b0 && adjust_s == 1'b0 && flag_m == 1'b0 )
adjust_m <= ~ adjust_m ;
else if( adjust_h == 1'b0 && adjust_m == 1'b0 && flag_s == 1'b0 )
adjust_s <= ~ adjust_s ;
else
begin
adjust_h <= adjust_h ;
adjust_m <= adjust_m ;
adjust_s <= adjust_s ;
end
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
adjust_en <= 1'b0 ;
else if( flag == 1'b0 )
adjust_en <= ~ adjust_en ;
else
adjust_en <= adjust_en ;
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
ledout[3] <= 1'b1 ;
else if( adjust_en && ( adjust_s || adjust_m || adjust_h ) )
ledout[3] <= 1'b0 ;
else
ledout[3] <= 1'b1 ;
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
ledout[2] <= 1'b1 ;
else if( adjust_s == 1'b1 )
ledout[2] <= 1'b0 ;
else
ledout[2] <= 1'b1 ;
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
ledout[1] <= 1'b1 ;
else if( adjust_m == 1'b1 )
ledout[1] <= 1'b0 ;
else
ledout[1] <= 1'b1 ;
always@( posedge sys_clk or negedge sys_rst_n )
if( sys_rst_n == 1'b0 )
ledout[0] <= 1'b1 ;
else if( adjust_h == 1'b1 )
ledout[0] <= 1'b0 ;
else
ledout[0] <= 1'b1 ;
7. 按键消抖模块key_filter
代码如下:
module key_filter
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire key_in ,
output reg key_flag
);
parameter CNT_MAX = 20'd999_999 ;
reg [19:0] cnt_20ms ;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'b0;
else if(key_in == 1'b1)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0)
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 1'b1;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= ~key_flag;
else
key_flag <= 1'b1 ;
endmodule
8. 顶层模块Digitalclock
实现对所有底层模块的例化
module Digitalclock
(
input wire sys_clk ,
input wire sys_rst_n ,
input wire [3:0] key_in ,
output wire [3:0] ledout ,
output wire beepout ,
output wire stcp ,
output wire shcp ,
output wire ds ,
output wire oe
);
wire [23:0] timer ;
wire [5:0] point ;
wire seg_en ;
wire sign ;
wire adjust_en;
wire adjust_h ;
wire adjust_m ;
wire adjust_s ;
Timer_gen Timer_gen_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.adjust_h (adjust_h ),
.adjust_m (adjust_m ),
.adjust_s (adjust_s ),
.adjust_en (adjust_en),
.timer (timer ),
.point (point ),
.seg_en (seg_en ),
.sign (sign )
);
display_595 display_595_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.timer (timer ),
.point (point ),
.seg_en (seg_en ),
.sign (sign ),
.adjust_h (adjust_h ),
.adjust_m (adjust_m ),
.adjust_s (adjust_s ),
.adjust_en (adjust_en ),
.stcp (stcp ),
.shcp (shcp ),
.ds (ds ),
.oe (oe )
);
buzzer buzzer_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.timer (timer ),
.beepout (beepout )
);
Adjust Adjust_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.key_in (key_in ),
.ledout (ledout ),
.adjust_h (adjust_h ),
.adjust_m (adjust_m ),
.adjust_s (adjust_s ),
.adjust_en (adjust_en )
);
endmodule
四. FPGA管脚配置
通过查询开发板硬件电路原理图可知:
五.工程运行烧录结果
实现00-00-00到23-59-59的24小时数字钟显示,并有整点报时以及调时功能。
下图显示时间01-02-39:
下图显示调时状态,数码管只显示小时,LED0亮起;LED3亮起,时自增调节: