[cxx]: GPIO CXX wrappers, experiemental CI rule

* Wrapper class for simple GPIO interaction
  like read/write without ISRs.
* Added rule to provoke builds after changes in
  the experimental C++ component.
This commit is contained in:
Jakob Hasse 2021-06-11 19:20:53 +08:00
parent b5f9149399
commit 06956d46c1
19 changed files with 1293 additions and 0 deletions

View File

@ -362,3 +362,10 @@ test_rom_on_linux_works:
- cd ${IDF_PATH}/components/esp_rom/host_test/rom_test - cd ${IDF_PATH}/components/esp_rom/host_test/rom_test
- idf.py build - idf.py build
- build/test_rom_host.elf - 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

View File

@ -37,6 +37,7 @@
.patterns-build_components: &patterns-build_components .patterns-build_components: &patterns-build_components
- "components/**/*" - "components/**/*"
- "examples/cxx/experimental/experimental_cpp_component/*"
.patterns-build_system: &patterns-build_system .patterns-build_system: &patterns-build_system
- "tools/cmake/**/*" - "tools/cmake/**/*"

View File

@ -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)

View File

@ -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
```

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.cpp"
INCLUDE_DIRS ".")

View File

@ -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 <cstdlib>
#include <thread>
#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");
}
}

View File

@ -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

View File

@ -1,6 +1,7 @@
idf_component_register(SRCS idf_component_register(SRCS
"esp_exception.cpp" "esp_exception.cpp"
"i2c_cxx.cpp" "i2c_cxx.cpp"
"gpio_cxx.cpp"
"esp_event_api.cpp" "esp_event_api.cpp"
"esp_event_cxx.cpp" "esp_event_cxx.cpp"
"esp_timer_cxx.cpp" "esp_timer_cxx.cpp"

View File

@ -0,0 +1,208 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if __cpp_exceptions
#include <array>
#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<uint32_t, 1> INVALID_GPIOS = {24};
#elif CONFIG_IDF_TARGET_ESP32
constexpr std::array<uint32_t, 1> INVALID_GPIOS = {24};
#elif CONFIG_IDF_TARGET_ESP32S2
constexpr std::array<uint32_t, 4> INVALID_GPIOS = {22, 23, 24, 25};
#elif CONFIG_IDF_TARGET_ESP32S3
constexpr std::array<uint32_t, 4> INVALID_GPIOS = {22, 23, 24, 25};
#elif CONFIG_IDF_TARGET_ESP32C3
constexpr std::array<uint32_t, 0> 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_t>(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<gpio_drive_cap_t>(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<uint32_t>(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<gpio_pull_mode_t>(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<gpio_int_type_t>(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

View File

@ -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<gpio_num_t>(num.get_num()), ESP_OK); gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(num.get_num()), mode, ESP_OK);
}
~GPIOFixture()
{
// Verify that all expected methods have been called.
Mockgpio_Verify();
}
idf::GPIONum num;
};

View File

@ -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)

View File

@ -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`

View File

@ -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)

View File

@ -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 <stdio.h>
#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<uint32_t>(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<uint32_t>(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<gpio_num_t>(VALID_GPIO.get_num()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(VALID_GPIO.get_num()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(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<gpio_num_t>(VALID_GPIO.get_num()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT,
ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(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<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(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<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(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<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(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<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(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<gpio_num_t>(VALID_GPIO.get_num()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_drive_capability_ExpectAndReturn(static_cast<gpio_num_t>(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<gpio_num_t>(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());
}

View File

@ -0,0 +1,3 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y

View File

@ -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<typename GPIONumFinalType>
class GPIONumBase final : public StrongValueComparable<uint32_t> {
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<uint32_t>(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<uint32_t>::operator==;
using StrongValueComparable<uint32_t>::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<class GPIONumType>;
/**
* 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<uint32_t> {
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<uint32_t>(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<uint32_t>::operator==;
using StrongValueComparable<uint32_t>::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<uint32_t> {
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<uint32_t>(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<uint32_t> {
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<uint32_t>(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<uint32_t>::operator==;
using StrongValueComparable<uint32_t>::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

View File

@ -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<typename ValueT>
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<typename ValueT>
class StrongValueComparable : public StrongValue<ValueT> {
protected:
StrongValueComparable(ValueT value_arg) : StrongValue<ValueT>(value_arg) { }
using StrongValue<ValueT>::get_value;
bool operator==(const StrongValueComparable<ValueT> &other_gpio) const
{
return get_value() == other_gpio.get_value();
}
bool operator!=(const StrongValueComparable<ValueT> &other_gpio) const
{
return get_value() != other_gpio.get_value();
}
};

View File

@ -4,3 +4,4 @@ cxx/experimental/experimental_cpp_component/
main/ main/
build_system/cmake/ build_system/cmake/
mb_example_common/ mb_example_common/
examples/cxx/experimental/blink_cxx

View File

@ -1,3 +1,4 @@
build_system/cmake build_system/cmake
temp_ temp_
examples/bluetooth/bluedroid/ble_50/ examples/bluetooth/bluedroid/ble_50/
examples/cxx/experimental/blink_cxx