Перейти к содержимому

Добрый день, уважаемый читатель! В данной статье продолжим обсуждать тему портов ввода/вывода ESP32, а конкретно рассмотрим работу с прерываниями.

/**********************************************
 * СХЕМА СОЕДИНЕНИЙ:
 *
 * На GPIO 18 подключена кнопка: одним выводом на GPIO 18, другим на землю.
 * GPIO 18 подтянут резистором 10кОм на шину питания +3,3В (но можно использовать и внутреннюю подтяжку).
 *
 * На GPIO 16 подключен светодиод (как в предыдущем примере) - одним выводом на +3,3В,
 * другим - к GPIO 16 через резистор
 *
 ***********************************************/

Среди ардуинщиков я иногда сталкивался с мнением (цитирую по памяти): “Да что все так носятся с этими прерываниями?! Мне вот на моем скетче прерывания вообще не нужны!!! Мне не сложно один раз в десять секунд измерить напряжение на входе“. Так-то оно может быть и так… Но это пока ваш контроллер выполняет только одно единственную задачу – меряет напряжение на входе и чем-то там “щёлкает”. Как только вы попытаетесь прикрутить к вашему устройству парочку дополнительных сервисов, “вечер перестанет быть томным”. Притом чем выше частота опроса GPIO, тем выше нагрузка на процессор, а при слишком длительных паузах можно попустить короткие импульсы. Да и вообще, я считаю такой “прямой” подход слишком грубым и неправильным; тем более, что никакой сложности в работе с прерываниями нет. Прерывания позволяют “нагружать” процессор только в моменты возникновения события, без необходимости выполнения пустой бесполезной работы.

 

Пример из жизни: ваш дверной звонок в доме. Без дверного звонка (или прослушивания чьего-то стука) вам пришлось бы периодически проверять, нет ли кого у двери. Это приводит к бесполезной трате времени в большинстве случаев, когда за дверью никого нет; а также не гарантирует, что если за дверью кто-то есть, вы своевременно откроете её.

Два API для работы с GPIO прерываниями

ESP-IDF предоставляет сразу два API (Application Program Interface) обработки прерываний, которые генерируются по сигналам GPIO.

  • gpio_isr_register() – эта функция позволяет зарегистрировать ISR (обработчик прерываний) для всех GPIO одновременно и сразу. Насколько я понимаю (поправьте меня, если я ошибаюсь) – это “низкоуровневый” метод регистрации GPIO прерываний. При регистрации обработчика (то есть при вызове gpio_isr_register()) не передается никаких данных о номере GPIO – если эта функция используется, для всех прерываний GPIO регистрируется один глобальный ISR. В обработчике прерываний вы сами должны будете определить, какой GPIO сгенерировал прерывание.
  • gpio_install_isr_service() – если эта функция используется, служба ISR предоставляет глобальный обработчик прерываний GPIO, а отдельные обработчики для каждого из выводов регистрируются с помощью функции gpio_isr_handler_add(). Это более простой для понимания программиста подход. Внутри gpio_install_isr_service() содержится вызов того самого gpio_isr_register(), то есть этот самый сервис GPIO ISR берет на себя всю низкоуровневую работу, вам остается только создать обработчики событий.

Сразу скажу, что я пока не вижу особого смысла забираться в дебри ESP-IDF и пользуюсь вторым способом. Тем более, что иногда при запуске прошивки оказывается, что “GPIO isr service already installed” (то есть этим сервисом “пользуется” сама ESP-IDF). Поэтому нет смысла от него отказываться. Давайте с него и начнем.

 

GPIO ISR service

