Journal Article

I2C与SPI总线详解:从协议差异到传感器手册实战接入

深入对比I2C和SPI总线的协议差异、时序特征、速率、引脚占用和适用场景,并通过传感器手册实战演示如何根据规格书正确接入I2C或SPI设备。

14 min13 views
加载时序图中...

I2C与SPI总线详解:从协议差异到传感器手册实战接入

I2C 和 SPI 是嵌入式开发中最常见的两种串行总线。选哪种?怎么接?看手册时看哪些参数?本文一次性讲透。


协议本质对比

时序波形对比

拓扑结构

波形特征对比

对比维度I2CSPI
总线信号SCL(时钟) + SDA(数据) 共2线SCK + MOSI + MISO + CS(片选) 共4线
总线空闲电平SCL=高, SDA=高SCK=高或低(CPOL决定)
传输开始标志SCL高时SDA拉低 (START)CS拉低
传输结束标志SCL高时SDA拉高 (STOP)CS拉高
数据传输方向SDA双向, 主机控制方向MOSI(主→从) + MISO(从→主) 并发
数据采样时机SCL第9个上升沿采样由CPOL/CPHA决定
应答方式第9位由接收方回ACK(0)或NACK(1)无应答, 由上层协议保证
从机寻址START后首字节发7/10位地址独立CS引脚片选
片选方式无物理片选, 由软件地址区分每个从机独占1根CS
多从机拓扑所有从机并联同一总线共享SCK/MOSI/MISO, CS独享

电气与性能对比

特性I2CSPI
线数2 根 (SCL + SDA)3+N (SCK+MOSI+MISO+N*CS)
通信方式半双工全双工
速率标准模式 100kbps<br/>快速模式 400kbps<br/>高速模式 3.4Mbps1Mbps~50Mbps+<br/>无固定上限
应答机制每字节 ACK/NACK
从机数量最多 127 (7位地址)受 CS 引脚数限制
布线距离较长(米级)较短(板级, <30cm)
输出方式开漏 + 外部上拉推挽
功耗较低高速时较高

I2C 时序与协议

起始与停止

c
void i2c_start(void) {
    SDA_HIGH();
    SCL_HIGH();
    delay_us(5);
    SDA_LOW();     // SCL 高时 SDA 拉低 = START
    delay_us(5);
    SCL_LOW();
}

void i2c_stop(void) {
    SDA_LOW();
    SCL_HIGH();
    delay_us(5);
    SDA_HIGH();    // SCL 高时 SDA 拉高 = STOP
    delay_us(5);
}

完整一帧(写操作)

code
START | 7bit地址+R/W | ACK | 8bit寄存器地址 | ACK | 8bit数据 | ACK | STOP
c
void i2c_write_reg(uint8_t dev_addr, uint8_t reg, uint8_t data) {
    i2c_start();
    i2c_send_byte(dev_addr << 1);     // 7位地址 + W=0
    i2c_wait_ack();
    i2c_send_byte(reg);               // 寄存器地址
    i2c_wait_ack();
    i2c_send_byte(data);              // 数据
    i2c_wait_ack();
    i2c_stop();
}

读操作

code
START | 7bit地址+W | ACK | 寄存器地址 | ACK | REPEAT START | 7bit地址+R | ACK | 数据 | NACK | STOP
c
uint8_t i2c_read_reg(uint8_t dev_addr, uint8_t reg) {
    uint8_t data;
    
    // 先写寄存器地址
    i2c_start();
    i2c_send_byte(dev_addr << 1);      // W=0
    i2c_wait_ack();
    i2c_send_byte(reg);
    i2c_wait_ack();
    
    // 再读数据
    i2c_start();                       // Repeat Start
    i2c_send_byte((dev_addr << 1) | 1); // R=1
    i2c_wait_ack();
    data = i2c_read_byte();
    i2c_send_nack();                   // 主机回 NACK 结束
    i2c_stop();
    
    return data;
}

