一.数字时钟设计
1.硬件资源:LCD1602液晶屏一块,FPGA开发板一块(A_C8V4);
2. 开发板资源:3颗独立按键,LCD1602接口;
3. 功能设计:三种功能:a.时钟功能;b.闹钟功能;c.校时功能;
4. 按键功能设计:按键1切换数字钟模式,按键2调整数字钟时钟显示(包括闹钟调时),按键3调整数字时钟分钟显示(包括闹钟调分);
二.数字时钟代码
1.数字时钟顶层模块RTL视图
1) 说明:这个为数字时钟的顶层模块,按键消抖模块debkey,数字钟功能模块digitalclk,LCD1602液晶显示模块lcd_ip;
2) 端口
输入:clk,reset,key;
输出:lcd_e,lcd_rw,lcd_rs,lcd_data,beep;
3) 代码
//数字时钟总模块
module digitalclk_top(clk,reset,key,lcd_rw,lcd_e,lcd_rs,lcd_data,beep);
input clk;
input reset;
input [2:0]key;
output lcd_rw;
output lcd_rs;
output lcd_e;
output [7:0]lcd_data;
output beep;
wire [2:0]debkey;
wire [255:0]disp;
debkey U0
(.clk(clk),
.reset(reset),
.key(key),
.debkey(debkey));
digitalclk U1
(.clk(clk),
.reset(reset),
.key(debkey),
.lcd_data_disp(disp),
.beep(beep));
lcd_ip U3
(.clk(clk),
.rst(reset),
.data_buf(disp),
.lcd_rw(lcd_rw),
.lcd_rs(lcd_rs),
.lcd_e(lcd_e),
.lcd_data(lcd_data));
endmodule
2.按键消抖模块
1) 说明:这个模块为按键消抖模块,三颗按键;
2) 端口
输入:clk,reset,key;
输出:debkey;
3) 代码
请参考按键消抖
3. LCD模块
1) 说明:这个为LCD1602模块,其中data_buf为256位,每一个格为8位,一共有32个,所以32x8=256;
2) 端口
输入:clk,rst,data_buf;
输出:lcd_e,lcd_rs,lcd_rw,lcd_data;
3)代码
请参考LCD模块
4.时钟钟功能模块
1) 说明:这个模块为数字钟功能模块,包括数字钟模式模块digitalclk_mode,数字钟时钟模块clock,数字钟闹钟模块alarm,数字钟校时模块adj,数字钟数据处理选择模块disp_sel,还有一个不和谐的闹铃铃声模块beep;
2) 端口
输入:clk,reset,key;
输出:beep,lcd_data_disp;
3) 代码
//数字时钟顶层模块
module digitalclk(clk,reset,key,lcd_data_disp,beep);
input clk;
input reset;
input [2:0]key;
output [255:0]lcd_data_disp;
output beep;
wire [2:0]mode;
wire [255:0]clock_disp,alarm_disp,adj_disp;
wire [255:0]sync_clock,sync_adj;
wire alarm_en;
digitalclk_mode U4
(.clk(clk),
.reset(reset),
.key(key),
.mode(mode));
clock U5
(.clk(clk),
.reset(reset),
.mode(mode),
.sync_clock(sync_clock),
.lcd_data_disp(clock_disp));
alarm U6
(.clk(clk),
.reset(reset),
.mode(mode),
.key(key),
.lcd_data_disp(alarm_disp));
adj U7
(.clk(clk),
.reset(reset),
.mode(mode),
.key(key),
.sync_adj(sync_adj),
.lcd_data_disp(adj_disp));
disp_sel U8
(.clk(clk),
.mode(mode),
.alarm_disp(alarm_disp),
.clock_disp(clock_disp),
.adj_disp(adj_disp),
.sync_clock(sync_clock),
.sync_adj(sync_adj),
.lcd_data_disp(lcd_data_disp),
.alarm_en(alarm_en));
beep U9
(.clk(clk),
.reset(reset),
.alarm_en(alarm_en),
.beep(beep));
endmodule
5.数字钟digitalclk_mode模块
1) 说明:这个为数字钟的模式切换模块
2) 端口
输入:clk,reset,key;
输出;mode;
3) 代码
//数字时钟功能模块
module digitalclk_mode(clk,reset,key,mode);
input clk;
input reset;
input [2:0]key;
output [2:0]mode;
//---------------------------------------------------------------
parameter clock=2'd0,alarm=2'd1,adj=2'd2;
//---------------------------------------------------------------
reg [2:0]func;
reg [2:0]mode;
always @(posedge key[0] or negedge reset)
if(!reset)
func <= 1'b0;
else
begin
func <= func + 1'b1; //产生不同的mode码
if(func == 2'd2)
func <= 1'b0;
end
//---------------------------------------------------------------
always @(posedge clk)
case(func)
clock : mode = 3'b001; //时钟模式
alarm : mode = 3'b010; //闹钟模式
adj : mode = 3'b100; //校时模式
default : mode = 3'b001;
endcase
endmodule
6.时钟模块
1) 说明:这个是数字钟的时钟模块,其中sync_clock是用来后台同步校时功能的时钟和分钟
2) 端口
输入:clk,reset,mode,sync_clock;
输出:lcd_data_displ;
3) 代码
//mode为0010时为时钟功能
//时钟功能
module clock(clk,reset,mode,sync_clock,lcd_data_disp);
input clk;
input reset;
input [2:0]mode;
input [255:0]sync_clock;
output [255:0]lcd_data_disp;
//---------------------------------------------------------------
//分频模块
reg clk_1Hz;
reg clk_100Hz;
integer i,j;
always @(posedge clk)
begin
i <= i + 1'b1;
if(i==32'd249999)
begin i <= 1'b0; clk_100Hz <= ~clk_100Hz;end
j <= j + 1'b1;
if(j==32'd24999999)
begin j <= 1'b0; clk_1Hz <= ~clk_1Hz; end
end
//---------------------------------------------------------------
//闪烁模块 1秒闪一次
always @(posedge clk)
if(mode==3'b001)
begin
clock_disp[23:16] <= clk_1Hz ? " " : ":";
clock_disp[47:40] <= clk_1Hz ? " " : ":";
clock_disp[71:64] <= clk_1Hz ? " " : ":";
end
//---------------------------------------------------------------
//百分秒模块
reg [255:0]clock_disp;
always @(posedge clk_100Hz or negedge reset)
if(!reset)
clock_disp[15:0] <= 1'b0;
else
begin
clock_disp[7:0] <= clock_disp[7:0] + 1'b1; //百分秒的个位
if(clock_disp[7:0]==4'h9)
begin
clock_disp[7:0] <= 1'b0;
clock_disp[15:8] <= clock_disp[15:8] + 1'b1; //百分秒的十位
if(clock_disp[15:8]==4'h9)
clock_disp[15:8] <= 1'b0;
end
end
//---------------------------------------------------------------
//计时模块
always @(posedge clk_1Hz or negedge reset)
if(!reset) //重置清零
begin
clock_disp[39:24] <= 1'b0;
clock_disp[63:48] <= 1'b0;
clock_disp[87:72] <= 1'b0;
end
else
begin
clock_disp[255:88] = "MODE H M S MSCLOCK";
//秒计时模块
clock_disp[31:24] <= clock_disp[31:24] + 1'b1; //秒的个位
if(clock_disp[31:24]==8'h9)
begin
clock_disp[31:24] <= 1'b0;
clock_disp[39:32] <= clock_disp[39:32] + 1'b1; //秒的十位
//---------------------------------------------------------------
//分计时模块
if(clock_disp[39:32]==8'h5)
begin
clock_disp[39:32] <= 1'b0;
clock_disp[55:48] <= clock_disp[55:48] + 1'b1; //分钟的个位
if(clock_disp[55:48]==8'h9)
begin
clock_disp[55:48] <= 1'b0;
clock_disp[63:56] <= clock_disp[63:56] + 1'b1; //分钟的十位
//---------------------------------------------------------------
//小时计时模块
if(clock_disp[63:56]==8'h5)
begin
clock_disp[63:56] <= 1'b0;
clock_disp[79:72] <= clock_disp[79:72] + 1'b1; //时钟的个位
if(clock_disp[79:72]==8'h9)
begin
clock_disp[79:72] <= 1'b0;
clock_disp[87:80] <= clock_disp[87:80] + 1'b1; //时钟的十位
end
end
//---------------------------------------------------------------
end
end
end
//---------------------------------------------------------------
//如果记到23:59:59 时清零 00:00:00
else if(clock_disp[87:72]==16'h0204) //0000 0010 0000 0100
clock_disp[87:72] <= 1'b0;
//----------------------------------------------------------
//后台同步校准模式的时钟和分钟
else if(mode==3'b100)
begin
clock_disp[87:72] = sync_clock[87:72];
clock_disp[63:48] = sync_clock[63:48];
end
end
//----------------------------------------------------------
assign lcd_data_disp = clock_disp ;
endmodule
7.闹钟模块
1)说明:这个是数字钟的闹钟模块,其中key是用来调闹钟的时钟和分钟;
2)端口
输入:clk,reset,mode,key;
输出:lcd_data_disp;
3)代码
//mode=3'b010时为闹钟功能
//校时可以长按按键,可以快速加数字
//闹钟功能
module alarm(clk,reset,mode,key,lcd_data_disp);
input clk;
input reset;
input [2:0]key;
input [2:0]mode;
output [255:0]lcd_data_disp;
//---------------------------------------------------------------
//校时分频200ms的频率用来更新时间
integer i;
reg clk_alarm;
always @(posedge clk)
begin
i <= i + 1'b1;
if(i==32'd4999999)
begin i <= 1'b0; clk_alarm <= ~clk_alarm; end
end
//---------------------------------------------------------------
reg [255:0]alarm_disp;
always @(posedge clk_alarm or negedge reset)
if(!reset) //复位时清零
alarm_disp[87:72] <= 1'b0;
else if(mode==3'b010)
begin
//设置一些常量,没使用到的要把初始值赋为0,可以减少警告
alarm_disp[255:88] <= "MODE H M S MSALARM";
alarm_disp[71:64] <= ":";
alarm_disp[47:40] <= ":";
alarm_disp[39:24] <= 1'b0;
alarm_disp[23:16] <= ":";
alarm_disp[15:0] <= 1'b0;
//---------------------------------------------------------------
//闹钟设置时钟
if(!key[1])
begin
alarm_disp[79:72] <= alarm_disp[79:72] + 1'b1; //时钟的个位
if(alarm_disp[79:72]==4'h9)
begin
alarm_disp[79:72] <= 1'b0;
alarm_disp[87:80] <= alarm_disp[87:80] + 1'b1; //时钟的十位
if(alarm_disp[87:80]==4'h5)
alarm_disp[87:80] <= 1'b0;
end
else if(alarm_disp[87:72]==16'h0203) //时钟计算到23的时候回00
alarm_disp[87:72] <= 1'b0;
end
//---------------------------------------------------------------
//闹钟设置分钟
if(!key[2])
begin
alarm_disp[55:48] <= alarm_disp[55:48] + 1'b1; //时钟的个位
if(alarm_disp[55:48]==4'h9)
begin
alarm_disp[55:48] <= 1'b0;
alarm_disp[63:56] <= alarm_disp[63:56] + 1'b1; //时钟的十位
if(alarm_disp[63:56]==4'h5)
alarm_disp[63:56] <= 1'b0;
end
end
end
//---------------------------------------------------------------
assign lcd_data_disp = alarm_disp ;
endmodule
8. 校时模块
1)说明:这个是数字钟的校时模快,其中key是用来调整校时模式下的时钟和分钟,sync_adj是用来后台同步时钟模式下的时钟数据
2)端口
输入:clk,reset,mode,key,sync_adj;
输出:lcd_data_disp;
3)代码
//mode=3'b100时为校时功能
//校时可以长按按键,可以快速加数字
//校时功能
module adj(reset,clk,mode,key,sync_adj,lcd_data_disp);
input clk;
input reset;
input [2:0]key;
input [2:0]mode;
input [255:0]sync_adj;
output [255:0]lcd_data_disp;
//---------------------------------------------------------------
//校时分频100ms的频率用来更新时间,
integer i,j;
reg clk_adj,clk_1Hz;
always @(posedge clk)
begin
i <= i + 1'b1;
if(i==32'd4999999)
begin i <= 1'b0; clk_adj <= ~clk_adj; end
j <= j + 1'b1;
if(j==32'd24999999)
begin j <= 1'b0; clk_1Hz <= ~clk_1Hz; end
end
//---------------------------------------------------------------
reg [255:0]adj_disp;
always @(posedge clk_adj or negedge reset)
if(!reset) //复位时清零
adj_disp[87:72] <= 1'b0;
else if(mode==3'b100)
begin
//设置一些常量,没使用到的要把初始值赋为0,可以减少警告
adj_disp[255:88] <= "MODE H M S MSADJ ";
adj_disp[71:64] <= ":";
adj_disp[47:40] <= ":";
adj_disp[23:16] <= ":";
adj_disp[15:0] <= 1'b0;
//---------------------------------------------------------------
//校时状态下调节时钟
if(!key[1])
begin
adj_disp[79:72] <= adj_disp[79:72] + 1'b1; //时钟的个位
if(adj_disp[79:72]==4'h9)
begin
adj_disp[79:72] <= 1'b0;
adj_disp[87:80] <= adj_disp[87:80] + 1'b1; //时钟的十位
if(adj_disp[87:80]==4'h5)
adj_disp[87:80] <= 1'b0;
end
else if(adj_disp[87:72]==16'h0203) //时钟计算到23的时候回00
adj_disp[87:72] <= 1'b0;
end
//---------------------------------------------------------------
//校时状态下调节分钟
if(!key[2])
begin
adj_disp[55:48] <= adj_disp[55:48] + 1'b1; //时钟的个位
if(adj_disp[55:48]==4'h9)
begin
adj_disp[55:48] <= 1'b0;
adj_disp[63:56] <= adj_disp[63:56] + 1'b1; //时钟的十位
if(adj_disp[63:56]==4'h5)
adj_disp[63:56] <= 1'b0;
end
end
end
//---------------------------------------------------------------
else if(mode==3'b001) //后台同步时钟
begin
adj_disp[87:72] = sync_adj[87:72];
adj_disp[63:48] = sync_adj[63:48];
end
//---------------------------------------------------------------
//校准模式下分钟显示
always @(posedge clk_1Hz or negedge reset)
if(!reset)
adj_disp[39:24] <= 1'b0;
else
begin
adj_disp[31:24] <= adj_disp[31:24] + 1'b1; //分钟的个位
if(adj_disp[31:24]==8'h9)
begin
adj_disp[31:24] <= 1'b0;
adj_disp[39:32] <= adj_disp[39:32] + 1'b1; //分钟的十位
if(adj_disp[39:32]==8'h5)
adj_disp[39:32] <= 1'b0;
end
end
//---------------------------------------------------------------
assign lcd_data_disp = adj_disp ;
endmodule
9.数据处理选择模块
1)说明:这个是用来处理时钟,闹钟,校时的数据,同时提供两条信号线sync_clock,sync_adj来给时钟和闹钟同步数据;其中alarm_en是闹钟铃声使能信号;
2)端口
输入:clk,mode,alarm_disp,clock_disp,adj_disp;
输出:alarm_en,sync_clock,sync_a,lcd_data_disp;
3)代码
//LCD数据选择
module disp_sel(clk,mode,alarm_disp,clock_disp,adj_disp,sync_clock,sync_adj,lcd_data_disp,alarm_en);
input clk;
input [2:0]mode;
input [255:0]clock_disp;
input [255:0]alarm_disp,adj_disp;
output reg[255:0]sync_clock;
output reg[255:0]sync_adj;
output [255:0]lcd_data_disp;
output alarm_en;
//---------------------------------------------------------------
reg[255:0] result_disp;
always @(posedge clk)
begin
if(mode==3'b001) //时钟功能时选择时钟的的数据
begin
result_disp <= clock_disp ;
sync_adj <= clock_disp;
end
else if(mode==3'b010) //闹钟功能时选择闹钟的数据
begin
result_disp <= alarm_disp;
end
else if(mode==3'b100) //校时功能时选择闹钟的数据
begin
result_disp <= adj_disp;
sync_clock <= adj_disp;
end
end
//---------------------------------------------------------------
//闹钟响蜂鸣器模块
reg alarm_en = 0;
always @(posedge clk)
if(mode==3'b001)
begin
if((clock_disp[63:48]!==16'b0) || (clock_disp[87:72]!==16'b0))//还没想到好的方法处理半夜0点尖叫,只能采用折中方法
begin //这类似几年前ios系统的0点不响事件,这是一个bug//
if(clock_disp[39:24]==alarm_disp[39:24]) //响一分钟模块
begin
if((clock_disp[87:72]==alarm_disp[87:72]) & (clock_disp[63:48]==alarm_disp[63:48]))
alarm_en <= 1;
else
alarm_en <= 0;
end
end
end
else alarm_en <= 0;
//---------------------------------------------------------------
//数据处理
assign lcd_data_disp[255:88] = result_disp[255:88];
assign lcd_data_disp[87:80] = result_disp[87:80] + 8'd48;
assign lcd_data_disp[79:72] = result_disp[79:72] + 8'd48;
assign lcd_data_disp[71:64] = result_disp[71:64];
assign lcd_data_disp[63:56] = result_disp[63:56] + 8'd48;
assign lcd_data_disp[55:48] = result_disp[55:48] + 8'd48;
assign lcd_data_disp[47:40] = result_disp[47:40];
assign lcd_data_disp[39:32] = result_disp[39:32] + 8'd48;
assign lcd_data_disp[31:24] = result_disp[31:24] + 8'd48;
assign lcd_data_disp[23:16] = result_disp[23:16];
assign lcd_data_disp[15:8] = result_disp[15:8] + 8'd48;
assign lcd_data_disp[7:0] = result_disp[7:0] + 8'd48;
endmodule
10.闹铃模块
1)说明:这个是闹钟的闹铃模块;
2)端口
输入:clk,reset,alarm_en;
输出:beep;
3)代码
//蜂鸣器模块
module beep(clk,reset,alarm_en,beep);
input clk;
input reset;
input alarm_en;
output beep;
//---------------------------------------------------------------
parameter IDLE= 8'b00000001,
DO = 8'b00000010,
RE = 8'b00000100,
MI = 8'b00001000,
FA = 8'b00010000,
SO = 8'b00100000,
LA = 8'b01000000,
SI = 8'b10000000;
//---------------------------------------------------------------
reg[31:0] cnt_1Hz;
always @(posedge clk or negedge reset)
if(!reset)
cnt_1Hz <= 32'd0;
else
begin
if(cnt_1Hz >= 32'd49_999_99)
cnt_1Hz <= 32'd0;
else
cnt_1Hz <= cnt_1Hz + 32'd1;
end
//---------------------------------------------------------------
reg[7:0]beep_status;
always @(posedge clk or negedge reset)
if(!reset)
beep_status <= IDLE;
else
begin
if( cnt_1Hz == 32'd49_999_99)
beep_status[7:0] <= {beep_status[6:0],beep_status[7]};
end
//---------------------------------------------------------------
reg[31:0]cnt;
reg beep;
always @(posedge clk or negedge reset)
begin
if(!reset)
begin
cnt <= 0;
beep <= 1;
end
else if(alarm_en)
case(beep_status)
IDLE: beep <= 1;
DO : begin if(cnt>=11944) begin cnt <= 0; beep <= ~beep ;end //1
else cnt <= cnt + 1; end
RE : begin if(cnt>=10642) begin cnt <= 0; beep <= ~beep ;end //2
else cnt <= cnt + 1; end
MI : begin if(cnt>=9480) begin cnt <= 0; beep <= ~beep ;end //3
else cnt <= cnt + 1; end
FA : begin if(cnt>=8947) begin cnt <= 0; beep <= ~beep ;end //4
else cnt <= cnt + 1; end
SO : begin if(cnt>=7971) begin cnt <= 0; beep <= ~beep ;end //5
else cnt <= cnt + 1; end
LA : begin if(cnt>=7102) begin cnt <= 0; beep <= ~beep ;end //6
else cnt <= cnt + 1; end
SI : begin if(cnt>=6327) begin cnt <= 0; beep <= ~beep ;end //7
else cnt <= cnt + 1; end
endcase
else beep <= 1;
end
endmodule
11.使用平台Altera CycloneII EP2C8Q208C8
开发板A_C8V4;
12.引脚绑定