diff --git a/.gitlab/ci/host-test.yml b/.gitlab/ci/host-test.yml index 40f5f4fec5..e238d79ee3 100644 --- a/.gitlab/ci/host-test.yml +++ b/.gitlab/ci/host-test.yml @@ -362,3 +362,10 @@ test_rom_on_linux_works: - cd ${IDF_PATH}/components/esp_rom/host_test/rom_test - idf.py build - build/test_rom_host.elf + +test_cxx_gpio: + extends: .host_test_template + script: + - cd ${IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/host_test/gpio + - idf.py build + - build/test_gpio_cxx_host.elf diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index ba17c56788..172563df51 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -37,6 +37,7 @@ .patterns-build_components: &patterns-build_components - "components/**/*" + - "examples/cxx/experimental/experimental_cpp_component/*" .patterns-build_system: &patterns-build_system - "tools/cmake/**/*" diff --git a/examples/cxx/experimental/blink_cxx/CMakeLists.txt b/examples/cxx/experimental/blink_cxx/CMakeLists.txt new file mode 100644 index 0000000000..6b27fac9f9 --- /dev/null +++ b/examples/cxx/experimental/blink_cxx/CMakeLists.txt @@ -0,0 +1,10 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(blink_cxx) diff --git a/examples/cxx/experimental/blink_cxx/README.md b/examples/cxx/experimental/blink_cxx/README.md new file mode 100644 index 0000000000..8c31d01abe --- /dev/null +++ b/examples/cxx/experimental/blink_cxx/README.md @@ -0,0 +1,57 @@ +# Example: Blink C++ example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates usage of the `GPIO_Output` C++ class in ESP-IDF. + +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++ APIs. + +## How to use example + +### Hardware Required + +Any ESP32 family development board. + +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. + +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. + +### Configure the project + +``` +idf.py menuconfig +``` + +### Build and Flash + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +... +I (339) cpu_start: Starting scheduler. +I (343) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +LED ON +LED OFF +LED ON +LED OFF +LED ON +LED OFF +LED ON +LED OFF +LED ON +LED OFF + +``` + diff --git a/examples/cxx/experimental/blink_cxx/main/CMakeLists.txt b/examples/cxx/experimental/blink_cxx/main/CMakeLists.txt new file mode 100644 index 0000000000..9eb7ec47a0 --- /dev/null +++ b/examples/cxx/experimental/blink_cxx/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.cpp" + INCLUDE_DIRS ".") diff --git a/examples/cxx/experimental/blink_cxx/main/main.cpp b/examples/cxx/experimental/blink_cxx/main/main.cpp new file mode 100644 index 0000000000..f997e13ac3 --- /dev/null +++ b/examples/cxx/experimental/blink_cxx/main/main.cpp @@ -0,0 +1,39 @@ +/* Blink C++ Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +#include +#include +#include "esp_log.h" +#include "gpio_cxx.hpp" + +using namespace idf; +using namespace std; + +extern "C" void app_main(void) +{ + /* The functions of GPIO_Output throws exceptions in case of parameter errors or if there are underlying driver + errors. */ + try { + /* This line may throw an exception if the pin number is invalid. + * Alternatively to 4, choose another output-capable pin. */ + GPIO_Output gpio(GPIONum(4)); + + while (true) { + printf("LED ON\n"); + gpio.set_high(); + this_thread::sleep_for(std::chrono::seconds(1)); + printf("LED OFF\n"); + gpio.set_low(); + this_thread::sleep_for(std::chrono::seconds(1)); + } + } catch (GPIOException &e) { + printf("GPIO exception occurred: %s\n", esp_err_to_name(e.error)); + printf("stopping.\n"); + } +} diff --git a/examples/cxx/experimental/blink_cxx/sdkconfig.defaults b/examples/cxx/experimental/blink_cxx/sdkconfig.defaults new file mode 100644 index 0000000000..a365ac6589 --- /dev/null +++ b/examples/cxx/experimental/blink_cxx/sdkconfig.defaults @@ -0,0 +1,3 @@ +# Enable C++ exceptions and set emergency pool size for exception objects +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 diff --git a/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt index ec30b2b291..7e430b5d5f 100644 --- a/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt +++ b/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt @@ -1,6 +1,7 @@ idf_component_register(SRCS "esp_exception.cpp" "i2c_cxx.cpp" + "gpio_cxx.cpp" "esp_event_api.cpp" "esp_event_cxx.cpp" "esp_timer_cxx.cpp" diff --git a/examples/cxx/experimental/experimental_cpp_component/gpio_cxx.cpp b/examples/cxx/experimental/experimental_cpp_component/gpio_cxx.cpp new file mode 100644 index 0000000000..3f67cb7102 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/gpio_cxx.cpp @@ -0,0 +1,208 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if __cpp_exceptions + +#include +#include "driver/gpio.h" +#include "gpio_cxx.hpp" + +namespace idf { + +#define GPIO_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), GPIOException) + +namespace { +#if CONFIG_IDF_TARGET_LINUX +constexpr std::array INVALID_GPIOS = {24}; +#elif CONFIG_IDF_TARGET_ESP32 +constexpr std::array INVALID_GPIOS = {24}; +#elif CONFIG_IDF_TARGET_ESP32S2 +constexpr std::array INVALID_GPIOS = {22, 23, 24, 25}; +#elif CONFIG_IDF_TARGET_ESP32S3 +constexpr std::array INVALID_GPIOS = {22, 23, 24, 25}; +#elif CONFIG_IDF_TARGET_ESP32C3 +constexpr std::array INVALID_GPIOS = {}; +#else +#error "No GPIOs defined for the current target" +#endif + +gpio_num_t gpio_to_driver_type(const GPIONum &gpio_num) +{ + return static_cast(gpio_num.get_num()); +} + +} + +GPIOException::GPIOException(esp_err_t error) : ESPException(error) { } + +esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept +{ + if (pin_num >= GPIO_NUM_MAX) { + return ESP_ERR_INVALID_ARG; + } + + for (auto num: INVALID_GPIOS) + { + if (pin_num == num) { + return ESP_ERR_INVALID_ARG; + } + } + + return ESP_OK; +} + +esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept +{ + if (strength >= GPIO_DRIVE_CAP_MAX) { + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +GPIOPullMode GPIOPullMode::FLOATING() +{ + return GPIOPullMode(GPIO_FLOATING); +} + +GPIOPullMode GPIOPullMode::PULLUP() +{ + return GPIOPullMode(GPIO_PULLUP_ONLY); +} + +GPIOPullMode GPIOPullMode::PULLDOWN() +{ + return GPIOPullMode(GPIO_PULLDOWN_ONLY); +} + +GPIOWakeupIntrType GPIOWakeupIntrType::LOW_LEVEL() +{ + return GPIOWakeupIntrType(GPIO_INTR_LOW_LEVEL); +} + +GPIOWakeupIntrType GPIOWakeupIntrType::HIGH_LEVEL() +{ + return GPIOWakeupIntrType(GPIO_INTR_HIGH_LEVEL); +} + +GPIODriveStrength GPIODriveStrength::DEFAULT() +{ + return MEDIUM(); +} + +GPIODriveStrength GPIODriveStrength::WEAK() +{ + return GPIODriveStrength(GPIO_DRIVE_CAP_0); +} + +GPIODriveStrength GPIODriveStrength::LESS_WEAK() +{ + return GPIODriveStrength(GPIO_DRIVE_CAP_1); +} + +GPIODriveStrength GPIODriveStrength::MEDIUM() +{ + return GPIODriveStrength(GPIO_DRIVE_CAP_2); +} + +GPIODriveStrength GPIODriveStrength::STRONGEST() +{ + return GPIODriveStrength(GPIO_DRIVE_CAP_3); +} + +GPIOBase::GPIOBase(GPIONum num) : gpio_num(num) +{ + GPIO_CHECK_THROW(gpio_reset_pin(gpio_to_driver_type(gpio_num))); +} + +void GPIOBase::hold_en() +{ + GPIO_CHECK_THROW(gpio_hold_en(gpio_to_driver_type(gpio_num))); +} + +void GPIOBase::hold_dis() +{ + GPIO_CHECK_THROW(gpio_hold_dis(gpio_to_driver_type(gpio_num))); +} + +void GPIOBase::set_drive_strength(GPIODriveStrength strength) +{ + GPIO_CHECK_THROW(gpio_set_drive_capability(gpio_to_driver_type(gpio_num), + static_cast(strength.get_strength()))); +} + +GPIO_Output::GPIO_Output(GPIONum num) : GPIOBase(num) +{ + GPIO_CHECK_THROW(gpio_set_direction(gpio_to_driver_type(gpio_num), GPIO_MODE_OUTPUT)); +} + +void GPIO_Output::set_high() +{ + GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 1)); +} + +void GPIO_Output::set_low() +{ + GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 0)); +} + +GPIODriveStrength GPIOBase::get_drive_strength() +{ + gpio_drive_cap_t strength; + GPIO_CHECK_THROW(gpio_get_drive_capability(gpio_to_driver_type(gpio_num), &strength)); + return GPIODriveStrength(static_cast(strength)); +} + +GPIOInput::GPIOInput(GPIONum num) : GPIOBase(num) +{ + GPIO_CHECK_THROW(gpio_set_direction(gpio_to_driver_type(gpio_num), GPIO_MODE_INPUT)); +} + +GPIOLevel GPIOInput::get_level() const noexcept +{ + int level = gpio_get_level(gpio_to_driver_type(gpio_num)); + if (level) { + return GPIOLevel::HIGH; + } else { + return GPIOLevel::LOW; + } +} + +void GPIOInput::set_pull_mode(GPIOPullMode mode) +{ + GPIO_CHECK_THROW(gpio_set_pull_mode(gpio_to_driver_type(gpio_num), + static_cast(mode.get_pull_mode()))); +} + +void GPIOInput::wakeup_enable(GPIOWakeupIntrType interrupt_type) +{ + GPIO_CHECK_THROW(gpio_wakeup_enable(gpio_to_driver_type(gpio_num), + static_cast(interrupt_type.get_level()))); +} + +void GPIOInput::wakeup_disable() +{ + GPIO_CHECK_THROW(gpio_wakeup_disable(gpio_to_driver_type(gpio_num))); +} + +GPIO_OpenDrain::GPIO_OpenDrain(GPIONum num) : GPIOInput(num) +{ + GPIO_CHECK_THROW(gpio_set_direction(gpio_to_driver_type(gpio_num), GPIO_MODE_INPUT_OUTPUT_OD)); +} + +void GPIO_OpenDrain::set_floating() +{ + GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 1)); +} + +void GPIO_OpenDrain::set_low() +{ + GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 0)); +} + +} + +#endif diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/fixtures/test_fixtures.hpp b/examples/cxx/experimental/experimental_cpp_component/host_test/fixtures/test_fixtures.hpp new file mode 100644 index 0000000000..0dcfdf5506 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/fixtures/test_fixtures.hpp @@ -0,0 +1,78 @@ +// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "catch.hpp" +#include "gpio_cxx.hpp" +extern "C" { +#include "Mockgpio.h" +} + +static const idf::GPIONum VALID_GPIO(18); + +/** + * Exception which is thrown if there is some internal cmock error which results in a + * longjump to the location of a TEST_PROTECT() call. + * + * @note This is a temporary solution until there is a better integration of CATCH into CMock. + * Note also that usually there will be a segfault when cmock fails a second time. + * This means paying attention to the first error message is crucial for removing errors. + */ +class CMockException : public std::exception { +public: + virtual ~CMockException() { } + + /** + * @return A reminder to look at the actual cmock log. + */ + virtual const char *what() const noexcept + { + return "CMock encountered an error. Look at the CMock log"; + } +}; + +/** + * Helper macro for setting up a test protect call for CMock. + * + * This macro should be used at the beginning of any test cases + * which use generated CMock mock functions. + * This is necessary because CMock uses longjmp which screws up C++ stacks and + * also the CATCH mechanisms. + * + * @note This is a temporary solution until there is a better integration of CATCH into CMock. + * Note also that usually there will be a segfault when cmock fails a second time. + * This means paying attention to the first error message is crucial for removing errors. + */ +#define CMOCK_SETUP() \ + do { \ + if (!TEST_PROTECT()) { \ + throw CMockException(); \ + } \ + } \ + while (0) + +struct GPIOFixture { + GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT) : num(gpio_num) + { + CMOCK_SETUP(); + gpio_reset_pin_ExpectAndReturn(static_cast(num.get_num()), ESP_OK); gpio_set_direction_ExpectAndReturn(static_cast(num.get_num()), mode, ESP_OK); + } + + ~GPIOFixture() + { + // Verify that all expected methods have been called. + Mockgpio_Verify(); + } + + idf::GPIONum num; +}; diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/CMakeLists.txt new file mode 100644 index 0000000000..e918d84f04 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) + +idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND) +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/mocks/driver/") +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/") +project(test_gpio_cxx_host) diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/README.md b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/README.md new file mode 100644 index 0000000000..a376df2c64 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/README.md @@ -0,0 +1,8 @@ +| Supported Targets | Linux | +| ----------------- | ----- | + +# Build +`idf.py build` (sdkconfig.defaults sets the linux target by default) + +# Run +`build/test_gpio_cxx_host.elf` diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/CMakeLists.txt new file mode 100644 index 0000000000..5a2a6bf745 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/CMakeLists.txt @@ -0,0 +1,13 @@ +idf_component_get_property(spi_flash_dir spi_flash COMPONENT_DIR) +idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR) + +idf_component_register(SRCS "gpio_cxx_test.cpp" + "${cpp_component}/esp_exception.cpp" + "${cpp_component}/gpio_cxx.cpp" + INCLUDE_DIRS + "." + "${cpp_component}/host_test/fixtures" + "${cpp_component}/include" + "${cpp_component}/test" # FIXME for unity_cxx.hpp, make it generally available instead + $ENV{IDF_PATH}/tools/catch + REQUIRES driver cmock esp_common) diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/gpio_cxx_test.cpp b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/gpio_cxx_test.cpp new file mode 100644 index 0000000000..dd6a9a084a --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/gpio_cxx_test.cpp @@ -0,0 +1,397 @@ +/* GPIO C++ unit tests + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#define CATCH_CONFIG_MAIN + +#include +#include "esp_err.h" +#include "freertos/portmacro.h" +#include "gpio_cxx.hpp" +#include "test_fixtures.hpp" + +#include "catch.hpp" + +extern "C" { +#include "Mockgpio.h" +} + +// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead +const char *esp_err_to_name(esp_err_t code) { + return "test"; +} + +using namespace std; +using namespace idf; + +TEST_CASE("gpio num out of range") +{ + CHECK_THROWS_AS(GPIONum(-1), GPIOException&); + CHECK_THROWS_AS(GPIONum(static_cast(GPIO_NUM_MAX)), GPIOException&); + CHECK_THROWS_AS(GPIONum(24), GPIOException&); // On ESP32, 24 isn't a valid GPIO number +} + +TEST_CASE("gpio num operator") +{ + GPIONum gpio_num_0(18u); + GPIONum gpio_num_1(18u); + GPIONum gpio_num_2(19u); + + CHECK(gpio_num_0 == gpio_num_1); + CHECK(gpio_num_2 != gpio_num_1); +} + +TEST_CASE("drive strength out of range") +{ + CHECK_THROWS_AS(GPIODriveStrength(-1), GPIOException&); + CHECK_THROWS_AS(GPIODriveStrength(static_cast(GPIO_DRIVE_CAP_MAX)), GPIOException&); +} + +TEST_CASE("drive strength as expected") +{ + CHECK(GPIODriveStrength::DEFAULT().get_strength() == GPIO_DRIVE_CAP_2); + CHECK(GPIODriveStrength::WEAK().get_strength() == GPIO_DRIVE_CAP_0); + CHECK(GPIODriveStrength::LESS_WEAK().get_strength() == GPIO_DRIVE_CAP_1); + CHECK(GPIODriveStrength::MEDIUM().get_strength() == GPIO_DRIVE_CAP_2); + CHECK(GPIODriveStrength::STRONGEST().get_strength() == GPIO_DRIVE_CAP_3); +} + +TEST_CASE("pull mode create functions work as expected") +{ + CHECK(GPIOPullMode::FLOATING().get_pull_mode() == 3); + CHECK(GPIOPullMode::PULLUP().get_pull_mode() == 0); + CHECK(GPIOPullMode::PULLDOWN().get_pull_mode() == 1); +} + +TEST_CASE("GPIOIntrType create functions work as expected") +{ + CHECK(GPIOWakeupIntrType::LOW_LEVEL().get_level() == GPIO_INTR_LOW_LEVEL); + CHECK(GPIOWakeupIntrType::HIGH_LEVEL().get_level() == GPIO_INTR_HIGH_LEVEL); +} + +TEST_CASE("output resetting pin fails") +{ + CMOCK_SETUP(); + gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_FAIL); + + CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&); + + Mockgpio_Verify(); +} + +TEST_CASE("output setting direction fails") +{ + CMOCK_SETUP(); + gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK); + gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL); + + CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&); + + Mockgpio_Verify(); +} + +TEST_CASE("output constructor sets correct arguments") +{ + CMOCK_SETUP(); + gpio_reset_pin_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), ESP_OK); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), GPIO_MODE_OUTPUT, ESP_OK); + + GPIO_Output gpio(VALID_GPIO); + + Mockgpio_Verify(); +} + +TEST_CASE("output set high fails") +{ + GPIOFixture fix; + gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_num()), 1, ESP_FAIL); + + GPIO_Output gpio(fix.num); + + CHECK_THROWS_AS(gpio.set_high(), GPIOException&); +} + +TEST_CASE("output set high success") +{ + GPIOFixture fix; + gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_num()), 1, ESP_OK); + + GPIO_Output gpio(fix.num); + + gpio.set_high(); +} + +TEST_CASE("output set low fails") +{ + GPIOFixture fix; + gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_num()), 0, ESP_FAIL); + + GPIO_Output gpio(fix.num); + + CHECK_THROWS_AS(gpio.set_low(), GPIOException&); +} + +TEST_CASE("output set low success") +{ + GPIOFixture fix; + gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_num()), 0, ESP_OK); + + GPIO_Output gpio(fix.num); + + gpio.set_low(); +} + +TEST_CASE("output set drive strength") +{ + GPIOFixture fix(VALID_GPIO); + gpio_set_drive_capability_ExpectAndReturn(static_cast(fix.num.get_num()), GPIO_DRIVE_CAP_0, ESP_OK); + + GPIO_Output gpio(fix.num); + + gpio.set_drive_strength(GPIODriveStrength::WEAK()); +} + +TEST_CASE("output get drive strength") +{ + GPIOFixture fix(VALID_GPIO); + gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3; + gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK); + gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength); + + GPIO_Output gpio(fix.num); + + CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST()); +} + +TEST_CASE("GPIOInput setting direction fails") +{ + CMOCK_SETUP(); + gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK); + gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL); + + CHECK_THROWS_AS(GPIOInput gpio(VALID_GPIO), GPIOException&); + + Mockgpio_Verify(); +} + +TEST_CASE("constructor sets correct arguments") +{ + CMOCK_SETUP(); + gpio_reset_pin_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), ESP_OK); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), GPIO_MODE_INPUT, ESP_OK); + + GPIOInput gpio(VALID_GPIO); + + Mockgpio_Verify(); +} + +TEST_CASE("get level low") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_get_level_ExpectAndReturn(static_cast(fix.num.get_num()), 0); + + GPIOInput gpio(fix.num); + + CHECK(gpio.get_level() == GPIOLevel::LOW); +} + +TEST_CASE("get level high") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_get_level_ExpectAndReturn(static_cast(fix.num.get_num()), 1); + + GPIOInput gpio(fix.num); + + CHECK(gpio.get_level() == GPIOLevel::HIGH); +} + +TEST_CASE("set pull mode fails") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_pull_mode_ExpectAndReturn(static_cast(fix.num.get_num()), GPIO_FLOATING, ESP_FAIL); + + GPIOInput gpio(fix.num); + + CHECK_THROWS_AS(gpio.set_pull_mode(GPIOPullMode::FLOATING()), GPIOException&); +} + +TEST_CASE("GPIOInput set pull mode floating") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_pull_mode_ExpectAndReturn(static_cast(fix.num.get_num()), GPIO_FLOATING, ESP_OK); + + GPIOInput gpio(fix.num); + + gpio.set_pull_mode(GPIOPullMode::FLOATING()); +} + +TEST_CASE("GPIOInput set pull mode pullup") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_pull_mode_ExpectAndReturn(static_cast(fix.num.get_num()), GPIO_PULLUP_ONLY, ESP_OK); + + GPIOInput gpio(fix.num); + + gpio.set_pull_mode(GPIOPullMode::PULLUP()); +} + +TEST_CASE("GPIOInput set pull mode pulldown") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_pull_mode_ExpectAndReturn(static_cast(fix.num.get_num()), GPIO_PULLDOWN_ONLY, ESP_OK); + + GPIOInput gpio(fix.num); + + gpio.set_pull_mode(GPIOPullMode::PULLDOWN()); +} + +TEST_CASE("GPIOInput wake up enable fails") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_wakeup_enable_ExpectAndReturn(static_cast(fix.num.get_num()), GPIO_INTR_LOW_LEVEL, ESP_FAIL); + + GPIOInput gpio(fix.num); + + CHECK_THROWS_AS(gpio.wakeup_enable(GPIOWakeupIntrType::LOW_LEVEL()), GPIOException&); +} + +TEST_CASE("GPIOInput wake up enable high int") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_wakeup_enable_ExpectAndReturn(static_cast(fix.num.get_num()), GPIO_INTR_HIGH_LEVEL, ESP_OK); + + GPIOInput gpio(fix.num); + + gpio.wakeup_enable(GPIOWakeupIntrType::HIGH_LEVEL()); +} + +TEST_CASE("GPIOInput wake up disable fails") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_wakeup_disable_ExpectAndReturn(static_cast(fix.num.get_num()), ESP_FAIL); + + GPIOInput gpio(fix.num); + + CHECK_THROWS_AS(gpio.wakeup_disable(), GPIOException&); +} + +TEST_CASE("GPIOInput wake up disable high int") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_wakeup_disable_ExpectAndReturn(static_cast(fix.num.get_num()), ESP_OK); + + GPIOInput gpio(fix.num); + + gpio.wakeup_disable(); +} + +TEST_CASE("GPIO_OpenDrain setting direction fails") +{ + CMOCK_SETUP(); + gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK); + gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL); + + CHECK_THROWS_AS(GPIO_OpenDrain gpio(VALID_GPIO), GPIOException&); + + Mockgpio_Verify(); +} + +TEST_CASE("GPIO_OpenDrain constructor sets correct arguments") +{ + CMOCK_SETUP(); + gpio_reset_pin_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), ESP_OK); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), + GPIO_MODE_INPUT, + ESP_OK); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), + GPIO_MODE_INPUT_OUTPUT_OD, + ESP_OK); + + GPIO_OpenDrain gpio(VALID_GPIO); + + Mockgpio_Verify(); +} + +TEST_CASE("GPIO_OpenDrain set floating fails") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), + GPIO_MODE_INPUT_OUTPUT_OD, + ESP_OK); + gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_num()), 1, ESP_FAIL); + + GPIO_OpenDrain gpio(fix.num); + + CHECK_THROWS_AS(gpio.set_floating(), GPIOException&); +} + +TEST_CASE("GPIO_OpenDrain set floating success") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), + GPIO_MODE_INPUT_OUTPUT_OD, + ESP_OK); + gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_num()), 1, ESP_OK); + + GPIO_OpenDrain gpio(fix.num); + + gpio.set_floating(); +} + +TEST_CASE("GPIO_OpenDrain set low fails") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), + GPIO_MODE_INPUT_OUTPUT_OD, + ESP_OK); + gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_num()), 0, ESP_FAIL); + + GPIO_OpenDrain gpio(fix.num); + + CHECK_THROWS_AS(gpio.set_low(), GPIOException&); +} + +TEST_CASE("GPIO_OpenDrain set low success") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), + GPIO_MODE_INPUT_OUTPUT_OD, + ESP_OK); + gpio_set_level_ExpectAndReturn(static_cast(fix.num.get_num()), 0, ESP_OK); + + GPIO_OpenDrain gpio(fix.num); + + gpio.set_low(); +} + +TEST_CASE("GPIO_OpenDrain set drive strength") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), + GPIO_MODE_INPUT_OUTPUT_OD, + ESP_OK); + + gpio_set_drive_capability_ExpectAndReturn(static_cast(fix.num.get_num()), GPIO_DRIVE_CAP_0, ESP_OK); + GPIO_OpenDrain gpio(fix.num); + + gpio.set_drive_strength(GPIODriveStrength::WEAK()); +} + +TEST_CASE("GPIO_OpenDrain get drive strength") +{ + GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT); + gpio_set_direction_ExpectAndReturn(static_cast(VALID_GPIO.get_num()), + GPIO_MODE_INPUT_OUTPUT_OD, + ESP_OK); + gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3; + gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK); + gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength); + + GPIO_OpenDrain gpio(fix.num); + + CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST()); +} diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/sdkconfig.defaults b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/sdkconfig.defaults new file mode 100644 index 0000000000..a057733348 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/gpio/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n +CONFIG_IDF_TARGET="linux" +CONFIG_CXX_EXCEPTIONS=y diff --git a/examples/cxx/experimental/experimental_cpp_component/include/gpio_cxx.hpp b/examples/cxx/experimental/experimental_cpp_component/include/gpio_cxx.hpp new file mode 100644 index 0000000000..24de17cdb9 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/include/gpio_cxx.hpp @@ -0,0 +1,402 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#if __cpp_exceptions + +#include "esp_exception.hpp" +#include "system_cxx.hpp" + +namespace idf { + +/** + * @brief Exception thrown for errors in the GPIO C++ API. + */ +struct GPIOException : public ESPException { + /** + * @param error The IDF error representing the error class of the error to throw. + */ + GPIOException(esp_err_t error); +}; + +/** + * Check if the numeric pin number is valid on the current hardware. + */ +esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept; + +/** + * Check if the numeric value of a drive strength is valid on the current hardware. + */ +esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept; + +/** + * This is a "Strong Value Type" class for GPIO. The GPIO pin number is checked during construction according to + * the hardware capabilities. This means that any GPIONumBase object is guaranteed to contain a valid GPIO number. + * See also the template class \c StrongValue. + */ +template +class GPIONumBase final : public StrongValueComparable { +public: + /** + * @brief Create a numerical pin number representation and make sure it's correct. + * + * @throw GPIOException if the number does not reflect a valid GPIO number on the current hardware. + */ + GPIONumBase(uint32_t pin) : StrongValueComparable(pin) + { + esp_err_t pin_check_result = check_gpio_pin_num(pin); + if (pin_check_result != ESP_OK) { + throw GPIOException(pin_check_result); + } + } + + using StrongValueComparable::operator==; + using StrongValueComparable::operator!=; + + /** + * Retrieves the valid numerical representation of the GPIO number. + */ + uint32_t get_num() const { return get_value(); }; +}; + +/** + * This is a TAG type whose sole purpose is to create a distinct type from GPIONumBase. + */ +class GPIONumType; + +/** + * A GPIO number type used for general GPIOs, in contrast to specific GPIO pins like e.g. SPI_SCLK. + */ +using GPIONum = GPIONumBase; + +/** + * Level of an input GPIO. + */ +enum class GPIOLevel { + HIGH, + LOW +}; + +/** + * Represents a valid pull up configuration for GPIOs. + * It is supposed to resemble an enum type, hence it has static creation methods and a private constructor. + * This class is a "Strong Value Type", see also the template class \c StrongValue for more properties. + */ +class GPIOPullMode final : public StrongValueComparable { +private: + /** + * Constructor is private since it should only be accessed by the static creation methods. + * + * @param pull_mode A valid numerical respresentation of the pull up configuration. Must be valid! + */ + GPIOPullMode(uint32_t pull_mode) : StrongValueComparable(pull_mode) { } + +public: + /** + * Create a representation of a floating pin configuration. + * For more information, check the driver and HAL files. + */ + static GPIOPullMode FLOATING(); + + /** + * Create a representation of a pullup configuration. + * For more information, check the driver and HAL files. + */ + static GPIOPullMode PULLUP(); + + /** + * Create a representation of a pulldown configuration. + * For more information, check the driver and HAL files. + */ + static GPIOPullMode PULLDOWN(); + + using StrongValueComparable::operator==; + using StrongValueComparable::operator!=; + + /** + * Retrieves the valid numerical representation of the pull mode. + */ + uint32_t get_pull_mode() const { return get_value(); }; +}; + +/** + * @brief Represents a valid wakup interrupt type for GPIO inputs. + * + * This class is a "Strong Value Type", see also the template class \c StrongValue for more properties. + * It is supposed to resemble an enum type, hence it has static creation methods and a private constructor. + * For a detailed mapping of interrupt types to numeric values, please refer to the driver types and implementation. + */ +class GPIOWakeupIntrType final: public StrongValueComparable { +private: + /** + * Constructor is private since it should only be accessed by the static creation methods. + * + * @param pull_mode A valid numerical respresentation of a possible interrupt level to wake up. Must be valid! + */ + GPIOWakeupIntrType(uint32_t interrupt_level) : StrongValueComparable(interrupt_level) { } + +public: + static GPIOWakeupIntrType LOW_LEVEL(); + static GPIOWakeupIntrType HIGH_LEVEL(); + + /** + * Retrieves the valid numerical representation of the pull mode. + */ + uint32_t get_level() const noexcept { return get_value(); }; +}; + +/** + * Class representing a valid drive strength for GPIO outputs. + * This class is a "Strong Value Type", see also the template class \c StrongValue for more properties. + * For a detailed mapping for values to drive strengths, please refer to the datasheet of the chip you are using. + * E.g. for ESP32, the values in general are the following: + * - WEAK: 5mA + * - STRONGER: 10mA + * - DEFAULT/MEDIUM: 20mA + * - STRONGEST: 40mA + */ +class GPIODriveStrength final : public StrongValueComparable { +public: + /** + * @brief Create a drive strength representation and checks its validity. + * + * After construction, this class should have a guaranteed valid strength representation. + * + * @param strength the numeric value mapping for a particular strength. For possible ranges, look at the + * static creation functions below. + * @throws GPIOException if the supplied number is out of the hardware capable range. + */ + GPIODriveStrength(uint32_t strength) : StrongValueComparable(strength) + { + esp_err_t strength_check_result = check_gpio_drive_strength(strength); + if (strength_check_result != ESP_OK) { + throw GPIOException(strength_check_result); + } + } + + /** + * Create a representation of the default drive strength. + * For more information, check the datasheet and driver and HAL files. + */ + static GPIODriveStrength DEFAULT(); + + /** + * Create a representation of the weak drive strength. + * For more information, check the datasheet and driver and HAL files. + */ + static GPIODriveStrength WEAK(); + + /** + * Create a representation of the less weak drive strength. + * For more information, check the datasheet and driver and HAL files. + */ + static GPIODriveStrength LESS_WEAK(); + + /** + * Create a representation of the medium drive strength. + * For more information, check the datasheet and driver and HAL files. + */ + static GPIODriveStrength MEDIUM(); + + /** + * Create a representation of the strong drive strength. + */ + static GPIODriveStrength STRONGEST(); + + using StrongValueComparable::operator==; + using StrongValueComparable::operator!=; + + /** + * Retrieves the valid numerical representation of the drive strength. + */ + uint32_t get_strength() const { return get_value(); }; + +}; + +/** + * @brief Implementations commonly used functionality for all GPIO configurations. + * + * Some functionality is only for specific configurations (set and get drive strength) but is necessary here + * to avoid complicating the inheritance hierarchy of the GPIO classes. + * Child classes implementing any GPIO configuration (output, input, etc.) are meant to intherit from this class + * and possibly make some of the functionality publicly available. + */ +class GPIOBase { +protected: + /** + * @brief Construct a GPIO. + * + * This constructor will only reset the GPIO but leaves the actual configuration (input, output, etc.) to + * the sub class. + * + * @param num GPIO pin number of the GPIO to be configured. + * + * @throws GPIOException + * - if the underlying driver function fails + */ + GPIOBase(GPIONum num); + + /** + * @brief Enable gpio pad hold function. + * + * The gpio pad hold function works in both input and output modes, but must be output-capable gpios. + * If pad hold enabled: + * in output mode: the output level of the pad will be force locked and can not be changed. + * in input mode: the input value read will not change, regardless the changes of input signal. + * + * @throws GPIOException if the underlying driver function fails. + */ + void hold_en(); + + /** + * @brief Disable gpio pad hold function. + * + * @throws GPIOException if the underlying driver function fails. + */ + void hold_dis(); + + /** + * @brief Configure the drive strength of the GPIO. + * + * @param strength The drive strength. Refer to \c GPIODriveStrength for more details. + * + * @throws GPIOException if the underlying driver function fails. + */ + void set_drive_strength(GPIODriveStrength strength); + + /** + * @brief Return the current drive strength of the GPIO. + * + * @return The currently configured drive strength. Refer to \c GPIODriveStrength for more details. + * + * @throws GPIOException if the underlying driver function fails. + */ + GPIODriveStrength get_drive_strength(); + + /** + * @brief The number of the configured GPIO pin. + */ + GPIONum gpio_num; +}; + +/** + * @brief This class represents a GPIO which is configured as output. + */ +class GPIO_Output : public GPIOBase { +public: + /** + * @brief Construct and configure a GPIO as output. + * + * @param num GPIO pin number of the GPIO to be configured. + * + * @throws GPIOException + * - if the underlying driver function fails + */ + GPIO_Output(GPIONum num); + + /** + * @brief Set GPIO to high level. + * + * @throws GPIOException if the underlying driver function fails. + */ + void set_high(); + + /** + * @brief Set GPIO to low level. + * + * @throws GPIOException if the underlying driver function fails. + */ + void set_low(); + + using GPIOBase::set_drive_strength; + using GPIOBase::get_drive_strength; +}; + +/** + * @brief This class represents a GPIO which is configured as input. + */ +class GPIOInput : public GPIOBase { +public: + /** + * @brief Construct and configure a GPIO as input. + * + * @param num GPIO pin number of the GPIO to be configured. + * + * @throws GPIOException + * - if the underlying driver function fails + */ + GPIOInput(GPIONum num); + + /** + * @brief Read the current level of the GPIO. + * + * @return The GPIO current level of the GPIO. + */ + GPIOLevel get_level() const noexcept; + + /** + * @brief Configure the internal pull-up and pull-down restors. + * + * @param mode The pull-up/pull-down configuration see \c GPIOPullMode. + * + * @throws GPIOException if the underlying driver function fails. + */ + void set_pull_mode(GPIOPullMode mode); + + /** + * @brief Configure the pin as wake up pin. + * + * @throws GPIOException if the underlying driver function fails. + */ + void wakeup_enable(GPIOWakeupIntrType interrupt_type); + + /** + * @brief Disable wake up functionality for this pin if it was enabled before. + * + * @throws GPIOException if the underlying driver function fails. + */ + void wakeup_disable(); +}; + +/** + * @brief This class represents a GPIO which is configured as open drain output and input at the same time. + * + * This class facilitates bit-banging for single wire protocols. + */ +class GPIO_OpenDrain : public GPIOInput { +public: + /** + * @brief Construct and configure a GPIO as open drain output as well as input. + * + * @param num GPIO pin number of the GPIO to be configured. + * + * @throws GPIOException + * - if the underlying driver function fails + */ + GPIO_OpenDrain(GPIONum num); + + /** + * @brief Set GPIO to floating level. + * + * @throws GPIOException if the underlying driver function fails. + */ + void set_floating(); + + /** + * @brief Set GPIO to low level. + * + * @throws GPIOException if the underlying driver function fails. + */ + void set_low(); + + using GPIOBase::set_drive_strength; + using GPIOBase::get_drive_strength; +}; + +} + +#endif diff --git a/examples/cxx/experimental/experimental_cpp_component/include/system_cxx.hpp b/examples/cxx/experimental/experimental_cpp_component/include/system_cxx.hpp new file mode 100644 index 0000000000..4879830840 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/include/system_cxx.hpp @@ -0,0 +1,53 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifndef __cpp_exceptions +#error system C++ classes only usable when C++ exceptions enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig +#endif + +/** + * This is a "Strong Value Type" base class for types in IDF C++ classes. + * The idea is that subclasses completely check the contained value during construction. + * After that, it's trapped and encapsulated inside and cannot be changed anymore. + * Consequently, the API functions receiving a correctly implemented sub class as parameter + * don't need to check it anymore. Only at API boundaries the valid value will be retrieved + * with get_value(). + */ +template +class StrongValue { +protected: + StrongValue(ValueT value_arg) : value(value_arg) { } + + ValueT get_value() const { + return value; + } + +private: + ValueT value; +}; + +/** + * This class adds comparison properties to StrongValue, but no sorting properties. + */ +template +class StrongValueComparable : public StrongValue { +protected: + StrongValueComparable(ValueT value_arg) : StrongValue(value_arg) { } + + using StrongValue::get_value; + + bool operator==(const StrongValueComparable &other_gpio) const + { + return get_value() == other_gpio.get_value(); + } + + bool operator!=(const StrongValueComparable &other_gpio) const + { + return get_value() != other_gpio.get_value(); + } +}; diff --git a/tools/ci/check_examples_cmake_make-cmake_ignore.txt b/tools/ci/check_examples_cmake_make-cmake_ignore.txt index 9552fb642d..1b4fe39517 100644 --- a/tools/ci/check_examples_cmake_make-cmake_ignore.txt +++ b/tools/ci/check_examples_cmake_make-cmake_ignore.txt @@ -4,3 +4,4 @@ cxx/experimental/experimental_cpp_component/ main/ build_system/cmake/ mb_example_common/ +examples/cxx/experimental/blink_cxx diff --git a/tools/ci/check_examples_cmake_make-make_ignore.txt b/tools/ci/check_examples_cmake_make-make_ignore.txt index d41c9f4a47..1c87e1808d 100644 --- a/tools/ci/check_examples_cmake_make-make_ignore.txt +++ b/tools/ci/check_examples_cmake_make-make_ignore.txt @@ -1,3 +1,4 @@ build_system/cmake temp_ examples/bluetooth/bluedroid/ble_50/ +examples/cxx/experimental/blink_cxx