From 0363eb2f0f65b28710b618606da9f472ea9f1ed4 Mon Sep 17 00:00:00 2001 From: Marek Knosala <44412229+rvbc1@users.noreply.github.com> Date: Tue, 26 May 2026 13:38:59 +0200 Subject: [PATCH] Migrate PCNT driver for ESP-IDF 5 --- src/ESP32Encoder.cpp | 276 ++++++++++++++++++++++++++++++++++++++++++- src/ESP32Encoder.h | 27 ++++- 2 files changed, 301 insertions(+), 2 deletions(-) diff --git a/src/ESP32Encoder.cpp b/src/ESP32Encoder.cpp index 660fdcb..dbd47a9 100644 --- a/src/ESP32Encoder.cpp +++ b/src/ESP32Encoder.cpp @@ -16,13 +16,17 @@ #include #if SOC_PCNT_SUPPORTED // Not all esp32 chips support the pcnt (notably the esp32c3 does not) -#include #include "esp_log.h" +#if ESP32ENCODER_USE_NEW_PCNT +#include +#else +#include #include "esp_ipc.h" #if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 3) ) #include #include #endif +#endif static const char* TAG_ENCODER = "ESP32Encoder"; @@ -44,7 +48,11 @@ ESP32Encoder::ESP32Encoder(bool always_interrupt_, enc_isr_cb_t enc_isr_cb, void always_interrupt{always_interrupt_}, aPinNumber{(gpio_num_t) 0}, bPinNumber{(gpio_num_t) 0}, +#if ESP32ENCODER_USE_NEW_PCNT + unit{-1}, +#else unit{(pcnt_unit_t) -1}, +#endif countsMode{2}, count{0}, r_enc_config{}, @@ -62,6 +70,271 @@ ESP32Encoder::ESP32Encoder(bool always_interrupt_, enc_isr_cb_t enc_isr_cb, void ESP32Encoder::~ESP32Encoder() {} +#if ESP32ENCODER_USE_NEW_PCNT + +bool ESP32Encoder::pcntCallback(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx) { + ESP32Encoder *esp32enc = static_cast(user_ctx); + if (esp32enc == nullptr || esp32enc->always_interrupt == false) { + return false; + } + if (edata == nullptr || (edata->watch_point_value != -1 && edata->watch_point_value != 1)) { + return false; + } + + int raw_count = 0; + pcnt_unit_get_count(unit, &raw_count); + _ENTER_CRITICAL(); + esp32enc->count = esp32enc->count + raw_count; + _EXIT_CRITICAL(); + pcnt_unit_clear_count(unit); + + if (esp32enc->_enc_isr_cb) { + esp32enc->_enc_isr_cb(esp32enc->_enc_isr_cb_data); + } + return false; +} + +void ESP32Encoder::detach(){ + if (unitHandle == nullptr) { + return; + } + + if (attached) { + if (working) { + pcnt_unit_stop(unitHandle); + working = false; + } + pcnt_unit_disable(unitHandle); + } + + if (channelHandles[0] != nullptr) { + pcnt_del_channel(channelHandles[0]); + channelHandles[0] = nullptr; + } + if (channelHandles[1] != nullptr) { + pcnt_del_channel(channelHandles[1]); + channelHandles[1] = nullptr; + } + + pcnt_del_unit(unitHandle); + unitHandle = nullptr; + if (unit >= 0 && unit < MAX_ESP32_ENCODERS) { + ESP32Encoder::encoders[unit] = NULL; + } + unit = -1; + attached = false; +} + +void ESP32Encoder::detatch(){ + this->detach(); +} + +void ESP32Encoder::attach(int a, int b, encType et) { + if (attached) { + ESP_LOGE(TAG_ENCODER, "attach: already attached"); + return; + } + int index = 0; + for (; index < MAX_ESP32_ENCODERS; index++) { + if (ESP32Encoder::encoders[index] == NULL) { + encoders[index] = this; + break; + } + } + if (index == MAX_ESP32_ENCODERS) { + while(1){ + ESP_LOGE(TAG_ENCODER, "Too many encoders, FAIL!"); + delay(100); + } + } + + unit = index; + this->aPinNumber = (gpio_num_t) a; + this->bPinNumber = (gpio_num_t) b; + + esp_rom_gpio_pad_select_gpio(aPinNumber); + esp_rom_gpio_pad_select_gpio(bPinNumber); + gpio_set_direction(aPinNumber, GPIO_MODE_INPUT); + gpio_set_direction(bPinNumber, GPIO_MODE_INPUT); + if(useInternalWeakPullResistors == puType::down){ + gpio_pulldown_en(aPinNumber); + gpio_pulldown_en(bPinNumber); + } + if(useInternalWeakPullResistors == puType::up){ + gpio_pullup_en(aPinNumber); + gpio_pullup_en(bPinNumber); + } + + r_enc_config = {}; + r_enc_config.high_limit = _INT16_MAX; + r_enc_config.low_limit = _INT16_MIN; + r_enc_config.flags.accum_count = true; + + esp_err_t err = pcnt_new_unit(&r_enc_config, &unitHandle); + if (err != ESP_OK) { + ESP32Encoder::encoders[index] = NULL; + unit = -1; + ESP_LOGE(TAG_ENCODER, "Encoder create PCNT unit failed"); + return; + } + + setFilter(250); + + pcnt_chan_config_t chan_a_config = {}; + chan_a_config.edge_gpio_num = aPinNumber; + chan_a_config.level_gpio_num = bPinNumber; + err = pcnt_new_channel(unitHandle, &chan_a_config, &channelHandles[0]); + if (err != ESP_OK) { + ESP_LOGE(TAG_ENCODER, "Encoder create channel A failed"); + detach(); + return; + } + + pcnt_channel_edge_action_t a_pos_action = et != encType::single ? PCNT_CHANNEL_EDGE_ACTION_DECREASE : PCNT_CHANNEL_EDGE_ACTION_HOLD; + pcnt_channel_edge_action_t a_neg_action = PCNT_CHANNEL_EDGE_ACTION_INCREASE; + pcnt_channel_set_edge_action(channelHandles[0], a_pos_action, a_neg_action); + pcnt_channel_set_level_action(channelHandles[0], PCNT_CHANNEL_LEVEL_ACTION_INVERSE, PCNT_CHANNEL_LEVEL_ACTION_KEEP); + + if (et == encType::full) { + pcnt_chan_config_t chan_b_config = {}; + chan_b_config.edge_gpio_num = bPinNumber; + chan_b_config.level_gpio_num = aPinNumber; + err = pcnt_new_channel(unitHandle, &chan_b_config, &channelHandles[1]); + if (err != ESP_OK) { + ESP_LOGE(TAG_ENCODER, "Encoder create channel B failed"); + detach(); + return; + } + pcnt_channel_set_edge_action(channelHandles[1], PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE); + pcnt_channel_set_level_action(channelHandles[1], PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE); + } + + pcnt_unit_add_watch_point(unitHandle, _INT16_MAX); + pcnt_unit_add_watch_point(unitHandle, _INT16_MIN); + + if (always_interrupt) { + pcnt_unit_add_watch_point(unitHandle, -1); + pcnt_unit_add_watch_point(unitHandle, 1); + pcnt_event_callbacks_t callbacks = {}; + callbacks.on_reach = ESP32Encoder::pcntCallback; + pcnt_unit_register_event_callbacks(unitHandle, &callbacks, this); + } + + err = pcnt_unit_enable(unitHandle); + if (err != ESP_OK) { + ESP_LOGE(TAG_ENCODER, "Encoder enable PCNT unit failed"); + detach(); + return; + } + attached = true; + pcnt_unit_clear_count(unitHandle); + err = pcnt_unit_start(unitHandle); + if (err != ESP_OK) { + ESP_LOGE(TAG_ENCODER, "Encoder start PCNT unit failed"); + detach(); + return; + } + + working = true; +} + +void ESP32Encoder::attachHalfQuad(int aPintNumber, int bPinNumber) { + attach(aPintNumber, bPinNumber, encType::half); +} + +void ESP32Encoder::attachSingleEdge(int aPintNumber, int bPinNumber) { + attach(aPintNumber, bPinNumber, encType::single); +} + +void ESP32Encoder::attachFullQuad(int aPintNumber, int bPinNumber) { + attach(aPintNumber, bPinNumber, encType::full); +} + +void ESP32Encoder::setCount(int64_t value) { + _ENTER_CRITICAL(); + count = value - getCountRaw(); + _EXIT_CRITICAL(); +} + +int64_t ESP32Encoder::getCountRaw() { + int raw_count = 0; + if (unitHandle != nullptr) { + pcnt_unit_get_count(unitHandle, &raw_count); + } + return raw_count; +} + +int64_t ESP32Encoder::getCount() { + _ENTER_CRITICAL(); + int64_t result = count + getCountRaw(); + _EXIT_CRITICAL(); + return result; +} + +int64_t ESP32Encoder::clearCount() { + _ENTER_CRITICAL(); + count = 0; + _EXIT_CRITICAL(); + if (unitHandle == nullptr) { + return ESP_ERR_INVALID_STATE; + } + return pcnt_unit_clear_count(unitHandle); +} + +int64_t ESP32Encoder::pauseCount() { + if (unitHandle == nullptr) { + return ESP_ERR_INVALID_STATE; + } + esp_err_t err = pcnt_unit_stop(unitHandle); + if (err == ESP_OK) { + working = false; + } + return err; +} + +int64_t ESP32Encoder::resumeCount() { + if (unitHandle == nullptr) { + return ESP_ERR_INVALID_STATE; + } + esp_err_t err = pcnt_unit_start(unitHandle); + if (err == ESP_OK) { + working = true; + } + return err; +} + +void ESP32Encoder::setFilter(uint16_t value) { + if (unitHandle == nullptr) { + return; + } + if(value>1023)value=1023; + + bool restart = attached && working; + if (attached) { + if (working) { + pcnt_unit_stop(unitHandle); + } + pcnt_unit_disable(unitHandle); + } + + if(value==0) { + pcnt_unit_set_glitch_filter(unitHandle, nullptr); + } else { + pcnt_glitch_filter_config_t filter_config = {}; + filter_config.max_glitch_ns = (static_cast(value) * 125 + 9) / 10; + pcnt_unit_set_glitch_filter(unitHandle, &filter_config); + } + + if (attached) { + pcnt_unit_enable(unitHandle); + if (restart) { + pcnt_unit_start(unitHandle); + } + } +} + +#else + /* Decode what PCNT's unit originated an interrupt * and pass this information together with the event type * the main program using a queue. @@ -332,6 +605,7 @@ void ESP32Encoder::setFilter(uint16_t value) { } } +#endif // ESP32ENCODER_USE_NEW_PCNT #else #warning PCNT not supported on this SoC, this will likely lead to linker errors when using ESP32Encoder #endif // SOC_PCNT_SUPPORTED diff --git a/src/ESP32Encoder.h b/src/ESP32Encoder.h index a8b3ae6..bf958b2 100644 --- a/src/ESP32Encoder.h +++ b/src/ESP32Encoder.h @@ -1,6 +1,17 @@ #pragma once #include +#if __has_include() +#include +#endif +#include + +#if defined(ESP_IDF_VERSION) && defined(ESP_IDF_VERSION_VAL) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#define ESP32ENCODER_USE_NEW_PCNT 1 +#include +#else +#define ESP32ENCODER_USE_NEW_PCNT 0 #include +#endif #ifndef ARDUINO #include #include @@ -8,7 +19,11 @@ #include #endif +#if ESP32ENCODER_USE_NEW_PCNT +#define MAX_ESP32_ENCODERS SOC_PCNT_UNITS_PER_GROUP +#else #define MAX_ESP32_ENCODERS PCNT_UNIT_MAX +#endif #define _INT16_MAX 32766 #define _INT16_MIN -32766 #define ISR_CORE_USE_DEFAULT (0xffffffff) @@ -57,10 +72,15 @@ class ESP32Encoder { bool always_interrupt; gpio_num_t aPinNumber; gpio_num_t bPinNumber; +#if ESP32ENCODER_USE_NEW_PCNT + int unit; + pcnt_unit_config_t r_enc_config; +#else pcnt_unit_t unit; + pcnt_config_t r_enc_config; +#endif int countsMode = 2; volatile int64_t count=0; - pcnt_config_t r_enc_config; static puType useInternalWeakPullResistors; static uint32_t isrServiceCpuCore; enc_isr_cb_t _enc_isr_cb; @@ -70,6 +90,11 @@ class ESP32Encoder { static bool attachedInterrupt; void attach(int aPintNumber, int bPinNumber, encType et); int64_t getCountRaw(); +#if ESP32ENCODER_USE_NEW_PCNT + static bool pcntCallback(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx); + pcnt_unit_handle_t unitHandle = nullptr; + pcnt_channel_handle_t channelHandles[2] = {nullptr, nullptr}; +#endif bool attached; bool direction; bool working;