Последовательность ваших действий в этом случае такова:

  • Вначале вам необходимо зарегистрировать глобальный обработчик GPIO ISR с помощью gpio_install_isr_service(int intr_alloc_flags). В качестве параметра можн0 передать просто 0. Если эта функция вернула ошибку ESP_ERR_INVALID_STATE – ничего страшного, просто вы уже установили этот сервис ранее (либо ESP-IDF сделала это за вас).
  • Затем вы должны создать обработчик события прерывания, используя прототип void IRAM_ATTR isrHandler(void* arg). К обработчикам прерываний предъявляются особые требования, про них я расскажу ниже.
  • После этого вы должны указать, по какому уровню сигнала мы будет генерировать событие с помощью функции gpio_set_intr_type(). Доступны следующие варианты:
    1. GPIO_INTR_DISABLE – отключено
    2. GPIO_INTR_POSEDGE – по изменению с 0 до 1
    3. GPIO_INTR_NEGEDGE – по изменению с 1 на 0
    4. GPIO_INTR_ANYEDGE – по любому изменению
    5. GPIO_INTR_LOW_LEVEL – по низкому уровню
    6. GPIO_INTR_HIGH_LEVEL – по высокому уровню
  • И в заключение разрешаем прерывания с помощью функции gpio_intr_enable().

Последовательность вызовов может выглядеть так:

Создание обработчика прерываний

Всё, что описано в данном разделе, можно смело относить и в случае использования gpio_isr_register(), так как по сути это одно и то же.

Обработчики прерываний – не совсем простые функции. Прерывание приостанавливает выполнение всех потоков вашей программы, поэтому к обработчикам прерываний предъявляются определенные требования:

  • Обработчик прерывания должен выполняться как можно меньше по времени, иначе сработает WDT для прерываний и устройство будет аварийно перезагружено.
  • Обработчик прерывания должен постоянно находится в быстрой памяти IRAM, поэтому его следует пометить соответствующим атрибутом IRAM_ATTR.
  • В обработчиках прерываний не допускается использование библиотечных функций ESP_LOGx, но можно использовать специальную облегченную версию ESP_DRAM_LOGx.
  • Следует всегда помнить, что обработчики прерываний выполняются вне контекста прикладных задач. Дабы не нарушать отчетности соблюдать потокобезопасность из обработчика прерываний лучше всего отправить какие-либо данные в очередь другой задачи, включить флаг в EventGroup и т.д. Да, в принципе можно просто изменить значение какой-либо глобальной статической переменной bool или int, которая будет управлять потоком в другой задаче, но это не приветствуется.
  • Если вам требуется прерывание только по GPIO_INTR_POSEDGE или только по GPIO_INTR_NEGEDGE, то проблем как бы нет. Проблемы начинаются, когда требуется обработать оба события – GPIO_INTR_ANYEDGE. Как определить, что произошло в текущий момент? Читать состояние GPIO с помощью gpio_get_level() из обработчика в принципе можно, но как бы не очень хорошо, поскольку при дребезге контактов состояние вывода может изменяться очень быстро. Поэтому если вам требуется “отловить” и момент нажатия, и момент отпускания кнопки – правильнее будет назначить два обработчика прерываний на разные события. Тему подавления дребезга контактов мы обсудим отдельно, после изучения таймеров.
  • После завершения прерывания можно выполнять переключение контекста путем вызова portYIELD_FROM_ISR. Зачем это нужно? Допустим, в текущий момент выполняется низкоприоритетная задача, а высокоприоритетная ожидает наступления некоторого прерывания. Далее происходит прерывание, но по окончании работы обработчика прерываний выполнение возвращается к текущей низкоприоритетной задаче, а высокоприоритетная ожидает, пока закончится текущий квант времени. Однако если после выполнения обработчика прерывания передать управление планировщику ( portYIELD_FROM_ISR ), то он передаст управление высокоприоритетной задаче, что позволяет значительно сократить время реакции системы на прерывание, связанное с внешним событием.

Учитывая всё вышесказанное напишем обработчик прерывания, который будет отправлять событие (bool) в очередь задачи, которая управляет переключением светодиода.

static xQueueHandle button_queue = NULL;

// Обработчик прерывания по нажатию кнопки
static void IRAM_ATTR isrButtonPress(void* arg)
{
  // Переменные для переключения контекста
  BaseType_t xHigherPriorityTaskWoken, xResult;
  xHigherPriorityTaskWoken = pdFALSE;
  // Поскольку мы "подписались" только на GPIO_INTR_NEGEDGE, мы уверены что это именно момент нажатия на кнопку
  bool pressed = true;
  // Отправляем в очередь задачи событие "кнопка нажата"
  xResult = xQueueSendFromISR(button_queue, &pressed, &xHigherPriorityTaskWoken);
  // Если высокоприоритетная задача ждет этого события, переключаем управление
  if (xResult == pdPASS) {
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
  };
}

