diff --git a/README.md b/README.md index 95a264a..44b216e 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 @@ -13,7 +13,8 @@ 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_SAF ``` ## Using @@ -36,7 +37,7 @@ In the application, add the component: ```c #include "zh_inclinometer.h" -double inclinometer_position = 0; +zh_inclinometer_handle_t inclinometer_handle = {0}; void app_main(void) { @@ -45,11 +46,12 @@ void app_main(void) config.a_gpio_number = GPIO_NUM_26; config.b_gpio_number = GPIO_NUM_27; config.encoder_pulses = 3600; - zh_inclinometer_init(&config); + zh_inclinometer_init(&config, &inclinometer_handle); for (;;) { - zh_inclinometer_get(&inclinometer_position); - printf("Inclinometer position is %0.2f degrees.\n", inclinometer_position); + float angle = 0; + zh_inclinometer_get(&inclinometer_handle, &angle); + printf("Inclinometer position is %0.2f degrees.\n", angle); vTaskDelay(100 / portTICK_PERIOD_MS); } } diff --git a/include/zh_inclinometer.h b/include/zh_inclinometer.h index 6de5536..3348475 100644 --- a/include/zh_inclinometer.h +++ b/include/zh_inclinometer.h @@ -6,6 +6,7 @@ #include "esp_log.h" #include "driver/gpio.h" +#include "driver/pulse_cnt.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -16,6 +17,7 @@ { \ .a_gpio_number = GPIO_NUM_MAX, \ .b_gpio_number = GPIO_NUM_MAX, \ + .pullup = true, \ .encoder_pulses = 0} #ifdef __cplusplus @@ -30,13 +32,27 @@ extern "C" { 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_inclinometer_init_config_t; + /** + * @brief Inclinometer handle. + */ + typedef struct + { + pcnt_unit_handle_t pcnt_unit_handle; /*!< Inclinometer unique pcnt unit handle. */ + pcnt_channel_handle_t pcnt_channel_a_handle; /*!< Inclinometer unique pcnt channel handle. */ + pcnt_channel_handle_t pcnt_channel_b_handle; /*!< Inclinometer unique pcnt channel handle. */ + float degrees_per_pulse; /*!< Number of degrees per pulse. */ + bool is_initialized; /*!< Inclinometer initialization flag. */ + } zh_inclinometer_handle_t; + /** * @brief Initialize inclinometer. * * @param[in] config Pointer to inclinometer initialized configuration structure. Can point to a temporary variable. + * @param[out] handle Pointer to unique inclinometer handle. * * @note Before initialize the inclinometer recommend initialize zh_inclinometer_init_config_t structure with default values. * @@ -44,32 +60,37 @@ extern "C" * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_inclinometer_init(const zh_inclinometer_init_config_t *config); + esp_err_t zh_inclinometer_init(const zh_inclinometer_init_config_t *config, zh_inclinometer_handle_t *handle); /** * @brief Deinitialize inclinometer. * + * @param[in, out] handle Pointer to unique inclinometer handle. + * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_inclinometer_deinit(void); + esp_err_t zh_inclinometer_deinit(zh_inclinometer_handle_t *handle); /** * @brief Get inclinometer position. * - * @param[out] position inclinometer position. + * @param[in] handle Pointer to unique inclinometer handle. + * @param[out] position Inclinometer position. * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_inclinometer_get(double *position); + esp_err_t zh_inclinometer_get(zh_inclinometer_handle_t *handle, float *angle); /** * @brief Reset inclinometer position. * * @note The inclinometer will be set to 0 position. * + * @param[in] handle Pointer to unique inclinometer handle. + * * @return ESP_OK if success or an error code otherwise. */ - esp_err_t zh_inclinometer_reset(void); + esp_err_t zh_inclinometer_reset(zh_inclinometer_handle_t *handle); #ifdef __cplusplus } diff --git a/version.txt b/version.txt index afaf360..359a5b9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.0 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/zh_inclinometer.c b/zh_inclinometer.c index 1d6ea95..a842a2d 100644 --- a/zh_inclinometer.c +++ b/zh_inclinometer.c @@ -13,82 +13,57 @@ return err; \ } -#define ZH_ENCODER_DIRECTION_CW 0x10 -#define ZH_ENCODER_DIRECTION_CCW 0x20 - -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}, -}; - -static portMUX_TYPE _spinlock = portMUX_INITIALIZER_UNLOCKED; - -static double _encoder_step; -volatile static double _encoder_position; -volatile static uint8_t _encoder_state; -static uint8_t _a_gpio_number; -static uint8_t _b_gpio_number; -static bool _is_initialized = false; -static bool _is_prev_gpio_isr_handler = false; - static esp_err_t _zh_inclinometer_validate_config(const zh_inclinometer_init_config_t *config); -static esp_err_t _zh_inclinometer_gpio_init(const zh_inclinometer_init_config_t *config); -static void _zh_inclinometer_isr_handler(void *arg); +static esp_err_t _zh_inclinometer_pcnt_init(const zh_inclinometer_init_config_t *config, zh_inclinometer_handle_t *handle); -esp_err_t zh_inclinometer_init(const zh_inclinometer_init_config_t *config) +esp_err_t zh_inclinometer_init(const zh_inclinometer_init_config_t *config, zh_inclinometer_handle_t *handle) { ZH_LOGI("Inclinometer initialization started."); + ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Inclinometer initialization failed. Invalid argument."); esp_err_t err = _zh_inclinometer_validate_config(config); ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Inclinometer initialization failed. Initial configuration check failed."); - err = _zh_inclinometer_gpio_init(config); - ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Inclinometer initialization failed. GPIO initialization failed."); - _encoder_position = 0; - _encoder_step = 360.0 / (double)config->encoder_pulses; - _a_gpio_number = config->a_gpio_number; - _b_gpio_number = config->b_gpio_number; - _is_initialized = true; + err = _zh_inclinometer_pcnt_init(config, handle); + ZH_ERROR_CHECK(err == ESP_OK, err, NULL, "Inclinometer initialization failed. PCNT initialization failed."); + handle->degrees_per_pulse = 360.0 / config->encoder_pulses; + handle->is_initialized = true; ZH_LOGI("Inclinometer initialization completed successfully."); return ESP_OK; } -esp_err_t zh_inclinometer_deinit(void) +esp_err_t zh_inclinometer_deinit(zh_inclinometer_handle_t *handle) { ZH_LOGI("Inclinometer deinitialization started."); - ZH_ERROR_CHECK(_is_initialized == true, ESP_FAIL, NULL, "Inclinometer deinitialization failed. Inclinometer not initialized."); - gpio_isr_handler_remove((gpio_num_t)_a_gpio_number); - gpio_isr_handler_remove((gpio_num_t)_b_gpio_number); - gpio_reset_pin((gpio_num_t)_a_gpio_number); - gpio_reset_pin((gpio_num_t)_b_gpio_number); - if (_is_prev_gpio_isr_handler == false) - { - gpio_uninstall_isr_service(); - } - _is_initialized = false; + ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Inclinometer deinitialization failed. Invalid argument."); + ZH_ERROR_CHECK(handle->is_initialized == true, ESP_FAIL, NULL, "Inclinometer deinitialization failed. Inclinometer not initialized."); + 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); + handle->is_initialized = false; ZH_LOGI("Inclinometer deinitialization completed successfully."); return ESP_OK; } -esp_err_t zh_inclinometer_get(double *position) +esp_err_t zh_inclinometer_get(zh_inclinometer_handle_t *handle, float *angle) { ZH_LOGI("Inclinometer get position started."); - ZH_ERROR_CHECK(position != NULL, ESP_ERR_INVALID_ARG, NULL, "Inclinometer get position failed. Invalid argument."); - ZH_ERROR_CHECK(_is_initialized == true, ESP_FAIL, NULL, "Inclinometer get position failed. Inclinometer not initialized."); - *position = _encoder_position; + ZH_ERROR_CHECK(handle != NULL && angle != NULL, ESP_ERR_INVALID_ARG, NULL, "Inclinometer get position failed. Invalid argument."); + ZH_ERROR_CHECK(handle->is_initialized == true, ESP_FAIL, NULL, "Inclinometer get position failed. Inclinometer not initialized."); + int pcnt_count = 0; + pcnt_unit_get_count(handle->pcnt_unit_handle, &pcnt_count); + float angle_temp = pcnt_count * handle->degrees_per_pulse; + *angle = (angle_temp < 0) ? -angle_temp : angle_temp; ZH_LOGI("Inclinometer get position completed successfully."); return ESP_OK; } -esp_err_t zh_inclinometer_reset(void) +esp_err_t zh_inclinometer_reset(zh_inclinometer_handle_t *handle) { ZH_LOGI("Inclinometer reset started."); - ZH_ERROR_CHECK(_is_initialized == true, ESP_FAIL, NULL, "Inclinometer reset failed. Inclinometer not initialized."); - taskENTER_CRITICAL(&_spinlock); - _encoder_position = 0; - taskEXIT_CRITICAL(&_spinlock); + ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Inclinometer reset failed. Invalid argument."); + ZH_ERROR_CHECK(handle->is_initialized == true, ESP_FAIL, NULL, "Inclinometer reset failed. Inclinometer not initialized."); + pcnt_unit_clear_count(handle->pcnt_unit_handle); ZH_LOGI("Inclinometer reset completed successfully."); return ESP_OK; } @@ -100,49 +75,61 @@ static esp_err_t _zh_inclinometer_validate_config(const zh_inclinometer_init_con return ESP_OK; } -static esp_err_t _zh_inclinometer_gpio_init(const zh_inclinometer_init_config_t *config) // -V2008 +static esp_err_t _zh_inclinometer_pcnt_init(const zh_inclinometer_init_config_t *config, zh_inclinometer_handle_t *handle) { + 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.") - 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_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), "Failed install isr service."); - if (err == ESP_ERR_INVALID_STATE) + pcnt_unit_config_t pcnt_unit_config = { + .high_limit = config->encoder_pulses, + .low_limit = -config->encoder_pulses, + }; + 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_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."); + if (config->pullup == false) { - _is_prev_gpio_isr_handler = true; - } - err = gpio_isr_handler_add((gpio_num_t)config->a_gpio_number, _zh_inclinometer_isr_handler, NULL); - 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), "Interrupt initialization failed."); - err = gpio_isr_handler_add((gpio_num_t)config->b_gpio_number, _zh_inclinometer_isr_handler, NULL); - 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), "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), "Interrupt initialization failed."); + 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 void IRAM_ATTR _zh_inclinometer_isr_handler(void *arg) -{ - _encoder_state = _encoder_matrix[_encoder_state & 0x0F][(gpio_get_level((gpio_num_t)_b_gpio_number) << 1) | gpio_get_level((gpio_num_t)_a_gpio_number)]; - switch (_encoder_state & 0x30) - { - case ZH_ENCODER_DIRECTION_CW: - _encoder_position = _encoder_position + _encoder_step; - break; - case ZH_ENCODER_DIRECTION_CCW: - _encoder_position = _encoder_position - _encoder_step; - break; - default: - break; - } } \ No newline at end of file