IIC 简介
IIC 总线是Philips (飞利浦)公司在八十年代初推出的一种串行、半双工总线主要用于近距离、低俗的芯片之间的通信;IIC 总线有两根双向的信号线,一根数据线SDA 用于收发数据,一根时钟线SCL 用于通信双方时钟的同步;IIC 总线硬件结构简单,成本较低,因此在各个领域得到了广泛的应用。
IIC 总线的特点
IIC 总线是一种多主机总线,连接在IIC 总线上的器件分为主机和从机。主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC 也具备冲突检测和仲裁的功能来防止错误产生;每个连接到IIC 总线上的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作;IIC 总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器。
IIC 总线的通信过程
宏观上,IIC 总线通信的流程:
- 主机发送起始信号启用总线;
- 主机发送一个字节数据指明从机地址和后续字节的传送方向;
- 被寻址的从机发送应答信号回应主机;
- 发送器发送一个字节数据;
- 接收器发送应答信号回应发送器;
- 循环4、5步骤;
- 通信完成后主机发送停止信号释放总线;
例如:下图中,单片机A 发送了起始信号,所有的设备包括单片机B 都可以接收到该起始信号。那么从此刻起,总线就被单片机A 占用了。
接下来单片机A发送一个字节的数据,这个字节里面包含了从机的地址(假设为日历时钟的地址),日历时钟就能知道主机要与自己进行通信。
该字节的一共有8bit,前7bit 用于指明地址,最后一个1bit 用于指明通信的方向(读写位),即主机给从机发数据,还是从机给主机发数据。
如下图所示:R/W 为“0”表示主机(发送器)发数据给从机(接收器),为“1”表从机(发送器)发数据给主机(接收器)。
从机(日历时钟)接收到该字节后,需要向主机回复一个应答信号,这样主机才能确定从机已经接收到了前面发送的那个字节。
确认好主从机之后,主从机就可以通过总线进行数据传输了。
通信结束后,主机会向总线上发送一个停止信号,其它设备就可以知道总线已经被释放。这样其它设备就可以启用总线了,而从机接收到该信号,也能知道主机已经结束通信。
IIC 总线寻址方式
IIC 总线上传送的数据是广义的,即包括地址,又包括真正的数据;
主机在发送起始信号后必须先发送一个字节的数据,该数据的高7位为从机地址,最低位表示后续字节的传送方向,“0” 表示主机发送数据,“1” 表示主机接收数据;
总线上所有的从机接收到该字节数据后都将这7位地址与字节的地址进行比较,如果相同,则认为自己被主机寻址,然后再根据第8位将自己定位发送器或接收器。
IIC 总线的时序
起始信号和停止信号
起始信号:SCL 为高电平时,SDA 由高变低表示起始信号
停止信号:SCL 为高电平时,SDA 由低变高表示停止信号
起始信号和停止信号都是由主机发出,起始信号产生后总线处于占用状态;停止信号产生后总线处于空闲状态。
字节传送与应答
IIC总线通信时,每个字节的宽度为8位长度,数据传送时,先传送最高位,后传送低位。发送器发送完一个字节数据后,接收器必须发送1位的应答来回应发送器,即一帧共有9位。
当发送器发送完一个字节后,接收器需要进行应答,如上图的SDA的第九个时钟处,当SDA为低电平时表示接收器应答,为高电平时表示接收器非应答。
同步信号
IIC 总线在进行数据传送时,时钟线SCL 为低电平期间发送器向数据发送一位数据,在此期间数据线上的信号允许发送变化,时钟线SCL 为高电平期间接收器从数据线上读取一位数据,在此期间数据线上的信号不允许发送变化,必须保持稳定。
SCL 为低电平,发送器发送数据的时期;SCL 为高电平,接收器接收数据的时期;
典型的IIC 时序
主机向从机发数据:
过程:
-
主机发送一个起始信号;
-
主机紧接着发送一个从机地址,并且读写位为0;
-
从机进行应答;
-
接着主机发数据,从机应答,重复该过程。直到主机不打算再发数据,或者从机不打算再接收数据;
-
若主机不打算发送数据,则会在发送完最后一个字节后,发送一个停止位;
若从机不打算接收数据,则会在接收到最后一个字节后不进行响应,稍后由主机发送停止位进行结束。
从机向主机发数据:
过程:
- 主机发送一个起始信号;
- 主机紧接着发送一个从机地址,并且读写位为1;
- 从机进行应答;
- 接着从机发数据,主机应答,重复该过程。直到主机不打算再接收数据;
- 主机则会在接收到最后一个字节后不进行响应,并发送停止位进行结束;
主机先向从机发,从机再向主机发:
过程:
- 主机发送一个起始信号;
- 主机紧接着发送一个从机地址,并且读写位为0;
- 从机进行应答;
- 接着主机发数据,从机应答,重复该过程。直到数据传输完成;
- 主机重新发送一个起始信号;
- 主机紧接着再发送一个从机地址,并且读写位为1;
- 从机进行应答;
- 接着从机发数据,主机应答,重复该过程。直到数据传输完成;
- 主机发送停止信号,结束通信;
注意:阴影部分表示数据由主机向从机传送,无阴影部分表示从机向主机传送;A表示应答,nA表示非应答,S表示起始位,P表示停止位;
IIC 总线的代码实现
初始化
void IIC_init() //IIC初始化
{
SCL = 1; // 首先把时钟线拉高
delay_us(4); // 延时函数
SDA = 1; // 在SCL为高的情况下把SDA拉高
delay_us(4); // 延时函数
}
起始信号
// 产生IIC起始信号
// 1.先拉高SDA,再拉高SCL,空闲状态
// 2.拉低SDA
void IIC_Start() //启动信号
{
SDA = 1; // 确保SDA线为高电平
delay_us(5);
SCL = 1; // 确保SCL高电平
delay_us(5);
SDA = 0; // 在SCL为高时拉低SDA线,即为起始信号
delay_us(5);
SCL = 0; // 钳住I2C总线,准备发送或接收数据
}
停止信号
//产生IIC停止信号
// 1.先拉低SDA,再拉低SCL
// 2.拉高SCL
// 3.拉高SDA
// 4.停止接收数据
void IIC_Stop(void)
{
IIC_SCL = 0;
IIC_SDA = 0; // STOP:当SCL高时,数据由低变高
IIC_SCL = 1; // SCL先拉高
delay_us(5); // 延迟大于4us
IIC_SDA = 1; // SDA再拉高,发送I2C总线结束信号
delay_us(5); // 延迟大于4.7us
}
应答信号
接收器实现:
// 主机产生应答信号ACK
// 1.先拉低SCL,再拉低SDA
// 2.拉高SCL
// 3.拉低SCL## 标题
void I2C_Ack(void)
{
IIC_SCL = 0; // 先拉低SCL,使得SDA数据可以发生改变
IIC_SDA = 0;
delay_us(2);
IIC_SCL = 1;
delay_us(5); // 延迟大于4us
IIC_SCL = 0;
}
// 主机不产生应答信号NACK
// 1.先拉低SCL,再拉高SDA
// 2.拉高SCL
// 3.拉低SCL
void I2C_NAck(void)
{
IIC_SCL = 0; // 先拉低SCL,使得SDA数据可以发生改变
IIC_SDA = 1; // 拉高SDA,不产生应答信号
delay_us(2);
IIC_SCL = 1;
delay_us(5); // 延迟大于4us
IIC_SCL = 0;
}
发送器实现:
// 等待应答信号到来
// @return 1,接收应答失败
// 0,接收应答成功
char IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
IIC_SDA = 1; delay_us(1);
IIC_SCL = 1; delay_us(1);
while(IIC_SDA) {
ucErrTime++;
if(ucErrTime > 250) {
IIC_Stop();
return 1;
}
}
IIC_SCL = 0; // 时钟输出0
return 0;
}