diff --git a/examples/TestCH422G/TestCH422G.ino b/examples/TestCH422G/TestCH422G.ino new file mode 100644 index 0000000..5450472 --- /dev/null +++ b/examples/TestCH422G/TestCH422G.ino @@ -0,0 +1,104 @@ +/** + * | Supported IO Expanders | CH422G | + * | ------------------------- | ------ | + * + * # CH422G Test Example + * + * The hardware device used in this example is waveshare ESP32-S3-Touch-LCD-4.3B-BOX. To test the simultaneous use of I/O input and OC output, connect DO0 to DI0, and connect DO1 to DI1. + * + * ## How to use + * + * 1. Enable USB CDC. + * 2. Verify and upload the example to your board. + * + * ## Serial Output + * + * ``` + * ... + * Test begin + * Set the OC pin to push-pull output mode. + * Set the IO0-7 pin to input mode. + * Set pint 8 and 9 to:0, 1 + * + * Read pin 0 and 5 level: 0, 1 + * + * Set pint 8 and 9 to:1, 0 + * + * Read pin 0 and 5 level: 1, 0 + * ... + * ``` + * + * ## Troubleshooting + * + * The driver initialization by default sets CH422G's IO0-7 to output high-level mode. + Since the input/output mode of CH422G's IO0-7 must remain consistent, the driver will only set IO0-7 to + input mode when it determines that all pins are configured as input. + Using pinMode and multiPinMode will be invalid. You can only set the pin working mode through enableAllIO_Input, enableAllIO_Output, enableOC_PushPull and enableOC_OpenDrain + * + */ + +#include +#include + +#define EXAMPLE_I2C_NUM (0) +#define EXAMPLE_I2C_SDA_PIN (8) +#define EXAMPLE_I2C_SCL_PIN (9) +#define EXAMPLE_I2C_ADDR (ESP_IO_EXPANDER_I2C_CH422G_ADDRESS) // Modify this value according to the \ + // hardware address + +ESP_IOExpander_CH422G *expander = NULL; + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println("Test begin"); + + expander = new ESP_IOExpander_CH422G((i2c_port_t)EXAMPLE_I2C_NUM, EXAMPLE_I2C_ADDR, + EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN); + expander->init(); + expander->begin(); + + /* For CH422G */ + Serial.println("Set the OC pin to push-pull output mode."); + static_cast(expander)->enableOC_PushPull(); + + // Serial.println("Set the OC pin to open_drain output mode."); + // static_cast(expander)->enableOC_OpenDrain(); + + Serial.println("Set the IO0-7 pin to input mode."); + static_cast(expander)->enableAllIO_Input(); + + // Serial.println("Set the IO0-7 pin to output mode."); + //static_cast(expander)->enableAllIO_Output(); +} + +int level[2] = { 0, 0 }; + +void loop() { + for (int i = 0; i < 100; i++) { + bool toggle = i % 2; + + Serial.print("Set pint 8 and 9 to:"); + Serial.print(toggle); + Serial.print(", "); + Serial.println(!toggle); + Serial.println(); + + // Set pin 8 and 9 level + expander->digitalWrite(8, toggle); + expander->digitalWrite(9, !toggle); + delay(1); + + // Read pin 0 and 5 level + level[0] = expander->digitalRead(0); + level[1] = expander->digitalRead(5); + + Serial.print("Read pin 0 and 5 level: "); + Serial.print(level[0]); + Serial.print(", "); + Serial.println(level[1]); + Serial.println(); + + delay(1000); + } +} diff --git a/examples/TestFunctions/TestFunctions.ino b/examples/TestFunctions/TestFunctions.ino index cac076b..e19befc 100644 --- a/examples/TestFunctions/TestFunctions.ino +++ b/examples/TestFunctions/TestFunctions.ino @@ -4,6 +4,8 @@ #define EXAMPLE_I2C_NUM (0) #define EXAMPLE_I2C_SDA_PIN (8) #define EXAMPLE_I2C_SCL_PIN (18) +#define EXAMPLE_I2C_ADDR (ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000) // Modify this value according to the + // hardware address /** * Create an ESP_IOExpander object, Currently supports: @@ -18,9 +20,18 @@ void setup() Serial.begin(115200); Serial.println("Test begin"); +<<<<<<< HEAD +======= + expander = new EXAMPLE_CHIP_CLASS(EXAMPLE_CHIP_NAME, (i2c_port_t)EXAMPLE_I2C_NUM, EXAMPLE_I2C_ADDR, + EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN); +>>>>>>> a426a76 (Add examples for CH422G) expander->init(); expander->begin(); + /* For CH422G */ + // static_cast(expander)->enableOC_PushPull(); + // static_cast(expander)->enableOC_OpenDrain(); + Serial.println("Original status:"); expander->printStatus(); diff --git a/src/chip/CH422G.cpp b/src/chip/CH422G.cpp new file mode 100644 index 0000000..c097867 --- /dev/null +++ b/src/chip/CH422G.cpp @@ -0,0 +1,271 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "driver/i2c.h" +#include "esp_bit_defs.h" +#include "esp_check.h" +#include "esp_log.h" + +#include "../private/CheckResult.h" +#include "CH422G.h" + +/* Timeout of each I2C communication */ +#define I2C_TIMEOUT_MS (10) + +#define IO_COUNT (12) + +/* Register address */ +#define CH422G_REG_WR_SET (0x48 >> 1) +#define CH422G_REG_WR_OC (0x46 >> 1) +#define CH422G_REG_WR_IO (0x70 >> 1) +#define CH422G_REG_RD_IO (0x4D >> 1) + +/* Default register value when reset */ +// *INDENT-OFF* +#define REG_WR_SET_DEFAULT_VAL (0x01UL) // Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + // | --- | --- | --- | --- | ------- | ------- | -------- | ------- | + // Value: | / | / | / | / | [SLEEP] | [OD_EN] | [A_SCAN] | [IO_OE] | + // | --- | --- | --- | --- | ------- | ------- | -------- | ------- | + // Default: | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | + +// *INDENT-OFF* +#define REG_WR_OC_DEFAULT_VAL (0x0FUL) +#define REG_WR_IO_DEFAULT_VAL (0xFFUL) +#define REG_OUT_DEFAULT_VAL ((REG_WR_OC_DEFAULT_VAL << 8) | REG_WR_IO_DEFAULT_VAL) +#define REG_DIR_DEFAULT_VAL (0xFFFUL) + +#define REG_WR_SET_BIT_IO_OE (1 << 0) +#define REG_WR_SET_BIT_OD_EN (1 << 2) + +/** + * @brief Device Structure Type + * + */ +typedef struct { + esp_io_expander_t base; + i2c_port_t i2c_num; + uint32_t i2c_address; + struct { + uint8_t wr_set; + uint8_t wr_oc; + uint8_t wr_io; + } regs; +} esp_io_expander_ch422g_t; + +static const char *TAG = "ch422g"; + +static esp_err_t esp_io_expander_new_i2c_ch422g(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle); +static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value); +static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value); +static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value); +static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value); +static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value); +static esp_err_t reset(esp_io_expander_t *handle); +static esp_err_t del(esp_io_expander_t *handle); + +ESP_IOExpander_CH422G::~ESP_IOExpander_CH422G() +{ + if (i2c_need_init) { + i2c_driver_delete(i2c_id); + } + if (handle) { + del(); + } +} + +void ESP_IOExpander_CH422G::begin(void) +{ + CHECK_ERROR_RETURN(esp_io_expander_new_i2c_ch422g(i2c_id, i2c_address, &handle)); +} + +void ESP_IOExpander_CH422G::enableOC_OpenDrain(void) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + uint8_t data = (uint8_t)(ch422g->regs.wr_set | REG_WR_SET_BIT_OD_EN); + + // WR-SET + CHECK_ERROR_RETURN( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_SET, &data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)) + ); + ch422g->regs.wr_set = data; +} + +void ESP_IOExpander_CH422G::enableOC_PushPull(void) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + uint8_t data = (uint8_t)(ch422g->regs.wr_set & ~REG_WR_SET_BIT_OD_EN); + + // WR-SET + CHECK_ERROR_RETURN( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_SET, &data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)) + ); + ch422g->regs.wr_set = data; +} + +void ESP_IOExpander_CH422G::enableAllIO_Input(void) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + uint8_t data = (uint8_t)(ch422g->regs.wr_set & ~REG_WR_SET_BIT_IO_OE); + + // WR-SET + CHECK_ERROR_RETURN( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_SET, &data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)) + ); + ch422g->regs.wr_set = data; + // Delay 1ms to wait for the IO expander to switch to input mode + vTaskDelay(pdMS_TO_TICKS(2)); +} + +void ESP_IOExpander_CH422G::enableAllIO_Output(void) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + uint8_t data = (uint8_t)(ch422g->regs.wr_set | REG_WR_SET_BIT_IO_OE); + + // WR-SET + CHECK_ERROR_RETURN( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_SET, &data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)) + ); + ch422g->regs.wr_set = data; +} + +static esp_err_t esp_io_expander_new_i2c_ch422g(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle) +{ + ESP_RETURN_ON_FALSE(i2c_num < I2C_NUM_MAX, ESP_ERR_INVALID_ARG, TAG, "Invalid i2c num"); + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid handle"); + + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)calloc(1, sizeof(esp_io_expander_ch422g_t)); + ESP_RETURN_ON_FALSE(ch422g, ESP_ERR_NO_MEM, TAG, "Malloc failed"); + + ch422g->base.config.io_count = IO_COUNT; + ch422g->i2c_num = i2c_num; + ch422g->i2c_address = i2c_address; + ch422g->regs.wr_set = REG_WR_SET_DEFAULT_VAL; + ch422g->regs.wr_oc = REG_WR_OC_DEFAULT_VAL; + ch422g->regs.wr_io = REG_WR_IO_DEFAULT_VAL; + ch422g->base.read_input_reg = read_input_reg; + ch422g->base.write_output_reg = write_output_reg; + ch422g->base.read_output_reg = read_output_reg; + ch422g->base.write_direction_reg = write_direction_reg; + ch422g->base.read_direction_reg = read_direction_reg; + ch422g->base.del = del; + ch422g->base.reset = reset; + + esp_err_t ret = ESP_OK; + /* Reset configuration and register status */ + ESP_GOTO_ON_ERROR(reset(&ch422g->base), err, TAG, "Reset failed"); + + *handle = &ch422g->base; + return ESP_OK; +err: + free(ch422g); + return ret; +} + +static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + uint8_t temp = 0; + + ESP_RETURN_ON_ERROR( + i2c_master_read_from_device(ch422g->i2c_num, CH422G_REG_RD_IO, &temp, 1, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Read RD-IO reg failed" + ); + *value = temp; + + return ESP_OK; +} + +static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + + uint8_t wr_oc_data = (value & 0xF00) >> 8; + uint8_t wr_io_data = value & 0xFF; + + // WR-OC + if (wr_oc_data) { + ESP_RETURN_ON_ERROR( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_OC, &wr_oc_data, sizeof(wr_oc_data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Write WR-OC reg failed" + ); + ch422g->regs.wr_oc = wr_oc_data; + } + + // WR-IO + if (wr_io_data) { + ESP_RETURN_ON_ERROR( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_IO, &wr_io_data, sizeof(wr_io_data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Write WR-IO reg failed" + ); + ch422g->regs.wr_io = wr_io_data; + } + + return ESP_OK; +} + +static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + + *value = ch422g->regs.wr_io | (((uint32_t)ch422g->regs.wr_oc) << 8); + + return ESP_OK; +} + +static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + uint8_t data = ch422g->regs.wr_set; + + value &= 0xFF; + if (value != 0) { + data |= REG_WR_SET_BIT_IO_OE; + } else { + data &= ~REG_WR_SET_BIT_IO_OE; + } + + // WR-SET + ESP_RETURN_ON_ERROR( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_SET, &data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Write WR_SET reg failed" + ); + ch422g->regs.wr_set = data; + + return ESP_OK; +} + +#define DIR_OUT_VALUE (0xFFF) +#define DIR_IN_VALUE (0xF00) + +static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + + *value = (ch422g->regs.wr_set & REG_WR_SET_BIT_IO_OE) ? DIR_OUT_VALUE : DIR_IN_VALUE; + + return ESP_OK; +} + +static esp_err_t reset(esp_io_expander_t *handle) +{ + ESP_RETURN_ON_ERROR(write_direction_reg(handle, REG_DIR_DEFAULT_VAL), TAG, "Write direction reg (WR_SET) failed"); + ESP_RETURN_ON_ERROR(write_output_reg(handle, REG_OUT_DEFAULT_VAL), TAG, "Write output reg (WR_OC & WR_IO) failed"); + + return ESP_OK; +} + +static esp_err_t del(esp_io_expander_t *handle) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + + free(ch422g); + + return ESP_OK; +} diff --git a/src/chip/CH422G.h b/src/chip/CH422G.h new file mode 100644 index 0000000..114d909 --- /dev/null +++ b/src/chip/CH422G.h @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "driver/i2c.h" +#include "esp_err.h" + +#include "../ESP_IOExpander.h" + +/** + * Pin mapping: + * + * | Pin Number | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | + * | ------------ | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | + * | Function | IO0 | IO1 | IO2 | IO3 | IO4 | IO5 | IO6 | IO7 | OC0 | OC1 | OC2 | OC3 | + */ +class ESP_IOExpander_CH422G: public ESP_IOExpander { +public: + /** + * @brief Constructor to create ESP_IOExpander object + * + * @note After using this function, call `init()` will initialize I2C bus. + * + * @param id I2C port number + * @param address I2C device address. Just to keep the same with other IO expanders, but it is ignored. + * @param config Pointer to I2C bus configuration + */ + ESP_IOExpander_CH422G(i2c_port_t id, uint8_t address, const i2c_config_t *config): ESP_IOExpander(id, 0xFF, config) { }; + + /** + * @brief Constructor to create ESP_IOExpander object + * + * @note After using this function, call `init()` will initialize I2C bus. + * + * @param id I2C port number + * @param address I2C device address. Just to keep the same with other IO expanders, but it is ignored. + * @param scl SCL pin number + * @param sda SDA pin number + */ + ESP_IOExpander_CH422G(i2c_port_t id, uint8_t address, int scl, int sda): ESP_IOExpander(id, 0xFF, scl, sda) { }; + + /** + * @brief Constructor to create ESP_IOExpander object + * + * @note If use this function, should initialize I2C bus before call `init()`. + * + * @param id I2C port number + * @param address I2C device address. Just to keep the same with other IO expanders, but it is ignored. + */ + ESP_IOExpander_CH422G(i2c_port_t id, uint8_t address): ESP_IOExpander(id, 0xFF) { }; + + /** + * @brief Destructor + * + * @note This function will delete I2C driver if it is initialized by ESP_IOExpander and delete ESP_IOExpander object. + */ + ~ESP_IOExpander_CH422G() override; + + /** + * @brief Begin IO expander + * + * @note The driver initialization by default sets CH422G's IO0-7 to output high-level mode. + * + */ + void begin(void) override; + + /** + * @brief Enable OC0-OC3 output open-drain + * + */ + void enableOC_OpenDrain(void); + + /** + * @brief Enable OC0-OC3 output push-pull (default mode when power-on) + * + */ + void enableOC_PushPull(void); + + /** + * @brief Enable IO0-7 input mode + * + * @note The driver initialization by default sets CH422G's IO0-7 to output high-level mode. + * @note Since the input/output mode of CH422G's IO0-7 must remain consistent, the driver will only set IO0-7 to + * input mode when it determines that all pins are configured as input. + * + */ + void enableAllIO_Input(void); + + /** + * @brief Enable IO0-7 output mode + * + */ + void enableAllIO_Output(void); +}; + +/** + * @brief I2C address of the ch422g. Just to keep the same with other IO expanders, but it is ignored. + * + */ +#define ESP_IO_EXPANDER_I2C_CH422G_ADDRESS (0x24) diff --git a/test_apps/main/test_ESP_IOExpander.cpp b/test_apps/main/test_ESP_IOExpander.cpp index 5d9d87a..9a1b0fa 100644 --- a/test_apps/main/test_ESP_IOExpander.cpp +++ b/test_apps/main/test_ESP_IOExpander.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -16,19 +16,17 @@ #include "ESP_IOExpander_Library.h" -// Refer to `esp32-hal-gpio.h` -#define INPUT 0x01 -#define OUTPUT 0x03 -#define LOW 0x0 -#define HIGH 0x1 - static const char *TAG = "ESP_IOxpander_test"; +#define CHIP_NAME TCA95xx_8bit #define I2C_HOST (I2C_NUM_0) #define I2C_SDA_PIN (8) #define I2C_SCL_PIN (18) -TEST_CASE("test ESP IO expander for TCA9554", "[tca9554]") +#define _EXAMPLE_CHIP_CLASS(name, ...) ESP_IOExpander_##name(__VA_ARGS__) +#define EXAMPLE_CHIP_CLASS(name, ...) _EXAMPLE_CHIP_CLASS(name, ##__VA_ARGS__) + +TEST_CASE("test ESP IO expander functions", "[io_expander]") { ESP_IOExpander *expander = NULL; const i2c_config_t i2c_config = EXPANDER_I2C_CONFIG_DEFAULT(I2C_SCL_PIN, I2C_SDA_PIN); @@ -36,7 +34,7 @@ TEST_CASE("test ESP IO expander for TCA9554", "[tca9554]") ESP_LOGI(TAG, "Test initialization with external I2C"); TEST_ASSERT_EQUAL(i2c_param_config(I2C_HOST, &i2c_config), ESP_OK); TEST_ASSERT_EQUAL(i2c_driver_install(I2C_HOST, i2c_config.mode, 0, 0, 0), ESP_OK); - expander = new ESP_IOExpander_TCA95xx_8bit(I2C_HOST, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000); + expander = new EXAMPLE_CHIP_CLASS(CHIP_NAME, I2C_HOST, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000); expander->init(); expander->begin(); expander->reset(); @@ -45,7 +43,7 @@ TEST_CASE("test ESP IO expander for TCA9554", "[tca9554]") i2c_driver_delete(I2C_HOST); ESP_LOGI(TAG, "Test initialization with internal I2C (with config)"); - expander = new ESP_IOExpander_TCA95xx_8bit(I2C_HOST, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &i2c_config); + expander = new EXAMPLE_CHIP_CLASS(CHIP_NAME, I2C_HOST, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &i2c_config); expander->init(); expander->begin(); expander->reset(); @@ -53,7 +51,7 @@ TEST_CASE("test ESP IO expander for TCA9554", "[tca9554]") delete expander; ESP_LOGI(TAG, "Test initialization with internal I2C (without config)"); - expander = new ESP_IOExpander_TCA95xx_8bit(I2C_HOST, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, I2C_SCL_PIN, I2C_SDA_PIN); + expander = new EXAMPLE_CHIP_CLASS(CHIP_NAME, I2C_HOST, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, I2C_SCL_PIN, I2C_SDA_PIN); expander->init(); expander->begin(); expander->reset();