Compare commits

...
11 Commits
Author SHA1 Message Date
aZholtikov c781acfc65 perf: updated error checks 2026-10-18 18:26:44 +03:00
aZholtikov c788e2f9d5 doc: added wiki 2026-10-17 20:15:36 +03:00
aZholtikov 95fca401de doc: updated readme 2026-10-17 20:15:16 +03:00
aZholtikov f47875b700 perf: updated error checks 2026-10-04 20:49:03 +03:00
aZholtikov eb70315c9a perf: updated error checks 2026-10-04 20:27:24 +03:00
aZholtikov e7edc8399f style: updated comments 2026-09-12 12:35:40 +03:00
aZholtikov 923dd6542f doc: updated readme 2026-08-30 17:26:12 +03:00
aZholtikov e2455322ac refactor: suppressed unused parameter warnings 2026-08-27 18:50:55 +03:00
aZholtikov f4f74e9d5a perf: optimized interrupt processing 2026-08-24 17:59:22 +03:00
aZholtikov 0785eeb3e5 doc: updated wiki 2026-08-16 08:48:11 +03:00
aZholtikov a124922338 doc: updated comments 2026-08-16 06:29:16 +03:00
6 changed files with 711 additions and 1105 deletions

No files matched your search

+286 -19
View File
@@ -1,30 +1,26 @@
# ESP32 ESP-IDF component for PCF8574 8-bit I/O expander
## Wiki
[EN](WIKI_EN.md) | [RU](WIKI_RU.md)
## Tested on
1. [ESP32 ESP-IDF v6.0.0](https://docs.espressif.com/projects/esp-idf/en/v6.0/esp32/index.html)
## SAST Tools
[PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) — static analyzer for C, C++, C#, and Java code.
## Features
1. I2C communication via esp-idf i2c_master driver.
2. Configurable GPIO direction for each pin (P0-P7).
3. Interrupt handling with GPIO level detection.
4. Event-based architecture using esp_event framework.
1. I2C communication via the ESP-IDF I2C master driver.
2. Configurable GPIO direction for each pin P0 through P7.
3. Interrupt handling with GPIO level detection and event notification.
4. Event-based architecture using the esp_event framework.
5. Statistics tracking for debugging and monitoring.
6. Multiple device instances on a single I2C bus.
## Attention
1. Requires ESP-IDF v5.0 or later with the I2C master driver.
2. Interrupt handling runs in task context and is not IRAM-safe.
3. A single interrupt GPIO pin is shared by all initialized devices.
4. The internal task is deleted only when the last device instance is deinitialized.
## Using
In an existing project, run the following command to install the components:
In an existing project, run the following command to install the component:
```text
```bash
cd ../your_project/components
git clone https://github.com/aZholtikov/zh_pcf8574
```
@@ -37,4 +33,275 @@ In the application, add the component:
## Examples
See Wiki [EN](WIKI_EN.md#usage-examples) | [RU](WIKI_RU.md#примеры-использования)
### Basic Example
```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);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
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);
zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
config.i2c_address = 0x38;
esp_err_t ret = zh_pcf8574_init(&config, &pcf8574_handle);
if (ret != ESP_OK)
{
printf("PCF8574 initialization error: %s\n", esp_err_to_name(ret));
return;
}
uint8_t reg = 0;
ret = zh_pcf8574_read(&pcf8574_handle, &reg);
if (ret == ESP_OK)
{
printf("GPIO status: 0x%02X\n", reg);
}
ret = zh_pcf8574_write(&pcf8574_handle, 0b10000010);
if (ret == ESP_OK)
{
printf("Write OK\n");
}
ret = zh_pcf8574_read(&pcf8574_handle, &reg);
if (ret == ESP_OK)
{
printf("GPIO status after write: 0x%02X\n", reg);
}
ret = zh_pcf8574_deinit(&pcf8574_handle);
if (ret == ESP_OK)
{
printf("Deinit OK\n");
}
}
```
---
### GPIO Example
```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);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
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);
zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
config.i2c_address = 0x38;
esp_err_t ret = zh_pcf8574_init(&config, &pcf8574_handle);
if (ret != ESP_OK)
{
printf("PCF8574 initialization error: %s\n", esp_err_to_name(ret));
return;
}
ret = zh_pcf8574_write_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P4, ZH_PCF8574_GPIO_HIGH);
if (ret == ESP_OK)
{
printf("P4 set high\n");
}
bool status = false;
ret = zh_pcf8574_read_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P4, &status);
if (ret == ESP_OK)
{
printf("P4 status: %d\n", status);
}
ret = zh_pcf8574_write_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P4, ZH_PCF8574_GPIO_LOW);
if (ret == ESP_OK)
{
printf("P4 set low\n");
}
ret = zh_pcf8574_read_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P4, &status);
if (ret == ESP_OK)
{
printf("P4 status: %d\n", status);
}
zh_pcf8574_deinit(&pcf8574_handle);
}
```
---
### Reset Example
```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);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
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);
zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
config.i2c_address = 0x38;
esp_err_t ret = zh_pcf8574_init(&config, &pcf8574_handle);
if (ret != ESP_OK)
{
printf("PCF8574 initialization error: %s\n", esp_err_to_name(ret));
return;
}
zh_pcf8574_write(&pcf8574_handle, 0xFF);
uint8_t reg = 0;
ret = zh_pcf8574_read(&pcf8574_handle, &reg);
if (ret == ESP_OK)
{
printf("GPIO before reset: 0x%02X\n", reg);
}
ret = zh_pcf8574_reset(&pcf8574_handle);
if (ret == ESP_OK)
{
printf("Reset OK\n");
}
ret = zh_pcf8574_read(&pcf8574_handle, &reg);
if (ret == ESP_OK)
{
printf("GPIO after reset: 0x%02X\n", reg);
}
zh_pcf8574_deinit(&pcf8574_handle);
}
```
---
### Interrupt Example
```c
#include "zh_pcf8574.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
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)
{
zh_pcf8574_event_on_isr_t *event = event_data;
printf("Interrupt on address 0x%02X pin P%d level %d time %lld\n",
event->i2c_address, event->gpio_number, event->gpio_level, event->interrupt_time);
}
void app_main(void)
{
esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
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);
esp_event_loop_create_default();
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;
esp_err_t ret = zh_pcf8574_init(&config, &pcf8574_handle);
if (ret != ESP_OK)
{
printf("PCF8574 initialization error: %s\n", esp_err_to_name(ret));
return;
}
for (;;)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
```
---
### Stats Example
```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);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
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);
zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT();
config.i2c_handle = i2c_bus_handle;
config.i2c_address = 0x38;
esp_err_t ret = zh_pcf8574_init(&config, &pcf8574_handle);
if (ret != ESP_OK)
{
printf("PCF8574 initialization error: %s\n", esp_err_to_name(ret));
return;
}
uint8_t reg = 0;
zh_pcf8574_read(&pcf8574_handle, &reg);
zh_pcf8574_write(&pcf8574_handle, 0xAA);
zh_pcf8574_read(&pcf8574_handle, &reg);
const zh_pcf8574_stats_t *stats = zh_pcf8574_get_stats();
printf("I2C driver errors: %ld\n", stats->i2c_driver_error);
printf("Event post errors: %ld\n", stats->event_post_error);
printf("Vector errors: %ld\n", stats->vector_error);
printf("Queue overflow errors: %ld\n", stats->queue_overflow_error);
printf("Min free stack size: %ld\n", stats->min_stack_size);
zh_pcf8574_reset_stats();
stats = zh_pcf8574_get_stats();
printf("After reset I2C driver errors: %ld\n", stats->i2c_driver_error);
zh_pcf8574_deinit(&pcf8574_handle);
}
```
-496
View File
@@ -1,496 +0,0 @@
# zh_pcf8574 - PCF8574(A) 8-bit I/O Expander Component for ESP-IDF
## Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Installation](#installation)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [Technical Specifications](#technical-specifications)
- [Error Codes](#error-codes)
- [Contributing](#contributing)
- [License](#license)
---
## Overview
`zh_pcf8574` is an ESP-IDF component for 8-bit I/O expanders PCF8574 and PCF8574A. It provides a convenient API for working with GPIO expanders via I2C bus. The component supports up to 16 expanders on a single I2C bus and can generate interrupts when input GPIO states change.
The component is designed specifically for ESP32 microcontrollers and uses ESP-IDF v5.0+ I2C driver API.
---
## Features
1. **Support for 16 Expanders**: Up to 16 PCF8574(A) on one I2C bus (addresses 0x20-0x27 and 0x38-0x3F)
2. **GPIO Modes**: Support for output and input modes for each of the 8 GPIO pins
3. **Interrupt Generation**: Support for interrupts from input GPIOs (negative edge)
4. **Error Statistics**: Built-in counters for I2C, event, vector, and queue overflow errors
5. **Thread-Safe**: Uses ESP-IDF I2C driver and FreeRTOS
6. **Minimal Overhead**: Low memory and CPU requirements
7. **Simple API**: Easy read/write for all GPIO and individual pins
---
## Installation
Navigate to your project's components directory:
```bash
cd ../your_project/components
```
Clone the repository:
```bash
git clone https://github.com/aZholtikov/zh_pcf8574.git
```
In your application, include the header:
```c
#include "zh_pcf8574.h"
```
The component will be automatically built with your project.
### Required menuconfig Settings
For correct operation, enable the following settings in menuconfig:
```text
GPIO_CTRL_FUNC_IN_IRAM
I2C_ISR_IRAM_SAFE
I2C_MASTER_ISR_HANDLER_IN_IRAM
```
---
## API Reference
### Macros
```c
#define ZH_PCF8574_GPIO_OUTPUT false
#define ZH_PCF8574_GPIO_INPUT true
#define ZH_PCF8574_GPIO_LOW false
#define ZH_PCF8574_GPIO_HIGH true
```
---
### zh_pcf8574_init_config_t Structure
```c
typedef struct
{
i2c_master_bus_handle_t i2c_handle; // Unique I2C bus handle
uint32_t i2c_frequency; // I2C frequency of the expander (max 100000 Hz)
uint32_t stack_size; // Stack size for interrupt processing task
uint8_t task_priority; // Priority of interrupt processing task
uint8_t i2c_address; // I2C address of the expander (0x20-0x27 or 0x38-0x3F)
bool p0_gpio_work_mode; // GPIO P0 work mode (false - output, true - input)
bool p1_gpio_work_mode; // GPIO P1 work mode
bool p2_gpio_work_mode; // GPIO P2 work mode
bool p3_gpio_work_mode; // GPIO P3 work mode
bool p4_gpio_work_mode; // GPIO P4 work mode
bool p5_gpio_work_mode; // GPIO P5 work mode
bool p6_gpio_work_mode; // GPIO P6 work mode
bool p7_gpio_work_mode; // GPIO P7 work mode
gpio_num_t interrupt_gpio; // Interrupt GPIO (GPIO_NUM_MAX - disable)
} zh_pcf8574_init_config_t;
```
Use `ZH_PCF8574_INIT_CONFIG_DEFAULT()` macro to initialize with default values:
- `i2c_frequency`: 100000 Hz
- `i2c_address`: 0xFF
- `p0_gpio_work_mode` ... `p7_gpio_work_mode`: `ZH_PCF8574_GPIO_OUTPUT`
- `interrupt_gpio`: `GPIO_NUM_MAX`
---
### zh_pcf8574_handle_t Structure
```c
typedef struct _zh_pcf8574_handle_t zh_pcf8574_handle_t;
```
Opaque handle for a PCF8574 device instance. The internal structure is minimal, storing only the I2C address — the full device state is maintained in an internal vector.
---
### zh_pcf8574_stats_t Structure
```c
typedef struct
{
uint32_t i2c_driver_error; // Number of I2C driver errors
uint32_t event_post_error; // Number of event post errors
uint32_t vector_error; // Number of vector errors
uint32_t queue_overflow_error; // Number of queue overflow errors
uint32_t min_stack_size; // Minimum free stack of the task
} zh_pcf8574_stats_t;
```
---
### zh_pcf8574_event_on_isr_t Structure
```c
typedef struct
{
uint64_t interrupt_time; // Interrupt time (microseconds)
zh_pcf8574_gpio_num_t gpio_number; // GPIO number that caused the interrupt
uint8_t i2c_address; // I2C address of the expander
bool gpio_level; // GPIO level at interrupt time
} zh_pcf8574_event_on_isr_t;
```
---
### zh_pcf8574_init()
Initializes the PCF8574 expander.
**Parameters:**
- `config` - Pointer to PCF8574 initialization configuration structure. Must not be NULL
- `handle` - Pointer to pointer to PCF8574 handle. Must be NULL
**Returns:**
- `ESP_OK` - Success
- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL config or handle)
- `ESP_ERR_INVALID_STATE` - Expander already initialized
- `ESP_ERR_NO_MEM` - Insufficient memory
- `ESP_ERR_NOT_SUPPORTED` - I2C initialization error
---
### zh_pcf8574_deinit()
Deinitializes the PCF8574 expander.
**Parameters:**
- `handle` - Pointer to pointer to PCF8574 handle. Must not be NULL
**Returns:**
- `ESP_OK` - Success
- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle or *handle)
---
### zh_pcf8574_read()
Reads the state of all GPIO pins of the expander.
**Parameters:**
- `handle` - Pointer to pointer to PCF8574 handle. Must not be NULL
- `reg` - Pointer to store GPIO status (bits 0-7). Must not be NULL
**Returns:**
- `ESP_OK` - Success
- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle or reg)
- `ESP_ERR_INVALID_STATE` - Expander not initialized
**Note:** For input GPIOs, the value will always be 1 (HIGH).
---
### zh_pcf8574_write()
Sets the state of all output GPIO pins of the expander.
**Parameters:**
- `handle` - Pointer to pointer to PCF8574 handle. Must not be NULL
- `reg` - GPIO status (bits 0-7)
**Returns:**
- `ESP_OK` - Success
- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle)
- `ESP_ERR_INVALID_STATE` - Expander not initialized
**Note:** Only output GPIOs are affected.
---
### zh_pcf8574_reset()
Resets all GPIO pins of the expander to the initial state.
**Parameters:**
- `handle` - Pointer to pointer to PCF8574 handle. Must not be NULL
**Returns:**
- `ESP_OK` - Success
- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle or *handle)
---
### zh_pcf8574_read_gpio()
Reads the state of one GPIO pin of the expander.
**Parameters:**
- `handle` - Pointer to pointer to PCF8574 handle. Must not be NULL
- `gpio` - GPIO number (ZH_PCF8574_GPIO_NUM_P0 ... ZH_PCF8574_GPIO_NUM_P7)
- `status` - Pointer to store GPIO status (true - HIGH, false - LOW). Must not be NULL
**Returns:**
- `ESP_OK` - Success
- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle, gpio, status or gpio out of range)
- `ESP_ERR_INVALID_STATE` - Expander not initialized
**Note:** For input GPIOs, the value will always be 1 (HIGH).
---
### zh_pcf8574_write_gpio()
Sets the state of one output GPIO pin of the expander.
**Parameters:**
- `handle` - Pointer to pointer to PCF8574 handle. Must not be NULL
- `gpio` - GPIO number (ZH_PCF8574_GPIO_NUM_P0 ... ZH_PCF8574_GPIO_NUM_P7)
- `status` - GPIO status (true - HIGH, false - LOW)
**Returns:**
- `ESP_OK` - Success
- `ESP_ERR_INVALID_ARG` - Invalid argument (NULL handle, gpio, status or gpio out of range)
- `ESP_ERR_INVALID_STATE` - Expander not initialized
**Note:** Only output GPIOs are affected.
---
### zh_pcf8574_get_stats()
Gets error statistics since last reset.
**Returns:**
- Pointer to the statistics structure
---
### zh_pcf8574_reset_stats()
Resets error statistics counters.
---
## Usage Examples
### Basic Example: Single Expander
```c
#include "zh_pcf8574.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_pcf8574_handle_t pcf8574_handle = {0};
void print_gpio_status(const char *message, uint8_t reg)
{
printf("%s", message);
for (uint8_t i = 0; i < 8; ++i)
{
printf("%c", (reg & 0x80) ? '1' : '0');
reg <<= 1;
}
printf(".\n");
}
void app_main(void)
{
esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
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);
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; // P0 - input
zh_pcf8574_init(&config, &pcf8574_handle);
uint8_t reg = 0;
zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("GPIO status: ", reg);
printf("Set P7 to 1, P1 to 1 and P0 to 0.\n");
zh_pcf8574_write(&pcf8574_handle, 0b10000010);
zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("GPIO status: ", reg);
printf("Set P0 to 0.\n");
zh_pcf8574_write_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P0, ZH_PCF8574_GPIO_LOW);
bool gpio = 0;
zh_pcf8574_read_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P0, &gpio);
printf("P0 status: %d.\n", gpio);
printf("Set P1 to 0.\n");
zh_pcf8574_write_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P1, ZH_PCF8574_GPIO_LOW);
zh_pcf8574_read_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P1, &gpio);
printf("P1 status: %d.\n", gpio);
zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("GPIO status: ", reg);
printf("Reset all GPIO.\n");
zh_pcf8574_reset(&pcf8574_handle);
zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("GPIO status: ", reg);
const zh_pcf8574_stats_t *stats = zh_pcf8574_get_stats();
printf("I2C errors: %ld.\n", stats->i2c_driver_error);
}
```
---
### Example with Interrupts
```c
#include "zh_pcf8574.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_pcf8574_handle_t pcf8574_handle = {0};
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 = event_data;
printf("Interrupt on device 0x%02X, GPIO %d, level %d, time %lld.\n",
event->i2c_address, event->gpio_number, event->gpio_level, event->interrupt_time);
}
void app_main(void)
{
esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
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);
esp_event_loop_create_default();
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; // P0 - input for interrupts
config.interrupt_gpio = GPIO_NUM_14; // Connect INT to GPIO 14
zh_pcf8574_init(&config, &pcf8574_handle);
for (;;)
{
const zh_pcf8574_stats_t *stats = zh_pcf8574_get_stats();
printf("I2C errors: %ld.\n", stats->i2c_driver_error);
printf("Event errors: %ld.\n", stats->event_post_error);
printf("Interrupt queue: %ld.\n", stats->queue_overflow_error);
printf("Minimum free stack: %ld.\n", stats->min_stack_size);
vTaskDelay(60000 / portTICK_PERIOD_MS);
}
}
```
---
## Technical Specifications
| Parameter | Value |
| ----------- | ------- |
| **Device Type** | 8-bit I2C GPIO expander |
| **Models** | PCF8574, PCF8574A |
| **Number of devices on bus** | Up to 16 (addresses 0x20-0x27, 0x38-0x3F) |
| **Number of GPIO** | 8 (P0-P7) |
| **I2C Address** | 0x20-0x27 (PCF8574), 0x38-0x3F (PCF8574A) |
| **I2C Frequency** | Up to 100 kHz |
| **GPIO Modes** | Input/Output (configurable per pin) |
| **Interrupts** | Yes (negative edge) |
| **ESP-IDF Version** | >= 5.0 |
| **Platform** | ESP32 series |
| **Language** | C (C99) |
---
## Error Codes
| Error Code | Description |
| ------------ | ------------- |
| `ESP_OK` | Operation successful |
| `ESP_ERR_INVALID_ARG` | Invalid argument (NULL pointer or invalid configuration) |
| `ESP_ERR_INVALID_STATE` | Device not initialized or already initialized |
| `ESP_ERR_NOT_FOUND` | Device not initialized |
| `ESP_ERR_NO_MEM` | Insufficient memory |
| `ESP_FAIL` | General failure (I2C communication error, initialization error, etc.) |
---
## Contributing
Contributions are welcome! To contribute:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
Please ensure your code follows the existing style and includes appropriate documentation.
---
## License
This project is licensed under the Apache License, Version 2.0 - see the [LICENSE](LICENSE) file for details.
### Apache License, Version 2.0
Copyright (c) 2026 Alexey Zholtikov
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---
## Additional Notes
- **I2C Pull-up Resistors**: Ensure pull-up resistors are connected to SDA and SCL lines (typically 4.7kΩ)
- **Input GPIOs**: All input GPIOs are internally pulled up to VCC (~100kΩ resistor)
- **Interrupts**: All INT outputs of expanders must be connected to one GPIO on ESP32
- **I2C_ISR_IRAM_SAFE**: For correct operation, enable `I2C_ISR_IRAM_SAFE` and `I2C_MASTER_ISR_HANDLER_IN_IRAM` in menuconfig
- **GPIO_CTRL_FUNC_IN_IRAM**: For GPIO interrupts, enable `GPIO_CTRL_FUNC_IN_IRAM` in menuconfig
---
"*Generated for zh_pcf8574 v3.0.0*"
-496
View File
@@ -1,496 +0,0 @@
# zh_pcf8574 - Компонент 8-битного расширителя GPIO PCF8574(A) для ESP-IDF
## Содержание
- [Обзор](#обзор)
- [Возможности](#возможности)
- [Установка](#установка)
- [Справочник API](#справочник-api)
- [Примеры использования](#примеры-использования)
- [Технические характеристики](#технические-характеристики)
- [Коды ошибок](#коды-ошибок)
- [Вклад в проект](#вклад-в-проект)
- [Лицензия](#лицензия)
---
## Обзор
`zh_pcf8574` - это компонент ESP-IDF для 8-битных расширителей GPIO PCF8574 и PCF8574A. Он предоставляет удобный API для работы с расширителем GPIO через шину I2C. Компонент поддерживает до 16 расширителей на одной шине I2C и может генерировать прерывания при изменении состояния входных GPIO.
Компонент разработан специально для микроконтроллеров ESP32 и использует API драйвера I2C ESP-IDF v5.0+.
---
## Возможности
- **Поддержка 16 расширителей**: До 16 PCF8574(A) на одной шине I2C (адреса 0x20-0x27 и 0x38-0x3F)
- **Режимы работы GPIO**: Поддержка выходных и входных режимов для каждого из 8 GPIO
- **Генерация прерываний**: Поддержка прерываний от входных GPIO (отрицательный фронт)
- **Статистика ошибок**: Встроенные счетчики ошибок I2C, событий, векторов и переполнения очереди
- **Потокобезопасность**: Использует драйвер I2C ESP-IDF и FreeRTOS
- **Минимальные накладные расходы**: Низкие требования к памяти и процессору
- **Простой API**: Легкое чтение/запись состояния всех GPIO и отдельных пинов
---
## Установка
Перейдите в каталог компонентов вашего проекта:
```bash
cd ../ваш_проект/components
```
Клонируйте репозиторий:
```bash
git clone https://github.com/aZholtikov/zh_pcf8574.git
```
В вашем приложении подключите заголовочный файл:
```c
#include "zh_pcf8574.h"
```
Компонент будет автоматически собран вместе с вашим проектом.
### Обязательные настройки в menuconfig
Для корректной работы компонента включите следующие настройки в menuconfig:
```text
GPIO_CTRL_FUNC_IN_IRAM
I2C_ISR_IRAM_SAFE
I2C_MASTER_ISR_HANDLER_IN_IRAM
```
---
## Справочник API
### Макросы
```c
#define ZH_PCF8574_GPIO_OUTPUT false
#define ZH_PCF8574_GPIO_INPUT true
#define ZH_PCF8574_GPIO_LOW false
#define ZH_PCF8574_GPIO_HIGH true
```
---
### Структура zh_pcf8574_init_config_t
```c
typedef struct
{
i2c_master_bus_handle_t i2c_handle; // Уникальный дескриптор шины I2C
uint32_t i2c_frequency; // Частота I2C расширителя (макс. 100000 Гц)
uint32_t stack_size; // Размер стека задачи обработки прерываний
uint8_t task_priority; // Приоритет задачи обработки прерываний
uint8_t i2c_address; // Адрес устройства расширителя I2C (0x20-0x27 или 0x38-0x3F)
bool p0_gpio_work_mode; // Режим работы GPIO P0 (false - output, true - input)
bool p1_gpio_work_mode; // Режим работы GPIO P1
bool p2_gpio_work_mode; // Режим работы GPIO P2
bool p3_gpio_work_mode; // Режим работы GPIO P3
bool p4_gpio_work_mode; // Режим работы GPIO P4
bool p5_gpio_work_mode; // Режим работы GPIO P5
bool p6_gpio_work_mode; // Режим работы GPIO P6
bool p7_gpio_work_mode; // Режим работы GPIO P7
gpio_num_t interrupt_gpio; // GPIO для прерываний (GPIO_NUM_MAX - отключить)
} zh_pcf8574_init_config_t;
```
Используйте макрос `ZH_PCF8574_INIT_CONFIG_DEFAULT()` для инициализации значениями по умолчанию:
- `i2c_frequency`: 100000 Гц
- `i2c_address`: 0xFF
- `p0_gpio_work_mode` ... `p7_gpio_work_mode`: `ZH_PCF8574_GPIO_OUTPUT`
- `interrupt_gpio`: `GPIO_NUM_MAX`
---
### Структура zh_pcf8574_handle_t
```c
typedef struct _zh_pcf8574_handle_t zh_pcf8574_handle_t;
```
Непрозрачный дескриптор экземпляра устройства PCF8574. Внутренняя структура минимальна — хранит только I2C-адрес; полное состояние устройства поддерживается во внутреннем векторе.
---
### Структура zh_pcf8574_stats_t
```c
typedef struct
{
uint32_t i2c_driver_error; // Количество ошибок драйвера I2C
uint32_t event_post_error; // Количество ошибок отправки событий
uint32_t vector_error; // Количество ошибок вектора
uint32_t queue_overflow_error; // Количество переполнений очереди
uint32_t min_stack_size; // Минимальный свободный стек задачи
} zh_pcf8574_stats_t;
```
---
### Структура zh_pcf8574_event_on_isr_t
```c
typedef struct
{
uint64_t interrupt_time; // Время прерывания (мкс)
zh_pcf8574_gpio_num_t gpio_number; // Номер GPIO, вызвавшего прерывание
uint8_t i2c_address; // I2C адрес расширителя
bool gpio_level; // Уровень GPIO при прерывании
} zh_pcf8574_event_on_isr_t;
```
---
### zh_pcf8574_init()
Инициализирует расширитель PCF8574.
**Параметры:**
- `config` - Указатель на структуру конфигурации инициализации PCF8574. Не должен быть NULL
- `handle` - Указатель на указатель на дескриптор PCF8574. Должен быть NULL
**Возвращает:**
- `ESP_OK` - Успех
- `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL config или handle)
- `ESP_ERR_INVALID_STATE` - Расширитель уже инициализирован
- `ESP_ERR_NO_MEM` - Недостаточно памяти
- `ESP_ERR_NOT_SUPPORTED` - Ошибка инициализации I2C
---
### zh_pcf8574_deinit()
Деинициализирует расширитель PCF8574.
**Параметры:**
- `handle` - Указатель на указатель на дескриптор PCF8574. Не должен быть NULL
**Возвращает:**
- `ESP_OK` - Успех
- `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL handle или *handle)
---
### zh_pcf8574_read()
Считывает состояние всех GPIO расширителя.
**Параметры:**
- `handle` - Указатель на указатель на дескриптор PCF8574. Не должен быть NULL
- `reg` - Указатель для хранения состояния GPIO (биты 0-7). Не должен быть NULL
**Возвращает:**
- `ESP_OK` - Успех
- `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL handle или reg)
- `ESP_ERR_INVALID_STATE` - Расширитель не инициализирован
**Примечание:** Для входных GPIO всегда будет 1 (HIGH).
---
### zh_pcf8574_write()
Устанавливает состояние всех выходных GPIO расширителя.
**Параметры:**
- `handle` - Указатель на указатель на дескриптор PCF8574. Не должен быть NULL
- `reg` - Состояние GPIO (биты 0-7)
**Возвращает:**
- `ESP_OK` - Успех
- `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL handle)
- `ESP_ERR_INVALID_STATE` - Расширитель не инициализирован
**Примечание:** Влияет только на выходные GPIO.
---
### zh_pcf8574_reset()
Сбрасывает все GPIO расширителя в начальное состояние.
**Параметры:**
- `handle` - Указатель на указатель на дескриптор PCF8574. Не должен быть NULL
**Возвращает:**
- `ESP_OK` - Успех
- `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL handle или *handle)
---
### zh_pcf8574_read_gpio()
Считывает состояние одного GPIO расширителя.
**Параметры:**
- `handle` - Указатель на указатель на дескриптор PCF8574. Не должен быть NULL
- `gpio` - Номер GPIO (ZH_PCF8574_GPIO_NUM_P0 ... ZH_PCF8574_GPIO_NUM_P7)
- `status` - Указатель для хранения состояния GPIO (true - HIGH, false - LOW). Не должен быть NULL
**Возвращает:**
- `ESP_OK` - Успех
- `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL handle, gpio, status или выход gpio за пределы)
- `ESP_ERR_INVALID_STATE` - Расширитель не инициализирован
**Примечание:** Для входных GPIO всегда будет 1 (HIGH).
---
### zh_pcf8574_write_gpio()
Устанавливает состояние одного выходного GPIO расширителя.
**Параметры:**
- `handle` - Указатель на указатель на дескриптор PCF8574. Не должен быть NULL
- `gpio` - Номер GPIO (ZH_PCF8574_GPIO_NUM_P0 ... ZH_PCF8574_GPIO_NUM_P7)
- `status` - Состояние GPIO (true - HIGH, false - LOW)
**Возвращает:**
- `ESP_OK` - Успех
- `ESP_ERR_INVALID_ARG` - Неверный аргумент (NULL handle, gpio, status или выход gpio за пределы)
- `ESP_ERR_INVALID_STATE` - Расширитель не инициализирован
**Примечание:** Влияет только на выходные GPIO.
---
### zh_pcf8574_get_stats()
Получает статистику ошибок с момента последнего сброса.
**Возвращает:**
- Указатель на структуру статистики
---
### zh_pcf8574_reset_stats()
Сбрасывает счетчики статистики ошибок.
---
## Примеры использования
### Базовый пример: Один расширитель
```c
#include "zh_pcf8574.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_pcf8574_handle_t pcf8574_handle = {0};
void print_gpio_status(const char *message, uint8_t reg)
{
printf("%s", message);
for (uint8_t i = 0; i < 8; ++i)
{
printf("%c", (reg & 0x80) ? '1' : '0');
reg <<= 1;
}
printf(".\n");
}
void app_main(void)
{
esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
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);
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; // P0 - вход
zh_pcf8574_init(&config, &pcf8574_handle);
uint8_t reg = 0;
zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("Статус GPIO: ", reg);
printf("Установить P7 в 1, P1 в 1 и P0 в 0.\n");
zh_pcf8574_write(&pcf8574_handle, 0b10000010);
zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("Статус GPIO: ", reg);
printf("Установить P0 в 0.\n");
zh_pcf8574_write_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P0, ZH_PCF8574_GPIO_LOW);
bool gpio = 0;
zh_pcf8574_read_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P0, &gpio);
printf("Статус P0: %d.\n", gpio);
printf("Установить P1 в 0.\n");
zh_pcf8574_write_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P1, ZH_PCF8574_GPIO_LOW);
zh_pcf8574_read_gpio(&pcf8574_handle, ZH_PCF8574_GPIO_NUM_P1, &gpio);
printf("Статус P1: %d.\n", gpio);
zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("Статус GPIO: ", reg);
printf("Сброс всех GPIO.\n");
zh_pcf8574_reset(&pcf8574_handle);
zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("Статус GPIO: ", reg);
const zh_pcf8574_stats_t *stats = zh_pcf8574_get_stats();
printf("Ошибки I2C: %ld.\n", stats->i2c_driver_error);
}
```
---
### Пример с прерываниями
```c
#include "zh_pcf8574.h"
#define I2C_PORT (I2C_NUM_MAX - 1)
zh_pcf8574_handle_t pcf8574_handle = {0};
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 = event_data;
printf("Прерывание на устройстве 0x%02X, GPIO %d, уровень %d, время %lld.\n",
event->i2c_address, event->gpio_number, event->gpio_level, event->interrupt_time);
}
void app_main(void)
{
esp_log_level_set("zh_pcf8574", ESP_LOG_ERROR);
esp_log_level_set("zh_vector", ESP_LOG_ERROR);
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);
esp_event_loop_create_default();
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; // P0 - вход для прерываний
config.interrupt_gpio = GPIO_NUM_14; // Подключение INT к GPIO 14
zh_pcf8574_init(&config, &pcf8574_handle);
for (;;)
{
const zh_pcf8574_stats_t *stats = zh_pcf8574_get_stats();
printf("Ошибки I2C: %ld.\n", stats->i2c_driver_error);
printf("Ошибки событий: %ld.\n", stats->event_post_error);
printf("Очередь прерываний: %ld.\n", stats->queue_overflow_error);
printf("Минимальный свободный стек: %ld.\n", stats->min_stack_size);
vTaskDelay(60000 / portTICK_PERIOD_MS);
}
}
```
---
## Технические характеристики
| Параметр | Значение |
| ---------- | ---------- |
| **Тип устройства** | 8-битный I2C GPIO расширитель |
| **Модели** | PCF8574, PCF8574A |
| **Количество устройств на шине** | До 16 (адреса 0x20-0x27, 0x38-0x3F) |
| **Количество GPIO** | 8 (P0-P7) |
| **Адрес I2C** | 0x20-0x27 (PCF8574), 0x38-0x3F (PCF8574A) |
| **Частота I2C** | До 100 кГц |
| **Режимы GPIO** | Вход/выход (настраиваемые для каждого пина) |
| **Прерывания** | Да (отрицательный фронт) |
| **Версия ESP-IDF** | >= 5.0 |
| **Платформа** | Семейство ESP32 |
| **Язык** | C (C99) |
---
## Коды ошибок
| Код ошибки | Описание |
| ------------ | ---------- |
| `ESP_OK` | Операция выполнена успешно |
| `ESP_ERR_INVALID_ARG` | Неверный аргумент (NULL указатель или неверная конфигурация) |
| `ESP_ERR_INVALID_STATE` | Устройство не инициализировано или уже инициализировано |
| `ESP_ERR_NOT_FOUND` | Устройство не инициализировано |
| `ESP_ERR_NO_MEM` | Недостаточно памяти |
| `ESP_FAIL` | Общая ошибка (ошибка связи I2C, инициализации и т.д.) |
---
## Вклад в проект
Вклад приветствуется! Чтобы внести свой вклад:
1. Сделайте форк репозитория
2. Создайте ветку функции (`git checkout -b feature/AmazingFeature`)
3. Закоммитьте ваши изменения (`git commit -m 'Add some AmazingFeature'`)
4. Отправьте в ветку (`git push origin feature/AmazingFeature`)
5. Откройте Pull Request
Пожалуйста, убедитесь, что ваш код следует существующему стилю и включает соответствующую документацию.
---
## Лицензия
Этот проект лицензирован по лицензии Apache, версия 2.0 - см. файл [LICENSE](LICENSE) для подробной информации.
### Apache License, Version 2.0
Авторское право (c) 2026 Алексей Жолтиков
Лицензировано по лицензии Apache License, Version 2.0 (далее — "Лицензия");
вы не можете использовать этот файл, кроме случаев, предусмотренных Лицензией.
Копию Лицензии можно получить по адресу:
http://www.apache.org/licenses/LICENSE-2.0
Если иное не требуется действующим законодательством или не согласовано в письменном виде,
программное обеспечение, распространяемое по Лицензии, распространяется на условиях "КАК ЕСТЬ",
БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, явных или подразумеваемых, включая, но не ограничиваясь, гарантии
ТОВАРНОГО СОСТОЯНИЯ, ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ И НЕНАРУШЕНИЯ ПРАВ.
Смотрите Лицензию для получения конкретных прав и ограничений.
---
## Дополнительные заметки
- **Подтягивающие резисторы I2C**: Убедитесь, что к линиям SDA и SCL подключены подтягивающие резисторы (обычно 4.7кОм)
- **Входные GPIO**: Все входные GPIO всегда подтянуты к питанию (внутренний подтягивающий резистор ~100кОм)
- **Прерывания**: Все выводы INT расширителей должны быть подключены к одному GPIO на ESP32
- **I2C_ISR_IRAM_SAFE**: Для правильной работы включите `I2C_ISR_IRAM_SAFE` и `I2C_MASTER_ISR_HANDLER_IN_IRAM` в menuconfig
- **GPIO_CTRL_FUNC_IN_IRAM**: Для работы прерываний GPIO включите `GPIO_CTRL_FUNC_IN_IRAM` в menuconfig
---
"*Сгенерировано для zh_pcf8574 v3.0.0*"
+42 -28
View File
@@ -16,7 +16,8 @@
*
* @note The driver creates an internal task for interrupt processing.
* @note Requires ESP-IDF v5.0+ with I2C master driver.
* @note Enable GPIO_CTRL_FUNC_IN_IRAM, I2C_ISR_IRAM_SAFE and I2C_MASTER_ISR_HANDLER_IN_IRAM in menuconfig.
* @warning All I2C and GPIO operations run in thread context with ESP_INTR_FLAG_LOWMED, so the
* interrupt service is not IRAM-safe and does not execute from IRAM.
*/
#pragma once
@@ -39,7 +40,7 @@
{ \
.task_priority = 1, \
.stack_size = configMINIMAL_STACK_SIZE, \
.i2c_address = 0xFF, \
.i2c_address = 0x20, \
.i2c_frequency = 100000, \
.p0_gpio_work_mode = ZH_PCF8574_GPIO_OUTPUT, \
.p1_gpio_work_mode = ZH_PCF8574_GPIO_OUTPUT, \
@@ -56,7 +57,7 @@ extern "C"
{
#endif
extern TaskHandle_t zh_pcf8574; /*!< Handle of the internal PCF8574 processing task. */
extern TaskHandle_t zh_pcf8574; /*!< Handle of the internal PCF8574 processing task */
/**
* @brief Opaque handle for PCF8574 device instance.
@@ -125,7 +126,7 @@ extern "C"
uint32_t event_post_error; /*!< Counter of event posting errors */
uint32_t vector_error; /*!< Counter of vector operation errors */
uint32_t queue_overflow_error; /*!< Counter of queue overflow errors */
uint32_t min_stack_size; /*!< Minimum remaining stack size recorded */
uint32_t min_stack_size; /*!< Minimum remaining stack size in words since last reset */
} zh_pcf8574_stats_t;
ESP_EVENT_DECLARE_BASE(ZH_PCF8574);
@@ -152,65 +153,76 @@ extern "C"
* provided configuration.
*
* @param[in] config Pointer to initialization configuration structure (must not be NULL)
* @param[out] handle Pointer to receive the device handle (must be NULL)
* @param[in,out] handle Pointer to the device handle (must not be NULL, the handle it points to must be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if config parameter is NULL
* @return ESP_ERR_INVALID_ARG if config is NULL or handle is NULL
* @return ESP_ERR_INVALID_STATE if the handle pointed to by handle is not NULL
* @return ESP_ERR_NO_MEM if memory allocation fails
* @return ESP_ERR_NOT_SUPPORTED if I2C initialization fails
* @return ESP_FAIL if configuration validation, I2C communication, or task creation fails
*/
esp_err_t zh_pcf8574_init(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t **handle);
/**
* @brief Deinitialize PCF8574 device.
*
* Deletes the internal task and releases all allocated resources.
* Releases I2C resources and the device handle. Deletes the internal
* task only when the last device instance is deinitialized.
*
* @param[in,out] handle Pointer to the device handle (must not be NULL)
* @param[in] handle Pointer to the device handle (must not be NULL, the handle it points to must not be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle parameter is NULL
* @return ESP_ERR_INVALID_ARG if handle is NULL or the handle it points to is NULL
* @return ESP_ERR_NOT_FOUND if handle does not correspond to a valid device instance
* @return ESP_FAIL if I2C removal or vector operations fail
*/
esp_err_t zh_pcf8574_deinit(zh_pcf8574_handle_t **handle);
/**
* @brief Read data from PCF8574 device.
*
* Reads a byte from the PCF8574 input ports via I2C communication.
* Reads a byte from the PCF8574 input ports via I2C communication
* and updates the internal device state.
*
* @param[in,out] handle Pointer to the device handle (must not be NULL)
* @param[in] handle Pointer to the device handle (must not be NULL, the handle it points to must not be NULL)
* @param[out] reg Pointer to receive the read value (must not be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle or reg is NULL
* @return ESP_ERR_INVALID_STATE if device is not initialized
* @return ESP_ERR_INVALID_ARG if handle or reg is NULL or the handle it points to is NULL
* @return ESP_ERR_NOT_FOUND if handle does not correspond to a valid device instance
* @return ESP_FAIL if I2C communication or vector operations fail
*/
esp_err_t zh_pcf8574_read(zh_pcf8574_handle_t **handle, uint8_t *reg);
/**
* @brief Write data to PCF8574 device.
*
* Writes a byte to the PCF8574 output ports via I2C communication.
* Writes a byte to the PCF8574 output ports via I2C communication
* and updates the internal device state.
*
* @param[in,out] handle Pointer to the device handle (must not be NULL)
* @param[in] handle Pointer to the device handle (must not be NULL, the handle it points to must not be NULL)
* @param[in] reg Value to write to the output ports
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle is NULL
* @return ESP_ERR_INVALID_STATE if device is not initialized
* @return ESP_ERR_INVALID_ARG if handle is NULL or the handle it points to is NULL
* @return ESP_ERR_NOT_FOUND if handle does not correspond to a valid device instance
* @return ESP_FAIL if I2C communication or vector operations fail
*/
esp_err_t zh_pcf8574_write(zh_pcf8574_handle_t **handle, uint8_t reg);
/**
* @brief Reset PCF8574 device.
*
* Resets the device to its default state, clearing all outputs
* and reinitializing internal structures.
* Writes the configured GPIO direction mask to the port, returning
* output pins to their low level and input pins to their high level,
* and updates the internal device state.
*
* @param[in,out] handle Pointer to the device handle (must not be NULL)
* @param[in] handle Pointer to the device handle (must not be NULL, the handle it points to must not be NULL)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle is NULL
* @return ESP_ERR_INVALID_ARG if handle is NULL or the handle it points to is NULL
* @return ESP_ERR_NOT_FOUND if handle does not correspond to a valid device instance
* @return ESP_FAIL if I2C communication or vector operations fail
*/
esp_err_t zh_pcf8574_reset(zh_pcf8574_handle_t **handle);
@@ -219,13 +231,14 @@ extern "C"
*
* Reads the current logical level of a specified GPIO pin.
*
* @param[in,out] handle Pointer to the device handle (must not be NULL)
* @param[in] handle Pointer to the device handle (must not be NULL, the handle it points to must not be NULL)
* @param[in] gpio GPIO pin number to read
* @param[out] status Pointer to receive the pin state (true = high, false = low)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle, gpio, or status is NULL
* @return ESP_ERR_INVALID_ARG if gpio number exceeds ZH_PCF8574_GPIO_NUM_MAX
* @return ESP_ERR_INVALID_ARG if handle or status is NULL or the handle it points to is NULL
* @return ESP_ERR_NOT_FOUND if handle does not correspond to a valid device instance
* @return ESP_FAIL if gpio is invalid or I2C communication fails
*/
esp_err_t zh_pcf8574_read_gpio(zh_pcf8574_handle_t **handle, zh_pcf8574_gpio_num_t gpio, bool *status);
@@ -234,13 +247,14 @@ extern "C"
*
* Sets the logical level of a specified GPIO pin configured as output.
*
* @param[in,out] handle Pointer to the device handle (must not be NULL)
* @param[in] handle Pointer to the device handle (must not be NULL, the handle it points to must not be NULL)
* @param[in] gpio GPIO pin number to write
* @param[in] status Logical level to set (true = high, false = low)
*
* @return ESP_OK on success
* @return ESP_ERR_INVALID_ARG if handle, gpio, or status is NULL
* @return ESP_ERR_INVALID_ARG if gpio number exceeds ZH_PCF8574_GPIO_NUM_MAX
* @return ESP_ERR_INVALID_ARG if handle is NULL or the handle it points to is NULL
* @return ESP_ERR_NOT_FOUND if handle does not correspond to a valid device instance
* @return ESP_FAIL if gpio is invalid or I2C communication fails
*/
esp_err_t zh_pcf8574_write_gpio(zh_pcf8574_handle_t **handle, zh_pcf8574_gpio_num_t gpio, bool status);
+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"
```
+82 -66
View File
@@ -18,7 +18,6 @@ static const char *TAG = "zh_pcf8574";
{ \
ZH_LOGE(msg, ESP_FAIL, ##__VA_ARGS__); \
cleanup; \
continue; \
}
/**
@@ -26,6 +25,9 @@ static const char *TAG = "zh_pcf8574";
*
* Minimal handle storing the I2C address of the device instance.
* The full state is maintained in the internal vector.
*
* @note The handle is allocated by zh_pcf8574_init() and freed by zh_pcf8574_deinit().
* @warning The handle becomes invalid after deinitialization and must not be used afterwards.
*/
struct _zh_pcf8574_handle_t
{
@@ -46,14 +48,14 @@ typedef struct
i2c_master_dev_handle_t dev_handle; /*!< I2C device handle for communication */
} _zh_pcf8574_vector_data_t;
TaskHandle_t zh_pcf8574 = NULL; /*!< Handle of the internal PCF8574 processing task */
static SemaphoreHandle_t _interrupt_semaphore = NULL; /*!< Semaphore for interrupt-to-task synchronization */
TaskHandle_t zh_pcf8574 = NULL;
static SemaphoreHandle_t _interrupt_semaphore = NULL;
volatile static gpio_num_t _interrupt_gpio = GPIO_NUM_MAX; /*!< GPIO pin used for interrupt detection */
static const uint8_t _gpio_matrix[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}; /*!< Bit mask for each GPIO pin (P0-P7) */
static zh_pcf8574_stats_t _stats = {0}; /*!< Statistics structure for error tracking */
volatile static gpio_num_t _interrupt_gpio = GPIO_NUM_MAX;
static const uint8_t _gpio_matrix[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
static zh_pcf8574_stats_t _stats = {0};
static zh_vector_t *_vector = NULL; /*!< Vector storing PCF8574 device data */
static zh_vector_t *_vector = NULL;
/**
* @brief Validate initialization configuration.
@@ -123,7 +125,6 @@ static esp_err_t _zh_pcf8574_task_init(const zh_pcf8574_init_config_t *config);
*
* Called when an interrupt occurs on the configured GPIO pin.
* Posts a signal to the semaphore to wake up the processing task.
* Executed in ISR context (IRAM).
*
* @param arg Unused callback argument
*/
@@ -134,7 +135,7 @@ static void _zh_pcf8574_isr_handler(void *arg);
*
* Waits for interrupt signals, reads all PCF8574 devices, detects
* level changes on input pins, and posts events for each change.
* Monitors stack usage continuously.
* Updates the remaining stack size statistic after each iteration.
*
* @param pvParameter Unused task parameter
*/
@@ -170,7 +171,7 @@ static esp_err_t _zh_pcf8574_write_register(_zh_pcf8574_vector_data_t *vector_da
ESP_EVENT_DEFINE_BASE(ZH_PCF8574);
esp_err_t zh_pcf8574_init(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t **handle) // -V2008
esp_err_t zh_pcf8574_init(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t **handle)
{
ZH_LOGI("PCF8574 initialization started.");
ZH_ERROR_CHECK(config != NULL && handle != NULL, ESP_ERR_INVALID_ARG, NULL, "PCF8574 initialization failed. Invalid argument.");
@@ -184,25 +185,29 @@ esp_err_t zh_pcf8574_init(const zh_pcf8574_init_config_t *config, zh_pcf8574_han
ZH_ERROR_CHECK(_zh_pcf8574_i2c_init(config, &vector_data) == ESP_OK, ESP_FAIL, NULL, "PCF8574 initialization failed. Failed to add I2C device.");
*handle = heap_caps_calloc(1, sizeof(zh_pcf8574_handle_t), MALLOC_CAP_8BIT);
ZH_ERROR_CHECK(*handle != NULL, ESP_ERR_NO_MEM,
{ZH_ERROR_CHECK(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")}, "PCF8574 initialization failed. Failed to allocate PCF8574 handle.");
{ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, NULL, "I2C remove device failed.")}, "PCF8574 initialization failed. Failed to allocate PCF8574 handle.");
ZH_ERROR_CHECK(zh_vector_push_back(&_vector, &vector_data) == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")};
{ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, NULL, "I2C remove device failed.")};
heap_caps_free(*handle); *handle = NULL, "PCF8574 initialization failed. Failed to add vector data.");
if (_interrupt_gpio == GPIO_NUM_MAX && config->interrupt_gpio < GPIO_NUM_MAX && vector_data.gpio_work_mode != 0)
{
ZH_ERROR_CHECK(_zh_pcf8574_gpio_init(config) == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")};
zh_vector_delete_back(&_vector); heap_caps_free(*handle); *handle = NULL, "PCF8574 initialization failed. Interrupt GPIO initialization failed.");
ZH_ERROR_CHECK(_zh_pcf8574_resources_init() == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")};
{ZH_ERROR_CHECK(gpio_isr_handler_remove(config->interrupt_gpio) == ESP_OK, ESP_FAIL, NULL, "Remove GPIO isr handler failed.")};
{ZH_ERROR_CHECK(gpio_reset_pin(config->interrupt_gpio) == ESP_OK, ESP_FAIL, NULL, "Reset GPIO failed.")};
zh_vector_delete_back(&_vector); heap_caps_free(*handle); *handle = NULL, "PCF8574 initialization failed. Resources initialization failed.");
{ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, NULL, "I2C remove device failed.")};
{ZH_ERROR_CHECK_CONT(zh_vector_delete_back(&_vector) == ESP_OK, NULL, "Failed delete vector data.")};
heap_caps_free(*handle); *handle = NULL, "PCF8574 initialization failed. Resources initialization failed.");
ZH_ERROR_CHECK(_zh_pcf8574_gpio_init(config) == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, NULL, "I2C remove device failed.")};
vSemaphoreDelete(_interrupt_semaphore); _interrupt_semaphore = NULL;
{ZH_ERROR_CHECK_CONT(zh_vector_delete_back(&_vector) == ESP_OK, NULL, "Failed delete vector data.")};
heap_caps_free(*handle);
*handle = NULL, "PCF8574 initialization failed. Interrupt GPIO initialization failed.");
ZH_ERROR_CHECK(_zh_pcf8574_task_init(config) == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")};
{ZH_ERROR_CHECK(gpio_isr_handler_remove(config->interrupt_gpio) == ESP_OK, ESP_FAIL, NULL, "Remove gpio isr handler failed.")};
{ZH_ERROR_CHECK(gpio_reset_pin(config->interrupt_gpio) == ESP_OK, ESP_FAIL, NULL, "Reset gpio failed.")};
vSemaphoreDelete(_interrupt_semaphore); _interrupt_semaphore = NULL; zh_vector_delete_back(&_vector); heap_caps_free(*handle); *handle = NULL, "PCF8574 initialization failed. Task initialization failed.");
{ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, NULL, "I2C remove device failed.")};
{ZH_ERROR_CHECK_CONT(gpio_isr_handler_remove(config->interrupt_gpio) == ESP_OK, NULL, "Remove gpio isr handler failed.")};
{ZH_ERROR_CHECK_CONT(gpio_reset_pin(config->interrupt_gpio) == ESP_OK, NULL, "Reset gpio failed.")};
vSemaphoreDelete(_interrupt_semaphore); _interrupt_semaphore = NULL;
{ZH_ERROR_CHECK_CONT(zh_vector_delete_back(&_vector) == ESP_OK, NULL, "Failed delete vector data.")};
heap_caps_free(*handle); *handle = NULL, "PCF8574 initialization failed. Task initialization failed.");
_interrupt_gpio = config->interrupt_gpio;
}
if (_stats.min_stack_size == 0)
@@ -214,7 +219,7 @@ esp_err_t zh_pcf8574_init(const zh_pcf8574_init_config_t *config, zh_pcf8574_han
return ESP_OK;
}
esp_err_t zh_pcf8574_deinit(zh_pcf8574_handle_t **handle) // -V2008
esp_err_t zh_pcf8574_deinit(zh_pcf8574_handle_t **handle)
{
ZH_LOGI("PCF8574 deinitialization started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "PCF8574 deinitialization failed. Invalid argument.");
@@ -223,20 +228,26 @@ esp_err_t zh_pcf8574_deinit(zh_pcf8574_handle_t **handle) // -V2008
ZH_ERROR_CHECK(zh_vector_find_item_in_field(&_vector, &vector_data, &vector_data.i2c_address, sizeof(vector_data.i2c_address), &(*handle)->i2c_address, 0, &index) == ESP_OK,
ESP_ERR_NOT_FOUND, NULL, "PCF8574 deinitialization failed. Failed to find vector item.");
ZH_ERROR_CHECK(zh_vector_get_item(&_vector, (uint16_t)index, &vector_data) == ESP_OK, ESP_FAIL, NULL, "PCF8574 deinitialization failed. Failed to get vector item data.");
ZH_ERROR_CHECK(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, ESP_FAIL, NULL, "PCF8574 deinitialization failed. I2C remove device failed.");
ZH_ERROR_CHECK(zh_vector_delete_item(&_vector, (uint16_t)index) == ESP_OK, ESP_FAIL, NULL, "PCF8574 deinitialization failed. Vector delete item failed.");
ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(vector_data.dev_handle) == ESP_OK, NULL, "PCF8574 deinitialization failed. I2C remove device failed.");
ZH_ERROR_CHECK_CONT(zh_vector_delete_item(&_vector, (uint16_t)index) == ESP_OK, NULL, "PCF8574 deinitialization failed. Vector delete item failed.");
uint16_t vector_size = 0;
ZH_ERROR_CHECK(zh_vector_get_size(&_vector, &vector_size) == ESP_OK, ESP_FAIL, NULL, "PCF8574 deinitialization failed. Failed to get vector size.");
if (vector_size == 0)
{
if (_interrupt_gpio != GPIO_NUM_MAX)
{
ZH_ERROR_CHECK(gpio_isr_handler_remove(_interrupt_gpio) == ESP_OK, ESP_FAIL, NULL, "PCF8574 deinitialization failed. Remove GPIO isr handler failed.");
ZH_ERROR_CHECK(gpio_reset_pin(_interrupt_gpio) == ESP_OK, ESP_FAIL, NULL, "PCF8574 deinitialization failed. Reset GPIO failed.");
vTaskDelete(zh_pcf8574);
zh_pcf8574 = NULL;
vSemaphoreDelete(_interrupt_semaphore);
_interrupt_semaphore = NULL;
ZH_ERROR_CHECK_CONT(gpio_isr_handler_remove(_interrupt_gpio) == ESP_OK, NULL, "PCF8574 deinitialization failed. Remove GPIO isr handler failed.");
ZH_ERROR_CHECK_CONT(gpio_reset_pin(_interrupt_gpio) == ESP_OK, NULL, "PCF8574 deinitialization failed. Reset GPIO failed.");
if (zh_pcf8574 != NULL)
{
vTaskDelete(zh_pcf8574);
zh_pcf8574 = NULL;
}
if (_interrupt_semaphore != NULL)
{
vSemaphoreDelete(_interrupt_semaphore);
_interrupt_semaphore = NULL;
}
_interrupt_gpio = GPIO_NUM_MAX;
}
ZH_ERROR_CHECK(zh_vector_free(&_vector) == ESP_OK, ESP_FAIL, NULL, "PCF8574 deinitialization failed. Free vector failed.");
@@ -247,7 +258,7 @@ esp_err_t zh_pcf8574_deinit(zh_pcf8574_handle_t **handle) // -V2008
return ESP_OK;
}
esp_err_t zh_pcf8574_read(zh_pcf8574_handle_t **handle, uint8_t *reg) // -V2008
esp_err_t zh_pcf8574_read(zh_pcf8574_handle_t **handle, uint8_t *reg)
{
ZH_LOGI("PCF8574 read register started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL && reg != NULL, ESP_ERR_INVALID_ARG, NULL, "PCF8574 read register failed. Invalid argument.");
@@ -262,7 +273,7 @@ esp_err_t zh_pcf8574_read(zh_pcf8574_handle_t **handle, uint8_t *reg) // -V2008
return ESP_OK;
}
esp_err_t zh_pcf8574_write(zh_pcf8574_handle_t **handle, uint8_t reg) // -V2008
esp_err_t zh_pcf8574_write(zh_pcf8574_handle_t **handle, uint8_t reg)
{
ZH_LOGI("PCF8574 write register started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "PCF8574 write register failed. Invalid argument.");
@@ -277,7 +288,7 @@ esp_err_t zh_pcf8574_write(zh_pcf8574_handle_t **handle, uint8_t reg) // -V2008
return ESP_OK;
}
esp_err_t zh_pcf8574_reset(zh_pcf8574_handle_t **handle) // -V2008
esp_err_t zh_pcf8574_reset(zh_pcf8574_handle_t **handle)
{
ZH_LOGI("PCF8574 reset register started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "PCF8574 reset register failed. Invalid argument.");
@@ -292,7 +303,7 @@ esp_err_t zh_pcf8574_reset(zh_pcf8574_handle_t **handle) // -V2008
return ESP_OK;
}
esp_err_t zh_pcf8574_read_gpio(zh_pcf8574_handle_t **handle, zh_pcf8574_gpio_num_t gpio, bool *status) // -V2008
esp_err_t zh_pcf8574_read_gpio(zh_pcf8574_handle_t **handle, zh_pcf8574_gpio_num_t gpio, bool *status)
{
ZH_LOGI("PCF8574 read GPIO started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL && status != NULL, ESP_ERR_INVALID_ARG, NULL, "PCF8574 read GPIO failed. Invalid argument.");
@@ -310,7 +321,7 @@ esp_err_t zh_pcf8574_read_gpio(zh_pcf8574_handle_t **handle, zh_pcf8574_gpio_num
return ESP_OK;
}
esp_err_t zh_pcf8574_write_gpio(zh_pcf8574_handle_t **handle, zh_pcf8574_gpio_num_t gpio, bool status) // -V2008
esp_err_t zh_pcf8574_write_gpio(zh_pcf8574_handle_t **handle, zh_pcf8574_gpio_num_t gpio, bool status)
{
ZH_LOGI("PCF8574 write GPIO started.");
ZH_ERROR_CHECK(handle != NULL && *handle != NULL, ESP_ERR_INVALID_ARG, NULL, "PCF8574 write GPIO failed. Invalid argument.");
@@ -322,11 +333,11 @@ esp_err_t zh_pcf8574_write_gpio(zh_pcf8574_handle_t **handle, zh_pcf8574_gpio_nu
ZH_ERROR_CHECK(zh_vector_get_item(&_vector, (uint16_t)index, &vector_data) == ESP_OK, ESP_FAIL, NULL, "PCF8574 write GPIO failed. Failed to get vector item data.");
if (status == true)
{
ZH_ERROR_CHECK(_zh_pcf8574_write_register(&vector_data, vector_data.gpio_status | vector_data.gpio_work_mode | _gpio_matrix[(uint8_t)gpio]) == ESP_OK, ESP_FAIL, NULL, "PCF8574 write GPIO failed. PCF8574 write register failed."); /*!< Set GPIO pin high, preserving work mode. */
ZH_ERROR_CHECK(_zh_pcf8574_write_register(&vector_data, vector_data.gpio_status | vector_data.gpio_work_mode | _gpio_matrix[(uint8_t)gpio]) == ESP_OK, ESP_FAIL, NULL, "PCF8574 write GPIO failed. PCF8574 write register failed.");
}
else
{
ZH_ERROR_CHECK(_zh_pcf8574_write_register(&vector_data, (vector_data.gpio_status & ~_gpio_matrix[(uint8_t)gpio]) | vector_data.gpio_work_mode) == ESP_OK, ESP_FAIL, NULL, "PCF8574 write GPIO failed. PCF8574 write register failed."); /*!< Clear GPIO pin, preserving work mode. */
ZH_ERROR_CHECK(_zh_pcf8574_write_register(&vector_data, (vector_data.gpio_status & ~_gpio_matrix[(uint8_t)gpio]) | vector_data.gpio_work_mode) == ESP_OK, ESP_FAIL, NULL, "PCF8574 write GPIO failed. PCF8574 write register failed.");
}
ZH_ERROR_CHECK(zh_vector_change_item(&_vector, (uint16_t)index, &vector_data) == ESP_OK, ESP_FAIL, NULL, "PCF8574 write GPIO failed. Failed to change vector item data.");
ZH_LOGI("PCF8574 write GPIO completed successfully.");
@@ -349,10 +360,10 @@ void zh_pcf8574_reset_stats(void)
ZH_LOGI("Error statistic reset successfully.");
}
static esp_err_t _zh_pcf8574_validate_config(const zh_pcf8574_init_config_t *config) // -V2008
static esp_err_t _zh_pcf8574_validate_config(const zh_pcf8574_init_config_t *config)
{
ZH_ERROR_CHECK((config->i2c_address >= 0x20 && config->i2c_address <= 0x27) || (config->i2c_address >= 0x38 && config->i2c_address <= 0x3F), ESP_ERR_INVALID_ARG, NULL, "Invalid I2C address.");
ZH_ERROR_CHECK(config->i2c_frequency <= 100000, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C frequency.");
ZH_ERROR_CHECK(config->i2c_frequency <= 100000 && config->i2c_frequency > 0, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C frequency.");
ZH_ERROR_CHECK(config->task_priority >= 1 && config->stack_size >= configMINIMAL_STACK_SIZE, ESP_ERR_INVALID_ARG, NULL, "Invalid task settings.");
ZH_ERROR_CHECK(config->interrupt_gpio <= GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, NULL, "Invalid GPIO number.");
ZH_ERROR_CHECK(config->i2c_handle != NULL, ESP_ERR_INVALID_ARG, NULL, "Invalid I2C handle.");
@@ -366,7 +377,7 @@ static esp_err_t _zh_pcf8574_validate_config(const zh_pcf8574_init_config_t *con
return ESP_OK;
}
static esp_err_t _zh_pcf8574_gpio_init(const zh_pcf8574_init_config_t *config) // -V2008
static esp_err_t _zh_pcf8574_gpio_init(const zh_pcf8574_init_config_t *config)
{
gpio_config_t interrupt_gpio_config = {
.intr_type = GPIO_INTR_NEGEDGE,
@@ -378,13 +389,13 @@ static esp_err_t _zh_pcf8574_gpio_init(const zh_pcf8574_init_config_t *config) /
ZH_ERROR_CHECK(gpio_config(&interrupt_gpio_config) == ESP_OK, ESP_FAIL, NULL, "GPIO configuration failed.")
esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_LOWMED);
ZH_ERROR_CHECK(err == ESP_OK || err == ESP_ERR_INVALID_STATE, ESP_FAIL,
{ZH_ERROR_CHECK(gpio_reset_pin(config->interrupt_gpio) == ESP_OK, ESP_FAIL, NULL, "Reset gpio failed.")}, "Failed install isr service.")
{ZH_ERROR_CHECK_CONT(gpio_reset_pin(config->interrupt_gpio) == ESP_OK, NULL, "Reset gpio failed.")}, "Failed install isr service.")
ZH_ERROR_CHECK(gpio_isr_handler_add(config->interrupt_gpio, _zh_pcf8574_isr_handler, NULL) == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK(gpio_reset_pin(config->interrupt_gpio) == ESP_OK, ESP_FAIL, NULL, "Reset gpio failed.")}, "Failed add isr handler.")
{ZH_ERROR_CHECK_CONT(gpio_reset_pin(config->interrupt_gpio) == ESP_OK, NULL, "Reset gpio failed.")}, "Failed add isr handler.")
return ESP_OK;
}
static esp_err_t _zh_pcf8574_i2c_init(const zh_pcf8574_init_config_t *config, _zh_pcf8574_vector_data_t *vector_data) // -V2008
static esp_err_t _zh_pcf8574_i2c_init(const zh_pcf8574_init_config_t *config, _zh_pcf8574_vector_data_t *vector_data)
{
i2c_device_config_t pcf8574_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
@@ -393,18 +404,18 @@ static esp_err_t _zh_pcf8574_i2c_init(const zh_pcf8574_init_config_t *config, _z
};
i2c_master_dev_handle_t _dev_handle = NULL;
ZH_ERROR_CHECK(i2c_master_bus_add_device(config->i2c_handle, &pcf8574_config, &_dev_handle) == ESP_OK, ESP_FAIL, NULL, "Failed to add I2C device.");
ZH_ERROR_CHECK(i2c_master_probe(config->i2c_handle, config->i2c_address, 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")}, "Expander not connected or not responding.");
ZH_ERROR_CHECK(i2c_master_probe(config->i2c_handle, config->i2c_address, 100) == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, NULL, "I2C remove device failed.")}, "Expander not connected or not responding.");
vector_data->gpio_work_mode = (config->p7_gpio_work_mode << 7) | (config->p6_gpio_work_mode << 6) | (config->p5_gpio_work_mode << 5) |
(config->p4_gpio_work_mode << 4) | (config->p3_gpio_work_mode << 3) | (config->p2_gpio_work_mode << 2) |
(config->p1_gpio_work_mode << 1) | (config->p0_gpio_work_mode << 0);
vector_data->dev_handle = _dev_handle;
vector_data->i2c_address = config->i2c_address;
ZH_ERROR_CHECK(_zh_pcf8574_write_register(vector_data, vector_data->gpio_work_mode) == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")}, "Failed extender initial GPIO setup.");
{ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, NULL, "I2C remove device failed.")}, "Failed extender initial GPIO setup.");
uint8_t reg_temp = 0;
ZH_ERROR_CHECK(_zh_pcf8574_read_register(vector_data, &reg_temp) == ESP_OK, ESP_FAIL,
{ZH_ERROR_CHECK(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, ESP_FAIL, NULL, "I2C remove device failed.")}, "Failed to read expander register.");
{ZH_ERROR_CHECK_CONT(i2c_master_bus_rm_device(_dev_handle) == ESP_OK, NULL, "I2C remove device failed.")}, "Failed to read expander register.");
return ESP_OK;
}
@@ -422,8 +433,9 @@ static esp_err_t _zh_pcf8574_task_init(const zh_pcf8574_init_config_t *config)
return ESP_OK;
}
static void IRAM_ATTR _zh_pcf8574_isr_handler(void *arg)
static void _zh_pcf8574_isr_handler(void *arg)
{
(void)arg;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (xSemaphoreGiveFromISR(_interrupt_semaphore, &xHigherPriorityTaskWoken) != pdTRUE)
{
@@ -435,8 +447,9 @@ static void IRAM_ATTR _zh_pcf8574_isr_handler(void *arg)
};
}
static void IRAM_ATTR _zh_pcf8574_isr_processing_task(void *pvParameter) // -V2008
static void _zh_pcf8574_isr_processing_task(void *pvParameter)
{
(void)pvParameter;
for (;;)
{
xSemaphoreTake(_interrupt_semaphore, portMAX_DELAY);
@@ -446,22 +459,25 @@ static void IRAM_ATTR _zh_pcf8574_isr_processing_task(void *pvParameter) // -V20
{
_zh_pcf8574_vector_data_t vector_data = {0};
ZH_ERROR_CHECK_CONT(zh_vector_get_item(&_vector, i, &vector_data) == ESP_OK, ++_stats.vector_error, "PCF8574 isr processing failed. Failed to get vector item data.");
zh_pcf8574_event_on_isr_t event = {0};
event.i2c_address = vector_data.i2c_address;
uint8_t old_reg = vector_data.gpio_status;
uint8_t new_reg = 0;
ZH_ERROR_CHECK_CONT(_zh_pcf8574_read_register(&vector_data, &new_reg) == ESP_OK, NULL, "PCF8574 isr processing failed. Failed to read expander register.");
ZH_ERROR_CHECK_CONT(zh_vector_change_item(&_vector, i, &vector_data) == ESP_OK, ++_stats.vector_error, "PCF8574 isr processing failed. Failed to change vector item data.");
for (uint8_t j = 0; j <= 7; ++j)
if (vector_data.gpio_work_mode != 0)
{
if ((vector_data.gpio_work_mode & _gpio_matrix[j]) != 0)
zh_pcf8574_event_on_isr_t event = {0};
event.i2c_address = vector_data.i2c_address;
uint8_t old_reg = vector_data.gpio_status;
uint8_t new_reg = 0;
ZH_ERROR_CHECK_CONT(_zh_pcf8574_read_register(&vector_data, &new_reg) == ESP_OK, continue, "PCF8574 isr processing failed. Failed to read expander register.");
ZH_ERROR_CHECK_CONT(zh_vector_change_item(&_vector, i, &vector_data) == ESP_OK, ++_stats.vector_error; continue, "PCF8574 isr processing failed. Failed to change vector item data.");
for (uint8_t j = 0; j <= 7; ++j)
{
if ((old_reg & _gpio_matrix[j]) != (new_reg & _gpio_matrix[j]))
if ((vector_data.gpio_work_mode & _gpio_matrix[j]) != 0)
{
event.gpio_number = j;
event.gpio_level = new_reg & _gpio_matrix[j];
event.interrupt_time = esp_timer_get_time();
ZH_ERROR_CHECK_CONT(esp_event_post(ZH_PCF8574, 0, &event, sizeof(event), 1000 / portTICK_PERIOD_MS) == ESP_OK, ++_stats.event_post_error, "PCF8574 isr processing failed. Failed to post interrupt event.");
if ((old_reg & _gpio_matrix[j]) != (new_reg & _gpio_matrix[j]))
{
event.gpio_number = j;
event.gpio_level = new_reg & _gpio_matrix[j];
event.interrupt_time = esp_timer_get_time();
ZH_ERROR_CHECK_CONT(esp_event_post(ZH_PCF8574, 0, &event, sizeof(event), pdMS_TO_TICKS(10)) == ESP_OK, ++_stats.event_post_error, "PCF8574 isr processing failed. Failed to post interrupt event.");
}
}
}
}
@@ -473,7 +489,7 @@ static void IRAM_ATTR _zh_pcf8574_isr_processing_task(void *pvParameter) // -V20
static esp_err_t _zh_pcf8574_read_register(_zh_pcf8574_vector_data_t *vector_data, uint8_t *reg)
{
ZH_ERROR_CHECK(i2c_master_receive(vector_data->dev_handle, &vector_data->gpio_status, sizeof(vector_data->gpio_status), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL,
ZH_ERROR_CHECK(i2c_master_receive(vector_data->dev_handle, &vector_data->gpio_status, sizeof(vector_data->gpio_status), 100) == ESP_OK, ESP_FAIL,
++_stats.i2c_driver_error, "I2C driver error.");
*reg = vector_data->gpio_status;
return ESP_OK;
@@ -481,7 +497,7 @@ static esp_err_t _zh_pcf8574_read_register(_zh_pcf8574_vector_data_t *vector_dat
static esp_err_t _zh_pcf8574_write_register(_zh_pcf8574_vector_data_t *vector_data, uint8_t reg)
{
ZH_ERROR_CHECK(i2c_master_transmit(vector_data->dev_handle, &reg, sizeof(reg), 1000 / portTICK_PERIOD_MS) == ESP_OK, ESP_FAIL,
ZH_ERROR_CHECK(i2c_master_transmit(vector_data->dev_handle, &reg, sizeof(reg), 100) == ESP_OK, ESP_FAIL,
++_stats.i2c_driver_error, "I2C driver error.");
vector_data->gpio_status = reg;
return ESP_OK;