Найти в Дзене
K12 :: О ESP32 и не только

ESP32 PCNT – аппаратные счётчики импульсов

Доброго здравия, уважаемые друзья и читатели! Дисклеймер. Данная статья на Дзене - несколько сокращенная версия статьи, опубликованной на сайте kotyara12.ru. В основной версии можно скопировать код примеров (Дзен не подходит для таких технических статей, как эта), а также вносятся правки при необходимости - поэтому версии на Дзене могут отличаться от статей на сайте. Благодарю за понимание. В данной статье обсудим работу с аппаратными контроллерами счетчиков импульсов, встроенными в чипы ESP32. Модуль PCNT (Pulse Counter) предназначен для подсчёта количества фронтов нарастающих и/или спадающих фронтов входных сигналов. Каждый PCNT unit — это независимый счётчик с несколькими входными каналами, где каждый канал может увеличивать или уменьшать значение счётчика при обнаружении соответствующего фронта. Каналы могут быть сконфигурированы индивидуально. PCNT поддерживает два типа сигналов: Также в PCNT реализован отдельный фильтр коротких импульсов (glitch filter), который позволяет отсеи
Оглавление

Доброго здравия, уважаемые друзья и читатели!

Дисклеймер. Данная статья на Дзене - несколько сокращенная версия статьи, опубликованной на сайте kotyara12.ru. В основной версии можно скопировать код примеров (Дзен не подходит для таких технических статей, как эта), а также вносятся правки при необходимости - поэтому версии на Дзене могут отличаться от статей на сайте. Благодарю за понимание.

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

Модуль PCNT (Pulse Counter) предназначен для подсчёта количества фронтов нарастающих и/или спадающих фронтов входных сигналов. Каждый PCNT unit — это независимый счётчик с несколькими входными каналами, где каждый канал может увеличивать или уменьшать значение счётчика при обнаружении соответствующего фронта. Каналы могут быть сконфигурированы индивидуально.

PCNT поддерживает два типа сигналов:

  • Edge signal — основной счётный сигнал, по фронтам или спадам которого происходит подсчёт (например, импульсы с датчика или кнопки).
  • Level signal (control signal) — дополнительный управляющий сигнал, который может изменять режим подсчёта (например, инвертировать направление счета или временно его приостанавливать).

Также в PCNT реализован отдельный фильтр коротких импульсов (glitch filter), который позволяет отсеивать помехи от дребезга контактов и считать только “чистые” импульсы.

Каналы PCNT можно настроить так, чтобы они реагировали на оба фронта входных импульсов (т. е. нарастающий и спадающий фронт), а также можно настроить реакции на эти изменения – увеличение, уменьшение или отсутствие действий со счетчиком блока для каждого фронта. Сигнал уровня — это так называемый управляющий сигнал, который используется для управления режимом подсчета сигналов фронта, которые присоединены к одному и тому же каналу. Объединяя использование сигналов фронта и уровня, блок PCNT может работать как квадратурный декодер.

Количество PCNT модулей (PCNT units) в различных чипах семейства ESP32 следующее:

  • ESP32 (и производные: ESP32-WROOM-32E, ESP32-WROVER-E, ESP32-PICO, ESP32-MINI-1 и др.):
  • 8 независимых PCNT units, каждый с двумя каналами
  • ESP32 Series Datasheet, ESP32-WROOM-32E Datasheet, ESP32-PICO Series Datasheet
  • ESP32-S2, ESP32-S3, ESP32-C6:
  • 4 независимых PCNT units, каждый с двумя каналами
  • ESP32-S2 Technical Reference Manual, ESP32-S3-WROOM-1 Datasheet, ESP32-C6 Technical Reference Manual

Таким образом, классические ESP32-чипы имеют 8 PCNT units, а более новые серии (S2, S3, C6) — 4 PCNT units.

Модуль PCNT можно использовать, например, в следующих случаях:

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

Обзор PCNT API

