Updated dependencies.

This commit is contained in:
Joe Kearney 2026-02-07 16:06:20 -06:00
parent d86c494d45
commit 58748fcef1
101 changed files with 5845 additions and 2391 deletions

View file

@ -1 +1 @@
30a3f495c3862d505ce6e41adbbd218b2750e9723ab2151feff00e9fe685b326
fccb18c37f1cfe0797b74a53a44d3f400f5fd01f4993b40052dfb7f401915089

View file

@ -1,5 +1,61 @@
# ChangeLog
## v4.1.5 - 2025-12-3
### Fix:
* Fixed the unreasonable function name `iot_button_get_ticks_time` and renamed it to `iot_button_get_pressed_time`
## v4.1.4 - 2025-10-08
### Fix:
* Fixed requires in CMake for IDF6.
## 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:

View file

@ -1,5 +1,9 @@
set(PRIVREQ esp_timer)
set(REQ driver)
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.3")
set(REQ esp_driver_gpio)
else()
set(REQ driver)
endif()
set(SRC_FILES "button_gpio.c" "iot_button.c" "button_matrix.c")
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
@ -7,26 +11,12 @@ if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
if(CONFIG_SOC_ADC_SUPPORTED)
list(APPEND SRC_FILES "button_adc.c")
endif()
else()
list(APPEND REQ esp_adc_cal)
list(APPEND SRC_FILES "button_adc.c")
endif()
idf_component_register(SRCS ${SRC_FILES}
INCLUDE_DIRS include
INCLUDE_DIRS include interface
REQUIRES ${REQ}
PRIV_REQUIRES ${PRIVREQ})
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS "5.0")
# Add the macro CONFIG_SOC_ADC_SUPPORTED for the following chips.
if(CONFIG_IDF_TARGET STREQUAL "esp32" OR
CONFIG_IDF_TARGET STREQUAL "esp32s2" OR
CONFIG_IDF_TARGET STREQUAL "esp32s3" OR
CONFIG_IDF_TARGET STREQUAL "esp32c3" OR
CONFIG_IDF_TARGET STREQUAL "esp32h2")
target_compile_definitions(${COMPONENT_LIB} PUBLIC CONFIG_SOC_ADC_SUPPORTED)
endif()
endif()
include(package_manager)
cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR})

View file

@ -24,22 +24,12 @@ menu "IoT Button"
range 500 5000
default 1500
config BUTTON_SERIAL_TIME_MS
int "BUTTON SERIAL TIME (MS)"
config BUTTON_LONG_PRESS_HOLD_SERIAL_TIME_MS
int "BUTTON LONG_PRESS_HOLD SERIAL TIME (MS)"
range 2 1000
default 20
help
"Serial trigger interval"
config GPIO_BUTTON_SUPPORT_POWER_SAVE
bool "GPIO BUTTON SUPPORT POWER SAVE"
default n
help
Enable GPIO button power save
The function enables the use of GPIO buttons during light sleep,
but enabling this function prevents the simultaneous use of other
types of buttons.
"Long press hold Serial trigger interval"
config ADC_BUTTON_MAX_CHANNEL
int "ADC BUTTON MAX CHANNEL"

View file

