add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1 @@
f53face2ab21fa0ffaf4cf0f6e513d393f56df6586bb2ad1146120f03f19ee05

View File

@@ -0,0 +1,201 @@
# ChangeLog
## v4.1.3 - 2025-04-11
### Fix:
* Added initialization for gpio_config. [!485](https://github.com/espressif/esp-iot-solution/pull/485)
## v4.1.2 - 2025-03-24
### Fix:
* fix incorrect long press start and release check.
## v4.1.1 - 2025-03-13
### Improve:
* include stdbool.h before using bool
## v4.1.0 - 2025-02-28
### Improve:
* Update the version of dependent cmake_utilities to *
## v4.0.0 - 2025-1-9
### Enhancements:
* Use the factory pattern to reduce the build size.
* Change the state machine to use enumerated values.
### Break change:
* Standardize the return value of the iot_button interface to esp_err_t.
* Remove support for the old ADC driver.
* Modify the callback registration interface to:
```c
esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_event_args_t *event_args, button_cb_t cb, void *usr_data);
```
* Modify the callback unregistration interface to:
```c
esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event, button_event_args_t *event_args);
```
## v3.5.0 - 2024-12-27
### Enhancements:
* Add config to disable gpio button internal pull resistor.
## v3.4.1 - 2024-12-6
### Fix:
* Fix the issue where `BUTTON_LONG_PRESS_START` is not triggered when the polling interval exceeds 20ms.
* Remove the `BUTTON_LONG_PRESS_TOLERANCE_MS` configuration option.
## v3.4.0 - 2024-10-22
### Enhancements:
* Supports a maximum button polling interval of 500ms.
* Fixed a potential counter overflow issue.
### Break change:
* The return value of `iot_button_get_ticks_time` has been changed from `uint16_t` to `uint32_t`.
## v3.3.2 - 2024-8-28
### Enhancements:
* Support macro CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP in power save mode.
* Supports retrieving and printing the string corresponding to a button event.
* Fixed the bug where the event was not assigned to `BUTTON_LONG_PRESS_START` before the `BUTTON_LONG_PRESS_START` event occurred.
## v3.3.1 - 2024-8-8
### Enhancements:
* Add Button Event **BUTTON_PRESS_END**.
## v3.3.0 - 2024-8-7
### Enhancements:
* Add Callback **button_power_save_cb_t** to support enter power save manually.
* Increase the maximum polling interval supported by the button from 20ms to 50ms.
## v3.2.3 - 2024-7-2
* Fixed the issue where the GPIO button in low-power mode continuously woke up the CPU after being pressed, causing abnormal power consumption.
## v3.2.2 - 2024-6-17
* Fix the compilation error for chips that do not support ADC.
## v3.2.1 - 2024-6-17
### bugfix
- Fixed ignored ADC button tied to GND. thanks `demianzenkov` for the fix.
## 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'.
## v3.1.2 - 2023-10-24
### bugfix
* Fixed a bug where iot_button_delete feature crashes for custom button
## v3.1.1 - 2023-10-18
### bugfix
* Fixed a bug where multiple callbacks feature crashes for BUTTON_MULTIPLE_CLICK
## v3.1.0 - 2023-10-9
### Enhancements:
* Support matrix keypad
## v3.0.1 - 2023-9-1
### Enhancements:
* Resolves bug for iot_button_unregister_event function returned error when reallocating with 0 byte.
* Update Test cases to test iot_button_unregister_event_cb
* Add api iot_button_stop & iot_button_resume for power save.
## v3.0.0 - 2023-8-15
### Enhancements:
* Add support to register multiple callbacks for a button_event
* Update iot_button_unregister_cb, to unregister all the callbacks for that event
* Add iot_button_unregister_event to unregister specific callbacks of that event
* Add iot_button_count_event to return number of callbacks registered for the event.
* Update iot_button_count_cb, to return sum of number of registered callbacks.
* Add support for Long press on specific time
* Add iot_button_register_event, which takes specific event_config_t data as input.
* Add BUTTON_LONG_PRESS_UP to trigger callback at the latest time of button release
* Update BUTTON_LONG_PRESS_START to trigger callback as the time passes for long_press.
* Add support to trigger callback for specified number of clicks.
## v2.5.6 - 2023-8-22
### bugfix
* Fixed a bug where the Serial trigger interval in button_long_press_hold event fires at an incorrect time
## v2.5.5 - 2023-8-3
* Add modify api which can change long_press_time and short_press_time
## v2.5.4 - 2023-7-27
### Enhancements:
* Add test apps and ci auto test
## v2.5.3 - 2023-7-26
### Enhancements:
* `repeat` defined in struct button_dev_t is reset to 0 after event `BUTTON_PRESS_REPEAT_DONE`
## v2.5.2 - 2023-7-18
### Enhancements:
* Set "event" member to BUTTON_PRESS_REPEAT before calling the BUTTON_PRESS_REPEAT callback
## v2.5.1 - 2023-3-14
### Enhancements:
* Update doc and code specification
* Avoid overwriting callback by @franz-ms-muc in #252
## v2.5.0 - 2023-2-1
### Enhancements:
* Support custom button
* Add BUTTON_PRESS_REPEAT_DONE event

View File

@@ -0,0 +1 @@
{"version": "1.0", "algorithm": "sha256", "created_at": "2025-05-21T17:11:06.395688+00:00", "files": [{"path": "CMakeLists.txt", "size": 570, "hash": "9513b53f1d41edae9533481dcb35769d85d183bce999aacab590f187dbd0e401"}, {"path": "CHANGELOG.md", "size": 5068, "hash": "681f26d00972c770f54e0e6772e3f74d7ec748ddec393fd84b38bff72aed0edd"}, {"path": "idf_component.yml", "size": 521, "hash": "d3fe4c5d8cac965da28fb81ceaa91fe38dec6e0e5051092c2b0f635eb76db52b"}, {"path": "Kconfig", "size": 1385, "hash": "5ea358f4e061a732c3c0d565826d18dcd3fc393a0fe67a3c317ceec2f669f68b"}, {"path": "button_adc.c", "size": 12504, "hash": "9a511f69768bb975bda030046e338e835e3704a21f406675ceb1ca74e2190e74"}, {"path": "README.md", "size": 1729, "hash": "adc2c93639fabed0e77ff75b209c13f37bb97a5c09fe0b9d3688376faeda1735"}, {"path": "iot_button.c", "size": 27819, "hash": "49a6762fe887ca9a27434075f222ad10fabca2de51ccf31659069fa96dea7e82"}, {"path": "button_gpio.c", "size": 5767, "hash": "c3f2efda4dfc1cd4a0a3d1f67c3a02afc0cc2e8b58d2857a79d7f48b828847db"}, {"path": "button_matrix.c", "size": 3616, "hash": "8a2316485a31c1d40b7e662a1f7fd86cf9c85bdfa670d290c164f5f349616e81"}, {"path": "license.txt", "size": 11358, "hash": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"}, {"path": "interface/button_interface.h", "size": 771, "hash": "7fc7b7c596a9fe4e42cfe1529f484345757ef4c7b6b46ada5040ecf60da051ad"}, {"path": "include/button_gpio.h", "size": 2174, "hash": "9c46c0929b449dc751eaaa128deff18dde32370242a1214c59fba9b277408129"}, {"path": "include/button_matrix.h", "size": 3244, "hash": "5b95aa72eb47cfa3f1d603317e844d8c823b97175a6cf577857014548bf0e5ee"}, {"path": "include/iot_button.h", "size": 7448, "hash": "a6abae10ca72994e59757e7b62f131fed5c89b70b121f0d82fa9251dd96c9a8e"}, {"path": "include/button_types.h", "size": 1758, "hash": "1b956e32616cc8e397a263afc0b6e1d8d555335fc9c0c342d8c46ae556065179"}, {"path": "include/button_adc.h", "size": 2487, "hash": "5d33d87d329aa854d0879d276f8cf0bf5f8afa17b3b485a66edb0f63c9f9d24a"}, {"path": "test_apps/CMakeLists.txt", "size": 350, "hash": "234fd5c4b8c16494d8169c1490c649d23306e4e20f08ae14b128cd56c17893d5"}, {"path": "test_apps/sdkconfig.defaults", "size": 213, "hash": "9a34a6cb08c49ec24007587e0c5d492f44b5a862d9c0f583cf9f6f643669b564"}, {"path": "test_apps/pytest_button.py", "size": 755, "hash": "c5e633c4175f5d6475f1a10cb6fb800629dc82faf86bc6058ac4b90c6e3104d4"}, {"path": "examples/button_power_save/CMakeLists.txt", "size": 246, "hash": "e321ec2107e16cea7753b5c572215f181647ef0472893b2d256e28ed050180fe"}, {"path": "examples/button_power_save/sdkconfig.ci.ls_manually", "size": 42, "hash": "097c0ff2b0685db3ee4bd8f5b24521405b1bac4367096f213e66262ad7561344"}, {"path": "examples/button_power_save/README.md", "size": 1961, "hash": "9ace22e9ed47a4d6e2b7a840aaf0381009a563a51d6d3772498960ffc02952a9"}, {"path": "examples/button_power_save/sdkconfig.defaults", "size": 351, "hash": "86844871a1893fdf8556bce5f8717cc9b3cf02e494500827d94420e4f5139679"}, {"path": "examples/button_power_save/main/CMakeLists.txt", "size": 75, "hash": "27d6d39ef0cb6a9097984b41e080b09f7ff972339a0bb4ddf771db4001cb9782"}, {"path": "examples/button_power_save/main/idf_component.yml", "size": 137, "hash": "db65d8c6aef7d84eb2b09dfcc6740303c5917e69e3d61b1a3a43abd133998495"}, {"path": "examples/button_power_save/main/main.c", "size": 3939, "hash": "159bfbf6ec082f4f2484d05d211a6ac529c7bcd81830b05ad28a7fccdbd92942"}, {"path": "examples/button_power_save/main/Kconfig.projbuild", "size": 3514, "hash": "2a755747a11b0257d05b68cfb1adfbede1f6cfeda06f6a8d8ee53822ff9ba8e2"}, {"path": "test_apps/main/CMakeLists.txt", "size": 356, "hash": "9a810145f11532c705fc71396919a1e7e645f8cd7ffafd3d35c8d5a9ee40171d"}, {"path": "test_apps/main/custom_button_test.c", "size": 3807, "hash": "b62c7d92d176d014c75a1b9b9b6584d6c366edca4ca89575c75ab5818d44cfc8"}, {"path": "test_apps/main/auto_test.c", "size": 10257, "hash": "cc110a3c6a850251cfb7f238c8b7c806c9ce69a600f3e017f41bd1b2749fc5de"}, {"path": "test_apps/main/matrix_button_test.c", "size": 2940, "hash": "c329c3ff2a68d6992672c5085a00e5c8c3ee1a639ebb193570ebb0ef8946f4ef"}, {"path": "test_apps/main/button_test_main.c", "size": 1342, "hash": "841b79a2a6bef5382e8abd325927031c049522bc1731e56aebf95cd8ea01a17f"}, {"path": "test_apps/main/adc_button_test.c", "size": 5080, "hash": "7c5be72e772f75b9894f86ca4b8a1899e08bd6e3eefaf93bd40adacb75d1d9fc"}, {"path": "test_apps/main/gpio_button_test.c", "size": 7185, "hash": "0e74382a762116e9028b29e24205d3995bf395cd8ec5f663e500acfc35b9fe3d"}]}

View File

@@ -0,0 +1,18 @@
set(PRIVREQ esp_timer)
set(REQ driver)
set(SRC_FILES "button_gpio.c" "iot_button.c" "button_matrix.c")
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
list(APPEND REQ esp_adc)
if(CONFIG_SOC_ADC_SUPPORTED)
list(APPEND SRC_FILES "button_adc.c")
endif()
endif()
idf_component_register(SRCS ${SRC_FILES}
INCLUDE_DIRS include interface
REQUIRES ${REQ}
PRIV_REQUIRES ${PRIVREQ})
include(package_manager)
cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR})

View File

@@ -0,0 +1,55 @@
menu "IoT Button"
config BUTTON_PERIOD_TIME_MS
int "BUTTON PERIOD TIME (MS)"
range 2 500
default 5
help
"Button scan interval"
config BUTTON_DEBOUNCE_TICKS
int "BUTTON DEBOUNCE TICKS"
range 1 7
default 2
help
"One CONFIG_BUTTON_DEBOUNCE_TICKS equal to CONFIG_BUTTON_PERIOD_TIME_MS"
config BUTTON_SHORT_PRESS_TIME_MS
int "BUTTON SHORT PRESS TIME (MS)"
range 50 800
default 180
config BUTTON_LONG_PRESS_TIME_MS
int "BUTTON LONG PRESS TIME (MS)"
range 500 5000
default 1500
config BUTTON_LONG_PRESS_HOLD_SERIAL_TIME_MS
int "BUTTON LONG_PRESS_HOLD SERIAL TIME (MS)"
range 2 1000
default 20
help
"Long press hold Serial trigger interval"
config ADC_BUTTON_MAX_CHANNEL
int "ADC BUTTON MAX CHANNEL"
range 1 5
default 3
help
"Maximum number of channels for ADC buttons"
config ADC_BUTTON_MAX_BUTTON_PER_CHANNEL
int "ADC BUTTON MAX BUTTON PER CHANNEL"
range 1 10
default 8
help
"Maximum number of buttons per channel"
config ADC_BUTTON_SAMPLE_TIMES
int "ADC BUTTON SAMPLE TIMES"
range 1 4
default 1
help
"Number of samples per scan"
endmenu

View File

@@ -0,0 +1,42 @@
[![Component Registry](https://components.espressif.com/components/espressif/button/badge.svg)](https://components.espressif.com/components/espressif/button)
# Component: Button
[Online documentation](https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/button.html)
After creating a new button object by calling function `button_create()`, the button object can create press events, every press event can have its own callback.
List of supported events:
* Button pressed
* Button released
* Button pressed 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
* Button Press end
![](https://dl.espressif.com/AE/esp-iot-solution/button_3.3.1.svg)
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. 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
Please use the component manager command `add-dependency` to add the `button` to your project's dependency, during the `CMake` step the component will be downloaded automatically
```
idf.py add-dependency "espressif/button=*"
```

View File

@@ -0,0 +1,327 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <inttypes.h>
#include "esp_log.h"
#include "esp_check.h"
#include "esp_timer.h"
#include "esp_idf_version.h"
#include "soc/soc_caps.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "button_adc.h"
#include "button_interface.h"
static const char *TAG = "adc_button";
#define DEFAULT_VREF 1100
#define NO_OF_SAMPLES CONFIG_ADC_BUTTON_SAMPLE_TIMES //Multisampling
/*!< Using atten bigger than 6db by default, it will be 11db or 12db in different target */
#define DEFAULT_ADC_ATTEN (ADC_ATTEN_DB_6 + 1)
#define ADC_BUTTON_WIDTH SOC_ADC_RTC_MAX_BITWIDTH
#define ADC_BUTTON_CHANNEL_MAX SOC_ADC_MAX_CHANNEL_NUM
#define ADC_BUTTON_ATTEN DEFAULT_ADC_ATTEN
#define ADC_BUTTON_MAX_CHANNEL CONFIG_ADC_BUTTON_MAX_CHANNEL
#define ADC_BUTTON_MAX_BUTTON CONFIG_ADC_BUTTON_MAX_BUTTON_PER_CHANNEL
// ESP32C3 ADC2 it has been deprecated.
#if (SOC_ADC_PERIPH_NUM >= 2) && !CONFIG_IDF_TARGET_ESP32C3
#define ADC_UNIT_NUM 2
#else
#define ADC_UNIT_NUM 1
#endif
typedef struct {
uint16_t min;
uint16_t max;
} button_data_t;
typedef struct {
uint8_t channel;
uint8_t is_init;
button_data_t btns[ADC_BUTTON_MAX_BUTTON]; /* all button on the channel */
uint64_t last_time; /* the last time of adc sample */
} btn_adc_channel_t;
typedef enum {
ADC_NONE_INIT = 0,
ADC_INIT_BY_ADC_BUTTON,
ADC_INIT_BY_USER,
} adc_init_info_t;
typedef struct {
adc_init_info_t is_configured;
adc_cali_handle_t adc_cali_handle;
adc_oneshot_unit_handle_t adc_handle;
btn_adc_channel_t ch[ADC_BUTTON_MAX_CHANNEL];
uint8_t ch_num;
} btn_adc_unit_t;
typedef struct {
btn_adc_unit_t unit[ADC_UNIT_NUM];
} button_adc_t;
typedef struct {
button_driver_t base;
adc_unit_t unit_id;
uint32_t ch;
uint32_t index;
} button_adc_obj;
static button_adc_t g_button = {0};
static int find_unused_channel(adc_unit_t unit_id)
{
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (0 == g_button.unit[unit_id].ch[i].is_init) {
return i;
}
}
return -1;
}
static int find_channel(adc_unit_t unit_id, uint8_t channel)
{
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (channel == g_button.unit[unit_id].ch[i].channel) {
return i;
}
}
return -1;
}
static bool adc_calibration_init(adc_unit_t unit, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
adc_cali_handle_t handle = NULL;
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
bool calibrated = false;
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BUTTON_WIDTH,
};
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");
adc_cali_line_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BUTTON_WIDTH,
};
ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
*out_handle = handle;
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Calibration Success");
} else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {
ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");
} else if (ret == ESP_ERR_NOT_SUPPORTED) {
ESP_LOGW(TAG, "Calibration not supported");
} else {
ESP_LOGE(TAG, "Invalid arg or no memory");
}
return calibrated;
}
static bool adc_calibration_deinit(adc_cali_handle_t handle)
{
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
if (adc_cali_delete_scheme_curve_fitting(handle) == ESP_OK) {
return true;
}
#endif
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
if (adc_cali_delete_scheme_line_fitting(handle) == ESP_OK) {
return true;
}
#endif
return false;
}
esp_err_t button_adc_del(button_driver_t *button_driver)
{
button_adc_obj *adc_btn = __containerof(button_driver, button_adc_obj, base);
ESP_RETURN_ON_FALSE(adc_btn->ch < ADC_BUTTON_CHANNEL_MAX, ESP_ERR_INVALID_ARG, TAG, "channel out of range");
ESP_RETURN_ON_FALSE(adc_btn->index < ADC_BUTTON_MAX_BUTTON, ESP_ERR_INVALID_ARG, TAG, "button_index out of range");
int ch_index = find_channel(adc_btn->unit_id, adc_btn->ch);
ESP_RETURN_ON_FALSE(ch_index >= 0, ESP_ERR_INVALID_ARG, TAG, "can't find the channel");
g_button.unit[adc_btn->unit_id].ch[ch_index].btns[adc_btn->index].max = 0;
g_button.unit[adc_btn->unit_id].ch[ch_index].btns[adc_btn->index].min = 0;
/** check button usage on the channel*/
uint8_t unused_button = 0;
for (size_t i = 0; i < ADC_BUTTON_MAX_BUTTON; i++) {
if (0 == g_button.unit[adc_btn->unit_id].ch[ch_index].btns[i].max) {
unused_button++;
}
}
if (unused_button == ADC_BUTTON_MAX_BUTTON && g_button.unit[adc_btn->unit_id].ch[ch_index].is_init) { /**< if all button is unused, deinit the channel */
g_button.unit[adc_btn->unit_id].ch[ch_index].is_init = 0;
g_button.unit[adc_btn->unit_id].ch[ch_index].channel = ADC_BUTTON_CHANNEL_MAX;
ESP_LOGD(TAG, "all button is unused on channel%d, deinit the channel", g_button.unit[adc_btn->unit_id].ch[ch_index].channel);
}
/** check channel usage on the adc*/
uint8_t unused_ch = 0;
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (0 == g_button.unit[adc_btn->unit_id].ch[i].is_init) {
unused_ch++;
}
}
if (unused_ch == ADC_BUTTON_MAX_CHANNEL && g_button.unit[adc_btn->unit_id].is_configured) { /**< if all channel is unused, deinit the adc */
if (g_button.unit[adc_btn->unit_id].is_configured == ADC_INIT_BY_ADC_BUTTON) {
esp_err_t ret = adc_oneshot_del_unit(g_button.unit[adc_btn->unit_id].adc_handle);
ESP_RETURN_ON_FALSE(ret == ESP_OK, ret, TAG, "adc oneshot del unit fail");
adc_calibration_deinit(g_button.unit[adc_btn->unit_id].adc_cali_handle);
}
g_button.unit[adc_btn->unit_id].is_configured = ADC_NONE_INIT;
memset(&g_button.unit[adc_btn->unit_id], 0, sizeof(btn_adc_unit_t));
ESP_LOGD(TAG, "all channel is unused, , deinit adc");
}
free(adc_btn);
return ESP_OK;
}
static uint32_t get_adc_volatge(adc_unit_t unit_id, uint8_t channel)
{
uint32_t adc_reading = 0;
int adc_raw = 0;
for (int i = 0; i < NO_OF_SAMPLES; i++) {
adc_oneshot_read(g_button.unit[unit_id].adc_handle, channel, &adc_raw);
adc_reading += adc_raw;
}
adc_reading /= NO_OF_SAMPLES;
//Convert adc_reading to voltage in mV
int voltage = 0;
adc_cali_raw_to_voltage(g_button.unit[unit_id].adc_cali_handle, adc_reading, &voltage);
ESP_LOGV(TAG, "Raw: %"PRIu32"\tVoltage: %dmV", adc_reading, voltage);
return voltage;
}
uint8_t button_adc_get_key_level(button_driver_t *button_driver)
{
button_adc_obj *adc_btn = __containerof(button_driver, button_adc_obj, base);
static uint16_t vol = 0;
uint32_t ch = adc_btn->ch;
uint32_t index = adc_btn->index;
ESP_RETURN_ON_FALSE(ch < ADC_BUTTON_CHANNEL_MAX, 0, TAG, "channel out of range");
ESP_RETURN_ON_FALSE(index < ADC_BUTTON_MAX_BUTTON, 0, TAG, "button_index out of range");
int ch_index = find_channel(adc_btn->unit_id, ch);
ESP_RETURN_ON_FALSE(ch_index >= 0, 0, TAG, "The button_index is not init");
/** It starts only when the elapsed time is more than 1ms */
if ((esp_timer_get_time() - g_button.unit[adc_btn->unit_id].ch[ch_index].last_time) > 1000) {
vol = get_adc_volatge(adc_btn->unit_id, ch);
g_button.unit[adc_btn->unit_id].ch[ch_index].last_time = esp_timer_get_time();
}
if (vol <= g_button.unit[adc_btn->unit_id].ch[ch_index].btns[index].max &&
vol >= g_button.unit[adc_btn->unit_id].ch[ch_index].btns[index].min) {
return BUTTON_ACTIVE;
}
return BUTTON_INACTIVE;
}
esp_err_t iot_button_new_adc_device(const button_config_t *button_config, const button_adc_config_t *adc_config, button_handle_t *ret_button)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(button_config && adc_config && ret_button, ESP_ERR_INVALID_ARG, TAG, "Invalid argument");
ESP_RETURN_ON_FALSE(adc_config->unit_id < ADC_UNIT_NUM, ESP_ERR_INVALID_ARG, TAG, "adc_handle out of range");
ESP_RETURN_ON_FALSE(adc_config->adc_channel < ADC_BUTTON_CHANNEL_MAX, ESP_ERR_INVALID_ARG, TAG, "channel out of range");
ESP_RETURN_ON_FALSE(adc_config->button_index < ADC_BUTTON_MAX_BUTTON, ESP_ERR_INVALID_ARG, TAG, "button_index out of range");
ESP_RETURN_ON_FALSE(adc_config->max > 0, ESP_ERR_INVALID_ARG, TAG, "key max voltage invalid");
button_adc_obj *adc_btn = calloc(1, sizeof(button_adc_obj));
ESP_RETURN_ON_FALSE(adc_btn, ESP_ERR_NO_MEM, TAG, "calloc fail");
adc_btn->unit_id = adc_config->unit_id;
int ch_index = find_channel(adc_btn->unit_id, adc_config->adc_channel);
if (ch_index >= 0) { /**< the channel has been initialized */
ESP_GOTO_ON_FALSE(g_button.unit[adc_btn->unit_id].ch[ch_index].btns[adc_config->button_index].max == 0, ESP_ERR_INVALID_STATE, err, TAG, "The button_index has been used");
} else { /**< this is a new channel */
int unused_ch_index = find_unused_channel(adc_config->unit_id);
ESP_GOTO_ON_FALSE(unused_ch_index >= 0, ESP_ERR_INVALID_STATE, err, TAG, "exceed max channel number, can't create a new channel");
ch_index = unused_ch_index;
}
/** initialize adc */
if (0 == g_button.unit[adc_btn->unit_id].is_configured) {
esp_err_t ret;
if (NULL == adc_config->adc_handle) {
//ADC1 Init
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = adc_btn->unit_id,
};
ret = adc_oneshot_new_unit(&init_config, &g_button.unit[adc_btn->unit_id].adc_handle);
ESP_GOTO_ON_FALSE(ret == ESP_OK, ESP_FAIL, err, TAG, "adc oneshot new unit fail!");
g_button.unit[adc_btn->unit_id].is_configured = ADC_INIT_BY_ADC_BUTTON;
} else {
g_button.unit[adc_btn->unit_id].adc_handle = *adc_config->adc_handle;
ESP_LOGI(TAG, "ADC1 has been initialized");
g_button.unit[adc_btn->unit_id].is_configured = ADC_INIT_BY_USER;
}
}
/** initialize adc channel */
if (0 == g_button.unit[adc_btn->unit_id].ch[ch_index].is_init) {
//ADC1 Config
adc_oneshot_chan_cfg_t oneshot_config = {
.bitwidth = ADC_BUTTON_WIDTH,
.atten = ADC_BUTTON_ATTEN,
};
esp_err_t ret = adc_oneshot_config_channel(g_button.unit[adc_btn->unit_id].adc_handle, adc_config->adc_channel, &oneshot_config);
ESP_GOTO_ON_FALSE(ret == ESP_OK, ESP_FAIL, err, TAG, "adc oneshot config channel fail!");
//-------------ADC1 Calibration Init---------------//
adc_calibration_init(adc_btn->unit_id, ADC_BUTTON_ATTEN, &g_button.unit[adc_btn->unit_id].adc_cali_handle);
g_button.unit[adc_btn->unit_id].ch[ch_index].channel = adc_config->adc_channel;
g_button.unit[adc_btn->unit_id].ch[ch_index].is_init = 1;
g_button.unit[adc_btn->unit_id].ch[ch_index].last_time = 0;
}
g_button.unit[adc_btn->unit_id].ch[ch_index].btns[adc_config->button_index].max = adc_config->max;
g_button.unit[adc_btn->unit_id].ch[ch_index].btns[adc_config->button_index].min = adc_config->min;
g_button.unit[adc_btn->unit_id].ch_num++;
adc_btn->ch = adc_config->adc_channel;
adc_btn->index = adc_config->button_index;
adc_btn->base.get_key_level = button_adc_get_key_level;
adc_btn->base.del = button_adc_del;
ret = iot_button_create(button_config, &adc_btn->base, ret_button);
ESP_GOTO_ON_FALSE(ret == ESP_OK, ESP_FAIL, err, TAG, "Create button failed");
return ESP_OK;
err:
if (adc_btn) {
free(adc_btn);
}
return ret;
}

