Интерфейс SPI (Serial Peripheral Interface) широко используется для связи микроконтроллеров с датчиками, памятью и другими устройствами. В большинстве проектов ESP32 выступает в роли мастера (Master), но иногда требуется настроить его как ведомое устройство (Slave) — например, для приёма данных от другого контроллера или специализированного чипа. В этом режиме ESP32 не инициирует обмен, а только реагирует на команды мастера, что открывает новые возможности для построения распределённых систем.
В отличие от режима Master, где ESP32 полностью контролирует шину, настройка Slave SPI имеет ряд нюансов: от выбора аппаратных выводов до обработки прерываний для своевременного чтения данных. В этой статье мы разберём уникальную особенность ESP32 — поддержку до 4-х независимых SPI-интерфейсов (SPI0–SPI3), где только SPI2 и SPI3 могут работать в режиме Slave, а также рассмотрим практические примеры для Arduino IDE и ESP-IDF.
Что такое режим Slave SPI и когда он нужен
В режиме Slave SPI микроконтроллер ESP32 не генерирует сигнал тактирования (SCK), а синхронизируется по внешнему источнику — мастеру. Основные особенности этого режима:
- 🔄 Пассивная роль: ESP32 только отвечает на запросы мастера, не может инициализировать передачу самостоятельно.
- 📡 Приём данных по прерыванию: для надёжной работы требуется обработчик прерываний (
ISR), иначе данные могут быть утеряны. - ⚡ Ограниченная скорость: максимальная частота зависит от тактовой частоты ESP32 и настройки делителя (до
80 МГцв теории, но на практике редко превышает20 МГц). - 🔌 Аппаратные ограничения: не все выводы ESP32 поддерживают Slave SPI (например,
SPI1может работать только как Master).
Типичные сценарии использования:
- 📊 Сбор данных с нескольких датчиков, подключённых к мастер-устройству (например, Raspberry Pi или STM32).
- 🔗 Реализация протоколов типа CAN или 1-Wire через SPI-мосты.
- 🖥️ Обмен данными с FPGA или специализированными чипами (например, AD9959 для генерации сигналов).
- 🔄 Дуплексная связь между двумя ESP32, где одно устройство всегда мастер, а другое — ведомое.
Важно понимать, что в режиме Slave ESP32 не может динамически переключаться на роль Master без переконфигурации шины. Это накладывает ограничения на архитектуру системы, но упрощает протокол обмена.
- Подключение датчиков
- Связь с другим микроконтроллером
- Работа с памятью (Flash/SRAM)
- Управление дисплеями
- Другое
Аппаратные возможности ESP32 для Slave SPI
ESP32 имеет 4 SPI-контроллера (SPI0–SPI3), но не все из них поддерживают режим Slave. В таблице ниже приведены ключевые характеристики:
| Контроллер | Режим Slave | Макс. частота (МГц) | Поддерживаемые выводы | Особенности |
|---|---|---|---|---|
SPI0 |
❌ Нет | — | — | Используется для flash-памяти, недоступен для пользователя. |
SPI1 |
❌ Нет | до 80 | HSPI | Только Master, часто используется для связи с дисплеями. |
SPI2 |
✅ Да | до 80 | VSPI | Поддерживает Slave, но требует ручной настройки выводов. |
SPI3 |
✅ Да | до 80 | HSPI (альтернативные выводы) | Рекомендуется для Slave, так как имеет выделенные сигналы SS. |
Для режима Slave оптимально использовать SPI2 или SPI3. При этом:
- 🔌 Вывод
SS(Slave Select) обязателен — без него ESP32 не сможет определить начало передачи. - 🔄 Выводы
MISO(Master In Slave Out) иMOSI(Master Out Slave In) могут быть переназначены, ноSCKдолжен быть жёстко привязан к определённому GPIO. - ⚡ Для стабильной работы рекомендуется использовать pull-up резисторы на линиях
MOSIиSCK(10 кОм).
⚠️ Внимание: Если вы используетеSPI3в режиме Slave, убедитесь, что в настройках menuconfig (для ESP-IDF) отключён параметрCONFIG_SPI_MASTER_ISR_IN_IRAM. В противном случае возможны конфликты с обработчиками прерываний.
Схема подключения ESP32 в режиме Slave SPI
Типовая схема подключения ESP32 как ведомого устройства включает 4 основных сигнала:
SCK(Serial Clock) — тактовый сигнал от мастера.MOSI(Master Out Slave In) — данные от мастера к ведомому.MISO(Master In Slave Out) — данные от ведомого к мастеру.SS(Slave Select) — сигнал выбора ведомого (активный низкий уровень).
Пример подключения ESP32 (Slave) к Arduino Uno (Master):
Arduino Uno (Master) ↔ ESP32 (Slave)
------------------- ---------------
5V → VCC (5V или 3.3V с делителем)
GND → GND
D13 (SCK) → GPIO 18 (SCK)
D11 (MOSI)→ GPIO 23 (MOSI)
D12 (MISO)← GPIO 19 (MISO)
D10 (SS) → GPIO 5 (SS)
Ключевые моменты:
- 🔋 Уровни напряжения: Если мастер работает на
5V, а ESP32 — на3.3V, используйте делитель напряжения или логический конвертер для линийMOSIиSCK. - 🔌 Pull-up резисторы: Рекомендуется установить резисторы
10 кОмна линииSCKиSSдля предотвращения ложных срабатываний. - ⚡ Длина проводов: Для частот выше
10 МГциспользуйте короткие провода (менее 10 см) или экранную витую пару.
Выводы SCK, MOSI, MISO подключены корректно|
Сигнал SS подключён к GPIO с поддержкой ввода|
Уровни напряжения совместимы (3.3V/5V)|
Установлены pull-up резисторы на SCK и SS|
Питание ESP32 стабильно (не менее 500 мА)-->
Настройка Slave SPI в Arduino IDE
Для работы с Slave SPI в Arduino IDE используется библиотека SPI, но с специфическими настройками. Основные шаги:
- Инициализация SPI в режиме Slave:
#include <SPI.h>void setup() {
SPI.begin(SPI_MODE_SLAVE); // Инициализация в режиме Slave
pinMode(SS, INPUT_PULLUP); // Настройка вывода SS как входа с pull-up
}
- Обработка прерывания: Данные приходят по прерыванию, поэтому нужно использовать
SPI.attachInterrupt():void setup() {SPI.begin(SPI_MODE_SLAVE);
SPI.attachInterrupt(); // Включение прерывания
}
// Обработчик прерывания
ISR(SPI_STC_vect) {
byte received = SPDR; // Чтение принятого байта
// Обработка данных
}
- Отправка данных мастеру: Для этого используется регистр
SPDR:void loop() {if (digitalRead(SS) == LOW) { // Если Slave выбран
SPDR = 0xAA; // Отправка байта мастеру
while (!(SPSR & (1 << SPIF))); // Ожидание завершения передачи
}
}
Пример полного кода для приёма данных от мастера и отправки ответа:
#include <SPI.h>
volatile bool dataReceived = false;
volatile byte receivedData;
void setup() {
Serial.begin(115200);
pinMode(SS, INPUT_PULLUP);
SPI.begin(SPI_MODE_SLAVE);
SPI.attachInterrupt();
}
ISR(SPI_STC_vect) {
receivedData = SPDR;
dataReceived = true;
}
void loop() {
if (dataReceived) {
Serial.print("Received: 0x");
Serial.println(receivedData, HEX);
SPDR = receivedData + 1; // Отправляем мастеру байт +1
dataReceived = false;
}
}
⚠️ Внимание: В Arduino IDE для ESP32 обработчик прерыванияISR(SPI_STC_vect)может конфликтовать с другими прерываниями. Если данные теряются, попробуйте увеличить приоритет прерывания SPI вesp_intr_alloc(требует перехода на ESP-IDF).
Если мастер не получает ответ от ведомого, проверьте, что сигнал SS удерживается в низком состоянии достаточно долго (не менее 1 такта SCK). Некоторые мастера (например, STM32) могут слишком быстро переключать SS, что приводит к пропуску данных.
Настройка Slave SPI в ESP-IDF
Для профессиональных проектов рекомендуется использовать ESP-IDF — официальный фреймворк от Espressif. Здесь настройка Slave SPI гибче, но требует больше кода.
Основные шаги:
- Конфигурация SPI: В файле
sdkconfigубедитесь, что включена поддержка SPI:CONFIG_SPI_MASTER_ISR_IN_IRAM=nCONFIG_SPI_SLAVE_ISR_IN_IRAM=y
- Инициализация шины:
#include "driver/spi_slave.h"spi_bus_config_t buscfg = {
.miso_io_num = GPIO_NUM_19, // MISO
.mosi_io_num = GPIO_NUM_23, // MOSI
.sclk_io_num = GPIO_NUM_18, // SCK
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 32, // Макс. размер передачи (байт)
};
spi_slave_interface_config_t slvcfg = {
.spics_io_num = GPIO_NUM_5, // SS
.flags = 0,
.queue_size = 7,
.mode = 0, // SPI_MODE0
.post_setup_cb = NULL,
.post_trans_cb = NULL
};
- Создание устройства Slave:
spi_device_handle_t spi;spi_slave_initialize(SPI2_HOST, &buscfg, &slvcfg, &spi);
- Приём и отправка данных:
uint8_t sendBuf[32] = {0};uint8_t recvBuf[32] = {0};
spi_slave_transaction_t t = {
.length = 32 * 8, // Размер в битах
.tx_buffer = sendBuf,
.rx_buffer = recvBuf
};
spi_slave_transmit(spi, &t, portMAX_DELAY);
Полный пример кода для ESP-IDF с обработкой прерываний:
Полный код для ESP-IDF с обработкой прерываний
#include "driver/spi_slave.h"
#include "esp_log.h"
static const char *TAG = "SPI_SLAVE";
void spi_task(void *arg) {
spi_bus_config_t buscfg = {
.miso_io_num = GPIO_NUM_19,
.mosi_io_num = GPIO_NUM_23,
.sclk_io_num = GPIO_NUM_18,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 32,
};
spi_slave_interface_config_t slvcfg = {
.spics_io_num = GPIO_NUM_5,
.flags = 0,
.queue_size = 7,
.mode = 0,
};
spi_device_handle_t spi;
spi_slave_initialize(SPI2_HOST, &buscfg, &slvcfg, &spi);
uint8_t sendBuf[32] = {0};
uint8_t recvBuf[32] = {0};
while (1) {
spi_slave_transaction_t t = {
.length = 32 * 8,
.tx_buffer = sendBuf,
.rx_buffer = recvBuf
};
spi_slave_transmit(spi, &t, portMAX_DELAY);
ESP_LOGI(TAG, "Received: %02X %02X %02X...", recvBuf[0], recvBuf[1], recvBuf[2]);
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void app_main() {
xTaskCreate(spi_task, "spi_task", 4096, NULL, 5, NULL);
}
Преимущества ESP-IDF перед Arduino IDE:
- 🔧 Гибкая настройка прерываний: можно задать приоритет и обработчик в
IRAM. - ⚡ Поддержка DMA: для высокоскоростного обмена (до
80 МГц). - 📊 Отладка через
ESP_LOG: удобный вывод логов по UART.
В ESP-IDF для Slave SPI обязательно настройте параметр max_transfer_sz в соответствии с максимальным пакетом данных. Если пакеты больше этого значения, данные будут обрезаны.
Обработка данных и типичные ошибки
При работе с Slave SPI на ESP32 часто возникают проблемы, связанные с синхронизацией, потерями данных или некорректной инициализацией. Рассмотрим типичные ошибки и способы их устранения.
1. Потеря данных при высокой скорости
Если мастер передаёт данные быстрее, чем ESP32 успевает их обработать, возможны потери. Решения:
- ⚡ Уменьшите частоту
SCKна стороне мастера (например, до1 МГц). - 🔄 Используйте
DMAв ESP-IDF для буферизации данных. - 📡 Увеличьте размер очереди (
queue_size) в конфигурации Slave.
2. Slave не отвечает мастеру
Если мастер не получает данные от ESP32, проверьте:
- 🔌 Корректность подключения
MISO(мастер должен читать с этого вывода). - ⚡ Уровень сигнала
SS: он должен быть низким во время передачи. - 🔄 Наличие
pull-upрезистора наSS(если мастер не удерживает сигнал).
3. Конфликты прерываний
Если данные принимаются с искажениями, возможно, обработчик прерывания SPI конфликтует с другими задачами. Решения:
- 🔧 В ESP-IDF назначьте прерыванию SPI высший приоритет:
esp_intr_alloc(SPI_INTR_SOURCE, ESP_INTR_FLAG_IRAM, spi_isr, NULL, &handle); - 📵 Отключите другие прерывания (например, Wi-Fi) на время критических операций.
⚠️ Внимание: Если вы используете Wi-Fi или Bluetooth одновременно с Slave SPI, возможны задержки в обработке прерываний из-за конфликтов за доступ к шинеAPB. В этом случае рекомендуется использоватьSPI3(он работает через отдельную шинуAHB).
4. Неправильный режим SPI (MODE0–MODE3)
ESP32 и мастер должны использовать одинаковый режим (CPOL и CPHA). Если данные искажаются, проверьте настройки:
| Режим | CPOL | CPHA | Описание |
|---|---|---|---|
MODE0 |
0 | 0 | Тактирование по переднему фронту, данные захват по заднему. |
MODE1 |
0 | 1 | Тактирование по заднему фронту, данные захват по переднему. |
MODE2 |
1 | 0 | CPOL=1, CPHA=0. |
MODE3 |
1 | 1 | CPOL=1, CPHA=1. |
В Arduino IDE режим задаётся при инициализации:
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
В ESP-IDF — в структуре spi_slave_interface_config_t (поле mode).
Практические примеры использования
Рассмотрим два реальных сценария применения Slave SPI на ESP32.
Пример 1: Приём данных от STM32 (Master)
Задача: STM32 отправляет пакеты данных (температура, влажность) по SPI, а ESP32 принимает их и передаёт по Wi-Fi.
Код для ESP32 (Slave):
#include <SPI.h>
#include <WiFi.h>
const char* ssid = "your_SSID";
const char* password = "your_PASSWORD";
volatile uint8_t temp, humidity;
volatile bool newData = false;
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
pinMode(SS, INPUT_PULLUP);
SPI.begin(SPI_MODE_SLAVE);
SPI.attachInterrupt();
}
ISR(SPI_STC_vect) {
static uint8_t index = 0;
uint8_t data = SPDR;
if (index == 0) temp = data;
else if (index == 1) {
humidity = data;
newData = true;
}
index = (index + 1) % 2;
SPDR = 0xFF; // Отправляем мастеру подтверждение
}
void loop() {
if (newData && WiFi.status() == WL_CONNECTED) {
Serial.printf("Temp: %d°C, Humidity: %d%%\n", temp, humidity);
// Отправка данных на сервер...
newData = false;
}
}
Пример 2: Обмен данными между двумя ESP32
Задача: Одна ESP32 (Master) отправляет команды, вторая (Slave) выполняет их и возвращает статус.
Код для Master (ESP32):
#include <SPI.h>
void setup() {
Serial.begin(115200);
SPI.begin();
pinMode(SS, OUTPUT);
digitalWrite(SS, HIGH);
}
void loop() {
digitalWrite(SS, LOW);
uint8_t command = 0x01; // Команда "включить светодиод"
uint8_t response = SPI.transfer(command);
digitalWrite(SS, HIGH);
Serial.print("Sent: 0x");
Serial.print(command, HEX);
Serial.print(", Received: 0x");
Serial.println(response, HEX);
delay(1000);
}
Код для Slave (ESP32):
#include <SPI.h>
const int ledPin = 2;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(SS, INPUT_PULLUP);
SPI.begin(SPI_MODE_SLAVE);
SPI.attachInterrupt();
}
ISR(SPI_STC_vect) {
uint8_t cmd = SPDR;
if (cmd == 0x01) {
digitalWrite(ledPin, HIGH);
SPDR = 0xAA; // Подтверждение выполнения
} else if (cmd == 0x02) {
digitalWrite(ledPin, LOW);
SPDR = 0x55;
} else {
SPDR = 0xFF; // Ошибка
}
}
void loop() {}
При обмене между двумя ESP32 убедитесь, что мастер удерживает сигнал SS низким на всё время передачи пакета. Если SS будет подниматься между байтами, Slave сбросит состояние.
FAQ: Частые вопросы по Slave SPI на ESP32
Можно ли использовать SPI1 в режиме Slave?
Нет, SPI1 (HSPI) в ESP32 поддерживает только режим Master. Для Slave доступны SPI2 (VSPI) и SPI3 (HSPI с альтернативными выводами).
Почему данные искажаются при частоте выше 10 МГц?
Это связано с задержками в обработке прерываний и длиной проводов. Попробуйте:
- Уменьшить частоту до
5–10 МГц. - Использовать экранные провода.
- В ESP-IDF включить
DMAдля SPI.
Как проверить, что Slave получает данные?
Подключите логический анализатор к линиям SCK, MOSI и SS. В Arduino IDE можно выводить принятые данные в Serial:
ISR(SPI_STC_vect) {
byte data = SPDR;
Serial.print("Received: ");
Serial.println(data, HEX);
}
В ESP-IDF используйте ESP_LOGI.
Можно ли использовать Slave SPI одновременно с Wi-Fi?
Да, но возможны задержки в обработке SPI из-за прерываний от Wi-Fi. Рекомендации:
- Используйте
SPI3(он работает черезAHB, что減少 конфликты). - Увеличьте приоритет прерывания SPI в ESP-IDF.
- Отключайте Wi-Fi на время критических операций SPI.
Какие резисторы нужны для подтяжки линий SPI?
Рекомендуется:
SCK:10 кОмpull-up к3.3V.SS:10 кОмpull-up к3.3V(если мастер не удерживает сигнал).MOSI: pull-up не обязателен, но может помочь при длинных проводах.
MISO обычно не требует подтяжки, так как управляется Slave.