Merge branch 'feature/spi_cxx' into 'master'

[cxx]: simple spi master class

Closes IDF-3750

See merge request espressif/esp-idf!13363
This commit is contained in:
Jakob Hasse 2021-10-26 02:44:35 +00:00
commit 04dc51732b
35 changed files with 2154 additions and 52 deletions

View File

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

View File

@ -27,6 +27,8 @@ typedef uint32_t TickType_t;
typedef int portMUX_TYPE;
#define portTICK_PERIOD_MS ( ( TickType_t ) 1 )
#ifdef __cplusplus
}
#endif

View File

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

View File

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

View File

@ -1,3 +1,3 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_ADD_INCLUDEDIRS := include private_include
COMPONENT_SRCDIRS := ./ driver

View File

@ -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<gpio_num_t>(num.get_num()), ESP_OK); gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(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<gpio_num_t>(num.get_num()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(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<spi_device_handle_t>(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<SPIDevFix*>(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<SPITransactionFix*> (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<SPITransactionFix*> (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<spi_device_handle_t>(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<SPITransactionDescriptorFix*> (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<SPITransactionDescriptorFix*> (g_trans_desc_fixture);
for (int i = 0; i < fix->size; i++) {
static_cast<uint8_t*>(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<uint8_t> tx_data;
std::vector<uint8_t> rx_data;
};

View File

@ -10,6 +10,7 @@
#include <stdio.h>
#include "esp_err.h"
#include "unity.h"
#include "freertos/portmacro.h"
#include "gpio_cxx.hpp"
#include "test_fixtures.hpp"

View File

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

View File

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

View File

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

View File

@ -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 <stdio.h>
#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<SPIDeviceHandle*>(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<uint8_t> 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<uint8_t> out_data = result.get();
SPITransactionDescriptor *transaction = reinterpret_cast<SPITransactionDescriptor*>(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<uint8_t> 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<bool*>(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<bool*>(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<void(void *)> pre_callback = [&] (void *user) {
pre_cb_called = true;
};
auto result = dev.transfer({47}, pre_callback);
vector<uint8_t> 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<SPITransactionDescriptor> trans(new SPITransactionDescriptor(std::vector<uint8_t>(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<SPITransactionDescriptor> transaction(new SPITransactionDescriptor(std::vector<uint8_t>(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<uint8_t> out_data = result.get();
CHECK(out_data.size() == 1);
}

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,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/"
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<size_t> {
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<size_t>(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<uint32_t> {
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<uint32_t>(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<class MOSI_type>;
/**
* @brief Represents a valid MISO signal pin number.
*/
class MISO_type;
using MISO = GPIONumBase<class MISO_type>;
/**
* @brief Represents a valid SCLK signal pin number.
*/
class SCLK_type;
using SCLK = GPIONumBase<class SCLK_type>;
/**
* @brief Represents a valid CS (chip select) signal pin number.
*/
class CS_type;
using CS = GPIONumBase<class CS_type>;
/**
* @brief Represents a valid QSPIWP signal pin number.
*/
class QSPIWP_type;
using QSPIWP = GPIONumBase<class QSPIWP_type>;
/**
* @brief Represents a valid QSPIHD signal pin number.
*/
class QSPIHD_type;
using QSPIHD = GPIONumBase<class QSPIHD_type>;
/**
* @brief Represents a valid SPI DMA configuration. Use it similar to an enum.
*/
class SPI_DMAConfig : public StrongValueComparable<uint32_t> {
/**
* Constructor is hidden to enforce object invariants.
* Use the static creation methods to create instances.
*/
explicit SPI_DMAConfig(uint32_t channel_num) : StrongValueComparable<uint32_t>(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

View File

@ -0,0 +1,414 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#if __cpp_exceptions
#include <exception>
#include <memory>
#include <chrono>
#include <vector>
#include <list>
#include <future>
#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<uint8_t> &data_to_send,
SPIDeviceHandle *handle,
std::function<void(void *)> pre_callback = nullptr,
std::function<void(void *)> 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<uint8_t> 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<void(void *)> pre_callback;
/**
* @brief If non-empty, this callback will be called directly after the transaction.
*/
std::function<void(void *)> 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<SPITransactionDescriptor> 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<uint8_t> 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<SPITransactionDescriptor> 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<uint8_t> &data_to_send,
std::function<void(void *)> pre_callback = nullptr,
std::function<void(void *)> 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<typename IteratorT>
SPIFuture transfer(IteratorT begin,
IteratorT end,
std::function<void(void *)> pre_callback = nullptr,
std::function<void(void *)> 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<SPITransactionDescriptor> 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<SPIDevice> create_dev(CS cs, Frequency frequency = Frequency::MHz(1));
private:
/**
* @brief Host identifier for internal use.
*/
SPINum spi_host;
};
template<typename IteratorT>
SPIFuture SPIDevice::transfer(IteratorT begin,
IteratorT end,
std::function<void(void *)> pre_callback,
std::function<void(void *)> post_callback,
void* user_data)
{
std::vector<uint8_t> write_data;
write_data.assign(begin, end);
return transfer(write_data, pre_callback, post_callback, user_data);
}
}
#endif

View File

@ -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<typename ValueT>
class StrongValueComparable : public StrongValue<ValueT> {
protected:
StrongValueComparable(ValueT value_arg) : StrongValue<ValueT>(value_arg) { }
public:
using StrongValue<ValueT>::get_value;
bool operator==(const StrongValueComparable<ValueT> &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<typename ValueT>
class StrongValueOrdered : public StrongValueComparable<ValueT> {
public:
StrongValueOrdered(ValueT value) : StrongValueComparable<ValueT>(value) { }
using StrongValueComparable<ValueT>::get_value;
bool operator>(const StrongValueOrdered<ValueT> &other) const
{
return get_value() > other.get_value();
}
bool operator<(const StrongValueOrdered<ValueT> &other) const
{
return get_value() < other.get_value();
}
bool operator>=(const StrongValueOrdered<ValueT> &other) const
{
return get_value() >= other.get_value();
}
bool operator<=(const StrongValueOrdered<ValueT> &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<size_t> {
public:
explicit Frequency(size_t frequency) : StrongValueOrdered<size_t>(frequency)
{
if (frequency == 0) {
throw ESPException(ESP_ERR_INVALID_ARG);
}
}
Frequency(const Frequency&) = default;
Frequency &operator=(const Frequency&) = default;
using StrongValueOrdered<size_t>::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;
};
}

View File

@ -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<spi_host_device_t>(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<SPITransactionDescriptor*>(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<SPITransactionDescriptor*>(driver_transaction->user);
if (transaction->post_callback) {
transaction->post_callback(transaction->user_data);
}
}
spi_device_handle_t handle;
};
}
#endif

View File

@ -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<uint32_t>(SPI_HOST_MAX)) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
SPI_DMAConfig SPI_DMAConfig::DISABLED() {
return SPI_DMAConfig(static_cast<uint32_t>(spi_common_dma_t::SPI_DMA_DISABLED));
}
SPI_DMAConfig SPI_DMAConfig::AUTO() {
return SPI_DMAConfig(static_cast<uint32_t>(spi_common_dma_t::SPI_DMA_CH_AUTO));
}
}
#endif

View File

@ -0,0 +1,267 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if __cpp_exceptions
#include <stdint.h>
#include <cstring>
#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<SPIDevice> SPIMaster::create_dev(CS cs, Frequency frequency)
{
return make_shared<SPIDevice>(spi_host, cs, frequency);
}
SPIFuture::SPIFuture()
: transaction(), is_valid(false)
{
}
SPIFuture::SPIFuture(shared_ptr<SPITransactionDescriptor> 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<uint8_t> 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<uint8_t> &data_to_send,
std::function<void(void *)> pre_callback,
std::function<void(void *)> post_callback,
void* user_data)
{
current_transaction = make_shared<SPITransactionDescriptor>(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<uint8_t> &data_to_send,
SPIDeviceHandle *handle,
std::function<void(void *)> pre_callback,
std::function<void(void *)> 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<spi_transaction_t*>(private_transaction_desc);
delete [] tx_buffer;
delete [] static_cast<uint8_t*>(trans_desc->rx_buffer);
delete trans_desc;
}
void SPITransactionDescriptor::start()
{
spi_transaction_t *trans_desc = reinterpret_cast<spi_transaction_t*>(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<spi_transaction_t*>(private_transaction_desc)) {
throw SPITransferException(ESP_ERR_INVALID_STATE);
}
received_data = true;
device_handle->release_bus();
return true;
}
std::vector<uint8_t> SPITransactionDescriptor::get()
{
if (!received_data) {
wait();
}
spi_transaction_t *trans_desc = reinterpret_cast<spi_transaction_t*>(private_transaction_desc);
const size_t TRANSACTION_LENGTH = trans_desc->length / 8;
vector<uint8_t> result(TRANSACTION_LENGTH);
for (int i = 0; i < TRANSACTION_LENGTH; i++) {
result[i] = static_cast<uint8_t*>(trans_desc->rx_buffer)[i];
}
return result;
}
} // idf
#endif // __cpp_exceptions

View File

@ -1,3 +1,9 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "unity.h"

View File

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

View File

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

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "simple_spi_rw_example.cpp"
INCLUDE_DIRS "."
REQUIRES experimental_cpp_component)

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -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 <iostream>
#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<SPIDevice> spi_dev = master.create_dev(CS(NSS.get_num()), Frequency::MHz(1));
vector<uint8_t> write_data = {MPU9250_WHO_AM_I_REG_ADDR | READ_FLAG, 0x00};
vector<uint8_t> 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;
}
}

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

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

View File

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

View File

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

View File

@ -4,5 +4,6 @@
- expect_any_args
- return_thru_ptr
- array
- ignore
- ignore_arg
- callback