diff --git a/.gitlab/ci/host-test.yml b/.gitlab/ci/host-test.yml index 87d8c2c12b..ba1d635693 100644 --- a/.gitlab/ci/host-test.yml +++ b/.gitlab/ci/host-test.yml @@ -364,6 +364,20 @@ test_cxx_gpio: - idf.py build - build/test_gpio_cxx_host.elf +test_spi_cxx: + extends: .host_test_template + script: + - cd ${IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/host_test/spi + - idf.py build + - build/test_spi_cxx_host.elf + +test_system_cxx: + extends: .host_test_template + script: + - cd ${IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/host_test/system + - idf.py build + - build/test_system_cxx_host.elf + test_linux_example: extends: .host_test_template script: diff --git a/components/freertos/port/linux/include/freertos/portmacro.h b/components/freertos/port/linux/include/freertos/portmacro.h index 720f128c80..cbd5e8390f 100644 --- a/components/freertos/port/linux/include/freertos/portmacro.h +++ b/components/freertos/port/linux/include/freertos/portmacro.h @@ -27,6 +27,8 @@ typedef uint32_t TickType_t; typedef int portMUX_TYPE; +#define portTICK_PERIOD_MS ( ( TickType_t ) 1 ) + #ifdef __cplusplus } #endif diff --git a/components/hal/include/hal/spi_types.h b/components/hal/include/hal/spi_types.h index 169069c5c6..300d7da46d 100644 --- a/components/hal/include/hal/spi_types.h +++ b/components/hal/include/hal/spi_types.h @@ -1,16 +1,8 @@ -// Copyright 2015-2019 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. +/* + * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -28,6 +20,7 @@ typedef enum { SPI1_HOST=0, ///< SPI1 SPI2_HOST=1, ///< SPI2 SPI3_HOST=2, ///< SPI3 + SPI_HOST_MAX, ///< invalid host value } spi_host_device_t; /// SPI Events diff --git a/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt index 20c8de477c..aee7a370aa 100644 --- a/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt +++ b/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt @@ -1,6 +1,6 @@ idf_build_get_property(target IDF_TARGET) -set(srcs "esp_timer_cxx.cpp" "esp_exception.cpp" "gpio_cxx.cpp") +set(srcs "esp_timer_cxx.cpp" "esp_exception.cpp" "gpio_cxx.cpp" "spi_cxx.cpp" "spi_host_cxx.cpp") set(requires "esp_timer" "driver") if(NOT ${target} STREQUAL "linux") @@ -13,4 +13,6 @@ endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "private_include" + PRIV_REQUIRES freertos REQUIRES ${requires}) diff --git a/examples/cxx/experimental/experimental_cpp_component/component.mk b/examples/cxx/experimental/experimental_cpp_component/component.mk index 6716993856..7d3b7d373a 100644 --- a/examples/cxx/experimental/experimental_cpp_component/component.mk +++ b/examples/cxx/experimental/experimental_cpp_component/component.mk @@ -1,3 +1,3 @@ -COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_ADD_INCLUDEDIRS := include private_include COMPONENT_SRCDIRS := ./ driver 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 index 0dcfdf5506..524f5cdec6 100644 --- 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 @@ -1,21 +1,23 @@ -// 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. +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0 + * + * 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 "catch.hpp" #include "gpio_cxx.hpp" +#include "driver/spi_master.h" +#include "spi_cxx.hpp" extern "C" { #include "Mockgpio.h" +#include "Mockspi_master.h" +#include "Mockspi_common.h" } static const idf::GPIONum VALID_GPIO(18); @@ -45,7 +47,7 @@ public: * 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. + * that uses generated CMock mock functions. * This is necessary because CMock uses longjmp which screws up C++ stacks and * also the CATCH mechanisms. * @@ -61,18 +63,236 @@ public: } \ while (0) -struct GPIOFixture { - GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT) : num(gpio_num) +struct CMockFixture { + CMockFixture() { 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() + ~CMockFixture() { // Verify that all expected methods have been called. Mockgpio_Verify(); + Mockspi_master_Verify(); + Mockspi_common_Verify(); + } +}; + +struct GPIOFixture : public CMockFixture { + GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT) + : CMockFixture(), num(gpio_num) + { + gpio_reset_pin_ExpectAndReturn(static_cast(num.get_num()), ESP_OK); + gpio_set_direction_ExpectAndReturn(static_cast(num.get_num()), mode, ESP_OK); } idf::GPIONum num; }; + +struct SPIFix; +struct SPIDevFix; +struct SPITransactionDescriptorFix; +struct SPITransactionTimeoutFix; +struct SPITransactionFix; + +static SPIFix *g_fixture; +static SPIDevFix *g_dev_fixture; +static SPITransactionDescriptorFix *g_trans_desc_fixture; +static SPITransactionTimeoutFix *g_trans_timeout_fixture; +static SPITransactionFix *g_trans_fixture; + +struct SPIFix : public CMockFixture { + SPIFix(spi_host_device_t host_id = spi_host_device_t(1), + uint32_t mosi = 1, + uint32_t miso = 2, + uint32_t sclk = 3) : CMockFixture(), bus_config() { + bus_config.mosi_io_num = mosi; + bus_config.miso_io_num = miso; + bus_config.sclk_io_num = sclk; + bus_config.quadwp_io_num = -1; + bus_config.quadhd_io_num = -1; + + spi_bus_initialize_ExpectWithArrayAndReturn(host_id, &bus_config, 1, spi_common_dma_t::SPI_DMA_CH_AUTO, ESP_OK); + spi_bus_free_ExpectAnyArgsAndReturn(ESP_OK); + + g_fixture = this; + } + + ~SPIFix() { + g_fixture = nullptr; + } + + spi_bus_config_t bus_config; +}; + +struct QSPIFix : public SPIFix { + QSPIFix(spi_host_device_t host_id = spi_host_device_t(1), + uint32_t mosi = 1, + uint32_t miso = 2, + uint32_t sclk = 3, + uint32_t wp = 4, + uint32_t hd = 5) : SPIFix(host_id, mosi, miso, sclk) + { + bus_config.quadwp_io_num = wp; + bus_config.quadhd_io_num = hd; + } +}; + +enum class CreateAnd { + FAIL, + SUCCEED, + IGNORE +}; + +struct SPIDevFix { + SPIDevFix(CreateAnd flags) + : dev_handle(reinterpret_cast(47)), + dev_config() + { + dev_config.spics_io_num = 4; + if (flags == CreateAnd::FAIL) { + spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_FAIL); + } else if (flags == CreateAnd::IGNORE) { + spi_bus_add_device_IgnoreAndReturn(ESP_OK); + spi_bus_remove_device_IgnoreAndReturn(ESP_OK); + } else { + spi_bus_add_device_AddCallback(add_dev_cb); + spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_OK); + spi_bus_remove_device_ExpectAndReturn(dev_handle, ESP_OK); + } + + g_dev_fixture = this; + } + + ~SPIDevFix() + { + spi_bus_add_device_AddCallback(nullptr); + g_dev_fixture = nullptr; + } + + spi_device_handle_t dev_handle; + spi_device_interface_config_t dev_config; + + static esp_err_t add_dev_cb(spi_host_device_t host_id, + const spi_device_interface_config_t* dev_config, + spi_device_handle_t* handle, + int cmock_num_calls) + { + SPIDevFix *fix = static_cast(g_dev_fixture); + *handle = fix->dev_handle; + fix->dev_config = *dev_config; + return ESP_OK; + } +}; + +struct SPITransactionFix { + SPITransactionFix(esp_err_t get_trans_return = ESP_OK) : get_transaction_return(get_trans_return) + { + spi_device_queue_trans_AddCallback(queue_trans_cb); + spi_device_get_trans_result_AddCallback(get_trans_result_cb); + + spi_device_queue_trans_ExpectAnyArgsAndReturn(ESP_OK); + spi_device_get_trans_result_ExpectAnyArgsAndReturn(get_trans_return); + + g_trans_fixture = this; + } + + ~SPITransactionFix() + { + spi_device_get_trans_result_AddCallback(nullptr); + spi_device_queue_trans_AddCallback(nullptr); + g_trans_fixture = nullptr; + } + + static esp_err_t queue_trans_cb(spi_device_handle_t handle, + spi_transaction_t* trans_desc, + TickType_t ticks_to_wait, + int cmock_num_calls) + { + SPITransactionFix *fix = static_cast (g_trans_fixture); + fix->orig_trans = trans_desc; + return ESP_OK; + } + + static esp_err_t get_trans_result_cb(spi_device_handle_t handle, + spi_transaction_t** trans_desc, + TickType_t ticks_to_wait, + int cmock_num_calls) + { + SPITransactionFix *fix = static_cast (g_trans_fixture); + + *trans_desc = fix->orig_trans; + + return fix->get_transaction_return; + } + + esp_err_t get_transaction_return; + spi_transaction_t *orig_trans; +}; + +struct SPITransactionDescriptorFix { + SPITransactionDescriptorFix(size_t size = 1, bool ignore_handle = false, TickType_t wait_time = portMAX_DELAY) + : size(size), handle(reinterpret_cast(0x01020304)) + { + spi_device_queue_trans_AddCallback(queue_trans_cb); + spi_device_get_trans_result_AddCallback(get_trans_result_cb); + + spi_device_acquire_bus_ExpectAndReturn(handle, portMAX_DELAY, ESP_OK); + if (ignore_handle) { + spi_device_acquire_bus_IgnoreArg_device(); + } + spi_device_queue_trans_ExpectAndReturn(handle, nullptr, 0, ESP_OK); + spi_device_queue_trans_IgnoreArg_trans_desc(); + if (ignore_handle) { + spi_device_queue_trans_IgnoreArg_handle(); + } + + spi_device_get_trans_result_ExpectAndReturn(handle, nullptr, wait_time, ESP_OK); + spi_device_get_trans_result_IgnoreArg_trans_desc(); + if (ignore_handle) { + spi_device_get_trans_result_IgnoreArg_handle(); + } + spi_device_release_bus_ExpectAnyArgs(); + + g_trans_desc_fixture = this; + } + + ~SPITransactionDescriptorFix() + { + spi_device_get_trans_result_AddCallback(nullptr); + spi_device_queue_trans_AddCallback(nullptr); + g_trans_desc_fixture = nullptr; + } + + static esp_err_t queue_trans_cb(spi_device_handle_t handle, + spi_transaction_t* trans_desc, + TickType_t ticks_to_wait, + int cmock_num_calls) + { + SPITransactionDescriptorFix *fix = static_cast (g_trans_desc_fixture); + fix->orig_trans = trans_desc; + return ESP_OK; + } + + static esp_err_t get_trans_result_cb(spi_device_handle_t handle, + spi_transaction_t** trans_desc, + TickType_t ticks_to_wait, + int cmock_num_calls) + { + SPITransactionDescriptorFix *fix = static_cast (g_trans_desc_fixture); + + for (int i = 0; i < fix->size; i++) { + static_cast(fix->orig_trans->rx_buffer)[i] = fix->rx_data[i]; + } + + *trans_desc = fix->orig_trans; + + return ESP_OK; + } + + size_t size; + spi_transaction_t *orig_trans; + spi_device_handle_t handle; + std::vector tx_data; + std::vector rx_data; +}; 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 index dd6a9a084a..1078a3e9cd 100644 --- 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 @@ -10,6 +10,7 @@ #include #include "esp_err.h" +#include "unity.h" #include "freertos/portmacro.h" #include "gpio_cxx.hpp" #include "test_fixtures.hpp" diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/spi/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/CMakeLists.txt new file mode 100644 index 0000000000..7f29a489ea --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/CMakeLists.txt @@ -0,0 +1,16 @@ +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) + +# Overriding components which should be mocked +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/") +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/") +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/") + +# Including experimental component here because it's outside IDF's main component directory +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/") + +project(test_spi_cxx_host) diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/spi/README.md b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/README.md new file mode 100644 index 0000000000..3b6179a6e4 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/README.md @@ -0,0 +1,8 @@ +| Supported Targets | Linux | +| ----------------- | ----- | + +# Build +`idf.py build` (sdkconfig.defaults sets the linux target by default) + +# Run +`build/host_spi_cxx_test.elf` diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/spi/main/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/main/CMakeLists.txt new file mode 100644 index 0000000000..fa465d396a --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/main/CMakeLists.txt @@ -0,0 +1,9 @@ +idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR) + +idf_component_register(SRCS "spi_cxx_test.cpp" + INCLUDE_DIRS + "." + "${cpp_component}/host_test/fixtures" + "${cpp_component}/private_include" + $ENV{IDF_PATH}/tools/catch + REQUIRES cmock driver experimental_cpp_component) diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/spi/main/spi_cxx_test.cpp b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/main/spi_cxx_test.cpp new file mode 100644 index 0000000000..a6e11b563a --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/main/spi_cxx_test.cpp @@ -0,0 +1,452 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0 + * + * This test 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 "unity.h" +#include "freertos/portmacro.h" +#include "spi_host_cxx.hpp" +#include "spi_host_private_cxx.hpp" +#include "system_cxx.hpp" +#include "test_fixtures.hpp" + +#include "catch.hpp" + +// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead +const char *esp_err_to_name(esp_err_t code) { + return "host_test error"; +} + +using namespace std; +using namespace idf; + +TEST_CASE("SPITransferSize basic construction") +{ + SPITransferSize transfer_size_0(0); + CHECK(0 == transfer_size_0.get_value()); + SPITransferSize transfer_size_1(47); + CHECK(47 == transfer_size_1.get_value()); + SPITransferSize transfer_size_default = SPITransferSize::default_size(); + CHECK(0 == transfer_size_default.get_value()); +} + +TEST_CASE("SPI gpio numbers work correctly") +{ + GPIONum gpio_num_0(19); + MOSI mosi_0(18); + MOSI mosi_1(gpio_num_0.get_num()); + MOSI mosi_2(mosi_0); + CHECK(mosi_0 != mosi_1); + CHECK(mosi_2 == mosi_0); + CHECK(mosi_2.get_num() == 18u); +} + +TEST_CASE("SPI_DMAConfig valid") +{ + CHECK(SPI_DMAConfig::AUTO().get_num() == spi_common_dma_t::SPI_DMA_CH_AUTO); + CHECK(SPI_DMAConfig::DISABLED().get_num() == spi_common_dma_t::SPI_DMA_DISABLED); +} + +TEST_CASE("SPINum invalid argument") +{ + CHECK_THROWS_AS(SPINum(-1), SPIException&); + uint32_t host_raw = spi_host_device_t::SPI_HOST_MAX; + CHECK_THROWS_AS(SPINum host(host_raw), SPIException&); +} + +TEST_CASE("Master init failure") +{ + CMockFixture cmock_fix; + spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_FAIL); + + CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&); +} + +TEST_CASE("Master invalid state") +{ + CMockFixture cmock_fix; + spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE); + + CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&); +} + +TEST_CASE("build master") +{ + SPIFix fix; + + SPIMaster master(SPINum(SPI2_HOST), + MOSI(fix.bus_config.mosi_io_num), + MISO(fix.bus_config.miso_io_num), + SCLK(fix.bus_config.sclk_io_num)); +} + +TEST_CASE("build QSPI master") +{ + QSPIFix fix; + + SPIMaster master(SPINum(SPI2_HOST), + MOSI(fix.bus_config.mosi_io_num), + MISO(fix.bus_config.miso_io_num), + SCLK(fix.bus_config.sclk_io_num), + QSPIWP(fix.bus_config.quadwp_io_num), + QSPIHD(fix.bus_config.quadhd_io_num)); +} + +TEST_CASE("Master build device") +{ + SPIFix fix; + SPIDevFix dev_fix(CreateAnd::SUCCEED); + + SPIMaster master(SPINum(SPI2_HOST), + MOSI(fix.bus_config.mosi_io_num), + MISO(fix.bus_config.miso_io_num), + SCLK(fix.bus_config.sclk_io_num)); + + master.create_dev(CS(4), Frequency::MHz(1)); +} + +TEST_CASE("SPIDeviceHandle throws on driver error") +{ + CMockFixture cmock_fix; + SPIDevFix dev_fix(CreateAnd::FAIL); + CHECK_THROWS_AS(SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)), SPIException&); +} + +TEST_CASE("SPIDeviceHandle succeed") +{ + CMockFixture cmock_fix; + SPIDevFix dev_fix(CreateAnd::SUCCEED); + SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); +} + +TEST_CASE("SPIDevice succeed") +{ + CMockFixture cmock_fix; + SPIDevFix dev_fix(CreateAnd::SUCCEED); + SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); +} + +TEST_CASE("SPI transaction empty data throws") +{ + CHECK_THROWS_AS(SPITransactionDescriptor transaction({}, reinterpret_cast(4747)), SPIException&); +} + +TEST_CASE("SPI transaction device handle nullptr throws") +{ + CHECK_THROWS_AS(SPITransactionDescriptor transaction({47}, nullptr), SPIException&); +} + +TEST_CASE("SPI transaction not started wait_for") +{ + CMockFixture cmock_fix; + SPIDevFix dev_fix(CreateAnd::IGNORE); + SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + SPITransactionDescriptor transaction({47}, &handle); + + CHECK_THROWS_AS(transaction.wait_for(std::chrono::milliseconds(47)), SPITransferException&); +} + +TEST_CASE("SPI transaction not started wait") +{ + CMockFixture cmock_fix; + SPIDevFix dev_fix(CreateAnd::IGNORE); + SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + SPITransactionDescriptor transaction({47}, &handle); + + CHECK_THROWS_AS(transaction.wait(), SPITransferException&); +} + +TEST_CASE("SPI transaction not started get") +{ + CMockFixture cmock_fix; + SPIDevFix dev_fix(CreateAnd::IGNORE); + SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + SPITransactionDescriptor transaction({47}, &handle); + + CHECK_THROWS_AS(transaction.get(), SPITransferException&); +} + +TEST_CASE("SPI transaction wait_for timeout") +{ + CMockFixture cmock_fix; + SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT); + SPIDevFix dev_fix(CreateAnd::IGNORE); + spi_device_acquire_bus_IgnoreAndReturn(ESP_OK); + spi_device_release_bus_Ignore(); + + SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + SPITransactionDescriptor transaction({47}, &handle); + transaction.start(); + + CHECK(transaction.wait_for(std::chrono::milliseconds(47)) == false); + + // We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the + // allocated transaction descriptor. + transaction_fix.get_transaction_return = ESP_OK; + spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK); + transaction.wait(); +} + +TEST_CASE("SPI transaction one byte") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix fix(1, true); + SPIDevFix dev_fix(CreateAnd::IGNORE); + fix.rx_data = {0xA6}; + + SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + SPITransactionDescriptor transaction({47}, &handle); + + transaction.start(); + auto out_data = transaction.get(); + + CHECK(1 * 8 == fix.orig_trans->length); + CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]); + REQUIRE(out_data.begin() != out_data.end()); + CHECK(0xA6 == out_data[0]); +} + +TEST_CASE("SPI transaction two byte") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix fix(2, true); + SPIDevFix dev_fix(CreateAnd::IGNORE); + fix.rx_data = {0xA6, 0xA7}; + + SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + SPITransactionDescriptor transaction({47, 48}, &handle); + transaction.start(); + auto out_data = transaction.get(); + + CHECK(fix.size * 8 == fix.orig_trans->length); + CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]); + CHECK(48 == ((uint8_t*) fix.orig_trans->tx_buffer)[1]); + REQUIRE(out_data.begin() != out_data.end()); + REQUIRE(out_data.size() == 2); + CHECK(0xA6 == out_data[0]); + CHECK(0xA7 == out_data[1]); +} + +TEST_CASE("SPI transaction future") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix trans_fix(1, true); + trans_fix.rx_data = {0xA6}; + SPIDevFix dev_fix(CreateAnd::IGNORE); + + SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + auto result = dev.transfer({47}); + vector out_data = result.get(); + + CHECK(1 * 8 == trans_fix.orig_trans->length); + CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]); + REQUIRE(out_data.begin() != out_data.end()); + CHECK(out_data.size() == 1); + CHECK(0xA6 == out_data[0]); +} + +TEST_CASE("SPI transaction with pre_callback") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix trans_fix(1, true); + trans_fix.rx_data = {0xA6}; + SPIDevFix dev_fix(CreateAnd::SUCCEED); + bool pre_cb_called = false; + + SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + auto result = dev.transfer({47}, [&] (void *user) { pre_cb_called = true; }); + vector out_data = result.get(); + + SPITransactionDescriptor *transaction = reinterpret_cast(trans_fix.orig_trans->user); + dev_fix.dev_config.pre_cb(trans_fix.orig_trans); + CHECK(true == pre_cb_called); + CHECK(1 * 8 == trans_fix.orig_trans->length); + CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]); + REQUIRE(out_data.begin() != out_data.end()); + CHECK(out_data.size() == 1); + CHECK(0xA6 == out_data[0]); +} + +TEST_CASE("SPI transaction with post_callback") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix trans_fix(1, true); + trans_fix.rx_data = {0xA6}; + SPIDevFix dev_fix(CreateAnd::SUCCEED); + bool post_cb_called = false; + SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + + auto result = dev.transfer({47}, [&] (void *user) { }, [&] (void *user) { post_cb_called = true; }); + vector out_data = result.get(); + + dev_fix.dev_config.post_cb(trans_fix.orig_trans); + CHECK(true == post_cb_called); + CHECK(1 * 8 == trans_fix.orig_trans->length); + CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]); + REQUIRE(out_data.begin() != out_data.end()); + CHECK(out_data.size() == 1); + CHECK(0xA6 == out_data[0]); +} + +TEST_CASE("SPI transaction data routed to pre callback") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix trans_fix(1, true); + trans_fix.rx_data = {0xA6}; + SPIDevFix dev_fix(CreateAnd::SUCCEED); + bool pre_cb_called = false; + SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + + auto result = dev.transfer({47}, + [&] (void *user) { *(static_cast(user)) = true; }, + [&] (void *user) { }, + &pre_cb_called); + result.get(); + + dev_fix.dev_config.pre_cb(trans_fix.orig_trans); + CHECK(true == pre_cb_called); + +} + +TEST_CASE("SPI transaction data routed to post callback") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix trans_fix(1, true); + trans_fix.rx_data = {0xA6}; + SPIDevFix dev_fix(CreateAnd::SUCCEED); + bool post_cb_called = false; + SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + + auto result = dev.transfer({47}, + [&] (void *user) { }, + [&] (void *user) { *(static_cast(user)) = true; }, + &post_cb_called); + result.get(); + + dev_fix.dev_config.post_cb(trans_fix.orig_trans); + CHECK(true == post_cb_called); +} + +TEST_CASE("SPI two transactions") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix trans_fix(1, true); + trans_fix.rx_data = {0xA6}; + SPIDevFix dev_fix(CreateAnd::SUCCEED); + bool pre_cb_called = false; + SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + std::function pre_callback = [&] (void *user) { + pre_cb_called = true; + }; + + auto result = dev.transfer({47}, pre_callback); + vector out_data = result.get(); + + dev_fix.dev_config.pre_cb(trans_fix.orig_trans); + CHECK(true == pre_cb_called); + + CHECK(1 * 8 == trans_fix.orig_trans->length); + CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]); + + REQUIRE(out_data.begin() != out_data.end()); + CHECK(out_data.size() == 1); + CHECK(0xA6 == out_data[0]); + + // preparing the second transfer + pre_cb_called = false; + spi_device_acquire_bus_ExpectAndReturn(trans_fix.handle, portMAX_DELAY, ESP_OK); + spi_device_acquire_bus_IgnoreArg_device(); + spi_device_queue_trans_ExpectAndReturn(trans_fix.handle, nullptr, 0, ESP_OK); + spi_device_queue_trans_IgnoreArg_trans_desc(); + spi_device_queue_trans_IgnoreArg_handle(); + spi_device_get_trans_result_ExpectAndReturn(trans_fix.handle, nullptr, portMAX_DELAY, ESP_OK); + spi_device_get_trans_result_IgnoreArg_trans_desc(); + spi_device_get_trans_result_IgnoreArg_handle(); + spi_device_release_bus_Ignore(); + + + result = dev.transfer({47}, pre_callback); + result.get(); + + dev_fix.dev_config.pre_cb(trans_fix.orig_trans); + CHECK(true == pre_cb_called); +} + +TEST_CASE("SPIFuture invalid after default construction") +{ + SPIFuture future; + CHECK(false == future.valid()); +} + +TEST_CASE("SPIFuture valid") +{ + CMOCK_SETUP(); + SPIDevFix dev_fix(CreateAnd::IGNORE); + + SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + shared_ptr trans(new SPITransactionDescriptor(std::vector(47), &handle)); + SPIFuture future(trans); + + CHECK(true == future.valid()); +} + +TEST_CASE("SPIFuture wait_for timeout") +{ + CMockFixture cmock_fix; + SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT); + SPIDevFix dev_fix(CreateAnd::IGNORE); + spi_device_acquire_bus_IgnoreAndReturn(ESP_OK); + spi_device_release_bus_Ignore(); + + SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + shared_ptr transaction(new SPITransactionDescriptor(std::vector(47), &handle)); + SPIFuture future(transaction); + transaction->start(); + + CHECK(future.wait_for(std::chrono::milliseconds(47)) == std::future_status::timeout); + + // We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the + // allocated transaction descriptor. + transaction_fix.get_transaction_return = ESP_OK; + spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK); + + future.wait(); +} + +TEST_CASE("SPIFuture wait_for on SPIFuture") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix trans_fix(1, true, 20); + trans_fix.rx_data = {0xA6}; + SPIDevFix dev_fix(CreateAnd::IGNORE); + + SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + auto result = dev.transfer({47}); + + CHECK(result.wait_for(std::chrono::milliseconds(20)) == std::future_status::ready); +} + +TEST_CASE("SPIFuture wait on SPIFuture") +{ + CMockFixture cmock_fix; + SPITransactionDescriptorFix trans_fix(1, true); + trans_fix.rx_data = {0xA6}; + SPIDevFix dev_fix(CreateAnd::IGNORE); + + SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)); + auto result = dev.transfer({47}); + + result.wait(); + + vector out_data = result.get(); + CHECK(out_data.size() == 1); +} diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/spi/sdkconfig.defaults b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/sdkconfig.defaults new file mode 100644 index 0000000000..a057733348 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/spi/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/host_test/system/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/host_test/system/CMakeLists.txt new file mode 100644 index 0000000000..876a4b8f1a --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/system/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) + +idf_component_set_property(driver USE_MOCK 1) + +# Overriding components which should be mocked +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/") +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/") + +# Including experimental component here because it's outside IDF's main component directory +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/") +project(test_system_cxx_host) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" + COMMAND lcov --capture --directory . --output-file coverage.info + COMMENT "Create coverage report" + ) + +add_custom_command( + OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage_report/" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" + COMMAND genhtml coverage.info --output-directory coverage_report/ + COMMENT "Turn coverage report into html-based visualization" + ) + +add_custom_target(coverage + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build" + DEPENDS "coverage_report/" + ) diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/system/README.md b/examples/cxx/experimental/experimental_cpp_component/host_test/system/README.md new file mode 100644 index 0000000000..6d04729934 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/system/README.md @@ -0,0 +1,8 @@ +| Supported Targets | Linux | +| ----------------- | ----- | + +# Build +`idf.py build` (sdkconfig.defaults sets the linux target by default) + +# Run +`build/system_cxx_host_test.elf` diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/system/main/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/host_test/system/main/CMakeLists.txt new file mode 100644 index 0000000000..107218154c --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/system/main/CMakeLists.txt @@ -0,0 +1,12 @@ +idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR) + +idf_component_register(SRCS "system_cxx_test.cpp" + "${cpp_component}/esp_exception.cpp" + INCLUDE_DIRS + "." + "${cpp_component}/include" + $ENV{IDF_PATH}/tools/catch + REQUIRES driver) + +target_compile_options(${COMPONENT_LIB} PUBLIC --coverage) +target_link_libraries(${COMPONENT_LIB} --coverage) diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/system/main/system_cxx_test.cpp b/examples/cxx/experimental/experimental_cpp_component/host_test/system/main/system_cxx_test.cpp new file mode 100644 index 0000000000..958a3bcee3 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/system/main/system_cxx_test.cpp @@ -0,0 +1,95 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0 + * + * This test 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 "catch.hpp" +#include "system_cxx.hpp" + +// 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("Frequency invalid") +{ + CHECK_THROWS_AS(Frequency(0), ESPException&); +} + +TEST_CASE("Frequency constructors correct") +{ + Frequency f0(440); + CHECK(440 == f0.get_value()); + Frequency f1 = Frequency::Hz(440); + CHECK(440 == f1.get_value()); + Frequency f2 = Frequency::KHz(440); + CHECK(440000 == f2.get_value()); + Frequency f3 = Frequency::MHz(440); + CHECK(440000000 == f3.get_value()); +} + +TEST_CASE("Frequency op ==") +{ + Frequency f0(440); + Frequency f1(440); + CHECK(f1 == f0); +} + +TEST_CASE("Frequency op !=") +{ + Frequency f0(440); + Frequency f1(441); + CHECK(f1 != f0); +} + +TEST_CASE("Frequency op >") +{ + Frequency f0(440); + Frequency f1(441); + Frequency f2(440); + CHECK(f1 > f0); + CHECK(!(f0 > f1)); + CHECK(!(f0 > f2)); +} + +TEST_CASE("Frequency op <") +{ + Frequency f0(440); + Frequency f1(441); + Frequency f2(440); + CHECK(f0 < f1); + CHECK(!(f1 < f0)); + CHECK(!(f0 < f2)); +} + +TEST_CASE("Frequency op >=") +{ + Frequency f0(440); + Frequency f1(441); + Frequency f2(440); + CHECK (f1 >= f0); + CHECK(!(f0 >= f1)); + CHECK (f0 >= f2); +} + +TEST_CASE("Frequency op <=") +{ + Frequency f0(440); + Frequency f1(441); + Frequency f2(440); + CHECK (f0 <= f1); + CHECK(!(f1 <= f0)); + CHECK (f0 <= f2); +} diff --git a/examples/cxx/experimental/experimental_cpp_component/host_test/system/sdkconfig.defaults b/examples/cxx/experimental/experimental_cpp_component/host_test/system/sdkconfig.defaults new file mode 100644 index 0000000000..a057733348 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/host_test/system/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/esp_exception.hpp b/examples/cxx/experimental/experimental_cpp_component/include/esp_exception.hpp index feb3cc7e1c..19f2f297f3 100644 --- a/examples/cxx/experimental/experimental_cpp_component/include/esp_exception.hpp +++ b/examples/cxx/experimental/experimental_cpp_component/include/esp_exception.hpp @@ -1,16 +1,8 @@ -// Copyright 2019 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. +/* + * SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once @@ -65,7 +57,7 @@ public: */ #define CHECK_THROW_SPECIFIC(error_, exception_type_) \ do { \ - esp_err_t result = error_; \ + esp_err_t result = (error_); \ if (result != ESP_OK) throw idf::exception_type_(result); \ } while (0) diff --git a/examples/cxx/experimental/experimental_cpp_component/include/spi_cxx.hpp b/examples/cxx/experimental/experimental_cpp_component/include/spi_cxx.hpp new file mode 100644 index 0000000000..4ff48f9fdb --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/include/spi_cxx.hpp @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#if __cpp_exceptions + +#include "esp_exception.hpp" +#include "gpio_cxx.hpp" +#include "system_cxx.hpp" + +namespace idf { + +/** + * @brief Exception which is thrown in the context of SPI C++ classes. + */ +struct SPIException : public ESPException { + SPIException(esp_err_t error); +}; + +/** + * @brief The maximum SPI transfer size in bytes. + */ +class SPITransferSize : public StrongValueOrdered { +public: + /** + * @brief Create a valid SPI transfer size. + * + * @param transfer_size The raw transfer size in bytes. + */ + explicit SPITransferSize(size_t transfer_size) noexcept : StrongValueOrdered(transfer_size) { } + + static SPITransferSize default_size() { + return SPITransferSize(0); + } +}; + +/** + * @brief Check if the raw uint32_t spi number is in the range according to the hardware. + */ +esp_err_t check_spi_num(uint32_t spi_num) noexcept; + +/** + * @brief Represents a valid SPI host number. + * + * ESP chips may have different independent SPI peripherals. This SPI number distinguishes between them. + */ +class SPINum : public StrongValueComparable { +public: + /** + * @brief Create a valid SPI host number. + * + * @param host_id_raw The raw SPI host number. + * + * @throw SPIException if the passed SPI host number is incorrect. + */ + SPINum(uint32_t host_id_raw) : StrongValueComparable(host_id_raw) + { + esp_err_t spi_num_check_result = check_spi_num(host_id_raw); + if (spi_num_check_result != ESP_OK) { + throw SPIException(spi_num_check_result); + } + } + + /** + * @brief Return the raw value of the SPI host. + * + * This should only be used when calling driver and other interfaces which don't support the C++ class. + * + * @return the raw value of the SPI host. + */ + uint32_t get_spi_num() const + { + return get_value(); + } +}; + +/** + * @brief Represents a valid MOSI signal pin number. + */ +class MOSI_type; +using MOSI = GPIONumBase; + +/** + * @brief Represents a valid MISO signal pin number. + */ +class MISO_type; +using MISO = GPIONumBase; + +/** + * @brief Represents a valid SCLK signal pin number. + */ +class SCLK_type; +using SCLK = GPIONumBase; + +/** + * @brief Represents a valid CS (chip select) signal pin number. + */ +class CS_type; +using CS = GPIONumBase; + +/** + * @brief Represents a valid QSPIWP signal pin number. + */ +class QSPIWP_type; +using QSPIWP = GPIONumBase; + +/** + * @brief Represents a valid QSPIHD signal pin number. + */ +class QSPIHD_type; +using QSPIHD = GPIONumBase; + +/** + * @brief Represents a valid SPI DMA configuration. Use it similar to an enum. + */ +class SPI_DMAConfig : public StrongValueComparable { + /** + * Constructor is hidden to enforce object invariants. + * Use the static creation methods to create instances. + */ + explicit SPI_DMAConfig(uint32_t channel_num) : StrongValueComparable(channel_num) { } + +public: + /** + * @brief Create a configuration with DMA disabled. + */ + static SPI_DMAConfig DISABLED(); + + /** + * @brief Create a configuration where the driver allocates DMA. + */ + static SPI_DMAConfig AUTO(); + + /** + * @brief Return the raw value of the DMA configuration. + * + * This should only be used when calling driver and other interfaces which don't support the C++ class. + * + * @return the raw value of the DMA configuration. + */ + uint32_t get_num() const { + return get_value(); + } +}; + +} + +#endif diff --git a/examples/cxx/experimental/experimental_cpp_component/include/spi_host_cxx.hpp b/examples/cxx/experimental/experimental_cpp_component/include/spi_host_cxx.hpp new file mode 100644 index 0000000000..57a194a24e --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/include/spi_host_cxx.hpp @@ -0,0 +1,414 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#if __cpp_exceptions + +#include +#include +#include +#include +#include +#include + +#include "system_cxx.hpp" +#include "spi_cxx.hpp" + +namespace idf { + +/** + * @brief Exception which is thrown in the context of SPI Transactions. + */ +struct SPITransferException : public SPIException { + SPITransferException(esp_err_t error); +}; + +class SPIDevice; +class SPIDeviceHandle; + +/** + * @brief Describes and encapsulates the transaction. + * + * @note This class is intended to be used internally by the SPI C++ classes, but not publicly. + * Furthermore, currently only one transaction per time can be handled. If you need to + * send several transactions in parallel, you need to build your own mechanism around a + * FreeRTOS task and a queue. + */ +class SPITransactionDescriptor { + friend class SPIDeviceHandle; +public: + /** + * @brief Create a SPITransactionDescriptor object, describing a full duplex transaction. + * + * @param data_to_send The data sent to the SPI device. It can be dummy data if a read-only + * transaction is intended. Its length determines the length of both write and read operation. + * @param handle to the internal driver handle + * @param pre_callback If non-empty, this callback will be called directly before the transaction. + * @param post_callback If non-empty, this callback will be called directly after the transaction. + * @param user_data optional data which will be accessible in the callbacks declared above + */ + SPITransactionDescriptor(const std::vector &data_to_send, + SPIDeviceHandle *handle, + std::function pre_callback = nullptr, + std::function post_callback = nullptr, + void* user_data = nullptr); + + /** + * @brief Deinitialize and delete all data of the transaction. + * + * @note This destructor must not becalled before the transaction is finished by the driver. + */ + ~SPITransactionDescriptor(); + + SPITransactionDescriptor(const SPITransactionDescriptor&) = delete; + SPITransactionDescriptor operator=(const SPITransactionDescriptor&) = delete; + + /** + * @brief Queue the transaction asynchronously. + */ + void start(); + + /** + * @brief Synchronously (blocking) wait for the result and return the result data or throw an exception. + * + * @return The data read from the SPI device. Its length is the length of \c data_to_send passed in the + * constructor. + * @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong + * transaction descriptor for some reason. In the former case, the error code is the one from the + * underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE. + */ + std::vector get(); + + /** + * @brief Wait until the asynchronous operation is done. + * + * @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong + * transaction descriptor for some reason. In the former case, the error code is the one from the + * underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE. + */ + void wait(); + + + /** + * @brief Wait for a result of the transaction up to timeout ms. + * + * @param timeout Maximum timeout value for waiting + * + * @return true if result is available, false if wait timed out + * + * @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong + * transaction descriptor for some reason. In the former case, the error code is the one from the + * underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE. + */ + bool wait_for(const std::chrono::milliseconds &timeout); + +private: + /** + * Private descriptor data. + */ + void *private_transaction_desc; + + /** + * Private device data. + */ + SPIDeviceHandle *device_handle; + + /** + * @brief If non-empty, this callback will be called directly before the transaction. + */ + std::function pre_callback; + + /** + * @brief If non-empty, this callback will be called directly after the transaction. + */ + std::function post_callback; + + /** + * Buffer in spi_transaction_t is const, so we have to declare it here because we want to + * allocate and delete it. + */ + uint8_t *tx_buffer; + + /** + * @brief User data which will be provided in the callbacks. + */ + void *user_data; + + /** + * Tells if data has been received, i.e. the transaction has finished and the result can be acquired. + */ + bool received_data; + + /** + * Tells if the transaction has been initiated and is at least in-flight, if not finished. + */ + bool started; +}; + +/** + * @brief SPIFuture for asynchronous transaction results, mostly equivalent to std::future. + * + * This re-implementation is necessary as std::future is incompatible with the IDF SPI driver interface. + */ +class SPIFuture { +public: + /** + * @brief Create an invalid future. + */ + SPIFuture(); + + /** + * @brief Create a valid future with \c transaction as shared state. + * + * @param transaction the shared transaction state + */ + SPIFuture(std::shared_ptr transaction); + + SPIFuture(const SPIFuture &other) = delete; + + /** + * @brief Move constructor as in std::future, leaves \c other invalid. + * + * @param other object to move from, will become invalid during this constructor + */ + SPIFuture(SPIFuture &&other) noexcept; + + /** + * @brief Move assignment as in std::future, leaves \c other invalid. + * + * @param other object to move from, will become invalid during this constructor + * @return A reference to the newly created SPIFuture object + */ + SPIFuture &operator=(SPIFuture&& other) noexcept; + + /** + * @brief Wait until the asynchronous operation is done and return the result or throw and exception. + * + * @throws std::future_error if this future is not valid. + * @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong + * transaction descriptor for some reason. In the former case, the error code is the one from the + * underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE. + * @return The result of the asynchronous SPI transaction. + */ + std::vector get(); + + /** + * @brief Wait for a result up to timeout ms. + * + * @param timeout Maximum timeout value for waiting + * + * @return std::future_status::ready if result is available, std::future_status::timeout if wait timed out + */ + std::future_status wait_for(std::chrono::milliseconds timeout); + + /** + * @brief Wait for a result indefinitely. + */ + void wait(); + + /** + * @return true if this future is valid, otherwise false. + */ + bool valid() const noexcept; + +private: + /** + * The SPITransactionDescriptor, which is the shared state of this future. + */ + std::shared_ptr transaction; + + /** + * Indicates if this future is valid. + */ + bool is_valid; +}; + +/** + * @brief Represents an device on an initialized Master Bus. + */ +class SPIDevice { +public: + /** + * @brief Create and initialize a device on the master bus corresponding to spi_host. + * + * @param cs The pin number of the chip select signal for the device to create. + * @param spi_host the spi_host (bus) to which the device shall be attached. + * @param frequency The devices frequency. this frequency will be set during transactions to the device which will be + * created. + * @param transaction_queue_size The of the transaction queue of this device. This determines how many + * transactions can be queued at the same time. Currently, it is set to 1 since the + * implementation exclusively acquires the bus for each transaction. This may change in the future. + */ + SPIDevice(SPINum spi_host, + CS cs, + Frequency frequency = Frequency::MHz(1), + QueueSize transaction_queue_size = QueueSize(1u)); + + SPIDevice(const SPIDevice&) = delete; + SPIDevice operator=(const SPIDevice&) = delete; + + /** + * @brief De-initializes and destroys the device. + * + * @warning Behavior is undefined if a device is destroyed while there is still an ongoing transaction + * from that device. + */ + ~SPIDevice(); + + /** + * @brief Queue a transfer to this device. + * + * This method creates a full-duplex transfer to the device represented by the current instance of this class. + * It then queues that transfer and returns a "future" object. The future object will become ready once + * the transfer finishes. + * + * @param data_to_send Data which will be sent to the device. The length of the data determines the length + * of the full-deplex transfer. I.e., the same amount of bytes will be received from the device. + * @param pre_callback If non-empty, this callback will be called directly before the transaction. + * If empty, it will be ignored. + * @param post_callback If non-empty, this callback will be called directly after the transaction. + * If empty, it will be ignored. + * @param user_data This pointer will be sent to pre_callback and/or pre_callback, if any of them is non-empty. + * + * @return a future object which will become ready once the transfer has finished. See also \c SPIFuture. + */ + SPIFuture transfer(const std::vector &data_to_send, + std::function pre_callback = nullptr, + std::function post_callback = nullptr, + void* user_data = nullptr); + + /** + * @brief Queue a transfer to this device like \c transfer, but using begin/end iterators instead of a + * data vector. + * + * This method is equivalent to \c transfer(), except for the parameters. + * + * @param begin Iterator to the begin of the data which will be sent to the device. + * @param end Iterator to the end of the data which will be sent to the device. + * This iterator determines the length of the data and hence the length of the full-deplex transfer. + * I.e., the same amount of bytes will be received from the device. + * @param pre_callback If non-empty, this callback will be called directly before the transaction. + * If empty, it will be ignored. + * @param post_callback If non-empty, this callback will be called directly after the transaction. + * If empty, it will be ignored. + * @param user_data This pointer will be sent to pre_callback and/or pre_callback, if any of them is non-empty. + * + * @return a future object which will become ready once the transfer has finished. See also \c SPIFuture. + */ + template + SPIFuture transfer(IteratorT begin, + IteratorT end, + std::function pre_callback = nullptr, + std::function post_callback = nullptr, + void* user_data = nullptr); + +private: + /** + * Private device data. + */ + SPIDeviceHandle *device_handle; + + /** + * Saves the current transaction descriptor in case the user's loses its future with the other + * reference to the transaction. + */ + std::shared_ptr current_transaction; +}; + +/** + * @brief Represents an SPI Master Bus. + */ +class SPIMaster { +public: + /* + * @brief Create an SPI Master Bus. + * + * @param host The SPI host (bus) which should be used. ESP chips have a number of different possible SPI hosts, + * each of which will create its own bus. Consult the datasheet and TRM on which host to choose. + * @param mosi The pin number for the MOSI signal of this bus. + * @param miso The pin number for the MISO signal of this bus. + * @param sclk The pin number for the clock signal of this bus. + * @param dma_config The DMA configuration for this bus, see \c DMAConfig. + * @param max_transfer_size The maximum transfer size in bytes. + * + * @throws SPIException with IDF error code if the underlying driver fails. + */ + explicit SPIMaster(SPINum host, + const MOSI &mosi, + const MISO &miso, + const SCLK &sclk, + SPI_DMAConfig dma_config = SPI_DMAConfig::AUTO(), + SPITransferSize max_transfer_size = SPITransferSize::default_size()); + + /* + * @brief Create an SPI Master Bus. + * + * @param host The SPI host (bus) which should be used. ESP chips have a number of different possible SPI hosts, + * each of which will create its own bus. Consult the datasheet and TRM on which host to choose. + * @param mosi The pin number for the MOSI signal of this bus. + * @param miso The pin number for the MISO signal of this bus. + * @param sclk The pin number for the clock signal of this bus. + * @param qspiwp The pin number for the QSPIWP signal of this bus. + * @param qspihd The pin number for the QSPIHD signal of this bus. + * @param dma_config The DMA configuration for this bus, see \c DMAConfig. + * @param max_transfer_size The maximum transfer size in bytes. + * + * @throws SPIException with IDF error code if the underlying driver fails. + */ + explicit SPIMaster(SPINum host, + const MOSI &mosi, + const MISO &miso, + const SCLK &sclk, + const QSPIWP &qspiwp, + const QSPIHD &qspihd, + SPI_DMAConfig dma_config = SPI_DMAConfig::AUTO(), + SPITransferSize max_transfer_size = SPITransferSize::default_size()); + + SPIMaster(const SPIMaster&) = delete; + SPIMaster operator=(const SPIMaster&) = delete; + + SPIMaster(SPIMaster&&) = default; + SPIMaster &operator=(SPIMaster&&) = default; + + /* + * @brief De-initializes and destroys the SPI Master Bus. + * + * @note Devices created before which try to initialize an exception after the bus is destroyed will throw + * and exception. + */ + virtual ~SPIMaster(); + + /** + * @brief Create a representation of a device on this bus. + * + * @param cs The pin number for the CS (chip select) signal to talk to the device. + * @param f The frequency used to talk to the device. + */ + std::shared_ptr create_dev(CS cs, Frequency frequency = Frequency::MHz(1)); + +private: + /** + * @brief Host identifier for internal use. + */ + SPINum spi_host; +}; + +template +SPIFuture SPIDevice::transfer(IteratorT begin, + IteratorT end, + std::function pre_callback, + std::function post_callback, + void* user_data) +{ + std::vector write_data; + write_data.assign(begin, end); + return transfer(write_data, pre_callback, post_callback, user_data); +} + +} + +#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 index 4879830840..bd229b201c 100644 --- a/examples/cxx/experimental/experimental_cpp_component/include/system_cxx.hpp +++ b/examples/cxx/experimental/experimental_cpp_component/include/system_cxx.hpp @@ -4,12 +4,19 @@ * SPDX-License-Identifier: Apache-2.0 */ +/** + * This file contains helper classes for commonly used IDF types. The classes make the use of these types easier and + * safer. + * In particular, their usage provides greater type-safety of function arguments and "correctness by construction". + */ #pragma once #ifndef __cpp_exceptions #error system C++ classes only usable when C++ exceptions enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig #endif +#include "esp_exception.hpp" + /** * 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. @@ -32,13 +39,14 @@ private: }; /** - * This class adds comparison properties to StrongValue, but no sorting properties. + * This class adds comparison properties to StrongValue, but no sorting and ordering properties. */ template class StrongValueComparable : public StrongValue { protected: StrongValueComparable(ValueT value_arg) : StrongValue(value_arg) { } +public: using StrongValue::get_value; bool operator==(const StrongValueComparable &other_gpio) const @@ -51,3 +59,87 @@ protected: return get_value() != other_gpio.get_value(); } }; + +namespace idf { + +/** + * This class adds ordering and sorting properties to StrongValue. + */ +template +class StrongValueOrdered : public StrongValueComparable { +public: + StrongValueOrdered(ValueT value) : StrongValueComparable(value) { } + + using StrongValueComparable::get_value; + + bool operator>(const StrongValueOrdered &other) const + { + return get_value() > other.get_value(); + } + + bool operator<(const StrongValueOrdered &other) const + { + return get_value() < other.get_value(); + } + + bool operator>=(const StrongValueOrdered &other) const + { + return get_value() >= other.get_value(); + } + + bool operator<=(const StrongValueOrdered &other) const + { + return get_value() <= other.get_value(); + } +}; + +/** + * A general frequency class to be used whereever an unbound frequency value is necessary. + */ +class Frequency : public StrongValueOrdered { +public: + explicit Frequency(size_t frequency) : StrongValueOrdered(frequency) + { + if (frequency == 0) { + throw ESPException(ESP_ERR_INVALID_ARG); + } + } + + Frequency(const Frequency&) = default; + Frequency &operator=(const Frequency&) = default; + + using StrongValueOrdered::get_value; + + static Frequency Hz(size_t frequency) + { + return Frequency(frequency); + } + + static Frequency KHz(size_t frequency) + { + return Frequency(frequency * 1000); + } + + static Frequency MHz(size_t frequency) + { + return Frequency(frequency * 1000 * 1000); + } +}; + +/** + * Queue size mainly for operating system queues. + */ +class QueueSize { +public: + explicit QueueSize(size_t q_size) : queue_size(q_size) { } + + size_t get_size() + { + return queue_size; + } + +private: + size_t queue_size; +}; + +} diff --git a/examples/cxx/experimental/experimental_cpp_component/private_include/spi_host_private_cxx.hpp b/examples/cxx/experimental/experimental_cpp_component/private_include/spi_host_private_cxx.hpp new file mode 100644 index 0000000000..253aaa8525 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/private_include/spi_host_private_cxx.hpp @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * The code in this file includes driver headers directly, hence it's a private include. + * It should only be used in C++ source files, while header files use forward declarations of the types. + * This way, public headers don't need to depend on (i.e. include) driver headers. + */ + +#ifdef __cpp_exceptions + +#include "hal/spi_types.h" +#include "driver/spi_master.h" + +using namespace std; + +namespace idf { + +#define SPI_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), SPIException) + +namespace { + +/** + * @brief Convenience method to convert a SPINum object into the driver type. Avoids long static casts. + */ +spi_host_device_t spi_num_to_driver_type(const SPINum &num) noexcept { + return static_cast(num.get_spi_num()); +} + +} + +/** + * This class wraps closely around the SPI master device driver functions. + * It is used to hide the implementation, in particular the dependencies on the driver and HAL layer headers. + * Public header files only use a pointer to this class which is forward declared in spi_host_cxx.hpp. + * Implementations (source files) can include this private header and use the class definitions. + * + * Furthermore, this class ensures RAII-capabilities of an SPI master device allocation and initiates pre- and + * post-transaction callback for each transfer. In constrast to the IDF driver, the callbacks are not per-device + * but per transaction in the C++ wrapper framework. + * + * For information on the public member functions, refer to the corresponding driver functions in spi_master.h + */ +class SPIDeviceHandle { +public: + /** + * Create a device instance on the SPI bus identified by spi_host, allocate all corresponding resources. + */ + SPIDeviceHandle(SPINum spi_host, CS cs, Frequency frequency, QueueSize q_size) + { + spi_device_interface_config_t dev_config = {}; + dev_config.clock_speed_hz = frequency.get_value(); + dev_config.spics_io_num = cs.get_num(); + dev_config.pre_cb = pr_cb; + dev_config.post_cb = post_cb; + dev_config.queue_size = q_size.get_size(); + SPI_CHECK_THROW(spi_bus_add_device(spi_num_to_driver_type(spi_host), &dev_config, &handle)); + } + + SPIDeviceHandle(const SPIDeviceHandle &other) = delete; + + SPIDeviceHandle(SPIDeviceHandle &&other) noexcept : handle(std::move(other.handle)) + { + // Only to indicate programming errors where users use an instance after moving it. + other.handle = nullptr; + } + + /** + * Remove device instance from the SPI bus, deallocate all corresponding resources. + */ + ~SPIDeviceHandle() + { + // We ignore the return value here. + // Only possible errors are wrong handle (impossible by object invariants) and + // handle already freed, which we can ignore. + spi_bus_remove_device(handle); + } + + SPIDeviceHandle &operator=(SPIDeviceHandle&& other) noexcept + { + if (this != &other) { + handle = std::move(other.handle); + + // Only to indicate programming errors where users use an instance after moving it. + other.handle = nullptr; + } + return *this; + } + + esp_err_t acquire_bus(TickType_t wait) + { + return spi_device_acquire_bus(handle, portMAX_DELAY); + } + + esp_err_t queue_trans(spi_transaction_t *trans_desc, TickType_t wait) + { + return spi_device_queue_trans(handle, trans_desc, wait); + } + + esp_err_t get_trans_result(spi_transaction_t **trans_desc, TickType_t ticks_to_wait) + { + return spi_device_get_trans_result(handle, trans_desc, ticks_to_wait); + } + + void release_bus() + { + spi_device_release_bus(handle); + } + +private: + /** + * Route the callback to the callback in the specific SPITransactionDescriptor instance. + */ + static void pr_cb(spi_transaction_t *driver_transaction) + { + SPITransactionDescriptor *transaction = static_cast(driver_transaction->user); + if (transaction->pre_callback) { + transaction->pre_callback(transaction->user_data); + } + } + + /** + * Route the callback to the callback in the specific SPITransactionDescriptor instance. + */ + static void post_cb(spi_transaction_t *driver_transaction) + { + SPITransactionDescriptor *transaction = static_cast(driver_transaction->user); + if (transaction->post_callback) { + transaction->post_callback(transaction->user_data); + } + } + + spi_device_handle_t handle; +}; + +} + +#endif diff --git a/examples/cxx/experimental/experimental_cpp_component/spi_cxx.cpp b/examples/cxx/experimental/experimental_cpp_component/spi_cxx.cpp new file mode 100644 index 0000000000..18f3e82dc7 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/spi_cxx.cpp @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if __cpp_exceptions + +#include "driver/spi_common.h" +#include "esp_exception.hpp" +#include "spi_cxx.hpp" + +namespace idf { + +esp_err_t check_spi_num(uint32_t spi_num) noexcept { + if (spi_num >= static_cast(SPI_HOST_MAX)) { + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +SPI_DMAConfig SPI_DMAConfig::DISABLED() { + return SPI_DMAConfig(static_cast(spi_common_dma_t::SPI_DMA_DISABLED)); +} + +SPI_DMAConfig SPI_DMAConfig::AUTO() { + return SPI_DMAConfig(static_cast(spi_common_dma_t::SPI_DMA_CH_AUTO)); +} + +} + +#endif diff --git a/examples/cxx/experimental/experimental_cpp_component/spi_host_cxx.cpp b/examples/cxx/experimental/experimental_cpp_component/spi_host_cxx.cpp new file mode 100644 index 0000000000..d635cd9f64 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/spi_host_cxx.cpp @@ -0,0 +1,267 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if __cpp_exceptions + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "hal/spi_types.h" +#include "driver/spi_master.h" +#include "spi_host_cxx.hpp" +#include "spi_host_private_cxx.hpp" + +using namespace std; + +namespace idf { + +SPIException::SPIException(esp_err_t error) : ESPException(error) { } + +SPITransferException::SPITransferException(esp_err_t error) : SPIException(error) { } + +SPIMaster::SPIMaster(SPINum host, + const MOSI &mosi, + const MISO &miso, + const SCLK &sclk, + SPI_DMAConfig dma_config, + SPITransferSize transfer_size) + : spi_host(host) +{ + spi_bus_config_t bus_config = {}; + bus_config.mosi_io_num = mosi.get_num(); + bus_config.miso_io_num = miso.get_num(); + bus_config.sclk_io_num = sclk.get_num(); + bus_config.quadwp_io_num = -1; + bus_config.quadhd_io_num = -1; + bus_config.max_transfer_sz = transfer_size.get_value(); + + SPI_CHECK_THROW(spi_bus_initialize(spi_num_to_driver_type(spi_host), &bus_config, dma_config.get_num())); +} + +SPIMaster::SPIMaster(SPINum host, + const MOSI &mosi, + const MISO &miso, + const SCLK &sclk, + const QSPIWP &qspiwp, + const QSPIHD &qspihd, + SPI_DMAConfig dma_config, + SPITransferSize transfer_size) + : spi_host(host) +{ + spi_bus_config_t bus_config = {}; + bus_config.mosi_io_num = mosi.get_num(); + bus_config.miso_io_num = miso.get_num(); + bus_config.sclk_io_num = sclk.get_num(); + bus_config.quadwp_io_num = qspiwp.get_num(); + bus_config.quadhd_io_num = qspihd.get_num(); + bus_config.max_transfer_sz = transfer_size.get_value(); + + SPI_CHECK_THROW(spi_bus_initialize(spi_num_to_driver_type(spi_host), &bus_config, dma_config.get_num())); +} + +SPIMaster::~SPIMaster() +{ + spi_bus_free(spi_num_to_driver_type(spi_host)); +} + +shared_ptr SPIMaster::create_dev(CS cs, Frequency frequency) +{ + return make_shared(spi_host, cs, frequency); +} + +SPIFuture::SPIFuture() + : transaction(), is_valid(false) +{ +} + +SPIFuture::SPIFuture(shared_ptr transaction) + : transaction(transaction), is_valid(true) +{ +} + +SPIFuture::SPIFuture(SPIFuture &&other) noexcept + : transaction(std::move(other.transaction)), is_valid(true) +{ + other.is_valid = false; +} + +SPIFuture &SPIFuture::operator=(SPIFuture &&other) noexcept +{ + if (this != &other) { + transaction = std::move(other.transaction); + is_valid = other.is_valid; + other.is_valid = false; + } + return *this; +} + +vector SPIFuture::get() +{ + if (!is_valid) { + throw std::future_error(future_errc::no_state); + } + + return transaction->get(); +} + +future_status SPIFuture::wait_for(chrono::milliseconds timeout) +{ + if (transaction->wait_for(timeout)) { + return std::future_status::ready; + } else { + return std::future_status::timeout; + } +} + +void SPIFuture::wait() +{ + transaction->wait(); +} + +bool SPIFuture::valid() const noexcept +{ + return is_valid; +} + +SPIDevice::SPIDevice(SPINum spi_host, CS cs, Frequency frequency, QueueSize q_size) : device_handle() +{ + device_handle = new SPIDeviceHandle(spi_host, cs, frequency, q_size); +} + +SPIDevice::~SPIDevice() +{ + delete device_handle; +} + +SPIFuture SPIDevice::transfer(const vector &data_to_send, + std::function pre_callback, + std::function post_callback, + void* user_data) +{ + current_transaction = make_shared(data_to_send, + device_handle, + std::move(pre_callback), + std::move(post_callback), + user_data); + current_transaction->start(); + return SPIFuture(current_transaction); +} + +SPITransactionDescriptor::SPITransactionDescriptor(const std::vector &data_to_send, + SPIDeviceHandle *handle, + std::function pre_callback, + std::function post_callback, + void* user_data_arg) + : device_handle(handle), + pre_callback(std::move(pre_callback)), + post_callback(std::move(post_callback)), + user_data(user_data_arg), + received_data(false), + started(false) +{ + // C++11 vectors don't have size() or empty() members yet + if (data_to_send.begin() == data_to_send.end()) { + throw SPITransferException(ESP_ERR_INVALID_ARG); + } + if (handle == nullptr) { + throw SPITransferException(ESP_ERR_INVALID_ARG); + } + + size_t trans_size = data_to_send.size(); + spi_transaction_t *trans_desc; + trans_desc = new spi_transaction_t; + memset(trans_desc, 0, sizeof(spi_transaction_t)); + trans_desc->rx_buffer = new uint8_t [trans_size]; + tx_buffer = new uint8_t [trans_size]; + for (size_t i = 0; i < trans_size; i++) { + tx_buffer[i] = data_to_send[i]; + } + trans_desc->length = trans_size * 8; + trans_desc->tx_buffer = tx_buffer; + trans_desc->user = this; + + private_transaction_desc = trans_desc; +} + +SPITransactionDescriptor::~SPITransactionDescriptor() +{ + if (started) { + assert(received_data); // We need to make sure that trans_desc has been received, otherwise the + // driver may still write into it afterwards. + } + + spi_transaction_t *trans_desc = reinterpret_cast(private_transaction_desc); + delete [] tx_buffer; + delete [] static_cast(trans_desc->rx_buffer); + delete trans_desc; +} + +void SPITransactionDescriptor::start() +{ + spi_transaction_t *trans_desc = reinterpret_cast(private_transaction_desc); + SPI_CHECK_THROW(device_handle->acquire_bus(portMAX_DELAY)); + SPI_CHECK_THROW(device_handle->queue_trans(trans_desc, 0)); + started = true; +} + +void SPITransactionDescriptor::wait() +{ + while (wait_for(chrono::milliseconds(portMAX_DELAY)) == false) { } +} + +bool SPITransactionDescriptor::wait_for(const chrono::milliseconds &timeout_duration) +{ + if (received_data) { + return true; + } + + if (!started) { + throw SPITransferException(ESP_ERR_INVALID_STATE); + } + + spi_transaction_t *acquired_trans_desc; + esp_err_t err = device_handle->get_trans_result(&acquired_trans_desc, + (TickType_t) timeout_duration.count() / portTICK_RATE_MS); + + if (err == ESP_ERR_TIMEOUT) { + return false; + } + + if (err != ESP_OK) { + throw SPITransferException(err); + } + + if (acquired_trans_desc != reinterpret_cast(private_transaction_desc)) { + throw SPITransferException(ESP_ERR_INVALID_STATE); + } + + received_data = true; + device_handle->release_bus(); + + return true; +} + +std::vector SPITransactionDescriptor::get() +{ + if (!received_data) { + wait(); + } + + spi_transaction_t *trans_desc = reinterpret_cast(private_transaction_desc); + const size_t TRANSACTION_LENGTH = trans_desc->length / 8; + vector result(TRANSACTION_LENGTH); + + for (int i = 0; i < TRANSACTION_LENGTH; i++) { + result[i] = static_cast(trans_desc->rx_buffer)[i]; + } + + return result; +} + +} // idf + +#endif // __cpp_exceptions diff --git a/examples/cxx/experimental/experimental_cpp_component/test/unity_cxx.hpp b/examples/cxx/experimental/experimental_cpp_component/test/unity_cxx.hpp index dfaf79ce63..a258f64a90 100644 --- a/examples/cxx/experimental/experimental_cpp_component/test/unity_cxx.hpp +++ b/examples/cxx/experimental/experimental_cpp_component/test/unity_cxx.hpp @@ -1,3 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + #pragma once #include "unity.h" diff --git a/examples/cxx/experimental/simple_spi_rw_example/CMakeLists.txt b/examples/cxx/experimental/simple_spi_rw_example/CMakeLists.txt new file mode 100644 index 0000000000..c826f53eef --- /dev/null +++ b/examples/cxx/experimental/simple_spi_rw_example/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(simple_spi_rw_example) diff --git a/examples/cxx/experimental/simple_spi_rw_example/README.md b/examples/cxx/experimental/simple_spi_rw_example/README.md new file mode 100644 index 0000000000..8549a27923 --- /dev/null +++ b/examples/cxx/experimental/simple_spi_rw_example/README.md @@ -0,0 +1,68 @@ +# Example: C++ SPI sensor read for MCU9250 inertial/giroscope sensor + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates usage of C++ SPI classes in ESP-IDF to read the `WHO_AM_I` register of the sensor. + +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++ SPI API. + +## How to use example + +### Hardware Required + +An MCU9250 sensor and any commonly available ESP32 development board. + +### 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 + +If the sensor is read correctly: + +``` +... +I (0) cpu_start: Starting scheduler on APP CPU. +Result of WHO_AM_I register: 0x71 +I (437) gpio: GPIO[23]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (447) gpio: GPIO[25]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (457) gpio: GPIO[26]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (467) gpio: GPIO[27]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 + +Done +``` + +If there's an error with the SPI peripheral: +``` +... +I (0) cpu_start: Starting scheduler on APP CPU. +E (434) spi: spicommon_bus_initialize_io(429): mosi not valid +Coulnd't read SPI! +``` + +If the SPI pins are not connected properly, the resulting read may just return 0, this error can not be detected: +``` +... +I (0) cpu_start: Starting scheduler on APP CPU. +Result of WHO_AM_I register: 0x00 +I (437) gpio: GPIO[23]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (447) gpio: GPIO[25]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (457) gpio: GPIO[26]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (467) gpio: GPIO[27]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +``` diff --git a/examples/cxx/experimental/simple_spi_rw_example/main/CMakeLists.txt b/examples/cxx/experimental/simple_spi_rw_example/main/CMakeLists.txt new file mode 100644 index 0000000000..b5339733ef --- /dev/null +++ b/examples/cxx/experimental/simple_spi_rw_example/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "simple_spi_rw_example.cpp" + INCLUDE_DIRS "." + REQUIRES experimental_cpp_component) diff --git a/examples/cxx/experimental/simple_spi_rw_example/main/component.mk b/examples/cxx/experimental/simple_spi_rw_example/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/cxx/experimental/simple_spi_rw_example/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/cxx/experimental/simple_spi_rw_example/main/simple_spi_rw_example.cpp b/examples/cxx/experimental/simple_spi_rw_example/main/simple_spi_rw_example.cpp new file mode 100644 index 0000000000..f63054e117 --- /dev/null +++ b/examples/cxx/experimental/simple_spi_rw_example/main/simple_spi_rw_example.cpp @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0 + * + * MPU9250 SPI Sensor 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 "spi_host_cxx.hpp" + +using namespace std; +using namespace idf; + +static const GPIONum NSS(23); +static const uint8_t READ_FLAG = 0x80; +static const uint8_t MPU9250_WHO_AM_I_REG_ADDR = 0x75; + +extern "C" void app_main(void) +{ + try { + + SPIMaster master(SPINum(2), + MOSI(25), + MISO(26), + SCLK(27)); + + shared_ptr spi_dev = master.create_dev(CS(NSS.get_num()), Frequency::MHz(1)); + + vector write_data = {MPU9250_WHO_AM_I_REG_ADDR | READ_FLAG, 0x00}; + vector result = spi_dev->transfer(write_data).get(); + + cout << "Result of WHO_AM_I register: 0x"; + printf("%02X", result[1]); + cout << endl; + + this_thread::sleep_for(std::chrono::seconds(2)); + + } catch (const SPIException &e) { + cout << "Coulnd't read SPI!" << endl; + } +} diff --git a/examples/cxx/experimental/simple_spi_rw_example/sdkconfig.defaults b/examples/cxx/experimental/simple_spi_rw_example/sdkconfig.defaults new file mode 100644 index 0000000000..a365ac6589 --- /dev/null +++ b/examples/cxx/experimental/simple_spi_rw_example/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/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 6234ba1179..3f07224b50 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -1583,7 +1583,6 @@ components/hal/include/hal/spi_flash_types.h components/hal/include/hal/spi_hal.h components/hal/include/hal/spi_slave_hal.h components/hal/include/hal/spi_slave_hd_hal.h -components/hal/include/hal/spi_types.h components/hal/include/hal/systimer_hal.h components/hal/include/hal/systimer_types.h components/hal/include/hal/touch_sensor_hal.h @@ -3356,18 +3355,15 @@ examples/cxx/experimental/experimental_cpp_component/esp_event_cxx.cpp examples/cxx/experimental/experimental_cpp_component/esp_exception.cpp examples/cxx/experimental/experimental_cpp_component/esp_timer_cxx.cpp examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/main/esp_timer_test.cpp -examples/cxx/experimental/experimental_cpp_component/host_test/fixtures/test_fixtures.hpp examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/gpio_cxx_test.cpp examples/cxx/experimental/experimental_cpp_component/i2c_cxx.cpp examples/cxx/experimental/experimental_cpp_component/include/esp_event_api.hpp examples/cxx/experimental/experimental_cpp_component/include/esp_event_cxx.hpp -examples/cxx/experimental/experimental_cpp_component/include/esp_exception.hpp examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp examples/cxx/experimental/experimental_cpp_component/test/test_cxx_exceptions.cpp examples/cxx/experimental/experimental_cpp_component/test/test_esp_event_cxx.cpp examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp examples/cxx/experimental/experimental_cpp_component/test/test_i2c.cpp -examples/cxx/experimental/experimental_cpp_component/test/unity_cxx.hpp examples/cxx/experimental/sensor_mcp9808/main/sensor_mcp9808.cpp examples/cxx/pthread/example_test.py examples/cxx/pthread/main/cpp_pthread.cpp diff --git a/tools/ci/check_examples_cmake_make-cmake_ignore.txt b/tools/ci/check_examples_cmake_make-cmake_ignore.txt index af00a26c0e..7bb9940a83 100644 --- a/tools/ci/check_examples_cmake_make-cmake_ignore.txt +++ b/tools/ci/check_examples_cmake_make-cmake_ignore.txt @@ -8,3 +8,4 @@ mb_example_common/ examples/cxx/experimental/blink_cxx examples/peripherals/lcd/lvgl examples/peripherals/i2s/i2s_es8311 +examples/cxx/experimental/simple_spi_rw_example diff --git a/tools/ci/check_examples_cmake_make-make_ignore.txt b/tools/ci/check_examples_cmake_make-make_ignore.txt index ecb9cd59cd..fa9fe6f9de 100644 --- a/tools/ci/check_examples_cmake_make-make_ignore.txt +++ b/tools/ci/check_examples_cmake_make-make_ignore.txt @@ -3,3 +3,4 @@ temp_ examples/bluetooth/bluedroid/ble_50/ examples/cxx/experimental/blink_cxx examples/cxx/experimental/esp_modem_cxx/ +examples/cxx/experimental/simple_spi_rw_example diff --git a/tools/mocks/driver/mock/mock_config.yaml b/tools/mocks/driver/mock/mock_config.yaml index ceeda099ee..596255b0ce 100644 --- a/tools/mocks/driver/mock/mock_config.yaml +++ b/tools/mocks/driver/mock/mock_config.yaml @@ -4,5 +4,6 @@ - expect_any_args - return_thru_ptr - array + - ignore - ignore_arg - callback