ESP32 通信总线完全入门:UART、I2C、SPI 选型与实战

为什么需要总线:GPIO/ADC 只能「读电压」,但 OLED 屏、传感器芯片、GPS 模块都有自己的想法——它们通过总线以协议对话。ESP32 有三大总线,选对总线是外设开发的第一决策。


一、三大总线一张图

UART(双线对话):        I2C(一主多从总线):       SPI(高速专线):
ESP32 ──TX──> RX── 设备   ESP32 ──SDA── 多个设备      ESP32 ──MOSI──> 设备
      <──RX<── TX──         └──SCL──(共享两根线)           <──MISO<──
                          每个从机有 7 位地址        ──SCK── + 每从机一根 CS
维度UARTI2CSPI
线数2(TX/RX)2(SDA/SCL)4+(MOSI/MISO/SCK/CS)
速度≤ 115.2kbps 常用100k/400kHz可达 80MHz
挂设备数1 对 1同总线多从机(地址区分)多从机(每机一根片选)
典型外设GPS、蓝牙模块、串口屏OLED、温湿度、IMUTFT 屏、SD 卡、Flash
上拉电阻不需要需要(4.7k~10k)不需要

选型口诀:慢速小器件选 I2C(省引脚)、屏幕存储选 SPI(要带宽)、模块对讲选 UART(现成协议)。


二、UART:不只是调试串口

Serial 只是 UART 的默认占用。ESP32 有 3 个硬件 UART,Serial1/Serial2 可重映射到任意 GPIO:

// GPS 模块接 Serial2:RX16 ← GPS TX,TX17 → GPS RX
#define GPS_RX 16
#define GPS_TX 17

HardwareSerial GPSSerial(2);   // 使用 UART2

void setup() {
  Serial.begin(115200);              // 调试口(UART0)
  GPSSerial.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX);
}

void loop() {
  while (GPSSerial.available()) {
    Serial.write(GPSSerial.read());  // 透传 NMEA 语句
  }
}

三个易错点:

  1. 交叉连接:ESP32 的 TX 接对方的 RX(就像电话两端,嘴对耳朵)
  2. 波特率必须两端一致(GPS 常见 9600,蓝牙 9600/115200)
  3. SERIAL_8N1 = 8 数据位、无校验、1 停止位——绝大多数设备默认它

中断篇的「重活放 loop」原则同样适用:UART 数据到达是事件,缓存累积后在 loop 解析。


三、I2C:两根线挂一堆传感器

1. 主从架构与 7 位地址

I2C 是「一主多从」的总线:主机(ESP32)发起一切,从机靠出厂固化的 7 位地址应答。常见地址:OLED SSD1306 = 0x3C,BMP280 = 0x76/0x77,MCP4725 DAC = 0x62。

2. 先跑扫描器(新模块第一步)

#include <Wire.h>

void setup() {
  Wire.begin(21, 22);   // SDA=21, SCL=22(可重映射)
  Serial.begin(115200);
}

void loop() {
  Serial.println("扫描 I2C 总线...");
  for (uint8_t addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {     // 0 = 有设备应答
      Serial.printf("发现设备: 0x%02X\n", addr);
    }
  }
  delay(5000);
}

扫不到任何设备 = 接线/上拉问题(见避坑表),先解决再谈协议。

3. 读温湿度(Wire 直接对话)

// AHT20(地址 0x38)读温湿度,示意主流程
Wire.beginTransmission(0x38);
Wire.write(0xAC); Wire.write(0x33); Wire.write(0x00);   // 触发测量
Wire.endTransmission();
delay(80);                                              // 等待转换

Wire.requestFrom(0x38, 7);                              // 读 7 字节
uint8_t data[7];
for (int i = 0; i < 7; i++) data[i] = Wire.read();
// data[1..6] 按数据手册公式换算温湿度

实战通常直接用传感器库(Adafruit_AHTX0 等),但看懂这层原始对话,库出问题时你才知道去查什么。

4. 上拉电阻

SDA/SCL 是开漏设计,必须有上拉才能出现高电平:

  • 模组板载 10kΩ 上拉 ✅(立创/淘宝模块基本都带)
  • 裸芯片裸接:自己加 4.7kΩ ×2 到 3.3V
  • 多个模块同总线:上拉并联会变小(两个 10k 并联 = 5k),一般无碍;挂太多才需摘除多余上拉