上拉电阻

I2C 是开漏输出,SCL 和 SDA 必须外接上拉电阻:

code
Rp = (Vcc - V_OL) / I_OL
典型值: 4.7kΩ (标准模式) / 2.2kΩ (快速模式)

SPI 时序与协议

四种工作模式(CPOL / CPHA)

模式CPOLCPHA空闲电平采样沿
000低电平上升沿
101低电平下降沿
210高电平下降沿
311高电平上升沿

最常用模式 0(CPOL=0, CPHA=0):空闲时 SCK 为低,数据在上升沿采样。

c
void spi_init(SPI_Mode mode) {
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = (mode >> 1) & 0x01;   // 时钟极性
    SPI_InitStructure.SPI_CPHA = mode & 0x01;           // 时钟相位
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;           // 软件 CS
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
    SPI_Init(SPI1, &SPI_InitStructure);
    SPI_Cmd(SPI1, ENABLE);
}

uint8_t spi_transfer(uint8_t tx_data) {
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    SPI_I2S_SendData(SPI1, tx_data);
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
    return SPI_I2S_ReceiveData(SPI1);
}

void spi_write_reg(uint8_t reg, uint8_t data) {
    CS_LOW();
    spi_transfer(reg);      // 寄存器地址
    spi_transfer(data);     // 数据
    CS_HIGH();
}

实战:看手册接入传感器

案例1:BH1750 光照传感器(I2C)

打开手册,首先确认通信接口

I²C bus Interface

  • Supports I²C Standard Mode (100kHz) and Fast Mode (400kHz)
  • Slave Address: 0100011 (ADDR pin = LOW) or 1011100 (ADDR pin = HIGH)

从手册提取的关键信息:

参数手册位置
接口类型I2CSection 1
速率100kHz / 400kHzPin Description
器件地址0x23 (ADDR=L) / 0x5C (ADDR=H)I²C bus Interface
工作时序指令后需等待 120ms/180msTiming Chart

接线:

code
STM32          BH1750
PB6 (SCL)  ─── SCL ──┬── 4.7kΩ ── VCC(3.3V)
PB7 (SDA)  ─── SDA ──┤
                     ├── ADDR ── GND  (地址 = 0x23)
                     └── VCC / GND

代码实现:

c
#define BH1750_ADDR   0x23    // ADDR 接地
#define BH1750_CMD    0x10    // 连续高分辨率模式

float bh1750_read_lux(void) {
    uint8_t data[2];
    
    // 发送测量指令
    i2c_start();
    i2c_send_byte(BH1750_ADDR << 1);
    i2c_wait_ack();
    i2c_send_byte(BH1750_CMD);
    i2c_wait_ack();
    i2c_stop();
    
    // 等待测量完成(手册:max 180ms)
    HAL_Delay(180);
    
    // 读取结果
    i2c_start();
    i2c_send_byte((BH1750_ADDR << 1) | 1);  // 读
    i2c_wait_ack();
    data[0] = i2c_read_byte();
    i2c_send_ack();
    data[1] = i2c_read_byte();
    i2c_send_nack();
    i2c_stop();
    
    uint16_t raw = (data[0] << 8) | data[1];
    return raw / 1.2f;  // 手册公式: Lux = raw / 1.2
}

案例2:MPU6050 六轴传感器(I2C)

手册关键信息:

I²C Interface

  • Slave Address: 110100X (AD0=0 → 0x68, AD0=1 → 0x69)
  • SCL: 400kHz Fast Mode
  • Register Map: 0x3B~0x48 for ACC/GYRO data
c
#define MPU6050_ADDR   0x68
#define WHO_AM_I_REG   0x75
#define ACCEL_XOUT_H   0x3B

// 验证器件
uint8_t mpu6050_check(void) {
    return i2c_read_reg(MPU6050_ADDR, WHO_AM_I_REG);
    // 手册: 正常返回 0x68
}