Подключить API счетчиков к вашей программе можно с помощью одной единственной строчки:

#include "driver/pulse_cnt.h"

Основные функций API счетчика импульсов (PCNT) можно разделить на несколько функциональных групп:

  • Создание дескрипторов и конфигурация модуля и каналов:
  • Для работы с PCNT потребуются дескрипторы модуля (pcnt_unit_handle_t) и канала (pcnt_channel_handle_t). Их необходимо создать с помощью функций pcnt_new_unit() и pcnt_new_channel() с соответствующими структурами конфигурации.
  • Подробнее о выделении ресурсов и конфигурации счетчиков
  • Включение счетчика и запуск / остановка счета:pcnt_unit_enable() — активация счетчика PCNT
  • pcnt_unit_start() — запускает счет
  • pcnt_unit_stop() — останавливает счет, при этом значение счетчика не сбрасывается
  • Описание функций управления счетом
  • Сброс и чтение значения счетчика:pcnt_unit_clear_count() — сбрасывает значение счетчика в ноль
  • pcnt_unit_get_count() — возвращает текущее значение счетчика
  • Описание функций получения и сброса значения счетчика
  • Настройка фильтрации помех:
  • pcnt_unit_set_glitch_filter() — включение и настройка фильтра коротких импульсов
  • Настройка фильтра помех
  • Работа с watch points:pcnt_unit_add_watch_point() — позволяет добавить точку генерации события при достижении определённого значения счетчика
  • pcnt_unit_register_event_callbacks() — регистрирует callback-функции для обработки событий (например, достижения watch point)
  • Точки наблюдения и события
  • Удаление и освобождение ресурсов:
  • Если вы завершили работу со счетчиком и он вам более не понадобиться, необходимо освободить аппаратные ресурсы и память, вызвав соответствующие функции.

Настройка счетчика и выделение аппаратных ресурсов

Модуль и канал PCNT представлены переменными типа pcnt_unit_handle_t и pcnt_channel_handle_t соответственно. Все доступные для текущего чипа ESP32 модули и каналы поддерживаются драйвером в общем пуле ресурсов, поэтому вам не нужно знать точный идентификатор базового экземпляра.

Установка и настройка устройства PCNT unit

Для установки модуля PCNT необходимо заранее задать структуру конфигурации: pcnt_unit_config_t:

-2

где:

  • low_limit и high_limit задают диапазон возможных значений для внутреннего аппаратного счётчика. Значение лимита не должно быть меньше 0 и не должно превышать 32767. Но это согласно документации, а на деле 0 устанавливать нельзя – для нижнего лимита значения должны быть в пределах -1~-32767, для верхнего 1~32767!
  • Счётчик автоматически сбрасывается в ноль при достижении верхнего или нижнего предела.
  • accum_count определяет, создавать ли внутренний аккумулятор для счётчика. Это полезно, если требуется расширить разрядность счётчика, которая определяется аппаратно и по умолчанию составляет не более 16 бит. См. также раздел «Компенсация потерь от переполнения», чтобы узнать, как использовать эту функцию.
  • intr_priority устанавливает приоритет прерывания. Если он равен 0, драйвер выделит прерывание с приоритетом по умолчанию, в противном случае драйвер будет использовать заданный приоритет.

Примечание. Поскольку все устройства PCNT используют один и тот же источник прерываний, при настройке нескольких устройств PCNT убедитесь, что приоритет прерывания intr_priority одинаков для каждого счетчика.

Выделение ресурсов и инициализация устройств счетчиков выполняется вызовом функции pcnt_new_unit() с pcnt_unit_config_t в качестве входного параметра. Функция вернет дескриптор устройства PCNT только при корректной работе. В частности, когда в пуле больше нет свободных аппаратных счетчиков PCNT (т.е. ресурсы счетчиков исчерпаны), эта функция вернет ошибку ESP_ERR_NOT_FOUND. Общее количество доступных устройств PCNT можно узнать с помощью SOC_PCNT_UNITS_PER_GROUP.

