一、I2C通信简介
●I2C总线(Inter IC BUS)是由Philips公司开发的一种简单、双向二线制同步串行总线。
●主要用于近距离、低速的芯片之间的通信。
●两根通信线:SCL(串行时钟线)、SDA(串行数据线)。
●带数据应答,即主机每次向从机发送数据后,从机会都会返回给主机一个应答,接收数据同理。
●支持总线挂载多个设备(一主多从、多主多从),而且都可以作为一个发送器或接收器。
●I2C总线是一个真正的多主机总线,如果两个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据被破坏,每个连接到总线上的器件都有唯一的地址(7bit),任何器件既可以做主机也可以做从机,但同一时刻只允许有一个主机。总线上器件的增加或删除不影响其他器件正常工作。
●连接到相同总线上的IC数量只受最大电容的限制,串行的8位双向数据传输速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Kbit/s。
●常用的电子元器件有四针OLED屏、MPU6050模块、AT24C02存储器模块等
二、I2C总线的硬件规定
●所有I2C设备的SCL连在一起,SDA连在一起
●设备在使用I2C总线协议时,在程序中需要将SDA、SCL接口配置成开漏模式
●SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
【注:在一主多从模式下,对于SCL时钟线,从机在任何时刻都只能被动的读取,从机不允许控制SCL线;对于SDA数据线,从机不允许主动发起对SDA的控制,只有在主机发送读取从机的命令后,或者从机应答时,从机才能短暂的获得SDA的控制权】
三、I2C通信过程
1、主机发送起始信号启用总线
2、主机发送一个字节数据指明从机地址和后续字节的传送方向
3、被寻址的从机发送应答信号回应主机
4、发送器发送一个字节数据
5、接收器发送应答信号回应发送器
6、......(循环步骤4、5)
7、通信完成后主机发送停止信号释放总线
【注:
●在4、5中用的是发送器和接收器,不是主机和从机,这是由于第一个字节的最后一位决定主给从发,还是主发从给。也就是说,第一个字节和最后的停止信号一定是主机发给从机,但中间就不一定了
●发送数据过程中不允许改变发送方向(除非重启一次通信)】
四、I2C通信分析
1、起始信号:
SCL高电平期间,SDA从高电平切换到低电平。
时序图:
程序:
void I2C_Start(void)
{
I2C_W_SDA(1);
I2C_W_SCL(1);
I2C_W_SDA(0);
I2C_W_SCL(0);
}
2、 终止信号:
SCL高电平期间,SDA从低电平切换到高电平。
时序图:
程序:
void I2C_Stop(void)
{
I2C_W_SDA(0);
I2C_W_SCL(1);
I2C_W_SDA(1);
}
【注:在一主多从模型内,起始和终止信号都是由主机产生,从机不允许产生起始和终止信号,所以在总线空闲状态时,从机必须始终保持放开状态】
3、发送一个字节:
SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
时序图:
程序:
void I2C_SendByte(uint8_t Byte)//I2C数据发送
{
uint8_t i;
for(i=0;i<8;i++)
{
I2C_W_SDA(Byte & (0x80 >> i));//每读取一个字节,就向右移一位读取读取次高位
I2C_W_SCL(1);
I2C_W_SCL(0);
}
}
4、接收一个字节:
SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA【释放SDA相当于切换成输入模式】)
时序图:
程序:
uint8_t I2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00;
I2C_W_SDA(1);//切换成输入模式
for(i=0;i<8;i++)
{
I2C_W_SCL(1);//SCL电平拉高,主机开始读取数据
if(I2C_R_SDA()==1){Byte |= (0x80 >>i);}//读取SDA电平状态,若不是高电平,则默认写进0
I2C_W_SCL(0);
}
return Byte;
}
5、发送应答:
主机在接收完一个字节后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
时序图:
程序:
void I2C_SendAck(uint8_t AckBit)
{
I2C_W_SDA(AckBit);//1:不应答 0:应答
I2C_W_SCL(1);
I2C_W_SCL(0);
}
6、接收应答:
主机在发送完一个字节后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收前,需要释放SDA)。
时序图:
程序:
uint8_t I2C_ReceiveAck(void)
{
uint8_t AckBit;
I2C_W_SDA(1);//主机释放SDA,防止从机干扰
I2C_W_SCL(1);//SCL高电平,主机读取应答位
AckBit = I2C_R_SDA();
I2C_W_SCL(0);
return AckBit;
}
五、I2C通信应用
1、I2C指定地址写入数据:
程序:
void Slave_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_Start();//开启时序
I2C_SendByte(SLAVE_ADDRESS);//向指定设备发送地址,做主从机联系
I2C_ReceiveAck();//接收应答
I2C_SendByte(RegAddress);//指定寄存器地址,将该字节存在指定设备的当前地址指针中,用于指定具体读写哪个寄存器
I2C_ReceiveAck();//接收应答
/*【若想写入多个字节,可:
uint8_t i;
for(i=0;i<?;i++)
{
I2C_SendByte(Data);//写入指定寄存器地址下的数据
I2C_ReceiveAck();//接收应答
}
】
*/
I2C_SendByte(Data);//写入指定寄存器地址下的数据
I2C_ReceiveAck();//接收应答
I2C_Stop();//终止时序
}
2、I2C指定地址读取数据:
程序:
uint8_t Slave_ReadReg(uint8_t RegAddress)//对指定的地址进行数据读取
{
uint8_t Data;
I2C_Start();//开启时序
I2C_SendByte(SLAVE_ADDRESS);//向指定设备发送地址,做主从机联系
I2C_ReceiveAck();//接收应答
I2C_SendByte(RegAddress);//指定寄存器地址,将该字节存在指定设备的当前地址指针中,用于指定具体读写哪个寄存器
I2C_ReceiveAck();//接收应答
I2C_Start();//再次开启时序
I2C_SendByte(ALAVE_ADDRESS | 0x01);//开启从机读,将写地址0xD0变为读地址0xD1
I2C_ReceiveAck();//接收应答
/*【若想指定地址读入多个字节,可:
uint8_t i;
for(i=0;i<?;i++)
{
Data = I2C_ReceiveByte();//将接收到的数据存储在变量Data中
I2C_SendAck(1);//不给从机应答,主机收回主线控制权
}
】
*/
Data = I2C_ReceiveByte();//将接收到的数据存储在变量Data中
I2C_SendAck(1);//不给从机应答,主机收回主线控制权
I2C_Stop();//终止时序
return Data;
}