View File

@@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "driver/gpio.h"
#include "button_gpio.h"
#include "esp_sleep.h"
#include "button_interface.h"
#include "iot_button.h"
static const char *TAG = "gpio_button";
typedef struct {
button_driver_t base; /**< button driver */
int32_t gpio_num; /**< num of gpio */
uint8_t active_level; /**< gpio level when press down */
bool enable_power_save; /**< enable power save */
} button_gpio_obj;
static esp_err_t button_gpio_del(button_driver_t *button_driver)
{
button_gpio_obj *gpio_btn = __containerof(button_driver, button_gpio_obj, base);
esp_err_t ret = gpio_reset_pin(gpio_btn->gpio_num);
free(gpio_btn);
return ret;
}
static uint8_t button_gpio_get_key_level(button_driver_t *button_driver)
{
button_gpio_obj *gpio_btn = __containerof(button_driver, button_gpio_obj, base);
int level = gpio_get_level(gpio_btn->gpio_num);
return level == gpio_btn->active_level ? 1 : 0;
}
static esp_err_t button_gpio_enable_gpio_wakeup(uint32_t gpio_num, uint8_t active_level, bool enable)
{
esp_err_t ret;
if (enable) {
gpio_intr_enable(gpio_num);
ret = gpio_wakeup_enable(gpio_num, active_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
} else {
gpio_intr_disable(gpio_num);
ret = gpio_wakeup_disable(gpio_num);
}
return ret;
}
static esp_err_t button_gpio_set_intr(int gpio_num, gpio_int_type_t intr_type, gpio_isr_t isr_handler)
{
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, (void *)gpio_num);
return ESP_OK;
}
static void button_power_save_isr_handler(void* arg)
{
/*!< resume the button */
iot_button_resume();
/*!< disable gpio wakeup not need active level*/
button_gpio_enable_gpio_wakeup((uint32_t)arg, 0, false);
}
static esp_err_t button_enter_power_save(button_driver_t *button_driver)
{
button_gpio_obj *gpio_btn = __containerof(button_driver, button_gpio_obj, base);
return button_gpio_enable_gpio_wakeup(gpio_btn->gpio_num, gpio_btn->active_level, true);
}
esp_err_t iot_button_new_gpio_device(const button_config_t *button_config, const button_gpio_config_t *gpio_cfg, button_handle_t *ret_button)
{
button_gpio_obj *gpio_btn = NULL;
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(button_config && gpio_cfg && ret_button, ESP_ERR_INVALID_ARG, err, TAG, "Invalid argument");
ESP_GOTO_ON_FALSE(GPIO_IS_VALID_GPIO(gpio_cfg->gpio_num), ESP_ERR_INVALID_ARG, err, TAG, "GPIO number error");
gpio_btn = (button_gpio_obj *)calloc(1, sizeof(button_gpio_obj));
ESP_GOTO_ON_FALSE(gpio_btn, ESP_ERR_NO_MEM, err, TAG, "No memory for gpio button");
gpio_btn->gpio_num = gpio_cfg->gpio_num;
gpio_btn->active_level = gpio_cfg->active_level;
gpio_btn->enable_power_save = gpio_cfg->enable_power_save;
gpio_config_t gpio_conf = {0};
gpio_conf.intr_type = GPIO_INTR_DISABLE;
gpio_conf.mode = GPIO_MODE_INPUT;
gpio_conf.pin_bit_mask = (1ULL << gpio_cfg->gpio_num);
if (!gpio_cfg->disable_pull) {
if (gpio_cfg->active_level) {
gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
} else {
gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
}
}
gpio_config(&gpio_conf);
if (gpio_cfg->enable_power_save) {
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
if (!esp_sleep_is_valid_wakeup_gpio(gpio_cfg->gpio_num)) {
ESP_LOGE(TAG, "GPIO %ld is not a valid wakeup source under CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE", gpio_cfg->gpio_num);
return ESP_FAIL;
}
gpio_hold_en(gpio_cfg->gpio_num);
#endif
/* Enable wake up from GPIO */
esp_err_t ret = gpio_wakeup_enable(gpio_cfg->gpio_num, gpio_cfg->active_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
ESP_GOTO_ON_FALSE(ret == ESP_OK, ESP_ERR_INVALID_STATE, err, TAG, "Enable gpio wakeup failed");
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
#if SOC_PM_SUPPORT_EXT1_WAKEUP
ret = esp_sleep_enable_ext1_wakeup_io((1ULL << gpio_cfg->gpio_num), gpio_cfg->active_level == 0 ? ESP_EXT1_WAKEUP_ANY_LOW : ESP_EXT1_WAKEUP_ANY_HIGH);
#else
/*!< Not support etc: esp32c2, esp32c3. Target must support ext1 wakeup */
ret = ESP_FAIL;
ESP_GOTO_ON_FALSE(ret == ESP_OK, ESP_FAIL, err, TAG, "Target must support ext1 wakeup");
#endif
#else
ret = esp_sleep_enable_gpio_wakeup();
#endif
ESP_GOTO_ON_FALSE(ret == ESP_OK, ESP_FAIL, err, TAG, "Configure gpio as wakeup source failed");
ret = button_gpio_set_intr(gpio_btn->gpio_num, gpio_cfg->active_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL, button_power_save_isr_handler);
ESP_GOTO_ON_FALSE(ret == ESP_OK, ESP_FAIL, err, TAG, "Set gpio interrupt failed");
gpio_btn->base.enable_power_save = true;
gpio_btn->base.enter_power_save = button_enter_power_save;
}
gpio_btn->base.get_key_level = button_gpio_get_key_level;
gpio_btn->base.del = button_gpio_del;
ret = iot_button_create(button_config, &gpio_btn->base, ret_button);
ESP_GOTO_ON_FALSE(ret == ESP_OK, ESP_FAIL, err, TAG, "Create button failed");
return ESP_OK;
err:
if (gpio_btn) {
free(gpio_btn);
}
return ret;
}

View File

@@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include "esp_log.h"
#include "esp_check.h"
#include "driver/gpio.h"
#include "button_matrix.h"
#include "button_interface.h"
static const char *TAG = "matrix_button";
typedef struct {
button_driver_t base; /**< base button driver */
int32_t row_gpio_num; /**< row gpio */
int32_t col_gpio_num; /**< col gpio */
} button_matrix_obj;
static esp_err_t button_matrix_gpio_init(int32_t gpio_num, gpio_mode_t mode)
{
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(gpio_num), ESP_ERR_INVALID_ARG, TAG, "gpio_num error");
gpio_config_t gpio_conf = {0};
gpio_conf.intr_type = GPIO_INTR_DISABLE;
gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpio_conf.pin_bit_mask = (1ULL << gpio_num);
gpio_conf.mode = mode;
gpio_config(&gpio_conf);
return ESP_OK;
}
esp_err_t button_matrix_del(button_driver_t *button_driver)
{
button_matrix_obj *matrix_btn = __containerof(button_driver, button_matrix_obj, base);
//Reset an gpio to default state (select gpio function, enable pullup and disable input and output).
gpio_reset_pin(matrix_btn->row_gpio_num);
gpio_reset_pin(matrix_btn->col_gpio_num);
free(matrix_btn);
return ESP_OK;
}
uint8_t button_matrix_get_key_level(button_driver_t *button_driver)
{
button_matrix_obj *matrix_btn = __containerof(button_driver, button_matrix_obj, base);
gpio_set_level(matrix_btn->row_gpio_num, 1);
uint8_t level = gpio_get_level(matrix_btn->col_gpio_num);
gpio_set_level(matrix_btn->row_gpio_num, 0);
return level;
}
esp_err_t iot_button_new_matrix_device(const button_config_t *button_config, const button_matrix_config_t *matrix_config, button_handle_t *ret_button, size_t *size)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(button_config && matrix_config && ret_button, ESP_ERR_INVALID_ARG, TAG, "Invalid argument");
ESP_RETURN_ON_FALSE(matrix_config->col_gpios && matrix_config->row_gpios, ESP_ERR_INVALID_ARG, TAG, "Invalid matrix config");
ESP_RETURN_ON_FALSE(matrix_config->col_gpio_num > 0 && matrix_config->row_gpio_num > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid matrix config");
ESP_RETURN_ON_FALSE(*size == matrix_config->row_gpio_num * matrix_config->col_gpio_num, ESP_ERR_INVALID_ARG, TAG, "Invalid size");
button_matrix_obj *matrix_btn = calloc(*size, sizeof(button_matrix_obj));
for (int i = 0; i < matrix_config->row_gpio_num; i++) {
button_matrix_gpio_init(matrix_config->row_gpios[i], GPIO_MODE_OUTPUT);
}
for (int i = 0; i < matrix_config->col_gpio_num; i++) {
button_matrix_gpio_init(matrix_config->col_gpios[i], GPIO_MODE_INPUT);
}
for (int i = 0; i < *size; i++) {
matrix_btn[i].base.get_key_level = button_matrix_get_key_level;
matrix_btn[i].base.del = button_matrix_del;
matrix_btn[i].row_gpio_num = matrix_config->row_gpios[i / matrix_config->col_gpio_num];
matrix_btn[i].col_gpio_num = matrix_config->col_gpios[i % matrix_config->col_gpio_num];
ESP_LOGD(TAG, "row_gpio_num: %"PRId32", col_gpio_num: %"PRId32"", matrix_btn[i].row_gpio_num, matrix_btn[i].col_gpio_num);
ret = iot_button_create(button_config, &matrix_btn[i].base, &ret_button[i]);
ESP_GOTO_ON_FALSE(ret == ESP_OK, ESP_FAIL, err, TAG, "Create button failed");
}
*size = matrix_config->row_gpio_num * matrix_config->col_gpio_num;
return ESP_OK;
err:
if (matrix_btn) {
free(matrix_btn);
}
return ret;
}