-3

Если ранее созданное устройство-счетчик PCNT больше не требуется, рекомендуется вернуть его в общий пул для повторного использования, вызвав функцию pcnt_del_unit(). Перед удалением устройства PCNT необходимо убедиться, что счетчик удовлетворяет двум условиям:

  • Счетчик находится в состоянии init, то есть он либо отключен функцией pcnt_unit_disable(), либо ещё не включен.
  • Все подключённые к данному счетчику каналы PCNT удалены функцией pcnt_del_channel().

Установка и настройка канала PCNT

Чтобы настроить канал PCNT, необходимо заполнить структуру pcnt_chan_config_t, а затем вызвать функцию pcnt_new_channel(). Поля конфигурации структуры pcnt_chan_config_t описаны ниже:

-4

где:

  • edge_gpio_num и level_gpio_num определяют номера GPIO, используемые сигналами типа «счетный фронт» и «управляющий уровень». Обратите внимание: любому из них можно присвоить значение -1, если он фактически не используется, и таким образом он будет считаться виртуальным входом.
  • В некоторых простых приложениях подсчёта импульсов, где один из сигналов уровня или фронта всегда фиксирован (т.е. никогда не меняется), можно освободить GPIO, назначив его виртуальным входом при выделении канала. Установка сигнала уровня/фронта в качестве виртуального входа приводит к тому, что этот сигнал внутренне маршрутизируется на заданный фиксированный логический уровень «Высокий/Низкий», что позволяет сохранить GPIO для других целей.
  • Параметры virt_edge_io_level и virt_level_io_level в этом случае определяют уровни виртуального ввода-вывода для входных сигналов. Обратите внимание: они действительны только в том случае, если edge_gpio_num или level_gpio_num присвоено значение -1.
  • Параметры invert_edge_input и invert_level_input используются для определения необходимости инвертирования входных сигналов перед их подачей на аппаратной устройство PCNT. Инвертирование выполняется матрицей GPIO, а не аппаратным обеспечением PCNT.

Выделение и инициализация каналов выполняется вызовом функции pcnt_new_channel() с указанным выше pcnt_chan_config_t в качестве входного параметра и дескриптором устройства PCNT, ранее созданным с помощью функции pcnt_new_unit(). Эта функция вернет дескриптор канала PCNT при корректной работе. В частности, когда в устройстве больше нет свободных каналов PCNT (т.е. ресурсы канала исчерпаны), функция вернет ошибку ESP_ERR_NOT_FOUND. Общее количество доступных каналов PCNT в устройстве определено в SOC_PCNT_CHANNELS_PER_UNIT. Обратите внимание, что при установке канала PCNT для конкретного устройства необходимо убедиться, что устройство находится в состоянии init, в противном случае функция вернет ошибку ESP_ERR_INVALID_STATE.

-5

Если ранее созданный канал PCNT больше не нужен, рекомендуется освободить аппаратные ресурсы, вызвав функцию pcnt_del_channel(). Это, в свою очередь, позволит использовать аппаратное обеспечение канала для других целей.

Для GPIO, задействованных в PCNT, можно настроить подтягивание к питанию или земле после инициализации PCNT с использованием таких функций, как gpio_pullup_en() и gpio_pullup_dis().

Настройка действий для канала

PCNT может увеличивать, уменьшать или сохранять внутреннее значение счётчика при переключении уровня импульсного сигнала на входе. Вы можете задать различные действия для фронта сигнала и/или уровня сигнала.

Функция pcnt_channel_set_edge_action() задаёт определённые действия для нарастающего и спадающего фронта сигнала, подключенного к edge_gpio_num. Поддерживаемые действия перечислены в pcnt_channel_edge_action_t.

-6

Функция pcnt_channel_set_level_action() задаёт определённые действия для высокого и низкого уровня сигнала, подключенного к level_gpio_num. Поддерживаемые действия перечислены в pcnt_channel_level_action_t. Эта функция не является обязательной, если level_gpio_num установлено в -1 при выделении канала PCNT функцией pcnt_new_channel().

