diff --git a/CMakeLists.txt b/CMakeLists.txt index 55597ed..0fbc965 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1 @@ -if(${IDF_TARGET} STREQUAL esp8266) - set(requires driver) -else() - set(requires driver esp_event) -endif() -idf_component_register(SRCS "zh_encoder.c" INCLUDE_DIRS "include" REQUIRES ${requires}) \ No newline at end of file +idf_component_register(SRCS "zh_encoder.c" INCLUDE_DIRS "include" REQUIRES esp_event driver esp_timer) \ No newline at end of file diff --git a/README.md b/README.md index b671e13..186cddd 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ 1. Support some encoders on one device. +## Attention + +1. If the button is not used, specify any free GPIO in the initial configuration. + ## Using In an existing project, run the following command to install the components: @@ -30,8 +34,12 @@ One encoder on device: ```c #include "zh_encoder.h" +#define ENCODER_NUMBER 0x01 + 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) @@ -42,19 +50,17 @@ void app_main(void) 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 = 3; + encoder_init_config.queue_size = 5; encoder_init_config.a_gpio_number = GPIO_NUM_27; encoder_init_config.b_gpio_number = GPIO_NUM_26; - encoder_init_config.encoder_min_value = -10; - encoder_init_config.encoder_max_value = 20; + encoder_init_config.s_gpio_number = GPIO_NUM_17; + encoder_init_config.encoder_min_value = 0; + encoder_init_config.encoder_max_value = 100; encoder_init_config.encoder_step = 0.1; - encoder_init_config.encoder_number = 1; + encoder_init_config.encoder_number = ENCODER_NUMBER; zh_encoder_init(&encoder_init_config, &encoder_handle); - double position = 0; - zh_encoder_get(&encoder_handle, &position); - printf("Encoder position %0.2f.\n", position); - zh_encoder_set(&encoder_handle, 5); - zh_encoder_reset(&encoder_handle); + zh_encoder_get(&encoder_handle, &encoder_position); + printf("Encoder position %0.2f.\n", encoder_position); for (;;) { const zh_encoder_stats_t *stats = zh_encoder_get_stats(); @@ -68,6 +74,21 @@ 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; - printf("Encoder number %d position %0.2f.\n", event->encoder_number, event->encoder_position); + switch (event->encoder_number) + { + 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"); + } + break; + default: + break; + } } ``` diff --git a/include/zh_encoder.h b/include/zh_encoder.h index 605b133..320463e 100644 --- a/include/zh_encoder.h +++ b/include/zh_encoder.h @@ -6,6 +6,7 @@ #include "esp_log.h" #include "driver/gpio.h" +#include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_event.h" @@ -20,6 +21,7 @@ .queue_size = 1, \ .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, \ .encoder_step = 1, \ @@ -42,6 +44,7 @@ extern "C" 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. */ int32_t encoder_min_value; /*!< Encoder min value. @note Must be less than encoder_max_value. */ int32_t encoder_max_value; /*!< Encoder max value. @note Must be greater than encoder_min_value. */ double encoder_step; /*!< Encoder step. @note Must be greater than 0. */ @@ -55,10 +58,12 @@ extern "C" { 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. */ int32_t encoder_min_value; /*!< Encoder min value. */ int32_t encoder_max_value; /*!< Encoder max value. */ double encoder_step; /*!< Encoder step. */ double encoder_position; /*!< Encoder position. */ + bool button_status; /*!< Encoder button status. */ uint8_t encoder_number; /*!< Encoder unique number. */ uint8_t encoder_state; /*!< Encoder internal state. */ bool is_initialized; /*!< Encoder initialization flag. */ @@ -85,6 +90,7 @@ extern "C" { uint8_t encoder_number; /*!< Encoder unique number. */ double encoder_position; /*!< Encoder current position. */ + bool button_status; /*!< Encoder button status. */ } zh_encoder_event_on_isr_t; /** diff --git a/version.txt b/version.txt index 867e524..589268e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.0 \ No newline at end of file +1.3.0 \ No newline at end of file diff --git a/zh_encoder.c b/zh_encoder.c index db61385..fdd81ea 100644 --- a/zh_encoder.c +++ b/zh_encoder.c @@ -16,6 +16,8 @@ #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}, @@ -27,6 +29,8 @@ static const uint8_t _encoder_matrix[7][4] = { TaskHandle_t zh_encoder = NULL; static QueueHandle_t _queue_handle = NULL; + +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}; @@ -51,13 +55,29 @@ esp_err_t zh_encoder_init(const zh_encoder_init_config_t *config, zh_encoder_han 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_reset_pin((gpio_num_t)config->a_gpio_number); - gpio_reset_pin((gpio_num_t)config->b_gpio_number), "Encoder initialization failed. Resources initialization failed."); + if (config->s_gpio_number != GPIO_NUM_MAX) + { + 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_reset_pin((gpio_num_t)config->a_gpio_number); + gpio_reset_pin((gpio_num_t)config->b_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_uninstall_isr_service(); - gpio_reset_pin((gpio_num_t)config->a_gpio_number); gpio_reset_pin((gpio_num_t)config->b_gpio_number), "Encoder initialization failed. Resources initialization failed."); + if (config->s_gpio_number != GPIO_NUM_MAX) + { + 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."); + } + 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), "Encoder initialization failed. Resources initialization failed."); + } } err = _zh_encoder_task_init(config); if (_is_prev_gpio_isr_handler == true) @@ -72,6 +92,8 @@ esp_err_t zh_encoder_init(const zh_encoder_init_config_t *config, zh_encoder_han } 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; @@ -90,8 +112,10 @@ esp_err_t zh_encoder_deinit(zh_encoder_handle_t *handle) 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); if (_encoder_counter == 1) { vQueueDelete(_queue_handle); @@ -164,11 +188,12 @@ static esp_err_t _zh_encoder_validate_config(const zh_encoder_init_config_t *con static esp_err_t _zh_encoder_gpio_init(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle) { - 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, "Both GPIO is same.") + 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->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), + .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); @@ -193,6 +218,17 @@ static esp_err_t _zh_encoder_gpio_init(const zh_encoder_init_config_t *config, z { 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), "Interrupt initialization failed."); } + err = gpio_isr_handler_add(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), "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), "Interrupt initialization failed."); + } return ESP_OK; } @@ -220,7 +256,19 @@ static void IRAM_ATTR _zh_encoder_isr_handler(void *arg) { zh_encoder_handle_t *encoder_handle = (zh_encoder_handle_t *)arg; BaseType_t xHigherPriorityTaskWoken = pdFALSE; - encoder_handle->encoder_state = _encoder_matrix[encoder_handle->encoder_state & 0x0F][(gpio_get_level(encoder_handle->b_gpio_number) << 1) | gpio_get_level(encoder_handle->a_gpio_number)]; + 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) { case ZH_ENCODER_DIRECTION_CW: @@ -252,6 +300,13 @@ static void IRAM_ATTR _zh_encoder_isr_handler(void *arg) } break; default: + if (button_flag == true) + { + if (xQueueSendFromISR(_queue_handle, encoder_handle, &xHigherPriorityTaskWoken) != pdTRUE) + { + ++_stats.queue_overflow_error; + } + } break; } if (xHigherPriorityTaskWoken == pdTRUE) @@ -268,6 +323,7 @@ static void IRAM_ATTR _zh_encoder_isr_processing_task(void *pvParameter) { 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); if (err != ESP_OK) {