View File

@@ -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)

View File

@@ -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
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,87 @@
menu "Example Configuration"
choice ENTER_LIGHT_SLEEP_MODE
prompt "Enter light sleep mode"
default ENTER_LIGHT_SLEEP_AUTO
depends on PM_ENABLE
help
Enable light sleep mode to save power.
config ENTER_LIGHT_SLEEP_AUTO
bool "Auto enter Light Sleep"
config ENTER_LIGHT_SLEEP_MODE_MANUALLY
bool "Manually enter Light Sleep"
endchoice
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

View File

@@ -0,0 +1,6 @@
version: "0.1.0"
dependencies:
idf: ">=4.4"
button:
version: "^4.0.0"
override_path: "../../../../components/button"

View File

@@ -0,0 +1,125 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#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"
#include "button_gpio.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";
static void button_event_cb(void *arg, void *data)
{
iot_button_print_event((button_handle_t)arg);
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);
}
}
#if CONFIG_ENTER_LIGHT_SLEEP_MODE_MANUALLY
void button_enter_power_save(void *usr_data)
{
ESP_LOGI(TAG, "Can enter power save now");
esp_light_sleep_start();
}
#endif
void button_init(uint32_t button_num)
{
button_config_t btn_cfg = {0};
button_gpio_config_t gpio_cfg = {
.gpio_num = button_num,
.active_level = BUTTON_ACTIVE_LEVEL,
.enable_power_save = true,
};
button_handle_t btn;
esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &gpio_cfg, &btn);
assert(ret == ESP_OK);
ret = iot_button_register_cb(btn, BUTTON_PRESS_DOWN, NULL, button_event_cb, NULL);
ret |= iot_button_register_cb(btn, BUTTON_PRESS_UP, NULL, button_event_cb, NULL);
ret |= iot_button_register_cb(btn, BUTTON_PRESS_REPEAT, NULL, button_event_cb, NULL);
ret |= iot_button_register_cb(btn, BUTTON_PRESS_REPEAT_DONE, NULL, button_event_cb, NULL);
ret |= iot_button_register_cb(btn, BUTTON_SINGLE_CLICK, NULL, button_event_cb, NULL);
ret |= iot_button_register_cb(btn, BUTTON_DOUBLE_CLICK, NULL, button_event_cb, NULL);
ret |= iot_button_register_cb(btn, BUTTON_LONG_PRESS_START, NULL, button_event_cb, NULL);
ret |= iot_button_register_cb(btn, BUTTON_LONG_PRESS_HOLD, NULL, button_event_cb, NULL);
ret |= iot_button_register_cb(btn, BUTTON_LONG_PRESS_UP, NULL, button_event_cb, NULL);
ret |= iot_button_register_cb(btn, BUTTON_PRESS_END, NULL, button_event_cb, NULL);
#if CONFIG_ENTER_LIGHT_SLEEP_MODE_MANUALLY
/*!< For enter Power Save */
button_power_save_config_t config = {
.enter_power_save_cb = button_enter_power_save,
};
ret |= iot_button_register_power_save_cb(&config);
#endif
ESP_ERROR_CHECK(ret);
}
#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)
{
button_init(BOOT_BUTTON_NUM);
#if CONFIG_ENTER_LIGHT_SLEEP_AUTO
power_save_init();
#else
esp_light_sleep_start();
#endif
}

View File

@@ -0,0 +1 @@
CONFIG_ENTER_LIGHT_SLEEP_MODE_MANUALLY=y\

View File

@@ -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

View File

