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

267 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Описание компонента 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"
```