diff --git a/README.md b/README.md index f56facd..0f5ff2d 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,27 @@ # ESP32 ESP-IDF component for TTP229 touch pad (via RMT driver) -## Tested on - -1. [ESP32 ESP-IDF v6.0.0](https://docs.espressif.com/projects/esp-idf/en/v6.0/esp32/index.html) - ## SAST Tools [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. ## Features -1. Support up to 4 touch pads on one device (depends on the used ESP32 chip). -2. Support 8 or 16 pad work mode. +1. Support for 8-pad and 16-pad TTP229 configurations. +2. RMT-based SPI-like communication without GPIO bit-banging. +3. Multi-device support with unique device numbering. +4. FreeRTOS task for debouncing and event dispatching. +5. Error statistics tracking (RMT driver, event post, queue overflow, stack). +6. Event-based API via esp_event framework. +7. IRAM-safe ISR handler and RMT callback. -## Note +## Attention 1. After start/reset first touch not work. 2. Multi touch not supported. -## Attention +## Note -1. For correct operation, please enable the following settings in the menuconfig: +Enable the following settings in menuconfig: ```text CONFIG_GPIO_CTRL_FUNC_IN_IRAM @@ -36,7 +37,7 @@ CONFIG_RMT_RX_ISR_CACHE_SAFE In an existing project, run the following command to install the components: -```text +```bash cd ../your_project/components git clone https://github.com/aZholtikov/zh_ttp229 ``` @@ -47,16 +48,14 @@ In the application, add the component: #include "zh_ttp229.h" ``` -## Examples - -One touch pad on device: +## Example ```c #include "zh_ttp229.h" #define TTP229_NUMBER 0x01 -zh_ttp229_handle_t ttp229_handle = {0}; +zh_ttp229_handle_t *ttp229_handle = NULL; void zh_ttp229_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 0000000..e0381fa --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,11 @@ +description: ESP32 ESP-IDF component for TTP229 touch pad (via RMT driver) +url: https://github.com/aZholtikov/zh_ttp229 +version: "2.0.0" +license: "Apache-2.0" +maintainers: + - "Alexey Zholtikov " +dependencies: + idf: ">=5.0" + zh_vector: + git: "https://github.com/aZholtikov/zh_vector.git" + version: "dev" \ No newline at end of file diff --git a/include/zh_ttp229.h b/include/zh_ttp229.h index fcc62af..13fc248 100644 --- a/include/zh_ttp229.h +++ b/include/zh_ttp229.h @@ -1,5 +1,22 @@ /** * @file zh_ttp229.h + * + * @brief Driver for TTP229 capacitive touch pad controller via SPI-like + * interface (SCL/SDO) using ESP-IDF RMT peripheral. + * + * This module provides a FreeRTOS-based driver for the TTP229 touch + * controller. It uses the RMT transmitter to generate SCL clock pulses + * and the RMT receiver to capture SDO data, enabling reading of touch + * status from 8 or 16 pad configurations. Each touch event triggers an + * ISR that initiates RMT transfer and posts an event with the touched + * pad number. + * + * Key features: + * - Supports 8-pad and 16-pad TTP229 configurations + * - RMT-based SPI-like communication (no GPIO bit-banging) + * - FreeRTOS task for debouncing and event dispatching + * - Per-device unique numbering for multi-device support + * - Error statistics tracking (RMT, queue, stack) */ #pragma once @@ -11,10 +28,8 @@ #include "driver/rmt_rx.h" #include "driver/gpio.h" #include "esp_event.h" +#include "zh_vector.h" -/** - * @brief TTP229 touch pad initial default values. - */ #define ZH_TTP229_INIT_CONFIG_DEFAULT() \ { \ .task_priority = 1, \ @@ -32,108 +47,122 @@ extern "C" { #endif - extern TaskHandle_t zh_ttp229; /*!< Unique task handle. */ + /** + * @brief External task handle for the TTP229 ISR processing task. + */ + extern TaskHandle_t zh_ttp229; /** - * @brief Enumeration of TTP229 touch pad work mode. + * @brief Opaque handle returned by zh_ttp229_init(). + * + * Internal fields are defined in zh_ttp229.c. Users must not access + * the handle's members directly. + */ + typedef struct _zh_ttp229_handle_t zh_ttp229_handle_t; + + /** + * @brief TTP229 touch pad work mode (number of pads). */ typedef enum { - ZH_TTP229_8_PAD = 8, - ZH_TTP229_16_PAD = 16, + ZH_TTP229_8_PAD = 8, /*!< 8-pad TTP229 configuration */ + ZH_TTP229_16_PAD = 16, /*!< 16-pad TTP229 configuration */ } zh_ttp229_work_mode_t; /** - * @brief Structure for initial initialization of TTP229 touch pad. + * @brief Configuration structure for TTP229 initialization. + * + * Use ZH_TTP229_INIT_CONFIG_DEFAULT() to obtain sensible defaults + * and override only the fields that differ from defaults. */ typedef struct { - uint16_t stack_size; /*!< Stack size for task for the TTP229 touch pad isr processing processing. @note The minimum size is configMINIMAL_STACK_SIZE. */ - uint16_t debounce_time; /*!< Touch pad button debounce time. @note Minimum value is 10 (in milliseconds). */ - uint8_t task_priority; /*!< Task priority for the TTP229 touch pad isr processing. @note Minimum value is 1. */ - uint8_t queue_size; /*!< Queue size for task for the touch pad processing. @note Minimum value is 1. */ - uint8_t device_number; /*!< Touch pad unique number. @note Must be greater than 0. */ - uint8_t rmt_tx_start_delay; /*!< The time between the low-level pulse in the interrupt and the actual start time of the RMT signal generator (in microseconds). @note Minimum value is 15. */ - gpio_num_t sdo_gpio; /*!< SDO GPIO. */ - gpio_num_t scl_gpio; /*!< SCL GPIO. */ - zh_ttp229_work_mode_t work_mode; /*!< Touch pad work mode. */ + uint16_t stack_size; /*!< Task stack size in bytes. @note Minimum: configMINIMAL_STACK_SIZE */ + uint16_t debounce_time; /*!< Button debounce time in milliseconds. @note Minimum: 10 ms */ + uint8_t task_priority; /*!< FreeRTOS task priority. @note Minimum: 1 */ + uint8_t queue_size; /*!< Event queue depth. @note Minimum: 1 */ + uint8_t device_number; /*!< Unique device identifier. @note Must be > 0 */ + uint8_t rmt_tx_start_delay; /*!< RMT TX delay in microseconds after SDO interrupt. @note Minimum: 15 us */ + gpio_num_t sdo_gpio; /*!< SDO (Serial Data Out) GPIO pin */ + gpio_num_t scl_gpio; /*!< SCL (Serial Clock) GPIO pin */ + zh_ttp229_work_mode_t work_mode; /*!< Touch pad configuration (8 or 16 pads) */ } zh_ttp229_init_config_t; /** - * @brief TTP229 touch pad handle. + * @brief Error and runtime statistics for the TTP229 driver. + * + * Retrieved via zh_ttp229_get_stats(). Reset with zh_ttp229_reset_stats(). */ typedef struct { - bool is_initialized; /*!< Touch pad initialization flag. */ - gpio_num_t sdo_gpio; /*!< SDO GPIO. */ - uint8_t device_number; /*!< Touch pad unique number. */ - uint8_t rmt_tx_start_delay; /*!< The time between the low-level pulse in the interrupt and the actual start time of the RMT signal generator (in microseconds). */ - uint16_t debounce_time; /*!< Touch pad button debounce_time. */ - zh_ttp229_work_mode_t work_mode; /*!< Touch pad work mode. */ - rmt_symbol_word_t rx_symbols; /*!< Received symbols. */ - rmt_channel_handle_t tx_channel; /*!< Unique RMT TX device handle. */ - rmt_channel_handle_t rx_channel; /*!< Unique RMT RX device handle. */ - rmt_encoder_handle_t copy_encoder; /*!< Unique copy encoder handle. */ - } zh_ttp229_handle_t; - - /** - * @brief Structure for error statistics storage. - */ - typedef struct - { - uint32_t rmt_driver_error; /*!< Number of RMT driver error. */ - uint32_t event_post_error; /*!< Number of event post error. */ - uint32_t queue_overflow_error; /*!< Number of queue overflow error. */ - uint32_t min_stack_size; /*!< Minimum free stack size. */ + uint32_t rmt_driver_error; /*!< Count of RMT driver errors */ + uint32_t event_post_error; /*!< Count of esp_event_post failures */ + uint32_t queue_overflow_error; /*!< Count of event queue overflows */ + uint32_t min_stack_size; /*!< Minimum free stack (bytes) observed */ } zh_ttp229_stats_t; ESP_EVENT_DECLARE_BASE(ZH_TTP229); /** - * @brief Structure for sending data to the event handler when cause an interrupt. + * @brief Event payload delivered on touch detection. * - * @note Should be used with ZH_TTP229 event base. + * Should be used with the ZH_TTP229 event base. */ typedef struct { - uint8_t pad_number; /*!< The pad that caused the interrupt. */ - uint8_t device_number; /*!< Touch pad unique number. */ + uint8_t pad_number; /*!< Number of the pad that triggered the interrupt (1-based) */ + uint8_t device_number; /*!< Unique device identifier matching the init config */ } zh_ttp229_event_on_isr_t; /** - * @brief Initialize TTP229 touch pad. + * @brief Initialize the TTP229 touch pad driver. * - * @param[in] config Pointer to TTP229 initialized configuration structure. Can point to a temporary variable. - * @param[out] handle Pointer to unique TTP229 handle. + * Allocates a handle, validates the configuration, initializes RMT + * TX/RMT channels, installs the GPIO ISR handler, and creates the + * processing task. Supports multiple devices via unique device + * numbers. * - * @attention I2C driver must be initialized first. + * @param[in] config Pointer to initialization configuration (must not be NULL) + * @param[out] handle Pointer to unique touch pad handle (must not be NULL) * - * @note Before initialize the expander recommend initialize zh_ttp229_init_config_t structure with default values. - * - * @code zh_ttp229_init_config_t config = ZH_TTP229_INIT_CONFIG_DEFAULT() @endcode - * - * @return ESP_OK if success or an error code otherwise. + * @return ESP_OK on success + * @return ESP_ERR_INVALID_ARG if config or handle is NULL, or if any configuration value is out of range + * @return ESP_ERR_INVALID_STATE if the device is already initialized + * @return ESP_FAIL on resource allocation or peripheral failure */ - esp_err_t zh_ttp229_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle); + esp_err_t zh_ttp229_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t **handle); /** - * @brief Deinitialize TTP229 touch pad. + * @brief Deinitialize a TTP229 touch pad instance. * - * @param[in] handle Pointer to unique TTP229 handle. + * Disables RMT channels, removes the GPIO ISR handler, frees the + * handle, and removes the device from the internal device list. + * The processing task and shared resources are deleted only when + * the last device is deinitialized. * - * @return ESP_OK if success or an error code otherwise. + * @param[in,out] handle Pointer to unique touch pad handle (must not be NULL) + * + * @return ESP_OK on success + * @return ESP_ERR_INVALID_ARG if handle is NULL or points to a device not found in the internal list */ - esp_err_t zh_ttp229_deinit(zh_ttp229_handle_t *handle); + esp_err_t zh_ttp229_deinit(zh_ttp229_handle_t **handle); /** - * @brief Get error statistics. + * @brief Retrieve a pointer to the current error statistics. * - * @return Pointer to the statistics structure. + * The returned pointer remains valid until zh_ttp229_reset_stats() + * is called or the module is deinitialized. + * + * @return Pointer to the static zh_ttp229_stats_t structure */ const zh_ttp229_stats_t *zh_ttp229_get_stats(void); /** - * @brief Reset error statistics. + * @brief Reset all error and runtime statistics to zero. + * + * Call this function to clear accumulated counters. The + * min_stack_size field is also cleared and will be updated on the + * next processing cycle. */ void zh_ttp229_reset_stats(void); diff --git a/version.txt b/version.txt index 7f20734..359a5b9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.0.1 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/zh_ttp229.c b/zh_ttp229.c index e230e75..9ab5d28 100644 --- a/zh_ttp229.c +++ b/zh_ttp229.c @@ -13,96 +13,208 @@ static const char *TAG = "zh_ttp229"; return err; \ } +#define ZH_ERROR_CHECK_CONT(cond, cleanup, msg, ...) \ + if (!(cond)) \ + { \ + ZH_LOGE(msg, ESP_FAIL, ##__VA_ARGS__); \ + cleanup; \ + continue; \ + } + +/** @brief Internal handle for a single TTP229 device instance. + * + * Fields are initialized by zh_ttp229_init() and used by the + * ISR handler, processing task, and deinitialization. + */ +struct _zh_ttp229_handle_t +{ + gpio_num_t sdo_gpio; /*!< SDO (Serial Data Out) GPIO pin for touch data */ + uint8_t device_number; /*!< Unique device number assigned at initialization */ + uint8_t rmt_tx_start_delay; /*!< Time between SDO low-level pulse and RMT TX start (in microseconds) */ + uint16_t debounce_time; /*!< Button debounce time in milliseconds */ + zh_ttp229_work_mode_t work_mode; /*!< Touch pad configuration (8 or 16 pads) */ + rmt_symbol_word_t rx_symbols; /*!< Received RMT symbol containing duration1 (pad data) */ + rmt_channel_handle_t tx_channel; /*!< RMT TX channel handle (SCL clock generation) */ + rmt_channel_handle_t rx_channel; /*!< RMT RX channel handle (SDO data capture) */ + rmt_encoder_handle_t copy_encoder; /*!< Copy encoder handle for RMT TX */ +}; + +/** @brief Queue entry passed from RMT RX callback to the ISR processing task. + * + * Contains a pointer to the TTP229 device handle for the device + * that triggered the interrupt. + */ typedef struct { - zh_ttp229_handle_t *handle; + zh_ttp229_handle_t *handle; /*!< Pointer to the TTP229 device handle */ } zh_ttp229_queue_t; -TaskHandle_t zh_ttp229 = NULL; -static QueueHandle_t _queue_handle = NULL; - -volatile static uint8_t _pad_counter = {0}; -volatile static uint8_t _pad_number_matrix[4] = {0}; -static zh_ttp229_stats_t _stats = {0}; +TaskHandle_t zh_ttp229 = NULL; /*!< FreeRTOS task handle for the ISR processing task */ +static QueueHandle_t _queue_handle = NULL; /*!< Internal event queue */ +static zh_ttp229_stats_t _stats = {0}; /*!< Accumulated error and runtime statistics */ +static zh_vector_t *_vector = NULL; /*!< Vector of device_number values for all initialized TTP229 devices */ +/** @brief Validate the user-provided initialization configuration. + * + * Checks all parameter ranges, ensures work_mode is valid, and + * verifies device_number uniqueness across initialized devices. + * + * @param config Pointer to the user configuration + * @param handle Pointer to the allocated handle (partially filled) + * + * @return ESP_OK if all checks pass + * @return ESP_ERR_INVALID_ARG on invalid parameter values + */ static esp_err_t _zh_ttp229_validate_config(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle); + +/** @brief Configure GPIO for SDO input with positive-edge interrupt. + * + * Installs the ISR service if not yet installed, configures SDO as + * input with GPIO_INTR_POSEDGE, and registers the ISR handler. + * + * @param config Pointer to the user configuration + * @param handle Pointer to the handle (stores sdo_gpio) + * + * @return ESP_OK on success + * @return ESP_ERR_INVALID_ARG if GPIO pins are invalid or identical + * @return ESP_FAIL on ISR service or handler installation failure + */ static esp_err_t _zh_ttp229_gpio_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle); + +/** @brief Initialize RMT TX and RX channels with callbacks. + * + * Creates TX channel on SCL GPIO, RX channel on SDO GPIO, allocates + * the copy encoder, enables both channels, and registers the RX done + * callback. + * + * @param config Pointer to the user configuration + * @param handle Pointer to the handle (stores RMT handles) + * + * @return ESP_OK on success + * @return ESP_FAIL on RMT channel, encoder, or callback registration failure + */ static esp_err_t _zh_ttp229_rmt_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle); + +/** @brief Create the shared event queue on first device initialization. + * + * The queue is created only when vector_size equals 1 (first device). + * + * @param config Pointer to the user configuration + * + * @return ESP_OK on success + * @return ESP_FAIL if queue allocation fails + */ static esp_err_t _zh_ttp229_resources_init(const zh_ttp229_init_config_t *config); + +/** @brief Create the ISR processing task on first device initialization. + * + * The task is created only when vector_size equals 1 (first device). + * + * @param config Pointer to the user configuration + * + * @return ESP_OK on success + * @return ESP_FAIL if task creation fails + */ static esp_err_t _zh_ttp229_task_init(const zh_ttp229_init_config_t *config); + +/** @brief GPIO ISR handler triggered on SDO positive edge. + * + * Disables further interrupts, starts RMT receive on SDO, generates + * SCL clock pulses via RMT transmit on SCL, and yields to higher + * priority tasks. + * + * @param arg Pointer to the zh_ttp229_handle_t for this device + */ static void _zh_ttp229_isr_handler(void *arg); + +/** @brief FreeRTOS task that processes RMT RX data and posts events. + * + * Receives queue entries from the RMT RX callback, decodes the pad + * number from rx_symbols.duration1, posts a ZH_TTP229 event for + * detected touches, debounces by delaying, then re-enables GPIO + * interrupts. Runs until the queue is deleted. + * + * @param pvParameter Unused + */ static void _zh_ttp229_isr_processing_task(void *pvParameter); + +/** @brief RMT RX done callback that enqueues data to the processing task. + * + * Sends a zh_ttp229_queue_t entry to _queue_handle. Increments + * queue overflow error counter if the send fails. + * + * @param channel RMT channel handle (unused) + * @param edata RX done event data (unused) + * @param user_data Pointer to the zh_ttp229_handle_t + * + * @return true if a context switch was requested + * @return false otherwise + */ static bool _zh_ttp229_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data); ESP_EVENT_DEFINE_BASE(ZH_TTP229); -esp_err_t zh_ttp229_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle) // -V2008 +esp_err_t zh_ttp229_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t **handle) // -V2008 { ZH_LOGI("Touch pad initialization started."); ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Touch pad initialization failed. Invalid argument."); - ZH_ERROR_CHECK(handle->is_initialized == false, ESP_ERR_INVALID_STATE, NULL, "Touch pad initialization failed. Touch pad is already initialized."); - ZH_ERROR_CHECK(_pad_counter < sizeof(_pad_number_matrix), ESP_ERR_INVALID_ARG, NULL, "Touch pad initialization failed. Maximum quantity reached."); - ZH_ERROR_CHECK(_zh_ttp229_validate_config(config, handle) == ESP_OK, ESP_FAIL, NULL, "Touch pad initialization failed. Initial configuration check failed."); - ZH_ERROR_CHECK(_zh_ttp229_resources_init(config) == ESP_OK, ESP_FAIL, NULL, "Touch pad initialization failed. Resources initialization failed."); + ZH_ERROR_CHECK(*handle == NULL, ESP_ERR_INVALID_STATE, NULL, "Touch pad initialization failed. Touch pad is already initialized."); + *handle = heap_caps_calloc(1, sizeof(zh_ttp229_handle_t), MALLOC_CAP_8BIT); + ZH_ERROR_CHECK(_zh_ttp229_validate_config(config, *handle) == ESP_OK, ESP_FAIL, heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. Initial configuration check failed."); + if (_vector == NULL) + { + ZH_ERROR_CHECK(zh_vector_init(&_vector, sizeof(uint8_t)) == ESP_OK, ESP_FAIL, heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. Failed to create vector."); + } + ZH_ERROR_CHECK(zh_vector_push_back(&_vector, &config->device_number) == ESP_OK, ESP_FAIL, heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. Failed to add vector data."); + ZH_ERROR_CHECK(_zh_ttp229_resources_init(config) == ESP_OK, ESP_FAIL, zh_vector_delete_back(&_vector); heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. Resources initialization failed."); + // clang-format off ZH_ERROR_CHECK(_zh_ttp229_task_init(config) == ESP_OK, ESP_FAIL, - vQueueDelete(_queue_handle), "Touch pad initialization failed. Processing task initialization failed."); - ZH_ERROR_CHECK(_zh_ttp229_rmt_init(config, handle) == ESP_OK, ESP_FAIL, - vQueueDelete(_queue_handle); - vTaskDelete(zh_ttp229), "Touch pad initialization failed. RMT initialization failed."); - ZH_ERROR_CHECK(_zh_ttp229_gpio_init(config, handle) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(rmt_disable(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel disable failed."); - ZH_ERROR_CHECK(rmt_disable(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "RX channel disable failed."); - ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete TX channel failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete RX channel failed."); - vQueueDelete(_queue_handle); - vTaskDelete(zh_ttp229), "Encoder initialization failed. GPIO initialization failed."); + zh_vector_delete_back(&_vector); heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. Processing task initialization failed."); + ZH_ERROR_CHECK(_zh_ttp229_rmt_init(config, *handle) == ESP_OK, ESP_FAIL, + zh_vector_delete_back(&_vector); heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. RMT initialization failed."); + // clang-format on + ZH_ERROR_CHECK(_zh_ttp229_gpio_init(config, *handle) == ESP_OK, ESP_FAIL, + {ZH_ERROR_CHECK(rmt_disable((*handle)->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel disable failed.")}; + {ZH_ERROR_CHECK(rmt_disable((*handle)->rx_channel) == ESP_OK, ESP_FAIL, NULL, "RX channel disable failed.")}; + {ZH_ERROR_CHECK(rmt_del_encoder((*handle)->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed.")}; + {ZH_ERROR_CHECK(rmt_del_channel((*handle)->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete TX channel failed.")}; + {ZH_ERROR_CHECK(rmt_del_channel((*handle)->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete RX channel failed.")}; + zh_vector_delete_back(&_vector); heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. GPIO initialization failed."); if (_stats.min_stack_size == 0) { _stats.min_stack_size = config->stack_size; } - handle->debounce_time = config->debounce_time; - handle->is_initialized = true; - ++_pad_counter; - for (uint8_t i = 0; i < sizeof(_pad_number_matrix); ++i) - { - if (_pad_number_matrix[i] == 0) - { - _pad_number_matrix[i] = handle->device_number; - break; - } - } + (*handle)->debounce_time = config->debounce_time; ZH_LOGI("Touch pad initialization completed successfully."); return ESP_OK; } -esp_err_t zh_ttp229_deinit(zh_ttp229_handle_t *handle) // -V2008 +esp_err_t zh_ttp229_deinit(zh_ttp229_handle_t **handle) // -V2008 { ZH_LOGI("Touch pad deinitialization started."); - ZH_ERROR_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Touch pad deinitialization failed. Invalid argument."); - ZH_ERROR_CHECK(handle->is_initialized == true, ESP_FAIL, NULL, "Touch pad deinitialization failed. Touch pad not initialized."); - ZH_ERROR_CHECK(rmt_disable(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. TX channel disable failed."); - ZH_ERROR_CHECK(rmt_disable(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. RX channel disable failed."); - ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Delete encoder failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Delete TX channel failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Delete RX channel failed."); - ZH_ERROR_CHECK(gpio_isr_handler_remove((gpio_num_t)handle->sdo_gpio) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Remove GPIO isr handler failed."); - ZH_ERROR_CHECK(gpio_reset_pin((gpio_num_t)handle->sdo_gpio) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Reset GPIO failed."); - if (_pad_counter == 1) + ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Touch pad deinitialization failed. Invalid argument."); + ZH_ERROR_CHECK(rmt_disable((*handle)->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. TX channel disable failed."); + ZH_ERROR_CHECK(rmt_disable((*handle)->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. RX channel disable failed."); + ZH_ERROR_CHECK(rmt_del_encoder((*handle)->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Delete encoder failed."); + ZH_ERROR_CHECK(rmt_del_channel((*handle)->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Delete TX channel failed."); + ZH_ERROR_CHECK(rmt_del_channel((*handle)->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Delete RX channel failed."); + ZH_ERROR_CHECK(gpio_isr_handler_remove((*handle)->sdo_gpio) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Remove GPIO isr handler failed."); + ZH_ERROR_CHECK(gpio_reset_pin((*handle)->sdo_gpio) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Reset GPIO failed."); + int32_t index = 0; + ZH_ERROR_CHECK(zh_vector_find_item(&_vector, &(*handle)->device_number, &index) == ESP_ERR_NOT_FOUND, ESP_ERR_INVALID_ARG, NULL, "Touch pad deinitialization failed. Failed to find vector item."); + ZH_ERROR_CHECK(zh_vector_delete_item(&_vector, (uint16_t)index) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Vector delete item failed."); + uint16_t vector_size = 0; + ZH_ERROR_CHECK(zh_vector_get_size(&_vector, &vector_size) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Failed to get vector size."); + if (vector_size == 0) { vQueueDelete(_queue_handle); + _queue_handle = NULL; vTaskDelete(zh_ttp229); + zh_ttp229 = NULL; + ZH_ERROR_CHECK(zh_vector_free(&_vector) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Free vector failed."); } - handle->is_initialized = false; - --_pad_counter; - for (uint8_t i = 0; i < sizeof(_pad_number_matrix); ++i) - { - if (_pad_number_matrix[i] == handle->device_number) - { - _pad_number_matrix[i] = 0; - break; - } - } + heap_caps_free(*handle); + *handle = NULL; ZH_LOGI("Touch pad deinitialization completed successfully."); return ESP_OK; } @@ -130,9 +242,10 @@ static esp_err_t _zh_ttp229_validate_config(const zh_ttp229_init_config_t *confi ZH_ERROR_CHECK(config->rmt_tx_start_delay >= 15, ESP_ERR_INVALID_ARG, NULL, "Invalid RMT TX start delay."); ZH_ERROR_CHECK(config->work_mode == ZH_TTP229_8_PAD || config->work_mode == ZH_TTP229_16_PAD, ESP_ERR_INVALID_ARG, NULL, "Invalid touch pad work mode."); ZH_ERROR_CHECK(config->device_number > 0, ESP_ERR_INVALID_ARG, NULL, "Invalid touch pad number."); - for (uint8_t i = 0; i < sizeof(_pad_number_matrix); ++i) + if (_vector != NULL) { - ZH_ERROR_CHECK(config->device_number != _pad_number_matrix[i], ESP_ERR_INVALID_ARG, NULL, "Touch pad number already present."); + int32_t index = 0; + ZH_ERROR_CHECK(zh_vector_find_item(&_vector, &config->device_number, &index) == ESP_ERR_NOT_FOUND, ESP_ERR_INVALID_ARG, NULL, "Touch pad number already present."); } handle->rmt_tx_start_delay = config->rmt_tx_start_delay; handle->device_number = config->device_number; @@ -151,9 +264,9 @@ static esp_err_t _zh_ttp229_gpio_init(const zh_ttp229_init_config_t *config, zh_ ZH_ERROR_CHECK(gpio_config(&sdo_config) == ESP_OK, ESP_FAIL, NULL, "GPIO initialization failed."); esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_LOWMED); ZH_ERROR_CHECK(err == ESP_OK || err == ESP_ERR_INVALID_STATE, ESP_FAIL, - ZH_ERROR_CHECK(gpio_reset_pin((gpio_num_t)config->sdo_gpio) == ESP_OK, ESP_FAIL, NULL, "Reset GPIO failed."), "Failed install isr service."); - ZH_ERROR_CHECK(gpio_isr_handler_add((gpio_num_t)config->sdo_gpio, _zh_ttp229_isr_handler, handle) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(gpio_reset_pin((gpio_num_t)config->sdo_gpio) == ESP_OK, ESP_FAIL, NULL, "Reset GPIO failed."), "Interrupt initialization failed."); + {ZH_ERROR_CHECK(gpio_reset_pin(config->sdo_gpio) == ESP_OK, ESP_FAIL, NULL, "Reset GPIO failed.")}, "Failed install isr service."); + ZH_ERROR_CHECK(gpio_isr_handler_add(config->sdo_gpio, _zh_ttp229_isr_handler, handle) == ESP_OK, ESP_FAIL, + {ZH_ERROR_CHECK(gpio_reset_pin(config->sdo_gpio) == ESP_OK, ESP_FAIL, NULL, "Reset GPIO failed.")}, "Interrupt initialization failed."); handle->sdo_gpio = config->sdo_gpio; return ESP_OK; } @@ -175,10 +288,10 @@ static esp_err_t _zh_ttp229_rmt_init(const zh_ttp229_init_config_t *config, zh_t ZH_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel creation failed."); rmt_copy_encoder_config_t copy_encoder_config = {}; ZH_ERROR_CHECK(rmt_new_copy_encoder(©_encoder_config, &handle->copy_encoder) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete channel failed."), "Copy encoder creation failed."); + {ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete channel failed.")}, "Copy encoder creation failed."); ZH_ERROR_CHECK(rmt_enable(handle->tx_channel) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete channel failed."), "Enable TX channel failed."); + {ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed.")}; + {ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete channel failed.")}, "Enable TX channel failed."); rmt_rx_channel_config_t rx_chan_config = { .clk_src = RMT_CLK_SRC_DEFAULT, .gpio_num = config->sdo_gpio, @@ -190,29 +303,31 @@ static esp_err_t _zh_ttp229_rmt_init(const zh_ttp229_init_config_t *config, zh_t .resolution_hz = 1000000, }; ZH_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &handle->rx_channel) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(rmt_disable(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel disable failed."); - ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete TX channel failed."), "TX channel creation failed."); + {ZH_ERROR_CHECK(rmt_disable(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel disable failed.")}; + {ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed.")}; + {ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete TX channel failed.")}, "TX channel creation failed."); ZH_ERROR_CHECK(rmt_enable(handle->rx_channel) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(rmt_disable(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel disable failed."); - ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete TX channel failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete RX channel failed."), "RX channel creation failed."); + {ZH_ERROR_CHECK(rmt_disable(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel disable failed.")}; + {ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed.")}; + {ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete TX channel failed.")}; + {ZH_ERROR_CHECK(rmt_del_channel(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete RX channel failed.")}, "RX channel creation failed."); rmt_rx_event_callbacks_t cbs = { .on_recv_done = _zh_ttp229_rmt_rx_done_callback, }; ZH_ERROR_CHECK(rmt_rx_register_event_callbacks(handle->rx_channel, &cbs, handle) == ESP_OK, ESP_FAIL, - ZH_ERROR_CHECK(rmt_disable(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel disable failed."); - ZH_ERROR_CHECK(rmt_disable(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "RX channel disable failed."); - ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete TX channel failed."); - ZH_ERROR_CHECK(rmt_del_channel(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete RX channel failed."), "TX callback registar failed."); + {ZH_ERROR_CHECK(rmt_disable(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel disable failed.")}; + {ZH_ERROR_CHECK(rmt_disable(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "RX channel disable failed.")}; + {ZH_ERROR_CHECK(rmt_del_encoder(handle->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed.")}; + {ZH_ERROR_CHECK(rmt_del_channel(handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete TX channel failed.")}; + {ZH_ERROR_CHECK(rmt_del_channel(handle->rx_channel) == ESP_OK, ESP_FAIL, NULL, "Delete RX channel failed.")}, "TX callback registar failed."); return ESP_OK; } static esp_err_t _zh_ttp229_resources_init(const zh_ttp229_init_config_t *config) { - if (_pad_counter == 0) + uint16_t vector_size = 0; + zh_vector_get_size(&_vector, &vector_size); + if (vector_size == 1) { _queue_handle = xQueueCreate(config->queue_size, sizeof(zh_ttp229_queue_t)); ZH_ERROR_CHECK(_queue_handle != NULL, ESP_FAIL, NULL, "Failed to create queue."); @@ -222,7 +337,9 @@ static esp_err_t _zh_ttp229_resources_init(const zh_ttp229_init_config_t *config static esp_err_t _zh_ttp229_task_init(const zh_ttp229_init_config_t *config) { - if (_pad_counter == 0) + uint16_t vector_size = 0; + zh_vector_get_size(&_vector, &vector_size); + if (vector_size == 1) { ZH_ERROR_CHECK(xTaskCreatePinnedToCore(&_zh_ttp229_isr_processing_task, "zh_ttp229_isr_processing", config->stack_size, NULL, config->task_priority, &zh_ttp229, tskNO_AFFINITY) == pdPASS, ESP_FAIL, NULL, "Failed to create isr processing task."); @@ -235,16 +352,19 @@ static void IRAM_ATTR _zh_ttp229_isr_handler(void *arg) zh_ttp229_handle_t *ttp229_handle = (zh_ttp229_handle_t *)arg; gpio_intr_disable(ttp229_handle->sdo_gpio); rmt_receive_config_t rx_receive_config = {.signal_range_min_ns = 100, .signal_range_max_ns = 2000000}; + // Start RMT reception on SDO to capture touch data if (rmt_receive(ttp229_handle->rx_channel, &ttp229_handle->rx_symbols, sizeof(ttp229_handle->rx_symbols), &rx_receive_config) != ESP_OK) { ++_stats.rmt_driver_error; } + // Toggle SDO: set output low then high to trigger the RMT RX start gpio_set_direction(ttp229_handle->sdo_gpio, GPIO_MODE_OUTPUT); gpio_set_level(ttp229_handle->sdo_gpio, 0); gpio_set_level(ttp229_handle->sdo_gpio, 1); gpio_set_direction(ttp229_handle->sdo_gpio, GPIO_MODE_INPUT); + // Generate SCL clock pulses via RMT transmit for the configured work mode rmt_symbol_word_t scl_symbols[ttp229_handle->work_mode]; - for (uint8_t i = 0; i < ttp229_handle->work_mode; ++i) + for (uint8_t i = 0; i < (uint8_t)ttp229_handle->work_mode; ++i) { scl_symbols[i] = (rmt_symbol_word_t){.duration0 = 2, .level0 = 0, .duration1 = 2, .level1 = 1}; } @@ -258,11 +378,15 @@ static void IRAM_ATTR _zh_ttp229_isr_handler(void *arg) static void IRAM_ATTR _zh_ttp229_isr_processing_task(void *pvParameter) // -V2008 { + (void)pvParameter; zh_ttp229_queue_t ttp229_queue = {0}; while (xQueueReceive(_queue_handle, &ttp229_queue, portMAX_DELAY) == pdTRUE) { zh_ttp229_handle_t *ttp229_handle = ttp229_queue.handle; uint8_t pad_number = 0; + // Decode pad number from duration1: each pad has a unique range + // of (rmt_tx_start_delay + offset) values, with offset increasing + // by 2 per pad (pad N uses offset 2*(N-1)+1, ±1 tolerance) if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay - 1)) || (ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay)) || (ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 1))) @@ -364,12 +488,7 @@ static void IRAM_ATTR _zh_ttp229_isr_processing_task(void *pvParameter) // -V200 zh_ttp229_event_on_isr_t ttp229_data = {0}; ttp229_data.pad_number = pad_number; ttp229_data.device_number = ttp229_handle->device_number; - esp_err_t err = esp_event_post(ZH_TTP229, 0, &ttp229_data, sizeof(zh_ttp229_event_on_isr_t), 1000 / portTICK_PERIOD_MS); - if (err != ESP_OK) - { - ++_stats.event_post_error; - ZH_LOGE("Touch pad isr processing failed. Failed to post interrupt event.", err); - } + ZH_ERROR_CHECK_CONT(esp_event_post(ZH_TTP229, 0, &ttp229_data, sizeof(zh_ttp229_event_on_isr_t), 1000 / portTICK_PERIOD_MS) == ESP_OK, ++_stats.event_post_error, "Touch pad isr processing failed. Failed to post interrupt event."); } vTaskDelay(ttp229_handle->debounce_time / portTICK_PERIOD_MS); gpio_intr_enable(ttp229_handle->sdo_gpio); @@ -380,6 +499,7 @@ static void IRAM_ATTR _zh_ttp229_isr_processing_task(void *pvParameter) // -V200 static bool IRAM_ATTR _zh_ttp229_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data) { + (void)edata; zh_ttp229_handle_t *ttp229_handle = (zh_ttp229_handle_t *)user_data; zh_ttp229_queue_t ttp229_queue = {0}; ttp229_queue.handle = ttp229_handle;