18 KiB
Описание компонента zh_pcf8574
Общие сведения
zh_pcf8574 — это драйвер для ESP-IDF, реализующий работу с 8-битным I2C GPIO-экспандером PCF8574.
Компонент поддерживает чтение и запись состояний GPIO-пинов, обработку прерываний с определением уровня сигнала, а также предоставляет статистику для отладки и мониторинга. Драйвер создаёт внутренний FreeRTOS-задачу для обработки прерываний от всех подключённых устройств PCF8574 и использует событийную модель на базе esp_event.
Основные возможности
- I2C-коммуникация — работа через новый I2C master driver из ESP-IDF v5.
- Конфигурация направления GPIO — каждая пин (P0–P7) может быть настроена как вход или выход.
- Обработка прерываний — обнаружение изменения уровня на входных пинах с временной меткой.
- Событийная архитектура — интеграция с
esp_eventдля асинхронной обработки событий. - Статистика ошибок — подсчёт ошибок I2C, очереди, вектора и мониторинг стека задачи.
- Поддержка нескольких устройств — работа с несколькими 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() |
Сбрасывает все счётчики ошибок и данные мониторинга. |
Потокобезопасность
- Семафор FreeRTOS — бинарный семафор
_interrupt_semaphoreзащищает синхронизацию между ISR (вызываемой по прерыванию) и задачей_zh_pcf8574_isr_processing_task. ISR отправляет сигнал черезxSemaphoreGiveFromISR(), задача блокируется наxSemaphoreTake(). - Глобальный вектор — доступ к вектору
_vectorне защищён мьютексом. Все операции с вектором выполняются либо в контексте API-функций (синхронных), либо в контексте задачи обработки прерываний. Одновременный доступ к вектору из разных задач не предусмотрен. - Глобальная статистика — счётчики в
_statsобновляются без блокировок. Чтение и запись происходят атомарно на уровнеuint32_t, что безопасно для ESP32.
Алгоритмы обработки прерываний
Задача обработки прерываний
- Задача блокируется на семафоре
xSemaphoreTake(_interrupt_semaphore, portMAX_DELAY). - При получении сигнала задача считывает размер вектора устройств.
- Для каждого устройства в векторе:
- Считываются старые данные из вектора.
- Выполняется чтение регистра через I2C (
_zh_pcf8574_read_register()). - Данные обновляются в векторе через
zh_vector_change_item().
- Для каждого пина, настроенного как вход:
- Сравниваются биты старого и нового значения регистра.
- При обнаружении различий формируется событие
zh_pcf8574_event_on_isr_tс временной меткой, адресом устройства, номером пина и текущим уровнем. - Событие отправляется через
esp_event_post()с таймаутом 10 мс.
- После обработки всех устройств обновляется статистика минимального размера стека задачи (
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, ®);
// Запись значения в порты (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-коммуникации, конфигурации или создания задачи |
Ограничения
- Только I2C Fast Mode (100 кГц) — частота ограничена максимальным значением 100 000 Гц.
- Ограниченный диапазон адресов — поддерживаются только адреса
0x20–0x27и0x38–0x3F(стандартные адреса PCF8574 с тремя адресными пинами). - Одно GPIO для всех прерываний — все устройства PCF8574 делят один GPIO-пин для прерываний. Все подключённые устройства должны иметь соединённые выводы INT.
- Задача обработки прерываний создаётся один раз — задача создаётся только при инициализации первого устройства с прерываниями. Все последующие устройства используют ту же задачу.
- Не IRAM-safe — обработка прерываний не выполняется в IRAM. ISR использует
ESP_INTR_FLAG_LOWMEDи не является безопасной для вызова из IRAM-контекста. - Нет мьютекса для вектора — вектор данных устройств не защищён мьютексом. Одновременный доступ из нескольких задач не поддерживается.
- Только отрицательный фронт прерывания — 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"