四、SPI:屏幕与存储的速度之王

TFT 彩屏每帧几百 KB 的数据量,I2C 的 400kHz 远远不够——SPI 用四根线换带宽:

信号方向作用
MOSI主→从数据(Master Out Slave In)
MISO从→主数据
SCK主→从时钟(决定速度)
CS主→从片选(选中哪个从机)
#include <SPI.h>

// VSPI 默认引脚:SCK=18, MISO=19, MOSI=23, CS=5
SPIClass SPI(VSPI);
#define TFT_CS 5

void setup() {
  SPI.begin(18, 19, 23, TFT_CS);
  SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0));  // 40MHz
  digitalWrite(TFT_CS, LOW);
  SPI.transfer16(0xABCD);   // 发送 16 位数据
  digitalWrite(TFT_CS, HIGH);
}

CPOL/CPHA:SPI_MODE 的四个模式

时钟极性(CPOL)与相位(CPHA)组合出 4 种模式,必须与从机要求一致:

模式CPOLCPHA空闲电平采样沿
MODE000低上升沿
MODE101低下降沿
MODE210高下降沿
MODE311高上升沿

屏幕白屏/花屏、SD 卡初始化失败时,先查模式(数据手册「SPI timing」节)再查接线。绝大多数设备是 MODE0 或 MODE3。

TFT 屏实战(TFT_eSPI 库配置)属 L3 显示驱动主题,本篇掌握底层即可。


五、避坑速查表(收藏级)

症状根因与对策
I2C 扫描无设备SDA/SCL 接反;无上拉;器件地址 8 位写法(手册常给 8 位,右移 1 位)
OLED 不亮但扫描到 0x3C地址对、初始化错(库参数 ssd1306_128x64 vs 72)
UART 收到乱码波特率不匹配;TX/RX 没交叉
SPI 屏白屏MODE 不对;CS 没拉低;速度超器件上限
两个 I2C 模块冲突地址相同——有的模块可焊改地址(0x76/0x77)
I2C 偶发卡死总线被干扰/上拉太弱;库卡住时 9 次时钟脉冲解锁(进阶技巧)
SPI 挂两个设备每个从机独立 CS,SCK/MOSI/MISO 共享

六、延伸阅读


版权声明:本文采用 CC BY-SA 4.0 协议授权。转载与二创请注明出处:ESPDIY 创客工坊 - espdiy.com

ESP32 Communication Buses Guide: UART, I2C and SPI

Why buses exist: GPIO/ADC only “read voltages” — but OLED screens, sensor chips and GPS modules have minds of their own. They converse through buses. Picking the right bus is the first decision of any peripheral project.


1. The three buses at a glance

UART (two-wire chat):     I2C (one-master bus):       SPI (high-speed dedicated):
ESP32 ──TX──> RX── device  ESP32 ──SDA── many devices  ESP32 ──MOSI──> device
      <──RX<── TX──           └──SCL── (shared 2 wires)      <──MISO<──
                           each slave has a 7-bit addr  ──SCK── + one CS per slave
AspectUARTI2CSPI
Wires2 (TX/RX)2 (SDA/SCL)4+ (MOSI/MISO/SCK/CS)
Speedcommonly ≤115.2kbps100k/400kHzup to 80MHz
Devices per bus1-to-1Many slaves (addressed)Many slaves (one CS each)
Typical peripheralsGPS, BT modules, serial screensOLED, temp/humidity, IMUTFT screens, SD cards, flash
Pull-ups neededNoYes (4.7k–10k)No

Selection mnemonic: slow small parts → I2C (saves pins); screens and storage → SPI (bandwidth); module conversations → UART (ready-made protocol).


2. UART: more than the debug port

Serial is just the default UART. The ESP32 has three hardware UARTs; Serial1/Serial2 can be remapped to any GPIO:

// GPS on Serial2: RX16 ← GPS TX, TX17 → GPS RX
#define GPS_RX 16
#define GPS_TX 17

HardwareSerial GPSSerial(2);   // using UART2

void setup() {
  Serial.begin(115200);              // debug (UART0)
  GPSSerial.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX);
}

void loop() {
  while (GPSSerial.available()) {
    Serial.write(GPSSerial.read());  // pipe NMEA sentences through
  }
}

