From a6e0f2876ce48ef36de9a7f22ace13f8d109b310 Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sat, 31 Jan 2026 23:21:25 +0300 Subject: [PATCH] wip: --- CMakeLists.txt | 2 +- README.md | 23 +++++- component.mk | 0 include/main.h | 0 include/zh_tachometer.h | 88 +++++++++++++++++++++++ main.c | 0 zh_tachometer.c | 156 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 267 insertions(+), 2 deletions(-) delete mode 100644 component.mk delete mode 100644 include/main.h create mode 100644 include/zh_tachometer.h delete mode 100644 main.c create mode 100644 zh_tachometer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ab5c51..a421f12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1 +1 @@ -idf_component_register(SRCS "main.c" INCLUDE_DIRS "include") \ No newline at end of file +idf_component_register(SRCS "zh_tachometer.c" INCLUDE_DIRS "include" REQUIRES driver esp_timer) \ No newline at end of file diff --git a/README.md b/README.md index 3df3e4d..ee36ec1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,24 @@ # esp_component_template -esp_component_template \ No newline at end of file +esp_component_template + +#include "zh_tachometer.h" + +zh_tachometer_handle_t tachometer_handle = {0}; + +void app_main(void) +{ + esp_log_level_set("zh_tachometer", ESP_LOG_ERROR); + zh_tachometer_init_config_t config = ZH_TACHOMETER_INIT_CONFIG_DEFAULT(); + config.a_gpio_number = GPIO_NUM_26; + config.b_gpio_number = GPIO_NUM_27; + config.encoder_pulses = 3600; + zh_tachometer_init(&config, &tachometer_handle); + for (;;) + { + int16_t value = 0; + zh_tachometer_get(&tachometer_handle, &value); + printf("Tachometer value is %d rpm.\n", value); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } +} \ No newline at end of file diff --git a/component.mk b/component.mk deleted file mode 100644 index e69de29..0000000 diff --git a/include/main.h b/include/main.h deleted file mode 100644 index e69de29..0000000 diff --git a/include/zh_tachometer.h b/include/zh_tachometer.h new file mode 100644 index 0000000..855fd29 --- /dev/null +++ b/include/zh_tachometer.h @@ -0,0 +1,88 @@ +/** + * @file zh_tachometer.h + */ + +#pragma once + +#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" + +/** + * @brief Tachometer initial default values. + */ +#define ZH_TACHOMETER_INIT_CONFIG_DEFAULT() \ + { \ + .a_gpio_number = GPIO_NUM_MAX, \ + .b_gpio_number = GPIO_NUM_MAX, \ + .pullup = true, \ + .encoder_pulses = 0} + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief Structure for initial initialization of tachometer. + */ + typedef struct + { + uint8_t a_gpio_number; /*!< Encoder A GPIO number. */ + uint8_t b_gpio_number; /*!< Encoder B GPIO number. */ + bool pullup; /*!< Pullup GPIO enable/disable. */ + uint16_t encoder_pulses; /*!< Number of pulses per one rotation. */ + } zh_tachometer_init_config_t; + + /** + * @brief Tachometer handle. + */ + typedef struct + { + pcnt_unit_handle_t pcnt_unit_handle; /*!< Tachometer unique pcnt unit handle. */ + pcnt_channel_handle_t pcnt_channel_a_handle; /*!< Tachometer unique pcnt channel handle. */ + pcnt_channel_handle_t pcnt_channel_b_handle; /*!< Tachometer unique pcnt channel handle. */ + esp_timer_handle_t esp_timer_handle; /*!< Tachometer unique timer handle. */ + uint16_t value; /*!< Tachometer value. */ + bool is_initialized; /*!< Tachometer initialization flag. */ + } zh_tachometer_handle_t; + + /** + * @brief Initialize tachometer. + * + * @param[in] config Pointer to tachometer initialized configuration structure. Can point to a temporary variable. + * @param[out] handle Pointer to unique tachometer handle. + * + * @note Before initialize the tachometer recommend initialize zh_tachometer_init_config_t structure with default values. + * + * @code zh_tachometer_init_config_t config = ZH_TACHOMETER_INIT_CONFIG_DEFAULT() @endcode + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_tachometer_init(const zh_tachometer_init_config_t *config, zh_tachometer_handle_t *handle); + + /** + * @brief Deinitialize tachometer. + * + * @param[in, out] handle Pointer to unique tachometer handle. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_tachometer_deinit(zh_tachometer_handle_t *handle); + + /** + * @brief Get tachometer value. + * + * @param[in] handle Pointer to unique tachometer handle. + * @param[out] value Tachometer value. + * + * @return ESP_OK if success or an error code otherwise. + */ + esp_err_t zh_tachometer_get(const zh_tachometer_handle_t *handle, uint16_t *value); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/main.c b/main.c deleted file mode 100644 index e69de29..0000000 diff --git a/zh_tachometer.c b/zh_tachometer.c new file mode 100644 index 0000000..aecd9d7 --- /dev/null +++ b/zh_tachometer.c @@ -0,0 +1,156 @@ +#include "zh_tachometer.h" + +#define TAG "zh_tachometer" + +#define ZH_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__) +#define ZH_LOGE(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__) + +#define ZH_ERROR_CHECK(cond, err, cleanup, msg, ...) \ + if (!(cond)) \ + { \ + ZH_LOGE(msg, err, ##__VA_ARGS__); \ + cleanup; \ + return err; \ + } + +static esp_err_t _zh_tachometer_validate_config(const zh_tachometer_init_config_t *config); +static esp_err_t _zh_tachometer_pcnt_init(const zh_tachometer_init_config_t *config, zh_tachometer_handle_t *handle); +static esp_err_t _zh_tachometer_timer_init(zh_tachometer_handle_t *handle); +static void _zh_tachometer_timer_on_alarm_cb(void *arg); + +esp_err_t zh_tachometer_init(const zh_tachometer_init_config_t *config, zh_tachometer_handle_t *handle) +{ + ZH_LOGI("Tachometer initialization started."); + ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Tachometer initialization failed. Invalid argument."); + ZH_ERROR_CHECK(handle->is_initialized == false, ESP_ERR_INVALID_STATE, NULL, "Tachometer initialization failed. Tachometer is already initialized."); + esp_err_t err = _zh_tachometer_validate_config(config); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Tachometer initialization failed. Initial configuration check failed."); + err = _zh_tachometer_timer_init(handle); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Tachometer initialization failed. Timer initialization failed."); + err = _zh_tachometer_pcnt_init(config, handle); + ZH_ERROR_CHECK(err == ESP_OK, err, esp_timer_stop(handle->esp_timer_handle); esp_timer_delete(handle->esp_timer_handle), "Tachometer initialization failed. PCNT initialization failed."); + handle->is_initialized = true; + ZH_LOGI("Tachometer initialization completed successfully."); + return ESP_OK; +} + +esp_err_t zh_tachometer_deinit(zh_tachometer_handle_t *handle) +{ + ZH_LOGI("Tachometer deinitialization started."); + ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Tachometer deinitialization failed. Invalid argument."); + ZH_ERROR_CHECK(handle->is_initialized == true, ESP_FAIL, NULL, "Tachometer deinitialization failed. Tachometer not initialized."); + pcnt_unit_stop(handle->pcnt_unit_handle); + pcnt_unit_disable(handle->pcnt_unit_handle); + pcnt_unit_remove_watch_point(handle->pcnt_unit_handle, 32767); + pcnt_unit_remove_watch_point(handle->pcnt_unit_handle, -32767); + pcnt_del_channel(handle->pcnt_channel_a_handle); + pcnt_del_channel(handle->pcnt_channel_b_handle); + pcnt_del_unit(handle->pcnt_unit_handle); + esp_timer_stop(handle->esp_timer_handle); + esp_timer_delete(handle->esp_timer_handle); + handle->is_initialized = false; + ZH_LOGI("Tachometer deinitialization completed successfully."); + return ESP_OK; +} + +esp_err_t zh_tachometer_get(const zh_tachometer_handle_t *handle, uint16_t *value) +{ + ZH_LOGI("Tachometer get position started."); + ZH_ERROR_CHECK(handle != NULL && value != NULL, ESP_ERR_INVALID_ARG, NULL, "Tachometer get position failed. Invalid argument."); + ZH_ERROR_CHECK(handle->is_initialized == true, ESP_FAIL, NULL, "Tachometer get position failed. Tachometer not initialized."); + *value = handle->value; + ZH_LOGI("Tachometer get position completed successfully."); + return ESP_OK; +} + +static esp_err_t _zh_tachometer_validate_config(const zh_tachometer_init_config_t *config) +{ + ZH_ERROR_CHECK(config->encoder_pulses > 0, ESP_ERR_INVALID_ARG, NULL, "Invalid encoder pulses."); + return ESP_OK; +} + +static esp_err_t _zh_tachometer_pcnt_init(const zh_tachometer_init_config_t *config, zh_tachometer_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, "Encoder A and B GPIO is same.") + pcnt_unit_config_t pcnt_unit_config = { + .high_limit = 32767, + .low_limit = -32767, + .flags.accum_count = true, + }; + 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, 32767); + 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, -32767); + ZH_ERROR_CHECK(err == ESP_OK, err, pcnt_unit_remove_watch_point(pcnt_unit_handle, 32767); 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_unit_remove_watch_point(pcnt_unit_handle, 32767); pcnt_unit_remove_watch_point(pcnt_unit_handle, -32767); 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_unit_remove_watch_point(pcnt_unit_handle, 32767); pcnt_unit_remove_watch_point(pcnt_unit_handle, -32767); + 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_unit_remove_watch_point(pcnt_unit_handle, 32767); pcnt_unit_remove_watch_point(pcnt_unit_handle, -32767); + pcnt_del_channel(pcnt_channel_a_handle); pcnt_del_channel(pcnt_channel_b_handle); pcnt_del_unit(pcnt_unit_handle), "PCNT initialization failed."); + if (config->pullup == false) + { + gpio_pullup_dis((gpio_num_t)config->a_gpio_number); + gpio_pullup_dis((gpio_num_t)config->b_gpio_number); + } + 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_tachometer_timer_init(zh_tachometer_handle_t *handle) +{ + const esp_timer_create_args_t timer_args = { + .callback = &_zh_tachometer_timer_on_alarm_cb, + .arg = handle, + }; + esp_err_t err = esp_timer_create(&timer_args, &handle->esp_timer_handle); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Timer initialization failed."); + err = esp_timer_start_periodic(handle->esp_timer_handle, 10000); // 0.01 sec. + ZH_ERROR_CHECK(err == ESP_OK, err, esp_timer_delete(handle->esp_timer_handle), "Timer initialization failed."); + return ESP_OK; +} + +static void IRAM_ATTR _zh_tachometer_timer_on_alarm_cb(void *arg) +{ + zh_tachometer_handle_t *handle = (zh_tachometer_handle_t *)arg; + int pcnt_count = 0; + pcnt_unit_get_count(handle->pcnt_unit_handle, &pcnt_count); + pcnt_unit_clear_count(handle->pcnt_unit_handle); + handle->value = (uint16_t)pcnt_count; +} \ No newline at end of file