Использование интерфейса SPI в ESP32 с помощью ESP-IDF


В данной статье мы изучим основные принципы работы интерфейса SPI и способы осуществления связи типа «ведущий-ведомый» (Master - Slave) между двумя платами разработки ESP32 с использованием ESP-IDF. Мы будем использовать драйверы SPI Slave и SPI Master для демонстрации связи между ведущим (хостом) и ведомым (устройством) устройствами по протоколу SPI.

Также про использование интерфейса SPI в ESP32 на нашем сайте вы можете прочитать в статье протоколы связи в ESP32-S3 с помощью ESP-IDF - возможно некоторые моменты в ней объяснены более понятным языком чем в данной статье.

Основные принципы работы интерфейса SPI

SPI-соединение, также известное как последовательный периферийный интерфейс, представляет собой полнодуплексный и синхронный последовательный интерфейс. Как ведущее (Master), так и ведомое (Slave) устройства могут одновременно обмениваться данными. Поэтому для передачи и приема данных необходим тактовый сигнал. Этот тактовый сигнал синхронизируется между ведущим и ведомым устройствами. В этом протоколе связи тактовый сигнал играет важную роль, поскольку он управляет передачей и приемом данных с обеих сторон. Ведущее устройство начинает передачу SPI-данных по шине и выступает в роли ведущего устройства SPI. Оно управляет тактовым сигналом и, таким образом, подает тактовый сигнал на все подключенные к нему ведомые устройства. Передача данных зависит от тактового сигнала и невозможна без него. Более того, обмен данными может происходить как между ведущим, так и между ведомым устройством.

SPI-соединение между двумя устройствами

Для связи по протоколу SPI оба устройства подключаются через 4-проводной интерфейс. Приведенная ниже блок-схема иллюстрирует соединения SPI между ведущим и ведомым устройствами.

Схема связи по протоколу SPI между ведущим и ведомым устройствами

В таблице ниже описаны четыре сигнальных контакта, необходимых для данного протокола связи.

SCLK или SCK Это последовательный тактовый сигнал, генерируемый ведущим устройством (хостом) для синхронизации передачи битов данных. Как уже упоминалось, только ведущее устройство может управлять тактовым сигналом, поэтому этот вывод будет обеспечивать тактовый сигнал для ведомых устройств, которые будут контролироваться ведущим устройством. Когда никаких операций не выполняется, этот вывод находится в режиме ожидания (неактивный или трехпозиционный).
SS или CS Это вывод сигнала выбора микросхемы или выбора ведомого устройства, используемый для выбора ведомого устройства, подключенного к шине, на которое ведущее устройство (хост) хочет отправлять или получать данные.
MOSI Это сигнальный вывод Master Out Slave In. Он отвечает за передачу данных от ведущего устройства (хоста) к ведомому устройству (устройству).
MISO Это сигнальный вывод Master In Slave Out. Он отвечает за передачу данных от ведомого устройства (Device) к ведущему устройству (Host).

SPI-соединения между ведущим и ведомым устройствами

В протоколе связи SPI ведущее устройство начинает передачу кадра данных и выбирает ведомое устройство, которому должны быть переданы данные, через вывод SS/CS. Для передачи или приема данных от ведомого устройства ведущее устройство изменяет состояние тактового сигнала с активного низкого уровня на активный высокий уровень. Данные отправляются ведущим устройством по линии MOSI и принимаются ведомым устройством по линии MISO.

Контакты SPI в ESP32

Плата разработки ESP32 поставляется с двумя универсальными SPI-контроллерами. Эти SPI-контроллеры могут быть сконфигурированы для использования в качестве ведомых узлов, управляемых внешним SPI-мастером. Они известны как SPI2 (HSPI) и SPI3 (VSPI). Оба имеют отдельные сигнальные шины. Следовательно, мы можем использовать их по отдельности как в качестве мастера, так и в качестве ведомого устройства. Каждая шина может управлять до тремя SPI-устройствами или ведомыми устройствами в режиме контроллера.

В таблице ниже показаны стандартные контакты SPI ESP32 для обоих каналов.

SPI-канал MOSI MISO SCK/CLK CS/SS
VSPI GPIO23 GPIO19 GPIO18 GPIO5
HSPI GPIO13 GPIO12 GPIO14 GPIO15