-7

Watch Points

Каждый блок PCNT можно настроить на отслеживание нескольких различных значений, которые вас интересуют. Отслеживаемое значение также называется Watch Points или точкой наблюдения. Значение самой точки наблюдения не может выходить за пределы диапазона, установленного в pcnt_unit_config_t с помощью low_limit и high_limit. Когда счётчик достигает любой из заданных точек наблюдения, генерируется событие наблюдения, которое уведомляет вас прерыванием, если какой-либо callback для события наблюдения был когда-либо зарегистрирован через pcnt_unit_register_event_callbacks().

Точку наблюдения можно добавлять и удалять с помощью функций pcnt_unit_add_watch_point() и pcnt_unit_remove_watch_point(). Наиболее часто используемые точки наблюдения: пересечение нуля, максимальное или минимальное значение счётчика и другие пороговые значения. Количество доступных точек наблюдения ограничено. Функция pcnt_unit_add_watch_point() вернёт ошибку ESP_ERR_NOT_FOUND, если не найдёт свободного аппаратного ресурса для сохранения точки наблюдения. Нельзя добавлять одну и ту же точку наблюдения несколько раз, в противном случае будет возвращена ошибка ESP_ERR_INVALID_STATE.

Важно! Из-за аппаратных ограничений после добавления точки наблюдения необходимо вызвать pcnt_unit_clear_count(), чтобы изменения вступили в силу.

-8

Регистрация callback функций для событий

Когда счетчик PCNT достигает любой из заданных точек наблюдения, генерируется прерывание. Если вам необходимо реагировать на это прерывание, необходимо создать функцию обратного вызова, которая должна выполняться при возникновении события, и подключить её к процедуре обработки прерываний, вызвав pcnt_unit_register_event_callbacks(). Все поддерживаемые обратные вызовы событий перечислены в pcnt_event_callbacks_t:

-9

где:

  • on_reach устанавливает функцию обратного вызова для события точки наблюдения. Поскольку эта функция вызывается в контексте ISR, необходимо убедиться, что функция не пытается блокироваться (например, убедившись, что внутри функции вызываются только API FreeRTOS с суффиксом ISR). Прототип функции объявлен в pcnt_watch_cb_t.

typedef bool (*pcnt_watch_cb_t)(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx);

Вы можете передать в pcnt_unit_register_event_callbacks() обработчик собственный контекст с помощью параметра user_ctx. Эти пользовательские данные будут напрямую переданы в функции обратного вызова.

В функции обратного вызова драйвер заполняет данные о событии. Например, данные о событии точки наблюдения или шага наблюдения объявляются как pcnt_watch_event_data_t:

-10

где:

  • watch_point_value содержит значение счётчика при возникновении события.
  • zero_cross_mode содержит информацию о том, как модуль PCNT пересекал нулевую точку в последний раз. Возможные режимы пересечения нуля перечислены в pcnt_unit_zero_cross_mode_t. Обычно разные режимы пересечения нуля означают разные направления счёта и размер шага счёта.

Регистрация функции обратного вызова приводит к отложенной установке обслуживания прерываний, поэтому эту функцию следует вызывать только до включения модуля функцией pcnt_unit_enable(). В противном случае может быть возвращена ошибка ESP_ERR_INVALID_STATE.

-11

Установка фильтра коротких помех

Каждый блок PCNT оснащен цифровыми фильтрами для подавления возможных коротких помех в сигналах, которые могут возникать, например, из-за дребезга контактов. Параметры, которые можно настроить для фильтра помех, перечислены в разделе pcnt_glitch_filter_config_t:

-12

где:

  • max_glitch_ns – устанавливает максимальную длительность импульса помехи в наносекундах. Если длительность импульса сигнала меньше этого значения, он будет рассматриваться как шум и не будет влиять на значение внутреннего счётчика.