案例3:ADC MCP3008(SPI)

手册关键信息:

SPI Interface

  • Mode 0 (CPOL=0, CPHA=0)
  • Max Clock: 3.6MHz @ 5V, 1.35MHz @ 2.7V
  • 10-bit resolution
  • Command: 1 start bit + 1 SGL/DIFF + 3 channel bits
code
手册时序图:
CS   ─┐                              ┌───
       └──────────────────────────────┘
SCK  ──┐  ┌┐  ┌┐  ┌┐  ┌┐  ┌┐  ┌┐  ┌┐  ...
        └──┘└──┘└──┘└──┘└──┘└──┘└──┘
DIN  ──X Start X SGL X D2 X D1 X D0 X──
DOUT ──X─────X─────X─────X─────X  B9 X B8 ...
c
uint16_t mcp3008_read(uint8_t channel) {
    uint8_t cmd = 0x80 | ((channel & 0x07) << 4);
    // Start=1, SGL=1, D2/D1/D0 = channel
    
    CS_LOW();
    spi_transfer(0x01);   // 起始位
    uint8_t high = spi_transfer(cmd) & 0x03;
    uint8_t low  = spi_transfer(0x00);
    CS_HIGH();
    
    return ((uint16_t)high << 8) | low;  // 10-bit 结果
}

手册阅读 Checklist

拿到一个传感器手册,按以下步骤快速提取接入信息:

text
1. □ 通信接口类型?(I2C / SPI / UART / 单总线)
   → 看目录找 "Interface" / "Communication" / "Bus"

2. □ I2C 器件地址是多少?有 ADDR 引脚可配置吗?
   → 搜 "Slave Address" / "Device Address"

3. □ SPI 用哪种模式(CPOL/CPHA)?
   → 看时序图,空闲时 SCK 电平 + 第几个沿采样

4. □ 最大时钟频率?
   → 搜 "Max Clock" / "SCL Frequency" / "Timing"

5. □ 寄存器地址和读写位怎么排?
   → 找 "Register Map" / "Register Description"

6. □ 数据格式?(大端/小端,符号位,精度)
   → 看寄存器描述 "MSB first" / "2's complement"

7. □ 是否需要上拉电阻?阻值多大?
   → 看典型应用电路 "Typical Application Circuit"

8. □ 上电后需要等待多久?初始化序列?
   → 搜 "Power-up" / "Start-up time" / "Initialization"

I2C vs SPI 选型指南

code
选 I2C 当:
  ✓ GPIO 引脚紧张(只占 2 根)
  ✓ 一个总线上挂多个传感器
  ✓ 速率要求不高(< 1MHz)
  ✓ PCB 走线较长
  ✓ 需要芯片可热插拔

选 SPI 当:
  ✓ 需要高速传输(> 1MHz)
  ✓ 全双工通信(同时收发)
  ✓ 响应要确定(无地址协商开销)
  ✓ 数据流连续(SD卡、LCD屏)
  ✓ 不需要多主架构

常见踩坑

表象原因解决
I2C 无响应总线一直拉低忘了上拉电阻加 4.7kΩ 上拉
I2C 乱码数据时对时错速率超出手册上限降频或换 400kHz 模式
SPI 全 0xFF读回全是 0xFFMOSI/MISO 接反交换引脚
SPI 数据错位值偏移一位CPOL/CPHA 不对对照时序图确认
电源噪声I2C/SPI 间歇性故障电机/大电流干扰走线远离,加去耦电容

总结

要点说明
I2C两线 + 地址寻址 + 上拉必须 + 速率较慢
SPI四线 + CS 片选 + 全双工 + 速率快
看手册先找接口类型 → 地址 → 时序图 → 寄存器表 → 上电初始化
排错I2C 先查上拉,SPI 先查 CS 和 CPOL/CPHA