Теперь перейдем непосредственно к написанию программы для работы с интерфейсом SPI в ESP32.

API драйвера ведомого устройства SPI для ESP32

Теперь давайте обсудим библиотеку драйверов SPI Slave и ее функции, предоставляемые ESP-IDF, которые будут использоваться в этом руководстве. ESP-IDF предоставляет библиотеку driver/spi_slave.h , которая управляет периферийными устройствами SPI ESP32, когда они работают в режиме ведомого устройства.

Это заголовочный файл, необходимый для включения драйвера SPI Slave:

Инициализация

Для инициализации шины SPI в качестве ведомого интерфейса вызывается функция spi_slave_initialize(). Она принимает четыре параметра.

  1. Первый параметр — это «хост», то есть периферийное устройство SPI, используемое в качестве ведомого интерфейса SPI.
  2. Второй параметр — это 'bus_config', который представляет собой указатель на структуру spi_bus_config_t, описывающую инициализацию хоста.
  3. Третий параметр — это 'slave_config', который представляет собой указатель на структуру spi_slave_interface_config_t, содержащую подробную информацию о подчиненном интерфейсе.
  4. Последний параметр — это 'dma_chan', то есть канал DMA, выбранный пользователем. Он позволяет осуществлять транзакции по шине, размер которых ограничен только объемом внутренней памяти. Может принимать одно из трех значений:
  • SPI_DMA_DISABLED: этот параметр ограничивает размер транзакций.
  • SPI_DMA_DISABLED: этот параметр выбирается, если только SPI-флэш-память использует эту шину.
  • SPI_DMA_CH_AUTO: это драйвер выделяет канал DMA.

Передача данных

Для выполнения SPI-транзакции на стороне ведомого устройства вызывается следующая функция: spi_slave_transmit(). Она принимает три параметра.

  1. Первый параметр — это «host», который обозначает периферийное устройство SPI, используемое в качестве интерфейса SPI.
  2. Второй параметр — 'trans_desc', который представляет собой указатель на переменную, содержащую указатель на описание выполняемой транзакции. Он не является константой, поскольку может возникнуть необходимость записать статус обратно в описание транзакции.
  3. Третий параметр — 'ticks_to_wait', который определяет количество тактов, в течение которых ожидается возврат значения. Мы можем использовать portMAX_DELAY, чтобы избежать таймаута.

API драйвера ведущего устройства SPI для ESP32

Теперь давайте обсудим библиотеку драйверов SPI Master и ее функции, предоставляемые ESP-IDF, которые будут использоваться в этом руководстве. ESP-IDF предоставляет библиотеку driver/spi_master.h , которая управляет периферийными устройствами SPI ESP32, когда они выступают в качестве ведущих устройств.

Это заголовочный файл, необходимый для включения драйвера SPI Master:

Инициализация

Для начала инициализируем SPI. Для этого используем функцию spi_bus_initialize(), которая принимает три параметра.

  1. Первый параметр — это 'host_id', который обозначает SPI-периферийное устройство, отвечающее за управление шиной.
  2. Второй параметр — это 'bus_config', который представляет собой указатель на структуру spi_bus_config_t, обозначающую инициализацию хоста.
  3. Третий параметр — 'dma_chan', это канал DMA, выбранный пользователем. Он позволяет осуществлять транзакции по шине, размер которых ограничен только объемом внутренней памяти. Может принимать одно из трех значений:
  • SPI_DMA_DISABLED: этот параметр ограничивает размер транзакций.
  • SPI_DMA_DISABLED: этот параметр выбирается, если только SPI-флэш-память использует эту шину.
  • SPI_DMA_CH_AUTO: драйвер выделяет канал DMA.

Регистрация ведомого устройства

Для назначения устройства на шине SPI мы будем использовать функцию spi_bus_add_device(). Эта функция зарегистрирует ведомое устройство, инициализируя его внутренние структуры и выделяя вывод CS. Как упоминалось ранее, ведущее устройство SPI может управлять до тремя ведомыми устройствами SPI. Эта функция принимает три параметра.

  • Первый параметр — это 'host_id', который указывает на периферийное устройство SPI, которому будет назначено устройство.
  • Второй параметр — 'dev_config', который представляет собой конфигурацию протокола интерфейса SPI для устройства.
  • Третий параметр — это 'handle', который представляет собой указатель на переменную, содержащую дескриптор устройства.

