Files
zh_pcf8574/wiki_ru.md
T
2026-10-17 20:15:36 +03:00

18 KiB
Raw Blame History

Описание компонента 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), чтобы входные пины не были перезаписаны нулём. Это гарантирует, что только выходные пины изменяют состояние.

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

Базовый пример: инициализация и чтение/запись

#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, &reg);

    // Запись значения в порты (P7=1, P1=1, P0=0)
    zh_pcf8574_write(&pcf8574_handle, 0b10000010);

    // Деинициализация устройства
    zh_pcf8574_deinit(&pcf8574_handle);
}

Работа с отдельными пинами

#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);
}

Обработка прерываний

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

// Установка уровня логирования 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)

Установка

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