IIC 即Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司在八十年代初设计出来的一种简单、双向、二线制、同步串行总线,主要是用来连接整体电路(ICS) ,IIC是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实时数据传输的控制源。这种方式简化了信号传输总线接口。
(来自百度百科)
简单讲IIC总线通信就像你在和你对象打电话,讲道理,步骤几乎完全一致。回忆一下自己打电话的过程。
你:拨号
你对象:喂?
你:巴拉巴拉–
你对象:哦
你:巴拉巴拉–
你对象:哦
你:挂啦,么么哒
你对象:哦
你:挂电话
通话结束
那现在来看一下IIC总线通信过程
对于你来说,你是打电话的,属于主动方,首先该你写入数据(以写入EEPROM为例)
1,起始信号(拨号动作 读写方向为 写 因为你要填号码)
2,发送要写入的数据地址(填入你对象手机号)
3,发送要存储数据(就是你巴拉巴拉–一堆的话)
4,每写入一个字节EEPROM会回应一个应答位0说明写入成功(就是你对象回了个“哦”),未回应则未成功写入(就像你对象没听明白,就不会回答“哦”)
5,停止信号(挂电话)
读数据流程,以EEPROM为例(就像打电话的过程中你听你对象说话)
1,起始信号(依旧是拨号动作 读写方向为写 因为你要填号码)
2,写入地址(你对象手机号)
3,重新进行起始信号(此时读写方向为 读)发送数据地址(比如你说了一句:明天去哪吃饭)
4,读取器件返回的地址(你对象说:去你家吧)
5,继续读数据则写应答,不读则写入非应答(你接着问:吃点啥,你对象回答:随便。你不想问了,说 挂了啊(非应答))
6,停止信号(挂电话)
(读写操作可以连续进行)
以上是读写过程,怎么使用单片机模拟出该过程,分别介绍各个步骤的函数实现
IIC总线有两条线,一条数据线SDA(记忆:DATA数据),一条时钟线SCL(记忆:CLOCK时钟)
(1),起始信号
由时序图可见SCL高电平期间,SDA出现一个下降沿表示起始信号。
void IIC_Start()
{
//数据线先保持为高,起始信号要该口的下降沿
SDA = 1;
//时钟线保持为高
SCL = 1;
//有一个大概4us的延时具体以器件而定
IIC_Delay();
//数据线拉低出现下降沿
SDA = 0;
//延时一小会,保证可靠的下降沿
IIC_Delay();
//拉低时钟线,保证接下来数据线允许改变
SCL = 0;
}
(2)数据传输
当SCL为低电平的时候SDA允许变化,即发送方必须先保证SCL为低电平再进行数据的读写操作,当SCL为高电平时,写数据一方不可以变化,此时读数据方读取该位数据的0/1,8位数据后跟应答位,来决定是否继续读写
void IIC_Send_Byte(uint8_t txd)
{
//定义一个计数变量
uint8_t i;
//将时钟线拉低允许数据改变
SCL = 0;
//按位发送数据
for(i = 0;i < 8; i ++)
{
//按位给SDA赋值 先发高位再发低位
SDA = (txd&0x80 >> 7);
//保证将要发送的位一直是最高位
txd <<= 1;
//延时,保证一段可靠的电平
IIC_Delay();
//时钟线拉高,此时数据线不得改变,用于对方读取数据
SCL = 1;
//延时,得到可靠的电平
IIC_Delay();
//时钟线拉低用于下一次数据改变
SCL = 0;
//延时 得到一段可靠的电平
IIC_Delay();
}
}
//返回值为收到的数据
//参数为是否应答1应答0不应答
uint8_t IIC_Read_Byte(uint8_t ack)
{
//定义计数变量
uint8_t i = 0;
//定义接收变量
uint8_t receive = 0;
//此时要把数据线的模式切换为输入模式 本程序中不予体现
for(i = 0;i < 8; i ++)
{
//时钟线拉高 读数据保证对方数据不改变
IIC_SCL = 1;
//来一个延时保证电平可靠
IIC_Delay();
//先左移接收变量,防止循环结束时改变该变量
receive <<= 1;
//判断数据线电平
if(SDA)
{
//高电平的话接收变量自加,低电平不变化只左移,即保证了该位为0
receive ++;
}
//延时一小会 保证一个可靠的电平
IIC_Delay()
//时钟线拉低,允许下一位数据改变
SCL = 0;
}
if(!ack)
{
//不需要应答 则给出非应答信号,不再继续
IIC_NAck();
}else{
//需要应答 则给应答
IIC_Ack();
}
}
(3)停止信号
SCL高电平期间,SDA产生一个上升沿 表示停止
void IIC_Stop()
{
//先保证时钟线为高电平
SCL = 1;
//保证数据线为低电平
SDA = 0;
//延时 以得到一个可靠的电平信号
IIC_Delay();
//数据线出现上升沿
SDA = 1;
//延时保证一个可靠的高电平
IIC_Delay();
}
(4)应答与非应答,等待应答
根据时序图可以看出,第九位数据持续为低电平的时候即为应答,这部分比较简单
//应答函数
void IIC_Ack()
{
//数据线一直保持为低电平,时钟线出现上升沿即为应答
SCL = 0;
SDA = 0;
IIC_Delay();
SCL = 1;
IIC_Delay();
//应答完成后 将时钟线拉低 允许数据修改
SCL = 0;
}
//非应答
void IIC_NAck()
{
//非应答即相反 与应答区别即为数据线保持高电平即可
SCL = 0;
SDA = 1;
IIC_Delay();
SCL = 1;
IIC_Delay();
//最后要将时钟线拉低 允许数据变化
SCL = 0;
}
//等待应答
uint8_t IIC_Wait_Ack()
{
//应答等待计数
uint8_t ackTime;
//先将数据线要设置成输入模式本程序未体现,有应答则会出现下降沿
//时钟线拉高
SCL = 1;
IIC_Delay();
//等待数据线拉低应答
while(SDA){
//如果在该时间内仍未拉低
ackTime ++;
if(ackTime > 250)
{
//认为非应答 停止信号
IIC_Stop();
return 1;
}
}
SCL = 0;
return 0 ;
}
(4)读写数据过程
写数据过程
起始信号-》发送器件地址-》等待应答-》发送写数据地址-》等待应答-》发送写入数据-》等待应答-》停止信号
(给你女票打电话-》等她接-》你说话-》等她哦-》你告诉他什么事-》等她哦-》你挂了)
读数据过程
起始信号 -》发送器件地址-》等待应答-》 发送读数据地址-》等待应答-》重新起始信号-》发送器件地址+1(设置为读模式)-》等待应答-》读数据-》-》继续读 给应答-》不继续给停止应答-》停止信号
(给你女票打电话-》等他接-》你说夫人有何指示-》她说你闭嘴听我说-》你们重新来了一遍拨号(可能女票脾气比较大加强迫症)-》等你接-》她说事-》你听着-》她说挂-》你挂)
写数据
void Device_WriteData(uint8_t DeciveAddr,uint8_t DataAddr,uint8_t Data)
{
//起始信号
IIC_Start();
//发送器件地址
IIC_Send_Byte(DeciveAddr);
//等待应答
IIC_Wait_Ack();
//发送数据地址
IIC_Send_Byte(DataAddr);
//等待应答
IIC_Wait_Ack();
//发送数据
IIC_Send_Byte(Data);
//等待应答
IIC_Wait_Ack();
//结束信号
IIC_Stop();
}
//读数据
//参数一 器件地址
//参数二 数据地址
//参数三 接收数据存储
//参数四 接收长度
//
void Decive_ReadData(uint8_t DeciveAddr,uint8_t DataAddr,uint8_t *ReciveData,uint8_t num)
{
//定义计数变量
uint8_t i;
//起始信号
IIC_Start();
//发送器件地址
IIC_Send_Byte(DeciveAddr);
//等待应答
IIC_Wait_Ack();
//发送数据地址
IIC_Send_Byte(DataAddr);
//等待应答
IIC_Wait_Ack();
//起始信号
IIC_Start()
//发送器件地址读模式
IIC_Send_Byte(DeciveAddr + 1);
//等待应答
IIC_Wait_Ack();
//读数据
for(i = 0;i < (num-1);i ++)
{
//前num-1位数据时需要给应答的因为要继续读
*ReciveData= IIC_Read_Byte(1);
ReciveData++;
}
//最后一位数据不需要给应答 因为不用读了
*ReciveData = IIC_Read_Byte(0);
//停止信号
IIC_Stop();
}
IIC总线流程基本结束,有些地方仍有不清晰之处,望提示待修缮。
对于STM32而言,IO的输入输出模式是需要人工切换的,这就使得原本速率就比较低的IIC总线通信变得更慢,现在主流的做法是在SDA使用前切换IO模式,但是对于STM32本身而言。它的开漏模式是可以同时兼任输入输出的,应当可以稍微的提高速率。