@@ -0,0 +1,12 @@
dependencies:
cmake_utilities: '*'
idf: '>=4.0'
description: GPIO and ADC and Matrix button driver
documentation: https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/button.html
issues: https://github.com/espressif/esp-iot-solution/issues
repository: git://github.com/espressif/esp-iot-solution.git
repository_info:
commit_sha: 3339788bc154632d7cd8e37bd4b1a83155835eee
path: components/button
url: https://github.com/espressif/esp-iot-solution/tree/master/components/button
version: 4.1.3

View File

@@ -0,0 +1,57 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/gpio.h"
#include "esp_adc/adc_oneshot.h"
#include "button_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief adc button configuration
*
*/
typedef struct {
adc_oneshot_unit_handle_t *adc_handle; /**< handle of adc unit, if NULL will create new one internal, else will use the handle */
adc_unit_t unit_id; /**< ADC unit */
uint8_t adc_channel; /**< Channel of ADC */
uint8_t button_index; /**< button index on the channel */
uint16_t min; /**< min voltage in mv corresponding to the button */
uint16_t max; /**< max voltage in mv corresponding to the button */
} button_adc_config_t;
/**
* @brief Create a new ADC button device
*
* This function initializes and configures a new ADC button device using the given configuration parameters.
* It manages the ADC unit, channels, and button-specific parameters, and ensures proper resource allocation
* for the ADC button object.
*
* @param[in] button_config Configuration for the button device, including callbacks and debounce parameters.
* @param[in] adc_config Configuration for the ADC channel and button, including the ADC unit, channel,
* button index, and voltage range (min and max).
* @param[out] ret_button Handle to the newly created button device.
*
* @return
* - ESP_OK: Successfully created the ADC button device.
* - ESP_ERR_INVALID_ARG: Invalid argument provided.
* - ESP_ERR_NO_MEM: Memory allocation failed.
* - ESP_ERR_INVALID_STATE: The requested button index or channel is already in use, or no channels are available.
* - ESP_FAIL: Failed to initialize or configure the ADC or button device.
*
* @note
* - If the ADC unit is not already configured, it will be initialized with the provided or default settings.
* - If the ADC channel is not initialized, it will be configured for the specified unit and calibrated.
* - This function ensures that ADC resources are reused whenever possible to optimize resource allocation.
*/
esp_err_t iot_button_new_adc_device(const button_config_t *button_config, const button_adc_config_t *adc_config, button_handle_t *ret_button);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,53 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "button_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief gpio button configuration
*
*/
typedef struct {
int32_t gpio_num; /**< num of gpio */
uint8_t active_level; /**< gpio level when press down */
bool enable_power_save; /**< enable power save mode */
bool disable_pull; /**< disable internal pull up or down */
} button_gpio_config_t;
/**
* @brief Create a new GPIO button device
*
* This function initializes and configures a GPIO-based button device using the given configuration parameters.
* It sets up the GPIO pin, configures its input mode, and optionally enables power-saving features or wake-up functionality.
*
* @param[in] button_config Configuration for the button device, including callbacks and debounce parameters.
* @param[in] gpio_cfg Configuration for the GPIO, including the pin number, active level, and power-save options.
* @param[out] ret_button Handle to the newly created GPIO button device.
*
* @return
* - ESP_OK: Successfully created the GPIO button device.
* - ESP_ERR_INVALID_ARG: Invalid argument provided, such as an invalid GPIO number.
* - ESP_ERR_NO_MEM: Memory allocation failed.
* - ESP_ERR_INVALID_STATE: Failed to configure GPIO wake-up or interrupt settings.
* - ESP_FAIL: General failure, such as unsupported wake-up configuration on the target.
*
* @note
* - If power-saving is enabled, the GPIO will be configured as a wake-up source for light sleep.
* - Pull-up or pull-down resistors are configured based on the `active_level` and the `disable_pull` flag.
* - This function checks for the validity of the GPIO as a wake-up source when power-saving is enabled.
* - If power-saving is not supported by the hardware or configuration, the function will return an error.
*/
esp_err_t iot_button_new_gpio_device(const button_config_t *button_config, const button_gpio_config_t *gpio_config, button_handle_t *ret_button);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "button_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Button matrix key configuration.
* Just need to configure the GPIO associated with this GPIO in the matrix keyboard.
*
* Matrix Keyboard Layout (3x3):
* ----------------------------------------
* | Button 1 | Button 2 | Button 3 |
* | (R1-C1) | (R1-C2) | (R1-C3) |
* |--------------------------------------|
* | Button 4 | Button 5 | Button 6 |
* | (R2-C1) | (R2-C2) | (R2-C3) |
* |--------------------------------------|
* | Button 7 | Button 8 | Button 9 |
* | (R3-C1) | (R3-C2) | (R3-C3) |
* ----------------------------------------
*
* - Button matrix key is driven using row scanning.
* - Buttons within the same column cannot be detected simultaneously,
* but buttons within the same row can be detected without conflicts.
*/
typedef struct {
int32_t *row_gpios; /**< GPIO number list for the row */
int32_t *col_gpios; /**< GPIO number list for the column */
uint32_t row_gpio_num; /**< Number of GPIOs associated with the row */
uint32_t col_gpio_num; /**< Number of GPIOs associated with the column */
} button_matrix_config_t;
/**
* @brief Create a new button matrix device
*
* This function initializes and configures a button matrix device using the specified row and column GPIOs.
* Each button in the matrix is represented as an independent button object, and its handle is returned in the `ret_button` array.
*
* @param[in] button_config Configuration for the button device, including callbacks and debounce parameters.
* @param[in] matrix_config Configuration for the matrix, including row and column GPIOs and their counts.
* @param[out] ret_button Array of handles for the buttons in the matrix.
* @param[inout] size Pointer to the total number of buttons in the matrix. Must match the product of row and column GPIO counts.
* On success, this value is updated to reflect the size of the button matrix.
*
* @return
* - ESP_OK: Successfully created the button matrix device.
* - ESP_ERR_INVALID_ARG: Invalid argument provided, such as null pointers or mismatched matrix dimensions.
* - ESP_ERR_NO_MEM: Memory allocation failed.
* - ESP_FAIL: General failure, such as button creation failure for one or more buttons.
*
* @note
* - Each row GPIO is configured as an output, while each column GPIO is configured as an input.
* - The total number of buttons in the matrix must equal the product of the row and column GPIO counts.
* - The `ret_button` array must be large enough to store handles for all buttons in the matrix.
* - If any button creation fails, the function will free all allocated resources and return an error.
*/
esp_err_t iot_button_new_matrix_device(const button_config_t *button_config, const button_matrix_config_t *matrix_config, button_handle_t *ret_button, size_t *size);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#include "button_interface.h"
#ifdef __cplusplus
extern "C" {
#endif
enum {
BUTTON_INACTIVE = 0,
BUTTON_ACTIVE,
};
typedef struct button_dev_t *button_handle_t;
/**
* @brief Button configuration
*
*/
typedef struct {
uint16_t long_press_time; /**< Trigger time(ms) for long press, if 0 default to BUTTON_LONG_PRESS_TIME_MS */
uint16_t short_press_time; /**< Trigger time(ms) for short press, if 0 default to BUTTON_SHORT_PRESS_TIME_MS */
} button_config_t;
/**
* @brief Create a new IoT button instance
*
* This function initializes a new button instance with the specified configuration
* and driver. It also sets up internal resources such as the button timer if not
* already initialized.
*
* @param[in] config Pointer to the button configuration structure
* @param[in] driver Pointer to the button driver structure
* @param[out] ret_button Pointer to where the handle of the created button will be stored
*
* @return
* - ESP_OK: Successfully created the button
* - ESP_ERR_INVALID_ARG: Invalid arguments passed to the function
* - ESP_ERR_NO_MEM: Memory allocation failed
*
* @note
* - The first call to this function logs the IoT Button version.
* - The function initializes a global button timer if it is not already running.
* - Timer is started only if the driver does not enable power-saving mode.
*/
esp_err_t iot_button_create(const button_config_t *config, const button_driver_t *driver, button_handle_t *ret_button);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,265 @@
/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "sdkconfig.h"
#include "esp_err.h"
#include "button_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (* button_cb_t)(void *button_handle, void *usr_data);
typedef void (* button_power_save_cb_t)(void *usr_data);
/**
* @brief Structs to store power save callback info
*
*/
typedef struct {
button_power_save_cb_t enter_power_save_cb; /**< Callback function when entering power save mode */
void *usr_data; /**< User data for the callback */
} button_power_save_config_t;
/**
* @brief Button events
*
*/
typedef enum {
BUTTON_PRESS_DOWN = 0,
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,
BUTTON_PRESS_END,
BUTTON_EVENT_MAX,
BUTTON_NONE_PRESS,
} button_event_t;
/**
* @brief Button events arg
*
*/
typedef union {
/**
* @brief Long press time event data
*
*/
struct long_press_t {
uint16_t press_time; /**< press time(ms) for the corresponding callback to trigger */
} long_press; /**< long press struct, for event BUTTON_LONG_PRESS_START and BUTTON_LONG_PRESS_UP */
/**
* @brief Multiple clicks event data
*
*/
struct multiple_clicks_t {
uint16_t clicks; /**< number of clicks, to trigger the callback */
} multiple_clicks; /**< multiple clicks struct, for event BUTTON_MULTIPLE_CLICK */
} button_event_args_t;
/**
* @brief Button parameter
*
*/
typedef enum {
BUTTON_LONG_PRESS_TIME_MS = 0,
BUTTON_SHORT_PRESS_TIME_MS,
BUTTON_PARAM_MAX,
} button_param_t;
/**
* @brief Delete a button
*
* @param btn_handle A button handle to delete
*
* @return
* - ESP_OK Success
* - ESP_FAIL Failure
*/
esp_err_t iot_button_delete(button_handle_t btn_handle);
/**
* @brief Register the button event callback function.
*
* @param btn_handle A button handle to register
* @param event Button event
* @param event_args Button event arguments
* @param cb Callback function.
* @param usr_data user data
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
* - ESP_ERR_INVALID_STATE The Callback is already registered. No free Space for another Callback.
* - ESP_ERR_NO_MEM No more memory allocation for the event
*/
esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_event_args_t *event_args, button_cb_t cb, void *usr_data);
/**
* @brief Unregister all the callbacks associated with the event.
*
* @param btn_handle A button handle to unregister
* @param event Button event
* @param event_args Used for unregistering a specific callback.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
* - ESP_ERR_INVALID_STATE No callbacks registered for the event
*/
esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event, button_event_args_t *event_args);
/**
* @brief counts total callbacks registered
*
* @param btn_handle A button handle to the button
*
* @return
* - 0 if no callbacks registered, or 1 .. (BUTTON_EVENT_MAX-1) for the number of Registered Buttons.
* - ESP_ERR_INVALID_ARG if btn_handle is invalid
*/
size_t iot_button_count_cb(button_handle_t btn_handle);
/**
* @brief how many callbacks are registered for the event
*
* @param btn_handle A button handle to the button
*
* @param event Button event
*
* @return
* - 0 if no callbacks registered, or 1 .. (BUTTON_EVENT_MAX-1) for the number of Registered Buttons.
* - ESP_ERR_INVALID_ARG if btn_handle is invalid
*/
size_t iot_button_count_event_cb(button_handle_t btn_handle, button_event_t event);
/**
* @brief Get button event
*
* @param btn_handle Button handle
*
* @return Current button event. See button_event_t
*/
button_event_t iot_button_get_event(button_handle_t btn_handle);
/**
* @brief Get the string representation of a button event.
*
* This function returns the corresponding string for a given button event.
* If the event value is outside the valid range, the function returns error string "event value is invalid".
*
* @param[in] event The button event to be converted to a string.
*
* @return
* - Pointer to the event string if the event is valid.
* - "invalid event" if the event value is invalid.
*/
const char *iot_button_get_event_str(button_event_t event);
/**
* @brief Log the current button event as a string.
*
* This function prints the string representation of the current event associated with the button.
*
* @param[in] btn_handle Handle to the button object.
*
* @return
* - ESP_OK: Successfully logged the event string.
* - ESP_FAIL: Invalid button handle.
*/
esp_err_t iot_button_print_event(button_handle_t btn_handle);
/**
* @brief Get button repeat times
*
* @param btn_handle Button handle
*
* @return button pressed times. For example, double-click return 2, triple-click return 3, etc.
*/
uint8_t iot_button_get_repeat(button_handle_t btn_handle);
/**
* @brief Get button ticks time
*
* @param btn_handle Button handle
*
* @return Actual time from press down to up (ms).
*/
uint32_t iot_button_get_ticks_time(button_handle_t btn_handle);
/**
* @brief Get button long press hold count
*
* @param btn_handle Button handle
*
* @return Count of trigger cb(BUTTON_LONG_PRESS_HOLD)
*/
uint16_t iot_button_get_long_press_hold_cnt(button_handle_t btn_handle);
/**
* @brief Dynamically change the parameters of the iot button
*
* @param btn_handle Button handle
* @param param Button parameter
* @param value new value
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
*/
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.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE timer state is invalid.
*/
esp_err_t iot_button_resume(void);
/**
* @brief stop button timer, if button timer is running. Make sure iot_button_create() is called before calling this API.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE timer state is invalid
*/
esp_err_t iot_button_stop(void);
/**
* @brief Register a callback function for power saving.
* The config->enter_power_save_cb function will be called when all keys stop working.
*
* @param config Button power save config
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE No button registered
* - ESP_ERR_INVALID_ARG Arguments is invalid
* - ESP_ERR_NO_MEM Not enough memory
*/
esp_err_t iot_button_register_power_save_cb(const button_power_save_config_t *config);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct button_driver_t button_driver_t; /*!< Type of button object */
struct button_driver_t {
/*!< (optional) Need Support Power Save */
bool enable_power_save;
/*!< (necessary) Get key level */
uint8_t (*get_key_level)(button_driver_t *button_driver);
/*!< (optional) Enter Power Save cb */
esp_err_t (*enter_power_save)(button_driver_t *button_driver);
/*!< (optional) Del the hardware driver and cleanup */
esp_err_t (*del)(button_driver_t *button_driver);
};
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,711 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "esp_check.h"
#include "iot_button.h"
#include "sdkconfig.h"
#include "button_interface.h"
static const char *TAG = "button";
static portMUX_TYPE s_button_lock = portMUX_INITIALIZER_UNLOCKED;
#define BUTTON_ENTER_CRITICAL() portENTER_CRITICAL(&s_button_lock)
#define BUTTON_EXIT_CRITICAL() portEXIT_CRITICAL(&s_button_lock)
#define BTN_CHECK(a, str, ret_val) \
if (!(a)) { \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
static const char *button_event_str[] = {
"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",
"BUTTON_PRESS_END",
"BUTTON_EVENT_MAX",
"BUTTON_NONE_PRESS",
};
enum {
PRESS_DOWN_CHECK = 0,
PRESS_UP_CHECK,
PRESS_REPEAT_DOWN_CHECK,
PRESS_REPEAT_UP_CHECK,
PRESS_LONG_PRESS_UP_CHECK,
};
/**
* @brief Structs to store callback info
*
*/
typedef struct {
button_cb_t cb;
void *usr_data;
button_event_args_t event_args;
} button_cb_info_t;
/**
* @brief Structs to record individual key parameters
*
*/
typedef struct button_dev_t {
uint32_t ticks; /*!< Count for the current button state. */
uint32_t long_press_ticks; /*!< Trigger ticks for long press, */
uint32_t short_press_ticks; /*!< Trigger ticks for repeat press */
uint32_t long_press_hold_cnt; /*!< Record long press hold count */
uint8_t repeat;
uint8_t state: 3;
uint8_t debounce_cnt: 4; /*!< Max 15 */
uint8_t button_level: 1;
button_event_t event;
button_driver_t *driver;
button_cb_info_t *cb_info[BUTTON_EVENT_MAX];
size_t size[BUTTON_EVENT_MAX];
int count[2];
struct button_dev_t *next;
} button_dev_t;
//button handle list head.
static button_dev_t *g_head_handle = NULL;
static esp_timer_handle_t g_button_timer_handle = NULL;
static bool g_is_timer_running = false;
static button_power_save_config_t power_save_usr_cfg = {0};
#define TICKS_INTERVAL CONFIG_BUTTON_PERIOD_TIME_MS
#define DEBOUNCE_TICKS CONFIG_BUTTON_DEBOUNCE_TICKS //MAX 8
#define SHORT_TICKS (CONFIG_BUTTON_SHORT_PRESS_TIME_MS /TICKS_INTERVAL)
#define LONG_TICKS (CONFIG_BUTTON_LONG_PRESS_TIME_MS /TICKS_INTERVAL)
#define SERIAL_TICKS (CONFIG_BUTTON_LONG_PRESS_HOLD_SERIAL_TIME_MS /TICKS_INTERVAL)
#define TOLERANCE (CONFIG_BUTTON_PERIOD_TIME_MS*4)
#define CALL_EVENT_CB(ev) \
if (btn->cb_info[ev]) { \
for (int i = 0; i < btn->size[ev]; i++) { \
btn->cb_info[ev][i].cb(btn, btn->cb_info[ev][i].usr_data); \
} \
} \
#define TIME_TO_TICKS(time, congfig_time) (0 == (time))?congfig_time:(((time) / TICKS_INTERVAL))?((time) / TICKS_INTERVAL):1
/**
* @brief Button driver core function, driver state machine.
*/
static void button_handler(button_dev_t *btn)
{
uint8_t read_gpio_level = btn->driver->get_key_level(btn->driver);
/** ticks counter working.. */
if ((btn->state) > 0) {
btn->ticks++;
}
/**< button debounce handle */
if (read_gpio_level != btn->button_level) {
if (++(btn->debounce_cnt) >= DEBOUNCE_TICKS) {
btn->button_level = read_gpio_level;
btn->debounce_cnt = 0;
}
} else {
btn->debounce_cnt = 0;
}
/** State machine */
switch (btn->state) {
case PRESS_DOWN_CHECK:
if (btn->button_level == BUTTON_ACTIVE) {
btn->event = (uint8_t)BUTTON_PRESS_DOWN;
CALL_EVENT_CB(BUTTON_PRESS_DOWN);
btn->ticks = 0;
btn->repeat = 1;
btn->state = PRESS_UP_CHECK;
} else {
btn->event = (uint8_t)BUTTON_NONE_PRESS;
}
break;
case PRESS_UP_CHECK:
if (btn->button_level != BUTTON_ACTIVE) {
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
btn->ticks = 0;
btn->state = PRESS_REPEAT_DOWN_CHECK;
} else if (btn->ticks >= btn->long_press_ticks) {
btn->event = (uint8_t)BUTTON_LONG_PRESS_START;
btn->state = PRESS_LONG_PRESS_UP_CHECK;
/** Calling callbacks for BUTTON_LONG_PRESS_START */
uint32_t ticks_time = iot_button_get_ticks_time(btn);
int32_t diff = ticks_time - btn->long_press_ticks * TICKS_INTERVAL;
if (btn->cb_info[btn->event] && btn->count[0] == 0) {
if (abs(diff) <= TOLERANCE && btn->cb_info[btn->event][btn->count[0]].event_args.long_press.press_time == (btn->long_press_ticks * TICKS_INTERVAL)) {
do {
btn->cb_info[btn->event][btn->count[0]].cb(btn, btn->cb_info[btn->event][btn->count[0]].usr_data);
btn->count[0]++;
if (btn->count[0] >= btn->size[btn->event]) {
break;
}
} while (btn->cb_info[btn->event][btn->count[0]].event_args.long_press.press_time == btn->long_press_ticks * TICKS_INTERVAL);
}
}
}
break;
case PRESS_REPEAT_DOWN_CHECK:
if (btn->button_level == BUTTON_ACTIVE) {
btn->event = (uint8_t)BUTTON_PRESS_DOWN;
CALL_EVENT_CB(BUTTON_PRESS_DOWN);
btn->event = (uint8_t)BUTTON_PRESS_REPEAT;
btn->repeat++;
CALL_EVENT_CB(BUTTON_PRESS_REPEAT); // repeat hit
btn->ticks = 0;
btn->state = PRESS_REPEAT_UP_CHECK;
} else if (btn->ticks > btn->short_press_ticks) {
if (btn->repeat == 1) {
btn->event = (uint8_t)BUTTON_SINGLE_CLICK;
CALL_EVENT_CB(BUTTON_SINGLE_CLICK);
} else if (btn->repeat == 2) {
btn->event = (uint8_t)BUTTON_DOUBLE_CLICK;
CALL_EVENT_CB(BUTTON_DOUBLE_CLICK); // repeat hit
}
btn->event = (uint8_t)BUTTON_MULTIPLE_CLICK;
/** Calling the callbacks for MULTIPLE BUTTON CLICKS */
for (int i = 0; i < btn->size[btn->event]; i++) {
if (btn->repeat == btn->cb_info[btn->event][i].event_args.multiple_clicks.clicks) {
do {
btn->cb_info[btn->event][i].cb(btn, btn->cb_info[btn->event][i].usr_data);
i++;
if (i >= btn->size[btn->event]) {
break;
}
} while (btn->cb_info[btn->event][i].event_args.multiple_clicks.clicks == btn->repeat);
}
}
btn->event = (uint8_t)BUTTON_PRESS_REPEAT_DONE;
CALL_EVENT_CB(BUTTON_PRESS_REPEAT_DONE); // repeat hit
btn->repeat = 0;
btn->state = 0;
btn->event = (uint8_t)BUTTON_PRESS_END;
CALL_EVENT_CB(BUTTON_PRESS_END);
}
break;
case 3:
if (btn->button_level != BUTTON_ACTIVE) {
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
if (btn->ticks < btn->short_press_ticks) {
btn->ticks = 0;
btn->state = PRESS_REPEAT_DOWN_CHECK; //repeat press
} else {
btn->state = PRESS_DOWN_CHECK;
btn->event = (uint8_t)BUTTON_PRESS_END;
CALL_EVENT_CB(BUTTON_PRESS_END);
}
}
break;
case PRESS_LONG_PRESS_UP_CHECK:
if (btn->button_level == BUTTON_ACTIVE) {
//continue hold trigger
if (btn->ticks >= (btn->long_press_hold_cnt + 1) * SERIAL_TICKS + btn->long_press_ticks) {
btn->event = (uint8_t)BUTTON_LONG_PRESS_HOLD;
btn->long_press_hold_cnt++;
CALL_EVENT_CB(BUTTON_LONG_PRESS_HOLD);
}
/** Calling callbacks for BUTTON_LONG_PRESS_START based on press_time */
uint32_t ticks_time = iot_button_get_ticks_time(btn);
if (btn->cb_info[BUTTON_LONG_PRESS_START]) {
button_cb_info_t *cb_info = btn->cb_info[BUTTON_LONG_PRESS_START];
uint16_t time = cb_info[btn->count[0]].event_args.long_press.press_time;
if (btn->long_press_ticks * TICKS_INTERVAL > time) {
for (int i = btn->count[0] + 1; i < btn->size[BUTTON_LONG_PRESS_START]; i++) {
time = cb_info[i].event_args.long_press.press_time;
if (btn->long_press_ticks * TICKS_INTERVAL <= time) {
btn->count[0] = i;
break;
}
}
}
if (btn->count[0] < btn->size[BUTTON_LONG_PRESS_START] && abs((int)ticks_time - (int)time) <= TOLERANCE) {
btn->event = (uint8_t)BUTTON_LONG_PRESS_START;
do {
cb_info[btn->count[0]].cb(btn, cb_info[btn->count[0]].usr_data);
btn->count[0]++;
if (btn->count[0] >= btn->size[BUTTON_LONG_PRESS_START]) {
break;
}
} while (time == cb_info[btn->count[0]].event_args.long_press.press_time);
}
}
/** Updating counter for BUTTON_LONG_PRESS_UP press_time */
if (btn->cb_info[BUTTON_LONG_PRESS_UP]) {
button_cb_info_t *cb_info = btn->cb_info[BUTTON_LONG_PRESS_UP];
uint16_t time = cb_info[btn->count[1] + 1].event_args.long_press.press_time;
if (btn->long_press_ticks * TICKS_INTERVAL > time) {
for (int i = btn->count[1] + 1; i < btn->size[BUTTON_LONG_PRESS_UP]; i++) {
time = cb_info[i].event_args.long_press.press_time;
if (btn->long_press_ticks * TICKS_INTERVAL <= time) {
btn->count[1] = i;
break;
}
}
}
if (btn->count[1] + 1 < btn->size[BUTTON_LONG_PRESS_UP] && abs((int)ticks_time - (int)time) <= TOLERANCE) {
do {
btn->count[1]++;
if (btn->count[1] + 1 >= btn->size[BUTTON_LONG_PRESS_UP]) {
break;
}
} while (time == cb_info[btn->count[1] + 1].event_args.long_press.press_time);
}
}
} else { //releasd
btn->event = BUTTON_LONG_PRESS_UP;
/** calling callbacks for BUTTON_LONG_PRESS_UP press_time */
if (btn->cb_info[btn->event] && btn->count[1] >= 0) {
button_cb_info_t *cb_info = btn->cb_info[btn->event];
do {
cb_info[btn->count[1]].cb(btn, cb_info[btn->count[1]].usr_data);
if (!btn->count[1]) {
break;
}
btn->count[1]--;
} while (cb_info[btn->count[1]].event_args.long_press.press_time == cb_info[btn->count[1] + 1].event_args.long_press.press_time);
/** Reset the counter */
btn->count[1] = -1;
}
/** Reset counter */
if (btn->cb_info[BUTTON_LONG_PRESS_START]) {
btn->count[0] = 0;
}
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
btn->state = PRESS_DOWN_CHECK; //reset
btn->long_press_hold_cnt = 0;
btn->event = (uint8_t)BUTTON_PRESS_END;
CALL_EVENT_CB(BUTTON_PRESS_END);
}
break;
}
}
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 */
bool enter_power_save_flag = true;
for (target = g_head_handle; target; target = target->next) {
button_handler(target);
if (!(target->driver->enable_power_save && target->debounce_cnt == 0 && target->event == BUTTON_NONE_PRESS)) {
enter_power_save_flag = false;
}
}
if (enter_power_save_flag) {
/*!< Stop esp timer for power save */
if (g_is_timer_running) {
esp_timer_stop(g_button_timer_handle);
g_is_timer_running = false;
}
for (target = g_head_handle; target; target = target->next) {
if (target->driver->enable_power_save && target->driver->enter_power_save) {
target->driver->enter_power_save(target->driver);
}
}
/*!< Notify the user that the Button has entered power save mode by calling this callback function. */
if (power_save_usr_cfg.enter_power_save_cb) {
power_save_usr_cfg.enter_power_save_cb(power_save_usr_cfg.usr_data);
}
}
}
esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_event_args_t *event_args, button_cb_t cb, void *usr_data)
{
ESP_RETURN_ON_FALSE(NULL != btn_handle, ESP_ERR_INVALID_ARG, TAG, "Pointer of handle is invalid");
button_dev_t *btn = (button_dev_t *) btn_handle;
ESP_RETURN_ON_FALSE(event < BUTTON_EVENT_MAX, ESP_ERR_INVALID_ARG, TAG, "event is invalid");
ESP_RETURN_ON_FALSE(NULL != cb, ESP_ERR_INVALID_ARG, TAG, "Pointer of cb is invalid");
ESP_RETURN_ON_FALSE(event != BUTTON_MULTIPLE_CLICK || event_args, ESP_ERR_INVALID_ARG, TAG, "event is invalid");
if (event_args) {
ESP_RETURN_ON_FALSE(!(event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) || event_args->long_press.press_time > btn->short_press_ticks * TICKS_INTERVAL, ESP_ERR_INVALID_ARG, TAG, "event_args is invalid");
ESP_RETURN_ON_FALSE(event != BUTTON_MULTIPLE_CLICK || event_args->multiple_clicks.clicks, ESP_ERR_INVALID_ARG, TAG, "event_args is invalid");
}
if (!btn->cb_info[event]) {
btn->cb_info[event] = calloc(1, sizeof(button_cb_info_t));
BTN_CHECK(NULL != btn->cb_info[event], "calloc cb_info failed", ESP_ERR_NO_MEM);
if (event == BUTTON_LONG_PRESS_START) {
btn->count[0] = 0;
} else if (event == BUTTON_LONG_PRESS_UP) {
btn->count[1] = -1;
}
} else {
button_cb_info_t *p = realloc(btn->cb_info[event], sizeof(button_cb_info_t) * (btn->size[event] + 1));
BTN_CHECK(NULL != p, "realloc cb_info failed", ESP_ERR_NO_MEM);
btn->cb_info[event] = p;
}
btn->cb_info[event][btn->size[event]].cb = cb;
btn->cb_info[event][btn->size[event]].usr_data = usr_data;
btn->size[event]++;
/** Inserting the event_args in sorted manner */
if (event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) {
uint16_t press_time = btn->long_press_ticks * TICKS_INTERVAL;
if (event_args) {
press_time = event_args->long_press.press_time;
}
BTN_CHECK(press_time / TICKS_INTERVAL > btn->short_press_ticks, "press_time event_args is less than short_press_ticks", ESP_ERR_INVALID_ARG);
if (btn->size[event] >= 2) {
for (int i = btn->size[event] - 2; i >= 0; i--) {
if (btn->cb_info[event][i].event_args.long_press.press_time > press_time) {
btn->cb_info[event][i + 1] = btn->cb_info[event][i];
btn->cb_info[event][i].event_args.long_press.press_time = press_time;
btn->cb_info[event][i].cb = cb;
btn->cb_info[event][i].usr_data = usr_data;
} else {
btn->cb_info[event][i + 1].event_args.long_press.press_time = press_time;
btn->cb_info[event][i + 1].cb = cb;
btn->cb_info[event][i + 1].usr_data = usr_data;
break;
}
}
} else {
btn->cb_info[event][btn->size[event] - 1].event_args.long_press.press_time = press_time;
}
int32_t press_ticks = press_time / TICKS_INTERVAL;
if (btn->short_press_ticks < press_ticks && press_ticks < btn->long_press_ticks) {
iot_button_set_param(btn, BUTTON_LONG_PRESS_TIME_MS, (void*)(intptr_t)press_time);
}
}
if (event == BUTTON_MULTIPLE_CLICK) {
uint16_t clicks = btn->long_press_ticks * TICKS_INTERVAL;
if (event_args) {
clicks = event_args->multiple_clicks.clicks;
}
if (btn->size[event] >= 2) {
for (int i = btn->size[event] - 2; i >= 0; i--) {
if (btn->cb_info[event][i].event_args.multiple_clicks.clicks > clicks) {
btn->cb_info[event][i + 1] = btn->cb_info[event][i];
btn->cb_info[event][i].event_args.multiple_clicks.clicks = clicks;
btn->cb_info[event][i].cb = cb;
btn->cb_info[event][i].usr_data = usr_data;
} else {
btn->cb_info[event][i + 1].event_args.multiple_clicks.clicks = clicks;
btn->cb_info[event][i + 1].cb = cb;
btn->cb_info[event][i + 1].usr_data = usr_data;
break;
}
}
} else {
btn->cb_info[event][btn->size[event] - 1].event_args.multiple_clicks.clicks = clicks;
}
}
return ESP_OK;
}
esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event, button_event_args_t *event_args)
{
ESP_RETURN_ON_FALSE(NULL != btn_handle, ESP_ERR_INVALID_ARG, TAG, "Pointer of handle is invalid");
ESP_RETURN_ON_FALSE(event < BUTTON_EVENT_MAX, ESP_ERR_INVALID_ARG, TAG, "event is invalid");
button_dev_t *btn = (button_dev_t *) btn_handle;
ESP_RETURN_ON_FALSE(btn->cb_info[event], ESP_ERR_INVALID_STATE, TAG, "No callbacks registered for the event");
int check = -1;
if ((event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) && event_args) {
if (event_args->long_press.press_time != 0) {
goto unregister_event;
}
}
if (event == BUTTON_MULTIPLE_CLICK && event_args) {
if (event_args->multiple_clicks.clicks != 0) {
goto unregister_event;
}
}
if (btn->cb_info[event]) {
free(btn->cb_info[event]);
/** Reset the counter */
if (event == BUTTON_LONG_PRESS_START) {
btn->count[0] = 0;
} else if (event == BUTTON_LONG_PRESS_UP) {
btn->count[1] = -1;
}
}
btn->cb_info[event] = NULL;
btn->size[event] = 0;
return ESP_OK;
unregister_event:
for (int i = 0; i < btn->size[event]; i++) {
if ((event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) && event_args->long_press.press_time) {
if (event_args->long_press.press_time != btn->cb_info[event][i].event_args.long_press.press_time) {
continue;
}
}
if (event == BUTTON_MULTIPLE_CLICK && event_args->multiple_clicks.clicks) {
if (event_args->multiple_clicks.clicks != btn->cb_info[event][i].event_args.multiple_clicks.clicks) {
continue;
}
}
check = i;
for (int j = i; j <= btn->size[event] - 1; j++) {
btn->cb_info[event][j] = btn->cb_info[event][j + 1];
}
if (btn->size[event] != 1) {
button_cb_info_t *p = realloc(btn->cb_info[event], sizeof(button_cb_info_t) * (btn->size[event] - 1));
BTN_CHECK(NULL != p, "realloc cb_info failed", ESP_ERR_NO_MEM);
btn->cb_info[event] = p;
btn->size[event]--;
} else {
free(btn->cb_info[event]);
btn->cb_info[event] = NULL;
btn->size[event] = 0;
}
break;
}
ESP_RETURN_ON_FALSE(check != -1, ESP_ERR_NOT_FOUND, TAG, "No such callback registered for the event");
return ESP_OK;
}
size_t iot_button_count_cb(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
size_t ret = 0;
for (size_t i = 0; i < BUTTON_EVENT_MAX; i++) {
if (btn->cb_info[i]) {
ret += btn->size[i];
}
}
return ret;
}
size_t iot_button_count_event_cb(button_handle_t btn_handle, button_event_t event)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
return btn->size[event];
}
button_event_t iot_button_get_event(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", BUTTON_NONE_PRESS);
button_dev_t *btn = (button_dev_t *) btn_handle;
return btn->event;
}
const char *iot_button_get_event_str(button_event_t event)
{
BTN_CHECK(event <= BUTTON_NONE_PRESS && event >= BUTTON_PRESS_DOWN, "event value is invalid", "invalid event");
return button_event_str[event];
}
esp_err_t iot_button_print_event(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_FAIL);
button_dev_t *btn = (button_dev_t *) btn_handle;
ESP_LOGI(TAG, "%s", button_event_str[btn->event]);
return ESP_OK;
}
uint8_t iot_button_get_repeat(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;
return btn->repeat;
}
uint32_t iot_button_get_ticks_time(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;
return (btn->ticks * TICKS_INTERVAL);
}
uint16_t iot_button_get_long_press_hold_cnt(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;
return btn->long_press_hold_cnt;
}
esp_err_t iot_button_set_param(button_handle_t btn_handle, button_param_t param, void *value)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
BUTTON_ENTER_CRITICAL();
switch (param) {
case BUTTON_LONG_PRESS_TIME_MS:
btn->long_press_ticks = (int32_t)value / TICKS_INTERVAL;
break;
case BUTTON_SHORT_PRESS_TIME_MS:
btn->short_press_ticks = (int32_t)value / TICKS_INTERVAL;
break;
default:
break;
}
BUTTON_EXIT_CRITICAL();
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->driver->get_key_level(btn->driver);
return level;
}
esp_err_t iot_button_resume(void)
{
if (!g_button_timer_handle) {
return ESP_ERR_INVALID_STATE;
}
if (!g_is_timer_running) {
esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U);
g_is_timer_running = true;
}
return ESP_OK;
}
esp_err_t iot_button_stop(void)
{
BTN_CHECK(g_button_timer_handle, "Button timer handle is invalid", ESP_ERR_INVALID_STATE);
BTN_CHECK(g_is_timer_running, "Button timer is not running", ESP_ERR_INVALID_STATE);
esp_err_t err = esp_timer_stop(g_button_timer_handle);
BTN_CHECK(ESP_OK == err, "Button timer stop failed", ESP_FAIL);
g_is_timer_running = false;
return ESP_OK;
}
esp_err_t iot_button_register_power_save_cb(const button_power_save_config_t *config)
{
BTN_CHECK(g_head_handle, "No button registered", ESP_ERR_INVALID_STATE);
BTN_CHECK(config->enter_power_save_cb, "Enter power save callback is invalid", ESP_ERR_INVALID_ARG);
power_save_usr_cfg.enter_power_save_cb = config->enter_power_save_cb;
power_save_usr_cfg.usr_data = config->usr_data;
return ESP_OK;
}
esp_err_t iot_button_create(const button_config_t *config, const button_driver_t *driver, button_handle_t *ret_button)
{
if (!g_head_handle) {
ESP_LOGI(TAG, "IoT Button Version: %d.%d.%d", BUTTON_VER_MAJOR, BUTTON_VER_MINOR, BUTTON_VER_PATCH);
}
ESP_RETURN_ON_FALSE(driver && config && ret_button, ESP_ERR_INVALID_ARG, TAG, "Invalid argument");
button_dev_t *btn = (button_dev_t *) calloc(1, sizeof(button_dev_t));
ESP_RETURN_ON_FALSE(btn, ESP_ERR_NO_MEM, TAG, "Button memory alloc failed");
btn->driver = (button_driver_t *)driver;
btn->long_press_ticks = TIME_TO_TICKS(config->long_press_time, LONG_TICKS);
btn->short_press_ticks = TIME_TO_TICKS(config->short_press_time, SHORT_TICKS);
btn->event = BUTTON_NONE_PRESS;
btn->button_level = BUTTON_INACTIVE;
btn->next = g_head_handle;
g_head_handle = btn;
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);
}
if (!driver->enable_power_save && !g_is_timer_running) {
esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U);
g_is_timer_running = true;
}
*ret_button = (button_handle_t)btn;
return ESP_OK;
}
esp_err_t iot_button_delete(button_handle_t btn_handle)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_FALSE(NULL != btn_handle, ESP_ERR_INVALID_ARG, TAG, "Pointer of handle is invalid");
button_dev_t *btn = (button_dev_t *)btn_handle;
for (int i = 0; i < BUTTON_EVENT_MAX; i++) {
if (btn->cb_info[i]) {
free(btn->cb_info[i]);
}
}
ret = btn->driver->del(btn->driver);
ESP_RETURN_ON_FALSE(ESP_OK == ret, ret, TAG, "Failed to delete button driver");
button_dev_t **curr;
for (curr = &g_head_handle; *curr;) {
button_dev_t *entry = *curr;
if (entry == btn) {
*curr = entry->next;
free(entry);
} else {
curr = &entry->next;
}
}
/* count button number */
uint16_t number = 0;
button_dev_t *target = g_head_handle;
while (target) {
target = target->next;
number++;
}
ESP_LOGD(TAG, "remain btn number=%d", number);
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;
}

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,9 @@
# The following 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)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components"
"../../button")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(button_test)