Примечание: про работу с очередями ( xQueueSendFromISR ) вы можете почитать здесь более подробно.

В функции задачи мы должны написать что-то примерно следующее:

// Функция задачи светодиода
void led_exec(void *pvParameters)
{
  // Настраиваем вывод GPIO_NUM_16 на выход без подтяжки
  gpio_pad_select_gpio(16);
  gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT_OD);
  gpio_set_pull_mode(GPIO_NUM_16, GPIO_FLOATING);

  // Управление светодиодом
  bool led_state = false;
  bool led_pressed;
  while(1) {
    // Ждем события нажатия кнопки в очереди
    if (xQueueReceive(button_queue, &led_pressed, portMAX_DELAY)) {
      ESP_LOGI("ISR", "Button is pressed");

      // Переключаем светодиод
      led_state = !led_state;
      gpio_set_level(GPIO_NUM_16, (uint32_t)led_state);
    };
  };
  vTaskDelete(NULL);
}
Ну и тело основной функции модифицируем следующим образом:

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

Полный исходный код для данного примера вы можете найти по ссылке: https://github.com/kotyara12/dzen/tree/master/gpio_isr_service

 

 

Низкоуровневый подход

Допустим, вы по какой-то причине решите воспользоваться более низкоуровневым подходом – через gpio_isr_register(). В этом случае используется один и тот же обработчик для всех GPIO. Если у вас используется только одно GPIO “на вход”, то проблем нет. А вот если вы будете использовать несколько – то вам самим придется определить, какое GPIO сгенерировало прерывание. Сделать это можно прочитав регистры прерываний GPIO: READ_PERI_REG(GPIO_STATUS_REG);

  // Считываем и сбрасываем регистры прерываний GPIO
  uint32_t gpio_intr_status = READ_PERI_REG(GPIO_STATUS_REG);     // Чтение регистров статуса прерывания для GPIO0-31
  SET_PERI_REG_MASK(GPIO_STATUS_W1TC_REG, gpio_intr_status);      // Очистка регистров прерывания для GPIO0-31
  uint32_t gpio_intr_status_h = READ_PERI_REG(GPIO_STATUS1_REG);  // Чтение статуса прерывания для GPIO32-39
  SET_PERI_REG_MASK(GPIO_STATUS1_W1TC_REG, gpio_intr_status_h);   // Очистка регистров прерывания для GPIO32-39

  // Определяем номер порта, с которого поступило событие
  uint32_t gpio_num = 0;
  do {
    if (gpio_num < 32) {
      if (gpio_intr_status & BIT(gpio_num)) {
        // Отправляем в очередь задачи номер нажатой кнопки
        xResult = xQueueSendFromISR(gpio_queue, &gpio_num, &xHigherPriorityTaskWoken);
      }
    } else {
      if (gpio_intr_status_h & BIT(gpio_num - 32)) {
        // Отправляем в очередь задачи номер нажатой кнопки
        xResult = xQueueSendFromISR(gpio_queue, &gpio_num, &xHigherPriorityTaskWoken);
      }
    };
  } while (++gpio_num < GPIO_PIN_COUNT);

В данном примере в очередь мы отправляем номер GPIO, которое сгенерировало прерывание. А уже внутри задачи проверяем полученный номер на соответствие заданному:

// Функция задачи светодиода
void led_exec(void *pvParameters)
{
  // Настраиваем вывод GPIO_NUM_16 на выход без подтяжки
  gpio_pad_select_gpio(16);
  gpio_set_direction(GPIO_NUM_16, GPIO_MODE_OUTPUT_OD);
  gpio_set_pull_mode(GPIO_NUM_16, GPIO_FLOATING);

  // Управление светодиодом
  bool led_state = false;
  uint32_t gpio_num;

  while(1) {
    // Ждем события в очереди
    if (xQueueReceive(gpio_queue, &gpio_num, portMAX_DELAY)) {
      // Если это кнопка...
      if (gpio_num == 18) {
        ESP_LOGI("ISR", "Button is pressed");

        // Переключаем светодиод
        led_state = !led_state;
        gpio_set_level(GPIO_NUM_16, (uint32_t)led_state);
      };
    };
  };
  vTaskDelete(NULL);
}

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