Вы можете включить фильтр помех для блока PCNT, вызвав pcnt_unit_set_glitch_filter() и передав конфигурацию фильтра, указанную выше. Вы также можете отключить фильтр помех позже, вызвав эту же функцию с конфигурацией NULL.

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

-13

римечание

Фильтр помех работает с использованием источника тактовой частоты APB. Чтобы счётчик не пропускал импульсы, максимальная длительность помехи должна быть больше одного цикла APB_CLK (обычно 12,5 нс при частоте APB 80 МГц). Поскольку частота APB может изменяться при динамическом масштабировании частоты (DFS), фильтр может работать некорректно. Поэтому драйвер PCNT может устанавливать блокировку управления питанием для каждого блока PCNT. Подробнее о стратегии управления питанием, используемой в драйвере PCNT, см. в разделе «Управление питанием».

Включение и отключение модуля счетчика

Прежде чем управлять счетом импульсов на каждом модуле PCNT, необходимо сначала включить его, вызвав pcnt_unit_enable(). Внутри эта функция выглядит так:

  • Переключает состояние драйвера PCNT с init на enable.
  • Включает службу прерываний, если она была ранее отложенно установлена с помощью pcnt_unit_register_event_callbacks().
  • Если выполнение прошло успешно, устанавливает блокировку управления питанием. Дополнительную информацию см. в разделе «Управление питанием».

Напротив, вызов  pcnt_unit_disable() приведёт к обратному результату, то есть вернёт драйвер PCNT в состояние init, отключит службу прерываний и снимет блокировку управления питанием.

Запуск/остановка счёта, сброс и чтение значения счётчика

Вызов pcnt_unit_start() запускает работу блока PCNT, после чего он начнет увеличивать или уменьшать счётчик в соответствии с входящими импульсными сигналами. Для остановки счетчика вызовите pcnt_unit_stop(). Обратите внимание: функции pcnt_unit_start() и pcnt_unit_stop() следует вызывать только после включения блока функцией pcnt_unit_enable(). В противном случае будет возвращена ошибка ESP_ERR_INVALID_STATE.

Сброс значения счётчика возможна с помощью вызова pcnt_unit_clear_count().

Вы можете считать текущее значение счётчика в любой момент, вызвав функцию pcnt_unit_get_count(). Возвращаемое значение — это целое число со знаком, который используется для указания направления.

-14

Компенсация переполнения

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

  1. Включить опцию accum_count при установке модуля PCNT.
  2. Добавить верхний/нижний предел в качестве контрольных точек.
  3. Теперь возвращаемое значение счётчика функцией pcnt_unit_get_count() не только отражает реальное аппаратное значение счётчика, но и суммирует с ним значения, сброшенные при переполнениях.

Примечание: pcnt_unit_clear_count() также сбросит и накопленное значение счётчика переполнений.

Управление питанием

Когда управление питанием для PCNT включено (т.е. опция CONFIG_PM_ENABLE включена), система автоматически корректирует опорную частоту APB перед переходом в режим лёгкого сна, что может привести к тому, что фильтр помех PCNT начнет ошибочно интерпретировать нормальные сигналы как шум. Чтобы предотвратить это, драйвер устанавливает блокировку управления питанием APB типа ESP_PM_APB_FREQ_MAX, гарантируя неизменность опорной частоты APB. Эта блокировка устанавливается при включении модуля PCNT через pcnt_unit_enable() и снимается при его отключении через pcnt_unit_disable().

Безопасность IRAM

По умолчанию прерывание PCNT будет отложено при отключении кэша, например, при записи или стирании Flash-памяти. Таким образом, прерывание счетчика не будет выполнено вовремя, что может быть не допустимым в приложениях реального времени.

Существует параметр Kconfig CONFIG_PCNT_ISR_IRAM_SAFE, который:

  • Включает обработку прерывания PCNT даже при отключенном кэше
  • Помещает все функции, используемые обработчиком прерывания (ISR) драйвера PCNT, в IRAM
  • Помещает объект драйвера в DRAM (на случай, если он случайно будет отображен в PSRAM)

