本帖最后由 dabing89 于 2018-10-13 12:18 编辑
关于IIC通信详解及PCF8563芯片显示时间数据
这一节,我们的程序代码主要是将PCF8563芯片采集到的时间数据显示在数码管上,但是在完成这个任务之前,我们先来看下IIC协议,了解这个协议很重要,没办法,IIC确实让人稀里糊涂的,先把PCF8563部分的代码贴出来,然后我们对照PCF8563的数据手册来看IIC的底层读写是怎么实现的。代码如下:解释看下面文字,我们的重点不是PCF8563这个芯片,而是IIC协议,这个代码也是我整理网上的,先把数据手册上传
PCF8563数据手册.pdf
(630.02 KB)
011 PCF8563显示时钟.rar
(201.48 KB)
]因为有30000个字符的限制,所以代码就不贴出来了,直接下载源文件你看好了。
打开器件的数据手册
关于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,结束通信,这就是应答和非应答的由来。
接下来我们再来看下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位数据发送完毕,就是这样。
再来看下面这种写法
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读单个字节函数
/*******************************************************************************
* 文件名: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 是读字节,是主机发出应答或者非应答,从机响应。
/*******************************************************************************
* 文件名: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读取到的时间信息显示在数码管上。
代码上传
|