Пример для данного варианта вы можете найти здесь: https://github.com/kotyara12/dzen/tree/master/gpio_isr_register

 

Флаги прерываний

В завершение оставлю небольшое замечание. В примерах выше вам попадались параметры int intr_alloc_flags. Вы можете поставить ноли или использовать комбинацию из следующих значений:

  • ESP_INTR_FLAG_LEVEL1 – Вектор прерывания уровня 1 (самый низкий приоритет)
  • ESP_INTR_FLAG_LEVEL2 – Вектор прерывания уровня 2
  • ESP_INTR_FLAG_LEVEL3 – Вектор прерывания уровня 3
  • ESP_INTR_FLAG_LEVEL4 – Вектор прерывания уровня 4
  • ESP_INTR_FLAG_LEVEL5 – Вектор прерывания уровня 5
  • ESP_INTR_FLAG_LEVEL6 – Вектор прерывания уровня 6
  • ESP_INTR_FLAG_NMI – Вектор прерывания уровня 7 (наивысший приоритет)

Дополнительные флаги:

  • ESP_INTR_FLAG_SHARED – Прерывание может быть разделено между несколькими ISR
  • ESP_INTR_FLAG_EDGE – Прерывание по фронту
  • ESP_INTR_FLAG_IRAM – ISR может быть вызван, если кеш отключен (шта?)
  • ESP_INTR_FLAG_INTRDISABLED – Возврат с отключенным прерыванием

Если вы захотите их использовать, важно понимать одну вещь – прерывания с уровнем до 3 включительно могут быть обработаны в Cи. Все прерывания с уровнями 4-7 могут быть обработаны только на ассемблере. Поэтому, если у вас нет опыта программирования на ассемблере, не стоит баловаться int intr_alloc_flags.


Полезные ссылки

  1. ESP-IDF — GPIO and RTC GPIO.
  2. Пример на GitHub

Материалы по теме

  1. ESP32: чипы, модули, платы…
  2. ESP32 pinout: ещё раз о GPIO & pаспределяем выводы с помощью excel
  3. Работа с портами ввода-вывода GPIO из ESP-IDF
  4. FreeRTOS: что это такое и чем его едят
  5. FreeRTOS: очереди задач
  6. FreeRTOS: циклы событий
  7. FreeRTOS: группы флагов (событий)

Пожалуйста, оцените статью:
[ 5 из 5, всего 1 оценок ]

