├── .editorconfig ├── .github └── workflows │ ├── build_examples.yml │ ├── component_registry.yml │ ├── test_host.yml │ └── test_target.yml ├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── esp_event_api.cpp ├── esp_event_cxx.cpp ├── esp_exception.cpp ├── esp_timer_cxx.cpp ├── examples ├── blink_cxx │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ └── main.cpp │ └── sdkconfig.defaults ├── esp_event_async_cxx │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── esp_event_async_cxx_example.cpp │ │ └── idf_component.yml │ └── sdkconfig.defaults ├── esp_timer_cxx │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── esp_timer_example.cpp │ │ └── idf_component.yml │ └── sdkconfig.defaults ├── simple_i2c_rw_example │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── Kconfig.projbuild │ │ ├── idf_component.yml │ │ └── simple_i2c_rw_example.cpp │ └── sdkconfig.defaults └── simple_spi_rw_example │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ ├── CMakeLists.txt │ ├── Kconfig.projbuild │ ├── idf_component.yml │ └── simple_spi_rw_example.cpp │ └── sdkconfig.defaults ├── gpio_cxx.cpp ├── host_test ├── esp_timer │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── esp_timer_test.cpp │ │ └── idf_component.yml │ └── sdkconfig.defaults ├── fixtures │ └── test_fixtures.hpp ├── gpio │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── gpio_cxx_test.cpp │ │ └── idf_component.yml │ └── sdkconfig.defaults ├── i2c │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── i2c_cxx_test.cpp │ │ └── idf_component.yml │ └── sdkconfig.defaults ├── spi │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ │ ├── CMakeLists.txt │ │ ├── idf_component.yml │ │ └── spi_cxx_test.cpp │ └── sdkconfig.defaults └── system │ ├── CMakeLists.txt │ ├── README.md │ ├── main │ ├── CMakeLists.txt │ ├── idf_component.yml │ └── system_cxx_test.cpp │ └── sdkconfig.defaults ├── i2c_cxx.cpp ├── idf_component.yml ├── include ├── esp_event_api.hpp ├── esp_event_cxx.hpp ├── esp_exception.hpp ├── esp_timer_cxx.hpp ├── gpio_cxx.hpp ├── i2c_cxx.hpp ├── spi_cxx.hpp ├── spi_host_cxx.hpp └── system_cxx.hpp ├── private_include └── spi_host_private_cxx.hpp ├── spi_cxx.cpp ├── spi_host_cxx.cpp └── test_apps ├── cxx_exception ├── CMakeLists.txt ├── README.md ├── main │ ├── CMakeLists.txt │ ├── cxx_exception.cpp │ └── idf_component.yml ├── sdkconfig.defaults └── test_cxx_exception.py ├── esp_event ├── CMakeLists.txt ├── main │ ├── CMakeLists.txt │ ├── idf_component.yml │ └── test_esp_event.cpp ├── sdkconfig.defaults └── test_cxx_esp_event.py ├── esp_timer ├── CMakeLists.txt ├── main │ ├── CMakeLists.txt │ ├── esp_timer_test.cpp │ └── idf_component.yml ├── sdkconfig.defaults ├── test_app_results_esp_event_esp32_latest.xml └── test_esp_timer.py └── include ├── unity_cxx.hpp └── utils_cxx.hpp /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [{*.md,*.rst}] 16 | trim_trailing_whitespace = false 17 | 18 | [*.py] 19 | max_line_length = 119 20 | 21 | [{*.cmake,CMakeLists.txt}] 22 | indent_style = space 23 | indent_size = 4 24 | max_line_length = 120 25 | 26 | [{*.sh,*.yml,*.yaml}] 27 | indent_size = 2 28 | -------------------------------------------------------------------------------- /.github/workflows/build_examples.yml: -------------------------------------------------------------------------------- 1 | name: Build Examples 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' # Once per day at midnight 6 | pull_request: 7 | types: [opened, reopened, synchronize] 8 | 9 | jobs: 10 | host_test: 11 | strategy: 12 | matrix: 13 | app_name: [blink_cxx, simple_i2c_rw_example, esp_event_async_cxx, esp_timer_cxx, simple_spi_rw_example] 14 | name: Build 15 | runs-on: ubuntu-20.04 16 | container: espressif/idf:release-v5.0 17 | steps: 18 | - name: Checkout esp-idf-cxx 19 | uses: actions/checkout@master 20 | 21 | - name: Build ${{ matrix.app_name }} 22 | shell: bash 23 | run: | 24 | apt-get update && apt-get install -y gcc-8 g++-8 25 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8 26 | . ${IDF_PATH}/export.sh 27 | cd $GITHUB_WORKSPACE/examples/${{ matrix.app_name }} 28 | idf.py build 29 | -------------------------------------------------------------------------------- /.github/workflows/component_registry.yml: -------------------------------------------------------------------------------- 1 | name: Push component to https://components.espressif.com 2 | # on: [push, pull_request] 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | jobs: 8 | upload_components: 9 | runs-on: ubuntu-20.04 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | submodules: "recursive" 14 | 15 | - name: Upload component to the component registry 16 | uses: espressif/upload-components-ci-action@v1 17 | with: 18 | name: "esp-idf-cxx" 19 | version: ${{ github.ref_name }} 20 | namespace: "espressif" 21 | api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test_host.yml: -------------------------------------------------------------------------------- 1 | name: Host test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | host_test: 7 | strategy: 8 | matrix: 9 | app_name: [esp_timer, gpio, i2c, spi, system] 10 | name: Build and test 11 | runs-on: ubuntu-20.04 12 | container: espressif/idf:release-v5.0 13 | steps: 14 | - name: Checkout esp-idf-cxx 15 | uses: actions/checkout@master 16 | 17 | - name: Build and Test 18 | shell: bash 19 | run: | 20 | apt-get update && apt-get install -y gcc-8 g++-8 ruby 21 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8 22 | . ${IDF_PATH}/export.sh 23 | cd $GITHUB_WORKSPACE/host_test/${{ matrix.app_name }} 24 | idf.py build 25 | ./build/test_${{ matrix.app_name }}_cxx_host.elf 26 | -------------------------------------------------------------------------------- /.github/workflows/test_target.yml: -------------------------------------------------------------------------------- 1 | name: Target test 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' # Once per day at midnight 6 | pull_request: 7 | types: [opened, reopened, synchronize] 8 | 9 | jobs: 10 | build: 11 | name: Build Test App 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | idf_ver: ["latest"] 16 | idf_target: ["esp32", "esp32c3"] 17 | test_app_name: ["esp_event", "esp_timer", "cxx_exception"] 18 | runs-on: ubuntu-20.04 19 | container: espressif/idf:${{ matrix.idf_ver }} 20 | steps: 21 | - uses: actions/checkout@v1 22 | with: 23 | submodules: 'true' 24 | - name: Build for ${{ matrix.idf_target }} 25 | env: 26 | IDF_TARGET: ${{ matrix.idf_target }} 27 | shell: bash 28 | working-directory: test_apps/${{ matrix.test_app_name }} 29 | run: | 30 | . ${IDF_PATH}/export.sh 31 | idf.py build 32 | - uses: actions/upload-artifact@v2 33 | with: 34 | name: test_app_bin_${{ matrix.test_app_name }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }} 35 | path: | 36 | test_apps/${{ matrix.test_app_name }}/build/bootloader/bootloader.bin 37 | test_apps/${{ matrix.test_app_name }}/build/partition_table/partition-table.bin 38 | test_apps/${{ matrix.test_app_name }}/build/test_${{ matrix.test_app_name }}.bin 39 | test_apps/${{ matrix.test_app_name }}/build/test_${{ matrix.test_app_name }}.elf 40 | test_apps/${{ matrix.test_app_name }}/build/flasher_args.json 41 | 42 | run-target: 43 | name: Run Test App on target 44 | needs: build 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | idf_ver: ["latest"] 49 | idf_target: ["esp32", "esp32c3"] 50 | test_app_name: ["esp_event", "esp_timer", "cxx_exception"] 51 | runs-on: [self-hosted, linux, docker, "${{ matrix.idf_target }}"] 52 | container: 53 | image: python:3.7-buster 54 | options: --privileged # Privileged mode has access to serial ports 55 | steps: 56 | - uses: actions/checkout@v3 57 | - uses: actions/download-artifact@v2 58 | with: 59 | name: test_app_bin_${{ matrix.test_app_name }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }} 60 | path: test_apps/${{ matrix.test_app_name }}/build 61 | - name: Install Python packages 62 | env: 63 | PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple" 64 | run: pip install --only-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf 65 | - name: Run Test App on ${{ matrix.idf_target }} 66 | working-directory: test_apps/${{ matrix.test_app_name }} 67 | run: pytest -s --junit-xml=./test_app_results_${{ matrix.test_app_name }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }}.xml --embedded-services esp,idf --target=${{ matrix.idf_target }} 68 | - uses: actions/upload-artifact@v2 69 | if: always() 70 | with: 71 | name: test_app_results_${{ matrix.test_app_name }}_${{ matrix.idf_target }}_${{ matrix.idf_ver }} 72 | path: test_apps/${{ matrix.test_app_name }}/*.xml 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .config 2 | *.o 3 | *.pyc 4 | 5 | # gtags 6 | GTAGS 7 | GRTAGS 8 | GPATH 9 | 10 | # emacs 11 | .dir-locals.el 12 | 13 | # emacs temp file suffixes 14 | *~ 15 | .#* 16 | \#*# 17 | 18 | # eclipse setting 19 | .settings 20 | 21 | # MacOS directory files 22 | .DS_Store 23 | 24 | # Example Apps files 25 | examples/**/build 26 | examples/**/sdkconfig 27 | examples/**/sdkconfig.old 28 | 29 | # Host Unit Test Apps files 30 | host_test/**/build 31 | host_test/**/sdkconfig 32 | host_test/**/sdkconfig.old 33 | 34 | # Unit test app build files 35 | test_apps/**/build 36 | test_apps/**/sdkconfig 37 | test_apps/**/sdkconfig.old 38 | 39 | # Unit Test CMake compile log folder 40 | log_ut_cmake 41 | 42 | TEST_LOGS 43 | 44 | # gcov coverage reports 45 | *.gcda 46 | *.gcno 47 | coverage.info 48 | coverage_report/ 49 | 50 | # VS Code Settings 51 | .vscode/ 52 | 53 | # VIM files 54 | *.swp 55 | *.swo 56 | 57 | # Clion IDE CMake build & config 58 | .idea/ 59 | cmake-build-*/ 60 | 61 | # Results for the checking of the Python coding style and static analysis 62 | .mypy_cache 63 | flake8_output.txt 64 | 65 | # lock files for tests 66 | dependencies.lock 67 | 68 | # managed_components for tests 69 | managed_components 70 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_build_get_property(target IDF_TARGET) 2 | 3 | set(srcs "esp_timer_cxx.cpp" "esp_exception.cpp" "gpio_cxx.cpp" "i2c_cxx.cpp" "spi_cxx.cpp" "spi_host_cxx.cpp") 4 | set(requires "esp_timer") 5 | 6 | if(NOT ${target} STREQUAL "linux") 7 | list(APPEND srcs 8 | "esp_event_api.cpp" 9 | "esp_event_cxx.cpp") 10 | list(APPEND requires "esp_event" "pthread") 11 | endif() 12 | 13 | idf_component_register(SRCS ${srcs} 14 | INCLUDE_DIRS "include" 15 | PRIV_INCLUDE_DIRS "private_include" 16 | PRIV_REQUIRES freertos driver 17 | REQUIRES ${requires}) 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ESP-IDF-CXX 2 | 3 | Contributions to ESP-IDF-CXX - fixing bugs, adding features, adding documentation - are welcome! We accept contributions via Github Pull Requests. 4 | 5 | Currently, we use the same contribution guidelines as ESP-IDF itself as basis. Please refer to the [ESP-IDF Contributions Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/contribute/index.html) for more information. However, the workflow is quite simplified, we only use github for collaboration. Furthermore, we use the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format for commits. There is an [editorconfig file](.editorconfig) to setup your editor or IDE with some basic options (tab-style, line ending, etc.). 6 | 7 | Please also consider the legal part: You will be required to sign the [Contributor Agreement](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/contribute/contributor-agreement.html) for any Pull Request. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP-IDF-C++ 2 | 3 | This project provides C++ wrapper classes around some components of [esp-idf](https://github.com/espressif/esp-idf). It is organized as a component for the [IDF component manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). You can find this component [in the component registry](https://components.espressif.com/components/espressif/esp-idf-cxx). 4 | 5 | ## *NOTE* 6 | This component is in a beta-release phase. Some bits that are still missing (non-exhaustive list): 7 | * MQTT C++ classes 8 | * Default pin definition on Kconfig for some examples 9 | 10 | A road map and detailed release document will be announced soon. 11 | 12 | ## Requirements 13 | 14 | * ESP-IDF and its requirements. 15 | Please follow the [ESP-IDF "Get Started" Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) to download, install and setup esp-idf. 16 | 17 | No other special requirements are necessary. 18 | 19 | ## Usage 20 | 21 | Set up the IDF environment (i.e., `. ./export.sh` inside [esp-idf](https://github.com/espressif/esp-idf)). Then go to your project directory, use `idf.py add-dependency espressif/esp-idf-cxx^1.0.0-beta ` (should only be done once) and you should be able to use this component. 22 | -------------------------------------------------------------------------------- /esp_event_api.cpp: -------------------------------------------------------------------------------- 1 | #include "esp_event.h" 2 | #include "esp_event_cxx.hpp" 3 | #include "esp_event_api.hpp" 4 | 5 | #ifdef __cpp_exceptions 6 | 7 | namespace idf { 8 | 9 | namespace event { 10 | 11 | ESPEventAPIDefault::ESPEventAPIDefault() 12 | { 13 | esp_err_t res = esp_event_loop_create_default(); 14 | if (res != ESP_OK) { 15 | throw idf::event::EventException(res); 16 | } 17 | } 18 | 19 | ESPEventAPIDefault::~ESPEventAPIDefault() 20 | { 21 | esp_event_loop_delete_default(); 22 | } 23 | 24 | esp_err_t ESPEventAPIDefault::handler_register(esp_event_base_t event_base, 25 | int32_t event_id, 26 | esp_event_handler_t event_handler, 27 | void *event_handler_arg, 28 | esp_event_handler_instance_t *instance) 29 | { 30 | return esp_event_handler_instance_register(event_base, 31 | event_id, 32 | event_handler, 33 | event_handler_arg, 34 | instance); 35 | } 36 | 37 | esp_err_t ESPEventAPIDefault::handler_unregister(esp_event_base_t event_base, 38 | int32_t event_id, 39 | esp_event_handler_instance_t instance) 40 | { 41 | return esp_event_handler_instance_unregister(event_base, event_id, instance); 42 | } 43 | 44 | esp_err_t ESPEventAPIDefault::post(esp_event_base_t event_base, 45 | int32_t event_id, 46 | void* event_data, 47 | size_t event_data_size, 48 | TickType_t ticks_to_wait) 49 | { 50 | return esp_event_post(event_base, 51 | event_id, 52 | event_data, 53 | event_data_size, 54 | ticks_to_wait); 55 | 56 | } 57 | 58 | ESPEventAPICustom::ESPEventAPICustom(const esp_event_loop_args_t &event_loop_args) 59 | { 60 | esp_err_t res = esp_event_loop_create(&event_loop_args, &event_loop); 61 | if (res != ESP_OK) { 62 | throw idf::event::EventException(res); 63 | } 64 | } 65 | 66 | ESPEventAPICustom::~ESPEventAPICustom() 67 | { 68 | esp_event_loop_delete(event_loop); 69 | } 70 | 71 | esp_err_t ESPEventAPICustom::handler_register(esp_event_base_t event_base, 72 | int32_t event_id, 73 | esp_event_handler_t event_handler, 74 | void *event_handler_arg, 75 | esp_event_handler_instance_t *instance) 76 | { 77 | return esp_event_handler_instance_register_with(event_loop, 78 | event_base, 79 | event_id, 80 | event_handler, 81 | event_handler_arg, 82 | instance); 83 | } 84 | 85 | esp_err_t ESPEventAPICustom::handler_unregister(esp_event_base_t event_base, 86 | int32_t event_id, 87 | esp_event_handler_instance_t instance) 88 | { 89 | return esp_event_handler_instance_unregister_with(event_loop, event_base, event_id, instance); 90 | } 91 | 92 | esp_err_t ESPEventAPICustom::post(esp_event_base_t event_base, 93 | int32_t event_id, 94 | void* event_data, 95 | size_t event_data_size, 96 | TickType_t ticks_to_wait) 97 | { 98 | return esp_event_post_to(event_loop, 99 | event_base, 100 | event_id, 101 | event_data, 102 | event_data_size, 103 | ticks_to_wait); 104 | } 105 | 106 | esp_err_t ESPEventAPICustom::run(TickType_t ticks_to_run) 107 | { 108 | return esp_event_loop_run(event_loop, ticks_to_run); 109 | } 110 | 111 | } // event 112 | 113 | } // idf 114 | 115 | #endif // __cpp_exceptions 116 | -------------------------------------------------------------------------------- /esp_event_cxx.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "esp_event_cxx.hpp" 8 | 9 | #ifdef __cpp_exceptions 10 | 11 | using namespace idf::event; 12 | using namespace std; 13 | 14 | namespace idf { 15 | 16 | namespace event { 17 | 18 | const std::chrono::milliseconds PLATFORM_MAX_DELAY_MS(portMAX_DELAY *portTICK_PERIOD_MS); 19 | 20 | ESPEventReg::ESPEventReg(std::function cb, 21 | const ESPEvent& ev, 22 | std::shared_ptr api) 23 | : cb(cb), event(ev), api(api) 24 | { 25 | if (!cb) throw EventException(ESP_ERR_INVALID_ARG); 26 | if (!api) throw EventException(ESP_ERR_INVALID_ARG); 27 | 28 | esp_err_t reg_result = api->handler_register(ev.base, ev.id.get_id(), event_handler_hook, this, &instance); 29 | if (reg_result != ESP_OK) { 30 | throw ESPEventRegisterException(reg_result, event); 31 | } 32 | } 33 | 34 | ESPEventReg::~ESPEventReg() 35 | { 36 | api->handler_unregister(event.base, event.id.get_id(), instance); 37 | } 38 | 39 | void ESPEventReg::dispatch_event_handling(ESPEvent event, void *event_data) 40 | { 41 | cb(event, event_data); 42 | } 43 | 44 | void ESPEventReg::event_handler_hook(void *handler_arg, 45 | esp_event_base_t event_base, 46 | int32_t event_id, 47 | void *event_data) 48 | { 49 | ESPEventReg *object = static_cast(handler_arg); 50 | object->dispatch_event_handling(ESPEvent(event_base, ESPEventID(event_id)), event_data); 51 | } 52 | 53 | ESPEventRegTimed::ESPEventRegTimed(std::function cb, 54 | const ESPEvent& ev, 55 | std::function timeout_cb, 56 | const std::chrono::microseconds &timeout, 57 | std::shared_ptr api) 58 | : ESPEventReg(cb, ev, api), timeout_cb(timeout_cb) 59 | { 60 | if (!timeout_cb || timeout < MIN_TIMEOUT) { 61 | throw EventException(ESP_ERR_INVALID_ARG); 62 | } 63 | 64 | const esp_timer_create_args_t oneshot_timer_args { 65 | timer_cb_hook, 66 | static_cast(this), 67 | ESP_TIMER_TASK, 68 | "event", 69 | false // skip_unhandled_events 70 | }; 71 | 72 | esp_err_t res = esp_timer_create(&oneshot_timer_args, &timer); 73 | if (res != ESP_OK) { 74 | throw EventException(res); 75 | } 76 | 77 | esp_err_t timer_result = esp_timer_start_once(timer, timeout.count()); 78 | if (timer_result != ESP_OK) { 79 | esp_timer_delete(timer); 80 | throw EventException(timer_result); 81 | } 82 | } 83 | 84 | ESPEventRegTimed::~ESPEventRegTimed() 85 | { 86 | std::lock_guard guard(timeout_mutex); 87 | esp_timer_stop(timer); 88 | esp_timer_delete(timer); 89 | // TODO: is it guaranteed that there is no pending timer callback for timer? 90 | } 91 | 92 | void ESPEventRegTimed::dispatch_event_handling(ESPEvent event, void *event_data) 93 | { 94 | if (timeout_mutex.try_lock()) { 95 | esp_timer_stop(timer); 96 | cb(event, event_data); 97 | timeout_mutex.unlock(); 98 | } 99 | } 100 | 101 | void ESPEventRegTimed::timer_cb_hook(void *arg) 102 | { 103 | ESPEventRegTimed *object = static_cast(arg); 104 | if (object->timeout_mutex.try_lock()) { 105 | object->timeout_cb(object->event); 106 | object->api->handler_unregister(object->event.base, object->event.id.get_id(), object->instance); 107 | object->timeout_mutex.unlock(); 108 | } 109 | } 110 | 111 | ESPEventLoop::ESPEventLoop(std::shared_ptr api) : api(api) { 112 | if (!api) throw EventException(ESP_ERR_INVALID_ARG); 113 | } 114 | 115 | ESPEventLoop::~ESPEventLoop() { } 116 | 117 | unique_ptr ESPEventLoop::register_event(const ESPEvent &event, 118 | function cb) 119 | { 120 | return unique_ptr(new ESPEventReg(cb, event, api)); 121 | } 122 | 123 | std::unique_ptr ESPEventLoop::register_event_timed(const ESPEvent &event, 124 | std::function cb, 125 | const std::chrono::microseconds &timeout, 126 | std::function timer_cb) 127 | { 128 | return std::unique_ptr(new ESPEventRegTimed(cb, event, timer_cb, timeout, api)); 129 | } 130 | 131 | void ESPEventLoop::post_event_data(const ESPEvent &event, 132 | const chrono::milliseconds &wait_time) 133 | { 134 | esp_err_t result = api->post(event.base, 135 | event.id.get_id(), 136 | nullptr, 137 | 0, 138 | convert_ms_to_ticks(wait_time)); 139 | 140 | if (result != ESP_OK) { 141 | throw ESPException(result); 142 | } 143 | } 144 | 145 | TickType_t convert_ms_to_ticks(const std::chrono::milliseconds &time) 146 | { 147 | return time.count() / portTICK_PERIOD_MS; 148 | } 149 | 150 | } // namespace event 151 | 152 | } // namespace idf 153 | 154 | #endif // __cpp_exceptions 155 | -------------------------------------------------------------------------------- /esp_exception.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifdef __cpp_exceptions 8 | 9 | #include "esp_exception.hpp" 10 | 11 | namespace idf { 12 | 13 | ESPException::ESPException(esp_err_t error) : error(error) { } 14 | 15 | const char *ESPException::what() const noexcept { 16 | return esp_err_to_name(error); 17 | } 18 | 19 | } // namespace idf 20 | 21 | #endif // __cpp_exceptions 22 | -------------------------------------------------------------------------------- /esp_timer_cxx.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifdef __cpp_exceptions 8 | 9 | #include 10 | #include "esp_timer_cxx.hpp" 11 | #include "esp_exception.hpp" 12 | 13 | using namespace std; 14 | 15 | namespace idf { 16 | 17 | namespace esp_timer { 18 | 19 | ESPTimer::ESPTimer(function timeout_cb, const string &timer_name) 20 | : timeout_cb(timeout_cb), name(timer_name) 21 | { 22 | if (timeout_cb == nullptr) { 23 | throw ESPException(ESP_ERR_INVALID_ARG); 24 | } 25 | 26 | esp_timer_create_args_t timer_args = {}; 27 | timer_args.callback = esp_timer_cb; 28 | timer_args.arg = this; 29 | timer_args.dispatch_method = ESP_TIMER_TASK; 30 | timer_args.name = name.c_str(); 31 | 32 | CHECK_THROW(esp_timer_create(&timer_args, &timer_handle)); 33 | } 34 | 35 | ESPTimer::~ESPTimer() 36 | { 37 | // Ignore potential ESP_ERR_INVALID_STATE here to not throw exception. 38 | esp_timer_stop(timer_handle); 39 | esp_timer_delete(timer_handle); 40 | } 41 | 42 | void ESPTimer::esp_timer_cb(void *arg) 43 | { 44 | ESPTimer *timer = static_cast(arg); 45 | timer->timeout_cb(); 46 | } 47 | 48 | } // esp_timer 49 | 50 | } // idf 51 | 52 | #endif // __cpp_exceptions 53 | -------------------------------------------------------------------------------- /examples/blink_cxx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | set(COMPONENTS main) 5 | project(blink_cxx) 6 | -------------------------------------------------------------------------------- /examples/blink_cxx/README.md: -------------------------------------------------------------------------------- 1 | # Example: Blink C++ example 2 | 3 | (See the README.md file in the upper level 'examples' directory for more information about examples.) 4 | 5 | This example demonstrates usage of the `GPIO_Output` C++ class in ESP-IDF. 6 | 7 | In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option. 8 | This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling. 9 | This is necessary for the C++ APIs. 10 | 11 | ## How to use example 12 | 13 | ### Hardware Required 14 | 15 | Any ESP32 family development board. 16 | 17 | Connect an LED to the corresponding pin (default is pin 4). If the board has a normal LED already, you can use the pin number to which that one is connected. 18 | 19 | Development boards with an RGB LED that only has one data line like the ESP32-C3-DevKitC-02 and ESP32-C3-DevKitM-1 will not work. In this case, please connect an external normal LED to the chosen pin. 20 | 21 | ### Configure the project 22 | 23 | ``` 24 | idf.py menuconfig 25 | ``` 26 | 27 | ### Build and Flash 28 | 29 | ``` 30 | idf.py -p PORT flash monitor 31 | ``` 32 | 33 | (Replace PORT with the name of the serial port.) 34 | 35 | (To exit the serial monitor, type ``Ctrl-]``.) 36 | 37 | See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. 38 | 39 | ## Example Output 40 | 41 | ``` 42 | ... 43 | I (339) cpu_start: Starting scheduler. 44 | I (343) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 45 | LED ON 46 | LED OFF 47 | LED ON 48 | LED OFF 49 | LED ON 50 | LED OFF 51 | LED ON 52 | LED OFF 53 | LED ON 54 | LED OFF 55 | 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /examples/blink_cxx/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "main.cpp" 2 | INCLUDE_DIRS ".") 3 | -------------------------------------------------------------------------------- /examples/blink_cxx/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | espressif/esp-idf-cxx: 5 | override_path: ../../../ 6 | version: "^1.0.0" 7 | -------------------------------------------------------------------------------- /examples/blink_cxx/main/main.cpp: -------------------------------------------------------------------------------- 1 | /* Blink C++ Example 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | */ 9 | 10 | #include 11 | #include 12 | #include "esp_log.h" 13 | #include "gpio_cxx.hpp" 14 | 15 | using namespace idf; 16 | using namespace std; 17 | 18 | extern "C" void app_main(void) 19 | { 20 | /* The functions of GPIO_Output throws exceptions in case of parameter errors or if there are underlying driver 21 | errors. */ 22 | try { 23 | /* This line may throw an exception if the pin number is invalid. 24 | * Alternatively to 4, choose another output-capable pin. */ 25 | const GPIO_Output gpio(GPIONum(4)); 26 | 27 | while (true) { 28 | printf("LED ON\n"); 29 | gpio.set_high(); 30 | this_thread::sleep_for(std::chrono::seconds(1)); 31 | printf("LED OFF\n"); 32 | gpio.set_low(); 33 | this_thread::sleep_for(std::chrono::seconds(1)); 34 | } 35 | } catch (GPIOException &e) { 36 | printf("GPIO exception occurred: %s\n", esp_err_to_name(e.error)); 37 | printf("stopping.\n"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/blink_cxx/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Enable C++ exceptions and set emergency pool size for exception objects 2 | CONFIG_COMPILER_CXX_EXCEPTIONS=y 3 | CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 4 | -------------------------------------------------------------------------------- /examples/esp_event_async_cxx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | set(COMPONENTS main) 5 | project(esp_event_async_cxx) 6 | -------------------------------------------------------------------------------- /examples/esp_event_async_cxx/README.md: -------------------------------------------------------------------------------- 1 | # ESP-Event asynchronous example 2 | 3 | (See the README.md file in the upper level 'examples' directory for more information about examples.) 4 | 5 | 6 | ## How to use example 7 | 8 | ### Configure the project 9 | 10 | ``` 11 | idf.py menuconfig 12 | ``` 13 | 14 | * Set serial port under Serial Flasher Options. 15 | 16 | ### Build and Flash 17 | 18 | Build the project and flash it to the board, then run monitor tool to view serial output: 19 | 20 | ``` 21 | idf.py -p PORT flash monitor 22 | ``` 23 | 24 | (To exit the serial monitor, type ``Ctrl-]``.) 25 | 26 | See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. 27 | 28 | ## Example Output 29 | 30 | The object is created twice, hence the started Eventloop and finished destruction lines appear twice. 31 | ``` 32 | NORMAL TESTING... 33 | received event: test/0; data: 47 34 | received event: test/1 35 | 36 | TIMEOUT TESTING... 37 | received event: test/0 38 | 39 | TIMEOUT for event: test/0 40 | I (10419) ESP Event C++ Async: Finished example 41 | ``` 42 | -------------------------------------------------------------------------------- /examples/esp_event_async_cxx/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "esp_event_async_cxx_example.cpp" 2 | INCLUDE_DIRS ".") 3 | -------------------------------------------------------------------------------- /examples/esp_event_async_cxx/main/esp_event_async_cxx_example.cpp: -------------------------------------------------------------------------------- 1 | /* ESP Event C++ Example 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | */ 9 | 10 | #include 11 | #include "esp_event_cxx.hpp" 12 | #include "esp_event.h" 13 | #include "esp_err.h" 14 | 15 | using namespace idf::event; 16 | using namespace std; 17 | 18 | ESP_EVENT_DEFINE_BASE(TEST_EVENT_BASE); 19 | const ESPEventID TEST_EVENT_ID_0(0); 20 | const ESPEventID TEST_EVENT_ID_1(1); 21 | 22 | ESPEvent TEMPLATE_EVENT_0(TEST_EVENT_BASE, TEST_EVENT_ID_0); 23 | ESPEvent TEMPLATE_EVENT_1(TEST_EVENT_BASE, TEST_EVENT_ID_1); 24 | 25 | // We use "normal" static functions here. However, passing std::function types also works with 26 | // ESPEventLoop::register_event() and ESPEventLoop::register_event_timed(), allowing to reference custom data. 27 | static void callback(const ESPEvent &event, void *data) 28 | { 29 | cout << "received event: " << event.base << "/" << event.id; 30 | if (data) { 31 | cout << "; data: " << *(static_cast(data)); 32 | } 33 | cout << endl; 34 | }; 35 | 36 | static void timeout_callback(const ESPEvent &event) 37 | { 38 | cout << "TIMEOUT for event: " << event.base << "/" << event.id << endl; 39 | }; 40 | 41 | extern "C" void app_main(void) 42 | { 43 | { 44 | cout << "Normal testing..." << endl; 45 | ESPEventLoop loop; 46 | int data = 47; 47 | int captured_data = 42; 48 | 49 | unique_ptr reg_1 = loop.register_event(TEMPLATE_EVENT_0, 50 | [captured_data](const ESPEvent &event, void *data) { 51 | cout << "received event: " << event.base << "/" << event.id; 52 | if (data) { 53 | cout << "; event data: " << *(static_cast(data)); 54 | } 55 | cout << "; handler data: " << captured_data << endl; 56 | }); 57 | unique_ptr reg_2; 58 | 59 | // Run for 4 seconds... 60 | for (int i = 0; i < 4; i++) { 61 | switch (i) { 62 | case 0: 63 | // will be received 64 | loop.post_event_data(TEMPLATE_EVENT_0, data); 65 | break; 66 | case 1: 67 | // will NOT be received because TEST_EVENT_ID_1 hasn't been registered yet 68 | loop.post_event_data(TEMPLATE_EVENT_1); 69 | break; 70 | case 2: 71 | // register TEST_EVENT_ID_1 72 | reg_2 = loop.register_event(TEMPLATE_EVENT_1, callback); 73 | // will be received 74 | loop.post_event_data(TEMPLATE_EVENT_1); 75 | break; 76 | case 3: 77 | // unregister callback with TEST_EVENT_ID_1 again 78 | reg_2.reset(); 79 | // will NOT be received 80 | loop.post_event_data(TEMPLATE_EVENT_1); 81 | break; 82 | 83 | } 84 | this_thread::sleep_for(chrono::seconds(1)); 85 | } 86 | } 87 | 88 | { 89 | cout << endl << "Timeout testing..." << endl; 90 | ESPEventLoop loop; 91 | 92 | // Setting timeout and sending event early enough. 93 | unique_ptr timed_reg = loop.register_event_timed(TEMPLATE_EVENT_0, 94 | callback, 95 | chrono::milliseconds(500), 96 | timeout_callback); 97 | loop.post_event_data(TEMPLATE_EVENT_0); 98 | cout << endl; 99 | 100 | // Setting timeout and sending event too late. 101 | // Note: the old registration will be properly unregistered by resetting the unique_ptr. 102 | timed_reg = loop.register_event_timed(TEMPLATE_EVENT_0, 103 | callback, 104 | chrono::milliseconds(500), 105 | timeout_callback); 106 | this_thread::sleep_for(chrono::seconds(1)); 107 | loop.post_event_data(TEMPLATE_EVENT_0); 108 | 109 | } 110 | cout << "Finished example" << endl; 111 | } 112 | -------------------------------------------------------------------------------- /examples/esp_event_async_cxx/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | espressif/esp-idf-cxx: 5 | override_path: ../../../ 6 | version: "^1.0.0" 7 | -------------------------------------------------------------------------------- /examples/esp_event_async_cxx/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Enable C++ exceptions and set emergency pool size for exception objects 2 | CONFIG_COMPILER_CXX_EXCEPTIONS=y 3 | CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 4 | -------------------------------------------------------------------------------- /examples/esp_timer_cxx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | set(COMPONENTS main) 5 | project(esp_timer_cxx_example) 6 | -------------------------------------------------------------------------------- /examples/esp_timer_cxx/README.md: -------------------------------------------------------------------------------- 1 | # Example: ESPTimer C++ class 2 | 3 | (See the README.md file in the upper level 'examples' directory for more information about examples.) 4 | 5 | This example demonstrates usage of the ESPTimer c++ class in ESP-IDF. 6 | 7 | In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option. 8 | This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling. 9 | This is necessary for the C++ APIs. 10 | 11 | ## How to use example 12 | 13 | ### Hardware Required 14 | 15 | Any ESP32 family development board. 16 | 17 | ### Configure the project 18 | 19 | ``` 20 | idf.py menuconfig 21 | ``` 22 | 23 | ### Build and Flash 24 | 25 | ``` 26 | idf.py -p PORT flash monitor 27 | ``` 28 | 29 | (Replace PORT with the name of the serial port.) 30 | 31 | (To exit the serial monitor, type ``Ctrl-]``.) 32 | 33 | See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. 34 | 35 | ## Example Output 36 | 37 | ``` 38 | Setting up timer to trigger in 500ms 39 | timeout 40 | Setting up timer to periodically every 200ms 41 | periodic timeout 42 | periodic timeout 43 | periodic timeout 44 | periodic timeout 45 | periodic timeout 46 | Finished 47 | ``` 48 | -------------------------------------------------------------------------------- /examples/esp_timer_cxx/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "esp_timer_example.cpp" 2 | INCLUDE_DIRS ".") 3 | -------------------------------------------------------------------------------- /examples/esp_timer_cxx/main/esp_timer_example.cpp: -------------------------------------------------------------------------------- 1 | /* ESP Timer C++ Example 2 | 3 | This example code is in the Public Domain (or CC0 licensed, at your option.) 4 | 5 | Unless required by applicable law or agreed to in writing, this 6 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 7 | CONDITIONS OF ANY KIND, either express or implied. 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "esp_timer_cxx.hpp" 15 | #include "esp_exception.hpp" 16 | 17 | using namespace std; 18 | using namespace idf; 19 | using namespace idf::esp_timer; 20 | 21 | extern "C" void app_main(void) 22 | { 23 | try { 24 | printf("Setting up timer to trigger in 500ms\n"); 25 | ESPTimer timer([]() { printf("timeout\n"); }); 26 | timer.start(chrono::microseconds(200 * 1000)); 27 | 28 | this_thread::sleep_for(std::chrono::milliseconds(550)); 29 | 30 | printf("Setting up timer to trigger periodically every 200ms\n"); 31 | ESPTimer timer2([]() { printf("periodic timeout\n"); }); 32 | timer2.start_periodic(chrono::microseconds(200 * 1000)); 33 | 34 | this_thread::sleep_for(std::chrono::milliseconds(1050)); 35 | } catch (const ESPException &e) { 36 | printf("Exception with error: %d\n", e.error); 37 | } 38 | printf("Finished\n"); 39 | } 40 | -------------------------------------------------------------------------------- /examples/esp_timer_cxx/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | espressif/esp-idf-cxx: 5 | override_path: ../../../ 6 | version: "^1.0.0" 7 | -------------------------------------------------------------------------------- /examples/esp_timer_cxx/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Enable C++ exceptions and set emergency pool size for exception objects 2 | CONFIG_COMPILER_CXX_EXCEPTIONS=y 3 | CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 4 | -------------------------------------------------------------------------------- /examples/simple_i2c_rw_example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | set(COMPONENTS main) 5 | project(simple_i2c_rw_example) 6 | -------------------------------------------------------------------------------- /examples/simple_i2c_rw_example/README.md: -------------------------------------------------------------------------------- 1 | # Example: C++ I2C sensor read for MPU9250 2 | 3 | (See the README.md file in the upper level 'examples' directory for more information about examples.) 4 | 5 | This example demonstrates usage of C++ exceptions in ESP-IDF. It is the C++ equivalent to the [I2C Simple Example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/i2c/i2c_simple/) which is written in C. 6 | 7 | In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option. This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling. This is necessary for the C++ I2C API. 8 | 9 | ## How to Use This Example 10 | 11 | ### Hardware Required 12 | 13 | To run this example, you should have one ESP32, ESP32-S series or ESP32-C series based development board as well as an MPU9250. MPU9250 is an inertial measurement unit, which contains an accelerometer, gyroscope as well as a magnetometer, for more information about it, you can read the [datasheet of the MPU9250 sensor](https://invensense.tdk.com/wp-content/uploads/2015/02/PS-MPU-9250A-01-v1.1.pdf). 14 | 15 | #### Pin Assignment: 16 | 17 | **Note:** The following pin assignments are used by default, you can change these in the `menuconfig` . 18 | 19 | | | SDA | SCL | 20 | | ---------------- | -------------- | -------------- | 21 | | ESP I2C Master | I2C_MASTER_SDA | I2C_MASTER_SCL | 22 | | MPU9250 Sensor | SDA | SCL | 23 | 24 | 25 | For the actual default value of `I2C_MASTER_SDA` and `I2C_MASTER_SCL`, see `Example Configuration` in `menuconfig`. 26 | 27 | **Note:** There's no need to add external pull-up resistors for SDA/SCL pins, because the driver will enable the internal pull-up resistors. 28 | 29 | ### Configure the project 30 | 31 | ``` 32 | idf.py menuconfig 33 | ``` 34 | 35 | ### Build and Flash 36 | 37 | ``` 38 | idf.py -p flash monitor 39 | ``` 40 | 41 | Replace with the name of the serial port. To exit the serial monitor, type ``Ctrl-]``. 42 | 43 | See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. 44 | 45 | ## Example Output 46 | 47 | If the sensor is read correctly: 48 | 49 | ```bash 50 | I (328) i2c-simple-example: I2C initialized successfully 51 | I (338) i2c-simple-example: WHO_AM_I = 71 52 | I (338) i2c-simple-example: I2C de-initialized successfully 53 | ``` 54 | 55 | If something went wrong: 56 | ``` 57 | I2C Exception with error: ESP_FAIL (-1) 58 | Couldn't read sensor! 59 | ``` 60 | -------------------------------------------------------------------------------- /examples/simple_i2c_rw_example/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "simple_i2c_rw_example.cpp" 2 | INCLUDE_DIRS ".") 3 | -------------------------------------------------------------------------------- /examples/simple_i2c_rw_example/main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Example Configuration" 2 | 3 | config I2C_MASTER_SCL 4 | int "SCL GPIO Num" 5 | default 6 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 6 | default 5 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2 7 | default 19 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 8 | help 9 | GPIO number for I2C Master clock line. 10 | 11 | config I2C_MASTER_SDA 12 | int "SDA GPIO Num" 13 | default 5 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 14 | default 4 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2 15 | default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 16 | help 17 | GPIO number for I2C Master data line. 18 | 19 | endmenu 20 | -------------------------------------------------------------------------------- /examples/simple_i2c_rw_example/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | espressif/esp-idf-cxx: 5 | override_path: ../../../ 6 | version: "^1.0.0" 7 | -------------------------------------------------------------------------------- /examples/simple_i2c_rw_example/main/simple_i2c_rw_example.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Unlicense OR CC0-1.0 5 | * 6 | * MPU9250 I2C Sensor C++ Example 7 | * 8 | * This example code is in the Public Domain (or CC0 licensed, at your option.) 9 | * 10 | * Unless required by applicable law or agreed to in writing, this 11 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | * CONDITIONS OF ANY KIND, either express or implied. 13 | */ 14 | 15 | #include "esp_log.h" 16 | #include "i2c_cxx.hpp" 17 | 18 | using namespace std; 19 | using namespace idf; 20 | 21 | static const char *TAG = "i2c-cxx-simple-example"; 22 | 23 | constexpr I2CNumber I2C_MASTER_NUM(I2CNumber::I2C0()); /*!< I2C master i2c port number, the number of i2c peripheral 24 | interfaces available will depend on the chip */ 25 | #define I2C_MASTER_SCL_IO SCL_GPIO(CONFIG_I2C_MASTER_SCL) /*!< GPIO number used for I2C master clock */ 26 | #define I2C_MASTER_SDA_IO SDA_GPIO(CONFIG_I2C_MASTER_SDA) /*!< GPIO number used for I2C master data */ 27 | 28 | #define MPU9250_SENSOR_ADDR I2CAddress(0x68) /*!< Slave address of the MPU9250 sensor */ 29 | constexpr uint8_t MPU9250_WHO_AM_I_REG_ADDR = 0x75; /*!< Register addresses of the "who am I" register */ 30 | 31 | extern "C" void app_main(void) 32 | { 33 | try { 34 | // creating master bus 35 | shared_ptr master(new I2CMaster(I2C_MASTER_NUM, 36 | I2C_MASTER_SCL_IO, 37 | I2C_MASTER_SDA_IO, 38 | Frequency(400000))); 39 | ESP_LOGI(TAG, "I2C initialized successfully"); 40 | 41 | // writing the pointer to the WHO_AM_I register to the device 42 | master->sync_write(MPU9250_SENSOR_ADDR, {MPU9250_WHO_AM_I_REG_ADDR}); 43 | 44 | // reading back the value of WHO_AM_I register which should be 71 45 | vector data = master->sync_read(MPU9250_SENSOR_ADDR, 2); 46 | 47 | ESP_LOGI(TAG, "WHO_AM_I = %X", data[0]); 48 | } catch (const I2CException &e) { 49 | ESP_LOGI(TAG, "I2C Exception with error: %s (0x%X)", e.what(), e.error); 50 | ESP_LOGI(TAG, "Couldn't read sensor!"); 51 | } 52 | 53 | // The I2CMaster object is de-initialized in its destructor when going out of scope. 54 | ESP_LOGI(TAG, "I2C de-initialized successfully"); 55 | } 56 | -------------------------------------------------------------------------------- /examples/simple_i2c_rw_example/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Enable C++ exceptions and set emergency pool size for exception objects 2 | CONFIG_COMPILER_CXX_EXCEPTIONS=y 3 | CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 4 | -------------------------------------------------------------------------------- /examples/simple_spi_rw_example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's CMakeLists 2 | # in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.16) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | set(COMPONENTS main) 7 | project(simple_spi_rw_example) 8 | -------------------------------------------------------------------------------- /examples/simple_spi_rw_example/README.md: -------------------------------------------------------------------------------- 1 | # Example: C++ SPI sensor read for MCU9250 inertial/giroscope sensor 2 | 3 | (See the README.md file in the upper level 'examples' directory for more information about examples.) 4 | 5 | This example demonstrates usage of C++ SPI classes in ESP-IDF to read the `WHO_AM_I` register of the sensor. 6 | 7 | In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option. 8 | This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling. 9 | This is necessary for the C++ SPI API. 10 | 11 | ## How to use example 12 | 13 | ### Hardware Required 14 | 15 | An MCU9250 sensor and any commonly available ESP32 development board. 16 | 17 | ### Configure the project 18 | 19 | ``` 20 | idf.py menuconfig 21 | ``` 22 | 23 | ### Build and Flash 24 | 25 | ``` 26 | idf.py -p PORT flash monitor 27 | ``` 28 | 29 | (Replace PORT with the name of the serial port.) 30 | 31 | (To exit the serial monitor, type ``Ctrl-]``.) 32 | 33 | See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. 34 | 35 | ## Example Output 36 | 37 | If the sensor is read correctly: 38 | 39 | ``` 40 | ... 41 | I (0) cpu_start: Starting scheduler on APP CPU. 42 | Result of WHO_AM_I register: 0x71 43 | I (437) gpio: GPIO[23]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 44 | I (447) gpio: GPIO[25]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 45 | I (457) gpio: GPIO[26]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 46 | I (467) gpio: GPIO[27]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 47 | 48 | Done 49 | ``` 50 | 51 | If there's an error with the SPI peripheral: 52 | ``` 53 | ... 54 | I (0) cpu_start: Starting scheduler on APP CPU. 55 | E (434) spi: spicommon_bus_initialize_io(429): mosi not valid 56 | Couldn't read SPI! 57 | ``` 58 | 59 | If the SPI pins are not connected properly, the resulting read may just return 0, this error can not be detected: 60 | ``` 61 | ... 62 | I (0) cpu_start: Starting scheduler on APP CPU. 63 | Result of WHO_AM_I register: 0x00 64 | I (437) gpio: GPIO[23]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 65 | I (447) gpio: GPIO[25]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 66 | I (457) gpio: GPIO[26]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 67 | I (467) gpio: GPIO[27]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 68 | ``` 69 | -------------------------------------------------------------------------------- /examples/simple_spi_rw_example/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "simple_spi_rw_example.cpp" 2 | INCLUDE_DIRS ".") 3 | -------------------------------------------------------------------------------- /examples/simple_spi_rw_example/main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Example Configuration" 2 | 3 | config SPI_NUM 4 | int "SPI Num" 5 | default 1 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 6 | default 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 7 | help 8 | The number of the chip's SPI peripheral. 9 | 10 | config SPI_CS 11 | int "CS GPIO Num" 12 | default 10 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2 13 | default 23 if IDF_TARGET_ESP32 14 | default 4 if IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 15 | help 16 | GPIO number for SPI CS line. 17 | 18 | config SPI_MOSI 19 | int "MOSI GPIO Num" 20 | default 11 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2 21 | default 25 if IDF_TARGET_ESP32 22 | default 5 if IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 23 | help 24 | GPIO number for SPI MOSI line. 25 | 26 | config SPI_MISO 27 | int "MISO GPIO Num" 28 | default 0 if IDF_TARGET_ESP32C6 29 | default 12 if IDF_TARGET_ESP32H2 30 | default 26 if IDF_TARGET_ESP32 31 | default 6 if IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 32 | help 33 | GPIO number for SPI MISO line. 34 | 35 | config SPI_SCLK 36 | int "SCLK GPIO Num" 37 | default 1 if IDF_TARGET_ESP32C6 38 | default 22 if IDF_TARGET_ESP32H2 39 | default 27 if IDF_TARGET_ESP32 40 | default 7 if IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2 41 | help 42 | GPIO number for SPI SCLK line. 43 | 44 | endmenu 45 | -------------------------------------------------------------------------------- /examples/simple_spi_rw_example/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | espressif/esp-idf-cxx: 5 | override_path: ../../../ 6 | version: "^1.0.0" 7 | -------------------------------------------------------------------------------- /examples/simple_spi_rw_example/main/simple_spi_rw_example.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: CC0-1.0 5 | * 6 | * MPU9250 SPI Sensor C++ Example 7 | * 8 | * This example code is in the Public Domain (or CC0 licensed, at your option.) 9 | * 10 | * Unless required by applicable law or agreed to in writing, this 11 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | * CONDITIONS OF ANY KIND, either express or implied. 13 | */ 14 | 15 | #include "sdkconfig.h" 16 | #include 17 | #include "spi_host_cxx.hpp" 18 | 19 | using namespace std; 20 | using namespace idf; 21 | 22 | static const uint8_t READ_FLAG = 0x80; 23 | static const uint8_t MPU9250_WHO_AM_I_REG_ADDR = 0x75; 24 | 25 | namespace { 26 | static const MOSI MOSI_PIN(CONFIG_SPI_MOSI); 27 | static const MISO MISO_PIN(CONFIG_SPI_MISO); 28 | static const SCLK SCLK_PIN(CONFIG_SPI_SCLK); 29 | static const CS CS_PIN(CONFIG_SPI_CS); 30 | } 31 | 32 | extern "C" void app_main(void) 33 | { 34 | SPIMaster master(SPINum(1), 35 | MOSI_PIN, 36 | MISO_PIN, 37 | SCLK_PIN); 38 | 39 | shared_ptr spi_dev = master.create_dev(CS_PIN, Frequency::MHz(1)); 40 | 41 | vector write_data = {MPU9250_WHO_AM_I_REG_ADDR | READ_FLAG, 0x00}; 42 | vector result = spi_dev->transfer(write_data).get(); 43 | 44 | printf("Result of WHO_AM_I register: 0x%02X\n", result[1]); 45 | 46 | this_thread::sleep_for(std::chrono::seconds(2)); 47 | } 48 | -------------------------------------------------------------------------------- /examples/simple_spi_rw_example/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Enable C++ exceptions and set emergency pool size for exception objects 2 | CONFIG_COMPILER_CXX_EXCEPTIONS=y 3 | CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 4 | -------------------------------------------------------------------------------- /gpio_cxx.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #if __cpp_exceptions 8 | 9 | #include 10 | #include "driver/gpio.h" 11 | #include "gpio_cxx.hpp" 12 | 13 | namespace idf { 14 | 15 | #define GPIO_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), GPIOException) 16 | 17 | namespace { 18 | #if CONFIG_IDF_TARGET_LINUX 19 | constexpr std::array INVALID_GPIOS = {24}; 20 | #elif CONFIG_IDF_TARGET_ESP32 21 | constexpr std::array INVALID_GPIOS = {24}; 22 | #elif CONFIG_IDF_TARGET_ESP32S2 23 | constexpr std::array INVALID_GPIOS = {22, 23, 24, 25}; 24 | #elif CONFIG_IDF_TARGET_ESP32S3 25 | constexpr std::array INVALID_GPIOS = {22, 23, 24, 25}; 26 | #elif CONFIG_IDF_TARGET_ESP32C3 27 | constexpr std::array INVALID_GPIOS = {}; 28 | #elif CONFIG_IDF_TARGET_ESP32C2 29 | constexpr std::array INVALID_GPIOS = {}; 30 | #elif CONFIG_IDF_TARGET_ESP32C6 31 | constexpr std::array INVALID_GPIOS = {}; 32 | #elif CONFIG_IDF_TARGET_ESP32H2 33 | constexpr std::array INVALID_GPIOS = {}; 34 | #else 35 | #error "No GPIOs defined for the current target" 36 | #endif 37 | 38 | } 39 | 40 | GPIOException::GPIOException(esp_err_t error) : ESPException(error) { } 41 | 42 | esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept 43 | { 44 | if (pin_num >= GPIO_NUM_MAX) { 45 | return ESP_ERR_INVALID_ARG; 46 | } 47 | 48 | for (auto num: INVALID_GPIOS) 49 | { 50 | if (pin_num == num) { 51 | return ESP_ERR_INVALID_ARG; 52 | } 53 | } 54 | 55 | return ESP_OK; 56 | } 57 | 58 | esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept 59 | { 60 | if (strength >= GPIO_DRIVE_CAP_MAX) { 61 | return ESP_ERR_INVALID_ARG; 62 | } 63 | 64 | return ESP_OK; 65 | } 66 | 67 | GPIOPullMode GPIOPullMode::FLOATING() 68 | { 69 | return GPIOPullMode(GPIO_FLOATING); 70 | } 71 | 72 | GPIOPullMode GPIOPullMode::PULLUP() 73 | { 74 | return GPIOPullMode(GPIO_PULLUP_ONLY); 75 | } 76 | 77 | GPIOPullMode GPIOPullMode::PULLDOWN() 78 | { 79 | return GPIOPullMode(GPIO_PULLDOWN_ONLY); 80 | } 81 | 82 | GPIOWakeupIntrType GPIOWakeupIntrType::LOW_LEVEL() 83 | { 84 | return GPIOWakeupIntrType(GPIO_INTR_LOW_LEVEL); 85 | } 86 | 87 | GPIOWakeupIntrType GPIOWakeupIntrType::HIGH_LEVEL() 88 | { 89 | return GPIOWakeupIntrType(GPIO_INTR_HIGH_LEVEL); 90 | } 91 | 92 | GPIODriveStrength GPIODriveStrength::DEFAULT() 93 | { 94 | return MEDIUM(); 95 | } 96 | 97 | GPIODriveStrength GPIODriveStrength::WEAK() 98 | { 99 | return GPIODriveStrength(GPIO_DRIVE_CAP_0); 100 | } 101 | 102 | GPIODriveStrength GPIODriveStrength::LESS_WEAK() 103 | { 104 | return GPIODriveStrength(GPIO_DRIVE_CAP_1); 105 | } 106 | 107 | GPIODriveStrength GPIODriveStrength::MEDIUM() 108 | { 109 | return GPIODriveStrength(GPIO_DRIVE_CAP_2); 110 | } 111 | 112 | GPIODriveStrength GPIODriveStrength::STRONGEST() 113 | { 114 | return GPIODriveStrength(GPIO_DRIVE_CAP_3); 115 | } 116 | 117 | GPIOBase::GPIOBase(GPIONum num) : gpio_num(num) 118 | { 119 | GPIO_CHECK_THROW(gpio_reset_pin(gpio_num.get_value())); 120 | } 121 | 122 | void GPIOBase::hold_en() 123 | { 124 | GPIO_CHECK_THROW(gpio_hold_en(gpio_num.get_value())); 125 | } 126 | 127 | void GPIOBase::hold_dis() 128 | { 129 | GPIO_CHECK_THROW(gpio_hold_dis(gpio_num.get_value())); 130 | } 131 | 132 | void GPIOBase::set_drive_strength(GPIODriveStrength strength) 133 | { 134 | GPIO_CHECK_THROW(gpio_set_drive_capability(gpio_num.get_value(), 135 | strength.get_value())); 136 | } 137 | 138 | GPIO_Output::GPIO_Output(GPIONum num) : GPIOBase(num) 139 | { 140 | GPIO_CHECK_THROW(gpio_set_direction(gpio_num.get_value(), GPIO_MODE_OUTPUT)); 141 | } 142 | 143 | void GPIO_Output::set_high() const 144 | { 145 | GPIO_CHECK_THROW(gpio_set_level(gpio_num.get_value(), 1)); 146 | } 147 | 148 | void GPIO_Output::set_low() const 149 | { 150 | GPIO_CHECK_THROW(gpio_set_level(gpio_num.get_value(), 0)); 151 | } 152 | 153 | GPIODriveStrength GPIOBase::get_drive_strength() 154 | { 155 | gpio_drive_cap_t strength; 156 | GPIO_CHECK_THROW(gpio_get_drive_capability(gpio_num.get_value(), &strength)); 157 | return GPIODriveStrength(static_cast(strength)); 158 | } 159 | 160 | GPIOInput::GPIOInput(GPIONum num) : GPIOBase(num) 161 | { 162 | GPIO_CHECK_THROW(gpio_set_direction(gpio_num.get_value(), GPIO_MODE_INPUT)); 163 | } 164 | 165 | GPIOLevel GPIOInput::get_level() const noexcept 166 | { 167 | int level = gpio_get_level(gpio_num.get_value()); 168 | if (level) { 169 | return GPIOLevel::HIGH; 170 | } else { 171 | return GPIOLevel::LOW; 172 | } 173 | } 174 | 175 | void GPIOInput::set_pull_mode(GPIOPullMode mode) const 176 | { 177 | GPIO_CHECK_THROW(gpio_set_pull_mode(gpio_num.get_value(), 178 | mode.get_value())); 179 | } 180 | 181 | void GPIOInput::wakeup_enable(GPIOWakeupIntrType interrupt_type) const 182 | { 183 | GPIO_CHECK_THROW(gpio_wakeup_enable(gpio_num.get_value(), 184 | interrupt_type.get_value())); 185 | } 186 | 187 | void GPIOInput::wakeup_disable() const 188 | { 189 | GPIO_CHECK_THROW(gpio_wakeup_disable(gpio_num.get_value())); 190 | } 191 | 192 | GPIO_OpenDrain::GPIO_OpenDrain(GPIONum num) : GPIOInput(num) 193 | { 194 | GPIO_CHECK_THROW(gpio_set_direction(gpio_num.get_value(), GPIO_MODE_INPUT_OUTPUT_OD)); 195 | } 196 | 197 | void GPIO_OpenDrain::set_floating() const 198 | { 199 | GPIO_CHECK_THROW(gpio_set_level(gpio_num.get_value(), 1)); 200 | } 201 | 202 | void GPIO_OpenDrain::set_low() const 203 | { 204 | GPIO_CHECK_THROW(gpio_set_level(gpio_num.get_value(), 0)); 205 | } 206 | 207 | } 208 | 209 | #endif 210 | -------------------------------------------------------------------------------- /host_test/esp_timer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | set(COMPONENTS main) 5 | 6 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/") 7 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/") 8 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/") 9 | 10 | # Registration of cxx component 11 | list(APPEND EXTRA_COMPONENT_DIRS "../../") 12 | 13 | project(test_esp_timer_cxx_host) 14 | -------------------------------------------------------------------------------- /host_test/esp_timer/README.md: -------------------------------------------------------------------------------- 1 | | Supported Targets | Linux | 2 | | ----------------- | ----- | 3 | 4 | # C++ ESPTimer test on Linux target 5 | 6 | This unit test tests basic functionality of the `ESPTimer` class. The test does not use mocks. Instead, it runs the whole implementation of the component on the Linux host. The test framework is CATCH. 7 | 8 | ## Requirements 9 | 10 | * A Linux system 11 | * The usual IDF requirements for Linux system, as described in the [Getting Started Guides](../../../../../../docs/en/get-started/index.rst). 12 | * The host's gcc/g++ 13 | 14 | This application has been tested on Ubuntu 20.04 with `gcc` version *9.3.0*. 15 | 16 | ## Build 17 | 18 | First, make sure that the target is set to Linux. Run `idf.py --preview set-target linux` if you are not sure. Then do a normal IDF build: `idf.py build`. 19 | 20 | ## Run 21 | 22 | IDF monitor doesn't work yet for Linux. You have to run the app manually: 23 | 24 | ```bash 25 | build/test_esp_timer_cxx_host.elf 26 | ``` 27 | 28 | ## Example Output 29 | 30 | Ideally, all tests pass, which is indicated by "All tests passed" in the last line: 31 | 32 | ```bash 33 | $ build/test_esp_timer_cxx_host.elf 34 | =============================================================================== 35 | All tests passed (9 assertions in 11 test cases) 36 | ``` 37 | -------------------------------------------------------------------------------- /host_test/esp_timer/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "esp_timer_test.cpp" 2 | INCLUDE_DIRS 3 | "." 4 | $ENV{IDF_PATH}/tools/catch 5 | PRIV_REQUIRES cmock esp_timer) 6 | -------------------------------------------------------------------------------- /host_test/esp_timer/main/esp_timer_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ESP Timer C++ unit tests 3 | * 4 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 5 | * 6 | * SPDX-License-Identifier: CC0 7 | * 8 | * This test code is in the Public Domain (or CC0 licensed, at your option.) 9 | * 10 | * Unless required by applicable law or agreed to in writing, this 11 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | * CONDITIONS OF ANY KIND, either express or implied. 13 | */ 14 | 15 | #define CATCH_CONFIG_MAIN 16 | 17 | #include 18 | #include 19 | #include "esp_err.h" 20 | #include "esp_timer_cxx.hpp" 21 | 22 | #include "catch.hpp" 23 | 24 | extern "C" { 25 | #include "Mockesp_timer.h" 26 | } 27 | 28 | // TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead 29 | const char *esp_err_to_name(esp_err_t code) { 30 | return "test"; 31 | } 32 | 33 | using namespace std; 34 | using namespace idf; 35 | using namespace idf::esp_timer; 36 | 37 | struct FixtureException : std::exception { 38 | const char *what() const noexcept override { 39 | return "CMock failed"; 40 | } 41 | }; 42 | 43 | struct TimerCreationFixture { 44 | TimerCreationFixture(bool expect_stop = false) : out_handle(reinterpret_cast(1)) 45 | { 46 | if (!TEST_PROTECT()) { 47 | throw FixtureException(); 48 | } 49 | esp_timer_create_ExpectAnyArgsAndReturn(ESP_OK); 50 | esp_timer_create_ReturnThruPtr_out_handle(&out_handle); 51 | if (expect_stop) { 52 | esp_timer_stop_ExpectAndReturn(out_handle, ESP_OK); // implementation may always call stop 53 | } else { 54 | esp_timer_stop_IgnoreAndReturn(ESP_OK); // implementation may always call stop 55 | } 56 | esp_timer_delete_ExpectAndReturn(out_handle, ESP_OK); 57 | } 58 | 59 | virtual ~TimerCreationFixture() 60 | { 61 | Mockesp_timer_Verify(); 62 | } 63 | 64 | esp_timer_handle_t out_handle; 65 | }; 66 | 67 | static void (*trigger_timer_callback)(void *data) = nullptr; 68 | 69 | esp_err_t cmock_timer_create_callback(const esp_timer_create_args_t* create_args, esp_timer_handle_t* out_handle, int cmock_num_calls) 70 | { 71 | trigger_timer_callback = create_args->callback; 72 | return ESP_OK; 73 | } 74 | 75 | struct TimerCallbackFixture : public TimerCreationFixture { 76 | TimerCallbackFixture(bool expect_stop = false) : TimerCreationFixture(expect_stop) 77 | { 78 | esp_timer_create_AddCallback(cmock_timer_create_callback); 79 | } 80 | 81 | ~TimerCallbackFixture() 82 | { 83 | trigger_timer_callback = nullptr; 84 | } 85 | }; 86 | 87 | TEST_CASE("get_time works") 88 | { 89 | esp_timer_get_time_ExpectAndReturn(static_cast(0xfeeddeadbeef)); 90 | 91 | CHECK(get_time() == std::chrono::microseconds(0xfeeddeadbeef)); 92 | } 93 | 94 | TEST_CASE("get_next_alarm works") 95 | { 96 | esp_timer_get_next_alarm_ExpectAndReturn(static_cast(47u)); 97 | 98 | CHECK(get_next_alarm() == std::chrono::microseconds(47u)); 99 | } 100 | 101 | TEST_CASE("ESPTimer null function") 102 | { 103 | CHECK_THROWS_AS(ESPTimer(nullptr), ESPException&); 104 | } 105 | 106 | TEST_CASE("ESPTimer empty std::function") 107 | { 108 | function nothing; 109 | CHECK_THROWS_AS(ESPTimer(nothing, "test"), ESPException&); 110 | } 111 | 112 | TEST_CASE("ESPTimer initializes and deletes itself") 113 | { 114 | TimerCreationFixture fix; 115 | 116 | function timer_cb = [&]() { }; 117 | 118 | ESPTimer(timer_cb, "test"); 119 | } 120 | 121 | TEST_CASE("ESPTimer start throws on invalid state failure") 122 | { 123 | TimerCreationFixture fix; 124 | esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE); 125 | 126 | function timer_cb = [&]() { }; 127 | 128 | ESPTimer timer(timer_cb); 129 | 130 | CHECK_THROWS_AS(timer.start(chrono::microseconds(5000)), ESPException&); 131 | } 132 | 133 | TEST_CASE("ESPTimer start periodically throws on invalid state failure") 134 | { 135 | TimerCreationFixture fix; 136 | esp_timer_start_periodic_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE); 137 | 138 | function timer_cb = [&]() { }; 139 | 140 | ESPTimer timer(timer_cb); 141 | 142 | CHECK_THROWS_AS(timer.start_periodic(chrono::microseconds(5000)), ESPException&); 143 | } 144 | 145 | TEST_CASE("ESPTimer stopp throws on invaid state failure") 146 | { 147 | TimerCreationFixture fix; 148 | 149 | // Overriding stop part of the fixture 150 | esp_timer_stop_StopIgnore(); 151 | esp_timer_stop_IgnoreAndReturn(ESP_ERR_INVALID_STATE); 152 | 153 | function timer_cb = [&]() { }; 154 | 155 | ESPTimer timer(timer_cb); 156 | 157 | CHECK_THROWS_AS(timer.stop(), ESPException&); 158 | } 159 | 160 | TEST_CASE("ESPTimer stops in destructor") 161 | { 162 | TimerCreationFixture fix(true); 163 | esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK); 164 | 165 | function timer_cb = [&]() { }; 166 | 167 | ESPTimer timer(timer_cb); 168 | 169 | timer.start(chrono::microseconds(5000)); 170 | } 171 | 172 | TEST_CASE("ESPTimer stops correctly") 173 | { 174 | TimerCreationFixture fix(true); 175 | esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK); 176 | 177 | // Additional stop needed because stop is called in ESPTimer::stop and ~ESPTimer. 178 | esp_timer_stop_ExpectAndReturn(fix.out_handle, ESP_OK); 179 | 180 | function timer_cb = [&]() { }; 181 | 182 | ESPTimer timer(timer_cb); 183 | 184 | timer.start(chrono::microseconds(5000)); 185 | 186 | timer.stop(); 187 | } 188 | 189 | TEST_CASE("ESPTimer callback works") 190 | { 191 | TimerCallbackFixture fix; 192 | int flag = 0; 193 | 194 | function timer_cb = [&]() { flag = 47; }; 195 | 196 | ESPTimer timer(timer_cb); 197 | 198 | trigger_timer_callback(&timer); 199 | 200 | REQUIRE(trigger_timer_callback != nullptr); 201 | CHECK(flag == 47); 202 | } 203 | -------------------------------------------------------------------------------- /host_test/esp_timer/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | esp-idf-cxx: 5 | path: ../../../ 6 | version: ">=0.1" 7 | -------------------------------------------------------------------------------- /host_test/esp_timer/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n 2 | CONFIG_IDF_TARGET="linux" 3 | CONFIG_CXX_EXCEPTIONS=y 4 | -------------------------------------------------------------------------------- /host_test/fixtures/test_fixtures.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Unlicense OR CC0-1.0 5 | * 6 | * This example code is in the Public Domain (or CC0 licensed, at your option.) 7 | * 8 | * Unless required by applicable law or agreed to in writing, this 9 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | * CONDITIONS OF ANY KIND, either express or implied. 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "catch.hpp" 16 | #include "gpio_cxx.hpp" 17 | #include "driver/spi_master.h" 18 | #include "spi_cxx.hpp" 19 | #include "i2c_cxx.hpp" 20 | extern "C" { 21 | #include "Mockgpio.h" 22 | #include "Mockspi_master.h" 23 | #include "Mockspi_common.h" 24 | #include "Mocki2c.h" 25 | } 26 | 27 | static const idf::GPIONum VALID_GPIO(18); 28 | 29 | /** 30 | * Exception which is thrown if there is some internal cmock error which results in a 31 | * longjump to the location of a TEST_PROTECT() call. 32 | * 33 | * @note This is a temporary solution until there is a better integration of CATCH into CMock. 34 | * Note also that usually there will be a segfault when cmock fails a second time. 35 | * This means paying attention to the first error message is crucial for removing errors. 36 | */ 37 | class CMockException : public std::exception { 38 | public: 39 | virtual ~CMockException() { } 40 | 41 | /** 42 | * @return A reminder to look at the actual cmock log. 43 | */ 44 | virtual const char *what() const noexcept 45 | { 46 | return "CMock encountered an error. Look at the CMock log"; 47 | } 48 | }; 49 | 50 | /** 51 | * Helper macro for setting up a test protect call for CMock. 52 | * 53 | * This macro should be used at the beginning of any test cases 54 | * that uses generated CMock mock functions. 55 | * This is necessary because CMock uses longjmp which screws up C++ stacks and 56 | * also the CATCH mechanisms. 57 | * 58 | * @note This is a temporary solution until there is a better integration of CATCH into CMock. 59 | * Note also that usually there will be a segfault when cmock fails a second time. 60 | * This means paying attention to the first error message is crucial for removing errors. 61 | */ 62 | #define CMOCK_SETUP() \ 63 | do { \ 64 | if (!TEST_PROTECT()) { \ 65 | throw CMockException(); \ 66 | } \ 67 | } \ 68 | while (0) 69 | 70 | struct CMockFixture { 71 | CMockFixture() 72 | { 73 | CMOCK_SETUP(); 74 | } 75 | 76 | ~CMockFixture() 77 | { 78 | // Verify that all expected methods have been called. 79 | Mockgpio_Verify(); 80 | Mockspi_master_Verify(); 81 | Mockspi_common_Verify(); 82 | } 83 | }; 84 | 85 | struct GPIOFixture : public CMockFixture { 86 | GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT) 87 | : CMockFixture(), num(gpio_num) 88 | { 89 | gpio_reset_pin_ExpectAndReturn(static_cast(num.get_value()), ESP_OK); 90 | gpio_set_direction_ExpectAndReturn(static_cast(num.get_value()), mode, ESP_OK); 91 | } 92 | 93 | idf::GPIONum num; 94 | }; 95 | 96 | struct SPIFix; 97 | struct SPIDevFix; 98 | struct SPITransactionDescriptorFix; 99 | struct SPITransactionTimeoutFix; 100 | struct SPITransactionFix; 101 | 102 | static SPIFix *g_fixture; 103 | static SPIDevFix *g_dev_fixture; 104 | static SPITransactionDescriptorFix *g_trans_desc_fixture; 105 | static SPITransactionTimeoutFix *g_trans_timeout_fixture; 106 | static SPITransactionFix *g_trans_fixture; 107 | 108 | struct SPIFix : public CMockFixture { 109 | SPIFix(spi_host_device_t host_id = spi_host_device_t(1), 110 | uint32_t mosi = 1, 111 | uint32_t miso = 2, 112 | uint32_t sclk = 3) : CMockFixture(), bus_config() { 113 | bus_config.mosi_io_num = mosi; 114 | bus_config.miso_io_num = miso; 115 | bus_config.sclk_io_num = sclk; 116 | bus_config.quadwp_io_num = -1; 117 | bus_config.quadhd_io_num = -1; 118 | 119 | spi_bus_initialize_ExpectWithArrayAndReturn(host_id, &bus_config, 1, spi_common_dma_t::SPI_DMA_CH_AUTO, ESP_OK); 120 | spi_bus_free_ExpectAnyArgsAndReturn(ESP_OK); 121 | 122 | g_fixture = this; 123 | } 124 | 125 | ~SPIFix() { 126 | g_fixture = nullptr; 127 | } 128 | 129 | spi_bus_config_t bus_config; 130 | }; 131 | 132 | struct QSPIFix : public SPIFix { 133 | QSPIFix(spi_host_device_t host_id = spi_host_device_t(1), 134 | uint32_t mosi = 1, 135 | uint32_t miso = 2, 136 | uint32_t sclk = 3, 137 | uint32_t wp = 4, 138 | uint32_t hd = 5) : SPIFix(host_id, mosi, miso, sclk) 139 | { 140 | bus_config.quadwp_io_num = wp; 141 | bus_config.quadhd_io_num = hd; 142 | } 143 | }; 144 | 145 | enum class CreateAnd { 146 | FAIL, 147 | SUCCEED, 148 | IGNORE 149 | }; 150 | 151 | struct SPIDevFix { 152 | SPIDevFix(CreateAnd flags) 153 | : dev_handle(reinterpret_cast(47)), 154 | dev_config() 155 | { 156 | dev_config.spics_io_num = 4; 157 | if (flags == CreateAnd::FAIL) { 158 | spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_FAIL); 159 | } else if (flags == CreateAnd::IGNORE) { 160 | spi_bus_add_device_IgnoreAndReturn(ESP_OK); 161 | spi_bus_remove_device_IgnoreAndReturn(ESP_OK); 162 | } else { 163 | spi_bus_add_device_AddCallback(add_dev_cb); 164 | spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_OK); 165 | spi_bus_remove_device_ExpectAndReturn(dev_handle, ESP_OK); 166 | } 167 | 168 | g_dev_fixture = this; 169 | } 170 | 171 | ~SPIDevFix() 172 | { 173 | spi_bus_add_device_AddCallback(nullptr); 174 | g_dev_fixture = nullptr; 175 | } 176 | 177 | spi_device_handle_t dev_handle; 178 | spi_device_interface_config_t dev_config; 179 | 180 | static esp_err_t add_dev_cb(spi_host_device_t host_id, 181 | const spi_device_interface_config_t* dev_config, 182 | spi_device_handle_t* handle, 183 | int cmock_num_calls) 184 | { 185 | SPIDevFix *fix = static_cast(g_dev_fixture); 186 | *handle = fix->dev_handle; 187 | fix->dev_config = *dev_config; 188 | return ESP_OK; 189 | } 190 | }; 191 | 192 | struct SPITransactionFix { 193 | SPITransactionFix(esp_err_t get_trans_return = ESP_OK) : get_transaction_return(get_trans_return) 194 | { 195 | spi_device_queue_trans_AddCallback(queue_trans_cb); 196 | spi_device_get_trans_result_AddCallback(get_trans_result_cb); 197 | 198 | spi_device_queue_trans_ExpectAnyArgsAndReturn(ESP_OK); 199 | spi_device_get_trans_result_ExpectAnyArgsAndReturn(get_trans_return); 200 | 201 | g_trans_fixture = this; 202 | } 203 | 204 | ~SPITransactionFix() 205 | { 206 | spi_device_get_trans_result_AddCallback(nullptr); 207 | spi_device_queue_trans_AddCallback(nullptr); 208 | g_trans_fixture = nullptr; 209 | } 210 | 211 | static esp_err_t queue_trans_cb(spi_device_handle_t handle, 212 | spi_transaction_t* trans_desc, 213 | TickType_t ticks_to_wait, 214 | int cmock_num_calls) 215 | { 216 | SPITransactionFix *fix = static_cast (g_trans_fixture); 217 | fix->orig_trans = trans_desc; 218 | return ESP_OK; 219 | } 220 | 221 | static esp_err_t get_trans_result_cb(spi_device_handle_t handle, 222 | spi_transaction_t** trans_desc, 223 | TickType_t ticks_to_wait, 224 | int cmock_num_calls) 225 | { 226 | SPITransactionFix *fix = static_cast (g_trans_fixture); 227 | 228 | *trans_desc = fix->orig_trans; 229 | 230 | return fix->get_transaction_return; 231 | } 232 | 233 | esp_err_t get_transaction_return; 234 | spi_transaction_t *orig_trans; 235 | }; 236 | 237 | struct SPITransactionDescriptorFix { 238 | SPITransactionDescriptorFix(size_t size = 1, bool ignore_handle = false, TickType_t wait_time = portMAX_DELAY) 239 | : size(size), handle(reinterpret_cast(0x01020304)) 240 | { 241 | spi_device_queue_trans_AddCallback(queue_trans_cb); 242 | spi_device_get_trans_result_AddCallback(get_trans_result_cb); 243 | 244 | spi_device_acquire_bus_ExpectAndReturn(handle, portMAX_DELAY, ESP_OK); 245 | if (ignore_handle) { 246 | spi_device_acquire_bus_IgnoreArg_device(); 247 | } 248 | spi_device_queue_trans_ExpectAndReturn(handle, nullptr, 0, ESP_OK); 249 | spi_device_queue_trans_IgnoreArg_trans_desc(); 250 | if (ignore_handle) { 251 | spi_device_queue_trans_IgnoreArg_handle(); 252 | } 253 | 254 | spi_device_get_trans_result_ExpectAndReturn(handle, nullptr, wait_time, ESP_OK); 255 | spi_device_get_trans_result_IgnoreArg_trans_desc(); 256 | if (ignore_handle) { 257 | spi_device_get_trans_result_IgnoreArg_handle(); 258 | } 259 | spi_device_release_bus_ExpectAnyArgs(); 260 | 261 | g_trans_desc_fixture = this; 262 | } 263 | 264 | ~SPITransactionDescriptorFix() 265 | { 266 | spi_device_get_trans_result_AddCallback(nullptr); 267 | spi_device_queue_trans_AddCallback(nullptr); 268 | g_trans_desc_fixture = nullptr; 269 | } 270 | 271 | static esp_err_t queue_trans_cb(spi_device_handle_t handle, 272 | spi_transaction_t* trans_desc, 273 | TickType_t ticks_to_wait, 274 | int cmock_num_calls) 275 | { 276 | SPITransactionDescriptorFix *fix = static_cast (g_trans_desc_fixture); 277 | fix->orig_trans = trans_desc; 278 | return ESP_OK; 279 | } 280 | 281 | static esp_err_t get_trans_result_cb(spi_device_handle_t handle, 282 | spi_transaction_t** trans_desc, 283 | TickType_t ticks_to_wait, 284 | int cmock_num_calls) 285 | { 286 | SPITransactionDescriptorFix *fix = static_cast (g_trans_desc_fixture); 287 | 288 | for (int i = 0; i < fix->size; i++) { 289 | static_cast(fix->orig_trans->rx_buffer)[i] = fix->rx_data[i]; 290 | } 291 | 292 | *trans_desc = fix->orig_trans; 293 | 294 | return ESP_OK; 295 | } 296 | 297 | size_t size; 298 | spi_transaction_t *orig_trans; 299 | spi_device_handle_t handle; 300 | std::vector tx_data; 301 | std::vector rx_data; 302 | }; 303 | 304 | struct I2CMasterFix { 305 | I2CMasterFix(i2c_port_t port_arg = 0) : i2c_conf(), port(port_arg) 306 | { 307 | i2c_conf.mode = i2c_mode_t::I2C_MODE_MASTER; 308 | i2c_conf.sda_io_num = 2; 309 | i2c_conf.scl_io_num = 1; 310 | i2c_conf.sda_pullup_en = true; 311 | i2c_conf.scl_pullup_en = true; 312 | i2c_conf.master.clk_speed = 400000; 313 | i2c_conf.clk_flags = 0; 314 | i2c_param_config_ExpectWithArrayAndReturn(i2c_port_t(0), &i2c_conf, 1, ESP_OK); 315 | i2c_driver_install_ExpectAndReturn(i2c_port_t(0), i2c_mode_t::I2C_MODE_MASTER, 0, 0, 0, ESP_OK); 316 | i2c_driver_delete_ExpectAndReturn(i2c_port_t(0), ESP_OK); 317 | } 318 | 319 | i2c_config_t i2c_conf; 320 | i2c_port_t port; 321 | }; 322 | 323 | #if CONFIG_SOC_I2C_SUPPORT_SLAVE 324 | struct I2CSlaveFix { 325 | I2CSlaveFix(CreateAnd flags, i2c_port_t port_arg = 0, size_t buffer_size = 64) : i2c_conf(), port(port_arg) 326 | { 327 | if (flags == CreateAnd::SUCCEED) { 328 | i2c_conf.mode = i2c_mode_t::I2C_MODE_SLAVE; 329 | i2c_conf.sda_io_num = 2; 330 | i2c_conf.scl_io_num = 1; 331 | i2c_conf.sda_pullup_en = true; 332 | i2c_conf.scl_pullup_en = true; 333 | i2c_conf.slave.addr_10bit_en = 0; 334 | i2c_conf.slave.slave_addr = 0x47; 335 | i2c_param_config_ExpectWithArrayAndReturn(port, &i2c_conf, 1, ESP_OK); 336 | i2c_driver_install_ExpectAndReturn(port, i2c_mode_t::I2C_MODE_SLAVE, buffer_size, buffer_size, 0, ESP_OK); 337 | i2c_driver_delete_ExpectAndReturn(port, ESP_OK); 338 | } else if (flags == CreateAnd::IGNORE) { 339 | i2c_param_config_IgnoreAndReturn(ESP_OK); 340 | i2c_driver_install_IgnoreAndReturn(ESP_OK); 341 | i2c_driver_delete_IgnoreAndReturn(ESP_OK); 342 | } else { 343 | throw idf::I2CException(ESP_ERR_INVALID_ARG); 344 | } 345 | } 346 | 347 | i2c_config_t i2c_conf; 348 | i2c_port_t port; 349 | }; 350 | #endif 351 | 352 | struct I2CCmdLinkFix 353 | { 354 | I2CCmdLinkFix(uint8_t expected_addr, i2c_rw_t type = I2C_MASTER_WRITE) : dummy_handle(reinterpret_cast(0xbeef)) 355 | { 356 | i2c_cmd_link_create_ExpectAndReturn(&dummy_handle); 357 | i2c_master_start_ExpectAndReturn(&dummy_handle, ESP_OK); 358 | i2c_master_write_byte_ExpectAndReturn(&dummy_handle, expected_addr << 1 | type, true, ESP_OK); 359 | i2c_cmd_link_delete_Expect(&dummy_handle); 360 | } 361 | 362 | i2c_cmd_handle_t dummy_handle; 363 | }; 364 | -------------------------------------------------------------------------------- /host_test/gpio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | set(COMPONENTS main) 5 | 6 | idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND) 7 | 8 | # Overriding components which should be mocked 9 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/") 10 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/") 11 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/") 12 | 13 | # Registration of cxx component 14 | list(APPEND EXTRA_COMPONENT_DIRS "../../") 15 | 16 | project(test_gpio_cxx_host) 17 | -------------------------------------------------------------------------------- /host_test/gpio/README.md: -------------------------------------------------------------------------------- 1 | | Supported Targets | Linux | 2 | | ----------------- | ----- | 3 | 4 | # Build 5 | `idf.py build` (sdkconfig.defaults sets the linux target by default) 6 | 7 | # Run 8 | `build/test_gpio_cxx_host.elf` 9 | -------------------------------------------------------------------------------- /host_test/gpio/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "gpio_cxx_test.cpp" 2 | INCLUDE_DIRS 3 | "." 4 | "../../fixtures" 5 | $ENV{IDF_PATH}/tools/catch 6 | PRIV_REQUIRES driver cmock) 7 | -------------------------------------------------------------------------------- /host_test/gpio/main/gpio_cxx_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * GPIO C++ unit tests 3 | * 4 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 5 | * 6 | * SPDX-License-Identifier: CC0 7 | * 8 | * This test code is in the Public Domain (or CC0 licensed, at your option.) 9 | * 10 | * Unless required by applicable law or agreed to in writing, this 11 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | * CONDITIONS OF ANY KIND, either express or implied. 13 | */ 14 | 15 | #define CATCH_CONFIG_MAIN 16 | 17 | #include 18 | #include "esp_err.h" 19 | #include "freertos/portmacro.h" 20 | #include "gpio_cxx.hpp" 21 | #include "test_fixtures.hpp" 22 | 23 | #include "catch.hpp" 24 | 25 | extern "C" { 26 | #include "Mockgpio.h" 27 | } 28 | 29 | // TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead 30 | const char *esp_err_to_name(esp_err_t code) { 31 | return "test"; 32 | } 33 | 34 | using namespace std; 35 | using namespace idf; 36 | 37 | TEST_CASE("gpio num out of range") 38 | { 39 | CHECK_THROWS_AS(GPIONum(-1), GPIOException&); 40 | CHECK_THROWS_AS(GPIONum(static_cast(GPIO_NUM_MAX)), GPIOException&); 41 | CHECK_THROWS_AS(GPIONum(24), GPIOException&); // On ESP32, 24 isn't a valid GPIO number 42 | } 43 | 44 | TEST_CASE("gpio num operator") 45 | { 46 | GPIONum gpio_num_0(18u); 47 | GPIONum gpio_num_1(18u); 48 | GPIONum gpio_num_2(19u); 49 | 50 | CHECK(gpio_num_0 == gpio_num_1); 51 | CHECK(gpio_num_2 != gpio_num_1); 52 | } 53 | 54 | TEST_CASE("drive strength out of range") 55 | { 56 | CHECK_THROWS_AS(GPIODriveStrength(-1), GPIOException&); 57 | CHECK_THROWS_AS(GPIODriveStrength(static_cast(GPIO_DRIVE_CAP_MAX)), GPIOException&); 58 | } 59 | 60 | TEST_CASE("drive strength as expected") 61 | { 62 | CHECK(GPIODriveStrength::DEFAULT().get_value() == GPIO_DRIVE_CAP_2); 63 | CHECK(GPIODriveStrength::WEAK().get_value() == GPIO_DRIVE_CAP_0); 64 | CHECK(GPIODriveStrength::LESS_WEAK().get_value() == GPIO_DRIVE_CAP_1); 65 | CHECK(GPIODriveStrength::MEDIUM().get_value() == GPIO_DRIVE_CAP_2); 66 | CHECK(GPIODriveStrength::STRONGEST().get_value() == GPIO_DRIVE_CAP_3); 67 | } 68 | 69 | TEST_CASE("pull mode create functions work as expected") 70 | { 71 | CHECK(GPIOPullMode::FLOATING().get_value() == 3); 72 | CHECK(GPIOPullMode::PULLUP().get_value() == 0); 73 | CHECK(GPIOPullMode::PULLDOWN().get_value() == 1); 74 | } 75 | 76 | TEST_CASE("GPIOIntrType create functions work as expected") 77 | { 78 | CHECK(GPIOWakeupIntrType::LOW_LEVEL().get_value() == GPIO_INTR_LOW_LEVEL); 79 | CHECK(GPIOWakeupIntrType::HIGH_LEVEL().get_value() == GPIO_INTR_HIGH_LEVEL); 80 | } 81 | 82 | TEST_CASE("output resetting pin fails") 83 | { 84 | CMOCK_SETUP(); 85 | gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_FAIL); 86 | 87 | CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&); 88 | 89 | Mockgpio_Verify(); 90 | } 91 | 92 | TEST_CASE("output setting direction fails") 93 | { 94 | CMOCK_SETUP(); 95 | gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK); 96 | gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL); 97 | 98 | CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&); 99 | 100 | Mockgpio_Verify(); 101 | } 102 | 103 | TEST_CASE("output constructor sets correct arguments") 104 | { 105 | CMOCK_SETUP(); 106 | gpio_reset_pin_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), ESP_OK); 107 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), GPIO_MODE_OUTPUT, ESP_OK); 108 | 109 | GPIO_Output gpio(VALID_GPIO); 110 | 111 | Mockgpio_Verify(); 112 | } 113 | 114 | TEST_CASE("output set high fails") 115 | { 116 | GPIOFixture fix; 117 | gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_value()), 1, ESP_FAIL); 118 | 119 | GPIO_Output gpio(fix.num); 120 | 121 | CHECK_THROWS_AS(gpio.set_high(), GPIOException&); 122 | } 123 | 124 | TEST_CASE("output set high success") 125 | { 126 | GPIOFixture fix; 127 | gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_value()), 1, ESP_OK); 128 | 129 | GPIO_Output gpio(fix.num); 130 | 131 | gpio.set_high(); 132 | } 133 | 134 | TEST_CASE("output set low fails") 135 | { 136 | GPIOFixture fix; 137 | gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_value()), 0, ESP_FAIL); 138 | 139 | GPIO_Output gpio(fix.num); 140 | 141 | CHECK_THROWS_AS(gpio.set_low(), GPIOException&); 142 | } 143 | 144 | TEST_CASE("output set low success") 145 | { 146 | GPIOFixture fix; 147 | gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_value()), 0, ESP_OK); 148 | 149 | GPIO_Output gpio(fix.num); 150 | 151 | gpio.set_low(); 152 | } 153 | 154 | TEST_CASE("output set drive strength") 155 | { 156 | GPIOFixture fix(VALID_GPIO); 157 | gpio_set_drive_capability_ExpectAndReturn(static_cast(fix.num.get_value()), GPIO_DRIVE_CAP_0, ESP_OK); 158 | 159 | GPIO_Output gpio(fix.num); 160 | 161 | gpio.set_drive_strength(GPIODriveStrength::WEAK()); 162 | } 163 | 164 | TEST_CASE("output get drive strength") 165 | { 166 | GPIOFixture fix(VALID_GPIO); 167 | gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3; 168 | gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK); 169 | gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength); 170 | 171 | GPIO_Output gpio(fix.num); 172 | 173 | CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST()); 174 | } 175 | 176 | TEST_CASE("GPIOInput setting direction fails") 177 | { 178 | CMOCK_SETUP(); 179 | gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK); 180 | gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL); 181 | 182 | CHECK_THROWS_AS(GPIOInput gpio(VALID_GPIO), GPIOException&); 183 | 184 | Mockgpio_Verify(); 185 | } 186 | 187 | TEST_CASE("constructor sets correct arguments") 188 | { 189 | CMOCK_SETUP(); 190 | gpio_reset_pin_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), ESP_OK); 191 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), GPIO_MODE_INPUT, ESP_OK); 192 | 193 | GPIOInput gpio(VALID_GPIO); 194 | 195 | Mockgpio_Verify(); 196 | } 197 | 198 | TEST_CASE("get level low") 199 | { 200 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 201 | gpio_get_level_ExpectAndReturn(static_cast(fix.num.get_value()), 0); 202 | 203 | GPIOInput gpio(fix.num); 204 | 205 | CHECK(gpio.get_level() == GPIOLevel::LOW); 206 | } 207 | 208 | TEST_CASE("get level high") 209 | { 210 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 211 | gpio_get_level_ExpectAndReturn(static_cast(fix.num.get_value()), 1); 212 | 213 | GPIOInput gpio(fix.num); 214 | 215 | CHECK(gpio.get_level() == GPIOLevel::HIGH); 216 | } 217 | 218 | TEST_CASE("set pull mode fails") 219 | { 220 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 221 | gpio_set_pull_mode_ExpectAndReturn(static_cast(fix.num.get_value()), GPIO_FLOATING, ESP_FAIL); 222 | 223 | GPIOInput gpio(fix.num); 224 | 225 | CHECK_THROWS_AS(gpio.set_pull_mode(GPIOPullMode::FLOATING()), GPIOException&); 226 | } 227 | 228 | TEST_CASE("GPIOInput set pull mode floating") 229 | { 230 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 231 | gpio_set_pull_mode_ExpectAndReturn(static_cast(fix.num.get_value()), GPIO_FLOATING, ESP_OK); 232 | 233 | GPIOInput gpio(fix.num); 234 | 235 | gpio.set_pull_mode(GPIOPullMode::FLOATING()); 236 | } 237 | 238 | TEST_CASE("GPIOInput set pull mode pullup") 239 | { 240 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 241 | gpio_set_pull_mode_ExpectAndReturn(static_cast(fix.num.get_value()), GPIO_PULLUP_ONLY, ESP_OK); 242 | 243 | GPIOInput gpio(fix.num); 244 | 245 | gpio.set_pull_mode(GPIOPullMode::PULLUP()); 246 | } 247 | 248 | TEST_CASE("GPIOInput set pull mode pulldown") 249 | { 250 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 251 | gpio_set_pull_mode_ExpectAndReturn(static_cast(fix.num.get_value()), GPIO_PULLDOWN_ONLY, ESP_OK); 252 | 253 | GPIOInput gpio(fix.num); 254 | 255 | gpio.set_pull_mode(GPIOPullMode::PULLDOWN()); 256 | } 257 | 258 | TEST_CASE("GPIOInput wake up enable fails") 259 | { 260 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 261 | gpio_wakeup_enable_ExpectAndReturn(static_cast(fix.num.get_value()), GPIO_INTR_LOW_LEVEL, ESP_FAIL); 262 | 263 | GPIOInput gpio(fix.num); 264 | 265 | CHECK_THROWS_AS(gpio.wakeup_enable(GPIOWakeupIntrType::LOW_LEVEL()), GPIOException&); 266 | } 267 | 268 | TEST_CASE("GPIOInput wake up enable high int") 269 | { 270 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 271 | gpio_wakeup_enable_ExpectAndReturn(static_cast(fix.num.get_value()), GPIO_INTR_HIGH_LEVEL, ESP_OK); 272 | 273 | GPIOInput gpio(fix.num); 274 | 275 | gpio.wakeup_enable(GPIOWakeupIntrType::HIGH_LEVEL()); 276 | } 277 | 278 | TEST_CASE("GPIOInput wake up disable fails") 279 | { 280 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 281 | gpio_wakeup_disable_ExpectAndReturn(static_cast(fix.num.get_value()), ESP_FAIL); 282 | 283 | GPIOInput gpio(fix.num); 284 | 285 | CHECK_THROWS_AS(gpio.wakeup_disable(), GPIOException&); 286 | } 287 | 288 | TEST_CASE("GPIOInput wake up disable high int") 289 | { 290 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 291 | gpio_wakeup_disable_ExpectAndReturn(static_cast(fix.num.get_value()), ESP_OK); 292 | 293 | GPIOInput gpio(fix.num); 294 | 295 | gpio.wakeup_disable(); 296 | } 297 | 298 | TEST_CASE("GPIO_OpenDrain setting direction fails") 299 | { 300 | CMOCK_SETUP(); 301 | gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK); 302 | gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL); 303 | 304 | CHECK_THROWS_AS(GPIO_OpenDrain gpio(VALID_GPIO), GPIOException&); 305 | 306 | Mockgpio_Verify(); 307 | } 308 | 309 | TEST_CASE("GPIO_OpenDrain constructor sets correct arguments") 310 | { 311 | CMOCK_SETUP(); 312 | gpio_reset_pin_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), ESP_OK); 313 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), 314 | GPIO_MODE_INPUT, 315 | ESP_OK); 316 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), 317 | GPIO_MODE_INPUT_OUTPUT_OD, 318 | ESP_OK); 319 | 320 | GPIO_OpenDrain gpio(VALID_GPIO); 321 | 322 | Mockgpio_Verify(); 323 | } 324 | 325 | TEST_CASE("GPIO_OpenDrain set floating fails") 326 | { 327 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 328 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), 329 | GPIO_MODE_INPUT_OUTPUT_OD, 330 | ESP_OK); 331 | gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_value()), 1, ESP_FAIL); 332 | 333 | GPIO_OpenDrain gpio(fix.num); 334 | 335 | CHECK_THROWS_AS(gpio.set_floating(), GPIOException&); 336 | } 337 | 338 | TEST_CASE("GPIO_OpenDrain set floating success") 339 | { 340 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 341 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), 342 | GPIO_MODE_INPUT_OUTPUT_OD, 343 | ESP_OK); 344 | gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_value()), 1, ESP_OK); 345 | 346 | GPIO_OpenDrain gpio(fix.num); 347 | 348 | gpio.set_floating(); 349 | } 350 | 351 | TEST_CASE("GPIO_OpenDrain set low fails") 352 | { 353 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 354 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), 355 | GPIO_MODE_INPUT_OUTPUT_OD, 356 | ESP_OK); 357 | gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_value()), 0, ESP_FAIL); 358 | 359 | GPIO_OpenDrain gpio(fix.num); 360 | 361 | CHECK_THROWS_AS(gpio.set_low(), GPIOException&); 362 | } 363 | 364 | TEST_CASE("GPIO_OpenDrain set low success") 365 | { 366 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 367 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), 368 | GPIO_MODE_INPUT_OUTPUT_OD, 369 | ESP_OK); 370 | gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_value()), 0, ESP_OK); 371 | 372 | GPIO_OpenDrain gpio(fix.num); 373 | 374 | gpio.set_low(); 375 | } 376 | 377 | TEST_CASE("GPIO_OpenDrain set drive strength") 378 | { 379 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 380 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), 381 | GPIO_MODE_INPUT_OUTPUT_OD, 382 | ESP_OK); 383 | 384 | gpio_set_drive_capability_ExpectAndReturn(static_cast(fix.num.get_value()), GPIO_DRIVE_CAP_0, ESP_OK); 385 | GPIO_OpenDrain gpio(fix.num); 386 | 387 | gpio.set_drive_strength(GPIODriveStrength::WEAK()); 388 | } 389 | 390 | TEST_CASE("GPIO_OpenDrain get drive strength") 391 | { 392 | GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); 393 | gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_value()), 394 | GPIO_MODE_INPUT_OUTPUT_OD, 395 | ESP_OK); 396 | gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3; 397 | gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK); 398 | gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength); 399 | 400 | GPIO_OpenDrain gpio(fix.num); 401 | 402 | CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST()); 403 | } 404 | -------------------------------------------------------------------------------- /host_test/gpio/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | esp-idf-cxx: 5 | path: ../../../ 6 | version: ">=0.1" 7 | -------------------------------------------------------------------------------- /host_test/gpio/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n 2 | CONFIG_IDF_TARGET="linux" 3 | CONFIG_CXX_EXCEPTIONS=y 4 | -------------------------------------------------------------------------------- /host_test/i2c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | set(COMPONENTS main) 5 | 6 | idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND) 7 | 8 | # Overriding components which should be mocked 9 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/") 10 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/") 11 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/") 12 | 13 | # Registration of cxx component 14 | list(APPEND EXTRA_COMPONENT_DIRS "../../") 15 | 16 | project(test_i2c_cxx_host) 17 | -------------------------------------------------------------------------------- /host_test/i2c/README.md: -------------------------------------------------------------------------------- 1 | | Supported Targets | Linux | 2 | | ----------------- | ----- | 3 | 4 | # Build 5 | `idf.py build` (sdkconfig.defaults sets the linux target by default) 6 | 7 | # Run 8 | `build/host_i2c_cxx_test.elf` 9 | -------------------------------------------------------------------------------- /host_test/i2c/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_get_property(cpp_component esp-idf-cxx COMPONENT_DIR) 2 | 3 | idf_component_register(SRCS "i2c_cxx_test.cpp" 4 | INCLUDE_DIRS 5 | "." 6 | "${cpp_component}/host_test/fixtures" 7 | "${cpp_component}/private_include" 8 | $ENV{IDF_PATH}/tools/catch 9 | PRIV_REQUIRES driver cmock) 10 | 11 | target_link_libraries(${COMPONENT_LIB} -lpthread) 12 | -------------------------------------------------------------------------------- /host_test/i2c/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | esp-idf-cxx: 5 | path: ../../../ 6 | version: ">=0.1" 7 | -------------------------------------------------------------------------------- /host_test/i2c/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n 2 | CONFIG_IDF_TARGET="linux" 3 | CONFIG_CXX_EXCEPTIONS=y 4 | CONFIG_SOC_I2C_SUPPORT_SLAVE=y 5 | -------------------------------------------------------------------------------- /host_test/spi/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | set(COMPONENTS main) 5 | 6 | idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND) 7 | 8 | # Overriding components which should be mocked 9 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/") 10 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/") 11 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/") 12 | 13 | # Registration of cxx component 14 | list(APPEND EXTRA_COMPONENT_DIRS "../../") 15 | 16 | project(test_spi_cxx_host) 17 | -------------------------------------------------------------------------------- /host_test/spi/README.md: -------------------------------------------------------------------------------- 1 | | Supported Targets | Linux | 2 | | ----------------- | ----- | 3 | 4 | # Build 5 | `idf.py build` (sdkconfig.defaults sets the linux target by default) 6 | 7 | # Run 8 | `build/host_spi_cxx_test.elf` 9 | -------------------------------------------------------------------------------- /host_test/spi/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_get_property(cpp_component esp-idf-cxx COMPONENT_DIR) 2 | 3 | idf_component_register(SRCS "spi_cxx_test.cpp" 4 | INCLUDE_DIRS 5 | "." 6 | "../../fixtures" 7 | "${cpp_component}/private_include" 8 | $ENV{IDF_PATH}/tools/catch 9 | PRIV_REQUIRES driver cmock) 10 | -------------------------------------------------------------------------------- /host_test/spi/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | esp-idf-cxx: 5 | path: ../../../ 6 | version: ">=0.1" 7 | -------------------------------------------------------------------------------- /host_test/spi/main/spi_cxx_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPI C++ unit tests 3 | * 4 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 5 | * 6 | * SPDX-License-Identifier: CC0 7 | * 8 | * This test code is in the Public Domain (or CC0 licensed, at your option.) 9 | * 10 | * Unless required by applicable law or agreed to in writing, this 11 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | * CONDITIONS OF ANY KIND, either express or implied. 13 | */ 14 | 15 | #define CATCH_CONFIG_MAIN 16 | #include 17 | #include "freertos/portmacro.h" 18 | #include "spi_host_cxx.hpp" 19 | #include "spi_host_private_cxx.hpp" 20 | #include "system_cxx.hpp" 21 | #include "test_fixtures.hpp" 22 | 23 | #include "catch.hpp" 24 | 25 | // TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead 26 | const char *esp_err_to_name(esp_err_t code) { 27 | return "host_test error"; 28 | } 29 | 30 | using namespace std; 31 | using namespace idf; 32 | 33 | TEST_CASE("SPITransferSize basic construction") 34 | { 35 | SPITransferSize transfer_size_0(0); 36 | CHECK(0 == transfer_size_0.get_value()); 37 | SPITransferSize transfer_size_1(47); 38 | CHECK(47 == transfer_size_1.get_value()); 39 | SPITransferSize transfer_size_default = SPITransferSize::default_size(); 40 | CHECK(0 == transfer_size_default.get_value()); 41 | } 42 | 43 | TEST_CASE("SPI gpio numbers work correctly") 44 | { 45 | GPIONum gpio_num_0(19); 46 | MOSI mosi_0(18); 47 | MOSI mosi_1(gpio_num_0.get_value()); 48 | MOSI mosi_2(mosi_0); 49 | CHECK(mosi_0 != mosi_1); 50 | CHECK(mosi_2 == mosi_0); 51 | CHECK(mosi_2.get_value() == 18u); 52 | } 53 | 54 | TEST_CASE("SPI_DMAConfig valid") 55 | { 56 | CHECK(SPI_DMAConfig::AUTO().get_value() == spi_common_dma_t::SPI_DMA_CH_AUTO); 57 | CHECK(SPI_DMAConfig::DISABLED().get_value() == spi_common_dma_t::SPI_DMA_DISABLED); 58 | } 59 | 60 | TEST_CASE("SPINum invalid argument") 61 | { 62 | CHECK_THROWS_AS(SPINum(-1), SPIException&); 63 | uint32_t host_raw = spi_host_device_t::SPI_HOST_MAX; 64 | CHECK_THROWS_AS(SPINum host(host_raw), SPIException&); 65 | } 66 | 67 | TEST_CASE("Master init failure") 68 | { 69 | CMockFixture cmock_fix; 70 | spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_FAIL); 71 | 72 | CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&); 73 | } 74 | 75 | TEST_CASE("Master invalid state") 76 | { 77 | CMockFixture cmock_fix; 78 | spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE); 79 | 80 | CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&); 81 | } 82 | 83 | TEST_CASE("build master") 84 | { 85 | SPIFix fix; 86 | 87 | SPIMaster master(SPINum(SPI2_HOST), 88 | MOSI(fix.bus_config.mosi_io_num), 89 | MISO(fix.bus_config.miso_io_num), 90 | SCLK(fix.bus_config.sclk_io_num)); 91 | } 92 | 93 | TEST_CASE("build QSPI master") 94 | { 95 | QSPIFix fix; 96 | 97 | SPIMaster master(SPINum(SPI2_HOST), 98 | MOSI(fix.bus_config.mosi_io_num), 99 | MISO(fix.bus_config.miso_io_num), 100 | SCLK(fix.bus_config.sclk_io_num), 101 | QSPIWP(fix.bus_config.quadwp_io_num), 102 | QSPIHD(fix.bus_config.quadhd_io_num)); 103 | } 104 | 105 | TEST_CASE("Master build device") 106 | { 107 | SPIFix fix; 108 | SPIDevFix dev_fix(CreateAnd::SUCCEED); 109 | 110 | SPIMaster master(SPINum(SPI2_HOST), 111 | MOSI(fix.bus_config.mosi_io_num), 112 | MISO(fix.bus_config.miso_io_num), 113 | SCLK(fix.bus_config.sclk_io_num)); 114 | 115 | master.create_dev(CS(4), Frequency::MHz(1)); 116 | } 117 | 118 | TEST_CASE("SPIDeviceHandle throws on driver error") 119 | { 120 | CMockFixture cmock_fix; 121 | SPIDevFix dev_fix(CreateAnd::FAIL); 122 | CHECK_THROWS_AS(SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)), SPIException&); 123 | } 124 | 125 | TEST_CASE("SPIDeviceHandle succeed") 126 | { 127 | CMockFixture cmock_fix; 128 | SPIDevFix dev_fix(CreateAnd::SUCCEED); 129 | SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 130 | } 131 | 132 | TEST_CASE("SPIDevice succeed") 133 | { 134 | CMockFixture cmock_fix; 135 | SPIDevFix dev_fix(CreateAnd::SUCCEED); 136 | SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 137 | } 138 | 139 | TEST_CASE("SPI transaction empty data throws") 140 | { 141 | CHECK_THROWS_AS(SPITransactionDescriptor transaction({}, reinterpret_cast(4747)), SPIException&); 142 | } 143 | 144 | TEST_CASE("SPI transaction device handle nullptr throws") 145 | { 146 | CHECK_THROWS_AS(SPITransactionDescriptor transaction({47}, nullptr), SPIException&); 147 | } 148 | 149 | TEST_CASE("SPI transaction not started wait_for") 150 | { 151 | CMockFixture cmock_fix; 152 | SPIDevFix dev_fix(CreateAnd::IGNORE); 153 | SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 154 | SPITransactionDescriptor transaction({47}, &handle); 155 | 156 | CHECK_THROWS_AS(transaction.wait_for(std::chrono::milliseconds(47)), SPITransferException&); 157 | } 158 | 159 | TEST_CASE("SPI transaction not started wait") 160 | { 161 | CMockFixture cmock_fix; 162 | SPIDevFix dev_fix(CreateAnd::IGNORE); 163 | SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 164 | SPITransactionDescriptor transaction({47}, &handle); 165 | 166 | CHECK_THROWS_AS(transaction.wait(), SPITransferException&); 167 | } 168 | 169 | TEST_CASE("SPI transaction not started get") 170 | { 171 | CMockFixture cmock_fix; 172 | SPIDevFix dev_fix(CreateAnd::IGNORE); 173 | SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 174 | SPITransactionDescriptor transaction({47}, &handle); 175 | 176 | CHECK_THROWS_AS(transaction.get(), SPITransferException&); 177 | } 178 | 179 | TEST_CASE("SPI transaction wait_for timeout") 180 | { 181 | CMockFixture cmock_fix; 182 | SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT); 183 | SPIDevFix dev_fix(CreateAnd::IGNORE); 184 | spi_device_acquire_bus_IgnoreAndReturn(ESP_OK); 185 | spi_device_release_bus_Ignore(); 186 | 187 | SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 188 | SPITransactionDescriptor transaction({47}, &handle); 189 | transaction.start(); 190 | 191 | CHECK(transaction.wait_for(std::chrono::milliseconds(47)) == false); 192 | 193 | // We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the 194 | // allocated transaction descriptor. 195 | transaction_fix.get_transaction_return = ESP_OK; 196 | spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK); 197 | transaction.wait(); 198 | } 199 | 200 | TEST_CASE("SPI transaction one byte") 201 | { 202 | CMockFixture cmock_fix; 203 | SPITransactionDescriptorFix fix(1, true); 204 | SPIDevFix dev_fix(CreateAnd::IGNORE); 205 | fix.rx_data = {0xA6}; 206 | 207 | SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 208 | SPITransactionDescriptor transaction({47}, &handle); 209 | 210 | transaction.start(); 211 | auto out_data = transaction.get(); 212 | 213 | CHECK(1 * 8 == fix.orig_trans->length); 214 | CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]); 215 | REQUIRE(out_data.begin() != out_data.end()); 216 | CHECK(0xA6 == out_data[0]); 217 | } 218 | 219 | TEST_CASE("SPI transaction two byte") 220 | { 221 | CMockFixture cmock_fix; 222 | SPITransactionDescriptorFix fix(2, true); 223 | SPIDevFix dev_fix(CreateAnd::IGNORE); 224 | fix.rx_data = {0xA6, 0xA7}; 225 | 226 | SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 227 | SPITransactionDescriptor transaction({47, 48}, &handle); 228 | transaction.start(); 229 | auto out_data = transaction.get(); 230 | 231 | CHECK(fix.size * 8 == fix.orig_trans->length); 232 | CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]); 233 | CHECK(48 == ((uint8_t*) fix.orig_trans->tx_buffer)[1]); 234 | REQUIRE(out_data.begin() != out_data.end()); 235 | REQUIRE(out_data.size() == 2); 236 | CHECK(0xA6 == out_data[0]); 237 | CHECK(0xA7 == out_data[1]); 238 | } 239 | 240 | TEST_CASE("SPI transaction future") 241 | { 242 | CMockFixture cmock_fix; 243 | SPITransactionDescriptorFix trans_fix(1, true); 244 | trans_fix.rx_data = {0xA6}; 245 | SPIDevFix dev_fix(CreateAnd::IGNORE); 246 | 247 | SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 248 | auto result = dev.transfer({47}); 249 | vector out_data = result.get(); 250 | 251 | CHECK(1 * 8 == trans_fix.orig_trans->length); 252 | CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]); 253 | REQUIRE(out_data.begin() != out_data.end()); 254 | CHECK(out_data.size() == 1); 255 | CHECK(0xA6 == out_data[0]); 256 | } 257 | 258 | TEST_CASE("SPI transaction with pre_callback") 259 | { 260 | CMockFixture cmock_fix; 261 | SPITransactionDescriptorFix trans_fix(1, true); 262 | trans_fix.rx_data = {0xA6}; 263 | SPIDevFix dev_fix(CreateAnd::SUCCEED); 264 | bool pre_cb_called = false; 265 | 266 | SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 267 | auto result = dev.transfer({47}, [&] (void *user) { pre_cb_called = true; }); 268 | vector out_data = result.get(); 269 | 270 | SPITransactionDescriptor *transaction = reinterpret_cast(trans_fix.orig_trans->user); 271 | dev_fix.dev_config.pre_cb(trans_fix.orig_trans); 272 | CHECK(true == pre_cb_called); 273 | CHECK(1 * 8 == trans_fix.orig_trans->length); 274 | CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]); 275 | REQUIRE(out_data.begin() != out_data.end()); 276 | CHECK(out_data.size() == 1); 277 | CHECK(0xA6 == out_data[0]); 278 | } 279 | 280 | TEST_CASE("SPI transaction with post_callback") 281 | { 282 | CMockFixture cmock_fix; 283 | SPITransactionDescriptorFix trans_fix(1, true); 284 | trans_fix.rx_data = {0xA6}; 285 | SPIDevFix dev_fix(CreateAnd::SUCCEED); 286 | bool post_cb_called = false; 287 | SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 288 | 289 | auto result = dev.transfer({47}, [&] (void *user) { }, [&] (void *user) { post_cb_called = true; }); 290 | vector out_data = result.get(); 291 | 292 | dev_fix.dev_config.post_cb(trans_fix.orig_trans); 293 | CHECK(true == post_cb_called); 294 | CHECK(1 * 8 == trans_fix.orig_trans->length); 295 | CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]); 296 | REQUIRE(out_data.begin() != out_data.end()); 297 | CHECK(out_data.size() == 1); 298 | CHECK(0xA6 == out_data[0]); 299 | } 300 | 301 | TEST_CASE("SPI transaction data routed to pre callback") 302 | { 303 | CMockFixture cmock_fix; 304 | SPITransactionDescriptorFix trans_fix(1, true); 305 | trans_fix.rx_data = {0xA6}; 306 | SPIDevFix dev_fix(CreateAnd::SUCCEED); 307 | bool pre_cb_called = false; 308 | SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 309 | 310 | auto result = dev.transfer({47}, 311 | [&] (void *user) { *(static_cast(user)) = true; }, 312 | [&] (void *user) { }, 313 | &pre_cb_called); 314 | result.get(); 315 | 316 | dev_fix.dev_config.pre_cb(trans_fix.orig_trans); 317 | CHECK(true == pre_cb_called); 318 | 319 | } 320 | 321 | TEST_CASE("SPI transaction data routed to post callback") 322 | { 323 | CMockFixture cmock_fix; 324 | SPITransactionDescriptorFix trans_fix(1, true); 325 | trans_fix.rx_data = {0xA6}; 326 | SPIDevFix dev_fix(CreateAnd::SUCCEED); 327 | bool post_cb_called = false; 328 | SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 329 | 330 | auto result = dev.transfer({47}, 331 | [&] (void *user) { }, 332 | [&] (void *user) { *(static_cast(user)) = true; }, 333 | &post_cb_called); 334 | result.get(); 335 | 336 | dev_fix.dev_config.post_cb(trans_fix.orig_trans); 337 | CHECK(true == post_cb_called); 338 | } 339 | 340 | TEST_CASE("SPI two transactions") 341 | { 342 | CMockFixture cmock_fix; 343 | SPITransactionDescriptorFix trans_fix(1, true); 344 | trans_fix.rx_data = {0xA6}; 345 | SPIDevFix dev_fix(CreateAnd::SUCCEED); 346 | bool pre_cb_called = false; 347 | SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 348 | std::function pre_callback = [&] (void *user) { 349 | pre_cb_called = true; 350 | }; 351 | 352 | auto result = dev.transfer({47}, pre_callback); 353 | vector out_data = result.get(); 354 | 355 | dev_fix.dev_config.pre_cb(trans_fix.orig_trans); 356 | CHECK(true == pre_cb_called); 357 | 358 | CHECK(1 * 8 == trans_fix.orig_trans->length); 359 | CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]); 360 | 361 | REQUIRE(out_data.begin() != out_data.end()); 362 | CHECK(out_data.size() == 1); 363 | CHECK(0xA6 == out_data[0]); 364 | 365 | // preparing the second transfer 366 | pre_cb_called = false; 367 | spi_device_acquire_bus_ExpectAndReturn(trans_fix.handle, portMAX_DELAY, ESP_OK); 368 | spi_device_acquire_bus_IgnoreArg_device(); 369 | spi_device_queue_trans_ExpectAndReturn(trans_fix.handle, nullptr, 0, ESP_OK); 370 | spi_device_queue_trans_IgnoreArg_trans_desc(); 371 | spi_device_queue_trans_IgnoreArg_handle(); 372 | spi_device_get_trans_result_ExpectAndReturn(trans_fix.handle, nullptr, portMAX_DELAY, ESP_OK); 373 | spi_device_get_trans_result_IgnoreArg_trans_desc(); 374 | spi_device_get_trans_result_IgnoreArg_handle(); 375 | spi_device_release_bus_Ignore(); 376 | 377 | 378 | result = dev.transfer({47}, pre_callback); 379 | result.get(); 380 | 381 | dev_fix.dev_config.pre_cb(trans_fix.orig_trans); 382 | CHECK(true == pre_cb_called); 383 | } 384 | 385 | TEST_CASE("SPIFuture invalid after default construction") 386 | { 387 | SPIFuture future; 388 | CHECK(false == future.valid()); 389 | } 390 | 391 | TEST_CASE("SPIFuture valid") 392 | { 393 | CMOCK_SETUP(); 394 | SPIDevFix dev_fix(CreateAnd::IGNORE); 395 | 396 | SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 397 | shared_ptr trans(new SPITransactionDescriptor(std::vector(47), &handle)); 398 | SPIFuture future(trans); 399 | 400 | CHECK(true == future.valid()); 401 | } 402 | 403 | TEST_CASE("SPIFuture wait_for timeout") 404 | { 405 | CMockFixture cmock_fix; 406 | SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT); 407 | SPIDevFix dev_fix(CreateAnd::IGNORE); 408 | spi_device_acquire_bus_IgnoreAndReturn(ESP_OK); 409 | spi_device_release_bus_Ignore(); 410 | 411 | SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 412 | shared_ptr transaction(new SPITransactionDescriptor(std::vector(47), &handle)); 413 | SPIFuture future(transaction); 414 | transaction->start(); 415 | 416 | CHECK(future.wait_for(std::chrono::milliseconds(47)) == std::future_status::timeout); 417 | 418 | // We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the 419 | // allocated transaction descriptor. 420 | transaction_fix.get_transaction_return = ESP_OK; 421 | spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK); 422 | 423 | future.wait(); 424 | } 425 | 426 | TEST_CASE("SPIFuture wait_for on SPIFuture") 427 | { 428 | CMockFixture cmock_fix; 429 | SPITransactionDescriptorFix trans_fix(1, true, 20); 430 | trans_fix.rx_data = {0xA6}; 431 | SPIDevFix dev_fix(CreateAnd::IGNORE); 432 | 433 | SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 434 | auto result = dev.transfer({47}); 435 | 436 | CHECK(result.wait_for(std::chrono::milliseconds(20)) == std::future_status::ready); 437 | } 438 | 439 | TEST_CASE("SPIFuture wait on SPIFuture") 440 | { 441 | CMockFixture cmock_fix; 442 | SPITransactionDescriptorFix trans_fix(1, true); 443 | trans_fix.rx_data = {0xA6}; 444 | SPIDevFix dev_fix(CreateAnd::IGNORE); 445 | 446 | SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); 447 | auto result = dev.transfer({47}); 448 | 449 | result.wait(); 450 | 451 | vector out_data = result.get(); 452 | CHECK(out_data.size() == 1); 453 | } 454 | -------------------------------------------------------------------------------- /host_test/spi/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n 2 | CONFIG_IDF_TARGET="linux" 3 | CONFIG_CXX_EXCEPTIONS=y 4 | -------------------------------------------------------------------------------- /host_test/system/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | set(COMPONENTS main) 5 | 6 | idf_component_set_property(driver USE_MOCK 1) 7 | 8 | # Overriding components which should be mocked 9 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/") 10 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/") 11 | list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/") 12 | 13 | # Registration of cxx component 14 | list(APPEND EXTRA_COMPONENT_DIRS "../../") 15 | 16 | project(test_system_cxx_host) 17 | 18 | add_custom_command( 19 | OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info" 20 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" 21 | COMMAND lcov --capture --directory . --output-file coverage.info 22 | COMMENT "Create coverage report" 23 | ) 24 | 25 | add_custom_command( 26 | OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage_report/" 27 | DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info" 28 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" 29 | COMMAND genhtml coverage.info --output-directory coverage_report/ 30 | COMMENT "Turn coverage report into html-based visualization" 31 | ) 32 | 33 | add_custom_target(coverage 34 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" 35 | DEPENDS "coverage_report/" 36 | ) 37 | -------------------------------------------------------------------------------- /host_test/system/README.md: -------------------------------------------------------------------------------- 1 | | Supported Targets | Linux | 2 | | ----------------- | ----- | 3 | 4 | # Build 5 | `idf.py build` (sdkconfig.defaults sets the linux target by default) 6 | 7 | # Run 8 | `build/system_cxx_host_test.elf` 9 | -------------------------------------------------------------------------------- /host_test/system/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_get_property(cpp_component esp-idf-cxx COMPONENT_DIR) 2 | 3 | idf_component_register(SRCS "system_cxx_test.cpp" 4 | "${cpp_component}/esp_exception.cpp" 5 | INCLUDE_DIRS 6 | "." 7 | "${cpp_component}/include" 8 | $ENV{IDF_PATH}/tools/catch 9 | PRIV_REQUIRES driver) 10 | 11 | target_compile_options(${COMPONENT_LIB} PUBLIC --coverage) 12 | target_link_libraries(${COMPONENT_LIB} --coverage) 13 | -------------------------------------------------------------------------------- /host_test/system/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | esp-idf-cxx: 5 | path: ../../../ 6 | version: ">=0.1" 7 | -------------------------------------------------------------------------------- /host_test/system/main/system_cxx_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * System C++ unit tests 3 | * 4 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 5 | * 6 | * SPDX-License-Identifier: CC0 7 | * 8 | * This test code is in the Public Domain (or CC0 licensed, at your option.) 9 | * 10 | * Unless required by applicable law or agreed to in writing, this 11 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | * CONDITIONS OF ANY KIND, either express or implied. 13 | */ 14 | 15 | #define CATCH_CONFIG_MAIN 16 | 17 | #include "catch.hpp" 18 | #include "system_cxx.hpp" 19 | 20 | // TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead 21 | const char *esp_err_to_name(esp_err_t code) { 22 | return "test"; 23 | } 24 | 25 | using namespace std; 26 | using namespace idf; 27 | 28 | TEST_CASE("Frequency invalid") 29 | { 30 | CHECK_THROWS_AS(Frequency(0), ESPException&); 31 | } 32 | 33 | TEST_CASE("Frequency constructors correct") 34 | { 35 | Frequency f0(440); 36 | CHECK(440 == f0.get_value()); 37 | Frequency f1 = Frequency::Hz(440); 38 | CHECK(440 == f1.get_value()); 39 | Frequency f2 = Frequency::KHz(440); 40 | CHECK(440000 == f2.get_value()); 41 | Frequency f3 = Frequency::MHz(440); 42 | CHECK(440000000 == f3.get_value()); 43 | } 44 | 45 | TEST_CASE("Frequency op ==") 46 | { 47 | Frequency f0(440); 48 | Frequency f1(440); 49 | CHECK(f1 == f0); 50 | } 51 | 52 | TEST_CASE("Frequency op !=") 53 | { 54 | Frequency f0(440); 55 | Frequency f1(441); 56 | CHECK(f1 != f0); 57 | } 58 | 59 | TEST_CASE("Frequency op >") 60 | { 61 | Frequency f0(440); 62 | Frequency f1(441); 63 | Frequency f2(440); 64 | CHECK(f1 > f0); 65 | CHECK(!(f0 > f1)); 66 | CHECK(!(f0 > f2)); 67 | } 68 | 69 | TEST_CASE("Frequency op <") 70 | { 71 | Frequency f0(440); 72 | Frequency f1(441); 73 | Frequency f2(440); 74 | CHECK(f0 < f1); 75 | CHECK(!(f1 < f0)); 76 | CHECK(!(f0 < f2)); 77 | } 78 | 79 | TEST_CASE("Frequency op >=") 80 | { 81 | Frequency f0(440); 82 | Frequency f1(441); 83 | Frequency f2(440); 84 | CHECK (f1 >= f0); 85 | CHECK(!(f0 >= f1)); 86 | CHECK (f0 >= f2); 87 | } 88 | 89 | TEST_CASE("Frequency op <=") 90 | { 91 | Frequency f0(440); 92 | Frequency f1(441); 93 | Frequency f2(440); 94 | CHECK (f0 <= f1); 95 | CHECK(!(f1 <= f0)); 96 | CHECK (f0 <= f2); 97 | } 98 | 99 | TEST_CASE("CHECK_THROW continues on ESP_OK") 100 | { 101 | esp_err_t error = ESP_OK; 102 | CHECK_THROW(error); 103 | } 104 | 105 | TEST_CASE("CHECK_THROW throws") 106 | { 107 | esp_err_t error = ESP_FAIL; 108 | CHECK_THROWS_AS(CHECK_THROW(error), ESPException&); 109 | } 110 | 111 | TEST_CASE("ESPException has working what() method") 112 | { 113 | try { 114 | throw ESPException(ESP_FAIL); 115 | } catch (ESPException &e) { 116 | CHECK(strcmp(esp_err_to_name(ESP_FAIL), e.what()) == 0); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /host_test/system/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n 2 | CONFIG_IDF_TARGET="linux" 3 | CONFIG_CXX_EXCEPTIONS=y 4 | -------------------------------------------------------------------------------- /i2c_cxx.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #ifdef __cpp_exceptions 8 | 9 | #include "driver/i2c.h" 10 | #include "i2c_cxx.hpp" 11 | 12 | using namespace std; 13 | 14 | namespace idf { 15 | 16 | #define I2C_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), I2CException) 17 | 18 | /** 19 | * I2C bus are defined in the header files, let's check that the values are correct 20 | */ 21 | #if SOC_I2C_NUM >= 2 22 | static_assert(I2C_NUM_1 == 1, "I2C_NUM_1 must be equal to 1"); 23 | #endif // SOC_I2C_NUM >= 2 24 | 25 | esp_err_t check_i2c_num(uint32_t i2c_num) noexcept 26 | { 27 | if (i2c_num >= I2C_NUM_MAX) { 28 | return ESP_ERR_INVALID_ARG; 29 | } 30 | 31 | return ESP_OK; 32 | } 33 | 34 | esp_err_t check_i2c_addr(uint32_t addr) noexcept 35 | { 36 | // maximum I2C address currently supported in the C++ classes is 127 37 | if (addr > 0x7f) { 38 | return ESP_ERR_INVALID_ARG; 39 | } 40 | 41 | return ESP_OK; 42 | } 43 | 44 | I2CException::I2CException(esp_err_t error) : ESPException(error) { } 45 | 46 | I2CTransferException::I2CTransferException(esp_err_t error) : I2CException(error) { } 47 | 48 | I2CAddress::I2CAddress(uint8_t addr) : StrongValueComparable (addr) 49 | { 50 | esp_err_t error = check_i2c_addr(addr); 51 | if (error != ESP_OK) { 52 | throw I2CException(error); 53 | } 54 | } 55 | 56 | I2CCommandLink::I2CCommandLink() 57 | { 58 | handle = i2c_cmd_link_create(); 59 | if (!handle) { 60 | throw I2CException(ESP_ERR_NO_MEM); 61 | } 62 | } 63 | 64 | I2CCommandLink::~I2CCommandLink() 65 | { 66 | i2c_cmd_link_delete(handle); 67 | } 68 | 69 | void I2CCommandLink::start() 70 | { 71 | I2C_CHECK_THROW(i2c_master_start(handle)); 72 | } 73 | 74 | void I2CCommandLink::write(const std::vector &bytes, bool expect_ack) 75 | { 76 | I2C_CHECK_THROW(i2c_master_write(handle, bytes.data(), bytes.size(), expect_ack)); 77 | } 78 | 79 | void I2CCommandLink::write_byte(uint8_t byte, bool expect_ack) 80 | { 81 | I2C_CHECK_THROW(i2c_master_write_byte(handle, byte, expect_ack)); 82 | } 83 | 84 | void I2CCommandLink::read(std::vector &bytes) 85 | { 86 | I2C_CHECK_THROW(i2c_master_read(handle, bytes.data(), bytes.size(), I2C_MASTER_LAST_NACK)); 87 | } 88 | 89 | void I2CCommandLink::stop() 90 | { 91 | I2C_CHECK_THROW(i2c_master_stop(handle)); 92 | } 93 | 94 | void I2CCommandLink::execute_transfer(I2CNumber i2c_num, chrono::milliseconds driver_timeout) 95 | { 96 | esp_err_t err = i2c_master_cmd_begin(i2c_num.get_value(), handle, driver_timeout.count() / portTICK_PERIOD_MS); 97 | if (err != ESP_OK) { 98 | throw I2CTransferException(err); 99 | } 100 | } 101 | 102 | I2CBus::I2CBus(I2CNumber i2c_number) : i2c_num(std::move(i2c_number)) { } 103 | 104 | I2CBus::~I2CBus() { } 105 | 106 | I2CMaster::I2CMaster(I2CNumber i2c_number, 107 | SCL_GPIO scl_gpio, 108 | SDA_GPIO sda_gpio, 109 | Frequency clock_speed, 110 | bool scl_pullup, 111 | bool sda_pullup) 112 | : I2CBus(std::move(i2c_number)) 113 | { 114 | i2c_config_t conf = {}; 115 | conf.mode = I2C_MODE_MASTER; 116 | conf.scl_io_num = scl_gpio.get_value(); 117 | conf.scl_pullup_en = scl_pullup; 118 | conf.sda_io_num = sda_gpio.get_value(); 119 | conf.sda_pullup_en = sda_pullup; 120 | conf.master.clk_speed = clock_speed.get_value(); 121 | I2C_CHECK_THROW(i2c_param_config(i2c_num.get_value(), &conf)); 122 | I2C_CHECK_THROW(i2c_driver_install(i2c_num.get_value(), conf.mode, 0, 0, 0)); 123 | } 124 | 125 | I2CMaster::~I2CMaster() 126 | { 127 | i2c_driver_delete(i2c_num.get_value()); 128 | } 129 | 130 | void I2CMaster::sync_write(I2CAddress i2c_addr, const vector &data) 131 | { 132 | I2CWrite writer(data); 133 | 134 | writer.do_transfer(i2c_num, i2c_addr); 135 | } 136 | 137 | std::vector I2CMaster::sync_read(I2CAddress i2c_addr, size_t n_bytes) 138 | { 139 | I2CRead reader(n_bytes); 140 | 141 | return reader.do_transfer(i2c_num, i2c_addr); 142 | } 143 | 144 | vector I2CMaster::sync_transfer(I2CAddress i2c_addr, 145 | const std::vector &write_data, 146 | size_t read_n_bytes) 147 | { 148 | I2CComposed composed_transfer; 149 | composed_transfer.add_write(write_data); 150 | composed_transfer.add_read(read_n_bytes); 151 | 152 | return composed_transfer.do_transfer(i2c_num, i2c_addr)[0]; 153 | } 154 | 155 | #if CONFIG_SOC_I2C_SUPPORT_SLAVE 156 | I2CSlave::I2CSlave(I2CNumber i2c_number, 157 | SCL_GPIO scl_gpio, 158 | SDA_GPIO sda_gpio, 159 | I2CAddress slave_addr, 160 | size_t rx_buf_len, 161 | size_t tx_buf_len, 162 | bool scl_pullup, 163 | bool sda_pullup) 164 | : I2CBus(std::move(i2c_number)) 165 | { 166 | i2c_config_t conf = {}; 167 | conf.mode = I2C_MODE_SLAVE; 168 | conf.scl_io_num = scl_gpio.get_value(); 169 | conf.scl_pullup_en = scl_pullup; 170 | conf.sda_io_num = sda_gpio.get_value(); 171 | conf.sda_pullup_en = sda_pullup; 172 | conf.slave.addr_10bit_en = 0; 173 | conf.slave.slave_addr = slave_addr.get_value(); 174 | I2C_CHECK_THROW(i2c_param_config(i2c_num.get_value(), &conf)); 175 | I2C_CHECK_THROW(i2c_driver_install(i2c_num.get_value(), conf.mode, rx_buf_len, tx_buf_len, 0)); 176 | } 177 | 178 | I2CSlave::~I2CSlave() 179 | { 180 | i2c_driver_delete(i2c_num.get_value()); 181 | } 182 | 183 | int I2CSlave::write_raw(const uint8_t *data, size_t data_len, chrono::milliseconds timeout) 184 | { 185 | return i2c_slave_write_buffer(i2c_num.get_value(), data, data_len, (TickType_t) timeout.count() / portTICK_PERIOD_MS); 186 | } 187 | 188 | int I2CSlave::read_raw(uint8_t *buffer, size_t buffer_len, chrono::milliseconds timeout) 189 | { 190 | return i2c_slave_read_buffer(i2c_num.get_value(), buffer, buffer_len, (TickType_t) timeout.count() / portTICK_PERIOD_MS); 191 | } 192 | #endif // CONFIG_SOC_I2C_SUPPORT_SLAVE 193 | 194 | I2CWrite::I2CWrite(const vector &bytes, chrono::milliseconds driver_timeout) 195 | : I2CTransfer(driver_timeout), bytes(bytes) 196 | { 197 | if (bytes.empty()) { 198 | throw I2CException(ESP_ERR_INVALID_ARG); 199 | } 200 | } 201 | 202 | void I2CWrite::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) 203 | { 204 | handle.start(); 205 | handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_WRITE); 206 | handle.write(bytes); 207 | } 208 | 209 | void I2CWrite::process_result() { } 210 | 211 | I2CRead::I2CRead(size_t size, chrono::milliseconds driver_timeout) 212 | : I2CTransfer >(driver_timeout), bytes(size) 213 | { 214 | if (size == 0) { 215 | throw I2CException(ESP_ERR_INVALID_ARG); 216 | } 217 | } 218 | 219 | void I2CRead::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) 220 | { 221 | handle.start(); 222 | handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_READ); 223 | handle.read(bytes); 224 | } 225 | 226 | vector I2CRead::process_result() 227 | { 228 | return bytes; 229 | } 230 | 231 | I2CComposed::I2CComposed(chrono::milliseconds driver_timeout) 232 | : I2CTransfer > >(driver_timeout), transfer_list() { } 233 | 234 | void I2CComposed::CompTransferNodeRead::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) 235 | { 236 | handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_READ); 237 | handle.read(bytes); 238 | } 239 | 240 | void I2CComposed::CompTransferNodeRead::process_result(std::vector > &read_results) 241 | { 242 | read_results.push_back(bytes); 243 | } 244 | 245 | void I2CComposed::CompTransferNodeWrite::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) 246 | { 247 | handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_WRITE); 248 | handle.write(bytes); 249 | } 250 | 251 | void I2CComposed::add_read(size_t size) 252 | { 253 | if (!size) { 254 | throw I2CException(ESP_ERR_INVALID_ARG); 255 | } 256 | 257 | transfer_list.push_back(make_shared(size)); 258 | } 259 | 260 | void I2CComposed::add_write(std::vector bytes) 261 | { 262 | if (bytes.empty()) { 263 | throw I2CException(ESP_ERR_INVALID_ARG); 264 | } 265 | 266 | transfer_list.push_back(make_shared(bytes)); 267 | } 268 | 269 | void I2CComposed::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) 270 | { 271 | for (auto it = transfer_list.begin(); it != transfer_list.end(); it++) { 272 | handle.start(); 273 | (*it)->queue_cmd(handle, i2c_addr); 274 | } 275 | } 276 | 277 | std::vector > I2CComposed::process_result() 278 | { 279 | std::vector > results; 280 | for (auto it = transfer_list.begin(); it != transfer_list.end(); it++) { 281 | (*it)->process_result(results); 282 | } 283 | return results; 284 | } 285 | 286 | } // idf 287 | 288 | #endif // __cpp_exceptions 289 | -------------------------------------------------------------------------------- /idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: '>=5.0' 4 | description: C++ wrapper classes around ESP IDF components 5 | url: https://github.com/espressif/esp-idf-cxx 6 | version: 1.0.0 7 | -------------------------------------------------------------------------------- /include/esp_event_api.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "esp_event.h" 10 | 11 | namespace idf { 12 | 13 | namespace event { 14 | 15 | /** 16 | * Abstract interface for direct calls to esp_event C-API. 17 | * This is generally not intended to be used directly. 18 | * It's main purpose is to provide ESPEventLoop a unified API not dependent on whether the default event loop or a 19 | * custom event loop is used. 20 | * The interface resembles the C-API, have a look there for further documentation. 21 | */ 22 | class ESPEventAPI { 23 | public: 24 | virtual ~ESPEventAPI() { } 25 | 26 | virtual esp_err_t handler_register(esp_event_base_t event_base, 27 | int32_t event_id, 28 | esp_event_handler_t event_handler, 29 | void* event_handler_arg, 30 | esp_event_handler_instance_t *instance) = 0; 31 | 32 | virtual esp_err_t handler_unregister(esp_event_base_t event_base, 33 | int32_t event_id, 34 | esp_event_handler_instance_t instance) = 0; 35 | 36 | virtual esp_err_t post(esp_event_base_t event_base, 37 | int32_t event_id, 38 | void* event_data, 39 | size_t event_data_size, 40 | TickType_t ticks_to_wait) = 0; 41 | }; 42 | 43 | /** 44 | * @brief API version with default event loop. 45 | * 46 | * It will direct calls to the default event loop API. 47 | */ 48 | class ESPEventAPIDefault : public ESPEventAPI { 49 | public: 50 | ESPEventAPIDefault(); 51 | virtual ~ESPEventAPIDefault(); 52 | 53 | /** 54 | * Copying would lead to deletion of event loop through destructor. 55 | */ 56 | ESPEventAPIDefault(const ESPEventAPIDefault &o) = delete; 57 | ESPEventAPIDefault& operator=(const ESPEventAPIDefault&) = delete; 58 | 59 | esp_err_t handler_register(esp_event_base_t event_base, 60 | int32_t event_id, 61 | esp_event_handler_t event_handler, 62 | void* event_handler_arg, 63 | esp_event_handler_instance_t *instance) override; 64 | 65 | esp_err_t handler_unregister(esp_event_base_t event_base, 66 | int32_t event_id, 67 | esp_event_handler_instance_t instance) override; 68 | 69 | esp_err_t post(esp_event_base_t event_base, 70 | int32_t event_id, 71 | void* event_data, 72 | size_t event_data_size, 73 | TickType_t ticks_to_wait) override; 74 | }; 75 | 76 | /** 77 | * @brief API version with custom event loop. 78 | * 79 | * It will direct calls to the custom event loop API. 80 | * The loop parameters are given in the constructor the same way it's done in esp_event_loop_create() in event.h. 81 | * This class also provides a run method in case the custom event loop was created without its own task. 82 | */ 83 | class ESPEventAPICustom : public ESPEventAPI { 84 | public: 85 | /** 86 | * @param event_loop_args the event loop arguments, refer to esp_event_loop_create() in event.h. 87 | */ 88 | ESPEventAPICustom(const esp_event_loop_args_t &event_loop_args); 89 | 90 | virtual ~ESPEventAPICustom(); 91 | 92 | /** 93 | * Copying would lead to deletion of event loop through destructor. 94 | */ 95 | ESPEventAPICustom(const ESPEventAPICustom &o) = delete; 96 | ESPEventAPICustom& operator=(const ESPEventAPICustom&) = delete; 97 | 98 | esp_err_t handler_register(esp_event_base_t event_base, 99 | int32_t event_id, 100 | esp_event_handler_t event_handler, 101 | void* event_handler_arg, 102 | esp_event_handler_instance_t *instance) override; 103 | 104 | esp_err_t handler_unregister(esp_event_base_t event_base, 105 | int32_t event_id, 106 | esp_event_handler_instance_t instance) override; 107 | 108 | esp_err_t post(esp_event_base_t event_base, 109 | int32_t event_id, 110 | void* event_data, 111 | size_t event_data_size, 112 | TickType_t ticks_to_wait) override; 113 | 114 | /** 115 | * Run the event loop. The behavior is the same as esp_event_loop_run in esp_event.h. 116 | */ 117 | esp_err_t run(TickType_t ticks_to_run); 118 | 119 | private: 120 | esp_event_loop_handle_t event_loop; 121 | }; 122 | 123 | } // event 124 | 125 | } // idf 126 | -------------------------------------------------------------------------------- /include/esp_event_cxx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #pragma once 8 | 9 | #ifdef __cpp_exceptions 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "esp_timer.h" 22 | #include "esp_err.h" 23 | #include "esp_log.h" 24 | #include "freertos/FreeRTOS.h" 25 | #include "freertos/queue.h" 26 | 27 | #include "esp_exception.hpp" 28 | #include "esp_event_api.hpp" 29 | 30 | namespace idf { 31 | 32 | namespace event { 33 | 34 | extern const std::chrono::milliseconds PLATFORM_MAX_DELAY_MS; 35 | 36 | const std::chrono::microseconds MIN_TIMEOUT(200); 37 | 38 | class EventException : public ESPException { 39 | public: 40 | EventException(esp_err_t error) : ESPException(error) { } 41 | }; 42 | 43 | /** 44 | * @brief 45 | * Thrown to signal a timeout in EventHandlerSync. 46 | */ 47 | class EventTimeout : public idf::event::EventException { 48 | public: 49 | EventTimeout(esp_err_t error) : EventException(error) { } 50 | }; 51 | 52 | /** 53 | * @brief 54 | * Event ID wrapper class to make C++ APIs more explicit. 55 | * 56 | * This prevents APIs from taking raw ints as event IDs which are not very expressive and may be 57 | * confused with other parameters of a function. 58 | */ 59 | class ESPEventID { 60 | public: 61 | ESPEventID() : id(0) { } 62 | explicit ESPEventID(int32_t event_id) : id(event_id) { } 63 | ESPEventID(const ESPEventID &rhs) : id(rhs.id) { } 64 | 65 | inline bool operator==(const ESPEventID &rhs) const { 66 | return id == rhs.get_id(); 67 | } 68 | 69 | inline ESPEventID &operator=(const ESPEventID& other) { 70 | id = other.id; 71 | return *this; 72 | } 73 | 74 | inline int32_t get_id() const { 75 | return id; 76 | } 77 | 78 | friend std::ostream& operator<<(std::ostream& os, const ESPEventID& id); 79 | 80 | private: 81 | int32_t id; 82 | }; 83 | 84 | inline std::ostream& operator<<(std::ostream &os, const ESPEventID& id) { 85 | os << id.id; 86 | return os; 87 | } 88 | 89 | /* 90 | * Helper struct to bundle event base and event ID. 91 | */ 92 | struct ESPEvent { 93 | ESPEvent() 94 | : base(nullptr), id() { } 95 | ESPEvent(esp_event_base_t event_base, const ESPEventID &event_id) 96 | : base(event_base), id(event_id) { } 97 | 98 | esp_event_base_t base; 99 | ESPEventID id; 100 | }; 101 | 102 | /** 103 | * Thrown if event registration, i.e. \c register_event() or \c register_event_timed(), fails. 104 | */ 105 | struct ESPEventRegisterException : public EventException { 106 | ESPEventRegisterException(esp_err_t err, const ESPEvent& event) 107 | : EventException(err), esp_event(event) { } 108 | 109 | const char *what() const noexcept 110 | { 111 | std::string ret_message = "Event base: " + std::string(esp_event.base) 112 | + ", Event ID: " + std::to_string(esp_event.id.get_id()); 113 | return ret_message.c_str(); 114 | } 115 | 116 | const ESPEvent esp_event; 117 | }; 118 | 119 | inline bool operator==(const ESPEvent &lhs, const ESPEvent &rhs) 120 | { 121 | return lhs.base == rhs.base && lhs.id == rhs.id; 122 | } 123 | 124 | TickType_t convert_ms_to_ticks(const std::chrono::milliseconds &time); 125 | 126 | /** 127 | * Callback-event combination for ESPEventLoop. 128 | * 129 | * Used to bind class-based handler instances to event_handler_hook which is registered into the C-based 130 | * esp event loop. 131 | * It can be used directly, however, the recommended way is to obtain a unique_ptr via ESPEventLoop::register_event(). 132 | */ 133 | class ESPEventReg { 134 | public: 135 | /** 136 | * Register the event handler \c cb to handle the events defined by \c ev. 137 | * 138 | * @param cb The handler to be called. 139 | * @param ev The event for which the handler is registered. 140 | * @param api The esp event api implementation. 141 | */ 142 | ESPEventReg(std::function cb, 143 | const ESPEvent& ev, 144 | std::shared_ptr api); 145 | 146 | /** 147 | * Unregister the event handler. 148 | */ 149 | virtual ~ESPEventReg(); 150 | 151 | protected: 152 | /** 153 | * This is esp_event's handler, all events registered go through this. 154 | */ 155 | static void event_handler_hook(void *handler_arg, 156 | esp_event_base_t event_base, 157 | int32_t event_id, 158 | void *event_data); 159 | 160 | /** 161 | * User event handler. 162 | */ 163 | std::function cb; 164 | 165 | /** 166 | * Helper function to enter the instance's scope from the generic \c event_handler_hook(). 167 | */ 168 | virtual void dispatch_event_handling(ESPEvent event, void *event_data); 169 | 170 | /** 171 | * Save the event here to be able to un-register from the event loop on destruction. 172 | */ 173 | ESPEvent event; 174 | 175 | /** 176 | * This API handle allows different sets of APIs to be applied, e.g. default event loop API and 177 | * custom event loop API. 178 | */ 179 | std::shared_ptr api; 180 | 181 | /** 182 | * Event handler instance from the esp event C API. 183 | */ 184 | esp_event_handler_instance_t instance; 185 | }; 186 | 187 | /** 188 | * Callback-event combination for ESPEventLoop with builtin timeout. 189 | * 190 | * Used to bind class-based handler instances to event_handler_hook which is registered into the C-based 191 | * esp event loop. 192 | * It can be used directly, however, the recommended way is to obtain a unique_ptr via ESPEventLoop::register_event(). 193 | */ 194 | class ESPEventRegTimed : public ESPEventReg { 195 | public: 196 | /** 197 | * Register the event handler \c cb to handle the events as well as a timeout callback in case the event doesn't 198 | * arrive on time. 199 | * 200 | * If the event \c ev is received before \c timeout milliseconds, then the event handler is invoked. 201 | * If no such event is received before \c timeout milliseconds, then the timeout callback is invoked. 202 | * After the timeout or the first occurance of the event, the timer will be deactivated. 203 | * The event handler registration will only be deactivated if the timeout occurs. 204 | * If event handler and timeout occur at the same time, only either the event handler or the timeout callback 205 | * will be invoked. 206 | * 207 | * @param cb The handler to be called. 208 | * @param ev The event for which the handler is registered. 209 | * @param timeout_cb The timeout callback which is called in case there is no event for \c timeout microseconds. 210 | * @param timeout The timeout in microseconds. 211 | * @param api The esp event api implementation. 212 | */ 213 | ESPEventRegTimed(std::function cb, 214 | const ESPEvent& ev, 215 | std::function timeout_cb, 216 | const std::chrono::microseconds &timeout, 217 | std::shared_ptr api); 218 | 219 | /** 220 | * Unregister the event handler, stop and delete the timer. 221 | */ 222 | virtual ~ESPEventRegTimed(); 223 | 224 | protected: 225 | 226 | /** 227 | * Helper function to hook directly into esp timer callback. 228 | */ 229 | static void timer_cb_hook(void *arg); 230 | 231 | /** 232 | * Helper function to enter the instance's scope from the generic \c event_handler_hook(). 233 | */ 234 | void dispatch_event_handling(ESPEvent event, void *event_data) override; 235 | 236 | /** 237 | * The timer callback which will be called on timeout. 238 | */ 239 | std::function timeout_cb; 240 | 241 | /** 242 | * Timer used for event timeouts. 243 | */ 244 | esp_timer_handle_t timer; 245 | 246 | /** 247 | * This mutex makes sure that a timeout and event callbacks aren't invoked both. 248 | */ 249 | std::mutex timeout_mutex; 250 | }; 251 | 252 | class ESPEventLoop { 253 | public: 254 | /** 255 | * Creates the ESP default event loop. 256 | * 257 | * @param api the interface to the esp_event api; this determines whether the default event loop is used 258 | * or a custom loop (or just a mock up for tests). May be nullptr, in which case it will created 259 | * here. 260 | * 261 | * @note may throw EventException 262 | */ 263 | ESPEventLoop(std::shared_ptr api = std::make_shared()); 264 | 265 | /** 266 | * Deletes the event loop implementation (depends on \c api). 267 | */ 268 | virtual ~ESPEventLoop(); 269 | 270 | /** 271 | * Registers a specific handler-event combination to the event loop. 272 | * 273 | * @return a reference to the combination of handler and event which can be used to unregister 274 | * this combination again later on. 275 | * 276 | * @note registering the same event twice will result in unregistering the earlier registered handler. 277 | * @note may throw EventException, ESPEventRegisterException 278 | */ 279 | std::unique_ptr register_event(const ESPEvent &event, 280 | std::function cb); 281 | 282 | /** 283 | * Sets a timeout for event. If the specified event isn't received within timeout, 284 | * timer_cb is called. 285 | * 286 | * @note this is independent from the normal event handling. Hence, registering an event for 287 | * timeout does not interfere with a different client that has registered normally for the 288 | * same event. 289 | */ 290 | std::unique_ptr register_event_timed(const ESPEvent &event, 291 | std::function cb, 292 | const std::chrono::microseconds &timeout, 293 | std::function timer_cb); 294 | 295 | /** 296 | * Posts an event and corresponding data. 297 | * 298 | * @param event the event to post 299 | * @param event_data The event data. A copy will be made internally and a pointer to the copy will be passed to the 300 | * event handler. 301 | * @param wait_time the maximum wait time the function tries to post the event 302 | */ 303 | template 304 | void post_event_data(const ESPEvent &event, 305 | T &event_data, 306 | const std::chrono::milliseconds &wait_time = PLATFORM_MAX_DELAY_MS); 307 | 308 | /** 309 | * Posts an event. 310 | * 311 | * No event data will be send. The event handler will receive a nullptr. 312 | * 313 | * @param event the event to post 314 | * @param wait_time the maximum wait time the function tries to post the event 315 | */ 316 | void post_event_data(const ESPEvent &event, 317 | const std::chrono::milliseconds &wait_time = PLATFORM_MAX_DELAY_MS); 318 | 319 | private: 320 | /** 321 | * This API handle allows different sets of APIs to be applied, e.g. default event loop API and 322 | * custom event loop API. 323 | */ 324 | std::shared_ptr api; 325 | }; 326 | 327 | template 328 | void ESPEventLoop::post_event_data(const ESPEvent &event, 329 | T &event_data, 330 | const std::chrono::milliseconds &wait_time) 331 | { 332 | esp_err_t result = api->post(event.base, 333 | event.id.get_id(), 334 | &event_data, 335 | sizeof(event_data), 336 | convert_ms_to_ticks(wait_time)); 337 | 338 | if (result != ESP_OK) { 339 | throw ESPException(result); 340 | } 341 | } 342 | 343 | } // namespace event 344 | 345 | } // namespace idf 346 | 347 | #endif // __cpp_exceptions 348 | -------------------------------------------------------------------------------- /include/esp_exception.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #pragma once 8 | 9 | #ifdef __cpp_exceptions 10 | 11 | #include "esp_err.h" 12 | #include 13 | 14 | namespace idf { 15 | 16 | /** 17 | * @brief 18 | * General exception class for all C++ exceptions in IDF. 19 | * 20 | * All throwing code in IDF should use either this exception directly or a sub-classes. 21 | * An error from the underlying IDF function is mandatory. The idea is to wrap the orignal IDF error code to keep 22 | * the error scheme partially compatible. If an exception occurs in a higher level C++ code not directly wrapping 23 | * IDF functions, an appropriate error code reflecting the cause must be chosen or newly created. 24 | */ 25 | class ESPException : public std::exception { 26 | public: 27 | /** 28 | * @param error Error from underlying IDF functions. 29 | */ 30 | ESPException(esp_err_t error); 31 | 32 | virtual ~ESPException() { } 33 | 34 | /** 35 | * @return A textual representation of the contained error. This method only wraps \c esp_err_to_name. 36 | */ 37 | virtual const char *what() const noexcept; 38 | 39 | /** 40 | * Error from underlying IDF functions. If an exception occurs in a higher level C++ code not directly wrapping 41 | * IDF functions, an appropriate error code reflecting the cause must be chosen or newly created. 42 | */ 43 | const esp_err_t error; 44 | }; 45 | 46 | /** 47 | * Convenience macro to help converting IDF error codes into ESPException. 48 | */ 49 | #define CHECK_THROW(error_) \ 50 | do { \ 51 | esp_err_t result = error_; \ 52 | if (result != ESP_OK) throw idf::ESPException(result); \ 53 | } while (0) 54 | 55 | /** 56 | * Convenience macro to help converting IDF error codes into a child of ESPException. 57 | */ 58 | #define CHECK_THROW_SPECIFIC(error_, exception_type_) \ 59 | do { \ 60 | esp_err_t result = (error_); \ 61 | if (result != ESP_OK) throw idf::exception_type_(result); \ 62 | } while (0) 63 | 64 | } // namespace idf 65 | 66 | #endif // __cpp_exceptions 67 | -------------------------------------------------------------------------------- /include/esp_timer_cxx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #pragma once 8 | 9 | #ifdef __cpp_exceptions 10 | 11 | #include 12 | #include 13 | #include 14 | #include "esp_exception.hpp" 15 | #include "esp_timer.h" 16 | 17 | namespace idf { 18 | 19 | namespace esp_timer { 20 | 21 | /** 22 | * @brief Get time since boot 23 | * @return time since \c esp_timer_init() was called (this normally happens early during application startup). 24 | */ 25 | static inline std::chrono::microseconds get_time() 26 | { 27 | return std::chrono::microseconds(esp_timer_get_time()); 28 | } 29 | 30 | /** 31 | * @brief Get the timestamp when the next timeout is expected to occur 32 | * @return Timestamp of the nearest timer event. 33 | * The timebase is the same as for the values returned by \c get_time(). 34 | */ 35 | static inline std::chrono::microseconds get_next_alarm() 36 | { 37 | return std::chrono::microseconds(esp_timer_get_next_alarm()); 38 | } 39 | 40 | 41 | /** 42 | * @brief 43 | * A timer using the esp_timer component which can be started either as one-shot timer or periodically. 44 | */ 45 | class ESPTimer { 46 | public: 47 | /** 48 | * @param timeout_cb The timeout callback. 49 | * @param timer_name The name of the timer (optional). This is for debugging using \c esp_timer_dump(). 50 | */ 51 | ESPTimer(std::function timeout_cb, const std::string &timer_name = "ESPTimer"); 52 | 53 | /** 54 | * Stop the timer if necessary and delete it. 55 | */ 56 | ~ESPTimer(); 57 | 58 | /** 59 | * Default copy constructor is deleted since one instance of esp_timer_handle_t must not be shared. 60 | */ 61 | ESPTimer(const ESPTimer&) = delete; 62 | 63 | /** 64 | * Default copy assignment is deleted since one instance of esp_timer_handle_t must not be shared. 65 | */ 66 | ESPTimer &operator=(const ESPTimer&) = delete; 67 | 68 | /** 69 | * @brief Start one-shot timer 70 | * 71 | * Timer should not be running (started) when this function is called. 72 | * 73 | * @param timeout timer timeout, in microseconds relative to the current moment. 74 | * 75 | * @throws ESPException with error ESP_ERR_INVALID_STATE if the timer is already running. 76 | */ 77 | inline void start(std::chrono::microseconds timeout) 78 | { 79 | CHECK_THROW(esp_timer_start_once(timer_handle, timeout.count())); 80 | } 81 | 82 | /** 83 | * @brief Start periodic timer 84 | * 85 | * Timer should not be running when this function is called. This function will 86 | * start a timer which will trigger every 'period' microseconds. 87 | * 88 | * Timer should not be running (started) when this function is called. 89 | * 90 | * @param timeout timer timeout, in microseconds relative to the current moment. 91 | * 92 | * @throws ESPException with error ESP_ERR_INVALID_STATE if the timer is already running. 93 | */ 94 | inline void start_periodic(std::chrono::microseconds period) 95 | { 96 | CHECK_THROW(esp_timer_start_periodic(timer_handle, period.count())); 97 | } 98 | 99 | /** 100 | * @brief Stop the previously started timer. 101 | * 102 | * This function stops the timer previously started using \c start() or \c start_periodic(). 103 | * 104 | * @throws ESPException with error ESP_ERR_INVALID_STATE if the timer has not been started yet. 105 | */ 106 | inline void stop() 107 | { 108 | CHECK_THROW(esp_timer_stop(timer_handle)); 109 | } 110 | 111 | private: 112 | /** 113 | * Internal callback to hook into esp_timer component. 114 | */ 115 | static void esp_timer_cb(void *arg); 116 | 117 | /** 118 | * Timer instance of the underlying esp_event component. 119 | */ 120 | esp_timer_handle_t timer_handle; 121 | 122 | /** 123 | * Callback which will be called once the timer triggers. 124 | */ 125 | std::function timeout_cb; 126 | 127 | /** 128 | * Name of the timer, will be passed to the underlying timer framework and is used for debugging. 129 | */ 130 | const std::string name; 131 | }; 132 | 133 | } // esp_timer 134 | 135 | } // idf 136 | 137 | #endif // __cpp_exceptions 138 | -------------------------------------------------------------------------------- /include/gpio_cxx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #pragma once 8 | 9 | #if __cpp_exceptions 10 | 11 | #include "esp_exception.hpp" 12 | #include "system_cxx.hpp" 13 | 14 | namespace idf { 15 | 16 | /** 17 | * @brief Exception thrown for errors in the GPIO C++ API. 18 | */ 19 | struct GPIOException : public ESPException { 20 | /** 21 | * @param error The IDF error representing the error class of the error to throw. 22 | */ 23 | GPIOException(esp_err_t error); 24 | }; 25 | 26 | /** 27 | * Check if the numeric pin number is valid on the current hardware. 28 | */ 29 | esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept; 30 | 31 | /** 32 | * Check if the numeric value of a drive strength is valid on the current hardware. 33 | */ 34 | esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept; 35 | 36 | /** 37 | * This is a "Strong Value Type" class for GPIO. The GPIO pin number is checked during construction according to 38 | * the hardware capabilities. This means that any GPIONumBase object is guaranteed to contain a valid GPIO number. 39 | * See also the template class \c StrongValue. 40 | */ 41 | template 42 | class GPIONumBase final : public StrongValueComparable { 43 | public: 44 | /** 45 | * @brief Create a numerical pin number representation and make sure it's correct. 46 | * 47 | * @throw GPIOException if the number does not reflect a valid GPIO number on the current hardware. 48 | */ 49 | explicit GPIONumBase(uint32_t pin) : StrongValueComparable(pin) 50 | { 51 | esp_err_t pin_check_result = check_gpio_pin_num(pin); 52 | if (pin_check_result != ESP_OK) { 53 | throw GPIOException(pin_check_result); 54 | } 55 | } 56 | 57 | using StrongValueComparable::operator==; 58 | using StrongValueComparable::operator!=; 59 | }; 60 | 61 | /** 62 | * This is a TAG type whose sole purpose is to create a distinct type from GPIONumBase. 63 | */ 64 | class GPIONumType; 65 | 66 | /** 67 | * A GPIO number type used for general GPIOs, in contrast to specific GPIO pins like e.g. SPI_SCLK. 68 | */ 69 | using GPIONum = GPIONumBase; 70 | 71 | /** 72 | * Level of an input GPIO. 73 | */ 74 | enum class GPIOLevel { 75 | HIGH, 76 | LOW 77 | }; 78 | 79 | /** 80 | * Represents a valid pull up configuration for GPIOs. 81 | * It is supposed to resemble an enum type, hence it has static creation methods and a private constructor. 82 | * This class is a "Strong Value Type", see also the template class \c StrongValue for more properties. 83 | */ 84 | class GPIOPullMode final : public StrongValueComparable { 85 | private: 86 | /** 87 | * Constructor is private since it should only be accessed by the static creation methods. 88 | * 89 | * @param pull_mode A valid numerical respresentation of the pull up configuration. Must be valid! 90 | */ 91 | explicit GPIOPullMode(uint32_t pull_mode) : StrongValueComparable(pull_mode) { } 92 | 93 | public: 94 | /** 95 | * Create a representation of a floating pin configuration. 96 | * For more information, check the driver and HAL files. 97 | */ 98 | static GPIOPullMode FLOATING(); 99 | 100 | /** 101 | * Create a representation of a pullup configuration. 102 | * For more information, check the driver and HAL files. 103 | */ 104 | static GPIOPullMode PULLUP(); 105 | 106 | /** 107 | * Create a representation of a pulldown configuration. 108 | * For more information, check the driver and HAL files. 109 | */ 110 | static GPIOPullMode PULLDOWN(); 111 | 112 | using StrongValueComparable::operator==; 113 | using StrongValueComparable::operator!=; 114 | }; 115 | 116 | /** 117 | * @brief Represents a valid wakup interrupt type for GPIO inputs. 118 | * 119 | * This class is a "Strong Value Type", see also the template class \c StrongValue for more properties. 120 | * It is supposed to resemble an enum type, hence it has static creation methods and a private constructor. 121 | * For a detailed mapping of interrupt types to numeric values, please refer to the driver types and implementation. 122 | */ 123 | class GPIOWakeupIntrType final: public StrongValueComparable { 124 | private: 125 | /** 126 | * Constructor is private since it should only be accessed by the static creation methods. 127 | * 128 | * @param pull_mode A valid numerical respresentation of a possible interrupt level to wake up. Must be valid! 129 | */ 130 | explicit GPIOWakeupIntrType(uint32_t interrupt_level) : StrongValueComparable(interrupt_level) { } 131 | 132 | public: 133 | static GPIOWakeupIntrType LOW_LEVEL(); 134 | static GPIOWakeupIntrType HIGH_LEVEL(); 135 | }; 136 | 137 | /** 138 | * Class representing a valid drive strength for GPIO outputs. 139 | * This class is a "Strong Value Type", see also the template class \c StrongValue for more properties. 140 | * For a detailed mapping for values to drive strengths, please refer to the datasheet of the chip you are using. 141 | * E.g. for ESP32, the values in general are the following: 142 | * - WEAK: 5mA 143 | * - STRONGER: 10mA 144 | * - DEFAULT/MEDIUM: 20mA 145 | * - STRONGEST: 40mA 146 | */ 147 | class GPIODriveStrength final : public StrongValueComparable { 148 | public: 149 | /** 150 | * @brief Create a drive strength representation and checks its validity. 151 | * 152 | * After construction, this class should have a guaranteed valid strength representation. 153 | * 154 | * @param strength the numeric value mapping for a particular strength. For possible ranges, look at the 155 | * static creation functions below. 156 | * @throws GPIOException if the supplied number is out of the hardware capable range. 157 | */ 158 | explicit GPIODriveStrength(uint32_t strength) : StrongValueComparable(strength) 159 | { 160 | esp_err_t strength_check_result = check_gpio_drive_strength(strength); 161 | if (strength_check_result != ESP_OK) { 162 | throw GPIOException(strength_check_result); 163 | } 164 | } 165 | 166 | /** 167 | * Create a representation of the default drive strength. 168 | * For more information, check the datasheet and driver and HAL files. 169 | */ 170 | static GPIODriveStrength DEFAULT(); 171 | 172 | /** 173 | * Create a representation of the weak drive strength. 174 | * For more information, check the datasheet and driver and HAL files. 175 | */ 176 | static GPIODriveStrength WEAK(); 177 | 178 | /** 179 | * Create a representation of the less weak drive strength. 180 | * For more information, check the datasheet and driver and HAL files. 181 | */ 182 | static GPIODriveStrength LESS_WEAK(); 183 | 184 | /** 185 | * Create a representation of the medium drive strength. 186 | * For more information, check the datasheet and driver and HAL files. 187 | */ 188 | static GPIODriveStrength MEDIUM(); 189 | 190 | /** 191 | * Create a representation of the strong drive strength. 192 | */ 193 | static GPIODriveStrength STRONGEST(); 194 | 195 | using StrongValueComparable::operator==; 196 | using StrongValueComparable::operator!=; 197 | }; 198 | 199 | /** 200 | * @brief Implementations commonly used functionality for all GPIO configurations. 201 | * 202 | * Some functionality is only for specific configurations (set and get drive strength) but is necessary here 203 | * to avoid complicating the inheritance hierarchy of the GPIO classes. 204 | * Child classes implementing any GPIO configuration (output, input, etc.) are meant to intherit from this class 205 | * and possibly make some of the functionality publicly available. 206 | */ 207 | class GPIOBase { 208 | protected: 209 | /** 210 | * @brief Construct a GPIO. 211 | * 212 | * This constructor will only reset the GPIO but leaves the actual configuration (input, output, etc.) to 213 | * the sub class. 214 | * 215 | * @param num GPIO pin number of the GPIO to be configured. 216 | * 217 | * @throws GPIOException 218 | * - if the underlying driver function fails 219 | */ 220 | GPIOBase(GPIONum num); 221 | 222 | /** 223 | * @brief Enable gpio pad hold function. 224 | * 225 | * The gpio pad hold function works in both input and output modes, but must be output-capable gpios. 226 | * If pad hold enabled: 227 | * in output mode: the output level of the pad will be force locked and can not be changed. 228 | * in input mode: the input value read will not change, regardless the changes of input signal. 229 | * 230 | * @throws GPIOException if the underlying driver function fails. 231 | */ 232 | void hold_en(); 233 | 234 | /** 235 | * @brief Disable gpio pad hold function. 236 | * 237 | * @throws GPIOException if the underlying driver function fails. 238 | */ 239 | void hold_dis(); 240 | 241 | /** 242 | * @brief Configure the drive strength of the GPIO. 243 | * 244 | * @param strength The drive strength. Refer to \c GPIODriveStrength for more details. 245 | * 246 | * @throws GPIOException if the underlying driver function fails. 247 | */ 248 | void set_drive_strength(GPIODriveStrength strength); 249 | 250 | /** 251 | * @brief Return the current drive strength of the GPIO. 252 | * 253 | * @return The currently configured drive strength. Refer to \c GPIODriveStrength for more details. 254 | * 255 | * @throws GPIOException if the underlying driver function fails. 256 | */ 257 | GPIODriveStrength get_drive_strength(); 258 | 259 | /** 260 | * @brief The number of the configured GPIO pin. 261 | */ 262 | GPIONum gpio_num; 263 | }; 264 | 265 | /** 266 | * @brief This class represents a GPIO which is configured as output. 267 | */ 268 | class GPIO_Output : public GPIOBase { 269 | public: 270 | /** 271 | * @brief Construct and configure a GPIO as output. 272 | * 273 | * @param num GPIO pin number of the GPIO to be configured. 274 | * 275 | * @throws GPIOException 276 | * - if the underlying driver function fails 277 | */ 278 | GPIO_Output(GPIONum num); 279 | 280 | /** 281 | * @brief Set GPIO to high level. 282 | * 283 | * @throws GPIOException if the underlying driver function fails. 284 | */ 285 | void set_high() const; 286 | 287 | /** 288 | * @brief Set GPIO to low level. 289 | * 290 | * @throws GPIOException if the underlying driver function fails. 291 | */ 292 | void set_low() const; 293 | 294 | using GPIOBase::set_drive_strength; 295 | using GPIOBase::get_drive_strength; 296 | }; 297 | 298 | /** 299 | * @brief This class represents a GPIO which is configured as input. 300 | */ 301 | class GPIOInput : public GPIOBase { 302 | public: 303 | /** 304 | * @brief Construct and configure a GPIO as input. 305 | * 306 | * @param num GPIO pin number of the GPIO to be configured. 307 | * 308 | * @throws GPIOException 309 | * - if the underlying driver function fails 310 | */ 311 | GPIOInput(GPIONum num); 312 | 313 | /** 314 | * @brief Read the current level of the GPIO. 315 | * 316 | * @return The GPIO current level of the GPIO. 317 | */ 318 | GPIOLevel get_level() const noexcept; 319 | 320 | /** 321 | * @brief Configure the internal pull-up and pull-down restors. 322 | * 323 | * @param mode The pull-up/pull-down configuration see \c GPIOPullMode. 324 | * 325 | * @throws GPIOException if the underlying driver function fails. 326 | */ 327 | void set_pull_mode(GPIOPullMode mode) const; 328 | 329 | /** 330 | * @brief Configure the pin as wake up pin. 331 | * 332 | * @throws GPIOException if the underlying driver function fails. 333 | */ 334 | void wakeup_enable(GPIOWakeupIntrType interrupt_type) const; 335 | 336 | /** 337 | * @brief Disable wake up functionality for this pin if it was enabled before. 338 | * 339 | * @throws GPIOException if the underlying driver function fails. 340 | */ 341 | void wakeup_disable() const; 342 | }; 343 | 344 | /** 345 | * @brief This class represents a GPIO which is configured as open drain output and input at the same time. 346 | * 347 | * This class facilitates bit-banging for single wire protocols. 348 | */ 349 | class GPIO_OpenDrain : public GPIOInput { 350 | public: 351 | /** 352 | * @brief Construct and configure a GPIO as open drain output as well as input. 353 | * 354 | * @param num GPIO pin number of the GPIO to be configured. 355 | * 356 | * @throws GPIOException 357 | * - if the underlying driver function fails 358 | */ 359 | GPIO_OpenDrain(GPIONum num); 360 | 361 | /** 362 | * @brief Set GPIO to floating level. 363 | * 364 | * @throws GPIOException if the underlying driver function fails. 365 | */ 366 | void set_floating() const; 367 | 368 | /** 369 | * @brief Set GPIO to low level. 370 | * 371 | * @throws GPIOException if the underlying driver function fails. 372 | */ 373 | void set_low() const; 374 | 375 | using GPIOBase::set_drive_strength; 376 | using GPIOBase::get_drive_strength; 377 | }; 378 | 379 | } 380 | 381 | #endif 382 | -------------------------------------------------------------------------------- /include/spi_cxx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #pragma once 8 | 9 | #if __cpp_exceptions 10 | 11 | #include "esp_exception.hpp" 12 | #include "gpio_cxx.hpp" 13 | #include "system_cxx.hpp" 14 | 15 | namespace idf { 16 | 17 | /** 18 | * @brief Exception which is thrown in the context of SPI C++ classes. 19 | */ 20 | struct SPIException : public ESPException { 21 | SPIException(esp_err_t error); 22 | }; 23 | 24 | /** 25 | * @brief The maximum SPI transfer size in bytes. 26 | */ 27 | class SPITransferSize : public StrongValueOrdered { 28 | public: 29 | /** 30 | * @brief Create a valid SPI transfer size. 31 | * 32 | * @param transfer_size The raw transfer size in bytes. 33 | */ 34 | explicit SPITransferSize(size_t transfer_size) noexcept : StrongValueOrdered(transfer_size) { } 35 | 36 | static SPITransferSize default_size() { 37 | return SPITransferSize(0); 38 | } 39 | }; 40 | 41 | /** 42 | * @brief Check if the raw uint32_t spi number is in the range according to the hardware. 43 | */ 44 | esp_err_t check_spi_num(uint32_t spi_num) noexcept; 45 | 46 | /** 47 | * @brief Represents a valid SPI host number. 48 | * 49 | * ESP chips may have different independent SPI peripherals. This SPI number distinguishes between them. 50 | */ 51 | class SPINum : public StrongValueComparable { 52 | public: 53 | /** 54 | * @brief Create a valid SPI host number. 55 | * 56 | * @param host_id_raw The raw SPI host number. 57 | * 58 | * @throw SPIException if the passed SPI host number is incorrect. 59 | */ 60 | explicit SPINum(uint32_t host_id_raw) : StrongValueComparable(host_id_raw) 61 | { 62 | esp_err_t spi_num_check_result = check_spi_num(host_id_raw); 63 | if (spi_num_check_result != ESP_OK) { 64 | throw SPIException(spi_num_check_result); 65 | } 66 | } 67 | }; 68 | 69 | /** 70 | * @brief Represents a valid MOSI signal pin number. 71 | */ 72 | class MOSI_type; 73 | using MOSI = GPIONumBase; 74 | 75 | /** 76 | * @brief Represents a valid MISO signal pin number. 77 | */ 78 | class MISO_type; 79 | using MISO = GPIONumBase; 80 | 81 | /** 82 | * @brief Represents a valid SCLK signal pin number. 83 | */ 84 | class SCLK_type; 85 | using SCLK = GPIONumBase; 86 | 87 | /** 88 | * @brief Represents a valid CS (chip select) signal pin number. 89 | */ 90 | class CS_type; 91 | using CS = GPIONumBase; 92 | 93 | /** 94 | * @brief Represents a valid QSPIWP signal pin number. 95 | */ 96 | class QSPIWP_type; 97 | using QSPIWP = GPIONumBase; 98 | 99 | /** 100 | * @brief Represents a valid QSPIHD signal pin number. 101 | */ 102 | class QSPIHD_type; 103 | using QSPIHD = GPIONumBase; 104 | 105 | /** 106 | * @brief Represents a valid SPI DMA configuration. Use it similar to an enum. 107 | */ 108 | class SPI_DMAConfig : public StrongValueComparable { 109 | /** 110 | * Constructor is hidden to enforce object invariants. 111 | * Use the static creation methods to create instances. 112 | */ 113 | explicit SPI_DMAConfig(uint32_t channel_num) : StrongValueComparable(channel_num) { } 114 | 115 | public: 116 | /** 117 | * @brief Create a configuration with DMA disabled. 118 | */ 119 | static SPI_DMAConfig DISABLED(); 120 | 121 | /** 122 | * @brief Create a configuration where the driver allocates DMA. 123 | */ 124 | static SPI_DMAConfig AUTO(); 125 | }; 126 | 127 | } 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /include/system_cxx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #pragma once 8 | 9 | /** 10 | * This file contains helper classes for commonly used IDF types. The classes make the use of these types easier and 11 | * safer. 12 | * In particular, their usage provides greater type-safety of function arguments and "correctness by construction". 13 | */ 14 | 15 | #ifndef __cpp_exceptions 16 | #error system C++ classes only usable when C++ exceptions enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig 17 | #endif 18 | 19 | #include "esp_exception.hpp" 20 | 21 | /** 22 | * This is a "Strong Value Type" base class for types in IDF C++ classes. 23 | * The idea is that subclasses completely check the contained value during construction. 24 | * After that, it's trapped and encapsulated inside and cannot be changed anymore. 25 | * Consequently, the API functions receiving a correctly implemented sub class as parameter 26 | * don't need to check it anymore. Only at API boundaries the valid value will be retrieved 27 | * with get_value(). 28 | */ 29 | template 30 | class StrongValue { 31 | protected: 32 | constexpr StrongValue(ValueT value_arg) : value(value_arg) { } 33 | 34 | template 35 | RawType get_value() const { 36 | return static_cast(value); 37 | } 38 | 39 | private: 40 | ValueT value; 41 | }; 42 | 43 | /** 44 | * This class adds comparison properties to StrongValue, but no sorting and ordering properties. 45 | */ 46 | template 47 | class StrongValueComparable : public StrongValue { 48 | protected: 49 | constexpr StrongValueComparable(ValueT value_arg) : StrongValue(value_arg) { } 50 | 51 | public: 52 | using StrongValue::get_value; 53 | 54 | bool operator==(const StrongValueComparable &other_gpio) const 55 | { 56 | return get_value() == other_gpio.get_value(); 57 | } 58 | 59 | bool operator!=(const StrongValueComparable &other_gpio) const 60 | { 61 | return get_value() != other_gpio.get_value(); 62 | } 63 | }; 64 | 65 | namespace idf { 66 | 67 | /** 68 | * This class adds ordering and sorting properties to StrongValue. 69 | */ 70 | template 71 | class StrongValueOrdered : public StrongValueComparable { 72 | public: 73 | StrongValueOrdered(ValueT value) : StrongValueComparable(value) { } 74 | 75 | using StrongValueComparable::get_value; 76 | 77 | bool operator>(const StrongValueOrdered &other) const 78 | { 79 | return get_value() > other.get_value(); 80 | } 81 | 82 | bool operator<(const StrongValueOrdered &other) const 83 | { 84 | return get_value() < other.get_value(); 85 | } 86 | 87 | bool operator>=(const StrongValueOrdered &other) const 88 | { 89 | return get_value() >= other.get_value(); 90 | } 91 | 92 | bool operator<=(const StrongValueOrdered &other) const 93 | { 94 | return get_value() <= other.get_value(); 95 | } 96 | }; 97 | 98 | /** 99 | * A general frequency class to be used whereever an unbound frequency value is necessary. 100 | */ 101 | class Frequency : public StrongValueOrdered { 102 | public: 103 | explicit Frequency(size_t frequency) : StrongValueOrdered(frequency) 104 | { 105 | if (frequency == 0) { 106 | throw ESPException(ESP_ERR_INVALID_ARG); 107 | } 108 | } 109 | 110 | Frequency(const Frequency&) = default; 111 | Frequency &operator=(const Frequency&) = default; 112 | 113 | using StrongValueOrdered::get_value; 114 | 115 | static Frequency Hz(size_t frequency) 116 | { 117 | return Frequency(frequency); 118 | } 119 | 120 | static Frequency KHz(size_t frequency) 121 | { 122 | return Frequency(frequency * 1000); 123 | } 124 | 125 | static Frequency MHz(size_t frequency) 126 | { 127 | return Frequency(frequency * 1000 * 1000); 128 | } 129 | }; 130 | 131 | /** 132 | * Queue size mainly for operating system queues. 133 | */ 134 | class QueueSize { 135 | public: 136 | explicit QueueSize(size_t q_size) : queue_size(q_size) { } 137 | 138 | size_t get_size() 139 | { 140 | return queue_size; 141 | } 142 | 143 | private: 144 | size_t queue_size; 145 | }; 146 | 147 | } 148 | -------------------------------------------------------------------------------- /private_include/spi_host_private_cxx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #pragma once 8 | 9 | /** 10 | * The code in this file includes driver headers directly, hence it's a private include. 11 | * It should only be used in C++ source files, while header files use forward declarations of the types. 12 | * This way, public headers don't need to depend on (i.e. include) driver headers. 13 | */ 14 | 15 | #ifdef __cpp_exceptions 16 | 17 | #include "hal/spi_types.h" 18 | #include "driver/spi_master.h" 19 | 20 | using namespace std; 21 | 22 | namespace idf { 23 | 24 | #define SPI_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), SPIException) 25 | 26 | /** 27 | * This class wraps closely around the SPI master device driver functions. 28 | * It is used to hide the implementation, in particular the dependencies on the driver and HAL layer headers. 29 | * Public header files only use a pointer to this class which is forward declared in spi_host_cxx.hpp. 30 | * Implementations (source files) can include this private header and use the class definitions. 31 | * 32 | * Furthermore, this class ensures RAII-capabilities of an SPI master device allocation and initiates pre- and 33 | * post-transaction callback for each transfer. In constrast to the IDF driver, the callbacks are not per-device 34 | * but per transaction in the C++ wrapper framework. 35 | * 36 | * For information on the public member functions, refer to the corresponding driver functions in spi_master.h 37 | */ 38 | class SPIDeviceHandle { 39 | public: 40 | /** 41 | * Create a device instance on the SPI bus identified by spi_host, allocate all corresponding resources. 42 | */ 43 | SPIDeviceHandle(SPINum spi_host, CS cs, Frequency frequency, QueueSize q_size) 44 | { 45 | spi_device_interface_config_t dev_config = {}; 46 | dev_config.clock_speed_hz = frequency.get_value(); 47 | dev_config.spics_io_num = cs.get_value(); 48 | dev_config.pre_cb = pr_cb; 49 | dev_config.post_cb = post_cb; 50 | dev_config.queue_size = q_size.get_size(); 51 | SPI_CHECK_THROW(spi_bus_add_device(spi_host.get_value(), &dev_config, &handle)); 52 | } 53 | 54 | SPIDeviceHandle(const SPIDeviceHandle &other) = delete; 55 | 56 | SPIDeviceHandle(SPIDeviceHandle &&other) noexcept : handle(std::move(other.handle)) 57 | { 58 | // Only to indicate programming errors where users use an instance after moving it. 59 | other.handle = nullptr; 60 | } 61 | 62 | /** 63 | * Remove device instance from the SPI bus, deallocate all corresponding resources. 64 | */ 65 | ~SPIDeviceHandle() 66 | { 67 | // We ignore the return value here. 68 | // Only possible errors are wrong handle (impossible by object invariants) and 69 | // handle already freed, which we can ignore. 70 | spi_bus_remove_device(handle); 71 | } 72 | 73 | SPIDeviceHandle &operator=(SPIDeviceHandle&& other) noexcept 74 | { 75 | if (this != &other) { 76 | handle = std::move(other.handle); 77 | 78 | // Only to indicate programming errors where users use an instance after moving it. 79 | other.handle = nullptr; 80 | } 81 | return *this; 82 | } 83 | 84 | esp_err_t acquire_bus(TickType_t wait) 85 | { 86 | return spi_device_acquire_bus(handle, portMAX_DELAY); 87 | } 88 | 89 | esp_err_t queue_trans(spi_transaction_t *trans_desc, TickType_t wait) 90 | { 91 | return spi_device_queue_trans(handle, trans_desc, wait); 92 | } 93 | 94 | esp_err_t get_trans_result(spi_transaction_t **trans_desc, TickType_t ticks_to_wait) 95 | { 96 | return spi_device_get_trans_result(handle, trans_desc, ticks_to_wait); 97 | } 98 | 99 | void release_bus() 100 | { 101 | spi_device_release_bus(handle); 102 | } 103 | 104 | private: 105 | /** 106 | * Route the callback to the callback in the specific SPITransactionDescriptor instance. 107 | */ 108 | static void pr_cb(spi_transaction_t *driver_transaction) 109 | { 110 | SPITransactionDescriptor *transaction = static_cast(driver_transaction->user); 111 | if (transaction->pre_callback) { 112 | transaction->pre_callback(transaction->user_data); 113 | } 114 | } 115 | 116 | /** 117 | * Route the callback to the callback in the specific SPITransactionDescriptor instance. 118 | */ 119 | static void post_cb(spi_transaction_t *driver_transaction) 120 | { 121 | SPITransactionDescriptor *transaction = static_cast(driver_transaction->user); 122 | if (transaction->post_callback) { 123 | transaction->post_callback(transaction->user_data); 124 | } 125 | } 126 | 127 | spi_device_handle_t handle; 128 | }; 129 | 130 | } 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /spi_cxx.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #if __cpp_exceptions 8 | 9 | #include "driver/spi_common.h" 10 | #include "esp_exception.hpp" 11 | #include "spi_cxx.hpp" 12 | 13 | namespace idf { 14 | 15 | esp_err_t check_spi_num(uint32_t spi_num) noexcept { 16 | if (spi_num >= static_cast(SPI_HOST_MAX)) { 17 | return ESP_ERR_INVALID_ARG; 18 | } 19 | 20 | return ESP_OK; 21 | } 22 | 23 | SPI_DMAConfig SPI_DMAConfig::DISABLED() { 24 | return SPI_DMAConfig(static_cast(spi_common_dma_t::SPI_DMA_DISABLED)); 25 | } 26 | 27 | SPI_DMAConfig SPI_DMAConfig::AUTO() { 28 | return SPI_DMAConfig(static_cast(spi_common_dma_t::SPI_DMA_CH_AUTO)); 29 | } 30 | 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /spi_host_cxx.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #if __cpp_exceptions 8 | 9 | #include 10 | #include 11 | #include "freertos/FreeRTOS.h" 12 | #include "freertos/portmacro.h" 13 | #include "hal/spi_types.h" 14 | #include "driver/spi_master.h" 15 | #include "spi_host_cxx.hpp" 16 | #include "spi_host_private_cxx.hpp" 17 | 18 | using namespace std; 19 | 20 | namespace idf { 21 | 22 | SPIException::SPIException(esp_err_t error) : ESPException(error) { } 23 | 24 | SPITransferException::SPITransferException(esp_err_t error) : SPIException(error) { } 25 | 26 | SPIMaster::SPIMaster(SPINum host, 27 | const MOSI &mosi, 28 | const MISO &miso, 29 | const SCLK &sclk, 30 | SPI_DMAConfig dma_config, 31 | SPITransferSize transfer_size) 32 | : spi_host(host) 33 | { 34 | spi_bus_config_t bus_config = {}; 35 | bus_config.mosi_io_num = mosi.get_value(); 36 | bus_config.miso_io_num = miso.get_value(); 37 | bus_config.sclk_io_num = sclk.get_value(); 38 | bus_config.quadwp_io_num = -1; 39 | bus_config.quadhd_io_num = -1; 40 | bus_config.max_transfer_sz = transfer_size.get_value(); 41 | 42 | SPI_CHECK_THROW(spi_bus_initialize(spi_host.get_value(), &bus_config, dma_config.get_value())); 43 | } 44 | 45 | SPIMaster::SPIMaster(SPINum host, 46 | const MOSI &mosi, 47 | const MISO &miso, 48 | const SCLK &sclk, 49 | const QSPIWP &qspiwp, 50 | const QSPIHD &qspihd, 51 | SPI_DMAConfig dma_config, 52 | SPITransferSize transfer_size) 53 | : spi_host(host) 54 | { 55 | spi_bus_config_t bus_config = {}; 56 | bus_config.mosi_io_num = mosi.get_value(); 57 | bus_config.miso_io_num = miso.get_value(); 58 | bus_config.sclk_io_num = sclk.get_value(); 59 | bus_config.quadwp_io_num = qspiwp.get_value(); 60 | bus_config.quadhd_io_num = qspihd.get_value(); 61 | bus_config.max_transfer_sz = transfer_size.get_value(); 62 | 63 | SPI_CHECK_THROW(spi_bus_initialize(spi_host.get_value(), &bus_config, dma_config.get_value())); 64 | } 65 | 66 | SPIMaster::~SPIMaster() 67 | { 68 | spi_bus_free(spi_host.get_value()); 69 | } 70 | 71 | shared_ptr SPIMaster::create_dev(CS cs, Frequency frequency) 72 | { 73 | return make_shared(spi_host, cs, frequency); 74 | } 75 | 76 | SPIFuture::SPIFuture() 77 | : transaction(), is_valid(false) 78 | { 79 | } 80 | 81 | SPIFuture::SPIFuture(shared_ptr transaction) 82 | : transaction(transaction), is_valid(true) 83 | { 84 | } 85 | 86 | SPIFuture::SPIFuture(SPIFuture &&other) noexcept 87 | : transaction(std::move(other.transaction)), is_valid(true) 88 | { 89 | other.is_valid = false; 90 | } 91 | 92 | SPIFuture &SPIFuture::operator=(SPIFuture &&other) noexcept 93 | { 94 | if (this != &other) { 95 | transaction = std::move(other.transaction); 96 | is_valid = other.is_valid; 97 | other.is_valid = false; 98 | } 99 | return *this; 100 | } 101 | 102 | vector SPIFuture::get() 103 | { 104 | if (!is_valid) { 105 | throw std::future_error(future_errc::no_state); 106 | } 107 | 108 | return transaction->get(); 109 | } 110 | 111 | future_status SPIFuture::wait_for(chrono::milliseconds timeout) 112 | { 113 | if (transaction->wait_for(timeout)) { 114 | return std::future_status::ready; 115 | } else { 116 | return std::future_status::timeout; 117 | } 118 | } 119 | 120 | void SPIFuture::wait() 121 | { 122 | transaction->wait(); 123 | } 124 | 125 | bool SPIFuture::valid() const noexcept 126 | { 127 | return is_valid; 128 | } 129 | 130 | SPIDevice::SPIDevice(SPINum spi_host, CS cs, Frequency frequency, QueueSize q_size) : device_handle() 131 | { 132 | device_handle = new SPIDeviceHandle(spi_host, cs, frequency, q_size); 133 | } 134 | 135 | SPIDevice::~SPIDevice() 136 | { 137 | delete device_handle; 138 | } 139 | 140 | SPIFuture SPIDevice::transfer(const vector &data_to_send, 141 | std::function pre_callback, 142 | std::function post_callback, 143 | void* user_data) 144 | { 145 | current_transaction = make_shared(data_to_send, 146 | device_handle, 147 | std::move(pre_callback), 148 | std::move(post_callback), 149 | user_data); 150 | current_transaction->start(); 151 | return SPIFuture(current_transaction); 152 | } 153 | 154 | SPITransactionDescriptor::SPITransactionDescriptor(const std::vector &data_to_send, 155 | SPIDeviceHandle *handle, 156 | std::function pre_callback, 157 | std::function post_callback, 158 | void* user_data_arg) 159 | : device_handle(handle), 160 | pre_callback(std::move(pre_callback)), 161 | post_callback(std::move(post_callback)), 162 | user_data(user_data_arg), 163 | received_data(false), 164 | started(false) 165 | { 166 | // C++11 vectors don't have size() or empty() members yet 167 | if (data_to_send.begin() == data_to_send.end()) { 168 | throw SPITransferException(ESP_ERR_INVALID_ARG); 169 | } 170 | if (handle == nullptr) { 171 | throw SPITransferException(ESP_ERR_INVALID_ARG); 172 | } 173 | 174 | size_t trans_size = data_to_send.size(); 175 | spi_transaction_t *trans_desc; 176 | trans_desc = new spi_transaction_t; 177 | memset(trans_desc, 0, sizeof(spi_transaction_t)); 178 | trans_desc->rx_buffer = new uint8_t [trans_size]; 179 | tx_buffer = new uint8_t [trans_size]; 180 | for (size_t i = 0; i < trans_size; i++) { 181 | tx_buffer[i] = data_to_send[i]; 182 | } 183 | trans_desc->length = trans_size * 8; 184 | trans_desc->tx_buffer = tx_buffer; 185 | trans_desc->user = this; 186 | 187 | private_transaction_desc = trans_desc; 188 | } 189 | 190 | SPITransactionDescriptor::~SPITransactionDescriptor() 191 | { 192 | if (started) { 193 | assert(received_data); // We need to make sure that trans_desc has been received, otherwise the 194 | // driver may still write into it afterwards. 195 | } 196 | 197 | spi_transaction_t *trans_desc = reinterpret_cast(private_transaction_desc); 198 | delete [] tx_buffer; 199 | delete [] static_cast(trans_desc->rx_buffer); 200 | delete trans_desc; 201 | } 202 | 203 | void SPITransactionDescriptor::start() 204 | { 205 | spi_transaction_t *trans_desc = reinterpret_cast(private_transaction_desc); 206 | SPI_CHECK_THROW(device_handle->acquire_bus(portMAX_DELAY)); 207 | SPI_CHECK_THROW(device_handle->queue_trans(trans_desc, 0)); 208 | started = true; 209 | } 210 | 211 | void SPITransactionDescriptor::wait() 212 | { 213 | while (wait_for(chrono::milliseconds(portMAX_DELAY)) == false) { } 214 | } 215 | 216 | bool SPITransactionDescriptor::wait_for(const chrono::milliseconds &timeout_duration) 217 | { 218 | if (received_data) { 219 | return true; 220 | } 221 | 222 | if (!started) { 223 | throw SPITransferException(ESP_ERR_INVALID_STATE); 224 | } 225 | 226 | spi_transaction_t *acquired_trans_desc; 227 | esp_err_t err = device_handle->get_trans_result(&acquired_trans_desc, 228 | (TickType_t) timeout_duration.count() / portTICK_PERIOD_MS); 229 | 230 | if (err == ESP_ERR_TIMEOUT) { 231 | return false; 232 | } 233 | 234 | if (err != ESP_OK) { 235 | throw SPITransferException(err); 236 | } 237 | 238 | if (acquired_trans_desc != reinterpret_cast(private_transaction_desc)) { 239 | throw SPITransferException(ESP_ERR_INVALID_STATE); 240 | } 241 | 242 | received_data = true; 243 | device_handle->release_bus(); 244 | 245 | return true; 246 | } 247 | 248 | std::vector SPITransactionDescriptor::get() 249 | { 250 | if (!received_data) { 251 | wait(); 252 | } 253 | 254 | spi_transaction_t *trans_desc = reinterpret_cast(private_transaction_desc); 255 | const size_t TRANSACTION_LENGTH = trans_desc->length / 8; 256 | vector result(TRANSACTION_LENGTH); 257 | 258 | for (int i = 0; i < TRANSACTION_LENGTH; i++) { 259 | result[i] = static_cast(trans_desc->rx_buffer)[i]; 260 | } 261 | 262 | return result; 263 | } 264 | 265 | } // idf 266 | 267 | #endif // __cpp_exceptions 268 | -------------------------------------------------------------------------------- /test_apps/cxx_exception/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about build system see 2 | # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html 3 | # The following five lines of boilerplate have to be in your project's 4 | # CMakeLists in this exact order for cmake to work correctly 5 | cmake_minimum_required(VERSION 3.16) 6 | 7 | set(COMPONENTS main) 8 | 9 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 10 | project(test_cxx_exception) 11 | -------------------------------------------------------------------------------- /test_apps/cxx_exception/README.md: -------------------------------------------------------------------------------- 1 | # Test App: CXX Exception 2 | 3 | This application tests various utilities that are used for exceptions in esp-idf-cxx. 4 | 5 | ## Hardware Required 6 | 7 | Any ESP32 family development board. 8 | 9 | ## Build Test App 10 | 11 | Before building, you may need to set the target: `idf.py set-target flash monitor 25 | ``` 26 | Then, choose the tests according to the test menu. 27 | 28 | ### Run automatically 29 | 30 | The following command runs the test app for target esp32. 31 | ``` 32 | pytest -s --junit-xml=./test_app_results_cxx_exception.xml --embedded-services esp,idf --target=esp32 33 | ``` 34 | This command will automatically run all tests and evaluate them on the host. 35 | 36 | If you want to run on a different target, you need to change --target and also build it for that target. 37 | -------------------------------------------------------------------------------- /test_apps/cxx_exception/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "cxx_exception.cpp" 2 | INCLUDE_DIRS "." "../../include" 3 | PRIV_REQUIRES unity) 4 | -------------------------------------------------------------------------------- /test_apps/cxx_exception/main/cxx_exception.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: CC0-1.0 OR Unlicense 5 | * 6 | * Exception-related unit tests 7 | * 8 | * This example code is in the Public Domain (or CC0 licensed, at your option.) 9 | * 10 | * Unless required by applicable law or agreed to in writing, this 11 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | * CONDITIONS OF ANY KIND, either express or implied. 13 | */ 14 | 15 | #include 16 | #include 17 | #include "unity.h" 18 | #include "utils_cxx.hpp" 19 | #include "unity_test_utils_memory.h" 20 | 21 | #include "unity_cxx.hpp" 22 | #include "esp_exception.hpp" 23 | 24 | using namespace std; 25 | using namespace idf; 26 | 27 | #define TAG "CXX Exception Test" 28 | 29 | extern "C" void setUp() 30 | { 31 | // The first exception thrown will allocate some memory, which is normal. 32 | // Hence, we throw and catch an exception here to avoid reporting a memory leak on first test. 33 | try { 34 | throw 47; 35 | } catch (int &e) { } 36 | unity_utils_set_leak_level(0); 37 | unity_utils_record_free_mem(); 38 | } 39 | 40 | extern "C" void tearDown() 41 | { 42 | unity_utils_evaluate_leaks(); 43 | } 44 | 45 | TEST_CASE("TEST_THROW catches exception", "[exception_utils]") 46 | { 47 | TEST_THROW(throw ESPException(ESP_FAIL);, ESPException); 48 | } 49 | 50 | TEST_CASE("CHECK_THROW continues on ESP_OK", "[exception_utils]") 51 | { 52 | esp_err_t error = ESP_OK; 53 | CHECK_THROW(error); 54 | } 55 | 56 | TEST_CASE("CHECK_THROW throws", "[exception_utils]") 57 | { 58 | esp_err_t error = ESP_FAIL; 59 | TEST_THROW(CHECK_THROW(error), ESPException); 60 | } 61 | 62 | TEST_CASE("ESPException has working what() method", "[exception_utils]") 63 | { 64 | try { 65 | throw ESPException(ESP_FAIL); 66 | } catch (ESPException &e) { 67 | TEST_ASSERT(strcmp(esp_err_to_name(ESP_FAIL), e.what()) == 0); 68 | } 69 | } 70 | 71 | /* The following two test cases are expected to fail */ 72 | 73 | TEST_CASE("TEST_THROW asserts catching different exception", "[test_throw_fail]") 74 | { 75 | TEST_THROW(throw std::exception();, ESPException); 76 | } 77 | 78 | TEST_CASE("TEST_THROW asserts not catching any exception", "[test_throw_fail]") 79 | { 80 | TEST_THROW(printf(" ");, ESPException); // need statement with effect 81 | } 82 | 83 | extern "C" void app_main(void) 84 | { 85 | TEST_APPS_LOG("CXX EXCEPTION TEST"); 86 | unity_run_menu(); 87 | } 88 | -------------------------------------------------------------------------------- /test_apps/cxx_exception/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | esp-idf-cxx: 5 | path: ../../../ 6 | version: ">=0.1" 7 | -------------------------------------------------------------------------------- /test_apps/cxx_exception/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_CXX_EXCEPTIONS=y 2 | CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 3 | CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y 4 | CONFIG_FREERTOS_HZ=1000 5 | CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y 6 | CONFIG_HEAP_POISONING_COMPREHENSIVE=y 7 | CONFIG_ESP_TASK_WDT=n 8 | CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y 9 | CONFIG_COMPILER_STACK_CHECK=y 10 | CONFIG_COMPILER_WARN_WRITE_STRINGS=y 11 | CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y 12 | CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3000 13 | -------------------------------------------------------------------------------- /test_apps/cxx_exception/test_cxx_exception.py: -------------------------------------------------------------------------------- 1 | def test_cxx_exception(dut): 2 | dut.expect_exact('Press ENTER to see the list of tests') 3 | dut.write('[exception_utils]') 4 | dut.expect_unity_test_output(timeout=30) 5 | 6 | def test_test_throw_fail(dut): 7 | dut.expect_exact('Press ENTER to see the list of tests') 8 | dut.write('[test_throw_fail]') 9 | dut.expect("2 Tests 2 Failures 0 Ignored") 10 | dut.expect("FAIL") 11 | -------------------------------------------------------------------------------- /test_apps/esp_event/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is the project CMakeLists.txt file for the test subproject 2 | cmake_minimum_required(VERSION 3.16) 3 | 4 | set(COMPONENTS main) 5 | 6 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 7 | project(test_esp_event) 8 | -------------------------------------------------------------------------------- /test_apps/esp_event/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "test_esp_event.cpp" 2 | INCLUDE_DIRS "../../include" 3 | PRIV_REQUIRES unity) 4 | -------------------------------------------------------------------------------- /test_apps/esp_event/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | esp-idf-cxx: 5 | path: ../../../ 6 | version: ">=0.1" 7 | -------------------------------------------------------------------------------- /test_apps/esp_event/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_UNITY_ENABLE_FIXTURE=y 2 | CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n 3 | CONFIG_UNITY_ENABLE_64BIT=y 4 | CONFIG_COMPILER_CXX_EXCEPTIONS=y 5 | -------------------------------------------------------------------------------- /test_apps/esp_event/test_cxx_esp_event.py: -------------------------------------------------------------------------------- 1 | def test_app(dut): 2 | dut.expect_unity_test_output(timeout=30) 3 | -------------------------------------------------------------------------------- /test_apps/esp_timer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is the project CMakeLists.txt file for the test subproject 2 | cmake_minimum_required(VERSION 3.16) 3 | 4 | set(COMPONENTS main) 5 | 6 | set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") 7 | 8 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 9 | project(test_esp_timer) 10 | -------------------------------------------------------------------------------- /test_apps/esp_timer/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "esp_timer_test.cpp" 2 | INCLUDE_DIRS "../../include" 3 | PRIV_REQUIRES test_utils unity) 4 | -------------------------------------------------------------------------------- /test_apps/esp_timer/main/esp_timer_test.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: CC0-1.0 OR Unlicense 5 | * 6 | * I2C C++ unit tests 7 | * 8 | * This example code is in the Public Domain (or CC0 licensed, at your option.) 9 | * 10 | * Unless required by applicable law or agreed to in writing, this 11 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | * CONDITIONS OF ANY KIND, either express or implied. 13 | */ 14 | 15 | #include 16 | 17 | #include "unity.h" 18 | #include "unity_cxx.hpp" 19 | #include "utils_cxx.hpp" 20 | #include "test_utils.h" // ref clock 21 | 22 | #include "freertos/FreeRTOS.h" 23 | #include "freertos/task.h" 24 | #include "freertos/semphr.h" 25 | 26 | #include "memory_checks.h" 27 | 28 | #include "esp_exception.hpp" 29 | #include "esp_timer_cxx.hpp" 30 | 31 | using namespace std; 32 | using namespace idf; 33 | using namespace idf::esp_timer; 34 | 35 | constexpr size_t LEAKS = 250; 36 | 37 | extern "C" void setUp() 38 | { 39 | test_utils_record_free_mem(); 40 | } 41 | 42 | extern "C" void tearDown() 43 | { 44 | test_utils_finish_and_evaluate_leaks(LEAKS, LEAKS); 45 | } 46 | 47 | struct RefClock { 48 | RefClock() 49 | { 50 | ref_clock_init(); 51 | }; 52 | 53 | ~RefClock() 54 | { 55 | ref_clock_deinit(); 56 | } 57 | }; 58 | 59 | TEST_CASE("ESPTimer produces correct delay", "[ESPTimer]") 60 | { 61 | int64_t t_end; 62 | 63 | RefClock ref_clock; 64 | 65 | function timer_cb = [&t_end]() { 66 | t_end = ref_clock_get(); 67 | }; 68 | 69 | ESPTimer timer(timer_cb, "timer1"); 70 | 71 | const int delays_ms[] = {20, 100, 200, 250}; 72 | const size_t delays_count = sizeof(delays_ms)/sizeof(delays_ms[0]); 73 | 74 | for (size_t i = 0; i < delays_count; ++i) { 75 | t_end = 0; 76 | int64_t t_start = ref_clock_get(); 77 | 78 | timer.start(chrono::microseconds(delays_ms[i] * 1000)); 79 | 80 | vTaskDelay(delays_ms[i] * 2 / portTICK_PERIOD_MS); 81 | TEST_ASSERT(t_end != 0); 82 | int32_t ms_diff = (t_end - t_start) / 1000; 83 | TEST_APPS_LOG("%d %ld", delays_ms[i], ms_diff); 84 | 85 | TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, delays_ms[i], ms_diff); 86 | } 87 | } 88 | 89 | TEST_CASE("ESPtimer produces correct periodic delays", "[ESPTimer]") 90 | { 91 | const size_t NUM_INTERVALS = 3u; 92 | 93 | size_t cur_interval = 0; 94 | int intervals[NUM_INTERVALS]; 95 | int64_t t_start; 96 | SemaphoreHandle_t done; 97 | 98 | const int DELAY_MS = 100; 99 | function timer_cb = [&]() { 100 | int64_t t_end = ref_clock_get(); 101 | int32_t ms_diff = (t_end - t_start) / 1000; 102 | TEST_APPS_LOG("timer #%d %ldms", cur_interval, ms_diff); 103 | if (cur_interval < NUM_INTERVALS) { 104 | intervals[cur_interval++] = ms_diff; 105 | } 106 | // Deliberately make timer handler run longer. 107 | // We check that this doesn't affect the result. 108 | esp_rom_delay_us(10*1000); 109 | if (cur_interval == NUM_INTERVALS) { 110 | TEST_APPS_LOG("done"); 111 | xSemaphoreGive(done); 112 | } 113 | }; 114 | 115 | ESPTimer timer(timer_cb, "timer1"); 116 | RefClock ref_clock; 117 | t_start = ref_clock_get(); 118 | done = xSemaphoreCreateBinary(); 119 | timer.start_periodic(chrono::microseconds(DELAY_MS * 1000)); 120 | 121 | TEST_ASSERT(xSemaphoreTake(done, DELAY_MS * NUM_INTERVALS * 2)); 122 | timer.stop(); 123 | 124 | TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, cur_interval); 125 | for (size_t i = 0; i < NUM_INTERVALS; ++i) { 126 | TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, (i + 1) * DELAY_MS, intervals[i]); 127 | } 128 | TEST_ESP_OK(esp_timer_dump(stdout)); 129 | 130 | vSemaphoreDelete(done); 131 | } 132 | 133 | extern "C" void app_main(void) 134 | { 135 | TEST_APPS_LOG("CXX ESP TIMER TEST"); 136 | unity_run_menu(); 137 | } 138 | -------------------------------------------------------------------------------- /test_apps/esp_timer/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0" 4 | esp-idf-cxx: 5 | path: ../../../ 6 | version: ">=0.1" 7 | -------------------------------------------------------------------------------- /test_apps/esp_timer/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_CXX_EXCEPTIONS=y 2 | CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 3 | CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y 4 | CONFIG_FREERTOS_HZ=1000 5 | CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y 6 | CONFIG_HEAP_POISONING_COMPREHENSIVE=y 7 | CONFIG_ESP_TASK_WDT=n 8 | CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y 9 | CONFIG_COMPILER_STACK_CHECK=y 10 | CONFIG_COMPILER_WARN_WRITE_STRINGS=y 11 | CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y 12 | CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3000 13 | -------------------------------------------------------------------------------- /test_apps/esp_timer/test_app_results_esp_event_esp32_latest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test_apps/esp_timer/test_esp_timer.py: -------------------------------------------------------------------------------- 1 | def test_app(dut): 2 | dut.expect_exact('Press ENTER to see the list of tests') 3 | dut.write('[ESPTimer]') 4 | dut.expect_unity_test_output(timeout=30) 5 | -------------------------------------------------------------------------------- /test_apps/include/unity_cxx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "unity.h" 10 | 11 | #define CXX_UNITY_TYPE_TO_STR(x) #x 12 | 13 | /** 14 | * Very simple helper macro to catch exceptions. 15 | * 16 | * @note 17 | * * If there is any exception which not a child of std::exception, it will terminate the program! 18 | * * If there is no exception, it will jump from the current frame without de-initializing 19 | * destructors! 20 | */ 21 | #define TEST_THROW(expr_, exception_) \ 22 | do { \ 23 | bool caught = false; \ 24 | bool caught_different = false; \ 25 | try { \ 26 | expr_; \ 27 | } catch ( exception_ &e) { \ 28 | caught = true; \ 29 | } catch ( std::exception &e) { \ 30 | caught_different = true; \ 31 | } \ 32 | TEST_ASSERT_FALSE_MESSAGE(caught_different, "ERROR: Expected " CXX_UNITY_TYPE_TO_STR(exception_) \ 33 | ", but caught different exception."); \ 34 | TEST_ASSERT_TRUE_MESSAGE(caught, "ERROR: Expected " CXX_UNITY_TYPE_TO_STR(exception_) \ 35 | ", but no exception thrown."); \ 36 | } \ 37 | while (0) 38 | -------------------------------------------------------------------------------- /test_apps/include/utils_cxx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: CC0-1.0 OR Unlicense 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "esp_log.h" 10 | 11 | const char *TEST_APPS_TAG = "test_apps"; 12 | 13 | #define TEST_APPS_LOG(fmt, ...) esp_log_write(ESP_LOG_INFO, TEST_APPS_TAG, LOG_FORMAT(I, fmt), esp_log_timestamp(), TEST_APPS_TAG, ##__VA_ARGS__) 14 | #define TEST_APPS_LOGW(fmt, ...) esp_log_write(ESP_LOG_WARN, TEST_APPS_TAG, LOG_FORMAT(W, fmt), esp_log_timestamp(), TEST_APPS_TAG, ##__VA_ARGS__) 15 | #define TEST_APPS_LOGE(fmt, ...) esp_log_write(ESP_LOG_ERROR, TEST_APPS_TAG, LOG_FORMAT(E, fmt), esp_log_timestamp(), TEST_APPS_TAG, ##__VA_ARGS__) 16 | --------------------------------------------------------------------------------