diff --git a/wiki_ru.md b/wiki_ru.md new file mode 100644 index 0000000..2f93a32 --- /dev/null +++ b/wiki_ru.md @@ -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" +```