【FPGA——Cyclone Ⅳ学习笔记】八.SPI协议和DS1302时钟芯片(EP4CE6F17C8)

一.原理图

在这里插入图片描述
在这里插入图片描述

二.SPI驱动及代码解释

此实验采用黑金的代码!
和之前IIC介绍相同,先单独介绍SPI的底层驱动模块。具体的SPI协议此处不做详细介绍。
底层的SPI驱动是以标准的四线SPI编写。

1.端口定义、I/O说明和变量声明

module spi_master(
	input                       sys_clk,	
	input                       rst,
	output                      nCS,       //SPI片选输出信号 
	output                      DCLK,      //SPI时钟
	output                      MOSI,      //SPI的MOSI,主机发送
	input                       MISO,      //SPI的MISO,主机接收
	input                       CPOL,		//SPI极性,=0表示时钟空闲低电平;=1表示时钟空闲高电平
	input                       CPHA,		//SPI相位,=0表示第一个边沿触发,=1表示第二个边沿触发
	input                       nCS_ctrl,	//片选控制
	input[15:0]                 clk_div,	//时钟分频
	input                       wr_req,		//写请求触发信号
	output                      wr_ack,		//传输结束反馈信号
	input[7:0]                  data_in,	//要写的数据
	output[7:0]                 data_out	//读到的数据
);

localparam                   IDLE            = 0;		//空闲状态
localparam                   DCLK_EDGE       = 1;		//时钟产生边沿状态
localparam                   DCLK_IDLE       = 2;		//时钟空闲(等待)状态
localparam                   ACK             = 3;		//返回结束信号状态
localparam                   LAST_HALF_CYCLE = 4;		//最后一个边沿状态
localparam                   ACK_WAIT        = 5;

reg                          DCLK_reg;					//暂存时钟输出信号
reg[7:0]                     MOSI_shift;				//暂存要写的数据
reg[7:0]                     MISO_shift;				//暂存读到的数据
reg[2:0]                     state;						//当前状态
reg[2:0]                     next_state;				//下一个状态
reg [15:0]                   clk_cnt;					//时钟等待计数器
reg[4:0]                     clk_edge_cnt;				//时钟边沿计数器

2.寄存值输出

assign MOSI = MOSI_shift[7];							//输出最高位
assign data_out = MISO_shift;							//读到的数据输出
assign nCS = nCS_ctrl;									//片选控制直接由输入转到输出
assign DCLK = DCLK_reg;									//时钟寄存后输出
	
assign wr_ack = (state == ACK);							//获得传输结束信号

3.SPI的CLK配置

此语句块用于产生SPI驱动所需的CLK(时钟线)脉冲信号

//SPI时钟线CLK状态机
always@(posedge sys_clk or posedge rst)
begin
	if(rst)
		state <= IDLE;
	else
		state <= next_state;
end

