2024年寒假练-基于STEP小脚丫FPGA套件实现的数字钟
该项目使用了STEP小脚丫FPGA套件,实现了具有定时报警功能的数字钟的设计,它的主要功能为:undefined。
标签
FPGA
2024寒假在家一起练
STEP小脚丫
我就是我
更新2024-04-01
北京邮电大学
457

一、 项目描述

1. 项目需求

设计一个能够显示时、分、秒钟的数字时钟,时间在7段数码管上显示。自定义扩展板上的矩阵按键调整数字时钟的时间并设定定时报警的时间,分别用四个键来控制分、时的增、减,将设定好的定时时间存储在EEPROM中,断电并再次上电以后(需要重新再调整当前时钟的时间)能够根据上次设定的定时报警的时间进行报警,到报警时间时蜂鸣器播放音乐5秒钟、核心板上的一颗RGB LED以呼吸灯的方式闪烁5秒钟,闪烁的过程中通过R、G、B颜色的不同组合显示不同颜色(类似警灯的效果)。

2. 需求分析

根据项目需求,该项目可以分解为以下功能:

(1) 驱动数码管:

驱动扩展底板的8位数码管,在数字时钟状态时显示当前时间,在设置当前时钟时间或报警时间时显示设置时间。

(2) 驱动矩阵按键:

驱动扩展底板的4x4矩阵按键,能够获取到当前按下的按键位置。

(3) 基本计时功能:

实现时钟基本计时功能,最小计时单位为1s,能够以24小时方式记录时分秒。

(4) 当前时间更改:

时钟每次上电会重新计时,所以需要支持更改当前时间,使时钟的时间与现实时间同步。

(5) 报警时间设置:

需要支持报警时间设置功能,使用矩阵按键,设定定时报警时间。

(6) 报警时间储存:

根据项目要求,设定的报警时间需要能够储存到扩展底板的EEPROM中并且可以读取,使得时钟每次上电后可以继续根据上次设定的报警时间完成定时报警功能。

(7) 定时报警功能:

当前时间为定时报警时间时,需要触发报警功能,执行报警动作,实现定时报警功能。

(8) 驱动蜂鸣器和RGB LED:

触发定时报警时,需要驱动蜂鸣器和RGB LED灯进行报警。具体功能为蜂鸣器发出声音,RGB实现呼吸灯效果。

二、功能实现

1. 实现方式

使用基于Lattice MXO2的小脚丫FPGA核心板及STEP BaseBoard V4.0第4代小脚丫FPGA扩展底板实现本项目。根据需求分析,针对每个需求提出实现方式:

(1) 驱动数码管:

数码管的显示方式有独立显示和扫描显示两种方式,为了节约I/O口,扩展底板支持扫描显示并提供了两个74HC595D芯片驱动数码管。在项目文件中,Segment_scan模块提供数码管显示功能。

(2) 驱动矩阵按键:

矩阵按键使用行线和列线分别连接到按键开关的两端,可以通过4根行线和4根列线(共8个I/O口)连接16个按键,节约了I/O口。通过对矩阵每行的扫描,每一时刻只有一行的输出为低电平,当该行的按键按下时,按键所在的列输入为低电平,从而确定按键位置。在项目文件中,Array_KeyBoard模块提供矩阵按键的位置检测功能。

(3) 基本计时功能:

为实现计时功能,首先需要获取1HZ时钟,由核心板的12MHZ时钟分频获得,分频功能由项目中的divide模块实现。其次实现计数功能,时间计数功能在主模块main_clock中实现。

(4) 当前时间更改及报警时间设置:

这两部分逻辑相同,均为改变储存时间的寄存器的值。具体实现思路为:将主程序设置三个状态,分别为:计时、时间更改和报警定时。在时间更改和报警定时状态时,通过按键分别加减时间的时、分、秒,然后回到计时状态,具体实现位于main_clock主模块。

(5) 报警时间储存:

该部分功能需要通过驱动EEPROM实现,通过I2C总线与扩展底板的AT24C02芯片通信实现数据的读写,将记录时间的24位数据存进EEPROM并可以读取。在项目文件中,与AT24C02交互的功能在at24_driver模块中实现。

(6) 定时报警功能:

同时使用1HZ时钟触发,每次检测一下当前时间是否为设定的报警时间,当时间与设定报警时间相同时,触发报警功能。