View File

@@ -0,0 +1,9 @@
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
list(APPEND PRIVREQ esp_adc)
endif()
idf_component_register(SRC_DIRS "."
PRIV_REQUIRES esp_event
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES unity test_utils button ${PRIVREQ}
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,134 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/timers.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include "esp_idf_version.h"
#include "esp_log.h"
#include "unity.h"
#include "iot_button.h"
#include "button_adc.h"
static const char *TAG = "ADC BUTTON TEST";
static void button_event_cb(void *arg, void *data)
{
button_event_t event = iot_button_get_event(arg);
ESP_LOGI(TAG, "BTN[%d] %s", (int)data, iot_button_get_event_str(event));
if (BUTTON_PRESS_REPEAT == event || BUTTON_PRESS_REPEAT_DONE == event) {
ESP_LOGI(TAG, "\tREPEAT[%d]", iot_button_get_repeat(arg));
}
if (BUTTON_PRESS_UP == event || BUTTON_LONG_PRESS_HOLD == event || BUTTON_LONG_PRESS_UP == event) {
ESP_LOGI(TAG, "\tTICKS[%"PRIu32"]", iot_button_get_ticks_time(arg));
}
if (BUTTON_MULTIPLE_CLICK == event) {
ESP_LOGI(TAG, "\tMULTIPLE[%d]", (int)data);
}
}
TEST_CASE("adc button test", "[button][adc]")
{
/** ESP32-S3-Korvo2 board */
const button_config_t btn_cfg = {0};
button_adc_config_t btn_adc_cfg = {
.unit_id = ADC_UNIT_1,
.adc_channel = 4,
};
button_handle_t btns[6] = {NULL};
const uint16_t vol[6] = {380, 820, 1180, 1570, 1980, 2410};
for (size_t i = 0; i < 6; i++) {
btn_adc_cfg.button_index = i;
if (i == 0) {
btn_adc_cfg.min = (0 + vol[i]) / 2;
} else {
btn_adc_cfg.min = (vol[i - 1] + vol[i]) / 2;
}
if (i == 5) {
btn_adc_cfg.max = (vol[i] + 3000) / 2;
} else {
btn_adc_cfg.max = (vol[i] + vol[i + 1]) / 2;
}
esp_err_t ret = iot_button_new_adc_device(&btn_cfg, &btn_adc_cfg, &btns[i]);
TEST_ASSERT(ret == ESP_OK);
TEST_ASSERT_NOT_NULL(btns[i]);
iot_button_register_cb(btns[i], BUTTON_PRESS_DOWN, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_PRESS_UP, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_PRESS_REPEAT, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_PRESS_REPEAT_DONE, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_SINGLE_CLICK, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_DOUBLE_CLICK, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_LONG_PRESS_START, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_LONG_PRESS_HOLD, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_LONG_PRESS_UP, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_PRESS_END, NULL, button_event_cb, (void *)i);
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
for (size_t i = 0; i < 6; i++) {
iot_button_delete(btns[i]);
}
}
TEST_CASE("adc button test memory leak", "[button][adc][memory leak]")
{
/** ESP32-S3-Korvo2 board */
const button_config_t btn_cfg = {0};
button_adc_config_t btn_adc_cfg = {
.unit_id = ADC_UNIT_1,
.adc_channel = 4,
};
button_handle_t btns[6] = {NULL};
const uint16_t vol[6] = {380, 820, 1180, 1570, 1980, 2410};
for (size_t i = 0; i < 6; i++) {
btn_adc_cfg.button_index = i;
if (i == 0) {
btn_adc_cfg.min = (0 + vol[i]) / 2;
} else {
btn_adc_cfg.min = (vol[i - 1] + vol[i]) / 2;
}
if (i == 5) {
btn_adc_cfg.max = (vol[i] + 3000) / 2;
} else {
btn_adc_cfg.max = (vol[i] + vol[i + 1]) / 2;
}
esp_err_t ret = iot_button_new_adc_device(&btn_cfg, &btn_adc_cfg, &btns[i]);
TEST_ASSERT(ret == ESP_OK);
TEST_ASSERT_NOT_NULL(btns[i]);
iot_button_register_cb(btns[i], BUTTON_PRESS_DOWN, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_PRESS_UP, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_PRESS_REPEAT, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_PRESS_REPEAT_DONE, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_SINGLE_CLICK, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_DOUBLE_CLICK, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_LONG_PRESS_START, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_LONG_PRESS_HOLD, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_LONG_PRESS_UP, NULL, button_event_cb, (void *)i);
iot_button_register_cb(btns[i], BUTTON_PRESS_END, NULL, button_event_cb, (void *)i);
}
for (size_t i = 0; i < 6; i++) {
iot_button_delete(btns[i]);
}
}

View File

@@ -0,0 +1,288 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "unity.h"
#include "iot_button.h"
#include "button_gpio.h"
#include "driver/gpio.h"
static const char *TAG = "BUTTON AUTO TEST";
#define GPIO_OUTPUT_IO_45 45
#define BUTTON_IO_NUM 0
#define BUTTON_ACTIVE_LEVEL 0
static EventGroupHandle_t g_check = NULL;
static SemaphoreHandle_t g_auto_check_pass = NULL;
static button_event_t state = BUTTON_PRESS_DOWN;
static void button_auto_press_test_task(void *arg)
{
// test BUTTON_PRESS_DOWN
xEventGroupWaitBits(g_check, BIT(0) | BIT(1), pdTRUE, pdTRUE, portMAX_DELAY);
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
vTaskDelay(pdMS_TO_TICKS(100));
// // test BUTTON_PRESS_UP
xEventGroupWaitBits(g_check, BIT(0) | BIT(1), pdTRUE, pdTRUE, portMAX_DELAY);
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
vTaskDelay(pdMS_TO_TICKS(200));
// test BUTTON_PRESS_REPEAT
xEventGroupWaitBits(g_check, BIT(0) | BIT(1), pdTRUE, pdTRUE, portMAX_DELAY);
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
vTaskDelay(pdMS_TO_TICKS(100));
// test BUTTON_PRESS_REPEAT_DONE
xEventGroupWaitBits(g_check, BIT(0) | BIT(1), pdTRUE, pdTRUE, portMAX_DELAY);
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
vTaskDelay(pdMS_TO_TICKS(200));
// test BUTTON_SINGLE_CLICK
xEventGroupWaitBits(g_check, BIT(0) | BIT(1), pdTRUE, pdTRUE, portMAX_DELAY);
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
vTaskDelay(pdMS_TO_TICKS(200));
// test BUTTON_DOUBLE_CLICK
xEventGroupWaitBits(g_check, BIT(0) | BIT(1), pdTRUE, pdTRUE, portMAX_DELAY);
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
vTaskDelay(pdMS_TO_TICKS(200));
// test BUTTON_MULTIPLE_CLICK
xEventGroupWaitBits(g_check, BIT(0) | BIT(1), pdTRUE, pdTRUE, portMAX_DELAY);
for (int i = 0; i < 4; i++) {
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
vTaskDelay(pdMS_TO_TICKS(100));
}
vTaskDelay(pdMS_TO_TICKS(100));
// test BUTTON_LONG_PRESS_START
xEventGroupWaitBits(g_check, BIT(0) | BIT(1), pdTRUE, pdTRUE, portMAX_DELAY);
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
vTaskDelay(pdMS_TO_TICKS(1600));
// test BUTTON_LONG_PRESS_HOLD and BUTTON_LONG_PRESS_UP
xEventGroupWaitBits(g_check, BIT(0) | BIT(1), pdTRUE, pdTRUE, portMAX_DELAY);
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
ESP_LOGI(TAG, "Auto Press Success!");
vTaskDelete(NULL);
}
static void button_auto_check_cb_1(void *arg, void *data)
{
if (iot_button_get_event(arg) == state) {
xEventGroupSetBits(g_check, BIT(1));
}
}
static void button_auto_check_cb(void *arg, void *data)
{
if (iot_button_get_event(arg) == state) {
ESP_LOGI(TAG, "Auto check: button event %s pass", iot_button_get_event_str(state));
xEventGroupSetBits(g_check, BIT(0));
if (++state >= BUTTON_EVENT_MAX) {
xSemaphoreGive(g_auto_check_pass);
return;
}
}
}
TEST_CASE("gpio button auto-test", "[button][iot][auto]")
{
state = BUTTON_PRESS_DOWN;
g_check = xEventGroupCreate();
g_auto_check_pass = xSemaphoreCreateBinary();
xEventGroupSetBits(g_check, BIT(0) | BIT(1));
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = BUTTON_IO_NUM,
.active_level = BUTTON_ACTIVE_LEVEL,
};
button_handle_t btn = NULL;
esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn);
TEST_ASSERT(ret == ESP_OK);
TEST_ASSERT_NOT_NULL(btn);
/* register iot_button callback for all the button_event */
for (uint8_t i = 0; i < BUTTON_EVENT_MAX; i++) {
if (i == BUTTON_MULTIPLE_CLICK) {
button_event_args_t args = {
.multiple_clicks.clicks = 4,
};
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_auto_check_cb_1, NULL);
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_auto_check_cb, NULL);
} else {
iot_button_register_cb(btn, i, NULL, button_auto_check_cb_1, NULL);
iot_button_register_cb(btn, i, NULL, button_auto_check_cb, NULL);
}
}
TEST_ASSERT_EQUAL(ESP_OK, iot_button_set_param(btn, BUTTON_LONG_PRESS_TIME_MS, (void *)1500));
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << GPIO_OUTPUT_IO_45),
.pull_down_en = 0,
.pull_up_en = 0,
};
gpio_config(&io_conf);
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
xTaskCreate(button_auto_press_test_task, "button_auto_press_test_task", 1024 * 4, NULL, 10, NULL);
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(g_auto_check_pass, pdMS_TO_TICKS(6000)));
for (uint8_t i = 0; i < BUTTON_EVENT_MAX; i++) {
button_event_args_t args;
if (i == BUTTON_MULTIPLE_CLICK) {
args.multiple_clicks.clicks = 4;
iot_button_unregister_cb(btn, i, &args);
} else if (i == BUTTON_LONG_PRESS_UP || i == BUTTON_LONG_PRESS_START) {
args.long_press.press_time = 1500;
iot_button_unregister_cb(btn, i, &args);
} else {
iot_button_unregister_cb(btn, i, NULL);
}
}
TEST_ASSERT_EQUAL(ESP_OK, iot_button_delete(btn));
vEventGroupDelete(g_check);
vSemaphoreDelete(g_auto_check_pass);
vTaskDelay(pdMS_TO_TICKS(100));
}
#define TOLERANCE (CONFIG_BUTTON_PERIOD_TIME_MS * 4)
uint16_t long_press_time[5] = {2000, 2500, 3000, 3500, 4000};
static SemaphoreHandle_t long_press_check = NULL;
static SemaphoreHandle_t long_press_auto_check_pass = NULL;
unsigned int status = 0;
static void button_auto_long_press_test_task(void *arg)
{
// Test for BUTTON_LONG_PRESS_START
for (int i = 0; i < 5; i++) {
xSemaphoreTake(long_press_check, portMAX_DELAY);
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
status = (BUTTON_LONG_PRESS_START << 16) | long_press_time[i];
if (i > 0) {
vTaskDelay(pdMS_TO_TICKS(long_press_time[i] - long_press_time[i - 1]));
} else {
vTaskDelay(pdMS_TO_TICKS(long_press_time[i]));
}
}
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
xSemaphoreGive(long_press_auto_check_pass);
vTaskDelay(pdMS_TO_TICKS(100));
// Test for BUTTON_LONG_PRESS_UP
for (int i = 0; i < 5; i++) {
xSemaphoreTake(long_press_check, portMAX_DELAY);
status = (BUTTON_LONG_PRESS_UP << 16) | long_press_time[i];
gpio_set_level(GPIO_OUTPUT_IO_45, 0);
vTaskDelay(pdMS_TO_TICKS(long_press_time[i] + 10));
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
}
ESP_LOGI(TAG, "Auto Long Press Success!");
vTaskDelete(NULL);
}
static void button_long_press_auto_check_cb(void *arg, void *data)
{
uint32_t value = (uint32_t)data;
uint16_t event = (0xffff0000 & value) >> 16;
uint16_t time = 0xffff & value;
uint32_t ticks_time = iot_button_get_ticks_time(arg);
int32_t diff = ticks_time - time;
if (status == value && abs(diff) <= TOLERANCE) {
ESP_LOGI(TAG, "Auto check: button event: %s and time: %d pass", iot_button_get_event_str(event), time);
if (event == BUTTON_LONG_PRESS_UP && time == long_press_time[4]) {
xSemaphoreGive(long_press_auto_check_pass);
}
xSemaphoreGive(long_press_check);
}
}
TEST_CASE("gpio button long_press auto-test", "[button][long_press][auto]")
{
ESP_LOGI(TAG, "Starting the test");
long_press_check = xSemaphoreCreateBinary();
long_press_auto_check_pass = xSemaphoreCreateBinary();
xSemaphoreGive(long_press_check);
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = BUTTON_IO_NUM,
.active_level = BUTTON_ACTIVE_LEVEL,
};
button_handle_t btn = NULL;
esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn);
TEST_ASSERT(ret == ESP_OK);
TEST_ASSERT_NOT_NULL(btn);
for (int i = 0; i < 5; i++) {
button_event_args_t args = {
.long_press.press_time = long_press_time[i],
};
uint32_t data = (BUTTON_LONG_PRESS_START << 16) | long_press_time[i];
iot_button_register_cb(btn, BUTTON_LONG_PRESS_START, &args, button_long_press_auto_check_cb, (void*)data);
}
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = (1ULL << GPIO_OUTPUT_IO_45),
.pull_down_en = 0,
.pull_up_en = 0,
};
gpio_config(&io_conf);
gpio_set_level(GPIO_OUTPUT_IO_45, 1);
xTaskCreate(button_auto_long_press_test_task, "button_auto_long_press_test_task", 1024 * 4, NULL, 10, NULL);
xSemaphoreTake(long_press_auto_check_pass, portMAX_DELAY);
iot_button_unregister_cb(btn, BUTTON_LONG_PRESS_START, NULL);
for (int i = 0; i < 5; i++) {
button_event_args_t args = {
.long_press.press_time = long_press_time[i]
};
uint32_t data = (BUTTON_LONG_PRESS_UP << 16) | long_press_time[i];
iot_button_register_cb(btn, BUTTON_LONG_PRESS_UP, &args, button_long_press_auto_check_cb, (void*)data);
}
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(long_press_auto_check_pass, pdMS_TO_TICKS(17000)));
TEST_ASSERT_EQUAL(ESP_OK, iot_button_delete(btn));
vSemaphoreDelete(long_press_check);
vSemaphoreDelete(long_press_auto_check_pass);
vTaskDelay(pdMS_TO_TICKS(100));
}

