first-commit

This commit is contained in:
alex04130 2024-03-05 16:09:49 -06:00
commit ce4d1953fe
1534 changed files with 260623 additions and 0 deletions

17
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
}
],
"version": 4
}

88
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,88 @@
{
"idf.portWin": "COM3",
"idf.adapterTargetName": "esp32s3",
"idf.openOcdConfigs": [
"interface/ftdi/esp32_devkitj_v1.cfg",
"target/esp32s3.cfg"
],
"idf.flashType": "UART",
"C_Cpp.default.compilerPath": "C:\\Users\\Administrator\\.espressif\\tools\\xtensa-esp32s3-elf\\esp-12.2.0_20230208\\xtensa-esp32s3-elf\\bin\\xtensa-esp32s3-elf-gcc.exe",
"files.associations": {
"tinyusb.h": "c",
"tusb.h": "c",
"tusb_tasks.h": "c",
"esp_err.h": "c",
"periph_ctrl.h": "c",
"usb_descriptors.h": "c",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"compare": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"*.inc": "cpp",
"ranges": "cpp",
"span": "cpp",
"xstring": "cpp",
"xutility": "cpp",
"charconv": "cpp",
"format": "cpp"
},
"cmake.configureOnOpen": false
}

9
CMakeLists.txt Normal file
View File

@ -0,0 +1,9 @@
# 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.16)
set(EXTRA_COMPONENT_DIRS "./include")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(SK231024827118)

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# CLSH1001
# 主控:ESP32S3
# 驱动开发中

27
dependencies.lock Normal file
View File

@ -0,0 +1,27 @@
dependencies:
espressif/esp-idf-cxx:
component_hash: 2fa63e4e725f0c2d3dd6d99da8a91d404b4b49667088b1be0f336eb9c13ff17f
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.0.2-beta
espressif/esp_tinyusb:
component_hash: 7e6836872bb3ba21e63280bf699550dbeb3a8237dd966164df6b726dc79a8c9a
source:
service_url: https://api.components.espressif.com/
type: service
version: 1.4.2
espressif/tinyusb:
component_hash: 632ddbd9c73bee5005e7684eed3142649a08d1f304dbe42abed8110b7384e2c2
source:
service_url: https://api.components.espressif.com/
type: service
version: 0.15.0~3
idf:
component_hash: null
source:
type: idf
version: 5.1.1
manifest_hash: b862f753e765acfd1ff5225c0281ee20ed0d404c89803e41011874fe176b1159
target: esp32s3
version: 1.0.0

View File

@ -0,0 +1 @@
{}

View File

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

225
include/bq4050/bq4050.cpp Normal file
View File

@ -0,0 +1,225 @@
#include <bq4050.h>
#include "driver/i2c.h"
#include "esp_log.h"
void ESP_I2C_ERROR_CHECK(esp_err_t errcode)
{
if (errcode != ESP_OK)
{
ESP_LOGI(BQ4050::BQ4050_TAG, "TinyUSB driver error\n:)");
while (1)
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
}
esp_err_t bq4050_register_read(uint8_t reg_addr, uint8_t *data, size_t len)
{
return i2c_master_write_read_device(BQ4050::I2C_MASTER, BQ4050_ADDR, &reg_addr, 1, data, len, BQ4050::I2C_MASTER_TIMEOUT / portTICK_PERIOD_MS);
}
esp_err_t bq4050_register_write(uint8_t *data, size_t len)
{
return i2c_master_write_to_device(BQ4050::I2C_MASTER, BQ4050_ADDR, data, len, BQ4050::I2C_MASTER_TIMEOUT / portTICK_PERIOD_MS);
}
// inblock前面必须加0x44地址
// inLen的长度不包含0x44地址标识符
void bq_MACReadBlock(uint8_t *inBlock, uint8_t inLen, uint8_t *outBlock, uint8_t outLen)
{
uint8_t retryCnt = 100;
while (1)
{
bq4050_register_write(inBlock, inLen + 1);
bq4050_register_read(0x44, outBlock, outLen);
if (inBlock[2] == outBlock[1] && inBlock[3] == outBlock[2])
{
break;
}
vTaskDelay(20 / portTICK_PERIOD_MS);
}
}
uint8_t bq_BattState() // Return CHG/DSG(0xC?/0x0?), OK/Bad(0x0?/0x3?), TCTDFCFD(bit3 2 1 0)
{
uint8_t battStatus[2], battDataBuf[7], ret = 0x00;
uint8_t battCmd[4] = {0x44, 0x02, 0x50, 0x00}; // SafetyAlert MAC Cmd
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x16, battStatus, 2));
ret |= (((battStatus[0] & 0x40) == 0x40) ? 0x00 : 0xc0);
bq_MACReadBlock(battCmd, 3, battDataBuf, 7);
battDataBuf[6] &= 0x0f; // Clear RSVD
battDataBuf[5] &= 0xfd;
battDataBuf[4] &= 0x7a;
battDataBuf[3] &= 0xbf;
if ((battDataBuf[3] | battDataBuf[4] | battDataBuf[5] | battDataBuf[6]) != 0)
{
ret |= 0x30;
return ret;
}
battCmd[2] = 0x54;
bq_MACReadBlock(battCmd, 3, battDataBuf, 7);
if ((battDataBuf[4] & 0x60) != 0)
{
ret |= 0x30;
}
battCmd[2] = 0x51;
bq_MACReadBlock(battCmd, 3, battDataBuf, 7);
battDataBuf[6] &= 0x0f; // Clear RSVD
battDataBuf[5] &= 0xd5;
battDataBuf[4] &= 0x7f;
battDataBuf[3] &= 0xff;
if ((battDataBuf[3] | battDataBuf[4] | battDataBuf[5] | battDataBuf[6]) != 0)
{
ret |= 0x30;
}
battCmd[2] = 0x56;
bq_MACReadBlock(battCmd, 3, battDataBuf, 5);
ret |= (battDataBuf[3] & 0x0f);
return ret;
}
uint16_t bq_GetAdvState()
{ // Return XDSG/XCHG/PF/SS FC/FD/TAPR/VCT CB/SMTH/SU/IN VDQ/FCCX/EDV2/EDV1
uint16_t ret = 0x0000;
uint8_t battDataBuf[7], battCmd[4] = {0x44, 0x02, 0x54, 0x00}; // OperationStatus MAC Cmd
bq_MACReadBlock(battCmd, 3, battDataBuf, 7);
ret |= ((battDataBuf[3] & 0x40) == 0x40) ? (uint16_t)1 << 6 : 0; // SMTH
ret |= ((battDataBuf[4] & 0x08) == 0x08) ? (uint16_t)1 << 12 : 0; // SS
ret |= ((battDataBuf[4] & 0x10) == 0x10) ? (uint16_t)1 << 13 : 0; // PF
ret |= ((battDataBuf[4] & 0x20) == 0x20) ? (uint16_t)1 << 15 : 0; // XDSG
ret |= ((battDataBuf[4] & 0x40) == 0x40) ? (uint16_t)1 << 14 : 0; // XCHG
ret |= ((battDataBuf[6] & 0x10) == 0x10) ? (uint16_t)1 << 7 : 0; // CB
battCmd[2] = 0x55; // ChargingStatus MAC Cmd
bq_MACReadBlock(battCmd, 3, battDataBuf, 6);
ret |= ((battDataBuf[3] & 0x10) == 0x10) ? (uint16_t)1 << 4 : 0; // IN
ret |= ((battDataBuf[3] & 0x20) == 0x20) ? (uint16_t)1 << 5 : 0; // SU
ret |= ((battDataBuf[3] & 0x80) == 0x80) ? (uint16_t)1 << 8 : 0; // VCT
ret |= ((battDataBuf[4] & 0x80) == 0x80) ? (uint16_t)1 << 9 : 0; // TAPR
battCmd[2] = 0x56; // GaugingStatus MAC Cmd
bq_MACReadBlock(battCmd, 3, battDataBuf, 5);
ret |= ((battDataBuf[3] & 0x01) == 0x01) ? (uint16_t)1 << 10 : 0; // FD
ret |= ((battDataBuf[3] & 0x02) == 0x02) ? (uint16_t)1 << 11 : 0; // FC
ret |= ((battDataBuf[4] & 0x04) == 0x04) ? (uint16_t)1 << 2 : 0; // FCCX
ret |= ((battDataBuf[4] & 0x20) == 0x20) ? (uint16_t)1 << 0 : 0; // EDV1
ret |= ((battDataBuf[4] & 0x40) == 0x40) ? (uint16_t)1 << 1 : 0; // EDV2
ret |= ((battDataBuf[4] & 0x80) == 0x80) ? (uint16_t)1 << 3 : 0; // VDQ
return ret;
}
void bq_GetLifetimeBlock(uint8_t blockNo, uint8_t *blockBuf)
{
uint8_t blockBufLen = 3, battCmd[4] = {0x44, 0x02, 0x60, 0x00}; // LifetimeDataBlockN MAC Cmd
battCmd[2] += (blockNo - 1);
switch (blockNo)
{
case 1:
case 4:
blockBufLen += 32;
break;
case 2:
blockBufLen += 8;
break;
case 3:
blockBufLen += 16;
break;
case 5:
blockBufLen += 20;
break;
default:
return;
}
bq_MACReadBlock(battCmd, 3, blockBuf, blockBufLen);
}
uint16_t bq_GetVoltage()
{ // Unit: mV
uint8_t battBuf[2];
uint16_t battVolt;
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x09, battBuf, 2));
battVolt = (battBuf[1] << 8) + battBuf[0];
return battVolt;
}
int16_t bq_GetCurrent()
{ // Unit: mA
uint8_t battBuf[2];
int16_t battCurrent;
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x0A, battBuf, 2));
battCurrent = uint8_t(battBuf[0] << 8) & battBuf[1];
return battCurrent;
}
uint8_t bq_GetRSOC()
{ // Unit: %
uint8_t battBuf[2];
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x0D, battBuf, 2));
return battBuf[0];
}
uint16_t bq_GetT2E()
{ // Unit: min
uint8_t battBuf[2];
uint16_t battT2E;
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x12, battBuf, 2));
battT2E = (battBuf[1] << 8) + battBuf[0];
return battT2E;
}
uint16_t bq_GetT2F()
{ // Unit: min
uint8_t battBuf[2];
uint16_t battT2F;
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x13, battBuf, 2));
battT2F = (battBuf[1] << 8) + battBuf[0];
return battT2F;
}
uint16_t bq_GetPackTemp()
{ // Unit: 0.1K
uint8_t battBuf[2];
uint16_t battPackTemp;
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x08, battBuf, 2));
battPackTemp = (battBuf[1] << 8) + battBuf[0];
return battPackTemp;
}
uint8_t bq_GetMaxErr()
{ // Unit: %
uint8_t battBuf[2];
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x0C, battBuf, 2));
return battBuf[0];
}
uint8_t bq_GetHealth()
{ // Unit: %
uint8_t battBuf[2];
uint16_t battFCC, battDC;
float battHealth;
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x10, battBuf, 2));
battFCC = (battBuf[1] << 8) + battBuf[0];
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x18, battBuf, 2));
battDC = (battBuf[1] << 8) + battBuf[0];
battHealth = ((float)battFCC * 100.0f / (float)battDC);
return (battHealth >= 100.0f ? 100 : (uint8_t)battHealth);
}
uint16_t bq_GetCellVolt(uint8_t cellNo)
{ // Unit: mV
uint8_t battBuf[2], cmd = 0x40;
uint16_t battCellVolt;
if (cellNo > 4 || cellNo < 1)
return 0;
cmd -= cellNo;
ESP_I2C_ERROR_CHECK(bq4050_register_read(cmd, battBuf, 2));
battCellVolt = (battBuf[1] << 8) + battBuf[0];
return battCellVolt;
}
uint16_t bq_GetCycleCnt()
{
uint8_t battBuf[2];
uint16_t battCycleCnt;
ESP_I2C_ERROR_CHECK(bq4050_register_read(0x17, battBuf, 2));
battCycleCnt = (battBuf[1] << 8) + battBuf[0];
return battCycleCnt;
}

38
include/bq4050/bq4050.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef __BQ4050_H__
#define __BQ4050_H__
#include <bq4050.h>
#include "driver/i2c.h"
#include "esp_log.h"
// BQ4050配置
#define BQ4050_ADDR 0x0B
namespace BQ4050
{
static i2c_port_t I2C_MASTER;
static int I2C_MASTER_TIMEOUT;
static const char *BQ4050_TAG = "BQ4050";
}
void bq_Init();
uint8_t bq_BattState(); // Return CHG/DSG(0xF?/0x0?), OK/Bad(0x?0/0x?F)
uint16_t bq_GetAdvState(); // Return XDSG/XCHG/PF/SS/FC/FD/TAPR/VCT/CB/SMTH/SU/IN/VDQ/FCCX/EDV2/EDV1
uint16_t bq_GetVoltage(); // Unit: mV
int16_t bq_GetCurrent(); // Unit: mA
uint8_t bq_GetRSOC(); // Unit: %
uint16_t bq_GetT2E(); // Unit: min
uint16_t bq_GetT2F(); // Unit: min
uint16_t bq_GetPackTemp(); // Unit: 0.1K
uint8_t bq_GetMaxErr(); // Unit: %
uint8_t bq_GetHealth(); // Unit: %
uint16_t bq_GetCellVolt(uint8_t cellNo); // Unit: mV
uint16_t bq_GetCycleCnt();
esp_err_t bq4050_register_read(uint8_t reg_addr, uint8_t *data, size_t len);
esp_err_t bq4050_register_write(uint8_t *data, size_t len);
void ESP_I2C_ERROR_CHECK(esp_err_t errcode);
void bq_GetLifetimeBlock(uint8_t blockNo, uint8_t *blockBuf);
void bq_MACReadBlock(uint8_t *inBlock, uint8_t inLen, uint8_t *outBlock, uint8_t outLen);
#endif

5
main/CMakeLists.txt Normal file
View File

@ -0,0 +1,5 @@
idf_component_register(SRCS "main.cpp"
INCLUDE_DIRS ".")
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

16
main/idf_component.yml Normal file
View File

@ -0,0 +1,16 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: ">=4.1.0"
# # Put list of dependencies here
# # For components maintained by Espressif:
# component: "~1.0.0"
# # For 3rd party components:
# username/component: ">=1.0.0,<2.0.0"
# username2/component2:
# version: "~1.0.0"
# # For transient dependencies `public` flag can be set.
# # `public` flag doesn't have an effect dependencies of the `main` component.
# # All dependencies of `main` are public by default.
# public: true

494
main/main.cpp Normal file
View File

