diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index 0b08f7051..a9323f292 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -445,6 +445,14 @@ build_example_get_started_button_power_save: variables: EXAMPLE_DIR: examples/get-started/button_power_save +build_example_get_started_knob_power_save: + extends: + - .build_examples_template + - .rules:build:example_get_started_knob_power_save + - .build_idf_active_release_version + variables: + EXAMPLE_DIR: examples/get-started/knob_power_save + build_example_gprof_gprof_simple: extends: - .build_examples_template diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index 9ffc411dd..5f6bbb4ae 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -301,7 +301,7 @@ - "components/i2c_bus/include/i2c_bus.h" - "components/ir/ir_learn/include/ir_learn.h" - "components/keyboard_button/include/keyboard_button.h" - - "components/knob/iot_knob.h" + - "components/knob/include/iot_knob.h" - "components/led/led_indicator/include/led_indicator.h" - "components/motor/esp_sensorless_bldc_control/control/include/bldc_control.h" - "components/motor/esp_sensorless_bldc_control/control/include/bldc_control_param.h" @@ -418,6 +418,9 @@ .patterns-example_get_started_button_power_save: &patterns-example_get_started_button_power_save - "examples/get-started/button_power_save/**/*" +.patterns-example_get_started_knob_power_save: &patterns-example_get_started_knob_power_save + - "examples/get-started/knob_power_save/**/*" + .patterns-example_indicator: &patterns-example_indicator - "examples/indicator/**/*" @@ -885,6 +888,18 @@ - <<: *if-dev-push changes: *patterns-example_get_started_button_power_save +.rules:build:example_get_started_knob_power_save: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-components_knob + - <<: *if-dev-push + changes: *patterns-example_get_started_knob_power_save + .rules:build:example_gprof_gprof_simple: rules: - <<: *if-protected @@ -1635,7 +1650,6 @@ - <<: *if-dev-push changes: *patterns-components_keyboard_button - .rules:build:components_expander_io_expander_mcp23017_test: rules: - <<: *if-protected diff --git a/components/knob/CHANGELOG.md b/components/knob/CHANGELOG.md index 8388a91de..63454a23e 100644 --- a/components/knob/CHANGELOG.md +++ b/components/knob/CHANGELOG.md @@ -1,5 +1,10 @@ # ChangeLog +## v0.1.5 - 2024-7-3 + +### Enhancements: +* Support power save mode + ## v0.1.4 - 2023-11-23 * Fix possible cmake_utilities dependency issue diff --git a/components/knob/CMakeLists.txt b/components/knob/CMakeLists.txt index 3c2e94a83..62e27a942 100644 --- a/components/knob/CMakeLists.txt +++ b/components/knob/CMakeLists.txt @@ -1,5 +1,5 @@ -idf_component_register(SRCS "iot_knob.c" - INCLUDE_DIRS "." +idf_component_register(SRCS "iot_knob.c" "knob_gpio.c" + INCLUDE_DIRS "include" REQUIRES driver PRIV_REQUIRES esp_timer) diff --git a/components/knob/idf_component.yml b/components/knob/idf_component.yml index aa9fa1d1a..785668abe 100644 --- a/components/knob/idf_component.yml +++ b/components/knob/idf_component.yml @@ -1,4 +1,4 @@ -version: "0.1.4" +version: "0.1.5" description: Knob driver implemented through software pcnt url: https://github.com/espressif/esp-iot-solution/tree/master/components/knob documentation: https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/knob.html @@ -9,3 +9,4 @@ dependencies: cmake_utilities: "0.*" examples: - path: ../../examples/usb/device/usb_surface_dial + - path: ../../examples/get-started/knob_power_save diff --git a/components/knob/iot_knob.h b/components/knob/include/iot_knob.h similarity index 80% rename from components/knob/iot_knob.h rename to components/knob/include/iot_knob.h index aaca33490..68f778d3f 100644 --- a/components/knob/iot_knob.h +++ b/components/knob/include/iot_knob.h @@ -6,6 +6,9 @@ #pragma once +#include +#include "esp_err.h" + #ifdef __cplusplus extern "C" { #endif @@ -24,6 +27,7 @@ typedef enum { KNOB_L_LIM, /*!< EVENT: Count reaches the minimum limit */ KNOB_ZERO, /*!< EVENT: Count back to 0 */ KNOB_EVENT_MAX, /*!< EVENT: Number of events */ + KNOB_NONE, /*!< EVENT: No event */ } knob_event_t; /** @@ -34,6 +38,7 @@ typedef struct { uint8_t default_direction; /*!< 0:positive increase 1:negative increase */ uint8_t gpio_encoder_a; /*!< Encoder Pin A */ uint8_t gpio_encoder_b; /*!< Encoder Pin B */ + bool enable_power_save; /*!< Enable power save mode */ } knob_config_t; /** @@ -110,6 +115,24 @@ int iot_knob_get_count_value(knob_handle_t knob_handle); */ esp_err_t iot_knob_clear_count_value(knob_handle_t knob_handle); +/** + * @brief resume knob timer, if knob timer is stopped. Make sure iot_knob_create() is called before calling this API. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE timer state is invalid. + */ +esp_err_t iot_knob_resume(void); + +/** + * @brief stop knob timer, if knob timer is running. Make sure iot_knob_create() is called before calling this API. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE timer state is invalid + */ +esp_err_t iot_knob_stop(void); + #ifdef __cplusplus } #endif diff --git a/components/knob/include/knob_gpio.h b/components/knob/include/knob_gpio.h new file mode 100644 index 000000000..9f2344761 --- /dev/null +++ b/components/knob/include/knob_gpio.h @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize a GPIO pin for knob input. + * + * This function configures a specified GPIO pin as an input for knob control. + * It sets the pin mode, disables interrupts, and enables the pull-up resistor. + * + * @param gpio_num The GPIO number to be configured. + * @return + * - ESP_OK: Configuration successful. + * - ESP_ERR_INVALID_ARG: Parameter error. + * - ESP_FAIL: Configuration failed. + */ +esp_err_t knob_gpio_init(uint32_t gpio_num); + +/** + * @brief Deinitialize a GPIO pin for knob input. + * + * This function resets the specified GPIO pin. + * + * @param gpio_num The GPIO number to be deinitialized. + * @return + * - ESP_OK: Reset successful. + * - ESP_FAIL: Reset failed. + */ +esp_err_t knob_gpio_deinit(uint32_t gpio_num); + +/** + * @brief Get the level of a GPIO pin. + * + * This function returns the current level (high or low) of the specified GPIO pin. + * + * @param gpio_num The GPIO number to read the level from. + * @return The level of the GPIO pin (0 or 1). + */ +uint8_t knob_gpio_get_key_level(void *gpio_num); + +/** + * @brief Initialize a GPIO pin with interrupt capability for knob input. + * + * This function configures a specified GPIO pin to trigger interrupts and installs + * an ISR service if not already installed. It adds an ISR handler for the GPIO pin. + * + * @param gpio_num The GPIO number to be configured. + * @param intr_type The type of interrupt to be configured. + * @param isr_handler The ISR handler function to be called on interrupt. + * @param args Arguments to be passed to the ISR handler. + * @return + * - ESP_OK: Configuration successful. + * - ESP_ERR_INVALID_ARG: Parameter error. + * - ESP_FAIL: Configuration failed. + */ +esp_err_t knob_gpio_init_intr(uint32_t gpio_num, gpio_int_type_t intr_type, gpio_isr_t isr_handler, void *args); + +/** + * @brief Set the interrupt type for a GPIO pin. + * + * This function sets the interrupt type for the specified GPIO pin. + * + * @param gpio_num The GPIO number to configure. + * @param intr_type The type of interrupt to be configured. + * @return + * - ESP_OK: Configuration successful. + * - ESP_ERR_INVALID_ARG: Parameter error. + * - ESP_FAIL: Configuration failed. + */ +esp_err_t knob_gpio_set_intr(uint32_t gpio_num, gpio_int_type_t intr_type); + +/** + * @brief Control the interrupt for a GPIO pin. + * + * This function enables or disables the interrupt for the specified GPIO pin. + * + * @param gpio_num The GPIO number to configure. + * @param enable Whether to enable or disable the interrupt. + * @return + * - ESP_OK: Configuration successful. + * - ESP_ERR_INVALID_ARG: Parameter error. + * - ESP_FAIL: Configuration failed. + */ +esp_err_t knob_gpio_intr_control(uint32_t gpio_num, bool enable); + +/** + * @brief Control the wake up functionality of GPIO pins. + * + * This function enables or disables the wake up functionality from GPIO pins. + * + * @param enable Whether to enable or disable the wake up functionality. + * @param wake_up_level Level of the GPIO when triggered. + * @param enable Enable or disable the GPIO wakeup. + * @return + * - ESP_OK: Operation successful. + * - ESP_FAIL: Operation failed. + */ +esp_err_t knob_gpio_wake_up_control(uint32_t gpio_num, uint8_t wake_up_level, bool enable); + +/** + * @brief Initialize wake up functionality for a GPIO pin. + * + * This function configures a specified GPIO pin to wake up the system from sleep + * based on the specified wake up level. + * + * @param gpio_num The GPIO number to configure. + * @param wake_up_level The level (0 or 1) to trigger wake up. + * @return + * - ESP_OK: Configuration successful. + * - ESP_ERR_INVALID_ARG: Parameter error. + * - ESP_FAIL: Configuration failed. + */ +esp_err_t knob_gpio_wake_up_init(uint32_t gpio_num, uint8_t wake_up_level); + +#ifdef __cplusplus +} +#endif diff --git a/components/knob/iot_knob.c b/components/knob/iot_knob.c index a8fd9e9a1..11a56dbfd 100644 --- a/components/knob/iot_knob.c +++ b/components/knob/iot_knob.c @@ -1,14 +1,16 @@ /* - * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include "driver/gpio.h" +#include "esp_attr.h" #include "esp_log.h" #include "esp_timer.h" #include "iot_knob.h" +#include "knob_gpio.h" static const char *TAG = "Knob"; @@ -36,6 +38,7 @@ typedef enum { typedef struct Knob { bool encoder_a_change; /*hal_knob_level(knob->encoder_a); uint8_t phb_value = knob->hal_knob_level(knob->encoder_b); - if ((knob->state) > 0) { knob->ticks++; } @@ -101,6 +103,8 @@ static void knob_handler(knob_dev_t *knob) knob->encoder_b_change = false; knob->ticks = 0; knob->state = KNOB_PHASE_B; + } else { + knob->event = KNOB_NONE; } break; @@ -138,6 +142,8 @@ static void knob_handler(knob_dev_t *knob) knob->encoder_a_change = false; knob->ticks = 0; knob->state = KNOB_READY; + } else { + knob->event = KNOB_NONE; } break; @@ -175,6 +181,8 @@ static void knob_handler(knob_dev_t *knob) knob->encoder_b_change = false; knob->ticks = 0; knob->state = KNOB_READY; + } else { + knob->event = KNOB_NONE; } break; @@ -183,83 +191,102 @@ static void knob_handler(knob_dev_t *knob) knob->state = KNOB_READY; knob->encoder_a_change = false; knob->encoder_b_change = false; + } else { + knob->event = KNOB_NONE; } break; } } -static esp_err_t _knob_gpio_init(uint8_t gpio_num) -{ - gpio_config_t gpio_cfg = { - .pin_bit_mask = (1ULL << gpio_num), - .mode = GPIO_MODE_INPUT, - .intr_type = GPIO_INTR_ANYEDGE, - .pull_up_en = 1, - }; - esp_err_t ret = gpio_config(&gpio_cfg); - - return ret; -} - -static esp_err_t _knob_gpio_deinit(int gpio_num) -{ - /** both disable pullup and pulldown */ - gpio_config_t gpio_conf = { - .intr_type = GPIO_INTR_DISABLE, - .mode = GPIO_MODE_INPUT, - .pin_bit_mask = (1ULL << gpio_num), - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .pull_up_en = GPIO_PULLUP_DISABLE, - }; - gpio_config(&gpio_conf); - return ESP_OK; -} - -static uint8_t _knob_gpio_get_key_level(void *gpio_num) -{ - return (uint8_t)gpio_get_level((uint32_t)gpio_num); -} - static void knob_cb(void *args) { knob_dev_t *target; + bool enter_power_save_flag = true; for (target = s_head_handle; target; target = target->next) { knob_handler(target); + if (!(target->enable_power_save && target->debounce_a_cnt == 0 && target->debounce_b_cnt == 0 && target->event == KNOB_NONE)) { + enter_power_save_flag = false; + } + } + if (enter_power_save_flag) { + /*!< Stop esp timer for power save */ + if (s_is_timer_running) { + esp_timer_stop(s_knob_timer_handle); + s_is_timer_running = false; + } + for (target = s_head_handle; target; target = target->next) { + if (target->enable_power_save) { + knob_gpio_wake_up_control((uint32_t)target->encoder_a, !target->encoder_a_level, true); + knob_gpio_wake_up_control((uint32_t)target->encoder_b, !target->encoder_b_level, true); + knob_gpio_set_intr((uint32_t)target->encoder_a, !target->encoder_a_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL); + knob_gpio_set_intr((uint32_t)target->encoder_b, !target->encoder_b_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL); + knob_gpio_intr_control((uint32_t)(target->encoder_a), true); + knob_gpio_intr_control((uint32_t)(target->encoder_b), true); + } + } } } +static void IRAM_ATTR knob_power_save_isr_handler(void* arg) +{ + if (!s_is_timer_running) { + esp_timer_start_periodic(s_knob_timer_handle, TICKS_INTERVAL * 1000U); + s_is_timer_running = true; + } + knob_gpio_intr_control((uint32_t)arg, false); + /*!< disable gpio wake up not need wake up level*/ + knob_gpio_wake_up_control((uint32_t)arg, 0, false); +} + knob_handle_t iot_knob_create(const knob_config_t *config) { KNOB_CHECK(NULL != config, "config pointer can't be NULL!", NULL) KNOB_CHECK(config->gpio_encoder_a != config->gpio_encoder_b, "encoder A can't be the same as encoder B", NULL); + + knob_dev_t *knob = (knob_dev_t *)calloc(1, sizeof(knob_dev_t)); + KNOB_CHECK(NULL != knob, "alloc knob failed", NULL); + esp_err_t ret = ESP_OK; - ret = _knob_gpio_init(config->gpio_encoder_a); + ret = knob_gpio_init(config->gpio_encoder_a); KNOB_CHECK(ESP_OK == ret, "encoder A gpio init failed", NULL); - ret = _knob_gpio_init(config->gpio_encoder_b); - KNOB_CHECK_GOTO(ESP_OK == ret, "encoder B gpio init failed", _encoder_a_deinit); + ret = knob_gpio_init(config->gpio_encoder_b); + KNOB_CHECK_GOTO(ESP_OK == ret, "encoder B gpio init failed", _encoder_deinit); - knob_dev_t *knob = (knob_dev_t *) calloc(1, sizeof(knob_dev_t)); - KNOB_CHECK_GOTO(NULL != knob, "alloc knob failed", _encoder_b_deinit); knob->default_direction = config->default_direction; - knob->hal_knob_level = _knob_gpio_get_key_level; + knob->hal_knob_level = knob_gpio_get_key_level; knob->encoder_a = (void *)(long)config->gpio_encoder_a; knob->encoder_b = (void *)(long)config->gpio_encoder_b; knob->encoder_a_level = knob->hal_knob_level(knob->encoder_a); knob->encoder_b_level = knob->hal_knob_level(knob->encoder_b); + if (config->enable_power_save) { + knob->enable_power_save = config->enable_power_save; + knob_gpio_init_intr(config->gpio_encoder_a, !knob->encoder_a_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL, knob_power_save_isr_handler, knob->encoder_a); + knob_gpio_init_intr(config->gpio_encoder_b, !knob->encoder_b_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL, knob_power_save_isr_handler, knob->encoder_b); + + ret = knob_gpio_wake_up_init(config->gpio_encoder_a, !knob->encoder_a_level); + KNOB_CHECK_GOTO(ESP_OK == ret, "encoder A wake up gpio init failed", _encoder_deinit); + ret = knob_gpio_wake_up_init(config->gpio_encoder_b, !knob->encoder_b_level); + KNOB_CHECK_GOTO(ESP_OK == ret, "encoder B wake up gpio init failed", _encoder_deinit); + } + knob->state = KNOB_CHECK; + knob->event = KNOB_NONE; knob->next = s_head_handle; s_head_handle = knob; - if (false == s_is_timer_running) { - esp_timer_create_args_t knob_timer; + if (!s_knob_timer_handle) { + esp_timer_create_args_t knob_timer = {0}; knob_timer.arg = NULL; knob_timer.callback = knob_cb; knob_timer.dispatch_method = ESP_TIMER_TASK; knob_timer.name = "knob_timer"; esp_timer_create(&knob_timer, &s_knob_timer_handle); + } + + if (!knob->enable_power_save && !s_is_timer_running) { esp_timer_start_periodic(s_knob_timer_handle, TICKS_INTERVAL * 1000U); s_is_timer_running = true; } @@ -267,10 +294,9 @@ knob_handle_t iot_knob_create(const knob_config_t *config) ESP_LOGI(TAG, "Iot Knob Config Succeed, encoder A:%d, encoder B:%d, direction:%d, Version: %d.%d.%d", config->gpio_encoder_a, config->gpio_encoder_b, config->default_direction, KNOB_VER_MAJOR, KNOB_VER_MINOR, KNOB_VER_PATCH); return (knob_handle_t)knob; -_encoder_b_deinit: - _knob_gpio_deinit(config->gpio_encoder_b); -_encoder_a_deinit: - _knob_gpio_deinit(config->gpio_encoder_a); +_encoder_deinit: + knob_gpio_deinit(config->gpio_encoder_b); + knob_gpio_deinit(config->gpio_encoder_a); return NULL; } @@ -279,7 +305,7 @@ esp_err_t iot_knob_delete(knob_handle_t knob_handle) esp_err_t ret = ESP_OK; KNOB_CHECK(NULL != knob_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); knob_dev_t *knob = (knob_dev_t *)knob_handle; - ret = _knob_gpio_deinit((int)(knob->usr_data)); + ret = knob_gpio_deinit((int)(knob->usr_data)); KNOB_CHECK(ESP_OK == ret, "knob deinit failed", ESP_FAIL); knob_dev_t **curr; for (curr = &s_head_handle; *curr;) { @@ -350,3 +376,25 @@ esp_err_t iot_knob_clear_count_value(knob_handle_t knob_handle) knob->count_value = 0; return ESP_OK; } + +esp_err_t iot_knob_resume(void) +{ + KNOB_CHECK(s_knob_timer_handle, "knob timer handle is invalid", ESP_ERR_INVALID_STATE); + KNOB_CHECK(!s_is_timer_running, "knob timer is already running", ESP_ERR_INVALID_STATE); + + esp_err_t err = esp_timer_start_periodic(s_knob_timer_handle, TICKS_INTERVAL * 1000U); + KNOB_CHECK(ESP_OK == err, "knob timer start failed", ESP_FAIL); + s_is_timer_running = true; + return ESP_OK; +} + +esp_err_t iot_knob_stop(void) +{ + KNOB_CHECK(s_knob_timer_handle, "knob timer handle is invalid", ESP_ERR_INVALID_STATE); + KNOB_CHECK(s_is_timer_running, "knob timer is not running", ESP_ERR_INVALID_STATE); + + esp_err_t err = esp_timer_stop(s_knob_timer_handle); + KNOB_CHECK(ESP_OK == err, "knob timer stop failed", ESP_FAIL); + s_is_timer_running = false; + return ESP_OK; +} diff --git a/components/knob/knob_gpio.c b/components/knob/knob_gpio.c new file mode 100644 index 000000000..c530c8c51 --- /dev/null +++ b/components/knob/knob_gpio.c @@ -0,0 +1,85 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_sleep.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "knob_gpio.h" + +static const char *TAG = "knob gpio"; + +esp_err_t knob_gpio_init(uint32_t gpio_num) +{ + gpio_config_t gpio_cfg = { + .pin_bit_mask = (1ULL << gpio_num), + .mode = GPIO_MODE_INPUT, + .intr_type = GPIO_INTR_DISABLE, + .pull_up_en = 1, + }; + esp_err_t ret = gpio_config(&gpio_cfg); + + return ret; +} + +esp_err_t knob_gpio_deinit(uint32_t gpio_num) +{ + return gpio_reset_pin(gpio_num); +} + +uint8_t knob_gpio_get_key_level(void *gpio_num) +{ + return (uint8_t)gpio_get_level((uint32_t)gpio_num); +} + +esp_err_t knob_gpio_init_intr(uint32_t gpio_num, gpio_int_type_t intr_type, gpio_isr_t isr_handler, void *args) +{ + static bool isr_service_installed = false; + gpio_set_intr_type(gpio_num, intr_type); + if (!isr_service_installed) { + gpio_install_isr_service(ESP_INTR_FLAG_IRAM); + isr_service_installed = true; + } + gpio_isr_handler_add(gpio_num, isr_handler, args); + return ESP_OK; +} + +esp_err_t knob_gpio_set_intr(uint32_t gpio_num, gpio_int_type_t intr_type) +{ + return gpio_set_intr_type(gpio_num, intr_type); +} + +esp_err_t knob_gpio_intr_control(uint32_t gpio_num, bool enable) +{ + if (enable) { + gpio_intr_enable(gpio_num); + } else { + gpio_intr_disable(gpio_num); + } + return ESP_OK; +} + +esp_err_t knob_gpio_wake_up_control(uint32_t gpio_num, uint8_t wake_up_level, bool enable) +{ + esp_err_t ret; + if (enable) { + ret = gpio_wakeup_enable(gpio_num, wake_up_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL); + } else { + ret = gpio_wakeup_disable(gpio_num); + } + return ret; +} + +esp_err_t knob_gpio_wake_up_init(uint32_t gpio_num, uint8_t wake_up_level) +{ + /* Enable wake up from GPIO */ + esp_err_t ret = gpio_wakeup_enable(gpio_num, wake_up_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL); + ESP_RETURN_ON_FALSE(ret == ESP_OK, ESP_FAIL, TAG, "Enable gpio wakeup failed"); + ret = esp_sleep_enable_gpio_wakeup(); + ESP_RETURN_ON_FALSE(ret == ESP_OK, ESP_FAIL, TAG, "esp sleep enable gpio wakeup failed"); + + return ESP_OK; +} diff --git a/components/knob/test_apps/main/knob_test.c b/components/knob/test_apps/main/knob_test.c index 15f59d178..138dc69aa 100644 --- a/components/knob/test_apps/main/knob_test.c +++ b/components/knob/test_apps/main/knob_test.c @@ -144,6 +144,6 @@ void tearDown(void) void app_main(void) { - printf("USB STREAM TEST \n"); + printf("IOT KNOB TEST\n"); unity_run_menu(); } diff --git a/components/knob/test_apps/sdkconfig.defaults b/components/knob/test_apps/sdkconfig.defaults new file mode 100644 index 000000000..3778471bc --- /dev/null +++ b/components/knob/test_apps/sdkconfig.defaults @@ -0,0 +1,9 @@ +# For IDF 5.0 +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_EN=n + +# For IDF4.4 +CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP_TASK_WDT=n diff --git a/docs/Doxyfile b/docs/Doxyfile index b62e0da3c..1f8c9b393 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -42,7 +42,7 @@ INPUT = \ $(PROJECT_PATH)/components/i2c_bus/include/i2c_bus.h \ $(PROJECT_PATH)/components/ir/ir_learn/include/ir_learn.h \ $(PROJECT_PATH)/components/keyboard_button/include/keyboard_button.h \ - $(PROJECT_PATH)/components/knob/iot_knob.h \ + $(PROJECT_PATH)/components/knob/include/iot_knob.h \ $(PROJECT_PATH)/components/led/led_indicator/include/led_indicator.h \ $(PROJECT_PATH)/components/motor/esp_sensorless_bldc_control/control/include/bldc_control_param.h \ $(PROJECT_PATH)/components/motor/esp_sensorless_bldc_control/include/bldc_control.h \ diff --git a/docs/_static/input_device/knob/knob_hardware.png b/docs/_static/input_device/knob/knob_hardware.png new file mode 100644 index 000000000..4a269c4b5 Binary files /dev/null and b/docs/_static/input_device/knob/knob_hardware.png differ diff --git a/docs/_static/input_device/knob/knob_one_cycle.png b/docs/_static/input_device/knob/knob_one_cycle.png new file mode 100644 index 000000000..d83f37b31 Binary files /dev/null and b/docs/_static/input_device/knob/knob_one_cycle.png differ diff --git a/docs/_static/input_device/knob/knob_power_save_one_cycle.png b/docs/_static/input_device/knob/knob_power_save_one_cycle.png new file mode 100644 index 000000000..a7bc8fc64 Binary files /dev/null and b/docs/_static/input_device/knob/knob_power_save_one_cycle.png differ diff --git a/docs/_static/input_device/knob/knob_power_save_ten_cycle.png b/docs/_static/input_device/knob/knob_power_save_ten_cycle.png new file mode 100644 index 000000000..85a68bae5 Binary files /dev/null and b/docs/_static/input_device/knob/knob_power_save_ten_cycle.png differ diff --git a/docs/en/input_device/knob.rst b/docs/en/input_device/knob.rst index 7dd887a39..c17c6bc27 100644 --- a/docs/en/input_device/knob.rst +++ b/docs/en/input_device/knob.rst @@ -12,6 +12,14 @@ This is suitable for low-speed rotary knob counting scenarios where the pulse ra .. Note:: For precise or fast pulse counting, please use the `hardware PCNT function `_. The hardware PCNT is supported by ESP32, ESP32-C6, ESP32-H2, ESP32-S2, ESP32-S3 chips. +Hardware Design +---------------- + +The reference design for the rotary encoder is shown below: + +.. figure:: ../../_static/input_device/knob/knob_hardware.png + :width: 650 + Knob Event ----------- @@ -73,10 +81,56 @@ Register callback function static void _knob_left_cb(void *arg, void *data) { - ESP_LOGI(TAG, "KONB: KONB_LEFT,count_value:%"PRId32"",iot_knob_get_count_value((button_handle_t)arg)); + ESP_LOGI(TAG, "KNOB: KNOB_LEFT,count_value:%"PRId32"",iot_knob_get_count_value((button_handle_t)arg)); } iot_knob_register_cb(s_knob, KNOB_LEFT, _knob_left_cb, NULL); +Low Power Support +------------------- + +In light_sleep mode, the `esp_timer` wakes up the CPU, resulting in high power consumption. The Knob component offers a low power solution through GPIO level wake-up. + +Required Configuration: + +- Enable the `enable_power_save` option in `knob_config_t`. + +Power Consumption Comparison: + +- Without low power mode, one rotation within 250ms + + .. figure:: ../../_static/input_device/knob/knob_one_cycle.png + :align: center + :width: 70% + :alt: One rotation without low power mode + +- With low power mode, one rotation within 250ms + + .. figure:: ../../_static/input_device/knob/knob_power_save_one_cycle.png + :align: center + :width: 70% + :alt: One rotation with low power mode + +- With low power mode, ten rotations within 4.5s + + .. figure:: ../../_static/input_device/knob/knob_power_save_ten_cycle.png + :align: center + :width: 70% + :alt: Ten rotations with low power mode + +The knob is responsive and consumes less power in low power mode. + +Enable and Disable +------------------- + +The component supports enabling and disabling at any time. + +.. code:: c + + // Stop knob + iot_knob_stop(); + // Resume knob + iot_knob_resume(); + API Reference ----------------- diff --git a/docs/zh_CN/input_device/knob.rst b/docs/zh_CN/input_device/knob.rst index dd17959c5..23a3ebf09 100644 --- a/docs/zh_CN/input_device/knob.rst +++ b/docs/zh_CN/input_device/knob.rst @@ -13,6 +13,14 @@ .. Note:: 如需精确或快速的脉冲计数,请使用 `硬件 PCNT 功能 `_ 。硬件 PCNT 支持的芯片 ESP32, ESP32-C6, ESP32-H2, ESP32-S2, ESP32-S3。 +硬件设计 +--------- + +旋转编码器的参考设计如下: + +.. figure:: ../../_static/input_device/knob/knob_hardware.png + :width: 650 + 旋钮事件 --------- @@ -74,10 +82,56 @@ static void _knob_left_cb(void *arg, void *data) { - ESP_LOGI(TAG, "KONB: KONB_LEFT,count_value:%"PRId32"",iot_knob_get_count_value((button_handle_t)arg)); + ESP_LOGI(TAG, "KNOB: KNOB_LEFT,count_value:%"PRId32"",iot_knob_get_count_value((button_handle_t)arg)); } iot_knob_register_cb(s_knob, KNOB_LEFT, _knob_left_cb, NULL); +低功耗支持 +^^^^^^^^^^^^^^^ + +在 light_sleep 模式下,esp_timer 定时器会唤醒 CPU,导致功耗居高不下,Knob 组件提供了通过 GPIO 电平唤醒的低功耗方案。 + +所需配置: + +- 在 ``knob_config_t`` 中打开 ``enable_power_save`` 选项 + +功耗对比: + +- 未开启低功耗模式,在 250ms 内旋转一次 + + .. figure:: ../../_static/input_device/knob/knob_one_cycle.png + :align: center + :width: 70% + :alt: 未开启低功耗模式,一次旋转 + +- 开启低功耗模式,在 250ms 内旋转一次 + + .. figure:: ../../_static/input_device/knob/knob_power_save_one_cycle.png + :align: center + :width: 70% + :alt: 开启低功耗模式,旋转一次 + +- 开启低功耗模式,在 4.5s 内旋转十次 + + .. figure:: ../../_static/input_device/knob/knob_power_save_ten_cycle.png + :align: center + :width: 70% + :alt: 开启低功耗模式,旋转十次 + +低功耗模式下的旋钮响应迅速,且功耗更低 + +开启和关闭 +^^^^^^^^^^^^^ + +组件支持在任意时刻开启和关闭。 + +.. code:: c + + // stop knob + iot_knob_stop(); + // resume knob + iot_knob_resume(); + API Reference ----------------- diff --git a/examples/.build-rules.yml b/examples/.build-rules.yml index ff264403f..533e1798b 100644 --- a/examples/.build-rules.yml +++ b/examples/.build-rules.yml @@ -168,6 +168,10 @@ examples/get-started/button_power_save: enable: - if: INCLUDE_DEFAULT == 1 +examples/get-started/knob_power_save: + enable: + - if: INCLUDE_DEFAULT == 1 + examples/indicator: enable: - if: INCLUDE_DEFAULT == 1 diff --git a/examples/get-started/knob_power_save/CMakeLists.txt b/examples/get-started/knob_power_save/CMakeLists.txt new file mode 100644 index 000000000..b8af71b6d --- /dev/null +++ b/examples/get-started/knob_power_save/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(knob_power_save) diff --git a/examples/get-started/knob_power_save/README.md b/examples/get-started/knob_power_save/README.md new file mode 100644 index 000000000..450484ff4 --- /dev/null +++ b/examples/get-started/knob_power_save/README.md @@ -0,0 +1,47 @@ +## Knob Power Save Example + +This example demonstrates how to utilize the `knob` component in conjunction with the light sleep low-power mode. + +* `knob` [Component Introduction](https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/knob.html) + +## Hardware + +Any GPIO on any development board can be used in this example, for the default GPIO, please refer to the table below. + +| Hardware | GPIO | +| :-----------------: | :---: | +| Encoder A (Phase A) | GPIO1 | +| Encoder B (Phase B) | GPIO2 | + +## Build and Flash + +Build the project and flash it to the board, then run the monitor tool to view the serial output: + +* Run `. ./export.sh` to set IDF environment +* Run `idf.py set-target esp32xx` to set target chip +* Run `idf.py -p PORT flash monitor` to build, flash and monitor the project + +(To exit the serial monitor, type `Ctrl-]`.) + +See the Getting Started Guide for all the steps to configure and use the ESP-IDF to build projects. + +## Example Output + +``` +I (316) pm: Frequency switching config: CPU_MAX: 80, APB_MAX: 80, APB_MIN: 10, Light sleep: ENABLED +I (322) sleep: Code start at 0x42000020, total 106515, data start at 0x3c020020, total 46384 Bytes +I (331) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (341) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (350) Knob: Iot Knob Config Succeed, encoder A:1, encoder B:2, direction:0, Version: 0.1.4 +I (359) main_task: Returned from app_main() +I (1503) knob_power_save: knob event KNOB_LEFT, -1 +I (1503) knob_power_save: Wake up from light sleep, reason 7 +I (1691) knob_power_save: knob event KNOB_LEFT, -2 +I (1691) knob_power_save: Wake up from light sleep, reason 7 +I (1860) knob_power_save: knob event KNOB_LEFT, -3 +I (1860) knob_power_save: Wake up from light sleep, reason 7 +I (1940) knob_power_save: knob event KNOB_LEFT, -4 +I (1940) knob_power_save: Wake up from light sleep, reason 7 +I (2017) knob_power_save: knob event KNOB_LEFT, -5 +I (2017) knob_power_save: Wake up from light sleep, reason 7 +``` \ No newline at end of file diff --git a/examples/get-started/knob_power_save/main/CMakeLists.txt b/examples/get-started/knob_power_save/main/CMakeLists.txt new file mode 100644 index 000000000..c64135986 --- /dev/null +++ b/examples/get-started/knob_power_save/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "knob_power_save.c" + INCLUDE_DIRS ".") diff --git a/examples/get-started/knob_power_save/main/Kconfig.projbuild b/examples/get-started/knob_power_save/main/Kconfig.projbuild new file mode 100644 index 000000000..a5cec22d7 --- /dev/null +++ b/examples/get-started/knob_power_save/main/Kconfig.projbuild @@ -0,0 +1,74 @@ +menu "Example Configuration" + + choice EXAMPLE_MAX_CPU_FREQ + prompt "Maximum CPU frequency" + default EXAMPLE_MAX_CPU_FREQ_80 if !IDF_TARGET_ESP32H2 + default EXAMPLE_MAX_CPU_FREQ_96 if IDF_TARGET_ESP32H2 + depends on PM_ENABLE + help + Maximum CPU frequency to use for dynamic frequency scaling. + + config EXAMPLE_MAX_CPU_FREQ_80 + bool "80 MHz" + config EXAMPLE_MAX_CPU_FREQ_96 + bool "96 MHz" + depends on IDF_TARGET_ESP32H2 + config EXAMPLE_MAX_CPU_FREQ_120 + bool "120 MHz" + depends on IDF_TARGET_ESP32C2 + config EXAMPLE_MAX_CPU_FREQ_160 + bool "160 MHz" + depends on !IDF_TARGET_ESP32C2 + config EXAMPLE_MAX_CPU_FREQ_240 + bool "240 MHz" + depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 + endchoice + + config EXAMPLE_MAX_CPU_FREQ_MHZ + int + default 80 if EXAMPLE_MAX_CPU_FREQ_80 + default 96 if EXAMPLE_MAX_CPU_FREQ_96 + default 120 if EXAMPLE_MAX_CPU_FREQ_120 + default 160 if EXAMPLE_MAX_CPU_FREQ_160 + default 240 if EXAMPLE_MAX_CPU_FREQ_240 + + choice EXAMPLE_MIN_CPU_FREQ + prompt "Minimum CPU frequency" + default EXAMPLE_MIN_CPU_FREQ_10M if !IDF_TARGET_ESP32H2 + default EXAMPLE_MIN_CPU_FREQ_32M if IDF_TARGET_ESP32H2 + depends on PM_ENABLE + help + Minimum CPU frequency to use for dynamic frequency scaling. + Should be set to XTAL frequency or XTAL frequency divided by integer. + + config EXAMPLE_MIN_CPU_FREQ_40M + bool "40 MHz (use with 40MHz XTAL)" + depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO || !IDF_TARGET_ESP32 + config EXAMPLE_MIN_CPU_FREQ_20M + bool "20 MHz (use with 40MHz XTAL)" + depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO || !IDF_TARGET_ESP32 + config EXAMPLE_MIN_CPU_FREQ_10M + bool "10 MHz (use with 40MHz XTAL)" + depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO || !IDF_TARGET_ESP32 + config EXAMPLE_MIN_CPU_FREQ_26M + bool "26 MHz (use with 26MHz XTAL)" + depends on XTAL_FREQ_26 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_26 || ESP32_XTAL_FREQ_AUTO + config EXAMPLE_MIN_CPU_FREQ_13M + bool "13 MHz (use with 26MHz XTAL)" + depends on XTAL_FREQ_26 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_26 || ESP32_XTAL_FREQ_AUTO + config EXAMPLE_MIN_CPU_FREQ_32M + bool "32 MHz (use with 32MHz XTAL)" + depends on IDF_TARGET_ESP32H2 + depends on XTAL_FREQ_32 || XTAL_FREQ_AUTO + endchoice + + config EXAMPLE_MIN_CPU_FREQ_MHZ + int + default 40 if EXAMPLE_MIN_CPU_FREQ_40M + default 20 if EXAMPLE_MIN_CPU_FREQ_20M + default 10 if EXAMPLE_MIN_CPU_FREQ_10M + default 26 if EXAMPLE_MIN_CPU_FREQ_26M + default 13 if EXAMPLE_MIN_CPU_FREQ_13M + default 32 if EXAMPLE_MIN_CPU_FREQ_32M + +endmenu diff --git a/examples/get-started/knob_power_save/main/idf_component.yml b/examples/get-started/knob_power_save/main/idf_component.yml new file mode 100644 index 000000000..a23f20a0d --- /dev/null +++ b/examples/get-started/knob_power_save/main/idf_component.yml @@ -0,0 +1,6 @@ +version: "0.0.1" +dependencies: + idf: ">=4.4" + knob: + version: "*" + override_path: "../../../../components/knob" diff --git a/examples/get-started/knob_power_save/main/knob_power_save.c b/examples/get-started/knob_power_save/main/knob_power_save.c new file mode 100644 index 000000000..c9c86485d --- /dev/null +++ b/examples/get-started/knob_power_save/main/knob_power_save.c @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_pm.h" +#include "iot_knob.h" +#include "esp_sleep.h" +#include "esp_idf_version.h" + +#define ENCODER_A_GPIO 1 +#define ENCODER_B_GPIO 2 + +static const char *TAG = "knob_power_save"; + +static knob_handle_t knob = NULL; + +const char *knob_event_table[] = { + "KNOB_LEFT", + "KNOB_RIGHT", + "KNOB_H_LIM", + "KNOB_L_LIM", + "KNOB_ZERO", +}; + +static void knob_event_cb(void *arg, void *data) +{ + ESP_LOGI(TAG, "knob event %s, %d", knob_event_table[(knob_event_t)data], iot_knob_get_count_value(knob)); + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + if (cause != ESP_SLEEP_WAKEUP_UNDEFINED) { + ESP_LOGI(TAG, "Wake up from light sleep, reason %d", cause); + } +} + +static void knob_init(uint32_t encoder_a, uint32_t encoder_b) +{ + knob_config_t cfg = { + .default_direction = 0, + .gpio_encoder_a = encoder_a, + .gpio_encoder_b = encoder_b, +#if CONFIG_PM_ENABLE + .enable_power_save = true, +#endif + }; + + knob = iot_knob_create(&cfg); + assert(knob); + esp_err_t err = iot_knob_register_cb(knob, KNOB_LEFT, knob_event_cb, (void *)KNOB_LEFT); + err |= iot_knob_register_cb(knob, KNOB_RIGHT, knob_event_cb, (void *)KNOB_RIGHT); + err |= iot_knob_register_cb(knob, KNOB_H_LIM, knob_event_cb, (void *)KNOB_H_LIM); + err |= iot_knob_register_cb(knob, KNOB_L_LIM, knob_event_cb, (void *)KNOB_L_LIM); + err |= iot_knob_register_cb(knob, KNOB_ZERO, knob_event_cb, (void *)KNOB_ZERO); + ESP_ERROR_CHECK(err); +} + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) +void power_save_init(void) +{ + esp_pm_config_t pm_config = { + .max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ, + .min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ, +#if CONFIG_FREERTOS_USE_TICKLESS_IDLE + .light_sleep_enable = true +#endif + }; + ESP_ERROR_CHECK(esp_pm_configure(&pm_config)); +} +#else +void power_save_init(void) +{ +#if CONFIG_IDF_TARGET_ESP32 + esp_pm_config_esp32_t pm_config = { +#elif CONFIG_IDF_TARGET_ESP32S2 + esp_pm_config_esp32s2_t pm_config = { +#elif CONFIG_IDF_TARGET_ESP32C3 + esp_pm_config_esp32c3_t pm_config = { +#elif CONFIG_IDF_TARGET_ESP32S3 + esp_pm_config_esp32s3_t pm_config = { +#elif CONFIG_IDF_TARGET_ESP32C2 + esp_pm_config_esp32c2_t pm_config = { +#endif + .max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ, + .min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ, +#if CONFIG_FREERTOS_USE_TICKLESS_IDLE + .light_sleep_enable = true +#endif + }; + ESP_ERROR_CHECK(esp_pm_configure(&pm_config)); +} +#endif + +void app_main(void) +{ + power_save_init(); + knob_init(ENCODER_A_GPIO, ENCODER_B_GPIO); +} diff --git a/examples/get-started/knob_power_save/sdkconfig.defaults b/examples/get-started/knob_power_save/sdkconfig.defaults new file mode 100644 index 000000000..538b12f6e --- /dev/null +++ b/examples/get-started/knob_power_save/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Enable support for power management +CONFIG_PM_ENABLE=y +# Enable tickless idle mode +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +# Put related source code in IRAM +CONFIG_PM_SLP_IRAM_OPT=y +CONFIG_PM_RTOS_IDLE_OPT=y +# Use 1000Hz freertos tick to lower sleep time threshold +CONFIG_FREERTOS_HZ=1000