6 Commits

4 changed files with 242 additions and 229 deletions

View File

@@ -15,7 +15,7 @@
1. Enable interrupt support only if input GPIO's are used. 1. Enable interrupt support only if input GPIO's are used.
2. All the INT GPIO's on the extenders must be connected to the one GPIO on ESP. 2. All the INT GPIO's on the extenders must be connected to the one GPIO on ESP.
3. The input GPIO's are always pullup to the power supply. They must be connected to ground to trigger an interrupt. 3. The input GPIO's are always pullup to the power supply.
## Dependencies ## Dependencies
@@ -47,7 +47,7 @@ One expander on bus. All GPIO's as output (except P0 - input). Interrupt is enab
#define I2C_PORT (I2C_NUM_MAX - 1) #define I2C_PORT (I2C_NUM_MAX - 1)
#ifndef CONFIG_IDF_TARGET_ESP8266 #ifndef CONFIG_IDF_TARGET_ESP8266
i2c_master_bus_handle_t i2c_bus_handle = {0}; i2c_master_bus_handle_t i2c_bus_handle = NULL;
#endif #endif
zh_pcf8574_handle_t pcf8574_handle = {0}; zh_pcf8574_handle_t pcf8574_handle = {0};
@@ -103,8 +103,8 @@ void app_main(void)
pcf8574_init_config.i2c_handle = i2c_bus_handle; pcf8574_init_config.i2c_handle = i2c_bus_handle;
#endif #endif
pcf8574_init_config.i2c_address = 0x38; pcf8574_init_config.i2c_address = 0x38;
pcf8574_init_config.p0_gpio_work_mode = EXP_GPIO_INPUT; // Required only for input GPIO. pcf8574_init_config.p0_gpio_work_mode = true; // Required only for input GPIO.
pcf8574_init_config.interrupt_gpio = GPIO_NUM_14; // Required only if used input GPIO interrupts. pcf8574_init_config.interrupt_gpio = GPIO_NUM_14; // Required only if used input GPIO interrupts.
zh_pcf8574_init(&pcf8574_init_config, &pcf8574_handle); zh_pcf8574_init(&pcf8574_init_config, &pcf8574_handle);
uint8_t reg = 0; uint8_t reg = 0;
zh_pcf8574_read(&pcf8574_handle, &reg); zh_pcf8574_read(&pcf8574_handle, &reg);
@@ -114,13 +114,13 @@ void app_main(void)
zh_pcf8574_read(&pcf8574_handle, &reg); zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("GPIO status: ", reg); print_gpio_status("GPIO status: ", reg);
printf("Sets P0 to 0.\n"); printf("Sets P0 to 0.\n");
zh_pcf8574_write_gpio(&pcf8574_handle, EXP_GPIO_NUM_P0, 0); // GPIO P0 will not be changed because it is operating in input mode. zh_pcf8574_write_gpio(&pcf8574_handle, 0, false); // GPIO P0 will not be changed because it is operating in input mode.
bool gpio = 0; bool gpio = 0;
zh_pcf8574_read_gpio(&pcf8574_handle, EXP_GPIO_NUM_P0, &gpio); zh_pcf8574_read_gpio(&pcf8574_handle, 0, &gpio);
printf("P0 status: %d.\n", gpio); printf("P0 status: %d.\n", gpio);
printf("Set P1 to 0.\n"); printf("Set P1 to 0.\n");
zh_pcf8574_write_gpio(&pcf8574_handle, EXP_GPIO_NUM_P1, 0); zh_pcf8574_write_gpio(&pcf8574_handle, 1, false);
zh_pcf8574_read_gpio(&pcf8574_handle, EXP_GPIO_NUM_P1, &gpio); zh_pcf8574_read_gpio(&pcf8574_handle, 1, &gpio);
printf("P1 status: %d.\n", gpio); printf("P1 status: %d.\n", gpio);
zh_pcf8574_read(&pcf8574_handle, &reg); zh_pcf8574_read(&pcf8574_handle, &reg);
print_gpio_status("GPIO status: ", reg); print_gpio_status("GPIO status: ", reg);
@@ -133,6 +133,6 @@ void app_main(void)
void zh_pcf8574_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) // Required only if used input GPIO interrupts. void zh_pcf8574_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) // Required only if used input GPIO interrupts.
{ {
zh_pcf8574_event_on_isr_t *event = event_data; zh_pcf8574_event_on_isr_t *event = event_data;
printf("Interrupt happened on device address 0x%02X on GPIO number %d.\n", event->i2c_address, event->gpio_number); printf("Interrupt happened on device address 0x%02X on GPIO number %d at level %d.\n", event->i2c_address, event->gpio_number, event->gpio_level);
} }
``` ```

View File

