FPGA开发(十二)----------读取DS1302内部时间数码管显示

        本次实验我们主要完成读取DS1302芯片内部的时间,并通过数码管进行时间的显示。

DS1302介绍

        DS1302是一款实时时钟芯片,内部可以实现时分秒年月日以及星期的保存,并在电源的驱动下,可以时间自动的增加计时。一般是DIP或者SOP封装,共有以下几个引脚,

                                                      FPGA开发(十二)----------读取DS1302内部时间数码管显示_第1张图片

        通过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的寄存器地址和数据格式如下
FPGA开发(十二)----------读取DS1302内部时间数码管显示_第2张图片

        因此按照上述时序,结合上表即可完成时间数据的读和写。

DS1302程序实现

        首先我们实现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 

        最终的效果如下:

                             FPGA开发(十二)----------读取DS1302内部时间数码管显示_第3张图片

        本项目的下载地址为: https://download.csdn.net/download/qq_34020487/12317996

你可能感兴趣的:(FPGA)