I2C通信协议(转)

来源-作者@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线由低电平向高电平的变化表示终止信号,总线处于控线。

扫描二维码关注公众号,回复: 122438 查看本文章

具体参考程序如下:

/*******************************************************************************  
* 函 数 名         : 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;
}


猜你喜欢

转载自blog.csdn.net/weixin_38491862/article/details/80162101