diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index 6123019db..cc62a2df0 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -282,6 +282,18 @@ build_example_get_started_blink: EXAMPLE_DIR: examples/get-started/blink IMAGE: espressif/idf:release-v4.4 +build_example_get_started_button_power_save: + extends: + - .build_examples_template + - .rules:build:example_get_started_button_power_save + parallel: + matrix: + - IMAGE: espressif/idf:release-v4.4 + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:latest + variables: + EXAMPLE_DIR: examples/get-started/button_power_save + build_example_gprof_gprof_simple: extends: - .build_examples_template diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index 788c4e460..79fa6069e 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -304,6 +304,9 @@ .patterns-example_get_started_blink: &patterns-example_get_started_blink - "examples/get-started/blink/**/*" +.patterns-example_get_started_button_power_save: &patterns-example_get_started_button_power_save + - "examples/get-started/button_power_save/**/*" + .patterns-example_lighting_lightbulb: &patterns-example_lighting_lightbulb - "examples/lighting/lightbulb/**/*" @@ -637,6 +640,18 @@ - <<: *if-dev-push changes: *patterns-example_get_started_blink +.rules:build:example_get_started_button_power_save: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-components_button + - <<: *if-dev-push + changes: *patterns-example_get_started_button_power_save + .rules:build:example_gprof_gprof_simple: rules: - <<: *if-protected diff --git a/components/button/CHANGELOG.md b/components/button/CHANGELOG.md index 07a77bcb0..3c37f3590 100644 --- a/components/button/CHANGELOG.md +++ b/components/button/CHANGELOG.md @@ -1,5 +1,12 @@ # ChangeLog + +## v3.2.0 - 2023-11-13 + +### Enhancements: + +* The power consumption of GPIO buttons is lower during light sleep mode. + ## v3.1.3 - 2023-11-13 * Resolved issue 'ADC_ATTEN_DB_11 is deprecated'. diff --git a/components/button/Kconfig b/components/button/Kconfig index f12a41e6e..352600545 100644 --- a/components/button/Kconfig +++ b/components/button/Kconfig @@ -9,7 +9,7 @@ menu "IoT Button" config BUTTON_DEBOUNCE_TICKS int "BUTTON DEBOUNCE TICKS" - range 1 8 + range 1 7 default 2 help "One CONFIG_BUTTON_DEBOUNCE_TICKS equal to CONFIG_BUTTON_PERIOD_TIME_MS" @@ -35,6 +35,16 @@ menu "IoT Button" help "Serial trigger interval" + config GPIO_BUTTON_SUPPORT_POWER_SAVE + bool "GPIO BUTTON SUPPORT POWER SAVE" + default n + help + Enable GPIO button power save + + The function enables the use of GPIO buttons during light sleep, + but enabling this function prevents the simultaneous use of other + types of buttons. + config ADC_BUTTON_MAX_CHANNEL int "ADC BUTTON MAX CHANNEL" range 1 5 diff --git a/components/button/README.md b/components/button/README.md index c437d14d6..96f3e4fc3 100644 --- a/components/button/README.md +++ b/components/button/README.md @@ -22,7 +22,15 @@ List of supported events: There are three ways this driver can handle buttons: 1. Buttons connected to standard digital GPIO 2. Multiple buttons connected to single ADC channel -3. Custom button connect to any driver +3. Matrix keyboard employs multiple GPIOs for operation. +4. Custom button connect to any driver + +The component supports the following functionalities: +1. Creation of an unlimited number of buttons, accommodating various types simultaneously. +2. Multiple callback functions for a single event. +3. Allowing customization of the consecutive key press count to any desired number. +4. Facilitating the setup of callbacks for any specified long-press duration. +5. Support power save mode (Only for gpio button) ## Add component to your project diff --git a/components/button/button_gpio.c b/components/button/button_gpio.c index a0a56043a..aecc9b5a5 100644 --- a/components/button/button_gpio.c +++ b/components/button/button_gpio.c @@ -6,6 +6,7 @@ #include "esp_log.h" #include "driver/gpio.h" #include "button_gpio.h" +#include "esp_sleep.h" static const char *TAG = "gpio button"; @@ -34,6 +35,16 @@ esp_err_t button_gpio_init(const button_gpio_config_t *config) } gpio_config(&gpio_conf); +#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE + if (config->enable_power_save) { + /* Enable wake up from GPIO */ + esp_err_t ret = gpio_wakeup_enable(config->gpio_num, config->active_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL); + GPIO_BTN_CHECK(ret == ESP_OK, "Enable gpio wakeup failed", ESP_FAIL); + ret = esp_sleep_enable_gpio_wakeup(); + GPIO_BTN_CHECK(ret == ESP_OK, "Configure gpio as wakeup source failed", ESP_FAIL); + } +#endif + return ESP_OK; } @@ -46,3 +57,25 @@ uint8_t button_gpio_get_key_level(void *gpio_num) { return (uint8_t)gpio_get_level((uint32_t)gpio_num); } + +esp_err_t button_gpio_set_intr(int 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 button_gpio_intr_control(int gpio_num, bool enable) +{ + if (enable) { + gpio_intr_enable(gpio_num); + } else { + gpio_intr_disable(gpio_num); + } + return ESP_OK; +} diff --git a/components/button/idf_component.yml b/components/button/idf_component.yml index eede59bad..c1e294289 100644 --- a/components/button/idf_component.yml +++ b/components/button/idf_component.yml @@ -1,9 +1,11 @@ -version: "3.1.3" +version: "3.2.0" description: GPIO and ADC button driver url: https://github.com/espressif/esp-iot-solution/tree/master/components/button repository: https://github.com/espressif/esp-iot-solution.git documentation: https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/button.html issues: https://github.com/espressif/esp-iot-solution/issues +examples: + - path: ../../examples/get-started/button_power_save dependencies: idf: ">=4.0" cmake_utilities: "0.*" diff --git a/components/button/include/button_gpio.h b/components/button/include/button_gpio.h index fb8bef45e..38171963a 100644 --- a/components/button/include/button_gpio.h +++ b/components/button/include/button_gpio.h @@ -1,4 +1,4 @@ -/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,6 +18,9 @@ extern "C" { typedef struct { int32_t gpio_num; /**< num of gpio */ uint8_t active_level; /**< gpio level when press down */ +#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE + bool enable_power_save; /**< enable power save mode */ +#endif } button_gpio_config_t; /** @@ -49,6 +52,26 @@ esp_err_t button_gpio_deinit(int gpio_num); */ uint8_t button_gpio_get_key_level(void *gpio_num); +/** + * @brief Sets up interrupt for GPIO button. + * + * @param gpio_num gpio number of button + * @param intr_type The type of GPIO interrupt. + * @param isr_handler The ISR (Interrupt Service Routine) handler function. + * @param args Arguments to be passed to the ISR handler function. + * @return Always return ESP_OK + */ +esp_err_t button_gpio_set_intr(int gpio_num, gpio_int_type_t intr_type, gpio_isr_t isr_handler, void *args); + +/** + * @brief Enable or disable interrupt for GPIO button. + * + * @param gpio_num gpio number of button + * @param enable enable or disable + * @return Always return ESP_OK + */ +esp_err_t button_gpio_intr_control(int gpio_num, bool enable); + #ifdef __cplusplus } #endif diff --git a/components/button/include/iot_button.h b/components/button/include/iot_button.h index d8eddd8ab..0c68441d1 100644 --- a/components/button/include/iot_button.h +++ b/components/button/include/iot_button.h @@ -1,4 +1,4 @@ -/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -268,6 +268,16 @@ uint16_t iot_button_get_long_press_hold_cnt(button_handle_t btn_handle); */ esp_err_t iot_button_set_param(button_handle_t btn_handle, button_param_t param, void *value); +/** + * @brief Get button key level + * + * @param btn_handle Button handle + * @return + * - 1 if key is pressed + * - 0 if key is released or invalid button handle + */ +uint8_t iot_button_get_key_level(button_handle_t btn_handle); + /** * @brief resume button timer, if button timer is stopped. Make sure iot_button_create() is called before calling this API. * diff --git a/components/button/iot_button.c b/components/button/iot_button.c index 28b326aca..e2cc12d56 100644 --- a/components/button/iot_button.c +++ b/components/button/iot_button.c @@ -1,4 +1,4 @@ -/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,10 +9,13 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/timers.h" -#include "esp_log.h" #include "driver/gpio.h" -#include "iot_button.h" #include "esp_timer.h" +#include "esp_log.h" +#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE +#include "esp_pm.h" +#endif +#include "iot_button.h" #include "sdkconfig.h" static const char *TAG = "button"; @@ -41,25 +44,26 @@ typedef struct { * */ typedef struct Button { - uint16_t ticks; - uint16_t long_press_ticks; /*! Trigger ticks for long press*/ - uint16_t short_press_ticks; /*! Trigger ticks for repeat press*/ - uint16_t long_press_hold_cnt; /*! Record long press hold count*/ - uint16_t long_press_ticks_default; - uint8_t repeat; - uint8_t state: 3; - uint8_t debounce_cnt: 3; - uint8_t active_level: 1; - uint8_t button_level: 1; - button_event_t event; + uint16_t ticks; + uint16_t long_press_ticks; /*! Trigger ticks for long press*/ + uint16_t short_press_ticks; /*! Trigger ticks for repeat press*/ + uint16_t long_press_hold_cnt; /*! Record long press hold count*/ + uint16_t long_press_ticks_default; + uint8_t repeat; + uint8_t state: 3; + uint8_t debounce_cnt: 3; + uint8_t active_level: 1; + uint8_t button_level: 1; + uint8_t enable_power_save: 1; + button_event_t event; uint8_t (*hal_button_Level)(void *hardware_data); esp_err_t (*hal_button_deinit)(void *hardware_data); - void *hardware_data; - button_type_t type; - button_cb_info_t *cb_info[BUTTON_EVENT_MAX]; - size_t size[BUTTON_EVENT_MAX]; - int count[2]; - struct Button *next; + void *hardware_data; + button_type_t type; + button_cb_info_t *cb_info[BUTTON_EVENT_MAX]; + size_t size[BUTTON_EVENT_MAX]; + int count[2]; + struct Button *next; } button_dev_t; //button handle list head. @@ -289,11 +293,43 @@ static void button_handler(button_dev_t *btn) static void button_cb(void *args) { button_dev_t *target; + /*!< When all buttons enter the BUTTON_NONE_PRESS state, the system enters low-power mode */ +#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE + bool enter_power_save_flag = true; +#endif for (target = g_head_handle; target; target = target->next) { button_handler(target); +#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE + if (!(target->enable_power_save && target->debounce_cnt == 0 && target->event == BUTTON_NONE_PRESS)) { + enter_power_save_flag = false; + } +#endif + } +#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE + if (enter_power_save_flag) { + /*!< Stop esp timer for power save */ + esp_timer_stop(g_button_timer_handle); + g_is_timer_running = false; + for (target = g_head_handle; target; target = target->next) { + if (target->type == BUTTON_TYPE_GPIO && target->enable_power_save) { + button_gpio_intr_control((int)(target->hardware_data), true); + } + } } +#endif } +#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE +static void IRAM_ATTR button_power_save_isr_handler(void* arg) +{ + if (!g_is_timer_running) { + esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U); + g_is_timer_running = true; + } + button_gpio_intr_control((int)arg, false); +} +#endif + static button_dev_t *button_create_com(uint8_t active_level, uint8_t (*hal_get_key_state)(void *hardware_data), void *hardware_data, uint16_t long_press_ticks, uint16_t short_press_ticks) { BTN_CHECK(NULL != hal_get_key_state, "Function pointer is invalid", NULL); @@ -313,15 +349,13 @@ static button_dev_t *button_create_com(uint8_t active_level, uint8_t (*hal_get_k btn->next = g_head_handle; g_head_handle = btn; - if (false == g_is_timer_running) { - esp_timer_create_args_t button_timer; + if (!g_button_timer_handle) { + esp_timer_create_args_t button_timer = {0}; button_timer.arg = NULL; button_timer.callback = button_cb; button_timer.dispatch_method = ESP_TIMER_TASK; button_timer.name = "button_timer"; esp_timer_create(&button_timer, &g_button_timer_handle); - esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U); - g_is_timer_running = true; } return btn; @@ -354,6 +388,7 @@ static esp_err_t button_delete_com(button_dev_t *btn) if (0 == number && g_is_timer_running) { /**< if all button is deleted, stop the timer */ esp_timer_stop(g_button_timer_handle); esp_timer_delete(g_button_timer_handle); + g_button_timer_handle = NULL; g_is_timer_running = false; } return ESP_OK; @@ -376,6 +411,12 @@ button_handle_t iot_button_create(const button_config_t *config) ret = button_gpio_init(cfg); BTN_CHECK(ESP_OK == ret, "gpio button init failed", NULL); btn = button_create_com(cfg->active_level, button_gpio_get_key_level, (void *)cfg->gpio_num, long_press_time, short_press_time); +#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE + if (cfg->enable_power_save) { + btn->enable_power_save = cfg->enable_power_save; + button_gpio_set_intr(cfg->gpio_num, cfg->active_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL, button_power_save_isr_handler, (void *)cfg->gpio_num); + } +#endif } break; case BUTTON_TYPE_ADC: { const button_adc_config_t *cfg = &(config->adc_button_config); @@ -410,6 +451,10 @@ button_handle_t iot_button_create(const button_config_t *config) } BTN_CHECK(NULL != btn, "button create failed", NULL); btn->type = config->type; + if (!btn->enable_power_save) { + esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U); + g_is_timer_running = true; + } return (button_handle_t)btn; } @@ -681,6 +726,14 @@ esp_err_t iot_button_set_param(button_handle_t btn_handle, button_param_t param, return ESP_OK; } +uint8_t iot_button_get_key_level(button_handle_t btn_handle) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0); + button_dev_t *btn = (button_dev_t *)btn_handle; + uint8_t level = btn->hal_button_Level(btn->hardware_data); + return (level == btn->active_level) ? 1 : 0; +} + esp_err_t iot_button_resume(void) { BTN_CHECK(g_button_timer_handle, "Button timer handle is invalid", ESP_ERR_INVALID_STATE); diff --git a/components/button/test_apps/main/button_test.c b/components/button/test_apps/main/button_test.c index a6bed4a3e..40935420a 100644 --- a/components/button/test_apps/main/button_test.c +++ b/components/button/test_apps/main/button_test.c @@ -1,4 +1,4 @@ -/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -173,6 +173,10 @@ TEST_CASE("gpio button test", "[button][iot]") iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb, NULL); iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT_DONE, button_press_repeat_done_cb, NULL); + uint8_t level = 0; + level = iot_button_get_key_level(g_btns[0]); + ESP_LOGI(TAG, "button level is %d", level); + while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); } @@ -699,6 +703,19 @@ void tearDown(void) void app_main(void) { - printf("USB STREAM TEST \n"); + /* + * ____ _ _ _______ _ + *| _ \ | | | | |__ __| | | + *| |_) | _ _ | |_ | |_ ___ _ __ | | ___ ___ | |_ + *| _ < | | | || __|| __|/ _ \ | '_ \ | | / _ \/ __|| __| + *| |_) || |_| || |_ | |_| (_) || | | | | || __/\__ \| |_ + *|____/ \__,_| \__| \__|\___/ |_| |_| |_| \___||___/ \__| + */ + printf(" ____ _ _ _______ _ \n"); + printf(" | _ \\ | | | | |__ __| | | \n"); + printf(" | |_) | _ _ | |_ | |_ ___ _ __ | | ___ ___ | |_ \n"); + printf(" | _ < | | | || __|| __|/ _ \\ | '_ \\ | | / _ \\/ __|| __|\n"); + printf(" | |_) || |_| || |_ | |_| (_) || | | | | || __/\\__ \\| |_ \n"); + printf(" |____/ \\__,_| \\__| \\__|\\___/ |_| |_| |_| \\___||___/ \\__|\n"); unity_run_menu(); } diff --git a/components/button/test_apps/pytest_button.py b/components/button/test_apps/pytest_button.py index 62921fba7..ab9ff9e1f 100644 --- a/components/button/test_apps/pytest_button.py +++ b/components/button/test_apps/pytest_button.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 ''' @@ -17,7 +17,13 @@ @pytest.mark.target('esp32s3') @pytest.mark.env('button') +@pytest.mark.parametrize( + 'config', + [ + 'defaults', + ], +) def test_usb_stream(dut: Dut)-> None: dut.expect_exact('Press ENTER to see the list of tests.') dut.write('[auto]') - dut.expect_unity_test_output(timeout = 60) + dut.expect_unity_test_output(timeout = 300) diff --git a/components/button/test_apps/sdkconfig.ci.defaults b/components/button/test_apps/sdkconfig.ci.defaults new file mode 100644 index 000000000..e69de29bb diff --git a/components/button/test_apps/sdkconfig.ci.pm b/components/button/test_apps/sdkconfig.ci.pm new file mode 100644 index 000000000..c3befd9ef --- /dev/null +++ b/components/button/test_apps/sdkconfig.ci.pm @@ -0,0 +1 @@ +CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE=y diff --git a/docs/_static/button_hardware.png b/docs/_static/input_device/button/button_hardware.png similarity index 100% rename from docs/_static/button_hardware.png rename to docs/_static/input_device/button/button_hardware.png diff --git a/docs/_static/input_device/button/button_one_press.png b/docs/_static/input_device/button/button_one_press.png new file mode 100644 index 000000000..1dbab226b Binary files /dev/null and b/docs/_static/input_device/button/button_one_press.png differ diff --git a/docs/_static/input_device/button/button_power_save_one_press.png b/docs/_static/input_device/button/button_power_save_one_press.png new file mode 100644 index 000000000..b7500b6d5 Binary files /dev/null and b/docs/_static/input_device/button/button_power_save_one_press.png differ diff --git a/docs/_static/input_device/button/button_power_save_three_press_4s.png b/docs/_static/input_device/button/button_power_save_three_press_4s.png new file mode 100644 index 000000000..0c4b3d4ad Binary files /dev/null and b/docs/_static/input_device/button/button_power_save_three_press_4s.png differ diff --git a/docs/_static/input_device/button/button_three_press_4s.png b/docs/_static/input_device/button/button_three_press_4s.png new file mode 100644 index 000000000..4c00bbfe0 Binary files /dev/null and b/docs/_static/input_device/button/button_three_press_4s.png differ diff --git a/docs/en/input_device/button.rst b/docs/en/input_device/button.rst index fc1d2fca4..ccc280065 100644 --- a/docs/en/input_device/button.rst +++ b/docs/en/input_device/button.rst @@ -5,16 +5,16 @@ The button component supports GPIO and ADC mode, and it allows the creation of two different kinds of the button at the same time. The following figure shows the hardware design of the button: -.. figure:: ../../_static/button_hardware.png +.. figure:: ../../_static/input_device/button/button_hardware.png :width: 650 - GPIO button: The advantage of the GPIO button is that each button occupies an independent IO and therefore does not affect each other, and has high stability however when the number of buttons increases, it may take too many IO resources. -- ADC button: The advantage of using the ADC button is that one ADC channel can share multiple buttons and occupy fewer IO resources. The disadvantages include that you cannot press multiple buttons at the same time, and instability increases due to increase in the closing resistance of the button due to oxidation and other factors. +- ADC button: The advantage of using the ADC button is that one ADC channel can share multiple buttons and occupy fewer IO resources. The disadvantages include that you cannot press multiple buttons at the same time, and instability increases due to increase in the closing resistance of the button due to oxidation and other factors. -.. note:: +.. note:: - The GPIO button needs to pay attention to the problem of pull-up and pull-down resistor inside the chip, which will be enabled by default. But there is no such resistor inside the IO that only supports input, **external connection requires**. - - The voltage of the ADC button should not exceed the ADC range. + - The voltage of the ADC button should not exceed the ADC range. Button event ------------ @@ -53,7 +53,7 @@ Triggering conditions for each button event are enlisted in the table below: Each button supports **call-back** and **pooling** mode. -- Call-back: Each event of a button can register a call-back function for it, and the call-back function will be called when an event is generated. This method has high efficiency and real-time performance, and no events will be lost. +- Call-back: Each event of a button can register a call-back function for it, and the call-back function will be called when an event is generated. This method has high efficiency and real-time performance, and no events will be lost. - Polling: Periodically call :c:func:`iot_button_get_event` in the program to query the current event of the button. This method is easy to use and is suitable for occasions with simple tasks .. note:: you can also combine the above two methods. @@ -70,9 +70,9 @@ Configuration - BUTTON_DEBOUNCE_TICKS : debounce time -- BUTTON_SHORT_PRESS_TIME_MS : short press down effective time +- BUTTON_SHORT_PRESS_TIME_MS : short press down effective time -- BUTTON_LONG_PRESS_TIME_MS : long press down effective time +- BUTTON_LONG_PRESS_TIME_MS : long press down effective time - ADC_BUTTON_MAX_CHANNEL : maximum number of channel for ADC @@ -161,8 +161,8 @@ Create a button ESP_LOGE(TAG, "Button create failed"); } -Register call-back -^^^^^^^^^^^^^^^^^^^ +Register callback function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The Button component supports registering callback functions for multiple events, with each event capable of having its own callback function. When an event occurs, the callback function will be invoked. @@ -186,7 +186,7 @@ In this context: - And here's an example involving multiple callback functions: .. code:: C - + static void button_long_press_1_cb(void *arg,void *usr_data) { ESP_LOGI(TAG, "BUTTON_LONG_PRESS_START_1"); @@ -224,6 +224,64 @@ Find an event Low power ^^^^^^^^^^^ +In light_sleep mode, the `esp_timer` triggers periodically, resulting in sustained high overall CPU power consumption. To address this issue, the button component offers a low-power mode. + +Configuration Required: + +- Enable the `CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE` option to include low-power-related code in the component. +- Ensure all created buttons type are GPIO type and have `enable_power_save` activated. The presence of other buttons may render the low-power mode ineffective. + +.. Note:: This feature ensures that the Button component only wakes up the CPU when in use, but does not guarantee the CPU will always enter low-power mode. + +Power Consumption Comparison: + +- Without enabling low-power mode, pressing the button once: + + .. figure:: ../../_static/input_device/button/button_one_press.png + :align: center + :width: 70% + :alt: Without enabling low-power mode, a single press + +- With low-power mode enabled, pressing the button once: + + .. figure:: ../../_static/input_device/button/button_power_save_one_press.png + :align: center + :width: 70% + :alt: With low-power mode enabled, a single press + +Because GPIO wakes up the CPU, supporting only level triggering, the CPU is awakened only when the button is at its operating level. Therefore, in low-power mode, the average current during a single press is higher than when low-power mode is not enabled, depending on the duration of the button press. However, over larger operational periods, it saves more power than when low-power mode is not enabled. + +- Without enabling low-power mode, pressing the button three times within 4 seconds: + + .. figure:: ../../_static/input_device/button/button_three_press_4s.png + :align: center + :width: 70% + +- With low-power mode enabled, pressing the button three times within 4 seconds: + + .. figure:: ../../_static/input_device/button/button_power_save_three_press_4s.png + :align: center + :width: 70% + +As shown, low-power mode results in more power savings. + +.. code:: c + + button_config_t btn_cfg = { + .type = BUTTON_TYPE_GPIO, + .gpio_button_config = { + .gpio_num = button_num, + .active_level = BUTTON_ACTIVE_LEVEL, + .enable_power_save = true, + }, + }; + button_handle_t btn = iot_button_create(&btn_cfg); + +Stop and resume +^^^^^^^^^^^^^^^^^ + +The component supports being turned on and off at any given moment. + .. code:: c // stop button @@ -234,4 +292,4 @@ Low power API Reference ----------------- -.. include-build-file:: inc/iot_button.inc \ No newline at end of file +.. include-build-file:: inc/iot_button.inc diff --git a/docs/zh_CN/input_device/button.rst b/docs/zh_CN/input_device/button.rst index 4d7858b2b..d511378ba 100644 --- a/docs/zh_CN/input_device/button.rst +++ b/docs/zh_CN/input_device/button.rst @@ -5,14 +5,14 @@ 按键组件实现了 GPIO 和 ADC 两种按键,并允许同时创建两种不同的按键。下图显示了两种按键的硬件设计: -.. figure:: ../../_static/button_hardware.png +.. figure:: ../../_static/input_device/button/button_hardware.png :width: 650 - GPIO 按键优点有:每一个按键占用独立的 IO,之间互不影响,稳定性高;缺点有:按键数量多时占用太多 IO 资源。 - ADC 按键优点有:可多个按键共用一个 ADC 通道,占用 IO 资源少;缺点有:不能同时按下多按键,当按键因氧化等因素导致闭合电阻增大时,容易误触,稳定性不高。 -.. note:: +.. note:: - GPIO 按键需注意上下拉问题,组件内部会启用芯片内部的上下拉电阻,但是在仅支持输入的 IO 内部没有电阻, **需要外部连接**。 - ADC 按键需注意电压不能超过 ADC 量程。 @@ -184,7 +184,7 @@ Button 组件支持为多个事件注册回调函数,每个事件都可以注 - 多个回调函数写法 .. code:: C - + static void button_long_press_1_cb(void *arg,void *usr_data) { ESP_LOGI(TAG, "BUTTON_LONG_PRESS_START_1"); @@ -212,7 +212,7 @@ Button 组件支持为多个事件注册回调函数,每个事件都可以注 button_event_t event; event = iot_button_get_event(button_handle); -动态修改按键默认值 +动态修改按键默认值 ^^^^^^^^^^^^^^^^^^ .. code:: c @@ -222,6 +222,66 @@ Button 组件支持为多个事件注册回调函数,每个事件都可以注 低功耗支持 ^^^^^^^^^^^ +在 light_sleep 模式下,esp_timer 定时器会定时触发,导致 cpu 整体功耗居高不下。为了解决这个问题,button 组件提供了低功耗模式。 + +所需配置: + +- 打开 `CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE` 选项, 会在组件中增加低功耗相关代码 +- 确保创建的所有按键类型为 GPIO 按键, 并且都开启了 `enable_power_save`,如存在其他按键,会导致低功耗模式失效 + +.. Note:: 该功能只保证 Button 组件只在使用中才唤醒 CPU, 不保证 CPU 一定会进入低功耗模式 + +功耗对比: + +- 未开启低功耗模式,按下一次按键 + + .. figure:: ../../_static/input_device/button/button_one_press.png + :align: center + :width: 70% + :alt: 未开启低功耗模式,一次按下 + +- 开启低功耗模式,按下一次按键 + + .. figure:: ../../_static/input_device/button/button_power_save_one_press.png + :align: center + :width: 70% + :alt: 开启低功耗模式,一次按下 + +因为 GPIO 唤醒 CPU, 仅支持电平触发,所以当按键为工作电平时,CPU 会支持的被唤醒,取决于按下去的时长,因此在低功耗模式下,单次按下的平均电流高于未开启低功耗模式。但是在大的工作周期中,会比未开启低功耗模式更加省电。 + +- 未开启低功耗模式下,在 4s 内按下三次按键 + + .. figure:: ../../_static/input_device/button/button_three_press_4s.png + :align: center + :width: 70% + :alt: 非低功耗模式下,在 4s 内按下三次按键 + +- 低功耗模式下,在 4s 内按下三次按键 + + .. figure:: ../../_static/input_device/button/button_power_save_three_press_4s.png + :align: center + :width: 70% + :alt: 低功耗模式下,在 4s 内按下三次按键 + +如图,低功耗模式下更加的省电。 + +.. code:: c + + button_config_t btn_cfg = { + .type = BUTTON_TYPE_GPIO, + .gpio_button_config = { + .gpio_num = button_num, + .active_level = BUTTON_ACTIVE_LEVEL, + .enable_power_save = true, + }, + }; + button_handle_t btn = iot_button_create(&btn_cfg); + +开启和关闭 +^^^^^^^^^^^^^ + +组件支持在任意时刻开启和关闭。 + .. code:: c // stop button diff --git a/examples/.build-rules.yml b/examples/.build-rules.yml index a9fc8793d..ebb93e1dc 100644 --- a/examples/.build-rules.yml +++ b/examples/.build-rules.yml @@ -91,6 +91,10 @@ examples/get-started/blink: enable: - if: (IDF_VERSION_MAJOR == 4 and IDF_VERSION_MINOR == 4) and IDF_TARGET in ["esp32","esp32s2","esp32s3"] +examples/get-started/button_power_save: + enable: + - if: INCLUDE_DEFAULT == 1 + examples/lighting/lightbulb: enable: - if: INCLUDE_DEFAULT == 1 diff --git a/examples/get-started/button_power_save/CMakeLists.txt b/examples/get-started/button_power_save/CMakeLists.txt new file mode 100644 index 000000000..78c65a134 --- /dev/null +++ b/examples/get-started/button_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(button_power_save) diff --git a/examples/get-started/button_power_save/README.md b/examples/get-started/button_power_save/README.md new file mode 100644 index 000000000..4e05d2e97 --- /dev/null +++ b/examples/get-started/button_power_save/README.md @@ -0,0 +1,43 @@ +## Button Power Save Example + +This example demonstrates how to utilize the `button` component in conjunction with the light sleep low-power mode. + +* `button` [Component Introduction](https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/button.html) + +## Hardware + +* Any GPIO on any development board can be used in this example. + +## 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 (1139) pm: Frequency switching config: CPU_MAX: 160, APB_MAX: 80, APB_MIN: 80, Light sleep: ENABLED +I (1149) sleep: Code start at 42000020, total 119.03 KiB, data start at 3c000000, total 49152.00 KiB +I (1159) button: IoT Button Version: 3.2.0 +I (1163) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (2922) button_power_save: Button event BUTTON_PRESS_DOWN +I (3017) button_power_save: Button event BUTTON_PRESS_UP +I (3017) button_power_save: Wake up from light sleep, reason 4 +I (3200) button_power_save: Button event BUTTON_SINGLE_CLICK +I (3200) button_power_save: Wake up from light sleep, reason 4 +I (3202) button_power_save: Button event BUTTON_PRESS_REPEAT_DONE +I (3208) button_power_save: Wake up from light sleep, reason 4 +I (3627) button_power_save: Button event BUTTON_PRESS_DOWN +I (3702) button_power_save: Button event BUTTON_PRESS_UP +I (3702) button_power_save: Wake up from light sleep, reason 4 +I (3887) button_power_save: Button event BUTTON_SINGLE_CLICK +I (3887) button_power_save: Wake up from light sleep, reason 4 +I (3889) button_power_save: Button event BUTTON_PRESS_REPEAT_DONE +``` \ No newline at end of file diff --git a/examples/get-started/button_power_save/main/CMakeLists.txt b/examples/get-started/button_power_save/main/CMakeLists.txt new file mode 100644 index 000000000..cf2c455cb --- /dev/null +++ b/examples/get-started/button_power_save/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS ".") diff --git a/examples/get-started/button_power_save/main/Kconfig.projbuild b/examples/get-started/button_power_save/main/Kconfig.projbuild new file mode 100644 index 000000000..a5cec22d7 --- /dev/null +++ b/examples/get-started/button_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/button_power_save/main/idf_component.yml b/examples/get-started/button_power_save/main/idf_component.yml new file mode 100644 index 000000000..f2375442b --- /dev/null +++ b/examples/get-started/button_power_save/main/idf_component.yml @@ -0,0 +1,6 @@ +version: "0.0.1" +dependencies: + idf: ">=4.4" + button: + version: "*" + override_path: "../../../../components/button" diff --git a/examples/get-started/button_power_save/main/main.c b/examples/get-started/button_power_save/main/main.c new file mode 100644 index 000000000..d109fc613 --- /dev/null +++ b/examples/get-started/button_power_save/main/main.c @@ -0,0 +1,116 @@ +/* + * 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_button.h" +#include "esp_sleep.h" +#include "esp_idf_version.h" + +/* Most development boards have "boot" button attached to GPIO0. + * You can also change this to another pin. + */ +#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32C6 +#define BOOT_BUTTON_NUM 9 +#else +#define BOOT_BUTTON_NUM 0 +#endif +#define BUTTON_ACTIVE_LEVEL 0 + +static const char *TAG = "button_power_save"; + +const char *button_event_table[] = { + "BUTTON_PRESS_DOWN", + "BUTTON_PRESS_UP", + "BUTTON_PRESS_REPEAT", + "BUTTON_PRESS_REPEAT_DONE", + "BUTTON_SINGLE_CLICK", + "BUTTON_DOUBLE_CLICK", + "BUTTON_MULTIPLE_CLICK", + "BUTTON_LONG_PRESS_START", + "BUTTON_LONG_PRESS_HOLD", + "BUTTON_LONG_PRESS_UP", +}; + +static void button_event_cb(void *arg, void *data) +{ + ESP_LOGI(TAG, "Button event %s", button_event_table[(button_event_t)data]); + 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); + } +} + +void button_init(uint32_t button_num) +{ + button_config_t btn_cfg = { + .type = BUTTON_TYPE_GPIO, + .gpio_button_config = { + .gpio_num = button_num, + .active_level = BUTTON_ACTIVE_LEVEL, +#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE + .enable_power_save = true, +#endif + }, + }; + button_handle_t btn = iot_button_create(&btn_cfg); + assert(btn); + esp_err_t err = iot_button_register_cb(btn, BUTTON_PRESS_DOWN, button_event_cb, (void *)BUTTON_PRESS_DOWN); + err |= iot_button_register_cb(btn, BUTTON_PRESS_UP, button_event_cb, (void *)BUTTON_PRESS_UP); + err |= iot_button_register_cb(btn, BUTTON_PRESS_REPEAT, button_event_cb, (void *)BUTTON_PRESS_REPEAT); + err |= iot_button_register_cb(btn, BUTTON_PRESS_REPEAT_DONE, button_event_cb, (void *)BUTTON_PRESS_REPEAT_DONE); + err |= iot_button_register_cb(btn, BUTTON_SINGLE_CLICK, button_event_cb, (void *)BUTTON_SINGLE_CLICK); + err |= iot_button_register_cb(btn, BUTTON_DOUBLE_CLICK, button_event_cb, (void *)BUTTON_DOUBLE_CLICK); + err |= iot_button_register_cb(btn, BUTTON_LONG_PRESS_START, button_event_cb, (void *)BUTTON_LONG_PRESS_START); + err |= iot_button_register_cb(btn, BUTTON_LONG_PRESS_HOLD, button_event_cb, (void *)BUTTON_LONG_PRESS_HOLD); + err |= iot_button_register_cb(btn, BUTTON_LONG_PRESS_UP, button_event_cb, (void *)BUTTON_LONG_PRESS_UP); + 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(); + button_init(BOOT_BUTTON_NUM); +} diff --git a/examples/get-started/button_power_save/sdkconfig.defaults b/examples/get-started/button_power_save/sdkconfig.defaults new file mode 100644 index 000000000..eeb3f14a4 --- /dev/null +++ b/examples/get-started/button_power_save/sdkconfig.defaults @@ -0,0 +1,11 @@ +# 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 +# For button power save +CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE=y