Initial public release of the 2024A software.

This commit is contained in:
Joe Kearney 2025-01-25 14:04:42 -06:00
parent 7b9ad3edfd
commit 303e9e1dad
361 changed files with 60083 additions and 2 deletions

View file

@ -0,0 +1 @@
30a3f495c3862d505ce6e41adbbd218b2750e9723ab2151feff00e9fe685b326

View file

@ -0,0 +1,157 @@
# ChangeLog
## v3.5.0 - 2024-12-27
### Enhancements:
* Add config to disable gpio button internal pull resistor.
## v3.4.1 - 2024-12-6
### Fix:
* Fix the issue where `BUTTON_LONG_PRESS_START` is not triggered when the polling interval exceeds 20ms.
* Remove the `BUTTON_LONG_PRESS_TOLERANCE_MS` configuration option.
## v3.4.0 - 2024-10-22
### Enhancements:
* Supports a maximum button polling interval of 500ms.
* Fixed a potential counter overflow issue.
### Break change:
* The return value of `iot_button_get_ticks_time` has been changed from `uint16_t` to `uint32_t`.
## v3.3.2 - 2024-8-28
### Enhancements:
* Support macro CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP in power save mode.
* Supports retrieving and printing the string corresponding to a button event.
* Fixed the bug where the event was not assigned to `BUTTON_LONG_PRESS_START` before the `BUTTON_LONG_PRESS_START` event occurred.
## v3.3.1 - 2024-8-8
### Enhancements:
* Add Button Event **BUTTON_PRESS_END**.
## v3.3.0 - 2024-8-7
### Enhancements:
* Add Callback **button_power_save_cb_t** to support enter power save manually.
* Increase the maximum polling interval supported by the button from 20ms to 50ms.
## v3.2.3 - 2024-7-2
* Fixed the issue where the GPIO button in low-power mode continuously woke up the CPU after being pressed, causing abnormal power consumption.
## v3.2.2 - 2024-6-17
* Fix the compilation error for chips that do not support ADC.
## v3.2.1 - 2024-6-17
### bugfix
- Fixed ignored ADC button tied to GND. thanks `demianzenkov` for the fix.
## v3.2.0 - 2023-11-13
### Enhancements:
* The power consumption of GPIO buttons is lower during light sleep mode.
## v3.1.3 - 2023-11-13
* Resolved issue 'ADC_ATTEN_DB_11 is deprecated'.
## v3.1.2 - 2023-10-24
### bugfix
* Fixed a bug where iot_button_delete feature crashes for custom button
## v3.1.1 - 2023-10-18
### bugfix
* Fixed a bug where multiple callbacks feature crashes for BUTTON_MULTIPLE_CLICK
## v3.1.0 - 2023-10-9
### Enhancements:
* Support matrix keypad
## v3.0.1 - 2023-9-1
### Enhancements:
* Resolves bug for iot_button_unregister_event function returned error when reallocating with 0 byte.
* Update Test cases to test iot_button_unregister_event_cb
* Add api iot_button_stop & iot_button_resume for power save.
## v3.0.0 - 2023-8-15
### Enhancements:
* Add support to register multiple callbacks for a button_event
* Update iot_button_unregister_cb, to unregister all the callbacks for that event
* Add iot_button_unregister_event to unregister specific callbacks of that event
* Add iot_button_count_event to return number of callbacks registered for the event.
* Update iot_button_count_cb, to return sum of number of registered callbacks.
* Add support for Long press on specific time
* Add iot_button_register_event, which takes specific event_config_t data as input.
* Add BUTTON_LONG_PRESS_UP to trigger callback at the latest time of button release
* Update BUTTON_LONG_PRESS_START to trigger callback as the time passes for long_press.
* Add support to trigger callback for specified number of clicks.
## v2.5.6 - 2023-8-22
### bugfix
* Fixed a bug where the Serial trigger interval in button_long_press_hold event fires at an incorrect time
## v2.5.5 - 2023-8-3
* Add modify api which can change long_press_time and short_press_time
## v2.5.4 - 2023-7-27
### Enhancements:
* Add test apps and ci auto test
## v2.5.3 - 2023-7-26
### Enhancements:
* `repeat` defined in struct button_dev_t is reset to 0 after event `BUTTON_PRESS_REPEAT_DONE`
## v2.5.2 - 2023-7-18
### Enhancements:
* Set "event" member to BUTTON_PRESS_REPEAT before calling the BUTTON_PRESS_REPEAT callback
## v2.5.1 - 2023-3-14
### Enhancements:
* Update doc and code specification
* Avoid overwriting callback by @franz-ms-muc in #252
## v2.5.0 - 2023-2-1
### Enhancements:
* Support custom button
* Add BUTTON_PRESS_REPEAT_DONE event

