From 27f59c6d92e22831596c8d03bbedebe52124d1c7 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sun, 8 Jun 2025 13:00:28 +0300 Subject: [PATCH] wip: --- .gitignore | 6 +- CMakeLists.txt | 7 +- include/zh_encoder.h | 223 ++++++++++++++++++++++- zh_encoder.c | 415 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 633 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 65225d8..496ee2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ -.DS_Store -.vscode -build -sdkconfig -sdkconfig.old \ No newline at end of file +.DS_Store \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 512d998..55597ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1 +1,6 @@ -idf_component_register(SRCS "zh_encoder.c" INCLUDE_DIRS "include" REQUIRES driver) \ No newline at end of file +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 diff --git a/include/zh_encoder.h b/include/zh_encoder.h index 05e3796..5dec487 100644 --- a/include/zh_encoder.h +++ b/include/zh_encoder.h @@ -2,14 +2,233 @@ #include "esp_log.h" #include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_event.h" + +#define ZH_ENCODER_INIT_CONFIG_DEFAULT() \ + { \ + .task_priority = 10, \ + .stack_size = 2048, \ + .queue_size = 10, \ + .a_gpio_number = 0, \ + .b_gpio_number = 0, \ + .encoder_min_value = -100, \ + .encoder_max_value = 100, \ + .encoder_step = 1, \ + .encoder_number = 0} #ifdef __cplusplus extern "C" { #endif - + typedef struct // Structure for initial initialization of encoder. + { + uint8_t task_priority; // Task priority for the encoder isr processing. @note It is not recommended to set a value less than 10. + uint16_t stack_size; // Stack size for task for the encoder isr processing processing. @note The minimum size is 2048 bytes. + uint8_t queue_size; // Queue size for task for the encoder processing. @note It is not recommended to set a value less than 10. + uint8_t a_gpio_number; // Encoder A GPIO number. + uint8_t b_gpio_number; // Encoder B 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. + float encoder_step; // Encoder step. @note Must be greater than 0. + uint8_t encoder_number; // Unique encoder number. + } zh_encoder_init_config_t; + + typedef struct // Encoder handle. + { + uint8_t a_gpio_number; // Encoder A GPIO number. + uint8_t b_gpio_number; // Encoder B 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. + float encoder_step; // Encoder step. @note Must be greater than 0. + float encoder_position; // Encoder position. + uint8_t encoder_number; // Encoder unique number. + bool is_initialized; // Encoder initialization flag. + + } zh_encoder_handle_t; + + ESP_EVENT_DECLARE_BASE(ZH_ENCODER); + + typedef struct // Structure for sending data to the event handler when cause an interrupt. @note Should be used with ZH_ENCODER event base. + { + uint8_t encoder_number; // Encoder unique number. + float encoder_position; // Encoder current position. + } zh_encoder_event_on_isr_t; + + /** + * @brief Initialize encoder. + * + * @note The encoder will be set to the position (encoder_min_value + encoder_max_value)/2. + * + * @param[in] config Pointer to encoder initialized configuration structure. Can point to a temporary variable. + * @param[out] handle Pointer to unique encoder handle. + * + * @note Before initialize the expander recommend initialize zh_encoder_init_config_t structure with default values. + * + * @code zh_encoder_init_config_t config = ZH_ENCODER_INIT_CONFIG_DEFAULT() @endcode + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_encoder_init(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle); + + /** + * @brief Set encoder position. + * + * @param[in, out] handle Pointer to unique encoder handle. + * @param[in] position Encoder position (must be between encoder_min_value and encoder_max_value). + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_encoder_set(zh_encoder_handle_t *handle, float position); + + /** + * @brief Reset encoder position. + * + * @note The encoder will be set to the position (encoder_min_value + encoder_max_value)/2. + * + * @param[in, out] handle Pointer to unique encoder handle. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_encoder_reset(zh_encoder_handle_t *handle); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif + +// #ifndef ROTARY_ENCODER_H +// #define ROTARY_ENCODER_H + +// #include +// #include + +// #include "freertos/FreeRTOS.h" +// #include "freertos/queue.h" +// #include "esp_err.h" +// #include "driver/gpio.h" + +// #ifdef __cplusplus +// extern "C" { +// #endif + +// typedef int32_t rotary_encoder_position_t; + +// /** +// * @brief Enum representing the direction of rotation. +// */ +// typedef enum +// { +// ROTARY_ENCODER_DIRECTION_NOT_SET = 0, ///< Direction not yet known (stationary since reset) +// ROTARY_ENCODER_DIRECTION_CLOCKWISE, +// ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE, +// } rotary_encoder_direction_t; + +// // Used internally +// ///@cond INTERNAL +// #define TABLE_COLS 4 +// typedef uint8_t table_row_t[TABLE_COLS]; +// ///@endcond + +// /** +// * @brief Struct represents the current state of the device in terms of incremental position and direction of last movement +// */ +// typedef struct +// { +// rotary_encoder_position_t position; ///< Numerical position since reset. This value increments on clockwise rotation, and decrements on counter-clockewise rotation. Counts full or half steps depending on mode. Set to zero on reset. +// rotary_encoder_direction_t direction; ///< Direction of last movement. Set to NOT_SET on reset. +// } rotary_encoder_state_t; + +// /** +// * @brief Struct carries all the information needed by this driver to manage the rotary encoder device. +// * The fields of this structure should not be accessed directly. +// */ +// typedef struct +// { +// gpio_num_t pin_a; ///< GPIO for Signal A from the rotary encoder device +// gpio_num_t pin_b; ///< GPIO for Signal B from the rotary encoder device +// QueueHandle_t queue; ///< Handle for event queue, created by ::rotary_encoder_create_queue +// const table_row_t * table; ///< Pointer to active state transition table +// uint8_t table_state; ///< Internal state +// volatile rotary_encoder_state_t state; ///< Device state +// } rotary_encoder_info_t; + +// /** +// * @brief Struct represents a queued event, used to communicate current position to a waiting task +// */ +// typedef struct +// { +// rotary_encoder_state_t state; ///< The device state corresponding to this event +// } rotary_encoder_event_t; + +// /** +// * @brief Initialise the rotary encoder device with the specified GPIO pins and full step increments. +// * This function will set up the GPIOs as needed, +// * Note: this function assumes that gpio_install_isr_service(0) has already been called. +// * @param[in, out] info Pointer to allocated rotary encoder info structure. +// * @param[in] pin_a GPIO number for rotary encoder output A. +// * @param[in] pin_b GPIO number for rotary encoder output B. +// * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. +// */ +// esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b); + +// /** +// * @brief Enable half-stepping mode. This generates twice as many counted steps per rotation. +// * @param[in] info Pointer to initialised rotary encoder info structure. +// * @param[in] enable If true, count half steps. If false, only count full steps. +// * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. +// */ +// esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable); + +// /** +// * @brief Reverse (flip) the sense of the direction. +// * Use this if clockwise/counterclockwise are not what you expect. +// * @param[in] info Pointer to initialised rotary encoder info structure. +// * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. +// */ +// esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info); + +// /** +// * @brief Remove the interrupt handlers installed by ::rotary_encoder_init. +// * Note: GPIOs will be left in the state they were configured by ::rotary_encoder_init. +// * @param[in] info Pointer to initialised rotary encoder info structure. +// * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. +// */ +// esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info); + +// /** +// * @brief Create a queue handle suitable for use as an event queue. +// * @return A handle to a new queue suitable for use as an event queue. +// */ +// QueueHandle_t rotary_encoder_create_queue(void); + +// /** +// * @brief Set the driver to use the specified queue as an event queue. +// * It is recommended that a queue constructed by ::rotary_encoder_create_queue is used. +// * @param[in] info Pointer to initialised rotary encoder info structure. +// * @param[in] queue Handle to queue suitable for use as an event queue. See ::rotary_encoder_create_queue. +// * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. +// */ +// esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue); + +// /** +// * @brief Get the current position of the rotary encoder. +// * @param[in] info Pointer to initialised rotary encoder info structure. +// * @param[in, out] state Pointer to an allocated rotary_encoder_state_t struct that will +// * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. +// */ +// esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state); + +// /** +// * @brief Reset the current position of the rotary encoder to zero. +// * @param[in] info Pointer to initialised rotary encoder info structure. +// * @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred. +// */ +// esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info); + +// #ifdef __cplusplus +// } +// #endif + +// #endif // ROTARY_ENCODER_H diff --git a/zh_encoder.c b/zh_encoder.c index 2dcf8aa..ace3e39 100644 --- a/zh_encoder.c +++ b/zh_encoder.c @@ -1,15 +1,410 @@ #include "zh_encoder.h" -static const char *TAG = "zh_encoder"; +#define TAG "zh_encoder" -#define ZH_CD74HC4067_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__) -#define ZH_CD74HC4067_LOGW(msg, ...) ESP_LOGW(TAG, msg, ##__VA_ARGS__) -#define ZH_CD74HC4067_LOGE(msg, ...) ESP_LOGE(TAG, msg, ##__VA_ARGS__) -#define ZH_CD74HC4067_LOGE_ERR(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__) +#define ZH_ENCODER_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__) +#define ZH_ENCODER_LOGW(msg, ...) ESP_LOGW(TAG, msg, ##__VA_ARGS__) +#define ZH_ENCODER_LOGE(msg, ...) ESP_LOGE(TAG, msg, ##__VA_ARGS__) +#define ZH_ENCODER_LOGE_ERR(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__) -#define ZH_CD74HC4067_CHECK(cond, err, msg, ...) \ - if (!(cond)) \ - { \ - ZH_CD74HC4067_LOGE_ERR(msg, err); \ - return err; \ +#define ZH_ENCODER_CHECK(cond, err, msg, ...) \ + if (!(cond)) \ + { \ + ZH_ENCODER_LOGE_ERR(msg, err); \ + return err; \ } + +#define TABLE_ROWS 7 +#define TABLE_COLS 4 + +#define DIR_NONE 0x0 // No complete step yet. +#define DIR_CW 0x10 // Clockwise step. +#define DIR_CCW 0x20 // Anti-clockwise step. + +// Create the half-step state table (emits a code at 00 and 11) +#define R_START 0x0 +#define H_CCW_BEGIN 0x1 +#define H_CW_BEGIN 0x2 +#define H_START_M 0x3 +#define H_CW_BEGIN_M 0x4 +#define H_CCW_BEGIN_M 0x5 + +static const uint8_t _encoder_matrix[TABLE_ROWS][TABLE_COLS] = { + // 00 01 10 11 // BA + {H_START_M, H_CW_BEGIN, H_CCW_BEGIN, R_START}, // R_START (00) + {H_START_M | DIR_CCW, R_START, H_CCW_BEGIN, R_START}, // H_CCW_BEGIN + {H_START_M | DIR_CW, H_CW_BEGIN, R_START, R_START}, // H_CW_BEGIN + {H_START_M, H_CCW_BEGIN_M, H_CW_BEGIN_M, R_START}, // H_START_M (11) + {H_START_M, H_START_M, H_CW_BEGIN_M, R_START | DIR_CW}, // H_CW_BEGIN_M + {H_START_M, H_CCW_BEGIN_M, H_START_M, R_START | DIR_CCW}, // H_CCW_BEGIN_M +}; + +static QueueHandle_t _queue_handle = NULL; +static bool _is_initialized = false; + +// #define ROTARY_ENCODER_DEBUG + +// Use a single-item queue so that the last value can be easily overwritten by the interrupt handler +// #define EVENT_QUEUE_LENGTH 10 + +// #define TABLE_ROWS 7 + +// #define DIR_NONE 0x0 // No complete step yet. +// #define DIR_CW 0x10 // Clockwise step. +// #define DIR_CCW 0x20 // Anti-clockwise step. + +// // Create the half-step state table (emits a code at 00 and 11) +// #define R_START 0x0 +// #define H_CCW_BEGIN 0x1 +// #define H_CW_BEGIN 0x2 +// #define H_START_M 0x3 +// #define H_CW_BEGIN_M 0x4 +// #define H_CCW_BEGIN_M 0x5 + +// static const uint8_t _ttable_half[TABLE_ROWS][TABLE_COLS] = { +// // 00 01 10 11 // BA +// {H_START_M, H_CW_BEGIN, H_CCW_BEGIN, R_START}, // R_START (00) +// {H_START_M | DIR_CCW, R_START, H_CCW_BEGIN, R_START}, // H_CCW_BEGIN +// {H_START_M | DIR_CW, H_CW_BEGIN, R_START, R_START}, // H_CW_BEGIN +// {H_START_M, H_CCW_BEGIN_M, H_CW_BEGIN_M, R_START}, // H_START_M (11) +// {H_START_M, H_START_M, H_CW_BEGIN_M, R_START | DIR_CW}, // H_CW_BEGIN_M +// {H_START_M, H_CCW_BEGIN_M, H_START_M, R_START | DIR_CCW}, // H_CCW_BEGIN_M +// }; + +// // Create the full-step state table (emits a code at 00 only) +// #define F_CW_FINAL 0x1 +// #define F_CW_BEGIN 0x2 +// #define F_CW_NEXT 0x3 +// #define F_CCW_BEGIN 0x4 +// #define F_CCW_FINAL 0x5 +// #define F_CCW_NEXT 0x6 + +// static const uint8_t _ttable_full[TABLE_ROWS][TABLE_COLS] = { +// // 00 01 10 11 // BA +// {R_START, F_CW_BEGIN, F_CCW_BEGIN, R_START}, // R_START +// {F_CW_NEXT, R_START, F_CW_FINAL, R_START | DIR_CW}, // F_CW_FINAL +// {F_CW_NEXT, F_CW_BEGIN, R_START, R_START}, // F_CW_BEGIN +// {F_CW_NEXT, F_CW_BEGIN, F_CW_FINAL, R_START}, // F_CW_NEXT +// {F_CCW_NEXT, R_START, F_CCW_BEGIN, R_START}, // F_CCW_BEGIN +// {F_CCW_NEXT, F_CCW_FINAL, R_START, R_START | DIR_CCW}, // F_CCW_FINAL +// {F_CCW_NEXT, F_CCW_FINAL, F_CCW_BEGIN, R_START}, // F_CCW_NEXT +// }; + +static esp_err_t _zh_encoder_validate_config(const zh_encoder_init_config_t *config); +static esp_err_t _zh_encoder_gpio_init(const zh_encoder_init_config_t *config); +static esp_err_t _zh_encoder_configure_interrupts(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle); +static esp_err_t _zh_encoder_init_resources(const zh_encoder_init_config_t *config); +static esp_err_t _zh_encoder_create_task(const zh_encoder_init_config_t *config); +static void _zh_encoder_isr_handler(void *arg); +static void _zh_encoder_isr_processing_task(void *pvParameter); + +ESP_EVENT_DEFINE_BASE(ZH_ENCODER); + +esp_err_t zh_encoder_init(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle) +{ + _zh_encoder_validate_config(config); + _zh_encoder_gpio_init(config); + _zh_encoder_configure_interrupts(config, handle); + _zh_encoder_init_resources(config); + _zh_encoder_create_task(config); + return ESP_OK; +} + +esp_err_t zh_encoder_set(zh_encoder_handle_t *handle, float position) +{ + ZH_ENCODER_LOGI("Encoder set position started."); + ZH_ENCODER_CHECK(handle->is_initialized == true, ESP_FAIL, "Encoder set position failed. Encoder not initialized."); + ZH_ENCODER_CHECK(position <= handle->encoder_max_value && position >= handle->encoder_min_value, ESP_ERR_INVALID_ARG, "Encoder set position failed. Invalid argument."); + handle->encoder_position = position; + ZH_ENCODER_LOGI("Encoder set position completed successfully."); + return ESP_OK; +} + +esp_err_t zh_encoder_reset(zh_encoder_handle_t *handle) +{ + ZH_ENCODER_LOGI("Encoder reset started."); + ZH_ENCODER_CHECK(handle->is_initialized == true, ESP_FAIL, "Encoder reset failed. Encoder not initialized."); + handle->encoder_position = (handle->encoder_min_value + handle->encoder_max_value) / 2; + ZH_ENCODER_LOGI("Encoder reset completed successfully."); + return ESP_OK; +} +// static uint8_t _process(rotary_encoder_info_t *info) +// { +// uint8_t event = 0; +// if (info != NULL) +// { +// // Get state of input pins. +// uint8_t pin_state = (gpio_get_level(info->pin_b) << 1) | gpio_get_level(info->pin_a); + +// // Determine new state from the pins and state table. +// #ifdef ROTARY_ENCODER_DEBUG +// uint8_t old_state = info->table_state; +// #endif +// info->table_state = info->table[info->table_state & 0xf][pin_state]; + +// // Return emit bits, i.e. the generated event. +// event = info->table_state & 0x30; +// #ifdef ROTARY_ENCODER_DEBUG +// ESP_EARLY_LOGD(TAG, "BA %d%d, state 0x%02x, new state 0x%02x, event 0x%02x", +// pin_state >> 1, pin_state & 1, old_state, info->table_state, event); +// #endif +// } +// return event; +// } + +// static void IRAM_ATTR _isr_rotenc(void *args) +// { +// rotary_encoder_info_t *info = (rotary_encoder_info_t *)args; +// uint8_t event = _process(info); +// bool send_event = false; + +// switch (event) +// { +// case DIR_CW: +// ++info->state.position; +// info->state.direction = ROTARY_ENCODER_DIRECTION_CLOCKWISE; +// send_event = true; +// break; +// case DIR_CCW: +// --info->state.position; +// info->state.direction = ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE; +// send_event = true; +// break; +// default: +// break; +// } + +// if (send_event && info->queue) +// { +// rotary_encoder_event_t queue_event = +// { +// .state = +// { +// .position = info->state.position, +// .direction = info->state.direction, +// }, +// }; +// BaseType_t task_woken = pdFALSE; +// xQueueSendFromISR(info->queue, &queue_event, &task_woken); +// if (task_woken) +// { +// portYIELD_FROM_ISR(); +// } +// } +// } + +// esp_err_t rotary_encoder_init(rotary_encoder_info_t *info, gpio_num_t pin_a, gpio_num_t pin_b) +// { +// esp_err_t err = ESP_OK; +// if (info) +// { +// info->pin_a = pin_a; +// info->pin_b = pin_b; +// info->table = &_ttable_full[0]; // enable_half_step ? &_ttable_half[0] : &_ttable_full[0]; +// info->table_state = R_START; +// info->state.position = 0; +// info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET; + +// gpio_config_t pin_config = { +// .mode = GPIO_MODE_INPUT, +// .pin_bit_mask = (1ULL << info->pin_a) | (1ULL << info->pin_b), +// .pull_up_en = GPIO_PULLUP_ENABLE, +// .intr_type = GPIO_INTR_ANYEDGE}; +// gpio_config(&pin_config); +// // configure GPIOs +// // gpio_pad_select_gpio(info->pin_a); +// // gpio_set_pull_mode(info->pin_a, GPIO_PULLUP_ONLY); +// // gpio_set_direction(info->pin_a, GPIO_MODE_INPUT); +// // gpio_set_intr_type(info->pin_a, GPIO_INTR_ANYEDGE); + +// // gpio_pad_select_gpio(info->pin_b); +// // gpio_set_pull_mode(info->pin_b, GPIO_PULLUP_ONLY); +// // gpio_set_direction(info->pin_b, GPIO_MODE_INPUT); +// // gpio_set_intr_type(info->pin_b, GPIO_INTR_ANYEDGE); + +// // install interrupt handlers +// gpio_isr_handler_add(info->pin_a, _isr_rotenc, info); +// gpio_isr_handler_add(info->pin_b, _isr_rotenc, info); +// } +// else +// { +// ESP_LOGE(TAG, "info is NULL"); +// err = ESP_ERR_INVALID_ARG; +// } +// return err; +// } + +// esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t *info, bool enable) +// { +// esp_err_t err = ESP_OK; +// if (info) +// { +// info->table = enable ? &_ttable_half[0] : &_ttable_full[0]; +// info->table_state = R_START; +// } +// else +// { +// ESP_LOGE(TAG, "info is NULL"); +// err = ESP_ERR_INVALID_ARG; +// } +// return err; +// } + +// esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t *info) +// { +// esp_err_t err = ESP_OK; +// if (info) +// { +// gpio_num_t temp = info->pin_a; +// info->pin_a = info->pin_b; +// info->pin_b = temp; +// } +// else +// { +// ESP_LOGE(TAG, "info is NULL"); +// err = ESP_ERR_INVALID_ARG; +// } +// return err; +// } + +// esp_err_t rotary_encoder_uninit(rotary_encoder_info_t *info) +// { +// esp_err_t err = ESP_OK; +// if (info) +// { +// gpio_isr_handler_remove(info->pin_a); +// gpio_isr_handler_remove(info->pin_b); +// } +// else +// { +// ESP_LOGE(TAG, "info is NULL"); +// err = ESP_ERR_INVALID_ARG; +// } +// return err; +// } + +// QueueHandle_t rotary_encoder_create_queue(void) +// { +// return xQueueCreate(EVENT_QUEUE_LENGTH, sizeof(rotary_encoder_event_t)); +// } + +// esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t *info, QueueHandle_t queue) +// { +// esp_err_t err = ESP_OK; +// if (info) +// { +// info->queue = queue; +// } +// else +// { +// ESP_LOGE(TAG, "info is NULL"); +// err = ESP_ERR_INVALID_ARG; +// } +// return err; +// } + +// esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t *info, rotary_encoder_state_t *state) +// { +// esp_err_t err = ESP_OK; +// if (info && state) +// { +// // make a snapshot of the state +// state->position = info->state.position; +// state->direction = info->state.direction; +// } +// else +// { +// ESP_LOGE(TAG, "info and/or state is NULL"); +// err = ESP_ERR_INVALID_ARG; +// } +// return err; +// } + +// esp_err_t rotary_encoder_reset(rotary_encoder_info_t *info) +// { +// esp_err_t err = ESP_OK; +// if (info) +// { +// info->state.position = 0; +// info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET; +// } +// else +// { +// ESP_LOGE(TAG, "info is NULL"); +// err = ESP_ERR_INVALID_ARG; +// } +// return err; +// } + +static esp_err_t _zh_encoder_validate_config(const zh_encoder_init_config_t *config) +{ + ZH_ENCODER_CHECK(config != NULL, ESP_ERR_INVALID_ARG, "Invalid configuration."); + ZH_ENCODER_CHECK(config->task_priority >= 10 && config->stack_size >= 2048, ESP_ERR_INVALID_ARG, "Invalid task settings."); + ZH_ENCODER_CHECK(config->queue_size >= 10, ESP_ERR_INVALID_ARG, "Invalid queue size."); + return ESP_OK; +} + +static esp_err_t _zh_encoder_gpio_init(const zh_encoder_init_config_t *config) +{ + ZH_ENCODER_CHECK(config->a_gpio_number < GPIO_NUM_MAX || config->b_gpio_number < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, "Invalid GPIO number.") + ZH_ENCODER_CHECK(config->a_gpio_number != config->b_gpio_number, ESP_ERR_INVALID_ARG, "Invalid GPIO number.") + gpio_config_t pin_config = { + .mode = GPIO_MODE_INPUT, + .pin_bit_mask = (1ULL << config->a_gpio_number) | (1ULL << config->b_gpio_number), + .pull_up_en = GPIO_PULLUP_ENABLE, + .intr_type = GPIO_INTR_ANYEDGE}; + esp_err_t err = gpio_config(&pin_config); + ZH_ENCODER_CHECK(err == ESP_OK, err, "GPIO initialization failed."); + return ESP_OK; +} + +static esp_err_t _zh_encoder_configure_interrupts(const zh_encoder_init_config_t *config, zh_encoder_handle_t *handle) +{ + gpio_install_isr_service(0); + esp_err_t err = gpio_isr_handler_add(config->a_gpio_number, _zh_encoder_isr_handler, handle); + ZH_ENCODER_CHECK(err == ESP_OK, err, "Interrupt initialization failed."); + err = gpio_isr_handler_add(config->b_gpio_number, _zh_encoder_isr_handler, handle); + ZH_ENCODER_CHECK(err == ESP_OK, err, "Interrupt initialization failed."); + return ESP_OK; +} + +static esp_err_t _zh_encoder_init_resources(const zh_encoder_init_config_t *config) +{ + if (_is_initialized == false) + { + _queue_handle = xQueueCreate(config->queue_size, sizeof(zh_encoder_handle_t)); + ZH_ENCODER_CHECK(_queue_handle != NULL, ESP_FAIL, "Queue creation failed."); + } + return ESP_OK; +} + +static esp_err_t _zh_encoder_create_task(const zh_encoder_init_config_t *config) +{ + if (_is_initialized == false) + { + BaseType_t err = xTaskCreatePinnedToCore( + &_zh_encoder_isr_processing_task, + "zh_encoder_isr_processing", + config->stack_size, + NULL, + config->task_priority, + NULL, + tskNO_AFFINITY); + ZH_ENCODER_CHECK(err == pdPASS, ESP_FAIL, "Task creation failed."); + } + return ESP_OK; +} + +static void _zh_encoder_isr_handler(void *arg) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + xQueueSendFromISR(_queue_handle, arg, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) + { + portYIELD_FROM_ISR(); + }; +} + +static void _zh_encoder_isr_processing_task(void *pvParameter) +{ +} \ No newline at end of file