Урок 5. Управление портами ввода-вывода общего назначения (GPIO).

Уроки ESP32

В уроке научимся управлять портами ввода-вывода с помощью функций API. Разработаем три учебных проектов.

Предыдущий урок     Список уроков     Следующий урок

В этом уроке рассмотрим самые востребованные операции с портами.

  • Установка режима вывода:
    • вход  с различными вариантами подключения подтягивающего резистора;
    • выход – активный или с открытым стоком.
    • отключение вывода.
  • Установка состояния выхода.
  • Чтение состояния входа.

Работа с прерываниями по изменению состояния выводов, использование их в энергосберегающих режимах и т.п. будут рассмотрены в последующих уроках.

 

Функции API управления GPIO-портами.

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

Разработчики ESP32 не гарантируют, что после аппаратного сброса микроконтроллера все его выводы установятся в режим ввода-вывода. Поэтому операцию перевода нужных выводов в режим GPIO необходимо делать всегда.

Производится это функцией:

void gpio_pad_select_gpio(uint8_t gpio_num);

  • gpio_num – номер вывода.

Номер вывода может быть задан числом, например 2, или символьной константой, например GPIO_NUM_2. Во втором случае программа лучше читается. Мы подчеркиваем, что используем порядковый номер вывода, а не его битовую маску.

Дальше необходимо определить направление работы вывода: вход или выход. Для этого существует функция:

esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode);

  • gpio_num – номер вывода;
  • mode – направление. Может принимать значения:
    • GPIO_MODE_INPUT – вход;
    • GPIO_MODE_OUTPUT – активный выход;
    • GPIO_MODE_OUTPUT_OD – выход в режиме открытого стока;
    • GPIO_MODE_INPUT_OUTPUT – вход и выход;
    • GPIO_MODE_INPUT_OUTPUT_OD – вход и выход с открытым стоком;
    • GPIO_MODE_DISABLE – запрещен и вход, и выход.

Функция возвращает значения:

  • ESP_OK – в случае успешного выполнения;
  • ESP_ERR_INVALID_ARG – в случае неправильно заданных параметров.

Управление подтягивающим резистором производится функцией

esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull);

  • gpio_num – номер вывода;
  • pull – режим подтягивающего резистора. Может принимать значения:
    • GPIO_PULLUP_ONLY – подключен к питанию;
    • GPIO_PULLDOWN_ONLY – подключен к земле;
    • GPIO_PULLUP_PULLDOWN – подключен и к питанию, и к земле;
    • GPIO_FLOATING – отключен.

Функция возвращает значения:

  • ESP_OK – в случае успешного выполнения;
  • ESP_ERR_INVALID_ARG – в случае неправильно заданных параметров.

Есть другой способ управления подтягивающим резистором - отдельные для каждого режима функции. Кому-то он покажется более удобным.

esp_err_t gpio_pullup_en(gpio_num_t gpio_num); - резистор подключен к питанию;

esp_err_t gpio_pullup_dis(gpio_num_t gpio_num); - резистор отключен от питания;

esp_err_t gpio_pulldown_en(gpio_num_t gpio_num); - резистор подключен к земле;

esp_err_t gpio_pulldown_dis(gpio_num_t gpio_num); - резистор отключен от земли.

Все функции имеют только один аргумент:

  • gpio_num – номер вывода.

Еще существует функция, которая позволяет устанавливать режимы сразу для нескольких выводов. Одним ее вызовом можно конфигурировать, например 8ми разрядную шину данных.

esp_err_t gpio_config(const gpio_config_t * pGPIOConfig)

Номера выводов, их режимы заданы в элементах структуры pGPIOConfig.

typedef struct {
    uint64_t pin_bit_mask;          /* битовая маска вывода */
    gpio_mode_t mode;               /* направление вывода */
    gpio_pullup_t pull_up_en;       /* разрешение подтягивающего резистора к питанию */
    gpio_pulldown_t pull_down_en;   /* разрешение подтягивающего резистора к земле */
    gpio_int_type_t intr_type;      /* управление прерыванием */
} gpio_config_t;

  • pin_bit_mask – это не номер, а маска выводов. Что позволяет задавать режим сразу нескольких выводов. Конечно, если у них одинаковая конфигурация.

Маска вывода задается, например 1<<2 или GPIO_SEL_2.

Сразу несколько выводов задаются выражением с логическим ИЛИ масок отдельных выводов.