View file

@ -0,0 +1,32 @@
set(PRIVREQ esp_timer)
set(REQ driver)
set(SRC_FILES "button_gpio.c" "iot_button.c" "button_matrix.c")
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
list(APPEND REQ esp_adc)
if(CONFIG_SOC_ADC_SUPPORTED)
list(APPEND SRC_FILES "button_adc.c")
endif()
else()
list(APPEND REQ esp_adc_cal)
list(APPEND SRC_FILES "button_adc.c")
endif()
idf_component_register(SRCS ${SRC_FILES}
INCLUDE_DIRS include
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

@ -0,0 +1,65 @@
menu "IoT Button"
config BUTTON_PERIOD_TIME_MS
int "BUTTON PERIOD TIME (MS)"
range 2 500
default 5
help
"Button scan interval"
config BUTTON_DEBOUNCE_TICKS
int "BUTTON DEBOUNCE TICKS"
range 1 7
default 2
help
"One CONFIG_BUTTON_DEBOUNCE_TICKS equal to CONFIG_BUTTON_PERIOD_TIME_MS"
config BUTTON_SHORT_PRESS_TIME_MS
int "BUTTON SHORT PRESS TIME (MS)"
range 50 800
default 180
config BUTTON_LONG_PRESS_TIME_MS
int "BUTTON LONG PRESS TIME (MS)"
range 500 5000
default 1500
config BUTTON_SERIAL_TIME_MS
int "BUTTON 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.
config ADC_BUTTON_MAX_CHANNEL
int "ADC BUTTON MAX CHANNEL"
range 1 5
default 3
help
"Maximum number of channels for ADC buttons"
config ADC_BUTTON_MAX_BUTTON_PER_CHANNEL
int "ADC BUTTON MAX BUTTON PER CHANNEL"
range 1 10
default 8
help
"Maximum number of buttons per channel"
config ADC_BUTTON_SAMPLE_TIMES
int "ADC BUTTON SAMPLE TIMES"
range 1 4
default 1
help
"Number of samples per scan"
endmenu

View file

@ -0,0 +1,42 @@
[![Component Registry](https://components.espressif.com/components/espressif/button/badge.svg)](https://components.espressif.com/components/espressif/button)
# Component: Button
[Online documentation](https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/button.html)
After creating a new button object by calling function `button_create()`, the button object can create press events, every press event can have its own callback.
List of supported events:
* Button pressed
* Button released
* Button pressed repeat
* Button press repeat done
* Button single click
* Button double click
* Button multiple click
* Button long press start
* Button long press hold
* Button long press up
* Button Press end
![](https://dl.espressif.com/AE/esp-iot-solution/button_3.3.1.svg)
There are three ways this driver can handle buttons:
1. Buttons connected to standard digital GPIO
2. Multiple buttons connected to single ADC channel
3. Matrix keyboard employs multiple GPIOs for operation.
4. Custom button connect to any driver
The component supports the following functionalities:
1. Creation of an unlimited number of buttons, accommodating various types simultaneously.
2. Multiple callback functions for a single event.
3. Allowing customization of the consecutive key press count to any desired number.
4. Facilitating the setup of callbacks for any specified long-press duration.
5. Support power save mode (Only for gpio button)
## Add component to your project
Please use the component manager command `add-dependency` to add the `button` to your project's dependency, during the `CMake` step the component will be downloaded automatically
```
idf.py add-dependency "espressif/button=*"
```

View file

@ -0,0 +1,312 @@
/* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <inttypes.h>
#include "esp_log.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"
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); \
}
#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)
#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
typedef struct {
uint16_t min;
uint16_t max;
} button_data_t;
typedef struct {
uint8_t channel;
uint8_t is_init;
button_data_t btns[ADC_BUTTON_MAX_BUTTON]; /* all button on the channel */
uint64_t last_time; /* the last time of adc sample */
} btn_adc_channel_t;
typedef 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
btn_adc_channel_t ch[ADC_BUTTON_MAX_CHANNEL];
uint8_t ch_num;
} adc_button_t;
static adc_button_t g_button = {0};
static int find_unused_channel(void)
{
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (0 == g_button.ch[i].is_init) {
return i;
}
}
return -1;
}
static int find_channel(uint8_t channel)
{
for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) {
if (channel == g_button.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)
{
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;
}
#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;
}
esp_err_t button_adc_deinit(uint8_t channel, int button_index)
{
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);
int ch_index = find_channel(channel);
ADC_BTN_CHECK(ch_index >= 0, "can't find the channel", ESP_ERR_INVALID_ARG);
g_button.ch[ch_index].btns[button_index].max = 0;
g_button.ch[ch_index].btns[button_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) {
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);
}
/** 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) {
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));
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
return ESP_OK;
}
static uint32_t get_adc_volatge(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_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);
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)
{
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);
/** 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 (vol <= g_button.ch[ch_index].btns[index].max &&
vol >= g_button.ch[ch_index].btns[index].min) {
return 1;
}
return 0;
}

View file

@ -0,0 +1,114 @@
/* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "driver/gpio.h"
#include "button_gpio.h"
#include "esp_sleep.h"
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); \
}
esp_err_t button_gpio_init(const button_gpio_config_t *config)
{
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);
gpio_config_t gpio_conf;
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.pull_down_en = GPIO_PULLDOWN_ENABLE;
gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
} else {
gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
}
}
gpio_config(&gpio_conf);
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
if (config->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);
return ESP_FAIL;
}
gpio_hold_en(config->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);
#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);
#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);
#endif
#else
ret = esp_sleep_enable_gpio_wakeup();
#endif
GPIO_BTN_CHECK(ret == ESP_OK, "Configure gpio as wakeup source failed", ESP_FAIL);
}
#endif
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);
}
return ret;
}

View file

@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "driver/gpio.h"
#include "button_matrix.h"
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); \
}
esp_err_t button_matrix_init(const button_matrix_config_t *config)
{
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
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_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)
{
//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);
return ESP_OK;
}
uint8_t button_matrix_get_key_level(void *hardware_data)
{
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);
return level;
}

View file

@ -0,0 +1,6 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(button_power_save)

View file

@ -0,0 +1,43 @@
## Button Power Save Example
This example demonstrates how to utilize the `button` component in conjunction with the light sleep low-power mode.
* `button` [Component Introduction](https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/button.html)
## Hardware
* Any GPIO on any development board can be used in this example.
## Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
* Run `. ./export.sh` to set IDF environment
* Run `idf.py set-target esp32xx` to set target chip
* Run `idf.py -p PORT flash monitor` to build, flash and monitor the project
(To exit the serial monitor, type `Ctrl-]`.)
See the Getting Started Guide for all the steps to configure and use the ESP-IDF to build projects.
## Example Output
```
I (1139) pm: Frequency switching config: CPU_MAX: 160, APB_MAX: 80, APB_MIN: 80, Light sleep: ENABLED
I (1149) sleep: Code start at 42000020, total 119.03 KiB, data start at 3c000000, total 49152.00 KiB
I (1159) button: IoT Button Version: 3.2.0
I (1163) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (2922) button_power_save: Button event BUTTON_PRESS_DOWN
I (3017) button_power_save: Button event BUTTON_PRESS_UP
I (3017) button_power_save: Wake up from light sleep, reason 4
I (3200) button_power_save: Button event BUTTON_SINGLE_CLICK
I (3200) button_power_save: Wake up from light sleep, reason 4
I (3202) button_power_save: Button event BUTTON_PRESS_REPEAT_DONE
I (3208) button_power_save: Wake up from light sleep, reason 4
I (3627) button_power_save: Button event BUTTON_PRESS_DOWN
I (3702) button_power_save: Button event BUTTON_PRESS_UP
I (3702) button_power_save: Wake up from light sleep, reason 4
I (3887) button_power_save: Button event BUTTON_SINGLE_CLICK
I (3887) button_power_save: Wake up from light sleep, reason 4
I (3889) button_power_save: Button event BUTTON_PRESS_REPEAT_DONE
```

View file

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

View file

@ -0,0 +1,87 @@
menu "Example Configuration"
choice ENTER_LIGHT_SLEEP_MODE
prompt "Enter light sleep mode"
default ENTER_LIGHT_SLEEP_AUTO
depends on PM_ENABLE
help
Enable light sleep mode to save power.
config ENTER_LIGHT_SLEEP_AUTO
bool "Auto enter Light Sleep"
config ENTER_LIGHT_SLEEP_MODE_MANUALLY
bool "Manually enter Light Sleep"
endchoice
choice EXAMPLE_MAX_CPU_FREQ
prompt "Maximum CPU frequency"
default EXAMPLE_MAX_CPU_FREQ_80 if !IDF_TARGET_ESP32H2
default EXAMPLE_MAX_CPU_FREQ_96 if IDF_TARGET_ESP32H2
depends on PM_ENABLE
help
Maximum CPU frequency to use for dynamic frequency scaling.
config EXAMPLE_MAX_CPU_FREQ_80
bool "80 MHz"
config EXAMPLE_MAX_CPU_FREQ_96
bool "96 MHz"
depends on IDF_TARGET_ESP32H2
config EXAMPLE_MAX_CPU_FREQ_120
bool "120 MHz"
depends on IDF_TARGET_ESP32C2
config EXAMPLE_MAX_CPU_FREQ_160
bool "160 MHz"
depends on !IDF_TARGET_ESP32C2
config EXAMPLE_MAX_CPU_FREQ_240
bool "240 MHz"
depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
endchoice
config EXAMPLE_MAX_CPU_FREQ_MHZ
int
default 80 if EXAMPLE_MAX_CPU_FREQ_80
default 96 if EXAMPLE_MAX_CPU_FREQ_96
default 120 if EXAMPLE_MAX_CPU_FREQ_120
default 160 if EXAMPLE_MAX_CPU_FREQ_160
default 240 if EXAMPLE_MAX_CPU_FREQ_240
choice EXAMPLE_MIN_CPU_FREQ
prompt "Minimum CPU frequency"
default EXAMPLE_MIN_CPU_FREQ_10M if !IDF_TARGET_ESP32H2
default EXAMPLE_MIN_CPU_FREQ_32M if IDF_TARGET_ESP32H2
depends on PM_ENABLE
help
Minimum CPU frequency to use for dynamic frequency scaling.
Should be set to XTAL frequency or XTAL frequency divided by integer.
config EXAMPLE_MIN_CPU_FREQ_40M
bool "40 MHz (use with 40MHz XTAL)"
depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO || !IDF_TARGET_ESP32
config EXAMPLE_MIN_CPU_FREQ_20M
bool "20 MHz (use with 40MHz XTAL)"
depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO || !IDF_TARGET_ESP32
config EXAMPLE_MIN_CPU_FREQ_10M
bool "10 MHz (use with 40MHz XTAL)"
depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO || !IDF_TARGET_ESP32
config EXAMPLE_MIN_CPU_FREQ_26M
bool "26 MHz (use with 26MHz XTAL)"
depends on XTAL_FREQ_26 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_26 || ESP32_XTAL_FREQ_AUTO
config EXAMPLE_MIN_CPU_FREQ_13M
bool "13 MHz (use with 26MHz XTAL)"
depends on XTAL_FREQ_26 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_26 || ESP32_XTAL_FREQ_AUTO
config EXAMPLE_MIN_CPU_FREQ_32M
bool "32 MHz (use with 32MHz XTAL)"
depends on IDF_TARGET_ESP32H2
depends on XTAL_FREQ_32 || XTAL_FREQ_AUTO
endchoice
config EXAMPLE_MIN_CPU_FREQ_MHZ
int
default 40 if EXAMPLE_MIN_CPU_FREQ_40M
default 20 if EXAMPLE_MIN_CPU_FREQ_20M
default 10 if EXAMPLE_MIN_CPU_FREQ_10M
default 26 if EXAMPLE_MIN_CPU_FREQ_26M
default 13 if EXAMPLE_MIN_CPU_FREQ_13M
default 32 if EXAMPLE_MIN_CPU_FREQ_32M
endmenu

View file

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

View file

@ -0,0 +1,125 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_pm.h"
#include "iot_button.h"
#include "esp_sleep.h"
#include "esp_idf_version.h"
/* 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

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

View file

@ -0,0 +1,11 @@
# Enable support for power management
CONFIG_PM_ENABLE=y
# Enable tickless idle mode
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
# Put related source code in IRAM
CONFIG_PM_SLP_IRAM_OPT=y
CONFIG_PM_RTOS_IDLE_OPT=y
# Use 1000Hz freertos tick to lower sleep time threshold
CONFIG_FREERTOS_HZ=1000
# For button power save
CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE=y

View file

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

View file

@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2022-2023 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
#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 {
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
*
* @param config pointer of configuration struct
*
* @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_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);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief gpio button configuration
*
*/
typedef struct {
int32_t gpio_num; /**< num of gpio */
uint8_t active_level; /**< gpio level when press down */
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
bool enable_power_save; /**< enable power save mode */
#endif
bool disable_pull; /**< disable internal pull or not */
} button_gpio_config_t;
/**
* @brief Initialize gpio button
*
* @param config pointer of configuration struct
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG Arguments is NULL.
*/
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);
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#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.
*
* Matrix Keyboard Layout (3x3):
* ----------------------------------------
* | Button 1 | Button 2 | Button 3 |
* | (R1-C1) | (R1-C2) | (R1-C3) |
* |--------------------------------------|
* | Button 4 | Button 5 | Button 6 |
* | (R2-C1) | (R2-C2) | (R2-C3) |
* |--------------------------------------|
* | Button 7 | Button 8 | Button 9 |
* | (R3-C1) | (R3-C2) | (R3-C3) |
* ----------------------------------------
*
* - Button matrix key is driven using row scanning.
* - Buttons within the same column cannot be detected simultaneously,
* but buttons within the same row can be detected without conflicts.
*/
typedef struct {
int32_t row_gpio_num; /**< GPIO number associated with the row */
int32_t col_gpio_num; /**< GPIO number associated with the column */
} button_matrix_config_t;
/**
* @brief Initialize a button matrix keyboard.
*
* @param config Pointer to the button matrix key configuration.
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG if the argument is NULL.
*
* @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.
*/
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);
#ifdef __cplusplus
}
#endif