Это позволяет прерыванию выполняться при отключенном кэше, но приводит к увеличению потребления IRAM.

Есть ещё один параметр Kconfig CONFIG_PCNT_CTRL_FUNC_IN_IRAM, который также позволяет помещать часто используемые функции управления PCNT в IRAM. Таким образом, перечисленные ниже функции могут быть выполнены и при отключенном кэше:

  • pcnt_unit_start()
  • pcnt_unit_stop()
  • pcnt_unit_clear_count()
  • pcnt_unit_get_count()

Потокобезопасность

Драйвер гарантирует потокобезопасность функций pcnt_new_unit() и pcnt_new_channel(), что означает, что их можно вызывать из разных задач RTOS без защиты дополнительными блокировками.

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

  • pcnt_unit_start()
  • pcnt_unit_stop()
  • pcnt_unit_clear_count()
  • pcnt_unit_get_count()

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

Параметры Kconfig

CONFIG_PCNT_CTRL_FUNC_IN_IRAM управляет размещением функций управления PCNT (IRAM или Flash). Подробнее см. выше в разделе «Безопасность IRAM».

CONFIG_PCNT_ISR_IRAM_SAFE управляет работой обработчика ISR по умолчанию при отключенном кэше. Подробнее см. выше в разделе «Безопасность IRAM».

CONFIG_PCNT_ENABLE_DEBUG_LOG используется для включения вывода журнала отладки PCNT. Включение этого параметра увеличивает размер двоичного файла прошивки.

Практические примеры

Конечно, посчитать количество импульсов на входе можно и без этих всяких специальных контроллеров – просто добавив прерывание на вход и таймер для “гашения” помех от дребезга контактов. Да так в 90% случаев все и делают! Но зачем?????

Ведь задействовав аппаратный контроллер PCNT, мы получим сплошные выгоды:

  • освободим CPU от необходимости реагировать на прерывания по каждом фронту / спаду импульса и считать их
  • нет необходимости в debounce-таймере, так как фильтрация осуществляется аппаратными средствами
  • аппаратный счетчик не зависит он FreeRTOS и многозадачности, поэтому сам счет будет вестись без пропусков и задержек

Недостаток я вижу только один:

  • необходимость освоения еще одного API ESP-IDF

1. Счетчик расхода воды / измеритель скорости ветра

Наиболее частая практическая задача, которая напрямую связана с подсчетом импульсов – измерение количества и (или) скорости количества расхода воды, воздуха или других сред с помощью различных импульсных датчиков. Для этого вам потребуется счетчик или анемометр с импульсным выходом:

Для каждого такого счетчика необходимо знать коэффициент пересчета количества импульсов на объем измеряемой среды (или скорости ветра) – K, например это может быть 25 мл  на импульс. Обычно эта информация указана в паспорте счетчика. Таким образом, объем перекачиваемой жидкости (или иной среды) можно узнать, просто умножив количество импульсов на этот коэффициент. Скорость потока можно вычислить, подсчитав количество импульсов за определённый интервал времени, например за минуту с учетом коэффициента пересчета.

Пример простой программы, которая считает скорость потока в секунду, приведен ниже:

-16

Здесь мы подразумеваем, что выхода из основного цикла не будет, поэтому не освобождаем занятые ресурсы. Но если теоретически это может произойти, необходимо будет добавить код, который удалит pcnt_channel и pcnt_unit.

2. Счетчик количества импульсов с таймаутом

Допустим у нас некое устройство, которое выдает некое значение в виде пачки импульсов. В количестве импульсов закодированы какие-то данные. Причем “прилететь” такая пачка может когда угодно – период повторения пачек может быть произвольным. Например это могут быть данные, передаваемые по проводному или радио-каналу. Длительность каждого отдельного импульса в данном случае нам не важна.

