Данная статья создана с ознакомительной целью и служит рекомендацией по работе с Raspberry Pi 4 Model B ("Ягода"), WEMOS WiFi & Bluetooth ESP32 ("ESP32") при настройки Serial Peripheral Interface (SPI).
Хочу выразить огромную благодарность соавтору данной статьи @Artuiu за плодотворную работу и помощь
P.S. Это наша первая серьёзная статья на данную тему, поэтому ждём от Вас комментарии, предложения и критику!
Материал, который мы использовали для изучения
https://microtechnics.ru/raspberry-pi-obmen-dannymi-po-interfejsu-spi/
https://www.seeedstudio.com/blog/2019/09/25/uart-vs-i2c-vs-spi-communication-protocols-and-uses/
https://blog.stabel.family/raspberry-pi-4-multiple-spis-and-the-device-tree/
Компоненты для работы
Микроконтроллер WEMOS WiFi & Bluetooth ESP32 - 5 шт.
Микрокомпьютер Raspberry Pi 4 Model B - 1 шт.
Монтажные провода типа "мама-мама" - 13 шт.
Монтажные провода типа "мама-папа" - 21 шт.
Кабель Ethernet - 1 шт.
Кабель USB type c - 1 шт.
Кабель USB micro USB - 1 шт.
Карта памяти microSD 64 Гб - 1 шт.
Аккумулятор 18650 Li-ion - 5 шт.
Теория
SPI (Serial Peripheral Interface) - это синхронный последовательный интерфейс обмена данными между микроконтроллерами, микропроцессорами и периферийными устройствами. Он позволяет передавать данные между устройствами по нескольким проводам, включая провод для синхронизации и по одному проводу на каждое направление передачи данных (один для отправки данных и один для приема).
Пины SPI: На Raspberry Pi используется несколько GPIO (General Purpose Input/Output) пинов для реализации интерфейса SPI. Основные пины, используемые для SPI, это:
SCLK (Serial Clock): Пин для синхронизации передачи данных между устройствами.
MISO (Master In Slave Out): Пин для передачи данных от периферийного устройства к микроконтроллеру (Raspberry Pi).
MOSI (Master Out Slave In): Пин для передачи данных от микрокомпьютера к микроконтроллеру.
CS (Chip Select): Пин для выбора конкретного микроконтроллера, с которым микрокомпютер будет взаимодействовать.
Raspberry Pi имеет три основных интерфейса SPI - SPI0, SPI1 и SPI2.
SPI0: Этот интерфейс доступен на GPIO пинах 9 (MISO), 10 (MOSI), 11 (SCLK) и 8 (CS0). Он также может использоваться для доступа к специфическим периферийным устройствам, таким как экраны или сенсорные панели.
SPI1: Этот интерфейс доступен на GPIO пинах 19 (MISO), 20 (MOSI), 21 (SCLK) и 18 (CS1). Он может быть использован для подключения к другим периферийным устройствам, требующим SPI интерфейса.
Подготовка к работе
1. Установка ПО "Raspberry Pi OS" на microSD через "Raspberry Pi Imager".
Скачать установщик с официального сайта: https://www.raspberrypi.com/software/. После чего открыть "Raspberry Pi Imager". Выбрать Operating System > Raspberry Pi OS (32-bit).
Вставьте microSD карту в ПК. Перейдите во вкладку Storage и выбрать microSD карту (у нас это Kingston 64 Gb). Сюда будет устанавливаться Ваше ПО. Форматировать заранее не нужно, установщик сделает это за Вас.
После чего нажать WRITE и ждать установку. После установки необходимо вставить microSD в лот на самой "Ягоде".
2. Установка Arduino IDE для работы с "ESP32". Заходим на официальный сайт https://www.arduino.cc/en/software, скачиваем Arduino IDE и устанавливаем.
Теперь настроим Arduino IDE. Для начала перейдём в Файл > Настройки и нажимаем на кнопку
После копируем ссылку на json файл https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
Вставляем в открытое окошко и нажимаем ОК
Далее заходим в Инструменты > Управлять библиотеками.
У Вас откроется меню менеджер библиотек. Ищем библиотеку esp32 от Espressif версии 1.0.6 (версии выше не работают в некоторых случаях) и устанавливаем.
После установки, Вам нужно выбрать плату, поэтому заходим в Инструменты > Плата > ESP32 Arduino > WeMos WiFi & Bluetooth Battery.
Также Вам нужно скачать библиотеку ESP32SPISlave.h
Установить можно здесь: https://github.com/hideakitai/ESP32SPISlave?ysclid=ll6fdafeu2422870462
Скачиваем библиотеку и распаковываем её там, где будет основной код.
3. Подготовка к удобной работе "Ягоды". Управление "Ягодой" будет осуществляться с помощью ноутбука, поэтому будем использовать систему удалённого доступа Virtual Network Computing (VNC).
Присоединяем ноутбук к “Ягоде” через порт для кабеля Ethernet. В программе "Putty" прописываем raspberrypi.local в HostName и указываем подключение по протоколу SSH (порт при подключении стандартный 22).
При первом входе требуется ввести логин/пароль: pi/raspberry. Сразу обновите ПО с помощью команды
sudo apt-get update && sudo apt-get upgrade -y
Дальше идём в менеджер пакетов с помощью команды
sudo raspi-config
Перейдите в Interface Options и включите VNC, также сразу активируем SPI
В появившемся окне выбрать "YES". Для завершения настройки необходимо перейти в System Options > Boot/Auto Login > Desktop Autologin
Наконец, нажмите "Enter", чтобы применить изменения. Потом нажмите "Tab" и "Enter". Это перезагрузит ваш Raspberry Pi, и сеанс SSH завершится.
Теперь закройте "Putty" и другие окна. Установим VNC Viewer на ноутбук, ссылка с официального сайта: https://www.realvnc.com/en/connect/download/viewer/.
Вводим raspberrypi.local
Вводим имя пользователя/пароль: pi/raspberry.
И если все сделано правильно, то у вас появится рабочий стол "Ягоды"
4. Активация дополнительных SPI интерфейсов. Стандартно "Ягода" имеет три интерфейса: SPI0-2. Для активации дополнительных интерфейсов нужно добавить строчки в файле config.txt (находится в папке boot)
Вводим cтрочку с текстом
spi5 - новый интерфейс
2cs - количество устройств, которые будут работать на этом интерфейсе
dtoverlay=spi5-1cs
Более подробно это можно прочитать тут: https://blog.stabel.family/raspberry-pi-4-multiple-spis-and-the-device-tree/
Практическая часть
Для установки соединения по технологии SPI понадобится: 1 "Ягода", 5 "ESP32", 1 ноутбук, 13 кабелей типа "мама-мама" и 31 кабель типа "папа-мама".
Чтобы установить соединение пяти "ESP32" с "Ягодой", нужно задействовать 5 пинов на каждой "ESP32": MISO, MOSI, SCLK, SS, Ground(GND)
И 19 пинов на "Ягоде"
SPI0: MISO - 21 (GPIO 9), MOSI - 19 (GPIO 10), SCLK - 23 (GPIO 11), SS0 - 24 (GPIO 8),
SS1 - 26 (GPIO 7);
SPI1: MISO - 35 (GPIO 19), MOSI - 38 (GPIO 20), SCLK - 40 (GPIO 21), SS1 - 11 (GPIO 17),
SS2 - 36 (GPIO 16);
SPI5: MISO - 33 (GPIO 13), MOSI - 8 (GPIO 14), SCLK - 10 (GPIO 15), SS0 - 32 (GPIO 12)
+1 GND для каждой из "ESP32"
Самое главное, надо помнить, что все пины надо подключать в сопоставлении с названием, то есть MISO к MISO, MOSI к MOSI и т.д.
Для наглядности распиновки прикладываю ниже принципиальную схему
Также вот макетная плата подключения пяти "ESP32"
Теперь вам необходимо подключить каждую "ESP32" через кабель USB micro USB по отдельности к ноутбуку и через Arduino IDE загрузить код, который используется для приёма данных с "Ягоды", выводит в монитор порта, и далее отправляет данные на "Ягоду".
Код для Arduino:
#include "ESP32SPISlave.h"
ESP32SPISlave slave;
// Создаём два буфера на приём и передачу данных
static constexpr uint32_t BUFFER_SIZE {51};
uint8_t spi_slave_tx_buf[BUFFER_SIZE];
uint8_t spi_slave_rx_buf[BUFFER_SIZE];
void setup() {
Serial.begin(115200);
delay(2000);
// HSPI = CS: 15, CLK: 14, MOSI: 13, MISO: 12
// VSPI = CS: 5, CLK: 18, MOSI: 23, MISO: 19
// Функция устанавливает режим работы шины SPI, задавая //уровень сигнала синхронизации и фазу синхронизации.
slave.setDataMode(SPI_MODE0);
slave.begin(VSPI);
// Заполняем буфер нулями
memset(spi_slave_tx_buf, 0, BUFFER_SIZE);
memset(spi_slave_rx_buf, 0, BUFFER_SIZE);
}
// В дальнейшем будет считывать задержку
int wd = 0;
void loop() {
// если в очереди нет транзакции, добавляем транзакцию
if (slave.remained() == 0) {
slave.queue(spi_slave_rx_buf, spi_slave_tx_buf, BUFFER_SIZE);
}
// если транзакция завершена из master,
// available() возвращает размер результатов транзакции,
// и буфер автоматически обновляется
while (slave.available()) {
Serial.println(micros() - wd);
wd = micros();
String receivedData;
// считываем полученный данные в переменную receivedData
for (size_t i = 0; i < slave.size(); ++i) {
receivedData += (char)spi_slave_rx_buf[i];
}
Serial.println("Received: " + receivedData);
String sentData = "0101010010101011010XCVASDFratatatata";
// Отправляем данные на raspberry
size_t len = sentData.length();
memcpy(spi_slave_tx_buf, sentData.c_str(), len);
slave.pop();
}
}
Компилируем код и так повторяем с каждым из пяти "ESP32"
Дальше заходим в "Ягоду" и будем использовать "Thonny" для написания скрипта на Python. Скрипт отвечает за отправку данных с "Ягоды" и приём данных с пяти "ESP32".
Заранее нужно будет скачать библиотеку spidev (заходим в терминал "Ягоды" и прописываем pip install spidev)
import spidev # Импорт библиотеки spidev для работы с SPI.
spi0 = spidev.SpiDev() # Создание объекта spi0 для управления SPI устройством на шине 0.
spi0.open(0, 0) # Открытие подключения к SPI устройству с номером шины 0 и устройством 0.
spi0.max_speed_hz = 100000 # Установка максимальной скорости передачи данных и количества бит на слово.
spi0.bits_per_word = 8
spi1 = spidev.SpiDev() # Создание объекта spi1 для управления другим SPI устройством на шине 1.
spi1.open(1, 0) # Открытие подключения к SPI устройству с номером шины 1 и устройством 0.
spi1.max_speed_hz = 100000
spi1.bits_per_word = 8
spi5 = spidev.SpiDev()
spi5.open(5, 0)
spi5.max_speed_hz = 100000
spi5.bits_per_word = 8
rx_data_len = 10
while True: # Организация цикла отправки и чтения данных для spi0.
for dev in range(1):
data_str = "B" * 10
data = data_str.encode()
spi0.open(0, dev)
spi0.writebytes(data)
rx_data = spi0.readbytes(rx_data_len)
received_data = bytes(rx_data)
print(f"B0 D{dev} R_Data: {received_data}\n")
spi0.close()
for dev in range(1): # Организация цикла отправки и чтения данных для spi1.
data_str = "A" * 10
data = data_str.encode()
spi1.open(1, dev)
spi1.writebytes(data)
rx_data = spi1.readbytes(rx_data_len)
received_data = bytes(rx_data)
print(f"B1 D{dev} R_Data: {received_data}\n")
spi1.close()
for dev in range(1): # Организация цикла отправки и чтения данных для spi5.
data_str = "C" * 10
data = data_str.encode()
spi5.open(5, dev)
spi5.writebytes(data)
rx_data = spi5.readbytes(rx_data_len)
received_data = bytes(rx_data)
print(f"B5 D{dev} R_Data: {received_data}\n")
spi5.close()
Дальше необходимо запустить скрипт на "Ягоде" и нажать физическую кнопку "Enable" на каждой "ESP32".
Все соединение SPI установлено. Не забудьте вставить аккумуляторы в "ESP32"
Ждём ваши комментарии и критику!
fiego
Идея интересная. Возникает вопрос: что это такое должно быть? Почему SPI? От себя: я знаю для чего я применил бы 3-4 управляемых ESP32, но для связи я бы взял I2C.
geoink Автор
Выбор пал на SPI ввиду быстроты работы в отличие от I2C. Ведь мы делаем проект, который напрямую связан со скоростью передачи сигнала
fiego
Это пока что не очевидно. Ну, вот мой пример: контроллер дисплеев. Я бы взял ESP32 в качестве контроллера дисплеев, по два на контроллер, каждый на своём SPI, а информацию что на дисплее показывать передавал бы через I2C. На мой взгляд, это было бы разумно: более медленный протокол передаёт более высокоуровневые команды, которые потом "простым" микроконтроллером транслируются в быстрые передачи по шине SPI, суммарно таким интеллектуальным образом увеличивая пропускную способность и разгружая центральный "мозг". А вот что у Вас за проект, мне пока не понятно. Если для связи с микроконтроллером используется SPI, то микроконтроллер должен много данных принимать быстро и что-то с ними делать ещё быстрее. И вот эта вот цель мне пока не понятна.
NutsUnderline
скрипт на Python съест все это быстродействие и не поморщится, не гворя о том что и по spi вроде малая скорость spi0.max_speed_hz = 100000 - как то нулей маловато.. достаточно подключить осцилограф на SCLK чтобы убедиться
но главная "ересь" этой истории что SPI рассчитано на последовательно подключение нескольких ведомых, а здесь ПЯТЬ интерфейсов SPI поднято.
То что высокоскоростное соединение сделано на таких проводах - вынесем за скобки, но если оставить так то будет море глюков при дуновении ветерка...
да и для VNC непонятна нужда, тут пока все из консоли прекрасно работает.
В общем не знаю еще что это за кластер такой (смутно помню видел подобное) но качество реализации уже под вопросом