(7) 驱动蜂鸣器和RGB LED:

通过脉宽调制技术(PWM:Pulse Width Modulation)来实现“呼吸灯”,通过调整占空比控制LED的亮度由暗到亮及由亮到暗,工程文件中的breath_led模块实现此功能。同时,蜂鸣器使用NPN三极管(S8050)驱动,三极管当开关用,当基极电压拉高时,蜂鸣器通电,当基极电压拉低时,蜂鸣器断电,FPGA控制GPIO口给三极管的基极输出不同频率的脉冲信号,蜂鸣器就可以发出不同的音节。在主模块中,使用breath_led模块的输出控制LED灯和蜂鸣器达到项目需求。

2. 功能框图

(1) 模块结构图

(2) main_clock主模块功能框图

3. 仿真波形

(1)分频模块(divide)

(2)矩阵按键驱动模块(Array_KeyBoard)

(3)数码管驱动模块(Segment_scan)

(4)呼吸灯模块(breath_led)

4. FPGA资源利用

三、代码片段说明(全部代码见附件)

1. 数码管显示控制

根据不同的状态(常规、更改时间、设定报警时间)控制数码管以不同的逻辑显示。

    //控制数码管
reg [1:0] set_cnt = 2'd0;
always@(posedge clk_12M or negedge rst)
begin
if(!rst)
begin
seg_data_en <= 8'hff;
seg_dot_en <= 8'hff;
end
//常规模式:时间常亮,时间分割符号“-”闪烁
else if(state == NORMAL && time_update == 1'b1)
begin
if(clk_1HZ) seg_data_en <= 8'hff;
else seg_data_en <= 8'hdb;
seg_data [7] <= time_cnt[3:0];
seg_data [6] <= time_cnt[7:4];
seg_data [5] <= 5'd16;
seg_data [4] <= time_cnt[11:8];
seg_data [3] <= time_cnt[15:12];
seg_data [2] <= 5'd16;
seg_data [1] <= time_cnt[19:16];
seg_data [0] <= time_cnt[23:20];
seg_dot_en <= 8'h0;
end
//设置时间模式:设置位置的数字闪烁(时、分、秒),其他位置常亮
else if(state == SET_TIME)
begin
if(clk_1HZ) seg_data_en <= 8'hff;
else
begin
case(set_cnt)
2'd0: seg_data_en <= 8'hfc;
2'd1: seg_data_en <= 8'he7;
2'd2: seg_data_en <= 8'h3f;
endcase
end
seg_data [7] <= time_set[3:0];
seg_data [6] <= time_set[7:4];
seg_data [5] <= 5'd16;
seg_data [4] <= time_set[11:8];
seg_data [3] <= time_set[15:12];
seg_data [2] <= 5'd16;
seg_data [1] <= time_set[19:16];
seg_data [0] <= time_set[23:20];
seg_dot_en <= 8'h0;
end
//设置定时报警
//报警开始时,显示效果和设置时间模式相同
else if(state == SET_ALARM && alarm_flag == 1'b1)
begin
if(clk_1HZ) seg_data_en <= 8'hff;
else
begin
case(set_cnt)
2'd0: seg_data_en <= 8'hfc;
2'd1: seg_data_en <= 8'he7;
2'd2: seg_data_en <= 8'h3f;
endcase
end
seg_data [7] <= time_alarm[3:0];
seg_data [6] <= time_alarm[7:4];
seg_data [5] <= 5'd16;
seg_data [4] <= time_alarm[11:8];
seg_data [3] <= time_alarm[15:12];
seg_data [2] <= 5'd16;
seg_data [1] <= time_alarm[19:16];
seg_data [0] <= time_alarm[23:20];
seg_dot_en <= 8'h0;
end
//报警关闭时,显示“--------”
else if(state == SET_ALARM && alarm_flag == 1'b0)
begin
seg_data_en <= 8'hff;
seg_data [7] <= 5'd16;
seg_data [6] <= 5'd16;
seg_data [5] <= 5'd16;
seg_data [4] <= 5'd16;
seg_data [3] <= 5'd16;
seg_data [2] <= 5'd16;
seg_data [1] <= 5'd16;
seg_data [0] <= 5'd16;
end
else
seg_data_en <= 8'hff;
end

2. 计时功能

实现时钟的计时功能,1HZ时钟上升沿触发,每次触发可以增加时间计数。

//计时部分
always@(posedge clk_changed or negedge rst) //时间增加
begin
if(!rst)
begin
time_cnt <= 24'd00_00_00;
time_update <= 1'b1;
end
//正常计时
else if(state == NORMAL)
begin
if(state_pre == SET_TIME && time_update == 0) //更新设置的时间
begin
time_cnt <= time_set;
time_update <= 1'b1;
end
//秒位计时增加
else if(time_cnt[3:0] != 4'd9) // 低位
time_cnt[3:0] <= time_cnt[3:0] + 1'd1;
else if(time_cnt[7:4] != 4'd5) //高位
time_cnt[7:0] <= {time_cnt[7:4] + 1'd1, 4'd0};
else
//分位计时增加
begin
time_cnt[7:0] <= 8'd0; //秒位进位
if(time_cnt[11:8] != 4'd9) //低位
time_cnt[11:8] <= time_cnt[11:8] + 1'd1;
else if(time_cnt[15:12] != 4'd5) //高位
time_cnt[15:8] <= {time_cnt[15:12] + 1'd1, 4'd0};
else
//时位计时增加
begin
time_cnt[15:8] <= 8'd0; //分位进位
if(time_cnt[23:16] == {4'd2,4'd3}) //低位
time_cnt[23:16] <= 8'd0;
else if(time_cnt[19:16] != 4'd9) //高位
time_cnt[19:16] <= time_cnt[19:16] + 1'd1;
else
time_cnt[23:16] <= {time_cnt[23:20] + 1'd1, 4'd0}; //进位
end
end
end
else if(state == SET_TIME)
time_update <= 0; //更新位设置
end

3. 时间设置

该部分由矩阵按键控制,提供时间更改、设定报警时间的功能,同时上电读取EEPROM时间和设置完成后写入EEPROM的功能也在此部分完成。

//设置时间
always@(posedge clk_12M or negedge rst)
begin
if(!rst)
begin
state <= NORMAL;
time_set <= 24'd00_00_00;
time_alarm <= 24'd00_00_00;
alarm_flag <= 1'b1;
alarm_write_flag <= 1'b0;
alarm_read_flag <= 1'b1;
control <= 1'b0;
end
else if(~key_out != 16'b0)
begin
//矩阵按键控制
key_out_pre <= key_out;
case(key_out^key_out_pre)
//时间设置
16'b0000_0000_0001_0000: //第二行第一列按键:进入设置时间模式
begin
state <= SET_TIME;
set_cnt <= 2'd0;
time_set <= time_cnt;
end
16'b0000_0000_0010_0000: //第二行第二列按键:时间减
begin
if(state == SET_TIME)
begin
case(set_cnt)
//时位
2'd0:
begin
if(time_set[23:16] == 8'd0)
time_set[23:16] <= {4'd2,4'd3};
else if(time_set[19:16] != 4'd0)
time_set[19:16] <= time_set[19:16] - 1'd1;
else
time_set[23:16] <= {time_set[23:20] - 1'd1, 4'd9};
end
//分位
2'd1:
begin
if(time_set[15:8] == 8'd0)
time_set[15:8] <= {4'd5,4'd9};
else if(time_set[11:8] != 4'd0)
time_set[11:8] <= time_set[11:8] - 1'd1;
else
time_set[15:8] <= {time_set[15:12] - 1'd1, 4'd9};
end
//秒位
2'd2:
begin
if(time_set[7:0] == 8'd0)
time_set[7:0] <= {4'd5,4'd9};
else if(time_set[3:0] != 4'd0)
time_set[3:0] <= time_set[3:0] - 1'd1;
else
time_set[7:0] <= {time_set[7:4] - 1'd1, 4'd9};
end
endcase
end
end
16'b0000_0000_0100_0000: //第二行第三列按键:时间加
begin
if(state == SET_TIME)
begin
case(set_cnt)
//时位
2'd0:
begin
if(time_set[23:16] == {4'd2,4'd3})
time_set[23:16] <= 8'd0;
else if(time_set[19:16] != 4'd9)
time_set[19:16] <= time_set[19:16] + 1'd1;
else
time_set[23:16] <= {time_set[23:20] + 1'd1, 4'd0};
end
//分位
2'd1:
begin
if(time_set[15:8] == {4'd5,4'd9})
time_set[15:8] <= 8'd0;
else if(time_set[11:8] != 4'd9)
time_set[11:8] <= time_set[11:8] + 1'd1;
else
time_set[15:8] <= {time_set[15:12] + 1'd1, 4'd0};
end
//秒位
2'd2:
begin
if(time_set[7:0] == {4'd5,4'd9})
time_set[7:0] <= 8'd0;
else if(time_set[3:0] != 4'd9)
time_set[3:0] <= time_set[3:0] + 1'd1;
else
time_set[7:0] <= {time_set[7:4] + 1'd1, 4'd0};
end
endcase
end
end
16'b0000_0000_1000_0000: //第二行第四列按键:设置确认
begin
if(state == SET_TIME)
begin
set_cnt <= set_cnt+1;
if(set_cnt == 2'd2)
state <= NORMAL;
state_pre <= state;
end
end
//设置定时报警时间
16'b0000_0001_0000_0000: //第三行第一列按键:进入定时报警模式
begin
if(state != SET_ALARM)
begin
state <= SET_ALARM;
set_cnt <= 2'd0;
time_set <= time_cnt;
end
else
alarm_flag <= ~alarm_flag; //第二次按:报警功能开关
end
16'b0000_0010_0000_0000: //第三行第二列按键:时间减
begin
if(state == SET_ALARM)
begin
case(set_cnt)
//时位
2'd0:
begin
if(time_alarm[23:16] == 8'd0)
time_alarm[23:16] <= {4'd2,4'd3};
else if(time_alarm[19:16] != 4'd0)
time_alarm[19:16] <= time_alarm[19:16] - 1'd1;
else
time_alarm[23:16] <= {time_alarm[23:20] - 1'd1, 4'd9};
end
//分位
2'd1:
begin
if(time_alarm[15:8] == 8'd0)
time_alarm[15:8] <= {4'd5,4'd9};
else if(time_alarm[11:8] != 4'd0)
time_alarm[11:8] <= time_alarm[11:8] - 1'd1;
else
time_alarm[15:8] <= {time_alarm[15:12] - 1'd1, 4'd9};
end
//秒位
2'd2:
begin
if(time_alarm[7:0] == 8'd0)
time_alarm[7:0] <= {4'd5,4'd9};
else if(time_alarm[3:0] != 4'd0)
time_alarm[3:0] <= time_alarm[3:0] - 1'd1;
else
time_alarm[7:0] <= {time_alarm[7:4] - 1'd1, 4'd9};
end
endcase
end
end
16'b0000_0100_0000_0000: //第三行第三列按键:时间加
begin
if(state == SET_ALARM)
begin
case(set_cnt)
//时位
2'd0:
begin
if(time_alarm[23:16] == {4'd2,4'd3})
time_alarm[23:16] <= 8'd0;
else if(time_alarm[19:16] != 4'd9)
time_alarm[19:16] <= time_alarm[19:16] + 1'd1;
else
time_alarm[23:16] <= {time_alarm[23:20] + 1'd1, 4'd0};
end
//分位
2'd1:
begin
if(time_alarm[15:8] == {4'd5,4'd9})
time_alarm[15:8] <= 8'd0;
else if(time_alarm[11:8] != 4'd9)
time_alarm[11:8] <= time_alarm[11:8] + 1'd1;
else
time_alarm[15:8] <= {time_alarm[15:12] + 1'd1, 4'd0};
end
//秒位
2'd2:
begin
if(time_alarm[7:0] == {4'd5,4'd9})
time_alarm[7:0] <= 8'd0;
else if(time_alarm[3:0] != 4'd9)
time_alarm[3:0] <= time_alarm[3:0] + 1'd1;
else
time_alarm[7:0] <= {time_alarm[7:4] + 1'd1, 4'd0};
end
endcase
end
end
16'b0000_1000_0000_0000: //第三行第四列按键:设置确认
begin
//设置时间写入EEPROM
if(state == SET_ALARM && alarm_flag == 1'b1)
begin
set_cnt <= set_cnt+1;
if(set_cnt == 2'd2)
state <= NORMAL;
state_pre <= state;
alarm_write_flag <= 1'b1;
end
else
begin
set_cnt <= 2'd0;
state <= NORMAL;
state_pre <= state;
end
end

endcase

end
else
begin
key_out_pre <= key_out;
//eeprom读写
//读
if(alarm_read_flag == 1'b1 && control == 1'b0)
begin
if(data_valid == 1'b1)
begin
time_alarm <= data_out;
if(time_alarm == data_out)
alarm_read_flag <= 1'b0;
end
end
//写
else if(alarm_write_flag == 1'b1)
begin
data_in <= time_alarm;
control <= 1'b1;
if(data_out == time_alarm)
begin
control <= 1'b0;
alarm_write_flag <= 1'b0;
end
end
end
end

4. 报警功能

触发报警后,控制蜂鸣器声音及RGB LED呼吸效果和变色效果的具体代码。

//报警
wire breath;
reg [3:0] cnt_alarm;
reg cnt_alarm_start = 1'b0;
breath_led RGB(//例化呼吸灯
.clk(clk_12M),
.rst(rst),
.led(breath));
//报警计时器
always@(posedge clk_1HZ or negedge rst)
begin
if(!rst)
begin
cnt_alarm <= 4'b0;
end
else if(cnt_alarm_start == 1'b1) cnt_alarm <= cnt_alarm + 1'b1;
else cnt_alarm <= 4'b0;
end
//检测当前时间是否报警
always@(posedge clk_12M or negedge rst)
begin
if(!rst)
begin
cnt_alarm_start <= 1'b0;
end
else if(cnt_alarm_start == 1'b0)
begin
if(alarm_flag == 1'b1)
begin
if(time_alarm == time_cnt && alarm_read_flag == 1'b0 && alarm_write_flag == 1'b0)
begin
cnt_alarm_start <= 1'b1;
end
end
end
else if(cnt_alarm_start == 1'b1)
begin
if(cnt_alarm >= 4'd5)
begin
cnt_alarm_start <= 1'b0;
end
end
end
//RGB 蜂鸣器控制
assign RGB1 = ~(cnt_alarm &{3{breath & cnt_alarm_start}});
//assign beep = ~(cnt_alarm &{3{breath & cnt_alarm_start & beep_sw}}); //蜂鸣器开关功能
assign beep = ~(cnt_alarm &{3{breath & cnt_alarm_start}});

5. 计时器触发时钟的更改(测试用:让时间更快)

为前期测试使用,提供的更改计时器触发时钟的功能。默认为1HZ,即时间每过1S计时器触发一次(数码管的秒位加一),为了方便测试可以增大计时器触发时钟的频率(10HZ/100HZ/1000HZ/10000HZ)对应于矩阵按键第一行的四位按键。

////////////分频选择//////////////////
wire clk_10HZ;
wire clk_100HZ;
wire clk_1000HZ;
wire clk_10000HZ;
reg clk_changed;
divide #(.N(12_000_00)) div_10HZ( //例化分频器
.clk(clk_12M),
.rst_n(rst),
.clkout(clk_10HZ)
);
divide #(.N(12_000_0)) div_100HZ( //例化分频器
.clk(clk_12M),
.rst_n(rst),
.clkout(clk_100HZ)
);
divide #(.N(12_000)) div_1000HZ( //例化分频器
.clk(clk_12M),
.rst_n(rst),
.clkout(clk_1000HZ)
);
divide #(.N(12_00)) div_10000HZ( //例化分频器
.clk(clk_12M),
.rst_n(rst),
.clkout(clk_10000HZ)
);
always@(posedge clk_12M or negedge rst)
begin
if(!rst)
clk_changed <= 0;
else
begin
case(~key_out[3:0])
4'b0001: clk_changed <= clk_10HZ;
4'b0010: clk_changed <= clk_100HZ;
4'b0100: clk_changed <= clk_1000HZ;
4'b1000: clk_changed <= clk_10000HZ;
default: clk_changed <= clk_1HZ;
endcase
end
end

四、改进方向

本项目实现了基础的数字时钟的设计,提供计时、时间设置、定时报警等功能。目前只是基础的功能,之后可以利用扩展板上面的硬件加入更多的功能。如:可以通过wifi模块联网进行时间同步,也可以调用温湿度传感器等硬件检测环境并通过wifi上传到上位机,使用TFTLCD屏幕显示更多的信息等功能。未来可以通过不断的完善使这个项目更加的完整,同时在不断的实践中学到更多的知识,积累在相关领域的经验。

附件下载
archive.zip
团队介绍
北京邮电大学
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号