View File

@@ -0,0 +1,41 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
#include "esp_heap_caps.h"
#include "sdkconfig.h"
#define LEAKS (400)
void setUp(void)
{
unity_utils_record_free_mem();
}
void tearDown(void)
{
unity_utils_evaluate_leaks_direct(LEAKS);
}
void app_main(void)
{
/*
* ____ _ _ _______ _
*| _ \ | | | | |__ __| | |
*| |_) | _ _ | |_ | |_ ___ _ __ | | ___ ___ | |_
*| _ < | | | || __|| __|/ _ \ | '_ \ | | / _ \/ __|| __|
*| |_) || |_| || |_ | |_| (_) || | | | | || __/\__ \| |_
*|____/ \__,_| \__| \__|\___/ |_| |_| |_| \___||___/ \__|
*/
printf(" ____ _ _ _______ _ \n");
printf(" | _ \\ | | | | |__ __| | | \n");
printf(" | |_) | _ _ | |_ | |_ ___ _ __ | | ___ ___ | |_ \n");
printf(" | _ < | | | || __|| __|/ _ \\ | '_ \\ | | / _ \\/ __|| __|\n");
printf(" | |_) || |_| || |_ | |_| (_) || | | | | || __/\\__ \\| |_ \n");
printf(" |____/ \\__,_| \\__| \\__|\\___/ |_| |_| |_| \\___||___/ \\__|\n");
unity_run_menu();
}