Передача данных

Функция spi_device_transmit() используется для отправки SPI-транзакции. После отправки транзакции она ожидает ее завершения, а затем возвращает управление. Эта функция принимает два параметра.

  • Первый параметр — это 'handle', который представляет собой дескриптор устройства. Он получается с помощью функции spi_host_add_dev.
  • Второй параметр — 'trans_desc', который представляет собой описание транзакции, подлежащей реализации.

Пример работы с ведомым устройством SPI с помощью ESP-IDF

В этом разделе мы создадим и протестируем проект, используя описанные ранее драйверы SPI Slave и SPI Master. Мы будем использовать пример SPI Slave, предоставленный ESP-IDF в разделе «Периферийные устройства». Он состоит из двух примеров: отправителя и приемника, которые будут установлены на отдельных платах ESP32. Платы будут соединены друг с другом через контакты SPI-связи, которые будут заданы в коде. В примере отправителя будет использоваться драйвер SPI Master, а в примере приемника — драйвер SPI Slave. Оба примера должны быть загружены на соответствующие платы ESP32, соединенные друг с другом, чтобы передача данных осуществлялась по SPI-связи. Более того, этот пример SPI Slave также включает в себя линию квитирования, которая позволит ведущему устройству опрашивать ведомое устройство только тогда, когда оно действительно готово к обработке транзакции.

Создание примера проекта

Откройте VS Code и перейдите в меню View > Command Palette. Введите в строке поиска ESP-IDF: New Project и нажмите Enter.

Укажите название проекта и каталог. Мы назвали наш проект «SPI_SLAVE_SENDER». Для платы ESP-IDF мы выбрали опцию custom board («Пользовательская плата»). В качестве целевой платформы ESP-IDF мы выбрали модуль ESP32. Нажмите кнопку ‘Choose Template’ («Выбрать шаблон»), чтобы продолжить.

Создание примера проекта в VS Code

В разделе Extension («Расширение») выберите опцию ESP-IDF:

Выбор опции ESP-IDF в разделе Extension («Расширение»)

В разделе Peripherals («Периферийные устройства») на вкладке spi_slave мы нажмем на кнопку ‘sender’ («Передатчик»). Затем нажмем ‘Create project using template sender’ («Создать проект, используя шаблон передатчика»).

Создание проекта с использованием шаблона передатчика

Вы получите уведомление о создании проекта. Чтобы открыть проект в новом окне, нажмите «Да».

Это откроет созданный нами проект SPI_SLAVE_SENDER во вкладке EXPLORER. Внутри папки проекта находится несколько папок. Это справедливо для всех проектов, которые вы будете создавать с помощью ESP-IDF Explorer. Перейдем к файлу app_main.c. Перейдите в  main > app_main.c  и откройте его. Откроется следующий код.

Код ESP-IDF: отправитель spi_slave

Как работает код?

Для начала мы подключим необходимые библиотеки, включая библиотеки FreeRTOS для генерации задержек, создания очередей и семафоров, driver/gpio.h, поскольку нам нужно назначить контакты связи SPI на контакты GPIO ESP32, driver/spi_master.h для функциональности SPI-мастера, esp_log.h в качестве библиотеки для вывода информационных сообщений и т.д.

Это контакты GPIO ESP32, которые будут использоваться для связи по протоколу SPI. Используйте эти контакты GPIO для соединения двух плат ESP32. Вы также можете изменить эти контакты.

Для ESP32 мы используем HSPI в качестве интерфейса SPI.

Мы будем использовать семафор для создания программного прерывания, при котором одна функция выполняется всякий раз, когда выполняется другая функция. Семафор будет отслеживать готовность ведомого устройства к анализу транзакции. Внутри функции gpio_handshake_isr_handler() мы используем функцию xSemaphoreGiveFromISR() для передачи семафора для прерывания. Она принимает два аргумента. Первый аргумент — это дескриптор семафора, а второй — приоритет. Более того, чтобы гарантировать наличие только одного запроса на прерывание и отсутствие двух прерываний, близких друг к другу, отслеживается время между прерываниями, и рассматриваются только действительные запросы на прерывание.