@ -0,0 +1,494 @@
// I2C设置
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C0_NUM I2C_NUM_0
#define I2C0_SCL_IO 42
#define I2C0_SDA_IO 41
#define I2C0_FREQ_HZ 40000
#define I2C_MASTER_TIMEOUT_MS 14000
#define SW7203_ADDR 0x3C
#include <cstdlib>
#include <iostream>
#include <string>
#include "driver/i2c.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "esp_log.h"
#include "bq4050.h"
#include "gpio_cxx.hpp"
#include "driver/gpio.h"
#include <thread>
using namespace std;
using namespace idf;
#define SetBitTrue(a, b) a |= (1 << b)
#define SetBitFalse(a, b) a &= ~(1 << b)
#define GetBit(a, b) a & (1 << b)
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_INOUT_DESC_LEN)
#define ITF_NUM_TOTAL 1
static esp_err_t bq4050_i2c_master_init()
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = BQ4050_I2C_MASTER_SDA_IO,
.scl_io_num = BQ4050_I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master{
.clk_speed = BQ4050_I2C_MASTER_FREQ_HZ},
};
BQ4050::I2C_MASTER = BQ4050_I2C_MASTER_NUM;
BQ4050::I2C_MASTER_TIMEOUT = BQ4050_I2C_MASTER_TIMEOUT_MS;
i2c_param_config(BQ4050_I2C_MASTER_NUM, &conf);
return i2c_driver_install(BQ4050_I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}
static esp_err_t sw7203_i2c_master_init()
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = SW7203_I2C_MASTER_SDA_IO,
.scl_io_num = SW7203_I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master{
.clk_speed = SW7203_I2C_MASTER_FREQ_HZ},
};
i2c_param_config(SW7203_I2C_MASTER_NUM, &conf);
return i2c_driver_install(SW7203_I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}
esp_err_t sw7203_register_read(uint8_t reg_addr, uint8_t *data)
{
return i2c_master_write_read_device(SW7203_I2C_MASTER_NUM, SW7203_ADDR, &reg_addr, 1, data, 1, SW7203_I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
}
esp_err_t sw7203_register_write(uint8_t *data)
{
return i2c_master_write_to_device(SW7203_I2C_MASTER_NUM, SW7203_ADDR, data, 2, SW7203_I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
}
void sw7203_check_error(esp_err_t errcode)
{
if (errcode != ESP_OK)
{
ESP_LOGI(TAG, "SW7203 I2C error\n:)");
ESP_ERROR_CHECK_WITHOUT_ABORT(err_code);
while (1)
{
ErrLED.set_high();
vTaskDelay(100 / portTICK_PERIOD_MS);
ErrLED.set_low();
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
}
#ifndef __SW7203_DEBUG__
void sw7203_start_charge()
{
unsigned int VBUS_Voltage = 0;
uint8_t data = 0;
sw7203_check_error(sw7203_register_read(0x11, &data));
VBUS_Voltage = data;
VBUS_Voltage = VBUS_Voltage << 4;
sw7203_check_error(sw7203_register_read(0x12, &data));
VBUS_Voltage &= (data & 0b00001111);
VBUS_Voltage *= 75;
VBUS_Voltage -= 7500;
VBUS_Voltage = (VBUS_Voltage - 40000) / 1000;
uint8_t VBUS_BitMask = 0b01111111, VBUS_Limit = VBUS_Voltage;
uint8_t cmd[3][2] = {{0x38, uint8_t(VBUS_Limit & VBUS_BitMask)}, {0x19, 0b00000100}, {0x0D, 0b00010000}};
sw7203_register_write(cmd[0]);
sw7203_register_write(cmd[1]);
sw7203_register_write(cmd[2]);
}
void sw7203_stop_charge()
{
uint8_t cmd[2][2] = {{0x04, 0b00000100}, {0x19, 0b00000000}};
sw7203_register_write(cmd[0]);
sw7203_register_write(cmd[1]);
}
void sw7203_irq_func(void *arg)
{
while (1)
{
if (gpio_get_level(GPIO_NUM_47) == 1)
{
ESP_LOGI(TAG, "SW 7203 INT ARG");
uint8_t data = 0;
sw7203_check_error(sw7203_register_read(0x04, &data));
if (data & 0x40)
{
ESP_LOGI(TAG, "VSYS voltage limit exceeded");
}
if (data & 0x20)
{
ESP_LOGI(TAG, "Battery charge time limit exceeded");
}
if (data & 0x10)
{
ESP_LOGI(TAG, "Battery fullcharged");
}
if (data & 0x08)
{
ESP_LOGI(TAG, "DCIN moved in");
AC_IN = true;
vTaskDelay(100 / portTICK_PERIOD_MS);
sw7203_start_charge();
}
if (data & 0x04)
{
ESP_LOGI(TAG, "DCIN moved out");
AC_IN = false;
sw7203_stop_charge();
}
sw7203_check_error(sw7203_register_read(0x05, &data));
if (data & 0x80)
{
ESP_LOGI(TAG, "SW7203 over temperature");
}
if (data & 0x10)
{
ESP_LOGI(TAG, "VBAT voltage limit exceeded");
}
if (data & 0x08)
{
ESP_LOGI(TAG, "VBAT voltage limit subceeded");
}
if (data & 0x01)
{
ESP_LOGI(TAG, "VBUS power limit exceeded");
}
sw7203_check_error(sw7203_register_read(0x06, &data));
if (data & 0x02)
{
NVDC_BAT_charge = true;
}
else
{
NVDC_BAT_charge = false;
}
uint8_t cmd[2][2] = {{0x04, 0b11111111}, {0x05, 0b11111111}};
sw7203_register_write(cmd[0]);
sw7203_register_write(cmd[1]);
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
#endif
extern "C" void app_main()
{
ErrLED.set_high();
#ifdef __USB_HID_ENABLE__
ESP_LOGI(TAG, "USB initialization");
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &descriptor_config,
.string_descriptor = string_descriptor,
.string_descriptor_count = sizeof(string_descriptor) / sizeof(string_descriptor[0]),
.external_phy = false,
.configuration_descriptor = desc_configuration,
.self_powered = false,
};
if ((err_code = tinyusb_driver_install(&tusb_cfg)) != ESP_OK)
{
ESP_LOGI(TAG, "TinyUSB driver error\n:)");
ESP_ERROR_CHECK_WITHOUT_ABORT(err_code);
while (1)
{
ErrLED.set_high();
vTaskDelay(1000 / portTICK_PERIOD_MS);
ErrLED.set_low();
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
ESP_LOGI(TAG, "USB initialization DONE");
#endif
uint8_t sw7203_config_data[][2] = {
{0x02, 0b00000011},
// 中断使能1 0使能 1禁止 7:NULL 6:VSYS过压中断 5:充电超时中断 4:充电充满中断
// 3: 适配器插入中断 2:适配器移出中断 1:A2负载接入中断 0:A1负载接入中断
{0x03, 0b01000110},
// {0x04, 0b11111111},
// {0x05, 0b11111111},
{0x0D, 0b00000000},
{0x0F, 0b00000001},
{0x10, 0b01000001},
{0x18, 0b00000011},
{0x19, 0b00000000},
{0x20, 0b10000100},
{0x21, 0b11111111},
{0x22, 0b10100000},
{0x26, 0b01011001},
{0x27, 0b01010101},
{0x28, 0b00000100},
{0x30, 0b00000011},
{0x31, 0b00000000},
{0x32, 0b11010000},
{0x34, 0b10101010},
{0x35, 0b00000000},
{0x36, 0b01011111},
{0x37, 0b00001111},
{0x38, 0b00000100},
{0x39, 0b01111111},
{0x3A, 0b00010011},
#ifdef __SW7203_DEBUG__
{0x40, 0b01000011},
#else
{0x40, 0b00000011},
#endif
{0x41, 0b00000100},
{0x42, 0b00100101}};
if ((err_code = bq4050_i2c_master_init()) != ESP_OK)
{
ESP_LOGI(TAG, "BQ4050 driver error\n:)");
ESP_ERROR_CHECK_WITHOUT_ABORT(err_code);
while (1)
{
ErrLED.set_high();
vTaskDelay(1000 / portTICK_PERIOD_MS);
ErrLED.set_low();
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
BatteryVoltage = bq_GetVoltage();
BatteryCurrentCapacity = bq_GetRSOC();
BatteryRunTimeToEmpty = bq_GetT2E();
BatteryRunTimeToFull = bq_GetT2F();
ESP_LOGI(TAG, "BQ4050 initialization DONE!\n:)");
if ((err_code = sw7203_i2c_master_init()) != ESP_OK)
{
ESP_LOGI(TAG, "SW7203 driver error\n:)");
ESP_ERROR_CHECK_WITHOUT_ABORT(err_code);
while (1)
{
ErrLED.set_high();
vTaskDelay(1000 / portTICK_PERIOD_MS);
ErrLED.set_low();
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
for (int i = 0; i < 27; i++)
{
if ((err_code = sw7203_register_write(sw7203_config_data[i])) != ESP_OK)
{
ESP_LOGI(TAG, "SW7203 I2C error\n:)");
ESP_ERROR_CHECK_WITHOUT_ABORT(err_code);
while (1)
{
ErrLED.set_high();
vTaskDelay(100 / portTICK_PERIOD_MS);
ErrLED.set_low();
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
}
ESP_LOGI(TAG, "SW7203 initialization DONE!\n:)");
#ifndef __SW7203_DEBUG__
gpio_config_t SW7203_IRQ_gpio_config = {
.pin_bit_mask = 1ull << 47,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
};
gpio_config(&SW7203_IRQ_gpio_config);
xTaskCreate(sw7203_irq_func, "SW7203IRQ", 10240, NULL, 20, NULL);
ESP_LOGI(TAG, "SW7203 intrupt initialization DONE!\n:)");
#endif
ESP_LOGI(TAG, "initialization DONE!\n:)");
ErrLED.set_low();
while (1)
{
BatteryCurrentCapacity = bq_GetRSOC();
BatteryRunTimeToEmpty = bq_GetT2E();
BatteryCurrentStatus = bq_BattState_u16(AC_IN, NVDC_BAT_charge);
#ifdef __USB_HID_ENABLE__
if ((BatteryCurrentCapacity != BatteryPrevCapacity) || (BatteryCurrentStatus != BatteryPrevStatus))
{
tud_hid_report(HID_PD_REMAININGCAPACITY, &BatteryCurrentCapacity, sizeof(BatteryCurrentCapacity));
tud_hid_report(HID_PD_PRESENTSTATUS, &BatteryCurrentStatus, sizeof(BatteryCurrentStatus));
if (BatteryCurrentStatus & (1 << PRESENTSTATUS_DISCHARGING))
tud_hid_report(HID_PD_RUNTIMETOEMPTY, &BatteryRunTimeToEmpty, sizeof(BatteryRunTimeToEmpty));
BatteryPrevCapacity = BatteryCurrentCapacity;
BatteryPrevStatus = BatteryCurrentStatus;
}
#endif
if (BatteryCurrentCapacity < 10)
PowerlossLED.set_high();
else
PowerlossLED.set_low();
if (BatteryCurrentStatus & (1 << PRESENTSTATUS_CHARGING))
ChargingLED.set_high();
else
ChargingLED.set_low();
ESP_LOGI(TAG, "Battery Current Capacity :%d\nBattery Current Status :%d\nBattery RunTime To Empty :%d\n", BatteryCurrentCapacity, BatteryCurrentStatus, BatteryRunTimeToEmpty);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
#ifdef __USB_HID_ENABLE__
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen)
{
switch (int(report_type))
{
case HID_REPORT_TYPE_FEATURE:
{
switch (report_id)
{
case HID_PD_PRESENTSTATUS:
{
#ifdef __SK_BQ4050_HID__
BatteryCurrentStatus = bq_BattState_u16(AC_IN, NVDC_BAT_charge);
buffer[0] = BatteryCurrentStatus & 0x00ff;
buffer[1] = BatteryCurrentStatus >> 8 & 0x00ff;
return 2;
#else
buffer[0] = BatteryCurrentStatus & 0x00ff;
buffer[1] = BatteryCurrentStatus >> 8 & 0x00ff;
return 2;
#endif
}
case HID_PD_VOLTAGE:
{
#ifdef __SK_BQ4050_HID__
BatteryVoltage = bq_GetVoltage();
buffer[0] = BatteryVoltage & 0x00ff;
buffer[1] = BatteryVoltage >> 8 & 0x00ff;
return 2;
#else
buffer[0] = BatteryVoltage & 0x00ff;
buffer[1] = BatteryVoltage >> 8 & 0x00ff;
return 2;
#endif
}
case HID_PD_DESIGNCAPACITY:
{
buffer[0] = BatteryDesignCapacity;
return 1;
}
case HID_PD_IDEVICECHEMISTRY:
{
buffer[0] = BatteryDeviceChemistry;
return 1;
}
case HID_PD_SERIAL:
{
buffer[0] = 3;
return 1;
}
case HID_PD_IOEMINFORMATION:
{
buffer[0] = BatteryOEMVendor;
return 1;
}
case HID_PD_IPRODUCT:
{
buffer[0] = IPRODUCT;
return 1;
}
case HID_PD_MANUFACTURER:
{
buffer[0] = IMANUFACTURER;
return 1;
}
case HID_PD_MANUFACTUREDATE:
{
buffer[0] = ManufactureDate & 0x00ff;
buffer[1] = ManufactureDate >> 8 & 0x00ff;
return 2;
}
case HID_PD_FULLCHRGECAPACITY:
{
buffer[0] = BatteryFullChargeCapacity;
return 1;
}
case HID_PD_WARNCAPACITYLIMIT:
{
buffer[0] = BatteryWarnCapacityLimit;
return 1;
}
case HID_PD_REMNCAPACITYLIMIT:
{
buffer[0] = BatteryRemnCapacityLimit;
return 1;
}
case HID_PD_REMAININGCAPACITY:
{
BatteryCurrentCapacity = bq_GetRSOC();
buffer[0] = BatteryCurrentCapacity;
return 1;
}
case HID_PD_RUNTIMETOEMPTY:
{
BatteryRunTimeToEmpty = bq_GetT2E();
buffer[0] = BatteryRunTimeToEmpty & 0x00ff;
buffer[1] = BatteryRunTimeToEmpty >> 8 & 0x00ff;
return 2;
}
case HID_PD_CPCTYGRANULARITY1:
{
buffer[0] = BatteryCapacityGranularity1;
return 1;
}
case HID_PD_CPCTYGRANULARITY2:
{
buffer[0] = BatteryCapacityGranularity2;
return 1;
}
case HID_PD_CAPACITYMODE:
{
buffer[0] = BatteryCapacityMode;
return 1;
}
}
}
}
ESP_LOGI(TAG, "Get_report Request: %d, type=%s , id: %d , len:%d\n",
instance,
(report_type == HID_REPORT_TYPE_INVALID ? "HID_REPORT_TYPE_INVALID" : (report_type == HID_REPORT_TYPE_INPUT ? "HID_REPORT_TYPE_INPUT" : (report_type == HID_REPORT_TYPE_OUTPUT ? "HID_REPORT_TYPE_OUTPUT" : "HID_REPORT_TYPE_FEATURE"))),
report_id,
reqlen);
for (int i = 0; i < reqlen; i++)
{
ESP_LOGI(TAG, "Report %d:%d\n", i, buffer[-i]);
}
ESP_LOGI(TAG, "end REQUSET\n");
return 0;
}
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance)
{
// We use only one interface and one HID report descriptor, so we can ignore parameter 'instance'
return ESP32UPS::desc_hid_report;
}
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize)
{
// ESP_LOGI(TAG, "Get_report Request: %d, type=%s , id: %d , len:%d\n",
// instance,
// (report_type == HID_REPORT_TYPE_INVALID ? "HID_REPORT_TYPE_INVALID" : (report_type == HID_REPORT_TYPE_INPUT ? "HID_REPORT_TYPE_INPUT" : (report_type == HID_REPORT_TYPE_OUTPUT ? "HID_REPORT_TYPE_OUTPUT" : "HID_REPORT_TYPE_FEATURE"))),
// report_id,
// bufsize);
// for (int i = 0; i < bufsize; i++)
// {
// ESP_LOGI(TAG, "Report %d:%d\n", i, buffer[i]);
// }
// ESP_LOGI(TAG, "end REQUSET\n");
}
#endif

View File

@ -0,0 +1 @@
2fa63e4e725f0c2d3dd6d99da8a91d404b4b49667088b1be0f336eb9c13ff17f

View File

@ -0,0 +1,27 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[{*.md,*.rst}]
trim_trailing_whitespace = false
[*.py]
max_line_length = 119
[{*.cmake,CMakeLists.txt}]
indent_style = space
indent_size = 4
max_line_length = 120
[{*.sh,*.yml,*.yaml}]
indent_size = 2

View File

@ -0,0 +1,69 @@
.config
*.o
*.pyc
# gtags
GTAGS
GRTAGS
GPATH
# emacs
.dir-locals.el
# emacs temp file suffixes
*~
.#*
\#*#
# eclipse setting
.settings
# MacOS directory files
.DS_Store
# Example Apps files
examples/**/build
examples/**/sdkconfig
examples/**/sdkconfig.old
# Host Unit Test Apps files
host_test/**/build
host_test/**/sdkconfig
host_test/**/sdkconfig.old
# Unit test app build files
test_apps/**/build
test_apps/**/sdkconfig
test_apps/**/sdkconfig.old
# Unit Test CMake compile log folder
log_ut_cmake
TEST_LOGS
# gcov coverage reports
*.gcda
*.gcno
coverage.info
coverage_report/
# VS Code Settings
.vscode/
# VIM files
*.swp
*.swo
# Clion IDE CMake build & config
.idea/
cmake-build-*/
# Results for the checking of the Python coding style and static analysis
.mypy_cache
flake8_output.txt
# lock files for tests
dependencies.lock
# managed_components for tests
managed_components

View File

@ -0,0 +1,17 @@
idf_build_get_property(target IDF_TARGET)
set(srcs "esp_timer_cxx.cpp" "esp_exception.cpp" "gpio_cxx.cpp" "i2c_cxx.cpp" "spi_cxx.cpp" "spi_host_cxx.cpp")
set(requires "esp_timer")
if(NOT ${target} STREQUAL "linux")
list(APPEND srcs
"esp_event_api.cpp"
"esp_event_cxx.cpp")
list(APPEND requires "esp_event" "pthread")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "private_include"
PRIV_REQUIRES freertos driver
REQUIRES ${requires})

View File

@ -0,0 +1,7 @@
# Contributing to ESP-IDF-CXX
Contributions to ESP-IDF-CXX - fixing bugs, adding features, adding documentation - are welcome! We accept contributions via Github Pull Requests.
Currently, we use the same contribution guidelines as ESP-IDF itself as basis. Please refer to the [ESP-IDF Contributions Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/contribute/index.html) for more information. However, the workflow is quite simplified, we only use github for collaboration. Furthermore, we use the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format for commits. There is an [editorconfig file](.editorconfig) to setup your editor or IDE with some basic options (tab-style, line ending, etc.).
Please also consider the legal part: You will be required to sign the [Contributor Agreement](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/contribute/contributor-agreement.html) for any Pull Request.

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,21 @@
# ESP-IDF-C++
This project provides C++ wrapper classes around some components of [esp-idf](https://github.com/espressif/esp-idf). It is organized as a component for the [IDF component manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). You can find this component [in the component registry](https://components.espressif.com/components/espressif/esp-idf-cxx).
## *NOTE*
This component is in a beta-release phase. Some bits that are still missing (non-exhaustive list):
* MQTT C++ classes
* Default pin definition on Kconfig for some examples
A road map and detailed release document will be announced soon.
## Requirements
* ESP-IDF and its requirements.
Please follow the [ESP-IDF "Get Started" Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) to download, install and setup esp-idf.
No other special requirements are necessary.
## Usage
Set up the IDF environment (i.e., `. ./export.sh` inside [esp-idf](https://github.com/espressif/esp-idf)). Then go to your project directory, use `idf.py add-dependency espressif/esp-idf-cxx^1.0.0-beta ` (should only be done once) and you should be able to use this component.

View File

@ -0,0 +1,115 @@
#include "esp_event.h"
#include "esp_event_cxx.hpp"
#include "esp_event_api.hpp"
#ifdef __cpp_exceptions
namespace idf {
namespace event {
ESPEventAPIDefault::ESPEventAPIDefault()
{
esp_err_t res = esp_event_loop_create_default();
if (res != ESP_OK) {
throw idf::event::EventException(res);
}
}
ESPEventAPIDefault::~ESPEventAPIDefault()
{
esp_event_loop_delete_default();
}
esp_err_t ESPEventAPIDefault::handler_register(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_t event_handler,
void *event_handler_arg,
esp_event_handler_instance_t *instance)
{
return esp_event_handler_instance_register(event_base,
event_id,
event_handler,
event_handler_arg,
instance);
}
esp_err_t ESPEventAPIDefault::handler_unregister(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_instance_t instance)
{
return esp_event_handler_instance_unregister(event_base, event_id, instance);
}
esp_err_t ESPEventAPIDefault::post(esp_event_base_t event_base,
int32_t event_id,
void* event_data,
size_t event_data_size,
TickType_t ticks_to_wait)
{
return esp_event_post(event_base,
event_id,
event_data,
event_data_size,
ticks_to_wait);
}
ESPEventAPICustom::ESPEventAPICustom(const esp_event_loop_args_t &event_loop_args)
{
esp_err_t res = esp_event_loop_create(&event_loop_args, &event_loop);
if (res != ESP_OK) {
throw idf::event::EventException(res);
}
}
ESPEventAPICustom::~ESPEventAPICustom()
{
esp_event_loop_delete(event_loop);
}
esp_err_t ESPEventAPICustom::handler_register(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_t event_handler,
void *event_handler_arg,
esp_event_handler_instance_t *instance)
{
return esp_event_handler_instance_register_with(event_loop,
event_base,
event_id,
event_handler,
event_handler_arg,
instance);
}
esp_err_t ESPEventAPICustom::handler_unregister(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_instance_t instance)
{
return esp_event_handler_instance_unregister_with(event_loop, event_base, event_id, instance);
}
esp_err_t ESPEventAPICustom::post(esp_event_base_t event_base,
int32_t event_id,
void* event_data,
size_t event_data_size,
TickType_t ticks_to_wait)
{
return esp_event_post_to(event_loop,
event_base,
event_id,
event_data,
event_data_size,
ticks_to_wait);
}
esp_err_t ESPEventAPICustom::run(TickType_t ticks_to_run)
{
return esp_event_loop_run(event_loop, ticks_to_run);
}
} // event
} // idf
#endif // __cpp_exceptions

View File

@ -0,0 +1,154 @@
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_event_cxx.hpp"
#ifdef __cpp_exceptions
using namespace idf::event;
using namespace std;
namespace idf {
namespace event {
const std::chrono::milliseconds PLATFORM_MAX_DELAY_MS(portMAX_DELAY *portTICK_PERIOD_MS);
ESPEventReg::ESPEventReg(std::function<void(const ESPEvent &, void*)> cb,
const ESPEvent& ev,
std::shared_ptr<ESPEventAPI> api)
: cb(cb), event(ev), api(api)
{
if (!cb) throw EventException(ESP_ERR_INVALID_ARG);
if (!api) throw EventException(ESP_ERR_INVALID_ARG);
esp_err_t reg_result = api->handler_register(ev.base, ev.id.get_id(), event_handler_hook, this, &instance);
if (reg_result != ESP_OK) {
throw ESPEventRegisterException(reg_result, event);
}
}
ESPEventReg::~ESPEventReg()
{
api->handler_unregister(event.base, event.id.get_id(), instance);
}
void ESPEventReg::dispatch_event_handling(ESPEvent event, void *event_data)
{
cb(event, event_data);
}
void ESPEventReg::event_handler_hook(void *handler_arg,
esp_event_base_t event_base,
int32_t event_id,
void *event_data)
{
ESPEventReg *object = static_cast<ESPEventReg*>(handler_arg);
object->dispatch_event_handling(ESPEvent(event_base, ESPEventID(event_id)), event_data);
}
ESPEventRegTimed::ESPEventRegTimed(std::function<void(const ESPEvent &, void*)> cb,
const ESPEvent& ev,
std::function<void(const ESPEvent &)> timeout_cb,
const std::chrono::microseconds &timeout,
std::shared_ptr<ESPEventAPI> api)
: ESPEventReg(cb, ev, api), timeout_cb(timeout_cb)
{
if (!timeout_cb || timeout < MIN_TIMEOUT) {
throw EventException(ESP_ERR_INVALID_ARG);
}
const esp_timer_create_args_t oneshot_timer_args {
timer_cb_hook,
static_cast<void*>(this),
ESP_TIMER_TASK,
"event",
false // skip_unhandled_events
};
esp_err_t res = esp_timer_create(&oneshot_timer_args, &timer);
if (res != ESP_OK) {
throw EventException(res);
}
esp_err_t timer_result = esp_timer_start_once(timer, timeout.count());
if (timer_result != ESP_OK) {
esp_timer_delete(timer);
throw EventException(timer_result);
}
}
ESPEventRegTimed::~ESPEventRegTimed()
{
std::lock_guard<mutex> guard(timeout_mutex);
esp_timer_stop(timer);
esp_timer_delete(timer);
// TODO: is it guaranteed that there is no pending timer callback for timer?
}
void ESPEventRegTimed::dispatch_event_handling(ESPEvent event, void *event_data)
{
if (timeout_mutex.try_lock()) {
esp_timer_stop(timer);
cb(event, event_data);
timeout_mutex.unlock();
}
}
void ESPEventRegTimed::timer_cb_hook(void *arg)
{
ESPEventRegTimed *object = static_cast<ESPEventRegTimed *>(arg);
if (object->timeout_mutex.try_lock()) {
object->timeout_cb(object->event);
object->api->handler_unregister(object->event.base, object->event.id.get_id(), object->instance);
object->timeout_mutex.unlock();
}
}
ESPEventLoop::ESPEventLoop(std::shared_ptr<ESPEventAPI> api) : api(api) {
if (!api) throw EventException(ESP_ERR_INVALID_ARG);
}
ESPEventLoop::~ESPEventLoop() { }
unique_ptr<ESPEventReg> ESPEventLoop::register_event(const ESPEvent &event,
function<void(const ESPEvent &, void*)> cb)
{
return unique_ptr<ESPEventReg>(new ESPEventReg(cb, event, api));
}
std::unique_ptr<ESPEventRegTimed> ESPEventLoop::register_event_timed(const ESPEvent &event,
std::function<void(const ESPEvent &, void*)> cb,
const std::chrono::microseconds &timeout,
std::function<void(const ESPEvent &)> timer_cb)
{
return std::unique_ptr<ESPEventRegTimed>(new ESPEventRegTimed(cb, event, timer_cb, timeout, api));
}
void ESPEventLoop::post_event_data(const ESPEvent &event,
const chrono::milliseconds &wait_time)
{
esp_err_t result = api->post(event.base,
event.id.get_id(),
nullptr,
0,
convert_ms_to_ticks(wait_time));
if (result != ESP_OK) {
throw ESPException(result);
}
}
TickType_t convert_ms_to_ticks(const std::chrono::milliseconds &time)
{
return time.count() / portTICK_PERIOD_MS;
}
} // namespace event
} // namespace idf
#endif // __cpp_exceptions

View File

@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2020 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef __cpp_exceptions
#include "esp_exception.hpp"
namespace idf {
ESPException::ESPException(esp_err_t error) : error(error) { }
const char *ESPException::what() const noexcept {
return esp_err_to_name(error);
}
} // namespace idf
#endif // __cpp_exceptions

View File

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2020 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef __cpp_exceptions
#include <functional>
#include "esp_timer_cxx.hpp"
#include "esp_exception.hpp"
using namespace std;
namespace idf {
namespace esp_timer {
ESPTimer::ESPTimer(function<void()> timeout_cb, const string &timer_name)
: timeout_cb(timeout_cb), name(timer_name)
{
if (timeout_cb == nullptr) {
throw ESPException(ESP_ERR_INVALID_ARG);
}
esp_timer_create_args_t timer_args = {};
timer_args.callback = esp_timer_cb;
timer_args.arg = this;
timer_args.dispatch_method = ESP_TIMER_TASK;
timer_args.name = name.c_str();
CHECK_THROW(esp_timer_create(&timer_args, &timer_handle));
}
ESPTimer::~ESPTimer()
{
// Ignore potential ESP_ERR_INVALID_STATE here to not throw exception.
esp_timer_stop(timer_handle);
esp_timer_delete(timer_handle);
}
void ESPTimer::esp_timer_cb(void *arg)
{
ESPTimer *timer = static_cast<ESPTimer*>(arg);
timer->timeout_cb();
}
} // esp_timer
} // idf
#endif // __cpp_exceptions

View File

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
project(blink_cxx)

View File

@ -0,0 +1,57 @@
# Example: Blink C++ example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates usage of the `GPIO_Output` C++ class in ESP-IDF.
In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option.
This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling.
This is necessary for the C++ APIs.
## How to use example
### Hardware Required
Any ESP32 family development board.
Connect an LED to the corresponding pin (default is pin 4). If the board has a normal LED already, you can use the pin number to which that one is connected.
Development boards with an RGB LED that only has one data line like the ESP32-C3-DevKitC-02 and ESP32-C3-DevKitM-1 will not work. In this case, please connect an external normal LED to the chosen pin.
### Configure the project
```
idf.py menuconfig
```
### Build and Flash
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
...
I (339) cpu_start: Starting scheduler.
I (343) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
LED ON
LED OFF
LED ON
LED OFF
LED ON
LED OFF
LED ON
LED OFF
LED ON
LED OFF
```

View File

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

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
espressif/esp-idf-cxx:
override_path: ../../../
version: "^1.0.0"

View File

@ -0,0 +1,39 @@
/* Blink C++ Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <cstdlib>
#include <thread>
#include "esp_log.h"
#include "gpio_cxx.hpp"
using namespace idf;
using namespace std;
extern "C" void app_main(void)
{
/* The functions of GPIO_Output throws exceptions in case of parameter errors or if there are underlying driver
errors. */
try {
/* This line may throw an exception if the pin number is invalid.
* Alternatively to 4, choose another output-capable pin. */
const GPIO_Output gpio(GPIONum(4));
while (true) {
printf("LED ON\n");
gpio.set_high();
this_thread::sleep_for(std::chrono::seconds(1));
printf("LED OFF\n");
gpio.set_low();
this_thread::sleep_for(std::chrono::seconds(1));
}
} catch (GPIOException &e) {
printf("GPIO exception occurred: %s\n", esp_err_to_name(e.error));
printf("stopping.\n");
}
}

View File

@ -0,0 +1,3 @@
# Enable C++ exceptions and set emergency pool size for exception objects
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024

View File

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
project(esp_event_async_cxx)

View File

@ -0,0 +1,41 @@
# ESP-Event asynchronous example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
## How to use example
### Configure the project
```
idf.py menuconfig
```
* Set serial port under Serial Flasher Options.
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(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
The object is created twice, hence the started Eventloop and finished destruction lines appear twice.
```
NORMAL TESTING...
received event: test/0; data: 47
received event: test/1
TIMEOUT TESTING...
received event: test/0
TIMEOUT for event: test/0
I (10419) ESP Event C++ Async: Finished example
```

View File

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

View File

@ -0,0 +1,111 @@
/* ESP Event 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 "esp_event_cxx.hpp"
#include "esp_event.h"
#include "esp_err.h"
using namespace idf::event;
using namespace std;
ESP_EVENT_DEFINE_BASE(TEST_EVENT_BASE);
const ESPEventID TEST_EVENT_ID_0(0);
const ESPEventID TEST_EVENT_ID_1(1);
ESPEvent TEMPLATE_EVENT_0(TEST_EVENT_BASE, TEST_EVENT_ID_0);
ESPEvent TEMPLATE_EVENT_1(TEST_EVENT_BASE, TEST_EVENT_ID_1);
// We use "normal" static functions here. However, passing std::function types also works with
// ESPEventLoop::register_event() and ESPEventLoop::register_event_timed(), allowing to reference custom data.
static void callback(const ESPEvent &event, void *data)
{
cout << "received event: " << event.base << "/" << event.id;
if (data) {
cout << "; data: " << *(static_cast<int*>(data));
}
cout << endl;
};
static void timeout_callback(const ESPEvent &event)
{
cout << "TIMEOUT for event: " << event.base << "/" << event.id << endl;
};
extern "C" void app_main(void)
{
{
cout << "Normal testing..." << endl;
ESPEventLoop loop;
int data = 47;
int captured_data = 42;
unique_ptr<ESPEventReg> reg_1 = loop.register_event(TEMPLATE_EVENT_0,
[captured_data](const ESPEvent &event, void *data) {
cout << "received event: " << event.base << "/" << event.id;
if (data) {
cout << "; event data: " << *(static_cast<int*>(data));
}
cout << "; handler data: " << captured_data << endl;
});
unique_ptr<ESPEventReg> reg_2;
// Run for 4 seconds...
for (int i = 0; i < 4; i++) {
switch (i) {
case 0:
// will be received
loop.post_event_data(TEMPLATE_EVENT_0, data);
break;
case 1:
// will NOT be received because TEST_EVENT_ID_1 hasn't been registered yet
loop.post_event_data(TEMPLATE_EVENT_1);
break;
case 2:
// register TEST_EVENT_ID_1
reg_2 = loop.register_event(TEMPLATE_EVENT_1, callback);
// will be received
loop.post_event_data(TEMPLATE_EVENT_1);
break;
case 3:
// unregister callback with TEST_EVENT_ID_1 again
reg_2.reset();
// will NOT be received
loop.post_event_data(TEMPLATE_EVENT_1);
break;
}
this_thread::sleep_for(chrono::seconds(1));
}
}
{
cout << endl << "Timeout testing..." << endl;
ESPEventLoop loop;
// Setting timeout and sending event early enough.
unique_ptr<ESPEventRegTimed> timed_reg = loop.register_event_timed(TEMPLATE_EVENT_0,
callback,
chrono::milliseconds(500),
timeout_callback);
loop.post_event_data(TEMPLATE_EVENT_0);
cout << endl;
// Setting timeout and sending event too late.
// Note: the old registration will be properly unregistered by resetting the unique_ptr.
timed_reg = loop.register_event_timed(TEMPLATE_EVENT_0,
callback,
chrono::milliseconds(500),
timeout_callback);
this_thread::sleep_for(chrono::seconds(1));
loop.post_event_data(TEMPLATE_EVENT_0);
}
cout << "Finished example" << endl;
}

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
espressif/esp-idf-cxx:
override_path: ../../../
version: "^1.0.0"

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

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
project(esp_timer_cxx_example)

View File

@ -0,0 +1,47 @@
# Example: ESPTimer C++ class
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates usage of the ESPTimer c++ class in ESP-IDF.
In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option.
This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling.
This is necessary for the C++ APIs.
## How to use example
### Hardware Required
Any ESP32 family development board.
### 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
```
Setting up timer to trigger in 500ms
timeout
Setting up timer to periodically every 200ms
periodic timeout
periodic timeout
periodic timeout
periodic timeout
periodic timeout
Finished
```

View File

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

View File

@ -0,0 +1,39 @@
/* ESP Timer 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 <thread>
#include <chrono>
#include "esp_timer_cxx.hpp"
#include "esp_exception.hpp"
using namespace std;
using namespace idf;
using namespace idf::esp_timer;
extern "C" void app_main(void)
{
try {
printf("Setting up timer to trigger in 500ms\n");
ESPTimer timer([]() { printf("timeout\n"); });
timer.start(chrono::microseconds(200 * 1000));
this_thread::sleep_for(std::chrono::milliseconds(550));
printf("Setting up timer to trigger periodically every 200ms\n");
ESPTimer timer2([]() { printf("periodic timeout\n"); });
timer2.start_periodic(chrono::microseconds(200 * 1000));
this_thread::sleep_for(std::chrono::milliseconds(1050));
} catch (const ESPException &e) {
printf("Exception with error: %d\n", e.error);
}
printf("Finished\n");
}

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
espressif/esp-idf-cxx:
override_path: ../../../
version: "^1.0.0"

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

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
project(simple_i2c_rw_example)

View File

@ -0,0 +1,59 @@
# Example: C++ I2C sensor read for MPU9250
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates usage of C++ exceptions in ESP-IDF. It is the C++ equivalent to the [I2C Simple Example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/i2c/i2c_simple/) which is written in C.
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++ I2C API.
## How to Use This Example
### Hardware Required
To run this example, you should have one ESP32, ESP32-S series or ESP32-C series based development board as well as an MPU9250. MPU9250 is an inertial measurement unit, which contains an accelerometer, gyroscope as well as a magnetometer, for more information about it, you can read the [datasheet of the MPU9250 sensor](https://invensense.tdk.com/wp-content/uploads/2015/02/PS-MPU-9250A-01-v1.1.pdf).
#### Pin Assignment:
**Note:** The following pin assignments are used by default, you can change these in the `menuconfig` .
| | SDA | SCL |
| ---------------- | -------------- | -------------- |
| ESP I2C Master | I2C_MASTER_SDA | I2C_MASTER_SCL |
| MPU9250 Sensor | SDA | SCL |
For the actual default value of `I2C_MASTER_SDA` and `I2C_MASTER_SCL`, see `Example Configuration` in `menuconfig`.
**Note:** There's no need to add external pull-up resistors for SDA/SCL pins, because the driver will enable the internal pull-up resistors.
### 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](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
If the sensor is read correctly:
```bash
I (328) i2c-simple-example: I2C initialized successfully
I (338) i2c-simple-example: WHO_AM_I = 71
I (338) i2c-simple-example: I2C de-initialized successfully
```
If something went wrong:
```
I2C Exception with error: ESP_FAIL (-1)
Couldn't read sensor!
```

View File

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

View File

@ -0,0 +1,19 @@
menu "Example Configuration"
config I2C_MASTER_SCL
int "SCL GPIO Num"
default 6 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2
default 5 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2
default 19 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
help
GPIO number for I2C Master clock line.
config I2C_MASTER_SDA
int "SDA GPIO Num"
default 5 if IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2
default 4 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
help
GPIO number for I2C Master data line.
endmenu

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
espressif/esp-idf-cxx:
override_path: ../../../
version: "^1.0.0"

View File

@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*
* MPU9250 I2C 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 "esp_log.h"
#include "i2c_cxx.hpp"
using namespace std;
using namespace idf;
static const char *TAG = "i2c-cxx-simple-example";
constexpr I2CNumber I2C_MASTER_NUM(I2CNumber::I2C0()); /*!< I2C master i2c port number, the number of i2c peripheral
interfaces available will depend on the chip */
#define I2C_MASTER_SCL_IO SCL_GPIO(CONFIG_I2C_MASTER_SCL) /*!< GPIO number used for I2C master clock */
#define I2C_MASTER_SDA_IO SDA_GPIO(CONFIG_I2C_MASTER_SDA) /*!< GPIO number used for I2C master data */
#define MPU9250_SENSOR_ADDR I2CAddress(0x68) /*!< Slave address of the MPU9250 sensor */
constexpr uint8_t MPU9250_WHO_AM_I_REG_ADDR = 0x75; /*!< Register addresses of the "who am I" register */
extern "C" void app_main(void)
{
try {
// creating master bus
shared_ptr<I2CMaster> master(new I2CMaster(I2C_MASTER_NUM,
I2C_MASTER_SCL_IO,
I2C_MASTER_SDA_IO,
Frequency(400000)));
ESP_LOGI(TAG, "I2C initialized successfully");
// writing the pointer to the WHO_AM_I register to the device
master->sync_write(MPU9250_SENSOR_ADDR, {MPU9250_WHO_AM_I_REG_ADDR});
// reading back the value of WHO_AM_I register which should be 71
vector<uint8_t> data = master->sync_read(MPU9250_SENSOR_ADDR, 2);
ESP_LOGI(TAG, "WHO_AM_I = %X", data[0]);
} catch (const I2CException &e) {
ESP_LOGI(TAG, "I2C Exception with error: %s (0x%X)", e.what(), e.error);
ESP_LOGI(TAG, "Couldn't read sensor!");
}
// The I2CMaster object is de-initialized in its destructor when going out of scope.
ESP_LOGI(TAG, "I2C de-initialized successfully");
}

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

@ -0,0 +1,7 @@
# 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.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
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
Couldn'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,2 @@
idf_component_register(SRCS "simple_spi_rw_example.cpp"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,44 @@
menu "Example Configuration"
config SPI_NUM
int "SPI Num"
default 1 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2
default 2 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
help
The number of the chip's SPI peripheral.
config SPI_CS
int "CS GPIO Num"
default 10 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2
default 23 if IDF_TARGET_ESP32
default 4 if IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2
help
GPIO number for SPI CS line.
config SPI_MOSI
int "MOSI GPIO Num"
default 11 if IDF_TARGET_ESP32C6 || IDF_TARGET_ESP32H2
default 25 if IDF_TARGET_ESP32
default 5 if IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2
help
GPIO number for SPI MOSI line.
config SPI_MISO
int "MISO GPIO Num"
default 0 if IDF_TARGET_ESP32C6
default 12 if IDF_TARGET_ESP32H2
default 26 if IDF_TARGET_ESP32
default 6 if IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2
help
GPIO number for SPI MISO line.
config SPI_SCLK
int "SCLK GPIO Num"
default 1 if IDF_TARGET_ESP32C6
default 22 if IDF_TARGET_ESP32H2
default 27 if IDF_TARGET_ESP32
default 7 if IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C2
help
GPIO number for SPI SCLK line.
endmenu

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
espressif/esp-idf-cxx:
override_path: ../../../
version: "^1.0.0"

View File

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*
* 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 "sdkconfig.h"
#include <thread>
#include "spi_host_cxx.hpp"
using namespace std;
using namespace idf;
static const uint8_t READ_FLAG = 0x80;
static const uint8_t MPU9250_WHO_AM_I_REG_ADDR = 0x75;
namespace {
static const MOSI MOSI_PIN(CONFIG_SPI_MOSI);
static const MISO MISO_PIN(CONFIG_SPI_MISO);
static const SCLK SCLK_PIN(CONFIG_SPI_SCLK);
static const CS CS_PIN(CONFIG_SPI_CS);
}
extern "C" void app_main(void)
{
SPIMaster master(SPINum(1),
MOSI_PIN,
MISO_PIN,
SCLK_PIN);
shared_ptr<SPIDevice> spi_dev = master.create_dev(CS_PIN, 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();
printf("Result of WHO_AM_I register: 0x%02X\n", result[1]);
this_thread::sleep_for(std::chrono::seconds(2));
}

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

@ -0,0 +1,209 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if __cpp_exceptions
#include <array>
#include "driver/gpio.h"
#include "gpio_cxx.hpp"
namespace idf {
#define GPIO_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), GPIOException)
namespace {
#if CONFIG_IDF_TARGET_LINUX
constexpr std::array<uint32_t, 1> INVALID_GPIOS = {24};
#elif CONFIG_IDF_TARGET_ESP32
constexpr std::array<uint32_t, 1> INVALID_GPIOS = {24};
#elif CONFIG_IDF_TARGET_ESP32S2
constexpr std::array<uint32_t, 4> INVALID_GPIOS = {22, 23, 24, 25};
#elif CONFIG_IDF_TARGET_ESP32S3
constexpr std::array<uint32_t, 4> INVALID_GPIOS = {22, 23, 24, 25};
#elif CONFIG_IDF_TARGET_ESP32C3
constexpr std::array<uint32_t, 0> INVALID_GPIOS = {};
#elif CONFIG_IDF_TARGET_ESP32C2
constexpr std::array<uint32_t, 0> INVALID_GPIOS = {};
#elif CONFIG_IDF_TARGET_ESP32C6
constexpr std::array<uint32_t, 0> INVALID_GPIOS = {};
#elif CONFIG_IDF_TARGET_ESP32H2
constexpr std::array<uint32_t, 0> INVALID_GPIOS = {};
#else
#error "No GPIOs defined for the current target"
#endif
}
GPIOException::GPIOException(esp_err_t error) : ESPException(error) { }
esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept
{
if (pin_num >= GPIO_NUM_MAX) {
return ESP_ERR_INVALID_ARG;
}
for (auto num: INVALID_GPIOS)
{
if (pin_num == num) {
return ESP_ERR_INVALID_ARG;
}
}
return ESP_OK;
}
esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept
{
if (strength >= GPIO_DRIVE_CAP_MAX) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
GPIOPullMode GPIOPullMode::FLOATING()
{
return GPIOPullMode(GPIO_FLOATING);
}
GPIOPullMode GPIOPullMode::PULLUP()
{
return GPIOPullMode(GPIO_PULLUP_ONLY);
}
GPIOPullMode GPIOPullMode::PULLDOWN()
{
return GPIOPullMode(GPIO_PULLDOWN_ONLY);
}
GPIOWakeupIntrType GPIOWakeupIntrType::LOW_LEVEL()
{
return GPIOWakeupIntrType(GPIO_INTR_LOW_LEVEL);
}
GPIOWakeupIntrType GPIOWakeupIntrType::HIGH_LEVEL()
{
return GPIOWakeupIntrType(GPIO_INTR_HIGH_LEVEL);
}
GPIODriveStrength GPIODriveStrength::DEFAULT()
{
return MEDIUM();
}
GPIODriveStrength GPIODriveStrength::WEAK()
{
return GPIODriveStrength(GPIO_DRIVE_CAP_0);
}
GPIODriveStrength GPIODriveStrength::LESS_WEAK()
{
return GPIODriveStrength(GPIO_DRIVE_CAP_1);
}
GPIODriveStrength GPIODriveStrength::MEDIUM()
{
return GPIODriveStrength(GPIO_DRIVE_CAP_2);
}
GPIODriveStrength GPIODriveStrength::STRONGEST()
{
return GPIODriveStrength(GPIO_DRIVE_CAP_3);
}
GPIOBase::GPIOBase(GPIONum num) : gpio_num(num)
{
GPIO_CHECK_THROW(gpio_reset_pin(gpio_num.get_value<gpio_num_t>()));
}
void GPIOBase::hold_en()
{
GPIO_CHECK_THROW(gpio_hold_en(gpio_num.get_value<gpio_num_t>()));
}
void GPIOBase::hold_dis()
{
GPIO_CHECK_THROW(gpio_hold_dis(gpio_num.get_value<gpio_num_t>()));
}
void GPIOBase::set_drive_strength(GPIODriveStrength strength)
{
GPIO_CHECK_THROW(gpio_set_drive_capability(gpio_num.get_value<gpio_num_t>(),
strength.get_value<gpio_drive_cap_t>()));
}
GPIO_Output::GPIO_Output(GPIONum num) : GPIOBase(num)
{
GPIO_CHECK_THROW(gpio_set_direction(gpio_num.get_value<gpio_num_t>(), GPIO_MODE_OUTPUT));
}
void GPIO_Output::set_high() const
{
GPIO_CHECK_THROW(gpio_set_level(gpio_num.get_value<gpio_num_t>(), 1));
}
void GPIO_Output::set_low() const
{
GPIO_CHECK_THROW(gpio_set_level(gpio_num.get_value<gpio_num_t>(), 0));
}
GPIODriveStrength GPIOBase::get_drive_strength()
{
gpio_drive_cap_t strength;
GPIO_CHECK_THROW(gpio_get_drive_capability(gpio_num.get_value<gpio_num_t>(), &strength));
return GPIODriveStrength(static_cast<uint32_t>(strength));
}
GPIOInput::GPIOInput(GPIONum num) : GPIOBase(num)
{
GPIO_CHECK_THROW(gpio_set_direction(gpio_num.get_value<gpio_num_t>(), GPIO_MODE_INPUT));
}
GPIOLevel GPIOInput::get_level() const noexcept
{
int level = gpio_get_level(gpio_num.get_value<gpio_num_t>());
if (level) {
return GPIOLevel::HIGH;
} else {
return GPIOLevel::LOW;
}
}
void GPIOInput::set_pull_mode(GPIOPullMode mode) const
{
GPIO_CHECK_THROW(gpio_set_pull_mode(gpio_num.get_value<gpio_num_t>(),
mode.get_value<gpio_pull_mode_t>()));
}
void GPIOInput::wakeup_enable(GPIOWakeupIntrType interrupt_type) const
{
GPIO_CHECK_THROW(gpio_wakeup_enable(gpio_num.get_value<gpio_num_t>(),
interrupt_type.get_value<gpio_int_type_t>()));
}
void GPIOInput::wakeup_disable() const
{
GPIO_CHECK_THROW(gpio_wakeup_disable(gpio_num.get_value<gpio_num_t>()));
}
GPIO_OpenDrain::GPIO_OpenDrain(GPIONum num) : GPIOInput(num)
{
GPIO_CHECK_THROW(gpio_set_direction(gpio_num.get_value<gpio_num_t>(), GPIO_MODE_INPUT_OUTPUT_OD));
}
void GPIO_OpenDrain::set_floating() const
{
GPIO_CHECK_THROW(gpio_set_level(gpio_num.get_value<gpio_num_t>(), 1));
}
void GPIO_OpenDrain::set_low() const
{
GPIO_CHECK_THROW(gpio_set_level(gpio_num.get_value<gpio_num_t>(), 0));
}
}
#endif

View File

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
# Registration of cxx component
list(APPEND EXTRA_COMPONENT_DIRS "../../")
project(test_esp_timer_cxx_host)

View File

@ -0,0 +1,36 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# C++ ESPTimer test on Linux target
This unit test tests basic functionality of the `ESPTimer` class. The test does not use mocks. Instead, it runs the whole implementation of the component on the Linux host. The test framework is CATCH.
## Requirements
* A Linux system
* The usual IDF requirements for Linux system, as described in the [Getting Started Guides](../../../../../../docs/en/get-started/index.rst).
* The host's gcc/g++
This application has been tested on Ubuntu 20.04 with `gcc` version *9.3.0*.
## Build
First, make sure that the target is set to Linux. Run `idf.py --preview set-target linux` if you are not sure. Then do a normal IDF build: `idf.py build`.
## Run
IDF monitor doesn't work yet for Linux. You have to run the app manually:
```bash
build/test_esp_timer_cxx_host.elf
```
## Example Output
Ideally, all tests pass, which is indicated by "All tests passed" in the last line:
```bash
$ build/test_esp_timer_cxx_host.elf
===============================================================================
All tests passed (9 assertions in 11 test cases)
```

View File

@ -0,0 +1,5 @@
idf_component_register(SRCS "esp_timer_test.cpp"
INCLUDE_DIRS
"."
$ENV{IDF_PATH}/tools/catch
PRIV_REQUIRES cmock esp_timer)

View File

@ -0,0 +1,202 @@
/*
* ESP Timer C++ unit tests
*
* 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 <stdexcept>
#include "esp_err.h"
#include "esp_timer_cxx.hpp"
#include "catch.hpp"
extern "C" {
#include "Mockesp_timer.h"
}
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "test";
}
using namespace std;
using namespace idf;
using namespace idf::esp_timer;
struct FixtureException : std::exception {
const char *what() const noexcept override {
return "CMock failed";
}
};
struct TimerCreationFixture {
TimerCreationFixture(bool expect_stop = false) : out_handle(reinterpret_cast<esp_timer_handle_t>(1))
{
if (!TEST_PROTECT()) {
throw FixtureException();
}
esp_timer_create_ExpectAnyArgsAndReturn(ESP_OK);
esp_timer_create_ReturnThruPtr_out_handle(&out_handle);
if (expect_stop) {
esp_timer_stop_ExpectAndReturn(out_handle, ESP_OK); // implementation may always call stop
} else {
esp_timer_stop_IgnoreAndReturn(ESP_OK); // implementation may always call stop
}
esp_timer_delete_ExpectAndReturn(out_handle, ESP_OK);
}
virtual ~TimerCreationFixture()
{
Mockesp_timer_Verify();
}
esp_timer_handle_t out_handle;
};
static void (*trigger_timer_callback)(void *data) = nullptr;
esp_err_t cmock_timer_create_callback(const esp_timer_create_args_t* create_args, esp_timer_handle_t* out_handle, int cmock_num_calls)
{
trigger_timer_callback = create_args->callback;
return ESP_OK;
}
struct TimerCallbackFixture : public TimerCreationFixture {
TimerCallbackFixture(bool expect_stop = false) : TimerCreationFixture(expect_stop)
{
esp_timer_create_AddCallback(cmock_timer_create_callback);
}
~TimerCallbackFixture()
{
trigger_timer_callback = nullptr;
}
};
TEST_CASE("get_time works")
{
esp_timer_get_time_ExpectAndReturn(static_cast<uint64_t>(0xfeeddeadbeef));
CHECK(get_time() == std::chrono::microseconds(0xfeeddeadbeef));
}
TEST_CASE("get_next_alarm works")
{
esp_timer_get_next_alarm_ExpectAndReturn(static_cast<uint64_t>(47u));
CHECK(get_next_alarm() == std::chrono::microseconds(47u));
}
TEST_CASE("ESPTimer null function")
{
CHECK_THROWS_AS(ESPTimer(nullptr), ESPException&);
}
TEST_CASE("ESPTimer empty std::function")
{
function<void()> nothing;
CHECK_THROWS_AS(ESPTimer(nothing, "test"), ESPException&);
}
TEST_CASE("ESPTimer initializes and deletes itself")
{
TimerCreationFixture fix;
function<void()> timer_cb = [&]() { };
ESPTimer(timer_cb, "test");
}
TEST_CASE("ESPTimer start throws on invalid state failure")
{
TimerCreationFixture fix;
esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
CHECK_THROWS_AS(timer.start(chrono::microseconds(5000)), ESPException&);
}
TEST_CASE("ESPTimer start periodically throws on invalid state failure")
{
TimerCreationFixture fix;
esp_timer_start_periodic_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
CHECK_THROWS_AS(timer.start_periodic(chrono::microseconds(5000)), ESPException&);
}
TEST_CASE("ESPTimer stopp throws on invaid state failure")
{
TimerCreationFixture fix;
// Overriding stop part of the fixture
esp_timer_stop_StopIgnore();
esp_timer_stop_IgnoreAndReturn(ESP_ERR_INVALID_STATE);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
CHECK_THROWS_AS(timer.stop(), ESPException&);
}
TEST_CASE("ESPTimer stops in destructor")
{
TimerCreationFixture fix(true);
esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
timer.start(chrono::microseconds(5000));
}
TEST_CASE("ESPTimer stops correctly")
{
TimerCreationFixture fix(true);
esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK);
// Additional stop needed because stop is called in ESPTimer::stop and ~ESPTimer.
esp_timer_stop_ExpectAndReturn(fix.out_handle, ESP_OK);
function<void()> timer_cb = [&]() { };
ESPTimer timer(timer_cb);
timer.start(chrono::microseconds(5000));
timer.stop();
}
TEST_CASE("ESPTimer callback works")
{
TimerCallbackFixture fix;
int flag = 0;
function<void()> timer_cb = [&]() { flag = 47; };
ESPTimer timer(timer_cb);
trigger_timer_callback(&timer);
REQUIRE(trigger_timer_callback != nullptr);
CHECK(flag == 47);
}

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
esp-idf-cxx:
path: ../../../
version: ">=0.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,363 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*
* 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.
*/
#pragma once
#include "catch.hpp"
#include "gpio_cxx.hpp"
#include "driver/spi_master.h"
#include "spi_cxx.hpp"
#include "i2c_cxx.hpp"
extern "C" {
#include "Mockgpio.h"
#include "Mockspi_master.h"
#include "Mockspi_common.h"
#include "Mocki2c.h"
}
static const idf::GPIONum VALID_GPIO(18);
/**
* Exception which is thrown if there is some internal cmock error which results in a
* longjump to the location of a TEST_PROTECT() call.
*
* @note This is a temporary solution until there is a better integration of CATCH into CMock.
* Note also that usually there will be a segfault when cmock fails a second time.
* This means paying attention to the first error message is crucial for removing errors.
*/
class CMockException : public std::exception {
public:
virtual ~CMockException() { }
/**
* @return A reminder to look at the actual cmock log.
*/
virtual const char *what() const noexcept
{
return "CMock encountered an error. Look at the CMock log";
}
};
/**
* Helper macro for setting up a test protect call for CMock.
*
* This macro should be used at the beginning of any test cases
* that uses generated CMock mock functions.
* This is necessary because CMock uses longjmp which screws up C++ stacks and
* also the CATCH mechanisms.
*
* @note This is a temporary solution until there is a better integration of CATCH into CMock.
* Note also that usually there will be a segfault when cmock fails a second time.
* This means paying attention to the first error message is crucial for removing errors.
*/
#define CMOCK_SETUP() \
do { \
if (!TEST_PROTECT()) { \
throw CMockException(); \
} \
} \
while (0)
struct CMockFixture {
CMockFixture()
{
CMOCK_SETUP();
}
~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_value()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(num.get_value()), 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;
};
struct I2CMasterFix {
I2CMasterFix(i2c_port_t port_arg = 0) : i2c_conf(), port(port_arg)
{
i2c_conf.mode = i2c_mode_t::I2C_MODE_MASTER;
i2c_conf.sda_io_num = 2;
i2c_conf.scl_io_num = 1;
i2c_conf.sda_pullup_en = true;
i2c_conf.scl_pullup_en = true;
i2c_conf.master.clk_speed = 400000;
i2c_conf.clk_flags = 0;
i2c_param_config_ExpectWithArrayAndReturn(i2c_port_t(0), &i2c_conf, 1, ESP_OK);
i2c_driver_install_ExpectAndReturn(i2c_port_t(0), i2c_mode_t::I2C_MODE_MASTER, 0, 0, 0, ESP_OK);
i2c_driver_delete_ExpectAndReturn(i2c_port_t(0), ESP_OK);
}
i2c_config_t i2c_conf;
i2c_port_t port;
};
#if CONFIG_SOC_I2C_SUPPORT_SLAVE
struct I2CSlaveFix {
I2CSlaveFix(CreateAnd flags, i2c_port_t port_arg = 0, size_t buffer_size = 64) : i2c_conf(), port(port_arg)
{
if (flags == CreateAnd::SUCCEED) {
i2c_conf.mode = i2c_mode_t::I2C_MODE_SLAVE;
i2c_conf.sda_io_num = 2;
i2c_conf.scl_io_num = 1;
i2c_conf.sda_pullup_en = true;
i2c_conf.scl_pullup_en = true;
i2c_conf.slave.addr_10bit_en = 0;
i2c_conf.slave.slave_addr = 0x47;
i2c_param_config_ExpectWithArrayAndReturn(port, &i2c_conf, 1, ESP_OK);
i2c_driver_install_ExpectAndReturn(port, i2c_mode_t::I2C_MODE_SLAVE, buffer_size, buffer_size, 0, ESP_OK);
i2c_driver_delete_ExpectAndReturn(port, ESP_OK);
} else if (flags == CreateAnd::IGNORE) {
i2c_param_config_IgnoreAndReturn(ESP_OK);
i2c_driver_install_IgnoreAndReturn(ESP_OK);
i2c_driver_delete_IgnoreAndReturn(ESP_OK);
} else {
throw idf::I2CException(ESP_ERR_INVALID_ARG);
}
}
i2c_config_t i2c_conf;
i2c_port_t port;
};
#endif
struct I2CCmdLinkFix
{
I2CCmdLinkFix(uint8_t expected_addr, i2c_rw_t type = I2C_MASTER_WRITE) : dummy_handle(reinterpret_cast<i2c_cmd_handle_t>(0xbeef))
{
i2c_cmd_link_create_ExpectAndReturn(&dummy_handle);
i2c_master_start_ExpectAndReturn(&dummy_handle, ESP_OK);
i2c_master_write_byte_ExpectAndReturn(&dummy_handle, expected_addr << 1 | type, true, ESP_OK);
i2c_cmd_link_delete_Expect(&dummy_handle);
}
i2c_cmd_handle_t dummy_handle;
};

View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.16)
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/")
# Registration of cxx component
list(APPEND EXTRA_COMPONENT_DIRS "../../")
project(test_gpio_cxx_host)

View File

@ -0,0 +1,8 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# Build
`idf.py build` (sdkconfig.defaults sets the linux target by default)
# Run
`build/test_gpio_cxx_host.elf`

View File

@ -0,0 +1,6 @@
idf_component_register(SRCS "gpio_cxx_test.cpp"
INCLUDE_DIRS
"."
"../../fixtures"
$ENV{IDF_PATH}/tools/catch
PRIV_REQUIRES driver cmock)

View File

@ -0,0 +1,403 @@
/*
* GPIO C++ unit tests
*
* 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 "esp_err.h"
#include "freertos/portmacro.h"
#include "gpio_cxx.hpp"
#include "test_fixtures.hpp"
#include "catch.hpp"
extern "C" {
#include "Mockgpio.h"
}
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "test";
}
using namespace std;
using namespace idf;
TEST_CASE("gpio num out of range")
{
CHECK_THROWS_AS(GPIONum(-1), GPIOException&);
CHECK_THROWS_AS(GPIONum(static_cast<uint32_t>(GPIO_NUM_MAX)), GPIOException&);
CHECK_THROWS_AS(GPIONum(24), GPIOException&); // On ESP32, 24 isn't a valid GPIO number
}
TEST_CASE("gpio num operator")
{
GPIONum gpio_num_0(18u);
GPIONum gpio_num_1(18u);
GPIONum gpio_num_2(19u);
CHECK(gpio_num_0 == gpio_num_1);
CHECK(gpio_num_2 != gpio_num_1);
}
TEST_CASE("drive strength out of range")
{
CHECK_THROWS_AS(GPIODriveStrength(-1), GPIOException&);
CHECK_THROWS_AS(GPIODriveStrength(static_cast<uint32_t>(GPIO_DRIVE_CAP_MAX)), GPIOException&);
}
TEST_CASE("drive strength as expected")
{
CHECK(GPIODriveStrength::DEFAULT().get_value() == GPIO_DRIVE_CAP_2);
CHECK(GPIODriveStrength::WEAK().get_value() == GPIO_DRIVE_CAP_0);
CHECK(GPIODriveStrength::LESS_WEAK().get_value() == GPIO_DRIVE_CAP_1);
CHECK(GPIODriveStrength::MEDIUM().get_value() == GPIO_DRIVE_CAP_2);
CHECK(GPIODriveStrength::STRONGEST().get_value() == GPIO_DRIVE_CAP_3);
}
TEST_CASE("pull mode create functions work as expected")
{
CHECK(GPIOPullMode::FLOATING().get_value() == 3);
CHECK(GPIOPullMode::PULLUP().get_value() == 0);
CHECK(GPIOPullMode::PULLDOWN().get_value() == 1);
}
TEST_CASE("GPIOIntrType create functions work as expected")
{
CHECK(GPIOWakeupIntrType::LOW_LEVEL().get_value() == GPIO_INTR_LOW_LEVEL);
CHECK(GPIOWakeupIntrType::HIGH_LEVEL().get_value() == GPIO_INTR_HIGH_LEVEL);
}
TEST_CASE("output resetting pin fails")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&);
Mockgpio_Verify();
}
TEST_CASE("output setting direction fails")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&);
Mockgpio_Verify();
}
TEST_CASE("output constructor sets correct arguments")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), GPIO_MODE_OUTPUT, ESP_OK);
GPIO_Output gpio(VALID_GPIO);
Mockgpio_Verify();
}
TEST_CASE("output set high fails")
{
GPIOFixture fix;
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1, ESP_FAIL);
GPIO_Output gpio(fix.num);
CHECK_THROWS_AS(gpio.set_high(), GPIOException&);
}
TEST_CASE("output set high success")
{
GPIOFixture fix;
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1, ESP_OK);
GPIO_Output gpio(fix.num);
gpio.set_high();
}
TEST_CASE("output set low fails")
{
GPIOFixture fix;
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0, ESP_FAIL);
GPIO_Output gpio(fix.num);
CHECK_THROWS_AS(gpio.set_low(), GPIOException&);
}
TEST_CASE("output set low success")
{
GPIOFixture fix;
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0, ESP_OK);
GPIO_Output gpio(fix.num);
gpio.set_low();
}
TEST_CASE("output set drive strength")
{
GPIOFixture fix(VALID_GPIO);
gpio_set_drive_capability_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_DRIVE_CAP_0, ESP_OK);
GPIO_Output gpio(fix.num);
gpio.set_drive_strength(GPIODriveStrength::WEAK());
}
TEST_CASE("output get drive strength")
{
GPIOFixture fix(VALID_GPIO);
gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3;
gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK);
gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength);
GPIO_Output gpio(fix.num);
CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST());
}
TEST_CASE("GPIOInput setting direction fails")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(GPIOInput gpio(VALID_GPIO), GPIOException&);
Mockgpio_Verify();
}
TEST_CASE("constructor sets correct arguments")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), GPIO_MODE_INPUT, ESP_OK);
GPIOInput gpio(VALID_GPIO);
Mockgpio_Verify();
}
TEST_CASE("get level low")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_get_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0);
GPIOInput gpio(fix.num);
CHECK(gpio.get_level() == GPIOLevel::LOW);
}
TEST_CASE("get level high")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_get_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1);
GPIOInput gpio(fix.num);
CHECK(gpio.get_level() == GPIOLevel::HIGH);
}
TEST_CASE("set pull mode fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_FLOATING, ESP_FAIL);
GPIOInput gpio(fix.num);
CHECK_THROWS_AS(gpio.set_pull_mode(GPIOPullMode::FLOATING()), GPIOException&);
}
TEST_CASE("GPIOInput set pull mode floating")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_FLOATING, ESP_OK);
GPIOInput gpio(fix.num);
gpio.set_pull_mode(GPIOPullMode::FLOATING());
}
TEST_CASE("GPIOInput set pull mode pullup")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_PULLUP_ONLY, ESP_OK);
GPIOInput gpio(fix.num);
gpio.set_pull_mode(GPIOPullMode::PULLUP());
}
TEST_CASE("GPIOInput set pull mode pulldown")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_PULLDOWN_ONLY, ESP_OK);
GPIOInput gpio(fix.num);
gpio.set_pull_mode(GPIOPullMode::PULLDOWN());
}
TEST_CASE("GPIOInput wake up enable fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_wakeup_enable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_INTR_LOW_LEVEL, ESP_FAIL);
GPIOInput gpio(fix.num);
CHECK_THROWS_AS(gpio.wakeup_enable(GPIOWakeupIntrType::LOW_LEVEL()), GPIOException&);
}
TEST_CASE("GPIOInput wake up enable high int")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_wakeup_enable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_INTR_HIGH_LEVEL, ESP_OK);
GPIOInput gpio(fix.num);
gpio.wakeup_enable(GPIOWakeupIntrType::HIGH_LEVEL());
}
TEST_CASE("GPIOInput wake up disable fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_wakeup_disable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), ESP_FAIL);
GPIOInput gpio(fix.num);
CHECK_THROWS_AS(gpio.wakeup_disable(), GPIOException&);
}
TEST_CASE("GPIOInput wake up disable high int")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_wakeup_disable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), ESP_OK);
GPIOInput gpio(fix.num);
gpio.wakeup_disable();
}
TEST_CASE("GPIO_OpenDrain setting direction fails")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(GPIO_OpenDrain gpio(VALID_GPIO), GPIOException&);
Mockgpio_Verify();
}
TEST_CASE("GPIO_OpenDrain constructor sets correct arguments")
{
CMOCK_SETUP();
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()), ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
GPIO_MODE_INPUT,
ESP_OK);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
GPIO_OpenDrain gpio(VALID_GPIO);
Mockgpio_Verify();
}
TEST_CASE("GPIO_OpenDrain set floating fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1, ESP_FAIL);
GPIO_OpenDrain gpio(fix.num);
CHECK_THROWS_AS(gpio.set_floating(), GPIOException&);
}
TEST_CASE("GPIO_OpenDrain set floating success")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 1, ESP_OK);
GPIO_OpenDrain gpio(fix.num);
gpio.set_floating();
}
TEST_CASE("GPIO_OpenDrain set low fails")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0, ESP_FAIL);
GPIO_OpenDrain gpio(fix.num);
CHECK_THROWS_AS(gpio.set_low(), GPIOException&);
}
TEST_CASE("GPIO_OpenDrain set low success")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), 0, ESP_OK);
GPIO_OpenDrain gpio(fix.num);
gpio.set_low();
}
TEST_CASE("GPIO_OpenDrain set drive strength")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_set_drive_capability_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_value()), GPIO_DRIVE_CAP_0, ESP_OK);
GPIO_OpenDrain gpio(fix.num);
gpio.set_drive_strength(GPIODriveStrength::WEAK());
}
TEST_CASE("GPIO_OpenDrain get drive strength")
{
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_value()),
GPIO_MODE_INPUT_OUTPUT_OD,
ESP_OK);
gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3;
gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK);
gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength);
GPIO_OpenDrain gpio(fix.num);
CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST());
}

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
esp-idf-cxx:
path: ../../../
version: ">=0.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,16 @@
cmake_minimum_required(VERSION 3.16)
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/")
# Registration of cxx component
list(APPEND EXTRA_COMPONENT_DIRS "../../")
project(test_i2c_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_i2c_cxx_test.elf`

View File

@ -0,0 +1,11 @@
idf_component_get_property(cpp_component esp-idf-cxx COMPONENT_DIR)
idf_component_register(SRCS "i2c_cxx_test.cpp"
INCLUDE_DIRS
"."
"${cpp_component}/host_test/fixtures"
"${cpp_component}/private_include"
$ENV{IDF_PATH}/tools/catch
PRIV_REQUIRES driver cmock)
target_link_libraries(${COMPONENT_LIB} -lpthread)

View File

@ -0,0 +1,462 @@
/*
* I2C C++ unit tests
*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*
* This example code is in the Public Domain (or CC0 licensed, at your option.)
*
* Unless required by applicable law or agreed to in writing, this
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied.
*/
#define CATCH_CONFIG_MAIN
#include <stdio.h>
#include "unity.h"
#include "freertos/portmacro.h"
#include "driver/i2c.h"
#include "i2c_cxx.hpp"
#include "system_cxx.hpp"
#include "test_fixtures.hpp"
#include "catch.hpp"
extern "C" {
#include "Mocki2c.h"
}
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "host_test error";
}
using namespace std;
using namespace idf;
TEST_CASE("I2CNumber")
{
CMockFixture fix;
CHECK(I2CNumber::I2C0().get_value() == 0);
}
TEST_CASE("I2CAddr")
{
CMockFixture fix;
CHECK_THROWS_AS(I2CAddress(-1), I2CException&);
I2CAddress(0);
I2CAddress(127);
CHECK_THROWS_AS(I2CAddress(128), I2CException&);
I2CAddress addr(47);
CHECK(addr.get_value() == 47);
}
TEST_CASE("I2CMaster parameter configuration fails")
{
CMockFixture fix;
i2c_param_config_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(I2CMaster(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000)), I2CException&);
}
TEST_CASE("I2CMaster driver install failure")
{
CMockFixture fix;
i2c_param_config_ExpectAnyArgsAndReturn(ESP_OK);
i2c_driver_install_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(I2CMaster(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000)), I2CException&);
}
TEST_CASE("I2CMaster success")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CMaster(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
}
TEST_CASE("I2CWrite empty data throws")
{
CMockFixture fix;
std::vector<uint8_t> empty;
CHECK_THROWS_AS(I2CWrite writer(empty), I2CException&);
}
TEST_CASE("I2CRead zero length throws")
{
CMockFixture fix;
std::vector<uint8_t> empty;
CHECK_THROWS_AS(I2CRead reader(0), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at link creation")
{
CMockFixture fix;
i2c_cmd_link_create_ExpectAndReturn(nullptr);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at start")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at write byte")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_IgnoreAndReturn(ESP_OK);
i2c_master_write_byte_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at write")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_IgnoreAndReturn(ESP_OK);
i2c_master_write_byte_IgnoreAndReturn(ESP_OK);
i2c_master_write_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer fails at stop")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_IgnoreAndReturn(ESP_OK);
i2c_master_write_byte_IgnoreAndReturn(ESP_OK);
i2c_master_write_IgnoreAndReturn(ESP_OK);
i2c_master_stop_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CWrite do_transfer execution times out")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_IgnoreAndReturn(&dummy_handle);
i2c_master_start_IgnoreAndReturn(ESP_OK);
i2c_master_write_byte_IgnoreAndReturn(ESP_OK);
i2c_master_write_IgnoreAndReturn(ESP_OK);
i2c_master_stop_IgnoreAndReturn(ESP_OK);
i2c_master_cmd_begin_ExpectAnyArgsAndReturn(ESP_ERR_TIMEOUT);
i2c_cmd_link_delete_Ignore();
I2CWrite writer({47});
CHECK_THROWS_AS(writer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CTransferException&);
}
TEST_CASE("I2CWrite calls driver correctly")
{
CMockFixture fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
uint8_t expected_write [] = {0xAB, 0xBA};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually write the data but for the tests it is enough for now
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
std::vector<uint8_t> WRITE_BYTES = {0xAB, 0xBA};
I2CWrite write(WRITE_BYTES);
write.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47));
}
TEST_CASE("I2CRead do_transfer fails at read")
{
CMockFixture fix;
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
i2c_cmd_link_create_ExpectAndReturn(&dummy_handle);
i2c_master_start_ExpectAnyArgsAndReturn(ESP_OK);
i2c_master_write_byte_ExpectAnyArgsAndReturn(ESP_OK);
i2c_master_read_ExpectAnyArgsAndReturn(ESP_FAIL);
i2c_cmd_link_delete_Ignore();
I2CRead reader(2);
CHECK_THROWS_AS(reader.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47)), I2CException&);
}
TEST_CASE("I2CRead calls driver correctly")
{
CMockFixture fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_READ);
uint8_t READ_DATA [] = {0xAB, 0xBA};
const size_t READ_SIZE = sizeof(READ_DATA);
i2c_master_read_ExpectAndReturn(&cmd_fix.dummy_handle, nullptr, READ_SIZE, i2c_ack_type_t::I2C_MASTER_LAST_NACK, ESP_OK);
i2c_master_read_IgnoreArg_data();
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually read the data but for the tests it is enough for now
i2c_master_read_ReturnArrayThruPtr_data(READ_DATA, READ_SIZE);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CRead reader(READ_SIZE);
std::vector<uint8_t> result = reader.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47));
CHECK(result[0] == 0xAB);
CHECK(result[1] == 0xBA);
}
TEST_CASE("I2CComposed try to read size 0 throws")
{
CMockFixture fix;
I2CComposed composed_transfer;
CHECK_THROWS_AS(composed_transfer.add_read(0), I2CException&);
}
TEST_CASE("I2CComposed try to write empy vector throws")
{
CMockFixture fix;
I2CComposed composed_transfer;
CHECK_THROWS_AS(composed_transfer.add_write({}), I2CException&);
}
TEST_CASE("I2CComposed calls driver correctly")
{
CMockFixture fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
uint8_t expected_write [] = {0x47, 0x48, 0x49};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
uint8_t READ_DATA [] = {0xAB, 0xBA};
const size_t READ_SIZE = sizeof(READ_DATA);
// the write-read transaction with repeated start:
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_start_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_write_byte_ExpectAndReturn(&cmd_fix.dummy_handle, 0x47 << 1 | I2C_MASTER_READ, true, ESP_OK);
i2c_master_read_ExpectAndReturn(&cmd_fix.dummy_handle, nullptr, 2, i2c_ack_type_t::I2C_MASTER_LAST_NACK, ESP_OK);
i2c_master_read_IgnoreArg_data();
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually read the data but for the tests it is enough for now
i2c_master_read_ReturnArrayThruPtr_data(READ_DATA, READ_SIZE);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CComposed composed_transfer;
composed_transfer.add_write({0x47, 0x48, 0x49});
composed_transfer.add_read(READ_SIZE);
vector<vector<uint8_t> > read_result = composed_transfer.do_transfer(I2CNumber::I2C0(), I2CAddress(0x47));
TEST_ASSERT_EQUAL(1, read_result.size());
TEST_ASSERT_EQUAL(READ_SIZE, read_result[0].size());
for (int i = 0; i < READ_SIZE; i++) {
TEST_ASSERT_EQUAL(READ_DATA[i], read_result[0][i]);
}
}
TEST_CASE("I2CWrite transfer calls driver correctly")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
uint8_t expected_write [] = {0xAB, 0xBA};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually write the data but for the tests it is enough for now
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CMaster master(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
std::vector<uint8_t> WRITE_BYTES = {0xAB, 0xBA};
auto writer = make_shared<I2CWrite>(WRITE_BYTES);
master.transfer(I2CAddress(0x47), writer);
}
TEST_CASE("I2CMaster synchronous write")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
uint8_t expected_write [] = {0xAB, 0xBA};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually write the data but for the tests it is enough for now
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CMaster master(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
std::vector<uint8_t> WRITE_BYTES = {0xAB, 0xBA};
master.sync_write(I2CAddress(0x47), WRITE_BYTES);
}
TEST_CASE("I2CMaster synchronous read")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_READ);
uint8_t READ_DATA [] = {0xAB, 0xBA};
const size_t READ_SIZE = sizeof(READ_DATA);
i2c_master_read_ExpectAndReturn(&cmd_fix.dummy_handle, nullptr, READ_SIZE, i2c_ack_type_t::I2C_MASTER_LAST_NACK, ESP_OK);
i2c_master_read_IgnoreArg_data();
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually read the data but for the tests it is enough for now
i2c_master_read_ReturnArrayThruPtr_data(READ_DATA, READ_SIZE);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CMaster master(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
std::vector<uint8_t> result = master.sync_read(I2CAddress(0x47), READ_SIZE);
REQUIRE(result.size() == READ_SIZE);
CHECK(result[0] == 0xAB);
CHECK(result[1] == 0xBA);
}
TEST_CASE("I2CMaster syncronous transfer (read and write)")
{
CMockFixture fix;
I2CMasterFix master_fix;
I2CCmdLinkFix cmd_fix(0x47, I2C_MASTER_WRITE);
i2c_cmd_handle_t dummy_handle = reinterpret_cast<i2c_cmd_handle_t>(0xbeef);
uint8_t expected_write [] = {0x47, 0x48, 0x49};
const size_t WRITE_SIZE = sizeof(expected_write);
const size_t EXPECTED_DATA_LEN = WRITE_SIZE;
uint8_t READ_DATA [] = {0xAB, 0xBA};
const size_t READ_SIZE = sizeof(READ_DATA);
// the write-read transaction with repeated start:
i2c_master_write_ExpectWithArrayAndReturn(&cmd_fix.dummy_handle, expected_write, WRITE_SIZE, EXPECTED_DATA_LEN, true, ESP_OK);
i2c_master_start_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_write_byte_ExpectAndReturn(&cmd_fix.dummy_handle, 0x47 << 1 | I2C_MASTER_READ, true, ESP_OK);
i2c_master_read_ExpectAndReturn(&cmd_fix.dummy_handle, nullptr, 2, i2c_ack_type_t::I2C_MASTER_LAST_NACK, ESP_OK);
i2c_master_read_IgnoreArg_data();
// note that this behavior is not entirely correct, in th real driver, only i2c_master_cmd_begin()
// will actually read the data but for the tests it is enough for now
i2c_master_read_ReturnArrayThruPtr_data(READ_DATA, READ_SIZE);
i2c_master_stop_ExpectAndReturn(&cmd_fix.dummy_handle, ESP_OK);
i2c_master_cmd_begin_ExpectAndReturn(0, &cmd_fix.dummy_handle, 1000 / portTICK_PERIOD_MS, ESP_OK);
I2CMaster master(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), Frequency(400000));
vector<uint8_t> read_result = master.sync_transfer(I2CAddress(0x47), {0x47, 0x48, 0x49}, READ_SIZE);
CHECK(read_result.size() == READ_SIZE);
for (int i = 0; i < READ_SIZE; i++) {
CHECK(read_result[i] == READ_DATA[i]);
}
}
#if SOC_I2C_SUPPORT_SLAVE
TEST_CASE("I2CSlave parameter configuration fails")
{
CMockFixture fix;
i2c_param_config_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(I2CSlave(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), I2CAddress(0x47), 64, 64), I2CException&);
}
TEST_CASE("I2CSlave driver installation fails")
{
CMockFixture fix;
i2c_param_config_IgnoreAndReturn(ESP_OK);
i2c_driver_install_IgnoreAndReturn(ESP_FAIL);
CHECK_THROWS_AS(I2CSlave (I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), I2CAddress(0x47), 64, 64), I2CException&);
}
TEST_CASE("I2CSlave calls driver functions correctly")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::SUCCEED);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(1), SDA_GPIO(2), I2CAddress(0x47), 64, 64);
}
TEST_CASE("I2CSlave write fails")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::IGNORE);
const uint8_t WRITE_BUFFER[] = {0xAB, 0xCD};
const size_t WRITE_BUFFER_LEN = sizeof(WRITE_BUFFER);
i2c_slave_write_buffer_ExpectAnyArgsAndReturn(-1);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(3), SDA_GPIO(4), I2CAddress(0x47), 64, 64);
CHECK(slave.write_raw(WRITE_BUFFER, WRITE_BUFFER_LEN, chrono::milliseconds(0)) == -1);
}
TEST_CASE("I2CSlave write calls driver functions correctly")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::IGNORE);
const uint8_t WRITE_BUFFER[] = {0xAB, 0xCD};
const size_t WRITE_BUFFER_LEN = sizeof(WRITE_BUFFER);
i2c_slave_write_buffer_ExpectWithArrayAndReturn(0,
WRITE_BUFFER,
WRITE_BUFFER_LEN,
WRITE_BUFFER_LEN,
500 / portTICK_PERIOD_MS,
WRITE_BUFFER_LEN);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(3), SDA_GPIO(4), I2CAddress(0x47), 64, 64);
CHECK(slave.write_raw(WRITE_BUFFER, WRITE_BUFFER_LEN, chrono::milliseconds(500)) == WRITE_BUFFER_LEN);
}
TEST_CASE("I2CSlave read fails")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::IGNORE);
const size_t READ_BUFFER_LEN = 2;
uint8_t read_buffer[READ_BUFFER_LEN];
i2c_slave_read_buffer_ExpectAnyArgsAndReturn(-1);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(3), SDA_GPIO(4), I2CAddress(0x47), 64, 64);
CHECK(slave.read_raw(read_buffer, READ_BUFFER_LEN, chrono::milliseconds(0)) == -1);
}
TEST_CASE("I2CSlave read calls driver functions correctly")
{
CMockFixture fix;
I2CSlaveFix slave_fix(CreateAnd::IGNORE);
uint8_t WRITE_BUFFER[] = {0xAB, 0xCD};
const size_t BUFFER_LEN = sizeof(WRITE_BUFFER);
uint8_t read_buffer[BUFFER_LEN];
i2c_slave_read_buffer_ExpectAndReturn(0, read_buffer, BUFFER_LEN, 500 / portTICK_PERIOD_MS, BUFFER_LEN);
i2c_slave_read_buffer_ReturnArrayThruPtr_data(WRITE_BUFFER, BUFFER_LEN);
I2CSlave slave(I2CNumber::I2C0(), SCL_GPIO(3), SDA_GPIO(4), I2CAddress(0x47), 64, 64);
CHECK(slave.read_raw(read_buffer, BUFFER_LEN, chrono::milliseconds(500)) == BUFFER_LEN);
for (size_t i = 0; i < BUFFER_LEN; i++) {
CHECK(read_buffer[i] == WRITE_BUFFER[i]);
}
}
#endif // SOC_I2C_SUPPORT_SLAVE

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
esp-idf-cxx:
path: ../../../
version: ">=0.1"

