diff --git a/.codespellrc b/.codespellrc index 0a3c74d..60bbe06 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,2 +1 @@ [codespell] -skip = ./src/touch/base/esp_lcd_touch_xpt2046.c diff --git a/.github/workflows/arduino_lint.yml b/.github/workflows/arduino_lint.yml index 0a6b9a8..a0f4707 100644 --- a/.github/workflows/arduino_lint.yml +++ b/.github/workflows/arduino_lint.yml @@ -4,6 +4,9 @@ on: workflow_dispatch: pull_request: types: [opened, reopened, synchronize] + push: + branches: + - master jobs: lint: diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 61831e0..e87ca79 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -4,6 +4,9 @@ on: workflow_dispatch: pull_request: types: [opened, reopened, synchronize] + push: + branches: + - master jobs: build: @@ -15,7 +18,7 @@ jobs: container: espressif/idf:${{ matrix.idf_ver }} steps: - uses: actions/checkout@v3 - - name: Build ESP_IOExpander Test Application + - name: Build Test Application env: IDF_TARGET: ${{ matrix.idf_target }} working-directory: test_apps diff --git a/.github/workflows/issue_comment.yml b/.github/workflows/issue_comment.yml deleted file mode 100644 index 3b6fcc1..0000000 --- a/.github/workflows/issue_comment.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Sync issue comments to JIRA - -# This workflow will be triggered when new issue comment is created (including PR comments) -on: issue_comment - -# Limit to single concurrent run for workflows which can create Jira issues. -# Same concurrency group is used in new_issues.yml -concurrency: jira_issues - -jobs: - sync_issue_comments_to_jira: - name: Sync Issue Comments to Jira - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Sync issue comments to JIRA - uses: espressif/github-actions/sync_issues_to_jira@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - JIRA_PASS: ${{ secrets.JIRA_PASS }} - JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} - JIRA_COMPONENT: ${{ secrets.JIRA_COMPONENT }} - JIRA_URL: ${{ secrets.JIRA_URL }} - JIRA_USER: ${{ secrets.JIRA_USER }} diff --git a/.github/workflows/new_issues.yml b/.github/workflows/new_issues.yml deleted file mode 100644 index f0fa402..0000000 --- a/.github/workflows/new_issues.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Sync issues to Jira - -# This workflow will be triggered when a new issue is opened -on: issues - -# Limit to single concurrent run for workflows which can create Jira issues. -# Same concurrency group is used in issue_comment.yml -concurrency: jira_issues - -jobs: - sync_issues_to_jira: - name: Sync issues to Jira - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Sync GitHub issues to Jira project - uses: espressif/github-actions/sync_issues_to_jira@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - JIRA_PASS: ${{ secrets.JIRA_PASS }} - JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} - JIRA_COMPONENT: ${{ secrets.JIRA_COMPONENT }} - JIRA_URL: ${{ secrets.JIRA_URL }} - JIRA_USER: ${{ secrets.JIRA_USER }} diff --git a/.github/workflows/new_prs.yml b/.github/workflows/new_prs.yml deleted file mode 100644 index 01d7fe2..0000000 --- a/.github/workflows/new_prs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Sync remain PRs to Jira - -# This workflow will be triggered every hour, to sync remaining PRs (i.e. PRs with zero comment) to Jira project -# Note that, PRs can also get synced when new PR comment is created -on: - schedule: - - cron: "0 * * * *" - -# Limit to single concurrent run for workflows which can create Jira issues. -# Same concurrency group is used in issue_comment.yml -concurrency: jira_issues - -jobs: - sync_prs_to_jira: - name: Sync PRs to Jira - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Sync PRs to Jira project - uses: espressif/github-actions/sync_issues_to_jira@master - with: - cron_job: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - JIRA_PASS: ${{ secrets.JIRA_PASS }} - JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} - JIRA_COMPONENT: ${{ secrets.JIRA_COMPONENT }} - JIRA_URL: ${{ secrets.JIRA_URL }} - JIRA_USER: ${{ secrets.JIRA_USER }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 7111ea9..cc477db 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -11,4 +11,4 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.3 + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index 45e9b78..333c718 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -15,6 +15,6 @@ jobs: - name: Upload components to component service uses: espressif/upload-components-ci-action@v1 with: - name: "ESP32_IO_Expander" + name: "esp32_io_expander" namespace: "espressif" api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index a7ec7f1..4e14044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # ChangeLog +## v1.1.1 - 2025-07-07 + +### Enhancements: + +* feat(ch422g): support enter/exit sleep + +### Bug Fixes: + +* fix(port): fix discarded qualifiers warning + +## v1.1.0 - 2025-02-07 + +### Enhancements: + +* feat(repo): add legacy header files to maintain compatibility +* feat(repo): update with esp-lib-utils v0.2.* + +## v1.0.1 - 2025-01-23 + +### Enhancements: + +* feat(base): update base class + +### Bug Fixes: + +* fix(repo): compile *.cpp files on MicroPython + +## v1.0.0 - 2024-12-06 + +### Enhancements: + +* feat(repo): refactor with esp-lib-utils +* feat(repo): support micropython + ## v0.1.0 - 2024-11-05 ### Enhancements: diff --git a/CMakeLists.txt b/CMakeLists.txt index 6eefe2d..350d52b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,4 +13,9 @@ idf_component_register( driver ) -target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-missing-field-initializers) +target_compile_options(${COMPONENT_LIB} + PUBLIC + -Wno-missing-field-initializers + PRIVATE + $<$:-std=gnu++17> +) diff --git a/README.md b/README.md index 81ccad3..6ebc43c 100644 --- a/README.md +++ b/README.md @@ -2,62 +2,106 @@ **Latest Arduino Library Version**: [![GitHub Release](https://img.shields.io/github/v/release/esp-arduino-libs/ESP32_IO_Expander)](https://github.com/esp-arduino-libs/ESP32_IO_Expander/releases) -**Latest Espressif Component Version**: [![Espressif Release](https://components.espressif.com/components/espressif/ESP32_IO_Expander/badge.svg)](https://components.espressif.com/components/espressif/ESP32_IO_Expander) +**Latest Espressif Component Version**: [![Espressif Release](https://components.espressif.com/components/espressif/esp32_io_expander/badge.svg)](https://components.espressif.com/components/espressif/esp32_io_expander) # ESP32_IO_Expander -ESP32_IO_Expander is a library designed for driving [IO expander chips](#supported-drivers) using ESP32 SoCs. +## Overview + +`ESP32_IO_Expander` is a library designed for driving [IO expander chips](#supported-drivers) using ESP SoCs. It encapsulates various components from the [Espressif Components Registry](https://components.espressif.com/) and includes the following features: + +* Supports various IO expander chips, such as TCA95xx, HT8574, and CH422G. +* Supports controlling individual IO pin with functions like `pinMode()`, `digitalWrite()`, and `digitalRead()`. +* Supports controlling multiple IO pins simultaneously with functions like `multiPinMode()`, `multiDigitalWrite()`, and `multiDigitalRead()`. +* Compatible with the `Arduino`, `ESP-IDF` and `MicroPython` for compilation. + +## Table of Contents + +- [ESP32\_IO\_Expander](#esp32_io_expander) + - [Overview](#overview) + - [Table of Contents](#table-of-contents) + - [Supported Drivers](#supported-drivers) + - [How to Use](#how-to-use) + - [ESP-IDF Framework](#esp-idf-framework) + - [Dependencies and Versions](#dependencies-and-versions) + - [Adding to Project](#adding-to-project) + - [Configuration Instructions](#configuration-instructions) + - [Arduino IDE](#arduino-ide) + - [Dependencies and Versions](#dependencies-and-versions-1) + - [Installing the Library](#installing-the-library) + - [Configuration Instructions](#configuration-instructions-1) + - [Examples](#examples) + - [Detailed Usage](#detailed-usage) + - [FAQ](#faq) + - [Where is the directory for Arduino libraries?](#where-is-the-directory-for-arduino-libraries) + - [How to Install ESP32\_IO\_Expander in Arduino IDE?](#how-to-install-esp32_io_expander-in-arduino-ide) -ESP32_IO_Expander encapsulates various components from the [Espressif Components Registry](https://components.espressif.com/). It is developed based on [arduino-esp32](https://github.com/espressif/arduino-esp32) or [esp-idf](https://github.com/espressif/esp-idf), and can be easily downloaded and integrated into the Arduino IDE. +## Supported Drivers -## Features +| **Driver** | **Version** | +| ---------------------------------------------------------------------------------------------------- | ----------- | +| [esp_io_expander](https://components.espressif.com/components/espressif/esp_io_expander) | 1.0.1 | +| [TCA95XX_8BIT](https://components.espressif.com/components/espressif/esp_io_expander_tca9554) | 1.0.1 | +| [TCA95XX_16BIT](https://components.espressif.com/components/espressif/esp_io_expander_tca95xx_16bit) | 1.0.0 | +| [HT8574](https://components.espressif.com/components/espressif/esp_io_expander_ht8574) | 1.0.0 | +| CH422G | x | -* Supports various IO expander chips. -* Supports controlling individual IO pin (using pinMode(), digitalRead(), and digitalWrite() functions). -* Supports controlling multiple IO pins simultaneously. -* Supports building on the Arduino IDE and the ESP-IDF framework. +## How to Use -## Supported Drivers +### ESP-IDF Framework -| **Driver** | **Version** | -| ------------------------------------------------------------------------------------------------------ | ----------- | -| [esp_io_expander](https://components.espressif.com/components/espressif/esp_io_expander) | 1.0.1 | -| [TCA95xx (8bit)](https://components.espressif.com/components/espressif/esp_io_expander_tca9554) | 1.0.1 | -| [TCA95xx (16bit)](https://components.espressif.com/components/espressif/esp_io_expander_tca95xx_16bit) | 1.0.0 | -| [HT8574](https://components.espressif.com/components/espressif/esp_io_expander_ht8574) | 1.0.0 | -| CH422G | x | +#### Dependencies and Versions -## Dependencies Version +| **Dependency** | **Version** | +| ------------------------------------------------------------------ | -------------------- | +| [esp-idf](https://github.com/espressif/esp-idf) | >= 5.1 | +| [esp-lib-utils](https://github.com/esp-arduino-libs/esp-lib-utils) | >= 0.1.0 && <= 0.2.0 | -### Arduino +#### Adding to Project -| **Name** | **Version** | -| ----------------------------------------------------------- | ----------- | -| [arduino-esp32](https://github.com/espressif/arduino-esp32) | >= v3.0.0 | +`ESP32_IO_Expander` has been uploaded to the [Espressif Component Registry](https://components.espressif.com/), and users can add it to their project using the `idf.py add-dependency` command, for example: -### ESP-IDF +```bash +idf.py add-dependency "espressif/ESP32_IO_Expander" +``` -| **Name** | **Version** | -| ----------------------------------------------- | ----------- | -| [esp-idf](https://github.com/espressif/esp-idf) | >= v5.1 | +Alternatively, users can create or modify the *idf_component.yml* file in the project directory. For more details, please refer to the [Espressif Documentation - IDF Component Manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). -## How to Use +#### Configuration Instructions + +Since `ESP32_IO_Expander` depends on the `esp-lib-utils` library which implements the `logging`, `checking`, and `memory` functions, to configure it when using ESP-IDF, please refer to the [instructions](https://github.com/esp-arduino-libs/esp-lib-utils#configuration-instructions). + +### Arduino IDE + +#### Dependencies and Versions + +| **Dependency** | **Version** | +| ------------------------------------------------------------------ | -------------------- | +| [arduino-esp32](https://github.com/espressif/arduino-esp32) | >= v3.0.0 | +| [esp-lib-utils](https://github.com/esp-arduino-libs/esp-lib-utils) | >= 0.1.0 && <= 0.2.0 | -For information on how to use the library in the Arduino IDE, please refer to the documentation for [Arduino IDE v1.x.x](https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries) or [Arduino IDE v2.x.x](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library). +#### Installing the Library + +For installation of the `ESP32_IO_Expander` library, refer to [How to Install ESP32_IO_Expander in Arduino IDE](#how-to-install-ESP32_IO_Expander-in-arduino-ide). + +#### Configuration Instructions + +Since `ESP32_IO_Expander` depends on the `esp-lib-utils` library which implements the `logging`, `checking`, and `memory` functions, to configure it when using Arduino, please refer to the [instructions](https://github.com/esp-arduino-libs/esp-lib-utils#configuration-instructions-1). ### Examples -* [Test Functions](examples/TestFunctions): Demonstrates how to use ESP32_IO_Expander and test all functions. -* [Test CH422G](examples/TestCH422G): Demonstrates how to use ESP32_IO_Expander with the CH422G chip. +* [General](examples/general): Demonstrates how to use `ESP32_IO_Expander` and test general functions. +* [CH422G](examples/ch422g): Demonstrates how to use `ESP32_IO_Expander` specifically with the CH422G chip. ### Detailed Usage ```cpp -#include +#include -// Create and initialize an ESP_IOExpander object according to the chip type -ESP_IOExpander *expander = new ESP_IOExpander_TCA95xx_8bit(EXAMPLE_I2C_NUM_0, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, - EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN); +// Create and initialize the IO expander chip, such as TCA95XX_8BIT +esp_expander::Base *expander = new esp_expander::TCA95XX_8BIT( + EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 +); expander->init(); expander->begin(); @@ -75,6 +119,18 @@ expander->multiDigitalWrite(IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, LOW); expander->multiPinMode(IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, INPUT); uint32_t level = expander->multiDigitalRead(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3); -// Release the ESP_IOExpander object +// Release the Base object delete expander; ``` + +## FAQ + +### Where is the directory for Arduino libraries? + +Users can find and modify the directory path for Arduino libraries by selecting `File` > `Preferences` > `Settings` > `Sketchbook location` from the menu bar in the Arduino IDE. + +### How to Install ESP32_IO_Expander in Arduino IDE? + +- **If users want to install online**, navigate to `Sketch` > `Include Library` > `Manage Libraries...` in the Arduino IDE, then search for `ESP32_IO_Expander` and click the `Install` button to install it. +- **If users want to install manually**, download the required version of the `.zip` file from [ESP32_IO_Expander](https://github.com/esp-arduino-libs/ESP32_IO_Expander), then navigate to `Sketch` > `Include Library` > `Add .ZIP Library...` in the Arduino IDE, select the downloaded `.zip` file, and click `Open` to install it. +- Users can also refer to the guides on library installation in the [Arduino IDE v1.x.x](https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries) or [Arduino IDE v2.x.x](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library) documentation. diff --git a/examples/TestFunctions/TestFunctions.ino b/examples/TestFunctions/TestFunctions.ino deleted file mode 100644 index a30791d..0000000 --- a/examples/TestFunctions/TestFunctions.ino +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include - -/** - * Create an ESP_IOExpander object, Currently supports: - * - TCA95xx_8bit - * - TCA95xx_16bit - * - HT8574 - * - CH422G - */ -#define EXAMPLE_CHIP_NAME TCA95xx_8bit -#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 - -#define _EXAMPLE_CHIP_CLASS(name, ...) ESP_IOExpander_##name(__VA_ARGS__) -#define EXAMPLE_CHIP_CLASS(name, ...) _EXAMPLE_CHIP_CLASS(name, ##__VA_ARGS__) - -ESP_IOExpander *expander = NULL; - -void setup() -{ - Serial.begin(115200); - Serial.println("Test begin"); - - expander = new EXAMPLE_CHIP_CLASS(EXAMPLE_CHIP_NAME, - (i2c_port_t)EXAMPLE_I2C_NUM, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, - EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN); - expander->init(); - expander->begin(); - - /* For CH422G */ - // static_cast(expander)->enableOC_PushPull(); - // static_cast(expander)->enableOC_OpenDrain(); - // static_cast(expander)->enableAllIO_Input(); - // static_cast(expander)->enableAllIO_Output(); - - Serial.println("Original status:"); - expander->printStatus(); - - expander->pinMode(0, OUTPUT); - expander->pinMode(1, OUTPUT); - expander->multiPinMode(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, OUTPUT); - - Serial.println("Set pint 0-3 to output mode:"); - expander->printStatus(); - - expander->digitalWrite(0, LOW); - expander->digitalWrite(1, LOW); - expander->multiDigitalWrite(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, LOW); - - Serial.println("Set pint 0-3 to low level:"); - expander->printStatus(); - - expander->pinMode(0, INPUT); - expander->pinMode(1, INPUT); - expander->multiPinMode(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, INPUT); - - Serial.println("Set pint 0-3 to input mode:"); - expander->printStatus(); -} - -int level[4] = {0, 0, 0, 0}; -uint32_t level_temp; -String level_str; - -void loop() -{ - // Read pin 0-3 level - level[0] = expander->digitalRead(0); - level[1] = expander->digitalRead(1); - level_temp = expander->multiDigitalRead(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3); - level[2] = level_temp & IO_EXPANDER_PIN_NUM_2 ? HIGH : LOW; - level[3] = level_temp & IO_EXPANDER_PIN_NUM_3 ? HIGH : LOW; - - Serial.print("Pin level: "); - Serial.print(level[0]); - Serial.print(", "); - Serial.print(level[1]); - Serial.print(", "); - Serial.print(level[2]); - Serial.print(", "); - Serial.println(level[3]); - - delay(1000); -} diff --git a/examples/TestCH422G/TestCH422G.ino b/examples/ch422g/ch422g.ino similarity index 89% rename from examples/TestCH422G/TestCH422G.ino rename to examples/ch422g/ch422g.ino index 016acdf..89709e1 100644 --- a/examples/TestCH422G/TestCH422G.ino +++ b/examples/ch422g/ch422g.ino @@ -1,5 +1,5 @@ /** - * | Supported IO Expanders | CH422G | + * | Supported IO Expanders | CH422G | * | ------------------------- | ------ | * * # CH422G Test Example @@ -34,29 +34,26 @@ 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 +#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) -ESP_IOExpander_CH422G *expander = NULL; +esp_expander::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 = new esp_expander::CH422G(EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN, EXAMPLE_I2C_ADDR); expander->init(); expander->begin(); - /* For CH422G */ Serial.println("Set the OC pin to push-pull output mode."); expander->enableOC_PushPull(); diff --git a/examples/general/general.ino b/examples/general/general.ino new file mode 100644 index 0000000..ccfd757 --- /dev/null +++ b/examples/general/general.ino @@ -0,0 +1,83 @@ +#include +#include + +/* The following default configurations are for the board 'Espressif: ESP32_S3_LCD_EV_BOARD_V1_5, TCA9554' */ +/** + * Choose one of the following chip names: + * - TCA95XX_8BIT + * - TCA95XX_16BIT + * - HT8574 + * - CH422G + */ +#define EXAMPLE_CHIP_NAME TCA95XX_8BIT +#define EXAMPLE_I2C_SDA_PIN (47) +#define EXAMPLE_I2C_SCL_PIN (48) +#define EXAMPLE_I2C_ADDR (ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000) // Change this value according to the + // hardware address + +#define _EXAMPLE_CHIP_CLASS(name, ...) esp_expander::name(__VA_ARGS__) +#define EXAMPLE_CHIP_CLASS(name, ...) _EXAMPLE_CHIP_CLASS(name, ##__VA_ARGS__) + +esp_expander::Base *expander = nullptr; + +void setup() +{ + Serial.begin(115200); + Serial.println("Test begin"); + + /** + * Taking `TCA95XX_8BIT` as an example, the following is the code after macro expansion: + * expander = new esp_expander::TCA95XX_8BIT((48), (47), (0x20)) + */ + expander = new EXAMPLE_CHIP_CLASS( + EXAMPLE_CHIP_NAME, EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 + ); + expander->init(); + expander->begin(); + + /* For CH422G */ + // static_cast(expander)->enableOC_OpenDrain(); + // static_cast(expander)->enableOC_PushPull(); + // static_cast(expander)->enableAllIO_Input(); + // static_cast(expander)->enableAllIO_Output(); + + Serial.println("Original status:"); + expander->printStatus(); + + expander->pinMode(0, OUTPUT); + expander->pinMode(1, OUTPUT); + expander->multiPinMode(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, OUTPUT); + + Serial.println("Set pint 0-3 to output mode:"); + expander->printStatus(); + + expander->digitalWrite(0, LOW); + expander->digitalWrite(1, LOW); + expander->multiDigitalWrite(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, LOW); + + Serial.println("Set pint 0-3 to low level:"); + expander->printStatus(); + + expander->pinMode(0, INPUT); + expander->pinMode(1, INPUT); + expander->multiPinMode(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, INPUT); + + Serial.println("Set pint 0-3 to input mode:"); + expander->printStatus(); +} + +int level[4] = {0, 0, 0, 0}; +uint32_t level_temp; + +void loop() +{ + // Read pin 0-3 level + level[0] = expander->digitalRead(0); + level[1] = expander->digitalRead(1); + level_temp = expander->multiDigitalRead(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3); + level[2] = level_temp & IO_EXPANDER_PIN_NUM_2 ? HIGH : LOW; + level[3] = level_temp & IO_EXPANDER_PIN_NUM_3 ? HIGH : LOW; + Serial.printf("Pin level: %d, %d, %d, %d\n", level[0], level[1], level[2], level[3]); + + delay(1000); +} diff --git a/idf_component.yml b/idf_component.yml index c503891..4ad0e50 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,7 +1,10 @@ -version: "0.1.0" -description: ESP32_IO_Expander is a library designed for driving IO expander chips using ESP32 SoCs +version: "1.1.1" +description: ESP32_IO_Expander is a library designed for driving IO expander chips using ESP SoCs url: https://github.com/esp-arduino-libs/ESP32_IO_Expander repository: https://github.com/esp-arduino-libs/ESP32_IO_Expander.git issues: https://github.com/esp-arduino-libs/ESP32_IO_Expander/issues dependencies: idf: ">=5.1" + espressif/esp-lib-utils: + version: "0.2.*" + public: true diff --git a/library.properties b/library.properties index 3ff246e..4065fba 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,11 @@ name=ESP32_IO_Expander -version=0.1.0 +version=1.1.1 author=espressif maintainer=espressif -sentence=ESP32_IO_Expander is a library designed for driving IO expander chips using ESP32 SoCs +sentence=ESP32_IO_Expander is a library designed for driving IO expander chips using ESP SoCs paragraph=Currently support TCA95xx(8bit), TCA95xx(16bit), HT8574, CH422G category=Other architectures=esp32 url=https://github.com/esp-arduino-libs/ESP32_IO_Expander -includes=ESP_IOExpander_Library.h +includes=esp_io_expander.hpp +depends=esp-lib-utils (>=0.2.0 && <0.3.0) diff --git a/micropython.cmake b/micropython.cmake new file mode 100644 index 0000000..f0caa53 --- /dev/null +++ b/micropython.cmake @@ -0,0 +1,23 @@ +# This file is to be given as "make USER_C_MODULES=..." when building Micropython port + +add_library(usermod_esp_io_expander INTERFACE) + +# Set the source directorya and find all source files. +set(SRC_DIR ${CMAKE_CURRENT_LIST_DIR}/src) +file(GLOB_RECURSE SRCS_C ${SRC_DIR}/*.c) +file(GLOB_RECURSE SRCS_CXX ${SRC_DIR}/*.cpp) + +# Add our source files to the library. +target_sources(usermod_esp_io_expander INTERFACE ${SRCS_C} ${SRCS_CXX}) + +# Add the current directory as an include directory. +target_include_directories(usermod_esp_io_expander INTERFACE ${SRC_DIR}) + +# Add compile options. Since the target is not created by `idf_component_register()`, we need to add the `ESP_PLATFORM` define manually. +target_compile_options(usermod_esp_io_expander + INTERFACE + -Wno-missing-field-initializers -DESP_PLATFORM $<$:-std=gnu++17> +) + +# Link our INTERFACE library to the usermod target. +target_link_libraries(usermod INTERFACE usermod_esp_io_expander) diff --git a/src/ESP_IOExpander.cpp b/src/ESP_IOExpander.cpp deleted file mode 100644 index f2e9a12..0000000 --- a/src/ESP_IOExpander.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "driver/i2c.h" - -#include "private/CheckResult.h" -#include "ESP_IOExpander.h" - -// Check whether it is a valid pin number -#define IS_VALID_PIN(pin_num) (pin_num < IO_COUNT_MAX) - -static const char *TAG = "ESP_IOExpander"; - -ESP_IOExpander::ESP_IOExpander(i2c_port_t id, uint8_t address, const i2c_config_t *config): - handle(NULL), - i2c_id(id), - i2c_config(*config), - i2c_address(address), - i2c_need_init(true) -{ -} - -ESP_IOExpander::ESP_IOExpander(i2c_port_t id, uint8_t address, int scl, int sda): - handle(NULL), - i2c_id(id), - i2c_config((i2c_config_t)EXPANDER_I2C_CONFIG_DEFAULT(scl, sda)), - i2c_address(address), - i2c_need_init(true) -{ -} - -ESP_IOExpander::ESP_IOExpander(i2c_port_t id, uint8_t address): - handle(NULL), - i2c_id(id), - i2c_address(address), - i2c_need_init(false) -{ -} - -void ESP_IOExpander::init(void) -{ - if (i2c_need_init) { - CHECK_ERROR_RETURN(i2c_param_config(i2c_id, &i2c_config)); - CHECK_ERROR_RETURN(i2c_driver_install(i2c_id, i2c_config.mode, 0, 0, 0)); - } -} - -void ESP_IOExpander::reset(void) -{ - CHECK_ERROR_RETURN(esp_io_expander_reset(handle)); -} - -void ESP_IOExpander::del(void) -{ - CHECK_ERROR_RETURN(esp_io_expander_del(handle)); - handle = NULL; -} - -esp_io_expander_handle_t ESP_IOExpander::getHandle(void) -{ - CHECK_NULL_GOTO(handle, err); -err: - return handle; -} - -void ESP_IOExpander::pinMode(uint8_t pin, uint8_t mode) -{ - CHECK_FALSE_RETURN(IS_VALID_PIN(pin)); - CHECK_FALSE_RETURN(mode == INPUT || mode == OUTPUT); - - esp_io_expander_dir_t dir = (mode == INPUT) ? IO_EXPANDER_INPUT : IO_EXPANDER_OUTPUT; - CHECK_ERROR_RETURN(esp_io_expander_set_dir(handle, BIT64(pin), dir)); -} - -void ESP_IOExpander::digitalWrite(uint8_t pin, uint8_t val) -{ - CHECK_FALSE_RETURN(IS_VALID_PIN(pin)); - CHECK_ERROR_RETURN(esp_io_expander_set_level(handle, BIT64(pin), val)); -} - -int ESP_IOExpander::digitalRead(uint8_t pin) -{ - uint32_t level = 0; - CHECK_FALSE_GOTO(IS_VALID_PIN(pin), err); - - CHECK_ERROR_GOTO(esp_io_expander_get_level(handle, BIT64(pin), &level), err); -err: - return (level & BIT64(pin)) ? HIGH : LOW; -} - -void ESP_IOExpander::multiPinMode(uint32_t pin_mask, uint8_t mode) -{ - CHECK_FALSE_RETURN(mode == INPUT || mode == OUTPUT); - - esp_io_expander_dir_t dir = (mode == INPUT) ? IO_EXPANDER_INPUT : IO_EXPANDER_OUTPUT; - CHECK_ERROR_RETURN(esp_io_expander_set_dir(handle, pin_mask, dir)); -} - -void ESP_IOExpander::multiDigitalWrite(uint32_t pin_mask, uint8_t value) -{ - CHECK_ERROR_RETURN(esp_io_expander_set_level(handle, pin_mask, value)); -} - -uint32_t ESP_IOExpander::multiDigitalRead(uint32_t pin_mask) -{ - uint32_t level = 0; - CHECK_ERROR_GOTO(esp_io_expander_get_level(handle, pin_mask, &level), err); -err: - return level; -} - -void ESP_IOExpander::printStatus(void) -{ - CHECK_ERROR_RETURN(esp_io_expander_print_state(handle)); -} diff --git a/src/ESP_IOExpander.h b/src/ESP_IOExpander.h index 8ca025c..7212f7d 100644 --- a/src/ESP_IOExpander.h +++ b/src/ESP_IOExpander.h @@ -1,196 +1,16 @@ + /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#ifndef ESP_IOEXPANDER_H -#define ESP_IOEXPANDER_H - -#include - -#include "driver/i2c.h" - -#include "base/esp_io_expander.h" - -// Refer to `esp32-hal-gpio.h` -#ifndef INPUT -#define INPUT 0x01 -#endif -#ifndef OUTPUT -#define OUTPUT 0x03 -#endif -#ifndef LOW -#define LOW 0x0 -#endif -#ifndef HIGH -#define HIGH 0x1 -#endif - -#define EXPANDER_I2C_CONFIG_DEFAULT(scl, sda) \ - { \ - .mode = I2C_MODE_MASTER, \ - .sda_io_num = sda, \ - .scl_io_num = scl, \ - .sda_pullup_en = GPIO_PULLUP_ENABLE, \ - .scl_pullup_en = GPIO_PULLUP_ENABLE, \ - .master = { \ - .clk_speed = 400000, \ - }, \ - .clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL, \ - } - /** - * @brief ESP_IOExpander class. - * + * This file is just to keep the compatibility with the old version of the library. Please use the file `chip/esp_expander_base.hpp` instead. */ -class 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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - * @param config Pointer to I2C bus configuration - */ - ESP_IOExpander(i2c_port_t id, uint8_t address, const i2c_config_t *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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - * @param scl SCL pin number - * @param sda SDA pin number - */ - ESP_IOExpander(i2c_port_t id, uint8_t address, int scl, int 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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - */ - ESP_IOExpander(i2c_port_t id, uint8_t address); - - /** - * @brief Initialize IO expander - * - * @note This function will initialize I2C bus if it is not initialized. - * - */ - void init(void); - - /** - * @brief Reset IO expander - * - */ - void reset(void); - - /** - * @brief Delete IO expander object - * - */ - void del(void); - - /** - * @brief Get IO expander handle - * - * @return IO expander handle, which can be use to call `esp_io_expander_*` functions - * - */ - esp_io_expander_handle_t getHandle(void); - - /** - * @brief Set pin mode - * - * @note This function is same as Arduino's `pinMode()`. - * - * @param pin Pin number (0-31) - * @param mode Pin mode (INPUT/OUTPUT) - */ - void pinMode(uint8_t pin, uint8_t mode); - - /** - * @brief Set pin level - * - * @note This function is same as Arduino's `digitalWrite()`. - * - * @param pin Pin number (0-31) - * @param val Pin level (HIGH/LOW) - */ - void digitalWrite(uint8_t pin, uint8_t val); - - /** - * @brief Read pin level - * - * @note This function is same as Arduino's `digitalRead()`. - * - * @param pin Pin number (0-31) - * @return Pin level (HIGH/LOW) - */ - int digitalRead(uint8_t pin); - - /** - * @brief Set multiple pin modes - * - * @param pin_mask Pin mask (Bitwise OR of `IO_EXPANDER_PIN_NUM_*`) - * @param mode Mode to set (INPUT/OUTPUT) - */ - void multiPinMode(uint32_t pin_mask, uint8_t mode); - - /** - * @brief Write to multiple pins - * - * @param pin_mask Pin mask (Bitwise OR of `IO_EXPANDER_PIN_NUM_*`) - * @param value Value to write (HIGH/LOW) - */ - void multiDigitalWrite(uint32_t pin_mask, uint8_t value); - - /** - * @brief Read multiple pin levels - * - * @param pin_mask Pin mask (Bitwise OR of `IO_EXPANDER_PIN_NUM_*`) - * @return Pin levels, every bit represents a pin (HIGH/LOW) - */ - uint32_t multiDigitalRead(uint32_t pin_mask); - - /** - * @brief Print IO expander status, include pin index, direction, input level and output level - * - */ - void printStatus(void); - - /** - * @brief Virtual destructor - */ - virtual ~ESP_IOExpander() = default; - - /** - * @brief Begin IO expander - * - */ - virtual void begin(void) = 0; - - -protected: - esp_io_expander_handle_t handle; /*!< IO expander handle */ - // I2C - i2c_port_t i2c_id; /*!< I2C port number */ - i2c_config_t i2c_config; /*!< I2C bus configuration */ - uint8_t i2c_address; /*!< I2C device address */ - bool i2c_need_init; /*!< Whether need to initialize I2C bus */ +#pragma once -}; +#warning "This file is deprecated. Please use the file `chip/esp_expander_base.hpp` instead." -#endif +#include "chip/esp_expander_base.hpp" diff --git a/src/ESP_IOExpander_Library.h b/src/ESP_IOExpander_Library.h index ccbeaf4..fb9fa32 100644 --- a/src/ESP_IOExpander_Library.h +++ b/src/ESP_IOExpander_Library.h @@ -4,14 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -#ifndef ESP_IOEXPANDER_LIBRARY_H -#define ESP_IOEXPANDER_LIBRARY_H +/** + * This file is just to keep the compatibility with the old version of the library. Please use the file `esp_io_expander.hpp` instead. + */ -#include "ESP_IOExpander.h" +#pragma once -#include "chip/TCA95xx_8bit.h" -#include "chip/TCA95xx_16bit.h" -#include "chip/HT8574.h" -#include "chip/CH422G.h" +#warning "This file is deprecated. Please use the file `esp_io_expander.hpp` instead." -#endif +#include "esp_io_expander.hpp" diff --git a/src/base/esp_io_expander.h b/src/base/esp_io_expander.h index da6e121..0f26e86 100644 --- a/src/base/esp_io_expander.h +++ b/src/base/esp_io_expander.h @@ -1,265 +1,16 @@ + /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#pragma once - -#include -#include - -#include "esp_err.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define IO_COUNT_MAX (sizeof(uint32_t) * 8) - -/** - * @brief IO Expander Device Type - * - */ -typedef struct esp_io_expander_s esp_io_expander_t; -typedef esp_io_expander_t *esp_io_expander_handle_t; - -/** - * @brief IO Expander Pin Num - * - */ -typedef enum { - IO_EXPANDER_PIN_NUM_0 = (1ULL << 0), - IO_EXPANDER_PIN_NUM_1 = (1ULL << 1), - IO_EXPANDER_PIN_NUM_2 = (1ULL << 2), - IO_EXPANDER_PIN_NUM_3 = (1ULL << 3), - IO_EXPANDER_PIN_NUM_4 = (1ULL << 4), - IO_EXPANDER_PIN_NUM_5 = (1ULL << 5), - IO_EXPANDER_PIN_NUM_6 = (1ULL << 6), - IO_EXPANDER_PIN_NUM_7 = (1ULL << 7), - IO_EXPANDER_PIN_NUM_8 = (1ULL << 8), - IO_EXPANDER_PIN_NUM_9 = (1ULL << 9), - IO_EXPANDER_PIN_NUM_10 = (1ULL << 10), - IO_EXPANDER_PIN_NUM_11 = (1ULL << 11), - IO_EXPANDER_PIN_NUM_12 = (1ULL << 12), - IO_EXPANDER_PIN_NUM_13 = (1ULL << 13), - IO_EXPANDER_PIN_NUM_14 = (1ULL << 14), - IO_EXPANDER_PIN_NUM_15 = (1ULL << 15), - IO_EXPANDER_PIN_NUM_16 = (1ULL << 16), - IO_EXPANDER_PIN_NUM_17 = (1ULL << 17), - IO_EXPANDER_PIN_NUM_18 = (1ULL << 18), - IO_EXPANDER_PIN_NUM_19 = (1ULL << 19), - IO_EXPANDER_PIN_NUM_20 = (1ULL << 20), - IO_EXPANDER_PIN_NUM_21 = (1ULL << 21), - IO_EXPANDER_PIN_NUM_22 = (1ULL << 22), - IO_EXPANDER_PIN_NUM_23 = (1ULL << 23), - IO_EXPANDER_PIN_NUM_24 = (1ULL << 24), - IO_EXPANDER_PIN_NUM_25 = (1ULL << 25), - IO_EXPANDER_PIN_NUM_26 = (1ULL << 26), - IO_EXPANDER_PIN_NUM_27 = (1ULL << 27), - IO_EXPANDER_PIN_NUM_28 = (1ULL << 28), - IO_EXPANDER_PIN_NUM_29 = (1ULL << 29), - IO_EXPANDER_PIN_NUM_30 = (1ULL << 30), - IO_EXPANDER_PIN_NUM_31 = (1ULL << 31), -} esp_io_expander_pin_num_t; - /** - * @brief IO Expander Pin direction - * + * This file is just to keep the compatibility with the old version of the library. Please use the file `port/esp_io_expander.h` instead. */ -typedef enum { - IO_EXPANDER_INPUT, /*!< Input direction */ - IO_EXPANDER_OUTPUT, /*!< Output direction */ -} esp_io_expander_dir_t; -/** - * @brief IO Expander Configuration Type - * - */ -typedef struct { - uint8_t io_count; /*!< Count of device's IO, must be less or equal than `IO_COUNT_MAX` */ - struct { - uint8_t dir_out_bit_zero : 1; /*!< If the direction of IO is output, the corresponding bit of the direction register is 0 */ - uint8_t input_high_bit_zero : 1; /*!< If the input level of IO is high, the corresponding bit of the input register is 0 */ - uint8_t output_high_bit_zero : 1; /*!< If the output level of IO is high, the corresponding bit of the output register is 0 */ - } flags; - /* Don't support with interrupt mode yet, will be added soon */ -} esp_io_expander_config_t; - -struct esp_io_expander_s { - - /** - * @brief Read value from input register (mandatory) - * - * @note The value represents the input level from IO - * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. - * - * @param handle: IO Expander handle - * @param value: Register's value - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ - esp_err_t (*read_input_reg)(esp_io_expander_handle_t handle, uint32_t *value); - - /** - * @brief Write value to output register (mandatory) - * - * @note The value represents the output level to IO - * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. - * - * @param handle: IO Expander handle - * @param value: Register's value - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ - esp_err_t (*write_output_reg)(esp_io_expander_handle_t handle, uint32_t value); - - /** - * @brief Read value from output register (mandatory) - * - * @note The value represents the expected output level to IO - * @note This function can be implemented by reading the physical output register, or simply by reading a variable that record the output value (more faster) - * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. - * - * @param handle: IO Expander handle - * @param value: Register's value - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ - esp_err_t (*read_output_reg)(esp_io_expander_handle_t handle, uint32_t *value); - - /** - * @brief Write value to direction register (mandatory) - * - * @note The value represents the direction of IO - * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. - * - * @param handle: IO Expander handle - * @param value: Register's value - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ - esp_err_t (*write_direction_reg)(esp_io_expander_handle_t handle, uint32_t value); - - /** - * @brief Read value from directioin register (mandatory) - * - * @note The value represents the expected direction of IO - * @note This function can be implemented by reading the physical direction register, or simply by reading a variable that record the direction value (more faster) - * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. - * - * @param handle: IO Expander handle - * @param value: Register's value - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ - esp_err_t (*read_direction_reg)(esp_io_expander_handle_t handle, uint32_t *value); - - /** - * @brief Reset the device to its initial state (mandatory) - * - * @note This function will reset all device's registers - * - * @param handle: IO Expander handle - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ - esp_err_t (*reset)(esp_io_expander_handle_t handle); - - /** - * @brief Delete device (mandatory) - * - * @param handle: IO Expander handle - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ - esp_err_t (*del)(esp_io_expander_handle_t handle); - - /** - * @brief Configuration structure - */ - esp_io_expander_config_t config; -}; - -/** - * @brief Set the direction of a set of target IOs - * - * @param handle: IO Exapnder handle - * @param pin_num_mask: Bitwise OR of allowed pin num with type of `esp_io_expander_pin_num_t` - * @param direction: IO direction (only support input or output now) - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ -esp_err_t esp_io_expander_set_dir(esp_io_expander_handle_t handle, uint32_t pin_num_mask, esp_io_expander_dir_t direction); - -/** - * @brief Set the output level of a set of target IOs - * - * @note All target IOs must be in output mode first, otherwise this function will return the error `ESP_ERR_INVALID_STATE` - * - * @param handle: IO Exapnder handle - * @param pin_num_mask: Bitwise OR of allowed pin num with type of `esp_io_expander_pin_num_t` - * @param level: 0 - Low level, 1 - High level - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ -esp_err_t esp_io_expander_set_level(esp_io_expander_handle_t handle, uint32_t pin_num_mask, uint8_t level); - -/** - * @brief Get the input level of a set of target IOs - * - * @note This function can be called whenever target IOs are in input mode or output mode - * - * @param handle: IO Exapnder handle - * @param pin_num_mask: Bitwise OR of allowed pin num with type of `esp_io_expander_pin_num_t` - * @param level_mask: Bitwise OR of levels. For each bit, 0 - Low level, 1 - High level - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ -esp_err_t esp_io_expander_get_level(esp_io_expander_handle_t handle, uint32_t pin_num_mask, uint32_t *level_mask); - -/** - * @brief Print the current status of each IO of the device, including direction, input level and output level - * - * @param handle: IO Exapnder handle - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ -esp_err_t esp_io_expander_print_state(esp_io_expander_handle_t handle); - -/** - * @brief Reset the device to its initial status - * - * @note This function will reset all device's registers - * - * @param handle: IO Expander handle - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ -esp_err_t esp_io_expander_reset(esp_io_expander_handle_t handle); +#pragma once -/** - * @brief Delete device - * - * @param handle: IO Expander handle - * - * @return - * - ESP_OK: Success, otherwise returns ESP_ERR_xxx - */ -esp_err_t esp_io_expander_del(esp_io_expander_handle_t handle); +#warning "This file is deprecated. Please use the file `port/esp_io_expander.h` instead." -#ifdef __cplusplus -} -#endif +#include "port/esp_io_expander.h" diff --git a/src/chip/CH422G.h b/src/chip/CH422G.h deleted file mode 100644 index 114d909..0000000 --- a/src/chip/CH422G.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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/src/chip/HT8574.h b/src/chip/HT8574.h deleted file mode 100644 index 00ef52e..0000000 --- a/src/chip/HT8574.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 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" - -class ESP_IOExpander_HT8574: 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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - * @param config Pointer to I2C bus configuration - */ - ESP_IOExpander_HT8574(i2c_port_t id, uint8_t address, const i2c_config_t *config): ESP_IOExpander(id, address, 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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - * @param scl SCL pin number - * @param sda SDA pin number - */ - ESP_IOExpander_HT8574(i2c_port_t id, uint8_t address, int scl, int sda): ESP_IOExpander(id, address, 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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - */ - ESP_IOExpander_HT8574(i2c_port_t id, uint8_t address): ESP_IOExpander(id, address) { }; - - /** - * @brief Destructor - * - * @note This function will delete I2C driver if it is initialized by ESP_IOExpander and delete ESP_IOExpander object. - */ - ~ESP_IOExpander_HT8574() override; - - /** - * @brief Begin IO expander - * - */ - void begin(void) override; -}; - -/** - * @brief I2C address of the ht8574 - * - * The 8-bit address format is as follows: - * - * (Slave Address) - * ┌─────────────────┷─────────────────┐ - * ┌─────┐─────┐─────┐─────┐─────┐─────┐─────┐─────┐ - * | 0 | 1 | 1 | 1 | A2 | A1 | A0 | R/W | - * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ - * └────────┯────────┘ └─────┯──────┘ - * (Fixed) (Hardware Selectable) - * - * And the 7-bit slave address is the most important data for users. - * For example, if a chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0111000b(0x38). - * Then users can use `ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_000` to init it. - */ -#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_000 (0x38) -#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_001 (0x29) -#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_010 (0x2A) -#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_011 (0x2B) -#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_100 (0x2C) diff --git a/src/chip/TCA95xx_8bit.cpp b/src/chip/TCA95xx_8bit.cpp deleted file mode 100644 index 3312d31..0000000 --- a/src/chip/TCA95xx_8bit.cpp +++ /dev/null @@ -1,175 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 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 "TCA95xx_8bit.h" - -/* Timeout of each I2C communication */ -#define I2C_TIMEOUT_MS (10) - -#define IO_COUNT (8) - -/* Register address */ -#define INPUT_REG_ADDR (0x00) -#define OUTPUT_REG_ADDR (0x01) -#define DIRECTION_REG_ADDR (0x03) - -/* Default register value on power-up */ -#define DIR_REG_DEFAULT_VAL (0xff) -#define OUT_REG_DEFAULT_VAL (0xff) - -/** - * @brief Device Structure Type - * - */ -typedef struct { - esp_io_expander_t base; - i2c_port_t i2c_num; - uint32_t i2c_address; - struct { - uint8_t direction; - uint8_t output; - } regs; -} esp_io_expander_tca95xx_8bit_t; - -static const char *TAG = "tca95xx_8bit"; - -static esp_err_t esp_io_expander_new_i2c_tca95xx_8bit(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle); - -ESP_IOExpander_TCA95xx_8bit::~ESP_IOExpander_TCA95xx_8bit() -{ - if (i2c_need_init) { - i2c_driver_delete(i2c_id); - } - if (handle) { - del(); - } -} - -void ESP_IOExpander_TCA95xx_8bit::begin(void) -{ - CHECK_ERROR_RETURN(esp_io_expander_new_i2c_tca95xx_8bit(i2c_id, i2c_address, &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); - -static esp_err_t esp_io_expander_new_i2c_tca95xx_8bit(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_tca95xx_8bit_t *tca = (esp_io_expander_tca95xx_8bit_t *)calloc(1, sizeof(esp_io_expander_tca95xx_8bit_t)); - ESP_RETURN_ON_FALSE(tca, ESP_ERR_NO_MEM, TAG, "Malloc failed"); - - tca->base.config.io_count = IO_COUNT; - tca->base.config.flags.dir_out_bit_zero = 1; - tca->i2c_num = i2c_num; - tca->i2c_address = i2c_address; - tca->base.read_input_reg = read_input_reg; - tca->base.write_output_reg = write_output_reg; - tca->base.read_output_reg = read_output_reg; - tca->base.write_direction_reg = write_direction_reg; - tca->base.read_direction_reg = read_direction_reg; - tca->base.del = del; - tca->base.reset = reset; - - esp_err_t ret = ESP_OK; - /* Reset configuration and register status */ - ESP_GOTO_ON_ERROR(reset(&tca->base), err, TAG, "Reset failed"); - - *handle = &tca->base; - return ESP_OK; -err: - free(tca); - return ret; -} - -static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value) -{ - esp_io_expander_tca95xx_8bit_t *tca = (esp_io_expander_tca95xx_8bit_t *)__containerof(handle, esp_io_expander_tca95xx_8bit_t, base); - - uint8_t temp = 0; - uint8_t reg = INPUT_REG_ADDR; - // *INDENT-OFF* - ESP_RETURN_ON_ERROR( - i2c_master_write_read_device(tca->i2c_num, tca->i2c_address, ®, 1, &temp, 1, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), - TAG, "Read input reg failed"); - // *INDENT-ON* - *value = temp; - return ESP_OK; -} - -static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value) -{ - esp_io_expander_tca95xx_8bit_t *tca = (esp_io_expander_tca95xx_8bit_t *)__containerof(handle, esp_io_expander_tca95xx_8bit_t, base); - value &= 0xff; - - uint8_t data[] = {OUTPUT_REG_ADDR, (uint8_t)value}; - ESP_RETURN_ON_ERROR( - i2c_master_write_to_device(tca->i2c_num, tca->i2c_address, data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), - TAG, "Write output reg failed"); - tca->regs.output = value; - return ESP_OK; -} - -static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value) -{ - esp_io_expander_tca95xx_8bit_t *tca = (esp_io_expander_tca95xx_8bit_t *)__containerof(handle, esp_io_expander_tca95xx_8bit_t, base); - - *value = tca->regs.output; - return ESP_OK; -} - -static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value) -{ - esp_io_expander_tca95xx_8bit_t *tca = (esp_io_expander_tca95xx_8bit_t *)__containerof(handle, esp_io_expander_tca95xx_8bit_t, base); - value &= 0xff; - - uint8_t data[] = {DIRECTION_REG_ADDR, (uint8_t)value}; - ESP_RETURN_ON_ERROR( - i2c_master_write_to_device(tca->i2c_num, tca->i2c_address, data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), - TAG, "Write direction reg failed"); - tca->regs.direction = value; - return ESP_OK; -} - -static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value) -{ - esp_io_expander_tca95xx_8bit_t *tca = (esp_io_expander_tca95xx_8bit_t *)__containerof(handle, esp_io_expander_tca95xx_8bit_t, base); - - *value = tca->regs.direction; - return ESP_OK; -} - -static esp_err_t reset(esp_io_expander_t *handle) -{ - ESP_RETURN_ON_ERROR(write_direction_reg(handle, DIR_REG_DEFAULT_VAL), TAG, "Write dir reg failed"); - ESP_RETURN_ON_ERROR(write_output_reg(handle, OUT_REG_DEFAULT_VAL), TAG, "Write output reg failed"); - return ESP_OK; -} - -static esp_err_t del(esp_io_expander_t *handle) -{ - esp_io_expander_tca95xx_8bit_t *tca = (esp_io_expander_tca95xx_8bit_t *)__containerof(handle, esp_io_expander_tca95xx_8bit_t, base); - - free(tca); - return ESP_OK; -} diff --git a/src/chip/esp_expander_base.cpp b/src/chip/esp_expander_base.cpp new file mode 100644 index 0000000..1ad2262 --- /dev/null +++ b/src/chip/esp_expander_base.cpp @@ -0,0 +1,316 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "inttypes.h" +#include "driver/i2c.h" +#include "esp_expander_utils.h" +#include "esp_expander_base.hpp" + +// Check whether it is a valid pin number +#define IS_VALID_PIN(pin_num) (pin_num < IO_COUNT_MAX) + +namespace esp_expander { + +void Base::Config::convertPartialToFull(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + if (isHostConfigValid() && std::holds_alternative(host.value())) { +#if ESP_UTILS_CONF_LOG_LEVEL == ESP_UTILS_LOG_LEVEL_DEBUG + printHostConfig(); +#endif // ESP_UTILS_LOG_LEVEL_DEBUG + auto &config = std::get(host.value()); + host = HostFullConfig{ + .mode = I2C_MODE_MASTER, + .sda_io_num = config.sda_io_num, + .scl_io_num = config.scl_io_num, + .sda_pullup_en = config.sda_pullup_en, + .scl_pullup_en = config.scl_pullup_en, + .master = { + .clk_speed = static_cast(config.clk_speed), + }, + .clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL, + }; + } + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); +} + +void Base::Config::printHostConfig(void) const +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + if (!isHostConfigValid()) { + ESP_UTILS_LOGI("\n\t{Host config}[skipped]"); + goto end; + } + + if (std::holds_alternative(host.value())) { + auto &config = std::get(host.value()); + ESP_UTILS_LOGI( + "\n\t{Host config}[full]\n" + "\t\t-> [host_id]: %d\n" + "\t\t-> [mode]: %d\n" + "\t\t-> [sda_io_num]: %d\n" + "\t\t-> [scl_io_num]: %d\n" + "\t\t-> [sda_pullup_en]: %d\n" + "\t\t-> [scl_pullup_en]: %d\n" + "\t\t-> [master.clk_speed]: %d\n" + "\t\t-> [clk_flags]: %d" + , static_cast(host_id) + , static_cast(config.mode) + , static_cast(config.sda_io_num) + , static_cast(config.scl_io_num) + , static_cast(config.sda_pullup_en) + , static_cast(config.scl_pullup_en) + , static_cast(config.master.clk_speed) + , static_cast(config.clk_flags) + ); + } else { + auto &config = std::get(host.value()); + ESP_UTILS_LOGI( + "\n\t{Host config}[partial]\n" + "\t\t-> [host_id]: %d\n" + "\t\t-> [sda_io_num]: %d\n" + "\t\t-> [scl_io_num]: %d\n" + "\t\t-> [sda_pullup_en]: %d\n" + "\t\t-> [scl_pullup_en]: %d\n" + "\t\t-> [clk_speed]: %d" + , static_cast(host_id) + , static_cast(config.sda_io_num) + , static_cast(config.scl_io_num) + , static_cast(config.sda_pullup_en) + , static_cast(config.scl_pullup_en) + , static_cast(config.clk_speed) + ); + } + +end: + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); +} + +void Base::Config::printDeviceConfig(void) const +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_LOGI( + "\n\t{Device config}[partial]\n" + "\t\t-> [host_id]: %d\n" + "\t\t-> [address]: 0x%02X" + , static_cast(host_id) + , static_cast(device.address) + ); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); +} + +bool Base::configHostSkipInit(bool skip_init) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(!isOverState(State::INIT), false, "Should be called before `init()`"); + + _is_host_skip_init = skip_init; + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool Base::init(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(!isOverState(State::INIT), false, "Already initialized"); + + // Convert the partial configuration to full configuration + _config.convertPartialToFull(); +#if ESP_UTILS_CONF_LOG_LEVEL == ESP_UTILS_LOG_LEVEL_DEBUG + _config.printHostConfig(); + _config.printDeviceConfig(); +#endif // ESP_UTILS_LOG_LEVEL_DEBUG + + // Initialize the I2C host if not skipped + if (!isHostSkipInit()) { + i2c_port_t host_id = static_cast(getConfig().host_id); + ESP_UTILS_CHECK_ERROR_RETURN( + i2c_param_config(host_id, getHostFullConfig()), false, "I2C param config failed" + ); + ESP_UTILS_CHECK_ERROR_RETURN( + i2c_driver_install(host_id, getHostFullConfig()->mode, 0, 0, 0), false, "I2C driver install failed" + ); + ESP_UTILS_LOGD("Init I2C host(%d)", static_cast(host_id)); + } + + setState(State::INIT); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool Base::reset(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_CHECK_ERROR_RETURN(esp_io_expander_reset(device_handle), false, "Reset failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool Base::del(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + if (device_handle != nullptr) { + ESP_UTILS_CHECK_ERROR_RETURN(esp_io_expander_del(device_handle), false, "Delete failed"); + device_handle = nullptr; + ESP_UTILS_LOGD("Delete @%p", device_handle); + } + + if (isOverState(State::INIT) && !isHostSkipInit()) { + i2c_port_t host_id = static_cast(getConfig().host_id); + ESP_UTILS_CHECK_ERROR_RETURN(i2c_driver_delete(host_id), false, "I2C driver delete failed"); + ESP_UTILS_LOGD("Delete I2C host(%d)", static_cast(host_id)); + } + + setState(State::DEINIT); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool Base::pinMode(uint8_t pin, uint8_t mode) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_LOGD("Param: pin(%d), mode(%d)", pin, mode); + ESP_UTILS_CHECK_FALSE_RETURN(IS_VALID_PIN(pin), false, "Invalid pin"); + ESP_UTILS_CHECK_FALSE_RETURN((mode == INPUT) || (mode == OUTPUT), false, "Invalid mode"); + + esp_io_expander_dir_t dir = (mode == INPUT) ? IO_EXPANDER_INPUT : IO_EXPANDER_OUTPUT; + ESP_UTILS_CHECK_ERROR_RETURN(esp_io_expander_set_dir(device_handle, BIT64(pin), dir), false, "Set dir failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool Base::digitalWrite(uint8_t pin, uint8_t value) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_LOGD("Param: pin(%d), value(%d)", pin, value); + ESP_UTILS_CHECK_FALSE_RETURN(IS_VALID_PIN(pin), false, "Invalid pin"); + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_set_level(device_handle, BIT64(pin), value), false, "Set level failed" + ); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +int Base::digitalRead(uint8_t pin) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_LOGD("Param: pin(%d)", pin); + ESP_UTILS_CHECK_FALSE_RETURN(IS_VALID_PIN(pin), -1, "Invalid pin"); + + uint32_t level = 0; + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_get_level(device_handle, BIT64(pin), &level), -1, "Get level failed" + ); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return (level & BIT64(pin)) ? HIGH : LOW; +} + +bool Base::multiPinMode(uint32_t pin_mask, uint8_t mode) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_LOGD("Param: pin_mask(0x%" PRIx32 "), mode(%d)", pin_mask, mode); + ESP_UTILS_CHECK_FALSE_RETURN((mode == INPUT) || (mode == OUTPUT), false, "Invalid mode"); + + esp_io_expander_dir_t dir = (mode == INPUT) ? IO_EXPANDER_INPUT : IO_EXPANDER_OUTPUT; + ESP_UTILS_CHECK_ERROR_RETURN(esp_io_expander_set_dir(device_handle, pin_mask, dir), false, "Set dir failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool Base::multiDigitalWrite(uint32_t pin_mask, uint8_t value) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_LOGD("Param: pin_mask(0x%" PRIx32 "), value(%d)", pin_mask, value); + + ESP_UTILS_CHECK_ERROR_RETURN(esp_io_expander_set_level(device_handle, pin_mask, value), false, "Set level failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +int64_t Base::multiDigitalRead(uint32_t pin_mask) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_LOGD("Param: pin_mask(0x%" PRIx32 ")", pin_mask); + + uint32_t level = 0; + ESP_UTILS_CHECK_ERROR_RETURN(esp_io_expander_get_level(device_handle, pin_mask, &level), false, "Get level failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return level; +} + +bool Base::printStatus(void) const +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_CHECK_ERROR_RETURN(esp_io_expander_print_state(device_handle), false, "Print state failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +Base::HostFullConfig *Base::getHostFullConfig() +{ + if (std::holds_alternative(_config.host.value())) { + _config.convertPartialToFull(); + } + + return &std::get(_config.host.value()); +} + +} // namespace esp_expander diff --git a/src/chip/esp_expander_base.hpp b/src/chip/esp_expander_base.hpp new file mode 100644 index 0000000..957a620 --- /dev/null +++ b/src/chip/esp_expander_base.hpp @@ -0,0 +1,324 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "driver/i2c.h" +#include "port/esp_io_expander.h" + +// Refer to `esp32-hal-gpio.h` in Arduino +#ifndef INPUT +#define INPUT 0x01 +#endif +#ifndef OUTPUT +#define OUTPUT 0x03 +#endif +#ifndef LOW +#define LOW 0x0 +#endif +#ifndef HIGH +#define HIGH 0x1 +#endif + +namespace esp_expander { + +/** + * @brief The IO expander device class + * + * @note This class is a base class for all types of IO expander chips. Due to it is a virtual class, users cannot use + * it directly + */ +class Base { +public: + /** + * Here are some default values for I2C bus + */ + constexpr static int I2C_HOST_ID_DEFAULT = static_cast(I2C_NUM_0); + constexpr static int I2C_CLK_SPEED_DEFAULT = 400 * 1000; + + using DeviceHandle = esp_io_expander_handle_t; + + struct HostPartialConfig { + int sda_io_num = -1; + int scl_io_num = -1; + bool sda_pullup_en = GPIO_PULLUP_ENABLE; + bool scl_pullup_en = GPIO_PULLUP_ENABLE; + int clk_speed = I2C_CLK_SPEED_DEFAULT; + }; + using HostFullConfig = i2c_config_t; + using HostConfig = std::variant; + + struct DeviceConfig { + uint8_t address = 0; + }; + + /** + * @brief Configuration for Base object + */ + struct Config { + void convertPartialToFull(void); + void printHostConfig(void) const; + void printDeviceConfig(void) const; + + bool isHostConfigValid(void) const + { + return host.has_value(); + } + + int host_id = I2C_HOST_ID_DEFAULT; /*!< I2C host ID */ + std::optional host; /*!< I2C host configuration */ + DeviceConfig device = {}; /*!< I2C device configuration */ + }; + + /** + * @brief The driver state enumeration + */ + enum class State : uint8_t { + DEINIT = 0, + INIT, + BEGIN, + }; + +// *INDENT-OFF* + /** + * @brief Construct a base device. With this function, call `init()` will initialize I2C by using the host + * configuration. + * + * @param[in] scl_io I2C SCL pin number + * @param[in] sda_io I2C SDA pin number + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + Base(int scl_io, int sda_io, uint8_t address): + _config{ + .host_id = I2C_HOST_ID_DEFAULT, + .host = HostPartialConfig{ + .sda_io_num = sda_io, + .scl_io_num = scl_io, + }, + .device = DeviceConfig{ + .address = address + } + } + { + } + + /** + * @brief Construct a base device. With this function, call `init()` will not initialize I2C, and users should + * initialize it manually. + * + * @param[in] host_id I2C host ID. + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + Base(int host_id, uint8_t address): + _config{ + .host_id = host_id, + .device = DeviceConfig{ + .address = address + } + } + { + } +// *INDENT-ON* + + /** + * @brief Construct a base device. + * + * @param[in] config Configuration for the object + */ + Base(const Config &config): _config(config) {} + + /** + * @brief Virtual desutruct object. + * + * @note Here make it virtual so that we can delete the derived object by using the base pointer. + */ + virtual ~Base() = default; + + /** + * @brief Configure whether to skip I2C initialization + * + * @note This function should be called before `init()`. + * + * @param[in] skip_init Whether to skip I2C initialization + * + * @return true if success, otherwise false + */ + bool configHostSkipInit(bool skip_init); + + /** + * @brief Initialize object + * + * @note This function will initialize I2C if needed. + * + * @return true if success, otherwise false + */ + bool init(void); + + /** + * @brief Begin object + * + * @note This function typically calls `esp_io_expander_new_i2c_*()` to create the IO expander handle. + */ + virtual bool begin(void) = 0; + + /** + * @brief Reset object + * + * @return true if success, otherwise false + */ + bool reset(void); + + /** + * @brief Delete object + */ + bool del(void); + + /** + * @brief Set pin mode + * + * @note This function is same as Arduino's `pinMode()`. + * + * @param[in] pin Pin number (0-31) + * @param[in] mode Pin mode (INPUT / OUTPUT) + * + * @return true if success, otherwise false + */ + bool pinMode(uint8_t pin, uint8_t mode); + + /** + * @brief Set pin level + * + * @note This function is same as Arduino's `digitalWrite()`. + * + * @param[in] pin Pin number (0-31) + * @param[in] value Pin level (HIGH / LOW) + * + * @return true if success, otherwise false + */ + bool digitalWrite(uint8_t pin, uint8_t value); + + /** + * @brief Read pin level + * + * @note This function is same as Arduino's `digitalRead()`. + * + * @param[in] pin Pin number (0-31) + * + * @return Pin level. HIGH or LOW if success, otherwise -1 + */ + int digitalRead(uint8_t pin); + + /** + * @brief Set multiple pin modes + * + * @param pin_mask Pin mask (Bitwise OR of `IO_EXPANDER_PIN_NUM_*`) + * @param mode Mode to set (INPUT / OUTPUT) + * + * @return true if success, otherwise false + */ + bool multiPinMode(uint32_t pin_mask, uint8_t mode); + + /** + * @brief Set multiple pins level + * + * @param pin_mask Pin mask (Bitwise OR of `IO_EXPANDER_PIN_NUM_*`) + * @param value Value to write (HIGH / LOW) + * + * @return true if success, otherwise false + */ + bool multiDigitalWrite(uint32_t pin_mask, uint8_t value); + + /** + * @brief Read multiple pin levels + * + * @param pin_mask Pin mask (Bitwise OR of `IO_EXPANDER_PIN_NUM_*`) + * + * @return Pin levels, every bit represents a pin (HIGH / LOW) + */ + int64_t multiDigitalRead(uint32_t pin_mask); + + /** + * @brief Print IO expander status, include pin index, direction, input level and output level + * + * @return Pin levels, every bit represents a pin (HIGH / LOW) + */ + bool printStatus(void) const; + + /** + * @brief Check if the driver has reached or passed the specified state + * + * @param[in] state The state to check against current state + * + * @return true if current state >= given state, otherwise false + */ + bool isOverState(State state) const + { + return (_state >= state); + } + + /** + * @brief Get the IO expander configuration + * + * @return IO expander Configuration + */ + const Config &getConfig(void) const + { + return _config; + } + + /** + * @brief Get low-level handle. Users can use this handle to call low-level functions (esp_io_expander_*()). + * + * @return Device handle if success, otherwise nullptr + */ + DeviceHandle getDeviceHandle(void) const + { + return device_handle; + } + + // TODO: Remove in the next major version + Base(i2c_port_t id, uint8_t address, int scl_io, int sda_io): + Base(scl_io, sda_io, address) + { + _config.host_id = id; + } + [[deprecated("Deprecated and will be removed in the next major version. Please use `getDeviceHandle()` instead.")]] + esp_io_expander_handle_t getHandle(void) const + { + return getDeviceHandle(); + } + +protected: + bool isHostSkipInit(void) const + { + return !_config.isHostConfigValid() || _is_host_skip_init; + } + + void setState(State state) + { + _state = state; + } + + DeviceHandle device_handle = nullptr; + +private: + HostFullConfig *getHostFullConfig(); + + State _state = State::DEINIT; + bool _is_host_skip_init = false; + Config _config = {}; +}; + +} // namespace esp_expander + +/** + * @brief Alias for backward compatibility + * + * @deprecated Use `esp_expander::Base` instead + */ +using ESP_IOExpander [[deprecated("Use `esp_expander::Base` instead.")]] = esp_expander::Base; diff --git a/src/chip/esp_expander_ch422g.cpp b/src/chip/esp_expander_ch422g.cpp new file mode 100644 index 0000000..dcf3223 --- /dev/null +++ b/src/chip/esp_expander_ch422g.cpp @@ -0,0 +1,137 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_expander_utils.h" +#include "port/esp_io_expander_ch422g.h" +#include "esp_expander_ch422g.hpp" + +namespace esp_expander { + +CH422G::~CH422G() +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_EXIT(del(), "Delete failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); +} + +bool CH422G::begin(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(!isOverState(State::BEGIN), false, "Already begun"); + + // Initialize the device if not initialized + if (!isOverState(State::INIT)) { + ESP_UTILS_CHECK_FALSE_RETURN(init(), false, "Init failed"); + } + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_new_i2c_ch422g( + static_cast(getConfig().host_id), getConfig().device.address, &device_handle + ), false, "Create CH422G failed" + ); + ESP_UTILS_LOGD("Create CH422G @%p", device_handle); + + setState(State::BEGIN); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool CH422G::enableOC_OpenDrain(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_ch422g_set_oc_open_drain(device_handle), false, "Set OC open-drain failed" + ); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool CH422G::enableOC_PushPull(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_ch422g_set_oc_push_pull(device_handle), false, "Set OC push-pull failed" + ); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool CH422G::enableAllIO_Input(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_ch422g_set_all_input(device_handle), false, "Set all input failed" + ); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool CH422G::enableAllIO_Output(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_ch422g_set_all_output(device_handle), false, "Set all output failed" + ); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool CH422G::enterSleep(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_ch422g_enter_sleep(device_handle), false, "Enter sleep failed" + ); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +bool CH422G::exitSleep(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(isOverState(State::BEGIN), false, "Not begun"); + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_ch422g_exit_sleep(device_handle), false, "Exit sleep failed" + ); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +} // namespace esp_expander diff --git a/src/chip/esp_expander_ch422g.hpp b/src/chip/esp_expander_ch422g.hpp new file mode 100644 index 0000000..c438bde --- /dev/null +++ b/src/chip/esp_expander_ch422g.hpp @@ -0,0 +1,123 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_expander_base.hpp" + +namespace esp_expander { + +/** + * @brief The CH422G IO expander device class + * + * @note This class is a derived class of `esp_expander::Base`, user can use it directly + * @note Pin map: + * | 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 CH422G: public Base { +public: + /** + * @brief Construct a CH422G device. With this function, call `init()` will initialize I2C by using the host + * configuration. + * + * @param[in] scl_io I2C SCL pin number + * @param[in] sda_io I2C SDA pin number + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + CH422G(int scl_io, int sda_io, uint8_t address): Base(scl_io, sda_io, address) {} + + /** + * @brief Construct a CH422G device. With this function, call `init()` will not initialize I2C, and users should + * initialize it manually. + * + * @param[in] host_id I2C host ID. + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + CH422G(int host_id, uint8_t address): Base(host_id, address) {} + + /** + * @brief Construct a CH422G device. + * + * @param[in] config Configuration for the object + */ + CH422G(const Config &config): Base(config) {} + + /** + * @deprecated Deprecated and will be removed in the next major version. Please use other constructors instead. + */ + [[deprecated("Deprecated and will be removed in the next major version. Please use other constructors instead.")]] + CH422G(i2c_port_t id, uint8_t address, int scl_io, int sda_io): Base(id, address, scl_io, sda_io) {} + + /** + * @brief Desutruct object. This function will call `del()` to delete the object. + */ + ~CH422G() override; + + /** + * @brief Begin object + * + * @note This function typically calls `esp_io_expander_new_i2c_*()` to create the IO expander handle. + * @note This function sets all IO0-7 pins to output high-level mode by default. + * + * @return true if success, otherwise false + */ + bool begin(void) override; + + /** + * @brief Enable OC0-OC3 output open-drain + * + * @return true if success, otherwise false + */ + bool enableOC_OpenDrain(void); + + /** + * @brief Enable OC0-OC3 output push-pull (default mode when power-on) + * + * @return true if success, otherwise false + */ + bool 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. + * + * @return true if success, otherwise false + */ + bool enableAllIO_Input(void); + + /** + * @brief Enable IO0-7 output mode + * + * @return true if success, otherwise false + */ + bool enableAllIO_Output(void); + + /** + * @brief Enter sleep mode + * + * @return true if success, otherwise false + */ + bool enterSleep(void); + + /** + * @brief Exit sleep mode + * + * @return true if success, otherwise false + */ + bool exitSleep(void); +}; + +} // namespace esp_expander + +/** + * @deprecated Deprecated and will be removed in the next major version. Please use `esp_expander::CH422G` instead. + */ +typedef esp_expander::CH422G ESP_IOExpander_CH422G __attribute__((deprecated("Deprecated and will be removed in the next major version. Please use `esp_expander::CH422G` instead."))); diff --git a/src/chip/esp_expander_ht8574.cpp b/src/chip/esp_expander_ht8574.cpp new file mode 100644 index 0000000..837f299 --- /dev/null +++ b/src/chip/esp_expander_ht8574.cpp @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_expander_utils.h" +#include "port/esp_io_expander_ht8574.h" +#include "esp_expander_ht8574.hpp" + +namespace esp_expander { + +HT8574::~HT8574() +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_EXIT(del(), "Delete failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); +} + +bool HT8574::begin(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(!isOverState(State::BEGIN), false, "Already begun"); + + // Initialize the device if not initialized + if (!isOverState(State::INIT)) { + ESP_UTILS_CHECK_FALSE_RETURN(init(), false, "Init failed"); + } + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_new_i2c_ht8574( + static_cast(getConfig().host_id), getConfig().device.address, &device_handle + ), false, "Create HT8574 failed" + ); + ESP_UTILS_LOGD("Create HT8574 @%p", device_handle); + + setState(State::BEGIN); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +} // namespace esp_expander diff --git a/src/chip/esp_expander_ht8574.hpp b/src/chip/esp_expander_ht8574.hpp new file mode 100644 index 0000000..e7b68ce --- /dev/null +++ b/src/chip/esp_expander_ht8574.hpp @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_expander_base.hpp" + +namespace esp_expander { + +/** + * @brief The HT8574 IO expander device class + * + * @note This class is a derived class of `esp_expander::Base`, user can use it directly + */ +class HT8574: public Base { +public: + /** + * @brief Construct a HT8574 device. With this function, call `init()` will initialize I2C by using the host + * configuration. + * + * @param[in] scl_io I2C SCL pin number + * @param[in] sda_io I2C SDA pin number + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + HT8574(int scl_io, int sda_io, uint8_t address): Base(scl_io, sda_io, address) {} + + /** + * @brief Construct a HT8574 device. With this function, call `init()` will not initialize I2C, and users should + * initialize it manually. + * + * @param[in] host_id I2C host ID. + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + HT8574(int host_id, uint8_t address): Base(host_id, address) {} + + /** + * @brief Construct a HT8574 device. + * + * @param[in] config Configuration for the object + */ + HT8574(const Config &config): Base(config) {} + + /** + * @deprecated Deprecated and will be removed in the next major version. Please use other constructors instead. + */ + [[deprecated("Deprecated and will be removed in the next major version. Please use other constructors instead.")]] + HT8574(i2c_port_t id, uint8_t address, int scl_io, int sda_io): Base(id, address, scl_io, sda_io) {} + + /** + * @brief Desutruct object. This function will call `del()` to delete the object. + */ + ~HT8574() override; + + /** + * @brief Begin object + * + * @note This function typically calls `esp_io_expander_new_i2c_*()` to create the IO expander handle. + * @note The driver initialization by default sets CH422G's IO0-7 to output high-level mode. + * + * @return true if success, otherwise false + */ + bool begin(void) override; +}; + +} // namespace esp_expander + +/** + * @deprecated Deprecated and will be removed in the next major version. Please use `esp_expander::HT8574` instead. + */ +typedef esp_expander::HT8574 ESP_IOExpander_HT8574 __attribute__((deprecated("Deprecated and will be removed in the next major version. Please use `esp_expander::HT8574` instead."))); diff --git a/src/chip/esp_expander_tca95xx_16bit.cpp b/src/chip/esp_expander_tca95xx_16bit.cpp new file mode 100644 index 0000000..68f4702 --- /dev/null +++ b/src/chip/esp_expander_tca95xx_16bit.cpp @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_expander_utils.h" +#include "port/esp_io_expander_tca95xx_16bit.h" +#include "esp_expander_tca95xx_16bit.hpp" + +namespace esp_expander { + +TCA95XX_16BIT::~TCA95XX_16BIT() +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_EXIT(del(), "Delete failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); +} + +bool TCA95XX_16BIT::begin(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(!isOverState(State::BEGIN), false, "Already begun"); + + // Initialize the bus if not initialized + if (!isOverState(State::INIT)) { + ESP_UTILS_CHECK_FALSE_RETURN(init(), false, "Init failed"); + } + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_new_i2c_tca95xx_16bit( + static_cast(getConfig().host_id), getConfig().device.address, &device_handle + ), false, "Create TCA95XX_16BIT failed" + ); + ESP_UTILS_LOGD("Create TCA95XX_16BIT @%p", device_handle); + + setState(State::BEGIN); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +} // namespace esp_expander diff --git a/src/chip/esp_expander_tca95xx_16bit.hpp b/src/chip/esp_expander_tca95xx_16bit.hpp new file mode 100644 index 0000000..94f4c35 --- /dev/null +++ b/src/chip/esp_expander_tca95xx_16bit.hpp @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_expander_base.hpp" + +namespace esp_expander { + +/** + * @brief The TCA95XX_16BIT IO expander device class + * + * @note This class is a derived class of `esp_expander::Base`, user can use it directly + */ +class TCA95XX_16BIT: public Base { +public: + /** + * @brief Construct a TCA95XX_16BIT device. With this function, call `init()` will initialize I2C by using the host + * configuration. + * + * @param[in] scl_io I2C SCL pin number + * @param[in] sda_io I2C SDA pin number + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + TCA95XX_16BIT(int scl_io, int sda_io, uint8_t address): Base(scl_io, sda_io, address) {} + + /** + * @brief Construct a TCA95XX_16BIT device. With this function, call `init()` will not initialize I2C, and users + * should initialize it manually. + * + * @param[in] host_id I2C host ID. + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + TCA95XX_16BIT(int host_id, uint8_t address): Base(host_id, address) {} + + /** + * @brief Construct a TCA95XX_16BIT device. + * + * @param[in] config Configuration for the object + */ + TCA95XX_16BIT(const Config &config): Base(config) {} + + /** + * @deprecated Deprecated and will be removed in the next major version. Please use other constructors instead. + */ + [[deprecated("Deprecated and will be removed in the next major version. Please use other constructors instead.")]] + TCA95XX_16BIT(i2c_port_t id, uint8_t address, int scl_io, int sda_io): Base(id, address, scl_io, sda_io) {} + + /** + * @brief Desutruct object. This function will call `del()` to delete the object. + */ + ~TCA95XX_16BIT() override; + + /** + * @brief Begin object + * + * @note This function typically calls `esp_io_expander_new_i2c_*()` to create the IO expander handle. + * @note This function sets all pins to inpurt mode by default. + * + * @return true if success, otherwise false + */ + bool begin(void) override; +}; + +} // namespace esp_expander + +/** + * @deprecated Deprecated and will be removed in the next major version. Please use `esp_expander::TCA95XX_16BIT` instead. + */ +typedef esp_expander::TCA95XX_16BIT ESP_IOExpander_TCA95xx_16bit __attribute__((deprecated("Deprecated and will be removed in the next major version. Please use `esp_expander::TCA95XX_16BIT` instead."))); diff --git a/src/chip/esp_expander_tca95xx_8bit.cpp b/src/chip/esp_expander_tca95xx_8bit.cpp new file mode 100644 index 0000000..60ebdca --- /dev/null +++ b/src/chip/esp_expander_tca95xx_8bit.cpp @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_expander_utils.h" +#include "port/esp_io_expander_tca9554.h" +#include "esp_expander_tca95xx_8bit.hpp" + +namespace esp_expander { + +TCA95XX_8BIT::~TCA95XX_8BIT() +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_EXIT(del(), "Delete failed"); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); +} + +bool TCA95XX_8BIT::begin(void) +{ + ESP_UTILS_LOG_TRACE_ENTER_WITH_THIS(); + + ESP_UTILS_CHECK_FALSE_RETURN(!isOverState(State::BEGIN), false, "Already begun"); + + // Initialize the device if not initialized + if (!isOverState(State::INIT)) { + ESP_UTILS_CHECK_FALSE_RETURN(init(), false, "Init failed"); + } + + ESP_UTILS_CHECK_ERROR_RETURN( + esp_io_expander_new_i2c_tca9554( + static_cast(getConfig().host_id), getConfig().device.address, &device_handle + ), false, "Create TCA95XX_8BIT failed" + ); + ESP_UTILS_LOGD("Create TCA95XX_8BIT @%p", device_handle); + + setState(State::BEGIN); + + ESP_UTILS_LOG_TRACE_EXIT_WITH_THIS(); + + return true; +} + +} // namespace esp_expander diff --git a/src/chip/esp_expander_tca95xx_8bit.hpp b/src/chip/esp_expander_tca95xx_8bit.hpp new file mode 100644 index 0000000..0946a3f --- /dev/null +++ b/src/chip/esp_expander_tca95xx_8bit.hpp @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "esp_expander_base.hpp" + +namespace esp_expander { + +/** + * @brief The TCA95XX_8BIT IO expander device class + * + * @note This class is a derived class of `esp_expander::Base`, user can use it directly + */ +class TCA95XX_8BIT: public Base { +public: + /** + * @brief Construct a TCA95XX_8BIT device. With this function, call `init()` will initialize I2C by using the host + * configuration. + * + * @param[in] scl_io I2C SCL pin number + * @param[in] sda_io I2C SDA pin number + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + TCA95XX_8BIT(int scl_io, int sda_io, uint8_t address): Base(scl_io, sda_io, address) {} + + /** + * @brief Construct a TCA95XX_8BIT device. With this function, call `init()` will not initialize I2C, and users should + * initialize it manually. + * + * @param[in] host_id I2C host ID. + * @param[in] address I2C device 7-bit address. Should be like `ESP_IO_EXPANDER_I2C__ADDRESS`. + */ + TCA95XX_8BIT(int host_id, uint8_t address): Base(host_id, address) {} + + /** + * @brief Construct a TCA95XX_8BIT device. + * + * @param[in] config Configuration for the object + */ + TCA95XX_8BIT(const Config &config): Base(config) {} + + /** + * @deprecated Deprecated and will be removed in the next major version. Please use other constructors instead. + */ + [[deprecated("Deprecated and will be removed in the next major version. Please use other constructors instead.")]] + TCA95XX_8BIT(i2c_port_t id, uint8_t address, int scl_io, int sda_io): Base(id, address, scl_io, sda_io) {} + + /** + * @brief Desutruct object. This function will call `del()` to delete the object. + */ + ~TCA95XX_8BIT() override; + + /** + * @brief Begin object + * + * @note This function typically calls `esp_io_expander_new_i2c_*()` to create the IO expander handle. + * @note This function sets all pins to inpurt mode by default. + * + * @return true if success, otherwise false + */ + bool begin(void) override; +}; + +} // namespace esp_expander + +/** + * @deprecated Deprecated and will be removed in the next major version. Please use `esp_expander::TCA95XX_8BIT` instead. + */ +typedef esp_expander::TCA95XX_8BIT ESP_IOExpander_TCA95xx_8bit __attribute__((deprecated("Deprecated and will be removed in the next major version. Please use `esp_expander::TCA95XX_8BIT` instead."))); diff --git a/src/esp_expander_utils.h b/src/esp_expander_utils.h new file mode 100644 index 0000000..922dc85 --- /dev/null +++ b/src/esp_expander_utils.h @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +// Define the log tag for the current library, should be declared before `esp_lib_utils.hpp` +#define ESP_UTILS_LOG_TAG "Expander" +#include "esp_lib_utils.h" +#include "esp_utils_helpers.h" diff --git a/src/esp_io_expander.hpp b/src/esp_io_expander.hpp new file mode 100644 index 0000000..e9a2062 --- /dev/null +++ b/src/esp_io_expander.hpp @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/* Porting drivers */ +#include "port/esp_io_expander.h" +#include "port/esp_io_expander_ch422g.h" +#include "port/esp_io_expander_ht8574.h" +#include "port/esp_io_expander_tca9554.h" +#include "port/esp_io_expander_tca95xx_16bit.h" + +/* Wrapper classes */ +#include "chip/esp_expander_base.hpp" +#include "chip/esp_expander_ch422g.hpp" +#include "chip/esp_expander_ht8574.hpp" +#include "chip/esp_expander_tca95xx_8bit.hpp" +#include "chip/esp_expander_tca95xx_16bit.hpp" diff --git a/src/base/esp_io_expander.c b/src/port/esp_io_expander.c similarity index 97% rename from src/base/esp_io_expander.c rename to src/port/esp_io_expander.c index 3cf86a8..f442168 100644 --- a/src/base/esp_io_expander.c +++ b/src/port/esp_io_expander.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,19 +9,16 @@ #include "esp_bit_defs.h" #include "esp_check.h" -#ifdef LOG_LOCAL_LEVEL -#undef LOG_LOCAL_LEVEL -#endif -#define LOG_LOCAL_LEVEL ESP_LOG_INFO #include "esp_log.h" #include "esp_io_expander.h" +#include "esp_expander_utils.h" + #define VALID_IO_COUNT(handle) ((handle)->config.io_count <= IO_COUNT_MAX ? (handle)->config.io_count : IO_COUNT_MAX) /** * @brief Register type - * */ typedef enum { REG_INPUT = 0, @@ -134,8 +131,6 @@ esp_err_t esp_io_expander_print_state(esp_io_expander_handle_t handle) { ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid handle"); - esp_log_level_set(TAG, ESP_LOG_INFO); - uint8_t io_count = VALID_IO_COUNT(handle); uint32_t input_reg, output_reg, dir_reg; ESP_RETURN_ON_ERROR(read_reg(handle, REG_INPUT, &input_reg), TAG, "Read input reg failed"); diff --git a/src/port/esp_io_expander.h b/src/port/esp_io_expander.h new file mode 100644 index 0000000..a01f066 --- /dev/null +++ b/src/port/esp_io_expander.h @@ -0,0 +1,266 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP IO expander + */ + +#pragma once + +#include +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define IO_COUNT_MAX (sizeof(uint32_t) * 8) + +/** + * @brief IO Expander Device Type + */ +typedef struct esp_io_expander_s esp_io_expander_t; +typedef esp_io_expander_t *esp_io_expander_handle_t; + +/** + * @brief IO Expander Pin Num + */ +typedef enum { + IO_EXPANDER_PIN_NUM_0 = (1ULL << 0), + IO_EXPANDER_PIN_NUM_1 = (1ULL << 1), + IO_EXPANDER_PIN_NUM_2 = (1ULL << 2), + IO_EXPANDER_PIN_NUM_3 = (1ULL << 3), + IO_EXPANDER_PIN_NUM_4 = (1ULL << 4), + IO_EXPANDER_PIN_NUM_5 = (1ULL << 5), + IO_EXPANDER_PIN_NUM_6 = (1ULL << 6), + IO_EXPANDER_PIN_NUM_7 = (1ULL << 7), + IO_EXPANDER_PIN_NUM_8 = (1ULL << 8), + IO_EXPANDER_PIN_NUM_9 = (1ULL << 9), + IO_EXPANDER_PIN_NUM_10 = (1ULL << 10), + IO_EXPANDER_PIN_NUM_11 = (1ULL << 11), + IO_EXPANDER_PIN_NUM_12 = (1ULL << 12), + IO_EXPANDER_PIN_NUM_13 = (1ULL << 13), + IO_EXPANDER_PIN_NUM_14 = (1ULL << 14), + IO_EXPANDER_PIN_NUM_15 = (1ULL << 15), + IO_EXPANDER_PIN_NUM_16 = (1ULL << 16), + IO_EXPANDER_PIN_NUM_17 = (1ULL << 17), + IO_EXPANDER_PIN_NUM_18 = (1ULL << 18), + IO_EXPANDER_PIN_NUM_19 = (1ULL << 19), + IO_EXPANDER_PIN_NUM_20 = (1ULL << 20), + IO_EXPANDER_PIN_NUM_21 = (1ULL << 21), + IO_EXPANDER_PIN_NUM_22 = (1ULL << 22), + IO_EXPANDER_PIN_NUM_23 = (1ULL << 23), + IO_EXPANDER_PIN_NUM_24 = (1ULL << 24), + IO_EXPANDER_PIN_NUM_25 = (1ULL << 25), + IO_EXPANDER_PIN_NUM_26 = (1ULL << 26), + IO_EXPANDER_PIN_NUM_27 = (1ULL << 27), + IO_EXPANDER_PIN_NUM_28 = (1ULL << 28), + IO_EXPANDER_PIN_NUM_29 = (1ULL << 29), + IO_EXPANDER_PIN_NUM_30 = (1ULL << 30), + IO_EXPANDER_PIN_NUM_31 = (1ULL << 31), +} esp_io_expander_pin_num_t; + +/** + * @brief IO Expander Pin direction + */ +typedef enum { + IO_EXPANDER_INPUT, /*!< Input direction */ + IO_EXPANDER_OUTPUT, /*!< Output direction */ +} esp_io_expander_dir_t; + +/** + * @brief IO Expander Configuration Type + */ +typedef struct { + uint8_t io_count; /*!< Count of device's IO, must be less or equal than `IO_COUNT_MAX` */ + struct { + uint8_t dir_out_bit_zero : 1; /*!< If the direction of IO is output, the corresponding bit of the direction register is 0 */ + uint8_t input_high_bit_zero : 1; /*!< If the input level of IO is high, the corresponding bit of the input register is 0 */ + uint8_t output_high_bit_zero : 1; /*!< If the output level of IO is high, the corresponding bit of the output register is 0 */ + } flags; + /* Don't support with interrupt mode yet, will be added soon */ +} esp_io_expander_config_t; + +struct esp_io_expander_s { + + /** + * @brief Read value from input register (mandatory) + * + * @note The value represents the input level from IO + * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. + * + * @param handle: IO Expander handle + * @param value: Register's value + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*read_input_reg)(esp_io_expander_handle_t handle, uint32_t *value); + + /** + * @brief Write value to output register (mandatory) + * + * @note The value represents the output level to IO + * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. + * + * @param handle: IO Expander handle + * @param value: Register's value + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*write_output_reg)(esp_io_expander_handle_t handle, uint32_t value); + + /** + * @brief Read value from output register (mandatory) + * + * @note The value represents the expected output level to IO + * @note This function can be implemented by reading the physical output register, or simply by reading a variable that record the output value (more faster) + * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. + * + * @param handle: IO Expander handle + * @param value: Register's value + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*read_output_reg)(esp_io_expander_handle_t handle, uint32_t *value); + + /** + * @brief Write value to direction register (mandatory) + * + * @note The value represents the direction of IO + * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. + * + * @param handle: IO Expander handle + * @param value: Register's value + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*write_direction_reg)(esp_io_expander_handle_t handle, uint32_t value); + + /** + * @brief Read value from directioin register (mandatory) + * + * @note The value represents the expected direction of IO + * @note This function can be implemented by reading the physical direction register, or simply by reading a variable that record the direction value (more faster) + * @note If there are multiple input registers in the device, their values should be spliced together in order to form the `value`. + * + * @param handle: IO Expander handle + * @param value: Register's value + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*read_direction_reg)(esp_io_expander_handle_t handle, uint32_t *value); + + /** + * @brief Reset the device to its initial state (mandatory) + * + * @note This function will reset all device's registers + * + * @param handle: IO Expander handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*reset)(esp_io_expander_handle_t handle); + + /** + * @brief Delete device (mandatory) + * + * @param handle: IO Expander handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*del)(esp_io_expander_handle_t handle); + + /** + * @brief Configuration structure + */ + esp_io_expander_config_t config; +}; + +/** + * @brief Set the direction of a set of target IOs + * + * @param handle: IO Exapnder handle + * @param pin_num_mask: Bitwise OR of allowed pin num with type of `esp_io_expander_pin_num_t` + * @param direction: IO direction (only support input or output now) + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_set_dir(esp_io_expander_handle_t handle, uint32_t pin_num_mask, esp_io_expander_dir_t direction); + +/** + * @brief Set the output level of a set of target IOs + * + * @note All target IOs must be in output mode first, otherwise this function will return the error `ESP_ERR_INVALID_STATE` + * + * @param handle: IO Exapnder handle + * @param pin_num_mask: Bitwise OR of allowed pin num with type of `esp_io_expander_pin_num_t` + * @param level: 0 - Low level, 1 - High level + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_set_level(esp_io_expander_handle_t handle, uint32_t pin_num_mask, uint8_t level); + +/** + * @brief Get the input level of a set of target IOs + * + * @note This function can be called whenever target IOs are in input mode or output mode + * + * @param handle: IO Exapnder handle + * @param pin_num_mask: Bitwise OR of allowed pin num with type of `esp_io_expander_pin_num_t` + * @param level_mask: Bitwise OR of levels. For each bit, 0 - Low level, 1 - High level + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_get_level(esp_io_expander_handle_t handle, uint32_t pin_num_mask, uint32_t *level_mask); + +/** + * @brief Print the current status of each IO of the device, including direction, input level and output level + * + * @param handle: IO Exapnder handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_print_state(esp_io_expander_handle_t handle); + +/** + * @brief Reset the device to its initial status + * + * @note This function will reset all device's registers + * + * @param handle: IO Expander handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_reset(esp_io_expander_handle_t handle); + +/** + * @brief Delete device + * + * @param handle: IO Expander handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_del(esp_io_expander_handle_t handle); + +#ifdef __cplusplus +} +#endif diff --git a/src/chip/CH422G.cpp b/src/port/esp_io_expander_ch422g.c similarity index 72% rename from src/chip/CH422G.cpp rename to src/port/esp_io_expander_ch422g.c index 5a9019f..f16fd02 100644 --- a/src/chip/CH422G.cpp +++ b/src/port/esp_io_expander_ch422g.c @@ -13,8 +13,10 @@ #include "esp_check.h" #include "esp_log.h" -#include "../private/CheckResult.h" -#include "CH422G.h" +#include "esp_io_expander.h" +#include "esp_io_expander_ch422g.h" + +#include "esp_expander_utils.h" /* Timeout of each I2C communication */ #define I2C_TIMEOUT_MS (10) @@ -36,17 +38,17 @@ // 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_WR_OC_DEFAULT_VAL (0x0FU) +#define REG_WR_IO_DEFAULT_VAL (0xFFU) #define REG_OUT_DEFAULT_VAL ((REG_WR_OC_DEFAULT_VAL << 8) | REG_WR_IO_DEFAULT_VAL) -#define REG_DIR_DEFAULT_VAL (0xFFFUL) +#define REG_DIR_DEFAULT_VAL (0xFFFU) -#define REG_WR_SET_BIT_IO_OE (1 << 0) -#define REG_WR_SET_BIT_OD_EN (1 << 2) +#define REG_WR_SET_BIT_IO_OE (1U << 0) +#define REG_WR_SET_BIT_OD_EN (1U << 2) +#define REG_WR_SET_BIT_SLEEP (1U << 3) /** * @brief Device Structure Type - * */ typedef struct { esp_io_expander_t base; @@ -61,7 +63,6 @@ typedef struct { 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); @@ -70,102 +71,137 @@ static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *v 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() +esp_err_t esp_io_expander_new_i2c_ch422g(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle) { - if (i2c_need_init) { - i2c_driver_delete(i2c_id); - } - if (handle) { - del(); - } -} + ESP_LOGI(TAG, "version: %d.%d.%d", ESP_IO_EXPANDER_CH422G_VER_MAJOR, ESP_IO_EXPANDER_CH422G_VER_MINOR, + ESP_IO_EXPANDER_CH422G_VER_PATCH); + 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"); -void ESP_IOExpander_CH422G::begin(void) -{ - CHECK_ERROR_RETURN(esp_io_expander_new_i2c_ch422g(i2c_id, i2c_address, &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; } -void ESP_IOExpander_CH422G::enableOC_OpenDrain(void) +esp_err_t esp_io_expander_ch422g_set_oc_open_drain(esp_io_expander_handle_t handle) { 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)) + 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; } -void ESP_IOExpander_CH422G::enableOC_PushPull(void) +esp_err_t esp_io_expander_ch422g_set_oc_push_pull(esp_io_expander_handle_t handle) { 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)) + 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; } -void ESP_IOExpander_CH422G::enableAllIO_Input(void) +esp_err_t esp_io_expander_ch422g_set_all_input(esp_io_expander_handle_t handle) { 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)) + 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; // Delay 1ms to wait for the IO expander to switch to input mode vTaskDelay(pdMS_TO_TICKS(2)); + + return ESP_OK; } -void ESP_IOExpander_CH422G::enableAllIO_Output(void) +esp_err_t esp_io_expander_ch422g_set_all_output(esp_io_expander_handle_t handle) { 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)) + 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; } -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_err_t esp_io_expander_ch422g_enter_sleep(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 *)__containerof(handle, esp_io_expander_ch422g_t, base); + uint8_t data = (uint8_t)(ch422g->regs.wr_set | REG_WR_SET_BIT_SLEEP); - 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"); + // 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; - 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; + return ESP_OK; +} - esp_err_t ret = ESP_OK; - /* Reset configuration and register status */ - ESP_GOTO_ON_ERROR(reset(&ch422g->base), err, TAG, "Reset failed"); +esp_err_t esp_io_expander_ch422g_exit_sleep(esp_io_expander_handle_t handle) +{ + 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_SLEEP); + + // 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; - *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) diff --git a/src/port/esp_io_expander_ch422g.h b/src/port/esp_io_expander_ch422g.h new file mode 100644 index 0000000..a312edf --- /dev/null +++ b/src/port/esp_io_expander_ch422g.h @@ -0,0 +1,57 @@ +/* + * 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_io_expander.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_IO_EXPANDER_CH422G_VER_MAJOR (0) +#define ESP_IO_EXPANDER_CH422G_VER_MINOR (1) +#define ESP_IO_EXPANDER_CH422G_VER_PATCH (0) + +/** + * @brief Create a new ch422g IO expander driver + * + * @note The I2C communication should be initialized before use this function + * + * @param i2c_num: I2C port num + * @param i2c_address: I2C address of chip + * @param handle: IO expander handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_new_i2c_ch422g(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle); + +/** + * @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) + +esp_err_t esp_io_expander_ch422g_set_oc_open_drain(esp_io_expander_handle_t handle); + +esp_err_t esp_io_expander_ch422g_set_oc_push_pull(esp_io_expander_handle_t handle); + +esp_err_t esp_io_expander_ch422g_set_all_input(esp_io_expander_handle_t handle); + +esp_err_t esp_io_expander_ch422g_set_all_output(esp_io_expander_handle_t handle); + +esp_err_t esp_io_expander_ch422g_enter_sleep(esp_io_expander_handle_t handle); + +esp_err_t esp_io_expander_ch422g_exit_sleep(esp_io_expander_handle_t handle); + +#ifdef __cplusplus +} +#endif diff --git a/src/chip/HT8574.cpp b/src/port/esp_io_expander_ht8574.c similarity index 87% rename from src/chip/HT8574.cpp rename to src/port/esp_io_expander_ht8574.c index 9a82d88..840992f 100644 --- a/src/chip/HT8574.cpp +++ b/src/port/esp_io_expander_ht8574.c @@ -13,8 +13,10 @@ #include "esp_check.h" #include "esp_log.h" -#include "../private/CheckResult.h" -#include "HT8574.h" +#include "esp_io_expander.h" +#include "esp_io_expander_ht8574.h" + +#include "esp_expander_utils.h" /* Timeout of each I2C communication */ #define I2C_TIMEOUT_MS (10) @@ -27,7 +29,6 @@ /** * @brief Device Structure Type - * */ typedef struct { esp_io_expander_t base; @@ -41,23 +42,6 @@ typedef struct { static const char *TAG = "ht8574"; -static esp_err_t esp_io_expander_new_i2c_ht8574(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle); - -ESP_IOExpander_HT8574::~ESP_IOExpander_HT8574() -{ - if (i2c_need_init) { - i2c_driver_delete(i2c_id); - } - if (handle) { - del(); - } -} - -void ESP_IOExpander_HT8574::begin(void) -{ - CHECK_ERROR_RETURN(esp_io_expander_new_i2c_ht8574(i2c_id, i2c_address, &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); @@ -66,8 +50,10 @@ static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *v static esp_err_t reset(esp_io_expander_t *handle); static esp_err_t del(esp_io_expander_t *handle); -static esp_err_t esp_io_expander_new_i2c_ht8574(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle) +esp_err_t esp_io_expander_new_i2c_ht8574(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle) { + ESP_LOGI(TAG, "version: %d.%d.%d", ESP_IO_EXPANDER_HT8574_VER_MAJOR, ESP_IO_EXPANDER_HT8574_VER_MINOR, + ESP_IO_EXPANDER_HT8574_VER_PATCH); 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"); @@ -78,7 +64,6 @@ static esp_err_t esp_io_expander_new_i2c_ht8574(i2c_port_t i2c_num, uint32_t i2c ht8574->base.config.flags.dir_out_bit_zero = 1; ht8574->i2c_num = i2c_num; ht8574->i2c_address = i2c_address; - ht8574->regs.output = OUT_REG_DEFAULT_VAL; ht8574->base.read_input_reg = read_input_reg; ht8574->base.write_output_reg = write_output_reg; ht8574->base.read_output_reg = read_output_reg; diff --git a/src/port/esp_io_expander_ht8574.h b/src/port/esp_io_expander_ht8574.h new file mode 100644 index 0000000..316994c --- /dev/null +++ b/src/port/esp_io_expander_ht8574.h @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "driver/i2c.h" +#include "esp_err.h" + +#include "esp_io_expander.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_IO_EXPANDER_HT8574_VER_MAJOR (0) +#define ESP_IO_EXPANDER_HT8574_VER_MINOR (1) +#define ESP_IO_EXPANDER_HT8574_VER_PATCH (0) + +/** + * @brief Create a new ht8574 IO expander driver + * + * @note The I2C communication should be initialized before use this function + * + * @param i2c_num: I2C port num + * @param i2c_address: I2C address of chip + * @param handle: IO expander handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_new_i2c_ht8574(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle); + +/** + * @brief I2C address of the ht8574 + * + * The 8-bit address format is as follows: + * + * (Slave Address) + * ┌─────────────────┷─────────────────┐ + * ┌─────┐─────┐─────┐─────┐─────┐─────┐─────┐─────┐ + * | 0 | 1 | 1 | 1 | A2 | A1 | A0 | R/W | + * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ + * └────────┯────────┘ └─────┯──────┘ + * (Fixed) (Hardware Selectable) + * + * And the 7-bit slave address is the most important data for users. + * For example, if a chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0111000b(0x38). + * Then users can use `ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_000` to init it. + */ +#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_000 (0x38) +#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_001 (0x29) +#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_010 (0x2A) +#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_011 (0x2B) +#define ESP_IO_EXPANDER_I2C_HT8574_ADDRESS_100 (0x2C) + + +#ifdef __cplusplus +} +#endif diff --git a/src/port/esp_io_expander_tca9554.c b/src/port/esp_io_expander_tca9554.c new file mode 100644 index 0000000..ef17872 --- /dev/null +++ b/src/port/esp_io_expander_tca9554.c @@ -0,0 +1,160 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 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 "esp_io_expander.h" +#include "esp_io_expander_tca9554.h" + +#include "esp_expander_utils.h" + +/* Timeout of each I2C communication */ +#define I2C_TIMEOUT_MS (10) + +#define IO_COUNT (8) + +/* Register address */ +#define INPUT_REG_ADDR (0x00) +#define OUTPUT_REG_ADDR (0x01) +#define DIRECTION_REG_ADDR (0x03) + +/* Default register value on power-up */ +#define DIR_REG_DEFAULT_VAL (0xff) +#define OUT_REG_DEFAULT_VAL (0xff) + +/** + * @brief Device Structure Type + */ +typedef struct { + esp_io_expander_t base; + i2c_port_t i2c_num; + uint32_t i2c_address; + struct { + uint8_t direction; + uint8_t output; + } regs; +} esp_io_expander_tca9554_t; + +static const char *TAG = "tca9554"; + +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_err_t esp_io_expander_new_i2c_tca9554(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle) +{ + ESP_LOGI(TAG, "version: %d.%d.%d", ESP_IO_EXPANDER_TCA9554_VER_MAJOR, ESP_IO_EXPANDER_TCA9554_VER_MINOR, + ESP_IO_EXPANDER_TCA9554_VER_PATCH); + 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_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)calloc(1, sizeof(esp_io_expander_tca9554_t)); + ESP_RETURN_ON_FALSE(tca9554, ESP_ERR_NO_MEM, TAG, "Malloc failed"); + + tca9554->base.config.io_count = IO_COUNT; + tca9554->base.config.flags.dir_out_bit_zero = 1; + tca9554->i2c_num = i2c_num; + tca9554->i2c_address = i2c_address; + tca9554->base.read_input_reg = read_input_reg; + tca9554->base.write_output_reg = write_output_reg; + tca9554->base.read_output_reg = read_output_reg; + tca9554->base.write_direction_reg = write_direction_reg; + tca9554->base.read_direction_reg = read_direction_reg; + tca9554->base.del = del; + tca9554->base.reset = reset; + + esp_err_t ret = ESP_OK; + /* Reset configuration and register status */ + ESP_GOTO_ON_ERROR(reset(&tca9554->base), err, TAG, "Reset failed"); + + *handle = &tca9554->base; + return ESP_OK; +err: + free(tca9554); + return ret; +} + +static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + + uint8_t temp = 0; + // *INDENT-OFF* + ESP_RETURN_ON_ERROR( + i2c_master_write_read_device(tca9554->i2c_num, tca9554->i2c_address, (uint8_t[]){INPUT_REG_ADDR}, 1, &temp, 1, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Read input reg failed"); + // *INDENT-ON* + *value = temp; + return ESP_OK; +} + +static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + value &= 0xff; + + uint8_t data[] = {OUTPUT_REG_ADDR, value}; + ESP_RETURN_ON_ERROR( + i2c_master_write_to_device(tca9554->i2c_num, tca9554->i2c_address, data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Write output reg failed"); + tca9554->regs.output = value; + return ESP_OK; +} + +static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + + *value = tca9554->regs.output; + return ESP_OK; +} + +static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + value &= 0xff; + + uint8_t data[] = {DIRECTION_REG_ADDR, value}; + ESP_RETURN_ON_ERROR( + i2c_master_write_to_device(tca9554->i2c_num, tca9554->i2c_address, data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Write direction reg failed"); + tca9554->regs.direction = value; + return ESP_OK; +} + +static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + + *value = tca9554->regs.direction; + return ESP_OK; +} + +static esp_err_t reset(esp_io_expander_t *handle) +{ + ESP_RETURN_ON_ERROR(write_direction_reg(handle, DIR_REG_DEFAULT_VAL), TAG, "Write dir reg failed"); + ESP_RETURN_ON_ERROR(write_output_reg(handle, OUT_REG_DEFAULT_VAL), TAG, "Write output reg failed"); + return ESP_OK; +} + +static esp_err_t del(esp_io_expander_t *handle) +{ + esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base); + + free(tca9554); + return ESP_OK; +} diff --git a/src/chip/TCA95xx_8bit.h b/src/port/esp_io_expander_tca9554.h similarity index 60% rename from src/chip/TCA95xx_8bit.h rename to src/port/esp_io_expander_tca9554.h index bab368a..b3f39ed 100644 --- a/src/chip/TCA95xx_8bit.h +++ b/src/port/esp_io_expander_tca9554.h @@ -1,9 +1,14 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ +/** + * @file + * @brief ESP IO expander: TCA9554 + */ + #pragma once #include @@ -11,59 +16,29 @@ #include "driver/i2c.h" #include "esp_err.h" -#include "../ESP_IOExpander.h" +#include "esp_io_expander.h" -class ESP_IOExpander_TCA95xx_8bit: 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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - * @param config Pointer to I2C bus configuration - */ - ESP_IOExpander_TCA95xx_8bit(i2c_port_t id, uint8_t address, const i2c_config_t *config): ESP_IOExpander(id, address, config) { }; +#ifdef __cplusplus +extern "C" { +#endif - /** - * @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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - * @param scl SCL pin number - * @param sda SDA pin number - */ - ESP_IOExpander_TCA95xx_8bit(i2c_port_t id, uint8_t address, int scl, int sda): ESP_IOExpander(id, address, scl, sda) { }; +#define ESP_IO_EXPANDER_TCA9554_VER_MAJOR (1) +#define ESP_IO_EXPANDER_TCA9554_VER_MINOR (0) +#define ESP_IO_EXPANDER_TCA9554_VER_PATCH (1) - /** - * @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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - */ - ESP_IOExpander_TCA95xx_8bit(i2c_port_t id, uint8_t address): ESP_IOExpander(id, address) { }; - - /** - * @brief Destructor - * - * @note This function will delete I2C driver if it is initialized by ESP_IOExpander and delete ESP_IOExpander object. - */ - ~ESP_IOExpander_TCA95xx_8bit() override; - - /** - * @brief Begin IO expander - * - */ - void begin(void) override; -}; +/** + * @brief Create a new TCA9554 IO expander driver + * + * @note The I2C communication should be initialized before use this function + * + * @param i2c_num: I2C port num + * @param i2c_address: I2C address of chip + * @param handle: IO expander handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_new_i2c_tca9554(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle); /** * @brief I2C address of the TCA9554 @@ -117,3 +92,7 @@ class ESP_IOExpander_TCA95xx_8bit: public ESP_IOExpander { #define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_101 (0x3D) #define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_110 (0x3E) #define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_111 (0x3F) + +#ifdef __cplusplus +} +#endif diff --git a/src/chip/TCA95xx_16bit.cpp b/src/port/esp_io_expander_tca95xx_16bit.c similarity index 81% rename from src/chip/TCA95xx_16bit.cpp rename to src/port/esp_io_expander_tca95xx_16bit.c index 7c73d61..4062d31 100644 --- a/src/chip/TCA95xx_16bit.cpp +++ b/src/port/esp_io_expander_tca95xx_16bit.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,8 +13,10 @@ #include "esp_check.h" #include "esp_log.h" -#include "../private/CheckResult.h" -#include "TCA95xx_16bit.h" +#include "esp_io_expander.h" +#include "esp_io_expander_tca95xx_16bit.h" + +#include "esp_expander_utils.h" /* Timeout of each I2C communication */ #define I2C_TIMEOUT_MS (10) @@ -32,36 +34,18 @@ /** * @brief Device Structure Type - * */ typedef struct { esp_io_expander_t base; i2c_port_t i2c_num; uint32_t i2c_address; struct { - uint8_t direction; - uint8_t output; + uint16_t direction; + uint16_t output; } regs; } esp_io_expander_tca95xx_16bit_t; -static const char *TAG = "tca95xx_16bit"; - -ESP_IOExpander_TCA95xx_16bit::~ESP_IOExpander_TCA95xx_16bit() -{ - if (i2c_need_init) { - i2c_driver_delete(i2c_id); - } - if (handle) { - del(); - } -} - -static esp_err_t esp_io_expander_new_i2c_tca95xx_16bit(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle); - -void ESP_IOExpander_TCA95xx_16bit::begin(void) -{ - CHECK_ERROR_RETURN(esp_io_expander_new_i2c_tca95xx_16bit(i2c_id, i2c_address, &handle)); -} +static const char *TAG = "tca95xx_16"; 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); @@ -71,8 +55,10 @@ static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *v static esp_err_t reset(esp_io_expander_t *handle); static esp_err_t del(esp_io_expander_t *handle); -static esp_err_t esp_io_expander_new_i2c_tca95xx_16bit(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle) +esp_err_t esp_io_expander_new_i2c_tca95xx_16bit(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle) { + ESP_LOGI(TAG, "version: %d.%d.%d", ESP_IO_EXPANDER_TCA95XX_16BIT_VER_MAJOR, ESP_IO_EXPANDER_TCA95XX_16BIT_VER_MINOR, + ESP_IO_EXPANDER_TCA95XX_16BIT_VER_PATCH); 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"); @@ -107,13 +93,12 @@ static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value esp_io_expander_tca95xx_16bit_t *tca = (esp_io_expander_tca95xx_16bit_t *)__containerof(handle, esp_io_expander_tca95xx_16bit_t, base); uint8_t temp[2] = {0, 0}; - uint8_t reg = INPUT_REG_ADDR; // *INDENT-OFF* ESP_RETURN_ON_ERROR( - i2c_master_write_read_device(tca->i2c_num, tca->i2c_address, ®, 1, (uint8_t*)&temp, 2, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + i2c_master_write_read_device(tca->i2c_num, tca->i2c_address, (uint8_t[]){INPUT_REG_ADDR}, 1, (uint8_t*)&temp, 2, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), TAG, "Read input reg failed"); // *INDENT-ON* - *value = (((uint32_t)temp[0]) << 8) | (temp[1]); + *value = (((uint32_t)temp[1]) << 8) | (temp[0]); return ESP_OK; } @@ -122,7 +107,7 @@ static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t valu esp_io_expander_tca95xx_16bit_t *tca = (esp_io_expander_tca95xx_16bit_t *)__containerof(handle, esp_io_expander_tca95xx_16bit_t, base); value &= 0xffff; - uint8_t data[] = {OUTPUT_REG_ADDR, (uint8_t)(value >> 8), (uint8_t)(value & 0xff)}; + uint8_t data[] = {OUTPUT_REG_ADDR, value & 0xff, value >> 8}; ESP_RETURN_ON_ERROR( i2c_master_write_to_device(tca->i2c_num, tca->i2c_address, data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), TAG, "Write output reg failed"); @@ -143,7 +128,7 @@ static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t v esp_io_expander_tca95xx_16bit_t *tca = (esp_io_expander_tca95xx_16bit_t *)__containerof(handle, esp_io_expander_tca95xx_16bit_t, base); value &= 0xffff; - uint8_t data[] = {DIRECTION_REG_ADDR, (uint8_t)(value >> 8), (uint8_t)(value & 0xff)}; + uint8_t data[] = {DIRECTION_REG_ADDR, value & 0xff, value >> 8}; ESP_RETURN_ON_ERROR( i2c_master_write_to_device(tca->i2c_num, tca->i2c_address, data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), TAG, "Write direction reg failed"); diff --git a/src/chip/TCA95xx_16bit.h b/src/port/esp_io_expander_tca95xx_16bit.h similarity index 56% rename from src/chip/TCA95xx_16bit.h rename to src/port/esp_io_expander_tca95xx_16bit.h index e3e110d..80cb54f 100644 --- a/src/chip/TCA95xx_16bit.h +++ b/src/port/esp_io_expander_tca95xx_16bit.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,59 +11,29 @@ #include "driver/i2c.h" #include "esp_err.h" -#include "../ESP_IOExpander.h" +#include "esp_io_expander.h" -class ESP_IOExpander_TCA95xx_16bit: 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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - * @param config Pointer to I2C bus configuration - */ - ESP_IOExpander_TCA95xx_16bit(i2c_port_t id, uint8_t address, const i2c_config_t *config): ESP_IOExpander(id, address, config) { }; +#ifdef __cplusplus +extern "C" { +#endif - /** - * @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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - * @param scl SCL pin number - * @param sda SDA pin number - */ - ESP_IOExpander_TCA95xx_16bit(i2c_port_t id, uint8_t address, int scl, int sda): ESP_IOExpander(id, address, scl, sda) { }; +#define ESP_IO_EXPANDER_TCA95XX_16BIT_VER_MAJOR (1) +#define ESP_IO_EXPANDER_TCA95XX_16BIT_VER_MINOR (0) +#define ESP_IO_EXPANDER_TCA95XX_16BIT_VER_PATCH (0) - /** - * @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. Should be like `ESP_IO_EXPANDER_I2C_*`. - * Can be found in the header file of each IO expander.h. - */ - ESP_IOExpander_TCA95xx_16bit(i2c_port_t id, uint8_t address): ESP_IOExpander(id, address) { }; - - /** - * @brief Destructor - * - * @note This function will delete I2C driver if it is initialized by ESP_IOExpander and delete ESP_IOExpander object. - */ - ~ESP_IOExpander_TCA95xx_16bit() override; - - /** - * @brief Begin IO expander - * - */ - void begin(void) override; -}; +/** + * @brief Create a new TCA95XX_16BIT IO expander driver + * + * @note The I2C communication should be initialized before use this function + * + * @param i2c_num: I2C port num + * @param i2c_address: I2C address of chip (\see esp_io_expander_tca_95xx_16bit_address) + * @param handle: IO expander handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_new_i2c_tca95xx_16bit(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle); /** * @brief I2C address of the TCA9539 or TCA9555 @@ -106,3 +76,7 @@ enum esp_io_expander_tca_95xx_16bit_address { ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_110 = 0b0100110, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_111 = 0b0100111, }; + +#ifdef __cplusplus +} +#endif diff --git a/src/private/CheckResult.cpp b/src/private/CheckResult.cpp deleted file mode 100644 index da17b7f..0000000 --- a/src/private/CheckResult.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include - -namespace esp_io_expander { - -const char *path_to_file_name(const char *path) -{ - size_t i = 0; - size_t pos = 0; - char *p = (char *)path; - while (*p) { - i++; - if (*p == '/' || *p == '\\') { - pos = i; - } - p++; - } - return path + pos; -} - -} diff --git a/src/private/CheckResult.h b/src/private/CheckResult.h deleted file mode 100644 index 18183c6..0000000 --- a/src/private/CheckResult.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef CHECKRESULT_H -#define CHECKRESULT_H - -#include -#include -#include - -#include "esp_check.h" -#include "esp_log.h" - -#define ERROR_CHECK_LOG_FORMAT(format) "[%s:%u] %s(): " format, esp_io_expander::path_to_file_name(__FILE__), __LINE__, __FUNCTION__ -#define ERROR_CHECK_LOGE(tag, format, ...) ESP_LOGE(tag, ERROR_CHECK_LOG_FORMAT(format), ##__VA_ARGS__) - -#define CHECK_ERROR_RETURN(x) do { \ - esp_err_t err_rc_ = (x); \ - if (unlikely(err_rc_ != ESP_OK)) { \ - ERROR_CHECK_LOGE(TAG, "Check error %d (%s)", err_rc_, esp_err_to_name(err_rc_)); \ - return; \ - } \ - } while(0) - -#define CHECK_ERROR_GOTO(x, goto_tag) do { \ - esp_err_t err_rc_ = (x); \ - if (unlikely(err_rc_ != ESP_OK)) { \ - ERROR_CHECK_LOGE(TAG, "Check error %d (%s)", err_rc_, esp_err_to_name(err_rc_)); \ - goto goto_tag; \ - } \ - } while(0) - -#define CHECK_NULL_RETURN(x) do { \ - if ((x) == NULL) { \ - ERROR_CHECK_LOGE(TAG, "Check NULL"); \ - return; \ - } \ - } while(0) - -#define CHECK_NULL_GOTO(x, goto_tag) do { \ - if ((x) == NULL) { \ - ERROR_CHECK_LOGE(TAG, "Check NULL"); \ - goto goto_tag; \ - } \ - } while(0) - -#define CHECK_FALSE_RETURN(x) do { \ - if (unlikely((x) == false)) { \ - ERROR_CHECK_LOGE(TAG, "Check false"); \ - return; \ - } \ - } while(0) - -#define CHECK_FALSE_GOTO(x, goto_tag) do { \ - if (unlikely((x) == false)) { \ - ERROR_CHECK_LOGE(TAG, "Check false"); \ - goto goto_tag; \ - } \ - } while(0) - -namespace esp_io_expander { -const char *path_to_file_name(const char *path); -} - -#endif diff --git a/test_apps/CMakeLists.txt b/test_apps/CMakeLists.txt index 6850cc5..55de4fe 100644 --- a/test_apps/CMakeLists.txt +++ b/test_apps/CMakeLists.txt @@ -1,5 +1,6 @@ # The following lines of boilerplate have to be in your project's CMakeLists # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) +set(component main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(io_expander_test) diff --git a/test_apps/main/CMakeLists.txt b/test_apps/main/CMakeLists.txt index d833001..4bf02cc 100644 --- a/test_apps/main/CMakeLists.txt +++ b/test_apps/main/CMakeLists.txt @@ -1,3 +1,4 @@ -idf_component_register(SRCS "test_app_main.cpp") - -target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-missing-field-initializers) +idf_component_register( + SRCS "test_app_main.cpp" "test_chip_general.cpp" + WHOLE_ARCHIVE +) diff --git a/test_apps/main/idf_component.yml b/test_apps/main/idf_component.yml index c6e36a8..6af3a2e 100644 --- a/test_apps/main/idf_component.yml +++ b/test_apps/main/idf_component.yml @@ -2,8 +2,6 @@ dependencies: test_utils: path: ${IDF_PATH}/tools/unit-test-app/components/test_utils - test_driver_utils: - path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils ESP32_IO_Expander: version: "*" override_path: "../../../ESP32_IO_Expander" diff --git a/test_apps/main/test_app_main.cpp b/test_apps/main/test_app_main.cpp index 9a1b0fa..ff6d33f 100644 --- a/test_apps/main/test_app_main.cpp +++ b/test_apps/main/test_app_main.cpp @@ -4,108 +4,36 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include - #include "driver/i2c.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_heap_caps.h" #include "esp_log.h" #include "unity.h" -#include "unity_test_runner.h" - -#include "ESP_IOExpander_Library.h" - -static const char *TAG = "ESP_IOxpander_test"; +#include "unity_test_utils.h" -#define CHIP_NAME TCA95xx_8bit -#define I2C_HOST (I2C_NUM_0) -#define I2C_SDA_PIN (8) -#define I2C_SCL_PIN (18) +#define TEST_MEMORY_LEAK_THRESHOLD (400) -#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]") +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +void setUp(void) { - ESP_IOExpander *expander = NULL; - const i2c_config_t i2c_config = EXPANDER_I2C_CONFIG_DEFAULT(I2C_SCL_PIN, I2C_SDA_PIN); - - 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 EXAMPLE_CHIP_CLASS(CHIP_NAME, I2C_HOST, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000); - expander->init(); - expander->begin(); - expander->reset(); - expander->del(); - delete expander; - i2c_driver_delete(I2C_HOST); - - ESP_LOGI(TAG, "Test initialization with internal I2C (with 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(); - expander->del(); - delete expander; - - ESP_LOGI(TAG, "Test initialization with internal I2C (without config)"); - 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(); - - ESP_LOGI(TAG, "Test input/output functions"); - ESP_LOGI(TAG, "Original status:"); - expander->printStatus(); - - expander->pinMode(0, OUTPUT); - expander->pinMode(1, OUTPUT); - expander->multiPinMode(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, OUTPUT); - - ESP_LOGI(TAG, "Set pint 0-3 to output mode:"); - expander->printStatus(); - - expander->digitalWrite(0, LOW); - expander->digitalWrite(1, LOW); - expander->multiDigitalWrite(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, LOW); - - ESP_LOGI(TAG, "Set pint 0-3 to low level:"); - expander->printStatus(); - - expander->pinMode(0, INPUT); - expander->pinMode(1, INPUT); - expander->multiPinMode(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, INPUT); - - ESP_LOGI(TAG, "Set pint 0-3 to input mode:"); - expander->printStatus(); - - int level[4] = {0, 0, 0, 0}; - uint32_t level_temp; - - // Read pin 0-3 level - level[0] = expander->digitalRead(0); - level[1] = expander->digitalRead(1); - level_temp = expander->multiDigitalRead(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3); - level[2] = level_temp & IO_EXPANDER_PIN_NUM_2 ? HIGH : LOW; - level[3] = level_temp & IO_EXPANDER_PIN_NUM_3 ? HIGH : LOW; - ESP_LOGI(TAG, "Pin 0-3 level: %d %d %d %d", level[0], level[1], level[2], level[3]); - - delete expander; + unity_utils_record_free_mem(); } -// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-300) - +void tearDown(void) +{ + esp_reent_cleanup(); //clean up some of the newlib's lazy allocations + unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); +} +#else static size_t before_free_8bit; static size_t before_free_32bit; static void check_leak(size_t before_free, size_t after_free, const char *type) { - ssize_t delta = after_free - before_free; + ssize_t delta = before_free - after_free; printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); - TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); + TEST_ASSERT_MESSAGE(delta < TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); } void setUp(void) @@ -121,6 +49,7 @@ void tearDown(void) check_leak(before_free_8bit, after_free_8bit, "8BIT"); check_leak(before_free_32bit, after_free_32bit, "32BIT"); } +#endif extern "C" void app_main(void) { diff --git a/test_apps/main/test_chip_general.cpp b/test_apps/main/test_chip_general.cpp new file mode 100644 index 0000000..007d627 --- /dev/null +++ b/test_apps/main/test_chip_general.cpp @@ -0,0 +1,175 @@ +/* + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "driver/i2c.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_io_expander.hpp" + +using namespace esp_expander; + +static const char *TAG = "general_test"; + +/* The following default configurations are for the board 'Espressif: ESP32_S3_LCD_EV_BOARD_V1_5, TCA9554' */ +#define TEST_HOST_ID (I2C_NUM_0) +#define TEST_HOST_I2C_SCL_PIN (48) +#define TEST_HOST_I2C_SDA_PIN (47) +#define TEST_DEVICE_ADDRESS (ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000) + +#define HOST_CONFIG_DEFAULT(scl, sda) \ + { \ + .mode = I2C_MODE_MASTER, \ + .sda_io_num = (sda), \ + .scl_io_num = (scl), \ + .sda_pullup_en = GPIO_PULLUP_ENABLE, \ + .scl_pullup_en = GPIO_PULLUP_ENABLE, \ + .master = { \ + .clk_speed = 400000, \ + }, \ + .clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL, \ + } + +#define _CREATE_DEVICE(name, ...) \ + ({ \ + ESP_LOGI(TAG, "Create device: " #name); \ + std::shared_ptr device = std::make_shared(__VA_ARGS__); \ + TEST_ASSERT_NOT_NULL_MESSAGE(device, "Create device failed"); \ + device; \ + }) +#define CREATE_DEVICE(name, ...) _CREATE_DEVICE(name, ##__VA_ARGS__) + +static void init_host(void) +{ + const i2c_config_t i2c_config = HOST_CONFIG_DEFAULT(TEST_HOST_I2C_SCL_PIN, TEST_HOST_I2C_SDA_PIN); + TEST_ASSERT_EQUAL(i2c_param_config(TEST_HOST_ID, &i2c_config), ESP_OK); + TEST_ASSERT_EQUAL(i2c_driver_install(TEST_HOST_ID, i2c_config.mode, 0, 0, 0), ESP_OK); +} + +static void deinit_host(void) +{ + TEST_ASSERT_EQUAL(i2c_driver_delete(TEST_HOST_ID), ESP_OK); +} + +static void test_device(std::shared_ptr device) +{ + TEST_ASSERT_MESSAGE(device->init(), "Device initialization failed"); + TEST_ASSERT_MESSAGE(device->begin(), "Device begin failed"); + TEST_ASSERT_MESSAGE(device->reset(), "Device reset failed"); + TEST_ASSERT_MESSAGE(device->del(), "Device del failed"); +} + +#define CREATE_TEST_CASE(device_name) \ + TEST_CASE("test " #device_name " general functions", "[io_expander][general][" #device_name "]") \ + { \ + std::shared_ptr expander = nullptr; \ + \ + ESP_LOGI(TAG, "Initialize I2C host"); \ + init_host(); \ + \ + ESP_LOGI(TAG, "Test constructor with (int host_id, uint8_t address) (external I2C)"); \ + expander = CREATE_DEVICE(device_name, TEST_HOST_ID, TEST_DEVICE_ADDRESS); \ + test_device(expander); \ + expander = nullptr; \ + \ + ESP_LOGI(TAG, "Test constructor with (const Config &config) (external I2C)"); \ + Base::Config external_i2c_config = { \ + .host_id = TEST_HOST_ID, \ + .device = Base::DeviceConfig{ \ + .address = TEST_DEVICE_ADDRESS, \ + }, \ + }; \ + expander = CREATE_DEVICE(device_name, external_i2c_config); \ + test_device(expander); \ + expander = nullptr; \ + \ + ESP_LOGI(TAG, "Deinitialize I2C host"); \ + deinit_host(); \ + \ + ESP_LOGI(TAG, "Test constructor with (int scl_io, int sda_io, uint8_t address) (internal I2C)"); \ + expander = CREATE_DEVICE(device_name, TEST_HOST_I2C_SCL_PIN, TEST_HOST_I2C_SDA_PIN, TEST_DEVICE_ADDRESS); \ + test_device(expander); \ + expander = nullptr; \ + \ + ESP_LOGI(TAG, "Test constructor with (const Config &config) (internal I2C)"); \ + Base::Config internal_i2c_config = { \ + .host_id = TEST_HOST_ID, \ + .host = Base::HostPartialConfig{ \ + .sda_io_num = TEST_HOST_I2C_SDA_PIN, \ + .scl_io_num = TEST_HOST_I2C_SCL_PIN, \ + }, \ + .device = Base::DeviceConfig{ \ + .address = TEST_DEVICE_ADDRESS, \ + }, \ + }; \ + expander = CREATE_DEVICE(device_name, internal_i2c_config); \ + test_device(expander); \ + expander = nullptr; \ + \ + expander = CREATE_DEVICE(device_name, TEST_HOST_I2C_SCL_PIN, TEST_HOST_I2C_SDA_PIN, TEST_DEVICE_ADDRESS); \ + TEST_ASSERT_MESSAGE(expander->init(), "Device initialization failed"); \ + TEST_ASSERT_MESSAGE(expander->begin(), "Device begin failed"); \ + \ + ESP_LOGI(TAG, "Test input/output functions"); \ + ESP_LOGI(TAG, "Original status:"); \ + TEST_ASSERT_MESSAGE(expander->printStatus(), "Print status failed"); \ + \ + TEST_ASSERT_MESSAGE(expander->pinMode(0, OUTPUT), "Set pin 0 to output mode failed"); \ + TEST_ASSERT_MESSAGE(expander->pinMode(1, OUTPUT), "Set pin 1 to output mode failed"); \ + TEST_ASSERT_MESSAGE( \ + expander->multiPinMode(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, OUTPUT), \ + "Set pin 2-3 to output mode failed" \ + ); \ + \ + ESP_LOGI(TAG, "Set pint 0-3 to output mode:"); \ + TEST_ASSERT_MESSAGE(expander->printStatus(), "Print status failed"); \ + \ + TEST_ASSERT_MESSAGE(expander->digitalWrite(0, LOW), "Set pin 0 to low level failed"); \ + TEST_ASSERT_MESSAGE(expander->digitalWrite(1, LOW), "Set pin 1 to low level failed"); \ + TEST_ASSERT_MESSAGE( \ + expander->multiDigitalWrite(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, LOW), \ + "Set pin 2-3 to low level failed" \ + ); \ + \ + ESP_LOGI(TAG, "Set pint 0-3 to low level:"); \ + TEST_ASSERT_MESSAGE(expander->printStatus(), "Print status failed"); \ + \ + TEST_ASSERT_MESSAGE(expander->pinMode(0, INPUT), "Set pin 0 to input mode failed"); \ + TEST_ASSERT_MESSAGE(expander->pinMode(1, INPUT), "Set pin 1 to input mode failed"); \ + TEST_ASSERT_MESSAGE( \ + expander->multiPinMode(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, INPUT), \ + "Set pin 2-3 to input mode failed" \ + ); \ + \ + ESP_LOGI(TAG, "Set pint 0-3 to input mode:"); \ + TEST_ASSERT_MESSAGE(expander->printStatus(), "Print status failed"); \ + \ + int level[4] = {0, 0, 0, 0}; \ + int64_t level_temp; \ + \ + level[0] = expander->digitalRead(0); \ + TEST_ASSERT_MESSAGE(level[0] >= 0, "Read pin 0 level failed"); \ + level[1] = expander->digitalRead(1); \ + TEST_ASSERT_MESSAGE(level[1] >= 0, "Read pin 0 level failed"); \ + level_temp = expander->multiDigitalRead(IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3); \ + TEST_ASSERT_MESSAGE(level_temp >= 0, "Read pin 2-3 level failed"); \ + level[2] = level_temp & IO_EXPANDER_PIN_NUM_2 ? HIGH : LOW; \ + level[3] = level_temp & IO_EXPANDER_PIN_NUM_3 ? HIGH : LOW; \ + ESP_LOGI(TAG, "Pin 0-3 level: %d %d %d %d", level[0], level[1], level[2], level[3]); \ + } + +/** + * Here to create test cases for different devices + */ +CREATE_TEST_CASE(TCA95XX_8BIT) +CREATE_TEST_CASE(TCA95XX_16BIT) +CREATE_TEST_CASE(CH422G) +CREATE_TEST_CASE(HT8574) diff --git a/test_apps/sdkconfig.ci.debug_log b/test_apps/sdkconfig.ci.debug_log new file mode 100644 index 0000000..191f9c8 --- /dev/null +++ b/test_apps/sdkconfig.ci.debug_log @@ -0,0 +1,2 @@ +CONFIG_ESP_UTILS_CONF_LOG_LEVEL_DEBUG=y +CONFIG_ESP_UTILS_CONF_ENABLE_LOG_TRACE=y diff --git a/test_apps/sdkconfig.defaults b/test_apps/sdkconfig.defaults index f61533c..0495c8f 100644 --- a/test_apps/sdkconfig.defaults +++ b/test_apps/sdkconfig.defaults @@ -1,10 +1,3 @@ -# For IDF 5.0 -CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y -CONFIG_FREERTOS_HZ=1000 CONFIG_ESP_TASK_WDT_EN=n -CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 - -# For IDF4.4 -CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y -CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y -CONFIG_ESP_TASK_WDT=n +CONFIG_FREERTOS_HZ=1000 +CONFIG_COMPILER_CXX_EXCEPTIONS=y pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy