Files
2026-10-17 09:31:50 +03:00

17 KiB
Raw Permalink Blame History

Описание компонента 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.

Примеры использования

Базовый пример

#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); // очистка ресурсов
}

Работа с несколькими устройствами

#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); // последний объект удаляет задачу и очередь
}

Мониторинг статистики

#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".

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)

Установка

cd ваш_проект/components
git clone https://github.com/aZholtikov/zh_ttp229
#include "zh_ttp229.h"