first-commit
This commit is contained in:
commit
ce4d1953fe
17
.vscode/c_cpp_properties.json
vendored
Normal file
17
.vscode/c_cpp_properties.json
vendored
Normal 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
88
.vscode/settings.json
vendored
Normal 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
9
CMakeLists.txt
Normal 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)
|
27
dependencies.lock
Normal file
27
dependencies.lock
Normal 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
|
1
esp_idf_project_configuration.json
Normal file
1
esp_idf_project_configuration.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
3
include/bq4050/CMakeLists.txt
Normal file
3
include/bq4050/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
idf_component_register(SRCS "bq4050.cpp"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES driver)
|
225
include/bq4050/bq4050.cpp
Normal file
225
include/bq4050/bq4050.cpp
Normal 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, ®_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
38
include/bq4050/bq4050.h
Normal 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
5
main/CMakeLists.txt
Normal 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
16
main/idf_component.yml
Normal 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
494
main/main.cpp
Normal 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, ®_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
|
|
@ -0,0 +1 @@
|
|||
2fa63e4e725f0c2d3dd6d99da8a91d404b4b49667088b1be0f336eb9c13ff17f
|
27
managed_components/espressif__esp-idf-cxx/.editorconfig
Normal file
27
managed_components/espressif__esp-idf-cxx/.editorconfig
Normal 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
|
69
managed_components/espressif__esp-idf-cxx/.gitignore
vendored
Normal file
69
managed_components/espressif__esp-idf-cxx/.gitignore
vendored
Normal 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
|
17
managed_components/espressif__esp-idf-cxx/CMakeLists.txt
Normal file
17
managed_components/espressif__esp-idf-cxx/CMakeLists.txt
Normal 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})
|
|
@ -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.
|
202
managed_components/espressif__esp-idf-cxx/LICENSE
Normal file
202
managed_components/espressif__esp-idf-cxx/LICENSE
Normal 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.
|
21
managed_components/espressif__esp-idf-cxx/README.md
Normal file
21
managed_components/espressif__esp-idf-cxx/README.md
Normal 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.
|
115
managed_components/espressif__esp-idf-cxx/esp_event_api.cpp
Normal file
115
managed_components/espressif__esp-idf-cxx/esp_event_api.cpp
Normal 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
|
154
managed_components/espressif__esp-idf-cxx/esp_event_cxx.cpp
Normal file
154
managed_components/espressif__esp-idf-cxx/esp_event_cxx.cpp
Normal 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
|
21
managed_components/espressif__esp-idf-cxx/esp_exception.cpp
Normal file
21
managed_components/espressif__esp-idf-cxx/esp_exception.cpp
Normal 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
|
52
managed_components/espressif__esp-idf-cxx/esp_timer_cxx.cpp
Normal file
52
managed_components/espressif__esp-idf-cxx/esp_timer_cxx.cpp
Normal 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
|
|
@ -0,0 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
set(COMPONENTS main)
|
||||
project(blink_cxx)
|
|
@ -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
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
idf_component_register(SRCS "main.cpp"
|
||||
INCLUDE_DIRS ".")
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
espressif/esp-idf-cxx:
|
||||
override_path: ../../../
|
||||
version: "^1.0.0"
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
idf_component_register(SRCS "esp_event_async_cxx_example.cpp"
|
||||
INCLUDE_DIRS ".")
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
espressif/esp-idf-cxx:
|
||||
override_path: ../../../
|
||||
version: "^1.0.0"
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
idf_component_register(SRCS "esp_timer_example.cpp"
|
||||
INCLUDE_DIRS ".")
|
|
@ -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");
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
espressif/esp-idf-cxx:
|
||||
override_path: ../../../
|
||||
version: "^1.0.0"
|
|
@ -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
|
|
@ -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)
|
|
@ -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!
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
idf_component_register(SRCS "simple_i2c_rw_example.cpp"
|
||||
INCLUDE_DIRS ".")
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
espressif/esp-idf-cxx:
|
||||
override_path: ../../../
|
||||
version: "^1.0.0"
|
|
@ -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");
|
||||
}
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
idf_component_register(SRCS "simple_spi_rw_example.cpp"
|
||||
INCLUDE_DIRS ".")
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
espressif/esp-idf-cxx:
|
||||
override_path: ../../../
|
||||
version: "^1.0.0"
|
|
@ -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));
|
||||
}
|
|
@ -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
|
209
managed_components/espressif__esp-idf-cxx/gpio_cxx.cpp
Normal file
209
managed_components/espressif__esp-idf-cxx/gpio_cxx.cpp
Normal 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
|
|
@ -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)
|
|
@ -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)
|
||||
```
|
|
@ -0,0 +1,5 @@
|
|||
idf_component_register(SRCS "esp_timer_test.cpp"
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
$ENV{IDF_PATH}/tools/catch
|
||||
PRIV_REQUIRES cmock esp_timer)
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
esp-idf-cxx:
|
||||
path: ../../../
|
||||
version: ">=0.1"
|
|
@ -0,0 +1,3 @@
|
|||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
|
@ -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;
|
||||
};
|
|
@ -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)
|
|
@ -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`
|
|
@ -0,0 +1,6 @@
|
|||
idf_component_register(SRCS "gpio_cxx_test.cpp"
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
"../../fixtures"
|
||||
$ENV{IDF_PATH}/tools/catch
|
||||
PRIV_REQUIRES driver cmock)
|
|
@ -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());
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
esp-idf-cxx:
|
||||
path: ../../../
|
||||
version: ">=0.1"
|
|
@ -0,0 +1,3 @@
|
|||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
|
@ -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)
|
|
@ -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`
|
|
@ -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)
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
esp-idf-cxx:
|
||||
path: ../../../
|
||||
version: ">=0.1"
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1,8 @@
|
|||
| Supported Targets | Linux |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# Build
|
||||
`idf.py build` (sdkconfig.defaults sets the linux target by default)
|
||||
|
||||
# Run
|
||||
`build/host_spi_cxx_test.elf`
|
|
@ -0,0 +1,9 @@
|
|||
idf_component_get_property(cpp_component 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)
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
esp-idf-cxx:
|
||||
path: ../../../
|
||||
version: ">=0.1"
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
|
@ -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/"
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
| Supported Targets | Linux |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# Build
|
||||
`idf.py build` (sdkconfig.defaults sets the linux target by default)
|
||||
|
||||
# Run
|
||||
`build/system_cxx_host_test.elf`
|
|
@ -0,0 +1,12 @@
|
|||
idf_component_get_property(cpp_component 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)
|
|
@ -0,0 +1,6 @@
|
|||
dependencies:
|
||||
idf:
|
||||
version: ">=5.0"
|
||||
esp-idf-cxx:
|
||||
path: ../../../
|
||||
version: ">=0.1"
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
288
managed_components/espressif__esp-idf-cxx/i2c_cxx.cpp
Normal file
288
managed_components/espressif__esp-idf-cxx/i2c_cxx.cpp
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
381
managed_components/espressif__esp-idf-cxx/include/gpio_cxx.hpp
Normal file
381
managed_components/espressif__esp-idf-cxx/include/gpio_cxx.hpp
Normal 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
|
613
managed_components/espressif__esp-idf-cxx/include/i2c_cxx.hpp
Normal file
613
managed_components/espressif__esp-idf-cxx/include/i2c_cxx.hpp
Normal 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
|
129
managed_components/espressif__esp-idf-cxx/include/spi_cxx.hpp
Normal file
129
managed_components/espressif__esp-idf-cxx/include/spi_cxx.hpp
Normal 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
|
|
@ -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
|
147
managed_components/espressif__esp-idf-cxx/include/system_cxx.hpp
Normal file
147
managed_components/espressif__esp-idf-cxx/include/system_cxx.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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
|
33
managed_components/espressif__esp-idf-cxx/spi_cxx.cpp
Normal file
33
managed_components/espressif__esp-idf-cxx/spi_cxx.cpp
Normal 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
Loading…
Reference in New Issue
Block a user