需求:
4个数码管,可实现分显示和秒显示
每过1s,灯闪烁一下
KEY有三个,分别功能为,分钟调整清零,秒钟调整清零,以及时钟暂停
比较清晰的是我们有三个模块:数码管部分,计数部分,按键检测部分
顶层设计代码如下:
module digital_clock(
CLK_50M,RST_N,KEY,
LED1,SEG_DATA,SEG_EN
);
//外部引脚
input CLK_50M;//晶振信号输入
input RST_N;//复位开关
input [2:0] KEY;
output LED1;//LED 引脚
output [7:0] SEG_DATA;//数码管数据
output [3:0] SEG_EN;//数码管使能
//内部引脚
wire [2:0] key_flag;//KEY模块连接到计数模块上
wire [3:0] second_units;//秒钟个位数据
wire [3:0] second_tens;//秒钟十位数据
wire [3:0] minute_units;//分钟个位数据
wire [3:0] minute_tens;//分钟个位数据
Segled segled_init(
.CLK_50M (CLK_50M),
.RST_N (RST_N),
.input_second_units (second_units),
.input_second_tens (second_tens),
.input_minute_units (minute_units),
.input_minute_tens (minute_tens),
.SEG_DATA (SEG_DATA),
.SEG_EN (SEG_EN)
);
key key_init(
.CLK_50M (CLK_50M),
.RST_N (RST_N),
.KEY (KEY),
.output_key_flag (key_flag)
);
Counter counter_init(
.CLK_50M (CLK_50M),
.RST_N (RST_N),
.key_flag (key_flag),
.second_units (second_units),
.second_tens (second_tens),
.minute_units (minute_units),
.minute_tens (minute_tens),
.LED1 (LED1)
);
endmodule
然后对KEY模块首先进行设计,完成的功能就是对输入按键KEY检测进行消抖,然后通过output_key_flag进行状态输出
//带消抖的按键检测
//要用到的有定时器,按键按下存储状态的寄存器,消抖后的寄存器
module key(
CLK_50M,RST_N,KEY,
output_key_flag
);
input CLK_50M;
input RST_N;
input [2:0] KEY;//输入的按键检测
output [2:0] output_key_flag;//输出给计数模块的指令值
reg [26:0] key_time_cnt;//最基本的计时器
reg [26:0] key_time_cnt_n;
reg [2:0] key_reg;//按键状态保留寄存器
reg [2:0] key_reg_n;
parameter SET_TIME_20MS=27'd1_000_000;//50_000_000是1s,差50倍
//定时器部分,定时时间为20ms
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
key_time_cnt<=27'b0;
else
key_time_cnt<=key_time_cnt_n;
end
always@(*)
begin
if(key_time_cnt==SET_TIME_20MS)
key_time_cnt_n=27'b0;
else
key_time_cnt_n=key_time_cnt+27'b1;
end
//按键检测
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
key_reg <= 3'b0;
else
key_reg <= key_reg_n;
end
//每20ms扫描一次,把按键的值赋给key的寄存器
always@(*)
begin
if(key_time_cnt==SET_TIME_20MS)
key_reg_n = KEY;
else
key_reg_n=key_reg;
end
//这个做法可以保证是边沿检测,就是按键松开的瞬间再输出数据
//111,下一刻变成110,然后111&(001)=001就是检测到了
assign output_key_flag = key_reg & (~key_reg_n);
endmodule
其次是计数模块,它的功能有三个
1.实现1S的LED闪烁
2.读取output_key_flag的数据,分别实现:a.时间暂停,b.秒加一调整 c.分加一调整
3.通过计时获得秒,十秒,分,十分的数据,然后数据传出到数码管模块
module Counter(
CLK_50M,RST_N,key_flag,
second_units,second_tens,minute_units,minute_tens,LED1
);
input CLK_50M;
input RST_N;
input [2:0] key_flag;//key1-3功能分别为暂停,分加1,秒加一
output [3:0] second_units;//秒钟个位
output [3:0] second_tens;//秒钟十位
output [3:0] minute_units;//分钟个位
output [3:0] minute_tens;//分钟十位
output LED1;//led输出
//基本计时器
reg [26:0] time_cnt;
reg [26:0] time_cnt_n;
//秒计数
reg [3:0] second_time_cnt;
reg [3:0] second_time_cnt_n;
//十秒位计数
reg [3:0] ten_second_time_cnt;
reg [3:0] ten_second_time_cnt_n;
//分钟计数
reg [3:0] minute_time_cnt;
reg [3:0] minute_time_cnt_n;
//十分钟计数
reg [3:0] ten_minute_time_cnt;
reg [3:0] ten_minute_time_cnt_n;
//led
reg led_reg;
reg led_reg_n;
//停止位(第一个KEY)
reg stop_reg;
reg stop_reg_n;
parameter SET_TIME_1S=27'd50_000_000;
//定时器部分,定时时间为1s
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
time_cnt<=27'b0;
else
time_cnt<=time_cnt_n;
end
// else if(key_flag[0]==1'b1)不可以这么写,因为按键边缘检测,只会是一瞬间的按起来的时候改变,如同一个很小的脉冲,因此我们需要的是检测这个脉冲并保留
always@(*)
begin
if(time_cnt==SET_TIME_1S)
time_cnt_n=27'b0;
else if(stop_reg==1'b1)//第一个按键用来暂停
time_cnt_n=time_cnt;
else
time_cnt_n=time_cnt+27'b1;
end
//按键使得时间停止的部分
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
stop_reg<=1'b0;
else
stop_reg<=stop_reg_n;
end
always@(*)
begin
if(key_flag[0]==1'b1)
stop_reg_n=~stop_reg;
else
stop_reg_n=stop_reg;
end
//1s计数部分
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
second_time_cnt<=4'b0;
else
second_time_cnt<=second_time_cnt_n;
end
always@(*)
begin
if(time_cnt==SET_TIME_1S|key_flag[1]==1'b1)//每当计数到1s后,计数器加1,或者第二个按键秒数加一
second_time_cnt_n=second_time_cnt+4'b1;
else if(second_time_cnt==4'd10)//当计数到10,计数器归零
second_time_cnt_n=4'd0;
else
second_time_cnt_n=second_time_cnt;//不然保持不变即可
end
//10s计数部分
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
ten_second_time_cnt<=4'b0;
else
ten_second_time_cnt<=ten_second_time_cnt_n;
end
always@(*)
begin
if(second_time_cnt==4'd10)//每当秒计数到10后,十位计数器加1
ten_second_time_cnt_n=ten_second_time_cnt+4'b1;
else if(ten_second_time_cnt==4'd6)//当计数到6,计数器归零
ten_second_time_cnt_n=4'd0;
else
ten_second_time_cnt_n=ten_second_time_cnt;//不然保持不变即可
end
//1minute计数部分
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
minute_time_cnt<=4'b0;
else
minute_time_cnt<=minute_time_cnt_n;
end
always@(*)
begin
if(ten_second_time_cnt==4'd6|key_flag[2]==1'b1)//每当十秒计数到6后,分钟计数器加1,或者第三个按键按下
minute_time_cnt_n=minute_time_cnt+4'b1;
else if(minute_time_cnt==4'd10)//当计数到10,计数器归零
minute_time_cnt_n=4'd0;
else
minute_time_cnt_n=minute_time_cnt;//不然保持不变即可
end
//10minute计数部分
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
ten_minute_time_cnt<=4'b0;
else
ten_minute_time_cnt<=ten_minute_time_cnt_n;
end
always@(*)
begin
if(minute_time_cnt==4'd10)//每当分钟计数到10后,十位计数器加1
ten_minute_time_cnt_n=ten_minute_time_cnt+4'b1;
else if(ten_minute_time_cnt==4'd6)//当计数到6,计数器归零
ten_minute_time_cnt_n=4'd0;
else
ten_minute_time_cnt_n=ten_minute_time_cnt;//不然保持不变即可
end
//led 部分
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
led_reg<=1'b1;
else
led_reg<=led_reg_n;
end
always@(*)
begin
if(time_cnt==SET_TIME_1S)
led_reg_n=~led_reg;
else
led_reg_n=led_reg;
end
assign second_units = second_time_cnt;
assign second_tens = ten_second_time_cnt;
assign minute_units = minute_time_cnt;
assign minute_tens = ten_minute_time_cnt;
assign LED1 = led_reg;
endmodule
最后就是单纯的数码管动态显示部分
/*
动态数码管原理就是,分别对每个管子使能,一个使能的时候其他不使能,只要切换的够快,人眼看起来就是同时的
这个原因是因为数据位的7位是共用的
*/
module Segled(
CLK_50M,RST_N,input_second_units,input_second_tens,input_minute_units,input_minute_tens,
SEG_DATA,SEG_EN
);
input CLK_50M;
input RST_N;
input [3:0] input_second_units;
input [3:0] input_second_tens;
input [3:0] input_minute_units;
input [3:0] input_minute_tens;
output reg [ 3:0] SEG_EN; //数码管使能端口
output reg [ 7:0] SEG_DATA; //数码管数据端口(查看管脚分配文档或者原理图)
reg [26:0] segled_time_cnt;//计时器
reg [26:0] segled_time_cnt_n;
reg [1:0] tenms_cnt;//计数器,每10ms加一
reg [1:0] tenms_cnt_n;
parameter SET_TIME_10MS=27'd50_000;
parameter SEG_EN1=4'b1110;
parameter SEG_EN2=4'b1101;
parameter SEG_EN3=4'b1011;
parameter SEG_EN4=4'b0111;
parameter SEG_DISEN=4'b1111;
/*读取txt文件中的对应于数码管0-9的16进制值
bf
86
db
cf
e6
ed
fd
87
ff
ef
*/
reg [7:0] SEG_NUM [9:0];
initial
begin
$readmemh("SEG_NUM.txt",SEG_NUM);
end
/*
parameter SEG_NUM0 = 8'hbf, //数字0
SEG_NUM1 = 8'h86, //数字1
SEG_NUM2 = 8'hdb, //数字2
SEG_NUM3 = 8'hcf, //数字3
SEG_NUM4 = 8'he6, //数字4
SEG_NUM5 = 8'hed, //数字5
SEG_NUM6 = 8'hfd, //数字6
SEG_NUM7 = 8'h87, //数字7
SEG_NUM8 = 8'hff, //数字8
SEG_NUM9 = 8'hef, //数字9
SEG_NUMA = 8'hf7, //数字A
SEG_NUMB = 8'hfc, //数字B
SEG_NUMC = 8'hb9, //数字C
SEG_NUMD = 8'hde, //数字D
SEG_NUME = 8'hf9, //数字E
SEG_NUMF = 8'hf1; //数字F
*/
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
segled_time_cnt<=27'b0;
else
segled_time_cnt<=segled_time_cnt_n;
end
always@(*)
begin
if(segled_time_cnt==SET_TIME_10MS)
segled_time_cnt_n=27'b0;
else
segled_time_cnt_n=segled_time_cnt+27'b1;
end
//每10ms后,进行计数
always@(posedge CLK_50M,negedge RST_N)
begin
if(!RST_N)
tenms_cnt<=2'b0;
else
tenms_cnt<=tenms_cnt_n;
end
always@(*)
begin
if(segled_time_cnt==SET_TIME_10MS)
tenms_cnt_n=tenms_cnt+2'b1;
else
tenms_cnt_n=tenms_cnt;
end
//每10ms切换一个数码管使能
always@(*)
begin
case(tenms_cnt)
2'b00 : SEG_EN=SEG_EN4;
2'b01 : SEG_EN=SEG_EN3;
2'b10 : SEG_EN=SEG_EN2;
2'b11 : SEG_EN=SEG_EN1;
endcase
end
//每10ms更新一下输出数据
always@(*)
begin
case(tenms_cnt)
2'b00 : SEG_DATA=SEG_NUM[input_second_units];//将计数模块的数据赋值给数码管
2'b01 : SEG_DATA=SEG_NUM[input_second_tens];
2'b10 : SEG_DATA=SEG_NUM[input_minute_units];
2'b11 : SEG_DATA=SEG_NUM[input_minute_tens];
endcase
end
endmodule