目录
一、I2C通信的硬件基础
二、I2C协议的基本时序单元
1.起始条件:
2.终止条件:
3.(主机)发送一个字节(给从机):
4.(主机)接收(从机)应答:
5.(主机)接受(从机)一个字节:
6.(主机)发送应答(给从机):
三. 由基本的I2C时序构成的通讯帧
1.(主机)指定从机地址写 :
2.(主机)指定从机地址读:
编辑
最近打算好好研究一下四阵脚的OLED屏幕,所以在这之前复习一下I2C通讯协议及其应用。
如下图所示,配置I2C时,需要将引脚配置成开漏输出的模式,开漏输出的特性即需要产生低电平时可以强下拉志低电平,产生高电平时,通过外接一个上拉电阻弱上拉至高电平。这样的好处是,防止配置成推挽输出时,一个设备强上拉一个设备强下拉导致的电源短路。
下面所说的一切都是在主机的角度上进行发送数据,接受数据,以及发送应答,接受应答的,这一点需要特别的注意。
代码前提:利用宏定义实现:
#define MPU6050_SCL_PORT GPIOE
#define MPU6050_SCL_PIN GPIO_Pin_12
#define MPU6050_SDA_PORT GPIOE
#define MPU6050_SDA_PIN GPIO_Pin_13
void MyI2C_W_SCL(uint8_t value)
{
GPIO_WriteBit(MPU6050_SCL_PORT,MPU6050_SCL_PIN,(BitAction)value);
delay_us(10);
}
void MyI2C_W_SDA(uint8_t value)
{
GPIO_WriteBit(MPU6050_SDA_PORT,MPU6050_SDA_PIN,(BitAction)value);
delay_us(10);
}
uint8_t MyI2C_R_SDA(void )
{
uint8_t temp=0;
temp=GPIO_ReadInputDataBit(MPU6050_SDA_PORT,MPU6050_SDA_PIN);
delay_us(10);
return temp;
}
即在SCL高电平期间,SDA由高电平变为低电平:由主机发起,这表征着通讯的开始。
void MyI2C_Start(void )
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);
}
即在SCL高电平期间,SDA由低电平变为高电平:由主机发起,这表征着通讯的结束。
void MyI2C_Stop(void )
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
注意,起始条件和终止条件都是在在SCL高电平的时候SDA变化,这是区别发送、接收、发送应答、接收应答的重要标志!
即在SCL低电平期间,主机将想要发送的数据放在SDA线上,随后拉高SCL,告知从机在SCL高电平期间将数据取走,SCL高电平期间,SDA线上的数据一定是稳定不变化的(SDA在SCL高电平期间变换就会产生起始条件或者终止条件导致通讯时序错误),连续进行8次即可完成一个字节的发送。值得注意的是:发送字节时高位先行,由D7->D0的方向发送。
void MyI2C_SendByte(uint8_t Byte)
{
for (int i = 0; i < 8; i++)
{
MyI2C_W_SDA(Byte & (0x80>>i));
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
主机发送完一个字节后,在下一个时钟里,释放SDA的控制权,并在SCL高电平期间接收来自从机的应答指令以判断从机是否收到数据。即0表示应答(即从机接收到了数据),1表示非应答(从机没有响应)。
uint8_t MyI2C_ReceiveAck(void )
{
uint8_t temp;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
temp=MyI2C_R_SDA();
MyI2C_W_SCL(0);
return temp;
}
通讯开始之前,主机一定要释放对SDA线的控制,即置为SDA线为1。在SCL低电平期间,从机将需要发送的数据放在SDA线上,之后主机置为SCL,并在SCL高电平期间读取从机传输的数据(高位在前)。同理在SCL高电平期间,从机的数据(即SDA线上的值不能改变),反复进行8次,主机即可完成一个字节的接受。
uint8_t MyI2C_ReceiveByte(void )
{
uint8_t Data=0x00;
MyI2C_W_SDA(1);
for (int i = 0; i < 8; i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA()==1)
{
Data |=(0x80>>i);
}
MyI2C_W_SCL(0);
}
return Data;
}
主机在接收一个字节之后,在下一个时钟周期内发送一个应答位给从机,已告诉从机是否收到数据。0表示主机收到从机数据,1表示没有收到。同理是在SCL高电平期间从机读取SDA总线上的数据。
void MyI2C_SendAck(uint8_t Ackbit)
{
MyI2C_W_SDA(Ackbit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
对于指定的设备(Slave Address),对该地址下指定的寄存器(Register Address)进行写数据(Data)的操作。 (0表示写,1表示读)
void I2C_W_REG(uint8_t RegAddress,uint8_t Data )
{
MyI2C_Start();
MyI2C_SendByte(MPU6050ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
}
对于指定的设备(Slave Address),对该地址下指定的寄存器(Register Address)进行读数据(Data)的操作。
uint8_t I2C_R_REG(uint8_t RegAddress )
{
uint8_t temp=0;
MyI2C_Start();
MyI2C_SendByte(MPU6050ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(MPU6050ADDRESS|0x01);
MyI2C_ReceiveAck();
temp=MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_Stop();
return temp;
}
I2C的基本介绍就是这样,根据不同的外设还有不同的用法。
下图是用Clion配置的STM32标准库开发环境,有兴趣的朋友可以私聊我。