单片机驱动SD3077时钟芯片

一、前言       

        前面辛辛苦苦找了一堆资料关于SD3077时钟(给我找麻了,资料很多但几乎都是是要币的),肝胆了一晚上找到了一个免费的资料获取方式SD3077、SD3078时钟芯片数据手册、例程https://blog.csdn.net/wfm700305/article/details/127076815?spm=1001.2014.3001.5502,既然资料找到了那就动手看看实际效果。

二、硬件部分

         我这里控制端用的是南京沁恒微电子的CH579主控,芯片资料和例程他们官网有可以直接下载我这里就不再解释了。至于硬件电路很简单就CH579最小主控 + SD3077参考电路(数据手册15页中部),显示部分手上没东西就用串口调试把数据打印出来。

三、驱动部分(参照芯片数据手册)       

1、SD3077时钟芯片驱动——开始、停止信号时序分析

        开始,停止时序如上图,这里就不解释(有点太基础了)代码如下:

开始信号:

/*********************************************
 * 函数名:IIC_Start_SD3077
 * 描  述:开启IIC总线
 * 输  入:无
 * 输  出:无
 ********************************************/
static UINT8 IIC_Start_SD3077(void)
{
	SDA_OUT_SD3077();     //sda线输出
	IIC_SDA_H_SD3077;	  	  
	IIC_SCL_H_SD3077;
	mDelayuS(4);
	if(!READ_SDA_SD3077)    return FALSE;	//SDA线为低电平则总线忙,退出
 	IIC_SDA_L_SD3077;    //START:when CLK is high,DATA change form high to low 
	mDelayuS(4);
	while(READ_SDA_SD3077)    return FALSE; 
	IIC_SCL_L_SD3077;    //钳住IIC总线,准备发送或接收数据
	mDelayuS(4);
	return TRUE;	
}

停止信号: 

/*********************************************
 * 函数名:IIC_Stop_SD3077
 * 描  述:释放IIC总线
 * 输  入:无
 * 输  出:无
 ********************************************/
static void IIC_Stop_SD3077(void)
{
    SDA_OUT_SD3077();    //sda线输出
	IIC_SCL_L_SD3077;
	IIC_SDA_L_SD3077;    //STOP:when CLK is high DATA change form low to high
	mDelayuS(4);
	IIC_SCL_H_SD3077; 
	IIC_SDA_H_SD3077;    //发送IIC总线结束信号
	mDelayuS(4);
}

2、SD3077时钟芯片驱动——数据传输准备(应答信号)

         IIC是按字节来传输的,当每传输完一个字节的数据,后面必须紧跟一个校验位,这个校验位是接收端通过控制SDA(数据线)来实现的,以提醒发送端数据我这边已经接收完成。(SDA=0为ACK 接收完成 ;SDA=1为NACK 繁忙中;)
        主机应答: 传输完一个字节数据以后(数据传输方向:从机——>主机),主机控制(此时是接收端)SDA来提醒从机(发送端)我这边接受完成;
        从机应答: 传输完一个字节数据以后(数据传输方向:主机——>从机),从机控制(此时是接收端)SDA来提醒主机(发送端)我这边接受完成

/*********************************************
 * 函数名:IIC_Ack_SD3077
 * 描  述:发送ASK
 * 输  入:无
 * 输  出:无
 ********************************************/
static void IIC_Ack_SD3077(void)
{
	IIC_SCL_L_SD3077;
	SDA_OUT_SD3077();
	IIC_SDA_L_SD3077;
	mDelayuS(4);
	IIC_SCL_H_SD3077;
	mDelayuS(4);
	IIC_SCL_L_SD3077;
}
/*********************************************
 * 函数名:IIC_NAck_SD3077
 * 描  述:发送NOASK
 * 输  入:无
 * 输  出:无
 ********************************************/
static void IIC_NoAck_SD3077(void)
{
	IIC_SCL_L_SD3077;
	SDA_OUT_SD3077();
	IIC_SDA_H_SD3077;
	mDelayuS(2);
	IIC_SCL_H_SD3077;
	mDelayuS(2);
	IIC_SCL_L_SD3077;
}
/*********************************************
 * 函数名:IIC_Wait_Ack_SD3077
 * 描  述:等待应答信号到来
 * 输  入:无
 * 返回值:1,接收应答失败
           0,接收应答成功
 ********************************************/