always@(*)
begin
	case(state)
		IDLE: //SPI空闲状态
			if(wr_req == 1'b1)
				next_state <= DCLK_IDLE;
			else
				next_state <= IDLE;
		DCLK_IDLE: //触发后,时钟先等待clk_div个系统时钟周期
			if(clk_cnt == clk_div)
				next_state <= DCLK_EDGE;
			else
				next_state <= DCLK_IDLE;
		DCLK_EDGE: //SPI时钟线开始产生8个时钟脉冲,每个系统时钟上升沿改变一次SPI时钟电平
			if(clk_edge_cnt == 5'd15)	//当计到第15个边沿时进入下一个状态,此时还差最后一个边沿
				next_state <= LAST_HALF_CYCLE;
			else
				next_state <= DCLK_IDLE;	
		LAST_HALF_CYCLE:	//准备最后一个边沿状态
			if(clk_cnt == clk_div)	//时钟等待clk_div个系统时钟周期
				next_state <= ACK;
			else
				next_state <= LAST_HALF_CYCLE; 	
		ACK: 	//响应状态
			next_state <= ACK_WAIT;
				//等待一个系统时钟周期,确定结束信号
		ACK_WAIT:
			next_state <= IDLE;
		default:
			next_state <= IDLE;
	endcase
end

//SPI时钟线产生时钟脉冲
always@(posedge sys_clk or posedge rst)
begin
	if(rst)
		DCLK_reg <= 1'b0;
	else if(state == IDLE)
		DCLK_reg <= CPOL;				//空闲时时钟恢复空闲极性
	else if(state == DCLK_EDGE)			//SPI时钟线每个系统时钟上升沿改变一次电平,产生时钟脉冲
		DCLK_reg <= ~DCLK_reg;			//相当于系统时钟的2分频
end

//SPI时钟线等待时间计数(传输开始和结束时使用)
always@(posedge sys_clk or posedge rst)
begin
	if(rst)
		clk_cnt <= 16'd0;
	else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE) 
		clk_cnt <= clk_cnt + 16'd1;
	else
		clk_cnt <= 16'd0;
end

//SPI时钟边沿个数计数器
always@(posedge sys_clk or posedge rst)
begin
	if(rst)
		clk_edge_cnt <= 5'd0;
	else if(state == DCLK_EDGE)	//CLK每改变一次电平计数器加1
		clk_edge_cnt <= clk_edge_cnt + 5'd1;
	else if(state == IDLE)
		clk_edge_cnt <= 5'd0;
end

4.读写时序描述

时序图
在这里插入图片描述
读/写数据:要在时钟有效触发边沿到来之前把要读/写的位准备好。

//SPI写数据
always@(posedge sys_clk or posedge rst)
begin
	if(rst)
		MOSI_shift <= 8'd0;
	else if(state == IDLE && wr_req)
		MOSI_shift <= data_in;			//一旦触发立即把要写的数据传递到寄存器
	else if(state == DCLK_EDGE)			//工作在边沿触发状态
		if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b1)	//第一个边沿触发且第二个边沿刚好到来(因此还未加1)
			MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};	//准备下一位,把第6高位调到最高位
		else if(CPHA == 1'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt[0] == 1'b0)) //每个CLK的第二个边沿
			MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
end

//SPI读数据,流程与写数据类似
always@(posedge sys_clk or posedge rst)
begin
	if(rst)
		MISO_shift <= 8'd0;
	else if(state == IDLE && wr_req)
		MISO_shift <= 8'h00;
	else if(state == DCLK_EDGE)
		if(CPHA == 1'b0 && clk_edge_cnt[0] == 1'b0)	//此处的if判断还未理解
			MISO_shift <= {MISO_shift[6:0],MISO};		//把刚读到的位放到最低位
		else if(CPHA == 1'b1 && (clk_edge_cnt[0] == 1'b1))
			MISO_shift <= {MISO_shift[6:0],MISO};
end
endmodule 

三.DS1302的读写

DS1302的读写时序图:
在这里插入图片描述

1.ds1302_io.v

此模块是对ds1302的完整地读/写字节过程进行封装(先写地址再读/写数据)。底层是SPI驱动模块。
ds1302采用的实际是三线SPI,即将MISO和MOSI两根线合并,从全双工变成半双工

