Files
zh_tachometer/wiki_ru.md
T
2026-11-03 19:23:03 +03:00

16 KiB
Raw Blame History

Описание компонента zh_tachometer

zh_tachometer — драйвер для ESP-IDF, измеряющий скорость вращения на основе квадратурного энкодера. Компонент использует периферийный модуль PCNT ESP32 в режиме квадратурного декодера для подсчёта импульсов энкодера и ESP-Timer, работающего с частотой 100 Гц, для периодического считывания накопленного значения и вычисления оборотов в минуту (RPM). Результатом является неблокирующий, совместимый с RTOS интерфейс, подходящий для мониторинга скорости двигателей.

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

Основные возможности

  • Поддержка квадратурного энкодера — подсчёт импульсов по двум фазам (A/B) через периферийный модуль PCNT в режиме квадратурного декодера.
  • Автоматический расчёт RPM — вычисление оборотов в минуту на основе настраиваемого количества импульсов на полный оборот энкодера.
  • Периодическое дискретное считывание — ESP-Timer запускается с частотой 100 Гц (интервал 10 мс) для чтения накопленного счёта PCNT и перевода в RPM.
  • Настраиваемые подтягивающие резисторы GPIO — возможность включения или отключения внутренних подтягивающих резисторов для линий A и B энкодера.
  • Фильтр глитчей PCNT — подавление ложных переходов с настройкой 1000 нс через встроенный фильтр PCNT.
  • Поддержка нескольких тахометров — каждый тахометр использует отдельный модуль PCNT и таймер ESP-Timer.

Внутреннее устройство

Структура zh_tachometer_handle_t

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

Поле Тип Описание
pcnt_unit_handle pcnt_unit_handle_t Управляющий объект модуля PCNT для квадратурного декодирования
pcnt_channel_a_handle pcnt_channel_handle_t Управляющий объект канала PCNT A (детектор фронтов)
pcnt_channel_b_handle pcnt_channel_handle_t Управляющий объект канала PCNT B (детектор фронтов)
esp_timer_handle esp_timer_handle_t Управляющий объект ESP-Timer для дискретного считывания RPM (100 Гц)
value volatile uint16_t Последнее вычисленное значение RPM (абсолютное, направление отбрасывается)
encoder_pulses uint16_t Количество импульсов на один полный оборот энкодера

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

Структура конфигурации zh_tachometer_init_config_t

Передаётся в функцию zh_tachometer_init() для настройки пин GPIO, подтягивающих резисторов и коэффициента импульсов на оборот.

Поле Тип Описание
a_gpio_number gpio_num_t Номер GPIO линии фазы A энкодера
b_gpio_number gpio_num_t Номер GPIO линии фазы B энкодера
pullup bool Включение или отключение подтягивающих резисторов GPIO
encoder_pulses uint16_t Количество импульсов на один полный оборот энкодера

По умолчанию ZH_TACHOMETER_INIT_CONFIG_DEFAULT() устанавливает GPIO в GPIO_NUM_MAX, подтягивание в true, а импульсы в 0.

Управление памятью

Память для структуры zh_tachometer_handle_t выделяется через heap_caps_calloc() с флагом MALLOC_CAP_8BIT при вызове zh_tachometer_init() и освобождается через heap_caps_free() при вызове zh_tachometer_deinit(). Управляющие объекты PCNT и ESP-Timer освобождаются через соответствующие функции ESP-IDF API (pcnt_del_unit(), pcnt_del_channel(), esp_timer_delete()).

API (интерфейс)

Инициализация и освобождение

Функция Описание
zh_tachometer_init() Создаёт объект тахометра, настраивает ESP-Timer (100 Гц) и периферийный модуль PCNT в режиме квадратурного декодера с фильтром глитчей
zh_tachometer_deinit() Останавливает модуль PCNT, удаляет каналы и точки наблюдения, удаляет ESP-Timer и освобождает память объекта

Получение данных

Функция Описание
zh_tachometer_get() Возвращает последнее вычисленное значение RPM. Значение является абсолютным (направление вращения отбрасывается), функция не блокирует выполнение и не обращается к периферийному модулю PCNT напрямую

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

  1. Общий доступ к данным — последнее значение RPM хранится в поле value с квалификатором volatile, что обеспечивает корректное чтение из любого контекса. Функция zh_tachometer_get() просто копирует значение из структуры, не требуя блокировок.

  2. Контекст таймера — обратный вызов ESP-Timer выполняется в контексте задачи esp_timer service task (метод диспетчеризации ESP_TIMER_TASK по умолчанию), а не из прерывания. Блокирующие или длительные операции в обратном вызове следует избегать.

Алгоритмы

Формула расчёта RPM

Обратный вызов ESP-Timer выполняется каждые 10 мс (100 Гц). За этот интервал считывается накопленное значение PCNT, обнуляется и преобразуется в RPM по формуле:

RPM = (count * 100.0 / (4.0 * pulses_per_rev)) * 60.0

Где:

  • count — накопленное значение PCNT за 10 мс.
  • 100.0 — масштабный коэффициент для перевода 10 мс в секунду (1000 мс / 10 мс).
  • 4.0 — коэффициент квадратурного кодирования: 4 фронта за один цикл (фазы A и B имеют сдвиг 90°).
  • pulses_per_rev — количество импульсов на полный оборот энкодера, задаётся в конфигурации.
  • 60.0 — перевод оборотов в секунду в обороты в минуту.

Результат берётся по модулю (fabsf()) — направление вращения игнорируется, возвращается абсолютное значение RPM.

Конфигурация PCNT

