Добрый день, уважаемый читатель! В этой статье обсудим методы работы со встроенными портами ввода-вывода GPIO в цифровом режиме.
Если вы создавали скетчи для Arduino IDE, то наверное, знаете, как осуществляется работа с GPIO для Arduino:
pinMode(10, OUTPUT); // Настраиваем PIN10 на выход digitalWrite(10, HIGH); // Записываем в PIN10 высокий уровень
В данном случае pinMode
настраивает порт ввода-вывода на режим “выход”, а digitalWrite
служит для записи в ранее настроенный порт логической единицы (или нуля). Это унифицированные функции Arduino, которые “внешне” не зависят от аппаратной платформы, а вот их внутренняя реализация будет зависеть от того, какой микроконтроллер вы используете. Это позволяет сравнительно легко переносить код с одного микроконтроллера на другой без адаптации (на самом деле это не всегда прокатывает, но тем не менее, разработчики платформы Arduino к этому стремятся).
На ESP-IDF необходимости в такой унификации нет, поэтому используются более специфичные функции. Их мы сегодня и обсудим.
Какие выводы GPIO можно использовать для ввод и вывод
Давайте вспомним, какие выводы можно использовать на ESP32 (я буду рассматривать линейки ESP32-WROOM или ESP32-WROVER).
Чип ESP32 имеет 34 физических контакта GPIO. Каждая контактная площадка может использоваться как вход/выход общего назначения (GPIO) или может быть подключена к внутреннему периферийному сигналу. Мультиплексоры IO_MUX, RTC IO_MUX и матрица GPIO отвечают за маршрутизацию сигналов от периферийных устройств к контактам GPIO. Вместе эти системы обеспечивают гибко настраиваемый ввод-вывод.
Порты ввода-вывода для ESP32-DevKitC V4
- На ввод и вывод для ESP32-WROOM можно смело использовать следующие 18 выводов GPIO: 4, 14, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33. Все указанные выводы имеют встроенные программно-подключаемые резисторы подтяжки 45 кОм (в документации это называется “слабая подтяжка”). Я буду называть эти порты “универсальными”, для простоты понимания. Некоторые из них можно назначить для использования различных интерфейсов: I2C, SPI и т.д. Примечания: для ESP32-WROVER выводы 16 и 17 использовать нельзя.
- Выводы GPIO 34, 35, 36, 39 можно использовать только на ввод, и у них отсутствуют встроенные резисторы слабой подтяжки. На эти же выводы выведен канал ADC1.
- Можно ещё использовать GPIO 0, 2, 5, 12, 15, но с учетом того, что их нельзя подтягивать ни к питанию, ни к земле при старте микроконтроллера.
На все эти выводы можно подключить обработчики прерываний для отслеживания изменения уровня извне микроконтроллера, но прерывания обсудим в следующей статье.
Хочу обратить ваше внимание только на то, что, согласно спецификации, выводы GPIO допускают ток аж до 40 mA “высокого” уровня и до 28 mA “низкого” уровня. Это позволяет управлять различными слаботочными устройствами типа светодиодов напрямую, без применения коммутирующих транзисторов, нужно только учитывать напряжение на выводе не более 3.3В.
Более подробно об выводах микроконтроллера ESP32 вы можете прочитать в отдельной статье. Там же можно скачать удобную excel-таблицу для распределения выводов в своем проекте с учетом их особенностей. Если вам не хватает свободных GPIO, вы можете воспользоваться I2C расширителями GPIO, например PCF8574 или MCP23017.
Настройка порта GPIO
Для работы с портами GPIO необходимо подключить модуль “driver/gpio.h”:
# include "driver/gpio.h"
Как и в Arduino IDE, прежде чем начинать работу с GPIO, его нужно настроить (сконфигурировать). Каждый “универсальный” порт можно настроить:
- направление: на вход или выход
- слабая подтяжка (45 КОм) к +3,3В или к “земле”
- прерывание (если необходимо)
ESP-IDF предлагает два метода конфигурации:
- Пакетная конфигурация сразу нескольких портов: с помощью функции
gpio_config (const gpio_config_t* pGPIOConfig)
. Эта функция позволяет за один вызов настроить все параметры для нескольких выбранных портов: направление, подтяжки, прерывания. Соответственно это чуть более быстрый способ. - С помощью набора функций
gpio_set_direction
,gpio_set_pull_mode
, и т.д. Это более “детализированные” функции и работают они только для одного выбранного порта. Я чаще использую именно этот способ, скорее всего “по привычке” (так как они более похожи на способ из Arduino IDE).
Выбор вывода для работы в режиме GPIO
Прежде всего необходимо настроить GPIO для использования в режиме ввода-вывода (так как GPIO на ESP32 могут быть использованы для разных целей с помощью мультиплексора выводов IO_MUX). Разработчики ESP32 не гарантируют, что после аппаратного сброса микроконтроллера все его выводы установятся в режим ввода-вывода. Поэтому операцию перевода нужных выводов в режим GPIO желательно делать всегда.
Сделать это можно с помощью функции:
esp_err_t gpio_reset_pin(gpio_num_t gpio_num)
где:
- gpio_num – номер вывода GPIO
Эта функция настраивает IOMUX для этого вывода на работу с GPIO, включает встроенную слабую подтяжку и отключает режимы работы на вход и на выход (то есть устанавливается режим GPIO_MODE_DISABLE – см. следующий раздел).
Для этой же цели можно воспользоваться другой функцией, которая просто перенастраивает IOMUX для этого вывода на работу с GPIO:
void gpio_pad_select_gpio(uint8_t gpio_num)
где:
- gpio_num – номер вывода GPIO
Примечание: функция gpio_pad_select_gpio является устаревшей и в ESP-IDF 5.x не доступна.
Режимы работы портов ввода-вывода
ESP32 поддерживает несколько режимов GPIO:
- GPIO_MODE_DISABLE – порт отключён
- GPIO_MODE_INPUT – порт работает только на вход
- GPIO_MODE_OUTPUT – порт работает только на выход
- GPIO_MODE_OUTPUT_OD – порт работает только на выход в режиме “открытый сток” (open-drain)
- GPIO_MODE_INPUT_OUTPUT_OD – порт может работать одновременно и на вход и на выход с открытым стоком
- GPIO_MODE_INPUT_OUTPUT – порт может работать одновременно и на вход и на выход
В режимах с открытым коллектором (OD) микроконтроллер управляет только низким логическим уровнем, при установке на GPIO логической единицы вывод отключается и остается “болтаться в воздухе”. Этот режим удобно использовать для датчиков типа DHT11-22 или для управления светодиодами “по низкому уровню” (катодом к выводу, анодом к +3,3В).
Для выбора режима порта используйте функцию
esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode)
где:
- gpio_num – идентификатор GPIO
- mode – режим работы
Переключить режим работы порта можно в любой момент, не обязательно делать это только при старте прошивки.
Встроенная подтяжка
Большинство портов имеют встроенные резисторы слабой подтяжки. Чтобы их задействовать, существует несколько функций.
Универсальная функция:
esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull)
где:
- gpio_num – идентификатор GPIO
- pull – режим подтяжки
Режим gpio_pull_mode_t может принимать одно из нескольких значений:
- GPIO_PULLUP_ONLY – подтяжка к питанию +3,3В
- GPIO_PULLDOWN_ONLY – подтяжка к “земле”
- GPIO_PULLUP_PULLDOWN – подтяжка одновременно к питанию +3,3В и “земле”
- GPIO_FLOATING – подтяжка отключена
Однако можно использовать и несколько более простых функций:
esp_err_t
gpio_pullup_en(gpio_num_t gpio_num)
– включить подтяжку к питанию +3,3Вesp_err_t gpio_pullup_dis(gpio_num_t gpio_num)
– отключить подтяжку к питанию +3,3Вesp_err_t gpio_pulldown_en(gpio_num_t gpio_num
)
– включить подтяжку к “земле”esp_err_t gpio_pulldown_dis(gpio_num_t gpio_num)
– отключить подтяжку к “земле”
Какой метод использовать – выбирайте на свой вкус.
Настройка допустимого выходного тока
Для GPIO, работающих на выход, ESP32 позволяет задать максимальный ток через выход. Это может быть полезно в некоторых случаях, например можно ограничить ток через светодиод без использования резистора. Для этого воспользуйтесь функцией:
esp_err_t gpio_set_drive_capability (gpio_num_t gpio_num , gpio_drive_cap_t strength)
где:
- gpio_num – идентификатор GPIO
- strength – максимально допустимый ток
Режим gpio_drive_cap_t может принимать одно из нескольких значений:
- GPIO_DRIVE_CAP_0 – слабый, до ~5мА
- GPIO_DRIVE_CAP_1 – сильнее, до ~10мА
- GPIO_DRIVE_CAP_2 – средний (по умолчанию), до ~20мА
- GPIO_DRIVE_CAP_3 – максимальный, до ~40мА
Как видите, по умолчанию ток высокого уровня ограничен на уровне 20мА. В большинстве случаев вызывать данную функцию при настройке порта не требуется. Но если ваше устройство на ESP32 требует большего тока (например при управлении мощным биполярным транзистором), то вы можете столкнуться с “неправильным” поведением.
Запись логического уровня в GPIO
Для записи данных в выходной порт необходимо воспользоваться функцией:
esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level)
где:
- gpio_num – идентификатор GPIO
- level – логический уровень, 0 (низкий) или 1 (высокий)
Ничего сложного, всё предельно просто.
Чтение логического уровня из GPIO
Для чтения данных из порта необходимо воспользоваться другой функцией:
uint32_t gpio_get_level(gpio_num_t gpio_num)
где:
- gpio_num – идентификатор GPIO
Если GPIO не настроен для ввода (или ввода и вывода), возвращаемое значение всегда равно 0.
Практическое использование GPIO в режиме вывода – мигаем светодиодом
Допустим, мы хотим использовать светодиод, подключенный к одному из выводов через токоограничительный резистор, например это GPIO12. Классика жанра.
Источник: Яндекс Картинки
- С управлением по высокому уровню. Анод через резистор к GPIO, катод – к общему проводу. В этом случае следует использовать режим GPIO_MODE_OUTPUT.
- С управлением по низкому уровню. Анод к +3,3В, катод через резистор к GPIO. В этом случае лучше использовать режим GPIO_MODE_OUTPUT_OD.
Для ESP32 наверное оптимальнее использовать первый способ, так как ток GPIO для высокого уровня может достигать до 40мА, а для низкого – только 28мА. Хотя для светодиода вполне достаточно 10мА, в крайнем случае – 20мА (для старых советских светодиодов зеленого цвета).
Настройка вывода будет выглядеть так:
Использование GPIO для пробуждения микроконтроллера
Если вы используете в своих разработках режим глубокого сна (например при питании от батарей), то вы можете использовать вывод GPIO для пробуждения микроконтроллера по внешнему сигналу. Задействовать данную функцию можно с помощью функции:
esp_err_t gpio_wakeup_enable (gpio_num_t gpio_num, gpio_int_type_t intr_type)
где:
- gpio_num – идентификатор GPIO
- intr_type – логический уровень на GPIO для пробуждения. Можно использовать только GPIO_INTR_LOW_LEVEL или GPIO_INTR_HIGH_LEVEL.
Отключить пробуждение можно с помощью функции gpio_wakeup_disable (gpio_num_t gpio_num).
Ну вот и всё, о чем я хотел рассказать в данной статье. Пример вы можете посмотреть на GitHub.
Прерывания по изменению уровня на GPIO мы обсудим в следующей статье, так как это отдельная большая тема.
Полезные ссылки
На этом пока всё, до встречи на сайте и на telegram-канале!. Если Вам понравилась статья – кликните на любое рекламное объявление, этого будет вполне достаточно для поддержки автора.
Доброе время суток, gpio_pad_select_gpio, откуда вы её все берёте ?
Добрый день. Это более низкоуровневая функция, чем остальные. Лежит она тут:
https://github.com/espressif/esp-idf/blob/master/components/esp_rom/include/esp32/rom/gpio.h#L229-L236
тогда почему не gpio_reset_pin, если речь идёт о driver/gpio.h ?
Хороший вариант, согласен. Не обращал внимания на эту функцию, спасибо. Надо будет дополнить статью.
PS: В данном случае сработал обычный принцип: “Работает и ладно”. Как у кого-то когда-то прочитал, так до сих пор сам и делаю.
#define GPIO_ENABLE_W1TS_REG *(volatile uint32_t*)0x3FF44024 // GPIO 0-31 output enable register_W1TS
#define GPIO_ENABLE_W1TC_REG *(volatile uint32_t*)0x3FF44028 // GPIO 0-31 output enable register_W1TC
#define LED_GPIO GPIO_NUM_12
GPIO_OUT_W1TC_REG |= (1ULL<<LED_GPIO); //включаем индикатор (0 на выход)
GPIO_OUT_W1TS_REG |= (1ULL<<LED_GPIO); //выключаем индикатор (1 на выход)
Да, можно работать и напрямую через регистры.
Но статья все таки для начинающих, а не для любителей погружения в глубины.
Тогда уж программируйте на ассемблере )))
А можно ссылку где об этом почитать. А то у меня выдает ошибку на GPIO_OUT_W1TC_REG
Не глубины ради – удобства для! ))
Вдруг кому-то понадобится разом включить/выключить несколько выводов… А тут по маске бац – и готово! Дешево и сердито.))
Кстати, подборка статей замечательная – для новичков то, что нужно! Простым языком и всё доходчиво написано. Читаю с большим удовольствием. Пожалуйста не забрасывайте это дело!
Удачи!
Отличная статья для начинающих, спасибо!!!
Если я верно понял, то в практическом примере с мигающим светодиодом, необходимо вначале инициализировать пин функцией gpio_reset_pin() или gpio_pad_select_gpio()? Прежде чем вызывать gpio_set_direction() и gpio_set_pull_mode(). И ещё, кстати, сейчас вместо функции gpio_pad_select_gpio() вроде как надо использовать esp_rom_gpio_pad_select_gpio(). Причём в официальной документации ESP-IDF ни той ни другой функции поиск не находит, что странно. Это недостаток именно документации или намёк на то, что эти функции использовать напрямую нежелательно?
Да, прежде чем использовать любой вывод как GPIO желательно перевести его в этот режим с помощью gpio_reset_pin().
Раньше можно было использовать gpio_pad_select_gpio(), и она очень часто встречается в устаревших примерах в интернете. Но с 5 версии она больше не доступна, вместо этого следует использовать только gpio_reset_pin().