总线之别:IIC与UART的本质分野及51单片机IIC协议全拆解

# 总线之别:IIC与UART的本质分野及51单片机IIC协议全拆解


串行通信协议繁多,初学者最易混淆的便是IIC与UART。两者均使用两根信号线,却遵循截然不同的设计哲学——**UART是全双工、点对点的字符流传输;IIC是半双工、多设备的总线式寄存器访问**。理解这一本质差异,比死记硬背时序图更为重要。本文从IIC的物理层、帧结构、通信流程切入,并与UART对比辨析,最终在51单片机上完整实现软件模拟IIC主机驱动。


---


## 一、IIC vs UART:设计定位的根本差异


**UART**:异步串行通信,两根线各自独立(TX/RX)。**全双工**,收发可同时进行。**点对点**,不支持多机挂接(仅限标准TTL电平)。帧结构面向字符,无设备地址概念。


**IIC**:同步串行通信,两根线为时钟SCL与数据SDA。**半双工**,同一时刻只能单向传输。**多主多从**,总线可挂载上百个设备,通过**7位从机地址**寻址。帧结构面向寄存器,包含起始、地址、读写位、数据、应答、停止等多个阶段。


**选型直觉**:连接GPS模块、蓝牙模块用UART;挂载温湿度传感器、EEPROM、RTC用IIC。


---


## 二、IIC物理层:开漏输出与上拉电阻


IIC总线的电气特性是**开漏输出**。这意味着:

- 器件只能拉低总线(输出低电平),无法主动输出高电平;

- 高电平由**上拉电阻**提供;

- 多个设备可并联,任一设备拉低总线即总线为低——**线与**特性。


**上拉电阻取值**:1kΩ~10kΩ。100kHz标准模式常用4.7kΩ,400kHz快速模式可降至2.2kΩ。阻值过小增加功耗,过大则上升沿变缓限制速率。


51单片机I/O口无开漏模式,模拟IIC需将引脚配置为**准双向口**(标准51模式),输出低电平时直接清零,输出高电平时置1并由外部上拉电阻拉高。


```c

sbit SDA = P1^0;

sbit SCL = P1^1;


#define SDA_HIGH()  SDA = 1

#define SDA_LOW()   SDA = 0

#define SCL_HIGH()  SCL = 1

#define SCL_LOW()   SCL = 0

```


---


## 三、IIC帧结构:从起始到停止的完整序列


**起始信号**:SCL高电平期间,SDA发生下降沿。


```c

void IIC_Start(void) {

    SDA_HIGH();

    SCL_HIGH();

    delay_us(5);

    SDA_LOW();      // 下降沿

    delay_us(5);

    SCL_LOW();      // 拉低时钟,准备传输数据

}

```


**停止信号**:SCL高电平期间,SDA发生上升沿。


```c

void IIC_Stop(void) {

    SDA_LOW();

    SCL_HIGH();

    delay_us(5);

    SDA_HIGH();     // 上升沿

    delay_us(5);

}

```


**字节发送**:SCL低电平时设置SDA,SCL高电平时从机采样。**高位先行**。


```c

void IIC_SendByte(unsigned char dat) {

    unsigned char i;

    for (i = 0; i < 8; i++) {

        SCL_LOW();

        if (dat & 0x80) SDA_HIGH();

        else SDA_LOW();

        dat <<= 1;

        SCL_HIGH();   // 从机在此沿采样

        delay_us(2);

    }

    SCL_LOW();

}

```

<"k8.a8k1.org.cn"><"p0.a8k1.org.cn"><"m4.a8k1.org.cn">

**应答检测**:第9个时钟周期,主机释放SDA,检测从机是否拉低SDA作为应答。


```c

unsigned char IIC_WaitAck(void) {

    unsigned char ack;

    SDA_HIGH();      // 释放总线

    SCL_HIGH();

    delay_us(2);

    ack = SDA;       // 0:应答, 1:非应答

    SCL_LOW();

    return ack;

}

```


**字节接收**:主机释放SDA,每时钟周期读取一位。


```c

unsigned char IIC_RecvByte(void) {

    unsigned char i, val = 0;

    SDA_HIGH();      // 释放总线

    for (i = 0; i < 8; i++) {

        val <<= 1;

        SCL_HIGH();

        if (SDA) val |= 0x01;

        SCL_LOW();

    }

    return val;

}

```