View File

@@ -0,0 +1,104 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "unity.h"
#include "iot_button.h"
#include "driver/gpio.h"
static const char *TAG = "CUSTOM BUTTON TEST";
#define BUTTON_IO_NUM 0
#define BUTTON_ACTIVE_LEVEL 0
static void button_event_cb(void *arg, void *data)
{
button_event_t event = iot_button_get_event(arg);
ESP_LOGI(TAG, "%s", iot_button_get_event_str(event));
if (BUTTON_PRESS_REPEAT == event || BUTTON_PRESS_REPEAT_DONE == event) {
ESP_LOGI(TAG, "\tREPEAT[%d]", iot_button_get_repeat(arg));
}
if (BUTTON_PRESS_UP == event || BUTTON_LONG_PRESS_HOLD == event || BUTTON_LONG_PRESS_UP == event) {
ESP_LOGI(TAG, "\tTICKS[%"PRIu32"]", iot_button_get_ticks_time(arg));
}
if (BUTTON_MULTIPLE_CLICK == event) {
ESP_LOGI(TAG, "\tMULTIPLE[%d]", (int)data);
}
}
typedef struct {
button_driver_t base;
int32_t gpio_num; /**< num of gpio */
uint8_t active_level; /**< gpio level when press down */
} custom_gpio_obj;
static uint8_t button_get_key_level(button_driver_t *button_driver)
{
custom_gpio_obj *custom_btn = __containerof(button_driver, custom_gpio_obj, base);
int level = gpio_get_level(custom_btn->gpio_num);
return level == custom_btn->active_level ? 1 : 0;
}
static esp_err_t button_del(button_driver_t *button_driver)
{
return ESP_OK;
}
TEST_CASE("custom button test", "[button][custom]")
{
gpio_config_t gpio_conf = {
.pin_bit_mask = 1ULL << BUTTON_IO_NUM,
.mode = GPIO_MODE_INPUT,
.pull_up_en = 1,
.pull_down_en = 0,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&gpio_conf);
custom_gpio_obj *custom_btn = (custom_gpio_obj *)calloc(1, sizeof(custom_gpio_obj));
TEST_ASSERT_NOT_NULL(custom_btn);
custom_btn->active_level = BUTTON_ACTIVE_LEVEL;
custom_btn->gpio_num = BUTTON_IO_NUM;
button_handle_t btn = NULL;
const button_config_t btn_cfg = {0};
custom_btn->base.get_key_level = button_get_key_level;
custom_btn->base.del = button_del;
esp_err_t ret = iot_button_create(&btn_cfg, &custom_btn->base, &btn);
TEST_ASSERT(ESP_OK == ret);
TEST_ASSERT_NOT_NULL(btn);
iot_button_register_cb(btn, BUTTON_PRESS_DOWN, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_UP, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_REPEAT, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_REPEAT_DONE, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_SINGLE_CLICK, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_DOUBLE_CLICK, NULL, button_event_cb, NULL);
/*!< Multiple Click must provide button_event_args_t */
/*!< Double Click */
button_event_args_t args = {
.multiple_clicks.clicks = 2,
};
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_event_cb, (void *)2);
/*!< Triple Click */
args.multiple_clicks.clicks = 3;
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_event_cb, (void *)3);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_START, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_HOLD, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_UP, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_END, NULL, button_event_cb, NULL);
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
iot_button_delete(btn);
}