static UINT8 IIC_Wait_Ack_SD3077(void)
{
	UINT8 ucErrTime=0;
	SDA_IN_SD3077();      //SDA设置为输入  
	IIC_SDA_H_SD3077;
	mDelayuS(1);	   
	IIC_SCL_H_SD3077;
	mDelayuS(1);	 
	while(READ_SDA_SD3077)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop_SD3077();
			return FALSE;
		}
	}
	IIC_SCL_L_SD3077;//时钟输出0 	   
	return TRUE;  
} 

3、SD3077时钟芯片驱动——数据/指令传输

       1、数据传输时序

         当SCL为低电平,且SDA线电平变化时,则数据由CPU传输给SD3077(高位在前、低位在后,下同);当SCL为高电平,且SDA线电平不变时,则CPU读取SD3077发送来的数据;当SCL为高电平,且SDA电平变化时,SD3077收到一个开始或停止条件。

2、数据传输格式

        当CPU发出开始条件与实时时钟建立连接后,CPU首先通过SDA总线连续输出7位器件地址和1位读/写指令来唤醒SD3077. 

一、器件代码:

BIT7BIT6BIT5BIT4BIT3BIT2BIT1BIT0
0110010R/W

        其中高7位BIT7~BIT1称“器件代码”,它代表实时时钟的器件地址,固定为“0110010”;BIT0为读/写位,“1”为读操作,“0”为写操作。

二、数据传输格式:

        在数据发送/接收时停止信号到来时,将结束其数据传输,同时内部7位地址归零(注:内部7位地址的缺省值为0000000B)。如果只有开始信号,而没有结束信号,接着重新产生起始信号,则还要重新设置器件代码(在传输方向需要改变时,就用这种传输方)。

三、SD3077数据传输的写模式

第一步:发送7位器件地址(0110010),第8位送入写命令(“0”),第9位是SD3077的响应位(ACK).SD3077进入写状态;

第二步:发送数据待写入的地址(一个字节的8位确定SD3077的内部地址(用户RAM:00H~71H)),第9位是SD3077的响应位;

第三步:开始写数据,每写完1个字节的数据之后,都经过1位的响应信号才能写下1字节的数据;如果要结束写数据过程,则在ACK后送出停止命令即可。

SD3077写数据示例(向14H,15H地址写数据):

 

特别注意:

1.除了WRTC1、WRTC2、WRTC3三个写允许位,对寄存器(00H~71H)的写操作必须确认

芯片处于写允状态,否则写无效。

2.写时间同步:每次对实时时间秒寄存器的写操作时,当秒数据的8个bit完全写入并收到ACK信号后,就会对秒以下的内部计数器清零,使时间同步。

3.从当前地址开始,每次读写完一个字节地址自动加1.

4.为了提高数据的可靠性,当写完成后,应将芯片置于写禁止状态

四、SD3077数据传输的读模式(SD3077有两种读数据方法)
 方法1:从指定的内部地址中读取数据
     第一步:重新发出开始命令以改变两线接口数据传输方向;
     第二步:再送7位器件地址(0110010)第8位送入读命令(“1”),第9位是SD3077的响应位(ACK),SD3077进入读状态;
     第三步:开始读数据,每读完1个字节的数据之后CPU都要送出1位的响应信号(ACK,低电平)才能读下1字节的数据:如果想要结束读数据过程,则CPU要送出1位的响应信号(ACK,高电平)后送出停止命令即可。
          SD3077读数据方法1示例(从7H-9H地址读取数据)

方法2:直接读取数据(从内部地址00H开始)
     第一步:重新发出开始命令以改变两线接口数据传输方向;
     第二步:再送7位器件地址(0110010)第8位送入读命令(“1”),第9位是SD3077的响应位(ACK),SD3077进入读状态;
     第三步:开始读数据,每读完1个字节的数据之后CPU都要送出1位的响应信号(ACK,低电平)才能读下1字节的数据:如果想要结束读数据过程,则CPU要送出1位的响应信号(ACK,高电平)后送出停止命令即可。

SD3077读数据方法2示例(从00H地址开始读取数据)

 

 特别注意:

        为了保证读写数据的有效性,SD3077的两线通信开始到结束需要在0.5S秒之内,如此可避