27 комментариев для “Обработка прерываний GPIO на ESP-IDF”

  1. Добрый день, спасибо за интересные статьи. нашёл решение проблемы с дребезгом контактов. Пачку сигналов больше не отправляет. Не знаю есть ли от вас обратная связь?
    “Поэтому этот код может не очень четко работать. Бороться с дребезгом контактов можно разными способами, я. пожалуй, оставлю это на отдельную статью (после знакомства с таймерами.)”

    1. Добрый день. На самом деле дребезг контактов – это не такая уж и проблема. Решить можно достаточно просто многими способами – и электроникой и программно. Иногда проще поставить обычный RC-фильтр, но чаще удобнее обычными таймерами, заодбно и время нажатия измерить – короткое оно или длинное, если это кнопка. Для входов оконечных датчиков или “охранки” удобнее RX-Фильтры. В общем подгоняем все “по месту”.
      В любом случае обсудить это в отдельной статье стоит. Интересно, а какой способ придумали вы?

  2. Это понятно, что у кнопки возможен дребезг контактов и что его можно решить. Проблема заключается в том, что кнопки нет, а дребезг есть. Я только решил познакомиться с контроллером, и мне необходимы прерывания. в арсенале ESP wroom 32, осциллограф, генератор сигналов. Программа Espressif\frameworks\esp-idf-v5.0-rc1. За основу кода Пример gpio_example_main.с.
    1. Замечено, что при выполнении условия прерывания по фронту. Прерывания вызывают “static void IRAM_ATTR gpio_isr_handler(void* arg)” с частотой 500кГц. Хотя, как я понимаю, должно вызваться один раз.
    2. Способ решения. В штатной библиотеке gpio.c есть функция ->>>> IRAM_ATTR gpio_isr_loop
    В этой функции необходимо убрать строки которые в комментарии.
    После изменения кода, функция прерывания вызывается в соответствии настройке прерывания:
    GPIO_INTR_POSEDGE – срабатывает один раз на фронте
    GPIO_INTR_NEGEDGE – срабатывает один раз на спаде
    GPIO_INTR_ANYEDGE – срабатывает два раза (фронт и спад)
    GPIO_INTR_HIGH_LEVEL – срабатывает с частотой 500кГц.

    static inline void IRAM_ATTR gpio_isr_loop(uint32_t status, const uint32_t gpio_num_start)”
    {
    while (status) {
    int nbit = __builtin_ffs(status) – 1;
    status &= ~(1 << nbit);
    int gpio_num = gpio_num_start + nbit;

    bool intr_status_bit_cleared = false;
    // Edge-triggered type interrupt can clear the interrupt status bit before entering per-pin interrupt handler
    // if ((1ULL << (gpio_num)) & gpio_context.isr_clr_on_entry_mask) {
    // intr_status_bit_cleared = true;
    // gpio_hal_clear_intr_status_bit(gpio_context.gpio_hal, gpio_num);
    // }

    if (gpio_context.gpio_isr_func[gpio_num].fn != NULL) {
    gpio_context.gpio_isr_func[gpio_num].fn(gpio_context.gpio_isr_func[gpio_num].args);
    }

    // If the interrupt status bit was not cleared at the entry, then must clear it before exiting
    if (!intr_status_bit_cleared) {
    gpio_hal_clear_intr_status_bit(gpio_context.gpio_hal, gpio_num);
    }
    }
    }
    P.S. скорость срабатывания прерывания с момента подачи сигнала на gpio, до момента получения с gpio составило менее 3мкс. Возможно кому-то это будет интересно.
    P.P.S. Особо не программирую, хобби, только учусь.

    1. То есть вы подаете на вход сигнал частотой 500 кГц с генератора сигналов – я правильно вас понимаю?
      Тогда “Хотя, как я понимаю, должно вызваться один раз.” – нет, не правильно. Прерываний будет ровно столько, сколько раз будет фронт импульса, или спад или что-там вы выберете в качестве условия.

      GPIO_INTR_POSEDGE – срабатывает ПРИ КАЖДОМ фронте
      GPIO_INTR_NEGEDGE – срабатывает ПРИ КАЖДОМ спаде
      ну и т.д.

      Что бы получить один вызов обработчика прерываний, нужно при первом же срабатывании временно отключить реакцию на прерывания на заданное время, например 500 мс. Это как раз и будет один из способов гашения дребезга. Осталось выяснить, каким образом включить прерывания обратно через заданное время.

      1. нет. 500кГц выдает сам контроллер при выполнении условия прерывания. С генератора пробовал подавать сигналы от 50 Гц до 15кГц, срабатывает корректно.
        Канал 1____————-_____ (генератор)
        Канал 2____|||||||||_____(Выход МК 500кГц)

        1. А вот это странно. Не было в моей практике еще такого.
          А ножка GPIO подтянута резистором к чему-нибудь?

          1. Ножка GPIO не подтянута резисторами. подключается на прямую к осциллографу.

            1. я не уверен на 100%, но возможно в этом и беда ваша. И потом – какая именно ножка? Есть GPIO, которые “по умолчанию” имеют внутреннюю подтяжку…

    2. Но ведь не известно на сколько отключать прерывания, и потом после возобновления прерывания функция продолжит вызываться с частотой 500кГц.

      1. Почему не известно – как правило известно… На практике дребезг обычно уже через 100мс “сходит на нет”.
        Прилетает первое прерывание -> блокируем прерывания по данному GPIO -> запускаем совтверный таймер -> по истечении таймера измеряем уровень + включаем прерывания обратно

    3. Возможно нужно попробовать подключить резисторы, но я не уверен в этом. Думаю всё-таки в коде проблема, там при обновлении gpio вызывается прерывание. использую 4 вход, 18 выход.

      1. GPIO 4 использовать можно, но нужно учитывать, что он по умолчанию подтянут к земле внутри МК
        GPIO 18 по умолчанию свободен, можете использовать произвольно.
        Если конечно вы номера GPIO имеете в виду, а не номера выводов модуля

    4. Да я и имел ввиду GPIO4 GPIO8
      в GPIO.С указано.
      // Edge-triggered type interrupt can clear the interrupt status bit before entering per-pin interrupt handler
      // for edge-triggered interrupts, interrupt status bits should be cleared before entering per-pin handlers
      и я не пойму, для чего очищать состояния прерывания для каждого контакта.

      особенно когда
      // If the interrupt status bit was not cleared at the entry, then must clear it before exiting
      т.е. очистится при выходе 100%. следует и при входе тоже будет очищено (понятно что код большой и в дальнейшем могут вылезть иголки.
      Наверное мне надо прочитать про использование подтяжек, я не совсем понимаю их назначение. и как правильно написать в коде использование подтяжки (где почитать?). ( ранее имел дело только с унифицированными сигналами, и схемотехники был 1 %)

      1. Я не спец по глубинам esp32, но я понимаю этот процесс так.

        Любое прерывание использует специальные регистры процессора.
        Два 16-битных (насколько я помню, могу ошибаться) регистра. Каждый вывод – один бит. Есть сигнал прерывания – бит устанавливается в 1, нет прерывания – стоит 0.
        Это не уровень на выводе, это именно сигнал “что-то произошло”! А что именно – вы указываете в GPIO_INTR_XXXX
        Далее вы должны обработать это “что-то произошло”. Узнать на каком выводе это произошло можно только прочитав регистр. Но чтобы повторно узнать о событии на том же выводе, после реакции на прерывание нужно сбросить флаг “что-то произошло” иначе мы не узнаем об этом.

        Так вот, судя по
        // Edge-triggered type interrupt can clear the interrupt status bit before entering per-pin interrupt handler
        // for edge-triggered interrupts, interrupt status bits should be cleared before entering per-pin handlers
        то прерывания по GPIO_INTR_POSEDGE сбросят соответствующий бит в регистре, даже если вы не потрудились его сбросить в обработчике. И прерывание по GPIO_INTR_POSEDGE будет всегда.

        А вот в остальных случаях
        // If the interrupt status bit was not cleared at the entry, then must clear it before exiting
        вы должны очистить этот бит самостоятельно в обработчике. На входе обработчика или на выходе, как вам удобнее. Иначе система ВОЗМОЖНО больше не оповестит вас о прерывании.

        Важный момент – если вы сами не сбросите соответствующий бит в регистре, то как вы узнаете какой GPIO вызывал прерывание? Обработчик прерываний в ESP32 один на все GPIO сразу. Узнать какие GPIO сработали, можно только из этих двух регистров. Допустим сработало прерывание по GPIO4. Установился бит 4 (условно), вы прочитали, обработали и забыли сбросить. Далее сработало прерывание по GPIO8. В регистре уже ДВА бита – 4 и 8. Но на GPIO 4 сейчас же не было никаких сигналов – скажете вы? Хм, а систему не волнует, биты оба установлены…

    5. Да, вы правы, это важный момент. в конце функции есть условие которое сбрасывает (gpio_hal_clear_intr_status_bit) и будет сбрасывать биты постоянно т.к. крутится while (status) {…}. т.е. даже если будут два и более бита (4 и 8…).
      По-идеи снятие бита перед обработкой прерывания дает возможность “поймать” прерывание по этому же биту, при обработке прерывания.
      Но на сколько известно при обработке прерывания, новые прерывания не обрабатываются ??? Если да, тогда нет смысла на проверку перед обработкой прерывания. да и для чего такие высокие частоты. На осциллографе замерил разницу во времени выполнения прерывания с проверкой и без проверки бита, составило ~ 0,5мкс.
      Я тоже только изучаю глубину ESP32…. ох-хо-хо…
      Да я указываю io_conf.intr_type = GPIO_INTR_POSEDGE;

      сейчас думаю сделать как вы подсказали.
      1. Вернуть всё назад (0,5мкс мне не важны).
      2. Запустить gptimer по прерыванию и отсчитать “100мс”.
      3. Обратным вызовом счетчика вызывать функцию.
      Вопрос такой, возможно ли запустить таймер по прерыванию GPIO?

      1. Можно. Только лучше если предварительно его создать, чтобы потом использовать. В обработчике прерывания вам останется только его запустить однократно. Это, разумеется, относится только к софтверным таймерам.

        1. Александр, спасибо за статью! Проработал вопрос с прерываниями и gptimer. Получилась заготовка для детектирования нуля синусоиды и отсчета времени до выдачи импульса, а также задача ширины импульса. На данный момент код работает как подсчет/(синхронизация) входной частоты. В зависимости от формы импульса можно подсчитать частоту до ~18кГц.
          P.S. Возможно поможет Вам для написания дальнейших статей исходник выложенный на https://github.com/AndreyRoc/DetectZeroSIN

    6. Владимир

      Здравствуйте. Столкнулся с такой проблемой при выполнении внешних прерываний. Время между вызовом обработчика прерывания и вызовом функции задачи варьируется от 23 до 4800 микросекунд. Попробовал поменять флаги прерываний, разницу не заметил. Не могу понять от чего эта задержка зависит. Задержку мерил так:
      static void IRAM_ATTR gpio_interrupt_handler(void *args)
      {
      int pinNumber = (int)args;
      start = esp_timer_get_time();
      xQueueSendFromISR(interputQueue, &pinNumber, NULL);
      }

      void isr_task(void *params)
      {
      int pinNumber;
      while(1)
      {
      if (xQueueReceive(interputQueue, &pinNumber, portMAX_DELAY))
      {
      stop = esp_timer_get_time();
      printf(“interrupt delay %llu us\n”, (stop – start));
      spirit_get_data_wg(&spirit1_handle);
      spirit_spi_cmd(&spirit1_handle, COMMAND_RX);
      }
      }
      }

      void app_main(void)
      {

      gpio_pad_select_gpio(35);
      gpio_set_direction(35, GPIO_MODE_INPUT);
      gpio_set_intr_type(35, GPIO_INTR_POSEDGE);
      interputQueue = xQueueCreate(10, sizeof(int));
      xTaskCreate(isr_task, “isr_task”, 2048, NULL, 20, NULL); //configMAX_PRIORITIES = 25
      gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);
      gpio_isr_handler_add(35, gpio_interrupt_handler, (void *)35);

      }

      Хотелось бы задержку не больше миллисекунды. Не подскажите как?
      Спасибо.

      1. Ну так всё логично – вы ведь пересылаете сигнал об прерывании через очередь. А это асинхронный процесс с неопределенной длительностью.
        Хотите минимальную задержку? Выполняйте всю полезную работу прямо внутри обработчика прерывания! Но эта работа не должна быть очень длинной, иначе будет срабатывание WDT.

    7. Владимир

      Спасибо. Так и есть, когда вызываю функцию из обработчика прерывания, срабатывает WDT.
      Guru Meditation Error: Core 0 panic’ed (Interrupt wdt timeout on CPU0).
      Выполнение функции длится около 500 микросекунд. Можно увеличить период WDT или отключить его на время выполнения функции? Не нашел где это можно сделать .

    8. Владимир

      Заработало без манипуляций с WDT. Если создать таск в обработчике прерывания, все работает.
      static void IRAM_ATTR gpio_interrupt_handler(void *args)
      {
      start = esp_timer_get_time();
      xTaskCreate(&get_hf_data, “get_hf_data”, 2048, NULL, 5, NULL);
      }

      void get_hf_data(void)
      {
      stop = esp_timer_get_time();
      printf(“interrupt delay %llu us\n”, (stop – start));
      hf_get_data_wg(&hf_handle);
      vTaskDelete(NULL);
      }

      Задержка 56 микросекунд.
      Благодарю за помощь!

      1. Кстати, я только сейчас обратил внимание..
        В вашем первом примере не происходит принудительное переключение контекста в обработчике прерывания. Отсюда лишние задержки.
        Зря вы portYIELD_FROM_ISR(xHigherPriorityTaskWoken); не использовали – вполне возможно и первый вариант работал бы

    Добавить комментарий для Владимир Отменить ответ

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