diff --git a/components/touch_element/CMakeLists.txt b/components/touch_element/CMakeLists.txt new file mode 100644 index 0000000000..67c0316d4e --- /dev/null +++ b/components/touch_element/CMakeLists.txt @@ -0,0 +1,8 @@ +if(IDF_TARGET STREQUAL "esp32s2") + idf_component_register(SRCS "touch_element.c" + "touch_button.c" + "touch_slider.c" + "touch_matrix.c" + INCLUDE_DIRS include + REQUIRES driver) +endif() diff --git a/components/touch_element/include/touch_element/touch_button.h b/components/touch_element/include/touch_element/touch_button.h new file mode 100644 index 0000000000..c1543a7bfb --- /dev/null +++ b/components/touch_element/include/touch_element/touch_button.h @@ -0,0 +1,203 @@ +// Copyright 2016-2020 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include "touch_element/touch_element.h" + +#ifdef __cplusplus +extern "C" { +#endif +/* --------------------------------- General button instance default configuration --------------------------------- */ +#define TOUCH_BUTTON_GLOBAL_DEFAULT_CONFIG() \ +{ \ + .threshold_divider = 0.8, \ + .default_lp_time = 1000 \ +} +/* ------------------------------------------------------------------------------------------------------------------ */ + +/** + * @brief Button initialization configuration passed to touch_button_install + */ +typedef struct { + float threshold_divider; //!< Button channel threshold divider + uint32_t default_lp_time; //!< Button default LongPress event time (ms) +} touch_button_global_config_t; + +/** + * @brief Button configuration (for new instance) passed to touch_button_create() + */ +typedef struct { + touch_pad_t channel_num; //!< Button channel number (index) + float channel_sens; //!< Button channel sensitivity +} touch_button_config_t; + +/** + * @brief Button event type + */ +typedef enum { + TOUCH_BUTTON_EVT_ON_PRESS, //!< Button Press event + TOUCH_BUTTON_EVT_ON_RELEASE, //!< Button Release event + TOUCH_BUTTON_EVT_ON_LONGPRESS, //!< Button LongPress event + TOUCH_BUTTON_EVT_MAX +} touch_button_event_t; + +/** + * @brief Button message type + */ +typedef struct { + touch_button_event_t event; //!< Button event +} touch_button_message_t; + +typedef touch_elem_handle_t touch_button_handle_t; //!< Button handle +typedef void(*touch_button_callback_t)(touch_button_handle_t, touch_button_message_t, void *); //!< Button callback type + +/** + * @brief Touch Button initialize + * + * This function initializes touch button global and acts on all + * touch button instances. + * + * @param[in] global_config Button object initialization configuration + * + * @return + * - ESP_OK: Successfully initialized touch button + * - ESP_ERR_INVALID_STATE: Touch element library was not initialized + * - ESP_ERR_INVALID_ARG: button_init is NULL + * - ESP_ERR_NO_MEM: Insufficient memory + */ +esp_err_t touch_button_install(const touch_button_global_config_t *global_config); + +/** + * @brief Release resources allocated using touch_button_install() + */ +void touch_button_uninstall(void); + +/** + * @brief Create a new touch button instance + * + * @param[in] button_config Button configuration + * @param[out] button_handle Button handle + * + * @note The sensitivity has to be explored in experiments, + * Sensitivity = (Raw(touch) - Raw(release)) / Raw(release) * 100% + * + * @return + * - ESP_OK: Successfully create touch button + * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized + * - ESP_ERR_NO_MEM: Insufficient memory + * - ESP_ERR_INVALID_ARG: Invalid configuration struct or arguments is NULL + */ +esp_err_t touch_button_create(const touch_button_config_t *button_config, touch_button_handle_t *button_handle); + +/** + * @brief Release resources allocated using touch_button_create() + * + * @param[in] button_handle Button handle + * @return + * - ESP_OK: Successfully released resources + * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized + * - ESP_ERR_INVALID_ARG: button_handle is null + * - ESP_ERR_NOT_FOUND: Input handle is not a button handle + */ +esp_err_t touch_button_delete(touch_button_handle_t button_handle); + +/** + * @brief Touch button subscribes event + * + * This function uses event mask to subscribe to touch button events, once one of + * the subscribed events occurs, the event message could be retrieved by calling + * touch_element_message_receive() or input callback routine. + * + * @param[in] button_handle Button handle + * @param[in] event_mask Button subscription event mask + * @param[in] arg User input argument + * + * @note Touch button only support three kind of event masks, they are + * TOUCH_ELEM_EVENT_ON_PRESS, TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_LONGPRESS. + * You can use those event masks in any combination to achieve the desired effect. + * + * @return + * - ESP_OK: Successfully subscribed touch button event + * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized + * - ESP_ERR_INVALID_ARG: button_handle is null or event is not supported + */ +esp_err_t touch_button_subscribe_event(touch_button_handle_t button_handle, uint32_t event_mask, void *arg); + +/** + * @brief Touch button set dispatch method + * + * This function sets a dispatch method that the driver core will use + * this method as the event notification method. + * + * @param[in] button_handle Button handle + * @param[in] dispatch_method Dispatch method (By callback/event) + * + * @return + * - ESP_OK: Successfully set dispatch method + * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized + * - ESP_ERR_INVALID_ARG: button_handle is null or dispatch_method is invalid + */ +esp_err_t touch_button_set_dispatch_method(touch_button_handle_t button_handle, touch_elem_dispatch_t dispatch_method); + +/** + * @brief Touch button set callback + * + * This function sets a callback routine into touch element driver core, + * when the subscribed events occur, the callback routine will be called. + * + * @param[in] button_handle Button handle + * @param[in] button_callback User input callback + * + * @warning Since this input callback routine runs on driver core (esp-timer callback routine), + * it should not do something that attempts to Block, such as calling vTaskDelay(). + * + * @return + * - ESP_OK: Successfully set callback + * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized + * - ESP_ERR_INVALID_ARG: button_handle or button_callback is null + */ +esp_err_t touch_button_set_callback(touch_button_handle_t button_handle, touch_button_callback_t button_callback); + +/** + * @brief Touch button set long press trigger time + * + * This function sets the threshold time (ms) for a long press event. If a button is pressed + * and held for a period of time that exceeds the threshold time, a long press event is triggered. + * + * @param[in] button_handle Button handle + * @param[in] threshold_time Threshold time (ms) of long press event occur + * + * @return + * - ESP_OK: Successfully set the threshold time of long press event + * - ESP_ERR_INVALID_STATE: Touch button driver was not initialized + * - ESP_ERR_INVALID_ARG: button_handle is null or time (ms) is not lager than 0 + */ +esp_err_t touch_button_set_longpress(touch_button_handle_t button_handle, uint32_t threshold_time); + +/** + * @brief Touch button get message + * + * This function decodes the element message from touch_element_message_receive() and return + * a button message pointer. + * + * @param[in] element_message element message + * + * @return Touch button message pointer + */ +const touch_button_message_t* touch_button_get_message(const touch_elem_message_t* element_message); + +#ifdef __cplusplus +} +#endif diff --git a/components/touch_element/include/touch_element/touch_element.h b/components/touch_element/include/touch_element/touch_element.h new file mode 100644 index 0000000000..a403bdf286 --- /dev/null +++ b/components/touch_element/include/touch_element/touch_element.h @@ -0,0 +1,273 @@ +// Copyright 2016-2020 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include "driver/touch_sensor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* -------------------------------- General hardware & system default configuration -------------------------------- */ +/* Since those are important hardware and algorithm parameters, user should not change them before knowing all details*/ +/* ------------------------------------------------------------------------------------------------------------------ */ +#define TOUCH_ELEM_GLOBAL_DEFAULT_CONFIG() \ +{ \ + .hardware = { \ + .upper_voltage = TOUCH_HVOLT_2V7, \ + .voltage_attenuation = TOUCH_HVOLT_ATTEN_0V5, \ + .lower_voltage = TOUCH_LVOLT_0V5, \ + .suspend_channel_polarity = TOUCH_PAD_CONN_HIGHZ, \ + .denoise_level = TOUCH_PAD_DENOISE_BIT4, \ + .denoise_equivalent_cap = TOUCH_PAD_DENOISE_CAP_L0, \ + .smooth_filter_mode = TOUCH_PAD_SMOOTH_IIR_2, \ + .benchmark_filter_mode = TOUCH_PAD_FILTER_IIR_16, \ + .sample_count = 500, \ + .sleep_cycle = 0xf, \ + .benchmark_debounce_count = 2, \ + .benchmark_calibration_threshold = 2, \ + .benchmark_jitter_step = 5 \ + }, \ + .software = { \ + .waterproof_threshold_divider = 0.8, \ + .processing_period = 10, \ + .intr_message_size = 14, \ + .event_message_size = 20 \ + } \ +} +/* ------------------------------------------------------------------------------------------------------------------ */ + +/* ---------------------------------------------- Event subscription ----------------------------------------------- */ +#define TOUCH_ELEM_EVENT_NONE BIT(0) //!< None event +#define TOUCH_ELEM_EVENT_ON_PRESS BIT(1) //!< On Press event +#define TOUCH_ELEM_EVENT_ON_RELEASE BIT(2) //!< On Release event +#define TOUCH_ELEM_EVENT_ON_LONGPRESS BIT(3) //!< On LongPress event +#define TOUCH_ELEM_EVENT_ON_CALCULATION BIT(4) //!< On Calculation event +/* ------------------------------------------------------------------------------------------------------------------ */ +#define TOUCH_WATERPROOF_GUARD_NOUSE (0) //!< Waterproof no use guard sensor +/* -------------------------------- Global hardware & software configuration struct --------------------------------- */ +/** + * @brief Touch element software configuration + */ +typedef struct { + float waterproof_threshold_divider; //!< Waterproof guard channel threshold divider + uint8_t processing_period; //!< Processing period(ms) + uint8_t intr_message_size; //!< Interrupt message queue size + uint8_t event_message_size; //!< Event message queue size +} touch_elem_sw_config_t; + +/** + * @brief Touch element hardware configuration + */ +typedef struct { + touch_high_volt_t upper_voltage; //!< Touch sensor channel upper charge voltage + touch_volt_atten_t voltage_attenuation; //!< Touch sensor channel upper charge voltage attenuation (Diff voltage is upper - attenuation - lower) + touch_low_volt_t lower_voltage; //!< Touch sensor channel lower charge voltage + touch_pad_conn_type_t suspend_channel_polarity; //!< Suspend channel polarity (High Impedance State or GND) + touch_pad_denoise_grade_t denoise_level; //!< Internal de-noise level + touch_pad_denoise_cap_t denoise_equivalent_cap; //!< Internal de-noise channel (Touch channel 0) equivalent capacitance + touch_smooth_mode_t smooth_filter_mode; //!< Smooth value filter mode (This only apply to touch_pad_filter_read_smooth()) + touch_filter_mode_t benchmark_filter_mode; //!< Benchmark filter mode + uint16_t sample_count; //!< The count of sample in each measurement of touch sensor + uint16_t sleep_cycle; //!< The cycle (RTC slow clock) of sleep + uint8_t benchmark_debounce_count; //!< Benchmark debounce count + uint8_t benchmark_calibration_threshold; //!< Benchmark calibration threshold + uint8_t benchmark_jitter_step; //!< Benchmark jitter filter step (This only works at while benchmark filter mode is jitter filter) +} touch_elem_hw_config_t; + +/** + * @brief Touch element global configuration passed to touch_element_install + */ +typedef struct { + touch_elem_hw_config_t hardware; //!< Hardware configuration + touch_elem_sw_config_t software; //!< Software configuration +} touch_elem_global_config_t; + +/** + * @brief Touch element waterproof configuration passed to touch_element_waterproof_install + */ +typedef struct { + touch_pad_t guard_channel; //!< Waterproof Guard-Sensor channel number (index) + float guard_sensitivity; //!< Waterproof Guard-Sensor sensitivity +} touch_elem_waterproof_config_t; +/* ------------------------------------------------------------------------------------------------------------------ */ +typedef void *touch_elem_handle_t; //!< Touch element handle type +typedef uint32_t touch_elem_event_t; //!< Touch element event type + +/** + * @brief Touch element handle type + */ +typedef enum { + TOUCH_ELEM_TYPE_BUTTON, //!< Touch element button + TOUCH_ELEM_TYPE_SLIDER, //!< Touch element slider + TOUCH_ELEM_TYPE_MATRIX, //!< Touch element matrix button +} touch_elem_type_t; + +/** + * @brief Touch element event dispatch methods (event queue/callback) + */ +typedef enum { + TOUCH_ELEM_DISP_EVENT, //!< Event queue dispatch + TOUCH_ELEM_DISP_CALLBACK, //!< Callback dispatch + TOUCH_ELEM_DISP_MAX +} touch_elem_dispatch_t; + +/** + * @brief Touch element event message type from touch_element_message_receive() + */ +typedef struct { + touch_elem_handle_t handle; //!< Touch element handle + touch_elem_type_t element_type; //!< Touch element type + void *arg; //!< User input argument + uint8_t child_msg[8]; //!< Encoded message +} touch_elem_message_t; +/* ------------------------------------------------------------------------------------------------------------------ */ + +/** + * @brief Touch element processing initialization + * + * @param[in] global_config Global initialization configuration structure + * + * @note To reinitialize the touch element object, call touch_element_uninstall() first + * + * @return + * - ESP_OK: Successfully initialized + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_NO_MEM: Insufficient memory + * - ESP_ERR_INVALID_STATE: Touch element is already initialized + * - Others: Unknown touch driver layer or lower layer error + */ +esp_err_t touch_element_install(const touch_elem_global_config_t *global_config); + +/** + * @brief Touch element processing start + * + * This function starts the touch element processing system + * + * @note This function must only be called after all the touch element instances finished creating + * + * @return + * - ESP_OK: Successfully started to process + * - Others: Unknown touch driver layer or lower layer error + */ +esp_err_t touch_element_start(void); + +/** + * @brief Touch element processing stop + * + * This function stops the touch element processing system + * + * @note This function must be called before changing the system (hardware, software) parameters + * + * @return + * - ESP_OK: Successfully stopped to process + * - Others: Unknown touch driver layer or lower layer error + */ +esp_err_t touch_element_stop(void); + +/** + * @brief Release resources allocated using touch_element_install + * + * @return + * - ESP_OK: Successfully released touch element object + * - ESP_ERR_INVALID_STATE: Touch element object is not initialized + * - Others: Unknown touch driver layer or lower layer error + */ +void touch_element_uninstall(void); + +/** + * @brief Get current event message of touch element instance + * + * This function will receive the touch element message (handle, event type, etc...) + * from te_event_give(). It will block until a touch element event or a timeout occurs. + * + * @param[out] element_message Touch element event message structure + * @param[in] ticks_to_wait Number of FreeRTOS ticks to block for waiting event + * @return + * - ESP_OK: Successfully received touch element event + * - ESP_ERR_INVALID_STATE: Touch element library is not initialized + * - ESP_ERR_INVALID_ARG: element_message is null + * - ESP_ERR_TIMEOUT: Timed out waiting for event + */ +esp_err_t touch_element_message_receive(touch_elem_message_t *element_message, uint32_t ticks_to_wait); + +/** + * @brief Touch element waterproof initialization + * + * This function enables the hardware waterproof, then touch element system uses Shield-Sensor + * and Guard-Sensor to mitigate the influence of water-drop and water-stream. + * + * @param[in] waterproof_config Waterproof configuration + * + * @note If the waterproof function is used, Shield-Sensor can not be disabled and it will use channel 14 as + * it's internal channel. Hence, the user can not use channel 14 for another propose. And the Guard-Sensor + * is not necessary since it is optional. + * + * @note Shield-Sensor: It always uses channel 14 as the shield channel, so user must connect + * the channel 14 and Shield-Layer in PCB since it will generate a synchronous signal automatically + * + * @note Guard-Sensor: This function is optional. If used, the user must connect the guard channel and Guard-Ring + * in PCB. Any channels user wants to protect should be added into Guard-Ring in PCB. + * + * @return + * - ESP_OK: Successfully initialized + * - ESP_ERR_INVALID_STATE: Touch element library is not initialized + * - ESP_ERR_INVALID_ARG: waterproof_config is null or invalid Guard-Sensor channel + * - ESP_ERR_NO_MEM: Insufficient memory + */ +esp_err_t touch_element_waterproof_install(const touch_elem_waterproof_config_t *waterproof_config); + +/** + * @brief Release resources allocated using touch_element_waterproof_install() + */ +void touch_element_waterproof_uninstall(void); + +/** + * @brief Add a masked handle to protect while Guard-Sensor has been triggered + * + * This function will add an application handle (button, slider, etc...) as a masked handle. While Guard-Sensor + * has been triggered, waterproof function will start working and lock the application internal state. While the + * influence of water is reduced, the application will be unlock and reset into IDLE state. + * + * @param[in] element_handle Touch element instance handle + * + * @note The waterproof protection logic must follow the real circuit in PCB, it means that all of the channels + * inside the input handle must be inside the Guard-Ring in real circuit. + * + * @return + * - ESP_OK: Successfully added a masked handle + * - ESP_ERR_INVALID_STATE: Waterproof is not initialized + * - ESP_ERR_INVALID_ARG: element_handle is null + */ +esp_err_t touch_element_waterproof_add(touch_elem_handle_t element_handle); + +/** + * @brief Remove a masked handle to protect + * + * This function will remove an application handle from masked handle table. + * + * @param[in] element_handle Touch element instance handle + * + * @return + * - ESP_OK: Successfully removed a masked handle + * - ESP_ERR_INVALID_STATE: Waterproof is not initialized + * - ESP_ERR_INVALID_ARG: element_handle is null + * - ESP_ERR_NOT_FOUND: Failed to search element_handle from waterproof mask_handle list + */ +esp_err_t touch_element_waterproof_remove(touch_elem_handle_t element_handle); + +#ifdef __cplusplus +} +#endif diff --git a/components/touch_element/include/touch_element/touch_element_private.h b/components/touch_element/include/touch_element/touch_element_private.h new file mode 100644 index 0000000000..1d5f837e58 --- /dev/null +++ b/components/touch_element/include/touch_element/touch_element_private.h @@ -0,0 +1,185 @@ +// Copyright 2016-2020 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include "touch_element/touch_element.h" +#include "touch_element/touch_button.h" +#include "touch_element/touch_slider.h" +#include "touch_element/touch_matrix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define TE_TAG "Touch Element" +#define TE_DEBUG_TAG "Touch Element Debug" +#define TE_UNUSED(arg) (void)arg + +#define TE_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + ESP_LOGE(TE_TAG, "%s:%d (%s)", __FILE__, __LINE__, __FUNCTION__); \ + return (ret_val); \ + } \ +}) + +#define TE_CHECK_GOTO(cond, label) ({ \ + if (!(cond)) { \ + goto label; \ + } \ +}) + +#define TE_FREE_AND_NULL(ptr) ({ \ + free(ptr); \ + (ptr) = NULL; \ +}) + +#define TE_DEFAULT_THRESHOLD_DIVIDER(obj) ((obj)->global_config->threshold_divider) +#define TE_DEFAULT_LONGPRESS_TIME(obj) ((obj)->global_config->default_lp_time) + +typedef enum { + TE_STATE_IDLE = 0, + TE_STATE_PRESS, + TE_STATE_RELEASE, +} te_state_t; + +typedef te_state_t te_dev_state_t; +typedef touch_elem_type_t te_dev_type_t; + +typedef struct { + float sens; //!< Touch channel sensitivity + touch_pad_t channel; //!< Touch channel number(index) + te_dev_type_t type; //!< Touch channel type TODO: need to refactor as te_class_type_t + te_dev_state_t state; //!< Touch channel current state +} te_dev_t; + +typedef enum { + TE_CLS_TYPE_BUTTON = 0, + TE_CLS_TYPE_SLIDER, + TE_CLS_TYPE_MATRIX, + TE_CLS_TYPE_MAX //TODO: add waterproof class +} te_class_type_t; + +typedef struct { + touch_elem_handle_t handle; + bool (*check_channel) (touch_pad_t); + esp_err_t (*set_threshold) (void); + void (*process_state) (void); + void (*update_state) (touch_pad_t, te_state_t); +} te_object_methods_t; + +/* -------------------------------------------- Waterproof basic type --------------------------------------------- */ +struct te_waterproof_s { + te_dev_t *guard_device; //Waterproof guard channel device + touch_elem_handle_t *mask_handle; //Waterproof masked handle array + touch_pad_t shield_channel; //Waterproof shield channel + bool is_shield_level_set; //Waterproof shield level setting bit +}; +typedef struct te_waterproof_s* te_waterproof_handle_t; +/* -------------------------------------------- Button basic type --------------------------------------------- */ +typedef struct { + touch_elem_dispatch_t dispatch_method; //Button dispatch method + touch_button_callback_t callback; //Button callback routine + uint32_t event_mask; //Button subscribed event mask + void *arg; //User input argument +} te_button_handle_config_t; + +typedef te_state_t te_button_state_t; //TODO: add Long Press state + +struct te_button_s { + te_button_handle_config_t *config; //Button configuration + te_dev_t *device; //Base device information + te_button_state_t current_state; //Button current state + te_button_state_t last_state; //Button last state + touch_button_event_t event; //Button outside state(for application layer) + uint32_t trigger_cnt; //Button long time trigger counter + uint32_t trigger_thr; //Button long time trigger counter threshold +}; + +typedef struct te_button_s* te_button_handle_t; +/* -------------------------------------------- Slider basic type --------------------------------------------- */ +typedef struct { + touch_elem_dispatch_t dispatch_method; //Slider dispatch method + touch_slider_callback_t callback; //Slider callback routine + uint32_t event_mask; //Slider subscribed event mask + void *arg; //User input argument +} te_slider_handle_config_t; + +typedef te_state_t te_slider_state_t; + +struct te_slider_s { + te_slider_handle_config_t *config; //Slider configuration + te_dev_t **device; //Base device information set + te_slider_state_t current_state; //Slider current state + te_slider_state_t last_state; //Slider last state + touch_slider_event_t event; //Slider outside state(for application layer) + float position_scale; //Slider position scale(step size) + float *quantify_signal_array; //Slider re-quantization array + uint32_t *channel_bcm; //Channel benchmark array + uint32_t channel_bcm_update_cnt; //Channel benchmark update counter + uint32_t filter_reset_cnt; //Slider reset counter + uint32_t last_position; //Slider last position + touch_slider_position_t position; //Slider position + uint8_t position_range; //Slider position range([0, position_range]) + uint8_t channel_sum; //Slider channel sum + uint8_t *pos_filter_window; //Slider position moving average filter window + uint8_t pos_window_idx; //Slider position moving average filter window index + bool is_first_sample; //Slider first time sample record bit +}; + +typedef struct te_slider_s* te_slider_handle_t; +/* -------------------------------------------- Matrix basic type --------------------------------------------- */ +typedef struct { + touch_elem_dispatch_t dispatch_method; //Matrix button dispatch method + touch_matrix_callback_t callback; //Matrix button callback routine + uint32_t event_mask; //Matrix button subscribed event mask + void *arg; //User input argument +} te_matrix_handle_config_t; + +typedef te_state_t te_matrix_state_t; //TODO: add Long Press state + +struct te_matrix_s { + te_matrix_handle_config_t *config; //Matrix button configuration + te_dev_t **device; //Base device information + te_matrix_state_t current_state; //Matrix button current state + te_matrix_state_t last_state; //Matrix button current state + touch_matrix_event_t event; //Matrix button outside state(for application layer) + uint32_t trigger_cnt; //Matrix button long time trigger counter + uint32_t trigger_thr; //Matrix button long time trigger counter threshold + touch_matrix_position_t position; //Matrix button position + uint8_t x_channel_num; //The number of touch sensor channel in x axis + uint8_t y_channel_num; //The number of touch sensor channel in y axis +}; + +typedef struct te_matrix_s* te_matrix_handle_t; +/* ------------------------------------------------------------------------------------------------------------------ */ + +/* --------------------------------------------- Global system methods ---------------------------------------------- */ +uint32_t te_read_smooth_signal(touch_pad_t channel_num); +bool te_system_check_state(void); +//TODO: Refactor this function with function overload +esp_err_t te_dev_init(te_dev_t **device, uint8_t device_num, te_dev_type_t type, const touch_pad_t *channel, const float *sens, float divider); +void te_dev_deinit(te_dev_t **device, uint8_t device_num); +esp_err_t te_dev_set_threshold(te_dev_t *device); +esp_err_t te_event_give(touch_elem_message_t te_message); +uint8_t te_get_timer_period(void); +void te_object_method_register(te_object_methods_t *object_methods, te_class_type_t object_type); +void te_object_method_unregister(te_class_type_t object_type); +bool te_object_check_channel(const touch_pad_t *channel_array, uint8_t channel_sum); +bool waterproof_check_mask_handle(touch_elem_handle_t te_handle); +/* ------------------------------------------------------------------------------------------------------------------ */ + +#ifdef __cplusplus +} +#endif diff --git a/components/touch_element/include/touch_element/touch_matrix.h b/components/touch_element/include/touch_element/touch_matrix.h new file mode 100644 index 0000000000..70173a0236 --- /dev/null +++ b/components/touch_element/include/touch_element/touch_matrix.h @@ -0,0 +1,221 @@ +// Copyright 2016-2020 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include "touch_element/touch_element.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------- General matrix button instance default configuration ------------------------------ */ +#define TOUCH_MATRIX_GLOBAL_DEFAULT_CONFIG() \ +{ \ + .threshold_divider = 0.8, \ + .default_lp_time = 1000 \ +} +/* ------------------------------------------------------------------------------------------------------------------ */ +/** + * @brief Matrix button initialization configuration passed to touch_matrix_install + */ +typedef struct { + float threshold_divider; //!< Matrix button channel threshold divider + uint32_t default_lp_time; //!< Matrix button default LongPress event time (ms) +} touch_matrix_global_config_t; + +/** + * @brief Matrix button configuration (for new instance) passed to touch_matrix_create() + */ +typedef struct { + const touch_pad_t *x_channel_array; //!< Matrix button x-axis channels array + const touch_pad_t *y_channel_array; //!< Matrix button y-axis channels array + const float *x_sensitivity_array; //!< Matrix button x-axis channels sensitivity array + const float *y_sensitivity_array; //!< Matrix button y-axis channels sensitivity array + uint8_t x_channel_num; //!< The number of channels in x-axis + uint8_t y_channel_num; //!< The number of channels in y-axis +} touch_matrix_config_t; + +/** + * @brief Matrix button event type + */ +typedef enum { + TOUCH_MATRIX_EVT_ON_PRESS, //!< Matrix button Press event + TOUCH_MATRIX_EVT_ON_RELEASE, //!< Matrix button Press event + TOUCH_MATRIX_EVT_ON_LONGPRESS, //!< Matrix button LongPress event + TOUCH_MATRIX_EVT_MAX +} touch_matrix_event_t; + +/** + * @brief Matrix button position data type + */ +typedef struct { + uint8_t x_axis; //!< Matrix button x axis position + uint8_t y_axis; //!< Matrix button y axis position + uint8_t index; //!< Matrix button position index +} touch_matrix_position_t; + +/** + * @brief Matrix message type + */ +typedef struct { + touch_matrix_event_t event; //!< Matrix event + touch_matrix_position_t position; //!< Matrix position +} touch_matrix_message_t; + +typedef touch_elem_handle_t touch_matrix_handle_t; //!< Matrix button instance handle +typedef void(*touch_matrix_callback_t)(touch_matrix_handle_t, touch_matrix_message_t, void *); //!< Matrix button callback type + +/** + * @brief Touch matrix button initialize + * + * This function initializes touch matrix button object and acts on all + * touch matrix button instances. + * + * @param[in] global_config Touch matrix global initialization configuration + * + * @return + * - ESP_OK: Successfully initialized touch matrix button + * - ESP_ERR_INVALID_STATE: Touch element library was not initialized + * - ESP_ERR_INVALID_ARG: matrix_init is NULL + * - ESP_ERR_NO_MEM: Insufficient memory + */ +esp_err_t touch_matrix_install(const touch_matrix_global_config_t *global_config); + +/** + * @brief Release resources allocated using touch_matrix_install() + * + * @return + * - ESP_OK: Successfully released resources + */ +void touch_matrix_uninstall(void); + +/** + * @brief Create a new touch matrix button instance + * + * @param[in] matrix_config Matrix button configuration + * @param[out] matrix_handle Matrix button handle + * + * @note Channel array and sensitivity array must be one-one correspondence in those array + * + * @note Touch matrix button does not support Multi-Touch now + * + * @return + * - ESP_OK: Successfully create touch matrix button + * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized + * - ESP_ERR_INVALID_ARG: Invalid configuration struct or arguments is NULL + * - ESP_ERR_NO_MEM: Insufficient memory + */ +esp_err_t touch_matrix_create(const touch_matrix_config_t *matrix_config, touch_matrix_handle_t *matrix_handle); + +/** + * @brief Release resources allocated using touch_matrix_create() + * + * @param[in] matrix_handle Matrix handle + * @return + * - ESP_OK: Successfully released resources + * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized + * - ESP_ERR_INVALID_ARG: matrix_handle is null + * - ESP_ERR_NOT_FOUND: Input handle is not a matrix handle + */ +esp_err_t touch_matrix_delete(touch_matrix_handle_t matrix_handle); + +/** + * @brief Touch matrix button subscribes event + * + * This function uses event mask to subscribe to touch matrix events, once one of + * the subscribed events occurs, the event message could be retrieved by calling + * touch_element_message_receive() or input callback routine. + * + * @param[in] matrix_handle Matrix handle + * @param[in] event_mask Matrix subscription event mask + * @param[in] arg User input argument + * + * @note Touch matrix button only support three kind of event masks, they are + * TOUCH_ELEM_EVENT_ON_PRESS, TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_LONGPRESS. You can use those event + * masks in any combination to achieve the desired effect. + * + * @return + * - ESP_OK: Successfully subscribed touch matrix event + * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized + * - ESP_ERR_INVALID_ARG: matrix_handle is null or event is not supported + */ +esp_err_t touch_matrix_subscribe_event(touch_matrix_handle_t matrix_handle, uint32_t event_mask, void *arg); + +/** + * @brief Touch matrix button set dispatch method + * + * This function sets a dispatch method that the driver core will use + * this method as the event notification method. + * + * @param[in] matrix_handle Matrix button handle + * @param[in] dispatch_method Dispatch method (By callback/event) + * + * @return + * - ESP_OK: Successfully set dispatch method + * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized + * - ESP_ERR_INVALID_ARG: matrix_handle is null or dispatch_method is invalid + */ +esp_err_t touch_matrix_set_dispatch_method(touch_matrix_handle_t matrix_handle, touch_elem_dispatch_t dispatch_method); + +/** + * @brief Touch matrix button set callback + * + * This function sets a callback routine into touch element driver core, + * when the subscribed events occur, the callback routine will be called. + * + * @param[in] matrix_handle Matrix button handle + * @param[in] matrix_callback User input callback + * + * @warning Since this input callback routine runs on driver core (esp-timer callback routine), + * it should not do something that attempts to Block, such as calling vTaskDelay(). + * + * @return + * - ESP_OK: Successfully set callback + * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized + * - ESP_ERR_INVALID_ARG: matrix_handle or matrix_callback is null + */ +esp_err_t touch_matrix_set_callback(touch_matrix_handle_t matrix_handle, touch_matrix_callback_t matrix_callback); + +/** + * @brief Touch matrix button set long press trigger time + * + * This function sets the threshold time (ms) for a long press event. If a matrix button is pressed + * and held for a period of time that exceeds the threshold time, a long press event is triggered. + * + * @param[in] matrix_handle Matrix button handle + * @param[in] threshold_time Threshold time (ms) of long press event occur + * + * @return + * - ESP_OK: Successfully set the time of long press event + * - ESP_ERR_INVALID_STATE: Touch matrix driver was not initialized + * - ESP_ERR_INVALID_ARG: matrix_handle is null or time (ms) is 0 + */ +esp_err_t touch_matrix_set_longpress(touch_matrix_handle_t matrix_handle, uint32_t threshold_time); + +/** + * @brief Touch matrix get message + * + * This function decodes the element message from touch_element_message_receive() and return + * a matrix message pointer. + * + * @param[in] element_message element message + * + * @return Touch matrix message pointer + */ +const touch_matrix_message_t* touch_matrix_get_message(const touch_elem_message_t* element_message); + +#ifdef __cplusplus +} +#endif diff --git a/components/touch_element/include/touch_element/touch_slider.h b/components/touch_element/include/touch_element/touch_slider.h new file mode 100644 index 0000000000..a10589c77f --- /dev/null +++ b/components/touch_element/include/touch_element/touch_slider.h @@ -0,0 +1,205 @@ +// Copyright 2016-2020 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#pragma once + +#include "touch_element/touch_element.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* --------------------------------- General slider instance default configuration --------------------------------- */ +#define TOUCH_SLIDER_GLOBAL_DEFAULT_CONFIG() \ +{ \ + .quantify_lower_threshold = 0.3, \ + .threshold_divider = 0.8, \ + .filter_reset_time = 50, \ + .benchmark_update_time = 500, \ + .position_filter_size = 10, \ + .position_filter_factor = 2, \ + .calculate_channel_count = 3 \ +} +/* ------------------------------------------------------------------------------------------------------------------ */ + +/** + * @brief Slider initialization configuration passed to touch_slider_install + */ +typedef struct { + float quantify_lower_threshold; //!< Slider signal quantification threshold + float threshold_divider; //!< Slider channel threshold divider + uint16_t filter_reset_time; //!< Slider position filter reset time (Unit is esp_timer callback tick) + uint16_t benchmark_update_time; //!< Slider benchmark update time (Unit is esp_timer callback tick) + uint8_t position_filter_size; //!< Moving window filter buffer size + uint8_t position_filter_factor; //!< One-order IIR filter factor + uint8_t calculate_channel_count; //!< The number of channels which will take part in calculation +} touch_slider_global_config_t; + +/** + * @brief Slider configuration (for new instance) passed to touch_slider_create() + */ +typedef struct { + const touch_pad_t *channel_array; //!< Slider channel array + const float *sensitivity_array; //!< Slider channel sensitivity array + uint8_t channel_num; //!< The number of slider channels + uint8_t position_range; //!< The right region of touch slider position range, [0, position_range (less than or equal to 255)] +} touch_slider_config_t; + +/** + * @brief Slider event type + */ +typedef enum { + TOUCH_SLIDER_EVT_ON_PRESS, //!< Slider on Press event + TOUCH_SLIDER_EVT_ON_RELEASE, //!< Slider on Release event + TOUCH_SLIDER_EVT_ON_CALCULATION, //!< Slider on Calculation event + TOUCH_SLIDER_EVT_MAX +} touch_slider_event_t; + +typedef uint32_t touch_slider_position_t; //!< Slider position data type + +/** + * @brief Slider message type + */ +typedef struct { + touch_slider_event_t event; //!< Slider event + touch_slider_position_t position; //!< Slider position +} touch_slider_message_t; + +typedef touch_elem_handle_t touch_slider_handle_t; //!< Slider instance handle +typedef void(*touch_slider_callback_t)(touch_slider_handle_t, touch_slider_message_t, void *); //!< Slider callback type + +/** + * @brief Touch slider initialize + * + * This function initializes touch slider object and acts on all + * touch slider instances. + * + * @param[in] global_config Touch slider global initialization configuration + * + * @return + * - ESP_OK: Successfully initialized touch slider + * - ESP_ERR_INVALID_STATE: Touch element library was not initialized + * - ESP_ERR_INVALID_ARG: slider_init is NULL + * - ESP_ERR_NO_MEM: Insufficient memory + */ +esp_err_t touch_slider_install(const touch_slider_global_config_t *global_config); + +/** + * @brief Release resources allocated using touch_slider_install() + * + * @return + * - ESP_OK: Successfully released resources + */ +void touch_slider_uninstall(void); + +/** +* @brief Create a new touch slider instance +* +* @param[in] slider_config Slider configuration +* @param[out] slider_handle Slider handle +* +* @note The index of Channel array and sensitivity array must be one-one correspondence +* +* @return +* - ESP_OK: Successfully create touch slider +* - ESP_ERR_INVALID_STATE: Touch slider driver was not initialized +* - ESP_ERR_INVALID_ARG: Invalid configuration struct or arguments is NULL +* - ESP_ERR_NO_MEM: Insufficient memory +*/ +esp_err_t touch_slider_create(const touch_slider_config_t *slider_config, touch_slider_handle_t *slider_handle); + +/** + * @brief Release resources allocated using touch_slider_create + * + * @param[in] slider_handle Slider handle + * @return + * - ESP_OK: Successfully released resources + * - ESP_ERR_INVALID_STATE: Touch slider driver was not initialized + * - ESP_ERR_INVALID_ARG: slider_handle is null + * - ESP_ERR_NOT_FOUND: Input handle is not a slider handle + */ +esp_err_t touch_slider_delete(touch_slider_handle_t slider_handle); + +/** + * @brief Touch slider subscribes event + * + * This function uses event mask to subscribe to touch slider events, once one of + * the subscribed events occurs, the event message could be retrieved by calling + * touch_element_message_receive() or input callback routine. + * + * @param[in] slider_handle Slider handle + * @param[in] event_mask Slider subscription event mask + * @param[in] arg User input argument + * + * @note Touch slider only support three kind of event masks, they are + * TOUCH_ELEM_EVENT_ON_PRESS, TOUCH_ELEM_EVENT_ON_RELEASE. You can use those event masks in any + * combination to achieve the desired effect. + * + * @return + * - ESP_OK: Successfully subscribed touch slider event + * - ESP_ERR_INVALID_STATE: Touch slider driver was not initialized + * - ESP_ERR_INVALID_ARG: slider_handle is null or event is not supported + */ +esp_err_t touch_slider_subscribe_event(touch_slider_handle_t slider_handle, uint32_t event_mask, void *arg); + +/** + * @brief Touch slider set dispatch method + * + * This function sets a dispatch method that the driver core will use + * this method as the event notification method. + * + * @param[in] slider_handle Slider handle + * @param[in] dispatch_method Dispatch method (By callback/event) + * + * @return + * - ESP_OK: Successfully set dispatch method + * - ESP_ERR_INVALID_STATE: Touch slider driver was not initialized + * - ESP_ERR_INVALID_ARG: slider_handle is null or dispatch_method is invalid + */ +esp_err_t touch_slider_set_dispatch_method(touch_slider_handle_t slider_handle, touch_elem_dispatch_t dispatch_method); + +/** + * @brief Touch slider set callback + * + * This function sets a callback routine into touch element driver core, + * when the subscribed events occur, the callback routine will be called. + * + * @param[in] slider_handle Slider handle + * @param[in] slider_callback User input callback + * + * @warning Since this input callback routine runs on driver core (esp-timer callback routine), + * it should not do something that attempts to Block, such as calling vTaskDelay(). + * + * @return + * - ESP_OK: Successfully set callback + * - ESP_ERR_INVALID_STATE: Touch slider driver was not initialized + * - ESP_ERR_INVALID_ARG: slider_handle or slider_callback is null + */ +esp_err_t touch_slider_set_callback(touch_slider_handle_t slider_handle, touch_slider_callback_t slider_callback); + +/** + * @brief Touch slider get message + * + * This function decodes the element message from touch_element_message_receive() and return + * a slider message pointer. + * + * @param[in] element_message element message + * + * @return Touch slider message pointer + */ +const touch_slider_message_t* touch_slider_get_message(const touch_elem_message_t* element_message); + +#ifdef __cplusplus +} +#endif diff --git a/components/touch_element/touch_button.c b/components/touch_element/touch_button.c new file mode 100644 index 0000000000..9f89f6ad41 --- /dev/null +++ b/components/touch_element/touch_button.c @@ -0,0 +1,426 @@ +// 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. + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "touch_element/touch_element_private.h" + +typedef struct te_button_handle_list { + te_button_handle_t button_handle; //Button handle + SLIST_ENTRY(te_button_handle_list) next; //Button handle list entry +} te_button_handle_list_t; + +typedef struct { + SLIST_HEAD(te_button_handle_list_head, te_button_handle_list) handle_list; //Button handle (instance) list + touch_button_global_config_t *global_config; //Button global configuration + SemaphoreHandle_t mutex; //Button object mutex +} te_button_obj_t; + +static te_button_obj_t *s_te_btn_obj = NULL; //Button object +/* ---------------------------------------- Button handle(instance) methods ----------------------------------------- */ +static bool button_channel_check(te_button_handle_t button_handle, touch_pad_t channel_num); +static esp_err_t button_set_threshold(te_button_handle_t button_handle); +static inline te_state_t button_get_state(te_dev_t *device); +static void button_reset_state(te_button_handle_t button_handle); +static void button_update_state(te_button_handle_t button_handle, touch_pad_t channel_num, te_state_t channel_state); +static void button_proc_state(te_button_handle_t button_handle); +static void button_event_give(te_button_handle_t button_handle); +static inline void button_dispatch(te_button_handle_t button_handle, touch_elem_dispatch_t dispatch_method); +/* ------------------------------------------ Button object(class) methods ------------------------------------------ */ +static esp_err_t button_object_add_instance(te_button_handle_t button_handle); +static esp_err_t button_object_remove_instance(te_button_handle_t button_handle); +static bool button_object_check_channel(touch_pad_t channel_num); +static esp_err_t button_object_set_threshold(void); +static void button_object_process_state(void); +static void button_object_update_state(touch_pad_t channel_num, te_state_t channel_state); +/* ------------------------------------------------------------------------------------------------------------------ */ + +esp_err_t touch_button_install(const touch_button_global_config_t *global_config) +{ + TE_CHECK(te_system_check_state() == true, ESP_ERR_INVALID_STATE); + TE_CHECK(global_config != NULL, ESP_ERR_INVALID_ARG); + //Fixme: Make it thread-safe + s_te_btn_obj = (te_button_obj_t *)calloc(1, sizeof(te_button_obj_t)); + TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_NO_MEM); + s_te_btn_obj->global_config = (touch_button_global_config_t *)calloc(1, sizeof(touch_button_global_config_t)); + s_te_btn_obj->mutex = xSemaphoreCreateMutex(); + TE_CHECK_GOTO(s_te_btn_obj->global_config != NULL && s_te_btn_obj->global_config != NULL, cleanup); + xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); + SLIST_INIT(&s_te_btn_obj->handle_list); + memcpy(s_te_btn_obj->global_config, global_config, sizeof(touch_button_global_config_t)); + te_object_methods_t button_methods = { + .handle = s_te_btn_obj, + .check_channel = button_object_check_channel, + .set_threshold = button_object_set_threshold, + .process_state = button_object_process_state, + .update_state = button_object_update_state + }; + te_object_method_register(&button_methods, TE_CLS_TYPE_BUTTON); + xSemaphoreGive(s_te_btn_obj->mutex); + return ESP_OK; + +cleanup: + TE_FREE_AND_NULL(s_te_btn_obj->global_config); + if (s_te_btn_obj->mutex != NULL) { + vSemaphoreDelete(s_te_btn_obj->mutex); + } + TE_FREE_AND_NULL(s_te_btn_obj); + return ESP_ERR_NO_MEM; +} + +void touch_button_uninstall(void) +{ + xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); + if (s_te_btn_obj == NULL) { + xSemaphoreGive(s_te_btn_obj->mutex); + return; + } + te_object_method_unregister(TE_CLS_TYPE_BUTTON); + free(s_te_btn_obj->global_config); + s_te_btn_obj->global_config = NULL; + while (!SLIST_EMPTY(&s_te_btn_obj->handle_list)) { + SLIST_FIRST(&s_te_btn_obj->handle_list); + SLIST_REMOVE_HEAD(&s_te_btn_obj->handle_list, next); + } + xSemaphoreGive(s_te_btn_obj->mutex); + vSemaphoreDelete(s_te_btn_obj->mutex); + free(s_te_btn_obj); + s_te_btn_obj = NULL; +} + +esp_err_t touch_button_create(const touch_button_config_t *button_config, touch_button_handle_t *button_handle) +{ + TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(button_handle != NULL && button_config != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(button_config->channel_num > TOUCH_PAD_NUM0 && + button_config->channel_num < TOUCH_PAD_MAX && + button_config->channel_sens > 0, + ESP_ERR_INVALID_ARG); + TE_CHECK(te_object_check_channel(&button_config->channel_num, 1) == false, ESP_ERR_INVALID_ARG); + te_button_handle_t te_button = (te_button_handle_t)calloc(1, sizeof(struct te_button_s)); + TE_CHECK(te_button != NULL, ESP_ERR_NO_MEM); + + esp_err_t ret = ESP_ERR_NO_MEM; + te_button->config = (te_button_handle_config_t *)calloc(1, sizeof(te_button_handle_config_t)); + te_button->device = (te_dev_t *)calloc(1, sizeof(te_dev_t)); + TE_CHECK_GOTO(te_button->config != NULL && te_button->device != NULL, cleanup); + + ret = te_dev_init(&te_button->device, 1, TOUCH_ELEM_TYPE_BUTTON, + &button_config->channel_num, &button_config->channel_sens, TE_DEFAULT_THRESHOLD_DIVIDER(s_te_btn_obj)); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + + te_button->config->event_mask = TOUCH_ELEM_EVENT_NONE; + te_button->config->dispatch_method = TOUCH_ELEM_DISP_MAX; + te_button->config->callback = NULL; + te_button->config->arg = NULL; + te_button->current_state = TE_STATE_IDLE; + te_button->last_state = TE_STATE_IDLE; + te_button->event = TOUCH_BUTTON_EVT_MAX; + te_button->trigger_cnt = 0; + te_button->trigger_thr = 0xffffffff; + ret = button_object_add_instance(te_button); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + *button_handle = (touch_elem_handle_t)te_button; + ESP_LOGD(TE_DEBUG_TAG, "channel: %d, channel_sens: %f", button_config->channel_num, button_config->channel_sens); + return ESP_OK; + +cleanup: + TE_FREE_AND_NULL(te_button->config); + TE_FREE_AND_NULL(te_button->device); + TE_FREE_AND_NULL(te_button); + return ret; +} + +esp_err_t touch_button_delete(touch_button_handle_t button_handle) +{ + TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret = button_object_remove_instance(button_handle); + TE_CHECK(ret == ESP_OK, ret); + te_button_handle_t te_button = (te_button_handle_t)button_handle; + te_dev_deinit(&te_button->device, 1); + free(te_button->config); + free(te_button->device); + free(te_button); + return ESP_OK; +} + +esp_err_t touch_button_set_dispatch_method(touch_button_handle_t button_handle, touch_elem_dispatch_t dispatch_method) +{ + TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(dispatch_method >= TOUCH_ELEM_DISP_EVENT && dispatch_method <= TOUCH_ELEM_DISP_MAX, ESP_ERR_INVALID_ARG); + xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); + te_button_handle_t te_button = (te_button_handle_t)button_handle; + te_button->config->dispatch_method = dispatch_method; + xSemaphoreGive(s_te_btn_obj->mutex); + return ESP_OK; +} + +esp_err_t touch_button_subscribe_event(touch_button_handle_t button_handle, uint32_t event_mask, void *arg) +{ + TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); + if (!(event_mask & TOUCH_ELEM_EVENT_ON_PRESS) && !(event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) && + !(event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) && !(event_mask & TOUCH_ELEM_EVENT_NONE)) { + ESP_LOGE(TE_TAG, "Touch button only support TOUCH_ELEM_EVENT_ON_PRESS, " + "TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_LONGPRESS event mask"); + return ESP_ERR_INVALID_ARG; + } + if (event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) { + touch_button_set_longpress(button_handle, TE_DEFAULT_LONGPRESS_TIME(s_te_btn_obj)); //set the default time(1000ms) for long press + } + xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); + te_button_handle_t te_button = (te_button_handle_t)button_handle; + te_button->config->event_mask = event_mask; + te_button->config->arg = arg; + xSemaphoreGive(s_te_btn_obj->mutex); + return ESP_OK; +} + +esp_err_t touch_button_set_callback(touch_button_handle_t button_handle, touch_button_callback_t button_callback) +{ + TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(button_callback != NULL, ESP_ERR_INVALID_ARG); + te_button_handle_t te_button = (te_button_handle_t)button_handle; + xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); + te_button->config->callback = button_callback; + xSemaphoreGive(s_te_btn_obj->mutex); + return ESP_OK; +} + +esp_err_t touch_button_set_longpress(touch_button_handle_t button_handle, uint32_t threshold_time) +{ + TE_CHECK(s_te_btn_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(button_handle != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(threshold_time > 0, ESP_ERR_INVALID_ARG); + te_button_handle_t te_button = (te_button_handle_t)button_handle; + touch_elem_dispatch_t dispatch_method = te_button->config->dispatch_method; + if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { + xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); + } + uint8_t timer_period = te_get_timer_period(); + te_button->trigger_thr = threshold_time / timer_period; + if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { + xSemaphoreGive(s_te_btn_obj->mutex); + } + return ESP_OK; +} + +const touch_button_message_t* touch_button_get_message(const touch_elem_message_t* element_message) +{ + return (touch_button_message_t*)&element_message->child_msg; + _Static_assert(sizeof(element_message->child_msg) >= sizeof(touch_button_message_t), "Message size overflow"); +} + +static bool button_object_check_channel(touch_pad_t channel_num) +{ + te_button_handle_list_t *item; + SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { + if (button_channel_check(item->button_handle, channel_num)) { + return true; + } + } + return false; +} + +static esp_err_t button_object_set_threshold(void) +{ + esp_err_t ret = ESP_OK; + te_button_handle_list_t *item; + SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { + ret = button_set_threshold(item->button_handle); + if (ret != ESP_OK) { + break; + } + } + return ret; +} + +static void button_object_process_state(void) +{ + te_button_handle_list_t *item; + SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { + if (waterproof_check_mask_handle(item->button_handle)) { + button_reset_state(item->button_handle); + continue; + } + button_proc_state(item->button_handle); + } +} + +static void button_object_update_state(touch_pad_t channel_num, te_state_t channel_state) +{ + te_button_handle_list_t *item; + SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { + if (waterproof_check_mask_handle(item->button_handle)) { + continue; + } + button_update_state(item->button_handle, channel_num, channel_state); + } +} + +static esp_err_t button_object_add_instance(te_button_handle_t button_handle) +{ + te_button_handle_list_t *item = (te_button_handle_list_t *)calloc(1, sizeof(te_button_handle_list_t)); + TE_CHECK(item != NULL, ESP_ERR_NO_MEM); + item->button_handle = button_handle; + xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); + SLIST_INSERT_HEAD(&s_te_btn_obj->handle_list, item, next); + xSemaphoreGive(s_te_btn_obj->mutex); + return ESP_OK; +} + +static esp_err_t button_object_remove_instance(te_button_handle_t button_handle) +{ + esp_err_t ret = ESP_ERR_NOT_FOUND; + te_button_handle_list_t *item; + SLIST_FOREACH(item, &s_te_btn_obj->handle_list, next) { + if (button_handle == item->button_handle) { + xSemaphoreTake(s_te_btn_obj->mutex, portMAX_DELAY); + SLIST_REMOVE(&s_te_btn_obj->handle_list, item, te_button_handle_list, next); + xSemaphoreGive(s_te_btn_obj->mutex); + free(item); + ret = ESP_OK; + break; + } + } + return ret; +} + +static bool button_channel_check(te_button_handle_t button_handle, touch_pad_t channel_num) +{ + return (channel_num == button_handle->device->channel); +} + +static esp_err_t button_set_threshold(te_button_handle_t button_handle) +{ + return te_dev_set_threshold(button_handle->device); +} + +static void button_update_state(te_button_handle_t button_handle, touch_pad_t channel_num, te_state_t channel_state) +{ + te_dev_t *device = button_handle->device; + if (channel_num != device->channel) { + return; + } + device->state = channel_state; +} + +static void button_reset_state(te_button_handle_t button_handle) +{ + button_handle->trigger_cnt = 0; + button_handle->current_state = TE_STATE_IDLE; + button_handle->device->state = TE_STATE_IDLE; +} + +static void button_event_give(te_button_handle_t button_handle) +{ + touch_elem_message_t element_message; + touch_button_message_t button_message = { + .event = button_handle->event + }; + element_message.handle = (touch_elem_handle_t)button_handle; + element_message.element_type = TOUCH_ELEM_TYPE_BUTTON; + element_message.arg = button_handle->config->arg; + memcpy(element_message.child_msg, &button_message, sizeof(button_message)); + te_event_give(element_message); +} + +static inline void button_dispatch(te_button_handle_t button_handle, touch_elem_dispatch_t dispatch_method) +{ + if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { + button_event_give(button_handle); //Event queue + } else if (dispatch_method == TOUCH_ELEM_DISP_CALLBACK) { + touch_button_message_t button_info; + button_info.event = button_handle->event; + button_handle->config->callback(button_handle, button_info, button_handle->config->arg); //Event callback + } +} + +/** + * @brief Button process + * + * This function will process the button state and maintain a button FSM: + * IDLE ----> Press ----> Release ----> IDLE + * + * The state transition procedure is as follow: + * (channel state ----> button state) + * + * TODO: add state transition diagram + */ +static void button_proc_state(te_button_handle_t button_handle) +{ + uint32_t event_mask = button_handle->config->event_mask; + touch_elem_dispatch_t dispatch_method = button_handle->config->dispatch_method; + + BaseType_t mux_ret = xSemaphoreTake(s_te_btn_obj->mutex, 0); + if (mux_ret != pdPASS) { + return; + } + + button_handle->current_state = button_get_state(button_handle->device); + + if (button_handle->current_state == TE_STATE_PRESS) { + if (button_handle->last_state == TE_STATE_IDLE) { //IDLE ---> Press = On_Press + ESP_LOGD(TE_DEBUG_TAG, "button press"); + if (event_mask & TOUCH_ELEM_EVENT_ON_PRESS) { + button_handle->event = TOUCH_BUTTON_EVT_ON_PRESS; + button_dispatch(button_handle, dispatch_method); + } + } else if (button_handle->last_state == TE_STATE_PRESS) { //Press ---> Press = On_LongPress + if (event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) { + if (++button_handle->trigger_cnt >= button_handle->trigger_thr) { + ESP_LOGD(TE_DEBUG_TAG, "button longpress"); + button_handle->event = TOUCH_BUTTON_EVT_ON_LONGPRESS; + button_dispatch(button_handle, dispatch_method); + button_handle->trigger_cnt = 0; + } + } + } + } else if (button_handle->current_state == TE_STATE_RELEASE) { + if (button_handle->last_state == TE_STATE_PRESS) { //Press ---> Release = On_Release + ESP_LOGD(TE_DEBUG_TAG, "button release"); + if (event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) { + button_handle->event = TOUCH_BUTTON_EVT_ON_RELEASE; + button_dispatch(button_handle, dispatch_method); + } + } else if (button_handle->last_state == TE_STATE_RELEASE) { // Release ---> Release = On_IDLE (Not dispatch) + button_reset_state(button_handle); //Reset the button state for the next time touch action detection + } + } else if (button_handle->current_state == TE_STATE_IDLE) { + if (button_handle->last_state == TE_STATE_RELEASE) { //Release ---> IDLE = On_IDLE (Not dispatch) + //Nothing + } else if (button_handle->last_state == TE_STATE_IDLE) { //IDLE ---> IDLE = Running IDLE (Not dispatch) + //Nothing + } + } + button_handle->last_state = button_handle->current_state; + xSemaphoreGive(s_te_btn_obj->mutex); +} + +static inline te_state_t button_get_state(te_dev_t *device) +{ + te_state_t button_state; + if (device->state == TE_STATE_PRESS) { + button_state = TE_STATE_PRESS; + } else if (device->state == TE_STATE_RELEASE) { + button_state = TE_STATE_RELEASE; + } else { + button_state = TE_STATE_IDLE; + } + return button_state; +} diff --git a/components/touch_element/touch_element.c b/components/touch_element/touch_element.c new file mode 100644 index 0000000000..d7162ba7f1 --- /dev/null +++ b/components/touch_element/touch_element.c @@ -0,0 +1,886 @@ +// Copyright 2016-2020 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "esp_timer.h" +#include "esp_log.h" +#include "hal/touch_sensor_hal.h" //TODO: remove hal +#include "touch_element/touch_element_private.h" + +#define TE_CLASS_ITEM(cls, cls_type, cls_item) ((&((cls)[cls_type]))->cls_item) + +#define TE_CLASS_FOREACH(cls_var, cls_start, cls_end) \ + for ((cls_var) = (cls_start); \ + (cls_var) < (cls_end); \ + (cls_var)++) + +#define TE_CLS_METHODS_INITIALIZER(cls, cls_start, cls_end) do { \ + typeof(cls_start) cls_method; \ + TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ + TE_CLASS_ITEM(cls, cls_method, handle) = NULL; \ + } \ +} while (0) + +#define TE_CLASS_FOREACH_CHECK_CHANNEL(cls, cls_start, cls_end, channel) ({ \ + bool ret = false; \ + typeof(cls_start) cls_method; \ + TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ + if (TE_CLASS_ITEM(cls, cls_method, handle) != NULL) { \ + ret |= TE_CLASS_ITEM(cls, cls_method, check_channel(channel)); \ + } \ + } \ + ret; \ +}) + +#define TE_CLASS_FOREACH_SET_THRESHOLD(cls, cls_start, cls_end) do { \ + typeof(cls_start) cls_method; \ + TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ + if (TE_CLASS_ITEM(cls, cls_method, handle) != NULL) { \ + TE_CLASS_ITEM(cls, cls_method, set_threshold()); \ + } \ + } \ +} while (0) + +#define TE_CLASS_FOREACH_PROCESS_STATE(cls, cls_start, cls_end) do { \ + typeof(cls_start) cls_method; \ + TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ + if (TE_CLASS_ITEM(cls, cls_method, handle) != NULL) { \ + TE_CLASS_ITEM(cls, cls_method, process_state()); \ + } \ + } \ +} while (0) + +#define TE_CLASS_FOREACH_UPDATE_STATE(cls, cls_start, cls_end, channel, state) do {\ + typeof(cls_start) cls_method; \ + TE_CLASS_FOREACH(cls_method, cls_start, cls_end) { \ + if (TE_CLASS_ITEM(cls, cls_method, handle) != NULL) { \ + TE_CLASS_ITEM(cls, cls_method, update_state(channel, state)); \ + } \ + } \ +} while (0) + +#define TE_PROCESSING_PERIOD(obj) ((obj)->global_config->software.processing_period) +#define TE_WATERPROOF_DIVIDER(obj) ((obj)->global_config->software.waterproof_threshold_divider) + +typedef enum { + TE_INTR_PRESS = 0, //Touch sensor press interrupt(TOUCH_PAD_INTR_MASK_ACTIVE) + TE_INTR_RELEASE, //Touch sensor release interrupt(TOUCH_PAD_INTR_MASK_INACTIVE) + TE_INTR_TIMEOUT, //Touch sensor scan timeout interrupt(TOUCH_PAD_INTR_MASK_TIMEOUT) + TE_INTR_SCAN_DONE, //Touch sensor scan done interrupt(TOUCH_PAD_INTR_MASK_SCAN_DONE), now just use for setting threshold + TE_INTR_MAX +} te_intr_t; + +typedef struct { + te_intr_t intr_type; //channel interrupt type + te_state_t channel_state; //channel state + touch_pad_t channel_num; //channel index +} te_intr_msg_t; + +typedef struct { + te_object_methods_t object_methods[TE_CLS_TYPE_MAX]; //Class(object) methods + touch_elem_global_config_t *global_config; //Global initialization + te_waterproof_handle_t waterproof_handle; //Waterproof configuration + esp_timer_handle_t proc_timer; //Processing timer handle + QueueHandle_t event_msg_queue; //Application event message queue (for user) + QueueHandle_t intr_msg_queue; //Interrupt message (for internal) + SemaphoreHandle_t mutex; //Global resource mutex + bool is_set_threshold; //Threshold configuration state bit + uint32_t denoise_channel_raw; //De-noise channel(TO) raw signal +} te_obj_t; + +static te_obj_t *s_te_obj = NULL; + +/** + * Internal de-noise channel(Touch channel 0) equivalent capacitance table, depends on hardware design + * + * Units: pF + */ +static const float denoise_channel_equ_cap[TOUCH_PAD_DENOISE_CAP_MAX] = {5.0f, 6.4f, 7.8f, 9.2f, 10.6f, 12.0f, 13.4f, 14.8f}; + +/** + * Waterproof shield channel(Touch channel 14) equivalent capacitance table, depends on hardware design + * + * Units: pF + */ +static const float shield_channel_ref_cap[TOUCH_PAD_SHIELD_DRV_MAX] = {40.0f, 80.0f, 120.0f, 160.0f, 200.0f, 240.0f, 280.0f, 320.0f}; + +/* -------------------------------------------- Internal shared methods --------------------------------------------- */ +/* ------------------------------------------------- */ +/* ------------------------------------------------- System methods ------------------------------------------------- */ +static esp_err_t te_hw_init(const touch_elem_hw_config_t *hardware_init); +static esp_err_t te_sw_init(const touch_elem_sw_config_t *software_init); +static inline float te_get_internal_equ_cap(touch_pad_denoise_cap_t denoise_level); +static float te_channel_get_equ_cap(touch_pad_t channel_num); +static uint32_t te_read_raw_signal(touch_pad_t channel_num); +static void te_intr_cb(void *arg); +static void te_proc_timer_cb(void *arg); +static inline esp_err_t te_object_set_threshold(void); +static inline void te_object_process_state(void); +static inline void te_object_update_state(te_intr_msg_t te_intr_msg); +/* ----------------------------------------------- Waterproof methods ----------------------------------------------- */ +static inline bool waterproof_check_state(void); +static inline bool waterproof_shield_check_state(void); +static inline bool waterproof_guard_check_state(void); +static bool waterproof_channel_check(touch_pad_t channel_num); +static void waterproof_guard_set_threshold(void); +static void waterproof_guard_update_state(touch_pad_t current_channel, te_state_t current_state); +static touch_pad_shield_driver_t waterproof_get_shield_level(touch_pad_t guard_channel_num); +/* ------------------------------------------------------------------------------------------------------------------ */ + +esp_err_t touch_element_install(const touch_elem_global_config_t *global_config) +{ + TE_CHECK(s_te_obj == NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(global_config != NULL, ESP_ERR_INVALID_ARG); + + s_te_obj = (te_obj_t *)calloc(1, sizeof(te_obj_t)); + TE_CHECK(s_te_obj != NULL, ESP_ERR_NO_MEM); + + esp_err_t ret = ESP_ERR_NO_MEM; + s_te_obj->global_config = (touch_elem_global_config_t *)calloc(1, sizeof(touch_elem_global_config_t)); + s_te_obj->mutex = xSemaphoreCreateMutex(); + TE_CHECK_GOTO(s_te_obj->global_config != NULL && s_te_obj->mutex != NULL, cleanup); + xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); + TE_CLS_METHODS_INITIALIZER(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX); + ret = te_hw_init(&global_config->hardware); + if (ret != ESP_OK) { + abort(); + } + ret = te_sw_init(&global_config->software); + if (ret != ESP_OK) { + xSemaphoreGive(s_te_obj->mutex); + goto cleanup; + } + xSemaphoreGive(s_te_obj->mutex); + return ESP_OK; + +cleanup: + TE_FREE_AND_NULL(s_te_obj->global_config); + if (s_te_obj->mutex != NULL) { + vSemaphoreDelete(s_te_obj->mutex); + } + TE_FREE_AND_NULL(s_te_obj); + return ret; +} + +esp_err_t touch_element_start(void) +{ + TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); + esp_err_t ret; + uint16_t inited_channel_mask; + do { + xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); + ret = touch_pad_get_channel_mask(&inited_channel_mask); + if (inited_channel_mask == 0x0) { + ESP_LOGE(TE_TAG, "Can not find Touch Sensor channel that has been initialized"); + ret = ESP_ERR_INVALID_STATE; + break; + } + if (ret != ESP_OK) { + break; + } + s_te_obj->is_set_threshold = false; //Threshold configuration will be set on touch sense start + ret = esp_timer_start_periodic(s_te_obj->proc_timer, TE_PROCESSING_PERIOD(s_te_obj) * 1000); + if (ret != ESP_OK) { + break; + } + ret = touch_pad_intr_enable(TOUCH_PAD_INTR_MASK_SCAN_DONE); //Use scan done interrupt to set threshold + if (ret != ESP_OK) { + break; + } + ret = touch_pad_fsm_start(); + if (ret != ESP_OK) { + break; + } + xQueueReset(s_te_obj->event_msg_queue); + xQueueReset(s_te_obj->intr_msg_queue); + xSemaphoreGive(s_te_obj->mutex); + return ESP_OK; + } while (0); + + ESP_LOGE(TE_TAG, "Touch interface start failed:(%s)", __FUNCTION__ ); + xSemaphoreGive(s_te_obj->mutex); + return ret; +} + +esp_err_t touch_element_stop(void) +{ + TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); + esp_err_t ret; + xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); + ret = touch_pad_fsm_stop(); + if (ret != ESP_OK) { + return ret; + } + ret = touch_pad_intr_disable(TOUCH_PAD_INTR_MASK_SCAN_DONE); + if (ret != ESP_OK) { + return ret; + } + ret = esp_timer_stop(s_te_obj->proc_timer); + if (ret != ESP_OK) { + return ret; + } + xSemaphoreGive(s_te_obj->mutex); + return ESP_OK; +} + +//TODO: add a new api that output system's run-time state + +void touch_element_uninstall(void) +{ + xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); + if (s_te_obj == NULL) { + xSemaphoreGive(s_te_obj->mutex); + return; + } + esp_err_t ret; + ret = touch_pad_deinit(); + if (ret != ESP_OK) { + abort(); + } + ret = esp_timer_delete(s_te_obj->proc_timer); + if (ret != ESP_OK) { + abort(); + } + ret = touch_pad_intr_disable(TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT); + if (ret != ESP_OK) { + abort(); + } + ret = touch_pad_isr_deregister(te_intr_cb, NULL); + if (ret != ESP_OK) { + abort(); + } + vQueueDelete(s_te_obj->event_msg_queue); + vQueueDelete(s_te_obj->intr_msg_queue); + xSemaphoreGive(s_te_obj->mutex); + vSemaphoreDelete(s_te_obj->mutex); + free(s_te_obj->global_config); + s_te_obj->global_config = NULL; + free(s_te_obj); + s_te_obj = NULL; +} + +esp_err_t touch_element_message_receive(touch_elem_message_t *element_message, uint32_t ticks_to_wait) +{ + //TODO: Use the generic data struct to refactor this api + TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(element_message != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(s_te_obj->event_msg_queue != NULL, ESP_ERR_INVALID_STATE); + int ret = xQueueReceive(s_te_obj->event_msg_queue, element_message, ticks_to_wait); + return (ret == pdTRUE) ? ESP_OK : ESP_ERR_TIMEOUT; +} + +static uint32_t te_read_raw_signal(touch_pad_t channel_num) +{ + uint32_t raw_signal = 0; + touch_pad_sleep_channel_t sleep_channel_info; + touch_pad_sleep_channel_get_info(&sleep_channel_info); + if (channel_num != sleep_channel_info.touch_num) { + touch_pad_read_raw_data(channel_num, &raw_signal); + } else { + touch_pad_sleep_channel_read_data(channel_num, &raw_signal); + } + return raw_signal; +} + +uint32_t te_read_smooth_signal(touch_pad_t channel_num) +{ + uint32_t smooth_signal = 0; + touch_pad_sleep_channel_t sleep_channel_info; + touch_pad_sleep_channel_get_info(&sleep_channel_info); + if (channel_num != sleep_channel_info.touch_num) { + touch_pad_filter_read_smooth(channel_num, &smooth_signal); + } else { + touch_pad_sleep_channel_read_smooth(channel_num, &smooth_signal); + } + return smooth_signal; +} + +esp_err_t te_event_give(touch_elem_message_t te_message) +{ + //TODO: add queue overwrite here when the queue is full + int ret = xQueueSend(s_te_obj->event_msg_queue, &te_message, 0); + if (ret != pdTRUE) { + ESP_LOGE(TE_TAG, "event queue send failed, event message queue is full"); + return ESP_ERR_TIMEOUT; + } + return ESP_OK; +} + +/** + * @brief Touch sensor interrupt service routine + * + * This function is touch sensor ISR, all the touch + * sensor channel state will be updated here. + */ +static void te_intr_cb(void *arg) +{ + TE_UNUSED(arg); + static int scan_done_cnt = 0; + int task_awoken = pdFALSE; + te_intr_msg_t te_intr_msg; + /*< Figure out which touch sensor channel is triggered and the trigger type */ + uint32_t intr_mask = touch_pad_read_intr_status_mask(); + te_intr_msg.channel_num = touch_pad_get_current_meas_channel(); + if (intr_mask == 0x0) { //For dummy interrupt + return; + } + bool need_send_queue = true; + if (intr_mask & TOUCH_PAD_INTR_MASK_ACTIVE) { + te_intr_msg.channel_state = TE_STATE_PRESS; + te_intr_msg.intr_type = TE_INTR_PRESS; + } else if (intr_mask & TOUCH_PAD_INTR_MASK_INACTIVE) { + te_intr_msg.channel_state = TE_STATE_RELEASE; + te_intr_msg.intr_type = TE_INTR_RELEASE; + } else if (intr_mask & TOUCH_PAD_INTR_MASK_TIMEOUT) { + te_intr_msg.channel_state = TE_STATE_IDLE; + te_intr_msg.intr_type = TE_INTR_TIMEOUT; + } else if (intr_mask & TOUCH_PAD_INTR_MASK_SCAN_DONE) { + te_intr_msg.channel_state = TE_STATE_IDLE; + te_intr_msg.intr_type = TE_INTR_SCAN_DONE; + need_send_queue = false; + /*< Due to a hardware issue, all of the data read operation(read raw, read smooth, read benchmark) */ + /*< must be after the second times of measure_done interrupt. */ + if (++scan_done_cnt >= 5) { + touch_hal_intr_disable(TOUCH_PAD_INTR_MASK_SCAN_DONE); //TODO: remove hal + scan_done_cnt = 0; + need_send_queue = true; + } + /*< De-noise channel signal must be read at the time between SCAN_DONE and next measurement beginning(sleep)!!! */ + touch_pad_denoise_read_data(&s_te_obj->denoise_channel_raw); //Update de-noise signal + } else { + te_intr_msg.intr_type = TE_INTR_MAX; // Unknown Exception + } + if (need_send_queue) { + xQueueSendFromISR(s_te_obj->intr_msg_queue, &te_intr_msg, &task_awoken); + } + if (task_awoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +/** + * @brief esp-timer callback routine + * + * This function is an esp-timer daemon routine, all the touch sensor + * application(button, slider, etc...) will be processed in here. + * + */ +static void te_proc_timer_cb(void *arg) +{ + TE_UNUSED(arg); + te_intr_msg_t te_intr_msg; + te_intr_msg.intr_type = TE_INTR_MAX; + BaseType_t ret = xSemaphoreTake(s_te_obj->mutex, 0); + if (ret != pdPASS) { + return; + } + ret = xQueueReceive(s_te_obj->intr_msg_queue, &te_intr_msg, 0); + if (ret == pdPASS) { + if (te_intr_msg.intr_type == TE_INTR_PRESS || te_intr_msg.intr_type == TE_INTR_RELEASE) { + te_object_update_state(te_intr_msg); + } else if (te_intr_msg.intr_type == TE_INTR_SCAN_DONE) { + if (s_te_obj->is_set_threshold != true) { + s_te_obj->is_set_threshold = true; + te_object_set_threshold(); //TODO: add set threshold error processing + ESP_LOGD(TE_DEBUG_TAG, "Set threshold"); + } + if (waterproof_check_state()) { + te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; + if (waterproof_handle->is_shield_level_set != true) { + waterproof_handle->is_shield_level_set = true; + touch_pad_waterproof_t wp_conf; + wp_conf.shield_driver = waterproof_get_shield_level(waterproof_handle->shield_channel); + wp_conf.guard_ring_pad = (waterproof_guard_check_state() ? waterproof_handle->guard_device->channel : TOUCH_WATERPROOF_GUARD_NOUSE); + touch_pad_waterproof_set_config(&wp_conf); + touch_pad_waterproof_enable(); + ESP_LOGD(TE_DEBUG_TAG, "Set waterproof shield level"); + } + } + ESP_LOGD(TE_DEBUG_TAG, "read denoise channel %d", s_te_obj->denoise_channel_raw); + } else if (te_intr_msg.intr_type == TE_INTR_TIMEOUT) { //Timeout processing + touch_pad_timeout_resume(); + } + } + te_object_process_state(); + xSemaphoreGive(s_te_obj->mutex); +} + +void te_object_method_register(te_object_methods_t *object_methods, te_class_type_t object_type) +{ + xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, handle) = object_methods->handle; + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, check_channel) = object_methods->check_channel; + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, set_threshold) = object_methods->set_threshold; + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, process_state) = object_methods->process_state; + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, update_state) = object_methods->update_state; + xSemaphoreGive(s_te_obj->mutex); +} + +void te_object_method_unregister(te_class_type_t object_type) +{ + xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, handle) = NULL; + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, check_channel) = NULL; + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, set_threshold) = NULL; + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, process_state) = NULL; + TE_CLASS_ITEM(s_te_obj->object_methods, object_type, update_state) = NULL; + xSemaphoreGive(s_te_obj->mutex); +} + +/** + * @brief Touch Sense channel check + * + * This function will check the input channel whether is + * associated with the Touch Sense Object + * + * @return + * - true: Channel has been initialized, pls adjust the input channel + * - false: Channel has not been initialized, pass + */ +bool te_object_check_channel(const touch_pad_t *channel_array, uint8_t channel_sum) +{ + touch_pad_t current_channel; + for (int idx = 0; idx < channel_sum; idx++) { + current_channel = channel_array[idx]; + if (waterproof_channel_check(current_channel)) { + goto INITIALIZED; + } + if (TE_CLASS_FOREACH_CHECK_CHANNEL(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX, current_channel)) { + goto INITIALIZED; + } + } + return false; + +INITIALIZED: + ESP_LOGE(TE_TAG, "Current channel [%d] has been initialized:(%s)", current_channel, __FUNCTION__ ); + return true; +} + + +static inline esp_err_t te_object_set_threshold(void) +{ + if (waterproof_guard_check_state() == true) { //TODO: add to object methods + waterproof_guard_set_threshold(); + } + + TE_CLASS_FOREACH_SET_THRESHOLD(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX); + return ESP_OK; +} + +static inline void te_object_process_state(void) +{ + TE_CLASS_FOREACH_PROCESS_STATE(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX); +} + +static inline void te_object_update_state(te_intr_msg_t te_intr_msg) +{ + if (waterproof_guard_check_state()) { + waterproof_guard_update_state(te_intr_msg.channel_num, te_intr_msg.channel_state); + } + TE_CLASS_FOREACH_UPDATE_STATE(s_te_obj->object_methods, TE_CLS_TYPE_BUTTON, TE_CLS_TYPE_MAX, + te_intr_msg.channel_num, te_intr_msg.channel_state); +} + +uint8_t te_get_timer_period(void) +{ + return (TE_PROCESSING_PERIOD(s_te_obj)); +} + +esp_err_t te_dev_init(te_dev_t **device, uint8_t device_num, te_dev_type_t type, const touch_pad_t *channel, const float *sens, float divider) +{ + for (int idx = 0; idx < device_num; idx++) { + device[idx]->channel = channel[idx]; + device[idx]->sens = sens[idx] * divider; + device[idx]->type = type; + device[idx]->state = TE_STATE_IDLE; + esp_err_t ret = touch_pad_config(device[idx]->channel); + TE_CHECK(ret == ESP_OK, ret); + } + return ESP_OK; +} + +void te_dev_deinit(te_dev_t **device, uint8_t device_num) +{ + for (int idx = 0; idx < device_num; idx++) { + touch_pad_clear_channel_mask((1UL << device[idx]->channel)); + } +} + +esp_err_t te_dev_set_threshold(te_dev_t *device) +{ + uint32_t smo_val = te_read_smooth_signal(device->channel); + esp_err_t ret = touch_pad_set_thresh(device->channel, device->sens * smo_val); + ESP_LOGD(TE_DEBUG_TAG, "channel: %d, smo_val: %d", device->channel, smo_val); + return ret; +} + +/** + * This function returns the s_te_obj whether is initialized + * + * @return + * - true: initialized + * - false: not initialized + */ +bool te_system_check_state(void) +{ + return (s_te_obj != NULL); +} + +static inline float te_get_internal_equ_cap(touch_pad_denoise_cap_t denoise_level) +{ + return denoise_channel_equ_cap[denoise_level]; +} + +/** + * @brief Get channel equivalent capacitance + * + * This function calculates the equivalent capacitance of input channel by + * using the Touch channel 0 equivalent capacitance. The formula is: + * + * Raw_N / Raw_0 = Cap_N / Cap_0 + * + * Note that Raw_N and Raw_0 are the raw data of touch channel N and touch channel 0 respectively, + * Cap_N and Cap_0 are the equivalent capacitance of touch channel N and touch channel 0. + * + * @param[in] channel_num Input touch sensor channel + * + * @note The unit is pF + * + * @return Specified channel equivalent capacitance. + */ +static float te_channel_get_equ_cap(touch_pad_t channel_num) +{ + //Fixme: add a mutex in here and prevent the system call this function + TE_CHECK(channel_num > TOUCH_PAD_NUM0 && channel_num < TOUCH_PAD_MAX, 0); + uint32_t tn_raw, t0_raw; + float tn_ref_cap, t0_ref_cap; + touch_pad_denoise_t denoise_channel_conf; + touch_pad_denoise_get_config(&denoise_channel_conf); + tn_raw = te_read_raw_signal(channel_num); + t0_raw = s_te_obj->denoise_channel_raw; + t0_ref_cap = te_get_internal_equ_cap(denoise_channel_conf.cap_level); + if (t0_raw == 0) { + return 0; + } + tn_ref_cap = (float)tn_raw / t0_raw * t0_ref_cap; + return tn_ref_cap; +} + +/** + * @brief Touch sensor driver default init [ESP32S2 only] + * + * 1. Channel measure time: Raw_value / RTC_FAST_CLK ==> Raw_value / 8000 000 + * 2. Channel sleep time: TOUCH_PAD_SLEEP_CYCLE_DEFAULT / RTC_SLOW_CLK ==> 0xf / 90 000(default) = 0.16ms + * 3. Channel charge voltage threshold(upper/lower): 2.7V upper voltage, 0.5V lower voltage, 0.5V attenuation voltage + * 4. IDLE channel processing: Connecting to GND + * 5. Interrupt type: ACTIVE, INACTIVE, TIMEOUT + * + * @note A touch sensor channel will spend the time = measure time + sleep time, RTC_FAST_CLK is 8M + * + */ +static esp_err_t te_hw_init(const touch_elem_hw_config_t *hardware_init) +{ + esp_err_t ret; + ret = touch_pad_init(); + TE_CHECK(ret == ESP_OK, ret); + ret = touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); + TE_CHECK(ret == ESP_OK, ret); + ret = touch_pad_set_meas_time(hardware_init->sleep_cycle, hardware_init->sample_count); + TE_CHECK(ret == ESP_OK, ret); + ret = touch_pad_set_voltage(hardware_init->upper_voltage, hardware_init->lower_voltage, + hardware_init->voltage_attenuation); + TE_CHECK(ret == ESP_OK, ret); + ret = touch_pad_set_idle_channel_connect(hardware_init->suspend_channel_polarity); + TE_CHECK(ret == ESP_OK, ret); + ret = touch_pad_isr_register(te_intr_cb, NULL, + TOUCH_PAD_INTR_MASK_ACTIVE | TOUCH_PAD_INTR_MASK_INACTIVE | + TOUCH_PAD_INTR_MASK_TIMEOUT | TOUCH_PAD_INTR_MASK_SCAN_DONE); + TE_CHECK(ret == ESP_OK, ret); + ret = touch_pad_intr_enable(TOUCH_PAD_INTR_MASK_ACTIVE | + TOUCH_PAD_INTR_MASK_INACTIVE | TOUCH_PAD_INTR_MASK_TIMEOUT); + TE_CHECK(ret == ESP_OK, ret); + + /*< Internal de-noise configuration */ + touch_pad_denoise_t denoise_config; + denoise_config.grade = hardware_init->denoise_level; + denoise_config.cap_level = hardware_init->denoise_equivalent_cap; + ret = touch_pad_denoise_set_config(&denoise_config); + TE_CHECK(ret == ESP_OK, ret); + ret = touch_pad_denoise_enable(); + TE_CHECK(ret == ESP_OK, ret); + + /*< benchmark filter configuration */ + touch_filter_config_t filter_config; + filter_config.smh_lvl = hardware_init->smooth_filter_mode; + filter_config.mode = hardware_init->benchmark_filter_mode; + filter_config.debounce_cnt = hardware_init->benchmark_debounce_count; + filter_config.noise_thr = hardware_init->benchmark_calibration_threshold; + filter_config.jitter_step = hardware_init->benchmark_jitter_step; + ret = touch_pad_filter_set_config(&filter_config); + TE_CHECK(ret == ESP_OK, ret); + ret = touch_pad_filter_enable(); + TE_CHECK(ret == ESP_OK, ret); + memcpy(&s_te_obj->global_config->hardware, hardware_init, sizeof(touch_elem_hw_config_t)); + return ESP_OK; +} + +static esp_err_t te_sw_init(const touch_elem_sw_config_t *software_init) +{ + TE_CHECK(software_init->processing_period > 1, ESP_ERR_INVALID_ARG); + TE_CHECK(software_init->waterproof_threshold_divider > 0, ESP_ERR_INVALID_ARG); + TE_CHECK(software_init->intr_message_size >= (TOUCH_PAD_MAX - 1), ESP_ERR_INVALID_ARG); + TE_CHECK(software_init->event_message_size > 0, ESP_ERR_INVALID_ARG); + + esp_err_t ret = ESP_ERR_NO_MEM; + s_te_obj->intr_msg_queue = xQueueCreate(software_init->intr_message_size, sizeof(te_intr_msg_t)); + s_te_obj->event_msg_queue = xQueueCreate(software_init->event_message_size, sizeof(touch_elem_message_t)); + TE_CHECK_GOTO(s_te_obj->event_msg_queue != NULL && s_te_obj->intr_msg_queue != NULL, cleanup); + + const esp_timer_create_args_t te_proc_timer_args = { + .name = "te_proc_timer_cb", + .arg = NULL, + .callback = &te_proc_timer_cb + }; + ret = esp_timer_create(&te_proc_timer_args, &s_te_obj->proc_timer); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + memcpy(&s_te_obj->global_config->software, software_init, sizeof(touch_elem_sw_config_t)); + return ret; + +cleanup: + if (s_te_obj->event_msg_queue != NULL) { + vQueueDelete(s_te_obj->event_msg_queue); + } + if (s_te_obj->intr_msg_queue != NULL) { + vQueueDelete(s_te_obj->intr_msg_queue); + } + return ret; +} + +//TODO: add waterproof guard-lock hysteresis +esp_err_t touch_element_waterproof_install(const touch_elem_waterproof_config_t *waterproof_config) +{ + TE_CHECK(s_te_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(waterproof_config != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(waterproof_config->guard_channel >= TOUCH_PAD_NUM0 && + waterproof_config->guard_channel < TOUCH_PAD_MAX, + ESP_ERR_INVALID_ARG); + te_waterproof_handle_t waterproof_handle = (te_waterproof_handle_t)calloc(1, sizeof(struct te_waterproof_s)); + TE_CHECK(waterproof_handle != NULL, ESP_ERR_NO_MEM); + waterproof_handle->shield_channel = TOUCH_PAD_NUM14; + + esp_err_t ret; + if (waterproof_config->guard_channel != TOUCH_WATERPROOF_GUARD_NOUSE) { //Use guard sensor + if (te_object_check_channel(&waterproof_config->guard_channel, 1)) { + ret = ESP_ERR_INVALID_ARG; + goto cleanup; + } + ret = ESP_ERR_NO_MEM; + waterproof_handle->mask_handle = (touch_elem_handle_t *) calloc(TOUCH_PAD_MAX, sizeof(touch_elem_handle_t)); + waterproof_handle->guard_device = (te_dev_t *)calloc(1, sizeof(te_dev_t)); + TE_CHECK_GOTO(waterproof_handle->mask_handle != NULL && waterproof_handle->guard_device, cleanup); + + ret = te_dev_init(&waterproof_handle->guard_device, 1, TOUCH_ELEM_TYPE_BUTTON, + &waterproof_config->guard_channel, &waterproof_config->guard_sensitivity, + TE_WATERPROOF_DIVIDER(s_te_obj)); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + waterproof_handle->guard_device->state = TE_STATE_RELEASE; + for (int idx = 0; idx < TOUCH_PAD_MAX; idx++) { + waterproof_handle->mask_handle[idx] = NULL; + } + } else { //No use waterproof guard sensor + waterproof_handle->guard_device = NULL; + waterproof_handle->mask_handle = NULL; + } + waterproof_handle->is_shield_level_set = 0; //Set a state bit so as to configure the shield level at the run-time + touch_pad_waterproof_t wp_conf; + wp_conf.shield_driver = TOUCH_PAD_SHIELD_DRV_L0; //Set a default shield level + wp_conf.guard_ring_pad = waterproof_config->guard_channel; + ret = touch_pad_waterproof_set_config(&wp_conf); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + ret = touch_pad_waterproof_enable(); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + s_te_obj->waterproof_handle = waterproof_handle; //Fixme: add mutex + return ESP_OK; + +cleanup: + TE_FREE_AND_NULL(waterproof_handle->mask_handle); + TE_FREE_AND_NULL(waterproof_handle->guard_device); + TE_FREE_AND_NULL(waterproof_handle); + return ret; +} + +esp_err_t touch_element_waterproof_add(touch_elem_handle_t element_handle) +{ + TE_CHECK(s_te_obj->waterproof_handle != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(s_te_obj->waterproof_handle->guard_device != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(element_handle != NULL, ESP_ERR_INVALID_ARG); + te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; + xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); + for (int idx = 0; idx < TOUCH_PAD_MAX; idx++) { + if (waterproof_handle->mask_handle[idx] == NULL) { + waterproof_handle->mask_handle[idx] = element_handle; + break; + } + } + xSemaphoreGive(s_te_obj->mutex); + return ESP_OK; +} + +esp_err_t touch_element_waterproof_remove(touch_elem_handle_t element_handle) +{ + TE_CHECK(s_te_obj->waterproof_handle != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(element_handle != NULL, ESP_ERR_INVALID_ARG); + esp_err_t ret = ESP_ERR_NOT_FOUND; + te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; + xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); + for (int idx = 0; idx < TOUCH_PAD_MAX; idx++) { + if (waterproof_handle->mask_handle[idx] == element_handle) { + waterproof_handle->mask_handle[idx] = NULL; + ret = ESP_OK; + break; + } + } + xSemaphoreGive(s_te_obj->mutex); + return ret; +} + +void touch_element_waterproof_uninstall(void) +{ + xSemaphoreTake(s_te_obj->mutex, portMAX_DELAY); + touch_pad_waterproof_disable(); + free(s_te_obj->waterproof_handle->guard_device); + free(s_te_obj->waterproof_handle->mask_handle); + free(s_te_obj->waterproof_handle); + s_te_obj->waterproof_handle = NULL; + xSemaphoreGive(s_te_obj->mutex); +} + +static touch_pad_shield_driver_t waterproof_get_shield_level(touch_pad_t guard_channel_num) +{ + touch_pad_shield_driver_t shield_level = TOUCH_PAD_SHIELD_DRV_L7; + float guard_ref_cap = te_channel_get_equ_cap(guard_channel_num); + for (int level = 0; level < TOUCH_PAD_SHIELD_DRV_MAX; level++) { + if (guard_ref_cap <= shield_channel_ref_cap[level]) { + shield_level = (touch_pad_shield_driver_t)level; + break; + } + } + return shield_level; +} + +/** + * This function returns the waterproof_handle whether is initialized + * + * @return + * - true: initialized + * - false: not initialized + */ +static inline bool waterproof_check_state(void) +{ + return (s_te_obj->waterproof_handle != NULL); +} + +static inline bool waterproof_shield_check_state(void) +{ + return waterproof_check_state(); //Driver does not allow to disable shield sensor after waterproof enabling +} + +static inline bool waterproof_guard_check_state(void) +{ + if (waterproof_check_state() == false) { + return false; + } + if (s_te_obj->waterproof_handle->guard_device == NULL || s_te_obj->waterproof_handle->mask_handle == NULL) { + return false; + } + return true; +} + +static bool waterproof_channel_check(touch_pad_t channel_num) +{ + if (waterproof_check_state() == false) { + return false; + } + te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; + if (waterproof_shield_check_state()) { + if (channel_num == waterproof_handle->shield_channel) { + ESP_LOGE(TE_TAG, "TOUCH_PAD_NUM%d has been used for waterproof shield channel," + " please change the touch sensor channel or disable waterproof", channel_num); + return true; + } + } + if (waterproof_guard_check_state()) { + if (channel_num == waterproof_handle->guard_device->channel) { + ESP_LOGE(TE_TAG, "TOUCH_PAD_NUM%d has been used for waterproof guard channel," + " please change the touch sensor channel or disable waterproof", channel_num); + return true; + } + } + return false; +} + +static void waterproof_guard_set_threshold(void) +{ + if (waterproof_check_state() == false) { + return; + } + if (waterproof_guard_check_state() == false) { + return; + } + te_dev_set_threshold(s_te_obj->waterproof_handle->guard_device); +} + +/** + * This function will figure out current handle whether is a masked channel + * while guard channel is triggered. + * + * @param[in] te_handle Touch sensor application handle + * @return + * - true current handle is a masked channel + * - false current handle is not a masked channel + */ +bool waterproof_check_mask_handle(touch_elem_handle_t te_handle) +{ + if (waterproof_check_state() == false) { + return false; + } + if (waterproof_guard_check_state() == false) { + return false; + } + te_waterproof_handle_t waterproof_handle = s_te_obj->waterproof_handle; + bool ret = false; + if (waterproof_handle->guard_device->state == TE_STATE_PRESS) { + for (int idx = 0; idx < TOUCH_PAD_MAX; idx++) { + if (waterproof_handle->mask_handle[idx] == NULL) { + break; + } + if (waterproof_handle->mask_handle[idx] == te_handle) { + ret = true; + } + } + } + return ret; +} + +static void waterproof_guard_update_state(touch_pad_t current_channel, te_state_t current_state) +{ + te_dev_t *guard_device = s_te_obj->waterproof_handle->guard_device; + if (current_channel == guard_device->channel) { + guard_device->state = current_state; + } + ESP_LOGD(TE_DEBUG_TAG, "waterproof guard state update %d", guard_device->state); +} diff --git a/components/touch_element/touch_matrix.c b/components/touch_element/touch_matrix.c new file mode 100644 index 0000000000..cd8c8e3f10 --- /dev/null +++ b/components/touch_element/touch_matrix.c @@ -0,0 +1,631 @@ +// 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. + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "touch_element/touch_element_private.h" + +#define TE_MAT_POS_MAX (0xff) //!< Matrix button startup position + +typedef struct te_matrix_handle_list { + te_matrix_handle_t matrix_handle; //Matrix handle + SLIST_ENTRY(te_matrix_handle_list) next; //Matrix handle list entry +} te_matrix_handle_list_t; + +typedef struct { + SLIST_HEAD(te_matrix_handle_list_head, te_matrix_handle_list) handle_list; //Matrix handle (instance) list + touch_matrix_global_config_t *global_config; //Matrix global configuration + SemaphoreHandle_t mutex; //Matrix object mutex +} te_matrix_obj_t; + +te_matrix_obj_t *s_te_mat_obj = NULL; +/* ---------------------------------------- Matrix handle(instance) methods ----------------------------------------- */ +static bool matrix_channel_check(te_matrix_handle_t matrix_handle, touch_pad_t channel_num); +static esp_err_t matrix_set_threshold(te_matrix_handle_t matrix_handle); +static inline te_state_t matrix_get_state(te_matrix_state_t x_axis_state, te_matrix_state_t y_axis_state); +static void matrix_reset_state(te_matrix_handle_t matrix_handle); +static void matrix_update_state(te_matrix_handle_t matrix_handle, touch_pad_t channel_num, te_state_t channel_state); +static void matrix_update_position(te_matrix_handle_t matrix_handle, touch_matrix_position_t new_pos); +static void matrix_proc_state(te_matrix_handle_t matrix_handle); +static void matrix_event_give(te_matrix_handle_t matrix_handle); +static inline void matrix_dispatch(te_matrix_handle_t matrix_handle, touch_elem_dispatch_t dispatch_method); +/* ------------------------------------------ Matrix object(class) methods ------------------------------------------ */ +static esp_err_t matrix_object_add_instance(te_matrix_handle_t matrix_handle); +static esp_err_t matrix_object_remove_instance(te_matrix_handle_t matrix_handle); +static bool matrix_object_check_channel(touch_pad_t channel_num); +static esp_err_t matrix_object_set_threshold(void); +static void matrix_object_process_state(void); +static void matrix_object_update_state(touch_pad_t channel_num, te_state_t channel_state); +/* ------------------------------------------------------------------------------------------------------------------ */ + +esp_err_t touch_matrix_install(const touch_matrix_global_config_t *global_config) +{ + TE_CHECK(te_system_check_state() == true, ESP_ERR_INVALID_STATE); + TE_CHECK(global_config != NULL, ESP_ERR_INVALID_ARG); + + //Fixme: Make it thread-safe + s_te_mat_obj = (te_matrix_obj_t *)calloc(1, sizeof(te_matrix_obj_t)); + TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_NO_MEM); + s_te_mat_obj->global_config = (touch_matrix_global_config_t *)calloc(1, sizeof(touch_matrix_global_config_t)); + s_te_mat_obj->mutex = xSemaphoreCreateMutex(); + TE_CHECK_GOTO(s_te_mat_obj->global_config != NULL && s_te_mat_obj->mutex != NULL, cleanup); + xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); + SLIST_INIT(&s_te_mat_obj->handle_list); + memcpy(s_te_mat_obj->global_config, global_config, sizeof(touch_matrix_global_config_t)); + te_object_methods_t matrix_methods = { + .handle = s_te_mat_obj, + .check_channel = matrix_object_check_channel, + .set_threshold = matrix_object_set_threshold, + .process_state = matrix_object_process_state, + .update_state = matrix_object_update_state + }; + te_object_method_register(&matrix_methods, TE_CLS_TYPE_MATRIX); + xSemaphoreGive(s_te_mat_obj->mutex); + return ESP_OK; + +cleanup: + TE_FREE_AND_NULL(s_te_mat_obj->global_config); + if (s_te_mat_obj->mutex != NULL) { + vSemaphoreDelete(s_te_mat_obj->mutex); + } + TE_FREE_AND_NULL(s_te_mat_obj); + return ESP_ERR_NO_MEM; +} + +void touch_matrix_uninstall(void) +{ + xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); + if (s_te_mat_obj == NULL) { + xSemaphoreGive(s_te_mat_obj->mutex); + return; + } + te_object_method_unregister(TE_CLS_TYPE_MATRIX); + free(s_te_mat_obj->global_config); + s_te_mat_obj->global_config = NULL; + while (!SLIST_EMPTY(&s_te_mat_obj->handle_list)) { + SLIST_FIRST(&s_te_mat_obj->handle_list); + SLIST_REMOVE_HEAD(&s_te_mat_obj->handle_list, next); + } + xSemaphoreGive(s_te_mat_obj->mutex); + vSemaphoreDelete(s_te_mat_obj->mutex); + free(s_te_mat_obj); + s_te_mat_obj = NULL; +} + +esp_err_t touch_matrix_create(const touch_matrix_config_t *matrix_config, touch_matrix_handle_t *matrix_handle) +{ + TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(matrix_handle != NULL && matrix_config != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(matrix_config->x_channel_array != NULL && + matrix_config->y_channel_array != NULL && + matrix_config->x_sensitivity_array != NULL && + matrix_config->y_sensitivity_array != NULL && + matrix_config->x_channel_num > 1 && + matrix_config->x_channel_num < TOUCH_PAD_MAX && + matrix_config->y_channel_num > 1 && + matrix_config->y_channel_num < TOUCH_PAD_MAX, + ESP_ERR_INVALID_ARG); + TE_CHECK(te_object_check_channel(matrix_config->x_channel_array, matrix_config->x_channel_num) == false && + te_object_check_channel(matrix_config->y_channel_array, matrix_config->y_channel_num) == false, + ESP_ERR_INVALID_ARG); + te_matrix_handle_t te_matrix = (te_matrix_handle_t)calloc(1, sizeof(struct te_slider_s)); + TE_CHECK(te_matrix != NULL, ESP_ERR_NO_MEM); + + esp_err_t ret = ESP_ERR_NO_MEM; + te_matrix->config = (te_matrix_handle_config_t *)calloc(1, sizeof(te_matrix_handle_config_t)); + te_matrix->device = (te_dev_t **)calloc(matrix_config->x_channel_num + matrix_config->y_channel_num, sizeof(te_dev_t *)); + TE_CHECK_GOTO(te_matrix->config != NULL && te_matrix->device != NULL, cleanup); + for (int idx = 0; idx < matrix_config->x_channel_num + matrix_config->y_channel_num; idx++) { + te_matrix->device[idx] = (te_dev_t *)calloc(1, sizeof(te_dev_t)); + if (te_matrix->device[idx] == NULL) { + ret = ESP_ERR_NO_MEM; + goto cleanup; + } + } + /*< Initialize x-axis */ + ret = te_dev_init(&te_matrix->device[0], matrix_config->x_channel_num, TOUCH_ELEM_TYPE_MATRIX, + matrix_config->x_channel_array, matrix_config->x_sensitivity_array, + TE_DEFAULT_THRESHOLD_DIVIDER(s_te_mat_obj)); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + /*< Initialize y-axis */ + ret = te_dev_init(&te_matrix->device[matrix_config->x_channel_num], matrix_config->y_channel_num, + TOUCH_ELEM_TYPE_MATRIX, matrix_config->y_channel_array, matrix_config->y_sensitivity_array, + TE_DEFAULT_THRESHOLD_DIVIDER(s_te_mat_obj)); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + + te_matrix->config->event_mask = TOUCH_ELEM_EVENT_NONE; + te_matrix->config->dispatch_method = TOUCH_ELEM_DISP_MAX; + te_matrix->config->callback = NULL; + te_matrix->config->arg = NULL; + te_matrix->current_state = TE_STATE_IDLE; + te_matrix->last_state = TE_STATE_IDLE; + te_matrix->event = TOUCH_MATRIX_EVT_MAX; + te_matrix->x_channel_num = matrix_config->x_channel_num; + te_matrix->y_channel_num = matrix_config->y_channel_num; + te_matrix->trigger_cnt = 0; + te_matrix->trigger_thr = 0xffffffff; + te_matrix->position.x_axis = TE_MAT_POS_MAX; + te_matrix->position.y_axis = TE_MAT_POS_MAX; + te_matrix->position.index = TE_MAT_POS_MAX; + ret = matrix_object_add_instance(te_matrix); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + *matrix_handle = (touch_elem_handle_t) te_matrix; + return ESP_OK; + +cleanup: + TE_FREE_AND_NULL(te_matrix->config); + if (te_matrix->device != NULL) { + for (int idx = 0; idx < matrix_config->x_channel_num + matrix_config->y_channel_num; idx++) { + TE_FREE_AND_NULL(te_matrix->device[idx]); + } + free(te_matrix->device); + te_matrix->device = NULL; + } + TE_FREE_AND_NULL(te_matrix); + return ret; +} + +esp_err_t touch_matrix_delete(touch_matrix_handle_t matrix_handle) +{ + TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); + /*< Release touch sensor application resource */ + esp_err_t ret = matrix_object_remove_instance(matrix_handle); + TE_CHECK(ret == ESP_OK, ret); + te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; + /*< Release touch sensor device resource */ + te_dev_deinit(te_matrix->device, te_matrix->x_channel_num + te_matrix->y_channel_num); + for (int idx = 0; idx < te_matrix->x_channel_num + te_matrix->y_channel_num; idx++) { + free(te_matrix->device[idx]); + } + free(te_matrix->config); + free(te_matrix->device); + free(te_matrix); + return ESP_OK; +} + +esp_err_t touch_matrix_set_dispatch_method(touch_matrix_handle_t matrix_handle, touch_elem_dispatch_t dispatch_method) +{ + TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(dispatch_method >= TOUCH_ELEM_DISP_EVENT && dispatch_method <= TOUCH_ELEM_DISP_MAX, ESP_ERR_INVALID_ARG); + xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); + te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; + te_matrix->config->dispatch_method = dispatch_method; + xSemaphoreGive(s_te_mat_obj->mutex); + return ESP_OK; +} + +esp_err_t touch_matrix_subscribe_event(touch_matrix_handle_t matrix_handle, uint32_t event_mask, void *arg) +{ + TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); + if (!(event_mask & TOUCH_ELEM_EVENT_ON_PRESS) && !(event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) && + !(event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) && !(event_mask & TOUCH_ELEM_EVENT_NONE)) { + ESP_LOGE(TE_TAG, "Touch button only support TOUCH_ELEM_EVENT_ON_PRESS, " + "TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_LONGPRESS event mask"); + return ESP_ERR_INVALID_ARG; + } + if (event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) { + touch_matrix_set_longpress(matrix_handle, TE_DEFAULT_LONGPRESS_TIME(s_te_mat_obj)); //set the default time(1000ms) for long press + } + xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); + te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; + te_matrix->config->event_mask = event_mask; + te_matrix->config->arg = arg; + xSemaphoreGive(s_te_mat_obj->mutex); + return ESP_OK; +} + +esp_err_t touch_matrix_set_callback(touch_matrix_handle_t matrix_handle, touch_matrix_callback_t matrix_callback) +{ + TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(matrix_callback != NULL, ESP_ERR_INVALID_ARG); + te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; + xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); + te_matrix->config->callback = matrix_callback; + xSemaphoreGive(s_te_mat_obj->mutex); + return ESP_OK; +} + +esp_err_t touch_matrix_set_longpress(touch_matrix_handle_t matrix_handle, uint32_t threshold_time) +{ + TE_CHECK(s_te_mat_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(matrix_handle != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(threshold_time > 0, ESP_ERR_INVALID_ARG); + te_matrix_handle_t te_matrix = (te_matrix_handle_t)matrix_handle; + touch_elem_dispatch_t dispatch_method = te_matrix->config->dispatch_method; + if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { + xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); + } + uint8_t timer_period = te_get_timer_period(); + te_matrix->trigger_thr = threshold_time / timer_period; + if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { + xSemaphoreGive(s_te_mat_obj->mutex); + } + return ESP_OK; +} + +const touch_matrix_message_t* touch_matrix_get_message(const touch_elem_message_t* element_message) +{ + return (touch_matrix_message_t*)&element_message->child_msg; + _Static_assert(sizeof(element_message->child_msg) >= sizeof(touch_matrix_message_t), "Message size overflow"); +} + +static bool matrix_object_check_channel(touch_pad_t channel_num) +{ + te_matrix_handle_list_t *item; + SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { + if (matrix_channel_check(item->matrix_handle, channel_num)) { + return true; + } + } + return false; +} + +static esp_err_t matrix_object_set_threshold(void) +{ + esp_err_t ret = ESP_OK; + te_matrix_handle_list_t *item; + SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { + ret = matrix_set_threshold(item->matrix_handle); + if (ret != ESP_OK) { + break; + } + } + return ret; +} + +static void matrix_object_process_state(void) +{ + te_matrix_handle_list_t *item; + SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { + if (waterproof_check_mask_handle(item->matrix_handle)) { + matrix_reset_state(item->matrix_handle); + continue; + } + matrix_proc_state(item->matrix_handle); + } +} + +static void matrix_object_update_state(touch_pad_t channel_num, te_state_t channel_state) +{ + te_matrix_handle_list_t *item; + SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { + if (waterproof_check_mask_handle(item->matrix_handle)) { + continue; + } + matrix_update_state(item->matrix_handle, channel_num, channel_state); + } +} + +static esp_err_t matrix_object_add_instance(te_matrix_handle_t matrix_handle) +{ + te_matrix_handle_list_t *item = (te_matrix_handle_list_t *)calloc(1, sizeof(te_matrix_handle_list_t)); + TE_CHECK(item != NULL, ESP_ERR_NO_MEM); + item->matrix_handle = matrix_handle; + xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); + SLIST_INSERT_HEAD(&s_te_mat_obj->handle_list, item, next); + xSemaphoreGive(s_te_mat_obj->mutex); + return ESP_OK; +} + +static esp_err_t matrix_object_remove_instance(te_matrix_handle_t matrix_handle) +{ + esp_err_t ret = ESP_ERR_NOT_FOUND; + te_matrix_handle_list_t *item; + SLIST_FOREACH(item, &s_te_mat_obj->handle_list, next) { + if (matrix_handle == item->matrix_handle) { + xSemaphoreTake(s_te_mat_obj->mutex, portMAX_DELAY); + SLIST_REMOVE(&s_te_mat_obj->handle_list, item, te_matrix_handle_list, next); + xSemaphoreGive(s_te_mat_obj->mutex); + free(item); + ret = ESP_OK; + break; + } + } + return ret; +} + +static bool matrix_channel_check(te_matrix_handle_t matrix_handle, touch_pad_t channel_num) +{ + te_dev_t *device; + for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { + device = matrix_handle->device[idx]; + if (device->channel == channel_num) { + return true; + } + } + return false; +} + +static esp_err_t matrix_set_threshold(te_matrix_handle_t matrix_handle) +{ + esp_err_t ret = ESP_OK; + for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { + ret |= te_dev_set_threshold(matrix_handle->device[idx]); + } + return ret; +} + +static void matrix_update_state(te_matrix_handle_t matrix_handle, touch_pad_t channel_num, te_state_t channel_state) +{ + te_dev_t *device; + for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { + device = matrix_handle->device[idx]; + if (channel_num == device->channel) { + device->state = channel_state; + } + } +} + +static void matrix_reset_state(te_matrix_handle_t matrix_handle) +{ + for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { + matrix_handle->device[idx]->state = TE_STATE_IDLE; + } + matrix_handle->trigger_cnt = 0; + matrix_handle->current_state = TE_STATE_IDLE; +} + +static void matrix_event_give(te_matrix_handle_t matrix_handle) +{ + touch_elem_message_t element_message; + touch_matrix_message_t matrix_message = { + .event = matrix_handle->event, + .position = matrix_handle->position + }; + element_message.handle = (touch_elem_handle_t)matrix_handle; + element_message.element_type = TOUCH_ELEM_TYPE_MATRIX; + element_message.arg = matrix_handle->config->arg; + memcpy(element_message.child_msg, &matrix_message, sizeof(matrix_message)); + te_event_give(element_message); +} + +static inline void matrix_dispatch(te_matrix_handle_t matrix_handle, touch_elem_dispatch_t dispatch_method) +{ + if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { + matrix_event_give(matrix_handle); //Event queue + } else if (dispatch_method == TOUCH_ELEM_DISP_CALLBACK) { + touch_matrix_message_t matrix_info; + matrix_info.event = matrix_handle->event; + matrix_info.position = matrix_handle->position; + void *arg = matrix_handle->config->arg; + matrix_handle->config->callback(matrix_handle, matrix_info, arg); //Event callback + } +} + +/** + * @brief Scan the matrix channel + * + * This function will output the press position and release position info + * so as to determine which operation(press/release) is happening, since there + * will get the invalid state if user operates multi-points at the same time. + * + */ +static void matrix_scan_axis(te_matrix_handle_t matrix_handle, touch_matrix_position_t *press_pos, + uint8_t *press_cnt, touch_matrix_position_t *release_pos, uint8_t *release_cnt) +{ + press_pos->x_axis = TE_MAT_POS_MAX; + press_pos->y_axis = TE_MAT_POS_MAX; + release_pos->x_axis = TE_MAT_POS_MAX; + release_pos->y_axis = TE_MAT_POS_MAX; + for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { + te_dev_t *device = matrix_handle->device[idx]; + if (device->state == TE_STATE_PRESS) { + if (idx < matrix_handle->x_channel_num) { + press_pos->x_axis = idx; //Write down the x axis info + } else { + press_pos->y_axis = idx - matrix_handle->x_channel_num; //Write down the y axis info + } + (*press_cnt)++; + } else if (device->state == TE_STATE_RELEASE) { + if (idx < matrix_handle->x_channel_num) { + release_pos->x_axis = idx; + } else { + release_pos->y_axis = idx - matrix_handle->x_channel_num; + } + (*release_cnt)++; + } + } +} + +/** + * @brief Pre-check and fix + * + * This function will pre-check and fix the invalid state, preparing for the + * next detection. + * + */ +static void matrix_pre_fixed(te_matrix_handle_t matrix_handle, touch_matrix_position_t *press_pos, + uint8_t press_cnt, touch_matrix_position_t *release_pos, uint8_t release_cnt) +{ + te_dev_t *device; + te_matrix_state_t last_state = matrix_handle->current_state; + touch_matrix_position_t last_pos = { + .x_axis = matrix_handle->position.x_axis, + .y_axis = matrix_handle->position.y_axis + }; + if (last_state == TE_STATE_IDLE) { + if (release_cnt > 0) { + /*< Release is not allowed while matrix is in IDLE state, */ + /*< if that happened, reset it from Release into IDLE. */ + for (int idx = 0; idx < matrix_handle->x_channel_num + matrix_handle->y_channel_num; idx++) { + device = matrix_handle->device[idx]; + if (device->state != TE_STATE_RELEASE) { + continue; + } + device->state = TE_STATE_IDLE; //Reset to IDLE + } + } + } else if (last_state == TE_STATE_PRESS) { + if (release_cnt > 0) { + /*< Release position must be the same as the last Press position, */ + /*< if it is not, reset it into IDLE. */ + if (release_pos->x_axis != TE_MAT_POS_MAX && release_pos->x_axis != last_pos.x_axis) { + device = matrix_handle->device[release_pos->x_axis]; + device->state = TE_STATE_IDLE; + } + if (release_pos->y_axis != TE_MAT_POS_MAX && release_pos->y_axis != last_pos.y_axis) { + device = matrix_handle->device[release_pos->y_axis + matrix_handle->x_channel_num]; + device->state = TE_STATE_IDLE; + } + } + if (press_cnt > 2) { //TODO: remove or rewrite here + /*< If the last state is Press and current press count more than 2, */ + /*< there must be multi-touch occurred, reset all of the channels */ + /*< into IDLE except the last position channels. */ + if (press_pos->x_axis != TE_MAT_POS_MAX && press_pos->x_axis != last_pos.x_axis) { + device = matrix_handle->device[press_pos->x_axis]; + device->state = TE_STATE_IDLE; + } + if (press_pos->y_axis != TE_MAT_POS_MAX && press_pos->y_axis != last_pos.y_axis) { + device = matrix_handle->device[press_pos->y_axis + matrix_handle->x_channel_num]; + device->state = TE_STATE_IDLE; + } + } + } +} + +//TODO: refactor this ugly implementation +static esp_err_t matrix_get_axis_state(touch_matrix_position_t *press_pos, uint8_t press_cnt, touch_matrix_position_t *release_pos, + uint8_t release_cnt, te_matrix_state_t *x_axis_state, te_matrix_state_t *y_axis_state) +{ + esp_err_t ret; + if (press_cnt >= 2) { + if (press_pos->x_axis != TE_MAT_POS_MAX && press_pos->y_axis != TE_MAT_POS_MAX) { + *x_axis_state = TE_STATE_PRESS; + *y_axis_state = TE_STATE_PRESS; + ret = ESP_OK; + } else { + ret = ESP_ERR_INVALID_STATE; + } + + } else if (release_cnt >= 2) { + if (release_pos->x_axis != TE_MAT_POS_MAX && release_pos->y_axis != TE_MAT_POS_MAX) { + *x_axis_state = TE_STATE_RELEASE; + *y_axis_state = TE_STATE_RELEASE; + ret = ESP_OK; + } else { + ret = ESP_ERR_INVALID_STATE; + } + } else { + ret = ESP_ERR_INVALID_STATE; + } + return ret; +} + +/** + * @brief Matrix button process + * + * This function will process the matrix button state and maintain a matrix FSM: + * IDLE ----> Press ----> Release ----> IDLE + * + * The state transition procedure is as follow: + * (channel state ----> x,y axis state ----> matrix button state) + * + * TODO: add state transition diagram + */ +static void matrix_proc_state(te_matrix_handle_t matrix_handle) +{ + esp_err_t ret; + uint8_t press_cnt = 0; + uint8_t release_cnt = 0; + touch_matrix_position_t press_pos; + touch_matrix_position_t release_pos; + te_matrix_state_t x_axis_state = TE_STATE_IDLE; + te_matrix_state_t y_axis_state = TE_STATE_IDLE; + uint32_t event_mask = matrix_handle->config->event_mask; + touch_elem_dispatch_t dispatch_method = matrix_handle->config->dispatch_method; + + BaseType_t mux_ret = xSemaphoreTake(s_te_mat_obj->mutex, 0); + if (mux_ret != pdPASS) { + return; + } + + //TODO: refactor those functions + /*< Scan the state of all the matrix buttons channel */ + matrix_scan_axis(matrix_handle, &press_pos, &press_cnt, &release_pos, &release_cnt); + + /*< Pre check and fixed the invalid state */ + matrix_pre_fixed(matrix_handle, &press_pos, press_cnt, &release_pos, release_cnt); + + /*< Figure out x,y axis state and take the position */ + ret = matrix_get_axis_state(&press_pos, press_cnt, &release_pos, release_cnt, &x_axis_state, &y_axis_state); + if (ret != ESP_OK) { //TODO: remove return + xSemaphoreGive(s_te_mat_obj->mutex); + return; + } + + matrix_handle->current_state = matrix_get_state(x_axis_state, y_axis_state); + + if (matrix_handle->current_state == TE_STATE_PRESS) { + if (matrix_handle->last_state == TE_STATE_IDLE) { //IDLE ---> Press = On_Press + matrix_update_position(matrix_handle, press_pos); + ESP_LOGD(TE_DEBUG_TAG, "matrix press (%d, %d)", matrix_handle->position.x_axis, matrix_handle->position.y_axis); + if (event_mask & TOUCH_ELEM_EVENT_ON_PRESS) { + matrix_handle->event = TOUCH_MATRIX_EVT_ON_PRESS; + matrix_dispatch(matrix_handle, dispatch_method); + } + } else if (matrix_handle->last_state == TE_STATE_PRESS) { //Press ---> Press = On_LongPress + if (event_mask & TOUCH_ELEM_EVENT_ON_LONGPRESS) { + if (++matrix_handle->trigger_cnt >= matrix_handle->trigger_thr) { + ESP_LOGD(TE_DEBUG_TAG, "matrix longpress (%d, %d)", matrix_handle->position.x_axis, matrix_handle->position.y_axis); + matrix_handle->event = TOUCH_MATRIX_EVT_ON_LONGPRESS; + matrix_dispatch(matrix_handle, dispatch_method); + matrix_handle->trigger_cnt = 0; + } + } + } + } else if (matrix_handle->current_state == TE_STATE_RELEASE) { + if (matrix_handle->last_state == TE_STATE_PRESS) { //Press ---> Release = On_Release + ESP_LOGD(TE_DEBUG_TAG, "matrix release (%d, %d)", matrix_handle->position.x_axis, matrix_handle->position.y_axis); + if (event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) { + matrix_handle->event = TOUCH_MATRIX_EVT_ON_RELEASE; + matrix_dispatch(matrix_handle, dispatch_method); + } + } else if (matrix_handle->last_state == TE_STATE_RELEASE) { // Release ---> Release = On_IDLE (Not dispatch) + matrix_reset_state(matrix_handle); + } + } else if (matrix_handle->current_state == TE_STATE_IDLE) { + if (matrix_handle->last_state == TE_STATE_RELEASE) { //Release ---> IDLE = On_IDLE (Not dispatch) + //Nothing + } else if (matrix_handle->last_state == TE_STATE_IDLE) { //IDLE ---> IDLE = Running IDLE (Not dispatch) + //Move Pre-fix into here + } + } + matrix_handle->last_state = matrix_handle->current_state; + xSemaphoreGive(s_te_mat_obj->mutex); +} + +static inline te_state_t matrix_get_state(te_matrix_state_t x_axis_state, te_matrix_state_t y_axis_state) +{ + te_state_t matrix_state; + if ((x_axis_state == TE_STATE_PRESS) && (y_axis_state == TE_STATE_PRESS)) { + matrix_state = TE_STATE_PRESS; + } else if ((x_axis_state == TE_STATE_RELEASE) && (y_axis_state == TE_STATE_RELEASE)) { + matrix_state = TE_STATE_RELEASE; + } else { + matrix_state = TE_STATE_IDLE; + } + return matrix_state; +} + +static void matrix_update_position(te_matrix_handle_t matrix_handle, touch_matrix_position_t new_pos) { + matrix_handle->position.x_axis = new_pos.x_axis; + matrix_handle->position.y_axis = new_pos.y_axis; + matrix_handle->position.index = matrix_handle->position.x_axis * matrix_handle->y_channel_num + matrix_handle->position.y_axis; +} diff --git a/components/touch_element/touch_slider.c b/components/touch_element/touch_slider.c new file mode 100644 index 0000000000..ff10b6da6f --- /dev/null +++ b/components/touch_element/touch_slider.c @@ -0,0 +1,661 @@ +// 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. + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "touch_element/touch_element_private.h" + +#define TE_SLD_DEFAULT_QTF_THR(obj) ((obj)->global_config->quantify_lower_threshold) +#define TE_SLD_DEFAULT_POS_FILTER_FACTOR(obj) ((obj)->global_config->position_filter_factor) +#define TE_SLD_DEFAULT_CALCULATE_CHANNEL(obj) ((obj)->global_config->calculate_channel_count) +#define TE_SLD_DEFAULT_BCM_UPDATE_TIME(obj) ((obj)->global_config->benchmark_update_time) +#define TE_SLD_DEFAULT_FILTER_RESET_TIME(obj) ((obj)->global_config->filter_reset_time) +#define TE_SLD_DEFAULT_POS_FILTER_SIZE(obj) ((obj)->global_config->position_filter_size) + +typedef struct te_slider_handle_list { + te_slider_handle_t slider_handle; //Slider handle + SLIST_ENTRY(te_slider_handle_list) next; //Slider handle list entry +} te_slider_handle_list_t; + +typedef struct { + SLIST_HEAD(te_slider_handle_list_head, te_slider_handle_list) handle_list; //Slider handle (instance) list + touch_slider_global_config_t *global_config; //Slider global configuration + SemaphoreHandle_t mutex; //Slider object mutex +} te_slider_obj_t; + +te_slider_obj_t *s_te_sld_obj = NULL; +/* ---------------------------------------- Slider handle(instance) methods ----------------------------------------- */ +static bool slider_channel_check(te_slider_handle_t slider_handle, touch_pad_t channel_num); +static esp_err_t slider_set_threshold(te_slider_handle_t slider_handle); +static inline te_state_t slider_get_state(te_dev_t **device, int device_num); +static void slider_reset_state(te_slider_handle_t slider_handle); +static void slider_update_position(te_slider_handle_t slider_handle); +static void slider_reset_position(te_slider_handle_t slider_handle); +static void slider_update_benchmark(te_slider_handle_t slider_handle); +static void slider_update_state(te_slider_handle_t slider_handle, touch_pad_t channel_num, te_state_t channel_state); +static void slider_proc_state(te_slider_handle_t slider_handle); +static void slider_event_give(te_slider_handle_t slider_handle); +static inline void slider_dispatch(te_slider_handle_t slider_handle, touch_elem_dispatch_t dispatch_method); +/* ------------------------------------------ Slider object(class) methods ------------------------------------------ */ +static esp_err_t slider_object_add_instance(te_slider_handle_t slider_handle); +static esp_err_t slider_object_remove_instance(te_slider_handle_t slider_handle); +static bool slider_object_check_channel(touch_pad_t channel_num); +static esp_err_t slider_object_set_threshold(void); +static void slider_object_process_state(void); +static void slider_object_update_state(touch_pad_t channel_num, te_state_t channel_state); +/* ------------------------------------------------------------------------------------------------------------------ */ + +esp_err_t touch_slider_install(const touch_slider_global_config_t *global_config) +{ + TE_CHECK(te_system_check_state() == true, ESP_ERR_INVALID_STATE); + TE_CHECK(global_config != NULL, ESP_ERR_INVALID_ARG); + //Fixme: Make it thread-safe + s_te_sld_obj = (te_slider_obj_t *)calloc(1, sizeof(te_slider_obj_t)); + TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_NO_MEM); + s_te_sld_obj->global_config = (touch_slider_global_config_t *)calloc(1, sizeof(touch_slider_global_config_t)); + s_te_sld_obj->mutex = xSemaphoreCreateMutex(); + TE_CHECK_GOTO(s_te_sld_obj->global_config != NULL && s_te_sld_obj->mutex != NULL, cleanup); + xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); + SLIST_INIT(&s_te_sld_obj->handle_list); + memcpy(s_te_sld_obj->global_config, global_config, sizeof(touch_slider_global_config_t)); + te_object_methods_t slider_methods = { + .handle = s_te_sld_obj, + .check_channel = slider_object_check_channel, + .set_threshold = slider_object_set_threshold, + .process_state = slider_object_process_state, + .update_state = slider_object_update_state + }; + te_object_method_register(&slider_methods, TE_CLS_TYPE_SLIDER); + xSemaphoreGive(s_te_sld_obj->mutex); + return ESP_OK; + +cleanup: + TE_FREE_AND_NULL(s_te_sld_obj->global_config); + if (s_te_sld_obj->mutex != NULL) { + vSemaphoreDelete(s_te_sld_obj->mutex); + } + TE_FREE_AND_NULL(s_te_sld_obj); + return ESP_ERR_NO_MEM; +} + +void touch_slider_uninstall(void) +{ + xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); + if (s_te_sld_obj == NULL) { + xSemaphoreGive(s_te_sld_obj->mutex); + return; + } + te_object_method_unregister(TE_CLS_TYPE_SLIDER); + free(s_te_sld_obj->global_config); + s_te_sld_obj->global_config = NULL; + while (!SLIST_EMPTY(&s_te_sld_obj->handle_list)) { + SLIST_FIRST(&s_te_sld_obj->handle_list); + SLIST_REMOVE_HEAD(&s_te_sld_obj->handle_list, next); + } + xSemaphoreGive(s_te_sld_obj->mutex); + vSemaphoreDelete(s_te_sld_obj->mutex); + free(s_te_sld_obj); + s_te_sld_obj = NULL; +} + +esp_err_t touch_slider_create(const touch_slider_config_t *slider_config, touch_slider_handle_t *slider_handle) +{ + TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(slider_handle != NULL && slider_config != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(slider_config->channel_num > 2 && + slider_config->channel_num < TOUCH_PAD_MAX && + slider_config->channel_array != NULL && + slider_config->sensitivity_array != NULL && + slider_config->position_range > slider_config->channel_num, + ESP_ERR_INVALID_ARG); + TE_CHECK(te_object_check_channel(slider_config->channel_array, slider_config->channel_num) == false, + ESP_ERR_INVALID_ARG); + te_slider_handle_t te_slider = (te_slider_handle_t)calloc(1, sizeof(struct te_slider_s)); + TE_CHECK(te_slider != NULL, ESP_ERR_NO_MEM); + + esp_err_t ret = ESP_ERR_NO_MEM; + te_slider->config = (te_slider_handle_config_t *)calloc(1, sizeof(te_slider_handle_config_t)); + te_slider->pos_filter_window = calloc(TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj), sizeof(uint8_t)); + te_slider->device = (te_dev_t **)calloc(slider_config->channel_num, sizeof(te_dev_t *)); + te_slider->channel_bcm = (uint32_t *)calloc(slider_config->channel_num, sizeof(uint32_t)); + te_slider->quantify_signal_array = (float *)calloc(slider_config->channel_num, sizeof(float)); + TE_CHECK_GOTO(te_slider->config != NULL && + te_slider->pos_filter_window != NULL && + te_slider->device != NULL && + te_slider->channel_bcm && + te_slider->quantify_signal_array, + cleanup); + for (int idx = 0; idx < slider_config->channel_num; idx++) { + te_slider->device[idx] = (te_dev_t *)calloc(1, sizeof(te_dev_t)); + if (te_slider->device[idx] == NULL) { + ret = ESP_ERR_NO_MEM; + goto cleanup; + } + } + ret = te_dev_init(te_slider->device, slider_config->channel_num, TOUCH_ELEM_TYPE_SLIDER, + slider_config->channel_array, slider_config->sensitivity_array, + TE_DEFAULT_THRESHOLD_DIVIDER(s_te_sld_obj)); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + + te_slider->config->event_mask = TOUCH_ELEM_EVENT_NONE; + te_slider->config->dispatch_method = TOUCH_ELEM_DISP_MAX; + te_slider->config->callback = NULL; + te_slider->config->arg = NULL; + te_slider->channel_bcm_update_cnt = TE_SLD_DEFAULT_BCM_UPDATE_TIME(s_te_sld_obj); //update at first time + te_slider->filter_reset_cnt = TE_SLD_DEFAULT_FILTER_RESET_TIME(s_te_sld_obj); //reset at first time + te_slider->channel_sum = slider_config->channel_num; + te_slider->position_range = slider_config->position_range; + te_slider->position_scale = (float)(slider_config->position_range) / (slider_config->channel_num - 1); + te_slider->current_state = TE_STATE_IDLE; + te_slider->last_state = TE_STATE_IDLE; + te_slider->event = TOUCH_SLIDER_EVT_MAX; + te_slider->position = 0; + te_slider->last_position = 0; + te_slider->pos_window_idx = 0; + te_slider->is_first_sample = true; + ret = slider_object_add_instance(te_slider); + TE_CHECK_GOTO(ret == ESP_OK, cleanup); + *slider_handle = (touch_elem_handle_t)te_slider; + return ESP_OK; + +cleanup: + TE_FREE_AND_NULL(te_slider->config); + TE_FREE_AND_NULL(te_slider->pos_filter_window); + TE_FREE_AND_NULL(te_slider->channel_bcm); + TE_FREE_AND_NULL(te_slider->quantify_signal_array); + if (te_slider->device != NULL) { + for (int idx = 0; idx < slider_config->channel_num; idx++) { + TE_FREE_AND_NULL(te_slider->device[idx]); + } + free(te_slider->device); + te_slider->device = NULL; + } + TE_FREE_AND_NULL(te_slider); + return ret; +} + +esp_err_t touch_slider_delete(touch_slider_handle_t slider_handle) +{ + TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(slider_handle != NULL, ESP_ERR_INVALID_ARG); + /*< Release touch sensor application resource */ + esp_err_t ret = slider_object_remove_instance(slider_handle); + TE_CHECK(ret == ESP_OK, ret); + te_slider_handle_t te_slider = (te_slider_handle_t)slider_handle; + /*< Release touch sensor device resource */ + te_dev_deinit(te_slider->device, te_slider->channel_sum); + for (int idx = 0; idx < te_slider->channel_sum; idx++) { + free(te_slider->device[idx]); + } + free(te_slider->config); + free(te_slider->quantify_signal_array); + free(te_slider->pos_filter_window); + free(te_slider->channel_bcm); + free(te_slider->device); + free(te_slider); + return ESP_OK; +} + +esp_err_t touch_slider_set_dispatch_method(touch_slider_handle_t slider_handle, touch_elem_dispatch_t dispatch_method) +{ + TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(slider_handle != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(dispatch_method >= TOUCH_ELEM_DISP_EVENT && dispatch_method <= TOUCH_ELEM_DISP_MAX, ESP_ERR_INVALID_ARG); + xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); + te_slider_handle_t te_slider = (te_slider_handle_t)slider_handle; + te_slider->config->dispatch_method = dispatch_method; + xSemaphoreGive(s_te_sld_obj->mutex); + return ESP_OK; +} + +esp_err_t touch_slider_subscribe_event(touch_slider_handle_t slider_handle, uint32_t event_mask, void *arg) +{ + TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(slider_handle != NULL, ESP_ERR_INVALID_ARG); + if (!(event_mask & TOUCH_ELEM_EVENT_ON_PRESS) && !(event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) && + !(event_mask & TOUCH_ELEM_EVENT_NONE) && !(event_mask & TOUCH_ELEM_EVENT_ON_CALCULATION)) { + ESP_LOGE(TE_TAG, "Touch button only support TOUCH_ELEM_EVENT_ON_PRESS, " + "TOUCH_ELEM_EVENT_ON_RELEASE, TOUCH_ELEM_EVENT_ON_CALCULATION event mask"); + return ESP_ERR_INVALID_ARG; + } + xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); + te_slider_handle_t te_slider = (te_slider_handle_t)slider_handle; + te_slider->config->event_mask = event_mask; + te_slider->config->arg = arg; + xSemaphoreGive(s_te_sld_obj->mutex); + return ESP_OK; +} + +esp_err_t touch_slider_set_callback(touch_slider_handle_t slider_handle, touch_slider_callback_t slider_callback) +{ + TE_CHECK(s_te_sld_obj != NULL, ESP_ERR_INVALID_STATE); + TE_CHECK(slider_handle != NULL, ESP_ERR_INVALID_ARG); + TE_CHECK(slider_callback != NULL, ESP_ERR_INVALID_ARG); + te_slider_handle_t te_slider = (te_slider_handle_t)slider_handle; + xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); + te_slider->config->callback = slider_callback; + xSemaphoreGive(s_te_sld_obj->mutex); + return ESP_OK; +} + +const touch_slider_message_t* touch_slider_get_message(const touch_elem_message_t* element_message) +{ + return (touch_slider_message_t*)&element_message->child_msg; + _Static_assert(sizeof(element_message->child_msg) >= sizeof(touch_slider_message_t), "Message size overflow"); +} + +static bool slider_object_check_channel(touch_pad_t channel_num) +{ + te_slider_handle_list_t *item; + SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { + if (slider_channel_check(item->slider_handle, channel_num)) { + return true; + } + } + return false; +} + +static esp_err_t slider_object_set_threshold(void) +{ + esp_err_t ret = ESP_OK; + te_slider_handle_list_t *item; + SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { + ret = slider_set_threshold(item->slider_handle); + if (ret != ESP_OK) { + break; + } + } + return ret; +} + +static void slider_object_process_state(void) +{ + te_slider_handle_list_t *item; + SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { + if (waterproof_check_mask_handle(item->slider_handle)) { + slider_reset_state(item->slider_handle); + slider_reset_position(item->slider_handle); + continue; + } + slider_proc_state(item->slider_handle); + } +} + +static void slider_object_update_state(touch_pad_t channel_num, te_state_t channel_state) +{ + te_slider_handle_list_t *item; + SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { + if (waterproof_check_mask_handle(item->slider_handle)) { + continue; + } + slider_update_state(item->slider_handle, channel_num, channel_state); + } +} + +static esp_err_t slider_object_add_instance(te_slider_handle_t slider_handle) +{ + te_slider_handle_list_t *item = (te_slider_handle_list_t *)calloc(1, sizeof(te_slider_handle_list_t)); + TE_CHECK(item != NULL, ESP_ERR_NO_MEM); + item->slider_handle = slider_handle; + xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); + SLIST_INSERT_HEAD(&s_te_sld_obj->handle_list, item, next); + xSemaphoreGive(s_te_sld_obj->mutex); + return ESP_OK; +} + +static esp_err_t slider_object_remove_instance(te_slider_handle_t slider_handle) +{ + esp_err_t ret = ESP_ERR_NOT_FOUND; + te_slider_handle_list_t *item; + SLIST_FOREACH(item, &s_te_sld_obj->handle_list, next) { + if (slider_handle == item->slider_handle) { + xSemaphoreTake(s_te_sld_obj->mutex, portMAX_DELAY); + SLIST_REMOVE(&s_te_sld_obj->handle_list, item, te_slider_handle_list, next); + xSemaphoreGive(s_te_sld_obj->mutex); + free(item); + ret = ESP_OK; + break; + } + } + return ret; +} + +static bool slider_channel_check(te_slider_handle_t slider_handle, touch_pad_t channel_num) +{ + te_dev_t *device; + for (int idx = 0; idx < slider_handle->channel_sum; idx++) { + device = slider_handle->device[idx]; + if (device->channel == channel_num) { + return true; + } + } + return false; +} + +static esp_err_t slider_set_threshold(te_slider_handle_t slider_handle) +{ + esp_err_t ret = ESP_OK; + for (int idx = 0; idx < slider_handle->channel_sum; idx++) { + ret |= te_dev_set_threshold(slider_handle->device[idx]); + } + slider_update_benchmark(slider_handle); //Update benchmark at startup + return ret; +} + +static void slider_update_benchmark(te_slider_handle_t slider_handle) +{ + for (int idx = 0; idx < slider_handle->channel_sum; idx++) { + uint32_t bcm_val; + te_dev_t *device = slider_handle->device[idx]; + bcm_val = te_read_smooth_signal(device->channel); + slider_handle->channel_bcm[idx] = bcm_val; + } +} + +static void slider_update_state(te_slider_handle_t slider_handle, touch_pad_t channel_num, te_state_t channel_state) +{ + te_dev_t *device; + for (int idx = 0; idx < slider_handle->channel_sum; idx++) { + device = slider_handle->device[idx]; + if (channel_num == device->channel) { + device->state = channel_state; + } + } +} + +static void slider_reset_state(te_slider_handle_t slider_handle) +{ + for (int idx = 0; idx < slider_handle->channel_sum; idx++) { + slider_handle->device[idx]->state = TE_STATE_IDLE; + } + slider_handle->current_state = TE_STATE_IDLE; +} + +static void slider_event_give(te_slider_handle_t slider_handle) +{ + touch_elem_message_t element_message; + touch_slider_message_t slider_message = { + .event = slider_handle->event, + .position = slider_handle->position + }; + element_message.handle = (touch_elem_handle_t)slider_handle; + element_message.element_type = TOUCH_ELEM_TYPE_SLIDER; + element_message.arg = slider_handle->config->arg; + memcpy(element_message.child_msg, &slider_message, sizeof(slider_message)); + te_event_give(element_message); +} + +static inline void slider_dispatch(te_slider_handle_t slider_handle, touch_elem_dispatch_t dispatch_method) +{ + if (dispatch_method == TOUCH_ELEM_DISP_EVENT) { + slider_event_give(slider_handle); //Event queue + } else if (dispatch_method == TOUCH_ELEM_DISP_CALLBACK) { + touch_slider_message_t slider_info; + slider_info.event = slider_handle->event; + slider_info.position = slider_handle->position; + void *arg = slider_handle->config->arg; + slider_handle->config->callback(slider_handle, slider_info, arg); //Event callback + } +} + +/** + * @brief Slider process + * + * This function will process the slider state and maintain a slider FSM: + * IDLE ----> Press ----> Release ----> IDLE + * + * The state transition procedure is as follow: + * (channel state ----> slider state) + * + * TODO: add state transition diagram + */ +static void slider_proc_state(te_slider_handle_t slider_handle) +{ + uint32_t event_mask = slider_handle->config->event_mask; + touch_elem_dispatch_t dispatch_method = slider_handle->config->dispatch_method; + BaseType_t mux_ret = xSemaphoreTake(s_te_sld_obj->mutex, 0); + if (mux_ret != pdPASS) { + return; + } + + slider_handle->current_state = slider_get_state(slider_handle->device, slider_handle->channel_sum); + + if (slider_handle->current_state == TE_STATE_PRESS) { + slider_handle->channel_bcm_update_cnt = 0; // Reset benchmark update counter + slider_update_position(slider_handle); + if (slider_handle->last_state == TE_STATE_IDLE) { //IDLE ---> Press = On_Press + ESP_LOGD(TE_DEBUG_TAG, "slider press"); + if (event_mask & TOUCH_ELEM_EVENT_ON_PRESS) { + slider_handle->event = TOUCH_SLIDER_EVT_ON_PRESS; + slider_dispatch(slider_handle, dispatch_method); + } + } else if (slider_handle->last_state == TE_STATE_PRESS) { //Press ---> Press = On_Calculation + ESP_LOGD(TE_DEBUG_TAG, "slider calculation"); + if (event_mask & TOUCH_ELEM_EVENT_ON_CALCULATION) { + slider_handle->event = TOUCH_SLIDER_EVT_ON_CALCULATION; + slider_dispatch(slider_handle, dispatch_method); + } + } + } else if (slider_handle->current_state == TE_STATE_RELEASE) { + if (slider_handle->last_state == TE_STATE_PRESS) { //Press ---> Release = On_Release + ESP_LOGD(TE_DEBUG_TAG, "slider release"); + if (event_mask & TOUCH_ELEM_EVENT_ON_RELEASE) { + slider_handle->event = TOUCH_SLIDER_EVT_ON_RELEASE; + slider_dispatch(slider_handle, dispatch_method); + } + } else if (slider_handle->last_state == TE_STATE_RELEASE) { // Release ---> Release = On_IDLE (Not dispatch) + slider_reset_state(slider_handle);//Reset the slider state for the next time touch action detection + } + } else if (slider_handle->current_state == TE_STATE_IDLE) { + if (slider_handle->last_state == TE_STATE_RELEASE) { //Release ---> IDLE = On_IDLE (Not dispatch) + //Nothing + } else if (slider_handle->last_state == TE_STATE_IDLE) { //IDLE ---> IDLE = Running IDLE (Not dispatch) + if (++slider_handle->channel_bcm_update_cnt >= TE_SLD_DEFAULT_BCM_UPDATE_TIME(s_te_sld_obj)) { //Update channel benchmark + slider_handle->channel_bcm_update_cnt = 0; + slider_update_benchmark(slider_handle); + ESP_LOGD(TE_DEBUG_TAG, "slider bcm update"); + } + if (++slider_handle->filter_reset_cnt >= TE_SLD_DEFAULT_FILTER_RESET_TIME(s_te_sld_obj)) { + slider_reset_position(slider_handle); //Reset slider filter so as to speed up next time position calculation + } + } + } + slider_handle->last_state = slider_handle->current_state; + xSemaphoreGive(s_te_sld_obj->mutex); +} + +static inline te_state_t slider_get_state(te_dev_t **device, int device_num) +{ + /*< Scan the state of all the slider channel and calculate the number of them if the state is Press*/ + uint8_t press_cnt = 0; + uint8_t idle_cnt = 0; + for (int idx = 0; idx < device_num; idx++) { //Calculate how many channel is pressed + if (device[idx]->state == TE_STATE_PRESS) { + press_cnt++; + } else if (device[idx]->state == TE_STATE_IDLE) { + idle_cnt++; + } + } + if (press_cnt > 0) { + return TE_STATE_PRESS; + } else if (idle_cnt == device_num) { + return TE_STATE_IDLE; + } else { + return TE_STATE_RELEASE; + } +} + +/** + * @brief Slider channel difference-rate re-quantization + * + * This function will re-quantifies the touch sensor slider channel difference-rate + * so as to make the different size of touch pad in PCB has the same difference value + * + */ +static inline void slider_quantify_signal(te_slider_handle_t slider_handle) +{ + float weight_sum = 0; + for (int idx = 0; idx < slider_handle->channel_sum; idx++) { + te_dev_t *device = slider_handle->device[idx]; + weight_sum += device->sens; + uint32_t current_signal = te_read_smooth_signal(device->channel); + int ans = current_signal - slider_handle->channel_bcm[idx]; + float diff_rate = (float)ans / slider_handle->channel_bcm[idx]; + slider_handle->quantify_signal_array[idx] = diff_rate / device->sens; + if (slider_handle->quantify_signal_array[idx] < TE_SLD_DEFAULT_QTF_THR(s_te_sld_obj)) { + slider_handle->quantify_signal_array[idx] = 0; + } + } + for (int idx = 0; idx < slider_handle->channel_sum; idx++) { + te_dev_t *device = slider_handle->device[idx]; + slider_handle->quantify_signal_array[idx] = slider_handle->quantify_signal_array[idx] * weight_sum / device->sens; + } +} + +/** + * @brief Calculate max sum subarray + * + * This function will figure out the max sum subarray from the + * input array, return the max sum and max sum start index + * + */ +static inline float slider_search_max_subarray(const float *array, int array_size, int *max_array_idx) +{ + *max_array_idx = 0; + float max_array_sum = 0; + float current_array_sum = 0; + for (int idx = 0; idx <= (array_size - TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj)); idx++) { + for (int x = idx; x < idx + TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj); x++) { + current_array_sum += array[x]; + } + if (max_array_sum < current_array_sum) { + max_array_sum = current_array_sum; + *max_array_idx = idx; + } + current_array_sum = 0; + } + return max_array_sum; +} + +/** + * @brief Calculate zero number + * + * This function will figure out the number of non-zero items from + * the subarray + */ +static inline uint8_t slider_get_non_zero_num(const float *array, uint8_t array_idx) +{ + uint8_t zero_cnt = 0; + for (int idx = array_idx; idx < array_idx + TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj); idx++) { + zero_cnt += (array[idx] > 0) ? 1 : 0; + } + return zero_cnt; +} + +static inline uint32_t slider_calculate_position(te_slider_handle_t slider_handle, int subarray_index, float subarray_sum, int non_zero_num) +{ + int range = slider_handle->position_range; + int array_size = slider_handle->channel_sum; + float scale = slider_handle->position_scale; + const float *array = slider_handle->quantify_signal_array; + uint32_t position = 0; + if (non_zero_num == 0) { + position = slider_handle->position; + } else if (non_zero_num == 1) { + for (int index = subarray_index; index < subarray_index + TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj); index++) { + if (0 != array[index]) { + if (index == array_size - 1) { + position = range; + } else { + position = (uint32_t)((float)index * scale); + } + break; + } + } + } else { + for (int idx = subarray_index; idx < subarray_index + TE_SLD_DEFAULT_CALCULATE_CHANNEL(s_te_sld_obj); idx++) { + position += ((float)idx * array[idx]); + } + position = position * scale / subarray_sum; + } + return position; +} + +static uint32_t slider_filter_average(te_slider_handle_t slider_handle, uint32_t current_position) +{ + uint32_t position_average = 0; + if (slider_handle->is_first_sample) { + for (int win_idx = 0; win_idx < TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj); win_idx++) { + slider_handle->pos_filter_window[win_idx] = current_position; //Preload filter buffer + } + slider_handle->is_first_sample = false; + } else { + slider_handle->pos_filter_window[slider_handle->pos_window_idx++] = current_position; //Moving average filter + if (slider_handle->pos_window_idx >= TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj)) { + slider_handle->pos_window_idx = 0; + } + } + + for (int win_idx = 0; win_idx < TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj); win_idx++) { //Moving average filter + position_average += slider_handle->pos_filter_window[win_idx]; + } + position_average = position_average / TE_SLD_DEFAULT_POS_FILTER_SIZE(s_te_sld_obj) + 0.5; + return position_average; +} + +static inline uint32_t slider_filter_iir(uint32_t in_now, uint32_t out_last, uint32_t k) +{ + if (k == 0) { + return in_now; + } else { + uint32_t out_now = (in_now + (k - 1) * out_last) / k; + return out_now; + } +} + +/** + * @brief touch sensor slider position update + * + * This function is the core algorithm about touch sensor slider + * position update, mainly has several steps: + * 1. Re-quantization + * 2. Figure out changed channel + * 3. Calculate position + * 4. Filter + * + */ +static void slider_update_position(te_slider_handle_t slider_handle) +{ + int max_array_idx = 0; + float max_array_sum; + uint8_t non_zero_num; + uint32_t current_position; + + slider_quantify_signal(slider_handle); + max_array_sum = slider_search_max_subarray(slider_handle->quantify_signal_array, slider_handle->channel_sum, &max_array_idx); + non_zero_num = slider_get_non_zero_num(slider_handle->quantify_signal_array, max_array_idx); + current_position = slider_calculate_position(slider_handle, max_array_idx, max_array_sum, non_zero_num); + uint32_t position_average = slider_filter_average(slider_handle, current_position); + slider_handle->last_position = slider_handle->last_position == 0 ? (position_average << 4) : slider_handle->last_position; + slider_handle->last_position = slider_filter_iir((position_average << 4), slider_handle->last_position, TE_SLD_DEFAULT_POS_FILTER_FACTOR(s_te_sld_obj)); + slider_handle->position = ((slider_handle->last_position + 8) >> 4); //(x + 8) >> 4 ----> (x + 8) / 16 ----> x/16 + 0.5 +} + +static void slider_reset_position(te_slider_handle_t slider_handle) +{ + slider_handle->is_first_sample = true; + slider_handle->last_position = 0; + slider_handle->pos_window_idx = 0; +}