Модуль PCNT настраивается в режиме квадратурного декодера:

  • Канал A: фронт по фазе A вызывает увеличение (INCREASE) или уменьшение (DECREASE) счётчика в зависимости от уровня фазы B.
  • Канал A: уровень фазы A удерживает счёт (KEEP) или инвертирует направление (INVERSE).
  • Канал B: фронт по фазе B вызывает увеличение или уменьшение счётчика в зависимости от уровня фазы A (обратное каналу A).
  • Канал B: уровень фазы B удерживает или инвертирует направление (обратное каналу A).
  • Фильтр глитчей: 1000 нс.
  • Точки наблюдения: ±32767 для обнаружения переполнения.
  • Накопление: включено (accum_count = true).

Процесс деинициализации

При вызове zh_tachometer_deinit() ресурсы освобождаются в строгом порядке:

  1. Остановка ESP-Timer (esp_timer_stop).
  2. Удаление ESP-Timer (esp_timer_delete).
  3. Остановка PCNT (pcnt_unit_stop).
  4. Отключение PCNT (pcnt_unit_disable).
  5. Удаление точек наблюдения: +32767, затем -32767.
  6. Удаление каналов A и B (pcnt_del_channel).
  7. Удаление модуля PCNT (pcnt_del_unit).
  8. Освобождение памяти объекта (heap_caps_free).
  9. Обнуление указателя на объект.

Все шаги используют ZH_ERROR_CHECK_CONT — ошибка логируется, но процесс деинициализации продолжается до полного освобождения ресурсов.

Примеры использования

Базовое использование

#include "zh_tachometer.h"

zh_tachometer_handle_t *tachometer_handle = NULL;

void app_main(void)
{
    esp_log_level_set("zh_tachometer", ESP_LOG_ERROR);

    zh_tachometer_init_config_t config = ZH_TACHOMETER_INIT_CONFIG_DEFAULT();
    config.a_gpio_number = GPIO_NUM_26;
    config.b_gpio_number = GPIO_NUM_27;
    config.encoder_pulses = 3600;

    esp_err_t ret = zh_tachometer_init(&config, &tachometer_handle);
    if (ret != ESP_OK)
    {
        printf("Tachometer initialization error: %s\n", esp_err_to_name(ret));
        return;
    }

    for (;;)
    {
        uint16_t value = 0;
        ret = zh_tachometer_get(&tachometer_handle, &value);
        if (ret == ESP_OK)
        {
            printf("Current RPM: %u\n", value);
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }

    zh_tachometer_deinit(&tachometer_handle);
}

Настройка с отключёнными подтягивающими резисторами

#include "zh_tachometer.h"

zh_tachometer_handle_t *tachometer_handle = NULL;

void app_main(void)
{
    esp_log_level_set("zh_tachometer", ESP_LOG_ERROR);

    zh_tachometer_init_config_t config = ZH_TACHOMETER_INIT_CONFIG_DEFAULT();
    config.a_gpio_number = GPIO_NUM_12;
    config.b_gpio_number = GPIO_NUM_13;
    config.pullup = false;
    config.encoder_pulses = 1800;

    esp_err_t ret = zh_tachometer_init(&config, &tachometer_handle);
    if (ret != ESP_OK)
    {
        printf("Tachometer initialization error: %s\n", esp_err_to_name(ret));
        return;
    }

    uint16_t rpm_value = 0;
    ret = zh_tachometer_get(&tachometer_handle, &rpm_value);
    if (ret == ESP_OK)
    {
        printf("RPM value: %u\n", rpm_value);
    }

    zh_tachometer_deinit(&tachometer_handle);
}

Логирование

Компонент использует ESP-IDF логирование с тегом "zh_tachometer".

esp_log_level_set("zh_tachometer", ESP_LOG_ERROR);

Доступные уровни: ESP_LOG_ERROR, ESP_LOG_INFO. Макрос ZH_LOGI выводит информационные сообщения о начале и завершении операций. Макрос ZH_LOGE выводит сообщения об ошибках с указанием файла, строки и имени кода ошибки.

Ошибки

Код Описание
ESP_OK Успешное выполнение
ESP_ERR_INVALID_ARG Неверный аргумент: config или handle равен NULL, encoder_pulses равен нулю, номера GPIO недействительны или совпадают, *handle или value равен NULL
ESP_ERR_INVALID_STATE Объект тахометра уже инициализирован (*handle не равен NULL)
ESP_ERR_NO_MEM Не удалось выделить память для объекта тахометра
ESP_FAIL Ошибка инициализации или конфигурации PCNT, ошибка создания или запуска ESP-Timer

Ограничения

  1. Переполнение PCNT счётчика — модуль PCNT использует точки наблюдения на ±32767. При высоких значениях RPM накопленный счётчик может переполниться. Необходимо убедиться, что комбинация количества импульсов на оборот и ожидаемого диапазона RPM остаётся в безопасных пределах.

  2. Абсолютное значение RPM — направление вращения игнорируется. Компонент возвращает только модуль скорости, без информации о направлении.

  3. 16-битное значение RPM — возвращаемое значение имеет тип uint16_t, максимальное значение — 65535. При очень высоких скоростях вращения значение может не поместиться.

  4. Один GPIO на фазу — каждая фаза энкодера (A и B) должна быть назначена на отдельный GPIO. Назначение одинакового номера GPIO на обе фазы вернёт ошибку ESP_ERR_INVALID_ARG.

Требования для сборки

  • ESP-IDF (фреймворк для ESP32)
  • FreeRTOS (входит в состав ESP-IDF)
  • Компонент ESP-IDF pulse_cnt (драйвер PCNT)
  • Компонент ESP-IDF esp_timer (ESP-Timer)
  • Компонент ESP-IDF driver (GPIO driver)

Установка

cd ваш_проект/components
git clone https://github.com/aZholtikov/zh_tachometer
#include "zh_tachometer.h"