View File

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

View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.16)
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/")
# Registration of cxx component
list(APPEND EXTRA_COMPONENT_DIRS "../../")
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 esp-idf-cxx COMPONENT_DIR)
idf_component_register(SRCS "spi_cxx_test.cpp"
INCLUDE_DIRS
"."
"../../fixtures"
"${cpp_component}/private_include"
$ENV{IDF_PATH}/tools/catch
PRIV_REQUIRES driver cmock)

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
esp-idf-cxx:
path: ../../../
version: ">=0.1"

View File

@ -0,0 +1,453 @@
/*
* SPI C++ unit tests
*
* 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 "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_value());
MOSI mosi_2(mosi_0);
CHECK(mosi_0 != mosi_1);
CHECK(mosi_2 == mosi_0);
CHECK(mosi_2.get_value() == 18u);
}
TEST_CASE("SPI_DMAConfig valid")
{
CHECK(SPI_DMAConfig::AUTO().get_value() == spi_common_dma_t::SPI_DMA_CH_AUTO);
CHECK(SPI_DMAConfig::DISABLED().get_value() == 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,36 @@
cmake_minimum_required(VERSION 3.16)
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/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/")
# Registration of cxx component
list(APPEND EXTRA_COMPONENT_DIRS "../../")
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 esp-idf-cxx 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
PRIV_REQUIRES driver)
target_compile_options(${COMPONENT_LIB} PUBLIC --coverage)
target_link_libraries(${COMPONENT_LIB} --coverage)

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: ">=5.0"
esp-idf-cxx:
path: ../../../
version: ">=0.1"

View File

@ -0,0 +1,118 @@
/*
* System C++ unit tests
*
* 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);
}
TEST_CASE("CHECK_THROW continues on ESP_OK")
{
esp_err_t error = ESP_OK;
CHECK_THROW(error);
}
TEST_CASE("CHECK_THROW throws")
{
esp_err_t error = ESP_FAIL;
CHECK_THROWS_AS(CHECK_THROW(error), ESPException&);
}
TEST_CASE("ESPException has working what() method")
{
try {
throw ESPException(ESP_FAIL);
} catch (ESPException &e) {
CHECK(strcmp(esp_err_to_name(ESP_FAIL), e.what()) == 0);
}
}

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,288 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifdef __cpp_exceptions
#include "driver/i2c.h"
#include "i2c_cxx.hpp"
using namespace std;
namespace idf {
#define I2C_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), I2CException)
/**
* I2C bus are defined in the header files, let's check that the values are correct
*/
#if SOC_I2C_NUM >= 2
static_assert(I2C_NUM_1 == 1, "I2C_NUM_1 must be equal to 1");
#endif // SOC_I2C_NUM >= 2
esp_err_t check_i2c_num(uint32_t i2c_num) noexcept
{
if (i2c_num >= I2C_NUM_MAX) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
esp_err_t check_i2c_addr(uint32_t addr) noexcept
{
// maximum I2C address currently supported in the C++ classes is 127
if (addr > 0x7f) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
I2CException::I2CException(esp_err_t error) : ESPException(error) { }
I2CTransferException::I2CTransferException(esp_err_t error) : I2CException(error) { }
I2CAddress::I2CAddress(uint8_t addr) : StrongValueComparable<uint8_t> (addr)
{
esp_err_t error = check_i2c_addr(addr);
if (error != ESP_OK) {
throw I2CException(error);
}
}
I2CCommandLink::I2CCommandLink()
{
handle = i2c_cmd_link_create();
if (!handle) {
throw I2CException(ESP_ERR_NO_MEM);
}
}
I2CCommandLink::~I2CCommandLink()
{
i2c_cmd_link_delete(handle);
}
void I2CCommandLink::start()
{
I2C_CHECK_THROW(i2c_master_start(handle));
}
void I2CCommandLink::write(const std::vector<uint8_t> &bytes, bool expect_ack)
{
I2C_CHECK_THROW(i2c_master_write(handle, bytes.data(), bytes.size(), expect_ack));
}
void I2CCommandLink::write_byte(uint8_t byte, bool expect_ack)
{
I2C_CHECK_THROW(i2c_master_write_byte(handle, byte, expect_ack));
}
void I2CCommandLink::read(std::vector<uint8_t> &bytes)
{
I2C_CHECK_THROW(i2c_master_read(handle, bytes.data(), bytes.size(), I2C_MASTER_LAST_NACK));
}
void I2CCommandLink::stop()
{
I2C_CHECK_THROW(i2c_master_stop(handle));
}
void I2CCommandLink::execute_transfer(I2CNumber i2c_num, chrono::milliseconds driver_timeout)
{
esp_err_t err = i2c_master_cmd_begin(i2c_num.get_value<i2c_port_t>(), handle, driver_timeout.count() / portTICK_PERIOD_MS);
if (err != ESP_OK) {
throw I2CTransferException(err);
}
}
I2CBus::I2CBus(I2CNumber i2c_number) : i2c_num(std::move(i2c_number)) { }
I2CBus::~I2CBus() { }
I2CMaster::I2CMaster(I2CNumber i2c_number,
SCL_GPIO scl_gpio,
SDA_GPIO sda_gpio,
Frequency clock_speed,
bool scl_pullup,
bool sda_pullup)
: I2CBus(std::move(i2c_number))
{
i2c_config_t conf = {};
conf.mode = I2C_MODE_MASTER;
conf.scl_io_num = scl_gpio.get_value();
conf.scl_pullup_en = scl_pullup;
conf.sda_io_num = sda_gpio.get_value();
conf.sda_pullup_en = sda_pullup;
conf.master.clk_speed = clock_speed.get_value();
I2C_CHECK_THROW(i2c_param_config(i2c_num.get_value<i2c_port_t>(), &conf));
I2C_CHECK_THROW(i2c_driver_install(i2c_num.get_value<i2c_port_t>(), conf.mode, 0, 0, 0));
}
I2CMaster::~I2CMaster()
{
i2c_driver_delete(i2c_num.get_value<i2c_port_t>());
}
void I2CMaster::sync_write(I2CAddress i2c_addr, const vector<uint8_t> &data)
{
I2CWrite writer(data);
writer.do_transfer(i2c_num, i2c_addr);
}
std::vector<uint8_t> I2CMaster::sync_read(I2CAddress i2c_addr, size_t n_bytes)
{
I2CRead reader(n_bytes);
return reader.do_transfer(i2c_num, i2c_addr);
}
vector<uint8_t> I2CMaster::sync_transfer(I2CAddress i2c_addr,
const std::vector<uint8_t> &write_data,
size_t read_n_bytes)
{
I2CComposed composed_transfer;
composed_transfer.add_write(write_data);
composed_transfer.add_read(read_n_bytes);
return composed_transfer.do_transfer(i2c_num, i2c_addr)[0];
}
#if CONFIG_SOC_I2C_SUPPORT_SLAVE
I2CSlave::I2CSlave(I2CNumber i2c_number,
SCL_GPIO scl_gpio,
SDA_GPIO sda_gpio,
I2CAddress slave_addr,
size_t rx_buf_len,
size_t tx_buf_len,
bool scl_pullup,
bool sda_pullup)
: I2CBus(std::move(i2c_number))
{
i2c_config_t conf = {};
conf.mode = I2C_MODE_SLAVE;
conf.scl_io_num = scl_gpio.get_value();
conf.scl_pullup_en = scl_pullup;
conf.sda_io_num = sda_gpio.get_value();
conf.sda_pullup_en = sda_pullup;
conf.slave.addr_10bit_en = 0;
conf.slave.slave_addr = slave_addr.get_value();
I2C_CHECK_THROW(i2c_param_config(i2c_num.get_value<i2c_port_t>(), &conf));
I2C_CHECK_THROW(i2c_driver_install(i2c_num.get_value<i2c_port_t>(), conf.mode, rx_buf_len, tx_buf_len, 0));
}
I2CSlave::~I2CSlave()
{
i2c_driver_delete(i2c_num.get_value<i2c_port_t>());
}
int I2CSlave::write_raw(const uint8_t *data, size_t data_len, chrono::milliseconds timeout)
{
return i2c_slave_write_buffer(i2c_num.get_value<i2c_port_t>(), data, data_len, (TickType_t) timeout.count() / portTICK_PERIOD_MS);
}
int I2CSlave::read_raw(uint8_t *buffer, size_t buffer_len, chrono::milliseconds timeout)
{
return i2c_slave_read_buffer(i2c_num.get_value<i2c_port_t>(), buffer, buffer_len, (TickType_t) timeout.count() / portTICK_PERIOD_MS);
}
#endif // CONFIG_SOC_I2C_SUPPORT_SLAVE
I2CWrite::I2CWrite(const vector<uint8_t> &bytes, chrono::milliseconds driver_timeout)
: I2CTransfer<void>(driver_timeout), bytes(bytes)
{
if (bytes.empty()) {
throw I2CException(ESP_ERR_INVALID_ARG);
}
}
void I2CWrite::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
handle.start();
handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_WRITE);
handle.write(bytes);
}
void I2CWrite::process_result() { }
I2CRead::I2CRead(size_t size, chrono::milliseconds driver_timeout)
: I2CTransfer<vector<uint8_t> >(driver_timeout), bytes(size)
{
if (size == 0) {
throw I2CException(ESP_ERR_INVALID_ARG);
}
}
void I2CRead::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
handle.start();
handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_READ);
handle.read(bytes);
}
vector<uint8_t> I2CRead::process_result()
{
return bytes;
}
I2CComposed::I2CComposed(chrono::milliseconds driver_timeout)
: I2CTransfer<vector<vector<uint8_t> > >(driver_timeout), transfer_list() { }
void I2CComposed::CompTransferNodeRead::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_READ);
handle.read(bytes);
}
void I2CComposed::CompTransferNodeRead::process_result(std::vector<std::vector<uint8_t> > &read_results)
{
read_results.push_back(bytes);
}
void I2CComposed::CompTransferNodeWrite::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
handle.write_byte(i2c_addr.get_value() << 1 | I2C_MASTER_WRITE);
handle.write(bytes);
}
void I2CComposed::add_read(size_t size)
{
if (!size) {
throw I2CException(ESP_ERR_INVALID_ARG);
}
transfer_list.push_back(make_shared<CompTransferNodeRead>(size));
}
void I2CComposed::add_write(std::vector<uint8_t> bytes)
{
if (bytes.empty()) {
throw I2CException(ESP_ERR_INVALID_ARG);
}
transfer_list.push_back(make_shared<CompTransferNodeWrite>(bytes));
}
void I2CComposed::queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr)
{
for (auto it = transfer_list.begin(); it != transfer_list.end(); it++) {
handle.start();
(*it)->queue_cmd(handle, i2c_addr);
}
}
std::vector<std::vector<uint8_t> > I2CComposed::process_result()
{
std::vector<std::vector<uint8_t> > results;
for (auto it = transfer_list.begin(); it != transfer_list.end(); it++) {
(*it)->process_result(results);
}
return results;
}
} // idf
#endif // __cpp_exceptions