View File

@@ -0,0 +1,186 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "unity.h"
#include "iot_button.h"
#include "button_gpio.h"
static const char *TAG = "GPIO BUTTON TEST";
#define BUTTON_IO_NUM 0
#define BUTTON_ACTIVE_LEVEL 0
static void button_event_cb(void *arg, void *data)
{
button_event_t event = iot_button_get_event(arg);
ESP_LOGI(TAG, "%s", iot_button_get_event_str(event));
if (BUTTON_PRESS_REPEAT == event || BUTTON_PRESS_REPEAT_DONE == event) {
ESP_LOGI(TAG, "\tREPEAT[%d]", iot_button_get_repeat(arg));
}
if (BUTTON_PRESS_UP == event || BUTTON_LONG_PRESS_HOLD == event || BUTTON_LONG_PRESS_UP == event) {
ESP_LOGI(TAG, "\tTICKS[%"PRIu32"]", iot_button_get_ticks_time(arg));
}
if (BUTTON_MULTIPLE_CLICK == event) {
ESP_LOGI(TAG, "\tMULTIPLE[%d]", (int)data);
}
}
TEST_CASE("gpio button test", "[button][gpio]")
{
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = BUTTON_IO_NUM,
.active_level = BUTTON_ACTIVE_LEVEL,
};
button_handle_t btn = NULL;
esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn);
TEST_ASSERT(ret == ESP_OK);
TEST_ASSERT_NOT_NULL(btn);
iot_button_register_cb(btn, BUTTON_PRESS_DOWN, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_UP, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_REPEAT, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_REPEAT_DONE, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_SINGLE_CLICK, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_DOUBLE_CLICK, NULL, button_event_cb, NULL);
/*!< Multiple Click must provide button_event_args_t */
/*!< Double Click */
button_event_args_t args = {
.multiple_clicks.clicks = 2,
};
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_event_cb, (void *)2);
/*!< Triple Click */
args.multiple_clicks.clicks = 3;
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_event_cb, (void *)3);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_START, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_HOLD, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_UP, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_END, NULL, button_event_cb, NULL);
uint8_t level = 0;
level = iot_button_get_key_level(btn);
ESP_LOGI(TAG, "button level is %d", level);
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
iot_button_delete(btn);
}
TEST_CASE("gpio button get event test", "[button][gpio][event test]")
{
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = BUTTON_IO_NUM,
.active_level = BUTTON_ACTIVE_LEVEL,
};
button_handle_t btn = NULL;
esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn);
TEST_ASSERT(ret == ESP_OK);
TEST_ASSERT_NOT_NULL(btn);
uint8_t level = 0;
level = iot_button_get_key_level(btn);
ESP_LOGI(TAG, "button level is %d", level);
while (1) {
button_event_t event = iot_button_get_event(btn);
if (event != BUTTON_NONE_PRESS) {
ESP_LOGI(TAG, "event is %s", iot_button_get_event_str(event));
}
vTaskDelay(pdMS_TO_TICKS(1));
}
iot_button_delete(btn);
}
TEST_CASE("gpio button test power save", "[button][gpio][power save]")
{
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = BUTTON_IO_NUM,
.active_level = BUTTON_ACTIVE_LEVEL,
.enable_power_save = true,
};
button_handle_t btn = NULL;
esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn);
TEST_ASSERT(ret == ESP_OK);
TEST_ASSERT_NOT_NULL(btn);
iot_button_register_cb(btn, BUTTON_PRESS_DOWN, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_UP, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_REPEAT, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_REPEAT_DONE, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_SINGLE_CLICK, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_DOUBLE_CLICK, NULL, button_event_cb, NULL);
/*!< Multiple Click must provide button_event_args_t */
/*!< Double Click */
button_event_args_t args = {
.multiple_clicks.clicks = 2,
};
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_event_cb, (void *)2);
/*!< Triple Click */
args.multiple_clicks.clicks = 3;
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_event_cb, (void *)3);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_START, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_HOLD, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_UP, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_END, NULL, button_event_cb, NULL);
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
iot_button_delete(btn);
}
TEST_CASE("gpio button test memory leak", "[button][gpio][memory leak]")
{
const button_config_t btn_cfg = {0};
const button_gpio_config_t btn_gpio_cfg = {
.gpio_num = BUTTON_IO_NUM,
.active_level = BUTTON_ACTIVE_LEVEL,
};
button_handle_t btn = NULL;
esp_err_t ret = iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn);
TEST_ASSERT(ret == ESP_OK);
TEST_ASSERT_NOT_NULL(btn);
iot_button_register_cb(btn, BUTTON_PRESS_DOWN, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_UP, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_REPEAT, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_REPEAT_DONE, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_SINGLE_CLICK, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_DOUBLE_CLICK, NULL, button_event_cb, NULL);
/*!< Multiple Click must provide button_event_args_t */
/*!< Double Click */
button_event_args_t args = {
.multiple_clicks.clicks = 2,
};
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_event_cb, (void *)2);
/*!< Triple Click */
args.multiple_clicks.clicks = 3;
iot_button_register_cb(btn, BUTTON_MULTIPLE_CLICK, &args, button_event_cb, (void *)3);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_START, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_HOLD, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_LONG_PRESS_UP, NULL, button_event_cb, NULL);
iot_button_register_cb(btn, BUTTON_PRESS_END, NULL, button_event_cb, NULL);
iot_button_delete(btn);
}

View File

@@ -0,0 +1,75 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "unity.h"
#include "iot_button.h"
#include "button_matrix.h"
static const char *TAG = "MATRIX BUTTON TEST";
static void button_event_cb(void *arg, void *data)
{
button_event_t event = iot_button_get_event(arg);
ESP_LOGI(TAG, "BUTTON[%d] %s", (int)data, iot_button_get_event_str(event));
if (BUTTON_PRESS_REPEAT == event || BUTTON_PRESS_REPEAT_DONE == event) {
ESP_LOGI(TAG, "\tREPEAT[%d]", iot_button_get_repeat(arg));
}
if (BUTTON_PRESS_UP == event || BUTTON_LONG_PRESS_HOLD == event || BUTTON_LONG_PRESS_UP == event) {
ESP_LOGI(TAG, "\tTICKS[%"PRIu32"]", iot_button_get_ticks_time(arg));
}
if (BUTTON_MULTIPLE_CLICK == event) {
ESP_LOGI(TAG, "\tMULTIPLE[%d]", (int)data);
}
}
TEST_CASE("matrix keyboard button test", "[button][matrix key]")
{
const button_config_t btn_cfg = {0};
const button_matrix_config_t matrix_cfg = {
.row_gpios = (int32_t[]){4, 5, 6, 7},
.col_gpios = (int32_t[]){3, 8, 16, 15},
.row_gpio_num = 4,
.col_gpio_num = 4,
};
button_handle_t btns[16] = {0};
size_t btn_num = 16;
esp_err_t ret = iot_button_new_matrix_device(&btn_cfg, &matrix_cfg, btns, &btn_num);
TEST_ASSERT(ret == ESP_OK);
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int index = i * 4 + j;
TEST_ASSERT_NOT_NULL(btns[index]);
iot_button_register_cb(btns[index], BUTTON_PRESS_DOWN, NULL, button_event_cb, (void *)index);
iot_button_register_cb(btns[index], BUTTON_PRESS_UP, NULL, button_event_cb, (void *)index);
iot_button_register_cb(btns[index], BUTTON_PRESS_REPEAT, NULL, button_event_cb, (void *)index);
iot_button_register_cb(btns[index], BUTTON_PRESS_REPEAT_DONE, NULL, button_event_cb, (void *)index);
iot_button_register_cb(btns[index], BUTTON_SINGLE_CLICK, NULL, button_event_cb, (void *)index);
iot_button_register_cb(btns[index], BUTTON_DOUBLE_CLICK, NULL, button_event_cb, (void *)index);
iot_button_register_cb(btns[index], BUTTON_LONG_PRESS_START, NULL, button_event_cb, (void *)index);
iot_button_register_cb(btns[index], BUTTON_LONG_PRESS_HOLD, NULL, button_event_cb, (void *)index);
iot_button_register_cb(btns[index], BUTTON_LONG_PRESS_UP, NULL, button_event_cb, (void *)index);
iot_button_register_cb(btns[index], BUTTON_PRESS_END, NULL, button_event_cb, (void *)index);
}
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
iot_button_delete(btns[i * 4 + j]);
}
}
}

View File

@@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
'''
Steps to run these cases:
- Build
- . ${IDF_PATH}/export.sh
- pip install idf_build_apps
- python tools/build_apps.py components/button/test_apps -t esp32s3
- Test
- pip install -r tools/requirements/requirement.pytest.txt
- pytest components/button/test_apps --target esp32s3
'''
import pytest
from pytest_embedded import Dut
@pytest.mark.target('esp32s3')
@pytest.mark.env('button')
@pytest.mark.parametrize(
'config',
[
'defaults',
],
)
def test_button(dut: Dut)-> None:
dut.expect_exact('Press ENTER to see the list of tests.')
dut.write('[auto]')
dut.expect_unity_test_output(timeout = 300)

View File

@@ -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