diff --git a/.github/scripts/check_versions.sh b/.github/scripts/check_versions.sh new file mode 100644 index 0000000..d458178 --- /dev/null +++ b/.github/scripts/check_versions.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +# Function: Check version format +# Input parameters: $1 The version number +# Return value: 0 if the version numbers are correct, 1 if the first version is incorrect, +check_version_format() { + version_regex="^v[0-9]+\.[0-9]+\.[0-9]+$" + + if [[ ! $1 =~ $version_regex ]]; then + return 1 + fi + + 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 +check_file_exists() { + if [ ! -f "$1" ]; then + echo "File '$1' not found." + return 1 + fi + return 0 +} + +# Function: Compare version numbers +# Input parameters: $1 The first version number, $2 The second version number +# Return value: 0 if the version numbers are equal, 1 if the first version is greater, +# 2 if the second version is greater, +compare_versions() { + version_regex="^v[0-9]+\.[0-9]+\.[0-9]+$" + + version1=$(echo "$1" | cut -c 2-) # Remove the 'v' at the beginning of the version number + version2=$(echo "$2" | cut -c 2-) + + IFS='.' read -ra v1_parts <<< "$version1" + IFS='.' read -ra v2_parts <<< "$version2" + + for ((i=0; i<${#v1_parts[@]}; i++)); do + if [[ "${v1_parts[$i]}" -lt "${v2_parts[$i]}" ]]; then + return 2 + elif [[ "${v1_parts[$i]}" -gt "${v2_parts[$i]}" ]]; then + return 1 + fi + done + + return 0 +} + +echo "Checking file: library.properties" +# Check if "library.properties" file exists +check_file_exists "${target_directory}/library.properties" +if [ $? -ne 0 ]; then + exit 1 +fi +# Read the version information from the file +arduino_version=v$(grep -E '^version=' "${target_directory}/library.properties" | cut -d '=' -f 2) +echo "Get Arduino version: ${arduino_version}" +# Check the version format +check_version_format "${arduino_version}" +result=$? +if [ ${result} -ne 0 ]; then + echo "Arduino version (${arduino_version}) format is incorrect." + exit 1 +fi + +# Compare Arduino Library version with the latest release version +compare_versions "${arduino_version}" "${latest_version}" +result=$? +if [ ${result} -ne 1 ]; then + if [ ${result} -eq 3 ]; then + echo "Arduino version (${arduino_version}) is incorrect." + else + echo "Arduino version (${arduino_version}) is not greater than the latest release version (${latest_version})." + exit 1 + fi +fi + +echo "Checking file: idf_component.yml" +# Check if "idf_component.yml" file exists +check_file_exists "${target_directory}/idf_component.yml" +if [ $? -eq 0 ]; then + # Read the version information from the file + idf_version=v$(grep -E '^version:' "${target_directory}/idf_component.yml" | awk -F'"' '{print $2}') + echo "Get IDF component version: ${idf_version}" + # Check the version format + check_version_format "${idf_version}" + result=$? + if [ ${result} -ne 0 ]; then + echo "IDF component (${idf_version}) format is incorrect." + exit 1 + fi + # Compare IDF Component version with Arduino Library version + compare_versions ${idf_version} ${arduino_version} + result=$? + if [ ${result} -ne 0 ]; then + if [ ${result} -eq 3 ]; then + echo "IDF component version (${idf_version}) is incorrect." + else + echo "IDF component version (${idf_version}) is not equal to the Arduino version (${arduino_version})." + exit 1 + fi + fi + # Compare IDF Component version with the latest release version + compare_versions ${idf_version} ${latest_version} + result=$? + if [ ${result} -ne 1 ]; then + if [ ${result} -eq 3 ]; then + echo "IDF component version (${idf_version}) is incorrect." + else + echo "IDF component version (${idf_version}) is not greater than the latest release version (${latest_version})." + exit 1 + fi + fi +fi diff --git a/.github/workflows/arduino_lint.yml b/.github/workflows/arduino_lint.yml new file mode 100644 index 0000000..0a6b9a8 --- /dev/null +++ b/.github/workflows/arduino_lint.yml @@ -0,0 +1,15 @@ +name: Arduino Lint Action + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: arduino/arduino-lint-action@v1 + with: + library-manager: update diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml new file mode 100644 index 0000000..9f1f2e9 --- /dev/null +++ b/.github/workflows/build_test.yml @@ -0,0 +1,28 @@ +name: Build Test Application + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + build: + strategy: + matrix: + idf_ver: ["v4.4.5", "v5.0", "v5.1"] + 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 + env: + IDF_TARGET: ${{ matrix.idf_target }} + working-directory: test_apps + shell: bash + run: | + . ${IDF_PATH}/export.sh + 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 diff --git a/.github/workflows/check_versions.yml b/.github/workflows/check_versions.yml new file mode 100644 index 0000000..eb2d116 --- /dev/null +++ b/.github/workflows/check_versions.yml @@ -0,0 +1,30 @@ +name: Check Versions + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + check_versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Get latest release info of repository + id: last_release + uses: InsonusK/get-latest-release@v1.1.0 + with: + myToken: ${{ github.token }} + exclude_types: "draft|prerelease" + view_top: 1 + - name: Print result + run: | + echo "id: ${{ steps.last_release.outputs.id }}" + echo "name: ${{ steps.last_release.outputs.name }}" + echo "tag_name: ${{ steps.last_release.outputs.tag_name }}" + echo "created_at: ${{ steps.last_release.outputs.created_at }}" + echo "draft: ${{ steps.last_release.outputs.draft }}" + 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 }} + diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..51f77cd --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,14 @@ +name: pre-commit + +on: + workflow_dispatch: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.3 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..657ffab --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +.config +*.o +*.pyc +*.orig + +# gtags +GTAGS +GRTAGS +GPATH + +# emacs +.dir-locals.el + +# emacs temp file suffixes +*~ +.#* +\#*# + +# eclipse setting +.settings + +# MacOS directory files +.DS_Store + +# Unit Test CMake compile log folder +log_ut_cmake + +TEST_LOGS + +# gcov coverage reports +*.gcda +*.gcno +coverage.info +coverage_report/ + +test_multi_heap_host + +# VS Code Settings +.vscode/ + +# VIM files +*.swp +*.swo + +# Clion IDE CMake build & config +.idea/ +cmake-build-*/ + +# Results for the checking of the Python coding style and static analysis +.mypy_cache +flake8_output.txt + +# esp-idf default build directory name +build +build_esp*/ +build_linux*/ +size_info.txt +sdkconfig +sdkconfig.old + +# lock files for examples and components +dependencies.lock + +# managed_components for examples +managed_components + +# pytest log +pytest_embedded_log/ +pytest_log/ +.pytest_cache/ +XUNIT_RESULT.xml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..8bb4083 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,25 @@ +repos: +- repo: https://github.com/igrr/astyle_py.git + rev: master + hooks: + - 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 + 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 + +- 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 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..91b1c5c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# ChangeLog + +## v0.0.1 - 2023-09-20 + +### Enhancements: + +* Support for various IO expander chips. +* Support to control individual IO in the same way as Arduino +* Support to control multiple IOs at the same time. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6eefe2d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +set(SRCS_DIR "src") + +file(GLOB_RECURSE CPP_SRCS "${SRCS_DIR}/*.cpp") +file(GLOB_RECURSE C_SRCS "${SRCS_DIR}/*.c") + +idf_component_register( + SRCS + ${C_SRCS} + ${CPP_SRCS} + INCLUDE_DIRS + ${SRCS_DIR} + REQUIRES + driver +) + +target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-missing-field-initializers) diff --git a/README.md b/README.md new file mode 100644 index 0000000..3da59a9 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +[![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) + +# ESP32_IO_Expander + +ESP32_IO_Expander is an Arduino library designed for driving [IO expander chips](#supported-drivers) using ESP32 SoCs. + +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. + +## Features + +* Supports various IO expander chips. +* Supports controlling individual IO pin (using pinMode(), digitalRead(), and digitalWrite() functions). +* Supports controlling multiple IO pins simultaneously. + +## Supported Drivers + +| **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 | + +## Dependencies Version + +| **Name** | **Version** | +| ----------------------------------------------------------- | ----------- | +| ESP32_IO_Expander | v0.x.x | +| [arduino-esp32](https://github.com/espressif/arduino-esp32) | >= v2.0.9 | + +## How to Use + +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). + +### Examples + +* [Test Functions](examples/TestFunctions): Demonstrates how to use ESP32_IO_Expander and test all functions. + +### Detailed Usage + +```cpp +#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); + +// Control a single pin (0-31) +expander->pinMode(0, OUTPUT); +expander->digitalWrite(0, HIGH); +expander->digitalWrite(0, LOW); +expander->pinMode(0, INPUT); +int level = expander->digitalRead(0); + +// Control multiple pins (IO_EXPANDER_PIN_NUM_0 - IO_EXPANDER_PIN_NUM_31) +expander->multiPinMode(IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, OUTPUT); +expander->multiDigitalWrite(IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, HIGH); +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 +delete expander; +``` diff --git a/check_copyright_config.yaml b/check_copyright_config.yaml new file mode 100644 index 0000000..d0c1e09 --- /dev/null +++ b/check_copyright_config.yaml @@ -0,0 +1,41 @@ +DEFAULT: + perform_check: yes # should the check be performed? + # Sections setting this to 'no' don't need to include any other options as they are ignored + # When a file is using a section with the option set to 'no', no checks are performed. + + # what licenses (or license expressions) are allowed for files in this section + # when setting this option in a section, you need to list all the allowed licenses + allowed_licenses: + - Apache-2.0 + 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 + /* + * SPDX-FileCopyrightText: {years} Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: {license} + */ + new_notice_python: | # notice for new python files + # SPDX-FileCopyrightText: {years} Espressif Systems (Shanghai) CO LTD + # SPDX-License-Identifier: {license} + + # comment lines matching: + # SPDX-FileCopyrightText: year[-year] Espressif Systems + # or + # SPDX-FileContributor: year[-year] Espressif Systems + # are replaced with this template prefixed with the correct comment notation (# or // or *) and SPDX- notation + espressif_copyright: '{years} Espressif Systems (Shanghai) CO LTD' + +# You can create your own rules for files or group of files +examples_and_unit_tests: + include: + - 'test_apps/' + 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 diff --git a/examples/TestFunctions/TestFunctions.ino b/examples/TestFunctions/TestFunctions.ino new file mode 100644 index 0000000..cac076b --- /dev/null +++ b/examples/TestFunctions/TestFunctions.ino @@ -0,0 +1,72 @@ +#include +#include + +#define EXAMPLE_I2C_NUM (0) +#define EXAMPLE_I2C_SDA_PIN (8) +#define EXAMPLE_I2C_SCL_PIN (18) + +/** + * Create an ESP_IOExpander object, Currently supports: + * - TCA95xx (8bit) + * - TCA95xx (16bit) + * - HT8574 + */ +ESP_IOExpander *expander = new ESP_IOExpander_TCA95xx_8bit(EXAMPLE_I2C_NUM, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN); + +void setup() +{ + Serial.begin(115200); + Serial.println("Test begin"); + + 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]); + + sleep(1); +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..a0eed8f --- /dev/null +++ b/library.properties @@ -0,0 +1,10 @@ +name=ESP32_IO_Expander +version=0.0.1 +author=Lzw655 +maintainer=lzw655 +sentence=ESP32_Display_Panel is a library designed for driving IO expander chips using ESP32 SoCs +paragraph=Currently support TCA95xx(8bit), TCA95xx(16bit), HT8574 +category=Other +architectures=esp32 +url=https://github.com/esp-arduino-libs/ESP32_IO_Expander +includes=ESP_IOExpander_Library.h diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/license.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/ESP_IOExpander.cpp b/src/ESP_IOExpander.cpp new file mode 100644 index 0000000..f2e9a12 --- /dev/null +++ b/src/ESP_IOExpander.cpp @@ -0,0 +1,118 @@ +/* + * 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 new file mode 100644 index 0000000..8ca025c --- /dev/null +++ b/src/ESP_IOExpander.h @@ -0,0 +1,196 @@ +/* + * SPDX-FileCopyrightText: 2023 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. + * + */ +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 */ + +}; + +#endif diff --git a/src/ESP_IOExpander_Library.h b/src/ESP_IOExpander_Library.h new file mode 100644 index 0000000..5f6f56a --- /dev/null +++ b/src/ESP_IOExpander_Library.h @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ESP_IOEXPANDER_LIBRARY_H +#define ESP_IOEXPANDER_LIBRARY_H + +#include "ESP_IOExpander.h" + +#include "chip/TCA95xx_8bit.h" +#include "chip/TCA95xx_16bit.h" +#include "chip/HT8574.h" + +#endif diff --git a/src/base/esp_io_expander.c b/src/base/esp_io_expander.c new file mode 100644 index 0000000..3cf86a8 --- /dev/null +++ b/src/base/esp_io_expander.c @@ -0,0 +1,234 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#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" + +#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, + REG_OUTPUT, + REG_DIRECTION, +} reg_type_t; + +static const char *TAG = "io_expander"; + +static esp_err_t write_reg(esp_io_expander_handle_t handle, reg_type_t reg, uint32_t value); +static esp_err_t read_reg(esp_io_expander_handle_t handle, reg_type_t reg, uint32_t *value); + +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) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid handle"); + if (pin_num_mask >= BIT64(VALID_IO_COUNT(handle))) { + ESP_LOGW(TAG, "Pin num mask out of range, bit higher than %d won't work", VALID_IO_COUNT(handle) - 1); + } + + bool is_output = (direction == IO_EXPANDER_OUTPUT) ? true : false; + uint32_t dir_reg, temp; + ESP_RETURN_ON_ERROR(read_reg(handle, REG_DIRECTION, &dir_reg), TAG, "Read direction reg failed"); + temp = dir_reg; + if ((is_output && !handle->config.flags.dir_out_bit_zero) || (!is_output && handle->config.flags.dir_out_bit_zero)) { + /* 1. Output && Set 1 to output */ + /* 2. Input && Set 1 to input */ + dir_reg |= pin_num_mask; + } else { + /* 3. Output && Set 0 to output */ + /* 4. Input && Set 0 to input */ + dir_reg &= ~pin_num_mask; + } + /* Write to reg only when different */ + if (dir_reg != temp) { + ESP_RETURN_ON_ERROR(write_reg(handle, REG_DIRECTION, dir_reg), TAG, "Write direction reg failed"); + } + + return ESP_OK; +} + +esp_err_t esp_io_expander_set_level(esp_io_expander_handle_t handle, uint32_t pin_num_mask, uint8_t level) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid handle"); + if (pin_num_mask >= BIT64(VALID_IO_COUNT(handle))) { + ESP_LOGW(TAG, "Pin num mask out of range, bit higher than %d won't work", VALID_IO_COUNT(handle) - 1); + } + + uint32_t dir_reg, dir_bit; + ESP_RETURN_ON_ERROR(read_reg(handle, REG_DIRECTION, &dir_reg), TAG, "Read direction reg failed"); + + uint8_t io_count = VALID_IO_COUNT(handle); + /* Check every target pin's direction, must be in output mode */ + for (int i = 0; i < io_count; i++) { + if (pin_num_mask & BIT(i)) { + dir_bit = dir_reg & BIT(i); + /* Check whether it is in input mode */ + if ((dir_bit && handle->config.flags.dir_out_bit_zero) || (!dir_bit && !handle->config.flags.dir_out_bit_zero)) { + /* 1. 1 && Set 1 to input */ + /* 2. 0 && Set 0 to input */ + ESP_LOGE(TAG, "Pin[%d] can't set level in input mode", i); + return ESP_ERR_INVALID_STATE; + } + } + } + + uint32_t output_reg, temp; + /* Read the current output level */ + ESP_RETURN_ON_ERROR(read_reg(handle, REG_OUTPUT, &output_reg), TAG, "Read Output reg failed"); + temp = output_reg; + /* Set expected output level */ + if ((level && !handle->config.flags.output_high_bit_zero) || (!level && handle->config.flags.output_high_bit_zero)) { + /* 1. High level && Set 1 to output high */ + /* 2. Low level && Set 1 to output low */ + output_reg |= pin_num_mask; + } else { + /* 3. High level && Set 0 to output high */ + /* 4. Low level && Set 0 to output low */ + output_reg &= ~pin_num_mask; + } + /* Write to reg only when different */ + if (output_reg != temp) { + ESP_RETURN_ON_ERROR(write_reg(handle, REG_OUTPUT, output_reg), TAG, "Write Output reg failed"); + } + + return ESP_OK; +} + +esp_err_t esp_io_expander_get_level(esp_io_expander_handle_t handle, uint32_t pin_num_mask, uint32_t *level_mask) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid handle"); + ESP_RETURN_ON_FALSE(level_mask, ESP_ERR_INVALID_ARG, TAG, "Invalid level"); + if (pin_num_mask >= BIT64(VALID_IO_COUNT(handle))) { + ESP_LOGW(TAG, "Pin num mask out of range, bit higher than %d won't work", VALID_IO_COUNT(handle) - 1); + } + + uint32_t input_reg; + ESP_RETURN_ON_ERROR(read_reg(handle, REG_INPUT, &input_reg), TAG, "Read input reg failed"); + if (!handle->config.flags.input_high_bit_zero) { + /* Get 1 when input high level */ + *level_mask = input_reg & pin_num_mask; + } else { + /* Get 0 when input high level */ + *level_mask = ~input_reg & pin_num_mask; + } + + return ESP_OK; +} + +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"); + ESP_RETURN_ON_ERROR(read_reg(handle, REG_OUTPUT, &output_reg), TAG, "Read output reg failed"); + ESP_RETURN_ON_ERROR(read_reg(handle, REG_DIRECTION, &dir_reg), TAG, "Read direction reg failed"); + /* Get 1 if high level */ + if (handle->config.flags.input_high_bit_zero) { + input_reg ^= 0xffffffff; + } + /* Get 1 if high level */ + if (handle->config.flags.output_high_bit_zero) { + output_reg ^= 0xffffffff; + } + /* Get 1 if output */ + if (handle->config.flags.dir_out_bit_zero) { + dir_reg ^= 0xffffffff; + } + + for (int i = 0; i < io_count; i++) { + ESP_LOGI(TAG, "Index[%d] | Dir[%s] | In[%d] | Out[%d]", i, (dir_reg & BIT(i)) ? "Out" : "In", + (input_reg & BIT(i)) ? 1 : 0, (output_reg & BIT(i)) ? 1 : 0); + } + + return ESP_OK; +} + +esp_err_t esp_io_expander_reset(esp_io_expander_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid handle"); + ESP_RETURN_ON_FALSE(handle->reset, ESP_ERR_NOT_SUPPORTED, TAG, "reset isn't implemented"); + + return handle->reset(handle); +} + +esp_err_t esp_io_expander_del(esp_io_expander_handle_t handle) +{ + ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "Invalid handle"); + ESP_RETURN_ON_FALSE(handle->del, ESP_ERR_NOT_SUPPORTED, TAG, "del isn't implemented"); + + return handle->del(handle); +} + +/** + * @brief Write the value to a specific register + * + * @param handle: IO Expander handle + * @param reg: Specific type of register + * @param value: Expected register's value + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +static esp_err_t write_reg(esp_io_expander_handle_t handle, reg_type_t reg, uint32_t value) +{ + switch (reg) { + case REG_OUTPUT: + ESP_RETURN_ON_FALSE(handle->write_output_reg, ESP_ERR_NOT_SUPPORTED, TAG, "write_output_reg isn't implemented"); + return handle->write_output_reg(handle, value); + case REG_DIRECTION: + ESP_RETURN_ON_FALSE(handle->write_direction_reg, ESP_ERR_NOT_SUPPORTED, TAG, "write_direction_reg isn't implemented"); + return handle->write_direction_reg(handle, value); + default: + return ESP_ERR_NOT_SUPPORTED; + } + + return ESP_OK; +} + +/** + * @brief Read the value from a specific register + * + * @param handle: IO Expander handle + * @param reg: Specific type of register + * @param value: Actual register's value + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +static esp_err_t read_reg(esp_io_expander_handle_t handle, reg_type_t reg, uint32_t *value) +{ + ESP_RETURN_ON_FALSE(value, ESP_ERR_INVALID_ARG, TAG, "Invalid value"); + + switch (reg) { + case REG_INPUT: + ESP_RETURN_ON_FALSE(handle->read_input_reg, ESP_ERR_NOT_SUPPORTED, TAG, "read_input_reg isn't implemented"); + return handle->read_input_reg(handle, value); + case REG_OUTPUT: + ESP_RETURN_ON_FALSE(handle->read_output_reg, ESP_ERR_NOT_SUPPORTED, TAG, "read_output_reg isn't implemented"); + return handle->read_output_reg(handle, value); + case REG_DIRECTION: + ESP_RETURN_ON_FALSE(handle->read_direction_reg, ESP_ERR_NOT_SUPPORTED, TAG, "read_direction_reg isn't implemented"); + return handle->read_direction_reg(handle, value); + default: + return ESP_ERR_NOT_SUPPORTED; + } + + return ESP_OK; +} diff --git a/src/base/esp_io_expander.h b/src/base/esp_io_expander.h new file mode 100644 index 0000000..bd6d71c --- /dev/null +++ b/src/base/esp_io_expander.h @@ -0,0 +1,265 @@ +/* + * SPDX-FileCopyrightText: 2023 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 + * + */ +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); + +/** + * @brief Delete device + * + * @param handle: IO Expander handle + * + * @return + * - ESP_OK: Success, otherwise returns ESP_ERR_xxx + */ +esp_err_t esp_io_expander_del(esp_io_expander_handle_t handle); + +#ifdef __cplusplus +} +#endif diff --git a/src/chip/HT8574.cpp b/src/chip/HT8574.cpp new file mode 100644 index 0000000..9a82d88 --- /dev/null +++ b/src/chip/HT8574.cpp @@ -0,0 +1,164 @@ +/* + * 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 "HT8574.h" + +/* Timeout of each I2C communication */ +#define I2C_TIMEOUT_MS (10) + +#define IO_COUNT (8) + +/* 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_ht8574_t; + +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); +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_ht8574(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_ht8574_t *ht8574 = (esp_io_expander_ht8574_t *)calloc(1, sizeof(esp_io_expander_ht8574_t)); + ESP_RETURN_ON_FALSE(ht8574, ESP_ERR_NO_MEM, TAG, "Malloc failed"); + + ht8574->base.config.io_count = IO_COUNT; + 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; + ht8574->base.write_direction_reg = write_direction_reg; + ht8574->base.read_direction_reg = read_direction_reg; + ht8574->base.del = del; + ht8574->base.reset = reset; + + esp_err_t ret = ESP_OK; + /* Reset configuration and register status */ + ESP_GOTO_ON_ERROR(reset(&ht8574->base), err, TAG, "Reset failed"); + + *handle = &ht8574->base; + return ESP_OK; +err: + free(ht8574); + return ret; +} + +static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value) +{ + esp_io_expander_ht8574_t *ht8574 = (esp_io_expander_ht8574_t *)__containerof(handle, esp_io_expander_ht8574_t, base); + + uint8_t temp = 0; + // *INDENT-OFF* + ESP_RETURN_ON_ERROR( + i2c_master_read_from_device(ht8574->i2c_num, ht8574->i2c_address, &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_ht8574_t *ht8574 = (esp_io_expander_ht8574_t *)__containerof(handle, esp_io_expander_ht8574_t, base); + value &= 0xff; + + uint8_t data = (uint8_t)value; + ESP_RETURN_ON_ERROR( + i2c_master_write_to_device(ht8574->i2c_num, ht8574->i2c_address, &data, 1, pdMS_TO_TICKS(I2C_TIMEOUT_MS)), + TAG, "Write output reg failed"); + ht8574->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_ht8574_t *ht8574 = (esp_io_expander_ht8574_t *)__containerof(handle, esp_io_expander_ht8574_t, base); + + *value = ht8574->regs.output; + return ESP_OK; +} + +static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value) +{ + esp_io_expander_ht8574_t *ht8574 = (esp_io_expander_ht8574_t *)__containerof(handle, esp_io_expander_ht8574_t, base); + value &= 0xff; + ht8574->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_ht8574_t *ht8574 = (esp_io_expander_ht8574_t *)__containerof(handle, esp_io_expander_ht8574_t, base); + + *value = ht8574->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_ht8574_t *ht8574 = (esp_io_expander_ht8574_t *)__containerof(handle, esp_io_expander_ht8574_t, base); + + free(ht8574); + return ESP_OK; +} diff --git a/src/chip/HT8574.h b/src/chip/HT8574.h new file mode 100644 index 0000000..54f8840 --- /dev/null +++ b/src/chip/HT8574.h @@ -0,0 +1,89 @@ +/* + * 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_16bit.cpp b/src/chip/TCA95xx_16bit.cpp new file mode 100644 index 0000000..7c73d61 --- /dev/null +++ b/src/chip/TCA95xx_16bit.cpp @@ -0,0 +1,175 @@ +/* + * 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_16bit.h" + +/* Timeout of each I2C communication */ +#define I2C_TIMEOUT_MS (10) + +#define IO_COUNT (16) + +/* Register address */ +#define INPUT_REG_ADDR (0x00) +#define OUTPUT_REG_ADDR (0x02) +#define DIRECTION_REG_ADDR (0x06) + +/* Default register value on power-up */ +#define DIR_REG_DEFAULT_VAL (0xffff) +#define OUT_REG_DEFAULT_VAL (0xffff) + +/** + * @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_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 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_16bit(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_16bit_t *tca = (esp_io_expander_tca95xx_16bit_t *)calloc(1, sizeof(esp_io_expander_tca95xx_16bit_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_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)), + TAG, "Read input reg failed"); + // *INDENT-ON* + *value = (((uint32_t)temp[0]) << 8) | (temp[1]); + return ESP_OK; +} + +static esp_err_t write_output_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); + value &= 0xffff; + + uint8_t data[] = {OUTPUT_REG_ADDR, (uint8_t)(value >> 8), (uint8_t)(value & 0xff)}; + 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_16bit_t *tca = (esp_io_expander_tca95xx_16bit_t *)__containerof(handle, esp_io_expander_tca95xx_16bit_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_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)}; + 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_16bit_t *tca = (esp_io_expander_tca95xx_16bit_t *)__containerof(handle, esp_io_expander_tca95xx_16bit_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_16bit_t *tca = (esp_io_expander_tca95xx_16bit_t *)__containerof(handle, esp_io_expander_tca95xx_16bit_t, base); + + free(tca); + return ESP_OK; +} diff --git a/src/chip/TCA95xx_16bit.h b/src/chip/TCA95xx_16bit.h new file mode 100644 index 0000000..795f2b4 --- /dev/null +++ b/src/chip/TCA95xx_16bit.h @@ -0,0 +1,108 @@ +/* + * 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_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) { }; + + /** + * @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) { }; + + /** + * @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 I2C address of the TCA9539 or TCA9555 + * + * The 8-bit address format for the TCA9539 is as follows: + * + * (Slave Address) + * ┌─────────────────┷─────────────────┐ + * ┌─────┐─────┐─────┐─────┐─────┐─────┐─────┐─────┐ + * | 1 | 1 | 1 | 0 | 1 | A1 | A0 | R/W | + * └─────┘─────┘─────┘─────┘─────┘─────┘─────┘─────┘ + * └────────┯──────────────┘ └──┯──┘ + * (Fixed) (Hareware Selectable) + * + * The 8-bit address format for the TCA9555 is as follows: + * + * (Slave Address) + * ┌─────────────────┷─────────────────┐ + * ┌─────┐─────┐─────┐─────┐─────┐─────┐─────┐─────┐ + * | 0 | 1 | 0 | 0 | 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 TCA9555 chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0b0100000. + * Then users can use `ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000` to init it. + */ +enum esp_io_expander_tca_95xx_16bit_address { + ESP_IO_EXPANDER_I2C_TCA9539_ADDRESS_00 = 0b1110100, + ESP_IO_EXPANDER_I2C_TCA9539_ADDRESS_01 = 0b1110101, + ESP_IO_EXPANDER_I2C_TCA9539_ADDRESS_10 = 0b1110110, + ESP_IO_EXPANDER_I2C_TCA9539_ADDRESS_11 = 0b1110111, + ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000 = 0b0100000, + ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_001 = 0b0100001, + ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_010 = 0b0100010, + ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_011 = 0b0100011, + ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_100 = 0b0100000, + ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_101 = 0b0100101, + ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_110 = 0b0100110, + ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_111 = 0b0100111, +}; diff --git a/src/chip/TCA95xx_8bit.cpp b/src/chip/TCA95xx_8bit.cpp new file mode 100644 index 0000000..3312d31 --- /dev/null +++ b/src/chip/TCA95xx_8bit.cpp @@ -0,0 +1,175 @@ +/* + * 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/TCA95xx_8bit.h b/src/chip/TCA95xx_8bit.h new file mode 100644 index 0000000..db061b5 --- /dev/null +++ b/src/chip/TCA95xx_8bit.h @@ -0,0 +1,119 @@ +/* + * 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_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) { }; + + /** + * @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) { }; + + /** + * @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 I2C address of the TCA9554 + * + * The 8-bit address format is as follows: + * + * (Slave Address) + * ┌─────────────────┷─────────────────┐ + * ┌─────┐─────┐─────┐─────┐─────┐─────┐─────┐─────┐ + * | 0 | 1 | 0 | 0 | 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 0100000b(0x20). + * Then users can use `ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000` to init it. + */ +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 (0x20) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_001 (0x21) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_010 (0x22) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_011 (0x23) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_100 (0x24) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_101 (0x25) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_110 (0x26) +#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_111 (0x27) + + +/** + * @brief I2C address of the TCA9554A + * + * 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_TCA9554A_ADDRESS_000` to init it. + */ +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000 (0x38) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_001 (0x39) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_010 (0x3A) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_011 (0x3B) +#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_100 (0x3C) +#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) diff --git a/src/private/CheckResult.cpp b/src/private/CheckResult.cpp new file mode 100644 index 0000000..da17b7f --- /dev/null +++ b/src/private/CheckResult.cpp @@ -0,0 +1,26 @@ +/* + * 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 new file mode 100644 index 0000000..18183c6 --- /dev/null +++ b/src/private/CheckResult.h @@ -0,0 +1,68 @@ +/* + * 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 new file mode 100644 index 0000000..19ad7fd --- /dev/null +++ b/test_apps/CMakeLists.txt @@ -0,0 +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" "../") +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(panel_io_3wire_spi_test) \ No newline at end of file diff --git a/test_apps/main/CMakeLists.txt b/test_apps/main/CMakeLists.txt new file mode 100644 index 0000000..e06a302 --- /dev/null +++ b/test_apps/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "test_ESP_IOExpander.cpp") + +target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-missing-field-initializers) diff --git a/test_apps/main/idf_component.yml b/test_apps/main/idf_component.yml new file mode 100644 index 0000000..29cf227 --- /dev/null +++ b/test_apps/main/idf_component.yml @@ -0,0 +1,3 @@ +## IDF Component Manager Manifest File +dependencies: + idf: ">=4.4" \ No newline at end of file diff --git a/test_apps/main/test_ESP_IOExpander.cpp b/test_apps/main/test_ESP_IOExpander.cpp new file mode 100644 index 0000000..5d9d87a --- /dev/null +++ b/test_apps/main/test_ESP_IOExpander.cpp @@ -0,0 +1,142 @@ +/* + * 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/pytest_esp_io_expander.py b/test_apps/pytest_esp_io_expander.py new file mode 100644 index 0000000..7c39966 --- /dev/null +++ b/test_apps/pytest_esp_io_expander.py @@ -0,0 +1,11 @@ +# 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.defaults b/test_apps/sdkconfig.defaults new file mode 100644 index 0000000..1e8cdb8 --- /dev/null +++ b/test_apps/sdkconfig.defaults @@ -0,0 +1,10 @@ +# 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