View File

@ -0,0 +1,6 @@
dependencies:
idf:
version: '>=5.0'
description: C++ wrapper classes around ESP IDF components
url: https://github.com/espressif/esp-idf-cxx
version: 1.0.2-beta

View File

@ -0,0 +1,125 @@
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_event.h"
namespace idf {
namespace event {
/**
* Abstract interface for direct calls to esp_event C-API.
* This is generally not intended to be used directly.
* It's main purpose is to provide ESPEventLoop a unified API not dependent on whether the default event loop or a
* custom event loop is used.
* The interface resembles the C-API, have a look there for further documentation.
*/
class ESPEventAPI {
public:
virtual ~ESPEventAPI() { }
virtual esp_err_t handler_register(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_t event_handler,
void* event_handler_arg,
esp_event_handler_instance_t *instance) = 0;
virtual esp_err_t handler_unregister(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_instance_t instance) = 0;
virtual esp_err_t post(esp_event_base_t event_base,
int32_t event_id,
void* event_data,
size_t event_data_size,
TickType_t ticks_to_wait) = 0;
};
/**
* @brief API version with default event loop.
*
* It will direct calls to the default event loop API.
*/
class ESPEventAPIDefault : public ESPEventAPI {
public:
ESPEventAPIDefault();
virtual ~ESPEventAPIDefault();
/**
* Copying would lead to deletion of event loop through destructor.
*/
ESPEventAPIDefault(const ESPEventAPIDefault &o) = delete;
ESPEventAPIDefault& operator=(const ESPEventAPIDefault&) = delete;
esp_err_t handler_register(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_t event_handler,
void* event_handler_arg,
esp_event_handler_instance_t *instance) override;
esp_err_t handler_unregister(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_instance_t instance) override;
esp_err_t post(esp_event_base_t event_base,
int32_t event_id,
void* event_data,
size_t event_data_size,
TickType_t ticks_to_wait) override;
};
/**
* @brief API version with custom event loop.
*
* It will direct calls to the custom event loop API.
* The loop parameters are given in the constructor the same way it's done in esp_event_loop_create() in event.h.
* This class also provides a run method in case the custom event loop was created without its own task.
*/
class ESPEventAPICustom : public ESPEventAPI {
public:
/**
* @param event_loop_args the event loop arguments, refer to esp_event_loop_create() in event.h.
*/
ESPEventAPICustom(const esp_event_loop_args_t &event_loop_args);
virtual ~ESPEventAPICustom();
/**
* Copying would lead to deletion of event loop through destructor.
*/
ESPEventAPICustom(const ESPEventAPICustom &o) = delete;
ESPEventAPICustom& operator=(const ESPEventAPICustom&) = delete;
esp_err_t handler_register(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_t event_handler,
void* event_handler_arg,
esp_event_handler_instance_t *instance) override;
esp_err_t handler_unregister(esp_event_base_t event_base,
int32_t event_id,
esp_event_handler_instance_t instance) override;
esp_err_t post(esp_event_base_t event_base,
int32_t event_id,
void* event_data,
size_t event_data_size,
TickType_t ticks_to_wait) override;
/**
* Run the event loop. The behavior is the same as esp_event_loop_run in esp_event.h.
*/
esp_err_t run(TickType_t ticks_to_run);
private:
esp_event_loop_handle_t event_loop;
};
} // event
} // idf

