16 KiB
Описание компонента 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 напрямую |
Потокобезопасность
-
Общий доступ к данным — последнее значение RPM хранится в поле
valueс квалификаторомvolatile, что обеспечивает корректное чтение из любого контекса. Функцияzh_tachometer_get()просто копирует значение из структуры, не требуя блокировок. -
Контекст таймера — обратный вызов 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() ресурсы освобождаются в строгом порядке:
- Остановка ESP-Timer (
esp_timer_stop). - Удаление ESP-Timer (
esp_timer_delete). - Остановка PCNT (
pcnt_unit_stop). - Отключение PCNT (
pcnt_unit_disable). - Удаление точек наблюдения: +32767, затем -32767.
- Удаление каналов A и B (
pcnt_del_channel). - Удаление модуля PCNT (
pcnt_del_unit). - Освобождение памяти объекта (
heap_caps_free). - Обнуление указателя на объект.
Все шаги используют 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 |
Ограничения
-
Переполнение PCNT счётчика — модуль PCNT использует точки наблюдения на ±32767. При высоких значениях RPM накопленный счётчик может переполниться. Необходимо убедиться, что комбинация количества импульсов на оборот и ожидаемого диапазона RPM остаётся в безопасных пределах.
-
Абсолютное значение RPM — направление вращения игнорируется. Компонент возвращает только модуль скорости, без информации о направлении.
-
16-битное значение RPM — возвращаемое значение имеет тип
uint16_t, максимальное значение — 65535. При очень высоких скоростях вращения значение может не поместиться. -
Один 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"