I2C, SPI и UART: три способа, которыми модули говорят с Arduino
Кнопка и светодиод обходятся одним проводом: на нём либо есть напряжение, либо нет. Дисплей, гироскоп или модуль часов должны передать числа — а числа одним проводом не передашь. Для этого придуманы интерфейсы: договорённости о том, как по двум-четырём проводам гонять данные. В мире Arduino их три, и у каждого своя ниша.
Коротко: чем они отличаются
| I2C | SPI | UART | |
|---|---|---|---|
| Проводов | 2 сигнальных | 4 сигнальных | 2 сигнальных |
| Устройств на шине | До 127, по адресам | Сколько угодно, по проводу выбора на каждое | Два: точка-точка |
| Скорость | Низкая, 100–400 кбит/с | Высокая, мегабиты | Средняя, 9600–115200 бит/с |
| Типичные модули | OLED, часы, гироскоп, расширители | TFT-дисплей, SD-карта, радио nRF24, RFID | Bluetooth, GPS, GSM, второй Arduino |
I2C: два провода на всех
Линия данных SDA и линия тактов SCL, плюс питание и земля. На одну пару проводов вешается сколько угодно модулей — дисплей, датчик, часы, — и все они различаются адресами. Плата говорит: «адрес 0x3C, прими картинку», и отвечает только дисплей.
Это делает I2C любимым интерфейсом для модулей: четыре провода, и дисплей работает. На UNO и Nano шина живёт на A4 и A5, и пока модуль подключён, эти выводы для другого недоступны. Библиотека — встроенная Wire, но напрямую с ней почти не работают: у каждого модуля своя библиотека поверх.
Если модуль молчит, первым делом узнают его адрес. Скетч ниже перебирает все адреса и печатает, кто отозвался. Пусто — значит, дело в проводах или питании; адрес есть, но не тот, что в примере, — поправьте его в коде.
// Сканер I2C: печатает адреса всех модулей на шине
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
Serial.println("Ищу устройства на I2C...");
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print("Найден адрес 0x");
Serial.println(addr, HEX);
}
}
Serial.println("Готово");
}
void loop() {}SPI: быстро, но проводов больше
Четыре линии: MOSI — от платы к модулю, MISO — обратно, SCK — такты, и SS (он же CS) — выбор устройства. Адресов нет: чтобы обратиться к модулю, плата притягивает его провод выбора к земле. Два модуля — два провода выбора, остальные три линии общие.
Зато скорость в десятки раз выше, чем у I2C. Поэтому всё, что гонит много данных, сидит на SPI: цветные дисплеи, карты памяти, радиомодули. На UNO это выводы 10–13, и вывод 13 совпадает со встроенным светодиодом — он будет мерцать при передаче, это нормально.
Модули SPI чаще всего рассчитаны на 3,3 В: TFT-дисплеи, nRF24L01, RFID RC522. С UNO их питают от вывода 3.3V, а вот сигнальные линии с 5-вольтовых выводов формально им великоваты — обычно терпят, но преобразователь уровней надёжнее.
UART: последовательный порт
Самый старый и самый простой: провод туда, провод обратно, общая земля. Никаких адресов и тактов — обе стороны заранее договариваются о скорости, и если она не совпала, в монитор порта сыплется мусор. Отсюда цифра 9600 в Serial.begin: она должна быть одинаковой с обеих сторон.
По UART плата общается с компьютером через USB — на UNO это выводы 0 и 1, и занимать их модулем нельзя: загрузка скетча перестанет работать. Для Bluetooth или GPS берут любые два других вывода с библиотекой SoftwareSerial, а на Mega и ESP32 есть настоящие дополнительные порты.
Правило, которое путают все: TX платы идёт в RX модуля, и наоборот. Что один передаёт — другой принимает. Соединить одинаковые надписи — значит соединить два выхода, и связи не будет.
Где какой интерфейс на каждой плате
| Плата | I2C | SPI | UART |
|---|---|---|---|
| Arduino UNO | A4 (SDA), A5 (SCL) | 11 MOSI, 12 MISO, 13 SCK, 10 SS | 0 RX, 1 TX |
| Arduino Nano | A4, A5 | 11, 12, 13, 10 | 0, 1 |
| Arduino Mega | 20 (SDA), 21 (SCL) | 51 MOSI, 50 MISO, 52 SCK, 53 SS | 0/1, 19/18, 17/16, 15/14 |
| ESP32 | 21 (SDA), 22 (SCL) | 23 MOSI, 19 MISO, 18 SCK, 5 SS | 3 RX, 1 TX и ещё два |
| ESP8266 | D2 (SDA), D1 (SCL) | D7 MOSI, D6 MISO, D5 SCK, D8 SS | RX, TX |
На ESP32 это выводы по умолчанию — там любой интерфейс можно перевести на другие выводы в коде. На Arduino выводы жёсткие.
На чём попадаются
Два I2C-модуля с одним адресом
Два одинаковых OLED на одной шине откликаются оба, и оба показывают мусор. На части модулей адрес меняется перемычкой на обороте; если нет — второй вешают на программную шину на других выводах.
Длинные провода на I2C
Шина рассчитана на сантиметры, не метры. Полметра шлейфа — и модуль то виден, то нет. Укоротить провода или снизить скорость шины помогает почти всегда.
Модуль на выводах 0 и 1
Скетч перестал загружаться с ошибкой «not in sync». Bluetooth-модуль на аппаратном порту перебивает загрузчик. Отключите его на время прошивки или переведите на другие выводы.
Скорость не совпала
В мониторе порта иероглифы. Цифра внизу окна монитора и цифра в Serial.begin должны быть одинаковыми. У модулей своя скорость по умолчанию: у Bluetooth HC-05 чаще 38400, у GPS — 9600.
Что дальше
- OLED SSD1306: модуль на I2C со схемой
- TFT ILI9341: модуль на SPI со схемой
- Распиновка UNO: где какой интерфейс
Сканер I2C собран под Arduino UNO перед публикацией