本次实验我们主要完成读取DS1302芯片内部的时间,并通过数码管进行时间的显示。
DS1302是一款实时时钟芯片,内部可以实现时分秒年月日以及星期的保存,并在电源的驱动下,可以时间自动的增加计时。一般是DIP或者SOP封装,共有以下几个引脚,
通过SPI通信协议实现信息的交流。其中
X1,X2为外接晶振的引脚,晶振为32.768KHz.
VCC1为外部的涓流电源,可以保证在系统掉电的情况下,芯片仍能够正常运行。
VCC2为正常的供电电源,可以和主控接到同一个电源内。
SCLK,I/O,RST为SPI通信的接口,实现外部通信的功能。
根据数据手册,DS1302的数据格式如下,数据最高位(LSB)必须是1,如果是0那么写数据被禁止。当BIT6是0,那么指定控制的是时钟数据(clock/calendar data),如果是1那么指定控制的是RAM。BIT1-5指定输入输出指定的寄存器。如果最低位是0,那么为数据输入,是1为数据输出。
因此按照上述时序,结合上表即可完成时间数据的读和写。
首先我们实现DS1302寄存器的读写控制,主要是实现读和写的时序,相应的模块为ds1302_io。
/*
功能:完成 DS1302 寄存器读写控制
状态“S_IDLE”空闲状态,收到读写寄存器请求写进入“S_CE_HIGH”状态,将 CE 拉高,然
后根据请求类型,进入读(S_READ)或写状态(S_WRITE)。 “S_WRITE”状态下一个状态进入写地址状态“S_WRITE_ADDR”,再进入写数据状态
“S_WRITE_DATA”,完成一个寄存器的写入,最后应答,拉低 CE。 “S_READ”状态下一个状态进入读地址状态“S_READ_ADDR”,再进入读数据状态
“S_READ_DATA”,完成一个寄存器的读取,最后应答,拉低 CE。
*/
module ds1302_io(
input clk,
input rst,
output ds1302_ce,
output ds1302_sclk,
inout ds1302_io,
input cmd_read,
input cmd_write,
output cmd_read_ack,
output cmd_write_ack,
input[7:0] read_addr,
input[7:0] write_addr,
output reg[7:0] read_data,
input[7:0] write_data
);
//状态机编号
localparam S_IDLE = 0;//空闲状态
localparam S_CE_HIGH = 1;//拉高CE的状态
localparam S_READ = 2;//读装药
localparam S_READ_ADDR = 3;//读地址状态
localparam S_READ_DATA = 4;//读数据状态
localparam S_WRITE = 5;//写状态
localparam S_WRITE_ADDR = 6;//写地址状态
localparam S_WRITE_DATA = 7;//写数据状态
localparam S_CE_LOW = 8;//CE拉低状态
localparam S_ACK = 9;//响应状态
reg[3:0] state, next_state;
reg[19:0] delay_cnt;
reg wr_req;
wire wr_ack;
reg wr_ack_d0;
reg CS_reg;
wire DCLK;
wire MOSI;
wire MISO;
reg[7:0] send_data;
wire[7:0] data_rec;
reg ds1302_io_dir;
assign ds1302_io = ~ds1302_io_dir ? MOSI : 1'bz;// 读数据的状态为1 那么输出为高阻态 否则输出需要输出的数据 也就是读取数据的时候不能输出数据
assign MISO = ds1302_io;//主机输入从机输出 读取DS1302输出的数据
//assign ds1302_ce = CS_reg;
assign ds1302_sclk = DCLK;
assign cmd_read_ack = (state == S_ACK);//读写的状态都来自于返回的相应状态
assign cmd_write_ack = (state == S_ACK);
always@(posedge clk or posedge rst)
begin
if(rst)
state <= S_IDLE;
else
state <= next_state;
end
//分配下一个状态
always@(*)
begin
case(state)
S_IDLE:
if(cmd_read || cmd_write)//如果读或写使能,将CE拉高
next_state <= S_CE_HIGH;
else
next_state <= S_IDLE;//如果没有信号 那仍然是空闲状态
S_CE_HIGH://如果CE拉高了
if(delay_cnt == 20'd255)
next_state <= cmd_read ? S_READ : S_WRITE;//如果延时达到一定时间 那么如果读使能 那么进入到S_READ状态 否者就是写状态
else
next_state <= S_CE_HIGH;//没有到时间 那么状态不变
S_READ://读状态 那么将下一个状态改成读地址
next_state <= S_READ_ADDR;
S_READ_ADDR://进入读地址状态
if(wr_ack)//如果收到应答 那么结束本状态进入下一个状态
next_state <= S_READ_DATA;//进入数据接收状态
else
next_state <= S_READ_ADDR;//否则进入到读地址状态
S_READ_DATA://读数据
if(wr_ack)//如果收到应答 那么结束本状态进入下一个状态
next_state <= S_ACK;
else
next_state <= S_READ_DATA;
S_WRITE:
next_state <= S_WRITE_ADDR;
S_WRITE_ADDR://写地址状态
if(wr_ack)//如果收到应答 那么结束本状态进入下一个状态
next_state <= S_WRITE_DATA;//写数据状态
else
next_state <= S_WRITE_ADDR;//写地址状态
S_WRITE_DATA:
if(wr_ack)//如果收到应答 那么结束本状态进入下一个状态
next_state <= S_ACK;
else
next_state <= S_WRITE_DATA;
S_ACK://应答信号 CE输出低
next_state <= S_CE_LOW;
S_CE_LOW://CE输出低
if(delay_cnt == 20'd255)//延时到达一定时间 空闲状态
next_state <= S_IDLE;
else
next_state <= S_CE_LOW;
default:next_state <= S_IDLE;
endcase
end
//延时模块 当开始 或者结束状态时候 使用
always@(posedge clk or posedge rst)
begin
if(rst)
delay_cnt <= 20'd0;
else if(state == S_CE_HIGH || state == S_CE_LOW)//改变CE信号进行的延时
delay_cnt <= delay_cnt + 20'd1;
else
delay_cnt <= 20'd0;
end
//如果是读数据 写数据的过程中 进行数据的请求
always@(posedge clk or posedge rst)
begin
if(rst)
wr_req <= 1'b0;
else if(wr_ack)
wr_req <= 1'b0;
else if(state == S_READ_ADDR || state == S_READ_DATA || state == S_WRITE_ADDR || state == S_WRITE_DATA)
wr_req <= 1'b1;
end
//给IO输出赋值 如果是读数据的状态 赋值为1
always@(posedge clk or posedge rst)
begin
if(rst)
ds1302_io_dir <= 1'b0;
else
ds1302_io_dir <= (state == S_READ_DATA);
end
//NCS控制
always@(posedge clk or posedge rst)
begin
if(rst)
CS_reg <= 1'b0;
else if(state == S_CE_HIGH)
CS_reg <= 1'b1;
else if(state == S_CE_LOW)
CS_reg <= 1'b0;
end
//将通信之后的获取数据 data_rec 保存下来 改变顺序
always@(posedge clk or posedge rst)
begin
if(rst)
read_data <= 8'h00;
else if(state == S_READ_DATA && wr_ack)
read_data <= {data_rec[0],data_rec[1],data_rec[2],data_rec[3],data_rec[4],data_rec[5],data_rec[6],data_rec[7]};
end
//需要发送的数据赋值
always@(posedge clk or posedge rst)
begin
if(rst)
send_data <= 8'h00;
else if(state == S_READ_ADDR)//发送读地址指令的状态时
send_data <= {1'b1,read_addr[1],read_addr[2],read_addr[3],read_addr[4],read_addr[5],read_addr[6],1'b1};
else if(state == S_WRITE_ADDR)//发送写数据的地址状态时
send_data <= {1'b0,write_addr[1],write_addr[2],write_addr[3],write_addr[4],write_addr[5],write_addr[6],1'b1};
else if(state == S_WRITE_DATA)//发送要写的数据时候
send_data <= {write_data[0],write_data[1],write_data[2],write_data[3],write_data[4],write_data[5],write_data[6],write_data[7]};
end
spi_master spi_master_m0(
.sys_clk(clk),
.rst(rst),
.nCS(ds1302_ce),
.DCLK(DCLK),
.MOSI(MOSI),
.MISO(MISO),
.CPOL(1'b0),
.CPHA(1'b0),
.nCS_ctrl(CS_reg),
.clk_div(16'd50),
.wr_req(wr_req),
.wr_ack(wr_ack),
.data_in(send_data),
.data_out(data_rec)
);
endmodule
之后实现DS1302时间寄存器的控制,主要实现年月日时分秒寄存器的读写,相应的模块为ds1302。
/*
实现功能:主要完成时间寄存器的读写控制, 直接读取或者输入时间数据
输入写数据请求和需写入的数据
或者读数据请求
当读数据请求时 输出读取的时间
*/
module ds1302(
input rst,
input clk,
output ds1302_ce,
output ds1302_sclk,
inout ds1302_io,
input write_time_req,
output write_time_ack,
input[7:0] write_second,
input[7:0] write_minute,
input[7:0] write_hour,
input[7:0] write_date,
input[7:0] write_month,
input[7:0] write_week,
input[7:0] write_year,
input read_time_req,
output read_time_ack,
output reg[7:0] read_second,
output reg[7:0] read_minute,
output reg[7:0] read_hour,
output reg[7:0] read_date,
output reg[7:0] read_month,
output reg[7:0] read_week,
output reg[7:0] read_year
);
localparam S_IDLE = 0;
localparam S_WR_WP = 1;
localparam S_WR_SEC = 2;
localparam S_WR_MIN = 3;
localparam S_WR_HOUR = 4;
localparam S_WR_MON = 5;
localparam S_WR_WEEK = 6;
localparam S_WR_YEAR = 7;
localparam S_RD_SEC = 8;
localparam S_RD_MIN = 9;
localparam S_RD_HOUR = 10;
localparam S_RD_MON = 11;
localparam S_RD_WEEK = 12;
localparam S_RD_YEAR = 13;
localparam S_RD_DATE = 15;
localparam S_ACK = 14;
localparam S_WR_DATE = 16;
reg[4:0] state, next_state;
reg[7:0] read_addr;
reg[7:0] write_addr;
reg[7:0] write_data;
wire[7:0] read_data;
reg cmd_write;
reg cmd_read;
wire cmd_read_ack;
wire cmd_write_ack;
assign write_time_ack = (state == S_ACK);
assign read_time_ack = (state == S_ACK);
//给cmd_write和cmd_read赋值
//通过检测目前的状态
//读时间的状态机 那么cmd_read=1
//写时间的状态机时 cmd_write=1
always@(posedge clk or posedge rst)
begin
if(rst)
cmd_write <= 1'b0;
else if(cmd_write_ack)
cmd_write <= 1'b0;
else
case(state)
S_WR_WP,
S_WR_SEC,
S_WR_MIN,
S_WR_HOUR,
S_WR_DATE,
S_WR_MON,
S_WR_WEEK,
S_WR_YEAR:
cmd_write <= 1'b1;
endcase
end
always@(posedge clk or posedge rst)
begin
if(rst)
cmd_read <= 1'b0;
else if(cmd_read_ack)
cmd_read <= 1'b0;
else
case(state)
S_RD_SEC,
S_RD_MIN,
S_RD_HOUR,
S_RD_DATE,
S_RD_MON,
S_RD_WEEK,
S_RD_YEAR:
cmd_read <= 1'b1;
endcase
end
//检测状态机 读数据的时候 将读取的数据赋值给对应的寄存器
always@(posedge clk or posedge rst)
begin
if(rst)
read_second <= 8'h00;
else if(state == S_RD_SEC && cmd_read_ack)
read_second <= read_data;
end
always@(posedge clk or posedge rst)
begin
if(rst)
read_minute <= 8'h00;
else if(state == S_RD_MIN && cmd_read_ack)
read_minute <= read_data;
end
always@(posedge clk or posedge rst)
begin
if(rst)
read_hour <= 8'h00;
else if(state == S_RD_HOUR && cmd_read_ack)
read_hour <= read_data;
end
always@(posedge clk or posedge rst)
begin
if(rst)
read_date <= 8'h00;
else if(state == S_RD_DATE && cmd_read_ack)
read_date <= read_data;
end
always@(posedge clk or posedge rst)
begin
if(rst)
read_month <= 8'h00;
else if(state == S_RD_MON && cmd_read_ack)
read_month <= read_data;
end
always@(posedge clk or posedge rst)
begin
if(rst)
read_week <= 8'h00;
else if(state == S_RD_WEEK && cmd_read_ack)
read_week <= read_data;
end
always@(posedge clk or posedge rst)
begin
if(rst)
read_year <= 8'h00;
else if(state == S_RD_YEAR && cmd_read_ack)
read_year <= read_data;
end
//按照状态机 给地址赋值
always@(posedge clk or posedge rst)
begin
if(rst)
read_addr <= 8'h00;
else
case(state)
S_RD_SEC:
read_addr <= 8'h81;
S_RD_MIN:
read_addr <= 8'h83;
S_RD_HOUR:
read_addr <= 8'h85;
S_RD_DATE:
read_addr <= 8'h87;
S_RD_MON:
read_addr <= 8'h89;
S_RD_WEEK:
read_addr <= 8'h8b;
S_RD_YEAR:
read_addr <= 8'h8d;
default:
read_addr <= read_addr;
endcase
end
//按照状态机 给写数据的地址和数据赋值
always@(posedge clk or posedge rst)
begin
if(rst)
begin
write_addr <= 8'h00;
write_data <= 8'h00;
end
else
case(state)
S_WR_WP:
begin
write_addr <= 8'h8e;
write_data <= 8'h00;
end
S_WR_SEC:
begin
write_addr <= 8'h80;
write_data <= write_second;
end
S_WR_MIN:
begin
write_addr <= 8'h82;
write_data <= write_minute;
end
S_WR_HOUR:
begin
write_addr <= 8'h84;
write_data <= write_hour;
end
S_WR_DATE:
begin
write_addr <= 8'h86;
write_data <= write_date;
end
S_WR_MON:
begin
write_addr <= 8'h88;
write_data <= write_month;
end
S_WR_WEEK:
begin
write_addr <= 8'h8a;
write_data <= write_week;
end
S_WR_YEAR:
begin
write_addr <= 8'h8c;
write_data <= write_year;
end
default:
begin
write_addr <= 8'h00;
write_data <= 8'h00;
end
endcase
end
//改变状态机
always@(posedge clk or posedge rst)
begin
if(rst)
state <= S_IDLE;
else
state <= next_state;
end
always@(*)
begin
case(state)
S_IDLE:
if(write_time_req)//写时间请求
next_state <= S_WR_WP;
else if(read_time_req)//读时间请求
next_state <= S_RD_SEC;
else
next_state <= S_IDLE;
S_WR_WP://写时间请求
if(cmd_write_ack)
next_state <= S_WR_SEC;//写秒
else
next_state <= S_WR_WP;
S_WR_SEC:
if(cmd_write_ack)
next_state <= S_WR_MIN;
else
next_state <= S_WR_SEC;
S_WR_MIN:
if(cmd_write_ack)
next_state <= S_WR_HOUR;
else
next_state <= S_WR_MIN;
S_WR_HOUR:
if(cmd_write_ack)
next_state <= S_WR_DATE;
else
next_state <= S_WR_HOUR;
S_WR_DATE:
if(cmd_write_ack)
next_state <= S_WR_MON;
else
next_state <= S_WR_DATE;
S_WR_MON:
if(cmd_write_ack)
next_state <= S_WR_WEEK;
else
next_state <= S_WR_MON;
S_WR_WEEK:
if(cmd_write_ack)
next_state <= S_WR_YEAR;
else
next_state <= S_WR_WEEK;
S_WR_YEAR:
if(cmd_write_ack)
next_state <= S_ACK;
else
next_state <= S_WR_YEAR;
S_RD_SEC:
if(cmd_read_ack)
next_state <= S_RD_MIN;
else
next_state <= S_RD_SEC;
S_RD_MIN:
if(cmd_read_ack)
next_state <= S_RD_HOUR;
else
next_state <= S_RD_MIN;
S_RD_HOUR:
if(cmd_read_ack)
next_state <= S_RD_DATE;
else
next_state <= S_RD_HOUR;
S_RD_DATE:
if(cmd_read_ack)
next_state <= S_RD_MON;
else
next_state <= S_RD_DATE;
S_RD_MON:
if(cmd_read_ack)
next_state <= S_RD_WEEK;
else
next_state <= S_RD_MON;
S_RD_WEEK:
if(cmd_read_ack)
next_state <= S_RD_YEAR;
else
next_state <= S_RD_WEEK;
S_RD_YEAR:
if(cmd_read_ack)
next_state <= S_ACK;
else
next_state <= S_RD_YEAR;
S_ACK:
next_state <= S_IDLE;
default:
next_state <= S_IDLE;
endcase
end
ds1302_io ds1302_io_m0(
.clk(clk),
.rst(rst),
.ds1302_ce(ds1302_ce),
.ds1302_sclk(ds1302_sclk),
.ds1302_io(ds1302_io),
.cmd_read(cmd_read),
.cmd_write(cmd_write),
.cmd_read_ack(cmd_read_ack),
.cmd_write_ack(cmd_write_ack),
.read_addr(read_addr),
.write_addr(write_addr),
.read_data(read_data),
.write_data(write_data)
);
endmodule
接着实现DS1302初始化的控制,上电之后呢,我们需要先检查一下是否保存了时间,如果已经保存那么我们将时间显示,如果没有的话,我们设置一个初始化的时间给DS1302。DS1302内部含有一个位于秒寄存器BIT7的标志位,当该位为1,那么表示已有时间,继续运行即可,否则,从新给定一个时间写入。相应的模块为ds1302_test。
/*
功能:实现DS1302的控制。根据三根数据线的操作实现,数据的读取
*/
module ds1302_test(
input rst,//复位信号
input clk,//时钟信号
output ds1302_ce,//DS1302的RST信号
output ds1302_sclk,//DS1302的SPI时钟信号
inout ds1302_io,//DS1302的数据信号
output[7:0] read_second,
output[7:0] read_minute,
output[7:0] read_hour,
output[7:0] read_date,
output[7:0] read_month,
output[7:0] read_week,
output[7:0] read_year
);
//状态机编号
localparam S_IDLE = 0;//空闲状态机编号
localparam S_READ = 1;//读状态机编号
localparam S_WRITE = 2;//写状态机编号
localparam S_READ_CH = 3;
localparam S_WRITE_CH = 4;
localparam S_WAIT = 5;
reg[2:0] state,next_state;//等待状态
reg write_time_req;
reg write_time_req_latch;
wire write_time_ack;
reg read_time_req;
wire read_time_ack;
reg[7:0] write_second_reg;
reg[7:0] write_minute_reg;
reg[7:0] write_hour_reg;
reg[7:0] write_date_reg;
reg[7:0] write_month_reg;
reg[7:0] write_week_reg;
reg[7:0] write_year_reg;
wire CH;
//wire[7:0] read_second;
//wire[7:0] read_minute;
//wire[7:0] read_hour;
//wire[7:0] read_date;
//wire[7:0] read_month;
//wire[7:0] read_week;
//wire[7:0] read_year;
assign CH = read_second[7];
ds1302 ds1302_m0(
.rst(rst),
.clk(clk),
.ds1302_ce(ds1302_ce),
.ds1302_sclk(ds1302_sclk),
.ds1302_io(ds1302_io),
.write_time_req(write_time_req),
.write_time_ack(write_time_ack),
.write_second(write_second_reg),
.write_minute(write_minute_reg),
.write_hour(write_hour_reg),
.write_date(write_date_reg),
.write_month(write_month_reg),
.write_week(write_week_reg),
.write_year(write_year_reg),
.read_time_req(read_time_req),
.read_time_ack(read_time_ack),
.read_second(read_second),
.read_minute(read_minute),
.read_hour(read_hour),
.read_date(read_date),
.read_month(read_month),
.read_week(read_week),
.read_year(read_year)
);
always@(posedge clk)
begin
if(write_time_ack)
write_time_req <= 1'b0;
else if(state == S_WRITE_CH)
write_time_req <= 1'b1;
end
always@(posedge clk)
begin
if(read_time_ack)
read_time_req <= 1'b0;
else if(state == S_READ || state == S_READ_CH)
read_time_req <= 1'b1;
end
always@(posedge clk or posedge rst)
begin
if(rst)
state <= S_IDLE;
else
state <= next_state;
end
always@(posedge clk or posedge rst)
begin
if(rst)
begin
write_second_reg <= 8'h00;
write_minute_reg <= 8'h00;
write_hour_reg <= 8'h00;
write_date_reg <= 8'h00;
write_month_reg <= 8'h00;
write_week_reg <= 8'h00;
write_year_reg <= 8'h00;
end
else if(state == S_WRITE_CH)
begin
// write_second_reg <= read_second;
// write_minute_reg <= read_minute;
// write_hour_reg <= read_hour;
// write_date_reg <= read_date;
// write_month_reg <= read_month;
// write_week_reg <= read_week;
// write_year_reg <= read_year;
write_second_reg <= 8'h01;
write_minute_reg <= 8'h10;
write_hour_reg <= 8'h13;
write_date_reg <= 8'h13;
write_month_reg <= 8'h12;
write_week_reg <= 8'h02;
write_year_reg <= 8'h16;//2016-12-13 13:10:01
end
end
always@(*)
begin
case(state)
S_IDLE:
next_state <= S_READ_CH;
S_READ_CH:
if(read_time_ack)
next_state <= CH ? S_WRITE_CH : S_READ;
else
next_state <= S_READ_CH;
S_WRITE_CH:
if(write_time_ack)
next_state <= S_WAIT;
else
next_state <= S_WRITE_CH;
S_WAIT:
next_state <= S_READ;
S_READ:
if(read_time_ack)
next_state <= S_IDLE;
else
next_state <= S_READ;
default:
next_state <= S_IDLE;
endcase
end
endmodule
最终的效果如下:
本项目的下载地址为: https://download.csdn.net/download/qq_34020487/12317996