0

0

0

修罗

站点介绍

只有了解事实才能获得真正的自由

I2C总线

修罗 2025-08-04 58 0条评论 51单片机

首页 / 正文

I2C总线

1.1 概述

I2C(Inter-Integrated Circuit),通常简称为IIC,是一种用在集成电路(IC)之间的串行通信总线。它是由Philips(现在的NXP半导体)在上世纪80年代开发的,并在之后广泛应用于各种电子设备和嵌入式系统中。

1.2 信号线

I2C同步串行通信,使用两根线路进行通信,分别是数据线(SDA)和时钟线(SCL),SDA线用于数据传输,SCL线用于数据传输的同步。SCL的每个时钟周期,SDA传输一位数据。

I2C规定,数据接收方会在每个时钟周期的高电平期间读取数据,具体来讲就是在SCL处于高电平时,读取SDA上的数据,如下图所示:

image-20250804111632421.png

​ 因此,SDA必须在SCL低电平期准备好要发送的下一位数据,然后在SCL高电平期间保持稳定。

1.3 主从架构

I2C采用主从架构,一个主设备可连接多个从设备。主设备负责发起通信和控制总线,而从设备负责响应主设备的请求。如下图所示:

image-20250804112032444.png

I2C总线中的每个设备都有一个唯一的地址(用7位二进制数字表示),用于在总线上标识自己。主设备可以根据地址选择性的与特定的从设备进行通信。

​ 需要注意的是,SCL信号线上的时钟信号始终由主设备产生,而SDA信号线上的数据信号既可由主设备产生,也可由从设备产生。当主设备向从设备发送数据时,SDA信号由主设备产生,从设备接收信号;当主设备从从设备读取数据时,SDA信号由从设备产生,主设备接收信号。

1.4 通信协议

1.4.1 空闲状态

I2C协议规定,当SDASCL均为高电平时,总线为空闲状态。

1.4.2 起始和结束信号

​ 主设备和从设备间的每次通信,都需要以一个起始信号开始,以一个结束信号终止。起始信号和结束信号的定义如下:

起始信号:当SCL处于高电平时,SDA由高变低。

停止信号:当SCL处于高电平时,SDA由低变高。

​ 如下图:

image-20250804140156266.png

起始信号和结束信号,都只能由主设备产生。

1.4.3 确认信号

I2C协议规定,发送方每发送一个字节(8位)的数据,接收方都要向发送方回复一个1位的确认信号,如下图所示。

image-20250804140427806.png

​ 如果该确认信号为0表示接收方已成功接收到该字节,发送方可继续发送下一字节,这个信号在I2C协议中称为ACK(Acknowledge);如果该信号为1,则表示接收方未能成功接收到该字节,或者不希望接收更多数据,该信号在I2C协议中称为NACK(Not Acknowledge)。

1.4.4 从机地址和读写标识

​ 由于一个I2C总线上可能有多个从设备,所以开始通信前,主设备需要先与目标设备取得联系,然后再进行数据传输,除此之外,主机还需要向目标设备明确本次通信的操作是写数据还是读数据。

​ 上述操作的实现思路如下:

  1. 当主设备发送起始信号之后,会向所有设备发送一个字节的数据,这一个字节中,前7位为目标设备地址第8位为读/写标识(1表示读,0表示写),如下图所示。

    image-20250804140717676.png

  1. 当各从设备收到这个字节的数据后,会将7位地址与自身进行对比,相同则会向主设备回复确认信号,不相同则不做任何回应。
  2. 当主设备收到目标设备的确认信号后,便会开始与该设备进行通信。

1.4.5 完整通信流程

​ 综上所述,当主设备想要与某个从设备进行通信时,需要经历如下流程。

image-20250804140929542.png

  1. 发送起始信号;
  2. 发送目标从设备地址+读写标识位;
  3. 接收从设备回复的确认信号;
  4. 与从设备进行数据传输(发送/接收);
  5. 发送终止信号;

1.5 基础驱动编写

1.5.1功能分析

​ 为方便后序使用I2C协议通信,此处先编写一个通用的基础的驱动,该驱动包含的具体函数如下图所示:

image-20250804141207297.png

1.5.2 发送起始信号

  1. 确保在空闲状态:当SDASCL均为高电平时,总线为空闲状态

image-20250804141408318.png

SCL = 1;
SDA = 1;
  1. 拉低SDA,发送起始信号

image-20250804141608389.png

SDA = 0;
  1. 拉低SCL,准备后面的数据位:

image-20250804141646941.png

SCL = 0;

代码:

void Dri_IIC_Start(){
    SCL = 1;
    SDA = 1;
    SDA = 0;
    SCL = 0;
}

1.5.3 发送一个字节

  1. SCL低电平期间准备数据位,例如:
SDA = 1

image-20250804142318161.png

  1. 拉高SCL,发送数据:

image-20250804142427045.png

SCL = 1
  1. 拉低SCL,准备下一位:

image-20250804142526157.png

SCL = 0
  1. 准备下一位数据:

image-20250804142605341.png

SDA = 0
  1. 拉高SCL,发送数据:

image-20250804142632516.png

SCL = 1

image-20250804142706638.png

SCL = 0

至此,发送了两个数据位,不难发现,发送每个数据位的操作是相同的,因此,想要发送一个字节,只需将发送一个数据位的操作循环执行8次即可:

image-20250804142746405.png

需要注意的是I2C协议规定,发送一个字节的数据时,要从高位开始,具体代码如下:

