На данном занятии мы начнем работу с интерфейсом SPI, который поддерживается аппаратно в микроконтроллере ESP32.
С данным интерфейсом мы очень неплохо знакомы, так как мы его постоянно используем при написании кода для других контроллеров, поэтому, дабы не тратить драгоценное время, мы не будем останавливаться на изучении передачи данных по шине и разновидностях режимов этой самой передачи.
Как мы знаем, у контроллера ESP32 целых четыре модуля SPI
Все они через мультиплексор общаются с ножками. Мы также можем эти ножки переопределять. Один из модулей (SPI0) у нас уже задействован для работы с внешней микросхемой FLASH-памяти и те ножки, по которым данный модуль общается с микросхемой, на нашей плате наружу не выведены, так что если что, мы не ошибёмся и не подключим к ним что-нибудь ещё.
Также с модулями SPI мы можем использовать DMA. Какой DMA с каким модулем использовать, мы разбираться сейчас не будем, так как за нас это всё проделает SDK, которым мы пользуемся. Возможно, в дальнейшем, мы займёмся отдельно периферией DMA в нашем контроллере. По рисунку, находящемуся выше, мы можем наблюдать, что SPI0 доступ к DMA не имеет вообще, остальные модули имеют, зато данный модуль работает с кэшем.
Также мы знаем, что SPI может работать как в режиме MASTER, так и в режиме SLAVE. Модуль SPI1 может быть использован только в качестве ведущего (MASTER). Модули SPI2 и SPI3 могут работать в обеих режимах, как в качестве ведущего, так и в качестве ведомого.
Шины сигналов SPI состоят из сигналов D, Q, CS0-CS2, CLK, WP и HD, как показано в таблице ниже. Контроллеры SPI0 и SPI1 совместно используют одну сигнальную шину через арбитр; сигналы общей шины начинаются с SPI. Контроллеры SPI2 и SPI3 используют сигнальные шины, начинающиеся с HSPI и VSPI соответственно. Линии ввода-вывода, включенные в вышеупомянутые сигнальные шины, могут быть сопоставлены с контактами либо через модуль IO_MUX, либо через матрицу GPIO
Вообщем, если нам вдруг потребуется что-то знать ещё по работе модулей, то мы вернёмся к документации уже по мере написания кода.
Работать на данном уроке с шиной SPI мы будем в режиме MASTER, так как он гораздо проще в программировании, с режимом SLAVE, если он нам вообще потребуется, мы познакомимся в будущих уроках. Также для ещё большей простоты программирования в данном уроке мы будем работать с шиной SPI только в режиме передачи. В качестве ведомого устройства, подключаемого по шине, мы возьмём восьмиразрядный семисегментный индикатор, динамическая индикация которого реализована на микросхеме-драйвере MAX7219. Данная микросхема общается с контроллером именно по шине SPI. Мы неоднократно уже работали с данной микросхемой с использованием других контроллеров, поэтому весь процесс работы с ней нам знаком.
Для подключения к данному индикатору на плате мы будем использовать следующие ножки
И в результате мы получим следующее соответствие ножек платы с ножками индикатора
3V3 — VCC
GND — GND
GPIO23 — DIN
GPIO5 — CS
GPIO18 — CLK
Также для исследования передачи данных мы подключим логический анализатор к ножкам GND, MOSI, CLK и CS. Получится следующая схема
Проект мы давайте сделаем из проекта урока 7 с именем I2C_LCD2004 и назовём его LED7219.
Прежде чем добавить данный проект в Espressif IDE, мы переименуем файлы lcd2004.h и lcd2004.h.c соответственно в max7219.h и max7219.c.
Сразу отредактируем их в блокноте и они приобретут следующий вид
1 2 3 4 5 6 7 8 9 10 11 |
#ifndef MAIN_LCD2004_H_ #define MAIN_LCD2004_H_ //================================================== #include <string.h> #include <stdio.h> #include <stdint.h> #include <driver/gpio.h> #include <driver/spi_master.h> //================================================== //================================================== #endif /* MAIN_LCD2004_H_ */ |
1 2 |
#include "max7219.h" //------------------------------------------------ |
Файлы i2c_user.h и i2c_user.c удалим.
Соответственно, в файле CMakeLists.txt также произойдут изменения
set(COMPONENT_SRCS «main.c» «max7219.c»)
Файл Kconfig.projbuild также приведём в соответствие используемым ножкам
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
menu "Example Configuration" config PIN_NUM_MOSI int "MOSI pin" range 0 48 default 23 help MOSI pin config PIN_NUM_CLK int "CLK pin" range 0 48 default 18 help CLK pin config PIN_NUM_CS int "CS pin" range 0 48 default 5 help CS pin endmenu |
Добавим наш проект в Espressif IDE и в файле main.h вместо подключения библиотеки для работы с I2C подключим библиотеку для SPI, а также изменим имя нашей подключаемой библиотеки для работы с индикатором. Содержимое файла станет следующим
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#ifndef MAIN_MAIN_H_ #define MAIN_MAIN_H_ //--------------------------------------------------------------------- #include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "driver/gpio.h" #include <driver/spi_master.h> #include "esp_err.h" #include "esp_log.h" #include "sdkconfig.h" #include "max7219.h" //--------------------------------------------------------------------- #endif /* MAIN_MAIN_H_ */ |
Перейдём в main.c и после строки
static const char *TAG = «main»;
удалим весь код до функции app_main, в которой оставим только бесконечный цикл и задержку
1 2 3 4 5 6 |
void app_main(void) { while (1) { vTaskDelay(100 / portTICK_PERIOD_MS); } } |
Попробуем собрать код, посмотреть содержимое конфигуратора, затем ещё раз собрать код.
Если всё нормально собирается, то идём дальше.
В функции app_main сконфигурируем модуль SPI. Делается это аналогично I2C заполнением полей переменной структуры и вызовом специальной функции для применения конфигурации. Перед заполнением полей объявим переменную для кода ошибки, а после вызова функции инициализации драйвера SPI выведем в лог код ошибки
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void app_main(void) { esp_err_t ret; // Configure SPI bus spi_bus_config_t cfg = { .mosi_io_num = CONFIG_PIN_NUM_MOSI, .miso_io_num = -1, .sclk_io_num = CONFIG_PIN_NUM_CLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 0, .flags = 0 }; ret = spi_bus_initialize(HSPI_HOST, &cfg, 1); ESP_LOGI(TAG, "spi bus initialize: %d", ret); |
В файле max7219.h объявим структуру для нашего устройства
1 2 3 4 5 6 7 8 |
#include <driver/spi_master.h> //================================================== typedef struct { spi_device_interface_config_t spi_cfg; spi_device_handle_t spi_dev; } max7219_t; //================================================== |
Вернёмся в main.c и в функции app_main объявим переменную типа данной структуры
1 2 |
ESP_LOGI(TAG, "spi bus initialize: %d", ret); max7219_t dev; |
Перейдём в файл max7219.c и объявим макросы для частоты шины и для количества используемых разрядов в индикаторе
1 2 3 4 5 |
#include "max7219.h" //================================================== #define CLOCK_SPEED_HZ (10000000) // 10 MHz #define ALL_DIGITS 8 //================================================== |
Ниже добавим функцию отправки пакета байтов в шину
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//================================================== void Send_7219 (max7219_t *dev, uint16_t *buf, uint8_t sz) { spi_transaction_t t; memset(&t, 0, sizeof(t)); for(uint8_t i=0; i<sz; i++) { t.length = 16; t.tx_buffer = buf + i; spi_device_transmit(dev->spi_dev, &t); } } //================================================== |
Код для отправки байтов в SPI чем-то напоминает отправку в I2C. Здесь в цикле заполняются поля структуры, а именно длина пакета (так как у нас всегда будет отправка по 2 байта — адрес регистра и сами данные, то мы будем сразу отправлять 16-разрядный пакет). Затем даём полю указатель на данные и вызываем функцию отправки пакета.
Ниже добавим функцию инициализации индикатора
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 |
//================================================== void Init_7219 (max7219_t *dev, spi_host_device_t host, gpio_num_t cs_pin) { uint16_t buf[1] = {0}; memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg)); dev->spi_cfg.spics_io_num = cs_pin; dev->spi_cfg.clock_speed_hz = CLOCK_SPEED_HZ; dev->spi_cfg.mode = 0; dev->spi_cfg.queue_size = 1; dev->spi_cfg.flags = SPI_DEVICE_NO_DUMMY; spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev); buf[0] = 0x0F | 0x00<<8;//отключим режим тестирования Send_7219(dev, buf, 1); buf[0] = 0x0C | 0x00<<8;//выключим индикатор Send_7219(dev, buf, 1); buf[0] = 0x0B | (ALL_DIGITS-1)<<8;//кол-во используемых разрядов Send_7219(dev, buf, 1); buf[0] = 0x09 | 0xFF<<8;//включим режим декодирования Send_7219(dev, buf, 1); buf[0] = 0x0A | 0x02<<8;//интенсивность свечения Send_7219(dev, buf, 1); buf[0] = 0x0C | 0x01<<8;//включим индикатор Send_7219(dev, buf, 1); } //================================================== |
Здесь мы конфигурируем устройство, а затем посылаем поочерёдно команды в микросхему таким же образом, как мы это делали и в случаях использования других контроллеров.
Выше добавим функцию вывода числа на индикаторе, а также функцию очистки разрядов индикатора, которые у нас остались неизменными со времён использования индикатора с другими контроллерами
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 |
//================================================== void Number_7219 (max7219_t *dev, volatile long n) { uint8_t ng=0;//переменная для минуса uint16_t buf[ALL_DIGITS] = {0}; if(n<0) { ng=1; n*=-1; } uint8_t i=0; do { ++i; buf[i-1] = i | (n%10)<<8;//символ цифры n/=10; } while(n); if(ng) { buf[i-1] = (i+1) | 0x0A<<8;//символ - } Send_7219(dev, buf, ALL_DIGITS); } //================================================== void Clear_7219 (max7219_t *dev) { uint8_t i=ALL_DIGITS; uint16_t buf[ALL_DIGITS] = {0}; do { buf[8-i] = i | 0x0F<<8; } while (--i); Send_7219(dev, buf, ALL_DIGITS); } //================================================== |
В функции Init_7219 по окончанию очистим наш индикатор
1 2 |
Send_7219(dev, buf, 1); Clear_7219(dev); |
Во всех функциях данного файла мы не проверяем выполнение функций и не возвращаем коды ошибок. Если у нас индикатор откажется работать, мы всегда можем это сделать.
В заголовочном файле max7219.h объявим прототипы на 3 функции, которые нам могут потребоваться в дальнейшем
1 2 3 4 5 6 |
} max7219_t; //================================================== void Init_7219 (max7219_t *dev, spi_host_device_t host, gpio_num_t cs_pin); void Clear_7219 (max7219_t *dev); void Number_7219 (max7219_t *dev, volatile long n); //================================================== |
Вернёмся в файл main.c и в функции app_main вызовем функцию инициализации индикатора
1 2 |
max7219_t dev; Init_7219(&dev, HSPI_HOST, CONFIG_PIN_NUM_CS); |
Далее попробуем вывести на индикатор число
1 2 |
Init_7219(&dev, HSPI_HOST, CONFIG_PIN_NUM_CS); Number_7219(&dev, 12345678); |
Соберём код, прошьём контроллер и, если всё нормально, то мы увидим наше число на индикаторе
Также в терминальной программе мы видим, что у нас инициализация прошла успешно
Посмотрим также, как передаются данные в программе логического анализа
Здесь видно, что всё передаётся правильно и шина работает на частоте ровно 10 МГц.
Подождём 2 секунды
1 2 |
Number_7219(&dev, 12345678); vTaskDelay(2000 / portTICK_PERIOD_MS); |
Объявим локальную целочисленную переменную
1 2 3 |
void app_main(void) { volatile long cnt; |
Затем в бесконечном цикле запустим счётчик, значения которого будем выводить на индикатор
1 2 3 4 5 |
while (1) { Clear_7219(&dev); Number_7219(&dev, cnt); cnt++; if(cnt>99999999) cnt = 0; |
Снова соберём код, прошьём контроллер и увидим, как наш счётчик начнет считать
Итак, на данном уроке мы начали знакомиться с работой модуля SPI в контроллере ESP32, пока используя его только для передачи данных в режиме MASTER. Также мы не использовали другие режимы полярности и фазы. В дальнейшем мы обязательно познакомимся с работой SPI в ESP32 и на приём, а также и в режиме ведомого (SLAVE).
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь: Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести здесь
Индикатор светодиодный семиразрядный с драйвером MAX7219
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добрый день, как еще можно поддержать ресурс, Юmoney почему-то уже которую неделю выдает ошибку? Также очень интересен был бы урок по TWAI (CAN).
В личку в контакте напишите.
Спасибо, проблема решилась путем регистрации и подтверждения аккаунта на Юmoney