**应答/非应答发送**:主机在第9时钟控制SDA。


```c

void IIC_SendAck(void) {

    SDA_LOW();       // 应答

    SCL_HIGH(); delay_us(2); SCL_LOW();

    SDA_HIGH();

}


void IIC_SendNak(void) {

    SDA_HIGH();      // 非应答

    SCL_HIGH(); delay_us(2); SCL_LOW();

}

```


---


## 四、完整通信流程:以24C02 EEPROM为例


**写单字节**:起始 → 从机地址+写位 → 等待应答 → 发送寄存器地址 → 等待应答 → 发送数据 → 等待应答 → 停止。


```c

bit EEPROM_WriteByte(unsigned char addr, unsigned char dat) {

    IIC_Start();

    IIC_SendByte(0xA0);      // 24C02器件地址+写

    if (IIC_WaitAck()) return 1;

    

    IIC_SendByte(addr);      // 寄存器地址

    if (IIC_WaitAck()) return 1;

    

    IIC_SendByte(dat);       // 写入数据

    if (IIC_WaitAck()) return 1;

    

    IIC_Stop();

    return 0;

}

```

<"a6.a8k1.org.cn"><"d3.a8k1.org.cn"><"h7.a8k1.org.cn">

**读单字节**:起始 → 从机地址+写位 → 发送寄存器地址 → 重启 → 从机地址+读位 → 接收数据 → 主机非应答 → 停止。


```c

unsigned char EEPROM_ReadByte(unsigned char addr) {

    unsigned char dat;

    

    IIC_Start();

    IIC_SendByte(0xA0);      // 伪写,设置寄存器指针

    IIC_WaitAck();

    IIC_SendByte(addr);

    IIC_WaitAck();

    

    IIC_Start();             // 重启信号

    IIC_SendByte(0xA1);      // 读操作

    IIC_WaitAck();

    

    dat = IIC_RecvByte();    // 接收数据

    IIC_SendNak();           // 非应答,结束读取

    IIC_Stop();

    

    return dat;

}

```


**重启信号**:不停止总线直接发起新起始,用于从写切换为读。


---


## 五、UART与IIC对比汇总


| 维度         | UART                          | IIC                              |

|--------------|-------------------------------|----------------------------------|

| 信号线       | TX, RX(独立)                | SCL, SDA(复用)                |

| 工作模式     | 全双工                        | 半双工                          |

| 拓扑结构     | 点对点                        | 多主多从,总线式                |

| 时钟         | 异步,双方约定波特率          | 同步,主机产生SCL              |

| 起始条件     | 下降沿(空闲高电平)          | SCL高时SDA下降沿               |

| 停止条件     | 空闲高电平                    | SCL高时SDA上升沿               |

| 设备寻址     | 无                            | 7位从机地址                    |

| 典型速率     | 9600~115200bps                | 100kHz/400kHz/1MHz             |

| 适用场景     | GPS/蓝牙/调试信息            | 传感器/存储器/RTC              |


---


## 六、常见问题与调试要点


**总线死锁**:从机异常拉低SDA,主机无法产生起始/停止。**发送9个以上SCL时钟**迫使从机释放总线。


```c

void IIC_Recover(void) {

    unsigned char i;

    SDA_HIGH();

    for (i = 0; i < 9; i++) {

        SCL_HIGH(); delay_us(5);

        SCL_LOW();  delay_us(5);

    }

    IIC_Stop();  // 尝试停止

}

```


**延时匹配**:IIC时序对延时敏感。标准模式100kHz需单周期≥5μs,快速模式400kHz需≥1.25μs。51单片机软件模拟需根据晶振调整`delay_us()`。


**地址左移**:7位从机地址如0x50在IIC总线上实际发送为`(0x50 << 1) | R/W`。初学者易直接发送0x50导致寻址失败。


**应答检测忽略**:部分示例代码不检测应答位,认为从机一定存在。**工程代码必须检测ACK**,超时或错误时应复位总线。


---


IIC与UART的混淆,源于对“总线”与“连接”概念的模糊。UART是**连接**,连接两端设备;IIC是**总线**,总线上所有设备按地址响应。理解这一层,便不会再用IIC去接蓝牙模块,也不会试图用UART挂载八个传感器。协议选对的背后,是对通信本质的洞察。


请使用浏览器的分享功能分享到微信等