void Dri_IIC_SendByte(u8 byte)
{
    u8 i;
    for (i = 0; i < 8; i++) {
        // SDA = (byte & (0x80 >> i)) == 0 ? 0 : 1;
        // 0x80: 1000 0000
        if((byte & 0x80) == 0x00) {
            SDA = 0;
        }else {
            SDA = 1;
        }
        byte <<= 1;
        SCL = 1;
        SCL = 0;
    }
}

1.5.4 接收确认信号

主设备释放SDA

image-20250804144158095.png

如何释放SDA?

  • I2C协议规定,所有设备的SCLSDA引脚都需要以开漏(Open-Drain)模式接入总线,如下图所示:

image-20250804144635747.png

  • 该模式的工作原理是:设备输出低电平时,MOS管导通,信号线接地为低电平; 设备输出高电平时,MOS管关闭,信号线被上拉电阻拉至高电平。
  • 因此主设备在接受从设备数据时,需要将SDA输出置为1,此时主设备的SDA输出和SDA信号线呈断开状态,从设备便可控制SDA信号线向主设备发送确认信号了。

释放步骤:

SDA = 1;

image-20250804145236398.png

  1. 设置SDA = 1 后,从设备会在SCL电平期间准备好确认信号

image-20250804145411604.png

  1. 拉高SCL, 读取确认信号:
SCL = 1;
ack = SDA;

image-20250804145559165.png

  1. 拉低SCL,准备下一位数据
SCL = 0;

image-20250804145750668.png

代码:

bit Dri_IIC_ReceiveAck()
{
    bit ack;
    SDA = 1;
    SCL = 1;
    ack = SDA;
    SCL = 0;
    return ack;
}

1.5.5 接收一个字节

  1. 主设备释放SDA,以允许从设备驱动SDA
SDA = 1

image-20250804151454725.png

image-20250804151518265.png

  1. 拉高SCL,并在SCL高电平期间读取SDA的值
SCL = 1;
current_bit = SDA;

image-20250804151640569.png

  1. 拉低SCL,以便从设备准备下一位数据:

image-20250804151725207.png

SCL = 0;

image-20250804151800025.png

image-20250804151818464.png

SCL = 1;
current_bit = SDA;

完整代码:

u8 Dri_IIC_ReceiveByte()
{
    u8 byte = 0;
    u8 i;
    SDA = 1;
    for (i = 0; i < 8; i++) {
        SCL = 1;
        byte = (byte << 1) | SDA;
        SCL = 0;
    }
    return byte;
}

1.5.6 发送确认信号

  1. image-20250804152024384.png
SDA = 0

image-20250804152057648.png

SCL = 1;

image-20250804152143433.png

SCL = 0;

代码:

void Dri_IIC_SendAck(bit ack)
{
    SDA = ack;
    SCL = 1;
    SCL = 0;
}

1.5.7 发送停止信号

image-20250804152515526.png

SDA = 0

image-20250804152635120.png

SCL = 1;

image-20250804152741840.png

SDA = 1

代码:

void Dri_IIC_Stop()
{
    SDA = 0;
    SCL = 1;
    SDA = 1;
}

1.5.8 完整代码

Dri_IIC.h

#ifndef __DRI_IIC_H__
#define __DRI_IIC_H__
#include <STC89C5xRC.H>
#include "Util.h"
#define SCL P17
#define SDA P16
void Dri_IIC_Start();
void Dri_IIC_Stop();

void Dri_IIC_SendByte(u8 byte);
u8 Dri_IIC_ReceiveByte();

void Dri_IIC_SendAck(bit ack);
bit Dri_IIC_ReceiveAck();

#endif /* __DRI_IIC_H__ */

Dri_IIC.c

#include "Dri_IIC.h"

void Dri_IIC_Start()
{
    SCL = 1;
    SDA = 1;
    SDA = 0;
    SCL = 0;
}

void Dri_IIC_Stop()
{
    SDA = 0;
    SCL = 1;
    SDA = 1;
}

void Dri_IIC_SendByte(u8 byte)
{
    u8 i;
    for (i = 0; i < 8; i++) {
        // SDA = (byte & (0x80 >> i)) == 0 ? 0 : 1;
        // 0x80: 1000 0000
        if((byte & 0x80) == 0x00) {
            SDA = 0;
        }else {
            SDA = 1;
        }
        byte <<= 1;
        SCL = 1;
        SCL = 0;
    }
}

u8 Dri_IIC_ReceiveByte()
{
    u8 byte = 0;
    u8 i;
    SDA = 1;
    for (i = 0; i < 8; i++) {
        SCL  = 1;
        byte = (byte << 1) | SDA;
        SCL  = 0;
    }
    return byte;
}

void Dri_IIC_SendAck(bit ack)
{
    SDA = ack;
    SCL = 1;
    SCL = 0;
}

bit Dri_IIC_ReceiveAck()
{
    bit ack;
    SDA = 1;
    SCL = 1;
    ack = SDA;
    SCL = 0;
    return ack;
}

评论(0)


最新评论

  • 1

    1

  • 1

    1

  • -1' OR 2+158-158-1=0+0+0+1 or 'TKCTZnRa'='

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • 1

    1

  • @@5Qa2D

    1

  • 1

    1

  • 1

    1

日历

2025年09月

 123456
78910111213
14151617181920
21222324252627
282930    

文章目录

推荐关键字: Linux webpack js 算法 MongoDB laravel JAVA jquery javase redis