@@ -17,46 +17,37 @@
.task_priority = 10, \ .task_priority = 10, \
.stack_size = 2048, \ .stack_size = 2048, \
.i2c_address = 0xFF, \ .i2c_address = 0xFF, \
.interrupt_gpio = GPIO_NUM_MAX} .p0_gpio_work_mode = 0, \
.p1_gpio_work_mode = 0, \
.p2_gpio_work_mode = 0, \
.p3_gpio_work_mode = 0, \
.p4_gpio_work_mode = 0, \
.p5_gpio_work_mode = 0, \
.p6_gpio_work_mode = 0, \
.p7_gpio_work_mode = 0, \
.interrupt_gpio = GPIO_NUM_MAX, \
.i2c_port = 0}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
{ {
#endif #endif
typedef enum // Enumeration of possible GPIO work mode.
{
EXP_GPIO_OUTPUT,
EXP_GPIO_INPUT
} zh_pcf8574_gpio_work_mode_t;
typedef enum // Enumeration of possible GPIO's number.
{
EXP_GPIO_NUM_P0 = 0x01,
EXP_GPIO_NUM_P1 = 0x02,
EXP_GPIO_NUM_P2 = 0x04,
EXP_GPIO_NUM_P3 = 0x08,
EXP_GPIO_NUM_P4 = 0x10,
EXP_GPIO_NUM_P5 = 0x20,
EXP_GPIO_NUM_P6 = 0x40,
EXP_GPIO_NUM_P7 = 0x80
} zh_pcf8574_gpio_number_t;
typedef struct // Structure for initial initialization of PCF8574 expander. typedef struct // Structure for initial initialization of PCF8574 expander.
{ {
uint8_t task_priority; // Task priority for the PCF8574 expander isr processing. @note It is not recommended to set a value less than 10. uint8_t task_priority; // Task priority for the PCF8574 expander isr processing. @note It is not recommended to set a value less than 10.
uint16_t stack_size; // Stack size for task for the PCF8574 expander isr processing processing. @note The minimum size is 2048 bytes. uint16_t stack_size; // Stack size for task for the PCF8574 expander isr processing processing. @note The minimum size is 2048 bytes.
uint8_t i2c_address; // Expander I2C address. uint8_t i2c_address; // Expander I2C address.
zh_pcf8574_gpio_work_mode_t p0_gpio_work_mode; // Expander GPIO PO work mode. bool p0_gpio_work_mode; // Expander GPIO PO work mode. True for input, false for output.
zh_pcf8574_gpio_work_mode_t p1_gpio_work_mode; // Expander GPIO P1 work mode. bool p1_gpio_work_mode; // Expander GPIO P1 work mode. True for input, false for output.
zh_pcf8574_gpio_work_mode_t p2_gpio_work_mode; // Expander GPIO P2 work mode. bool p2_gpio_work_mode; // Expander GPIO P2 work mode. True for input, false for output.
zh_pcf8574_gpio_work_mode_t p3_gpio_work_mode; // Expander GPIO P3 work mode. bool p3_gpio_work_mode; // Expander GPIO P3 work mode. True for input, false for output.
zh_pcf8574_gpio_work_mode_t p4_gpio_work_mode; // Expander GPIO P4 work mode. bool p4_gpio_work_mode; // Expander GPIO P4 work mode. True for input, false for output.
zh_pcf8574_gpio_work_mode_t p5_gpio_work_mode; // Expander GPIO P5 work mode. bool p5_gpio_work_mode; // Expander GPIO P5 work mode. True for input, false for output.
zh_pcf8574_gpio_work_mode_t p6_gpio_work_mode; // Expander GPIO P6 work mode. bool p6_gpio_work_mode; // Expander GPIO P6 work mode. True for input, false for output.
zh_pcf8574_gpio_work_mode_t p7_gpio_work_mode; // Expander GPIO P7 work mode. bool p7_gpio_work_mode; // Expander GPIO P7 work mode. True for input, false for output.
gpio_num_t interrupt_gpio; // Interrupt GPIO. @attention Must be same for all PCF8574 expanders. uint8_t interrupt_gpio; // Interrupt GPIO. @attention Must be same for all PCF8574 expanders.
bool i2c_port; // I2C port. @attention Must be same for all PCF8574 expanders. bool i2c_port; // I2C port. @attention Must be same for all PCF8574 expanders.
#ifndef CONFIG_IDF_TARGET_ESP8266 #ifndef CONFIG_IDF_TARGET_ESP8266
i2c_master_bus_handle_t i2c_handle; // Unique I2C bus handle. @attention Must be same for all PCF8574 expanders. i2c_master_bus_handle_t i2c_handle; // Unique I2C bus handle. @attention Must be same for all PCF8574 expanders.
#endif #endif
@@ -73,6 +64,7 @@ extern "C"
i2c_master_bus_handle_t i2c_handle; // Unique I2C bus handle. i2c_master_bus_handle_t i2c_handle; // Unique I2C bus handle.
i2c_master_dev_handle_t dev_handle; // Unique I2C device handle. i2c_master_dev_handle_t dev_handle; // Unique I2C device handle.
#endif #endif
void *system; // System pointer for use in another components.
} zh_pcf8574_handle_t; } zh_pcf8574_handle_t;
ESP_EVENT_DECLARE_BASE(ZH_PCF8574); ESP_EVENT_DECLARE_BASE(ZH_PCF8574);
@@ -81,6 +73,7 @@ extern "C"
{ {
uint8_t i2c_address; // The i2c address of PCF8574 expander that caused the interrupt. uint8_t i2c_address; // The i2c address of PCF8574 expander that caused the interrupt.
uint8_t gpio_number; // The GPIO that caused the interrupt. uint8_t gpio_number; // The GPIO that caused the interrupt.
bool gpio_level; // The GPIO level that caused the interrupt.
} zh_pcf8574_event_on_isr_t; } zh_pcf8574_event_on_isr_t;
/** /**
@@ -95,12 +88,7 @@ extern "C"
* *
* @code zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT() @endcode * @code zh_pcf8574_init_config_t config = ZH_PCF8574_INIT_CONFIG_DEFAULT() @endcode
* *
* @return * @return ESP_OK if success or an error code otherwise.
* - ESP_OK if initialization was success
* - ESP_ERR_INVALID_ARG if parameter error or invalid i2c adress
* - ESP_ERR_NOT_FOUND if expander not connected or not responded
* - ESP_ERR_INVALID_RESPONSE if I2C driver error
* - ESP_FAIL if internal error
*/ */
esp_err_t zh_pcf8574_init(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t *handle); esp_err_t zh_pcf8574_init(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t *handle);
@@ -112,11 +100,7 @@ extern "C"
* *
* @note For input GPIO's status will be 1 (HIGH) always. * @note For input GPIO's status will be 1 (HIGH) always.
* *
* @return * @return ESP_OK if success or an error code otherwise.
* - ESP_OK if read was success
* - ESP_ERR_INVALID_ARG if parameter error
* - ESP_ERR_NOT_FOUND if expander is not initialized
* - ESP_ERR_INVALID_RESPONSE if I2C driver error
*/ */
esp_err_t zh_pcf8574_read(zh_pcf8574_handle_t *handle, uint8_t *reg); esp_err_t zh_pcf8574_read(zh_pcf8574_handle_t *handle, uint8_t *reg);
@@ -128,11 +112,7 @@ extern "C"
* *
* @attention Only the GPIO outputs are affected. * @attention Only the GPIO outputs are affected.
* *
* @return * @return ESP_OK if success or an error code otherwise.
* - ESP_OK if write was success
* - ESP_ERR_INVALID_ARG if parameter error
* - ESP_ERR_NOT_FOUND if expander is not initialized
* - ESP_ERR_INVALID_RESPONSE if I2C driver error
*/ */
esp_err_t zh_pcf8574_write(zh_pcf8574_handle_t *handle, uint8_t reg); esp_err_t zh_pcf8574_write(zh_pcf8574_handle_t *handle, uint8_t reg);
@@ -141,11 +121,7 @@ extern "C"
* *
* @param[in] handle Pointer to unique PCF8574 handle. * @param[in] handle Pointer to unique PCF8574 handle.
* *
* @return * @return ESP_OK if success or an error code otherwise.
* - ESP_OK if reset was success
* - ESP_ERR_INVALID_ARG if parameter error
* - ESP_ERR_NOT_FOUND if expander is not initialized
* - ESP_ERR_INVALID_RESPONSE if I2C driver error
*/ */
esp_err_t zh_pcf8574_reset(zh_pcf8574_handle_t *handle); esp_err_t zh_pcf8574_reset(zh_pcf8574_handle_t *handle);
@@ -154,34 +130,26 @@ extern "C"
* *
* @param[in] handle Pointer to unique PCF8574 handle. * @param[in] handle Pointer to unique PCF8574 handle.
* @param[in] gpio GPIO number. * @param[in] gpio GPIO number.
* @param[out] status Pointer to GPIO status. * @param[out] status Pointer to GPIO status (true - HIGH, false - LOW).
* *
* @note For input GPIO's status will be 1 (HIGH) always. * @note For input GPIO's status will be 1 (HIGH) always.
* *
* @return * @return ESP_OK if success or an error code otherwise.
* - ESP_OK if read was success
* - ESP_ERR_INVALID_ARG if parameter error
* - ESP_ERR_NOT_FOUND if expander is not initialized
* - ESP_ERR_INVALID_RESPONSE if I2C driver error
*/ */
esp_err_t zh_pcf8574_read_gpio(zh_pcf8574_handle_t *handle, zh_pcf8574_gpio_number_t gpio, bool *status); esp_err_t zh_pcf8574_read_gpio(zh_pcf8574_handle_t *handle, uint8_t gpio, bool *status);
/** /**
* @brief Set PCF8574 GPIO status. * @brief Set PCF8574 GPIO status.
* *
* @param[in] handle Pointer to unique PCF8574 handle. * @param[in] handle Pointer to unique PCF8574 handle.
* @param[in] gpio GPIO number. * @param[in] gpio GPIO number.
* @param[in] status Pointer to GPIO status. * @param[in] status GPIO status (true - HIGH, false - LOW).
* *
* @attention Only the GPIO output is affected. * @attention Only the GPIO output is affected.
* *
* @return * @return ESP_OK if success or an error code otherwise.
* - ESP_OK if write was success
* - ESP_ERR_INVALID_ARG if parameter error
* - ESP_ERR_NOT_FOUND if expander is not initialized
* - ESP_ERR_INVALID_RESPONSE if I2C driver error
*/ */
esp_err_t zh_pcf8574_write_gpio(zh_pcf8574_handle_t *handle, zh_pcf8574_gpio_number_t gpio, bool status); esp_err_t zh_pcf8574_write_gpio(zh_pcf8574_handle_t *handle, uint8_t gpio, bool status);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -1 +1 @@
1.0.0 1.3.1

View File

@@ -2,122 +2,201 @@
static const char *TAG = "zh_pcf8574"; static const char *TAG = "zh_pcf8574";
static gpio_num_t _interrupt_gpio = GPIO_NUM_MAX; #define ZH_PCF8574_LOGI(msg, ...) ESP_LOGI(TAG, msg, ##__VA_ARGS__)
static SemaphoreHandle_t _interrupt_semaphore = {0}; #define ZH_PCF8574_LOGW(msg, ...) ESP_LOGW(TAG, msg, ##__VA_ARGS__)
#define ZH_PCF8574_LOGE(msg, ...) ESP_LOGE(TAG, msg, ##__VA_ARGS__)
#define ZH_PCF8574_LOGE_ERR(msg, err, ...) ESP_LOGE(TAG, "[%s:%d:%s] " msg, __FILE__, __LINE__, esp_err_to_name(err), ##__VA_ARGS__)
#define ZH_PCF8574_CHECK(cond, err, msg, ...) \
if (!(cond)) \
{ \
ZH_PCF8574_LOGE_ERR(msg, err); \
return err; \
}
static uint8_t _interrupt_gpio = GPIO_NUM_MAX;
static SemaphoreHandle_t _interrupt_semaphore = NULL;
static uint8_t _gpio_matrix[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
static zh_vector_t _vector = {0}; static zh_vector_t _vector = {0};
static void _zh_isr_handler(void *arg); static esp_err_t _zh_pcf8574_validate_config(const zh_pcf8574_init_config_t *config);
static void _zh_isr_processing_task(void *pvParameter); static esp_err_t _zh_pcf8574_configure_i2c_device(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t *handle);
static esp_err_t _zh_read_register(zh_pcf8574_handle_t *handle, uint8_t *reg); static esp_err_t _zh_pcf8574_configure_interrupts(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t handle);
static esp_err_t _zh_write_register(zh_pcf8574_handle_t *handle, uint8_t reg); static void _zh_pcf8574_isr_handler(void *arg);
static void _zh_pcf8574_isr_processing_task(void *pvParameter);
static esp_err_t _zh_pcf8574_read_register(zh_pcf8574_handle_t *handle, uint8_t *reg);
static esp_err_t _zh_pcf8574_write_register(zh_pcf8574_handle_t *handle, uint8_t reg);
ESP_EVENT_DEFINE_BASE(ZH_PCF8574); ESP_EVENT_DEFINE_BASE(ZH_PCF8574);
esp_err_t zh_pcf8574_init(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t *handle) esp_err_t zh_pcf8574_init(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t *handle)
{ {
ESP_LOGI(TAG, "PCF8574 initialization begin."); ZH_PCF8574_LOGI("PCF8574 initialization started.");
if (config == NULL || handle == NULL || config->i2c_address == 0xFF) ZH_PCF8574_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, "PCF8574 initialization failed. Invalid argument.");
ZH_PCF8574_CHECK(handle->is_initialized == false, ESP_ERR_INVALID_STATE, "PCF8574 initialization failed. PCF8574 is already initialized.");
esp_err_t err = _zh_pcf8574_validate_config(config);
ZH_PCF8574_CHECK(err == ESP_OK, err, "PCF8574 initialization failed. Initial configuration check failed.");
ZH_PCF8574_LOGI("PCF8574 initial configuration check completed successfully.");
err = _zh_pcf8574_configure_i2c_device(config, handle);
ZH_PCF8574_CHECK(err == ESP_OK, err, "PCF8574 initialization failed. Failed to add I2C device.");
ZH_PCF8574_LOGI("PCF8574 add I2C device completed successfully.");
err = _zh_pcf8574_write_register(handle, handle->gpio_work_mode);
if (err != ESP_OK)
{ {
ESP_LOGE(TAG, "PCF8574 initialization fail. Invalid argument.");
return ESP_ERR_INVALID_ARG;
}
handle->i2c_address = config->i2c_address;
handle->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);
handle->gpio_status = handle->gpio_work_mode;
handle->i2c_port = config->i2c_port;
#ifndef CONFIG_IDF_TARGET_ESP8266
handle->i2c_handle = config->i2c_handle;
i2c_device_config_t pcf8574_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = handle->i2c_address,
.scl_speed_hz = 100000,
};
i2c_master_bus_add_device(handle->i2c_handle, &pcf8574_config, &handle->dev_handle);
if (i2c_master_probe(handle->i2c_handle, handle->i2c_address, 1000 / portTICK_PERIOD_MS) != ESP_OK)
{
ESP_LOGE(TAG, "PCF8574 initialization fail. Expander not connected or not responded.");
return ESP_ERR_NOT_FOUND;
}
#endif
handle->is_initialized = true;
if (_zh_write_register(handle, handle->gpio_work_mode) != ESP_OK)
{
ESP_LOGE(TAG, "PCF8574 initialization fail. I2C driver error at line %d.", __LINE__);
handle->is_initialized = false; handle->is_initialized = false;
return ESP_ERR_INVALID_RESPONSE; ZH_PCF8574_LOGE_ERR("PCF8574 initialization failed. Failed GPIO setup.", err);
return err;
} }
if (_interrupt_gpio == GPIO_NUM_MAX && config->interrupt_gpio != GPIO_NUM_MAX) ZH_PCF8574_LOGI("GPIO setup completed successfully.");
if (config->interrupt_gpio < GPIO_NUM_MAX && handle->gpio_work_mode != 0)
{ {
zh_vector_init(&_vector, sizeof(zh_pcf8574_handle_t)); err = _zh_pcf8574_configure_interrupts(config, *handle);
_interrupt_gpio = config->interrupt_gpio; if (err != ESP_OK)
gpio_config_t isr_pin_config = {0};
isr_pin_config.intr_type = GPIO_INTR_NEGEDGE;
isr_pin_config.mode = GPIO_MODE_INPUT;
isr_pin_config.pin_bit_mask = (1ULL << _interrupt_gpio);
isr_pin_config.pull_down_en = GPIO_PULLDOWN_DISABLE;
isr_pin_config.pull_up_en = GPIO_PULLUP_ENABLE;
if (gpio_config(&isr_pin_config) != ESP_OK)
{ {
ESP_LOGW(TAG, "PCF8574 initialization warning. GPIO driver error at line %d.", __LINE__); handle->is_initialized = false;
} ZH_PCF8574_LOGE_ERR("PCF8574 initialization failed. Interrupt setup failed.", err);
else return err;
{
_interrupt_semaphore = xSemaphoreCreateBinary();
if (xTaskCreatePinnedToCore(&_zh_isr_processing_task, "_zh_isr_processing_task", config->stack_size, NULL, config->task_priority, NULL, tskNO_AFFINITY) != pdPASS)
{
ESP_LOGE(TAG, "PCF8574 initialization warning. Internal error at line %d.", __LINE__);
return ESP_FAIL;
}
gpio_install_isr_service(0);
gpio_isr_handler_add(_interrupt_gpio, _zh_isr_handler, NULL);
} }
ZH_PCF8574_LOGI("Interrupt setup completed successfully.");
} }
if (_interrupt_gpio != GPIO_NUM_MAX && handle->gpio_work_mode != 0) handle->is_initialized = true;
{ ZH_PCF8574_LOGI("PCF8574 initialization completed successfully.");
zh_vector_push_back(&_vector, handle);
}
ESP_LOGI(TAG, "PCF8574 initialization success.");
return ESP_OK; return ESP_OK;
} }
esp_err_t zh_pcf8574_read(zh_pcf8574_handle_t *handle, uint8_t *reg) esp_err_t zh_pcf8574_read(zh_pcf8574_handle_t *handle, uint8_t *reg)
{ {
return _zh_read_register(handle, reg); return _zh_pcf8574_read_register(handle, reg);
} }
esp_err_t zh_pcf8574_write(zh_pcf8574_handle_t *handle, uint8_t reg) esp_err_t zh_pcf8574_write(zh_pcf8574_handle_t *handle, uint8_t reg)
{ {
return _zh_write_register(handle, (reg | handle->gpio_work_mode)); return _zh_pcf8574_write_register(handle, (reg | handle->gpio_work_mode));
} }
esp_err_t zh_pcf8574_reset(zh_pcf8574_handle_t *handle) esp_err_t zh_pcf8574_reset(zh_pcf8574_handle_t *handle)
{ {
return _zh_write_register(handle, handle->gpio_work_mode); return _zh_pcf8574_write_register(handle, handle->gpio_work_mode);
} }
esp_err_t zh_pcf8574_read_gpio(zh_pcf8574_handle_t *handle, zh_pcf8574_gpio_number_t gpio, bool *status) esp_err_t zh_pcf8574_read_gpio(zh_pcf8574_handle_t *handle, uint8_t gpio, bool *status)
{ {
ZH_PCF8574_CHECK(gpio <= 7, ESP_FAIL, "Invalid GPIO number.")
uint8_t gpio_temp = _gpio_matrix[gpio];
uint8_t reg_temp = 0; uint8_t reg_temp = 0;
esp_err_t err = _zh_read_register(handle, &reg_temp); esp_err_t err = _zh_pcf8574_read_register(handle, &reg_temp);
*status = ((reg_temp & gpio) ? 1 : 0); *status = ((reg_temp & gpio_temp) ? 1 : 0);
return err; return err;
} }
esp_err_t zh_pcf8574_write_gpio(zh_pcf8574_handle_t *handle, zh_pcf8574_gpio_number_t gpio, bool status) esp_err_t zh_pcf8574_write_gpio(zh_pcf8574_handle_t *handle, uint8_t gpio, bool status)
{ {
ZH_PCF8574_CHECK(gpio <= 7, ESP_FAIL, "Invalid GPIO number.")
uint8_t gpio_temp = _gpio_matrix[gpio];
if (status == true) if (status == true)
{ {
return _zh_write_register(handle, handle->gpio_status | handle->gpio_work_mode | gpio); return _zh_pcf8574_write_register(handle, handle->gpio_status | handle->gpio_work_mode | gpio_temp);
}
else
{
return _zh_write_register(handle, (handle->gpio_status ^ gpio) | handle->gpio_work_mode);
} }
return _zh_pcf8574_write_register(handle, (handle->gpio_status ^ gpio_temp) | handle->gpio_work_mode);
} }
void IRAM_ATTR _zh_isr_handler(void *arg) static esp_err_t _zh_pcf8574_validate_config(const zh_pcf8574_init_config_t *config)
{
ZH_PCF8574_CHECK(config != NULL, ESP_ERR_INVALID_ARG, "Invalid configuration.");
ZH_PCF8574_CHECK((config->i2c_address >= 0x20 && config->i2c_address <= 0x27) || (config->i2c_address >= 0x38 && config->i2c_address <= 0x3F), ESP_ERR_INVALID_ARG, "Invalid I2C address.");
ZH_PCF8574_CHECK(config->task_priority >= 10 && config->stack_size >= 2048, ESP_ERR_INVALID_ARG, "Invalid task settings.");
ZH_PCF8574_CHECK(config->interrupt_gpio >= 0 && config->interrupt_gpio <= GPIO_NUM_MAX, ESP_ERR_INVALID_ARG, "Invalid GPIO number.");
#ifndef CONFIG_IDF_TARGET_ESP8266
ZH_PCF8574_CHECK(config->i2c_handle != NULL, ESP_ERR_INVALID_ARG, "Invalid I2C handle.");
#endif
return ESP_OK;
}
static esp_err_t _zh_pcf8574_configure_i2c_device(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t *handle)
{
#ifndef CONFIG_IDF_TARGET_ESP8266
i2c_device_config_t pcf8574_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = config->i2c_address,
.scl_speed_hz = 100000,
};
i2c_master_dev_handle_t _dev_handle = NULL;
esp_err_t err = i2c_master_bus_add_device(config->i2c_handle, &pcf8574_config, &_dev_handle);
if (err != ESP_OK)
{
ZH_PCF8574_LOGE_ERR("Failed to add I2C device.", err);
i2c_master_bus_rm_device(_dev_handle);
handle->dev_handle = NULL;
return err;
}
err = i2c_master_probe(config->i2c_handle, config->i2c_address, 1000 / portTICK_PERIOD_MS);
if (err != ESP_OK)
{
ZH_PCF8574_LOGE_ERR("Expander not connected or not responding.", err);
i2c_master_bus_rm_device(_dev_handle);
handle->dev_handle = NULL;
return err;
}
handle->i2c_handle = config->i2c_handle;
handle->dev_handle = _dev_handle;
#endif
handle->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);
handle->gpio_status = handle->gpio_work_mode;
handle->i2c_address = config->i2c_address;
handle->i2c_port = config->i2c_port;
handle->is_initialized = true;
return ESP_OK;
}
static esp_err_t _zh_pcf8574_configure_interrupts(const zh_pcf8574_init_config_t *config, zh_pcf8574_handle_t handle)
{
if (_interrupt_gpio != GPIO_NUM_MAX)
{
esp_err_t err = zh_vector_push_back(&_vector, &handle);
ZH_PCF8574_CHECK(err == ESP_OK, err, "Failed add item to vector.")
return ESP_OK;
}
_interrupt_gpio = config->interrupt_gpio;
esp_err_t err = zh_vector_init(&_vector, sizeof(zh_pcf8574_handle_t));
ZH_PCF8574_CHECK(err == ESP_OK, err, "Failed create vector.")
err = zh_vector_push_back(&_vector, &handle);
ZH_PCF8574_CHECK(err == ESP_OK, err, "Failed add item to vector.")
gpio_config_t isr_pin_config = {
.intr_type = GPIO_INTR_NEGEDGE,
.mode = GPIO_MODE_INPUT,
.pin_bit_mask = (1ULL << _interrupt_gpio),
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_ENABLE,
};
err = gpio_config(&isr_pin_config);
ZH_PCF8574_CHECK(err == ESP_OK, err, "GPIO configuration failed.")
err = gpio_install_isr_service(0);
ZH_PCF8574_CHECK(err == ESP_OK, err, "Failed install isr service.")
err = gpio_isr_handler_add(_interrupt_gpio, _zh_pcf8574_isr_handler, NULL);
ZH_PCF8574_CHECK(err == ESP_OK, err, "Failed add isr handler.")
_interrupt_semaphore = xSemaphoreCreateBinary();
ZH_PCF8574_CHECK(_interrupt_semaphore != NULL, ESP_ERR_NO_MEM, "Failed to create semaphore.")
BaseType_t x_err = xTaskCreatePinnedToCore(
&_zh_pcf8574_isr_processing_task,
"_zh_pcf8574_isr_processing_task",
config->stack_size,
NULL,
config->task_priority,
NULL,
tskNO_AFFINITY);
if (x_err != pdPASS)
{
ZH_PCF8574_LOGE("Failed to create isr processing task.");
vSemaphoreDelete(_interrupt_semaphore);
return ESP_FAIL;
}
return ESP_OK;
}
static void IRAM_ATTR _zh_pcf8574_isr_handler(void *arg)
{ {
BaseType_t xHigherPriorityTaskWoken = pdFALSE; BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(_interrupt_semaphore, &xHigherPriorityTaskWoken); xSemaphoreGiveFromISR(_interrupt_semaphore, &xHigherPriorityTaskWoken);
@@ -127,130 +206,96 @@ void IRAM_ATTR _zh_isr_handler(void *arg)
}; };
} }
void _zh_isr_processing_task(void *pvParameter) static void IRAM_ATTR _zh_pcf8574_isr_processing_task(void *pvParameter)
{ {
for (;;) for (;;)
{ {
xSemaphoreTake(_interrupt_semaphore, portMAX_DELAY); xSemaphoreTake(_interrupt_semaphore, portMAX_DELAY);
ESP_LOGI(TAG, "PCF8574 isr processing begin."); ZH_PCF8574_LOGI("PCF8574 isr processing begin.");
for (uint16_t i = 0; i < zh_vector_get_size(&_vector); ++i) for (uint16_t i = 0; i < zh_vector_get_size(&_vector); ++i)
{ {
zh_pcf8574_handle_t *handle = zh_vector_get_item(&_vector, i); zh_pcf8574_handle_t *handle = zh_vector_get_item(&_vector, i);
if (handle == NULL)
{
ZH_PCF8574_LOGE("PCF8574 isr processing failed. Failed to get vector item data.");
continue;
}
zh_pcf8574_event_on_isr_t event = {0}; zh_pcf8574_event_on_isr_t event = {0};
event.i2c_address = handle->i2c_address; event.i2c_address = handle->i2c_address;
event.gpio_number = 0xFF; event.gpio_number = 0xFF;
uint8_t reg_temp = 0; uint8_t old_reg = handle->gpio_status;
_zh_read_register(handle, &reg_temp); uint8_t new_reg = 0;
if (((handle->gpio_work_mode & EXP_GPIO_NUM_P0) != 0) && ((reg_temp & EXP_GPIO_NUM_P0) == 0)) esp_err_t err = _zh_pcf8574_read_register(handle, &new_reg);
if (err != ESP_OK)
{ {
event.gpio_number = 0; ZH_PCF8574_LOGE_ERR("PCF8574 isr processing failed. Failed to read expander register.", err);
continue;
} }
else if (((handle->gpio_work_mode & EXP_GPIO_NUM_P1) != 0) && ((reg_temp & EXP_GPIO_NUM_P1) == 0)) for (uint8_t j = 0; j <= 7; ++j)
{ {
event.gpio_number = 1; if ((handle->gpio_work_mode & _gpio_matrix[j]) != 0)
} {
else if (((handle->gpio_work_mode & EXP_GPIO_NUM_P2) != 0) && ((reg_temp & EXP_GPIO_NUM_P2) == 0)) if ((old_reg & _gpio_matrix[j]) != (new_reg & _gpio_matrix[j]))
{ {
event.gpio_number = 2; event.gpio_number = j;
} event.gpio_level = new_reg & _gpio_matrix[j];
else if (((handle->gpio_work_mode & EXP_GPIO_NUM_P3) != 0) && ((reg_temp & EXP_GPIO_NUM_P3) == 0)) }
{ }
event.gpio_number = 3;
}
else if (((handle->gpio_work_mode & EXP_GPIO_NUM_P4) != 0) && ((reg_temp & EXP_GPIO_NUM_P4) == 0))
{
event.gpio_number = 4;
}
else if (((handle->gpio_work_mode & EXP_GPIO_NUM_P5) != 0) && ((reg_temp & EXP_GPIO_NUM_P5) == 0))
{
event.gpio_number = 5;
}
else if (((handle->gpio_work_mode & EXP_GPIO_NUM_P6) != 0) && ((reg_temp & EXP_GPIO_NUM_P6) == 0))
{
event.gpio_number = 6;
}
else if (((handle->gpio_work_mode & EXP_GPIO_NUM_P7) != 0) && ((reg_temp & EXP_GPIO_NUM_P7) == 0))
{
event.gpio_number = 7;
} }
if (event.gpio_number != 0xFF) if (event.gpio_number != 0xFF)
{ {
if (esp_event_post(ZH_PCF8574, 0, &event, sizeof(event), portTICK_PERIOD_MS) != ESP_OK) err = esp_event_post(ZH_PCF8574, 0, &event, sizeof(event), portTICK_PERIOD_MS);
if (err != ESP_OK)
{ {
ESP_LOGE(TAG, "PCF8574 isr processing task internal error at line %d.", __LINE__); ZH_PCF8574_LOGE_ERR("PCF8574 isr processing failed. Failed to post interrupt event.", err);
} }
} }
} }
ESP_LOGI(TAG, "PCF8574 isr processing success."); ZH_PCF8574_LOGI("PCF8574 isr processing completed successfully.");
} }
vTaskDelete(NULL); vTaskDelete(NULL);
} }
esp_err_t _zh_read_register(zh_pcf8574_handle_t *handle, uint8_t *reg) static esp_err_t _zh_pcf8574_read_register(zh_pcf8574_handle_t *handle, uint8_t *reg)
{ {
ESP_LOGI(TAG, "PCF8574 read begin."); ZH_PCF8574_LOGI("PCF8574 read started.");
if (handle == NULL) ZH_PCF8574_CHECK(handle != NULL || reg != NULL, ESP_ERR_INVALID_ARG, "PCF8574 read failed. Invalid argument.");
{ ZH_PCF8574_CHECK(handle->is_initialized == true, ESP_ERR_NOT_FOUND, "PCF8574 read failed. PCF8574 not initialized.");
ESP_LOGE(TAG, "PCF8574 read fail. Invalid argument.");
return ESP_ERR_INVALID_ARG;
}
if (handle->is_initialized == false)
{
ESP_LOGE(TAG, "PCF8574 read fail. PCF8574 not initialized.");
return ESP_ERR_NOT_FOUND;
}
esp_err_t err = ESP_OK;
#ifdef CONFIG_IDF_TARGET_ESP8266 #ifdef CONFIG_IDF_TARGET_ESP8266
i2c_cmd_handle_t i2c_cmd_handle = i2c_cmd_link_create(); i2c_cmd_handle_t i2c_cmd_handle = i2c_cmd_link_create();
i2c_master_start(i2c_cmd_handle); i2c_master_start(i2c_cmd_handle);
i2c_master_write_byte(i2c_cmd_handle, handle->i2c_address << 1 | I2C_MASTER_READ, true); i2c_master_write_byte(i2c_cmd_handle, handle->i2c_address << 1 | I2C_MASTER_READ, true);
i2c_master_read_byte(i2c_cmd_handle, &handle->gpio_status, I2C_MASTER_NACK); i2c_master_read_byte(i2c_cmd_handle, &handle->gpio_status, I2C_MASTER_NACK);
i2c_master_stop(i2c_cmd_handle); i2c_master_stop(i2c_cmd_handle);
err = i2c_master_cmd_begin(handle->i2c_port, i2c_cmd_handle, 1000 / portTICK_PERIOD_MS); esp_err_t err = i2c_master_cmd_begin(handle->i2c_port, i2c_cmd_handle, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(i2c_cmd_handle); i2c_cmd_link_delete(i2c_cmd_handle);
#else #else
err = i2c_master_receive(handle->dev_handle, &handle->gpio_status, sizeof(handle->gpio_status), 1000 / portTICK_PERIOD_MS); esp_err_t err = i2c_master_receive(handle->dev_handle, &handle->gpio_status, sizeof(handle->gpio_status), 1000 / portTICK_PERIOD_MS);
#endif #endif
if (err != ESP_OK) ZH_PCF8574_CHECK(err == ESP_OK, err, "PCF8574 read failed. I2C driver error.");
{
ESP_LOGE(TAG, "PCF8574 read fail. I2C driver error at line %d.", __LINE__);
return ESP_ERR_INVALID_RESPONSE;
}
*reg = handle->gpio_status; *reg = handle->gpio_status;
ESP_LOGI(TAG, "PCF8574 read success."); ZH_PCF8574_LOGI("PCF8574 read completed successfully.");
return ESP_OK; return ESP_OK;
} }
esp_err_t _zh_write_register(zh_pcf8574_handle_t *handle, uint8_t reg)
static esp_err_t _zh_pcf8574_write_register(zh_pcf8574_handle_t *handle, uint8_t reg)
{ {
ESP_LOGI(TAG, "PCF8574 write begin."); ZH_PCF8574_LOGI("PCF8574 write started.");
if (handle == NULL) ZH_PCF8574_CHECK(handle != NULL, ESP_ERR_INVALID_ARG, "PCF8574 write failed. Invalid argument.");
{ ZH_PCF8574_CHECK(handle->is_initialized == true, ESP_ERR_NOT_FOUND, "PCF8574 write failed. PCF8574 not initialized.");
ESP_LOGE(TAG, "PCF8574 write fail. Invalid argument.");
return ESP_ERR_INVALID_ARG;
}
if (handle->is_initialized == false)
{
ESP_LOGE(TAG, "PCF8574 write fail. PCF8574 not initialized.");
return ESP_ERR_NOT_FOUND;
}
esp_err_t err = ESP_OK;
#ifdef CONFIG_IDF_TARGET_ESP8266 #ifdef CONFIG_IDF_TARGET_ESP8266
i2c_cmd_handle_t i2c_cmd_handle = i2c_cmd_link_create(); i2c_cmd_handle_t i2c_cmd_handle = i2c_cmd_link_create();
i2c_master_start(i2c_cmd_handle); i2c_master_start(i2c_cmd_handle);
i2c_master_write_byte(i2c_cmd_handle, handle->i2c_address << 1 | I2C_MASTER_WRITE, true); i2c_master_write_byte(i2c_cmd_handle, handle->i2c_address << 1 | I2C_MASTER_WRITE, true);
i2c_master_write_byte(i2c_cmd_handle, reg, true); i2c_master_write_byte(i2c_cmd_handle, reg, true);
i2c_master_stop(i2c_cmd_handle); i2c_master_stop(i2c_cmd_handle);
err = i2c_master_cmd_begin(handle->i2c_port, i2c_cmd_handle, 1000 / portTICK_PERIOD_MS); esp_err_t err = i2c_master_cmd_begin(handle->i2c_port, i2c_cmd_handle, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(i2c_cmd_handle); i2c_cmd_link_delete(i2c_cmd_handle);
#else #else
err = i2c_master_transmit(handle->dev_handle, &reg, sizeof(reg), 1000 / portTICK_PERIOD_MS); esp_err_t err = i2c_master_transmit(handle->dev_handle, &reg, sizeof(reg), 1000 / portTICK_PERIOD_MS);
#endif #endif
if (err != ESP_OK) ZH_PCF8574_CHECK(err == ESP_OK, err, "PCF8574 write failed. I2C driver error.");
{
ESP_LOGE(TAG, "PCF8574 write fail. I2C driver error at line %d.", __LINE__);
return ESP_ERR_INVALID_RESPONSE;
}
handle->gpio_status = reg; handle->gpio_status = reg;
ESP_LOGI(TAG, "PCF8574 write success."); ZH_PCF8574_LOGI("PCF8574 write completed successfully.");
return ESP_OK; return ESP_OK;
} }