Інтерфейс I2C добре підходить для підключення великої кількості пристроїв лише двома лініями. Але невисока швидкість передачі, напівдуплексний режим роботи, залежність від підтягуючих резисторів і чутливість до перешкод роблять його не найкращим вибором для задач, де важлива продуктивність і стабільність. Особливо це помітно при роботі з дисплеями, пам’яттю або високочастотними сенсорами.
Саме тут на сцену виходить SPI — інтерфейс, який жертвує простотою підключення заради швидкості та надійності. Він дозволяє передавати дані значно швидше, підтримує повнодуплексний режим і забезпечує більш передбачувану роботу без складних механізмів арбітражу шини. SPI стає логічним вибором у системах, де важлива кожна мілісекунда і немає місця для компромісів.
SPI (від англ. Serial Peripheral Interface) - це високошвидкісний, синхронний, повнодуплексний, послідовний протокол зв'язку. Він розрахований на роботу на коротких дистанціях (в межах пристрою, як UART та I2C). Працює на швидкостях 1MHz - 200MHz, в залежності від частоти таймера мікроконтролера. SPI-зв’язок зазвичай використовується для взаємодії з такими пристроями, як пам’ять (SD-карти, EEPROM, Flash), дисплеї (OLED), датчики, а також АЦП/ЦАП.

Для зв'язку використовується 4 лінії (дроти):
- MOSI (Master OUT, Slave IN) - лінія передачі даних від ведучого пристрою до веденого.
- MISO (Master IN, Slave OUT) - лінія передачі даних від веденого до ведучого.
- SCK (Serial Clock) - лінія сигналу синхронізації, генерується ведучим.
- CS/SS (Chip Select або Slave Select) - лінія вибору (активації) конкретного веденого пристрою.
На відміну від I2C, SPI не використовує адресацію для підтримки декількох ведених пристроїв. Натомість використовуються окремі лінії CS/SS — по одній для кожного пристрою.
Ведучий пристрій тримає всі лінії CS/SS у високому рівні (неактивними) і знижує рівень на потрібній лінії, щоб активувати відповідний ведений пристрій.
Режими SPI
SPI має 4 режими роботи, які визначаються двома параметрами:
- CPOL (Clock Polarity) — рівень тактового сигналу (SCK) у стані спокою.
- CPHA (Clock Phase) — на якому фронті сигналу зчитуються дані.
Комбінація цих параметрів утворює 4 режими:
| Режим | CPOL | CPHA |
| Mode 0 | 0 (станом спокою SCK вважається LOW) | 0 (дані зчитуються в момент зміни сигналу SCK з LOW на HIGH) |
| Mode 1 | 0 (станом спокою SCK вважається LOW) | 1 (дані зчитуються в момент зміни сигналу SCK з HIGH на LOW) |
| Mode 2 | 1 (станом спокою SCK вважається HIGH) | 0 (дані зчитуються в момент зміни сигналу SCK з LOW на HIGH) |
| Mode 3 | 1 (станом спокою SCK вважається HIGH) | 1 (дані зчитуються в момент зміни сигналу SCK з HIGH на LOW) |
Дуже важливо! Режим SPI має збігатися як у ведучого (Master), так і у веденого (Slave) пристрою. Якщо вони працюють у різних режимах, дані будуть зчитуватись у неправильний момент — у результаті будуть отримані некоректні значення або взагалі не буде відповіді.
На практиці все просто: кожен SPI-пристрій (датчик, дисплей, пам’ять) має в документації вказаний параметр SPI Mode (0–3). Потрібно лише встановити цей самий режим в мікроконтролері. Загалом, якщо після підключення пристрій не працює, перше, що варто перевірити — чи правильно встановлений режим SPI.
Передача даних
Ведучий пристрій (Master) встановлює лінію SS/CS у низький рівень (LOW), щоб розпочати обмін даними з конкретним веденим пристроєм (Slave).
SPI є повнодуплексним протоколом, тому і ведучий, і ведений пристрої одночасно передають і приймають дані побітово, синхронізуючись із тактовим сигналом.
Якщо ведучий пристрій хоче лише отримати дані від веденого, він повинен надсилати фіктивні (dummy) дані, щоб згенерувати відповідь.
Передача даних у SPI може відбуватись у двох напрямках: від старшого біта до молодшого (MSB → LSB) або від молодшого до старшого (LSB → MSB).
Робота з SPI в Arduino
Фреймворк Arduino має бібліотеку SPI.h для роботи з інтерфейсом SPI, яка має такі функції:
| Функції | Опис |
SPI.begin() |
Ініціалізує шину SPI, встановлюючи SCK, MOSI як виходи. |
SPI.beginTransaction(settings) |
Ініціалізує шину SPI. Зверніть увагу, що виклик SPI.begin() є обов’язковим перед викликом цього методу. |
SPISettings(speedMaximum, dataOrder, dataMode) |
Об’єкт SPISettings використовується з SPI.beginTransaction() для налаштування SPI. |
byte received = SPI.transfer(sentByte); |
Передає байт і одночасно приймає байт по SPI. |
SPI.endTransaction() |
Припиняє використання шини SPI. |

Код ведучого пристрою:
#include <SPI.h>const int SS1 = 10;const int SS2 = 9;void setup() {SPI.begin(); // запускаємо SPI як master// SS піни керують вибором slavepinMode(SS1, OUTPUT);pinMode(SS2, OUTPUT);// Вимикаємо всі slave (HIGH = неактивний)digitalWrite(SS1, HIGH);digitalWrite(SS2, HIGH);//Запускаємо Serial Monitor для виводу в консольSerial.begin(9600);}void loop() {// Обмін зі slave 1digitalWrite(SS1, LOW); // активуємо перший slave// SPI.transfer:// - відправляє байт// - одночасно читає байт у відповідьbyte response = SPI.transfer(0xA1);digitalWrite(SS1, HIGH); // деактивуємо slaveSerial.print("Відповідь від Slave1: ");Serial.println(response);delay(500);// Обмін зі slave 2digitalWrite(SS2, LOW); // активуємо другий slaveresponse = SPI.transfer(0xA1);digitalWrite(SS2, HIGH); // деактивуємо slaveSerial.print("Відповідь від Slave2: ");Serial.println(response);delay(500);}
Код веденого:
#include <SPI.h>// Останні отримані дані від mastervolatile byte received = 0;// Дані, які slave буде повертати mastervolatile byte toSend = 0x11;void setup() {pinMode(MISO, OUTPUT); // лінія передачі даних назад masterSPCR |= _BV(SPE); // вмикаємо SPI у режимі slaveSPI.attachInterrupt(); // дозволяємо обробку через переривання}// Викликається автоматично при кожному байті SPIISR(SPI_STC_vect) {received = SPDR; // читаємо що прийшло від master з регіструSPDR = toSend; // готуємо відповідь для master}void loop() {// просто змінюємо дані, які віддаємоtoSend++; // кожен цикл буде інше значення}
