doc: added wiki
This commit is contained in:
1 parent
95fca401de
commit
c788e2f9d5
1 file changed
+301
+301
@@ -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"
|
||||||
|
```
|
||||||
Reference in new issue
Block a user