View file

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

View file

@ -0,0 +1,821 @@
/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "esp_log.h"
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
#include "esp_pm.h"
#endif
#include "iot_button.h"
#include "sdkconfig.h"
static const char *TAG = "button";
static portMUX_TYPE s_button_lock = portMUX_INITIALIZER_UNLOCKED;
#define BUTTON_ENTER_CRITICAL() portENTER_CRITICAL(&s_button_lock)
#define BUTTON_EXIT_CRITICAL() portEXIT_CRITICAL(&s_button_lock)
#define BTN_CHECK(a, str, ret_val) \
if (!(a)) { \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
static const char *button_event_str[] = {
"BUTTON_PRESS_DOWN",
"BUTTON_PRESS_UP",
"BUTTON_PRESS_REPEAT",
"BUTTON_PRESS_REPEAT_DONE",
"BUTTON_SINGLE_CLICK",
"BUTTON_DOUBLE_CLICK",
"BUTTON_MULTIPLE_CLICK",
"BUTTON_LONG_PRESS_START",
"BUTTON_LONG_PRESS_HOLD",
"BUTTON_LONG_PRESS_UP",
"BUTTON_PRESS_END",
"BUTTON_EVENT_MAX",
"BUTTON_NONE_PRESS",
};
/**
* @brief Structs to store callback info
*
*/
typedef struct {
button_cb_t cb;
void *usr_data;
button_event_data_t event_data;
} 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;
} 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 TOLERANCE (CONFIG_BUTTON_PERIOD_TIME_MS*4)
#define CALL_EVENT_CB(ev) \
if (btn->cb_info[ev]) { \
for (int i = 0; i < btn->size[ev]; i++) { \
btn->cb_info[ev][i].cb(btn, btn->cb_info[ev][i].usr_data); \
} \
} \
#define TIME_TO_TICKS(time, congfig_time) (0 == (time))?congfig_time:(((time) / TICKS_INTERVAL))?((time) / TICKS_INTERVAL):1
/**
* @brief Button driver core function, driver state machine.
*/
static void button_handler(button_dev_t *btn)
{
uint8_t read_gpio_level = btn->hal_button_Level(btn->hardware_data);
/** ticks counter working.. */
if ((btn->state) > 0) {
btn->ticks++;
}
/**< button debounce handle */
if (read_gpio_level != btn->button_level) {
if (++(btn->debounce_cnt) >= DEBOUNCE_TICKS) {
btn->button_level = read_gpio_level;
btn->debounce_cnt = 0;
}
} else {
btn->debounce_cnt = 0;
}
/** State machine */
switch (btn->state) {
case 0:
if (btn->button_level == btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_DOWN;
CALL_EVENT_CB(BUTTON_PRESS_DOWN);
btn->ticks = 0;
btn->repeat = 1;
btn->state = 1;
} else {
btn->event = (uint8_t)BUTTON_NONE_PRESS;
}
break;
case 1:
if (btn->button_level != btn->active_level) {
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
btn->ticks = 0;
btn->state = 2;
} else if (btn->ticks >= btn->long_press_ticks) {
btn->event = (uint8_t)BUTTON_LONG_PRESS_START;
btn->state = 4;
/** Calling callbacks for BUTTON_LONG_PRESS_START */
uint32_t ticks_time = iot_button_get_ticks_time(btn);
int32_t diff = ticks_time - btn->long_press_ticks * TICKS_INTERVAL;
if (btn->cb_info[btn->event] && btn->count[0] == 0) {
if (abs(diff) <= TOLERANCE && btn->cb_info[btn->event][btn->count[0]].event_data.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);
}
}
}
break;
case 2:
if (btn->button_level == btn->active_level) {
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;
} else if (btn->ticks > btn->short_press_ticks) {
if (btn->repeat == 1) {
btn->event = (uint8_t)BUTTON_SINGLE_CLICK;
CALL_EVENT_CB(BUTTON_SINGLE_CLICK);
} else if (btn->repeat == 2) {
btn->event = (uint8_t)BUTTON_DOUBLE_CLICK;
CALL_EVENT_CB(BUTTON_DOUBLE_CLICK); // repeat hit
}
btn->event = (uint8_t)BUTTON_MULTIPLE_CLICK;
/** Calling the callbacks for MULTIPLE BUTTON CLICKS */
for (int i = 0; i < btn->size[btn->event]; i++) {
if (btn->repeat == btn->cb_info[btn->event][i].event_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);
}
}
btn->event = (uint8_t)BUTTON_PRESS_REPEAT_DONE;
CALL_EVENT_CB(BUTTON_PRESS_REPEAT_DONE); // repeat hit
btn->repeat = 0;
btn->state = 0;
btn->event = (uint8_t)BUTTON_PRESS_END;
CALL_EVENT_CB(BUTTON_PRESS_END);
}
break;
case 3:
if (btn->button_level != btn->active_level) {
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
} else {
btn->state = 0;
btn->event = (uint8_t)BUTTON_PRESS_END;
CALL_EVENT_CB(BUTTON_PRESS_END);
}
}
break;
case 4:
if (btn->button_level == btn->active_level) {
//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;
}
}
}
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);
}
}
/** 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;
}
}
}
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);
}
}
}
} else { //releasd
btn->event = BUTTON_LONG_PRESS_UP;
/** calling callbacks for BUTTON_LONG_PRESS_UP press_time */
if (btn->cb_info[btn->event] && btn->count[1] >= 0) {
button_cb_info_t *cb_info = btn->cb_info[btn->event];
do {
cb_info[btn->count[1]].cb(btn, cb_info[btn->count[1]].usr_data);
if (!btn->count[1]) {
break;
}
btn->count[1]--;
} while (cb_info[btn->count[1]].event_data.long_press.press_time == cb_info[btn->count[1] + 1].event_data.long_press.press_time);
/** Reset the counter */
btn->count[1] = -1;
}
/** Reset counter */
if (btn->cb_info[BUTTON_LONG_PRESS_START]) {
btn->count[0] = 0;
}
btn->event = (uint8_t)BUTTON_PRESS_UP;
CALL_EVENT_CB(BUTTON_PRESS_UP);
btn->state = 0; //reset
btn->long_press_hold_cnt = 0;
btn->event = (uint8_t)BUTTON_PRESS_END;
CALL_EVENT_CB(BUTTON_PRESS_END);
}
break;
}
}
static void button_cb(void *args)
{
button_dev_t *target;
/*!< When all buttons enter the BUTTON_NONE_PRESS state, the system enters low-power mode */
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
bool enter_power_save_flag = true;
#endif
for (target = g_head_handle; target; target = target->next) {
button_handler(target);
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
if (!(target->enable_power_save && target->debounce_cnt == 0 && target->event == BUTTON_NONE_PRESS)) {
enter_power_save_flag = false;
}
#endif
}
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
if (enter_power_save_flag) {
/*!< Stop esp timer for power save */
if (g_is_timer_running) {
esp_timer_stop(g_button_timer_handle);
g_is_timer_running = false;
}
for (target = g_head_handle; target; target = target->next) {
if (target->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);
}
}
/*!< Notify the user that the Button has entered power save mode by calling this callback function. */
if (power_save_usr_cfg.enter_power_save_cb) {
power_save_usr_cfg.enter_power_save_cb(power_save_usr_cfg.usr_data);
}
}
#endif
}
#if CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE
static void IRAM_ATTR button_power_save_isr_handler(void* arg)
{
if (!g_is_timer_running) {
esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U);
g_is_timer_running = true;
}
button_gpio_intr_control((int)arg, false);
/*!< 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);
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,
};
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;
}
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);
if (event == BUTTON_LONG_PRESS_START) {
btn->count[0] = 0;
} else if (event == BUTTON_LONG_PRESS_UP) {
btn->count[1] = -1;
}
} else {
button_cb_info_t *p = realloc(btn->cb_info[event], sizeof(button_cb_info_t) * (btn->size[event] + 1));
BTN_CHECK(NULL != p, "realloc cb_info failed", ESP_ERR_NO_MEM);
btn->cb_info[event] = p;
}
btn->cb_info[event][btn->size[event]].cb = cb;
btn->cb_info[event][btn->size[event]].usr_data = usr_data;
btn->size[event]++;
/** Inserting the event_data 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);
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) {
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].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].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;
}
int32_t press_ticks = press_time / TICKS_INTERVAL;
if (btn->short_press_ticks < press_ticks && press_ticks < btn->long_press_ticks) {
iot_button_set_param(btn, BUTTON_LONG_PRESS_TIME_MS, (void*)(intptr_t)press_time);
}
}
if (event == BUTTON_MULTIPLE_CLICK) {
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) {
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].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].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;
}
}
return ESP_OK;
}
esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event)
{
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);
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);
if (btn->cb_info[event]) {
free(btn->cb_info[event]);
/** Reset the counter */
if (event == BUTTON_LONG_PRESS_START) {
btn->count[0] = 0;
} else if (event == BUTTON_LONG_PRESS_UP) {
btn->count[1] = -1;
}
}
btn->cb_info[event] = NULL;
btn->size[event] = 0;
return ESP_OK;
}
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;
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_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;
}
}
BTN_CHECK(check != -1, "No such callback registered for the event", ESP_ERR_INVALID_STATE);
return ESP_OK;
}
size_t iot_button_count_cb(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
size_t ret = 0;
for (size_t i = 0; i < BUTTON_EVENT_MAX; i++) {
if (btn->cb_info[i]) {
ret += btn->size[i];
}
}
return ret;
}
size_t iot_button_count_event(button_handle_t btn_handle, button_event_t event)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
return btn->size[event];
}
button_event_t iot_button_get_event(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", BUTTON_NONE_PRESS);
button_dev_t *btn = (button_dev_t *) btn_handle;
return btn->event;
}
const char *iot_button_get_event_str(button_event_t event)
{
BTN_CHECK(event <= BUTTON_NONE_PRESS && event >= BUTTON_PRESS_DOWN, "event value is invalid", "invalid event");
return button_event_str[event];
}
esp_err_t iot_button_print_event(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_FAIL);
button_dev_t *btn = (button_dev_t *) btn_handle;
ESP_LOGI(TAG, "%s", button_event_str[btn->event]);
return ESP_OK;
}
uint8_t iot_button_get_repeat(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0);
button_dev_t *btn = (button_dev_t *) btn_handle;
return btn->repeat;
}
uint32_t iot_button_get_ticks_time(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0);
button_dev_t *btn = (button_dev_t *) btn_handle;
return (btn->ticks * TICKS_INTERVAL);
}
uint16_t iot_button_get_long_press_hold_cnt(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0);
button_dev_t *btn = (button_dev_t *) btn_handle;
return btn->long_press_hold_cnt;
}
esp_err_t iot_button_set_param(button_handle_t btn_handle, button_param_t param, void *value)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
button_dev_t *btn = (button_dev_t *) btn_handle;
BUTTON_ENTER_CRITICAL();
switch (param) {
case BUTTON_LONG_PRESS_TIME_MS:
btn->long_press_ticks = (int32_t)value / TICKS_INTERVAL;
break;
case BUTTON_SHORT_PRESS_TIME_MS:
btn->short_press_ticks = (int32_t)value / TICKS_INTERVAL;
break;
default:
break;
}
BUTTON_EXIT_CRITICAL();
return ESP_OK;
}
uint8_t iot_button_get_key_level(button_handle_t btn_handle)
{
BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0);
button_dev_t *btn = (button_dev_t *)btn_handle;
uint8_t level = btn->hal_button_Level(btn->hardware_data);
return (level == btn->active_level) ? 1 : 0;
}
esp_err_t iot_button_resume(void)
{
BTN_CHECK(g_button_timer_handle, "Button timer handle is invalid", ESP_ERR_INVALID_STATE);
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;
return ESP_OK;
}
esp_err_t iot_button_stop(void)
{
BTN_CHECK(g_button_timer_handle, "Button timer handle is invalid", ESP_ERR_INVALID_STATE);
BTN_CHECK(g_is_timer_running, "Button timer is not running", ESP_ERR_INVALID_STATE);
esp_err_t err = esp_timer_stop(g_button_timer_handle);
BTN_CHECK(ESP_OK == err, "Button timer stop failed", ESP_FAIL);
g_is_timer_running = false;
return ESP_OK;
}
#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);
BTN_CHECK(config->enter_power_save_cb, "Enter power save callback is invalid", ESP_ERR_INVALID_ARG);
power_save_usr_cfg.enter_power_save_cb = config->enter_power_save_cb;
power_save_usr_cfg.usr_data = config->usr_data;
return ESP_OK;
}
#endif

View file

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

View file

@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components"
"../../button")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(button_test)

View file

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

View file

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

View file

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

View file

@ -0,0 +1,9 @@
# For IDF 5.0
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT_EN=n
# For IDF4.4
CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP_TASK_WDT=n