加载时序图中...
I2C与SPI总线详解:从协议差异到传感器手册实战接入
I2C 和 SPI 是嵌入式开发中最常见的两种串行总线。选哪种?怎么接?看手册时看哪些参数?本文一次性讲透。
协议本质对比
时序波形对比
拓扑结构
波形特征对比
| 对比维度 | I2C | SPI |
|---|---|---|
| 总线信号 | 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独享 |
电气与性能对比
| 特性 | I2C | SPI |
|---|---|---|
| 线数 | 2 根 (SCL + SDA) | 3+N (SCK+MOSI+MISO+N*CS) |
| 通信方式 | 半双工 | 全双工 |
| 速率 | 标准模式 100kbps<br/>快速模式 400kbps<br/>高速模式 3.4Mbps | 1Mbps~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)
| 模式 | CPOL | CPHA | 空闲电平 | 采样沿 |
|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 |
最常用模式 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) or1011100(ADDR pin = HIGH)
从手册提取的关键信息:
| 参数 | 值 | 手册位置 |
|---|---|---|
| 接口类型 | I2C | Section 1 |
| 速率 | 100kHz / 400kHz | Pin Description |
| 器件地址 | 0x23 (ADDR=L) / 0x5C (ADDR=H) | I²C bus Interface |
| 工作时序 | 指令后需等待 120ms/180ms | Timing 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~0x48for 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 | 读回全是 0xFF | MOSI/MISO 接反 | 交换引脚 |
| SPI 数据错位 | 值偏移一位 | CPOL/CPHA 不对 | 对照时序图确认 |
| 电源噪声 | I2C/SPI 间歇性故障 | 电机/大电流干扰 | 走线远离,加去耦电容 |
总结
| 要点 | 说明 |
|---|---|
| I2C | 两线 + 地址寻址 + 上拉必须 + 速率较慢 |
| SPI | 四线 + CS 片选 + 全双工 + 速率快 |
| 看手册 | 先找接口类型 → 地址 → 时序图 → 寄存器表 → 上电初始化 |
| 排错 | I2C 先查上拉,SPI 先查 CS 和 CPOL/CPHA |
评论 (0)