diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..60bbe06 --- /dev/null +++ b/.codespellrc @@ -0,0 +1 @@ +[codespell] diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..648e169 --- /dev/null +++ b/.flake8 @@ -0,0 +1,141 @@ +[flake8] + +select = + # Full lists are given in order to suppress all errors from other plugins + # Full list of pyflakes error codes: + F401, # module imported but unused + F402, # import module from line N shadowed by loop variable + F403, # 'from module import *' used; unable to detect undefined names + F404, # future import(s) name after other statements + F405, # name may be undefined, or defined from star imports: module + F406, # 'from module import *' only allowed at module level + F407, # an undefined __future__ feature name was imported + F601, # dictionary key name repeated with different values + F602, # dictionary key variable name repeated with different values + F621, # too many expressions in an assignment with star-unpacking + F622, # two or more starred expressions in an assignment (a, *b, *c = d) + F631, # assertion test is a tuple, which are always True + F701, # a break statement outside of a while or for loop + F702, # a continue statement outside of a while or for loop + F703, # a continue statement in a finally block in a loop + F704, # a yield or yield from statement outside of a function + F705, # a return statement with arguments inside a generator + F706, # a return statement outside of a function/method + F707, # an except: block as not the last exception handler + F721, F722, # doctest syntax error syntax error in forward type annotation + F811, # redefinition of unused name from line N + F812, # list comprehension redefines name from line N + F821, # undefined name name + F822, # undefined name name in __all__ + F823, # local variable name referenced before assignment + F831, # duplicate argument name in function definition + F841, # local variable name is assigned to but never used + F901, # raise NotImplemented should be raise NotImplementedError + + # Full list of pycodestyle violations: + E101, # indentation contains mixed spaces and tabs + E111, # indentation is not a multiple of four + E112, # expected an indented block + E113, # unexpected indentation + E114, # indentation is not a multiple of four (comment) + E115, # expected an indented block (comment) + E116, # unexpected indentation (comment) + E121, # continuation line under-indented for hanging indent + E122, # continuation line missing indentation or outdented + E123, # closing bracket does not match indentation of opening bracket's line + E124, # closing bracket does not match visual indentation + E125, # continuation line with same indent as next logical line + E126, # continuation line over-indented for hanging indent + E127, # continuation line over-indented for visual indent + E128, # continuation line under-indented for visual indent + E129, # visually indented line with same indent as next logical line + E131, # continuation line unaligned for hanging indent + E133, # closing bracket is missing indentation + E201, # whitespace after '(' + E202, # whitespace before ')' + E203, # whitespace before ':' + E211, # whitespace before '(' + E221, # multiple spaces before operator + E222, # multiple spaces after operator + E223, # tab before operator + E224, # tab after operator + E225, # missing whitespace around operator + E226, # missing whitespace around arithmetic operator + E227, # missing whitespace around bitwise or shift operator + E228, # missing whitespace around modulo operator + E231, # missing whitespace after ',', ';', or ':' + E241, # multiple spaces after ',' + E242, # tab after ',' + E251, # unexpected spaces around keyword / parameter equals + E261, # at least two spaces before inline comment + E262, # inline comment should start with '# ' + E265, # block comment should start with '# ' + E266, # too many leading '#' for block comment + E271, # multiple spaces after keyword + E272, # multiple spaces before keyword + E273, # tab after keyword + E274, # tab before keyword + E275, # missing whitespace after keyword + E301, # expected 1 blank line, found 0 + E302, # expected 2 blank lines, found 0 + E303, # too many blank lines + E304, # blank lines found after function decorator + E305, # expected 2 blank lines after end of function or class + E306, # expected 1 blank line before a nested definition + E401, # multiple imports on one line + E402, # module level import not at top of file + E501, # line too long (82 > 79 characters) + E502, # the backslash is redundant between brackets + E701, # multiple statements on one line (colon) + E702, # multiple statements on one line (semicolon) + E703, # statement ends with a semicolon + E704, # multiple statements on one line (def) + E711, # comparison to None should be 'if cond is None:' + E712, # comparison to True should be 'if cond is True:' or 'if cond:' + E713, # test for membership should be 'not in' + E714, # test for object identity should be 'is not' + E721, # do not compare types, use 'isinstance()' + E722, # do not use bare except, specify exception instead + E731, # do not assign a lambda expression, use a def + E741, # do not use variables named 'l', 'O', or 'I' + E742, # do not define classes named 'l', 'O', or 'I' + E743, # do not define functions named 'l', 'O', or 'I' + E901, # SyntaxError or IndentationError + E902, # IOError + W191, # indentation contains tabs + W291, # trailing whitespace + W292, # no newline at end of file + W293, # blank line contains whitespace + W391, # blank line at end of file + W503, # line break before binary operator + W504, # line break after binary operator + W505, # doc line too long (82 > 79 characters) + W601, # .has_key() is deprecated, use 'in' + W602, # deprecated form of raising exception + W603, # '<>' is deprecated, use '!=' + W604, # backticks are deprecated, use 'repr()' + W605, # invalid escape sequence 'x' + W606, # 'async' and 'await' are reserved keywords starting with Python 3.7 + + # Full list of flake8 violations + E999, # failed to compile a file into an Abstract Syntax Tree for the plugins that require it + + # Full list of mccabe violations + C901 # complexity value provided by the user + +ignore = + E221, # multiple spaces before operator + E231, # missing whitespace after ',', ';', or ':' + E241, # multiple spaces after ',' + W503, # line break before binary operator + W504 # line break after binary operator + +max-line-length = 160 + +show_source = True + +statistics = True + +exclude = + .git, + __pycache__, diff --git a/.github/scripts/check_versions.sh b/.github/scripts/check_lib_versions.sh old mode 100644 new mode 100755 similarity index 89% rename from .github/scripts/check_versions.sh rename to .github/scripts/check_lib_versions.sh index d458178..cdfe6d8 --- a/.github/scripts/check_versions.sh +++ b/.github/scripts/check_lib_versions.sh @@ -13,26 +13,6 @@ check_version_format() { return 0 } -if [ $# -lt 1 ]; then - latest_version="0.0.0" - echo "Don't get the lastest version, use \"0.0.0\" as default" -else - # Get the first input parameter as the version to be compared - latest_version="$1" - # Check the version format - check_version_format "${latest_version}" - result=$? - if [ ${result} -ne 0 ]; then - echo "The latest release version (${latest_version}) format is incorrect." - exit 1 - fi -fi - -# Specify the directory path -target_directory="./" - -echo "Checking directory: ${target_directory}" - # Function: Check if a file exists # Input parameters: $1 The file to check # Return value: 0 if the file exists, 1 if the file does not exist @@ -68,6 +48,32 @@ compare_versions() { return 0 } +# Get the latest release version +latest_version="v0.0.0" +for i in "$@"; do + case $i in + --latest_version=*) + latest_version="${i#*=}" + shift + ;; + *) + ;; + esac +done +# Check the version format +check_version_format "${latest_version}" +result=$? +if [ ${result} -ne 0 ]; then + echo "The latest release version (${latest_version}) format is incorrect." + exit 1 +fi +echo "Get the latest release version: ${latest_version}" + +# Specify the directory path +target_directory="./" + +echo "Checking directory: ${target_directory}" + echo "Checking file: library.properties" # Check if "library.properties" file exists check_file_exists "${target_directory}/library.properties" 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 9f1f2e9..e87ca79 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -4,18 +4,21 @@ on: workflow_dispatch: pull_request: types: [opened, reopened, synchronize] + push: + branches: + - master jobs: build: strategy: matrix: - idf_ver: ["v4.4.5", "v5.0", "v5.1"] + idf_ver: ["release-v5.1", "release-v5.2", "release-v5.3"] idf_target: ["esp32", "esp32s2", "esp32c3", "esp32s3"] runs-on: ubuntu-20.04 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 @@ -25,4 +28,4 @@ jobs: export PEDANTIC_FLAGS="-DIDF_CI_BUILD -Werror -Werror=deprecated-declarations -Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function" export EXTRA_CFLAGS="${PEDANTIC_FLAGS} -Wstrict-prototypes" export EXTRA_CXXFLAGS="${PEDANTIC_FLAGS}" - idf.py build \ No newline at end of file + idf.py build diff --git a/.github/workflows/check_versions.yml b/.github/workflows/check_lib_versions.yml similarity index 84% rename from .github/workflows/check_versions.yml rename to .github/workflows/check_lib_versions.yml index eb2d116..48a4899 100644 --- a/.github/workflows/check_versions.yml +++ b/.github/workflows/check_lib_versions.yml @@ -1,11 +1,11 @@ -name: Check Versions +name: Check Library Versions on: pull_request: types: [opened, reopened, synchronize] jobs: - check_versions: + check_lib_versions: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -26,5 +26,4 @@ jobs: echo "prerelease: ${{ steps.last_release.outputs.prerelease }}" echo "url: ${{ steps.last_release.outputs.url }}" - name: Check & Compare versions - run: bash ./.github/scripts/check_versions.sh ${{ steps.last_release.outputs.tag_name }} - + run: bash ./.github/scripts/check_lib_versions.sh --latest_version=${{ steps.last_release.outputs.tag_name }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 51f77cd..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 \ No newline at end of file + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml new file mode 100644 index 0000000..333c718 --- /dev/null +++ b/.github/workflows/upload_component.yml @@ -0,0 +1,20 @@ +name: Push components to Espressif Component Service + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + upload_components: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + with: + submodules: 'recursive' + - name: Upload components to component service + uses: espressif/upload-components-ci-action@v1 + with: + name: "esp32_io_expander" + namespace: "espressif" + api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8bb4083..9100936 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,25 +1,82 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks + +exclude: 'libraries/ui/' repos: -- repo: https://github.com/igrr/astyle_py.git - rev: master + - repo: https://github.com/igrr/astyle_py.git + rev: v1.0.5 hooks: - - id: astyle_py + - id: astyle_py args: ['--style=otbs', '--attach-namespaces', '--attach-classes', '--indent=spaces=4', '--convert-tabs', '--align-pointer=name', '--align-reference=name', '--keep-one-line-statements', '--pad-header', '--pad-oper'] -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + - repo: https://github.com/espressif/check-copyright/ + rev: v1.0.3 + hooks: + - id: check-copyright + args: ['--config', 'check_copyright_config.yaml'] + + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + types: [python] + args: ['--config=.flake8', '--tee', '--benchmark'] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 hooks: - - id: trailing-whitespace - types_or: [c, c++] - - id: end-of-file-fixer - types_or: [c, c++] - - id: check-merge-conflict - - id: mixed-line-ending - types_or: [c, c++] - args: ['--fix=lf'] - description: Forces to replace line ending by the UNIX 'lf' character + - id: trailing-whitespace + # note: whitespace exclusions use multiline regex, see https://pre-commit.com/#regular-expressions + # items are: + # 1 - some file extensions + # 2 - any file matching *test*/*expected* (for host tests, if possible use this naming pattern always) + # 3 - any file with known-warnings in the name + # 4 - any directory named 'testdata' + # 5 - protobuf auto-generated files + exclude: &whitespace_excludes | + (?x)^( + .+\.(md|rst|map|bin)| + .+test.*\/.*expected.*| + .+known-warnings.*| + .+\/testdata\/.+| + .*_pb2.py| + .*.pb-c.h| + .*.pb-c.c| + .*.yuv + )$ + - id: end-of-file-fixer + exclude: *whitespace_excludes + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: mixed-line-ending + args: ['-f=lf'] + - id: double-quote-string-fixer + - id: no-commit-to-branch + name: Do not use more than one slash in the branch name + args: ['--pattern', '^[^/]*/[^/]*/'] + - id: no-commit-to-branch + name: Do not use uppercase letters in the branch name + args: ['--pattern', '^[^A-Z]*[A-Z]'] -- repo: https://github.com/espressif/check-copyright/ - rev: v1.0.3 - hooks: - - id: check-copyright - args: ['--config', 'check_copyright_config.yaml'] \ No newline at end of file + - repo: https://github.com/espressif/conventional-precommit-linter + rev: v1.8.0 + hooks: + - id: conventional-precommit-linter + stages: [commit-msg] + args: + - --subject-min-length=15 + - --body-max-line-length=200 + + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + args: ['-w' , '--config', '.codespellrc'] + + - repo: local + hooks: + - id: check-library-versions + name: Check library versions + entry: ./.github/scripts/check_lib_versions.sh + language: system + files: '(idf_component.yml|library.properties)' diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b88de5..4e14044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # 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: + +* Upload to the ESP Registry and support to build on the esp-idf +* Update pre-commit configuration + +## v0.0.4 - 2024-10-18 + +### Enhancements: + +* Update CH422G and add a new example to show how to use it (@H-sw123) + ## v0.0.3 - 2024-05-07 ### 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 fe8ae4c..6ebc43c 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,109 @@ [![Arduino Lint](https://github.com/esp-arduino-libs/ESP32_IO_Expander/actions/workflows/arduino_lint.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_IO_Expander/actions/workflows/arduino_lint.yml) [![pre-commit](https://github.com/esp-arduino-libs/ESP32_IO_Expander/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_IO_Expander/actions/workflows/pre-commit.yml) [![Build Test Apps](https://github.com/esp-arduino-libs/ESP32_IO_Expander/actions/workflows/build_test.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_IO_Expander/actions/workflows/build_test.yml) +**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) + # ESP32_IO_Expander -ESP32_IO_Expander is an Arduino 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) 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. +## 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 | -| **Name** | **Version** | -| ----------------------------------------------------------- | ----------- | -| [arduino-esp32](https://github.com/espressif/arduino-esp32) | >= v2.0.9 | +#### Adding to Project -## How to Use +`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: + +```bash +idf.py add-dependency "espressif/ESP32_IO_Expander" +``` + +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). + +#### 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). -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). +### 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 | + +#### 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. +* [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 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(); // Control a single pin (0-31) expander->pinMode(0, OUTPUT); @@ -59,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/check_copyright_config.yaml b/check_copyright_config.yaml index d0c1e09..78761a6 100644 --- a/check_copyright_config.yaml +++ b/check_copyright_config.yaml @@ -7,6 +7,7 @@ DEFAULT: # when setting this option in a section, you need to list all the allowed licenses allowed_licenses: - Apache-2.0 + - MIT license_for_new_files: Apache-2.0 # license to be used when inserting a new copyright notice new_notice_c: | # notice for new C, CPP, H, HPP and LD files /* @@ -28,14 +29,14 @@ DEFAULT: # You can create your own rules for files or group of files examples_and_unit_tests: include: - - 'test_apps/' + - 'test_apps/' + - 'examples/' allowed_licenses: - Apache-2.0 - Unlicense - CC0-1.0 license_for_new_files: CC0-1.0 -ignore: # You can also select ignoring files here - perform_check: no # Don't check files from that block - include: - - 'examples/' \ No newline at end of file +# ignore: # You can also select ignoring files here +# perform_check: no # Don't check files from that block +# include: diff --git a/examples/TestFunctions/TestFunctions.ino b/examples/TestFunctions/TestFunctions.ino deleted file mode 100644 index 39f33a5..0000000 --- a/examples/TestFunctions/TestFunctions.ino +++ /dev/null @@ -1,80 +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_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(); - - 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/ch422g/ch422g.ino b/examples/ch422g/ch422g.ino new file mode 100644 index 0000000..89709e1 --- /dev/null +++ b/examples/ch422g/ch422g.ino @@ -0,0 +1,99 @@ +/** + * | Supported IO Expanders | CH422G | + * | ------------------------- | ------ | + * + * # CH422G Test Example + * + * The hardware device used in this example is waveshare ESP32-S3-Touch-LCD-4.3B-BOX. To test the simultaneous use of I/O input and OC output, connect DO0 to DI0, and connect DO1 to DI1. + * + * ## How to use + * + * 1. Enable USB CDC. + * 2. Verify and upload the example to your board. + * + * ## Serial Output + * + * ``` + * ... + * Test begin + * Set the OC pin to push-pull output mode. + * Set the IO0-7 pin to input mode. + * Set pint 8 and 9 to:0, 1 + * + * Read pin 0 and 5 level: 0, 1 + * + * Set pint 8 and 9 to:1, 0 + * + * Read pin 0 and 5 level: 1, 0 + * ... + * ``` + * + * ## Troubleshooting + * + * The driver initialization by default sets CH422G's IO0-7 to output high-level mode. + Since the input/output mode of CH422G's IO0-7 must remain consistent, the driver will only set IO0-7 to + input mode when it determines that all pins are configured as input. + Using pinMode and multiPinMode will be invalid. You can only set the pin working mode through enableAllIO_Input, enableAllIO_Output, enableOC_PushPull and enableOC_OpenDrain + */ + +#include +#include + +#define EXAMPLE_I2C_SDA_PIN (8) +#define EXAMPLE_I2C_SCL_PIN (9) +#define EXAMPLE_I2C_ADDR (ESP_IO_EXPANDER_I2C_CH422G_ADDRESS) + +esp_expander::CH422G *expander = NULL; + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println("Test begin"); + + expander = new esp_expander::CH422G(EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN, EXAMPLE_I2C_ADDR); + expander->init(); + expander->begin(); + + Serial.println("Set the OC pin to push-pull output mode."); + expander->enableOC_PushPull(); + + // Serial.println("Set the OC pin to open_drain output mode."); + // expander->enableOC_OpenDrain(); + + Serial.println("Set the IO0-7 pin to input mode."); + expander->enableAllIO_Input(); + + // Serial.println("Set the IO0-7 pin to output mode."); + // expander->enableAllIO_Output(); +} + +int level[2] = { 0, 0 }; + +void loop() { + for (int i = 0; i < 100; i++) { + bool toggle = i % 2; + + Serial.print("Set pint 8 and 9 to:"); + Serial.print(toggle); + Serial.print(", "); + Serial.println(!toggle); + Serial.println(); + + // Set pin 8 and 9 level + expander->digitalWrite(8, toggle); + expander->digitalWrite(9, !toggle); + delay(1); + + // Read pin 0 and 5 level + level[0] = expander->digitalRead(0); + level[1] = expander->digitalRead(5); + + Serial.print("Read pin 0 and 5 level: "); + Serial.print(level[0]); + Serial.print(", "); + Serial.println(level[1]); + Serial.println(); + + delay(1000); + } +} diff --git a/examples/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 new file mode 100644 index 0000000..4ad0e50 --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,10 @@ +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 5c080fb..4065fba 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,11 @@ name=ESP32_IO_Expander -version=0.0.3 -author=lzw655 +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 bd6d71c..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 dircetion */ -} 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 diection 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 intput 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.cpp b/src/chip/CH422G.cpp deleted file mode 100644 index 56abfba..0000000 --- a/src/chip/CH422G.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include - -#include "driver/i2c.h" -#include "esp_bit_defs.h" -#include "esp_check.h" -#include "esp_log.h" - -#include "../private/CheckResult.h" -#include "CH422G.h" - -/* Timeout of each I2C communication */ -#define I2C_TIMEOUT_MS (10) - -#define IO_COUNT (8) - -/* Register address */ -#define CH422G_REG_IN (0x26) -#define CH422G_REG_OUT (0x38) - -/* Default register value on power-up */ -#define DIR_REG_DEFAULT_VAL (0xff) -#define OUT_REG_DEFAULT_VAL (0xdf) - -/** - * @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_ch422g_t; - -static const char *TAG = "ch422g"; - -static esp_err_t esp_io_expander_new_i2c_ch422g(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle); - -ESP_IOExpander_CH422G::~ESP_IOExpander_CH422G() -{ - if (i2c_need_init) { - i2c_driver_delete(i2c_id); - } - if (handle) { - del(); - } -} - -void ESP_IOExpander_CH422G::begin(void) -{ - CHECK_ERROR_RETURN(esp_io_expander_new_i2c_ch422g(i2c_id, i2c_address, &handle)); -} - -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_ch422g(i2c_port_t i2c_num, uint32_t i2c_address, esp_io_expander_handle_t *handle) -{ - ESP_RETURN_ON_FALSE(i2c_num < I2C_NUM_MAX, ESP_ERR_INVALID_ARG, TAG, "Invalid i2c num"); - ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid handle"); - - esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)calloc(1, sizeof(esp_io_expander_ch422g_t)); - ESP_RETURN_ON_FALSE(ch422g, ESP_ERR_NO_MEM, TAG, "Malloc failed"); - - ch422g->base.config.io_count = IO_COUNT; - ch422g->base.config.flags.dir_out_bit_zero = 1; - ch422g->i2c_num = i2c_num; - ch422g->i2c_address = i2c_address; - ch422g->regs.output = OUT_REG_DEFAULT_VAL; - ch422g->base.read_input_reg = read_input_reg; - ch422g->base.write_output_reg = write_output_reg; - ch422g->base.read_output_reg = read_output_reg; - ch422g->base.write_direction_reg = write_direction_reg; - ch422g->base.read_direction_reg = read_direction_reg; - ch422g->base.del = del; - ch422g->base.reset = reset; - - esp_err_t ret = ESP_OK; - /* Reset configuration and register status */ - ESP_GOTO_ON_ERROR(reset(&ch422g->base), err, TAG, "Reset failed"); - - *handle = &ch422g->base; - return ESP_OK; -err: - free(ch422g); - return ret; -} - -static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value) -{ - esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); - - uint8_t temp = 0; - - ESP_RETURN_ON_ERROR( - i2c_master_read_from_device(ch422g->i2c_num, ch422g->i2c_address, &temp, 1, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), - TAG, "Read input reg failed"); - - // *INDENT-OFF* - ESP_RETURN_ON_ERROR( - i2c_master_read_from_device(ch422g->i2c_num, CH422G_REG_IN, &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_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); - value &= 0xff; - - uint8_t out_temp = 0x01; - ESP_RETURN_ON_ERROR( - i2c_master_write_to_device(ch422g->i2c_num, ch422g->i2c_address, &out_temp, 1, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), - TAG, "Write output reg failed"); - - uint8_t data = (uint8_t)value; - ESP_RETURN_ON_ERROR( - i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_OUT, &data, 1, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), - TAG, "Write output reg failed"); - ch422g->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_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); - - *value = ch422g->regs.output; - return ESP_OK; -} - -static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value) -{ - esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); - value &= 0xff; - ch422g->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_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); - - *value = ch422g->regs.direction; - return ESP_OK; -} - -static esp_err_t reset(esp_io_expander_t *handle) -{ - 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_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); - - free(ch422g); - return ESP_OK; -} diff --git a/src/chip/CH422G.h b/src/chip/CH422G.h deleted file mode 100644 index 1153009..0000000 --- a/src/chip/CH422G.h +++ /dev/null @@ -1,75 +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" - -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. 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_CH422G(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_CH422G(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_CH422G(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_CH422G() override; - - /** - * @brief Begin IO expander - * - */ - void begin(void) override; -}; - -/** - * @brief I2C address of the ch422g - * - * 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 1001000b(0x48). - * Then users can use `ESP_IO_EXPANDER_I2C_CH422G_ADDRESS_000` to init it. - */ -#define ESP_IO_EXPANDER_I2C_CH422G_ADDRESS_000 (0x24) diff --git a/src/chip/HT8574.h b/src/chip/HT8574.h deleted file mode 100644 index 54f8840..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) (Hareware 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/port/esp_io_expander_ch422g.c b/src/port/esp_io_expander_ch422g.c new file mode 100644 index 0000000..f16fd02 --- /dev/null +++ b/src/port/esp_io_expander_ch422g.c @@ -0,0 +1,306 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "driver/i2c.h" +#include "esp_bit_defs.h" +#include "esp_check.h" +#include "esp_log.h" + +#include "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) + +#define IO_COUNT (12) + +/* Register address */ +#define CH422G_REG_WR_SET (0x48 >> 1) +#define CH422G_REG_WR_OC (0x46 >> 1) +#define CH422G_REG_WR_IO (0x70 >> 1) +#define CH422G_REG_RD_IO (0x4D >> 1) + +/* Default register value when reset */ +// *INDENT-OFF* +#define REG_WR_SET_DEFAULT_VAL (0x01UL) // Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + // | --- | --- | --- | --- | ------- | ------- | -------- | ------- | + // Value: | / | / | / | / | [SLEEP] | [OD_EN] | [A_SCAN] | [IO_OE] | + // | --- | --- | --- | --- | ------- | ------- | -------- | ------- | + // Default: | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | + +// *INDENT-OFF* +#define REG_WR_OC_DEFAULT_VAL (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 (0xFFFU) + +#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; + i2c_port_t i2c_num; + uint32_t i2c_address; + struct { + uint8_t wr_set; + uint8_t wr_oc; + uint8_t wr_io; + } regs; +} esp_io_expander_ch422g_t; + +static const char *TAG = "ch422g"; + +static esp_err_t 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_ch422g(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_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"); + + 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; +} + +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 + 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; +} + +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 + 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; +} + +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 + 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; +} + +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 + 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; +} + +esp_err_t esp_io_expander_ch422g_enter_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; + + return ESP_OK; +} + +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; + + return ESP_OK; +} + +static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + uint8_t temp = 0; + + ESP_RETURN_ON_ERROR( + i2c_master_read_from_device(ch422g->i2c_num, CH422G_REG_RD_IO, &temp, 1, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Read RD-IO reg failed" + ); + *value = temp; + + return ESP_OK; +} + +static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + + uint8_t wr_oc_data = (value & 0xF00) >> 8; + uint8_t wr_io_data = value & 0xFF; + + // WR-OC + if (wr_oc_data) { + ESP_RETURN_ON_ERROR( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_OC, &wr_oc_data, sizeof(wr_oc_data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Write WR-OC reg failed" + ); + ch422g->regs.wr_oc = wr_oc_data; + } + + // WR-IO + if (wr_io_data) { + ESP_RETURN_ON_ERROR( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_IO, &wr_io_data, sizeof(wr_io_data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Write WR-IO reg failed" + ); + ch422g->regs.wr_io = wr_io_data; + } + + return ESP_OK; +} + +static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + + *value = ch422g->regs.wr_io | (((uint32_t)ch422g->regs.wr_oc) << 8); + + return ESP_OK; +} + +static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + uint8_t data = ch422g->regs.wr_set; + + value &= 0xFF; + if (value != 0) { + data |= REG_WR_SET_BIT_IO_OE; + } else { + data &= ~REG_WR_SET_BIT_IO_OE; + } + + // WR-SET + ESP_RETURN_ON_ERROR( + i2c_master_write_to_device(ch422g->i2c_num, CH422G_REG_WR_SET, &data, sizeof(data), pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Write WR_SET reg failed" + ); + ch422g->regs.wr_set = data; + + return ESP_OK; +} + +#define DIR_OUT_VALUE (0xFFF) +#define DIR_IN_VALUE (0xF00) + +static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + + *value = (ch422g->regs.wr_set & REG_WR_SET_BIT_IO_OE) ? DIR_OUT_VALUE : DIR_IN_VALUE; + + return ESP_OK; +} + +static esp_err_t reset(esp_io_expander_t *handle) +{ + ESP_RETURN_ON_ERROR(write_direction_reg(handle, REG_DIR_DEFAULT_VAL), TAG, "Write direction reg (WR_SET) failed"); + ESP_RETURN_ON_ERROR(write_output_reg(handle, REG_OUT_DEFAULT_VAL), TAG, "Write output reg (WR_OC & WR_IO) failed"); + + return ESP_OK; +} + +static esp_err_t del(esp_io_expander_t *handle) +{ + esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); + + free(ch422g); + return ESP_OK; +} diff --git a/src/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 58% rename from src/chip/TCA95xx_8bit.h rename to src/port/esp_io_expander_tca9554.h index db061b5..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 @@ -76,7 +51,7 @@ class ESP_IOExpander_TCA95xx_8bit: public ESP_IOExpander { * | 0 | 1 | 0 | 0 | A2 | A1 | A0 | R/W | * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ * └────────┯────────┘ └─────┯──────┘ - * (Fixed) (Hareware Selectable) + * (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 0100000b(0x20). @@ -103,7 +78,7 @@ class ESP_IOExpander_TCA95xx_8bit: public ESP_IOExpander { * | 0 | 1 | 1 | 1 | A2 | A1 | A0 | R/W | * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ * └────────┯────────┘ └─────┯──────┘ - * (Fixed) (Hareware Selectable) + * (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). @@ -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 54% rename from src/chip/TCA95xx_16bit.h rename to src/port/esp_io_expander_tca95xx_16bit.h index 795f2b4..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 @@ -76,7 +46,7 @@ class ESP_IOExpander_TCA95xx_16bit: public ESP_IOExpander { * | 1 | 1 | 1 | 0 | 1 | A1 | A0 | R/W | * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ * └────────┯──────────────┘ └──┯──┘ - * (Fixed) (Hareware Selectable) + * (Fixed) (Hardware Selectable) * * The 8-bit address format for the TCA9555 is as follows: * @@ -86,7 +56,7 @@ class ESP_IOExpander_TCA95xx_16bit: public ESP_IOExpander { * | 0 | 1 | 0 | 0 | A2 | A1 | A0 | R/W | * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ * └────────┯────────┘ └─────┯──────┘ - * (Fixed) (Hareware Selectable) + * (Fixed) (Hardware Selectable) * * And the 7-bit slave address is the most important data for users. * For example, if a TCA9555 chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0b0100000. @@ -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 19ad7fd..55de4fe 100644 --- a/test_apps/CMakeLists.txt +++ b/test_apps/CMakeLists.txt @@ -1,6 +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(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components" "../") +set(component main) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(panel_io_3wire_spi_test) \ No newline at end of file +project(io_expander_test) diff --git a/test_apps/main/CMakeLists.txt b/test_apps/main/CMakeLists.txt index e06a302..4bf02cc 100644 --- a/test_apps/main/CMakeLists.txt +++ b/test_apps/main/CMakeLists.txt @@ -1,3 +1,4 @@ -idf_component_register(SRCS "test_ESP_IOExpander.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 29cf227..6af3a2e 100644 --- a/test_apps/main/idf_component.yml +++ b/test_apps/main/idf_component.yml @@ -1,3 +1,7 @@ ## IDF Component Manager Manifest File dependencies: - idf: ">=4.4" \ No newline at end of file + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + ESP32_IO_Expander: + version: "*" + override_path: "../../../ESP32_IO_Expander" diff --git a/test_apps/main/test_ESP_IOExpander.cpp b/test_apps/main/test_ESP_IOExpander.cpp deleted file mode 100644 index 5d9d87a..0000000 --- a/test_apps/main/test_ESP_IOExpander.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * 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" - -// Refer to `esp32-hal-gpio.h` -#define INPUT 0x01 -#define OUTPUT 0x03 -#define LOW 0x0 -#define HIGH 0x1 - -static const char *TAG = "ESP_IOxpander_test"; - -#define I2C_HOST (I2C_NUM_0) -#define I2C_SDA_PIN (8) -#define I2C_SCL_PIN (18) - -TEST_CASE("test ESP IO expander for TCA9554", "[tca9554]") -{ - 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 ESP_IOExpander_TCA95xx_8bit(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 ESP_IOExpander_TCA95xx_8bit(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 ESP_IOExpander_TCA95xx_8bit(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; -} - -// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-300) - -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; - 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"); -} - -void setUp(void) -{ - before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); -} - -void tearDown(void) -{ - size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); - check_leak(before_free_8bit, after_free_8bit, "8BIT"); - check_leak(before_free_32bit, after_free_32bit, "32BIT"); -} - -extern "C" void app_main(void) -{ - // _____ ___ __ _ - // \_ \/___\ /__\_ ___ __ __ _ _ __ __| | ___ _ __ - // / /\// // /_\ \ \/ / '_ \ / _` | '_ \ / _` |/ _ \ '__| - // /\/ /_/ \_// //__ > <| |_) | (_| | | | | (_| | __/ | - // \____/\___/ \__/ /_/\_\ .__/ \__,_|_| |_|\__,_|\___|_| - // |_| - printf(" _____ ___ __ _\r\n"); - printf(" \\_ \\/___\\ /__\\_ ___ __ __ _ _ __ __| | ___ _ __\r\n"); - printf(" / /\\// // /_\\ \\ \\/ / '_ \\ / _` | '_ \\ / _` |/ _ \\ '__|\r\n"); - printf("/\\/ /_/ \\_// //__ > <| |_) | (_| | | | | (_| | __/ |\r\n"); - printf("\\____/\\___/ \\__/ /_/\\_\\ .__/ \\__,_|_| |_|\\__,_|\\___|_|\r\n"); - printf(" |_|\r\n"); - unity_run_menu(); -} diff --git a/test_apps/main/test_app_main.cpp b/test_apps/main/test_app_main.cpp new file mode 100644 index 0000000..ff6d33f --- /dev/null +++ b/test_apps/main/test_app_main.cpp @@ -0,0 +1,69 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#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_utils.h" + +#define TEST_MEMORY_LEAK_THRESHOLD (400) + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +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 = 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"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + 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) +{ + // _____ ___ __ _ + // \_ \/___\ /__\_ ___ __ __ _ _ __ __| | ___ _ __ + // / /\// // /_\ \ \/ / '_ \ / _` | '_ \ / _` |/ _ \ '__| + // /\/ /_/ \_// //__ > <| |_) | (_| | | | | (_| | __/ | + // \____/\___/ \__/ /_/\_\ .__/ \__,_|_| |_|\__,_|\___|_| + // |_| + printf(" _____ ___ __ _\r\n"); + printf(" \\_ \\/___\\ /__\\_ ___ __ __ _ _ __ __| | ___ _ __\r\n"); + printf(" / /\\// // /_\\ \\ \\/ / '_ \\ / _` | '_ \\ / _` |/ _ \\ '__|\r\n"); + printf("/\\/ /_/ \\_// //__ > <| |_) | (_| | | | | (_| | __/ |\r\n"); + printf("\\____/\\___/ \\__/ /_/\\_\\ .__/ \\__,_|_| |_|\\__,_|\\___|_|\r\n"); + printf(" |_|\r\n"); + unity_run_menu(); +} 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/pytest_esp_io_expander.py b/test_apps/pytest_esp_io_expander.py deleted file mode 100644 index 7c39966..0000000 --- a/test_apps/pytest_esp_io_expander.py +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: CC0-1.0 - -import pytest -from pytest_embedded import Dut - -@pytest.mark.target('esp32s3') -@pytest.mark.env('esp32_s3_lcd_ev_board') -def test_usb_stream(dut: Dut)-> None: - dut.run_all_single_board_cases() - 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 1e8cdb8..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 \ No newline at end of file +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