pin_bit_mask = GPIO_SEL_2 | GPIO_SEL_5 | GPIO_SEL_10;

  • mode – определяет направление вывода:
    • GPIO_MODE_INPUT – вход;
    • GPIO_MODE_OUTPUT – активный выход;
    • GPIO_MODE_OUTPUT_OD – выход в режиме открытого стока;
    • GPIO_MODE_INPUT_OUTPUT – вход и выход;
    • GPIO_MODE_INPUT_OUTPUT_OD – вход и выход с открытым стоком;
    • GPIO_MODE_DISABLE – запрещен и вход, и выход.
  • pull_up_en – управляет подтягивающим резистором, подключенным к питанию:
    • GPIO_PULLUP_DISABLE – резистор отключен;
    • GPIO_PULLUP_ENABLE – резистор подключен.
  • pull_down_en – управляет подтягивающим резистором, подключенным к земле.
    • GPIO_PULLDOWN_DISABLE – резистор отключен;
    • GPIO_PULLDOWN_ENABLE – резистор подключен.

Чтобы конфигурировать нужные выводы необходимо задать элементы структуры типа gpio_config_t и вызвать функцию gpio_config с указателем на структуру в качестве аргумента.

Например, следующие строки конфигурируют выводы 2, 5 и 10.

gpio_config_t conf_gpio; // объявление структуры конфигурации
conf_gpio .pin_bit_mask = GPIO_SEL_2 | GPIO_SEL_5 | GPIO_SEL_10; // выводы
conf_gpio.mode = GPIO_MODE_INPUT; // входы
conf_gpio.pull_up_en = GPIO_PULLUP_ENABLE; // резистор на питание
conf_gpio.pull_down_en = GPIO_PULLDOWN_DISABLE; // без резистора на землю
conf_gpio.intr_type = GPIO_PIN_INTR_DISABLE;  // прерывание запрещено

gpio_config(&conf_gpio); // установка конфигурации

Осталось выяснить, как устанавливать состояние выходов и считывать состояние входов. Для этого есть две простые функции.

esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level)

Устанавливает выход номер gpio_num в состояние level.

Аргумент level – может иметь значения:

  • 0 – низкий уровень;
  • 1 – высокий уровень.

int gpio_get_level(gpio_num_t gpio_num)

Возвращает состояние вывода с номером gpio_num.

  • 0 – низкий уровень;
  • 1 – высокий уровень.

Пока все функции, которые нам необходимы. Даже без примеров все просто и понятно.

 

Структура программы ESP-IDF.

Структура программы в среде ESP-IDF не отличается от типичной структуры C-программ. Это последовательность директив препроцессора, описаний, определений, глобальных объектов и функций.

Единственная особенность – главная функция должна иметь имя app_main().

Давайте создадим первую программу, на примере которой рассмотрим структуру исходного кода и научимся работать с портами в режиме выходов. Во многом эта программа будет повторять пример blink. Но мы не будем копировать готовый код, а разработаем его осознанно.

Заставим светодиод, подключенный к выводу 2, мигать с частотой 2 раза в секунду.

Создадим новый проект. Последовательность действий описана в конце урока 3.

  • В рабочем каталоге урока 5 я создал паку Lesson5_1.
  • Копировал в нее содержимое папки шаблона проекта template из урока 3.
  • Задал имя проекта Lesson5_1 в файлах CMakeLists.txt и Makefile.
  • Открыл проект в VS  Code.
  • Удалил содержимое файла app_main.c. Оставил пустую страницу. Ее и будем заполнять исходным кодом.

Напишем название программы

// светодиод на плате мигает 2 раза в секунду

Подключим необходимые файлы библиотек.

#include <stdio.h>

Библиотека необходима нам, для использования функции printf. С помощь нее будем выводить данные в терминал для отладки. В нашей программе это сообщения о состоянии светодиода.

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

Этими файлами подключим операционную систему реального времени FreeRTOS. О ней поговорим в последующих уроках. Это обширная тема.  В данном приложении она нужна только для формирования временных задержек. Работать с таймерами мы еще не умеем, а FreeRTOS позволит нам использовать функцию vTaskDelay, которая останавливает выполнение задачи на заданное время.

#include "driver/gpio.h"

Эта директива необходима для подключения API драйвера управления GPIO. Без нее выше описанные функции управления портами работать не будут.

Определяем номер вывода, к которому подключен светодиод.

#define LED_GPIO GPIO_NUM_2 // вывод светодиода

Создаем главную функцию, внутри которой будет основной код.

void app_main(void)
{

}

Конфигурируем вывод светодиода как активный выход.