module ds1302_io(
	input           clk,
	input           rst,
	output          ds1302_ce,			//ds1302的ce片选脚
	output          ds1302_sclk,		//ds1302的时钟管脚
	inout           ds1302_io,			//ds1302的数据管脚
	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;		//片选置高状态
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;		//片选拉低状态
localparam S_ACK          =  9;		//流程结束反馈状态

reg[3:0] state, next_state;
reg[19:0] delay_cnt;
reg wr_req;							//SPI读写请求触发信号,实例化SPI模块
wire wr_ack;						//SPI操作结束信号,实例化SPI模块
reg CS_reg;							//暂存片选信号,实例化SPI模块
wire DCLK;							//SPI的时钟线,实例化SPI模块
wire MOSI;							//SPI的MOSI,实例化SPI模块
wire MISO;							//SPI的MISO,实例化SPI模块
reg[7:0] send_data;					//暂存需要写入的数据,实例化SPI模块
wire[7:0] data_rec;					//暂存读到的数据,实例化SPI模块
reg ds1302_io_dir;					//数据线方向,=0写输出(高阻态),=1读输入

assign ds1302_io = ~ds1302_io_dir ? MOSI : 1'bz;	//数据管脚方向,输出或者高阻态
assign MISO = ds1302_io;							//把MISO和MOSI合并到di1302_io上,变成三线SPI
//assign ds1302_ce = CS_reg;
assign ds1302_sclk = DCLK;
assign cmd_read_ack = (state == S_ACK);
assign cmd_write_ack = (state == S_ACK);

//ds1302数据流状态机
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)	//有读或写的命令
				next_state <= S_CE_HIGH;
			else
				next_state <= S_IDLE;
		S_CE_HIGH:		//片选拉高
			if(delay_cnt == 20'd255)	//延时
				next_state <= cmd_read ? S_READ : S_WRITE;	//判断读或写
			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:				//准备结束
			next_state <= S_CE_LOW;
		S_CE_LOW:
			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)
		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

//片选信号改变后延时
always@(posedge clk or posedge rst)
begin
	if(rst)
		delay_cnt <= 20'd0;
	else if(state == S_CE_HIGH || state == S_CE_LOW)
		delay_cnt <= delay_cnt + 20'd1;
	else
		delay_cnt <= 20'd0;
end

//配置SPI请求触发信号
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;	//该四种状态需要触发SPI驱动
end

//改变数据口方向
always@(posedge clk or posedge rst)
begin
	if(rst)
		ds1302_io_dir <= 1'b0;
	else
		ds1302_io_dir <= (state == S_READ_DATA);
end

//将SPI驱动模块读到的数据输出
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

//把要写的地址或数据传给SPI驱动模块的输入
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驱动模块
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

2.ds1302.v

地址/控制字节:
在这里插入图片描述
第7位:固定值1;
第6位:1—使用RAM功能,0—使用时钟功能;
第5~1位:寄存器地址;
第0位:1—读,0—写;

DS1302相关寄存器:
在这里插入图片描述
前7个寄存器是保存时间(通过上表可看出,时间的存储方式采用BCD码)。
补充:第一个寄存器的最高位CH用来控制是否开始计数。0—正常计数;1—停止计数。默认为0。

第8个寄存器是写保护寄存器:第7位为1—打开写保护,禁止向其他寄存器写数据;为0—关闭写保护。

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,		//bcd码存储,高四位代表十位,低四位代表个位
	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_DATE      =  5;	//写日
localparam S_WR_MON       =  6;	//写月
localparam S_WR_WEEK      =  7;	//写周
localparam S_WR_YEAR      =  8;	//写年
localparam S_RD_SEC       =  9;
localparam S_RD_MIN       = 10;
localparam S_RD_HOUR      = 11;
localparam S_RD_MON       = 12;
localparam S_RD_WEEK      = 13;
localparam S_RD_YEAR      = 14;
localparam S_RD_DATE      = 15;
localparam S_ACK          = 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);

//给出写命令信号
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读写数据流模块
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

3.ds1302_test.v

ds1302的测试模块,后续的使用只需更改此模块即可,上面的三个模块均可直接调用。
同时可以看到对于ds1302的读写采用了模块的层层嵌套。

module ds1302_test(
	input       rst,
	input       clk,
	
	//ds1302的三根线
	output      ds1302_ce,
	output      ds1302_sclk,
	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;			//写请求
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;							//ds1302计数使能位

//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];	//得到计数使能CH位的值

//标记写请求
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;	//更换此块代码可以使ds1302从上一次的时间继续计数
//		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:	//判断CH位的值,计数是否打开,0—打开,1—关闭
			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

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)
	
);