View File

@ -0,0 +1,347 @@
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cpp_exceptions
#include <functional>
#include <string>
#include <memory>
#include <vector>
#include <utility>
#include <exception>
#include <mutex>
#include <thread>
#include <atomic>
#include <iostream>
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "esp_exception.hpp"
#include "esp_event_api.hpp"
namespace idf {
namespace event {
extern const std::chrono::milliseconds PLATFORM_MAX_DELAY_MS;
const std::chrono::microseconds MIN_TIMEOUT(200);
class EventException : public ESPException {
public:
EventException(esp_err_t error) : ESPException(error) { }
};
/**
* @brief
* Thrown to signal a timeout in EventHandlerSync.
*/
class EventTimeout : public idf::event::EventException {
public:
EventTimeout(esp_err_t error) : EventException(error) { }
};
/**
* @brief
* Event ID wrapper class to make C++ APIs more explicit.
*
* This prevents APIs from taking raw ints as event IDs which are not very expressive and may be
* confused with other parameters of a function.
*/
class ESPEventID {
public:
ESPEventID() : id(0) { }
explicit ESPEventID(int32_t event_id) : id(event_id) { }
ESPEventID(const ESPEventID &rhs) : id(rhs.id) { }
inline bool operator==(const ESPEventID &rhs) const {
return id == rhs.get_id();
}
inline ESPEventID &operator=(const ESPEventID& other) {
id = other.id;
return *this;
}
inline int32_t get_id() const {
return id;
}
friend std::ostream& operator<<(std::ostream& os, const ESPEventID& id);
private:
int32_t id;
};
inline std::ostream& operator<<(std::ostream &os, const ESPEventID& id) {
os << id.id;
return os;
}
/*
* Helper struct to bundle event base and event ID.
*/
struct ESPEvent {
ESPEvent()
: base(nullptr), id() { }
ESPEvent(esp_event_base_t event_base, const ESPEventID &event_id)
: base(event_base), id(event_id) { }
esp_event_base_t base;
ESPEventID id;
};
/**
* Thrown if event registration, i.e. \c register_event() or \c register_event_timed(), fails.
*/
struct ESPEventRegisterException : public EventException {
ESPEventRegisterException(esp_err_t err, const ESPEvent& event)
: EventException(err), esp_event(event) { }
const char *what() const noexcept
{
std::string ret_message = "Event base: " + std::string(esp_event.base)
+ ", Event ID: " + std::to_string(esp_event.id.get_id());
return ret_message.c_str();
}
const ESPEvent esp_event;
};
inline bool operator==(const ESPEvent &lhs, const ESPEvent &rhs)
{
return lhs.base == rhs.base && lhs.id == rhs.id;
}
TickType_t convert_ms_to_ticks(const std::chrono::milliseconds &time);
/**
* Callback-event combination for ESPEventLoop.
*
* Used to bind class-based handler instances to event_handler_hook which is registered into the C-based
* esp event loop.
* It can be used directly, however, the recommended way is to obtain a unique_ptr via ESPEventLoop::register_event().
*/
class ESPEventReg {
public:
/**
* Register the event handler \c cb to handle the events defined by \c ev.
*
* @param cb The handler to be called.
* @param ev The event for which the handler is registered.
* @param api The esp event api implementation.
*/
ESPEventReg(std::function<void(const ESPEvent &, void*)> cb,
const ESPEvent& ev,
std::shared_ptr<ESPEventAPI> api);
/**
* Unregister the event handler.
*/
virtual ~ESPEventReg();
protected:
/**
* This is esp_event's handler, all events registered go through this.
*/
static void event_handler_hook(void *handler_arg,
esp_event_base_t event_base,
int32_t event_id,
void *event_data);
/**
* User event handler.
*/
std::function<void(const ESPEvent &, void*)> cb;
/**
* Helper function to enter the instance's scope from the generic \c event_handler_hook().
*/
virtual void dispatch_event_handling(ESPEvent event, void *event_data);
/**
* Save the event here to be able to un-register from the event loop on destruction.
*/
ESPEvent event;
/**
* This API handle allows different sets of APIs to be applied, e.g. default event loop API and
* custom event loop API.
*/
std::shared_ptr<ESPEventAPI> api;
/**
* Event handler instance from the esp event C API.
*/
esp_event_handler_instance_t instance;
};
/**
* Callback-event combination for ESPEventLoop with builtin timeout.
*
* Used to bind class-based handler instances to event_handler_hook which is registered into the C-based
* esp event loop.
* It can be used directly, however, the recommended way is to obtain a unique_ptr via ESPEventLoop::register_event().
*/
class ESPEventRegTimed : public ESPEventReg {
public:
/**
* Register the event handler \c cb to handle the events as well as a timeout callback in case the event doesn't
* arrive on time.
*
* If the event \c ev is received before \c timeout milliseconds, then the event handler is invoked.
* If no such event is received before \c timeout milliseconds, then the timeout callback is invoked.
* After the timeout or the first occurance of the event, the timer will be deactivated.
* The event handler registration will only be deactivated if the timeout occurs.
* If event handler and timeout occur at the same time, only either the event handler or the timeout callback
* will be invoked.
*
* @param cb The handler to be called.
* @param ev The event for which the handler is registered.
* @param timeout_cb The timeout callback which is called in case there is no event for \c timeout microseconds.
* @param timeout The timeout in microseconds.
* @param api The esp event api implementation.
*/
ESPEventRegTimed(std::function<void(const ESPEvent &, void*)> cb,
const ESPEvent& ev,
std::function<void(const ESPEvent &)> timeout_cb,
const std::chrono::microseconds &timeout,
std::shared_ptr<ESPEventAPI> api);
/**
* Unregister the event handler, stop and delete the timer.
*/
virtual ~ESPEventRegTimed();
protected:
/**
* Helper function to hook directly into esp timer callback.
*/
static void timer_cb_hook(void *arg);
/**
* Helper function to enter the instance's scope from the generic \c event_handler_hook().
*/
void dispatch_event_handling(ESPEvent event, void *event_data) override;
/**
* The timer callback which will be called on timeout.
*/
std::function<void(const ESPEvent &)> timeout_cb;
/**
* Timer used for event timeouts.
*/
esp_timer_handle_t timer;
/**
* This mutex makes sure that a timeout and event callbacks aren't invoked both.
*/
std::mutex timeout_mutex;
};
class ESPEventLoop {
public:
/**
* Creates the ESP default event loop.
*
* @param api the interface to the esp_event api; this determines whether the default event loop is used
* or a custom loop (or just a mock up for tests). May be nullptr, in which case it will created
* here.
*
* @note may throw EventException
*/
ESPEventLoop(std::shared_ptr<ESPEventAPI> api = std::make_shared<ESPEventAPIDefault>());
/**
* Deletes the event loop implementation (depends on \c api).
*/
virtual ~ESPEventLoop();
/**
* Registers a specific handler-event combination to the event loop.
*
* @return a reference to the combination of handler and event which can be used to unregister
* this combination again later on.
*
* @note registering the same event twice will result in unregistering the earlier registered handler.
* @note may throw EventException, ESPEventRegisterException
*/
std::unique_ptr<ESPEventReg> register_event(const ESPEvent &event,
std::function<void(const ESPEvent &, void*)> cb);
/**
* Sets a timeout for event. If the specified event isn't received within timeout,
* timer_cb is called.
*
* @note this is independent from the normal event handling. Hence, registering an event for
* timeout does not interfere with a different client that has registered normally for the
* same event.
*/
std::unique_ptr<ESPEventRegTimed> register_event_timed(const ESPEvent &event,
std::function<void(const ESPEvent &, void*)> cb,
const std::chrono::microseconds &timeout,
std::function<void(const ESPEvent &)> timer_cb);
/**
* Posts an event and corresponding data.
*
* @param event the event to post
* @param event_data The event data. A copy will be made internally and a pointer to the copy will be passed to the
* event handler.
* @param wait_time the maximum wait time the function tries to post the event
*/
template<typename T>
void post_event_data(const ESPEvent &event,
T &event_data,
const std::chrono::milliseconds &wait_time = PLATFORM_MAX_DELAY_MS);
/**
* Posts an event.
*
* No event data will be send. The event handler will receive a nullptr.
*
* @param event the event to post
* @param wait_time the maximum wait time the function tries to post the event
*/
void post_event_data(const ESPEvent &event,
const std::chrono::milliseconds &wait_time = PLATFORM_MAX_DELAY_MS);
private:
/**
* This API handle allows different sets of APIs to be applied, e.g. default event loop API and
* custom event loop API.
*/
std::shared_ptr<ESPEventAPI> api;
};
template<typename T>
void ESPEventLoop::post_event_data(const ESPEvent &event,
T &event_data,
const std::chrono::milliseconds &wait_time)
{
esp_err_t result = api->post(event.base,
event.id.get_id(),
&event_data,
sizeof(event_data),
convert_ms_to_ticks(wait_time));
if (result != ESP_OK) {
throw ESPException(result);
}
}
} // namespace event
} // namespace idf
#endif // __cpp_exceptions