Необходимо среагировать на первый импульс, посчитать их количество, и по окончании пачки уведомить программу о полученном количестве импульсов.

Порядок решения данной задачи может быть следующим:

  1. Настроить счет импульсов как это было сделано в предыдущем примере
  2. Создать программный таймер с периодом, заведомо превышающим длительность одного импульса
  3. Добавить watch point с количеством импульсов, равным 1
  4. В обработчике прерывания для события watch point запустить однократный программный таймер. Таким образом, с приходом каждого последующего импульса в одной пачке таймер будет перезапускаться.
  5. При срабатывании обработчика обратного вызова таймера понимаем, что пачка окончена и мы можем считать значение счетчика и сбросить счетчик. Результат можно передать в любую другую задачу с помощью очереди или посредством событий.

В демонстрационном примере ниже я использовал очередь, значения из которой считываются и выводятся в лог после завершения всех необходимых манипуляций. Однако вы можете использовать любой другой метод (даже отправить на MQTT, например, так как callback для таймера вызывается из задачи таймера, а не из прерывания).

-17
-18

Примечание: после вызова pcnt_unit_add_watch_point() нужно обязательно вызвать pcnt_unit_clear_count(), но сделать это можно и позже, например непосредственно pcnt_unit_start(). В данном примере я сделал так, в следующем – по другому. С той же целью я переместил настройку фильтра помех на один шаг выше – потому что так тоже можно. А вот в остальном последовательность лучше не нарушать.

Проверим… все работает как часы (оно и понятно, таймер “внутри”):

-19

3. Обработка данных с энкодера

Ещё один пример для эффективного применения счетчика импульсов – обработка сигналов с повортного энкодера. С его помощью можно удобно управлять устройствами, например перемещением по экранному меню.

-20

Принцип работы энкодера заключается в том, что при вращении он выдает импульсы сразу на два выхода А и В, но сигналы с которых смещены на четверть периода. По тому, какой импульс (в канале А или в канале В) приходит первым – можно судить о направлении вращения, а по количеству импульсов – о величине поворота.

-21

Источник изображения: Яндекс Картинки

Идея здесь заключается в одновременном использовании двух каналов счёта, подключенных к одному и тому же счётчику:

-22
  • В первом канале GPIO A является счетным, а GPIO B – управляющим (например). При этом фронты импульсов на входе GPIO A приводят к уменьшению значения счетчика, а спады – к его увеличению. А на управляющем входе GPIO B фронт импульса ничего не меняет, а вот спад меняет направление счета на противоположное.
  • Во втором канале тот же самый GPIO B является счетным, а GPIO A – уже управляющим. При этом реакция на фронты импульсов изменилась на противоположную: на входе GPIO B приводят к увеличению значения счетчика, а спады – к его уменьшению. А на управляющем входе GPIO А ничего не изменилось: фронт импульса ничего не меняет, а вот спад меняет направление счета на противоположное.

Изначально в примере от Espressif было настроено так:

-23

Но такое поведение не очень корректно работает – ниже вы увидите почему.

Поэтому я сделал так:

-24

Суть работы проста как две копейки – “кто первый встал, того и тапки“. Пришедший первым импульс блокирует считает в одном канале, и одновременно блокирует противоположный канал.

Реагировать на импульсы с энкодера мы можем с помощью watch point, настроенный на единицу и минус единицу – тогда обработчик будет генерировать прерывание при каждом отдельном шаге энкодера. Либо можно использовать подход, изложенный в предыдущем примере – тогда callback будет вызываться уже после того, как энкодер будет повернут на один или несколько шагов. Как вам удобнее – думайте сами.

-25
-26
-27

И теперь он работает совершенно корректно:

-28

Таким образом мы получаем надежный аппаратный обработчик сигналов энкодера с фильтрацией bounce-помех в полностью асинхронном режиме. Без регистрации и СМС таймеров и сложных обработчиков прерываний GPIO.

Основная статья: https://kotyara12.ru/iot/esp32-pcnt/