gpio_pad_select_gpio(LED_GPIO); // режим ввода-вывода
gpio_set_direction(LED_GPIO, GPIO_MODE_OUTPUT); // активный выход

Остается создать бесконечный цикл, в котором зажигать и гасить светодиод с задержками 0,25 секунд.

while(1) {
    gpio_set_level(LED_GPIO, 0); // светодиод погашен
    printf("Светодиод погас\n");
    vTaskDelay(250 / portTICK_PERIOD_MS); //задержка 0,25 сек
    gpio_set_level(LED_GPIO, 1); // светодиод горит
    printf("Светодиод горит\n");
    vTaskDelay(250 / portTICK_PERIOD_MS); //задержка 0,25 сек
}

Проект можно загрузить по ссылке.

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Компилируем, загружаем в FLASH, проверяем.

 

Модифицируем предыдущую программу. Добавим кнопку и реализуем алгоритм, при котором нажатие кнопки уменьшает частоту мигания светодиодов в 2 раза.

Я подключил кнопку между выводом 13 и сигналом GND.

Копировал предыдущий проект. Изменил имя на Lesson5_2.

Добавил определение вывода кнопки.

#define BUTTON_GPIO GPIO_NUM_13 // вывод кнопки

Конфигурировал вывод кнопки, как вход с подтягивающим резистором.

gpio_pad_select_gpio(BUTTON_GPIO); // режим ввода-вывода
gpio_set_direction(BUTTON_GPIO, GPIO_MODE_INPUT); // вход
gpio_pullup_en(BUTTON_GPIO); // резистор подключен к питанию
gpio_pulldown_dis(BUTTON_GPIO); // резистор отключен от земли

Осталось изменять временные задержки в зависимости от состояния входа кнопки.

if( gpio_get_level(BUTTON_GPIO) != 0 ) vTaskDelay(250 / portTICK_PERIOD_MS); //задержка 0,25 сек
else vTaskDelay(500 / portTICK_PERIOD_MS); //задержка 0,5 сек

Вот мой проект

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Согласитесь, работать с портами ESP32 очень просто.

 

Функции работы с несколькими битами одновременно.

Существуют еще 4 функции недокументированные в API, которые позволяют значительно ускорить работу с портами.

Функции gpio_set_level и gpio_get_level позволяют устанавливать и считывать состояние только одного вывода. Но часто возникает необходимость работать с несколькими битами одновременно. Это не только ускоряет работу программы, но иногда бывает абсолютно необходимым. Например, считывать шину данных по стробу или устанавливать сигналы, временные параметры которых критичны к сдвигу начальной фазы.

uint32_t gpio_input_get(void);

Функция считывает и возвращает одним словом состояние выводов 0-31.

uint32_t gpio_input_get_high(void);

Функция считывает и возвращает состояние выводов 32-39.

Достаточно вызвать функцию, и мы получим 32 разрядное слово, каждый разряд которого соответствует состоянию выводов микроконтроллера.

void gpio_output_set(uint32_t set_mask, uint32_t clear_mask, uint32_t enable_mask, uint32_t disable_mask);

Функция устанавливает, сбрасывает состояние выводов 0-31, а также разрешает и запрещает их работу.

  • set_mask – маска выводов, которые необходимо установить;
  • clear_mask  – маска выводов, которые необходимо сбросить;
  • enable_mask – маска выводов, работу которых необходимо разрешить;
  • disable_mask – маска выводов, работу которых необходимо запретить.

void gpio_output_set_high(uint32_t set_mask, uint32_t clear_mask, uint32_t enable_mask, uint32_t disable_mask);

Функция устанавливает, сбрасывает состояние выводов 32-39, а также разрешает и запрещает их работу. В остальном формат аналогичен предыдущей функции.

Кроме управления состоянием сигналов на выходах функции еще разрешают и запрещают их работу, что удобно при организации двунаправленных сигналов и шин данных.

Вот проект, в котором я реализовал предыдущую задачу с использованием функций gpio_input_get и gpio_output_set .

 

Зарегистрируйтесь и оплатитеВсего 60 руб. в месяц за доступ ко всем ресурсам сайта!
 

Программа получилась несколько сложнее. Результат от применения этих функций заметен только при работе с несколькими выводами портов.

 

В следующем уроке научимся работать с таймерами ESP32.

Предыдущий урок     Список уроков     Следующий урок

0

Автор публикации

не в сети 8 часов

Эдуард

285
Комментарии: 1946Публикации: 198Регистрация: 13-12-2015

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

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

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".