app_main()

Внутри функции app_main() сначала задаются параметры конфигурации шины SPI. Они показаны ниже:

Также задаются параметры конфигурации ведомого устройства. Это включает в себя тактовую частоту, коэффициент заполнения, режим работы и т. д.

Следующая структура содержит конфигурацию GPIO для линии квитирования. Она включает в себя тип прерывания GPIO, режим GPIO (вход или выход), подтягивающий резистор GPIO и контакт GPIO, устанавливаемый с помощью битовой маски, где каждый бит соответствует контакту GPIO.

Затем мы создаём бинарный семафор с помощью функции xSemaphoreCreateBinary(). Она не принимает никаких аргументов и возвращает переменную типа SemaphoreHandle_t. rdySem — это глобальная переменная, которую мы используем для хранения семафора.

Следующие строки кода настраивают прерывание по линии подтверждения связи.

Начинается все с вызова функции gpio_config(), которая используется для настройки общей конфигурации GPIO. Она принимает один параметр — указатель на структуру конфигурации GPIO.

Далее мы вызываем функцию gpio_install_isr_service() для установки службы обработки прерываний. Она также принимает один параметр — флаг выделения прерываний.

Затем мы устанавливаем тип запуска прерывания с помощью функции gpio_set_intr_type(). Она принимает два параметра. Первый параметр — это номер контакта GPIO, который будет использоваться для установки типа прерывания, в данном случае это GPIO2. Второй параметр — это тип прерывания, который в данном случае указывается как 'GPIO_INTR_POSEDGE'. Таким образом, прерывание будет срабатывать по положительному фронту (от низкого к высокому) тактового сигнала.

Наконец, чтобы добавить обработчик прерываний для нашего GPIO-контакта, используемого для подтверждения соединения, мы вызовем функцию gpio_isr_handler_add(). Эта функция принимает три параметра. Первый параметр — номер GPIO. Второй параметр — обработчик прерываний для этого конкретного GPIO-контакта, а третий параметр — значение обработчика прерываний, которое в данном случае равно NULL.

Инициализация и регистрация устройства

Далее мы инициализируем шину SPI и регистрируем ведомое устройство.

Для инициализации шины SPI мы вызываем функцию spi_bus_initialze(). Она принимает три параметра.

  • Первый параметр — это используемый экземпляр SPI. Как мы уже упоминали, ESP32 имеет четыре экземпляра SPI: SPI0, SPI1, SPI2 и SPI3. Однако мы можем использовать только SPI2 и SPI3, поскольку остальные зарезервированы для флэш-памяти. В данном случае мы указали первый параметр как 'SENDER_HOST', который определен как HSPI_HOST. Следовательно, в данном случае используется SPI2.
  • Второй параметр — это указатель на константную структуру конфигурации типа spi_bus_config_t, которую мы определили ранее. Она содержит информацию о контактах GPIO, используемых для связи по протоколу SPI.

  • Третий параметр указывает используемый канал DMA. Эта функция позволяет экземпляру SPI получать доступ к оперативной памяти в качестве буфера передачи. В нашем случае он установлен как 'SPI_DMA_CH_AUTO', что означает, что драйвер выделит канал DMA самостоятельно.

Для регистрации ведомого устройства мы вызываем функцию spi_bus_add_device(). Она принимает три параметра.

  • Первый параметр — это используемый экземпляр SPI, которым является SPI2.
  • Второй параметр — это указатель на константную структуру конфигурации типа spi_device_interface_config_t, которую мы определили ранее. Она содержит информацию о ведомом устройстве, включая тактовую частоту, режим работы, выделенный вывод CS и т. д.

  • Третий параметр — это указатель на переменную типа spi_device_handle_t.

Затем семафор отпускается.

Внутри бесконечного цикла данные передаются на подчиненное устройство с помощью функции spi_device_transmit().

Затем, чтобы удалить устройство из шины SPI, мы вызываем функцию spi_bus_remove_device(). Она принимает один параметр — дескриптор устройства.

Компиляция скетча

Для прошивки микросхемы введите следующую команду в последовательном терминале. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.

 

 

 

статья в разработке

(Проголосуй первым!)
Загрузка...
3 просмотров

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *