diff --git a/README.md b/README.md index 46271d5..a388700 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,20 @@ -# ESP32 ESP-IDF component for rotary encoder using PCNT peripheral +# ESP32 ESP-IDF component for rotary encoder (PCNT peripheral) ## Features -1. Quadrature decoding via PCNT peripheral. -2. Configurable min/max/step values with float precision. -3. Push-button support with debouncing. -4. Multiple simultaneous encoders (up to 8). -5. Event-based notification via esp_event (ISR context). -6. Error statistics tracking for diagnostics. +1. Quadrature decoding through the PCNT peripheral hardware. +2. Configurable value range and step with float precision. +3. Push-button support with software debouncing timer. +4. Multiple encoders sharing a single processing task (limited by PCNT units and GPIOs). +5. Asynchronous rotation and button notifications through esp_event. +6. Built-in error statistics with queue overflow and stack watermark counters. -## Note +## Attention -Enable the following settings in menuconfig: - -```text -GPIO_CTRL_FUNC_IN_IRAM -PCNT_CTRL_FUNC_IN_IRAM -PCNT_ISR_IRAM_SAFE -``` +1. Encoder number must be unique across all initialized encoders and greater than zero. +2. Task priority, stack size and queue size of the first initialized encoder are used by the shared processing task. +3. Position is initialized to the midpoint of the configured range. +4. Position is clamped to the configured minimum and maximum values. ## Using @@ -34,62 +31,236 @@ In the application, add the component: #include "zh_encoder.h" ``` -## Example +## Examples + +### Basic Example: Init and Get Position ```c #include "zh_encoder.h" -#define ENCODER_NUMBER 0x01 - zh_encoder_handle_t *encoder_handle = NULL; -void zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); - 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_4; config.b_gpio_number = GPIO_NUM_16; - config.s_gpio_number = GPIO_NUM_15; config.encoder_min_value = -10; config.encoder_max_value = 10; - config.encoder_step = 0.001; - config.encoder_number = ENCODER_NUMBER; - zh_encoder_init(&config, &encoder_handle); - float encoder_position = 0; - zh_encoder_get(&encoder_handle, &encoder_position); - printf("Encoder position %0.3f.\n", encoder_position); - for (;;) + config.encoder_step = 1; + config.encoder_number = 1; + esp_err_t ret = zh_encoder_init(&config, &encoder_handle); + if (ret != ESP_OK) { - const zh_encoder_stats_t *stats = zh_encoder_get_stats(); - printf("Number of event post error: %ld.\n", stats->event_post_error); - printf("Number of queue overflow error: %ld.\n", stats->queue_overflow_error); - printf("Minimum free stack size: %ld.\n", stats->min_stack_size); - vTaskDelay(60000 / portTICK_PERIOD_MS); + printf("Encoder initialization error: %s\n", esp_err_to_name(ret)); + return; + } + float position = 0; + ret = zh_encoder_get(&encoder_handle, &position); + if (ret == ESP_OK) + { + printf("Encoder position: %0.3f\n", position); + } + else + { + printf("Error getting position: %s\n", esp_err_to_name(ret)); + } + ret = zh_encoder_deinit(&encoder_handle); + if (ret != ESP_OK) + { + printf("Error deinitializing encoder: %s\n", esp_err_to_name(ret)); } } +``` + +### Set and Reset Position Example + +```c +#include "zh_encoder.h" + +zh_encoder_handle_t *encoder_handle = NULL; + +void app_main(void) +{ + esp_log_level_set("zh_encoder", ESP_LOG_ERROR); + zh_encoder_init_config_t config = ZH_ENCODER_INIT_CONFIG_DEFAULT(); + config.a_gpio_number = GPIO_NUM_4; + config.b_gpio_number = GPIO_NUM_16; + config.encoder_min_value = -100; + config.encoder_max_value = 100; + config.encoder_step = 0.5; + config.encoder_number = 1; + esp_err_t ret = zh_encoder_init(&config, &encoder_handle); + if (ret != ESP_OK) + { + printf("Encoder initialization error: %s\n", esp_err_to_name(ret)); + return; + } + float position = 25.0f; + ret = zh_encoder_set(&encoder_handle, position); + if (ret == ESP_OK) + { + printf("Position set to %0.3f\n", position); + } + else + { + printf("Error setting position: %s\n", esp_err_to_name(ret)); + } + float current = 0; + ret = zh_encoder_get(&encoder_handle, ¤t); + if (ret == ESP_OK) + { + printf("Current position: %0.3f\n", current); + } + else + { + printf("Error getting position: %s\n", esp_err_to_name(ret)); + } + ret = zh_encoder_reset(&encoder_handle); + if (ret == ESP_OK) + { + printf("Position reset to midpoint\n"); + } + else + { + printf("Error resetting position: %s\n", esp_err_to_name(ret)); + } + zh_encoder_deinit(&encoder_handle); +} +``` + +### Reinit Range Example + +```c +#include "zh_encoder.h" + +zh_encoder_handle_t *encoder_handle = NULL; + +void app_main(void) +{ + esp_log_level_set("zh_encoder", ESP_LOG_ERROR); + zh_encoder_init_config_t config = ZH_ENCODER_INIT_CONFIG_DEFAULT(); + config.a_gpio_number = GPIO_NUM_4; + config.b_gpio_number = GPIO_NUM_16; + config.encoder_min_value = -10; + config.encoder_max_value = 10; + config.encoder_step = 1; + config.encoder_number = 1; + esp_err_t ret = zh_encoder_init(&config, &encoder_handle); + if (ret != ESP_OK) + { + printf("Encoder initialization error: %s\n", esp_err_to_name(ret)); + return; + } + ret = zh_encoder_reinit(&encoder_handle, 0, 255, 1); + if (ret == ESP_OK) + { + printf("Encoder range changed to 0..255\n"); + } + else + { + printf("Error reinitializing encoder: %s\n", esp_err_to_name(ret)); + } + float position = 0; + ret = zh_encoder_get(&encoder_handle, &position); + if (ret == ESP_OK) + { + printf("Position after reinit: %0.3f\n", position); + } + else + { + printf("Error getting position: %s\n", esp_err_to_name(ret)); + } + zh_encoder_deinit(&encoder_handle); +} +``` + +--- + +### Button Event Example + +```c +#include "zh_encoder.h" + +zh_encoder_handle_t *encoder_handle = NULL; void zh_encoder_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { (void)arg; - switch (event_id) + (void)event_base; + if (event_id == ZH_BUTTON_EVENT) { - case ZH_BUTTON_EVENT: - zh_encoder_button_event_on_isr_t *button_event = event_data; - printf("Encoder number %d button was %s.\n", button_event->encoder_number, (button_event->button_status == true) ? "released" : "pressed"); - break; - case ZH_ENCODER_EVENT: - zh_encoder_event_on_isr_t *encoder_event = event_data; - printf("Encoder number %d position %0.3f was %s.\n", encoder_event->encoder_number, encoder_event->encoder_position, (encoder_event->encoder_status == true) ? "increased" : "reduced"); - break; - default: - break; + zh_encoder_button_event_on_isr_t *button_event = (zh_encoder_button_event_on_isr_t *)event_data; + printf("Encoder %d button %s\n", button_event->encoder_number, button_event->button_status ? "released" : "pressed"); + } + else if (event_id == ZH_ENCODER_EVENT) + { + zh_encoder_event_on_isr_t *encoder_event = (zh_encoder_event_on_isr_t *)event_data; + printf("Encoder %d position %0.3f %s\n", encoder_event->encoder_number, encoder_event->encoder_position, encoder_event->encoder_status ? "increased" : "reduced"); + } +} + +void app_main(void) +{ + esp_log_level_set("zh_encoder", ESP_LOG_ERROR); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(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.a_gpio_number = GPIO_NUM_4; + config.b_gpio_number = GPIO_NUM_16; + config.s_gpio_number = GPIO_NUM_15; + config.s_gpio_debounce_time = 50000; + config.encoder_min_value = 0; + config.encoder_max_value = 100; + config.encoder_step = 1; + config.encoder_number = 1; + esp_err_t ret = zh_encoder_init(&config, &encoder_handle); + if (ret != ESP_OK) + { + printf("Encoder initialization error: %s\n", esp_err_to_name(ret)); + return; + } + for (;;) + { + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} +``` + +--- + +### Error Statistics Example + +```c +#include "zh_encoder.h" + +zh_encoder_handle_t *encoder_handle = NULL; + +void app_main(void) +{ + esp_log_level_set("zh_encoder", ESP_LOG_ERROR); + zh_encoder_init_config_t config = ZH_ENCODER_INIT_CONFIG_DEFAULT(); + config.a_gpio_number = GPIO_NUM_4; + config.b_gpio_number = GPIO_NUM_16; + config.encoder_min_value = -10; + config.encoder_max_value = 10; + config.encoder_step = 1; + config.encoder_number = 1; + esp_err_t ret = zh_encoder_init(&config, &encoder_handle); + if (ret != ESP_OK) + { + printf("Encoder initialization error: %s\n", esp_err_to_name(ret)); + return; + } + for (;;) + { + const zh_encoder_stats_t *stats = zh_encoder_get_stats(); + printf("Event post errors: %lu\n", (unsigned long)stats->event_post_error); + printf("Queue overflow errors: %lu\n", (unsigned long)stats->queue_overflow_error); + printf("Minimum free stack: %lu\n", (unsigned long)stats->min_stack_size); + zh_encoder_reset_stats(); + vTaskDelay(pdMS_TO_TICKS(60000)); } } ```