FPGA学习之 状态机实现数字时钟
开发板型号:EP4CE6F17C8
六位数码管原理图:
由图可知,数码管段选和片选均为低电平有效。
由于人眼的视觉残留,我们控制一定频率对每一位数码管进行刷新,就能实现显示6位数字时钟。
项目功能描述:
1.实现数字时钟自动计时,设置6个计数器,分别计数时分秒的十位和个位
2.时间设置功能
3.闹钟设置
4.按键实现模式切换(主要是 初始计时、时间设置、闹钟设置)
5.蜂鸣器发声(设置的闹钟时间到来时,蜂鸣器按一定频率播放”两只老虎“)
代码:
顶层模块:
module top(
input clk ,
input rst_n ,
input [2:0] key_in ,
output [5:0] sel ,
output [7:0] dig ,
output beep
);
wire [23:0] time_data;//显示数据
wire [2:0] key_out;//按键消抖输出
wire alarm_clk_flag;//闹钟响标志
wire [5:0] shan_shuo;
//数字时钟显示驱动
seg_driver u_seg_driver(
.clk (clk ),
.rst_n (rst_n ),
.din (time_data ),
.sel (sel ),
.dig (dig ),
.shan_shuo(shan_shuo)
);
//按键消抖
fsm_key_debounce # (.KEY_W(3)) u_fsm_key_debounce(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key_in ),
.key_out (key_out )
);
//控制模块
control u_control(
.clk (clk ),
.rst_n (rst_n ),
.key (key_out ),
.time_data (time_data ),
.alarm_clk_flag (alarm_clk_flag ),
.shan_shuo (shan_shuo)
);
//蜂鸣器
beep u_beep(
.clk (clk ),
.rst_n (rst_n ),
.alarm_clk_flag (alarm_clk_flag ),
.beep (beep )
);
endmodule
按键消抖模块
module fsm_key_debounce # (parameter KEY_W = 3,TIME_20MS = 1_000_000)(
input clk ,
input rst_n ,
input [KEY_W - 1:0] key_in ,
output [KEY_W - 1:0] key_out
);
//参数定义
localparam IDLE = 4'b0001;//初始状态
localparam DOWN = 4'b0010;//按键按下抖动
localparam HOLD = 4'b0100;//按键按下后稳定
localparam UP = 4'b1000;//按键上升抖动
//信号定义
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
//状态转移条件定义
wire idle2down;
wire down2idle;
wire down2hold;
wire hold2up ;
wire up2idle ;
reg [KEY_W - 1:0] key_r0;//同步
reg [KEY_W - 1:0] key_r1;//打拍
wire [KEY_W - 1:0] nedge;//下降沿
wire [KEY_W - 1:0] pedge;//上升沿
//20ms计数器
reg [19:0] cnt_20ms;
wire add_cnt_20ms;
wire end_cnt_20ms;
reg [KEY_W - 1:0] key_out_r;//输出寄存
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
always@(*)begin
case(state_c)
IDLE:begin
if(idle2down)begin
state_n = DOWN;
end
else begin
state_n = state_c;
end
end
DOWN:begin
if(down2idle)begin
state_n = IDLE;
end
else if(down2hold)begin
state_n = HOLD;
end
else begin
state_n = state_c;
end
end
HOLD:begin
if(hold2up)begin
state_n = UP;
end
else begin
state_n = state_c;
end
end
UP:begin
if(up2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:state_n = state_c;
endcase
end
assign idle2down = (state_c == IDLE) && nedge;//检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge&& end_cnt_20ms);//计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms);//计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up = (state_c == HOLD) && (pedge);//检测到上升沿跳转到上升态
assign up2idle = (state_c == UP) && end_cnt_20ms;//计数器计数到20ms跳转到初始态
//20ms计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP;//当按键按下或上弹时开始计数
assign end_cnt_20ms = add_cnt_20ms && ((cnt_20ms == TIME_20MS - 1) || pedge);//当计数到最大值或检测到上升沿计数器清零
//同步打拍
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {KEY_W{1'b1}};
key_r1 <= {KEY_W{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
end
end
assign nedge = ~key_r0 & key_r1;//检测下降沿
assign pedge = key_r0 & ~key_r1;//检测上升沿
//按键赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out_r <= {KEY_W{1'b0}};
end
else if(state_c == HOLD && hold2up)begin
key_out_r <= ~key_r1;
end
else begin
key_out_r <= {KEY_W{1'b0}};
end
end
assign key_out = key_out_r;
endmodule
蜂鸣器模块
module beep(
input clk ,
input rst_n ,
input alarm_clk_flag ,
output beep
);
parameter M1 = 17'd95600;
parameter M2 = 17'd85150;
parameter M3 = 17'd75850;
parameter M4 = 17'd71600;
parameter M5 = 17'd63750;
parameter M6 = 17'd56800;
parameter M7 = 17'd50600;
reg beep_r;
reg alarm_clk_flag_r;
reg [16:0] cnt0;//音符周期计数器
wire add_cnt0;
wire end_cnt0;
reg [8:0] cnt1;//音符重复次数计数器
wire add_cnt1;
wire end_cnt1;
reg [4:0] cnt2;//音符总次数
wire add_cnt2;
wire end_cnt2;
reg [1:0] cnt3;//音乐时间计数器 7次完结
wire add_cnt3;
wire end_cnt3;
reg [16:0] preset_note;//预设音符周期数
wire [16:0] preset_duty;//占空比
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
alarm_clk_flag_r <= 1'b0;
end
else if(alarm_clk_flag)begin
alarm_clk_flag_r <= 1'b1;
end
else if(cnt3 == 1)begin
alarm_clk_flag_r <= 1'b0;
end
end
//音符周期计数
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 17'b0;
end
else if(add_cnt0)begin