@ -1,4 +1,4 @@
/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -6,48 +6,38 @@
#include <string.h>
#include <inttypes.h>
#include "esp_log.h"
#include "esp_check.h"
#include "esp_timer.h"
#include "esp_idf_version.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "soc/soc_caps.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#else
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"
#endif
#include "button_adc.h"
#include "button_interface.h"
static const char *TAG = "adc button";
#define ADC_BTN_CHECK(a, str, ret_val) \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
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 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
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define ADC_BUTTON_WIDTH SOC_ADC_RTC_MAX_BITWIDTH
#define ADC1_BUTTON_CHANNEL_MAX SOC_ADC_MAX_CHANNEL_NUM
#define ADC_BUTTON_ATTEN DEFAULT_ADC_ATTEN
#else
#define ADC_BUTTON_WIDTH ADC_WIDTH_MAX-1
#define ADC1_BUTTON_CHANNEL_MAX ADC1_CHANNEL_MAX
#define ADC_BUTTON_ATTEN DEFAULT_ADC_ATTEN
#endif
#define ADC_BUTTON_ADC_UNIT ADC_UNIT_1
#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;
@ -60,45 +50,56 @@ typedef struct {
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 {
bool is_configured;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
adc_cali_handle_t adc1_cali_handle;
adc_oneshot_unit_handle_t adc1_handle;
#else
esp_adc_cal_characteristics_t adc_chars;
#endif
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;
} adc_button_t;
} btn_adc_unit_t;
static adc_button_t g_button = {0};
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 int find_unused_channel(void)
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.ch[i].is_init) {
if (0 == g_button.unit[unit_id].ch[i].is_init) {
return i;
}
}
return -1;
}
static int find_channel(uint8_t channel)
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.ch[i].channel) {
if (channel == g_button.unit[unit_id].ch[i].channel) {
return i;
}
}
return -1;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static esp_err_t adc_calibration_init(adc_unit_t unit, adc_atten_t atten, adc_cali_handle_t *out_handle)
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_FAIL;
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
bool calibrated = false;
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
@ -136,177 +137,191 @@ static esp_err_t adc_calibration_init(adc_unit_t unit, adc_atten_t atten, adc_ca
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 ? ESP_OK : ESP_FAIL;
}
#endif
esp_err_t button_adc_init(const button_adc_config_t *config)
{
ADC_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG);
ADC_BTN_CHECK(config->adc_channel < ADC1_BUTTON_CHANNEL_MAX, "channel out of range", ESP_ERR_NOT_SUPPORTED);
ADC_BTN_CHECK(config->button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_NOT_SUPPORTED);
ADC_BTN_CHECK(config->max > 0, "key max voltage invalid", ESP_ERR_INVALID_ARG);
int ch_index = find_channel(config->adc_channel);
if (ch_index >= 0) { /**< the channel has been initialized */
ADC_BTN_CHECK(g_button.ch[ch_index].btns[config->button_index].max == 0, "The button_index has been used", ESP_ERR_INVALID_STATE);
} else { /**< this is a new channel */
int unused_ch_index = find_unused_channel();
ADC_BTN_CHECK(unused_ch_index >= 0, "exceed max channel number, can't create a new channel", ESP_ERR_INVALID_STATE);
ch_index = unused_ch_index;
}
/** initialize adc */
if (0 == g_button.is_configured) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_err_t ret;
if (NULL == config->adc_handle) {
//ADC1 Init
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
};
ret = adc_oneshot_new_unit(&init_config, &g_button.adc1_handle);
ADC_BTN_CHECK(ret == ESP_OK, "adc oneshot new unit fail!", ESP_FAIL);
} else {
g_button.adc1_handle = *config->adc_handle ;
ESP_LOGI(TAG, "ADC1 has been initialized");
}
#else
//Configure ADC
adc1_config_width(ADC_BUTTON_WIDTH);
//Characterize ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_BUTTON_ADC_UNIT, ADC_BUTTON_ATTEN, ADC_BUTTON_WIDTH, DEFAULT_VREF, &g_button.adc_chars);
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
ESP_LOGI(TAG, "Characterized using Two Point Value");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
ESP_LOGI(TAG, "Characterized using eFuse Vref");
} else {
ESP_LOGI(TAG, "Characterized using Default Vref");
}
#endif
g_button.is_configured = 1;
}
/** initialize adc channel */
if (0 == g_button.ch[ch_index].is_init) {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
//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.adc1_handle, config->adc_channel, &oneshot_config);
ADC_BTN_CHECK(ret == ESP_OK, "adc oneshot config channel fail!", ESP_FAIL);
//-------------ADC1 Calibration Init---------------//
ret = adc_calibration_init(ADC_BUTTON_ADC_UNIT, ADC_BUTTON_ATTEN, &g_button.adc1_cali_handle);
ADC_BTN_CHECK(ret == ESP_OK, "ADC1 Calibration Init False", 0);
#else
adc1_config_channel_atten(config->adc_channel, ADC_BUTTON_ATTEN);
#endif
g_button.ch[ch_index].channel = config->adc_channel;
g_button.ch[ch_index].is_init = 1;
g_button.ch[ch_index].last_time = 0;
}
g_button.ch[ch_index].btns[config->button_index].max = config->max;
g_button.ch[ch_index].btns[config->button_index].min = config->min;
g_button.ch_num++;
return ESP_OK;
return calibrated;
}
esp_err_t button_adc_deinit(uint8_t channel, int button_index)
static bool adc_calibration_deinit(adc_cali_handle_t handle)
{
ADC_BTN_CHECK(channel < ADC1_BUTTON_CHANNEL_MAX, "channel out of range", ESP_ERR_INVALID_ARG);
ADC_BTN_CHECK(button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_INVALID_ARG);
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
if (adc_cali_delete_scheme_curve_fitting(handle) == ESP_OK) {
return true;
}
#endif
int ch_index = find_channel(channel);
ADC_BTN_CHECK(ch_index >= 0, "can't find the channel", ESP_ERR_INVALID_ARG);
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
if (adc_cali_delete_scheme_line_fitting(handle) == ESP_OK) {
return true;
}
#endif
g_button.ch[ch_index].btns[button_index].max = 0;
g_button.ch[ch_index].btns[button_index].min = 0;
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.ch[ch_index].btns[i].max) {
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.ch[ch_index].is_init) { /**< if all button is unused, deinit the channel */
g_button.ch[ch_index].is_init = 0;
g_button.ch[ch_index].channel = ADC1_BUTTON_CHANNEL_MAX;
ESP_LOGD(TAG, "all button is unused on channel%d, deinit the channel", g_button.ch[ch_index].channel);
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.ch[i].is_init) {
if (0 == g_button.unit[adc_btn->unit_id].ch[i].is_init) {
unused_ch++;
}
}
if (unused_ch == ADC_BUTTON_MAX_CHANNEL && g_button.is_configured) { /**< if all channel is unused, deinit the adc */
g_button.is_configured = false;
memset(&g_button, 0, sizeof(adc_button_t));
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");
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_err_t ret = adc_oneshot_del_unit(g_button.adc1_handle);
ADC_BTN_CHECK(ret == ESP_OK, "adc oneshot deinit fail", ESP_FAIL);
#endif
free(adc_btn);
return ESP_OK;
}
static uint32_t get_adc_volatge(uint8_t channel)
static uint32_t get_adc_voltage(adc_unit_t unit_id, uint8_t channel)
{
uint32_t adc_reading = 0;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
int adc_raw = 0;
for (int i = 0; i < NO_OF_SAMPLES; i++) {
adc_oneshot_read(g_button.adc1_handle, channel, &adc_raw);
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.adc1_cali_handle, adc_reading, &voltage);
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);
#else
//Multisampling
for (int i = 0; i < NO_OF_SAMPLES; i++) {
adc_reading += adc1_get_raw(channel);
}
adc_reading /= NO_OF_SAMPLES;
//Convert adc_reading to voltage in mV
uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, &g_button.adc_chars);
ESP_LOGV(TAG, "Raw: %"PRIu32"\tVoltage: %"PRIu32"mV", adc_reading, voltage);
#endif
return voltage;
}
uint8_t button_adc_get_key_level(void *button_index)
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_BUTTON_SPLIT_CHANNEL(button_index);
uint32_t index = ADC_BUTTON_SPLIT_INDEX(button_index);
ADC_BTN_CHECK(ch < ADC1_BUTTON_CHANNEL_MAX, "channel out of range", 0);
ADC_BTN_CHECK(index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", 0);
int ch_index = find_channel(ch);
ADC_BTN_CHECK(ch_index >= 0, "The button_index is not init", 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.ch[ch_index].last_time) > 1000) {
vol = get_adc_volatge(ch);
g_button.ch[ch_index].last_time = esp_timer_get_time();
if ((esp_timer_get_time() - g_button.unit[adc_btn->unit_id].ch[ch_index].last_time) > 1000) {
vol = get_adc_voltage(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.ch[ch_index].btns[index].max &&
vol >= g_button.ch[ch_index].btns[index].min) {
return 1;
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 0;
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

@ -4,33 +4,96 @@
*/
#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";
static const char *TAG = "gpio_button";
#define GPIO_BTN_CHECK(a, str, ret_val) \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
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;
esp_err_t button_gpio_init(const button_gpio_config_t *config)
static esp_err_t button_gpio_del(button_driver_t *button_driver)
{
GPIO_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG);
GPIO_BTN_CHECK(GPIO_IS_VALID_GPIO(config->gpio_num), "GPIO number error", ESP_ERR_INVALID_ARG);
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;
}
gpio_config_t gpio_conf;
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 << config->gpio_num);
if (config->disable_pull) {
gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
} else {
if (config->active_level) {
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 {
@ -40,75 +103,47 @@ esp_err_t button_gpio_init(const button_gpio_config_t *config)
}
gpio_config(&gpio_conf);
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
if (config->enable_power_save) {
if (gpio_cfg->enable_power_save) {
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
if (!esp_sleep_is_valid_wakeup_gpio(config->gpio_num)) {
ESP_LOGE(TAG, "GPIO %ld is not a valid wakeup source under CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE", config->gpio_num);
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(config->gpio_num);
gpio_hold_en(gpio_cfg->gpio_num);
#endif
/* Enable wake up from GPIO */
esp_err_t ret = gpio_wakeup_enable(config->gpio_num, config->active_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
GPIO_BTN_CHECK(ret == ESP_OK, "Enable gpio wakeup failed", ESP_FAIL);
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 << config->gpio_num), config->active_level == 0 ? ESP_EXT1_WAKEUP_ANY_LOW : ESP_EXT1_WAKEUP_ANY_HIGH);
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;
GPIO_BTN_CHECK(ret == ESP_OK, "Target must support ext1 wakeup", 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
GPIO_BTN_CHECK(ret == ESP_OK, "Configure gpio as wakeup source failed", ESP_FAIL);
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;
}
#endif
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;
}
esp_err_t button_gpio_deinit(int gpio_num)
{
return gpio_reset_pin(gpio_num);;
}
uint8_t button_gpio_get_key_level(void *gpio_num)
{
return (uint8_t)gpio_get_level((uint32_t)gpio_num);
}
esp_err_t button_gpio_set_intr(int gpio_num, gpio_int_type_t intr_type, gpio_isr_t isr_handler, void *args)
{
static bool isr_service_installed = false;
gpio_set_intr_type(gpio_num, intr_type);
if (!isr_service_installed) {
gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
isr_service_installed = true;
}
gpio_isr_handler_add(gpio_num, isr_handler, args);
return ESP_OK;
}
esp_err_t button_gpio_intr_control(int gpio_num, bool enable)
{
if (enable) {
gpio_intr_enable(gpio_num);
} else {
gpio_intr_disable(gpio_num);
}
return ESP_OK;
}
esp_err_t button_gpio_enable_gpio_wakeup(uint32_t gpio_num, uint8_t active_level, bool enable)
{
esp_err_t ret;
if (enable) {
ret = gpio_wakeup_enable(gpio_num, active_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
} else {
ret = gpio_wakeup_disable(gpio_num);
err:
if (gpio_btn) {
free(gpio_btn);
}
return ret;
}

View file

@ -1,59 +1,88 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* 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";
static const char *TAG = "matrix_button";
#define MATRIX_BTN_CHECK(a, str, ret_val) \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
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;
esp_err_t button_matrix_init(const button_matrix_config_t *config)
static esp_err_t button_matrix_gpio_init(int32_t gpio_num, gpio_mode_t mode)
{
MATRIX_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG);
MATRIX_BTN_CHECK(GPIO_IS_VALID_GPIO(config->row_gpio_num), "row GPIO number error", ESP_ERR_INVALID_ARG);
MATRIX_BTN_CHECK(GPIO_IS_VALID_GPIO(config->col_gpio_num), "col GPIO number error", ESP_ERR_INVALID_ARG);
// set row gpio as output
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.mode = GPIO_MODE_OUTPUT;
gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpio_conf.pin_bit_mask = (1ULL << config->row_gpio_num);
gpio_conf.pin_bit_mask = (1ULL << gpio_num);
gpio_conf.mode = mode;
gpio_config(&gpio_conf);
// set col gpio as input
gpio_conf.mode = GPIO_MODE_INPUT;
gpio_conf.pin_bit_mask = (1ULL << config->col_gpio_num);
gpio_config(&gpio_conf);
return ESP_OK;
}
esp_err_t button_matrix_deinit(int row_gpio_num, int col_gpio_num)
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(row_gpio_num);
gpio_reset_pin(col_gpio_num);
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(void *hardware_data)
uint8_t button_matrix_get_key_level(button_driver_t *button_driver)
{
uint32_t row = MATRIX_BUTTON_SPLIT_ROW(hardware_data);
uint32_t col = MATRIX_BUTTON_SPLIT_COL(hardware_data);
gpio_set_level(row, 1);
uint8_t level = gpio_get_level(col);
gpio_set_level(row, 0);
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

@ -1,6 +0,0 @@
# 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

@ -1,43 +0,0 @@
## 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

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

View file

@ -1,87 +0,0 @@
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

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

View file

@ -1,125 +0,0 @@
/*
* 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"
/* 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 = {
.type = BUTTON_TYPE_GPIO,
.gpio_button_config = {
.gpio_num = button_num,
.active_level = BUTTON_ACTIVE_LEVEL,
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
.enable_power_save = true,
#endif
},
};
button_handle_t btn = iot_button_create(&btn_cfg);
assert(btn);
esp_err_t err = iot_button_register_cb(btn, BUTTON_PRESS_DOWN, button_event_cb, NULL);
err |= iot_button_register_cb(btn, BUTTON_PRESS_UP, button_event_cb, NULL);
err |= iot_button_register_cb(btn, BUTTON_PRESS_REPEAT, button_event_cb, NULL);
err |= iot_button_register_cb(btn, BUTTON_PRESS_REPEAT_DONE, button_event_cb, NULL);
err |= iot_button_register_cb(btn, BUTTON_SINGLE_CLICK, button_event_cb, NULL);
err |= iot_button_register_cb(btn, BUTTON_DOUBLE_CLICK, button_event_cb, NULL);
err |= iot_button_register_cb(btn, BUTTON_LONG_PRESS_START, button_event_cb, NULL);
err |= iot_button_register_cb(btn, BUTTON_LONG_PRESS_HOLD, button_event_cb, NULL);
err |= iot_button_register_cb(btn, BUTTON_LONG_PRESS_UP, button_event_cb, NULL);
err |= iot_button_register_cb(btn, BUTTON_PRESS_END, 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,
};
err |= iot_button_register_power_save_cb(&config);
#endif
ESP_ERROR_CHECK(err);
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
void power_save_init(void)
{
esp_pm_config_t pm_config = {
.max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ,
.min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ,
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
.light_sleep_enable = true
#endif
};
ESP_ERROR_CHECK(esp_pm_configure(&pm_config));
}
#else
void power_save_init(void)
{
#if CONFIG_IDF_TARGET_ESP32
esp_pm_config_esp32_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S2
esp_pm_config_esp32s2_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32C3
esp_pm_config_esp32c3_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S3
esp_pm_config_esp32s3_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32C2
esp_pm_config_esp32c2_t pm_config = {
#endif
.max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ,
.min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ,
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
.light_sleep_enable = true
#endif
};
ESP_ERROR_CHECK(esp_pm_configure(&pm_config));
}
#endif
void app_main(void)
{
button_init(BOOT_BUTTON_NUM);
#if CONFIG_ENTER_LIGHT_SLEEP_AUTO
power_save_init();
#else
esp_light_sleep_start();
#endif
}

View file

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

View file

@ -1,11 +0,0 @@
# 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

@ -1,12 +1,12 @@
dependencies:
cmake_utilities: 0.*
cmake_utilities: '*'
idf: '>=4.0'
description: GPIO and ADC button driver
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: ef19f4a5524a0ea147c9eccde0438123b41aeeb1
commit_sha: 91aeb7fb41e8a3e76aeb21371f9f83711c74cf3f
path: components/button
url: https://github.com/espressif/esp-iot-solution/tree/master/components/button
version: 3.5.0
version: 4.1.5

View file

@ -1,75 +1,56 @@
/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_idf_version.h"
#include "driver/gpio.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "esp_adc/adc_oneshot.h"
#else
#include "driver/adc.h"
#endif
#include "button_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ADC_BUTTON_COMBINE(channel, index) ((channel)<<8 | (index))
#define ADC_BUTTON_SPLIT_INDEX(data) ((uint32_t)(data)&0xff)
#define ADC_BUTTON_SPLIT_CHANNEL(data) (((uint32_t)(data) >> 8) & 0xff)
/**
* @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 */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
adc_oneshot_unit_handle_t *adc_handle; /**< handle of adc unit, if NULL will create new one internal, else will use the handle */
#endif
} button_adc_config_t;
/**
* @brief Initialize gpio button
* @brief Create a new ADC button device
*
* @param config pointer of configuration struct
* 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 on success
* - ESP_ERR_INVALID_ARG Arguments is NULL.
* - ESP_ERR_NOT_SUPPORTED Arguments out of range.
* - ESP_ERR_INVALID_STATE State is error.
* - 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 button_adc_init(const button_adc_config_t *config);
/**
* @brief Deinitialize gpio button
*
* @param channel ADC channel
* @param button_index Button index on the channel
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
*/
esp_err_t button_adc_deinit(uint8_t channel, int button_index);
/**
* @brief Get the adc button level
*
* @param button_index It is compressed by ADC channel and button index, use the macro ADC_BUTTON_COMBINE to generate. It will be treated as a uint32_t variable.
*
* @return
* - 0 Not pressed
* - 1 Pressed
*/
uint8_t button_adc_get_key_level(void *button_index);
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
}

View file

@ -1,11 +1,12 @@
/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/gpio.h"
#include "esp_err.h"
#include "button_types.h"
#ifdef __cplusplus
extern "C" {
@ -18,74 +19,34 @@ extern "C" {
typedef struct {
int32_t gpio_num; /**< num of gpio */
uint8_t active_level; /**< gpio level when press down */
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
bool enable_power_save; /**< enable power save mode */
#endif
bool disable_pull; /**< disable internal pull or not */
bool disable_pull; /**< disable internal pull up or down */
} button_gpio_config_t;
/**
* @brief Initialize gpio button
* @brief Create a new GPIO button device
*
* @param config pointer of configuration struct
* 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 on success
* - ESP_ERR_INVALID_ARG Arguments is NULL.
* - 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 button_gpio_init(const button_gpio_config_t *config);
/**
* @brief Deinitialize gpio button
*
* @param gpio_num gpio number of button
*
* @return Always return ESP_OK
*/
esp_err_t button_gpio_deinit(int gpio_num);
/**
* @brief Get current level on button gpio
*
* @param gpio_num gpio number of button, it will be treated as a uint32_t variable.
*
* @return Level on gpio
*/
uint8_t button_gpio_get_key_level(void *gpio_num);
/**
* @brief Sets up interrupt for GPIO button.
*
* @param gpio_num gpio number of button
* @param intr_type The type of GPIO interrupt.
* @param isr_handler The ISR (Interrupt Service Routine) handler function.
* @param args Arguments to be passed to the ISR handler function.
* @return Always return ESP_OK
*/
esp_err_t button_gpio_set_intr(int gpio_num, gpio_int_type_t intr_type, gpio_isr_t isr_handler, void *args);
/**
* @brief Enable or disable interrupt for GPIO button.
*
* @param gpio_num gpio number of button
* @param enable enable or disable
* @return Always return ESP_OK
*/
esp_err_t button_gpio_intr_control(int gpio_num, bool enable);
/**
* @brief Enable or disable GPIO wakeup functionality.
*
* This function allows enabling or disabling GPIO wakeup feature.
*
* @param gpio_num GPIO number for wakeup functionality.
* @param active_level Active level of the GPIO when triggered.
* @param enable Enable or disable the GPIO wakeup.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if trigger was not active or in conflict.
*/
esp_err_t button_gpio_enable_gpio_wakeup(uint32_t gpio_num, uint8_t active_level, bool enable);
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
}

View file

@ -1,18 +1,17 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* 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
#define MATRIX_BUTTON_COMBINE(row_gpio, col_gpio) ((row_gpio)<<8 | (col_gpio))
#define MATRIX_BUTTON_SPLIT_COL(data) ((uint32_t)(data)&0xff)
#define MATRIX_BUTTON_SPLIT_ROW(data) (((uint32_t)(data) >> 8) & 0xff)
/**
* @brief Button matrix key configuration.
* Just need to configure the GPIO associated with this GPIO in the matrix keyboard.
@ -34,46 +33,37 @@ extern "C" {
* but buttons within the same row can be detected without conflicts.
*/
typedef struct {
int32_t row_gpio_num; /**< GPIO number associated with the row */
int32_t col_gpio_num; /**< GPIO number associated with the column */
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 Initialize a button matrix keyboard.
* @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.
*
* @param config Pointer to the button matrix key configuration.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if the argument is NULL.
* - 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 When initializing the button matrix keyboard, the row GPIO pins will be set as outputs,
* and the column GPIO pins will be set as inputs, both with pull-down resistors enabled.
* @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 button_matrix_init(const button_matrix_config_t *config);
/**
* @brief Deinitialize a button in the matrix keyboard.
*
* @param row_gpio_num GPIO number of the row where the button is located.
* @param col_gpio_num GPIO number of the column where the button is located.
* @return
* - ESP_OK if the button is successfully deinitialized
*
* @note When deinitializing a button, please exercise caution and avoid deinitializing a button individually, as it may affect the proper functioning of other buttons in the same row or column.
*/
esp_err_t button_matrix_deinit(int row_gpio_num, int col_gpio_num);
/**
* @brief Get the key level from the button matrix hardware.
*
* @param hardware_data Pointer to hardware-specific data containing information about row GPIO and column GPIO.
* @return uint8_t[out] The key level read from the hardware.
*
* @note This function retrieves the key level from the button matrix hardware.
* The `hardware_data` parameter should contain information about the row and column GPIO pins,
* and you can access this information using the `MATRIX_BUTTON_SPLIT_COL` and `MATRIX_BUTTON_SPLIT_ROW` macros.
*/
uint8_t button_matrix_get_key_level(void *hardware_data);
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
}

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

@ -1,4 +1,4 @@
/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -6,12 +6,8 @@
#pragma once
#include "sdkconfig.h"
#if CONFIG_SOC_ADC_SUPPORTED
#include "button_adc.h"
#endif
#include "button_gpio.h"
#include "button_matrix.h"
#include "esp_err.h"
#include "button_types.h"
#ifdef __cplusplus
extern "C" {
@ -19,7 +15,6 @@ extern "C" {
typedef void (* button_cb_t)(void *button_handle, void *usr_data);
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
typedef void (* button_power_save_cb_t)(void *usr_data);
/**
@ -27,12 +22,9 @@ typedef void (* button_power_save_cb_t)(void *usr_data);
*
*/
typedef struct {
button_power_save_cb_t enter_power_save_cb;
void *usr_data;
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;
#endif
typedef void *button_handle_t;
/**
* @brief Button events
@ -55,7 +47,7 @@ typedef enum {
} button_event_t;
/**
* @brief Button events data
* @brief Button events arg
*
*/
typedef union {
@ -74,27 +66,7 @@ typedef union {
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_data_t;
/**
* @brief Button events configuration
*
*/
typedef struct {
button_event_t event; /**< button event type */
button_event_data_t event_data; /**< event data corresponding to the event */
} button_event_config_t;
/**
* @brief Supported button type
*
*/
typedef enum {
BUTTON_TYPE_GPIO,
BUTTON_TYPE_ADC,
BUTTON_TYPE_MATRIX,
BUTTON_TYPE_CUSTOM
} button_type_t;
} button_event_args_t;
/**
* @brief Button parameter
@ -106,45 +78,6 @@ typedef enum {
BUTTON_PARAM_MAX,
} button_param_t;
/**
* @brief custom button configuration
*
*/
typedef struct {
uint8_t active_level; /**< active level when press down */
esp_err_t (*button_custom_init)(void *param); /**< user defined button init */
uint8_t (*button_custom_get_key_value)(void *param); /**< user defined button get key value */
esp_err_t (*button_custom_deinit)(void *param); /**< user defined button deinit */
void *priv; /**< private data used for custom button, MUST be allocated dynamically and will be auto freed in iot_button_delete*/
} button_custom_config_t;
/**
* @brief Button configuration
*
*/
typedef struct {
button_type_t type; /**< button type, The corresponding button configuration must be filled */
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 */
union {
button_gpio_config_t gpio_button_config; /**< gpio button configuration */
#if CONFIG_SOC_ADC_SUPPORTED
button_adc_config_t adc_button_config; /**< adc button configuration */
#endif
button_matrix_config_t matrix_button_config; /**< matrix key button configuration */
button_custom_config_t custom_button_config; /**< custom button configuration */
}; /**< button configuration */
} button_config_t;
/**
* @brief Create a button
*
* @param config pointer of button configuration, must corresponding the button type
*
* @return A handle to the created button, or NULL in case of error.
*/
button_handle_t iot_button_create(const button_config_t *config);
/**
* @brief Delete a button
*
@ -161,6 +94,7 @@ esp_err_t iot_button_delete(button_handle_t btn_handle);
*
* @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
*
@ -170,51 +104,21 @@ esp_err_t iot_button_delete(button_handle_t btn_handle);
* - 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_cb_t cb, void *usr_data);
/**
* @brief Register the button event callback function.
*
* @param btn_handle A button handle to register
* @param event_cfg Button event configuration
* @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_event_cb(button_handle_t btn_handle, button_event_config_t event_cfg, button_cb_t cb, void *usr_data);
/**
* @brief Unregister the button event callback function.
* In case event_data is also passed it will unregister function for that particular event_data only.
*
* @param btn_handle A button handle to unregister
* @param event_cfg Button event
* @param cb callback to unregister
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is invalid.
* - ESP_ERR_INVALID_STATE The Callback was never registered with the event
*/
esp_err_t iot_button_unregister_event(button_handle_t btn_handle, button_event_config_t event_cfg, button_cb_t cb);
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);
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
@ -238,7 +142,7 @@ size_t iot_button_count_cb(button_handle_t btn_handle);
* - 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(button_handle_t btn_handle, button_event_t event);
size_t iot_button_count_event_cb(button_handle_t btn_handle, button_event_t event);
/**
* @brief Get button event
@ -286,12 +190,25 @@ esp_err_t iot_button_print_event(button_handle_t btn_handle);
uint8_t iot_button_get_repeat(button_handle_t btn_handle);
/**
* @brief Get button ticks time
* @brief Get button pressed time
*
* @param btn_handle Button handle
*
* @return Actual time from press down to up (ms).
*/
uint32_t iot_button_get_pressed_time(button_handle_t btn_handle);
/**
* @brief Get button ticks time
*
* @deprecated This function is deprecated and will be removed in a future release.
* Please use iot_button_get_pressed_time() instead.
*
* @param btn_handle Button handle
*
* @return Actual time from press down to up (ms).
*/
__attribute__((deprecated("Use iot_button_get_pressed_time() instead")))
uint32_t iot_button_get_ticks_time(button_handle_t btn_handle);
/**
@ -343,7 +260,6 @@ esp_err_t iot_button_resume(void);
*/
esp_err_t iot_button_stop(void);
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
/**
* @brief Register a callback function for power saving.
* The config->enter_power_save_cb function will be called when all keys stop working.
@ -356,7 +272,6 @@ esp_err_t iot_button_stop(void);
* - ESP_ERR_NO_MEM Not enough memory
*/
esp_err_t iot_button_register_power_save_cb(const button_power_save_config_t *config);
#endif
#ifdef __cplusplus
}

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

@ -1,4 +1,4 @@
/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -12,11 +12,10 @@
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_log.h"
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
#include "esp_pm.h"
#endif
#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;
@ -45,6 +44,14 @@ static const char *button_event_str[] = {
"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
*
@ -52,48 +59,41 @@ static const char *button_event_str[] = {
typedef struct {
button_cb_t cb;
void *usr_data;
button_event_data_t event_data;
button_event_args_t event_args;
} button_cb_info_t;
/**
* @brief Structs to record individual key parameters
*
*/
typedef struct Button {
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: 3;
uint8_t active_level: 1;
uint8_t button_level: 1;
uint8_t enable_power_save: 1;
button_event_t event;
uint8_t (*hal_button_Level)(void *hardware_data);
esp_err_t (*hal_button_deinit)(void *hardware_data);
void *hardware_data;
button_type_t type;
button_cb_info_t *cb_info[BUTTON_EVENT_MAX];
size_t size[BUTTON_EVENT_MAX];
int count[2];
struct Button *next;
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;
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
static button_power_save_config_t power_save_usr_cfg = {0};
#endif
#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_SERIAL_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) \
@ -110,7 +110,7 @@ static button_power_save_config_t power_save_usr_cfg = {0};
*/
static void button_handler(button_dev_t *btn)
{
uint8_t read_gpio_level = btn->hal_button_Level(btn->hardware_data);
uint8_t read_gpio_level = btn->driver->get_key_level(btn->driver);
/** ticks counter working.. */
if ((btn->state) > 0) {
@ -129,54 +129,54 @@ static void button_handler(button_dev_t *btn)
/** State machine */
switch (btn->state) {
case 0:
if (btn->button_level == btn->active_level) {
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 = 1;
btn->state = PRESS_UP_CHECK;
} else {
btn->event = (uint8_t)BUTTON_NONE_PRESS;
}
break;
case 1:
if (btn->button_level != btn->active_level) {
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 = 2;
btn->state = PRESS_REPEAT_DOWN_CHECK;
} else if (btn->ticks >= btn->long_press_ticks) {
btn->event = (uint8_t)BUTTON_LONG_PRESS_START;
btn->state = 4;
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;
uint32_t pressed_time = iot_button_get_pressed_time(btn);
int32_t diff = pressed_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_data.long_press.press_time == (btn->long_press_ticks * TICKS_INTERVAL)) {
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_data.long_press.press_time == btn->long_press_ticks * TICKS_INTERVAL);
} while (btn->cb_info[btn->event][btn->count[0]].event_args.long_press.press_time == btn->long_press_ticks * TICKS_INTERVAL);
}
}
}
break;
case 2:
if (btn->button_level == btn->active_level) {
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 = 3;
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;
@ -190,14 +190,8 @@ static void button_handler(button_dev_t *btn)
/** 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_data.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_data.multiple_clicks.clicks == btn->repeat);
if (btn->repeat == btn->cb_info[btn->event][i].event_args.multiple_clicks.clicks) {
btn->cb_info[btn->event][i].cb(btn, btn->cb_info[btn->event][i].usr_data);
}
}
@ -211,75 +205,75 @@ static void button_handler(button_dev_t *btn)
break;
case 3:
if (btn->button_level != btn->active_level) {
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 = 2; //repeat press
btn->state = PRESS_REPEAT_DOWN_CHECK; //repeat press
} else {
btn->state = 0;
btn->state = PRESS_DOWN_CHECK;
btn->event = (uint8_t)BUTTON_PRESS_END;
CALL_EVENT_CB(BUTTON_PRESS_END);
}
}
break;
case 4:
if (btn->button_level == btn->active_level) {
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_data.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_data.long_press.press_time;
if (btn->long_press_ticks * TICKS_INTERVAL <= time) {
btn->count[0] = i;
break;
}
/** Calling callbacks for BUTTON_LONG_PRESS_START based on press_time */
uint32_t pressed_time = iot_button_get_pressed_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_data.long_press.press_time);
}
}
if (btn->count[0] < btn->size[BUTTON_LONG_PRESS_START] && abs((int)pressed_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_data.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_data.long_press.press_time;
if (btn->long_press_ticks * TICKS_INTERVAL <= time) {
btn->count[1] = i;
break;
}
/** 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_data.long_press.press_time);
}
}
if (btn->count[1] + 1 < btn->size[BUTTON_LONG_PRESS_UP] && abs((int)pressed_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
@ -295,7 +289,7 @@ static void button_handler(button_dev_t *btn)
break;
}
btn->count[1]--;
} while (cb_info[btn->count[1]].event_data.long_press.press_time == cb_info[btn->count[1] + 1].event_data.long_press.press_time);
} 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;
@ -307,7 +301,7 @@ static void button_handler(button_dev_t *btn)
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
btn->state = 0; //reset
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);
@ -320,18 +314,13 @@ static void button_cb(void *args)
{
button_dev_t *target;
/*!< When all buttons enter the BUTTON_NONE_PRESS state, the system enters low-power mode */
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
bool enter_power_save_flag = true;
#endif
for (target = g_head_handle; target; target = target->next) {
button_handler(target);
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
if (!(target->enable_power_save && target->debounce_cnt == 0 && target->event == BUTTON_NONE_PRESS)) {
if (!(target->driver->enable_power_save && target->debounce_cnt == 0 && target->event == BUTTON_NONE_PRESS)) {
enter_power_save_flag = false;
}
#endif
}
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
if (enter_power_save_flag) {
/*!< Stop esp timer for power save */
if (g_is_timer_running) {
@ -339,9 +328,8 @@ static void button_cb(void *args)
g_is_timer_running = false;
}
for (target = g_head_handle; target; target = target->next) {
if (target->type == BUTTON_TYPE_GPIO && target->enable_power_save) {
button_gpio_intr_control((int)(target->hardware_data), true);
button_gpio_enable_gpio_wakeup((uint32_t)(target->hardware_data), target->active_level, true);
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. */
@ -349,212 +337,21 @@ static void button_cb(void *args)
power_save_usr_cfg.enter_power_save_cb(power_save_usr_cfg.usr_data);
}
}
#endif
}
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
static void IRAM_ATTR button_power_save_isr_handler(void* arg)
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)
{
if (!g_is_timer_running) {
esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U);
g_is_timer_running = true;
}
button_gpio_intr_control((int)arg, false);
/*!< disable gpio wakeup not need active level*/
button_gpio_enable_gpio_wakeup((uint32_t)arg, 0, false);
}
#endif
static button_dev_t *button_create_com(uint8_t active_level, uint8_t (*hal_get_key_state)(void *hardware_data), void *hardware_data, uint16_t long_press_ticks, uint16_t short_press_ticks)
{
BTN_CHECK(NULL != hal_get_key_state, "Function pointer is invalid", NULL);
button_dev_t *btn = (button_dev_t *) calloc(1, sizeof(button_dev_t));
BTN_CHECK(NULL != btn, "Button memory alloc failed", NULL);
btn->hardware_data = hardware_data;
btn->event = BUTTON_NONE_PRESS;
btn->active_level = active_level;
btn->hal_button_Level = hal_get_key_state;
btn->button_level = !active_level;
btn->long_press_ticks = long_press_ticks;
btn->short_press_ticks = short_press_ticks;
/** Add handle to list */
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);
}
return btn;
}
static esp_err_t button_delete_com(button_dev_t *btn)
{
BTN_CHECK(NULL != btn, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
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;
}
button_handle_t iot_button_create(const button_config_t *config)
{
ESP_LOGI(TAG, "IoT Button Version: %d.%d.%d", BUTTON_VER_MAJOR, BUTTON_VER_MINOR, BUTTON_VER_PATCH);
BTN_CHECK(config, "Invalid button config", NULL);
esp_err_t ret = ESP_OK;
button_dev_t *btn = NULL;
uint16_t long_press_time = 0;
uint16_t short_press_time = 0;
long_press_time = TIME_TO_TICKS(config->long_press_time, LONG_TICKS);
short_press_time = TIME_TO_TICKS(config->short_press_time, SHORT_TICKS);
switch (config->type) {
case BUTTON_TYPE_GPIO: {
const button_gpio_config_t *cfg = &(config->gpio_button_config);
ret = button_gpio_init(cfg);
BTN_CHECK(ESP_OK == ret, "gpio button init failed", NULL);
btn = button_create_com(cfg->active_level, button_gpio_get_key_level, (void *)cfg->gpio_num, long_press_time, short_press_time);
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
if (cfg->enable_power_save) {
btn->enable_power_save = cfg->enable_power_save;
button_gpio_set_intr(cfg->gpio_num, cfg->active_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL, button_power_save_isr_handler, (void *)cfg->gpio_num);
}
#endif
} break;
#if CONFIG_SOC_ADC_SUPPORTED
case BUTTON_TYPE_ADC: {
const button_adc_config_t *cfg = &(config->adc_button_config);
ret = button_adc_init(cfg);
BTN_CHECK(ESP_OK == ret, "adc button init failed", NULL);
btn = button_create_com(1, button_adc_get_key_level, (void *)ADC_BUTTON_COMBINE(cfg->adc_channel, cfg->button_index), long_press_time, short_press_time);
} break;
#endif
case BUTTON_TYPE_MATRIX: {
const button_matrix_config_t *cfg = &(config->matrix_button_config);
ret = button_matrix_init(cfg);
BTN_CHECK(ESP_OK == ret, "matrix button init failed", NULL);
btn = button_create_com(1, button_matrix_get_key_level, (void *)MATRIX_BUTTON_COMBINE(cfg->row_gpio_num, cfg->col_gpio_num), long_press_time, short_press_time);
} break;
case BUTTON_TYPE_CUSTOM: {
if (config->custom_button_config.button_custom_init) {
ret = config->custom_button_config.button_custom_init(config->custom_button_config.priv);
BTN_CHECK(ESP_OK == ret, "custom button init failed", NULL);
}
btn = button_create_com(config->custom_button_config.active_level,
config->custom_button_config.button_custom_get_key_value,
config->custom_button_config.priv,
long_press_time, short_press_time);
if (btn) {
btn->hal_button_deinit = config->custom_button_config.button_custom_deinit;
}
} break;
default:
ESP_LOGE(TAG, "Unsupported button type");
break;
}
BTN_CHECK(NULL != btn, "button create failed", NULL);
btn->type = config->type;
if (!btn->enable_power_save && !g_is_timer_running) {
esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U);
g_is_timer_running = true;
}
return (button_handle_t)btn;
}
esp_err_t iot_button_delete(button_handle_t btn_handle)
{
esp_err_t ret = ESP_OK;
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *)btn_handle;
switch (btn->type) {
case BUTTON_TYPE_GPIO:
ret = button_gpio_deinit((int)(btn->hardware_data));
break;
#if CONFIG_SOC_ADC_SUPPORTED
case BUTTON_TYPE_ADC:
ret = button_adc_deinit(ADC_BUTTON_SPLIT_CHANNEL(btn->hardware_data), ADC_BUTTON_SPLIT_INDEX(btn->hardware_data));
break;
#endif
case BUTTON_TYPE_MATRIX:
ret = button_matrix_deinit(MATRIX_BUTTON_SPLIT_ROW(btn->hardware_data), MATRIX_BUTTON_SPLIT_COL(btn->hardware_data));
break;
case BUTTON_TYPE_CUSTOM:
if (btn->hal_button_deinit) {
ret = btn->hal_button_deinit(btn->hardware_data);
}
break;
default:
break;
}
BTN_CHECK(ESP_OK == ret, "button deinit failed", ESP_FAIL);
for (int i = 0; i < BUTTON_EVENT_MAX; i++) {
if (btn->cb_info[i]) {
free(btn->cb_info[i]);
}
}
button_delete_com(btn);
return ESP_OK;
}
esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_cb_t cb, void *usr_data)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
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;
BTN_CHECK(event != BUTTON_MULTIPLE_CLICK, "event argument is invalid", ESP_ERR_INVALID_ARG);
button_event_config_t event_cfg = {
.event = event,
};
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 == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) && !event_cfg.event_data.long_press.press_time) {
event_cfg.event_data.long_press.press_time = btn->long_press_ticks * TICKS_INTERVAL;
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");
}
return iot_button_register_event_cb(btn_handle, event_cfg, cb, usr_data);
}
esp_err_t iot_button_register_event_cb(button_handle_t btn_handle, button_event_config_t event_cfg, button_cb_t cb, void *usr_data)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
button_event_t event = event_cfg.event;
BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG);
BTN_CHECK(!(event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) || event_cfg.event_data.long_press.press_time > btn->short_press_ticks * TICKS_INTERVAL, "event_data is invalid", ESP_ERR_INVALID_ARG);
BTN_CHECK(event != BUTTON_MULTIPLE_CLICK || event_cfg.event_data.multiple_clicks.clicks, "event_data is invalid", ESP_ERR_INVALID_ARG);
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);
@ -573,27 +370,30 @@ esp_err_t iot_button_register_event_cb(button_handle_t btn_handle, button_event_
btn->cb_info[event][btn->size[event]].usr_data = usr_data;
btn->size[event]++;
/** Inserting the event_data in sorted manner */
/** Inserting the event_args in sorted manner */
if (event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) {
uint16_t press_time = event_cfg.event_data.long_press.press_time;
BTN_CHECK(press_time / TICKS_INTERVAL > btn->short_press_ticks, "press_time event_data is less than short_press_ticks", ESP_ERR_INVALID_ARG);
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_data.long_press.press_time > press_time) {
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_data.long_press.press_time = press_time;
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_data.long_press.press_time = press_time;
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_data.long_press.press_time = press_time;
btn->cb_info[event][btn->size[event] - 1].event_args.long_press.press_time = press_time;
}
int32_t press_ticks = press_time / TICKS_INTERVAL;
@ -603,35 +403,52 @@ esp_err_t iot_button_register_event_cb(button_handle_t btn_handle, button_event_
}
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_data.multiple_clicks.clicks > event_cfg.event_data.multiple_clicks.clicks) {
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_data.multiple_clicks.clicks = event_cfg.event_data.multiple_clicks.clicks;
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_data.multiple_clicks.clicks = event_cfg.event_data.multiple_clicks.clicks;
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_data.multiple_clicks.clicks = event_cfg.event_data.multiple_clicks.clicks;
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)
esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event, button_event_args_t *event_args)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG);
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;
BTN_CHECK(NULL != btn->cb_info[event], "No callbacks registered for the event", ESP_ERR_INVALID_STATE);
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]);
@ -648,52 +465,40 @@ esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t ev
btn->cb_info[event] = NULL;
btn->size[event] = 0;
return ESP_OK;
}
esp_err_t iot_button_unregister_event(button_handle_t btn_handle, button_event_config_t event_cfg, button_cb_t cb)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_event_t event = event_cfg.event;
BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG);
BTN_CHECK(NULL != cb, "Pointer to function callback is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
int check = -1;
unregister_event:
for (int i = 0; i < btn->size[event]; i++) {
if (cb == btn->cb_info[event][i].cb) {
if ((event == BUTTON_LONG_PRESS_START || event == BUTTON_LONG_PRESS_UP) && event_cfg.event_data.long_press.press_time) {
if (event_cfg.event_data.long_press.press_time != btn->cb_info[event][i].event_data.long_press.press_time) {
continue;
}
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_cfg.event_data.multiple_clicks.clicks) {
if (event_cfg.event_data.multiple_clicks.clicks != btn->cb_info[event][i].event_data.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;
}
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;
}
BTN_CHECK(check != -1, "No such callback registered for the event", ESP_ERR_INVALID_STATE);
ESP_RETURN_ON_FALSE(check != -1, ESP_ERR_NOT_FOUND, TAG, "No such callback registered for the event");
return ESP_OK;
}
@ -710,7 +515,7 @@ size_t iot_button_count_cb(button_handle_t btn_handle)
return ret;
}
size_t iot_button_count_event(button_handle_t btn_handle, button_event_t event)
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;
@ -745,13 +550,18 @@ uint8_t iot_button_get_repeat(button_handle_t btn_handle)
return btn->repeat;
}
uint32_t iot_button_get_ticks_time(button_handle_t btn_handle)
uint32_t iot_button_get_pressed_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);
}
uint32_t iot_button_get_ticks_time(button_handle_t btn_handle)
{
return iot_button_get_pressed_time(btn_handle);
}
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);
@ -782,18 +592,19 @@ uint8_t iot_button_get_key_level(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0);
button_dev_t *btn = (button_dev_t *)btn_handle;
uint8_t level = btn->hal_button_Level(btn->hardware_data);
return (level == btn->active_level) ? 1 : 0;
uint8_t level = btn->driver->get_key_level(btn->driver);
return level;
}
esp_err_t iot_button_resume(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 already running", ESP_ERR_INVALID_STATE);
esp_err_t err = esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U);
BTN_CHECK(ESP_OK == err, "Button timer start failed", ESP_FAIL);
g_is_timer_running = true;
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;
}
@ -808,7 +619,6 @@ esp_err_t iot_button_stop(void)
return ESP_OK;
}
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
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);
@ -818,4 +628,83 @@ esp_err_t iot_button_register_power_save_cb(const button_power_save_config_t *co
power_save_usr_cfg.usr_data = config->usr_data;
return ESP_OK;
}
#endif
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

