Интерфейс SPI: устройство шины и работа с ней в Arduino

Самый быстрый из интерфейсов Arduino: линии MOSI, MISO, SCK и SS, режимы работы, библиотека SPI и два практических примера.
Для чего нужен SPI
SPI — шина для подключения периферийных устройств. Она связывает одно ведущее устройство (Master) с одним или несколькими ведомыми (Slave). Ведущий всегда один: он управляет обменом и только он формирует тактовые импульсы. В проектах на Arduino эту роль выполняет микроконтроллер, а ведомыми обычно оказываются датчики, дисплеи, микросхемы ЦАП и АЦП, RFID-считыватели, модули беспроводной связи. Для связи контроллеров между собой интерфейс применяют редко, хотя это возможно.
SPI — самый быстрый из доступных на Arduino интерфейсов и самый нетребовательный к ресурсам. Плата за это — количество проводов. Только для данных их три:
- MOSI (Master Output Slave Input) — ведущий передаёт, ведомый принимает;
- MISO (Master Input Slave Output) — ведущий принимает, ведомый передаёт;
- SCLK, или SCK (Serial Clock) — тактовый сигнал.
От UART здесь две независимые линии на приём и передачу, от I2C — общий тактовый сигнал. Адресов у ведомых нет, поэтому добавлена ещё одна линия — SS (Slave Select). Каждый ведомый следит за своим выводом SS: низкий уровень означает, что ведущий обращается именно к нему.
Три схемы подключения
Если ведомый один, одноимённые выводы просто соединяются напрямую. Классический вариант для нескольких устройств — параллельное включение: MISO, MOSI и SCK общие, а линия SS к каждому ведомому своя. Схема понятная, но на каждое устройство уходит отдельный пин контроллера.

Третий способ — цепочка, или кольцо. Линия SS одна на всех, а данные проходят сквозь устройства от одного к другому. Пока на SS низкий уровень, данные распределяются по местам, после подъёма уровня устройства начинают их обрабатывать. Пинов требуется меньше, но сквозную передачу поддерживают далеко не все устройства.

Режимы, сильные и слабые стороны
Протокол простой, и вариантов у него немного. Режим задают два параметра: CPOL — исходный уровень тактового сигнала (0 — низкий, 1 — высокий) и CPHA — фаза синхронизации, то есть на каком фронте импульса данные считываются, а на каком устанавливаются. При CPHA = 0 считывание идёт по переднему фронту, установка по заднему, при 1 — наоборот. Все устройства на шине должны работать в одном режиме.
В сравнении с I2C у SPI выше скорость: она может достигать десятков мегагерц, что позволяет передавать большие объёмы данных потоком. Все линии однонаправленные, поэтому проще преобразовывать уровни, проста и программная реализация. Недостатки — больше проводов и пинов, причём их число растёт с числом устройств, и меньшая стандартизация: есть риск встретить устройства, работающие в разных режимах.
Библиотека SPI и выводы плат
Большинство SPI-модулей для Arduino поставляются с собственными библиотеками, так что напрямую с шиной программист работает редко. Встроенная библиотека SPI невелика и рассчитана в основном на ведущее устройство. Основные её функции:
- begin() — запускает аппаратную шину, сама настраивает режимы и уровни пинов;
- end() — отключает шину, настройка пинов при этом сохраняется;
- setBitOrder(order) — порядок битов в байте: LSBFIRST (младший первым) или MSBFIRST (старший первым);
- setClockDivider() — делитель частоты контроллера для тактовой частоты шины, аргумент SPI_CLOCK_DIVx, где x равен 2, 4, 8, 16, 32, 64 или 128;
- setDataMode(mode) — режим шины, аргумент SPI_MODEx с номером от 0 до 3;
- transfer() — обмен байтом; приём и передача происходят одновременно: in = SPI.transfer(out).

При параллельном подключении нескольких устройств для дополнительных линий SS годятся любые свободные пины. Кроме того, почти на всех платах Arduino есть шестиконтактный разъём ICSP, одинаковый независимо от модели. Основное его назначение — прошивка микроконтроллера программатором, например обновление загрузчика, но через него можно подключать и SPI-устройства или соединять две платы, в том числе разнотипные.

Пример: сдвиговый регистр 74HC595
При программном управлении регистром роль SS играет защёлка LATCH, роль MOSI — вход DATA, роль SCK — CLOCK. С аппаратным SPI скетч становится короче: после SPI.begin() в цикле на линии выбора выставляется низкий уровень, байт отправляется вызовом SPI.transfer(b), уровень возвращается в высокий, и единица в байте сдвигается на разряд. По выходам регистра бежит огонёк с шагом 100 мс, а контроллер немного разгружается, поскольку отправкой занимается аппаратный интерфейс.
Пример: две платы Mega
В примере две платы Mega соединены одноимёнными выводами SPI напрямую; на ведомой это MOSI — 51, MISO — 50, SCK — 52, SS — 53. К ведущей подключена кнопка на пине 7, на ведомой используется встроенный светодиод на пине 13.
Ведущий работает через библиотеку SPI.h: опрашивает кнопку каждые 100 мс и при нажатии отправляет 1, при отпускании 0, получая в ответ байт и печатая его в монитор порта. Ведомый обходится без библиотеки, которая слабо поддерживает эту роль. Он обнуляет регистр управления SPCR и включает SPI строкой SPCR = (1 << SPE), настраивает MISO на выход, а остальные линии на вход. Пока на SS низкий уровень, программа ждёт флаг SPIF в регистре SPSR, читает принятый байт из SPDR, выводит его на светодиод, увеличивает счётчик и записывает его в SPDR для отправки ведущему.
Ведомый не может передать что-либо, пока к нему не обратились. Простых выходов два: регулярно его опрашивать или провести от ведомого к ведущему дополнительный сигнальный провод.
Практические замечания
- На длинные дистанции SPI не рассчитан: расстояния исчисляются максимум метрами и зависят от проводов, экранирования, скорости тактирования и помех.
- Диод на линии MISO исправным устройствам не нужен. Он защищает от изделий с дефектом, которые не отключаются от MISO при высоком уровне на SS и создают коллизии; сам лишний сигнал диод при этом не устраняет.