免总线挂死的现象。因此在SD3077中,IIC通信方式会在第一个开始信号(START到来之后的0.5秒之内自动终止本次通信。

写允许程序:

/*********************************************
 * 函数名:WriteRTC_Enable
 * 描  述:RTC写允许程序
 * 输  入:无
 * 输  出:TRUE:操作成功,FALSE:操作失败
 ********************************************/
UINT8 WriteRTC_Enable(void)
{
	if(FALSE == IIC_Start_SD3077()) return FALSE;
    IIC_Send_Byte_SD3077(RTC_Address); 
    if(IIC_Wait_Ack_SD3077()== FALSE){IIC_Stop_SD3077();return FALSE;}
    IIC_Send_Byte_SD3077(CTR2);      
    IIC_Wait_Ack_SD3077();	
    IIC_Send_Byte_SD3077(0x80);//置WRTC1=1      
    IIC_Wait_Ack_SD3077();
    IIC_Stop_SD3077(); 
										
    IIC_Start_SD3077();
    IIC_Send_Byte_SD3077(RTC_Address);      
    IIC_Wait_Ack_SD3077();   
    IIC_Send_Byte_SD3077(CTR1);
    IIC_Wait_Ack_SD3077();	
    IIC_Send_Byte_SD3077(0x84);//置WRTC2,WRTC3=1      
    IIC_Wait_Ack_SD3077();
    IIC_Stop_SD3077(); 
    return TRUE;
}

 写禁止程序:

/*********************************************
 * 函数名:WriteRTC_Disable
 * 描  述:RTC写禁止程序
 * 输  入:无
 * 输  出:TRUE:操作成功,FALSE:操作失败
 ********************************************/
UINT8 WriteRTC_Disable(void)
{
    if(FALSE == IIC_Start_SD3077()) return FALSE;
    IIC_Send_Byte_SD3077(RTC_Address); 
    if(!IIC_Wait_Ack_SD3077()){IIC_Stop_SD3077(); return FALSE;}
    IIC_Send_Byte_SD3077(CTR1);//设置写地址0FH      
    IIC_Wait_Ack_SD3077();	
    IIC_Send_Byte_SD3077(0x0) ;//置WRTC2,WRTC3=0      
    IIC_Wait_Ack_SD3077();
    IIC_Send_Byte_SD3077(0x0) ;//置WRTC1=0(10H地址)      
    IIC_Wait_Ack_SD3077();
    IIC_Stop_SD3077(); 
    return TRUE; 
}

MCU发送一个字节:

/*********************************************
 * 函数名:IICSendByte
 * 描  述:mcu发送一个字节
 * 输  入:无
 * 输  出:无
 ********************************************/
static void IIC_Send_Byte_SD3077(UINT8 SendByte) //数据从高位到低位
{                        
    UINT8 t;   
	SDA_OUT_SD3077(); 	    
    IIC_SCL_L_SD3077;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        if((SendByte&0x80)>>7) IIC_SDA_H_SD3077;
		else  IIC_SDA_L_SD3077;
        SendByte<<=1; 	  
		mDelayuS(2);   
		IIC_SCL_H_SD3077;
		mDelayuS(2); 
		IIC_SCL_L_SD3077;	
		mDelayuS(2);
    }	 
}

 MCU读取一个字节:

/*********************************************
 * 函数名:IIC_Read_Byte_SD3077
 * 描  述:MCU读入一个字节
 * 输  入:ack=1时,发送ACK,ack=0,发送nACK  
 * 输  出:ReceiveByte
 ********************************************/
UINT8 IIC_Read_Byte_SD3077(void)
{
	UINT8 i=8;
	UINT8 receive=0;
	SDA_IN_SD3077();//SDA设置为输入
	while(i--)
	{
		receive<<=1;      //数据从高位开始读取
		IIC_SCL_L_SD3077;
		mDelayuS(2);
		IIC_SCL_H_SD3077;
		mDelayuS(2);       //从高位开始 ddata|=SDA;ddata<<=1
		if(READ_SDA_SD3077)	{receive++;}
	}
	IIC_SCL_L_SD3077;
	return receive;
}

MCU从一字节指定地址读取数据: 

