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

В протоколе связи 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:
|
1 |
#include "driver/spi_slave.h" |
Инициализация
Для инициализации шины SPI в качестве ведомого интерфейса вызывается функция spi_slave_initialize(). Она принимает четыре параметра.
- Первый параметр — это «хост», то есть периферийное устройство SPI, используемое в качестве ведомого интерфейса SPI.
- Второй параметр — это 'bus_config', который представляет собой указатель на структуру spi_bus_config_t, описывающую инициализацию хоста.
- Третий параметр — это 'slave_config', который представляет собой указатель на структуру spi_slave_interface_config_t, содержащую подробную информацию о подчиненном интерфейсе.
- Последний параметр — это 'dma_chan', то есть канал DMA, выбранный пользователем. Он позволяет осуществлять транзакции по шине, размер которых ограничен только объемом внутренней памяти. Может принимать одно из трех значений:
- SPI_DMA_DISABLED: этот параметр ограничивает размер транзакций.
- SPI_DMA_DISABLED: этот параметр выбирается, если только SPI-флэш-память использует эту шину.
- SPI_DMA_CH_AUTO: это драйвер выделяет канал DMA.
|
1 |
esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, const spi_slave_interface_config_t *slave_config, spi_dma_chan_t dma_chan) |
Передача данных
Для выполнения SPI-транзакции на стороне ведомого устройства вызывается следующая функция: spi_slave_transmit(). Она принимает три параметра.
- Первый параметр — это «host», который обозначает периферийное устройство SPI, используемое в качестве интерфейса SPI.
- Второй параметр — 'trans_desc', который представляет собой указатель на переменную, содержащую указатель на описание выполняемой транзакции. Он не является константой, поскольку может возникнуть необходимость записать статус обратно в описание транзакции.
- Третий параметр — 'ticks_to_wait', который определяет количество тактов, в течение которых ожидается возврат значения. Мы можем использовать portMAX_DELAY, чтобы избежать таймаута.
|
1 |
esp_err_t spi_slave_transmit(spi_host_device_t host, spi_slave_transaction_t *trans_desc, TickType_t ticks_to_wait) |
API драйвера ведущего устройства SPI для ESP32
Теперь давайте обсудим библиотеку драйверов SPI Master и ее функции, предоставляемые ESP-IDF, которые будут использоваться в этом руководстве. ESP-IDF предоставляет библиотеку driver/spi_master.h , которая управляет периферийными устройствами SPI ESP32, когда они выступают в качестве ведущих устройств.
Это заголовочный файл, необходимый для включения драйвера SPI Master:
|
1 |
#include "driver/spi_master.h" |
Инициализация
Для начала инициализируем SPI. Для этого используем функцию spi_bus_initialize(), которая принимает три параметра.
- Первый параметр — это 'host_id', который обозначает SPI-периферийное устройство, отвечающее за управление шиной.
- Второй параметр — это 'bus_config', который представляет собой указатель на структуру spi_bus_config_t, обозначающую инициализацию хоста.
- Третий параметр — 'dma_chan', это канал DMA, выбранный пользователем. Он позволяет осуществлять транзакции по шине, размер которых ограничен только объемом внутренней памяти. Может принимать одно из трех значений:
- SPI_DMA_DISABLED: этот параметр ограничивает размер транзакций.
- SPI_DMA_DISABLED: этот параметр выбирается, если только SPI-флэш-память использует эту шину.
- SPI_DMA_CH_AUTO: драйвер выделяет канал DMA.
|
1 |
esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t *bus_config, spi_dma_chan_t dma_chan) |
Регистрация ведомого устройства
Для назначения устройства на шине SPI мы будем использовать функцию spi_bus_add_device(). Эта функция зарегистрирует ведомое устройство, инициализируя его внутренние структуры и выделяя вывод CS. Как упоминалось ранее, ведущее устройство SPI может управлять до тремя ведомыми устройствами SPI. Эта функция принимает три параметра.
- Первый параметр — это 'host_id', который указывает на периферийное устройство SPI, которому будет назначено устройство.
- Второй параметр — 'dev_config', который представляет собой конфигурацию протокола интерфейса SPI для устройства.
- Третий параметр — это 'handle', который представляет собой указатель на переменную, содержащую дескриптор устройства.
|
1 |
esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle) |
Передача данных
Функция spi_device_transmit() используется для отправки SPI-транзакции. После отправки транзакции она ожидает ее завершения, а затем возвращает управление. Эта функция принимает два параметра.
- Первый параметр — это 'handle', который представляет собой дескриптор устройства. Он получается с помощью функции spi_host_add_dev.
- Второй параметр — 'trans_desc', который представляет собой описание транзакции, подлежащей реализации.
|
1 |
esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *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’ («Выбрать шаблон»), чтобы продолжить.
В разделе Extension («Расширение») выберите опцию ESP-IDF:
В разделе 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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
/* SPI Slave example, sender (uses SPI master driver) This example code is in the Public Domain (or CC0 licensed, at your option.) Unless required by applicable law or agreed to in writing, this software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ #include <stdio.h> #include <stdint.h> #include <stddef.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "freertos/queue.h" #include "lwip/sockets.h" #include "lwip/dns.h" #include "lwip/netdb.h" #include "lwip/igmp.h" #include "esp_wifi.h" #include "esp_system.h" #include "esp_event.h" #include "nvs_flash.h" #include "soc/rtc_periph.h" #include "driver/spi_master.h" #include "esp_log.h" #include "esp_spi_flash.h" #include "driver/gpio.h" #include "esp_intr_alloc.h" /* SPI sender (master) example. This example is supposed to work together with the SPI receiver. It uses the standard SPI pins (MISO, MOSI, SCLK, CS) to transmit data over in a full-duplex fashion, that is, while the master puts data on the MOSI pin, the slave puts its own data on the MISO pin. This example uses one extra pin: GPIO_HANDSHAKE is used as a handshake pin. The slave makes this pin high as soon as it is ready to receive/send data. This code connects this line to a GPIO interrupt which gives the rdySem semaphore. The main task waits for this semaphore to be given before queueing a transmission. */ /* Pins in use. The SPI Master can use the GPIO mux, so feel free to change these if needed. */ #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 #define GPIO_HANDSHAKE 2 #define GPIO_MOSI 12 #define GPIO_MISO 13 #define GPIO_SCLK 15 #define GPIO_CS 14 #elif CONFIG_IDF_TARGET_ESP32C3 #define GPIO_HANDSHAKE 3 #define GPIO_MOSI 7 #define GPIO_MISO 2 #define GPIO_SCLK 6 #define GPIO_CS 10 #endif //CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 #ifdef CONFIG_IDF_TARGET_ESP32 #define SENDER_HOST HSPI_HOST #elif defined CONFIG_IDF_TARGET_ESP32S2 #define SENDER_HOST SPI2_HOST #elif defined CONFIG_IDF_TARGET_ESP32C3 #define SENDER_HOST SPI2_HOST #endif //The semaphore indicating the slave is ready to receive stuff. static xQueueHandle rdySem; /* This ISR is called when the handshake line goes high. */ static void IRAM_ATTR gpio_handshake_isr_handler(void* arg) { //Sometimes due to interference or ringing or something, we get two irqs after eachother. This is solved by //looking at the time between interrupts and refusing any interrupt too close to another one. static uint32_t lasthandshaketime; uint32_t currtime=esp_cpu_get_ccount(); uint32_t diff=currtime-lasthandshaketime; if (diff<240000) return; //ignore everything <1ms after an earlier irq lasthandshaketime=currtime; //Give the semaphore. BaseType_t mustYield=false; xSemaphoreGiveFromISR(rdySem, &mustYield); if (mustYield) portYIELD_FROM_ISR(); } //Main application void app_main(void) { esp_err_t ret; spi_device_handle_t handle; //Configuration for the SPI bus spi_bus_config_t buscfg={ .mosi_io_num=GPIO_MOSI, .miso_io_num=GPIO_MISO, .sclk_io_num=GPIO_SCLK, .quadwp_io_num=-1, .quadhd_io_num=-1 }; //Configuration for the SPI device on the other side of the bus spi_device_interface_config_t devcfg={ .command_bits=0, .address_bits=0, .dummy_bits=0, .clock_speed_hz=5000000, .duty_cycle_pos=128, //50% duty cycle .mode=0, .spics_io_num=GPIO_CS, .cs_ena_posttrans=3, //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK .queue_size=3 }; //GPIO config for the handshake line. gpio_config_t io_conf={ .intr_type=GPIO_INTR_POSEDGE, .mode=GPIO_MODE_INPUT, .pull_up_en=1, .pin_bit_mask=(1<<GPIO_HANDSHAKE) }; int n=0; char sendbuf[128] = {0}; char recvbuf[128] = {0}; spi_transaction_t t; memset(&t, 0, sizeof(t)); //Create the semaphore. rdySem=xSemaphoreCreateBinary(); //Set up handshake line interrupt. gpio_config(&io_conf); gpio_install_isr_service(0); gpio_set_intr_type(GPIO_HANDSHAKE, GPIO_INTR_POSEDGE); gpio_isr_handler_add(GPIO_HANDSHAKE, gpio_handshake_isr_handler, NULL); //Initialize the SPI bus and add the device we want to send stuff to. ret=spi_bus_initialize(SENDER_HOST, &buscfg, SPI_DMA_CH_AUTO); assert(ret==ESP_OK); ret=spi_bus_add_device(SENDER_HOST, &devcfg, &handle); assert(ret==ESP_OK); //Assume the slave is ready for the first transmission: if the slave started up before us, we will not detect //positive edge on the handshake line. xSemaphoreGive(rdySem); while(1) { int res = snprintf(sendbuf, sizeof(sendbuf), "Sender, transmission no. %04i. Last time, I received: \"%s\"", n, recvbuf); if (res >= sizeof(sendbuf)) { printf("Data truncated\n"); } t.length=sizeof(sendbuf)*8; t.tx_buffer=sendbuf; t.rx_buffer=recvbuf; //Wait for slave to be ready for next byte before sending xSemaphoreTake(rdySem, portMAX_DELAY); //Wait until slave is ready ret=spi_device_transmit(handle, &t); printf("Received: %s\n", recvbuf); n++; } //Never reached. ret=spi_bus_remove_device(handle); assert(ret==ESP_OK); } |
Как работает код?
Для начала мы подключим необходимые библиотеки, включая библиотеки FreeRTOS для генерации задержек, создания очередей и семафоров, driver/gpio.h, поскольку нам нужно назначить контакты связи SPI на контакты GPIO ESP32, driver/spi_master.h для функциональности SPI-мастера, esp_log.h в качестве библиотеки для вывода информационных сообщений и т.д.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <stdio.h> #include <stdint.h> #include <stddef.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "freertos/queue.h" #include "lwip/sockets.h" #include "lwip/dns.h" #include "lwip/netdb.h" #include "lwip/igmp.h" #include "esp_wifi.h" #include "esp_system.h" #include "esp_event.h" #include "nvs_flash.h" #include "soc/rtc_periph.h" #include "driver/spi_master.h" #include "esp_log.h" #include "esp_spi_flash.h" #include "driver/gpio.h" #include "esp_intr_alloc.h" |
Это контакты GPIO ESP32, которые будут использоваться для связи по протоколу SPI. Используйте эти контакты GPIO для соединения двух плат ESP32. Вы также можете изменить эти контакты.
|
1 2 3 4 5 6 |
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 #define GPIO_HANDSHAKE 2 #define GPIO_MOSI 12 #define GPIO_MISO 13 #define GPIO_SCLK 15 #define GPIO_CS 14 |
Для ESP32 мы используем HSPI в качестве интерфейса SPI.
|
1 2 |
#ifdef CONFIG_IDF_TARGET_ESP32 #define SENDER_HOST HSPI_HOST |
Мы будем использовать семафор для создания программного прерывания, при котором одна функция выполняется всякий раз, когда выполняется другая функция. Семафор будет отслеживать готовность ведомого устройства к анализу транзакции. Внутри функции gpio_handshake_isr_handler() мы используем функцию xSemaphoreGiveFromISR() для передачи семафора для прерывания. Она принимает два аргумента. Первый аргумент — это дескриптор семафора, а второй — приоритет. Более того, чтобы гарантировать наличие только одного запроса на прерывание и отсутствие двух прерываний, близких друг к другу, отслеживается время между прерываниями, и рассматриваются только действительные запросы на прерывание.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static xQueueHandle rdySem; static void IRAM_ATTR gpio_handshake_isr_handler(void* arg) { static uint32_t lasthandshaketime; uint32_t currtime=esp_cpu_get_ccount(); uint32_t diff=currtime-lasthandshaketime; if (diff<240000) return; lasthandshaketime=currtime; BaseType_t mustYield=false; xSemaphoreGiveFromISR(rdySem, &mustYield); if (mustYield) portYIELD_FROM_ISR(); } |
app_main()
Внутри функции app_main() сначала задаются параметры конфигурации шины SPI. Они показаны ниже:
|
1 2 3 4 5 6 7 |
spi_bus_config_t buscfg={ .mosi_io_num=GPIO_MOSI, .miso_io_num=GPIO_MISO, .sclk_io_num=GPIO_SCLK, .quadwp_io_num=-1, .quadhd_io_num=-1 }; |
Также задаются параметры конфигурации ведомого устройства. Это включает в себя тактовую частоту, коэффициент заполнения, режим работы и т. д.
|
1 2 3 4 5 6 7 8 9 10 11 |
spi_device_interface_config_t devcfg={ .command_bits=0, .address_bits=0, .dummy_bits=0, .clock_speed_hz=5000000, .duty_cycle_pos=128, //50% duty cycle .mode=0, .spics_io_num=GPIO_CS, .cs_ena_posttrans=3, //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK .queue_size=3 }; |
Следующая структура содержит конфигурацию GPIO для линии квитирования. Она включает в себя тип прерывания GPIO, режим GPIO (вход или выход), подтягивающий резистор GPIO и контакт GPIO, устанавливаемый с помощью битовой маски, где каждый бит соответствует контакту GPIO.
|
1 2 3 4 5 6 |
gpio_config_t io_conf={ .intr_type=GPIO_INTR_POSEDGE, .mode=GPIO_MODE_INPUT, .pull_up_en=1, .pin_bit_mask=(1<<GPIO_HANDSHAKE) }; |
Затем мы создаём бинарный семафор с помощью функции xSemaphoreCreateBinary(). Она не принимает никаких аргументов и возвращает переменную типа SemaphoreHandle_t. rdySem — это глобальная переменная, которую мы используем для хранения семафора.
|
1 |
rdySem=xSemaphoreCreateBinary(); |
Следующие строки кода настраивают прерывание по линии подтверждения связи.
|
1 2 3 4 |
gpio_config(&io_conf); gpio_install_isr_service(0); gpio_set_intr_type(GPIO_HANDSHAKE, GPIO_INTR_POSEDGE); gpio_isr_handler_add(GPIO_HANDSHAKE, gpio_handshake_isr_handler, NULL); |
Начинается все с вызова функции gpio_config(), которая используется для настройки общей конфигурации GPIO. Она принимает один параметр — указатель на структуру конфигурации GPIO.
|
1 |
gpio_config(&io_conf); |
Далее мы вызываем функцию gpio_install_isr_service() для установки службы обработки прерываний. Она также принимает один параметр — флаг выделения прерываний.
|
1 |
gpio_install_isr_service(0); |
Затем мы устанавливаем тип запуска прерывания с помощью функции gpio_set_intr_type(). Она принимает два параметра. Первый параметр — это номер контакта GPIO, который будет использоваться для установки типа прерывания, в данном случае это GPIO2. Второй параметр — это тип прерывания, который в данном случае указывается как 'GPIO_INTR_POSEDGE'. Таким образом, прерывание будет срабатывать по положительному фронту (от низкого к высокому) тактового сигнала.
|
1 |
gpio_set_intr_type(GPIO_HANDSHAKE, GPIO_INTR_POSEDGE); |
Наконец, чтобы добавить обработчик прерываний для нашего GPIO-контакта, используемого для подтверждения соединения, мы вызовем функцию gpio_isr_handler_add(). Эта функция принимает три параметра. Первый параметр — номер GPIO. Второй параметр — обработчик прерываний для этого конкретного GPIO-контакта, а третий параметр — значение обработчика прерываний, которое в данном случае равно NULL.
|
1 |
gpio_isr_handler_add(GPIO_HANDSHAKE, gpio_handshake_isr_handler, NULL); |
Инициализация и регистрация устройства
Далее мы инициализируем шину SPI и регистрируем ведомое устройство.
|
1 2 3 4 |
ret=spi_bus_initialize(SENDER_HOST, &buscfg, SPI_DMA_CH_AUTO); assert(ret==ESP_OK); ret=spi_bus_add_device(SENDER_HOST, &devcfg, &handle); assert(ret==ESP_OK); |
Для инициализации шины SPI мы вызываем функцию spi_bus_initialze(). Она принимает три параметра.
|
1 |
ret=spi_bus_initialize(SENDER_HOST, &buscfg, SPI_DMA_CH_AUTO); |
- Первый параметр — это используемый экземпляр SPI. Как мы уже упоминали, ESP32 имеет четыре экземпляра SPI: SPI0, SPI1, SPI2 и SPI3. Однако мы можем использовать только SPI2 и SPI3, поскольку остальные зарезервированы для флэш-памяти. В данном случае мы указали первый параметр как 'SENDER_HOST', который определен как HSPI_HOST. Следовательно, в данном случае используется SPI2.
- Второй параметр — это указатель на константную структуру конфигурации типа spi_bus_config_t, которую мы определили ранее. Она содержит информацию о контактах GPIO, используемых для связи по протоколу SPI.
|
1 2 3 4 5 6 7 |
spi_bus_config_t buscfg={ .mosi_io_num=GPIO_MOSI, .miso_io_num=GPIO_MISO, .sclk_io_num=GPIO_SCLK, .quadwp_io_num=-1, .quadhd_io_num=-1 }; |
- Третий параметр указывает используемый канал DMA. Эта функция позволяет экземпляру SPI получать доступ к оперативной памяти в качестве буфера передачи. В нашем случае он установлен как 'SPI_DMA_CH_AUTO', что означает, что драйвер выделит канал DMA самостоятельно.
Для регистрации ведомого устройства мы вызываем функцию spi_bus_add_device(). Она принимает три параметра.
|
1 |
ret=spi_bus_add_device(SENDER_HOST, &devcfg, &handle); |
- Первый параметр — это используемый экземпляр SPI, которым является SPI2.
- Второй параметр — это указатель на константную структуру конфигурации типа spi_device_interface_config_t, которую мы определили ранее. Она содержит информацию о ведомом устройстве, включая тактовую частоту, режим работы, выделенный вывод CS и т. д.
|
1 2 3 4 5 6 7 8 9 10 11 |
spi_device_interface_config_t devcfg={ .command_bits=0, .address_bits=0, .dummy_bits=0, .clock_speed_hz=5000000, .duty_cycle_pos=128, //50% duty cycle .mode=0, .spics_io_num=GPIO_CS, .cs_ena_posttrans=3, //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK .queue_size=3 }; |
- Третий параметр — это указатель на переменную типа spi_device_handle_t.
|
1 |
spi_device_handle_t handle; |
Затем семафор отпускается.
|
1 |
xSemaphoreGive(rdySem); |
Внутри бесконечного цикла данные передаются на подчиненное устройство с помощью функции spi_device_transmit().
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
while(1) { int res = snprintf(sendbuf, sizeof(sendbuf), "Sender, transmission no. %04i. Last time, I received: \"%s\"", n, recvbuf); if (res >= sizeof(sendbuf)) { printf("Data truncated\n"); } t.length=sizeof(sendbuf)*8; t.tx_buffer=sendbuf; t.rx_buffer=recvbuf; xSemaphoreTake(rdySem, portMAX_DELAY); ret=spi_device_transmit(handle, &t); printf("Received: %s\n", recvbuf); n++; } |
Затем, чтобы удалить устройство из шины SPI, мы вызываем функцию spi_bus_remove_device(). Она принимает один параметр — дескриптор устройства.
|
1 2 |
ret=spi_bus_remove_device(handle); assert(ret==ESP_OK); |
Компиляция скетча
Для прошивки микросхемы введите следующую команду в последовательном терминале. Не забудьте заменить COM-порт на тот, через который подключена ваша плата.
статья в разработке
3 просмотров