View File

@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cpp_exceptions
#include "esp_err.h"
#include <exception>
namespace idf {
/**
* @brief
* General exception class for all C++ exceptions in IDF.
*
* All throwing code in IDF should use either this exception directly or a sub-classes.
* An error from the underlying IDF function is mandatory. The idea is to wrap the orignal IDF error code to keep
* the error scheme partially compatible. If an exception occurs in a higher level C++ code not directly wrapping
* IDF functions, an appropriate error code reflecting the cause must be chosen or newly created.
*/
class ESPException : public std::exception {
public:
/**
* @param error Error from underlying IDF functions.
*/
ESPException(esp_err_t error);
virtual ~ESPException() { }
/**
* @return A textual representation of the contained error. This method only wraps \c esp_err_to_name.
*/
virtual const char *what() const noexcept;
/**
* Error from underlying IDF functions. If an exception occurs in a higher level C++ code not directly wrapping
* IDF functions, an appropriate error code reflecting the cause must be chosen or newly created.
*/
const esp_err_t error;
};
/**
* Convenience macro to help converting IDF error codes into ESPException.
*/
#define CHECK_THROW(error_) \
do { \
esp_err_t result = error_; \
if (result != ESP_OK) throw idf::ESPException(result); \
} while (0)
/**
* Convenience macro to help converting IDF error codes into a child of ESPException.
*/
#define CHECK_THROW_SPECIFIC(error_, exception_type_) \
do { \
esp_err_t result = (error_); \
if (result != ESP_OK) throw idf::exception_type_(result); \
} while (0)
} // namespace idf
#endif // __cpp_exceptions