endmodule 

四.数码管模块

数码管结构图:
在这里插入图片描述

seg_bcd.v

数码管的顶层模块。解码器模块和动态扫描此处不再列出(第六节下篇有相关代码)

module seg_bcd(
	input clk,
	input rst_n,
	output[5:0]  seg_sel,		//数码管位选
	output[7:0]  seg_data,		//数码管段选
	input [23:0] seg_bcd		//需要显示的数据
);

//实例化解码器模块。对数据解码,转换成数码管段选。每四位一个数据。(bcd码)
wire[6:0] seg7_data_0;
seg_decoder seg_decoder_m0(
	.bin_data(seg_bcd[23:20]),
	.seg_data(seg7_data_0)
);
wire[6:0] seg7_data_1;
seg_decoder seg_decoder_m1(
	.bin_data(seg_bcd[19:16]),
	.seg_data(seg7_data_1)
);
wire[6:0] seg7_data_2;
seg_decoder seg_decoder_m2(
	.bin_data(seg_bcd[15:12]),
	.seg_data(seg7_data_2)
);
wire[6:0] seg7_data_3;
seg_decoder seg_decoder_m3(
	.bin_data(seg_bcd[11:8]),
	.seg_data(seg7_data_3)
);
wire[6:0] seg7_data_4;
seg_decoder seg_decoder_m4(
	.bin_data(seg_bcd[7:4]),
	.seg_data(seg7_data_4)
);

wire[6:0] seg7_data_5;
seg_decoder seg_decoder_m5(
	.bin_data(seg_bcd[3:0]),
	.seg_data(seg7_data_5)
);

wire[7:0] seg_data_0;
wire[7:0] seg_data_1;
wire[7:0] seg_data_2;
wire[7:0] seg_data_3;
wire[7:0] seg_data_4;
wire[7:0] seg_data_5;

assign seg_data_0 = {1'b1,seg7_data_0};			//最高位为小数点,共阳极数码管,高电平熄灭
assign seg_data_1 = {1'b1,seg7_data_1};
assign seg_data_2 = {1'b1,seg7_data_2};
assign seg_data_3 = {1'b1,seg7_data_3};
assign seg_data_4 = {1'b1,seg7_data_4};
assign seg_data_5 = {1'b1,seg7_data_5};

//实例化动态扫描模块
seg_scan seg_scan_m0(
	.clk(clk),
	.rst_n(rst_n),
	.seg_sel(seg_sel),
	.seg_data(seg_data),
	.seg_data_0(seg_data_0),
	.seg_data_1(seg_data_1),
	.seg_data_2(seg_data_2),
	.seg_data_3(seg_data_3),
	.seg_data_4(seg_data_4),
	.seg_data_5(seg_data_5)
);
endmodule 

五.主模块

整体结构图:
在这里插入图片描述

top.v

module top(
	input        clk,   		//系统时钟
	input        rst_n,			//复位按键
	output       rtc_sclk,		//ds1302时钟线
	output       rtc_ce,		//ds1302片选线
	inout        rtc_data,		//ds1302数据线
	
	output [5:0] seg_sel,		//数码管位选
	output [7:0] seg_data		//数码管段选
);

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;

//数码管动态扫描实例化
seg_bcd seg_bcd_m0(
    .clk          (clk),
    .rst_n        (rst_n),
    .seg_sel      (seg_sel),
    .seg_data     (seg_data),
    .seg_bcd      ({read_hour,read_minute,read_second})	//显示时分秒
);

//ds1302模块实例化
ds1302_test ds1302_test_m0(
    .rst         (~rst_n),
    .clk         (clk),
    .ds1302_ce   (rtc_ce),
    .ds1302_sclk (rtc_sclk),
    .ds1302_io   (rtc_data),
    .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)
);
endmodule 
  • 9
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默默无闻小菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值