单片机模拟I²C通信详解

1. I²C 总线理论基础

I²C(Inter-Integrated Circuit)是一种由飞利浦(现 NXP)开发的 同步串行通信协议,广泛应用于嵌入式系统中的外设互联。其核心特点包括 双线通信(SCL、SDA)多主多从架构 以及 基于时钟同步的半双工数据传输。与 SPI 相比,I²C 仅需两根信号线,适用于对功耗和 PCB 资源要求较高的应用。

1.1 I²C 的工作原理

I²C 采用 主-从结构,由主设备控制通信时序,并通过 时钟信号(SCL) 进行数据同步。I²C 设备之间使用 唯一地址 进行寻址,通信过程中数据通过 串行数据线(SDA) 进行传输。I²C 主要由以下几部分组成:

  • 主机(Master):负责产生时钟信号、发送控制命令,并与从机进行数据交换。

  • 从机(Slave):响应主机命令,并按照协议返回数据。

  • SCL(串行时钟线):用于同步数据传输,由主机控制。

  • SDA(串行数据线):双向数据传输线,采用 开漏驱动 方式,并需要外部 上拉电阻 维持信号完整性。

  • IIC使用两根信号线进行通信:一根时钟线SCL,一根数据线SDA。IIC将SCL处于高时SDA拉低的动作作为开始信号,SCL处于高时SDA拉高的动作作为结束信号;传输数据时,SDA在SCL低电平时改变数据,在SCL高电平时保持数据,每个SCL脉冲的高电平传递1位数据。

  • 1.2 线与(Wired-AND)

    I²C 总线使用 开漏驱动(Open-Drain) 方式,即设备只能拉低 SDA 线,而不能主动拉高。因此,I²C 采用 线与(Wired-AND) 逻辑来进行数据传输:

  • 当所有设备都释放 SDA(即输出高阻态),上拉电阻将 SDA 拉高,表示逻辑 1

  • 当任意设备拉低 SDA,整条总线都会变成低电平,表示逻辑 0

  • 这种 线与特性 允许多个设备共享 SDA 线,同时避免了驱动冲突,提高了总线的可靠性。

    2. I²C 总线通信机制

    2.1 总线特性

  • 寻址机制

  • I²C 设备地址分为 7-bit(128 设备)和 10-bit(1024 设备)

  • 采用地址帧标识从机,主机通过 发送地址+读/写标志位 选择通信对象。

  • 数据有效性

  • SCL 高电平 时,SDA 必须稳定,数据有效。

  • SCL 低电平 时,SDA 可发生变化。

  • 数据传输速率

  • 标准模式(100kbps)

  • 快速模式(400kbps)

  • 高速模式(3.4Mbps)

  • 2.2 总线仲裁机制

    由于 I²C 允许 多主设备 连接在同一条总线上,因此可能会出现 多个主机同时发起通信 的情况。为避免冲突,I²C 采用 仲裁机制(Arbitration)

    1. 所有主机同时监听 SDA 线状态

    2. 如果某个主机输出“1”,但检测到 SDA 变为“0”,说明有其他主机正在主导通信,此时该主机必须 停止发送,让出总线。

    3. 最终胜出的主机继续传输,其他主机等待下一次通信机会

    仲裁机制保证了 I²C 总线的稳定性,避免了多个主机同时驱动 SDA 线导致的冲突。

    3.  单片机软件 I²C 实现

    由于 51 单片机(如 STC89C52)缺乏硬件 I²C 模块,因此需采用 GPIO 模拟 I²C。下面是基于单片机的 I²C 驱动实现。

    开始信号(START/S)

     SCL为高时,SDA从高到低的跳变产生开始信号

    结束信号(STOP/P)  

     SCL为高时,SDA从低到高的跳变产生结束信号

     重复开始信号

       重复开始信号(ReSTART/Sr): 在结束时不给出STOP信号,而以一个时钟周期内再次给出开始信号作为替代

  • #include <reg52.h>
    #define SDA P2_1   // 数据线
    #define SCL P2_0   // 时钟线
    
    void IIC_Delay() {
        unsigned char i;
        for(i = 0; i < 10; i++);
    }
    
    void IIC_Start() {
        SDA = 1; SCL = 1;
        IIC_Delay();
        SDA = 0;
        IIC_Delay();
        SCL = 0;
    }
    
    void IIC_Stop() {
        SDA = 0; SCL = 1;
        IIC_Delay();
        SDA = 1;
    }
    写数据操作

    写字节操作的流程如下:

  • void IIC_WriteByte(unsigned char data) {
    {
        unsigned char i,temp;
        temp = data;
        for(i=0;i<8;i++)
    	{
            SCL = 0;
            IIC_Delay();
            if(temp&0x80)    
                SDA=1;
            else 
    			SDA=0;
    		IIC_Delay();
    		temp = temp<<1;
    		SCL = 1;
        }
        SCL = 0;
        IIC_Delay();
    }
     读字节操作

    IIC 的数据读取动作都在 SCL为高 时产生,SCL为低时是数据改变的时期,无论SDA如何变化都不影响读取。所以,传输数据的过程中,当SCL为高时,数据应当保持稳定,避免数据的采集出错。

  • unsigned char IIC_ReadByte(bit ack) {
        unsigned char i, receivedData = 0;
        for(i = 0; i < 8; i++) {
            receivedData <<= 1;  // 左移一位
            SCL = 1;  // 时钟拉高
            IIC_Delay();
            if(SDA)  // 读取 SDA 线上的数据
                receivedData |= 0x01;
            SCL = 0;  // 时钟拉低
            IIC_Delay();
        }
        // 发送ACK或NACK
        if(ack) {
            SDA = 0;  // 发送 ACK
        } else {
            SDA = 1;  // 发送 NACK
        }
        IIC_Delay();
        SCL = 1;  // 时钟拉高
        IIC_Delay();
        SCL = 0;  // 时钟拉低
        return receivedData;
    }

    应答与非应答机制

    在 I²C 协议中,每当传输完一个字节后,接收方(无论是主机还是从机)都需要发送一个应答信号(ACK)或非应答信号(NACK)。这些信号由接收端通过控制 SDA 线的状态来生成:

  • ACK(应答):接收方通过拉低 SDA 线来表示它已经成功接收到一个字节的数据。通常在每个数据字节传输完毕后,接收方都会发出 ACK 信号,告知发送方继续传输下一个字节。

  • NACK(非应答):接收方通过保持 SDA 线为高电平来表示它已经成功接收到字节,但不希望再接收更多数据。通常在数据传输结束时,接收方会在最后一个字节后发送 NACK 信号,表明此次通信结束。

  • void IIC_SendAck(bit ack) {
        // 如果是发送 ACK(拉低 SDA)
        if(ack) {
            SDA = 0;  // 发送 ACK
        } else {
            SDA = 1;  // 发送 NACK
        }
        IIC_Delay();
        SCL = 1;  // 时钟拉高
        IIC_Delay();
        SCL = 0;  // 时钟拉低
    }
    

    写字节时等待应答

    在写字节时,主设备在发送完每个字节后,需要调用 IIC_WaitAck() 来等待从机的应答信号。如果从机返回 ACK,则继续写入下一个字节。如果返回 NACK,则表示通信有误,主设备应该处理错误或停止通信。

    unsigned char wait_ack() {
        unsigned char wait_time = 0;
        SCL = 1;
        IIC_Delay();
        while(SDA) {
            wait_time++;
            if(wait_time > 200) {
                stop();
                return 1;
            }
        }
        SCL = 0;
    	IIC_Delay();
        return 0;
    }

    4.I²C 总线总结

    I²C(Inter-Integrated Circuit)是一种广泛应用于嵌入式系统中的同步串行通信协议。它的核心特点是使用两根信号线(SCL 和 SDA)进行数据传输,通过主设备控制通信时序,支持多主多从的架构。与 SPI 协议相比,I²C 不需要多条信号线,适用于对功耗和 PCB 资源要求较高的应用。

    I²C 的工作原理基于主设备控制时序,通过时钟信号(SCL)同步数据传输。通信时,主设备和从设备通过唯一的地址进行识别,且使用开漏驱动方式进行信号传输。开漏驱动方式通过线与(Wired-AND)特性实现数据共享,避免了驱动冲突,增强了总线的可靠性。

    I²C 总线采用了仲裁机制,确保在多主设备同时发起通信时不会发生冲突。当总线上的多个主设备尝试发送数据时,I²C 通过比对数据位来决定哪个主设备继续控制总线,从而避免通信冲突。

    在实际的嵌入式系统中,由于一些单片机(如 51 单片机)没有硬件 I²C 模块,我们可以通过模拟 I²C 协议实现通信。这通常需要通过 GPIO 控制时钟线(SCL)和数据线(SDA)来实现数据的发送与接收。在软件模拟 I²C 中,写字节和读字节操作是基础,主设备在每次发送数据后等待从设备的应答信号(ACK/NACK),并根据从设备的反馈决定是否继续通信。

    在 I²C 软件实现中,最常见的操作是写字节和读字节,其中写字节操作需要发送完一个字节后等待从设备的应答,而读字节操作则根据是否发送 ACK 或 NACK 来控制通信的继续或停止。为了提高通信的稳定性和可靠性,我们可以在实现时增加延时和错误检测机制,避免由于时序问题导致的数据丢失或冲突。

    总的来说,I²C 是一种低成本、高效的通信协议,适合用于嵌入式系统中多个外设的互联。尽管其通信速度相对较慢,但其简单的硬件要求和易于实现的软件支持,使得 I²C 在许多应用场景中都得到了广泛的应用。通过模拟实现 I²C 协议,可以让我们在没有硬件支持的情况下也能轻松实现设备间的通信。

    下一篇将根据IIC,读取BMP180的数据,温度,大气压;

    作者:黑旋风_李kui

    物联沃分享整理
    物联沃-IOTWORD物联网 » 单片机模拟I²C通信详解

    发表回复