From 121ba14a49046793996d6f889faf1f959f5f6d61 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sat, 24 Jan 2026 10:17:43 +0300 Subject: [PATCH] feat!: changed driver on pcnt --- README.md | 58 ++++---- include/zh_encoder.h | 78 +++++++---- version.txt | 2 +- zh_encoder.c | 326 ++++++++++++++++++++++--------------------- 4 files changed, 244 insertions(+), 220 deletions(-) diff --git a/README.md b/README.md index 97af081..5c40c6f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Tested on -1. [ESP32 ESP-IDF v5.5.1](https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32/index.html) +1. [ESP32 ESP-IDF v5.5.2](https://docs.espressif.com/projects/esp-idf/en/v5.5.2/esp32/index.html) ## SAST Tools @@ -14,11 +14,12 @@ ## Attention -1. If the button is not used, specify any free GPIO in the initial configuration. -2. For correct operation, please enable the following settings in the menuconfig: +1. For correct operation, please enable the following settings in the menuconfig: ```text GPIO_CTRL_FUNC_IN_IRAM +PCNT_CTRL_FUNC_IN_IRAM +PCNT_ISR_IRAM_SAFE ``` ## Using @@ -38,7 +39,7 @@ In the application, add the component: ## Examples -One encoder on device: +One encoder with button on device: ```c #include "zh_encoder.h" @@ -47,8 +48,6 @@ One encoder on device: zh_encoder_handle_t encoder_handle = {0}; -double encoder_position = 0; - void zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); void app_main(void) @@ -56,20 +55,21 @@ void app_main(void) esp_log_level_set("zh_encoder", ESP_LOG_ERROR); esp_event_loop_create_default(); esp_event_handler_instance_register(ZH_ENCODER, ESP_EVENT_ANY_ID, &zh_encoder_event_handler, NULL, NULL); - zh_encoder_init_config_t config = ZH_ENCODER_INIT_CONFIG_DEFAULT(); - config.task_priority = 5; - config.stack_size = configMINIMAL_STACK_SIZE; - config.queue_size = 5; - config.a_gpio_number = GPIO_NUM_27; - config.b_gpio_number = GPIO_NUM_26; - config.s_gpio_number = GPIO_NUM_17; - config.encoder_min_value = 0; - config.encoder_max_value = 100; - config.encoder_step = 0.1; - config.encoder_number = ENCODER_NUMBER; - zh_encoder_init(&config, &encoder_handle); + zh_encoder_init_config_t encoder_init_config = ZH_ENCODER_INIT_CONFIG_DEFAULT(); + encoder_init_config.task_priority = 5; + encoder_init_config.stack_size = configMINIMAL_STACK_SIZE; + encoder_init_config.queue_size = 5; + encoder_init_config.a_gpio_number = GPIO_NUM_4; + encoder_init_config.b_gpio_number = GPIO_NUM_16; + encoder_init_config.s_gpio_number = GPIO_NUM_15; + encoder_init_config.encoder_min_value = -10; + encoder_init_config.encoder_max_value = 10; + encoder_init_config.encoder_step = 0.001; + encoder_init_config.encoder_number = ENCODER_NUMBER; + zh_encoder_init(&encoder_init_config, &encoder_handle); + double encoder_position = 0; zh_encoder_get(&encoder_handle, &encoder_position); - printf("Encoder position %0.2f.\n", encoder_position); + printf("Encoder position %0.3f.\n", encoder_position); for (;;) { const zh_encoder_stats_t *stats = zh_encoder_get_stats(); @@ -82,19 +82,15 @@ void app_main(void) void zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { - zh_encoder_event_on_isr_t *event = event_data; - switch (event->encoder_number) + switch (event_id) { - case ENCODER_NUMBER: - if (encoder_position == event->encoder_position) - { - printf("Encoder number %d button %s.\n", event->encoder_number, event->button_status == 1 ? "released" : "pressed"); - } - else - { - encoder_position = event->encoder_position; - printf("Encoder number %d position %0.2f with button %s.\n", event->encoder_number, event->encoder_position, event->button_status == 1 ? "released" : "pressed"); - } + case ZH_BUTTON_EVENT: + zh_encoder_button_event_on_isr_t *button_event = event_data; + printf("Encoder number %d button %s.\n", button_event->encoder_number, button_event->button_status == 1 ? "released" : "pressed"); + break; + case ZH_ENCODER_EVENT: + zh_encoder_event_on_isr_t *encoder_event = event_data; + printf("Encoder number %d position %0.3f.\n", encoder_event->encoder_number, encoder_event->encoder_position); break; default: break; diff --git a/include/zh_encoder.h b/include/zh_encoder.h index b175017..fc1a60a 100644 --- a/include/zh_encoder.h +++ b/include/zh_encoder.h @@ -6,6 +6,7 @@ #include "esp_log.h" #include "driver/gpio.h" +#include "driver/pulse_cnt.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -22,8 +23,9 @@ .a_gpio_number = GPIO_NUM_MAX, \ .b_gpio_number = GPIO_NUM_MAX, \ .s_gpio_number = GPIO_NUM_MAX, \ - .encoder_min_value = -100, \ - .encoder_max_value = 100, \ + .s_gpio_debounce_time = 10, \ + .encoder_min_value = -10, \ + .encoder_max_value = 10, \ .encoder_step = 1, \ .encoder_number = 0} @@ -32,23 +34,24 @@ extern "C" { #endif - extern TaskHandle_t zh_encoder; /*!< Unique encoder Task Handle. */ + extern TaskHandle_t zh_encoder; /*!< Encoder Task Handle. */ /** * @brief Structure for initial initialization of encoder. */ typedef struct { - double encoder_step; /*!< Encoder step. @note Must be greater than 0. */ - double encoder_min_value; /*!< Encoder min value. @note Must be less than encoder_max_value. */ - double encoder_max_value; /*!< Encoder max value. @note Must be greater than encoder_min_value. */ - uint8_t task_priority; /*!< Task priority for the encoder isr processing. @note Minimum value is 1. */ - uint8_t queue_size; /*!< Queue size for task for the encoder processing. @note Minimum value is 1. */ - uint8_t a_gpio_number; /*!< Encoder A GPIO number. */ - uint8_t b_gpio_number; /*!< Encoder B GPIO number. */ - uint8_t s_gpio_number; /*!< Encoder button GPIO number. */ - uint8_t encoder_number; /*!< Unique encoder number. @note Must be greater than 0. */ - uint16_t stack_size; /*!< Stack size for task for the encoder isr processing processing. @note The minimum size is configMINIMAL_STACK_SIZE. */ + double encoder_step; /*!< Encoder step. @note Must be greater than 0. */ + double encoder_min_value; /*!< Encoder min value. @note Must be less than encoder_max_value. */ + double encoder_max_value; /*!< Encoder max value. @note Must be greater than encoder_min_value. */ + uint8_t task_priority; /*!< Task priority for the encoder isr processing. @note Minimum value is 1. */ + uint8_t queue_size; /*!< Queue size for task for the encoder processing. @note Minimum value is 1. */ + uint8_t a_gpio_number; /*!< Encoder A GPIO number. */ + uint8_t b_gpio_number; /*!< Encoder B GPIO number. */ + uint8_t s_gpio_number; /*!< Encoder button GPIO number. */ + uint16_t s_gpio_debounce_time; /*!< Encoder button debounce_time. @note In microseconds. */ + uint8_t encoder_number; /*!< Unique encoder number. @note Must be greater than 0. */ + uint16_t stack_size; /*!< Stack size for task for the encoder isr processing processing. @note The minimum size is configMINIMAL_STACK_SIZE. */ } zh_encoder_init_config_t; /** @@ -56,17 +59,19 @@ extern "C" */ typedef struct // -V802 { - double encoder_step; /*!< Encoder step. */ - double encoder_position; /*!< Encoder position. */ - double encoder_min_value; /*!< Encoder min value. */ - double encoder_max_value; /*!< Encoder max value. */ - uint8_t a_gpio_number; /*!< Encoder A GPIO number. */ - uint8_t b_gpio_number; /*!< Encoder B GPIO number. */ - uint8_t s_gpio_number; /*!< Encoder button GPIO number. */ - uint8_t encoder_number; /*!< Encoder unique number. */ - uint8_t encoder_state; /*!< Encoder internal state. */ - bool button_status; /*!< Encoder button status. */ - bool is_initialized; /*!< Encoder initialization flag. */ + pcnt_unit_handle_t pcnt_unit_handle; /*!< Encoder unique pcnt unit handle. */ + pcnt_channel_handle_t pcnt_channel_a_handle; /*!< Encoder unique pcnt channel handle. */ + pcnt_channel_handle_t pcnt_channel_b_handle; /*!< Encoder unique pcnt channel handle. */ + double encoder_step; /*!< Encoder step. */ + double encoder_position; /*!< Encoder position. */ + double encoder_min_value; /*!< Encoder min value. */ + double encoder_max_value; /*!< Encoder max value. */ + uint8_t encoder_number; /*!< Encoder unique number. */ + uint8_t s_gpio_number; /*!< Encoder button GPIO number. */ + uint16_t s_gpio_debounce_time; /*!< Encoder button debounce_time. */ + uint64_t s_gpio_prev_time; /*!< Encoder button prev interrupt time. */ + bool s_gpio_status; /*!< Encoder button status. */ + bool is_initialized; /*!< Encoder initialization flag. */ } zh_encoder_handle_t; /** @@ -79,20 +84,39 @@ extern "C" uint32_t min_stack_size; /*!< Minimum free stack size. */ } zh_encoder_stats_t; + /** + * @brief Enumeration of encoder event ID. + */ + typedef enum + { + ZH_BUTTON_EVENT, /*!< Button event. */ + ZH_ENCODER_EVENT /*!< Encoder event. */ + } zh_encoder_event_id_t; + ESP_EVENT_DECLARE_BASE(ZH_ENCODER); /** - * @brief Structure for sending data to the event handler when cause an interrupt. + * @brief Structure for sending data to the event handler when cause encoder interrupt. * - * @note Should be used with ZH_ENCODER event base. + * @note Should be used with ZH_ENCODER event base and ZH_ENCODER_EVENT event ID. */ typedef struct { double encoder_position; /*!< Encoder current position. */ uint8_t encoder_number; /*!< Encoder unique number. */ - bool button_status; /*!< Encoder button status. */ } zh_encoder_event_on_isr_t; + /** + * @brief Structure for sending data to the event handler when cause encoder button interrupt. + * + * @note Should be used with ZH_ENCODER event base and ZH_BUTTON_EVENT event ID. + */ + typedef struct + { + uint8_t encoder_number; /*!< Encoder unique number. */ + bool button_status; /*!< Encoder button status. */ + } zh_encoder_button_event_on_isr_t; + /** * @brief Initialize encoder. * diff --git a/version.txt b/version.txt index 3e1ad72..359a5b9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.5.0 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/zh_encoder.c b/zh_encoder.c index ddfd2bc..667aa5d 100644 --- a/zh_encoder.c +++ b/zh_encoder.c @@ -13,83 +13,46 @@ return err; \ } -#define ZH_ENCODER_DIRECTION_CW 0x10 -#define ZH_ENCODER_DIRECTION_CCW 0x20 - -#define BUTTON_DEBOUNCE_TIME 20 - -static const uint8_t _encoder_matrix[7][4] = { - {0x03, 0x02, 0x01, 0x00}, - {0x23, 0x00, 0x01, 0x00}, - {0x13, 0x02, 0x00, 0x00}, - {0x03, 0x05, 0x04, 0x00}, - {0x03, 0x03, 0x04, 0x00}, - {0x03, 0x05, 0x03, 0x00}, -}; +#define ZH_ENCODER_DIRECTION_CW 1 +#define ZH_ENCODER_DIRECTION_CCW -1 TaskHandle_t zh_encoder = NULL; static QueueHandle_t _queue_handle = NULL; static portMUX_TYPE _spinlock = portMUX_INITIALIZER_UNLOCKED; -static volatile uint64_t _prev_us = 0; static uint8_t _encoder_counter = 0; -static bool _is_prev_gpio_isr_handler = false; static zh_encoder_stats_t _stats = {0}; -static uint8_t _encoder_number_matrix[10] = {0}; +static uint8_t _encoder_number_matrix[CONFIG_SOC_PCNT_UNITS_PER_GROUP] = {0}; -static esp_err_t _zh_encoder_validate_config(const zh_encoder_init_config_t *config); +static esp_err_t _zh_encoder_validate_config(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle); +static esp_err_t _zh_encoder_pcnt_init(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle); static esp_err_t _zh_encoder_gpio_init(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle); static esp_err_t _zh_encoder_resources_init(const zh_encoder_init_config_t *config); static esp_err_t _zh_encoder_task_init(const zh_encoder_init_config_t *config); -static void _zh_encoder_isr_handler(void *arg); +static bool _zh_encoder_isr_handler(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx); static void _zh_encoder_isr_processing_task(void *pvParameter); +static void _zh_encoder_button_isr_handler(void *arg); ESP_EVENT_DEFINE_BASE(ZH_ENCODER); esp_err_t zh_encoder_init(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle) // -V2008 { ZH_LOGI("Encoder initialization started."); - ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Encoder initialization failed. Invalid argument."); - esp_err_t err = _zh_encoder_validate_config(config); + ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Encoder initialization failed. Invalid argument."); + ZH_ERROR_CHECK(_encoder_counter < sizeof(_encoder_number_matrix), ESP_ERR_INVALID_ARG, NULL, "Encoder initialization failed. Maximum quantity reached."); + esp_err_t err = _zh_encoder_validate_config(config, handle); ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Encoder initialization failed. Initial configuration check failed."); - err = _zh_encoder_gpio_init(config, handle); - ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Encoder initialization failed. GPIO initialization failed."); err = _zh_encoder_resources_init(config); - if (_is_prev_gpio_isr_handler == true) - { - ZH_ERROR_CHECK(err == ESP_OK, err, gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); gpio_isr_handler_remove((gpio_num_t)config->b_gpio_number); - gpio_isr_handler_remove((gpio_num_t)config->s_gpio_number); gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number); - gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Encoder initialization failed. Resources initialization failed."); - } - else - { - ZH_ERROR_CHECK(err == ESP_OK, err, gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); gpio_isr_handler_remove((gpio_num_t)config->b_gpio_number); - gpio_isr_handler_remove((gpio_num_t)config->s_gpio_number); gpio_uninstall_isr_service(); gpio_reset_pin((gpio_num_t)config->a_gpio_number); - gpio_reset_pin((gpio_num_t)config->b_gpio_number); gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Encoder initialization failed. Resources initialization failed."); - } + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Encoder initialization failed. Resources initialization failed."); err = _zh_encoder_task_init(config); - if (_is_prev_gpio_isr_handler == true) - { - ZH_ERROR_CHECK(err == ESP_OK, err, vQueueDelete(_queue_handle); _queue_handle = NULL; gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); - gpio_isr_handler_remove((gpio_num_t)config->b_gpio_number); gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number), - "Encoder initialization failed. Processing task initialization failed."); - } - else - { - ZH_ERROR_CHECK(err == ESP_OK, err, vQueueDelete(_queue_handle); _queue_handle = NULL; gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); - gpio_isr_handler_remove((gpio_num_t)config->b_gpio_number); gpio_isr_handler_remove((gpio_num_t)config->s_gpio_number); gpio_uninstall_isr_service(); - gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number); gpio_reset_pin((gpio_num_t)config->s_gpio_number), - "Encoder initialization failed. Processing task initialization failed."); - } - handle->a_gpio_number = config->a_gpio_number; - handle->b_gpio_number = config->b_gpio_number; - handle->s_gpio_number = config->s_gpio_number; - handle->button_status = gpio_get_level((gpio_num_t)config->s_gpio_number); - handle->encoder_number = config->encoder_number; - handle->encoder_min_value = config->encoder_min_value; - handle->encoder_max_value = config->encoder_max_value; - handle->encoder_step = config->encoder_step; - handle->encoder_position = (handle->encoder_min_value + handle->encoder_max_value) / 2; + ZH_ERROR_CHECK(err == ESP_OK, err, vQueueDelete(_queue_handle); _queue_handle = NULL, "Encoder initialization failed. Processing task initialization failed."); + err = _zh_encoder_pcnt_init(config, handle); + ZH_ERROR_CHECK(err == ESP_OK, err, vQueueDelete(_queue_handle); _queue_handle = NULL; vTaskDelete(zh_encoder); zh_encoder = NULL, + "Encoder initialization failed. PCNT initialization failed."); + err = _zh_encoder_gpio_init(config, handle); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_unit_stop(handle->pcnt_unit_handle); pcnt_unit_disable(handle->pcnt_unit_handle); pcnt_del_channel(handle->pcnt_channel_a_handle); + pcnt_del_channel(handle->pcnt_channel_b_handle); pcnt_del_unit(handle->pcnt_unit_handle); vQueueDelete(_queue_handle); _queue_handle = NULL; + vTaskDelete(zh_encoder); zh_encoder = NULL, "Encoder initialization failed. GPIO initialization failed."); handle->is_initialized = true; ++_encoder_counter; for (uint8_t i = 0; i < sizeof(_encoder_number_matrix); ++i) @@ -109,20 +72,22 @@ esp_err_t zh_encoder_deinit(zh_encoder_handle_t *handle) ZH_LOGI("Encoder deinitialization started."); ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Encoder deinitialization failed. Invalid argument."); ZH_ERROR_CHECK(handle->is_initialized == true, ESP_FAIL, NULL, "Encoder deinitialization failed. Encoder not initialized."); - gpio_isr_handler_remove((gpio_num_t)handle->a_gpio_number); - gpio_isr_handler_remove((gpio_num_t)handle->b_gpio_number); - gpio_isr_handler_remove((gpio_num_t)handle->s_gpio_number); - gpio_reset_pin((gpio_num_t)handle->a_gpio_number); - gpio_reset_pin((gpio_num_t)handle->b_gpio_number); - gpio_reset_pin((gpio_num_t)handle->s_gpio_number); + pcnt_unit_stop(handle->pcnt_unit_handle); + pcnt_unit_disable(handle->pcnt_unit_handle); + pcnt_del_channel(handle->pcnt_channel_a_handle); + pcnt_del_channel(handle->pcnt_channel_b_handle); + pcnt_del_unit(handle->pcnt_unit_handle); + if (handle->s_gpio_number != GPIO_NUM_MAX) + { + gpio_isr_handler_remove((gpio_num_t)handle->s_gpio_number); + gpio_reset_pin((gpio_num_t)handle->s_gpio_number); + } if (_encoder_counter == 1) { vQueueDelete(_queue_handle); + _queue_handle = NULL; vTaskDelete(zh_encoder); - if (_is_prev_gpio_isr_handler == false) - { - gpio_uninstall_isr_service(); - } + zh_encoder = NULL; } handle->is_initialized = false; --_encoder_counter; @@ -171,7 +136,7 @@ esp_err_t zh_encoder_set(zh_encoder_handle_t *handle, double position) esp_err_t zh_encoder_get(const zh_encoder_handle_t *handle, double *position) { ZH_LOGI("Encoder get position started."); - ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Encoder get position failed. Invalid argument."); + ZH_ERROR_CHECK(handle != NULL && position != NULL, ESP_ERR_INVALID_ARG, NULL, "Encoder get position failed. Invalid argument."); ZH_ERROR_CHECK(handle->is_initialized == true, ESP_FAIL, NULL, "Encoder get position failed. Encoder not initialized."); *position = handle->encoder_position; ZH_LOGI("Encoder get position completed successfully."); @@ -204,9 +169,9 @@ void zh_encoder_reset_stats(void) ZH_LOGI("Error statistic reset successfully."); } -static esp_err_t _zh_encoder_validate_config(const zh_encoder_init_config_t *config) // -V2008 +static esp_err_t _zh_encoder_validate_config(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle) // -V2008 { - ZH_ERROR_CHECK(config != NULL, ESP_ERR_INVALID_ARG, NULL, "Invalid configuration."); + ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Invalid argument."); ZH_ERROR_CHECK(config->task_priority >= 1 && config->stack_size >= configMINIMAL_STACK_SIZE, ESP_ERR_INVALID_ARG, NULL, "Invalid task settings."); ZH_ERROR_CHECK(config->queue_size >= 1, ESP_ERR_INVALID_ARG, NULL, "Invalid queue size."); ZH_ERROR_CHECK(config->encoder_max_value > config->encoder_min_value, ESP_ERR_INVALID_ARG, NULL, "Invalid encoder min/max value."); @@ -216,65 +181,98 @@ static esp_err_t _zh_encoder_validate_config(const zh_encoder_init_config_t *con { ZH_ERROR_CHECK(config->encoder_number != _encoder_number_matrix[i], ESP_ERR_INVALID_ARG, NULL, "Encoder number already present."); } + handle->encoder_number = config->encoder_number; + handle->encoder_min_value = config->encoder_min_value; + handle->encoder_max_value = config->encoder_max_value; + handle->encoder_step = config->encoder_step; + handle->encoder_position = (handle->encoder_min_value + handle->encoder_max_value) / 2; + return ESP_OK; +} + +static esp_err_t _zh_encoder_pcnt_init(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle) // -V2008 +{ + ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Invalid argument."); + ZH_ERROR_CHECK(config->a_gpio_number < GPIO_NUM_MAX && config->b_gpio_number < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, NULL, "Invalid GPIO number.") + ZH_ERROR_CHECK(config->a_gpio_number != config->b_gpio_number, ESP_ERR_INVALID_ARG, NULL, "Encoder A and B GPIO is same.") + pcnt_unit_config_t pcnt_unit_config = { + .high_limit = 10, + .low_limit = -10, + }; + pcnt_unit_handle_t pcnt_unit_handle = NULL; + esp_err_t err = pcnt_new_unit(&pcnt_unit_config, &pcnt_unit_handle); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "PCNT initialization failed."); + pcnt_glitch_filter_config_t pcnt_glitch_filter_config = { + .max_glitch_ns = 1000, + }; + err = pcnt_unit_set_glitch_filter(pcnt_unit_handle, &pcnt_glitch_filter_config); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + pcnt_chan_config_t pcnt_chan_a_config = { + .edge_gpio_num = config->a_gpio_number, + .level_gpio_num = config->b_gpio_number, + }; + pcnt_channel_handle_t pcnt_channel_a_handle = NULL; + err = pcnt_new_channel(pcnt_unit_handle, &pcnt_chan_a_config, &pcnt_channel_a_handle); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + pcnt_chan_config_t pcnt_chan_b_config = { + .edge_gpio_num = config->b_gpio_number, + .level_gpio_num = config->a_gpio_number, + }; + pcnt_channel_handle_t pcnt_channel_b_handle = NULL; + err = pcnt_new_channel(pcnt_unit_handle, &pcnt_chan_b_config, &pcnt_channel_b_handle); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + err = pcnt_channel_set_edge_action(pcnt_channel_a_handle, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + err = pcnt_channel_set_level_action(pcnt_channel_a_handle, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_HOLD); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + err = pcnt_channel_set_edge_action(pcnt_channel_b_handle, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + err = pcnt_channel_set_level_action(pcnt_channel_b_handle, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_HOLD); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + err = pcnt_unit_add_watch_point(pcnt_unit_handle, ZH_ENCODER_DIRECTION_CW); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + err = pcnt_unit_add_watch_point(pcnt_unit_handle, ZH_ENCODER_DIRECTION_CCW); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + pcnt_event_callbacks_t cbs = { + .on_reach = _zh_encoder_isr_handler, + }; + err = pcnt_unit_register_event_callbacks(pcnt_unit_handle, &cbs, handle); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); pcnt_del_unit(pcnt_unit_handle), + "PCNT initialization failed."); + err = pcnt_unit_enable(pcnt_unit_handle); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); pcnt_del_unit(pcnt_unit_handle), + "PCNT initialization failed."); + err = pcnt_unit_clear_count(pcnt_unit_handle); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_unit_disable(pcnt_unit_handle); pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); + pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + err = pcnt_unit_start(pcnt_unit_handle); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_unit_disable(pcnt_unit_handle); pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); + pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + handle->pcnt_unit_handle = pcnt_unit_handle; + handle->pcnt_channel_a_handle = pcnt_channel_a_handle; + handle->pcnt_channel_b_handle = pcnt_channel_b_handle; return ESP_OK; } static esp_err_t _zh_encoder_gpio_init(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle) // -V2008 { - ZH_ERROR_CHECK(config->a_gpio_number < GPIO_NUM_MAX && config->b_gpio_number < GPIO_NUM_MAX && config->s_gpio_number < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, NULL, "Invalid GPIO number.") - ZH_ERROR_CHECK(config->a_gpio_number != config->b_gpio_number, ESP_ERR_INVALID_ARG, NULL, "Encoder A and B GPIO is same.") + ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Invalid argument."); + ZH_ERROR_CHECK(config->s_gpio_number <= GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, NULL, "Invalid GPIO number.") ZH_ERROR_CHECK(config->a_gpio_number != config->s_gpio_number && config->b_gpio_number != config->s_gpio_number, ESP_ERR_INVALID_ARG, NULL, "Encoder GPIO and button GPIO is same.") - gpio_config_t pin_config = { - .mode = GPIO_MODE_INPUT, - .pin_bit_mask = (1ULL << config->a_gpio_number) | (1ULL << config->b_gpio_number) | (1ULL << config->s_gpio_number), - .pull_up_en = GPIO_PULLUP_ENABLE, - .intr_type = GPIO_INTR_ANYEDGE}; - esp_err_t err = gpio_config(&pin_config); - ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "GPIO initialization failed."); - if (_encoder_counter == 0) + if (config->s_gpio_number != GPIO_NUM_MAX) { + gpio_config_t pin_config = { + .mode = GPIO_MODE_INPUT, + .pin_bit_mask = (1ULL << config->s_gpio_number), + .intr_type = GPIO_INTR_ANYEDGE}; + esp_err_t err = gpio_config(&pin_config); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "GPIO initialization failed."); err = gpio_install_isr_service(ESP_INTR_FLAG_LOWMED); - ZH_ERROR_CHECK(err == ESP_OK || err == ESP_ERR_INVALID_STATE, err, gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number); - gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Failed install isr service."); - if (err == ESP_ERR_INVALID_STATE) - { - _is_prev_gpio_isr_handler = true; - } - } - err = gpio_isr_handler_add((gpio_num_t)config->a_gpio_number, _zh_encoder_isr_handler, handle); - if (_is_prev_gpio_isr_handler == true) - { - ZH_ERROR_CHECK(err == ESP_OK, err, gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number); - gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Interrupt initialization failed."); - } - else - { - ZH_ERROR_CHECK(err == ESP_OK, err, gpio_uninstall_isr_service(); gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number); - gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Interrupt initialization failed."); - } - err = gpio_isr_handler_add((gpio_num_t)config->b_gpio_number, _zh_encoder_isr_handler, handle); - if (_is_prev_gpio_isr_handler == true) - { - ZH_ERROR_CHECK(err == ESP_OK, err, gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->a_gpio_number); - gpio_reset_pin((gpio_num_t)config->b_gpio_number); gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Interrupt initialization failed."); - } - else - { - ZH_ERROR_CHECK(err == ESP_OK, err, gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); gpio_uninstall_isr_service(); gpio_reset_pin((gpio_num_t)config->a_gpio_number); - gpio_reset_pin((gpio_num_t)config->b_gpio_number); gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Interrupt initialization failed."); - } - err = gpio_isr_handler_add((gpio_num_t)config->s_gpio_number, _zh_encoder_isr_handler, handle); - if (_is_prev_gpio_isr_handler == true) - { - ZH_ERROR_CHECK(err == ESP_OK, err, gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); gpio_isr_handler_remove((gpio_num_t)config->b_gpio_number); - gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number); gpio_reset_pin((gpio_num_t)config->s_gpio_number), - "Interrupt initialization failed."); - } - else - { - ZH_ERROR_CHECK(err == ESP_OK, err, gpio_isr_handler_remove((gpio_num_t)config->a_gpio_number); gpio_isr_handler_remove((gpio_num_t)config->b_gpio_number); - gpio_uninstall_isr_service(); gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number); - gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Interrupt initialization failed."); + ZH_ERROR_CHECK(err == ESP_OK || err == ESP_ERR_INVALID_STATE, err, gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Failed install isr service."); + err = gpio_isr_handler_add((gpio_num_t)config->s_gpio_number, _zh_encoder_button_isr_handler, handle); + ZH_ERROR_CHECK(err == ESP_OK, err, gpio_reset_pin((gpio_num_t)config->s_gpio_number), "Interrupt initialization failed."); + handle->s_gpio_number = config->s_gpio_number; + handle->s_gpio_debounce_time = config->s_gpio_debounce_time; + handle->s_gpio_status = gpio_get_level((gpio_num_t)config->s_gpio_number); } return ESP_OK; } @@ -283,7 +281,7 @@ static esp_err_t _zh_encoder_resources_init(const zh_encoder_init_config_t *conf { if (_encoder_counter == 0) { - _queue_handle = xQueueCreate(config->queue_size, sizeof(zh_encoder_handle_t)); + _queue_handle = xQueueCreate(config->queue_size, sizeof(zh_encoder_event_on_isr_t)); ZH_ERROR_CHECK(_queue_handle != NULL, ESP_FAIL, NULL, "Failed to create queue."); } return ESP_OK; @@ -293,31 +291,19 @@ static esp_err_t _zh_encoder_task_init(const zh_encoder_init_config_t *config) { if (_encoder_counter == 0) { - BaseType_t err = xTaskCreatePinnedToCore(&_zh_encoder_isr_processing_task, "zh_encoder_isr_processing", config->stack_size, NULL, config->task_priority, &zh_encoder, tskNO_AFFINITY); + BaseType_t err = xTaskCreatePinnedToCore(&_zh_encoder_isr_processing_task, "zh_encoder_isr_processing", config->stack_size, NULL, config->task_priority, + &zh_encoder, tskNO_AFFINITY); ZH_ERROR_CHECK(err == pdPASS, ESP_FAIL, NULL, "Failed to create isr processing task."); } return ESP_OK; } -static void IRAM_ATTR _zh_encoder_isr_handler(void *arg) +static bool IRAM_ATTR _zh_encoder_isr_handler(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx) { - zh_encoder_handle_t *encoder_handle = (zh_encoder_handle_t *)arg; BaseType_t xHigherPriorityTaskWoken = pdFALSE; - uint64_t _current_us = esp_timer_get_time(); - bool button_flag = false; - if (_current_us - _prev_us >= BUTTON_DEBOUNCE_TIME) - { - bool button_status = gpio_get_level((gpio_num_t)encoder_handle->s_gpio_number); - if (encoder_handle->button_status != button_status) - { - button_flag = true; - encoder_handle->button_status = button_status; - } - } - _prev_us = _current_us; - encoder_handle->encoder_state = _encoder_matrix[encoder_handle->encoder_state & 0x0F] - [(gpio_get_level((gpio_num_t)encoder_handle->b_gpio_number) << 1) | gpio_get_level((gpio_num_t)encoder_handle->a_gpio_number)]; - switch (encoder_handle->encoder_state & 0x30) + zh_encoder_handle_t *encoder_handle = (zh_encoder_handle_t *)user_ctx; + pcnt_unit_clear_count(unit); + switch (edata->watch_point_value) { case ZH_ENCODER_DIRECTION_CW: if (encoder_handle->encoder_position < encoder_handle->encoder_max_value) @@ -327,10 +313,6 @@ static void IRAM_ATTR _zh_encoder_isr_handler(void *arg) { encoder_handle->encoder_position = encoder_handle->encoder_max_value; } - if (xQueueSendFromISR(_queue_handle, encoder_handle, &xHigherPriorityTaskWoken) != pdTRUE) - { - ++_stats.queue_overflow_error; - } } break; case ZH_ENCODER_DIRECTION_CCW: @@ -341,38 +323,32 @@ static void IRAM_ATTR _zh_encoder_isr_handler(void *arg) { encoder_handle->encoder_position = encoder_handle->encoder_min_value; } - if (xQueueSendFromISR(_queue_handle, encoder_handle, &xHigherPriorityTaskWoken) != pdTRUE) - { - ++_stats.queue_overflow_error; - } } break; default: - if (button_flag == true) - { - if (xQueueSendFromISR(_queue_handle, encoder_handle, &xHigherPriorityTaskWoken) != pdTRUE) - { - ++_stats.queue_overflow_error; - } - } + return false; break; } + zh_encoder_event_on_isr_t encoder_data = {0}; + encoder_data.encoder_number = encoder_handle->encoder_number; + encoder_data.encoder_position = encoder_handle->encoder_position; + if (xQueueSendFromISR(_queue_handle, &encoder_data, &xHigherPriorityTaskWoken) != pdTRUE) + { + ++_stats.queue_overflow_error; + } if (xHigherPriorityTaskWoken == pdTRUE) { - portYIELD_FROM_ISR(); + return true; }; + return false; } static void IRAM_ATTR _zh_encoder_isr_processing_task(void *pvParameter) { - zh_encoder_handle_t queue = {0}; zh_encoder_event_on_isr_t encoder_data = {0}; - while (xQueueReceive(_queue_handle, &queue, portMAX_DELAY) == pdTRUE) + while (xQueueReceive(_queue_handle, &encoder_data, portMAX_DELAY) == pdTRUE) { - encoder_data.encoder_number = queue.encoder_number; - encoder_data.encoder_position = queue.encoder_position; - encoder_data.button_status = queue.button_status; - esp_err_t err = esp_event_post(ZH_ENCODER, 0, &encoder_data, sizeof(zh_encoder_event_on_isr_t), 1000 / portTICK_PERIOD_MS); + esp_err_t err = esp_event_post(ZH_ENCODER, ZH_ENCODER_EVENT, &encoder_data, sizeof(zh_encoder_event_on_isr_t), 1000 / portTICK_PERIOD_MS); if (err != ESP_OK) { ++_stats.event_post_error; @@ -381,4 +357,32 @@ static void IRAM_ATTR _zh_encoder_isr_processing_task(void *pvParameter) _stats.min_stack_size = (uint32_t)uxTaskGetStackHighWaterMark(NULL); } vTaskDelete(NULL); +} + +static void IRAM_ATTR _zh_encoder_button_isr_handler(void *arg) +{ + zh_encoder_handle_t *encoder_handle = (zh_encoder_handle_t *)arg; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + uint64_t _current_us = esp_timer_get_time(); + if (_current_us - encoder_handle->s_gpio_prev_time >= encoder_handle->s_gpio_debounce_time) + { + bool s_gpio_status = gpio_get_level((gpio_num_t)encoder_handle->s_gpio_number); + if (encoder_handle->s_gpio_status != s_gpio_status) + { + encoder_handle->s_gpio_status = s_gpio_status; + zh_encoder_button_event_on_isr_t encoder_data = {0}; + encoder_data.encoder_number = encoder_handle->encoder_number; + encoder_data.button_status = encoder_handle->s_gpio_status; + esp_err_t err = esp_event_isr_post(ZH_ENCODER, ZH_BUTTON_EVENT, &encoder_data, sizeof(zh_encoder_button_event_on_isr_t), &xHigherPriorityTaskWoken); + if (err != ESP_OK) + { + ++_stats.event_post_error; + } + } + } + encoder_handle->s_gpio_prev_time = _current_us; + if (xHigherPriorityTaskWoken == pdTRUE) + { + portYIELD_FROM_ISR(); + }; } \ No newline at end of file