来源-作者@WeiKo90 &:https://blog.csdn.net/WeiKo90/article/details/77512135
建议读者阅读原文,确保获得完整的信息
1.I2C是什么
①I2C总线(Inter-Integrated Circuit)是由Philips公司开发的一种简单、双向二线制同步串行总线。由SCL和SDA两根线即可连接于总线上的器件之间传送信息。
②SCL为时钟线,SDA为数据线,在时钟线SCL控制的时钟信号下,SDA进行数据的传送。
③SDA上传送的每个字节必须为8位,每个字节后跟一个响应位。数据的传输是一位位进行的,其首先传输的是最高位(MSB)。
2.I2C时序图及程序
①起始信号和终止信号
1)起始信号:SCL高电平时,SDA线由高电平向低电平的变化表示起始信号,总线就被占用。
2)终止信号:SCL高电平时,SDA线由低电平向高电平的变化表示终止信号,总线处于控线。
具体参考程序如下:
/*******************************************************************************
* 函 数 名 : I2C_Start()
* 函数功能 : 起始信号:在I2C_SCL时钟信号在高电平期间I2C_SDA信号产生一个下降沿
* 输 入 : 无
* 输 出 : 无
* 备 注 : 起始之后I2C_SDA和I2C_SCL都为0,只有SCL=0时,SDA才允许高低电平变化。
*******************************************************************************/
void I2C_Start()
{
I2C_SDA = 1;
I2C_SCL = 1;
I2C_Delay_us(5);//建立时间是I2C_SDA保持时间>4.7us
I2C_SDA = 0;
I2C_Delay_us(4);//保持时间是>4us
I2C_SCL = 0;
}
/*******************************************************************************
* 函 数 名 : I2C_Stop()
* 函数功能 : 终止信号:在I2C_SCL时钟信号高电平期间I2C_SDA信号产生一个上升沿
* 输 入 : 无
* 输 出 : 无
* 备 注 : 结束之后保持I2C_SDA和I2C_SCL都为1;表示总线空闲
*******************************************************************************/
void I2C_Stop()
{
I2C_SDA = 0;
I2C_Delay_us(4);
I2C_SCL = 1;
I2C_Delay_us(4);
I2C_SDA = 1;
}
②I2C发送字节和读取字节
1)SCL为低电平时,SDA处于数据转换的状态,此时数据状态是不稳定的也是不确定的。
2)SCL为高电平时,SDA上传输的数据稳定,但传输数据可能是高电平,也可能是低电平。
3)发送字节时,先拉低SCL,根据要发送的字节的当前正在处理的某一位的高低电平来转换给SDA线。转换完成后,拉高SCL,进行数据传输。
4)读取字节时,先拉高SCL,判断SDA上的传输数据是高电平还是低电平并赋值给相应的变量,再拉低SCL。
具体参考程序如下:
/*******************************************************************************
* 函 数 名 : I2C_SendByte(unsigned char byt)
* 函数功能 : 发送一个字节
* 输 入 : byt:发送的字节
* 输 出 : 无
* 备 注 : 无
*******************************************************************************/
void I2C_SendByte(unsigned char byt)
{
unsigned char i;
I2C_SCL = 0;
for(i = 0;i < 8;i++)
{
if(byt&0x80) //从最高位开始判断发送的字节byt是0还是1来判断I2C_SDA是0还是1
{
I2C_SDA = 1;
}
else
{
I2C_SDA = 0;
}
byt <<= 1; //判断完一位后,那么要左移一位,让下一位继续判断,一个字节8位,所以一共是要判断8次
delay_us(2);
I2C_SCL = 1;
delay_us(2);
I2C_SCL = 0;
}
I2C_SCL = 0;
}
/*******************************************************************************
* 函 数 名 : unsigned char IIC_Read_Byte(unsigned char ack)
* 函数功能 : 读一个字节
* 输 入 : ack(0或1)
* 输 出 : 读到的一个字节dat
* 备 注 : 无
*******************************************************************************/
unsigned char IIC_Read_Byte(unsigned char ack)
{
unsigned char i,dat = 0;
I2C_SCL = 0;
for(i = 0;i < 8;i++ )
{
IIC_SCL = 1;
dat <<= 1;
if(I2C_SDA) //根据I2C_SDA的高低来一位一位赋值给dat
{
dat |= 0x01;
}
delay_us(1);
I2C_SCL = 0;
}
IIC_Ack(ack); //如果只接收1个字节的数据,则发送非应答信号1,
//然后产生stop信号,告诉从机单片机停止接收数据,也就是不用再发了
//如果要接收多个字节数据,则接收完一个字节数据后要发送应答信号0,
//告诉从机要继续发给单片机
return dat;
}
③I2C主机等待应答和产生应答
等待应答和产生应答类似于读字节和写字节,不同的是读和写字节是一个字节8位,因此函数内部进行了8次循环,而等待应答和产生应答只需处理一位数据,所以不需要循环,其本质是差不多的。
/*******************************************************************************
* 函 数 名 : I2C_Wait_Ack()
* 函数功能 : 等待应答:即等待从设备把I2C_SDA拉低
* 输 入 : 无
* 输 出 : 0或1
* 备 注 : 0表示应答发送失败或非应答,1表示接收到应答
*******************************************************************************/
unsigned char I2C_Wait_Ack(void)
{
unsigned char acktime;
I2C_SCL = 1;
delay_us(1);
while(I2C_SDA) //等待应答,即等待从设备把I2C_SDA拉低
{
acktime++;
if(acktime>200) //如果超过200us没有应答,则发送失败,或者为非应答,表示接受结束
{
I2C_SCL = 0;
delay_us(1);
return 0;
}
}
delay_us(1);
I2C_SCL = 0;
return 1;
}
/*******************************************************************************
* 函 数 名 : IIC_Ack(unsigned char ackbit)
* 函数功能 : 产生应答
* 输 入 : 0或1
* 输 出 : 无
* 备 注 : 0表示产生应答,1表示不产生应答
*******************************************************************************/
void IIC_Ack(unsigned char ackbit)
{
I2C_SCL = 0;
I2C_SDA = ackbit;
delay_us(2);
I2C_SCL = 1;
}
3.主从机I2C通信过程
①主机发送过程
1)主机在检测到总线为“空闲状态”(即 SDA、SCL 线均为高电平)时,发送一个启动信号“S”,开始一次通信的开始。
2)主机接着发送一个命令字节。该字节由 7 位的外围器件地址和 1 位读写控制位 R/W组成。(此时 R/W=0)
3)相对应的从机收到命令字节后向主机回馈应答信号 ACK。(ACK=0)
4)主机收到从机的应答信号后开始发送第一个字节的数据。
5)从机收到数据后返回一个应答信号 ACK。
6)主机收到应答信号后再发送下一个数据字节。
7)当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号P结束本次通信并释放总线。从机收到P信号后也退出与主机之间的通信。
1)主机发送启动信号后,接着发送命令字节。(其中 R/W=1)
2)对应的从机收到地址字节后,返回一个应答信号并向主机发送数据。
3)主机收到数据后向从机反馈一个应答信号。
4)从机收到应答信号后再向主机发送下一个数据 。
5)当主机完成接收数据后,向从机发送一个“非应答信号(ACK=1)”,从机收到ASK=1 的非应答信号后便停止发送。
6)主机发送非应答信号后,再发送一个停止信号,释放总线结束通信。
/*******************************************************************************
* 函 数 名 : write_eeprom(unsigned char add,unsigned char val)
* 函数功能 : 向eeprom写一个字节
* 输 入 : eeprom中某个地址和要写入的数据字节
* 输 出 : 无
* 备 注 : 无
*******************************************************************************/
void write_eeprom(unsigned char add,unsigned char val)
{
I2C_Start();
I2C_SendByte(0xa0);
I2C_Wait_Ack();
I2C_SendByte(add);
I2C_Wait_Ack();
I2C_SendByte(val);
I2C_Wait_Ack();
I2C_Stop();
}
/*******************************************************************************
* 函 数 名 : unsigned char read_eeprom(unsigned char add)
* 函数功能 : 从eeprom读一个字节
* 输 入 : eeprom中的某个地址
* 输 出 : 选中地址里面存储的数据
* 备 注 : 无
*******************************************************************************/
unsigned char read_eeprom(unsigned char add)
{
unsigned char da;
I2C_Start();
I2C_SendByte(0xa0);
I2C_Wait_Ack();
I2C_SendByte(add);
I2C_Wait_Ack();
I2C_Start();
I2C_SendByte(0xa1);
I2C_Wait_Ack();
da = IIC_Read_Byte();
I2C_Stop();
return da;
}