doc: added wiki

This commit is contained in:
aZholtikov committed 2026-10-17 20:15:36 +03:00
1 parent 95fca401de
commit c788e2f9d5
1 file changed
+301
+301
View File
@@ -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, &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"
```