View File

@ -0,0 +1,137 @@
/*
* SPDX-FileCopyrightText: 2020 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cpp_exceptions
#include <chrono>
#include <functional>
#include <string>
#include "esp_exception.hpp"
#include "esp_timer.h"
namespace idf {
namespace esp_timer {
/**
* @brief Get time since boot
* @return time since \c esp_timer_init() was called (this normally happens early during application startup).
*/
static inline std::chrono::microseconds get_time()
{
return std::chrono::microseconds(esp_timer_get_time());
}
/**
* @brief Get the timestamp when the next timeout is expected to occur
* @return Timestamp of the nearest timer event.
* The timebase is the same as for the values returned by \c get_time().
*/
static inline std::chrono::microseconds get_next_alarm()
{
return std::chrono::microseconds(esp_timer_get_next_alarm());
}
/**
* @brief
* A timer using the esp_timer component which can be started either as one-shot timer or periodically.
*/
class ESPTimer {
public:
/**
* @param timeout_cb The timeout callback.
* @param timer_name The name of the timer (optional). This is for debugging using \c esp_timer_dump().
*/
ESPTimer(std::function<void()> timeout_cb, const std::string &timer_name = "ESPTimer");
/**
* Stop the timer if necessary and delete it.
*/
~ESPTimer();
/**
* Default copy constructor is deleted since one instance of esp_timer_handle_t must not be shared.
*/
ESPTimer(const ESPTimer&) = delete;
/**
* Default copy assignment is deleted since one instance of esp_timer_handle_t must not be shared.
*/
ESPTimer &operator=(const ESPTimer&) = delete;
/**
* @brief Start one-shot timer
*
* Timer should not be running (started) when this function is called.
*
* @param timeout timer timeout, in microseconds relative to the current moment.
*
* @throws ESPException with error ESP_ERR_INVALID_STATE if the timer is already running.
*/
inline void start(std::chrono::microseconds timeout)
{
CHECK_THROW(esp_timer_start_once(timer_handle, timeout.count()));
}
/**
* @brief Start periodic timer
*
* Timer should not be running when this function is called. This function will
* start a timer which will trigger every 'period' microseconds.
*
* Timer should not be running (started) when this function is called.
*
* @param timeout timer timeout, in microseconds relative to the current moment.
*
* @throws ESPException with error ESP_ERR_INVALID_STATE if the timer is already running.
*/
inline void start_periodic(std::chrono::microseconds period)
{
CHECK_THROW(esp_timer_start_periodic(timer_handle, period.count()));
}
/**
* @brief Stop the previously started timer.
*
* This function stops the timer previously started using \c start() or \c start_periodic().
*
* @throws ESPException with error ESP_ERR_INVALID_STATE if the timer has not been started yet.
*/
inline void stop()
{
CHECK_THROW(esp_timer_stop(timer_handle));
}
private:
/**
* Internal callback to hook into esp_timer component.
*/
static void esp_timer_cb(void *arg);
/**
* Timer instance of the underlying esp_event component.
*/
esp_timer_handle_t timer_handle;
/**
* Callback which will be called once the timer triggers.
*/
std::function<void()> timeout_cb;
/**
* Name of the timer, will be passed to the underlying timer framework and is used for debugging.
*/
const std::string name;
};
} // esp_timer
} // idf
#endif // __cpp_exceptions

View File