Three classic mistakes:

  1. Cross wiring: ESP32 TX goes to the peer’s RX (mouth to ear, like a phone call)
  2. Baud rates must match on both ends (GPS commonly 9600; BT 9600/115200)
  3. SERIAL_8N1 = 8 data bits, no parity, 1 stop bit — the default of nearly everything

The interrupts “heavy work in loop” principle applies: UART data arrival is an event — buffer, then parse in loop.


3. I2C: a pile of sensors on two wires

1. Master-slave and 7-bit addresses

I2C is a one-master bus: the ESP32 initiates everything; slaves answer to factory-hardwired 7-bit addresses. Common ones: SSD1306 OLED = 0x3C, BMP280 = 0x76/0x77, MCP4725 DAC = 0x62.

2. Run the scanner first (step one for any new module)

#include <Wire.h>

void setup() {
  Wire.begin(21, 22);   // SDA=21, SCL=22 (remappable)
  Serial.begin(115200);
}

void loop() {
  Serial.println("Scanning I2C bus...");
  for (uint8_t addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {     // 0 = a device ACKed
      Serial.printf("Device found: 0x%02X\n", addr);
    }
  }
  delay(5000);
}

Nothing found = wiring/pull-up trouble (see the cheat sheet). Fix that before touching the protocol.

3. Reading temperature (raw Wire conversation)

// AHT20 (address 0x38), master flow shown
Wire.beginTransmission(0x38);
Wire.write(0xAC); Wire.write(0x33); Wire.write(0x00);   // trigger measurement
Wire.endTransmission();
delay(80);                                              // conversion time

Wire.requestFrom(0x38, 7);                              // read 7 bytes
uint8_t data[7];
for (int i = 0; i < 7; i++) data[i] = Wire.read();
// convert data[1..6] per the datasheet formulas

In practice you’ll use a sensor library (Adafruit_AHTX0 etc.) — but understanding this raw conversation tells you what to check when the library misbehaves.

4. Pull-up resistors

SDA/SCL are open-drain — high levels only exist with pull-ups:

  • Module boards with onboard 10kΩ ✅ (practically all Taobao modules)
  • Bare chips: add 4.7kΩ ×2 to 3.3V yourself
  • Multiple modules on one bus: pulls parallel to smaller values (two 10k = 5k) — usually fine; only trim when stacking many

4. SPI: the speed king for screens and storage

A color TFT moves hundreds of KB per frame — I2C’s 400kHz chokes. SPI trades four wires for bandwidth:

SignalDirectionRole
MOSImaster→slavedata (Master Out Slave In)
MISOslave→masterdata
SCKmaster→slaveclock (sets the speed)
CSmaster→slavechip select (which slave is active)
#include <SPI.h>

// VSPI defaults: SCK=18, MISO=19, MOSI=23, CS=5
SPIClass SPI(VSPI);
#define TFT_CS 5

void setup() {
  SPI.begin(18, 19, 23, TFT_CS);
  SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0));  // 40MHz
  digitalWrite(TFT_CS, LOW);
  SPI.transfer16(0xABCD);   // send 16 bits
  digitalWrite(TFT_CS, HIGH);
}

CPOL/CPHA: the four SPI modes

Clock polarity (CPOL) and phase (CPHA) combine into 4 modes that must match the slave:

ModeCPOLCPHAIdle levelSampling edge
MODE000lowrising
MODE101lowfalling
MODE210highfalling
MODE311highrising

A white/garbled screen or an SD init failure: check the mode first (datasheet “SPI timing”), then the wiring. Most devices are MODE0 or MODE3.

Full TFT practice (TFT_eSPI configuration) is an L3 display topic — here you only need the underlying layer.


5. Pitfall cheat sheet (bookmark this)

SymptomCause and fix
I2C scan finds nothingSDA/SCL swapped; no pull-ups; datasheet gave an 8-bit address (shift right by 1)
OLED dark but scanner sees 0x3CRight address, wrong init (library: ssd1306_128x64 vs 72)
UART garbageBaud mismatch; TX/RX not crossed
SPI screen stays whiteWrong MODE; CS not asserted; speed beyond the device
Two I2C modules clashSame address — some modules are solder-configurable (0x76/0x77)
I2C occasionally hangsNoise/weak pull-ups; advanced: 9-clock-bus unstick trick
Two devices on SPIIndependent CS per slave; SCK/MOSI/MISO shared

6. Further reading


License: CC BY-SA 4.0. Repost & remix with attribution: ESPDIY Maker Lab - espdiy.com