17 KiB
Описание компонента zh_ttp229
Общие сведения
zh_ttp229 — это компонент ESP-IDF для работы с контроллером ёмкостных сенсорных кнопок TTP229 по интерфейсу SCL/SDO. Чтение состояния кнопок выполняется через периферию RMT вместо программного переключения GPIO (bit-banging).
Компонент регистрирует прерывание на выводе SDO, по фронтальному фронту генерирует тактовые импульсы SCL через RMT TX, принимает импульс с линии SDO через RMT RX и публикует событие ZH_TTP229 с номером нажатой кнопки.
Основные возможности
- Два режима работы — конфигурации на 8 и на 16 кнопок.
- RMT-обмен — тактирование и приём без bit-banging, с IRAM-safe обработчиками.
- Многоустройственность — несколько плат TTP229 с уникальными номерами устройств.
- Событийный API — уведомления через фреймворк
esp_event. - Debounce по тикам — отбрасывание дребезга без задержек в задаче обработки.
- Восстановление RX-канала — автоматический сброс RMT RX при ошибках драйвера.
- Сбор статистики — счётчики ошибок RMT, постинга событий, переполнения очереди, остаток стека.
- Самоочистка ресурсов — общая очередь и задача удаляются при деинициализации последнего устройства.
Внутреннее устройство
Структура 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. |
Потокобезопасность
- Очередь FreeRTOS — единственной синхронизирующей примитивом является общая очередь
_queue_handle: ISR handler и RMT RX callback кладут в неё записи черезxQueueSendFromISR(), задача обработки забирает черезxQueueReceive(). Взаимные исключения между задачами пользователя не предусмотрены. - Флаг
rx_needs_reset— объявлен какvolatile bool, устанавливается из ISR и сбрасывается в задаче обработки; используется для отложенного сброса RMT RX канала вне контекста прерывания. - Отключение прерываний GPIO — на время цикла чтения прерывание SDO отключается (
gpio_intr_disable()) и включается обратно в задаче обработки после постинга события, что исключает повторный вход в ISR во время обмена. - Удаление — при переполнении очереди или ошибке RMT прерывания GPIO включаются сразу из ISR, чтобы устройство не осталось «зависшим».
Алгоритмы
Опрос кнопок (протокол SCL/SDO)
- Фронтальный фронт на SDO (контроллер готов передать данные) вызывает
_zh_ttp229_isr_handler(). - ISR вооружает приём одного символа
rmt_receive()на RX канале (окно сигнала 100 нс – 2 мкс). - Кратковременный импульс на SDO (OUTPUT low → high → INPUT) синхронизирует контроллер.
- Через RMT TX передаются
scl_symbols_countтактовых символов SCL (каждый: 2 мкс low + 2 мкс high,init_level = 1). - По завершении приёма 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; устройство не найдено в векторе при деинициализации |
Ограничения
- Первое нажатие после старта/сброса не работает — особенность контроллера TTP229.
- Мультитач не поддерживается — за один цикл декодируется только одна кнопка.
device_numberдолжен быть уникальным и > 0 — используется как ключ в векторе устройств.- Общая очередь и задача используют параметры первого устройства —
queue_size,stack_sizeиtask_priorityпоследующих устройств игнорируются. rmt_tx_start_delayподбирается индивидуально — минимум 15 мкс, точность зависит от платы; определяется экспериментально или логическим анализатором.- Первое нажатие после инициализации может быть потеряно из-за синхронизации контроллера.
- Потокобезопасность ограничена — функции
init/deinitне защищены от одновременного вызова из нескольких задач. 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"