From c788e2f9d5316936026e6987b044f4bddb08458f Mon Sep 17 00:00:00 2001 From: Alexey Zholtikov Date: Sat, 17 Oct 2026 20:15:36 +0300 Subject: [PATCH] doc: added wiki --- wiki_ru.md | 301 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 wiki_ru.md diff --git a/wiki_ru.md b/wiki_ru.md new file mode 100644 index 0000000..11ab795 --- /dev/null +++ b/wiki_ru.md @@ -0,0 +1,301 @@ +# Описание компонента zh_pcf8574 + +## Общие сведения + +**zh_pcf8574** — это драйвер для ESP-IDF, реализующий работу с 8-битным I2C GPIO-экспандером PCF8574. + +Компонент поддерживает чтение и запись состояний GPIO-пинов, обработку прерываний с определением уровня сигнала, а также предоставляет статистику для отладки и мониторинга. Драйвер создаёт внутренний FreeRTOS-задачу для обработки прерываний от всех подключённых устройств PCF8574 и использует событийную модель на базе `esp_event`. + +## Основные возможности + +1. **I2C-коммуникация** — работа через новый I2C master driver из ESP-IDF v5. +2. **Конфигурация направления GPIO** — каждая пин (P0–P7) может быть настроена как вход или выход. +3. **Обработка прерываний** — обнаружение изменения уровня на входных пинах с временной меткой. +4. **Событийная архитектура** — интеграция с `esp_event` для асинхронной обработки событий. +5. **Статистика ошибок** — подсчёт ошибок I2C, очереди, вектора и мониторинг стека задачи. +6. **Поддержка нескольких устройств** — работа с несколькими PCF8574 на одной I2C-шине с разными адресами. + +## Внутреннее устройство + +### Структура `zh_pcf8574_handle_t` + +Публичная непрозрачная структура-хендл, используемая всеми API-функциями для идентификации экземпляра устройства. + +| Поле | Тип | Описание | +|------|-----|----------| +| `i2c_address` | `uint8_t` | 7-битный I2C-адрес данного экземпляра устройства | + +### Структура `_zh_pcf8574_vector_data_t` + +Внутренняя структура данных, хранимая в векторе `zh_vector` для каждого подключённого устройства. + +| Поле | Тип | Описание | +|------|-----|----------| +| `i2c_address` | `uint8_t` | I2C-адрес устройства | +| `gpio_work_mode` | `uint8_t` | Маска направлений GPIO (1 = вход, 0 = выход) | +| `gpio_status` | `uint8_t` | Текущее состояние GPIO-портов | +| `dev_handle` | `i2c_master_dev_handle_t` | I2C-хендл устройства для коммуникации | + +### Управление памятью + +- Хендл устройства (`zh_pcf8574_handle_t`) выделяется через `heap_caps_calloc()` при инициализации и освобождается через `heap_caps_free()` при деинициализации. +- Состояние всех устройств хранится в глобальном векторе `_vector` типа `zh_vector_t`, реализованном в компоненте `zh_vector`. +- Вектор инициализируется при первом вызове `zh_pcf8574_init()` и освобождается при удалении последнего устройства в `zh_pcf8574_deinit()`. +- При удалении устройства данные удаляются из вектора через `zh_vector_delete_item()`. + +### Глобальные ресурсы + +- `_vector` — глобальный вектор для хранения данных всех подключённых устройств. +- `_interrupt_semaphore` — бинарный семафор FreeRTOS для синхронизации ISR и задачи обработки прерываний. +- `_interrupt_gpio` — номер GPIO, используемого для прерываний (GPIO_NUM_MAX, если не используется). +- `_gpio_matrix` — статический массив `{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}` для побитовой маскировки пинов. +- `_stats` — глобальная структура статистики ошибок. +- `zh_pcf8574` — глобальный хендл FreeRTOS-задачи обработки прерываний. + +## API (интерфейс) + +### Инициализация и освобождение + +| Функция | Описание | +|---------|----------| +| `zh_pcf8574_init()` | Инициализирует устройство PCF8574: создаёт I2C-соединение, настраивает GPIO и при необходимости создаёт задачу обработки прерываний. | +| `zh_pcf8574_deinit()` | Освобождает I2C-ресурсы и хендл устройства. Удаляет задачу обработки прерываний только при удалении последнего экземпляра. | + +### Чтение и запись + +| Функция | Описание | +|---------|----------| +| `zh_pcf8574_read()` | Считывает байт с входных портов PCF8574 через I2C и обновляет внутреннее состояние устройства. | +| `zh_pcf8574_write()` | Записывает байт в выходные порты PCF8574 через I2C и обновляет внутреннее состояние устройства. | +| `zh_pcf8574_reset()` | Сбрасывает все выходные пины в низкий уровень, а входные — в высокий, записывая маску направлений в порт. | + +### Доступ к отдельным пинам + +| Функция | Описание | +|---------|----------| +| `zh_pcf8574_read_gpio()` | Считывает текущий логический уровень указанного GPIO-пина. | +| `zh_pcf8574_write_gpio()` | Устанавливает логический уровень указанного GPIO-пина, настроенного как выход. | + +### Статистика + +| Функция | Описание | +|---------|----------| +| `zh_pcf8574_get_stats()` | Возвращает указатель на структуру со счётчиками ошибок и данными мониторинга. | +| `zh_pcf8574_reset_stats()` | Сбрасывает все счётчики ошибок и данные мониторинга. | + +## Потокобезопасность + +1. **Семафор FreeRTOS** — бинарный семафор `_interrupt_semaphore` защищает синхронизацию между ISR (вызываемой по прерыванию) и задачей `_zh_pcf8574_isr_processing_task`. ISR отправляет сигнал через `xSemaphoreGiveFromISR()`, задача блокируется на `xSemaphoreTake()`. +2. **Глобальный вектор** — доступ к вектору `_vector` не защищён мьютексом. Все операции с вектором выполняются либо в контексте API-функций (синхронных), либо в контексте задачи обработки прерываний. Одновременный доступ к вектору из разных задач не предусмотрен. +3. **Глобальная статистика** — счётчики в `_stats` обновляются без блокировок. Чтение и запись происходят атомарно на уровне `uint32_t`, что безопасно для ESP32. + +## Алгоритмы обработки прерываний + +### Задача обработки прерываний + +1. Задача блокируется на семафоре `xSemaphoreTake(_interrupt_semaphore, portMAX_DELAY)`. +2. При получении сигнала задача считывает размер вектора устройств. +3. Для каждого устройства в векторе: + - Считываются старые данные из вектора. + - Выполняется чтение регистра через I2C (`_zh_pcf8574_read_register()`). + - Данные обновляются в векторе через `zh_vector_change_item()`. +4. Для каждого пина, настроенного как вход: + - Сравниваются биты старого и нового значения регистра. + - При обнаружении различий формируется событие `zh_pcf8574_event_on_isr_t` с временной меткой, адресом устройства, номером пина и текущим уровнем. + - Событие отправляется через `esp_event_post()` с таймаутом 10 мс. +5. После обработки всех устройств обновляется статистика минимального размера стека задачи (`uxTaskGetStackHighWaterMark()`). + +### Запись в регистр + +При записи в регистр (`zh_pcf8574_write()` и `zh_pcf8574_write_gpio()`) значение маскируется с маской направлений (`gpio_work_mode`), чтобы входные пины не были перезаписаны нулём. Это гарантирует, что только выходные пины изменяют состояние. + +## Примеры использования + +### Базовый пример: инициализация и чтение/запись + +```c +#include "zh_pcf8574.h" + +#define I2C_PORT (I2C_NUM_MAX - 1) + +zh_pcf8574_handle_t *pcf8574_handle = NULL; + +void app_main(void) +{ + // Настройка уровня логирования + esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR); + + // Настройка I2C-шины + i2c_master_bus_config_t i2c_bus_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = I2C_PORT, + .scl_io_num = GPIO_NUM_22, + .sda_io_num = GPIO_NUM_21, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + i2c_master_bus_handle_t i2c_bus_handle = NULL; + i2c_new_master_bus(&i2c_bus_config, &i2c_bus_handle); + + // Инициализация конфигурации PCF8574 + zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT(); + config.i2c_handle = i2c_bus_handle; + config.i2c_address = 0x38; + config.p0_gpio_work_mode = ZH_PCF8574_GPIO_INPUT; + config.p1_gpio_work_mode = ZH_PCF8574_GPIO_OUTPUT; + + // Инициализация устройства + esp_err_t err = zh_pcf8574_init(&config, &pcf8574_handle); + if (err != ESP_OK) + { + ESP_LOGE("APP", "Failed to initialize PCF8574: %s", esp_err_to_name(err)); + return; + } + + // Чтение текущего состояния портов + uint8_t reg = 0; + zh_pcf8574_read(&pcf8574_handle, ®); + + // Запись значения в порты (P7=1, P1=1, P0=0) + zh_pcf8574_write(&pcf8574_handle, 0b10000010); + + // Деинициализация устройства + zh_pcf8574_deinit(&pcf8574_handle); +} +``` + +### Работа с отдельными пинами + +```c +#include "zh_pcf8574.h" + +zh_pcf8574_handle_t *pcf8574_handle = NULL; + +void app_main(void) +{ + // ... инициализация I2C и PCF8574 ... + + bool gpio_status = false; + + // Чтение состояния пина P0 + zh_pcf8574_read_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P0, &gpio_status); + printf("P0 status: %d\n", gpio_status); + + // Установка пина P7 в высокий уровень + zh_pcf8574_write_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P7, ZH_PCF8574_GPIO_HIGH); + + // Сброс пина P4 в низкий уровень + zh_pcf8574_write_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P4, ZH_PCF8574_GPIO_LOW); + + // Чтение состояния пина P4 после записи + zh_pcf8574_read_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P4, &gpio_status); + printf("P4 status: %d\n", gpio_status); + + // Сброс всех пинов + zh_pcf8574_reset(&pcf8574_handle); +} +``` + +### Обработка прерываний + +```c +#include "zh_pcf8574.h" + +zh_pcf8574_handle_t *pcf8574_handle = NULL; + +// Регистратор обработчика событий +void zh_pcf8574_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data); + +void app_main(void) +{ + // ... инициализация I2C ... + + // Создание событийной петли + esp_event_loop_create_default(); + + // Регистрация обработчика событий PCF8574 + esp_event_handler_instance_register(ZH_PCF8574, ESP_EVENT_ANY_ID, + &zh_pcf8574_event_handler, NULL, NULL); + + // Инициализация с поддержкой прерываний + zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT(); + config.i2c_handle = i2c_bus_handle; + config.i2c_address = 0x38; + config.p0_gpio_work_mode = ZH_PCF8574_GPIO_INPUT; // Вход для прерываний + config.interrupt_gpio = GPIO_NUM_14; // GPIO для прерывания + + zh_pcf8574_init(&config, &pcf8574_handle); + + for (;;) + { + vTaskDelay(60000 / portTICK_PERIOD_MS); + } +} + +// Обработчик событий прерываний +void zh_pcf8574_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + zh_pcf8574_event_on_isr_t *event = (zh_pcf8574_event_on_isr_t *)event_data; + printf("Interrupt on 0x%02X, GPIO %d, level %d, time %lld us\n", + event->i2c_address, event->gpio_number, + event->gpio_level, event->interrupt_time); +} +``` + +## Логирование + +Компонент использует ESP-IDF логирование с тегом `"zh_pcf8574"`. + +```c +// Установка уровня логирования ERROR +esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR); + +// Установка уровня логирования INFO +esp_log_level_set("zh_pcf8574", ESP_LOG_INFO); +``` + +Макросы логирования: +- `ZH_LOGI` — информационные сообщения (через `ESP_LOGI`). +- `ZH_LOGE` — сообщения об ошибках с кодом ошибки, файлом и строкой (через `ESP_LOGE`). + +## Ошибки + +| Код | Описание | +|-----|----------| +| `ESP_OK` | Успешное выполнение | +| `ESP_ERR_INVALID_ARG` | Неверный аргумент (NULL-указатели, недопустимые значения) | +| `ESP_ERR_INVALID_STATE` | Устройство уже инициализировано | +| `ESP_ERR_NOT_FOUND` | Хендл не соответствует валидному экземпляру устройства | +| `ESP_ERR_NO_MEM` | Не удалось выделить память | +| `ESP_FAIL` | Ошибка I2C-коммуникации, конфигурации или создания задачи | + +## Ограничения + +1. **Только I2C Fast Mode (100 кГц)** — частота ограничена максимальным значением 100 000 Гц. +2. **Ограниченный диапазон адресов** — поддерживаются только адреса `0x20–0x27` и `0x38–0x3F` (стандартные адреса PCF8574 с тремя адресными пинами). +3. **Одно GPIO для всех прерываний** — все устройства PCF8574 делят один GPIO-пин для прерываний. Все подключённые устройства должны иметь соединённые выводы INT. +4. **Задача обработки прерываний создаётся один раз** — задача создаётся только при инициализации первого устройства с прерываниями. Все последующие устройства используют ту же задачу. +5. **Не IRAM-safe** — обработка прерываний не выполняется в IRAM. ISR использует `ESP_INTR_FLAG_LOWMED` и не является безопасной для вызова из IRAM-контекста. +6. **Нет мьютекса для вектора** — вектор данных устройств не защищён мьютексом. Одновременный доступ из нескольких задач не поддерживается. +7. **Только отрицательный фронт прерывания** — ISR настроен на детектирование отрицательного фронта (`GPIO_INTR_NEGEDGE`). + +## Требования для сборки + +- ESP-IDF v5.0+ (с новым I2C master driver) +- FreeRTOS (входит в состав ESP-IDF) +- Компонент `zh_vector` (встроенный компонент для работы с векторами) +- `esp_timer` (для временных меток прерываний) +- `esp_event` (событийная framework) + +## Установка + +```bash +cd ваш_проект/components +git clone https://github.com/aZholtikov/zh_pcf8574 +``` + +```c +#include "zh_pcf8574.h" +```