@ -1,9 +1,8 @@
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
list(APPEND PRIVREQ esp_adc)
else()
list(APPEND PRIVREQ esp_adc_cal)
endif()
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES unity test_utils button ${PRIVREQ})
PRIV_REQUIRES esp_event 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, "\tPressed Time[%"PRIu32"]", iot_button_get_pressed_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 pressed_time = iot_button_get_pressed_time(arg);
int32_t diff = pressed_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

@ -1,743 +0,0 @@
/* SPDX-FileCopyrightText: 2022-2024 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 "sdkconfig.h"
static const char *TAG = "BUTTON TEST";
#define TEST_MEMORY_LEAK_THRESHOLD (-400)
#define BUTTON_IO_NUM 0
#define BUTTON_ACTIVE_LEVEL 0
#define BUTTON_NUM 16
static size_t before_free_8bit;
static size_t before_free_32bit;
static button_handle_t g_btns[BUTTON_NUM] = {0};
static int get_btn_index(button_handle_t btn)
{
for (size_t i = 0; i < BUTTON_NUM; i++) {
if (btn == g_btns[i]) {
return i;
}
}
return -1;
}
static void button_press_down_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_PRESS_DOWN, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_DOWN", get_btn_index((button_handle_t)arg));
}
static void button_press_up_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_PRESS_UP, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_UP[%"PRIu32"]", get_btn_index((button_handle_t)arg), iot_button_get_ticks_time((button_handle_t)arg));
}
static void button_press_repeat_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_REPEAT[%d]", get_btn_index((button_handle_t)arg), iot_button_get_repeat((button_handle_t)arg));
}
static void button_single_click_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_SINGLE_CLICK, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_SINGLE_CLICK", get_btn_index((button_handle_t)arg));
}
static void button_double_click_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_DOUBLE_CLICK, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_DOUBLE_CLICK", get_btn_index((button_handle_t)arg));
}
static void button_long_press_start_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_LONG_PRESS_START, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_LONG_PRESS_START", get_btn_index((button_handle_t)arg));
}
static void button_long_press_hold_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_LONG_PRESS_HOLD, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_LONG_PRESS_HOLD[%"PRIu32"],count is [%d]", get_btn_index((button_handle_t)arg), iot_button_get_ticks_time((button_handle_t)arg), iot_button_get_long_press_hold_cnt((button_handle_t)arg));
}
static void button_press_repeat_done_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(BUTTON_PRESS_REPEAT_DONE, iot_button_get_event(arg));
ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_REPEAT_DONE[%d]", get_btn_index((button_handle_t)arg), iot_button_get_repeat((button_handle_t)arg));
}
static esp_err_t custom_button_gpio_init(void *param)
{
button_gpio_config_t *cfg = (button_gpio_config_t *)param;
return button_gpio_init(cfg);
}
static uint8_t custom_button_gpio_get_key_value(void *param)
{
button_gpio_config_t *cfg = (button_gpio_config_t *)param;
return button_gpio_get_key_level((void *)cfg->gpio_num);
}
static esp_err_t custom_button_gpio_deinit(void *param)
{
button_gpio_config_t *cfg = (button_gpio_config_t *)param;
return button_gpio_deinit(cfg->gpio_num);
}
TEST_CASE("custom button test", "[button][iot]")
{
button_gpio_config_t *gpio_cfg = calloc(1, sizeof(button_gpio_config_t));
gpio_cfg->active_level = 0;
gpio_cfg->gpio_num = 0;
button_config_t cfg = {
.type = BUTTON_TYPE_CUSTOM,
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
.custom_button_config = {
.button_custom_init = custom_button_gpio_init,
.button_custom_deinit = custom_button_gpio_deinit,
.button_custom_get_key_value = custom_button_gpio_get_key_value,
.active_level = 0,
.priv = gpio_cfg,
},
};
g_btns[0] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[0]);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_DOWN, button_press_down_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_UP, button_press_up_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT, button_press_repeat_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_SINGLE_CLICK, button_single_click_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_DOUBLE_CLICK, button_double_click_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_START, button_long_press_start_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT_DONE, button_press_repeat_done_cb, NULL);
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
iot_button_delete(g_btns[0]);
}
TEST_CASE("gpio button test", "[button][iot]")
{
button_config_t cfg = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
.gpio_button_config = {
.gpio_num = 0,
.active_level = 0,
},
};
g_btns[0] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[0]);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_DOWN, button_press_down_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_UP, button_press_up_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT, button_press_repeat_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_SINGLE_CLICK, button_single_click_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_DOUBLE_CLICK, button_double_click_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_START, button_long_press_start_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT_DONE, button_press_repeat_done_cb, NULL);
uint8_t level = 0;
level = iot_button_get_key_level(g_btns[0]);
ESP_LOGI(TAG, "button level is %d", level);
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
iot_button_delete(g_btns[0]);
}
TEST_CASE("gpio button get event test", "[button][iot]")
{
button_config_t cfg = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
.gpio_button_config = {
.gpio_num = 0,
.active_level = 0,
},
};
g_btns[0] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[0]);
uint8_t level = 0;
level = iot_button_get_key_level(g_btns[0]);
ESP_LOGI(TAG, "button level is %d", level);
while (1) {
button_event_t event = iot_button_get_event(g_btns[0]);
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(g_btns[0]);
}
TEST_CASE("gpio button test power save", "[button][iot][power save]")
{
button_config_t cfg = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
.gpio_button_config = {
.gpio_num = 0,
.active_level = 0,
},
};
g_btns[0] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[0]);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_DOWN, button_press_down_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_UP, button_press_up_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT, button_press_repeat_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_SINGLE_CLICK, button_single_click_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_DOUBLE_CLICK, button_double_click_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_START, button_long_press_start_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb, NULL);
iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT_DONE, button_press_repeat_done_cb, NULL);
TEST_ASSERT_EQUAL(ESP_OK, iot_button_stop());
vTaskDelay(pdMS_TO_TICKS(1000));
TEST_ASSERT_EQUAL(ESP_OK, iot_button_resume());
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
iot_button_delete(g_btns[0]);
}
TEST_CASE("matrix keyboard button test", "[button][matrix key]")
{
int32_t row_gpio[4] = {4, 5, 6, 7};
int32_t col_gpio[4] = {3, 8, 16, 15};
button_config_t cfg = {
.type = BUTTON_TYPE_MATRIX,
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
.matrix_button_config = {
.row_gpio_num = 0,
.col_gpio_num = 0,
}
};
for (int i = 0; i < 4; i++) {
cfg.matrix_button_config.row_gpio_num = row_gpio[i];
for (int j = 0; j < 4; j++) {
cfg.matrix_button_config.col_gpio_num = col_gpio[j];
g_btns[i * 4 + j] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[i * 4 + j]);
iot_button_register_cb(g_btns[i * 4 + j], BUTTON_PRESS_DOWN, button_press_down_cb, NULL);
iot_button_register_cb(g_btns[i * 4 + j], BUTTON_PRESS_UP, button_press_up_cb, NULL);
iot_button_register_cb(g_btns[i * 4 + j], BUTTON_PRESS_REPEAT, button_press_repeat_cb, NULL);
iot_button_register_cb(g_btns[i * 4 + j], BUTTON_SINGLE_CLICK, button_single_click_cb, NULL);
iot_button_register_cb(g_btns[i * 4 + j], BUTTON_DOUBLE_CLICK, button_double_click_cb, NULL);
iot_button_register_cb(g_btns[i * 4 + j], BUTTON_LONG_PRESS_START, button_long_press_start_cb, NULL);
iot_button_register_cb(g_btns[i * 4 + j], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb, NULL);
iot_button_register_cb(g_btns[i * 4 + j], BUTTON_PRESS_REPEAT_DONE, button_press_repeat_done_cb, NULL);
}
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
iot_button_delete(g_btns[i * 4 + j]);
}
}
}
#if CONFIG_SOC_ADC_SUPPORTED
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
TEST_CASE("adc button test", "[button][iot]")
{
/** ESP32-S3-Korvo board */
const uint16_t vol[6] = {380, 820, 1180, 1570, 1980, 2410};
button_config_t cfg = {0};
cfg.type = BUTTON_TYPE_ADC;
cfg.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS;
cfg.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS;
for (size_t i = 0; i < 6; i++) {
cfg.adc_button_config.adc_channel = 7,
cfg.adc_button_config.button_index = i;
if (i == 0) {
cfg.adc_button_config.min = (0 + vol[i]) / 2;
} else {
cfg.adc_button_config.min = (vol[i - 1] + vol[i]) / 2;
}
if (i == 5) {
cfg.adc_button_config.max = (vol[i] + 3000) / 2;
} else {
cfg.adc_button_config.max = (vol[i] + vol[i + 1]) / 2;
}
g_btns[i] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[i]);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_DOWN, button_press_down_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_UP, button_press_up_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_REPEAT, button_press_repeat_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_SINGLE_CLICK, button_single_click_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_DOUBLE_CLICK, button_double_click_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_START, button_long_press_start_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_REPEAT_DONE, button_press_repeat_done_cb, NULL);
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
for (size_t i = 0; i < 6; i++) {
iot_button_delete(g_btns[i]);
}
}
#else
#include "esp_adc/adc_cali.h"
static esp_err_t adc_calibration_init(adc_unit_t unit, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define ADC_BUTTON_WIDTH SOC_ADC_RTC_MAX_BITWIDTH
#else
#define ADC_BUTTON_WIDTH ADC_WIDTH_MAX - 1
#endif
adc_cali_handle_t handle = NULL;
esp_err_t ret = ESP_FAIL;
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 {
ESP_LOGE(TAG, "Invalid arg or no memory");
}
return calibrated ? ESP_OK : ESP_FAIL;
}
TEST_CASE("adc button idf5 drive test", "[button][iot]")
{
adc_oneshot_unit_handle_t adc1_handle;
adc_cali_handle_t adc1_cali_handle;
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
};
esp_err_t ret = adc_oneshot_new_unit(&init_config, &adc1_handle);
TEST_ASSERT_TRUE(ret == ESP_OK);
/*!< use atten 11db or 12db */
adc_calibration_init(ADC_UNIT_1, 3, &adc1_cali_handle);
/** ESP32-S3-Korvo board */
const uint16_t vol[6] = {380, 820, 1180, 1570, 1980, 2410};
button_config_t cfg = {0};
cfg.type = BUTTON_TYPE_ADC;
cfg.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS;
cfg.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS;
for (size_t i = 0; i < 6; i++) {
cfg.adc_button_config.adc_handle = &adc1_handle;
cfg.adc_button_config.adc_channel = 7,
cfg.adc_button_config.button_index = i;
if (i == 0) {
cfg.adc_button_config.min = (0 + vol[i]) / 2;
} else {
cfg.adc_button_config.min = (vol[i - 1] + vol[i]) / 2;
}
if (i == 5) {
cfg.adc_button_config.max = (vol[i] + 3000) / 2;
} else {
cfg.adc_button_config.max = (vol[i] + vol[i + 1]) / 2;
}
g_btns[i] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[i]);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_DOWN, button_press_down_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_UP, button_press_up_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_REPEAT, button_press_repeat_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_SINGLE_CLICK, button_single_click_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_DOUBLE_CLICK, button_double_click_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_START, button_long_press_start_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb, NULL);
iot_button_register_cb(g_btns[i], BUTTON_PRESS_REPEAT_DONE, button_press_repeat_done_cb, NULL);
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
for (size_t i = 0; i < 6; i++) {
iot_button_delete(g_btns[i]);
}
}
#endif
#endif // CONFIG_SOC_ADC_SUPPORTED
#define GPIO_OUTPUT_IO_45 45
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(g_btns[0]) == state) {
xEventGroupSetBits(g_check, BIT(1));
}
}
static void button_auto_check_cb(void *arg, void *data)
{
if (iot_button_get_event(g_btns[0]) == 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));
button_config_t cfg = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
.gpio_button_config = {
.gpio_num = 0,
.active_level = 0,
},
};
g_btns[0] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[0]);
/* 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_config_t btn_cfg;
btn_cfg.event = i;
btn_cfg.event_data.multiple_clicks.clicks = 4;
iot_button_register_event_cb(g_btns[0], btn_cfg, button_auto_check_cb_1, NULL);
iot_button_register_event_cb(g_btns[0], btn_cfg, button_auto_check_cb, NULL);
} else {
iot_button_register_cb(g_btns[0], i, button_auto_check_cb_1, NULL);
iot_button_register_cb(g_btns[0], i, button_auto_check_cb, NULL);
}
}
TEST_ASSERT_EQUAL(ESP_OK, iot_button_set_param(g_btns[0], 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_config_t btn_cfg;
btn_cfg.event = i;
if (i == BUTTON_MULTIPLE_CLICK) {
btn_cfg.event_data.multiple_clicks.clicks = 4;
} else if (i == BUTTON_LONG_PRESS_UP || i == BUTTON_LONG_PRESS_START) {
btn_cfg.event_data.long_press.press_time = 1500;
}
iot_button_unregister_event(g_btns[0], btn_cfg, button_auto_check_cb);
iot_button_unregister_event(g_btns[0], btn_cfg, button_auto_check_cb_1);
}
TEST_ASSERT_EQUAL(ESP_OK, iot_button_delete(g_btns[0]));
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(g_btns[0]);
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(state), 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);
button_config_t cfg = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS,
.short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS,
.gpio_button_config = {
.gpio_num = 0,
.active_level = 0,
},
};
g_btns[0] = iot_button_create(&cfg);
TEST_ASSERT_NOT_NULL(g_btns[0]);
button_event_config_t btn_cfg;
btn_cfg.event = BUTTON_LONG_PRESS_START;
for (int i = 0; i < 5; i++) {
btn_cfg.event_data.long_press.press_time = long_press_time[i];
uint32_t data = (btn_cfg.event << 16) | long_press_time[i];
iot_button_register_event_cb(g_btns[0], btn_cfg, 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(g_btns[0], BUTTON_LONG_PRESS_START);
btn_cfg.event = BUTTON_LONG_PRESS_UP;
for (int i = 0; i < 5; i++) {
btn_cfg.event_data.long_press.press_time = long_press_time[i];
uint32_t data = (btn_cfg.event << 16) | long_press_time[i];
iot_button_register_event_cb(g_btns[0], btn_cfg, 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(g_btns[0]));
vSemaphoreDelete(long_press_check);
vSemaphoreDelete(long_press_auto_check_pass);
vTaskDelay(pdMS_TO_TICKS(100));
}
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}
void app_main(void)
{
/*
* ____ _ _ _______ _
*| _ \ | | | | |__ __| | |
*| |_) | _ _ | |_ | |_ ___ _ __ | | ___ ___ | |_
*| _ < | | | || __|| __|/ _ \ | '_ \ | | / _ \/ __|| __|
*| |_) || |_| || |_ | |_| (_) || | | | | || __/\__ \| |_
*|____/ \__,_| \__| \__|\___/ |_| |_| |_| \___||___/ \__|
*/
printf(" ____ _ _ _______ _ \n");
printf(" | _ \\ | | | | |__ __| | | \n");
printf(" | |_) | _ _ | |_ | |_ ___ _ __ | | ___ ___ | |_ \n");
printf(" | _ < | | | || __|| __|/ _ \\ | '_ \\ | | / _ \\/ __|| __|\n");
printf(" | |_) || |_| || |_ | |_| (_) || | | | | || __/\\__ \\| |_ \n");
printf(" |____/ \\__,_| \\__| \\__|\\___/ |_| |_| |_| \\___||___/ \\__|\n");
unity_run_menu();
}

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, "\tPressed Time[%"PRIu32"]", iot_button_get_pressed_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, "\tPressed Time[%"PRIu32"]", iot_button_get_pressed_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, "\tPressed Time[%"PRIu32"]", iot_button_get_pressed_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

@ -1 +0,0 @@
CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE=y