活动:2024寒假在家一起练
平台:小脚丫FPGA套件STEP BaseBoard V4.0
任务:具有定时报警功能的数字钟
软件:WebIDE
实验任务:
设计一个能够显示时、分、秒钟的数字时钟,时间在7段数码管上显示。自定义扩展板上的矩阵按键调整数字时钟的时间并设定定时报警的时间,分别用四个键来控制分、时的增、减,将设定好的定时时间存储在EEPROM中,断电并再次上电以后(需要重新再调整当前时钟的时间)能够根据上次设定的定时报警的时间进行报警,到报警时间时蜂鸣器播放音乐5秒钟、核心板上的一颗RGB LED以呼吸灯的方式闪烁5秒钟,闪烁的过程中通过R、G、B颜色的不同组合显示不同颜色(类似警灯的效果)。
设计思路:
根据实验任务,需要驱动的外设有:矩阵键盘(4X4)、数码管(74HC595)、EEPROM(AT24C02)、RGB LED、蜂鸣器,故在顶层模块中例化以上五个外设对应的驱动模块,外加一个BIN转BCD模块,模式切换、计时、当前时间设置、报警时间设置、报警时间读写、报警控制等逻辑在顶层模块中完成。
设计框图:
实验原理:
1、设计【驱动矩阵键盘(4X4)】代码,通过按键8来控制模式切换,按键4、3控制当前时间、报警时间分位加减,按键2、1控制当前时间、报警时间时位加减
//矩阵键盘定义区
parameter array_keyboard_divide_N = 60000;
wire [15:0] key_pulse;
//矩阵键盘例化区
array_keyboard #
(
.divide_N(array_keyboard_divide_N)
)
u1
(
.rst(rst),
.clk(clk),
.col(col),
.row(row),
.key_out(),
.key_pulse(key_pulse),
.key_number()
);
2、设计【模式切换】代码,mode等于0时为【计时和当前时间设置】,mode等于1时为【报警时间设置】
//模式切换定义区
reg mode = 1'b0; //0为计时和当前时间设置,1为报警时间设置
reg mode_r = 1'b0; //上一拍的模式
//模式切换工作区
always @(posedge clk or negedge rst) begin
if(!rst) begin
mode <= 1'b0;
end
else if(key_pulse[7] == 1'b1) begin
mode <= ~mode;
end
end
always @(posedge clk or negedge rst) begin
if(!rst) begin
mode_r <= 1'b0;
end
else begin
mode_r <= mode;
end
end
3、设计【计时和当前时间设置】代码,完成时分秒的正确计时和通过按键设置当前时间
//计时和当前时间设置定义区
reg [23:0] cnt_clk = 24'd0; //数时钟周期
reg [7:0] cnt_s = 8'd0; //当前时间秒
reg flag_min= 1'b0; //进位标志
reg [7:0] cnt_min = 8'd0; //当前时间分
reg flag_h = 1'b0; //进位标志
reg [7:0] cnt_h = 8'd0; //当前时间时
//计时和当前时间设置工作区
//计数器工作区
always @(posedge clk or negedge rst) begin
if(!rst)
cnt_clk <= 24'd0;
else if(cnt_clk == 24'd11_999_999)
cnt_clk <= 24'd0;
else
cnt_clk <= cnt_clk + 1'b1;
end
//秒计时工作区
always @(posedge clk or negedge rst) begin
if(!rst) begin
cnt_s <= 8'd00;
flag_min <= 1'b0;
end
else if(cnt_clk == 24'd11_999_999) begin
if(cnt_s == 8'd59) begin
cnt_s <= 8'd0;
flag_min <= 1'b1;
end
else begin
cnt_s <= cnt_s +1'b1;
end
end
if(flag_min == 1'b1) begin
flag_min <= 1'b0;
end
end
//分计时工作区
always @(posedge clk or negedge rst) begin
if(!rst) begin
cnt_min <= 8'd00;
flag_h <= 1'b0;
end
else if(mode == 1'b0 && key_pulse[3] == 1'b1) begin //按下矩阵键盘按键4,当前时间分加1
if(cnt_min == 8'd59)
cnt_min <= 8'd0;
else
cnt_min <= cnt_min + 1'b1;
end
else if(mode == 1'b0 && key_pulse[2] == 1'b1) begin //按下矩阵键盘按键3,当前时间分减1
if(cnt_min == 8'd0)
cnt_min <= 8'd59;
else
cnt_min <= cnt_min - 1'b1;
end
else if(flag_min == 1'b1) begin
if(cnt_min == 8'd59) begin
cnt_min <= 8'd0;
flag_h <= 1'b1;
end
else begin
cnt_min <= cnt_min +1'b1;
end
end
if(flag_h == 1'b1) begin
flag_h <= 1'b0;
end
end
//时计时工作区
always @(posedge clk or negedge rst) begin
if(!rst) begin
cnt_h <= 8'd12;
end
else if(mode == 1'b0 && key_pulse[1] == 1'b1) begin //按下矩阵键盘按键2,当前时间时加1
if(cnt_h == 8'd23)
cnt_h <= 8'd0;
else
cnt_h <= cnt_h + 1'b1;
end
else if(mode == 1'b0 && key_pulse[0] == 1'b1) begin //按下矩阵键盘按键1,当前时间时减1
if(cnt_h == 8'd0)
cnt_h <= 8'd23;
else
cnt_h <= cnt_h - 1'b1;
end
else if(flag_h == 1'b1) begin
if(cnt_h == 8'd23) begin
cnt_h <= 8'd0;
end
else begin
cnt_h <= cnt_h +1'b1;
end
end
end
4、设计【报警时间设置】代码,使系统重启后自动读取EEPROM中存储的报警时间和通过按键设置报警时间
//报警时间设置定义区
reg [7:0] flag_time_min = 8'd0; //报警时间分
reg [7:0] flag_time_h = 8'd0; //报警时间时
reg first_time = 1'b1; //系统重启标志
//报警时间设置工作区
always @(posedge clk or negedge rst) begin
if(!rst) begin
flag_time_min <= 8'd0;
flag_time_h <= 8'd0;
first_time <= 1'b1;
end
else if(cnt_clk == 24'd1_199_999 && first_time == 1'b1) begin//系统重启后读取EEPROM中存储的报警时间
flag_time_min <= data_out[7:0];
flag_time_h <= data_out[15:8];
first_time <= 1'b0;
end
else if(mode == 1'b1) begin
if(key_pulse[3] == 1'b1) begin //按下矩阵键盘按键4,报警时间分加1
if(flag_time_min == 8'd59)
flag_time_min <= 8'd0;
else
flag_time_min <= flag_time_min + 1'b1;
end
else if(key_pulse[2] == 1'b1) begin //按下矩阵键盘按键3,报警时间分减1
if(flag_time_min == 8'd0)
flag_time_min <= 8'd59;
else
flag_time_min <= flag_time_min - 1'b1;
end
else if(key_pulse[1] == 1'b1) begin //按下矩阵键盘按键2,报警时间时加1
if(flag_time_h == 8'd23)
flag_time_h <= 8'd0;
else
flag_time_h <= flag_time_h + 1'b1;
end
else if(key_pulse[0] == 1'b1) begin //按下矩阵键盘按键1,报警时间时减1
if(flag_time_h == 8'd0)
flag_time_h <= 8'd23;
else
flag_time_h <= flag_time_h - 1'b1;
end
end
end
5、设计【报警时间读写控制】代码,以此来控制EEPROM驱动模块的读写模式和传入EEPROM的数据
//报警时间读写控制定义区
reg rd_or_wr = 1'b0; //EEPROM读写模式
wire [15:0] data_in; //传入EEPROM的数据
//报警时间读写控制工作区
always @(posedge clk or negedge rst) begin
if(!rst) begin
rd_or_wr <= 1'b0;
end
else if(mode_r == 1'b0 && mode == 1'b1) begin
rd_or_wr <= 1'b0;
end
else if(mode_r == 1'b1 && mode == 1'b0) begin
rd_or_wr <= 1'b1;
end
end
assign data_in = {flag_time_h,flag_time_min}; //传入EEPROM数据,高8位为报警时间时,低8位为报警时间分
6、【驱动EEPROM(AT24C02)】,用EEPROM存储用户设定的报警时间
//EEPROM_AT24C02定义区
wire [15:0] data_out; //EEPROM传出的数据
//EEPROM_AT24C02例化区
at24_driver u2
(
.clk(clk),
.rst(rst),
.rd_or_wr(rd_or_wr),
.data_in(data_in),
.data_out(data_out),
.data_valid(),
.iic_scl(iic_scl),
.iic_sda(iic_sda)
);
7、设计【BIN转BDC】代码,将当前时间-时-分-秒、报警时间-时-分转为BCD码,方便将其显示在数码管上
//BIN转BCD定义区
wire [11:0] seg_s; //当前时间秒-BCD
wire [11:0] seg_min; //当前时间分-BCD
wire [11:0] seg_h; //当前时间时-BCD
wire [11:0] seg_flag_time_min; //报警时间分-BCD
wire [11:0] seg_flag_time_h; //报警时间时-BCD
//BIN转BCD工作区
bin2bcd u3
(
.bitcode(cnt_s), //需要进行BCD转码的二进制数据
.bcdcode(seg_s) //转码后的BCD码型数据输出
);
bin2bcd u4
(
.bitcode(cnt_min), //需要进行BCD转码的二进制数据
.bcdcode(seg_min) //转码后的BCD码型数据输出
);
bin2bcd u5
(
.bitcode(cnt_h), //需要进行BCD转码的二进制数据
.bcdcode(seg_h) //转码后的BCD码型数据输出
);
bin2bcd u6
(
.bitcode(flag_time_min), //需要进行BCD转码的二进制数据
.bcdcode(seg_flag_time_min) //转码后的BCD码型数据输出
);
bin2bcd u7
(
.bitcode(flag_time_h), //需要进行BCD转码的二进制数据
.bcdcode(seg_flag_time_h) //转码后的BCD码型数据输出
);
8、设计【驱动数码管(74HC595)】代码,显示当前时间和报警时间
//数码管_74HC595例化区
segment_scan u8
(
.clk(clk),
.rst(rst),
.dat_1(seg_h[7:4]),
.dat_2(seg_h[3:0]),
.dat_3(mode ? seg_flag_time_h[7:4] : 4'd10),
.dat_4(mode ? seg_flag_time_h[3:0] : seg_min[7:4]),
.dat_5(mode ? seg_flag_time_min[7:4] : seg_min[3:0]),
.dat_6(mode ? seg_flag_time_min[3:0] : 4'd10),
.dat_7(seg_s[7:4]),
.dat_8(seg_s[3:0]),
.dat_en(mode ? 8'b0011_1100 : 8'b1111_1111), //[MSB~LSB]=[SEG1~SEG8]
.dot_en(mode ? 8'b0001_0000 : 8'b0000_0000), //[MSB~LSB]=[SEG1~SEG8]
.seg_rck(RCK),
.seg_sck(SCK),
.seg_din(SER)
);
9、设计【驱动RGB LED】代码,实现不同颜色呼吸灯效果
//breath_led定义区
wire [2:0] breath_led; //RGB呼吸灯控制
//breath_led例化区
breath_led #
(
.CNT_NUM(2450)
)
u9
(
.clk_in(clk),
.rst_n_in(rst),
.breath_led(breath_led)
);
10、设计【驱动蜂鸣器】代码,实现播放音乐效果
//beep_music例化区
beep_music u10
(
.clk(clk),
.rst_n(rst),
.beep(beep_music)
);
//beep_musicdy区
wire beep_music; //BBEP播放音乐控制
11、设计【报警控制】代码,实现到报警时间自动报警:RGB LED以呼吸灯的方式闪烁5秒钟,蜂鸣器播播放音乐5秒钟
//报警控制定义区
reg [25:0] cnt_5s = 26'd0; //报警持续时间
//报警控制工作区
always @(posedge clk or negedge rst) begin
if(!rst) begin
rgb <= 3'b111;
beep <= 1'b0;
cnt_5s <= 26'd0;
end
else if(cnt_h == flag_time_h && cnt_min == flag_time_min)begin
if(cnt_5s < 26'd59_999_999) begin
rgb <= breath_led;
beep <= beep_music;
cnt_5s <= cnt_5s + 1'b1;
end
else begin
rgb <= 3'b111;
beep <= 1'b0;
end
end
else begin
rgb <= 3'b111;
beep <= 1'b0;
cnt_5s <= 26'd0;
end
end
12、顶层模块输入输出端口
module alarm_clock
(
input rst,
input clk,
//矩阵键盘
input [3:0] col,
output [3:0] row,
//EEPROM_AT24C02
output iic_scl,
inout iic_sda,
//数码管_74HC595
output SCK,
output SER,
output RCK,
//RGB
output reg [2:0] rgb = 3'b111,
//BEEP
output reg beep = 1'b0
);
//逻辑代码
endmodule
仿真结果:
1、驱动矩阵键盘(4X4)模块
放大后
2、驱动EEPROM(AT24C02)模块
3、BIN转BCD模块
bitcode以10进制观察,bcdcode以16进制观察
4、驱动数码管(74HC595)模块
5、驱动RGB LED模块
6、驱动蜂鸣器模块
引脚分配:
资源报告:
知识点:
本项目难点主要在于使用IIC对EEPROM读写,故在此补充相关知识点。
1、AT24CXXX容量
AT24C01,AT24C02,AT24C04,AT24C08,AT24C16,AT24C32,AT24C64,AT24C128,AT24C256…不同的xxx代表不同的容量
2、AT24CXXX页与页内单元
总容量(Byte容量) = 页数 × 页内字节单元数
3、AT24CXXXX寻址方式(不是IIC地址,是存储器内部寻址)
对AT24CXXX进行读写操作时,都得先访问存储地址、比如AT24C01写一个字节的IIC时序:
先发送设备地址,收到应答后再发送需要写数据的地址(WORD ADDRESS)。AT24C01容量为128Byte则WORD ADDRESS只需要7bit就可以覆盖128Byte的数据地址。通俗的讲就是128Byte就占用了128个地址,一个7bit的数据范围为(0-127)刚好128,所以128Byte的字节地址需要一个7bit的数据来表示。
AT24CXXX 字节地址如下(*表示无效位)
4、AT24CXXX页地址与页内单元地址
5、AT24CXXX数据的读写
字节写入:
页面写入:
写周期时序:
当前地址读取:
随机读取:
顺序读取:
6、总结
代码及JED附件:
见下方链接