@ -0,0 +1,381 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#if __cpp_exceptions
#include "esp_exception.hpp"
#include "system_cxx.hpp"
namespace idf {
/**
* @brief Exception thrown for errors in the GPIO C++ API.
*/
struct GPIOException : public ESPException {
/**
* @param error The IDF error representing the error class of the error to throw.
*/
GPIOException(esp_err_t error);
};
/**
* Check if the numeric pin number is valid on the current hardware.
*/
esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept;
/**
* Check if the numeric value of a drive strength is valid on the current hardware.
*/
esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept;
/**
* This is a "Strong Value Type" class for GPIO. The GPIO pin number is checked during construction according to
* the hardware capabilities. This means that any GPIONumBase object is guaranteed to contain a valid GPIO number.
* See also the template class \c StrongValue.
*/
template<typename GPIONumFinalType>
class GPIONumBase final : public StrongValueComparable<uint32_t> {
public:
/**
* @brief Create a numerical pin number representation and make sure it's correct.
*
* @throw GPIOException if the number does not reflect a valid GPIO number on the current hardware.
*/
explicit GPIONumBase(uint32_t pin) : StrongValueComparable<uint32_t>(pin)
{
esp_err_t pin_check_result = check_gpio_pin_num(pin);
if (pin_check_result != ESP_OK) {
throw GPIOException(pin_check_result);
}
}
using StrongValueComparable<uint32_t>::operator==;
using StrongValueComparable<uint32_t>::operator!=;
};
/**
* This is a TAG type whose sole purpose is to create a distinct type from GPIONumBase.
*/
class GPIONumType;
/**
* A GPIO number type used for general GPIOs, in contrast to specific GPIO pins like e.g. SPI_SCLK.
*/
using GPIONum = GPIONumBase<class GPIONumType>;
/**
* Level of an input GPIO.
*/
enum class GPIOLevel {
HIGH,
LOW
};
/**
* Represents a valid pull up configuration for GPIOs.
* It is supposed to resemble an enum type, hence it has static creation methods and a private constructor.
* This class is a "Strong Value Type", see also the template class \c StrongValue for more properties.
*/
class GPIOPullMode final : public StrongValueComparable<uint32_t> {
private:
/**
* Constructor is private since it should only be accessed by the static creation methods.
*
* @param pull_mode A valid numerical respresentation of the pull up configuration. Must be valid!
*/
explicit GPIOPullMode(uint32_t pull_mode) : StrongValueComparable<uint32_t>(pull_mode) { }
public:
/**
* Create a representation of a floating pin configuration.
* For more information, check the driver and HAL files.
*/
static GPIOPullMode FLOATING();
/**
* Create a representation of a pullup configuration.
* For more information, check the driver and HAL files.
*/
static GPIOPullMode PULLUP();
/**
* Create a representation of a pulldown configuration.
* For more information, check the driver and HAL files.
*/
static GPIOPullMode PULLDOWN();
using StrongValueComparable<uint32_t>::operator==;
using StrongValueComparable<uint32_t>::operator!=;
};
/**
* @brief Represents a valid wakup interrupt type for GPIO inputs.
*
* This class is a "Strong Value Type", see also the template class \c StrongValue for more properties.
* It is supposed to resemble an enum type, hence it has static creation methods and a private constructor.
* For a detailed mapping of interrupt types to numeric values, please refer to the driver types and implementation.
*/
class GPIOWakeupIntrType final: public StrongValueComparable<uint32_t> {
private:
/**
* Constructor is private since it should only be accessed by the static creation methods.
*
* @param pull_mode A valid numerical respresentation of a possible interrupt level to wake up. Must be valid!
*/
explicit GPIOWakeupIntrType(uint32_t interrupt_level) : StrongValueComparable<uint32_t>(interrupt_level) { }
public:
static GPIOWakeupIntrType LOW_LEVEL();
static GPIOWakeupIntrType HIGH_LEVEL();
};
/**
* Class representing a valid drive strength for GPIO outputs.
* This class is a "Strong Value Type", see also the template class \c StrongValue for more properties.
* For a detailed mapping for values to drive strengths, please refer to the datasheet of the chip you are using.
* E.g. for ESP32, the values in general are the following:
* - WEAK: 5mA
* - STRONGER: 10mA
* - DEFAULT/MEDIUM: 20mA
* - STRONGEST: 40mA
*/
class GPIODriveStrength final : public StrongValueComparable<uint32_t> {
public:
/**
* @brief Create a drive strength representation and checks its validity.
*
* After construction, this class should have a guaranteed valid strength representation.
*
* @param strength the numeric value mapping for a particular strength. For possible ranges, look at the
* static creation functions below.
* @throws GPIOException if the supplied number is out of the hardware capable range.
*/
explicit GPIODriveStrength(uint32_t strength) : StrongValueComparable<uint32_t>(strength)
{
esp_err_t strength_check_result = check_gpio_drive_strength(strength);
if (strength_check_result != ESP_OK) {
throw GPIOException(strength_check_result);
}
}
/**
* Create a representation of the default drive strength.
* For more information, check the datasheet and driver and HAL files.
*/
static GPIODriveStrength DEFAULT();
/**
* Create a representation of the weak drive strength.
* For more information, check the datasheet and driver and HAL files.
*/
static GPIODriveStrength WEAK();
/**
* Create a representation of the less weak drive strength.
* For more information, check the datasheet and driver and HAL files.
*/
static GPIODriveStrength LESS_WEAK();
/**
* Create a representation of the medium drive strength.
* For more information, check the datasheet and driver and HAL files.
*/
static GPIODriveStrength MEDIUM();
/**
* Create a representation of the strong drive strength.
*/
static GPIODriveStrength STRONGEST();
using StrongValueComparable<uint32_t>::operator==;
using StrongValueComparable<uint32_t>::operator!=;
};
/**
* @brief Implementations commonly used functionality for all GPIO configurations.
*
* Some functionality is only for specific configurations (set and get drive strength) but is necessary here
* to avoid complicating the inheritance hierarchy of the GPIO classes.
* Child classes implementing any GPIO configuration (output, input, etc.) are meant to intherit from this class
* and possibly make some of the functionality publicly available.
*/
class GPIOBase {
protected:
/**
* @brief Construct a GPIO.
*
* This constructor will only reset the GPIO but leaves the actual configuration (input, output, etc.) to
* the sub class.
*
* @param num GPIO pin number of the GPIO to be configured.
*
* @throws GPIOException
* - if the underlying driver function fails
*/
GPIOBase(GPIONum num);
/**
* @brief Enable gpio pad hold function.
*
* The gpio pad hold function works in both input and output modes, but must be output-capable gpios.
* If pad hold enabled:
* in output mode: the output level of the pad will be force locked and can not be changed.
* in input mode: the input value read will not change, regardless the changes of input signal.
*
* @throws GPIOException if the underlying driver function fails.
*/
void hold_en();
/**
* @brief Disable gpio pad hold function.
*
* @throws GPIOException if the underlying driver function fails.
*/
void hold_dis();
/**
* @brief Configure the drive strength of the GPIO.
*
* @param strength The drive strength. Refer to \c GPIODriveStrength for more details.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_drive_strength(GPIODriveStrength strength);
/**
* @brief Return the current drive strength of the GPIO.
*
* @return The currently configured drive strength. Refer to \c GPIODriveStrength for more details.
*
* @throws GPIOException if the underlying driver function fails.
*/
GPIODriveStrength get_drive_strength();
/**
* @brief The number of the configured GPIO pin.
*/
GPIONum gpio_num;
};
/**
* @brief This class represents a GPIO which is configured as output.
*/
class GPIO_Output : public GPIOBase {
public:
/**
* @brief Construct and configure a GPIO as output.
*
* @param num GPIO pin number of the GPIO to be configured.
*
* @throws GPIOException
* - if the underlying driver function fails
*/
GPIO_Output(GPIONum num);
/**
* @brief Set GPIO to high level.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_high() const;
/**
* @brief Set GPIO to low level.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_low() const;
using GPIOBase::set_drive_strength;
using GPIOBase::get_drive_strength;
};
/**
* @brief This class represents a GPIO which is configured as input.
*/
class GPIOInput : public GPIOBase {
public:
/**
* @brief Construct and configure a GPIO as input.
*
* @param num GPIO pin number of the GPIO to be configured.
*
* @throws GPIOException
* - if the underlying driver function fails
*/
GPIOInput(GPIONum num);
/**
* @brief Read the current level of the GPIO.
*
* @return The GPIO current level of the GPIO.
*/
GPIOLevel get_level() const noexcept;
/**
* @brief Configure the internal pull-up and pull-down restors.
*
* @param mode The pull-up/pull-down configuration see \c GPIOPullMode.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_pull_mode(GPIOPullMode mode) const;
/**
* @brief Configure the pin as wake up pin.
*
* @throws GPIOException if the underlying driver function fails.
*/
void wakeup_enable(GPIOWakeupIntrType interrupt_type) const;
/**
* @brief Disable wake up functionality for this pin if it was enabled before.
*
* @throws GPIOException if the underlying driver function fails.
*/
void wakeup_disable() const;
};
/**
* @brief This class represents a GPIO which is configured as open drain output and input at the same time.
*
* This class facilitates bit-banging for single wire protocols.
*/
class GPIO_OpenDrain : public GPIOInput {
public:
/**
* @brief Construct and configure a GPIO as open drain output as well as input.
*
* @param num GPIO pin number of the GPIO to be configured.
*
* @throws GPIOException
* - if the underlying driver function fails
*/
GPIO_OpenDrain(GPIONum num);
/**
* @brief Set GPIO to floating level.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_floating() const;
/**
* @brief Set GPIO to low level.
*
* @throws GPIOException if the underlying driver function fails.
*/
void set_low() const;
using GPIOBase::set_drive_strength;
using GPIOBase::get_drive_strength;
};
}
#endif

View File

@ -0,0 +1,613 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifndef __cpp_exceptions
#error I2C class can only be used when __cpp_exceptions is enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
#endif
#include <exception>
#include <memory>
#include <chrono>
#include <vector>
#include <list>
#include <future>
#include "sdkconfig.h"
#include "esp_exception.hpp"
#include "system_cxx.hpp"
#include "gpio_cxx.hpp"
namespace idf {
/**
* @brief Check if the provided numerical value is a valid I2C address.
*
* @param addr raw number to be checked.
* @return ESP_OK if \c addr is a valid I2C address, otherwise ESP_ERR_INVALID_ARG.
*/
esp_err_t check_i2c_addr(uint32_t addr) noexcept;
struct I2CException : public ESPException {
I2CException(esp_err_t error);
};
struct I2CTransferException : public I2CException {
I2CTransferException(esp_err_t error);
};
/**
* @brief Represents a valid SDA signal pin number.
*/
class SDA_type;
using SDA_GPIO = GPIONumBase<class SDA_type>;
/**
* @brief Represents a valid SCL signal pin number.
*/
class SCL_type;
using SCL_GPIO = GPIONumBase<class SCL_type>;
/**
* @brief Valid representation of I2C number.
*
* A chip can have multiple I2C interfaces, each identified by a bus number, subsequently called I2C number.
* Instances of this class are guaranteed to always contain a valid I2C number.
*/
class I2CNumber : public StrongValueComparable<uint32_t> {
/**
* Construct a valid representation of the I2C number.
*
* This constructor is private because the it can only be accessed but the static creation methods below.
* This guarantees that an instance of I2CNumber always carries a valid number.
*/
constexpr explicit I2CNumber(uint32_t number) : StrongValueComparable<uint32_t>(number) { }
public:
/**
* @brief create an I2C number representing the first I2C bus of the chip.
*/
constexpr static I2CNumber I2C0() {
return I2CNumber(0);
}
#if CONFIG_SOC_I2C_NUM == 2
/**
* @brief create an I2C number representing the second I2C bus of the chip.
*/
constexpr static I2CNumber I2C1() {
return I2CNumber(1);
}
#endif
};
/**
* @brief Valid representation of I2C address.
*
* Instances of this class are guaranteed to always contain a valid I2C address.
*/
class I2CAddress : public StrongValueComparable<uint8_t> {
public:
/**
*
*/
explicit I2CAddress(uint8_t addr);
};
/**
* @brief Low-level I2C transaction descriptor
*
* This class records and decribes a low-level transaction. Users use the methods (except \c execute_transfer)
* to record the transaction. Afterwards, the transaction will be executed by calling \c execute_transfer,
* which blocks until the transaction is finished.
*
* @note This is a low-level class, which only exists due to the underlying I2C driver. All data referenced in
* read and write calls must not be changed and must stay allocated until at least \c execute_transfer
* has finished.
*/
class I2CCommandLink {
public:
/**
* @brief Allocate and create the transaction descriptor.
*/
I2CCommandLink();
/**
* @brief Delete the transaction descriptor, de-allocate all resources.
*/
~I2CCommandLink();
I2CCommandLink(const I2CCommandLink&) = delete;
I2CCommandLink operator=(const I2CCommandLink&) = delete;
/**
* @brief Record a start signal on the I2C bus.
*/
void start();
/**
* @brief Record a write of the vector \c bytes on the I2C bus.
*
* @param[in] bytes The data to be written. Must stay allocated until execute_transfer has finished or
* destructor of this class has been called.
* @param[in] expect_ack If acknowledgement shall be requested after each written byte, pass true,
* otherwise false.
*/
void write(const std::vector<uint8_t> &bytes, bool expect_ack = true);
/**
* @brief Record a one-byte-write on the I2C bus.
*
* @param[in] byte The data to be written. No restrictions apply.
* @param[in] expect_ack If acknowledgement shall be requested after writing the byte, pass true,
* otherwise false.
*/
void write_byte(uint8_t byte, bool expect_ack = true);
/**
* @brief Record a read of the size of vector \c bytes on the I2C bus.
*
* @param[in] bytes Vector with the size of the data to be read (in bytes). Must stay allocated until
* execute_transfer has finished or destructor of this class has been called.
* @param[in] expect_ack If acknowledgement shall be requested after each written byte, pass true,
* otherwise false.
*/
void read(std::vector<uint8_t> &bytes);
/**
* @brief Record a stop command on the I2C bus.
*/
void stop();
/**
* @brief Execute the transaction and wait until it has finished.
*
* This method will issue the transaction with the operations in the order in which they have been recorded
* before.
*
* @param i2c_num I2C bus number on the chip.
* @param driver_timeout Timeout for this transaction.
*/
void execute_transfer(I2CNumber i2c_num, std::chrono::milliseconds driver_timeout);
private:
/**
* @brief Internal driver data.
*/
void *handle;
};
/**
* Superclass for all transfer objects which are accepted by \c I2CMaster::transfer().
*/
template<typename TReturn>
class I2CTransfer {
public:
/**
* Helper typedef to facilitate type resolution during calls to I2CMaster::transfer().
*/
typedef TReturn TransferReturnT;
/**
* @param driver_timeout The timeout used for calls like i2c_master_cmd_begin() to the underlying driver.
*/
I2CTransfer(std::chrono::milliseconds driver_timeout_arg = std::chrono::milliseconds(1000));
virtual ~I2CTransfer() { }
/**
* Do all general parts of the I2C transfer:
* - initialize the command link
* - issuing a start to the command link queue
* - calling \c queue_cmd() in the subclass to issue specific commands to the command link queue
* - issuing a stop to the command link queue
* - executing the assembled commands on the I2C bus
* - calling \c process_result() to process the results of the commands or calling process_exception() if
* there was an exception
* - deleting the command link
* This method is normally called by I2CMaster, but can also be used stand-alone if the bus corresponding to
* \c i2c_num has be initialized.
*
* @throws I2CException for any particular I2C error
*/
TReturn do_transfer(I2CNumber i2c_num, I2CAddress i2c_addr);
protected:
/**
* Implementation of the I2C command is implemented by subclasses.
* The I2C command handle is initialized already at this stage.
* The first action is issuing the I2C address and the read/write bit, depending on what the subclass implements.
* On error, this method has to throw an instance of I2CException.
*
* @param handle the initialized command handle of the I2C driver.
* @param i2c_addr The slave's I2C address.
*
* @throw I2CException
*/
virtual void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) = 0;
/**
* Implementation of whatever neccessary action after successfully sending the I2C command.
* On error, this method has to throw an instance of I2CException.
*
* @throw I2CException
*/
virtual TReturn process_result() = 0;
/**
* For some calls to the underlying driver (e.g. \c i2c_master_cmd_begin() ), this general timeout will be passed.
*/
std::chrono::milliseconds driver_timeout;
};
/**
* @brief Super class for any I2C master or slave
*/
class I2CBus {
public:
/*
* @brief Initialize I2C master bus.
*
* Initialize and install the bus driver in master mode.
*
* @param i2c_number The I2C port number.
*/
explicit I2CBus(I2CNumber i2c_number);
/**
* @brief uninstall the bus driver.
*/
virtual ~I2CBus();
/**
* The I2C port number.
*/
const I2CNumber i2c_num;
};
/**
* @brief Simple I2C Master object
*
* This class provides to ways to issue I2C read and write requests. The simplest way is to use \c sync_write() and
* sync_read() to write and read, respectively. As the name suggests, they block during the whole transfer.
* For all asynchrounous transfers as well as combined write-read transfers, use \c transfer().
*/
class I2CMaster : public I2CBus {
public:
/**
* Initialize and install the driver of an I2C master peripheral.
*
* Initialize and install the bus driver in master mode. Pullups will be enabled for both pins. If you want a
* different configuration, use configure() and i2c_set_pin() of the underlying driver to disable one or both
* pullups.
*
* @param i2c_number The number of the I2C device.
* @param scl_gpio GPIO number of the SCL line.
* @param sda_gpio GPIO number of the SDA line.
* @param clock_speed The master clock speed.
* @param scl_pullup Enable SCL pullup.
* @param sda_pullup Enable SDA pullup.
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
*/
explicit I2CMaster(I2CNumber i2c_number,
SCL_GPIO scl_gpio,
SDA_GPIO sda_gpio,
Frequency clock_speed,
bool scl_pullup = true,
bool sda_pullup = true);
/**
* Delete the driver.
*/
virtual ~I2CMaster();
/**
* Issue an asynchronous I2C transfer which is executed in the background.
*
* This method uses a C++ \c std::future as mechanism to wait for the asynchronous return value.
* The return value can be accessed with \c future::get(). \c future::get() also synchronizes with the thread
* doing the work in the background, i.e. it waits until the return value has been issued.
*
* The actual implementation is delegated to the TransferT object. It will be given the I2C number to work
* with.
*
* Requirements for TransferT: It should implement or imitate the interface of I2CTransfer.
*
* @param xfer The transfer to execute. What the transfer does, depends on it's implementation in
* \c TransferT::do_transfer(). It also determines the future template of this function, indicated by
* \c TransferT::TransferReturnT.
*
* @param i2c_addr The address of the I2C slave device targeted by the transfer.
*
* @return A future with \c TransferT::TransferReturnT. It depends on which template type is used for xfer.
* In case of a simple write (I2CWrite), it's future<void>.
* In case of a read (I2CRead), it's future<vector<uint8_t> > corresponding to the length of the read
* operation.
* If TransferT is a combined transfer with repeated reads (I2CComposed), then the return type is
* future<vector<vector<uint8_t> > >, a vector of results corresponding to the queued read operations.
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
* @throws std::exception for failures in libstdc++
*/
template<typename TransferT>
std::future<typename TransferT::TransferReturnT> transfer(I2CAddress i2c_addr, std::shared_ptr<TransferT> xfer);
/**
* Do a synchronous write.
*
* All data in data will be written to the I2C device with i2c_addr at once.
* This method will block until the I2C write is complete.
*
* @param i2c_addr The address of the I2C device to which the data shall be sent.
* @param data The data to send (size to be sent is determined by data.size()).
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
* @throws std::exception for failures in libstdc++
*/
void sync_write(I2CAddress i2c_addr, const std::vector<uint8_t> &data);
/**
* Do a synchronous read.
* This method will block until the I2C read is complete.
*
* n_bytes bytes of data will be read from the I2C device with i2c_addr.
* While reading the last byte, the master finishes the reading by sending a NACK, before issuing a stop.
*
* @param i2c_addr The address of the I2C device from which to read.
* @param n_bytes The number of bytes to read.
*
* @return the read bytes
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
* @throws std::exception for failures in libstdc++
*/
std::vector<uint8_t> sync_read(I2CAddress i2c_addr, size_t n_bytes);
/**
* Do a simple synchronous write-read transfer.
*
* First, \c write_data will be written to the bus, then a number of \c read_n_bytes will be read from the bus
* with a repeated start condition. The slave device is determined by \c i2c_addr.
* While reading the last byte, the master finishes the reading by sending a NACK, before issuing a stop.
* This method will block until the I2C transfer is complete.
*
* @param i2c_addr The address of the I2C device from which to read.
* @param write_data The data to write to the bus before reading.
* @param read_n_bytes The number of bytes to read.
*
* @return the read bytes
*
* @throws I2CException with the corrsponding esp_err_t return value if something goes wrong
* @throws std::exception for failures in libstdc++
*/
std::vector<uint8_t> sync_transfer(I2CAddress i2c_addr,
const std::vector<uint8_t> &write_data,
size_t read_n_bytes);
};
#if CONFIG_SOC_I2C_SUPPORT_SLAVE
/**
* @brief Responsible for initialization and de-initialization of an I2C slave peripheral.
*/
class I2CSlave : public I2CBus {
public:
/**
* Initialize and install the driver of an I2C slave peripheral.
*
* Initialize and install the bus driver in slave mode. Pullups will be enabled for both pins. If you want a
* different configuration, use configure() and i2c_set_pin() of the underlying driver to disable one or both
* pullups.
*
* @param i2c_number The number of the I2C device.
* @param scl_gpio GPIO number of the SCL line.
* @param sda_gpio GPIO number of the SDA line.
* @param slave_addr The address of the slave device on the I2C bus.
* @param rx_buf_len Receive buffer length.
* @param tx_buf_len Transmit buffer length.
* @param scl_pullup Enable SCL pullup.
* @param sda_pullup Enable SDA pullup.
*
* @throws
*/
I2CSlave(I2CNumber i2c_number,
SCL_GPIO scl_gpio,
SDA_GPIO sda_gpio,
I2CAddress slave_addr,
size_t rx_buf_len,
size_t tx_buf_len,
bool scl_pullup = true,
bool sda_pullup = true);
/**
* Delete the driver.
*/
virtual ~I2CSlave();
/**
* Schedule a raw data write once master is ready.
*
* The data is saved in a buffer, waiting for the master to pick it up.
*/
virtual int write_raw(const uint8_t* data, size_t data_len, std::chrono::milliseconds timeout);
/**
* Read raw data from the bus.
*
* The data is read directly from the buffer. Hence, it has to be written already by master.
*/
virtual int read_raw(uint8_t* buffer, size_t buffer_len, std::chrono::milliseconds timeout);
};
#endif // CONFIG_SOC_I2C_SUPPORT_SLAVE
/**
* Implementation for simple I2C writes, which can be executed by \c I2CMaster::transfer().
* It stores the bytes to be written as a vector.
*/
class I2CWrite : public I2CTransfer<void> {
public:
/**
* @param bytes The bytes which should be written.
* @param driver_timeout The timeout used for calls like i2c_master_cmd_begin() to the underlying driver.
*/
I2CWrite(const std::vector<uint8_t> &bytes, std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
protected:
/**
* Write the address and set the read bit to 0 to issue the address and request a write.
* Then write the bytes.
*
* @param handle The initialized I2C command handle.
* @param i2c_addr The I2C address of the slave.
*/
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
/**
* Set the value of the promise to unblock any callers waiting on it.
*/
void process_result() override;
private:
/**
* The bytes to write.
*/
std::vector<uint8_t> bytes;
};
/**
* Implementation for simple I2C reads, which can be executed by \c I2CMaster::transfer().
* It stores the bytes to be read as a vector to be returned later via a future.
*/
class I2CRead : public I2CTransfer<std::vector<uint8_t> > {
public:
/**
* @param The number of bytes to read.
* @param driver_timeout The timeout used for calls like i2c_master_cmd_begin() to the underlying driver.
*/
I2CRead(size_t size, std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
protected:
/**
* Write the address and set the read bit to 1 to issue the address and request a read.
* Then read into bytes.
*
* @param handle The initialized I2C command handle.
* @param i2c_addr The I2C address of the slave.
*/
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
/**
* Set the return value of the promise to unblock any callers waiting on it.
*/
std::vector<uint8_t> process_result() override;
private:
/**
* The bytes to read.
*/
std::vector<uint8_t> bytes;
};
/**
* This kind of transfer uses repeated start conditions to chain transfers coherently.
* In particular, this can be used to chain multiple single write and read transfers into a single transfer with
* repeated starts as it is commonly done for I2C devices.
* The result is a vector of vectors representing the reads in the order of how they were added using add_read().
*/
class I2CComposed : public I2CTransfer<std::vector<std::vector<uint8_t> > > {
public:
I2CComposed(std::chrono::milliseconds driver_timeout = std::chrono::milliseconds(1000));
/**
* Add a read to the chain.
*
* @param size The size of the read in bytes.
*/
void add_read(size_t size);
/**
* Add a write to the chain.
*
* @param bytes The bytes to write; size will be bytes.size()
*/
void add_write(std::vector<uint8_t> bytes);
protected:
/**
* Write all chained transfers, including a repeated start issue after each but the last transfer.
*
* @param handle The initialized I2C command handle.
* @param i2c_addr The I2C address of the slave.
*/
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
/**
* Creates the vector with the vectors from all reads.
*/
std::vector<std::vector<uint8_t> > process_result() override;
private:
class CompTransferNode {
public:
virtual ~CompTransferNode() { }
virtual void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) = 0;
virtual void process_result(std::vector<std::vector<uint8_t> > &read_results) { }
};
class CompTransferNodeRead : public CompTransferNode {
public:
CompTransferNodeRead(size_t size) : bytes(size) { }
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
void process_result(std::vector<std::vector<uint8_t> > &read_results) override;
private:
std::vector<uint8_t> bytes;
};
class CompTransferNodeWrite : public CompTransferNode {
public:
CompTransferNodeWrite(std::vector<uint8_t> bytes) : bytes(bytes) { }
void queue_cmd(I2CCommandLink &handle, I2CAddress i2c_addr) override;
private:
std::vector<uint8_t> bytes;
};
/**
* The chained transfers.
*/
std::list<std::shared_ptr<CompTransferNode> > transfer_list;
};
template<typename TReturn>
I2CTransfer<TReturn>::I2CTransfer(std::chrono::milliseconds driver_timeout_arg)
: driver_timeout(driver_timeout_arg) { }
template<typename TReturn>
TReturn I2CTransfer<TReturn>::do_transfer(I2CNumber i2c_num, I2CAddress i2c_addr)
{
I2CCommandLink cmd_link;
queue_cmd(cmd_link, i2c_addr);
cmd_link.stop();
cmd_link.execute_transfer(i2c_num, driver_timeout);
return process_result();
}
template<typename TransferT>
std::future<typename TransferT::TransferReturnT> I2CMaster::transfer(I2CAddress i2c_addr, std::shared_ptr<TransferT> xfer)
{
if (!xfer) throw I2CException(ESP_ERR_INVALID_ARG);
return std::async(std::launch::async, [this](std::shared_ptr<TransferT> xfer, I2CAddress i2c_addr) {
return xfer->do_transfer(i2c_num, i2c_addr);
}, xfer, i2c_addr);
}
} // idf

View File

@ -0,0 +1,129 @@
/*
* 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.
*/
explicit 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 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();
};
}
#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

@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/**
* 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".
*/
#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.
* After that, it's trapped and encapsulated inside and cannot be changed anymore.
* Consequently, the API functions receiving a correctly implemented sub class as parameter
* don't need to check it anymore. Only at API boundaries the valid value will be retrieved
* with get_value().
*/
template<typename ValueT>
class StrongValue {
protected:
constexpr StrongValue(ValueT value_arg) : value(value_arg) { }
template<typename RawType = ValueT>
RawType get_value() const {
return static_cast<RawType>(value);
}
private:
ValueT value;
};
/**
* This class adds comparison properties to StrongValue, but no sorting and ordering properties.
*/
template<typename ValueT>
class StrongValueComparable : public StrongValue<ValueT> {
protected:
constexpr StrongValueComparable(ValueT value_arg) : StrongValue<ValueT>(value_arg) { }
public:
using StrongValue<ValueT>::get_value;
bool operator==(const StrongValueComparable<ValueT> &other_gpio) const
{
return get_value() == other_gpio.get_value();
}
bool operator!=(const StrongValueComparable<ValueT> &other_gpio) const
{
return get_value() != other_gpio.get_value();
}
};
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,132 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/**
* 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)
/**
* 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_value();
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_host.get_value<spi_host_device_t>(), &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

Some files were not shown because too many files have changed in this diff Show More