Files
2026-10-17 20:15:36 +03:00

302 lines
18 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_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, &reg);
// Запись значения в порты (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"
```