单片机驱动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.
一、器件代码:
BIT7 | BIT6 | BIT5 | BIT4 | BIT3 | BIT2 | BIT1 | BIT0 |
0 | 1 | 1 | 0 | 0 | 1 | 0 | R/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);
串口读取到的数据如下:
这个芯片还有很多其他功能,例如各种报警功能(温度、时间、设定等)这里由于时间因素暂时先放置,后面再更新。