Шины I2C, SPI и UART: чем отличаются и что выбрать
Выводов у микроконтроллера мало, а датчиков хочется много. Шины решают именно эту задачу: по двум-четырём проводам можно поговорить с десятком устройств. Три шины покрывают почти всю любительскую электронику — I2C, SPI и UART. Разбираем, чем они отличаются, какие выводы занимают и что делать, когда датчик не отвечает.
Сравнение
| I2C | SPI | UART | |
|---|---|---|---|
| Проводов (кроме питания) | 2 | 4 и по одному на устройство | 2 |
| Устройств на шине | 112 адресов | сколько хватит выводов | одно |
| Скорость | 100–400 кГц | десятки МГц | обычно до 115 200 бод |
| Обмен | по очереди | одновременно в обе стороны | одновременно в обе стороны |
| Синхронизация | тактовый провод | тактовый провод | по скорости, заранее |
| Нужны резисторы | да, подтяжки | нет | нет |
Практическое правило: I2C — когда устройств много, а скорость не важна. SPI — когда нужна скорость. UART — когда устройство одно и оно «говорит» само.
I2C: два провода на всех
I2C использует всего две линии: SDA для данных и SCL для тактов. Все устройства
висят на них параллельно, а различаются адресом — числом от 0x08 до 0x77, зашитым
производителем.
#include <Wire.h>
const uint8_t sensorAddress = 0x76;
void setup() {
Serial.begin(115200);
Wire.begin(); // на ESP32 можно указать выводы: Wire.begin(21, 22)
Wire.beginTransmission(sensorAddress);
Wire.write(0xD0); // адрес регистра внутри датчика
Wire.endTransmission();
Wire.requestFrom(sensorAddress, (uint8_t) 1);
if (Wire.available()) {
Serial.println(Wire.read(), HEX);
}
}
void loop() {
}
Схема обмена почти всегда одинакова: открыть передачу на адрес, написать номер регистра, закрыть передачу, запросить нужное число байтов, прочитать.
Выводы I2C
| Плата | SDA | SCL |
|---|---|---|
| Arduino UNO, Nano | A4 | A5 |
| Arduino Mega 2560 | 20 | 21 |
| Классический ESP32 (по умолчанию в Arduino) | GPIO 21 | GPIO 22 |
У ESP32 аппаратных выводов I2C нет вообще: контроллер маршрутизируется через
коммутационную матрицу на любые свободные пины. Пара 21 и 22 — это значение по
умолчанию ядра Arduino, а не свойство чипа. Задать свои можно прямо в begin():
Wire.begin(18, 19). Какие выводы свободны — в статье Пины ESP32.
Подтягивающие резисторы
Это то, на чём чаще всего спотыкаются. Линии I2C устроены так, что устройства умеют только «прижимать» их к земле, а поднимать обратно к питанию должны резисторы — обычно 4,7 кОм на каждую линию.
Без них шина не работает вовсе. Хорошая новость: почти на всех готовых модулях с AliExpress эти резисторы уже стоят. Плохая: если повесить на шину пять таких модулей, пять пар резисторов окажутся включены параллельно, суммарное сопротивление упадёт до килоома, и шина начнёт сбоить. При большом числе устройств лишние подтяжки выпаивают, оставляя одну пару.
Сканер адресов
Первое, что стоит запустить при любой проблеме с I2C, — сканер. Он перебирает все адреса и печатает те, что ответили:
#include <Wire.h>
void setup() {
Serial.begin(115200);
Wire.begin();
Serial.println("Сканирую шину I2C…");
uint8_t found = 0;
for (uint8_t address = 1; address < 127; address++) {
Wire.beginTransmission(address);
if (Wire.endTransmission() == 0) {
Serial.print("Найдено устройство: 0x");
if (address < 16) Serial.print("0");
Serial.println(address, HEX);
found++;
}
}
if (found == 0) {
Serial.println("Ничего не найдено — проверьте провода и подтяжки");
}
}
void loop() {
}
Если сканер молчит — проблема в проводах, питании или подтяжках, и лезть в код датчика бессмысленно. Если адрес нашёлся, но не тот, что в документации, — почти наверняка дело в семибитной адресации, см. ниже.
Библиотека Wire работает с семибитными адресами, а в даташитах датчиков часто
приводят восьмибитный вариант — тот же адрес, сдвинутый влево на один бит, и обычно
два числа: для чтения и для записи. Если в документации 0xEC и 0xED, а сканер
нашёл 0x76 — это одно и то же устройство. Делите на два.
SPI: быстро, но проводов больше
SPI использует четыре линии: MOSI (от контроллера к устройству), MISO (обратно),
SCK (такты) и CS — выбор устройства. Первые три общие для всех, а CS нужен
свой на каждое: контроллер прижимает его к земле, обращаясь к конкретному чипу.
#include <SPI.h>
const uint8_t chipSelectPin = 5;
void setup() {
pinMode(chipSelectPin, OUTPUT);
digitalWrite(chipSelectPin, HIGH); // не выбрано
SPI.begin();
}
uint8_t readRegister(uint8_t address) {
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
digitalWrite(chipSelectPin, LOW);
SPI.transfer(address | 0x80); // старший бит — признак чтения у многих чипов
uint8_t value = SPI.transfer(0x00); // отправляем пустышку, получаем ответ
digitalWrite(chipSelectPin, HIGH);
SPI.endTransaction();
return value;
}
void loop() {
}
SPISettings задаёт три параметра: скорость в герцах, порядок битов и режим. Режимы
SPI_MODE0…SPI_MODE3 отличаются полярностью и фазой тактового сигнала — нужный
указан в даташите устройства. Если данные приходят как мусор, а провода точно верные,
первым делом пробуют сменить режим.
Выводы SPI
| Плата | MOSI | MISO | SCK | CS по умолчанию |
|---|---|---|---|---|
| Arduino UNO, Nano | 11 | 12 | 13 | 10 |
| Arduino Mega 2560 | 51 | 50 | 52 | 53 |
| Классический ESP32, шина VSPI | GPIO 23 | GPIO 19 | GPIO 18 | GPIO 5 |
| Классический ESP32, шина HSPI | GPIO 13 | GPIO 12 | GPIO 14 | GPIO 15 |
У классического ESP32 две свободные шины SPI, и их выводы, как и у I2C, переназначаются:
SPI.begin(18, 19, 23, 5); // SCK, MISO, MOSI, CS
Вывод CS библиотека не трогает — им управляют вручную через digitalWrite().
Именно поэтому в примере выше он поднимается и опускается явно.
UART: один на один
UART отличается от первых двух принципиально: у него нет тактового провода и нет
адресов. Два устройства просто договариваются о скорости заранее и шлют байты
навстречу друг другу по линиям TX и RX, перекрёстно — передача одного идёт на
приём другого.
Из-за отсутствия адресации на одной паре проводов живёт ровно одно устройство. Зато подключение предельно простое, и так работает большинство GPS-модулей, Bluetooth-модулей и датчиков качества воздуха.
У классического ESP32 свободны Serial1 и Serial2, выводы задаются в begin():
Serial2.begin(9600, SERIAL_8N1, 16, 17); // RX на GPIO 16, TX на GPIO 17
Подробный разбор — в отдельной статье про Serial-порт.
Как выбрать
Берите I2C, если устройств несколько, а данных мало: датчики температуры, давления, освещённости, часы реального времени, небольшие OLED-дисплеи, расширители портов. Два провода на всё — главный аргумент.
Берите SPI, если данных много: цветные дисплеи, карты памяти, радиомодули, быстрые АЦП. Разница в скорости не косметическая: обновление TFT-дисплея по I2C займёт секунды, по SPI — десятки миллисекунд.
UART достаётся без выбора — если модуль умеет только его, вопрос закрыт.
Комбинировать шины можно свободно: дисплей по SPI, датчики по I2C и GPS по UART одновременно — обычная ситуация, они друг другу не мешают.
Частые проблемы
Сканер I2C ничего не находит. Проверьте питание модуля (3,3 В или 5 В — смотрите
документацию), не перепутаны ли SDA и SCL, и есть ли подтяжки. На ESP32 убедитесь,
что выбранные выводы вообще свободны.
Адрес не совпадает с даташитом. Семь бит против восьми, см. врезку выше.
Устройство работало одно, перестало в паре. Совпали адреса. У многих модулей есть перемычка или площадка для смены адреса — обычно она меняет младший бит.
SPI отдаёт мусор. Неверный режим SPI_MODE, слишком высокая скорость или длинные
провода. Начните с 1 МГц и SPI_MODE0, потом ускоряйтесь.
Всё работает на макетке и сбоит в корпусе. Длина линий. I2C рассчитан на сантиметры, а не метры: на длинных проводах растёт ёмкость, фронты заваливаются. Для расстояний берите UART с преобразователем в RS-485.
Смешались уровни 3,3 и 5 вольт. ESP32 работает от 3,3 В, и подача пяти вольт на его вывод может повредить чип. Пятивольтовые модули подключают через преобразователь уровней.
Что дальше
- Какие выводы ESP32 вообще свободны — Пины ESP32.
- Обмен по UART подробно — Serial-порт.
- Установка библиотек для датчиков — Библиотеки Arduino IDE.