SPI (Serial Peripheral Interface) - интерфейс связи между цифровыми устройствами, по нему МК может общаться с датчиками и модулями или с другими МК. Среди основных интерфейсов связи (UART, I2C, SPI) SPI - самый простой, самый быстрый и неприхотливый, но требует больше пинов для подключения.
- Синхронный (есть тактовая линия)
- Последовательный (данные передаются бит за битом по одному проводу)
- Количество пинов: от 2 до 4
- Скорость: зависит от устройства и качества линии, от 0 Гц до 150 МГц
- Топология: одно ведущее устройство
- Количество ведомых на шине: не ограничено
Основы #
Роли #
SPI является шиной, т.е. к одним и тем же выводам может быть подключено несколько устройств. На шине есть только одно ведущее устройство - Master, остальные - ведомые, Slave. Ведущее выбирает, с каким из ведомых взаимодействовать, может отправлять и читать с него данные. Ведущим обычно является основной МК в схеме, а остальные - различные цифровые микросхемы, датчики или вспомогательные МК.
В современной литературе используется другая терминология: Controller и Peripheral. Уход от классических Master (хозяин) и Slave (раб) был связан со слишком близкой аналогией с работой на плантациях
Пины #
SPI использует максимум 4 пина для подключения. Рассмотрим их функции:
MOSI(Master Out Slave In) - ведущее устройство отправляет данные, ведомое принимает. На "модуле" может быть подписан как SDI, DI, DIN, SIMISO(Master In Slave Out) - ведомое устройство отправляет данные, ведущее принимает. На "модуле" может быть подписан как SDO, DO, DON, SOSS(Slave Select) - выбор ведомого устройства, сигналом управляет мастер. На "модуле" может быть подписан как CS (Chip Select)SCK(Serial Clock) - тактирование (клок, импульсы синхронизации), сигналом управляет мастер. На "модуле" может быть подписан как SCLK, CLK, SPC
"Режимы работы" пинов на устройствах:
| Пин | Master | Slave |
|---|---|---|
MISO |
INPUT |
OUTPUT |
MOSI |
OUTPUT |
INPUT |
SCK |
OUTPUT |
INPUT |
SS |
OUTPUT |
INPUT |
Подключение #
SPI - очень примитивный интерфейс, поэтому может подключаться на любые цифровые пины МК и работать в "ручном режиме" или при помощи функций типа shiftIn() и shiftOut() - опять же это программный SPI. Многие МК имеют и аппаратный SPI, который обычно в разы быстрее программной реализации, а также может отправлять данные асинхронно.
SPI, как шина, позволяет подключать сколько угодно устройств на одни и те же пины: объединять можно все пины, кроме CS, т.к. при помощи него выбирается активное устройство.
На модуле или микросхеме может быть неполный набор пинов, но их будет как минимум два - дата (MOSI или MISO) и клок (SCK), если микросхема передаёт данные только в одну сторону.
На некоторых микросхемах и китайских модулях отсутствует пин CS - такие модули нельзя подключать на одни пины (MISO-MOSI-SCK) с остальными - они не смогут корректно работать, т.к. "активны" по умолчанию и будут реагировать на передачи для соседей
Расположение аппаратных выводов SPI микроконтроллера или отладочной платы всегда указано на распиновке. Рассмотрим подключение нескольких условных модулей к аппаратному SPI на Arduino Nano:
На распиновке можно увидеть также пин SS - на тот случай, если Nano будет являться ведомым устройством
Здесь есть все три варианта:
DEVICE1- только принимает данныеDEVICE2- только отправляет данныеDEVICE3- принимает и отправляет данные
Процесс передачи #
Процесс передачи начинается с выбора активного ведомого устройства - для этого его пин SS обычно подключается к GND (подаётся низкий сигнал, LOW). То есть в остальное время на пине должен быть высокий сигнал, начиная с запуска МК.
После выбора начинается передача: на дата-пинах появляется логический уровень, соответствующий передаваемому биту. После этого мастер меняет сигнал на пине SCK - ведомое устройство видит его и читает данные от мастера на пине MOSI, а мастер читает данные с пина MISO. Сигнал на SCK снова меняется, в это время устройства выводят на свои дата-пины следующий бит.
Процесс повторяется до тех пор, пока мастер не "отпустит" SS. Сколько бит будет передано или получено - зависит от конкретного устройства и программы на мастере, но в большинстве случаев кратно 8, т.е. идёт обмен байтами.
Таким образом, передача идёт одновременно в обе стороны. На практике такое встречается редко, обычно устройство или читает, или отвечает, даже если имеет оба дата-пина
Режимы SPI #
На самом деле всё чуть сложнее, чем написано выше - есть 4 варианта, что считать за "изменение сигнала" на пине SCK: начальный уровень клока (Clock Polarity, CPOL), а также работа по его восходящему или нисходящему фронту (Clock Phase, CPHA). Режимы имеют общепринятые номера от 0 до 3, в Arduino это константы SPI_MODEn:
| Режим | CPOL | CPHA | Вывод данных | Чтение |
|---|---|---|---|---|
SPI_MODE0 |
0 | 0 | SCK ↓ или SS | SCK ↑ |
SPI_MODE1 |
0 | 1 | SCK ↑ | SCK ↓ |
SPI_MODE2 |
1 | 0 | SCK ↑ или SS | SCK ↓ |
SPI_MODE3 |
1 | 1 | SCK ↓ | SCK ↑ |
Самым распространённым является первый режим - SPI_MODE0, а иногда устройства поддерживают сразу SPI_MODE0 и SPI_MODE3
Arduino Shift #
Полное описание смотри в справочнике
В фреймворке Arduino есть две функции, которые позволяют отправить и прочитать байт данных при помощи программного SPI, т.е. с любых цифровых пинов. Это удобно, когда не нужна высокая скорость передачи, неудобно использовать пины аппаратной шины или устройство не имеет пина CS и не может работать на основной шине вместе с остальными:
uint8_t shiftIn(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder)- читает и возвращает 1 байт (8 бит) с дата-пинаdataPinи клок-пинаclockPinв порядкеbitOrdervoid shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val)- отправляет 1 байт (8 бит) значениеvalс дата-пинаdataPinи клок-пинаclockPinв порядкеbitOrder
Где bitOrder:
MSBFIRST- сначала старший битLSBFIRST- сначала младший бит
В большинстве устройств используется порядок MSBFIRST, уточнить можно в документации на конкретную микросхему
Обе функции работают в режиме SPI_MODE0, он не настраивается
Пример отправки #
В качестве примера возьмём сдвиговый регистр 74HC595, он позволит буквально увидеть передаваемые данные при помощи светодиодов.
| В наборе GyverKIT | Aliexpress | |
|---|---|---|
![]() |
START |
Купить |
Подключить всё это можно вот по такой схеме (подробнее - в уроке про 74HC595), также есть аналогичная схема в онлайн-симуляторе, вот ссылка на проект.
Вот программа, которая готовит пины к работе и отправляет биты 0b11001111 в чип, а он в свою очередь выводит их как напряжение на свои пины. Мы подключили к ним светодиоды, так что "увидим" переданные данные:
#define CS_595 10
#define DAT_595 11
#define CLK_595 13
void sendByte(uint8_t data) {
digitalWrite(CS_595, LOW); // выбрать активным
shiftOut(DAT_595, CLK_595, MSBFIRST, data); // отправить
digitalWrite(CS_595, HIGH); // деактивировать
}
void setup() {
// пины как выходы
pinMode(CS_595, OUTPUT);
pinMode(DAT_595, OUTPUT);
pinMode(CLK_595, OUTPUT);
// деактивировать
digitalWrite(CS_595, HIGH);
delay(100);
// отправить
sendByte(0b11001111);
}
void loop() {
}
Результат:
Если сменить режим на LSBFIRST, то светодиоды загорятся в обратном порядке:
Вот реальный сигнал с пинов, снятый при помощи логического анализатора. Данные передавались в режиме MSBFIRST:
Скорость #
Частота тактирования (и передачи данных) у этих функций по моим тестам следующая:
| Платформа | Частота, MHz |
|---|---|
| AVR | 0.06 |
| ESP8266 | 0.2 |
| ESP32 | 0.96 |
| ESP32C3 | 0.35 |
Что в десятки раз ниже скорости аппаратного SPI на этих же платформах, например на Arduino Nano можно передавать с частотой до 8 MHz!
В моей библиотеке GyverIO есть ускоренные версии этих функций для всех платформ. Если вам нужен быстрый программный SPI - используйте её. Например, на Arduino Nano частота составляет 1.3 MHz, что в 22 раза быстрее стандартных функций
Библиотека SPI.h #
Полное описание смотри в справочнике
В фреймворке Arduino есть инструмент для работы с аппаратным SPI - библиотека SPI.h, она позволяет отправлять данные с более высокой скоростью, а также с возможностью настроить её и остальные параметры шины. Рассмотрим минимальный пример:
#include <SPI.h>
void setup() {
// запустить шину
SPI.begin();
// активировать CS ведомого
// начать передачу с параметрами 1 МГц, MSBFIRST, SPI_MODE0
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
// передать байты
SPI.transfer(123);
SPI.transfer(0b10100101);
// массив тоже можно
uint8_t data[] = {0xab, 0xcd};
SPI.transfer(data, 2);
// закончить передачу
SPI.endTransaction();
// деактивировать CS ведомого
// остановить шину (необязательно)
SPI.end();
}
void loop() {}
Методы beginTransaction() и endTransaction() позволяют нескольким SPI-устройствам безопасно использовать шину с разными настройками
Примечание (есть в справочнике, но продублирую здесь):
- Вызов
SPI.begin()переводит пины SCK и MOSI в режимOUTPUT, при запущенной шине они не подчиняются функцииdigitalWrite(). Пин SS переводится вOUTPUTс высоким сигналом и его можно использоваться как выход в своих целях, но нельзя переключать в режим входа - при низком сигнале на SS в режиме входа SPI переключится в режим Slave и перестанет работать - Вызов
SPI.end()не меняет режим пинов шины - Если все три параметра
SPISettings- константы, то эффективнее вызывать начало передачи сразу с конструктором настроек:SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));, как в примере выше - Если хотя бы один - не константа, то эффективнее создать объект
SPISettingsотдельно и передать его вbeginTransaction():
uint32_t freq; // = ...
SPISettings spiset(freq, MSBFIRST, SPI_MODE0);
SPI.beginTransaction(spiset);
Чтение #
Для чтения данных нужно получить их как результат функции transfer(uint8_t), т.е. для чтения нужно в любом случае вызвать передачу с любыми данными. Например uint8_t read = SPI.transfer(0); - мы передаём устройству 0 (оно его игнорирует, если мы знаем что делаем), а в ответ получаем от него байт данных или 0, если оно ничего не ответило.
Второй вариант - при отправке массива transfer(uint8_t*, size_t) библиотека записывает в него ответ устройства, т.е. перезаписывает наши данные! Бывают ситуации, когда они нам всё ещё нужны, например буфер кадра дисплея. Даже если устройство ничего не ответит, если провод не подключен - массив заполнится нулями. А если мы ожидаем ответ - то нужными данными: сразу после вызова transfer они будут доступны для использования.
Пример отправки #
Отправим тот же байт в ту же схему с 74HC595 и светодиодами, на этот раз при помощи аппаратного SPI. Результат на светодиодах будет таким же:
#include <Arduino.h>
#include <SPI.h>
#define CS_595 10 // пин CS, остальные на шине
void sendByte(uint8_t data) {
digitalWrite(CS_595, LOW); // выбрать активным
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); // начать
SPI.transfer(data); // отправить
SPI.endTransaction(); // закончить отправку
digitalWrite(CS_595, HIGH); // деактивировать
}
void setup() {
// запустить шину
SPI.begin();
// пин CS как выход
pinMode(CS_595, OUTPUT);
// деактивировать
digitalWrite(CS_595, HIGH);
delay(100);
sendByte(0b11001111);
}
void loop() {
}
Снимок с логического анализатора. Обратите внимание на время передачи, как оно уменьшилось по сравнению с shiftOut():
Как можно заметить, скорость очень сильно выросла - теперь её даже ограничивает медленный digitalWrite() на пине CS. Заменим его на быстрый аналог из GyverIO:
Гораздо лучше, теперь байт передался за 1.6 микросекунды!
Скорость #
Нужно быть аккуратнее с выбором частоты тактирования шины:
- Чем длиннее провода и хуже подключение, тем ниже должна быть скорость. Для подключения на макетной плате лучше не использовать выше 1 МГц
- Нижний порог по скорости обычно отсутствует, поэтому допустимый диапазон скоростей очень большой и можно пользоваться функциями типа
shiftOut(), которые сделаны буквально "на коленке" - Максимальная частота для устройства обычно указана в документации к нему. Например, тот же 74HC595 может работать на 25 MHz при питании от 4.5V и на 5 MHz от 2V. У драйвера матриц и дисплеев MAX7219 в документации указан минимальный CLK Clock Period 100 ns (наносекунд), т.е. максимальная частота - 10 МГц
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])
- Поддержать автора за работу над уроками