/******I2C读一个字节程序******/
UINT8 IIC_Read_One_Byte_SD3077(UINT8 daddr,UINT8 addr)
{				  	  	    																 
    UINT8 data;
	if(!IIC_Start_SD3077())return FALSE;
	IIC_Send_Byte_SD3077(daddr); 
	if(!IIC_Wait_Ack_SD3077()){IIC_Stop_SD3077(); return FALSE;}
	IIC_Send_Byte_SD3077(addr);		//设置要读的地址
	IIC_Wait_Ack_SD3077();
	IIC_Start_SD3077();
	IIC_Send_Byte_SD3077(daddr+1);		   
	IIC_Wait_Ack_SD3077();	 
    data = IIC_Read_Byte_SD3077();	//读数据   
    IIC_NoAck_SD3077();
	IIC_Stop_SD3077();	
	
	return  data;
}

 MCU向一字节指定地址写入数据:

/******I2C写一个字节******/
UINT8 IIC_Write_One_Byte_SD3077(UINT8 daddr,UINT8 addr,UINT8 data)
{				   	  	    																  
	WriteRTC_Enable();				//使能,开锁
	
	if(!IIC_Start_SD3077())	return FALSE;
	IIC_Send_Byte_SD3077(daddr);
	IIC_Wait_Ack_SD3077();
	IIC_Send_Byte_SD3077(addr);		//设置写地址      
	IIC_Wait_Ack_SD3077();	   	 										  		   
	IIC_Send_Byte_SD3077(data);      //写数据						   
	IIC_Wait_Ack_SD3077();  		    	   
    IIC_Stop_SD3077();
	
	WriteRTC_Disable();				//关锁
	return	TRUE;	 
}

I2C在指定地址写多字节数据: 

/*********************************************
 * 函数名     :I2CWriteSerial
 * 描  述     :I2C在指定地址写多字节数据
 * Device_Addr:I2C设备地址
 * Address    :内部起始地址
 * length     :字节长度
 * ps         :缓存区指针
 * 输出       :TRUE 成功,FALSE 失败
 ********************************************/	
UINT8 I2CWriteSerial(UINT8 DeviceAddress, UINT8 Address, UINT8 length, UINT8 *ps)
{
	UINT8 i;
	if(!WriteRTC_Enable())return FALSE;
	if(!IIC_Start_SD3077())return FALSE;
	IIC_Send_Byte_SD3077(DeviceAddress);      //器件地址
	if(!IIC_Wait_Ack_SD3077()){IIC_Stop_SD3077(); return FALSE;}
	IIC_Send_Byte_SD3077(Address);                //设置起始地址
	IIC_Wait_Ack_SD3077();
	for(i=0;i<length;i++)
	{
		IIC_Send_Byte_SD3077(*(ps++));                
		IIC_Ack_SD3077();                                        
	}
	IIC_Stop_SD3077(); 
	WriteRTC_Disable();
	return TRUE;
}

 I2C在指定地址读多字节数据:

/*********************************************
 * 函数名     :I2CReadSerial
 * 描  述     :I2C在指定地址读多字节数据
 * Device_Addr:I2C设备地址
 * Address    :内部起始地址
 * length     :字节长度
 * ps         :缓存区指针
 * 输出       :TRUE 成功,FALSE 失败
 ********************************************/	
UINT8 I2CReadSerial(UINT8 DeviceAddress, UINT8 Address, UINT8 length, UINT8 *ps)
{
	UINT8 i;
	if(!IIC_Start_SD3077())return FALSE;
	IIC_Send_Byte_SD3077(DeviceAddress);      
	if(!IIC_Wait_Ack_SD3077()){IIC_Stop_SD3077(); return FALSE;}
	IIC_Send_Byte_SD3077(Address);                        //设置要读的地址
	IIC_Wait_Ack_SD3077();
	IIC_Start_SD3077();        
	IIC_Send_Byte_SD3077(DeviceAddress+1);
	IIC_Wait_Ack_SD3077();
	for(i=0;i<length-1;i++,ps++)
	{
		*ps=IIC_Read_Byte_SD3077();                //读数据
		IIC_Ack_SD3077();
	}
	*ps=IIC_Read_Byte_SD3077();        
	IIC_NoAck_SD3077();
	IIC_Stop_SD3077(); 
	return TRUE;
}

四、调试过程

功能一:实时读取时钟数据

驱动代码:读RTC实时数据寄存器

/*********************************************
 * 函数名:RTC_ReadDate
 * 描  述:读RTC实时数据寄存器
 * 输  入:时间结构体指针
 * 输  出:TRUE:操作成功,FALSE:操作失败
 ********************************************/
