Интерфейс SPI и Arduino
Изучаем интерфейс SPI и подключаем к Arduino сдвиговый регистр, к которому мы будем обращаться по этому протоколу для управления светодиодами.
Инструкция по работе Arduino с интерфейсом SPI
Нам понадобится:
- Arduino UNO или иная совместимая плата;
- сдвиговый регистр 74HC595;
- 8 светодиодов (к примеру, вот из такого набора);
- 8 резисторов по 220 Ом (рекомендую приобрести набор резисторов с номиналами от 10 Ом до 1 МОм);
- соединительные провода (например, вот такой хороший набор);
- макетная плата;
- персональный компьютер со средой разработки Arduino IDE.
1Описание последовательного интерфейса SPI
SPI – Serial Peripheral Interface или «Последовательный периферийный интерфейс» – это синхронный протокол передачи данных для сопряжения ведущего устройства (Master) с периферийными устройствами (Slave). Ведущим устройством часто является микроконтроллер. Связь между устройствами осуществляется по четырём проводам, поэтому SPI иногда называют «четырёхпроводной интерфейс». Вот эти шины:
Название | Назначение шины SPI |
---|---|
MOSI (Master Out Slave In) | линия передачи данных от ведущего к ведомым устройствам; |
MISO (Master In Slave Out) | линия передачи от ведомого к ведущему устройству; |
SCLK (Serial Clock) | тактовые импульсы синхронизации, генерируемые ведущим устройством; |
SS (Slave Select) | линия выбора ведомого устройства; когда на линии логический "0", ведомое устройство «понимает», что сейчас обращаются к нему. |
Существует четыре режима передачи данных (SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3), обусловленные сочетанием полярности тактовых импульсов (работаем по уровню HIGH или LOW), Clock Polarity, CPOL, и фазой тактовых импульсов (синхронизация по переднему или заднему фронту тактового импульса), Clock Phase, CPHA. В последнем столбце таблицы приведены поясняющие иллюстрации. На них Sample обозначены моменты, когда данные на линии должны быть готовы и считываются устройствами. Буквой Z отмечено, что состояние данных на линии неизвестно или не важно.
Режим | Полярность тактовых импульсов (CPOL) | Фаза тактовых импульсов (CPHA) | Диаграмма режима |
---|---|---|---|
SPI_MODE0 | 0 | 0 | |
SPI_MODE1 | 0 | 1 | |
SPI_MODE2 | 1 | 0 | |
SPI_MODE3 | 1 | 1 |
Интерфейс SPI предусматривает несколько вариантов подключения ведомых устройств: независимое и каскадное. При независимом подключении к шине SPI ведущее устройство обращается к каждому ведомому устройству индивидуально. При каскадном подключении ведомые устройства срабатывают поочерёдно, как бы каскадом.
2Реализация интерфейса SPI на платах семейства Arduino
В Arduino шины интерфейса SPI находятся на определённых портах. У каждой платы своё соответствие выводов. Для удобства выводы продублированы и вынесены также на отдельный разъём ICSP (In Circuit Serial Programming, программирование устройства, включённого в цепь, по последовательному протоколу). Обратите внимание, что на разъёме ICSP отсутствует пин выбора ведомого – SS, т.к. подразумевается, что Arduino будет использоваться как ведущее устройство в сети. Но при необходимости вы можете назначить любой цифровой вывод Ардуино в качестве SS.
На рисунке приведено стандартное соответствие выводов шинам SPI для Arduino UNO и Nano.
3Стандартная библиотека для работы по интерфейсу SPI
Для Arduino написана специальная библиотека, которая реализует протокол SPI. Она устанавливается вместе со средой разработки Arduino IDE. Подключается она так: в начале программы добавляем #include SPI.h.
Чтобы начать работу по протоколу SPI, нужно задать настройки и затем инициализировать протокол с помощью процедуры SPI.beginTransaction(). Можно выполнить это одной инструкцией: SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0))
Это значит, что мы инициализируем протокол SPI на частоте 14 МГц, передача данных идёт, начиная с MSB (наиболее значимого бита), в режиме SPI_MODE0.
После инициализации выбираем ведомое устройство, переводя соответствующий пин SS в состояние LOW. Затем передаём ведомому устройству данные командой SPI.transfer(). После передачи возвращаем SS в состояние HIGH.
Работа с протоколом завершается командой SPI.endTransaction().
Желательно минимизировать время выполнения передачи между инструкциями SPI.beginTransaction() и SPI.endTransaction(), чтобы не возникло накладок, если другое устройство попробует инициализировать передачу данных, используя другие настройки.
Если вы планируете в своём скетче использовать стандартные пины Arduino, можно не описывать их в начале программы, т.к. они уже определены в самой библиотеке и имеют следующие имена:
#define PIN_SPI_SS (10) #define PIN_SPI_MOSI (11) #define PIN_SPI_MISO (12) #define PIN_SPI_SCK (13)
Данные пины определены в файле pins_arduino.h , который находится по пути %programfiles%\arduino-(версия)\hardware\arduino\avr\variants\ (если вы устанавливали программу в стандартное расположение). То есть, например, чтобы опустить пин выбора ведомого в состояние "0", можно написать:
digitalWrite(PIN_SPI_SS, LOW);
4Подключение сдвигового регистра к Arduino
Рассмотрим практическое применение интерфейса SPI. Будем зажигать светодиоды, управляя 8-битным сдвиговым регистром по шине SPI. Подключим к Arduino сдвиговый регистр 74HC595. К каждому из 8-ми выходов регистра через ограничительный резистор подключим по светодиоду номиналом 220 Ом. Схема приводится на рисунке.
5Скетч для управления сдвиговым регистром по интерфейсу SPI
Напишем скетч, реализующий «бегущую волну», последовательно зажигая светодиоды, подключённые к выходам сдвигового регистра.
#include <SPI.h> const int pinSelect = 8; // пин выбора регистра void setup() { SPI.begin(); // инициализация интерфейса SPI pinMode(pinSelect, OUTPUT); // digitalWrite(pinSelect, LOW); // выбор ведомого устройств (регистра) SPI.transfer(0); // очищаем содержимое регистра digitalWrite(pinSelect, HIGH); // конец передачи Serial.begin(9600); } void loop() { for (int i=0; i<8; i++) { double L = pow(2, i); // вычисляем активный светодиод int leds = round(L); // округляем число до целого digitalWrite(pinSelect, LOW); // выбор регистра сдвига SPI.transfer(leds); // передаём значение в сдвиговый регистр digitalWrite(pinSelect, HIGH); // конец передачи Serial.println(leds, BIN); // вывод в двоичном представлении delay(125); // задержка 125 мс } Serial.println("--------"); }
Сначала подключим библиотеку SPI и инициализируем интерфейс SPI. Определим пин 8 как пин выбора ведомого устройства SS. Очистим сдвиговый регистр, послав в него значение "0". Инициализируем последовательный порт.
Чтобы зажечь определённый светодиод с помощью сдвигового регистра, нужно подать на его вход 8-разрядное число. Например, чтобы загорелся первый светодиод – подаём двоичное число 00000001, чтобы второй – 00000010, чтобы третий – 00000100, и т.д. Эти двоичные числа при переводе в десятичную систему счисления образуют такую последовательность: 1, 2, 4, 8, 16, 32, 64, 128 и являются степенями двойки от 0 до 7.
Соответственно, в цикле loop() по количеству светодиодов делаем пересчёт от 0 до 7. Функция pow(основание, степень) возводит 2 в степень счётчика цикла. Микроконтроллеры не очень точно работают с числами типа "double", поэтому для преобразования результата в целое число используем функцию округления round(). И передаём получившееся число в сдвиговый регистр. Для наглядности в монитор последовательного порта выводятся значения, которые получаются при этой операции: единичка «бежит» по разрядам – светодиоды загораются волной.
6«Бегущая волна» из светодиодов
Светодиоды загораются по очереди, и мы наблюдаем бегущую «волну» из огоньков. Управление светодиодами осуществляется с помощью сдвигового регистра, к которому мы подключились по интерфейсу SPI. В результате для управления 8-ю светодиодами задействованы всего 3 вывода Arduino. Если бы мы подключали светодиоды напрямую к цифровым портам Arduino, нам бы потребовалось для каждого светодиода использовать отдельный порт.
Мы изучили самый простой пример работы Arduino с шиной SPI. Более подробно рассмотрим работу нескольких сдвиговых регистров при независимом и каскадном подключениях в отдельной статье.