mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
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:
commit
04dc51732b
@ -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:
|
||||
|
@ -27,6 +27,8 @@ typedef uint32_t TickType_t;
|
||||
|
||||
typedef int portMUX_TYPE;
|
||||
|
||||
#define portTICK_PERIOD_MS ( ( TickType_t ) 1 )
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -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
|
||||
|
@ -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})
|
||||
|
@ -1,3 +1,3 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_ADD_INCLUDEDIRS := include private_include
|
||||
|
||||
COMPONENT_SRCDIRS := ./ driver
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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)
|
@ -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`
|
@ -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)
|
@ -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);
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
@ -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/"
|
||||
)
|
@ -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`
|
@ -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)
|
@ -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);
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
@ -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)
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -1,3 +1,9 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "unity.h"
|
||||
|
@ -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)
|
68
examples/cxx/experimental/simple_spi_rw_example/README.md
Normal file
68
examples/cxx/experimental/simple_spi_rw_example/README.md
Normal 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
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "simple_spi_rw_example.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES experimental_cpp_component)
|
@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -4,5 +4,6 @@
|
||||
- expect_any_args
|
||||
- return_thru_ptr
|
||||
- array
|
||||
- ignore
|
||||
- ignore_arg
|
||||
- callback
|
||||
|
Loading…
x
Reference in New Issue
Block a user