UINT8 RTC_ReadDate(Time_Def	*psRTC)
{
	if(!IIC_Start_SD3077())return FALSE;
	IIC_Send_Byte_SD3077(RTC_Address+1);      
	if(!IIC_Wait_Ack_SD3077()){IIC_Stop_SD3077(); return FALSE;}

	psRTC->second= IIC_Read_Byte_SD3077();
	IIC_Ack_SD3077();
	psRTC->minute= IIC_Read_Byte_SD3077();
	IIC_Ack_SD3077();
	psRTC->hour  = IIC_Read_Byte_SD3077() & 0x7F;
	IIC_Ack_SD3077();
	psRTC->week  = IIC_Read_Byte_SD3077();
	IIC_Ack_SD3077();
	psRTC->day   = IIC_Read_Byte_SD3077();
	IIC_Ack_SD3077();
	psRTC->month = IIC_Read_Byte_SD3077();
	IIC_Ack_SD3077();
	psRTC->year  = IIC_Read_Byte_SD3077();		
	IIC_NoAck_SD3077();
	IIC_Stop_SD3077(); 
	return	TRUE;
}

 在主程序中调用 读RTC实时数据寄存器驱动代码,用串口打印实时测量数据。串口读取到的数据如下:

 

功能二:芯片时间更新(给芯片写初始时间)

驱动代码:写RTC实时数据寄存器

/*********************************************
 * 函数名:RTC_WriteDate
 * 描  述:写RTC实时数据寄存器
 * 输  入:时间结构体指针
 * 输  出:TRUE:操作成功,FALSE:操作失败
 ********************************************/
UINT8 RTC_WriteDate(Time_Def	*psRTC)	//写时间操作要求一次对实时时间寄存器(00H~06H)依次写入,
{                               //不可以单独对7个时间数据中的某一位进行写操作,否则可能会引起时间数据的错误进位. 
                                //要修改其中某一个数据 , 应一次性写入全部 7 个实时时钟数据.
	WriteRTC_Enable();				//使能,开锁

	if(!IIC_Start_SD3077())return FALSE;
	IIC_Send_Byte_SD3077(RTC_Address); 
	if(!IIC_Wait_Ack_SD3077()){IIC_Stop_SD3077(); return FALSE;}
	IIC_Send_Byte_SD3077(0x00);			//设置写起始地址      
	IIC_Wait_Ack_SD3077();	
	IIC_Send_Byte_SD3077(psRTC->second);		//second     
	IIC_Wait_Ack_SD3077();	
	IIC_Send_Byte_SD3077(psRTC->minute);		//minute      
	IIC_Wait_Ack_SD3077();	
	IIC_Send_Byte_SD3077(psRTC->hour|0x80);//hour ,同时设置小时寄存器最高位(0:为12小时制,1:为24小时制)
	IIC_Wait_Ack_SD3077();	
	IIC_Send_Byte_SD3077(psRTC->week);		//week      
	IIC_Wait_Ack_SD3077();	
	IIC_Send_Byte_SD3077(psRTC->day);		//day      
	IIC_Wait_Ack_SD3077();	
	IIC_Send_Byte_SD3077(psRTC->month);		//month      
	IIC_Wait_Ack_SD3077();	
	IIC_Send_Byte_SD3077(psRTC->year);		//year      
	IIC_Wait_Ack_SD3077();	
	IIC_Stop_SD3077();

	WriteRTC_Disable();				//关锁
	return	TRUE;
}

在主程序中调用 写RTC实时数据寄存器驱动代码,用串口打印实时测量数据。串口读取到的数据如下:

 

 

功能三:用户RAM区读写数据

/************* RTC用户RAM区写数据——>0X30地址写读取到的秒钟数值************/	
IIC_Write_One_Byte_SD3077(RTC_Address,0x30,sysTime.second);
			
/************* RTC用户RAM区读数据——>读取0X30地址数据************/	
//方法一:I2C读指定地址一个字节程序
i = IIC_Read_One_Byte_SD3077(RTC_Address,0x30);
printf("保存时间01:%02X\r\n",IIC_Read_One_Byte_SD3077(RTC_Address,0x30));

//方法二:I2C在指定地址读多字节数据
I2CReadSerial(RTC_Address,0x30,1,&ps);
printf("保存时间00:%02X\r\n",ps);

串口读取到的数据如下: 

 

这个芯片还有很多其他功能,例如各种报警功能(温度、时间、设定等)这里由于时间因素暂时先放置,后面再更新。

posted @ 2022-10-17 10:37  归依龙井  阅读(261)  评论(0编辑  收藏  举报  来源