I2C时序基本单元
I2C起始条件
起始条件:SCL高电平期间,SDA从高电平切换到低电平
代码示例
void MYI2C_Start(void)
{
//空闲状态SDA和SCL均为高电平
MYI2C_W_SDA (1);
MYI2C_W_SCL (1);
//SCL高电平期间,拉低SDA。再拉低SCL
MYI2C_W_SDA (0);
MYI2C_W_SCL (0);
}
注意: 这里先拉高SDA再拉高SCL是防止读成停止信号(SCL高电平期间,SDA拉高)。
I2C停止条件
起始条件:SCL高电平期间,SDA从低电平切换到高电平
代码示例
void MYI2C_Stop(void)
{
MYI2C_W_SDA (0);
//SCL高电平期间拉高SDA再拉高SCL
MYI2C_W_SCL (1);
MYI2C_W_SDA (1);
}
注意: 这里先拉低SDA目的是在发送一个数据位时,SCL高电平期间从机读取SDA上的数据,发送完成后SCL拉低。在接收一个数据位时,主机在SCL高电平期间读取SDA上的数据,读取完成后拉低SCL。两个过程中SCL均被拉低,但是SDA上的电平未知,所以确保SDA为低电平,首先拉低SDA。
I2C发送一个字节
I2C发送时,主机对SCL和SDA有控制权,SCL在低电平期间,主机将数据一位一位放置在SDA上(高位先行)。然后主机释放SCL,从机在SCL高电平期间读取一位数据,读取完成后主机再拉低SCL等待下一位数据放置在SDA上。将此过程重复8次,一个字节的数据就发送完成。
简化过程就是,主机拉低SCL,把数据放置在SDA上。主机释放SCL,从机读取SDA上的数据,循环八次就完成了一个字节的发送。
注意:
①在从机读取时,SDA不允许有电平变化。
②主机释放SCL时,从机应该尽快读取数据。
③主机在发送过程中对SCL和SDA有控制权,从机只能被动读取。
代码示例
void MYI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for(i=0;i<8;i++)
{
MYI2C_W_SDA (Byte & (0x80>>i));
MYI2C_W_SCL (1);
MYI2C_W_SCL (0);
}
}
I2C接收一个字节
I2C接收数据时,主机在接收前应该释放SDA,从机拥有SDA控制权,主机仍有SCL的控制权。SCL低电平期间,从机将数据一位一位的放置在SDA线上(高位先行)。主机释放SCL,主机将在SCL高电平期间读取数据位,此过程循环八次即可接收一个字节。
注意:
①在主机读取时,SDA不允许有电平变化。
②从机数据电平变换要在SCL低电平期间尽快操作。
代码示例
uint8_t MYI2C_ReceiveByte(void)
{
MYI2C_W_SDA (1); //主机释放SDA
uint8_t i,Byte=0x00;
for(i=0;i<8;i++)
{
MYI2C_W_SCL (1); //SCL高电平期间主机接收SDA上的数据
if(MYI2C_R_SDA ()==1)
{
Byte |=(0x80>>i);
}
MYI2C_W_SCL (0);
}
return Byte ; //返回接收到的数据
}
I2C发送应答
主机在接收完一个字节的数据之后,在下个时钟发送一位数据,数据0表示应答,数据1表示非应答。
代码示例
void MYI2C_SendAck(uint8_t AckBit)
{
MYI2C_W_SDA (AckBit);
MYI2C_W_SCL (1);
MYI2C_W_SCL (0);
}
I2C接收应答
主机在发送一个字节数据后,在下个时钟发送一位数据,判断从机是否应答。主机在接收之前,应该把SDA释放,将SDA的控制权交给从机。从机在应答位对SDA进行操作(数据0表示应答,数据1表示非应答)。主机在SCL高电平期间读取SDA上的数据,来判断从机是否收到字节数据。同时主机在接收到应答位后,主机还需发送一个发送应答,目的是告诉从机是否要继续发送数据
代码示例
uint8_t MYI2C_ReceiveAck(void)
{
uint8_t AckBit;
MYI2C_W_SDA (1); //主机释放SDA
MYI2C_W_SCL (1); //SCL高电平期间主机接收SDA上的数据
AckBit=MYI2C_R_SDA (); //将读取的数据赋值给AckBit
MYI2C_W_SCL (0);
return AckBit;
}
I2C完整数据帧
(这里以MPU6050为例)
指定地址写
对指定设备的指定地址下写入数据。
①在SCL高电平期间拉低SDA产生起始条件。
②发送设备地址(然后对指定设备开始读写用于确定通信对象和判断写入还是读出操作)。字节内容必须是从机地址+读/写位(从机地址是7位,读写位1位,读写位置0表示写入数据)。
③接收应答。主机释放SDA,SDA回弹置高电平,从机应答需要把SDA置低电平,应答结束后从机释放SDA(方便主机把下一位数据放置在SDA上),SDA置高电平。
④根据设备不同来发送寄存器地址/指令控制字。
⑤接收应答位
⑥在指定设备下的指定地址发送数据
⑦接收应答(接收应答结束后把SDA拉低,为后续SDA置高电平准备条件)
⑧停止条件
代码示例
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
MyI2C_Start(); //I2C起始
MyI2C_SendByte(MPU6050_ADDRESS); //发送从机地址,读写位为0,表示即将写入
MyI2C_ReceiveAck(); //接收应答
MyI2C_SendByte(RegAddress); //发送寄存器地址
MyI2C_ReceiveAck(); //接收应答
MyI2C_SendByte(Data); //发送要写入寄存器的数据
MyI2C_ReceiveAck(); //接收应答
MyI2C_Stop(); //I2C终止
}
当前地址读
对指定设备,在当前地址
①在SCL高电平期间拉低SDA产生起始条件。
②发送设备地址(对指定设备开始读写用于确定通信对象和判断写入还是读出操作)。字节内容必须是从机地址+读/写位(从机地址是7位,读写位1位,读写位置1表示读取数据)。
③接收应答位。
④主机开始接收一个字节。所以主机释放SDA的控制权,从机控制SDA,主机读取SDA上的数据。
⑤接收应答位。(接收应答结束后把SDA拉低,为后续SDA置高电平准备条件)
⑥停止条件 。
指定地址读
对于指定设备,在指定地址下读取从机数据。(指定地址写+当前地址读)
①在SCL高电平期间拉低SDA产生起始条件。
②发送设备地址(然后对指定设备开始读写用于确定通信对象和判断写入还是读出操作)。字节内容必须是从机地址+读/写位(从机地址是7位,读写位1位,读写位置0表示写入数据)。
③接收应答位。主机释放SDA,SDA回弹置高电平,从机应答需要把SDA置低电平,应答结束后从机释放SDA(方便主机把下一位数据放置在SDA上),SDA置高电平。
④根据设备不同来发送寄存器地址/指令控制字。
⑤接收应答位。
⑥重新置起始条件。
⑦发送设备地址(对指定设备开始读写用于确定通信对象和判断写入还是读出操作)。字节内容必须是从机地址+读/写位(从机地址是7位,读写位1位,读写位置1表示读取数据)。
⑧接收应答位。
⑨主机开始接收一个字节。所以主机释放SDA的控制权,从机控制SDA,主机读取SDA上的数据。
⑩发送应答。
⑪停止条件。
代码示例
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
MyI2C_Start(); //I2C起始
MyI2C_SendByte(MPU6050_ADDRESS); //发送从机地址,读写位为0,表示即将写入
MyI2C_ReceiveAck(); //接收应答
MyI2C_SendByte(RegAddress); //发送寄存器地址
MyI2C_ReceiveAck(); //接收应答
MyI2C_Start(); //I2C重复起始
MyI2C_SendByte(MPU6050_ADDRESS | 0x01); //发送从机地址,读写位为1,表示即将读取
MyI2C_ReceiveAck(); //接收应答
Data = MyI2C_ReceiveByte(); //接收指定寄存器的数据
MyI2C_SendAck(1); //发送应答,给从机非应答,终止从机的数据输出
MyI2C_Stop(); //I2C终止
return Data;
}