Плавный график с ползущей развёрткой я так и не добил на I2C. Кадр — 1024 байта, на стандартных ста килогерцах шины он идёт около 92 мс, и даже после разгона до 400 кГц оставшиеся 23 мс на кадр давали максимум пару десятков обновлений в секунду. Для цифр хватает, для движущейся развёртки — нет: глаз честно видел слайд-шоу с дрожью. Хуже того, на тех же A4 и A5 висели часы DS3231, и во время передачи кадра их опрос вжимался в паузы между кадрами и начинал глохнуть.
Выход оказался скучным и старым как мир: убрать кадр с I2C. У SSD1306, кроме двухпроводной шины, есть режим SPI — последовательный интерфейс без адресов и подтверждений, где библиотека Adafruit крутит линию данных на 8 МГц. Восемь мегабит в секунду против четырёхсот тысяч у разогнанного I2C — разница в двадцать раз ещё до всякой оптимизации кода.
В этом уроке — цена перехода и сама процедура: семь проводов вместо четырёх, три новых пина управления, конструктор, в котором &Wire меняется на &SPI, и честный список случаев, когда эти три лишних провода окупаются, а когда они остаются просто проводами. Схема четырёхпроводного I2C-варианта уже разобрана в уроке про подключение по I2C — полезно держать её перед глазами, чтобы видеть, что именно меняется.
Семь проводов вместо четырёх
Провода данных едут на аппаратные пины SPI: MOSI — на D11, SCK — на D13 у Uno и Nano. Эти два пина не выбираются: кремний платы жёстко привязал их к периферии, и переназначить их, как это делали с пинами программной шины, здесь не выйдет. DC, CS и RESET — линии управления, их вешают на любые свободные цифровые пины; в моих схемах это D9, D8 и D10.
Роли у новых линий простые. DC сообщает контроллеру, что летит по линии данных: ноль — команда настройки, единица — байт картинки; на I2C эту работу делал служебный байт перед каждой посылкой, на SPI для неё выделили отдельный провод. CS выбирает слейв: адресов на SPI нет, кто услышал строб по своей CS, тот и отвечает. RESET дёргает контроллер при старте, но его можно не тянуть: минус единица в конструкторе ставит сброс общим с платой, и модуль просыпается вместе с Arduino. Я обычно пишу -1 и экономлю провод; в чужих примерах RST заведён на пин — если скетч с чужой разводкой молчит, сверьте аргументы конструктора с реальной схемой.
Пара неочевидных следствий. SCK сидит на D13 — там же, где встроенный светодиод платы: при активной передаче он подмигивает, и это бесплатный индикатор жизни шины, а не глюк. И второе: SCK с MOSI общие для всех устройств шины — к тем же D11 и D13 можно посадить SD-карту или второй экран, каждому свой CS. Семь проводов — цена входа, а не цена каждого устройства: четвёртый и пятый модуль добавят по одному проводу выбора.
| Линии | I2C-модуль (4 провода) | SPI-модуль (7 проводов) |
|---|---|---|
| Питание | VCC и GND — два провода | VCC и GND — те же два провода |
| Данные | SDA и SCL — на A4 и A5 | MOSI на D11, SCK на D13 — аппаратные, без выбора |
| Управление | не нужно — адрес в каждом пакете | DC на D9, CS на D10, RESET на D8 или -1 |
| Итог | 4 провода, A4/A5 заняты шиной | 7 проводов, A4/A5 свободны для датчиков |
Код меняется ровно в одном месте — в конструкторе. Было Adafruit_SSD1306 display(128, 64, &Wire, -1); стало Adafruit_SSD1306 display(128, 64, &SPI, 9, 8, 10); — шина, затем пин DC, пин RESET, пин CS. begin() для SPI адреса не требует: слейва выбирает CS, а не адресный байт, поэтому достаточно begin(SSD1306_SWITCHCAPVCC). Всё остальное — clearDisplay, setTextSize, print, display() — без единого изменения: конвейер кадра из урока про буфер и display() остаётся тем же до байта.
Арифметика победы: кадр — 8192 бита, на скорости 8 МГц это около миллисекунды чистой передачи. Против 92 мс у I2C на 100 кГц и 23 мс на 400 кГц. Даже с надбавкой за служебные посылки между командами провод отдаёт кадр в девяносто раз быстрее стандартного I2C — вот куда уходит слайд-шоу из первой истории.
Условие, о котором честно: миллисекунда — это только провод. На Uno следующим ограничителем становится не шина, а отрисовка в буфер и сам скетч: пока процессор рисует зоны, кадр никуда не едет. Поэтому в живых интерфейсах SPI даёт не тысячи кадров, а десятки — их глазу хватает с запасом; куда именно упирается ваш скетч, покажет замер из следующего урока. Есть и второй подарок: на I2C display() стоит блокирующей паузой, девяносто миллисекунд шина глотает кадр, и опрос кнопки, зажатый после него, отзывается с запозданием. SPI-посылка живёт миллисекунду, и цикл остаётся отзывчивым даже при частых кадрах.
#include <SPI.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h> // Аппаратный SPI: MOSI -> D11, SCK -> D13 (на Uno и Nano других нет)// Линии управления — любые свободные цифровые пины#define PIN_DC 9#define PIN_RST 8#define PIN_CS 10 Adafruit_SSD1306 display(128, 64, &SPI, PIN_DC, PIN_RST, PIN_CS); void setup() { // Адрес не нужен: слейва на SPI выбирает CS, а не байт адреса display.begin(SSD1306_SWITCHCAPVCC); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println("SPI 128x64"); display.println("--------------------"); display.setCursor(0, 20); display.println("MOSI -> D11"); display.println("SCK -> D13"); display.println("DC/RST/CS -> 9/8/10"); display.display();} void loop() {}Когда SPI оправдан, а когда — лишние провода
Моё правило по умолчанию: дашборд с цифрами и текстом живёт на I2C. Обновление раз в 100–250 мс — это 4–10 кадров в секунду, шине такие темпы доступны даже без разгона, а четыре провода и свобода D9–D13 стоят дороже скорости, которую глаз всё равно не увидит. Если экран один на шине и показывает температуру раз в секунду — SPI я не беру, и вы не берите. Граница у меня проходит так: если экран обновляется реже пяти раз в секунду и соседей на шине нет, SPI не нужен даже в проектах с претензией на скорость.
SPI появляется по двум причинам, и обе проверяемы числами. Первая — темп: анимация, осциллограф, игра, ползущая развёртка — всё, где нужно больше 15–20 кадров в секунду; на 400 кГц I2C балансирует на границе, SPI проходит её с запасом. Вторая — соседи: если на A4 и A5 уже сидят RTC, датчики и второй дисплей, кадр OLED выжимает из шины почти всё время между их опросами, и увод экрана на SPI освобождает I2C целиком. Про совместную жизнь устройств на двух проводах говорили в уроке про I2C, про дисциплину цветных проводов на макетке — в разборе перемычек.
Что SPI не даёт: экономии памяти. Буфер кадра — те же 1024 байта в ОЗУ платы при любой шине, половина SRAM Uno уходит и с I2C, и с SPI. И на батарейном узле шина не главный расход: пиксели едят ток независимо от того, как быстро до них добрались байты, экономия заряда достигается гашением экрана в простое, о чём шла речь в уроке про графику. Честный выигрыш SPI в другом: процессор освобождается сразу после display() и успевает делать настоящую работу — тайминги которой держатся на millis вместо delay.
- SPI-вариант SSD1306 — 7 проводов против 4: данные едут по аппаратным D11 (MOSI) и D13 (SCK), управление — DC, CS, RESET на любых цифровых
- Конструктор: Adafruit_SSD1306 display(128, 64, &SPI, DC, RST, CS); begin без адреса — слейва выбирает CS, а не байт адреса
- Кадр 8192 бита на 8 МГц — около 1 мс передачи против 92 мс у I2C на 100 кГц; упор смещается в отрисовку, а не в шину
- SPI оправдан при темпе выше 15–20 кадров в секунду или при занятом I2C; дашборду с обновлением раз в 100–250 мс хватает I2C
- Память не экономится: буфер 1024 байта при любой шине; экономится время процессора и шины
- SCK на D13 подмигивает встроенным светодиодом при передаче — индикатор жизни шины, а не неисправность
- 1. Сколько проводов занимает OLED 128x64 по SPI, если RESET выведен на пин?
- 2. Какие пины Uno нельзя выбрать иначе под аппаратный SPI?
- 3. Что меняется в конструкторе при переходе с I2C на SPI?
- 4. Зачем нужна линия DC?
- 5. Экран показывает температуру раз в 200 мс, шина I2C свободна. Что советую я?
Теперь у раздела есть обе шины: I2C для спокойных дашбордов и SPI для быстрых экранов и занятых шин. Следующий шаг — числа: соберём один скетч-замерщик FPS и прогоним его через обе шины, чтобы спор «I2C против SPI» решать цифрами со своего стенда, а не чужими мнениями с чужих плат.
Домашнее задание: посчитайте пины своего модуля — четыре или семь. Если семь, соберите SPI-версию каркаса из урока, подставив свои пины DC, RESET и CS, и добейтесь тестовых строк на экране; при каждом кадре светодиод на D13 будет подмигивать — так сигналит ваша новая шина. Сравните скорость на глаз с I2C-версией того же каркаса: на статичном экране разницы вы не увидите, и это полезный замер сам по себе — SPI покупают под темп, а не под текст.
Частые вопросы по теме урока
- Сколько проводов занимает OLED 128x64 по SPI, если RESET выведен на пин?
- VCC, GND, MOSI, SCK и три линии управления: DC, CS, RESET. С общим сбросом (аргумент -1) проводов шесть. Семь: питание, два данных, три управления
- Какие пины Uno нельзя выбрать иначе под аппаратный SPI?
- MOSI и SCK жёстко привязаны к периферии платы: D11 и D13 у Uno и Nano. Выбираются только линии DC, CS и RESET. MOSI на D11 и SCK на D13
- Что меняется в конструкторе при переходе с I2C на SPI?
- Adafruit_SSD1306 display(128, 64, &SPI, DC, RST, CS) — и begin без адреса, потому что слейва выбирает линия CS. &Wire заменяется на &SPI и добавляются пины DC, RESET, CS
- Зачем нужна линия DC?
- DC различает команду настройки и байт картинки. На I2C эту работу делал служебный байт, на SPI для неё выделен отдельный провод. Отмечает, команда идёт или данные
- Экран показывает температуру раз в 200 мс, шина I2C свободна. Что советую я?
- Моё правило по умолчанию: дашборд живёт на I2C. SPI покупают под темп выше 15–20 кадров в секунду или под занятую шину. Оставить I2C: темпа в 4–5 кадров в секунду хватает, а три провода остаются свободными