/******************************************************************************** 文件名:PCF8563.c * 描 述:工程c模块文件 * 功 能:底层模块文件 * 作者:MADE BY DA HE TAO * 版本号:1.0.4(2015.03.02) *******************************************************************************/ #define _PCF8563_C #include "main.h" /******************************************************************************* * 文件名:变量定义 * 描 述: * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ uint8 buffer[7];//数据存储缓存 sTime CurTime; /******************************************************************************* * 文件名:void IIC_Delay() * 描 述: * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void IIC_Delay() { _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); _nop_(); } /******************************************************************************* * 文件名:void IIC_Start(void) * 描 述: IIC启动 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void IIC_Start(void) { IIC_SDA = 1; //为SDA下降启动做准备 IIC_SCL = 1; //在SCL高电平时,SDA为下降沿时候总线启动 IIC_Delay(); IIC_SDA = 0; //突变,总线启动 IIC_Delay(); IIC_SCL = 0; IIC_Delay(); } /******************************************************************************* * 文件名:void IIC_Stop(void) * 描 述: IIC停止 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void IIC_Stop(void) { IIC_SDA = 0; //为SDA上升做准备 IIC_Delay(); IIC_SCL = 1; //在SCL高电平时,SDA为上升沿时候总线停止 IIC_Delay(); IIC_SDA = 1; //突变,总线停止 IIC_Delay(); } /******************************************************************************* * 文件名:void IIC_Ack(u8 a) * 描 述: 主机向从机发送应答信号 * 功 能:0:应答信号 1:非应答信号 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void IIC_Ack(bit a) { if(a) IIC_SDA = 1; //放上应答信号电平 else IIC_SDA = 0; IIC_Delay(); IIC_SCL = 1; //为SCL下降做准备 IIC_Delay(); IIC_SCL = 0; //突变,将应答信号发送过去 IIC_Delay(); } /******************************************************************************* * 文件名:u8 IIC_Write_Byte(u8 dat) * 描 述: 向IIC总线发送一个字节数据 * 功 能:dat:要发送的数据 ack:返回应答信号 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ uint8 IIC_Write_Byte(uint8 dat) { uint8 iic_ack=0; //iic应答标志 uint8 mask = 0; // for(i = 0;i < 8;i++) // { // if(dat & 0x80) IIC_SDA = 1; //判断发送位,先发送高位 // else IIC_SDA = 0; // IIC_Delay(); // IIC_SCL = 1; //为SCL下降做准备 // IIC_Delay(); // IIC_SCL = 0; //突变,将数据位发送过去 // dat<<=1; //数据左移一位 // } //字节发送完成,开始接收应答信号 for(mask = 0x80; mask!= 0x00; mask >>= 1) { if(mask & dat) { IIC_SDA = 1; } else { IIC_SDA = 0; } IIC_Delay(); IIC_SCL = 1; //为SCL下降做准备 IIC_Delay(); IIC_SCL = 0; //突变,将数据位发送过去 } IIC_SDA = 1; //释放数据线 IIC_Delay(); IIC_SCL = 1; //为SCL下降做准备 IIC_Delay(); //字节发送完成,开始接收应答信号 iic_ack |= IIC_SDA; //读入应答位 IIC_SCL = 0; return iic_ack; //返回应答信号 } /******************************************************************************* * 文件名:u8 IIC_Read_Byte(void) * 描 述: 从IIC总线上读取一个字节数据 * 功 能:x:读取到的数据 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ uint8 IIC_Read_Byte(void) { uint8 x=0; uint8 mask; IIC_SDA = 1; //首先置数据线为高电平 // for(i = 0;i < 8;i++) // { // x <<= 1; //读入数据,高位在前 // IIC_Delay(); // IIC_SCL = 1; //突变 // IIC_Delay(); // // if(IIC_SDA) // x |= 0x01; //收到高电平 // // IIC_SCL = 0; // IIC_Delay(); // } //数据接收完成 for(mask = 0x80; mask != 0x00; mask >>= 1) { IIC_Delay(); IIC_SCL = 1; //突变 IIC_Delay(); if(IIC_SDA) { x |= mask; } else { x &= ~mask;// } IIC_SCL = 0; IIC_Delay(); } IIC_SCL = 0; return x; //返回读取到的数据 } /******************************************************************************* * 文件名:static unsigned char RTC_BinToBcd2(unsigned char BINValue) * 描 述: 将BIN转换为BCD * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ static unsigned char RTC_BinToBcd2(unsigned char BINValue) { unsigned char bcdhigh = 0; while (BINValue >= 10) { bcdhigh++; BINValue -= 10; } return ((unsigned char)(bcdhigh << 4) | BINValue); } /******************************************************************************* * 文件名:static unsigned char RTC_Bcd2ToBin(unsigned char BCDValue) * 描 述: 将BCD转换为BIN * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ static unsigned char RTC_Bcd2ToBin(unsigned char BCDValue) { unsigned char tmp = 0; tmp = ((unsigned char)(BCDValue & (unsigned char)0xF0) >> (unsigned char)0x04) * 10; return (tmp + (BCDValue & (unsigned char)0x0F)); } /******************************************************************************* * 文件名:void PCF8563_Write_Byte(unsigned char REG_ADD, unsigned char dat) * 描 述: PCF8563某寄存器写入一个字节数据 * 功 能:REG_ADD:要操作寄存器地址 dat: 要写入的数据 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void PCF8563_Write_Byte(unsigned char REG_ADD, unsigned char dat) { IIC_Start(); if(!(IIC_Write_Byte(0xa2))) //发送写命令并检查应答位 { IIC_Write_Byte(REG_ADD); IIC_Write_Byte(dat); //发送数据 } IIC_Stop(); } /******************************************************************************* * 文件名:unsigned char PCF8563_Read_Byte(unsigned char REG_ADD) * 描 述: PCF8563某寄存器读取一个字节数据 * 功 能:REG_ADD:要操作寄存器地址 读取得到的寄存器的值 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ unsigned char PCF8563_Read_Byte(unsigned char REG_ADD) { unsigned char ReData; IIC_Start(); if(!(IIC_Write_Byte(0xa2))) //发送写命令并检查应答位 { IIC_Write_Byte(REG_ADD); //确定要操作的寄存器 IIC_Start(); //重启总线 IIC_Write_Byte(0xa3); //发送读取命令 ReData = IIC_Read_Byte(); //读取数据 IIC_Ack(1); //发送非应答信号结束数据传送 } IIC_Stop(); return ReData; } /******************************************************************************* * 文件名:void PCF8563_Write_nByte(unsigned char REG_ADD, unsigned char num, unsigned char *pBuff) * 描 述: PCF8563写入多组数据 * 功 能:REG_ADD:要操作寄存器起始地址 num: 写入数据数量 *WBuff: 写入数据缓存 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void PCF8563_Write_nByte(unsigned char REG_ADD, unsigned char num, unsigned char *pBuff) { unsigned char i = 0; IIC_Start(); if(!(IIC_Write_Byte(0xa2))) //发送写命令并检查应答位 { IIC_Write_Byte(REG_ADD); //定位起始寄存器地址 for(i = 0;i < num;i++) { IIC_Write_Byte(*pBuff); //写入数据 pBuff++; } } IIC_Stop(); } /******************************************************************************* * 文件名:void PCF8563_Read_nByte(unsigned char REG_ADD, unsigned char num, unsigned char *pBuff) * 描 述: PCF8563读取多组数据 * 功 能:REG_ADD:要操作寄存器起始地址 num: 写入数据数量 *WBuff: 读取数据缓存 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void PCF8563_Read_nByte(unsigned char REG_ADD, unsigned char num, unsigned char *pBuff) { unsigned char i = 0; IIC_Start(); if(!(IIC_Write_Byte(0xa2))) //发送写命令并检查应答位 { IIC_Write_Byte(REG_ADD); //定位起始寄存器地址 IIC_Start(); //重启总线 IIC_Write_Byte(0xa3); //发送读取命令 for(i = 0;i < num;i++) { *pBuff = IIC_Read_Byte(); //读取数据 if(i == (num - 1)) IIC_Ack(1); //发送非应答信号 else IIC_Ack(0); //发送应答信号 pBuff++; } } IIC_Stop(); } /******************************************************************************* * 文件名:void SetRealTime(sTime* time) * 描 述: PCF8563设置时间信息 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void SetRealTime(sTime* time) { // //拷贝数据 time->mon &= ~PCF_Century_SetBitC; buffer[0] = time->sec; buffer[1] = time->min; buffer[2] = time->hour; buffer[3] = time->day; buffer[4] = time->week; buffer[5] = time->mon; buffer[6] = time->year; //写入数据到寄存器 // PCF8563_Write_nByte(PCF8563_Address_Seconds, 7, buffer); } /******************************************************************************* * 文件名:void GetRealTime(sTime* time) * 描 述: PCF8563读取时间信息 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void GetRealTime(sTime* time) { PCF8563_Read_nByte(PCF8563_Address_Seconds, 7, buffer); //读取寄存器数值 buffer[0] &= PCF8563_Shield_Seconds; //屏蔽无效位 buffer[1] &= PCF8563_Shield_Minutes; buffer[2] &= PCF8563_Shield_Hours; buffer[3] &= PCF8563_Shield_Days; buffer[4] &= PCF8563_Shield_WeekDays; buffer[5] &= PCF8563_Shield_Months_Century; buffer[6] &= PCF8563_Shield_Years; time->year = buffer[6]; time->mon = buffer[5]; time->week = buffer[4]; time->day = buffer[3]; time->hour = buffer[2]; time->min = buffer[1]; time->sec = buffer[0]; } /******************************************************************************* * 文件名: void PCF8563Init(void) * 描 述: PCF8563初始化时间 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ void PCF8563Init(void) { sTime code InitTime[] = {0x17,0x06,0x21, 0x22,0x11,0x00,0x03}; //年月日时分秒星期 SetRealTime(&InitTime); }
复制代码器件的数据手册
关于IIC的起始信号和停止信号的定义
从上图可以看出,在SCL高电平的时候,SDA由高电平到低电平的跳变作为START信号
* 文件名:void IIC_Start(void)* 描 述: IIC启动 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void IIC_Start(void) { IIC_SDA = 1; //为SDA下降启动做准备 IIC_SCL = 1; //在SCL高电平时,SDA为下降沿时候总线启动 IIC_Delay(); IIC_SDA = 0; //突变,总线启动 IIC_Delay(); IIC_SCL = 0; IIC_Delay(); }
复制代码再来看停止信号的定义
在SCL为高电平的时候,SDA由低电平到高电平的跳变作为停止信号
/******************************************************************************** 文件名:void IIC_Stop(void) * 描 述: IIC停止 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void IIC_Stop(void) { IIC_SDA = 0; //为SDA上升做准备 IIC_Delay(); IIC_SCL = 1; //在SCL高电平时,SDA为上升沿时候总线停止 IIC_Delay(); IIC_SDA = 1; //突变,总线停止 IIC_Delay(); }
复制代码看着时序图很容易就能够理解的。
接下来我们看下位传输的示意图
接下来我们看下位传输的示意图
从上图可以看出,SCL为高电平的时候,SDA上的数据被认为是有效的,是可以被读取或者写入的,SCL为低电平的时候,SDA的数据是不稳定的,可以任意变化。
关于应答信号和非应答信号
IIC规定应答信号是0,非应答信号是1,如下图所示这样,相关程序如下:
/******************************************************************************** 文件名:void IIC_Ack(u8 a) * 描 述: 主机向从机发送应答信号 * 功 能:0:应答信号 1:非应答信号 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void IIC_Ack(bit a) { if(a) IIC_SDA = 1; //放上应答信号电平 else IIC_SDA = 0; IIC_Delay(); IIC_SCL = 1; //为SCL下降做准备 IIC_Delay(); IIC_SCL = 0; //突变,将应答信号发送过去 IIC_Delay(); }
复制代码请注意这个子函数不是用来写字节使用的,是单片机读数据专用的,啥意思呢?单片机写字节的时候,是从机返回的应答位,是PCF8563主动返回的,这里的主动相当于是一个按键信号,这里的IO是作为按键输入了,不再是输出信号,这也解释了为什么很多人写代码,总要加一个拉高等于释放的解释,什么是释放?从51单片机IO的结构上判断,如下图所示,内部总线写一个0,在写锁存器的帮助下,Q会输出一个0,Q~输出一个1,MOS管导通,拉低到低电位,如果内部总线写一个1,MOS不导通,那么IO会被上拉电阻拉到高电平。这就是IO的输出0或者1的过程。我们再来看一下输入信号,
如果你输出一个0,那么单片机还能够读取外部信号吗?不可能的,因为一直都是0的,就算你按下按键也是百搭的,所以必须要拉高,释放掉,这样按键按下的时候才会在单片机读引脚信号的帮助下被写入内部总线上。这下应该明白了吧?
刚才我们说了写字节要返回的应答位,那么上面这个子函数是读字节的,谁来读?当然是单片机来读啊,也就是说单片机每读取一个字节,都要向PCF8563发送一个应答位,表示我还要读你的数据,PCF8563这边的地址都是自动累加的,不需要管,等到单片机不想读了,发送一个非应答信号1,结束通信,这就是应答和非应答的由来。
刚才我们说了写字节要返回的应答位,那么上面这个子函数是读字节的,谁来读?当然是单片机来读啊,也就是说单片机每读取一个字节,都要向PCF8563发送一个应答位,表示我还要读你的数据,PCF8563这边的地址都是自动累加的,不需要管,等到单片机不想读了,发送一个非应答信号1,结束通信,这就是应答和非应答的由来。
接下来我们再来看下IIC总线读写字节函数,这个很有参考意义,因为不管是啥协议,不是从高位开始读写就是从低位开始读写,具体的时序可能不一样,但是这个实现的过程是比较有参考意义的,先来看IIC总线写字节函数,代码如下
/******************************************************************************** 文件名:u8 IIC_Write_Byte(u8 dat) * 描 述: 向IIC总线发送一个字节数据 * 功 能:dat:要发送的数据 ack:返回应答信号 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ uint8 IIC_Write_Byte(uint8 dat) { uint8 iic_ack=0; //iic应答标志 uint8 mask = 0; // for(i = 0;i < 8;i++) // { // if(dat & 0x80) IIC_SDA = 1; //判断发送位,先发送高位 // else IIC_SDA = 0; // IIC_Delay(); // IIC_SCL = 1; //为SCL下降做准备 // IIC_Delay(); // IIC_SCL = 0; //突变,将数据位发送过去 // dat<<=1; //数据左移一位 // } //字节发送完成,开始接收应答信号 for(mask = 0x80; mask!= 0x00; mask >>= 1) { if(mask & dat) { IIC_SDA = 1; } else { IIC_SDA = 0; } IIC_Delay(); IIC_SCL = 1; //为SCL下降做准备 IIC_Delay(); IIC_SCL = 0; //突变,将数据位发送过去 } IIC_SDA = 1; //释放数据线 IIC_Delay(); IIC_SCL = 1; //为SCL下降做准备 IIC_Delay(); //字节发送完成,开始接收应答信号 iic_ack |= IIC_SDA; //读入应答位 IIC_SCL = 0; return iic_ack; //返回应答信号 }
复制代码看到被注释掉的那部分了吗?和没有被注释掉的那部分的作用是一样一样的,这个我们先不管他,对于写单个字节,IIC是这么规定的,先从高位开始读写,每写入一个字节,从机要返回一个应答位,这里的主机指的是单片机,这里的从机指的是采用IIC接口的器件,比如AT24C02,PCF8563等等,也就是说单片机写入PCF8563某寄存器一个字节后,PCF8563要主动返回一个应答位,这个应答位是0来表示,非应答位用1来表示,实现过程看前边关于IO的解释。上面这2种写字节是一样的,先来看第一种,我们要读一个字节,总共8位数据,那么,新建一个循环,先从高位开始发,那么,就这样写,如果data数据的最高位是1,那么IIC_SDA就是1,否则IIC_SDA就是0,然后就是拉高IIC_SCL让数据保持稳定,让总线来接收这个信号,结束完之后,数据左移就可以了,直到把8位数据发送完毕,就是这样。
再来看下面这种写法
这种写法是移位,先读高位,让位移动,而不是让数据移动,正好移动8次,这个写法很好,推荐给大家,很容易让人理解,写完8个循环之后,拉高SDA,准备读取SDA输入的信号,就是这样。同样的读字节函数,请自我分析完成,一样的道理。
再来看下要操作PCF8563这个芯片,要遵循下面这个时序,先来看代码
再来看下面这种写法
for(mask = 0x80; mask!= 0x00; mask >>= 1) { if(mask & dat) { IIC_SDA = 1; } else { IIC_SDA = 0; } IIC_Delay(); IIC_SCL = 1; //为SCL下降做准备 IIC_Delay(); IIC_SCL = 0; //突变,将数据位发送过去 }
复制代码这种写法是移位,先读高位,让位移动,而不是让数据移动,正好移动8次,这个写法很好,推荐给大家,很容易让人理解,写完8个循环之后,拉高SDA,准备读取SDA输入的信号,就是这样。同样的读字节函数,请自我分析完成,一样的道理。
IIC_SDA = 1; //释放数据线 IIC_Delay(); IIC_SCL = 1; //为SCL下降做准备 IIC_Delay(); //字节发送完成,开始接收应答信号 iic_ack |= IIC_SDA; //读入应答位 IIC_SCL = 0; return iic_ack; //返回应答信号
复制代码再来看下要操作PCF8563这个芯片,要遵循下面这个时序,先来看代码
/******************************************************************************** 文件名:void PCF8563_Write_Byte(unsigned char REG_ADD, unsigned char dat) * 描 述: PCF8563某寄存器写入一个字节数据 * 功 能:REG_ADD:要操作寄存器地址 dat: 要写入的数据 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void PCF8563_Write_Byte(unsigned char REG_ADD, unsigned char dat) { IIC_Start(); if(!(IIC_Write_Byte(0xa2))) //发送写命令并检查应答位 { IIC_Write_Byte(REG_ADD); IIC_Write_Byte(dat); //发送数据 } IIC_Stop(); }
复制代码从上图可以看出一个完整的写模式包括 起始信号+设备地址+应答信号+数据地址+应答信号+数据+应答信号+停止信号 组成,每写入一个字节,PCF8563都要返回一个应答信号0,表示我正确的接收了,相当于一个回执。PCF8563的写设备地址固定是0XA2,通过程序,可以很容易的理解上面的代码了。
再来看下PCF8563读单个字节函数
我们看下他的时序图:
再来看下PCF8563读单个字节函数
/******************************************************************************** 文件名:unsigned char PCF8563_Read_Byte(unsigned char REG_ADD) * 描 述: PCF8563某寄存器读取一个字节数据 * 功 能:REG_ADD:要操作寄存器地址 读取得到的寄存器的值 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ unsigned char PCF8563_Read_Byte(unsigned char REG_ADD) { unsigned char ReData; IIC_Start(); if(!(IIC_Write_Byte(0xa2))) //发送写命令并检查应答位 { IIC_Write_Byte(REG_ADD); //确定要操作的寄存器 IIC_Start(); //重启总线 IIC_Write_Byte(0xa3); //发送读取命令 ReData = IIC_Read_Byte(); //读取数据 IIC_Ack(1); //发送非应答信号结束数据传送 } IIC_Stop(); return ReData; }
复制代码我们看下他的时序图:
注意PCF8563读寄存器字节的设备地址固定0XA3,按照时序图,很容易就能够理解这段代码的。
写多个字节和读多个字节的函数也是比较容易的,唯一要注意的是时序图中的解释,ack from master 和ack from slave 这个一定要理解明白,写字节对应的是slave,是主机写,从机返回应答,from master 是读字节,是主机发出应答或者非应答,从机响应。
好了,就到这里吧,这个程序的结果是写入PCF8563时间信息,PCF8563读取到的时间信息显示在数码管上。
写多个字节和读多个字节的函数也是比较容易的,唯一要注意的是时序图中的解释,ack from master 和ack from slave 这个一定要理解明白,写字节对应的是slave,是主机写,从机返回应答,from master 是读字节,是主机发出应答或者非应答,从机响应。
/******************************************************************************** 文件名:void PCF8563_Write_nByte(unsigned char REG_ADD, unsigned char num, unsigned char *pBuff) * 描 述: PCF8563写入多组数据 * 功 能:REG_ADD:要操作寄存器起始地址 num: 写入数据数量 *WBuff: 写入数据缓存 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void PCF8563_Write_nByte(unsigned char REG_ADD, unsigned char num, unsigned char *pBuff) { unsigned char i = 0; IIC_Start(); if(!(IIC_Write_Byte(0xa2))) //发送写命令并检查应答位 { IIC_Write_Byte(REG_ADD); //定位起始寄存器地址 for(i = 0;i < num;i++) { IIC_Write_Byte(*pBuff); //写入数据 pBuff++; } } IIC_Stop(); } /******************************************************************************* * 文件名:void PCF8563_Read_nByte(unsigned char REG_ADD, unsigned char num, unsigned char *pBuff) * 描 述: PCF8563读取多组数据 * 功 能:REG_ADD:要操作寄存器起始地址 num: 写入数据数量 *WBuff: 读取数据缓存 * 作 者:大核桃 * 版本号:1.0.1(2017.03.03) *******************************************************************************/ void PCF8563_Read_nByte(unsigned char REG_ADD, unsigned char num, unsigned char *pBuff) { unsigned char i = 0; IIC_Start(); if(!(IIC_Write_Byte(0xa2))) //发送写命令并检查应答位 { IIC_Write_Byte(REG_ADD); //定位起始寄存器地址 IIC_Start(); //重启总线 IIC_Write_Byte(0xa3); //发送读取命令 for(i = 0;i < num;i++) { *pBuff = IIC_Read_Byte(); //读取数据 if(i == (num - 1)) IIC_Ack(1); //发送非应答信号 else IIC_Ack(0); //发送应答信号 pBuff++; } } IIC_Stop(); }
复制代码好了,就到这里吧,这个程序的结果是写入PCF8563时间信息,PCF8563读取到的时间信息显示在数码管上。