Compare commits

...
10 Commits
Author SHA1 Message Date
aZholtikov 58d86a0c71 doc: added wiki 2026-10-17 09:31:50 +03:00
aZholtikov 6f79a3c111 doc: updated readme 2026-10-17 09:30:13 +03:00
aZholtikov b6f88f48fb doc: updated example 2026-10-05 18:04:01 +03:00
aZholtikov d648ef45ec perf: updated error checks 2026-10-04 20:42:17 +03:00
aZholtikov ed161e43ee perf: updated error checks 2026-10-04 20:10:24 +03:00
aZholtikov 1516081fb8 perf: updated error checks 2026-10-04 12:52:16 +03:00
aZholtikov 0f02a9f951 refactor: minor refactoring 2026-09-30 18:33:02 +03:00
aZholtikov aaab3996a3 style: updated comments 2026-09-27 11:40:09 +03:00
aZholtikov 0a6c75af02 perf: updated error check 2026-09-20 15:01:55 +03:00
aZholtikov bff0335ac2 style: updated comments 2026-09-12 12:27:18 +03:00
4 changed files with 704 additions and 214 deletions

No files matched your search

+138 -31
View File
@@ -1,8 +1,4 @@
# ESP32 ESP-IDF component for TTP229 touch pad (via RMT driver) # ESP32 ESP-IDF component for TTP229 touch pad
## 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 ## Features
@@ -19,23 +15,9 @@
1. After start/reset first touch not work. 1. After start/reset first touch not work.
2. Multi touch not supported. 2. Multi touch not supported.
## Note
Enable the following settings in menuconfig:
```text
CONFIG_GPIO_CTRL_FUNC_IN_IRAM
CONFIG_RMT_ENCODER_FUNC_IN_IRAM
CONFIG_RMT_TX_ISR_HANDLER_IN_IRAM
CONFIG_RMT_RX_ISR_HANDLER_IN_IRAM
CONFIG_RMT_RECV_FUNC_IN_IRAM
CONFIG_RMT_TX_ISR_CACHE_SAFE
CONFIG_RMT_RX_ISR_CACHE_SAFE
```
## Using ## Using
In an existing project, run the following command to install the components: In an existing project, run the following command to install the component:
```bash ```bash
cd ../your_project/components cd ../your_project/components
@@ -48,7 +30,9 @@ In the application, add the component:
#include "zh_ttp229.h" #include "zh_ttp229.h"
``` ```
## Example ## Examples
### Basic Example: Single Device
```c ```c
#include "zh_ttp229.h" #include "zh_ttp229.h"
@@ -62,6 +46,7 @@ void zh_ttp229_event_handler(void *arg, esp_event_base_t event_base, int32_t eve
void app_main(void) void app_main(void)
{ {
esp_log_level_set("zh_ttp229", ESP_LOG_ERROR); esp_log_level_set("zh_ttp229", ESP_LOG_ERROR);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
esp_event_loop_create_default(); esp_event_loop_create_default();
esp_event_handler_instance_register(ZH_TTP229, ESP_EVENT_ANY_ID, &zh_ttp229_event_handler, NULL, NULL); esp_event_handler_instance_register(ZH_TTP229, ESP_EVENT_ANY_ID, &zh_ttp229_event_handler, NULL, NULL);
zh_ttp229_init_config_t config = ZH_TTP229_INIT_CONFIG_DEFAULT(); zh_ttp229_init_config_t config = ZH_TTP229_INIT_CONFIG_DEFAULT();
@@ -70,18 +55,21 @@ void app_main(void)
config.work_mode = ZH_TTP229_16_PAD; config.work_mode = ZH_TTP229_16_PAD;
config.device_number = TTP229_NUMBER; config.device_number = TTP229_NUMBER;
config.debounce_time = 300; config.debounce_time = 300;
// The time between the low-level pulse in the interrupt and the actual start time of the RMT signal generator (in microseconds). config.rmt_tx_start_delay = 23;
// It is determined experimentally or using a logic analyzer. esp_err_t ret = zh_ttp229_init(&config, &ttp229_handle);
config.rmt_tx_start_delay = 19; if (ret != ESP_OK)
zh_ttp229_init(&config, &ttp229_handle); {
printf("TTP229 initialization error: %s\n", esp_err_to_name(ret));
return;
}
for (;;) for (;;)
{ {
const zh_ttp229_stats_t *stats = zh_ttp229_get_stats(); vTaskDelay(1000 / portTICK_PERIOD_MS);
printf("Number of RMT driver error: %ld.\n", stats->rmt_driver_error); }
printf("Number of event post error: %ld.\n", stats->event_post_error); ret = zh_ttp229_deinit(&ttp229_handle);
printf("Number of queue overflow error: %ld.\n", stats->queue_overflow_error); if (ret != ESP_OK)
printf("Minimum free stack size: %ld.\n", stats->min_stack_size); {
vTaskDelay(60000 / portTICK_PERIOD_MS); printf("TTP229 deinit error: %s\n", esp_err_to_name(ret));
} }
} }
@@ -91,3 +79,122 @@ void zh_ttp229_event_handler(void *arg, esp_event_base_t event_base, int32_t eve
printf("Touch pad number %d pad number %d pressed.\n", ttp229_event->device_number, ttp229_event->pad_number); printf("Touch pad number %d pad number %d pressed.\n", ttp229_event->device_number, ttp229_event->pad_number);
} }
``` ```
---
### Stats Example
```c
#include "zh_ttp229.h"
#define TTP229_NUMBER 0x01
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);
void app_main(void)
{
esp_log_level_set("zh_ttp229", ESP_LOG_ERROR);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
esp_event_loop_create_default();
esp_event_handler_instance_register(ZH_TTP229, ESP_EVENT_ANY_ID, &zh_ttp229_event_handler, NULL, NULL);
zh_ttp229_init_config_t config = ZH_TTP229_INIT_CONFIG_DEFAULT();
config.scl_gpio = GPIO_NUM_25;
config.sdo_gpio = GPIO_NUM_27;
config.work_mode = ZH_TTP229_16_PAD;
config.device_number = TTP229_NUMBER;
config.debounce_time = 300;
config.rmt_tx_start_delay = 23;
esp_err_t ret = zh_ttp229_init(&config, &ttp229_handle);
if (ret != ESP_OK)
{
printf("TTP229 initialization error: %s\n", esp_err_to_name(ret));
return;
}
for (;;)
{
const zh_ttp229_stats_t *stats = zh_ttp229_get_stats();
printf("Number of RMT driver error: %ld.\n", stats->rmt_driver_error);
printf("Number of event post error: %ld.\n", stats->event_post_error);
printf("Number of queue overflow error: %ld.\n", stats->queue_overflow_error);
printf("Minimum free stack size: %ld.\n", stats->min_stack_size);;
vTaskDelay(60000 / portTICK_PERIOD_MS);
}
zh_ttp229_reset_stats();
ret = zh_ttp229_deinit(&ttp229_handle);
if (ret != ESP_OK)
{
printf("TTP229 deinit error: %s\n", esp_err_to_name(ret));
}
}
void zh_ttp229_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
zh_ttp229_event_on_isr_t *ttp229_event = event_data;
printf("Touch pad number %d pad number %d pressed.\n", ttp229_event->device_number, ttp229_event->pad_number);
}
```
---
### Multi-Device Example
```c
#include "zh_ttp229.h"
#define TTP229_DEVICE_1 0x01
#define TTP229_DEVICE_2 0x02
zh_ttp229_handle_t *ttp229_handle_1 = NULL;
zh_ttp229_handle_t *ttp229_handle_2 = NULL;
void zh_ttp229_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
void app_main(void)
{
esp_log_level_set("zh_ttp229", ESP_LOG_ERROR);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
esp_event_loop_create_default();
esp_event_handler_instance_register(ZH_TTP229, ESP_EVENT_ANY_ID, &zh_ttp229_event_handler, NULL, NULL);
zh_ttp229_init_config_t config_1 = ZH_TTP229_INIT_CONFIG_DEFAULT();
config_1.scl_gpio = GPIO_NUM_25;
config_1.sdo_gpio = GPIO_NUM_27;
config_1.work_mode = ZH_TTP229_16_PAD;
config_1.device_number = TTP229_DEVICE_1;
config_1.debounce_time = 300;
config_1.rmt_tx_start_delay = 23;
esp_err_t ret = zh_ttp229_init(&config_1, &ttp229_handle_1);
if (ret != ESP_OK)
{
printf("TTP229 device 1 initialization error: %s\n", esp_err_to_name(ret));
return;
}
zh_ttp229_init_config_t config_2 = ZH_TTP229_INIT_CONFIG_DEFAULT();
config_2.scl_gpio = GPIO_NUM_26;
config_2.sdo_gpio = GPIO_NUM_28;
config_2.work_mode = ZH_TTP229_16_PAD;
config_2.device_number = TTP229_DEVICE_2;
config_2.debounce_time = 300;
config_2.rmt_tx_start_delay = 23;
ret = zh_ttp229_init(&config_2, &ttp229_handle_2);
if (ret != ESP_OK)
{
printf("TTP229 device 2 initialization error: %s\n", esp_err_to_name(ret));
zh_ttp229_deinit(&ttp229_handle_1);
return;
}
for (;;)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
zh_ttp229_deinit(&ttp229_handle_2);
zh_ttp229_deinit(&ttp229_handle_1);
}
void zh_ttp229_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
zh_ttp229_event_on_isr_t *ttp229_event = event_data;
printf("Touch pad number %d pad number %d pressed.\n", ttp229_event->device_number, ttp229_event->pad_number);
}
```
+43 -28
View File
@@ -7,14 +7,14 @@
* This module provides a FreeRTOS-based driver for the TTP229 touch * This module provides a FreeRTOS-based driver for the TTP229 touch
* controller. It uses the RMT transmitter to generate SCL clock pulses * controller. It uses the RMT transmitter to generate SCL clock pulses
* and the RMT receiver to capture SDO data, enabling reading of touch * and the RMT receiver to capture SDO data, enabling reading of touch
* status from 8 or 16 pad configurations. Each touch event triggers an * status from 8 or 16 pad configurations. A positive edge on SDO triggers
* ISR that initiates RMT transfer and posts an event with the touched * an ISR that starts the RMT transfer; a dedicated FreeRTOS task then
* pad number. * decodes the touched pad number and posts an event with it.
* *
* Key features: * Key features:
* - Supports 8-pad and 16-pad TTP229 configurations * - Supports 8-pad and 16-pad TTP229 configurations
* - RMT-based SPI-like communication (no GPIO bit-banging) * - RMT-based SPI-like communication (no GPIO bit-banging)
* - FreeRTOS task for debouncing and event dispatching * - FreeRTOS task for decoding, debouncing and event dispatching
* - Per-device unique numbering for multi-device support * - Per-device unique numbering for multi-device support
* - Error statistics tracking (RMT, queue, stack) * - Error statistics tracking (RMT, queue, stack)
*/ */
@@ -22,6 +22,7 @@
#pragma once #pragma once
#include "esp_log.h" #include "esp_log.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "driver/rmt_tx.h" #include "driver/rmt_tx.h"
@@ -34,11 +35,11 @@
{ \ { \
.task_priority = 1, \ .task_priority = 1, \
.stack_size = configMINIMAL_STACK_SIZE, \ .stack_size = configMINIMAL_STACK_SIZE, \
.queue_size = 1, \ .queue_size = 5, \
.work_mode = ZH_TTP229_8_PAD, \ .work_mode = ZH_TTP229_8_PAD, \
.rmt_tx_start_delay = 0, \ .rmt_tx_start_delay = 15, \
.debounce_time = 100, \ .debounce_time = 100, \
.device_number = 0, \ .device_number = 1, \
.scl_gpio = GPIO_NUM_MAX, \ .scl_gpio = GPIO_NUM_MAX, \
.sdo_gpio = GPIO_NUM_MAX} .sdo_gpio = GPIO_NUM_MAX}
@@ -48,7 +49,11 @@ extern "C"
#endif #endif
/** /**
* @brief External task handle for the TTP229 ISR processing task. * @brief Handle of the shared TTP229 event processing task.
*
* Created when the first device is initialized and set back to NULL
* when the last device is deinitialized or the first device
* initialization fails.
*/ */
extern TaskHandle_t zh_ttp229; extern TaskHandle_t zh_ttp229;
@@ -62,6 +67,10 @@ extern "C"
/** /**
* @brief TTP229 touch pad work mode (number of pads). * @brief TTP229 touch pad work mode (number of pads).
*
* Selects the number of capacitive touch pads configured on the
* TTP229 controller. The value determines how many SCL clock pulses
* are generated during a read cycle.
*/ */
typedef enum typedef enum
{ {
@@ -74,15 +83,18 @@ extern "C"
* *
* Use ZH_TTP229_INIT_CONFIG_DEFAULT() to obtain sensible defaults * Use ZH_TTP229_INIT_CONFIG_DEFAULT() to obtain sensible defaults
* and override only the fields that differ from defaults. * and override only the fields that differ from defaults.
*
* @note Minimum values: stack_size >= configMINIMAL_STACK_SIZE, debounce_time >= 10 ms,
* task_priority >= 1, queue_size >= 1, rmt_tx_start_delay >= 15 us, device_number > 0.
*/ */
typedef struct typedef struct
{ {
uint16_t stack_size; /*!< Task stack size in bytes. @note Minimum: configMINIMAL_STACK_SIZE */ uint16_t stack_size; /*!< Task stack size in bytes */
uint16_t debounce_time; /*!< Button debounce time in milliseconds. @note Minimum: 10 ms */ uint16_t debounce_time; /*!< Button debounce time in milliseconds */
uint8_t task_priority; /*!< FreeRTOS task priority. @note Minimum: 1 */ uint8_t task_priority; /*!< FreeRTOS task priority */
uint8_t queue_size; /*!< Event queue depth. @note Minimum: 1 */ uint8_t queue_size; /*!< Event queue depth */
uint8_t device_number; /*!< Unique device identifier. @note Must be > 0 */ uint8_t device_number; /*!< Unique device identifier */
uint8_t rmt_tx_start_delay; /*!< RMT TX delay in microseconds after SDO interrupt. @note Minimum: 15 us */ uint8_t rmt_tx_start_delay; /*!< RMT TX delay in microseconds after SDO interrupt */
gpio_num_t sdo_gpio; /*!< SDO (Serial Data Out) GPIO pin */ gpio_num_t sdo_gpio; /*!< SDO (Serial Data Out) GPIO pin */
gpio_num_t scl_gpio; /*!< SCL (Serial Clock) 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_work_mode_t work_mode; /*!< Touch pad configuration (8 or 16 pads) */
@@ -117,41 +129,44 @@ extern "C"
/** /**
* @brief Initialize the TTP229 touch pad driver. * @brief Initialize the TTP229 touch pad driver.
* *
* Allocates a handle, validates the configuration, initializes RMT * Allocates a handle, validates the configuration, creates the shared
* TX/RMT channels, installs the GPIO ISR handler, and creates the * queue and the processing task on the first device, initializes GPIO
* processing task. Supports multiple devices via unique device * with the ISR handler, and creates the RMT TX/RX channels. Supports
* numbers. * multiple devices via unique device numbers.
* *
* @param[in] config Pointer to initialization configuration (must not be NULL) * @param[in] config Pointer to initialization configuration (must not be NULL)
* @param[out] handle Pointer to unique touch pad handle (must not be NULL) * @param[out] handle Pointer to a handle location that must be NULL on input (must not be NULL)
* *
* @return ESP_OK on success * @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_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_ERR_INVALID_STATE if *handle is not NULL
* @return ESP_FAIL on resource allocation or peripheral failure * @return ESP_ERR_NO_MEM if handle allocation fails
* @return ESP_FAIL if creating the shared resources fails, or on GPIO or RMT initialization 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 a TTP229 touch pad instance. * @brief Deinitialize a TTP229 touch pad instance.
* *
* Disables RMT channels, removes the GPIO ISR handler, frees the * Removes the device from the internal device list, removes the GPIO
* handle, and removes the device from the internal device list. * ISR handler, disables and deletes the RMT channels and the encoder,
* The processing task and shared resources are deleted only when * resets the GPIO pins, and frees the handle. The processing task,
* the last device is deinitialized. * the queue, and the device list are deleted only when the last
* device is deinitialized.
* *
* @param[in,out] handle Pointer to unique touch pad handle (must not be NULL) * @param[in,out] handle Pointer to unique touch pad handle (must not be NULL)
* *
* @return ESP_OK on success * @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 * @return ESP_ERR_INVALID_ARG if handle or *handle is NULL
* @return ESP_FAIL if the device is not found in the internal device list, or if deleting the device list entries or the shared resources fails
*/ */
esp_err_t zh_ttp229_deinit(zh_ttp229_handle_t **handle); esp_err_t zh_ttp229_deinit(zh_ttp229_handle_t **handle);
/** /**
* @brief Retrieve a pointer to the current error statistics. * @brief Retrieve a pointer to the current error statistics.
* *
* The returned pointer remains valid until zh_ttp229_reset_stats() * The returned pointer refers to a module static structure and
* is called or the module is deinitialized. * remains valid for the lifetime of the application.
* *
* @return Pointer to the static zh_ttp229_stats_t structure * @return Pointer to the static zh_ttp229_stats_t structure
*/ */
+266
View File
@@ -0,0 +1,266 @@
# Описание компонента zh_ttp229
## Общие сведения
**zh_ttp229** — это компонент ESP-IDF для работы с контроллером ёмкостных сенсорных кнопок TTP229 по интерфейсу SCL/SDO. Чтение состояния кнопок выполняется через периферию RMT вместо программного переключения GPIO (bit-banging).
Компонент регистрирует прерывание на выводе SDO, по фронтальному фронту генерирует тактовые импульсы SCL через RMT TX, принимает импульс с линии SDO через RMT RX и публикует событие `ZH_TTP229` с номером нажатой кнопки.
## Основные возможности
1. **Два режима работы** — конфигурации на 8 и на 16 кнопок.
2. **RMT-обмен** — тактирование и приём без bit-banging, с IRAM-safe обработчиками.
3. **Многоустройственность** — несколько плат TTP229 с уникальными номерами устройств.
4. **Событийный API** — уведомления через фреймворк `esp_event`.
5. **Debounce по тикам** — отбрасывание дребезга без задержек в задаче обработки.
6. **Восстановление RX-канала** — автоматический сброс RMT RX при ошибках драйвера.
7. **Сбор статистики** — счётчики ошибок RMT, постинга событий, переполнения очереди, остаток стека.
8. **Самоочистка ресурсов** — общая очередь и задача удаляются при деинициализации последнего устройства.
## Внутреннее устройство
### Структура `zh_ttp229_handle_t`
Непрозрачный дескриптор одного устройства, определяется в `zh_ttp229.c`.
| Поле | Тип | Описание |
|------|-----|----------|
| `sdo_gpio` | `gpio_num_t` | Вывод данных SDO (прерывание и приём) |
| `scl_gpio` | `gpio_num_t` | Вывод тактирования SCL (RMT TX) |
| `device_number` | `uint8_t` | Уникальный номер устройства, заданный при инициализации |
| `rmt_tx_start_delay` | `uint8_t` | Задержка (мкс) между импульсом на SDO и стартом генератора RMT |
| `scl_symbols_count` | `uint8_t` | Число тактовых символов SCL (8 или 16) |
| `debounce_time` | `uint16_t` | Время debounce в миллисекундах |
| `last_event_tick` | `TickType_t` | Тик последнего отправленного события, `-1` до первого события |
| `rx_needs_reset` | `volatile bool` | Флаг запроса сброса RMT RX канала, устанавливается из ISR |
| `scl_symbols` | `rmt_symbol_word_t [16]` | Предвычисленные символы тактового сигнала SCL |
| `rx_symbols` | `rmt_symbol_word_t` | Принятый символ с линии SDO |
| `tx_channel` | `rmt_channel_handle_t` | Дескриптор RMT TX канала |
| `rx_channel` | `rmt_channel_handle_t` | Дескриптор RMT RX канала |
| `copy_encoder` | `rmt_encoder_handle_t` | Copy-энкодер для RMT TX |
Поле `rx_channel` обнуляется при деинициализации — обработчик прерывания проверяет его и завершается, не обращаясь к удалённому каналу.
### Структура `zh_ttp229_queue_t`
Элемент очереди, передаваемый из RMT RX callback в задачу обработки.
| Поле | Тип | Описание |
|------|-----|----------|
| `handle` | `zh_ttp229_handle_t *` | Указатель на дескриптор устройства-инициатора |
| `rx_symbol` | `rmt_symbol_word_t` | Снимок принятого символа SDO |
### Модульные статические данные
| Переменная | Тип | Описание |
|------------|-----|----------|
| `zh_ttp229` | `TaskHandle_t` | Задача обработки прерываний; создаётся для первого устройства |
| `_queue_handle` | `QueueHandle_t` | Общая очередь уведомлений от RMT RX callback |
| `_stats` | `zh_ttp229_stats_t` | Накопленная статистика ошибок |
| `_vector` | `zh_vector_t *` | Вектор номеров всех инициализированных устройств (компонент `zh_vector`) |
### Управление памятью
Дескриптор выделяется через `heap_caps_calloc()` с флагом `MALLOC_CAP_8BIT` при инициализации и освобождается в `zh_ttp229_deinit()`. Общая память (очередь, задача, вектор) принадлежит первому устройству: при ошибке инициализации первого устройства выполняется откат (`_zh_ttp229_rollback_task_and_resources()`), при удалении последнего — полное освобождение.
## API (интерфейс)
### Инициализация и освобождение
| Функция | Описание |
|---------|----------|
| `zh_ttp229_init()` | Инициализация устройства: валидация конфигурации, создание общих ресурсов (для первого устройства), настройка GPIO с прерыванием по фронтальному фронту, создание RMT TX/RX каналов. Требует `*handle == NULL` на входе. |
| `zh_ttp229_deinit()` | Деинициализация устройства: удаление из вектора, снятие ISR handler, удаление RMT каналов и энкодера, сброс GPIO. Общие ресурсы удаляются только для последнего устройства. |
### Получение информации и сброс
| Функция | Описание |
|---------|----------|
| `zh_ttp229_get_stats()` | Возвращает указатель на статическую структуру `zh_ttp229_stats_t`, действительную всё время работы приложения. |
| `zh_ttp229_reset_stats()` | Сбрасывает все счётчики статистики в ноль. |
### Типы и константы
| Элемент | Описание |
|---------|----------|
| `ZH_TTP229_INIT_CONFIG_DEFAULT()` | Макрос конфигурации по умолчанию (приоритет 1, очередь 5, 8 кнопок, debounce 100 мс, задержка 15 мкс). |
| `ZH_TTP229` | База событий `esp_event` для уведомлений о нажатиях. |
| `zh_ttp229_work_mode_t` | Режим работы: `ZH_TTP229_8_PAD` (8) или `ZH_TTP229_16_PAD` (16). |
| `zh_ttp229_init_config_t` | Конфигурация инициализации (GPIO, режим, debounce, параметры задачи и очереди). |
| `zh_ttp229_stats_t` | Статистика ошибок и остатка стека. |
| `zh_ttp229_event_on_isr_t` | Полезная нагрузка события: `pad_number` (1-based) и `device_number`. |
## Потокобезопасность
1. **Очередь FreeRTOS** — единственной синхронизирующей примитивом является общая очередь `_queue_handle`: ISR handler и RMT RX callback кладут в неё записи через `xQueueSendFromISR()`, задача обработки забирает через `xQueueReceive()`. Взаимные исключения между задачами пользователя не предусмотрены.
2. **Флаг `rx_needs_reset`** — объявлен как `volatile bool`, устанавливается из ISR и сбрасывается в задаче обработки; используется для отложенного сброса RMT RX канала вне контекста прерывания.
3. **Отключение прерываний GPIO** — на время цикла чтения прерывание SDO отключается (`gpio_intr_disable()`) и включается обратно в задаче обработки после постинга события, что исключает повторный вход в ISR во время обмена.
4. **Удаление** — при переполнении очереди или ошибке RMT прерывания GPIO включаются сразу из ISR, чтобы устройство не осталось «зависшим».
## Алгоритмы
### Опрос кнопок (протокол SCL/SDO)
1. Фронтальный фронт на SDO (контроллер готов передать данные) вызывает `_zh_ttp229_isr_handler()`.
2. ISR вооружает приём одного символа `rmt_receive()` на RX канале (окно сигнала 100 нс – 2 мкс).
3. Кратковременный импульс на SDO (OUTPUT low → high → INPUT) синхронизирует контроллер.
4. Через RMT TX передаются `scl_symbols_count` тактовых символов SCL (каждый: 2 мкс low + 2 мкс high, `init_level = 1`).
5. По завершении приёма RMT RX callback кладёт измеренный символ в очередь.
### Декодирование номера кнопки
Номер кнопки определяется по длительности `duration1` принятого символа (в мкс, разрешение RMT 1 МГц). Обозначим `base = rmt_tx_start_delay`. Для кнопки N (1..16) ожидаемое окно:
- N = 1: `duration1 ∈ {base-1, base, base+1}`
- N > 1: `duration1 ∈ {base + 4·(N-1) - 1, base + 4·(N-1), base + 4·(N-1) + 1}`
Тolerance ±1 мкс компенсирует джиттер. Распознанный номер больше `scl_symbols_count` отбрасывается.
### Debounce
- Событие публикуется, если с предыдущего события прошло не менее `pdMS_TO_TICKS(debounce_time)` тиков, либо событие первое (`last_event_tick == -1`).
- Окно сравнивается по арифметике тиков без вычитания переполнения (`now - last_event_tick`), что корректно при переполнении счётчика.
- подавленные события не обновляют `last_event_tick`, поэтому кнопка «срабатывает» сразу после окончания окна.
### Восстановление RX канала
- При ошибке `rmt_receive()`/`rmt_transmit()` устанавливается `rx_needs_reset` и в очередь ставится пустая запись-уведомление.
- Задача обработки выполняет `rmt_disable()` + `rmt_enable()` для RX канала вне ISR.
## Примеры использования
### Базовый пример
```c
#include "zh_ttp229.h"
static zh_ttp229_handle_t *ttp229_handle = NULL;
void touch_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
zh_ttp229_event_on_isr_t *event = event_data;
printf("Устройство %d: нажата кнопка %d\n", event->device_number, event->pad_number);
}
void app_main(void)
{
esp_event_loop_create_default();
esp_event_handler_instance_register(ZH_TTP229, ESP_EVENT_ANY_ID, &touch_handler, NULL, NULL);
zh_ttp229_init_config_t config = ZH_TTP229_INIT_CONFIG_DEFAULT();
config.scl_gpio = GPIO_NUM_25;
config.sdo_gpio = GPIO_NUM_27;
config.work_mode = ZH_TTP229_16_PAD;
config.rmt_tx_start_delay = 23; // подбирается экспериментально
if (zh_ttp229_init(&config, &ttp229_handle) != ESP_OK)
{
return; // обработка ошибки инициализации
}
// ... работа ...
zh_ttp229_deinit(&ttp229_handle); // очистка ресурсов
}
```
### Работа с несколькими устройствами
```c
#include "zh_ttp229.h"
#define DEV1 0x01
#define DEV2 0x02
static zh_ttp229_handle_t *dev1 = NULL;
static zh_ttp229_handle_t *dev2 = NULL;
void app_main(void)
{
esp_event_loop_create_default();
zh_ttp229_init_config_t config1 = ZH_TTP229_INIT_CONFIG_DEFAULT();
config1.scl_gpio = GPIO_NUM_25;
config1.sdo_gpio = GPIO_NUM_26;
config1.device_number = DEV1;
zh_ttp229_init_config_t config2 = ZH_TTP229_INIT_CONFIG_DEFAULT();
config2.scl_gpio = GPIO_NUM_27;
config2.sdo_gpio = GPIO_NUM_22;
config2.device_number = DEV2;
zh_ttp229_init(&config1, &dev1);
zh_ttp229_init(&config2, &dev2); // общее ресурсы не пересоздаются
// ... работа ...
zh_ttp229_deinit(&dev2);
zh_ttp229_deinit(&dev1); // последний объект удаляет задачу и очередь
}
```
### Мониторинг статистики
```c
#include "zh_ttp229.h"
void stats_task(void *arg)
{
for (;;)
{
const zh_ttp229_stats_t *stats = zh_ttp229_get_stats();
if (stats->rmt_driver_error > 0 || stats->queue_overflow_error > 0)
{
printf("Ошибки RMT: %lu, переполнения очереди: %lu\n",
stats->rmt_driver_error, stats->queue_overflow_error);
zh_ttp229_reset_stats(); // сброс счётчиков
}
vTaskDelay(pdMS_TO_TICKS(60000));
}
}
```
## Логирование
Компонент использует ESP-IDF логирование с тегом `"zh_ttp229"`.
```c
esp_log_level_set("zh_ttp229", ESP_LOG_ERROR);
```
Также компонент зависит от `zh_vector` с тегом `"zh_vector"`, который стоит приглушить отдельно.
## Ошибки
| Код | Описание |
|-----|----------|
| `ESP_OK` | Успех |
| `ESP_ERR_INVALID_ARG` | NULL-аргумент, значение вне допустимого диапазона, дублирующийся `device_number` |
| `ESP_ERR_INVALID_STATE` | `*handle` не равен `NULL` при вызове `zh_ttp229_init()` |
| `ESP_ERR_NO_MEM` | Не удалось выделить память под дескриптор |
| `ESP_FAIL` | Ошибка создания очереди/задачи, инициализации GPIO, ISR-сервиса или RMT; устройство не найдено в векторе при деинициализации |
## Ограничения
1. **Первое нажатие после старта/сброса не работает** — особенность контроллера TTP229.
2. **Мультитач не поддерживается** — за один цикл декодируется только одна кнопка.
3. **`device_number` должен быть уникальным и > 0** — используется как ключ в векторе устройств.
4. **Общая очередь и задача используют параметры первого устройства** — `queue_size`, `stack_size` и `task_priority` последующих устройств игнорируются.
5. **`rmt_tx_start_delay` подбирается индивидуально** — минимум 15 мкс, точность зависит от платы; определяется экспериментально или логическим анализатором.
6. **Первое нажатие после инициализации** может быть потеряно из-за синхронизации контроллера.
7. **Потокобезопасность ограничена** — функции `init`/`deinit` не защищены от одновременного вызова из нескольких задач.
8. **`min_stack_size` обновляется только при событиях** — при отсутствии нажатий значение остаётся равным размеру стека из конфигурации.
## Требования для сборки
- ESP-IDF >= 5.0 (в `CMakeLists.txt` учтены различия `driver` и `esp_driver_rmt`/`esp_driver_gpio` для IDF 6.x)
- FreeRTOS (входит в состав ESP-IDF)
- `esp_event` (входит в состав ESP-IDF)
- `zh_vector` — динамический вектор (подтягивается автоматически через `idf_component.yml`)
## Установка
```bash
cd ваш_проект/components
git clone https://github.com/aZholtikov/zh_ttp229
```
```c
#include "zh_ttp229.h"
```
+257 -155
View File
@@ -13,15 +13,23 @@ static const char *TAG = "zh_ttp229";
return err; \ return err; \
} }
#define ZH_ERROR_CHECK_VOID(cond, cleanup, msg, ...) \
if (!(cond)) \
{ \
ZH_LOGE(msg, ESP_FAIL, ##__VA_ARGS__); \
cleanup; \
return; \
}
#define ZH_ERROR_CHECK_CONT(cond, cleanup, msg, ...) \ #define ZH_ERROR_CHECK_CONT(cond, cleanup, msg, ...) \
if (!(cond)) \ if (!(cond)) \
{ \ { \
ZH_LOGE(msg, ESP_FAIL, ##__VA_ARGS__); \ ZH_LOGE(msg, ESP_FAIL, ##__VA_ARGS__); \
cleanup; \ cleanup; \
continue; \
} }
/** @brief Internal handle for a single TTP229 device instance. /**
* @brief Internal handle for a single TTP229 device instance.
* *
* Fields are initialized by zh_ttp229_init() and used by the * Fields are initialized by zh_ttp229_init() and used by the
* ISR handler, processing task, and deinitialization. * ISR handler, processing task, and deinitialization.
@@ -29,35 +37,43 @@ static const char *TAG = "zh_ttp229";
struct _zh_ttp229_handle_t struct _zh_ttp229_handle_t
{ {
gpio_num_t sdo_gpio; /*!< SDO (Serial Data Out) GPIO pin for touch data */ gpio_num_t sdo_gpio; /*!< SDO (Serial Data Out) GPIO pin for touch data */
gpio_num_t scl_gpio; /*!< SCL (Serial Clock) GPIO pin for clock generation */
uint8_t device_number; /*!< Unique device number assigned at initialization */ 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) */ uint8_t rmt_tx_start_delay; /*!< Time between SDO low-level pulse and RMT TX start (in microseconds) */
uint8_t scl_symbols_count; /*!< Number of SCL clock symbols to transmit (8 or 16) */
uint16_t debounce_time; /*!< Button debounce time in milliseconds */ uint16_t debounce_time; /*!< Button debounce time in milliseconds */
zh_ttp229_work_mode_t work_mode; /*!< Touch pad configuration (8 or 16 pads) */ TickType_t last_event_tick; /*!< Tick count of the last posted event, -1 before first event */
rmt_symbol_word_t rx_symbols; /*!< Received RMT symbol containing duration1 (pad data) */ volatile bool rx_needs_reset; /*!< Flag requesting RMT RX channel reset, set from ISR context */
rmt_symbol_word_t scl_symbols[16]; /*!< Precomputed SCL clock symbols for RMT TX (up to 16 pads) */
rmt_symbol_word_t rx_symbols; /*!< Single RMT RX symbol buffer captured from the SDO line */
rmt_channel_handle_t tx_channel; /*!< RMT TX channel handle (SCL clock generation) */ 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_channel_handle_t rx_channel; /*!< RMT RX channel handle (SDO data capture) */
rmt_encoder_handle_t copy_encoder; /*!< Copy encoder handle for RMT TX */ 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. /**
* @brief Queue entry passed from RMT RX callback to the ISR processing task.
* *
* Contains a pointer to the TTP229 device handle for the device * Contains a pointer to the TTP229 device handle for the device
* that triggered the interrupt. * that triggered the interrupt and the first RMT symbol captured
* from the SDO line.
*/ */
typedef struct typedef struct
{ {
zh_ttp229_handle_t *handle; /*!< Pointer to the TTP229 device handle */ zh_ttp229_handle_t *handle; /*!< Pointer to the TTP229 device handle */
rmt_symbol_word_t rx_symbol; /*!< RMT symbol word captured from the SDO line */
} zh_ttp229_queue_t; } zh_ttp229_queue_t;
TaskHandle_t zh_ttp229 = NULL; /*!< FreeRTOS task handle for the ISR processing task */ TaskHandle_t zh_ttp229 = NULL; /*!< FreeRTOS task handle for the ISR processing task */
static QueueHandle_t _queue_handle = NULL; /*!< Internal event queue */ static QueueHandle_t _queue_handle = NULL; /*!< Internal event queue for RMT RX notifications */
static zh_ttp229_stats_t _stats = {0}; /*!< Accumulated error and runtime statistics */ 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 */ static zh_vector_t *_vector = NULL; /*!< Vector of device number values for all initialized TTP229 devices */
/** @brief Validate the user-provided initialization configuration. /**
* @brief Validate the user-provided initialization configuration.
* *
* Checks all parameter ranges, ensures work_mode is valid, and * Checks all parameter ranges, ensures work_mode is valid, and
* verifies device_number uniqueness across initialized devices. * verifies device number uniqueness across initialized devices.
* *
* @param config Pointer to the user configuration * @param config Pointer to the user configuration
* @param handle Pointer to the allocated handle (partially filled) * @param handle Pointer to the allocated handle (partially filled)
@@ -67,13 +83,15 @@ static zh_vector_t *_vector = NULL; /*!< Vector of device_number values f
*/ */
static esp_err_t _zh_ttp229_validate_config(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle); 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. /**
* @brief Configure GPIO for SDO input with positive-edge interrupt.
* *
* Installs the ISR service if not yet installed, configures SDO as * Checks that both GPIO pins are valid and different, installs the ISR
* input with GPIO_INTR_POSEDGE, and registers the ISR handler. * 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 config Pointer to the user configuration
* @param handle Pointer to the handle (stores sdo_gpio) * @param handle Pointer to the handle (stores sdo_gpio and scl_gpio)
* *
* @return ESP_OK on success * @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if GPIO pins are invalid or identical * @return ESP_ERR_INVALID_ARG if GPIO pins are invalid or identical
@@ -81,7 +99,8 @@ static esp_err_t _zh_ttp229_validate_config(const zh_ttp229_init_config_t *confi
*/ */
static esp_err_t _zh_ttp229_gpio_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle); 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. /**
* @brief Initialize RMT TX and RX channels with callbacks.
* *
* Creates TX channel on SCL GPIO, RX channel on SDO GPIO, allocates * Creates TX channel on SCL GPIO, RX channel on SDO GPIO, allocates
* the copy encoder, enables both channels, and registers the RX done * the copy encoder, enables both channels, and registers the RX done
@@ -95,7 +114,8 @@ static esp_err_t _zh_ttp229_gpio_init(const zh_ttp229_init_config_t *config, zh_
*/ */
static esp_err_t _zh_ttp229_rmt_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle); 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. /**
* @brief Create the shared event queue on first device initialization.
* *
* The queue is created only when vector_size equals 1 (first device). * The queue is created only when vector_size equals 1 (first device).
* *
@@ -106,7 +126,8 @@ static esp_err_t _zh_ttp229_rmt_init(const zh_ttp229_init_config_t *config, zh_t
*/ */
static esp_err_t _zh_ttp229_resources_init(const zh_ttp229_init_config_t *config); 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. /**
* @brief Create the ISR processing task on first device initialization.
* *
* The task is created only when vector_size equals 1 (first device). * The task is created only when vector_size equals 1 (first device).
* *
@@ -117,31 +138,49 @@ 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); static esp_err_t _zh_ttp229_task_init(const zh_ttp229_init_config_t *config);
/** @brief GPIO ISR handler triggered on SDO positive edge. /**
* @brief Roll back shared resources (queue, task) if the failed init was
* the first (and only) device.
* *
* Disables further interrupts, starts RMT receive on SDO, generates * Must be called BEFORE zh_vector_delete_back(&_vector) — otherwise the
* SCL clock pulses via RMT transmit on SCL, and yields to higher * vector size will no longer reflect "first device".
* priority tasks. */
static void _zh_ttp229_rollback_task_and_resources(void);
/**
* @brief GPIO ISR handler triggered on SDO positive edge.
*
* Disables further GPIO interrupts, arms RMT receive on SDO, emits the
* SDO start pulse, and issues the SCL clock pulses via RMT transmit on
* SCL. On a transmit failure the RX reset flag is set and a notify entry
* is enqueued so the processing task can recover the RX channel; GPIO
* interrupts are re-enabled on any error path.
* *
* @param arg Pointer to the zh_ttp229_handle_t for this device * @param arg Pointer to the zh_ttp229_handle_t for this device
*/ */
static void _zh_ttp229_isr_handler(void *arg); static void _zh_ttp229_isr_handler(void *arg);
/** @brief FreeRTOS task that processes RMT RX data and posts events. /**
* @brief FreeRTOS task that processes RMT RX data and posts events.
* *
* Receives queue entries from the RMT RX callback, decodes the pad * Receives queue entries from the RMT RX callback, resets the RX channel
* number from rx_symbols.duration1, posts a ZH_TTP229 event for * when flagged, decodes the pad number from the entry's rx_symbol
* detected touches, debounces by delaying, then re-enables GPIO * duration1, and posts a ZH_TTP229 event for a detected touch. Touches
* interrupts. Runs until the queue is deleted. * within the debounce window are dropped by comparing tick counts rather
* than delaying. Re-enables GPIO interrupts after each entry and runs
* until the queue is deleted.
* *
* @param pvParameter Unused * @param pvParameter Unused
*/ */
static void _zh_ttp229_isr_processing_task(void *pvParameter); static void _zh_ttp229_isr_processing_task(void *pvParameter);
/** @brief RMT RX done callback that enqueues data to the processing task. /**
* @brief RMT RX done callback that enqueues data to the processing task.
* *
* Sends a zh_ttp229_queue_t entry to _queue_handle. Increments * Returns early when the queue is not yet created. Otherwise copies the
* queue overflow error counter if the send fails. * received symbol into a zh_ttp229_queue_t entry and sends it to
* _queue_handle. On a full queue the overflow error counter is incremented
* and GPIO interrupts are re-enabled so the device is not left stalled.
* *
* @param channel RMT channel handle (unused) * @param channel RMT channel handle (unused)
* @param edata RX done event data (unused) * @param edata RX done event data (unused)
@@ -154,64 +193,78 @@ static bool _zh_ttp229_rmt_rx_done_callback(rmt_channel_handle_t channel, const
ESP_EVENT_DEFINE_BASE(ZH_TTP229); 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)
{ {
ZH_LOGI("Touch pad initialization started."); 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(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Touch pad initialization failed. Invalid argument.");
ZH_ERROR_CHECK(*handle == NULL, ESP_ERR_INVALID_STATE, NULL, "Touch pad initialization failed. Touch pad is already initialized."); 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); *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."); ZH_ERROR_CHECK(*handle != NULL, ESP_ERR_NO_MEM, NULL, "Touch pad initialization failed. Failed to allocate touch pad handle.");
ZH_ERROR_CHECK(_zh_ttp229_validate_config(config, *handle) == ESP_OK, ESP_ERR_INVALID_ARG, heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. Initial configuration check failed.");
if (_vector == NULL) 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_init(&_vector, (uint16_t)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_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."); ZH_ERROR_CHECK(_zh_ttp229_resources_init(config) == ESP_OK, ESP_FAIL, _zh_ttp229_rollback_task_and_resources();
// clang-format off {ZH_ERROR_CHECK_CONT(zh_vector_delete_back(&_vector) == ESP_OK, NULL, "Failed delete vector data.")};
ZH_ERROR_CHECK(_zh_ttp229_task_init(config) == ESP_OK, ESP_FAIL, heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. Resources 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_task_init(config) == ESP_OK, ESP_FAIL, _zh_ttp229_rollback_task_and_resources();
{ZH_ERROR_CHECK_CONT(zh_vector_delete_back(&_vector) == ESP_OK, NULL, "Failed delete vector data.")};
heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. Processing task initialization failed.");
(*handle)->debounce_time = config->debounce_time;
(*handle)->last_event_tick = -1;
ZH_ERROR_CHECK(_zh_ttp229_gpio_init(config, *handle) == ESP_OK, ESP_FAIL, _zh_ttp229_rollback_task_and_resources();
{ZH_ERROR_CHECK_CONT(zh_vector_delete_back(&_vector) == ESP_OK, NULL, "Failed delete vector data.")};
heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. GPIO initialization failed.");
ZH_ERROR_CHECK(_zh_ttp229_rmt_init(config, *handle) == ESP_OK, ESP_FAIL, 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."); {ZH_ERROR_CHECK_CONT(gpio_isr_handler_remove((*handle)->sdo_gpio) == ESP_OK, NULL, "Remove GPIO isr handler failed.")};
// clang-format on {ZH_ERROR_CHECK_CONT(gpio_reset_pin((*handle)->sdo_gpio) == ESP_OK, NULL, "Reset GPIO failed.")};
ZH_ERROR_CHECK(_zh_ttp229_gpio_init(config, *handle) == ESP_OK, ESP_FAIL, {ZH_ERROR_CHECK_CONT(gpio_reset_pin((*handle)->scl_gpio) == ESP_OK, NULL, "Reset GPIO failed.")};
{ZH_ERROR_CHECK(rmt_disable((*handle)->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel disable failed.")}; _zh_ttp229_rollback_task_and_resources();
{ZH_ERROR_CHECK(rmt_disable((*handle)->rx_channel) == ESP_OK, ESP_FAIL, NULL, "RX channel disable failed.")}; {ZH_ERROR_CHECK_CONT(zh_vector_delete_back(&_vector) == ESP_OK, NULL, "Failed delete vector data.")};
{ZH_ERROR_CHECK(rmt_del_encoder((*handle)->copy_encoder) == ESP_OK, ESP_FAIL, NULL, "Delete encoder failed.")}; heap_caps_free(*handle); *handle = NULL, "Touch pad initialization failed. RMT initialization 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) if (_stats.min_stack_size == 0)
{ {
_stats.min_stack_size = config->stack_size; _stats.min_stack_size = config->stack_size;
} }
(*handle)->debounce_time = config->debounce_time; (*handle)->rx_needs_reset = false;
ZH_LOGI("Touch pad initialization completed successfully."); ZH_LOGI("Touch pad initialization completed successfully.");
return ESP_OK; 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)
{ {
ZH_LOGI("Touch pad deinitialization started."); ZH_LOGI("Touch pad deinitialization started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Touch pad deinitialization failed. Invalid argument."); 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; 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_find_item(&_vector, &(*handle)->device_number, &index) == ESP_OK, ESP_FAIL, 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."); ZH_ERROR_CHECK_CONT(zh_vector_delete_item(&_vector, (uint16_t)index) == ESP_OK, NULL, "Touch pad deinitialization failed. Vector delete item failed.");
ZH_ERROR_CHECK_CONT(gpio_isr_handler_remove((*handle)->sdo_gpio) == ESP_OK, NULL, "Touch pad deinitialization failed. Remove GPIO isr handler failed.");
ZH_ERROR_CHECK_CONT(rmt_disable((*handle)->tx_channel) == ESP_OK, NULL, "Touch pad deinitialization failed. TX channel disable failed.");
ZH_ERROR_CHECK_CONT(rmt_disable((*handle)->rx_channel) == ESP_OK, NULL, "Touch pad deinitialization failed. RX channel disable failed.");
ZH_ERROR_CHECK_CONT(rmt_del_encoder((*handle)->copy_encoder) == ESP_OK, NULL, "Touch pad deinitialization failed. Delete encoder failed.");
ZH_ERROR_CHECK_CONT(rmt_del_channel((*handle)->tx_channel) == ESP_OK, NULL, "Touch pad deinitialization failed. Delete TX channel failed.");
ZH_ERROR_CHECK_CONT(rmt_del_channel((*handle)->rx_channel) == ESP_OK, NULL, "Touch pad deinitialization failed. Delete RX channel failed.");
ZH_ERROR_CHECK_CONT(gpio_reset_pin((*handle)->sdo_gpio) == ESP_OK, NULL, "Touch pad deinitialization failed. Reset GPIO failed.");
ZH_ERROR_CHECK_CONT(gpio_reset_pin((*handle)->scl_gpio) == ESP_OK, NULL, "Touch pad deinitialization failed. Reset GPIO failed.");
(*handle)->rx_channel = NULL;
(*handle)->tx_channel = NULL;
uint16_t vector_size = 0; 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."); ZH_ERROR_CHECK(zh_vector_get_size(&_vector, &vector_size) == ESP_OK, ESP_FAIL, heap_caps_free(*handle); *handle = NULL, "Touch pad deinitialization failed. Failed to get vector size.");
if (vector_size == 0) if (vector_size == 0)
{
if (zh_ttp229 != NULL)
{
vTaskDelete(zh_ttp229);
zh_ttp229 = NULL;
}
if (_queue_handle != NULL)
{ {
vQueueDelete(_queue_handle); vQueueDelete(_queue_handle);
_queue_handle = NULL; _queue_handle = NULL;
vTaskDelete(zh_ttp229); }
zh_ttp229 = NULL; ZH_ERROR_CHECK(zh_vector_free(&_vector) == ESP_OK, ESP_FAIL, heap_caps_free(*handle); *handle = NULL, "Touch pad deinitialization failed. Free vector failed.");
ZH_ERROR_CHECK(zh_vector_free(&_vector) == ESP_OK, ESP_FAIL, NULL, "Touch pad deinitialization failed. Free vector failed.");
} }
heap_caps_free(*handle); heap_caps_free(*handle);
*handle = NULL; *handle = NULL;
@@ -234,7 +287,7 @@ void zh_ttp229_reset_stats(void)
ZH_LOGI("Error statistic reset successfully."); ZH_LOGI("Error statistic reset successfully.");
} }
static esp_err_t _zh_ttp229_validate_config(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle) // -V2008 static esp_err_t _zh_ttp229_validate_config(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle)
{ {
ZH_ERROR_CHECK(config->task_priority >= 1 && config->stack_size >= configMINIMAL_STACK_SIZE, ESP_ERR_INVALID_ARG, NULL, "Invalid task settings."); ZH_ERROR_CHECK(config->task_priority >= 1 && config->stack_size >= configMINIMAL_STACK_SIZE, ESP_ERR_INVALID_ARG, NULL, "Invalid task settings.");
ZH_ERROR_CHECK(config->queue_size >= 1, ESP_ERR_INVALID_ARG, NULL, "Invalid queue size."); ZH_ERROR_CHECK(config->queue_size >= 1, ESP_ERR_INVALID_ARG, NULL, "Invalid queue size.");
@@ -249,14 +302,13 @@ static esp_err_t _zh_ttp229_validate_config(const zh_ttp229_init_config_t *confi
} }
handle->rmt_tx_start_delay = config->rmt_tx_start_delay; handle->rmt_tx_start_delay = config->rmt_tx_start_delay;
handle->device_number = config->device_number; handle->device_number = config->device_number;
handle->work_mode = config->work_mode;
return ESP_OK; return ESP_OK;
} }
static esp_err_t _zh_ttp229_gpio_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle) // -V2008 static esp_err_t _zh_ttp229_gpio_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle)
{ {
ZH_ERROR_CHECK(config->scl_gpio < GPIO_NUM_MAX && config->sdo_gpio < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, NULL, "Invalid GPIO number.") ZH_ERROR_CHECK(config->scl_gpio < GPIO_NUM_MAX && config->sdo_gpio < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, NULL, "Invalid GPIO number.");
ZH_ERROR_CHECK(config->scl_gpio != config->sdo_gpio, ESP_ERR_INVALID_ARG, NULL, "SCL GPIO and SDO GPIO is same.") ZH_ERROR_CHECK(config->scl_gpio != config->sdo_gpio, ESP_ERR_INVALID_ARG, NULL, "SCL GPIO and SDO GPIO is same.");
gpio_config_t sdo_config = { gpio_config_t sdo_config = {
.mode = GPIO_MODE_INPUT, .mode = GPIO_MODE_INPUT,
.pin_bit_mask = (1ULL << config->sdo_gpio), .pin_bit_mask = (1ULL << config->sdo_gpio),
@@ -264,69 +316,80 @@ 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."); 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); 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(err == ESP_OK || err == ESP_ERR_INVALID_STATE, ESP_FAIL,
{ZH_ERROR_CHECK(gpio_reset_pin(config->sdo_gpio) == ESP_OK, ESP_FAIL, NULL, "Reset GPIO failed.")}, "Failed install isr service."); {ZH_ERROR_CHECK_CONT(gpio_reset_pin(config->sdo_gpio) == ESP_OK, 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_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."); {ZH_ERROR_CHECK_CONT(gpio_reset_pin(config->sdo_gpio) == ESP_OK, NULL, "Reset GPIO failed.")}, "Interrupt initialization failed.");
handle->sdo_gpio = config->sdo_gpio; handle->sdo_gpio = config->sdo_gpio;
handle->scl_gpio = config->scl_gpio;
return ESP_OK; return ESP_OK;
} }
static esp_err_t _zh_ttp229_rmt_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle) // -V2008 static esp_err_t _zh_ttp229_rmt_init(const zh_ttp229_init_config_t *config, zh_ttp229_handle_t *handle)
{ {
rmt_tx_channel_config_t tx_chan_config = { rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, .clk_src = RMT_CLK_SRC_DEFAULT,
.gpio_num = config->scl_gpio, .gpio_num = config->scl_gpio,
#if defined CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 #if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)
.mem_block_symbols = 64, .mem_block_symbols = 64,
#else #else
.mem_block_symbols = 48, .mem_block_symbols = 48,
#endif #endif
.resolution_hz = 1000000, .resolution_hz = 1000000,
.trans_queue_depth = 4, .trans_queue_depth = 1,
.flags.init_level = 1, .flags.init_level = 1,
}; };
ZH_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &handle->tx_channel) == ESP_OK, ESP_FAIL, NULL, "TX channel creation failed."); 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 = {}; rmt_copy_encoder_config_t copy_encoder_config = {};
ZH_ERROR_CHECK(rmt_new_copy_encoder(&copy_encoder_config, &handle->copy_encoder) == ESP_OK, ESP_FAIL, ZH_ERROR_CHECK(rmt_new_copy_encoder(&copy_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_CONT(rmt_del_channel(handle->tx_channel) == ESP_OK, NULL, "Delete channel failed.")}, "Copy encoder creation failed.");
ZH_ERROR_CHECK(rmt_enable(handle->tx_channel) == ESP_OK, ESP_FAIL, 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_CONT(rmt_del_encoder(handle->copy_encoder) == ESP_OK, 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_CONT(rmt_del_channel(handle->tx_channel) == ESP_OK, NULL, "Delete channel failed.")}, "Enable TX channel failed.");
rmt_rx_channel_config_t rx_chan_config = { rmt_rx_channel_config_t rx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, .clk_src = RMT_CLK_SRC_DEFAULT,
.gpio_num = config->sdo_gpio, .gpio_num = config->sdo_gpio,
#if defined CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 #if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)
.mem_block_symbols = 64, .mem_block_symbols = 64,
#else #else
.mem_block_symbols = 48, .mem_block_symbols = 48,
#endif #endif
.resolution_hz = 1000000, .resolution_hz = 1000000,
}; };
handle->scl_symbols_count = (uint8_t)config->work_mode;
for (uint8_t i = 0; i < handle->scl_symbols_count; ++i)
{
handle->scl_symbols[i] = (rmt_symbol_word_t){
.duration0 = 2,
.level0 = 0,
.duration1 = 2,
.level1 = 1,
};
}
ZH_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &handle->rx_channel) == ESP_OK, ESP_FAIL, 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_CONT(rmt_disable(handle->tx_channel) == ESP_OK, 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_CONT(rmt_del_encoder(handle->copy_encoder) == ESP_OK, 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_CONT(rmt_del_channel(handle->tx_channel) == ESP_OK, NULL, "Delete TX channel failed.")}, "RX channel creation failed.");
ZH_ERROR_CHECK(rmt_enable(handle->rx_channel) == ESP_OK, ESP_FAIL, 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_CONT(rmt_disable(handle->tx_channel) == ESP_OK, 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_CONT(rmt_del_encoder(handle->copy_encoder) == ESP_OK, 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_CONT(rmt_del_channel(handle->tx_channel) == ESP_OK, 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_CONT(rmt_del_channel(handle->rx_channel) == ESP_OK, NULL, "Delete RX channel failed.")}, "RX channel creation failed.");
rmt_rx_event_callbacks_t cbs = { rmt_rx_event_callbacks_t cbs = {
.on_recv_done = _zh_ttp229_rmt_rx_done_callback, .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_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_CONT(rmt_disable(handle->tx_channel) == ESP_OK, 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_CONT(rmt_disable(handle->rx_channel) == ESP_OK, 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_CONT(rmt_del_encoder(handle->copy_encoder) == ESP_OK, 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_CONT(rmt_del_channel(handle->tx_channel) == ESP_OK, 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_CONT(rmt_del_channel(handle->rx_channel) == ESP_OK, NULL, "Delete RX channel failed.")}, "TX callback register failed.");
return ESP_OK; return ESP_OK;
} }
static esp_err_t _zh_ttp229_resources_init(const zh_ttp229_init_config_t *config) static esp_err_t _zh_ttp229_resources_init(const zh_ttp229_init_config_t *config)
{ {
uint16_t vector_size = 0; uint16_t vector_size = 0;
zh_vector_get_size(&_vector, &vector_size); ZH_ERROR_CHECK(zh_vector_get_size(&_vector, &vector_size) == ESP_OK, ESP_FAIL, NULL, "Failed to get vector size.");
if (vector_size == 1) if (vector_size == 1)
{ {
_queue_handle = xQueueCreate(config->queue_size, sizeof(zh_ttp229_queue_t)); _queue_handle = xQueueCreate(config->queue_size, sizeof(zh_ttp229_queue_t));
@@ -338,7 +401,7 @@ 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) static esp_err_t _zh_ttp229_task_init(const zh_ttp229_init_config_t *config)
{ {
uint16_t vector_size = 0; uint16_t vector_size = 0;
zh_vector_get_size(&_vector, &vector_size); ZH_ERROR_CHECK(zh_vector_get_size(&_vector, &vector_size) == ESP_OK, ESP_FAIL, NULL, "Failed to get vector size.");
if (vector_size == 1) 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, 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,
@@ -347,166 +410,205 @@ static esp_err_t _zh_ttp229_task_init(const zh_ttp229_init_config_t *config)
return ESP_OK; return ESP_OK;
} }
static void IRAM_ATTR _zh_ttp229_isr_handler(void *arg) static void _zh_ttp229_rollback_task_and_resources(void)
{
if (_vector != NULL)
{
uint16_t vector_size = 0;
ZH_ERROR_CHECK_VOID(zh_vector_get_size(&_vector, &vector_size) == ESP_OK, NULL, "Failed to get vector size.");
if (vector_size == 1)
{
if (zh_ttp229 != NULL)
{
vTaskDelete(zh_ttp229);
zh_ttp229 = NULL;
}
if (_queue_handle != NULL)
{
vQueueDelete(_queue_handle);
_queue_handle = NULL;
}
}
}
}
static void _zh_ttp229_isr_handler(void *arg)
{ {
zh_ttp229_handle_t *ttp229_handle = (zh_ttp229_handle_t *)arg; zh_ttp229_handle_t *ttp229_handle = (zh_ttp229_handle_t *)arg;
if (ttp229_handle->rx_channel == NULL || ttp229_handle->tx_channel == NULL)
{
return;
}
gpio_intr_disable(ttp229_handle->sdo_gpio); gpio_intr_disable(ttp229_handle->sdo_gpio);
rmt_receive_config_t rx_receive_config = {.signal_range_min_ns = 100, .signal_range_max_ns = 2000000}; 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) 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; ++_stats.rmt_driver_error;
ttp229_handle->rx_needs_reset = true;
if (_queue_handle != NULL)
{
zh_ttp229_queue_t notify = {0};
notify.handle = ttp229_handle;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (xQueueSendFromISR(_queue_handle, &notify, &xHigherPriorityTaskWoken) != pdTRUE)
{
++_stats.queue_overflow_error;
gpio_intr_enable(ttp229_handle->sdo_gpio);
}
if (xHigherPriorityTaskWoken == pdTRUE)
{
portYIELD_FROM_ISR(pdTRUE);
}
}
return;
} }
// 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_direction(ttp229_handle->sdo_gpio, GPIO_MODE_OUTPUT);
gpio_set_level(ttp229_handle->sdo_gpio, 0); gpio_set_level(ttp229_handle->sdo_gpio, 0);
gpio_set_level(ttp229_handle->sdo_gpio, 1); gpio_set_level(ttp229_handle->sdo_gpio, 1);
gpio_set_direction(ttp229_handle->sdo_gpio, GPIO_MODE_INPUT); 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 < (uint8_t)ttp229_handle->work_mode; ++i)
{
scl_symbols[i] = (rmt_symbol_word_t){.duration0 = 2, .level0 = 0, .duration1 = 2, .level1 = 1};
}
rmt_transmit_config_t tx_transmit_config = {.loop_count = 0, .flags.eot_level = 1, .flags.queue_nonblocking = 1}; rmt_transmit_config_t tx_transmit_config = {.loop_count = 0, .flags.eot_level = 1, .flags.queue_nonblocking = 1};
if (rmt_transmit(ttp229_handle->tx_channel, ttp229_handle->copy_encoder, scl_symbols, sizeof(scl_symbols), &tx_transmit_config) != ESP_OK) if (rmt_transmit(ttp229_handle->tx_channel, ttp229_handle->copy_encoder, ttp229_handle->scl_symbols, ttp229_handle->scl_symbols_count * sizeof(rmt_symbol_word_t), &tx_transmit_config) != ESP_OK)
{ {
++_stats.rmt_driver_error; ++_stats.rmt_driver_error;
ttp229_handle->rx_needs_reset = true;
if (_queue_handle != NULL)
{
zh_ttp229_queue_t notify = {0};
notify.handle = ttp229_handle;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (xQueueSendFromISR(_queue_handle, &notify, &xHigherPriorityTaskWoken) != pdTRUE)
{
++_stats.queue_overflow_error;
gpio_intr_enable(ttp229_handle->sdo_gpio);
}
if (xHigherPriorityTaskWoken == pdTRUE)
{
portYIELD_FROM_ISR(pdTRUE);
}
}
return;
} }
portYIELD_FROM_ISR();
} }
static void IRAM_ATTR _zh_ttp229_isr_processing_task(void *pvParameter) // -V2008 static void _zh_ttp229_isr_processing_task(void *pvParameter)
{ {
(void)pvParameter; (void)pvParameter;
zh_ttp229_queue_t ttp229_queue = {0}; zh_ttp229_queue_t ttp229_queue = {0};
while (xQueueReceive(_queue_handle, &ttp229_queue, portMAX_DELAY) == pdTRUE) while (xQueueReceive(_queue_handle, &ttp229_queue, portMAX_DELAY) == pdTRUE)
{ {
zh_ttp229_handle_t *ttp229_handle = ttp229_queue.handle; zh_ttp229_handle_t *ttp229_handle = ttp229_queue.handle;
if (ttp229_handle->rx_needs_reset == true)
{
ttp229_handle->rx_needs_reset = false;
ZH_ERROR_CHECK_CONT(rmt_disable(ttp229_handle->rx_channel) == ESP_OK, ++_stats.rmt_driver_error, "Failed to disable RX channel.");
ZH_ERROR_CHECK_CONT(rmt_enable(ttp229_handle->rx_channel) == ESP_OK, ++_stats.rmt_driver_error, "Failed to enable RX channel.");
}
const uint16_t duration1 = ttp229_queue.rx_symbol.duration1;
const uint16_t base = ttp229_handle->rmt_tx_start_delay;
uint8_t pad_number = 0; uint8_t pad_number = 0;
// Decode pad number from duration1: each pad has a unique range if (duration1 == (base - 1) || duration1 == base || duration1 == (base + 1))
// 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)))
{ {
pad_number = 1; pad_number = 1;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 3)) || else if (duration1 == (base + 3) || duration1 == (base + 4) || duration1 == (base + 5))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 4)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 5)))
{ {
pad_number = 2; pad_number = 2;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 7)) || else if (duration1 == (base + 7) || duration1 == (base + 8) || duration1 == (base + 9))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 8)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 9)))
{ {
pad_number = 3; pad_number = 3;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 11)) || else if (duration1 == (base + 11) || duration1 == (base + 12) || duration1 == (base + 13))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 12)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 13)))
{ {
pad_number = 4; pad_number = 4;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 15)) || else if (duration1 == (base + 15) || duration1 == (base + 16) || duration1 == (base + 17))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 16)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 17)))
{ {
pad_number = 5; pad_number = 5;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 19)) || else if (duration1 == (base + 19) || duration1 == (base + 20) || duration1 == (base + 21))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 20)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 21)))
{ {
pad_number = 6; pad_number = 6;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 23)) || else if (duration1 == (base + 23) || duration1 == (base + 24) || duration1 == (base + 25))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 24)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 25)))
{ {
pad_number = 7; pad_number = 7;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 27)) || else if (duration1 == (base + 27) || duration1 == (base + 28) || duration1 == (base + 29))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 28)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 29)))
{ {
pad_number = 8; pad_number = 8;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 31)) || else if (duration1 == (base + 31) || duration1 == (base + 32) || duration1 == (base + 33))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 32)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 33)))
{ {
pad_number = 9; pad_number = 9;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 35)) || else if (duration1 == (base + 35) || duration1 == (base + 36) || duration1 == (base + 37))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 36)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 37)))
{ {
pad_number = 10; pad_number = 10;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 39)) || else if (duration1 == (base + 39) || duration1 == (base + 40) || duration1 == (base + 41))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 40)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 41)))
{ {
pad_number = 11; pad_number = 11;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 43)) || else if (duration1 == (base + 43) || duration1 == (base + 44) || duration1 == (base + 45))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 44)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 45)))
{ {
pad_number = 12; pad_number = 12;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 47)) || else if (duration1 == (base + 47) || duration1 == (base + 48) || duration1 == (base + 49))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 48)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 49)))
{ {
pad_number = 13; pad_number = 13;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 51)) || else if (duration1 == (base + 51) || duration1 == (base + 52) || duration1 == (base + 53))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 52)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 53)))
{ {
pad_number = 14; pad_number = 14;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 55)) || else if (duration1 == (base + 55) || duration1 == (base + 56) || duration1 == (base + 57))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 56)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 57)))
{ {
pad_number = 15; pad_number = 15;
} }
else if ((ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 59)) || else if (duration1 == (base + 59) || duration1 == (base + 60) || duration1 == (base + 61))
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 60)) ||
(ttp229_handle->rx_symbols.duration1 == (ttp229_handle->rmt_tx_start_delay + 61)))
{ {
pad_number = 16; pad_number = 16;
} }
if (pad_number != 0) if (pad_number > ttp229_handle->scl_symbols_count)
{ {
pad_number = 0;
}
TickType_t now = xTaskGetTickCount();
TickType_t debounce_ticks = pdMS_TO_TICKS(ttp229_handle->debounce_time);
bool first_event = (ttp229_handle->last_event_tick == (TickType_t)-1) ? true : false;
bool debounce_elapsed = first_event || ((now - ttp229_handle->last_event_tick) >= debounce_ticks) ? true : false;
if (pad_number != 0 && debounce_elapsed == true)
{
ttp229_handle->last_event_tick = now;
zh_ttp229_event_on_isr_t ttp229_data = {0}; zh_ttp229_event_on_isr_t ttp229_data = {0};
ttp229_data.pad_number = pad_number; ttp229_data.pad_number = pad_number;
ttp229_data.device_number = ttp229_handle->device_number; ttp229_data.device_number = ttp229_handle->device_number;
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."); ZH_ERROR_CHECK_CONT(esp_event_post(ZH_TTP229, 0, &ttp229_data, sizeof(zh_ttp229_event_on_isr_t), pdMS_TO_TICKS(10)) == 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); gpio_intr_enable(ttp229_handle->sdo_gpio);
_stats.min_stack_size = (uint32_t)uxTaskGetStackHighWaterMark(NULL); _stats.min_stack_size = (uint32_t)uxTaskGetStackHighWaterMark(NULL);
} }
vTaskDelete(NULL); vTaskDelete(NULL);
} }
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) static bool _zh_ttp229_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{ {
(void)channel;
(void)edata; (void)edata;
zh_ttp229_handle_t *ttp229_handle = (zh_ttp229_handle_t *)user_data; zh_ttp229_handle_t *ttp229_handle = (zh_ttp229_handle_t *)user_data;
if (_queue_handle == NULL)
{
return false;
}
zh_ttp229_queue_t ttp229_queue = {0}; zh_ttp229_queue_t ttp229_queue = {0};
ttp229_queue.handle = ttp229_handle; ttp229_queue.handle = ttp229_handle;
ttp229_queue.rx_symbol = ttp229_handle->rx_symbols;
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (xQueueSendFromISR(_queue_handle, &ttp229_queue, &xHigherPriorityTaskWoken) != pdTRUE) if (xQueueSendFromISR(_queue_handle, &ttp229_queue, &xHigherPriorityTaskWoken) != pdTRUE)
{ {
++_stats.queue_overflow_error; ++_stats.queue_overflow_error;
gpio_intr_enable(ttp229_handle->sdo_gpio);
} }
if (xHigherPriorityTaskWoken == pdTRUE) if (xHigherPriorityTaskWoken == pdTRUE)
{ {