Данная статья создана с ознакомительной целью и служит рекомендацией по работе с Raspberry Pi 4 Model B ("Ягода"), WEMOS WiFi & Bluetooth ESP32 ("ESP32") при настройки Serial Peripheral Interface (SPI).

Хочу выразить огромную благодарность соавтору данной статьи @Artuiu за плодотворную работу и помощь

P.S. Это наша первая серьёзная статья на данную тему, поэтому ждём от Вас комментарии, предложения и критику!

Материал, который мы использовали для изучения

Компоненты для работы

  • Микроконтроллер 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).

"Raspberry Pi Imager" —  Выбор ПО
"Raspberry Pi Imager" — Выбор ПО

Вставьте microSD карту в ПК. Перейдите во вкладку Storage и выбрать microSD карту (у нас это Kingston 64 Gb). Сюда будет устанавливаться Ваше ПО. Форматировать заранее не нужно, установщик сделает это за Вас.

Raspberry Pi Imager —  Выбор хранилища для ПО
Raspberry Pi Imager — Выбор хранилища для ПО

После чего нажать 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
Вставляем в открытое окошко и нажимаем ОК

Arduino IDE — Доп. ссылка для Менеджера плат
Arduino IDE — Доп. ссылка для Менеджера плат

Далее заходим в Инструменты > Управлять библиотеками.
У Вас откроется меню менеджер библиотек. Ищем библиотеку esp32 от Espressif версии 1.0.6 (версии выше не работают в некоторых случаях) и устанавливаем.

Arduino IDE — Выбор esp32 от Espressif версии 1.0.6
Arduino IDE — Выбор esp32 от Espressif версии 1.0.6

После установки, Вам нужно выбрать плату, поэтому заходим в Инструменты > Плата > ESP32 Arduino > WeMos WiFi & Bluetooth Battery.

Arduino IDE — Выбор платы WeMos WiFi&Bluetooth Battery
Arduino IDE — Выбор платы 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).

"Putty" — SSH подключение к "Ягоде"
"Putty" — SSH подключение к "Ягоде"

При первом входе требуется ввести логин/пароль: pi/raspberry. Сразу обновите ПО с помощью команды

sudo apt-get update && sudo apt-get upgrade -y

Дальше идём в менеджер пакетов с помощью команды

sudo raspi-config
Менеджер пакетов
Менеджер пакетов

Перейдите в Interface Options и включите VNC, также сразу активируем SPI

Активация VNC и SPI
Активация 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

"VNC Viewer" — Выбор подключения
"VNC Viewer" — Выбор подключения

Вводим имя пользователя/пароль: pi/raspberry.

"VNC Viewer" — Ввод имени пользователя и пароля
"VNC Viewer" — Ввод имени пользователя и пароля

И если все сделано правильно, то у вас появится рабочий стол "Ягоды"

Рабочий стол "Ягоды"
Рабочий стол "Ягоды"

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"

Готовое подключение
Готовое подключение

Ждём ваши комментарии и критику!

Комментарии (4)


  1. fiego
    14.08.2023 19:55
    +1

    Идея интересная. Возникает вопрос: что это такое должно быть? Почему SPI? От себя: я знаю для чего я применил бы 3-4 управляемых ESP32, но для связи я бы взял I2C.


    1. geoink Автор
      14.08.2023 19:55

      Выбор пал на SPI ввиду быстроты работы в отличие от I2C. Ведь мы делаем проект, который напрямую связан со скоростью передачи сигнала


      1. fiego
        14.08.2023 19:55
        +1

        Это пока что не очевидно. Ну, вот мой пример: контроллер дисплеев. Я бы взял ESP32 в качестве контроллера дисплеев, по два на контроллер, каждый на своём SPI, а информацию что на дисплее показывать передавал бы через I2C. На мой взгляд, это было бы разумно: более медленный протокол передаёт более высокоуровневые команды, которые потом "простым" микроконтроллером транслируются в быстрые передачи по шине SPI, суммарно таким интеллектуальным образом увеличивая пропускную способность и разгружая центральный "мозг". А вот что у Вас за проект, мне пока не понятно. Если для связи с микроконтроллером используется SPI, то микроконтроллер должен много данных принимать быстро и что-то с ними делать ещё быстрее. И вот эта вот цель мне пока не понятна.


      1. NutsUnderline
        14.08.2023 19:55

        скрипт на Python съест все это быстродействие и не поморщится, не гворя о том что и по spi вроде малая скорость spi0.max_speed_hz = 100000 - как то нулей маловато.. достаточно подключить осцилограф на SCLK  чтобы убедиться

        но главная "ересь" этой истории что SPI рассчитано на последовательно подключение нескольких ведомых, а здесь ПЯТЬ интерфейсов SPI поднято.

        То что высокоскоростное соединение сделано на таких проводах - вынесем за скобки, но если оставить так то будет море глюков при дуновении ветерка...

        да и для VNC непонятна нужда, тут пока все из консоли прекрасно работает.

        В общем не знаю еще что это за кластер такой (смутно помню видел подобное) но качество реализации уже под вопросом