first commit

This commit is contained in:
stuce-bot 2025-06-30 20:47:33 +02:00
commit 5893b00dd2
1669 changed files with 1982740 additions and 0 deletions

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 M5Stack Technology CO LTD
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,127 @@
# M5UnitUnified(α リリース)
[English](README.md)
**M5Stack に色々な M5 ユニットをつないで扱う為の新たなアプローチ**
M5Stack シリーズ、 M5Unitシリーズの為のライブラリです。
**注意: 現在αバージョンです**
ご意見ご要望などは Issue または PR にてお願いします。
## 概要
M5UnitUnified は、様々な M5 ユニット製品を統一的に扱うためのライブラリです。
### APIの統一化
各ユニットの外部ライブラリは、それぞれ独自の API デザインがされています。
基本的なAPIを統一し、すべてのユニットが同じように扱えるようにします。
### 接続と通信の統一化
各ユニットの外部ライブラリは独自の通信機能と前提条件が必要です。
前提条件や通信方法を統一化します。
将来的には[M5HAL(Hardware Abstraction Layer)](https://github.com/m5stack/M5HAL)と連携し、各ユニットとの通信を統一する予定です。
### ライセンスの統一化
各ユニットの外部ライブラリのライセンスは様々な物が混在しています。
すべてのM5UnitUnifiedおよび関連ライブラリは、[MITライセンス](LICENSE)下にあります。
## インストール方法
アルファ版ですが Arduino/PlatformIO のライブラリマネージャーに登録されています
### ArduinoIDE
1. ライブリマネージャから使用したいユニットのライブラリ (M5Unit-Foo) を選択してください
依存する M5UnitUnfied 関連のライブラリは自動で DL されます
### PlatformIO
1. platformio.ini の lib\_deps に記述してください
```ini
lib_deps=m5stack/M5Unit-foo ;使用したいユニットのライブラリ
```
依存する M5UnitUnfied 関連のライブラリは自動で DL されます
## 使い方
各ユニットのレポジトリの例も参照のこと。
### Unit コンポーネントを UnitUnified とともに使用する (標準的な使用法)
```cpp
// 他のユニットを使用する場合、インクルードファイル (*1)、インスタンス (*2)、値の取得 (*3) を変更する
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedENV.h> // *1 使用するユニットのヘッダ
m5::unit::UnitUnified Units;
m5::unit::UnitCO2 unit; // *2 使用するユニットのインスタンス
void setup() {
M5.begin();
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
M5.Display.clear(TFT_DARKGREEN);
if (!Units.add(unit, Wire) // unit を UnitUnified マネージャへ追加
|| !Units.begin()) { // ユニットの始動
M5_LOGE("Failed to add/begin");
M5.Display.clear(TFT_RED);
}
}
void loop() {
M5.begin();
Units.update();
if (unit.updated()) {
// *3 ユニット固有の計測値の取得
M5_LOGI("CO2:%u Temp:%f Hum:%f", unit.co2(), unit.temperature(), unit.humidity());
}
}
```
- 標準外の使い方
- [自分でユニットの更新を行う例](examples/Basic/SelfUpdate)
- [UnitUnified マネージャを使用せず、コンポーネントのみでの例](examples/Basic/ComponentOnly)
## サポートされているもの
### サポートされるフレームワーク
- Arduino
ESP-IDF は将来対応予定です。
### サポートされる通信
- TwoWire による I2C 通信
- GPIO (現在は各ユニットに必要な機能のみ搭載)
UART は将来対応予定です。
### サポートされるデバイス,ユニット
[Wiki](https://github.com/m5stack/M5UnitUnified/wiki/)を参照
## Examples
各ユニットのサンプルについては、各ユニットのリポジトリを参照してください。
[このリポジトリにあるサンプル](examples/Basic)は M5UnitUnified 全般のものです。
## Doxygen ドキュメント
[GitHub Pages](https://m5stack.github.io/M5UnitUnified/)
あなたのローカルマシンでドキュメントを生成したい場合は、以下のコマンドを実行してください。
```
bash docs/doxy.sh
```
docs/html の下に出力されます。
Git コミットのハッシュを html に出力したい場合は、 git クローンしたフォルダに対して実行してください。
### 必要な物
- [Doxyegn](https://www.doxygen.nl/)
- [pcregrep](https://formulae.brew.sh/formula/pcre2)
- [Git](https://git-scm.com/)

View file

@ -0,0 +1,129 @@
# M5UnitUnified(α release)
[日本語](README.ja.md)
**A new approach to connect and handle various M5 units in the M5Stack**
Library for M5Stack Series and M5Unit Series
**Notice: Now α version**
Please send your comments and requests to Issue or PR.
## Overview
M5UnitUnified is a library for unified handling of various M5 units products.
### Unified APIs
Each unit's external library has its own API design.
Unify basic APIs so that all units can be handled in the same way.
### Unified connections and communications
Each unit's external library requires its own communication functions and assumptions.
Unify prerequisites and communication methods.
In the future, we plan to work with [M5HAL (Hardware Abstraction Layer)](https://github.com/m5stack/M5HAL) to unified communicatation with each unit.
### Unified Licensing
External library licenses for each unit are mixed.
All M5UnitUnified and related libraries are under the [MIT license](LICENSE).
## How to install
Alpha version, but registered in the Library Manager
### Arduino IDE
1. Using library manager and select the library of the unit you want to use (e.g. M5Unit-GESTURE)
Dependent M5UnitUnfied related libraries will be downloaded automatically.
### PlatformIO
1. Write lib\_deps settings to platformio.ini
```ini
lib_deps= m5stack/M5Unit-foo ; Unit to be used
```
Dependent M5UnitUnfied related libraries will be downloaded automatically.
## How to use
See also examples for each unit repositry too.
### UnitComponent with UnitUnified (Standard usage)
Simple example of the UnitCO2
UnitCO2 is started with default settings in Units.begin(), and loop() print logs measurement data.
```cpp
// If you use other units, change include files(*1), instances(*2), and get values(*3)
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedENV.h> // *1 Include the header of the unit to be used
m5::unit::UnitUnified Units;
m5::unit::UnitCO2 unit; // *2 Instance of the unit
void setup() {
M5.begin();
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
M5.Display.clear(TFT_DARKGREEN);
if (!Units.add(unit, Wire) // Add unit to UnitUnified manager
|| !Units.begin()) { // Begin each unit
M5_LOGE("Failed to add/begin");
M5.Display.clear(TFT_RED);
}
}
void loop() {
M5.begin();
Units.update();
if (unit.updated()) {
// *3 Obtaining unit-specific measurements
M5_LOGI("CO2:%u Temp:%f Hum:%f", unit.co2(), unit.temperature(), unit.humidity());
}
}
```
- Nonstandard usage
- [To update the unit yourself usage example](examples/Basic/SelfUpdate)
- [Using only unit component without UnitUnified manager](examples/Basic/ComponentOnly)
## Supported things
### Supported frameworks
- Arduino
Support ESP-IDF with M5HAL in the future.
### Supported connection
- I2C with TwoWire
- GPIO (Currently only functions required for the units are included)
Support UART in the future.
### Supported devices, units
See also [Wiki](https://github.com/m5stack/M5UnitUnified/wiki/)
## Examples
For exampless of each unit, please refer to the respective unit's repository.
[The examples in this repository](examples/Basic) are for M5UnitUnified in general
## Doxygen document
[GitHub Pages](https://m5stack.github.io/M5UnitUnified/)
If you want to generate documents on your local machine, execute the following command
```
bash docs/doxy.sh
```
It will output it under docs/html
If you want to output Git commit hashes to html, do it for the git cloned folder.
### Required
- [Doxyegn](https://www.doxygen.nl/)
- [pcregrep](https://formulae.brew.sh/formula/pcre2)
- [Git](https://git-scm.com/) (Output commit hash to html)

View file

@ -0,0 +1,41 @@
{
"build": {
"arduino": {
"memory_type": "qio_opi",
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_M5STACK_ATOMS3R",
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "dio",
"mcu": "esp32s3",
"variant": "m5stack_atoms3"
},
"connectivity": [
"bluetooth",
"wifi"
],
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stack AtomS3R",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 460800
},
"url": "https://docs.m5stack.com/en/core/AtomS3R",
"vendor": "M5Stack"
}

View file

@ -0,0 +1,33 @@
{
"build": {
"core": "esp32",
"extra_flags": [
"-DARDUINO_M5STACK_NANOC6"
],
"f_cpu": "160000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"mcu": "esp32c6",
"variant": "esp32c6"
},
"connectivity": [
"wifi"
],
"debug": {
"openocd_target": "esp32c6.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stack NanoC6",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194384,
"require_upload_port": true,
"speed": 460800
},
"url": "https://docs.m5stack.com/en/core/M5NanoC6",
"vendor": "M5Stack"
}

View file

@ -0,0 +1,40 @@
{
"build": {
"arduino":{
"ldscript": "esp32_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DM5STACK_M5STICK_CPLUS2",
"-DBOARD_HAS_PSRAM",
"-mfix-esp32-psram-cache-issue",
"-mfix-esp32-psram-cache-strategy=memw",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "dio",
"mcu": "esp32",
"variant": "m5stick_c"
},
"connectivity": [
"wifi",
"bluetooth"
],
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stick-CPlus2",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 1500000
},
"url": "https://docs.m5stack.com/en/core/M5StickC%20PLUS2",
"vendor": "M5Stack"
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
#!/bin/bash
# Please execute on repositry root
## Get version from library.properties
## Get git rev of HEAD
LIB_VERSION="$(pcregrep -o1 "^\s*version\s*=\s*(\*|\d+(\.\d+){0,3}(\.\*)?)" library.properties)"
#echo ${DOXYGEN_PROJECT_NUMBER}
DOXYGEN_PROJECT_NUMBER="${LIB_VERSION} git rev:$(git rev-parse --short HEAD)" doxygen docs/Doxyfile

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Example of using only unit component without UnitUnified manager
*/
#include "main/ComponentOnly.cpp"

View file

@ -0,0 +1,25 @@
# M5UnitUnified example
## ComponentOnly
### Overview
This is an example of using only the component of unit without the management mechanism of the unit.
The source uses UnitCO2, so if you want to use other units, you will need to modify it accordingly.
### ArduinoIDE
Install each unit's library with the library manager.
### PlatformIO
For convenience of unit testing, the libraries for each unit are registered in lib\_deps.
Use the env of the applicable Core device.
---
### 概要
ユニットの管理機構を使わず、各ユニットのコンポーネントのみで使用する例です。
ソースでは UnitCO2 を使用していますので、他のユニットを使用する場合は適宜変更が必要です。
### ArduinoIDE
各ユニットのライブラリをライブラリマネージャでインストールしてください。
### PlatformIO
ユニットテストの都合上、各ユニット毎のライブラリは lib\_deps に登録されています
該当する Core デバイスの env を使用しください。

View file

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Example of using only unit component without UnitUnified manager
If you use other units, change include files(*1), instances(*2), and get values(*3)
*/
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedENV.h> // *1 Include the header of the unit to be used
m5::unit::UnitCO2 unit; // *2 Instance of the unit
void setup()
{
M5.begin();
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
M5.Display.clear(TFT_DARKGREEN);
if (!unit.assign(Wire) // Assign Wire
|| unit.begin()) { // Begin unit
M5_LOGE("Failed to assign/begin");
M5.Display.clear(TFT_RED);
}
}
void loop()
{
M5.update();
unit.update(); // Explicitly call unit.update() yourself
if (unit.updated()) {
// *3 Obtaining unit-specific measurements
M5_LOGI("CO2:%u Temp:%f Hum:%f", unit.co2(), unit.temperature(), unit.humidity());
}
}

View file

@ -0,0 +1,25 @@
# M5UnitUnified example
## SelfUpdate
### Overview
Here is an example of how to update a unit independently in a separate task.
The source uses UnitCO2, so if you want to use other units, you will need to modify it accordingly.
### ArduinoIDE
Install each unit's library with the library manager.
### PlatformIO
For convenience of unit testing, the libraries for each unit are registered in lib\_deps.
Use the env of the applicable Core device.
---
### 概要
ユニットの更新を別タスクで独自に行う場合の例です。
ソースでは UnitCO2 を使用していますので、他のユニットを使用する場合は適宜変更が必要です。
### ArduinoIDE
各ユニットのライブラリをライブラリマネージャでインストールしてください。
### PlatformIO
ユニットテストの都合上、各ユニット毎のライブラリは lib\_deps に登録されています
該当する Core デバイスの env を使用しください。

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
To update the unit yourself usage example
*/
#include "main/SelfUpdate.cpp"

View file

@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
To update the unit yourself usage example (UnitCO2)
If you use other units, change include files(*1), instances(*2), and get values(*3)
*/
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedENV.h> // *1 Include the header of the unit to be used
m5::unit::UnitUnified Units;
m5::unit::UnitCO2 unit; // *2 Instance of the unit
void update_task(void*)
{
for (;;) {
// If exclusive control is required for access to Wire, insert appropriate controls
unit.update(); // Explicitly call unit.update() yourself
if (unit.updated()) {
// *3 Obtaining unit-specific measurements
M5_LOGI("CO2:%u Temp:%f Hum:%f", unit.co2(), unit.temperature(), unit.humidity());
}
m5::utility::delay(1);
}
}
void setup()
{
M5.begin();
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
auto ccfg = unit.component_config();
ccfg.self_update = true;
unit.component_config(ccfg);
M5.Display.clear(TFT_DARKGREEN);
if (!Units.add(unit, Wire) // Add unit to UnitUnified manager
|| !Units.begin()) { // Begin each unit
M5_LOGE("Failed to add/begin");
M5.Display.clear(TFT_RED);
return;
}
xTaskCreateUniversal(update_task, "update_task", 8192, nullptr, 1, nullptr,
#if defined(CONFIG_IDF_TARGET_ESP32C6)
PRO_CPU_NUM);
#else
APP_CPU_NUM);
#endif
}
void loop()
{
M5.update();
Units.update(); // unit.update() is not called within this function
M5_LOGI("loop");
m5::utility::delay(1000);
}

View file

@ -0,0 +1,24 @@
# M5UnitUnified example
## Simple
### Overview
Here is an example of the most standard usage.
The source uses UnitCO2, so if you want to use other units, you will need to modify it accordingly.
### ArduinoIDE
Install each unit's library with the library manager.
### PlatformIO
For convenience of unit testing, the libraries for each unit are registered in lib\_deps.
Use the env of the applicable Core device.
---
### 概要
最もスタンダードな使い方の例です。
ソースでは UnitCO2 を使用していますので、他のユニットを使用する場合は適宜変更が必要です。
### ArduinoIDE
各ユニットのライブラリをライブラリマネージャでインストールしてください。
### PlatformIO
ユニットテストの都合上、各ユニット毎のライブラリは lib\_deps に登録されています
該当する Core デバイスの env を使用しください。

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Simple usage example
*/
#include "main/Simple.cpp"

View file

@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Simple usage example (UnitCO2)
If you use other units, change include files(*1), instances(*2), and get values(*3)
*/
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedENV.h> // *1 Include the header of the unit to be used
m5::unit::UnitUnified Units;
m5::unit::UnitCO2 unit; // *2 Instance of the unit
void setup()
{
M5.begin();
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
M5.Display.clear(TFT_DARKGREEN);
if (!Units.add(unit, Wire) // Add unit to UnitUnified manager
|| !Units.begin()) { // Begin each unit
M5_LOGE("Failed to add/begin");
M5.Display.clear(TFT_RED);
}
}
void loop()
{
M5.update();
Units.update();
if (unit.updated()) {
// *3 Obtaining unit-specific measurements
M5_LOGI("CO2:%u Temp:%f Hum:%f", unit.co2(), unit.temperature(), unit.humidity());
}
}

View file

@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
For ArduinoIDE
*/
#include "main/MultipleUnits.cpp"

View file

@ -0,0 +1,33 @@
# M5UnitUnified example
## MultipleUnits
### Overview
This is a demo program that was shown at MFT2024 and M5JPTour2024.
It displays information on 4 units (Vmeter, TVOC, ENVIII, HEART) via UnitPaHub2.
The address of UnitPaHub2 must be 0x71 (because it conflicts with ENVIII).
See also https://docs.m5stack.com/en/unit/pahub2
NOTICE: Use Core devices capable of displaying 320x240 pixels
### ArduinoIDE
Install each unit's library with the library manager.
### PlatformIO
For convenience of unit testing, the libraries for each unit are registered in lib\_deps.
Use the env of the applicable Core device.
---
### 概要
MFT2024, M5JPTour2024 にて公開されていたデモプログラムです。
UnitPaHub2 を介して 4つのユニット (Vmeter, TVOC, ENVIII, HEART) の情報を表示します。
UnitPaHub2 のアドレスを 0x71 にする必要があります(ENVIII と衝突するため)
こちらを参照 https://docs.m5stack.com/en/unit/pahub2
注意: 320x240 ピクセルの表示ができる Core デバイスを使用してください。
### ArduinoIDE
各ユニットのライブラリをライブラリマネージャでインストールしてください。
### PlatformIO
ユニットテストの都合上、各ユニット毎のライブラリは lib\_deps に登録されています
該当する Core デバイスの env を使用しください。

View file

@ -0,0 +1,432 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Demonstration of using M5UnitUnified with multiple units
Required Devices:
- Any Core with LCD
- UnitPaHub2
- UnitVmeter : 0
- UnitTVOC : 1
- UnitENV3 : 2
- UnitHEART : 3
*/
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedHUB.h>
#include <M5UnitUnifiedENV.h> // ENVIII,TVOC
#include <M5UnitUnifiedMETER.h> // Vmeter
#include <M5UnitUnifiedHEART.h> // HEART
#include <Wire.h>
#if __has_include(<esp_idf_version.h>)
#include <esp_idf_version.h>
#else // esp_idf_version.h has been introduced in Arduino 1.0.5 (ESP-IDF3.3)
#define ESP_IDF_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(3, 2, 0)
#endif
#include "../src/ui/ui_UnitVmeter.hpp"
#include "../src/ui/ui_UnitTVOC.hpp"
#include "../src/ui/ui_UnitENV3.hpp"
#include "../src/ui/ui_UnitHEART.hpp"
using namespace m5::unit;
namespace {
LGFX_Sprite strips[2];
constexpr uint32_t SPLIT_NUM{4};
int32_t strip_height{};
auto& lcd = M5.Display;
UnitUnified Units;
UnitPaHub2 unitPaHub{0x71}; // NEED changed register to 0x71. see also https://docs.m5stack.com/en/unit/pahub2
UnitVmeter unitVmeter; // channel 0
UnitTVOC unitTVOC; // channel 1
UnitENV3 unitENV3; // channel 2
UnitHEART unitHeart; // channel 3
auto& unitSHT30 = unitENV3.sht30; // alias
auto& unitQMP6988 = unitENV3.qmp6988; // alias
UnitVmeterSmallUI vmeterSmallUI(&lcd);
UnitTVOCSmallUI tvocSmallUI(&lcd);
UnitHEARTSmallUI heartSmallUI(&lcd);
UnitENV3SmallUI env3SmallUI(&lcd);
volatile SemaphoreHandle_t _updateLock{};
constexpr TickType_t ui_take_wait{0};
void prepare()
{
// Each unit settings
{
auto ccfg = unitVmeter.component_config();
ccfg.self_update = true; // Don't update in UnitUnified::update, update explicitly myself.
ccfg.stored_size = 64; // Number of elements in the circular buffer that the instance has.
unitVmeter.component_config(ccfg);
// Setup fro begin
auto cfg = unitVmeter.config();
// 12 ms is used by TVOC, so frequency is reduced
cfg.rate = m5::unit::ads111x::Sampling::Rate64; // 64mps
unitVmeter.config(cfg);
}
{
// Setup fro begin
auto cfg = unitTVOC.config();
cfg.interval = 1000 / 10; // 10 mps
unitTVOC.config(cfg);
auto ccfg = unitTVOC.component_config();
ccfg.self_update = true; // Don't update in UnitUnified::update, update explicitly myself.
ccfg.stored_size = 1000 / cfg.interval; // Number of elements in the circular buffer that the instance has
unitTVOC.component_config(ccfg);
}
{
auto ccfg = unitSHT30.component_config();
ccfg.self_update = true;
ccfg.stored_size = 10; // Number of elements in the circular buffer that the instance has
unitSHT30.component_config(ccfg);
// Setup fro begin
auto cfg = unitSHT30.config();
cfg.mps = m5::unit::sht30::MPS::Ten; // 10 mps
unitSHT30.config(cfg);
}
{
auto ccfg = unitQMP6988.component_config();
ccfg.self_update = true;
ccfg.stored_size = 16; // Number of elements in the circular buffer that the instance has
unitQMP6988.component_config(ccfg);
// Setup fro begin
auto cfg = unitQMP6988.config();
cfg.standby =
m5::unit::qmp6988::Standby::Time50ms; // about 16 mps (Calculated from other parameters and this value
unitQMP6988.config(cfg);
}
{
auto ccfg = unitHeart.component_config();
ccfg.self_update = true; // Don't update in UnitUnified::update, update explicitly myself.
ccfg.stored_size = 160; // Number of elements in the circular buffer that the instance has
unitHeart.component_config(ccfg);
}
// UI
heartSmallUI.construct();
tvocSmallUI.construct();
vmeterSmallUI.construct();
env3SmallUI.construct();
heartSmallUI.monitor().setSamplingRate(m5::unit::max30100::getSamplingRate(unitHeart.config().sampling_rate));
}
// task for Vmeter
void update_vmeter(void*)
{
static uint32_t fcnt{}, mps{}, mcnt{};
static unsigned long start_at{};
for (;;) {
// Exclusive control of TwoWire access and unit updates
xSemaphoreTake(_updateLock, portMAX_DELAY);
unitVmeter.update();
xSemaphoreGive(_updateLock);
// If measurement data is available, acquire it and pass it to the UI.
// If the UI is locked, skip and continue.
if (!unitVmeter.empty()) {
if (vmeterSmallUI.lock(ui_take_wait)) {
mcnt += unitVmeter.available();
while (unitVmeter.available()) {
vmeterSmallUI.push_back(unitVmeter.voltage()); // Gets the oldest data
unitVmeter.discard(); // Discard oldest one
}
vmeterSmallUI.unlock();
}
}
// std::this_thread::yield();
m5::utility::delay(1);
++fcnt;
auto now = m5::utility::millis();
if (now >= start_at + 1000) {
mps = fcnt;
M5_LOGD("Vmeter:%u (%u)", mps, mcnt);
fcnt = mcnt = 0;
start_at = now;
}
}
}
// Task for TVOC
void update_tvoc(void*)
{
static uint32_t fcnt{}, mps{}, mcnt{};
static unsigned long start_at{};
// Waiting for SGP30 to start periodic measurement (15sec)
for (;;) {
if (unitTVOC.canMeasurePeriodic()) {
break;
}
// Exclusive control of TwoWire access and unit updates
xSemaphoreTake(_updateLock, portMAX_DELAY);
unitTVOC.update();
xSemaphoreGive(_updateLock);
m5::utility::delay(1000);
}
for (;;) {
// Exclusive control of TwoWire access and unit updates
xSemaphoreTake(_updateLock, portMAX_DELAY);
// TVOC measuement needs 12ms for read...
unitTVOC.update();
xSemaphoreGive(_updateLock);
// If measurement data is available, acquire it and pass it to the UI.
// If the UI is locked, skip and continue.
if (!unitTVOC.empty()) {
if (tvocSmallUI.lock(ui_take_wait)) {
mcnt += unitTVOC.available();
while (unitTVOC.available()) {
tvocSmallUI.push_back(unitTVOC.co2eq(), unitTVOC.tvoc()); // Gets the oldest data
unitTVOC.discard(); // Discard oldest one
}
tvocSmallUI.unlock();
}
}
// std::this_thread::yield();
m5::utility::delay(1);
++fcnt;
auto now = m5::utility::millis();
if (now >= start_at + 1000) {
mps = fcnt;
M5_LOGD("TVOC:%u (%u)", mps, mcnt);
fcnt = mcnt = 0;
start_at = now;
}
}
}
// Task for SHT30(ENV3)
void update_sht30(void*)
{
static uint32_t fcnt{}, mps{}, mcnt{};
static unsigned long start_at{};
for (;;) {
// Exclusive control of TwoWire access and unit updates
xSemaphoreTake(_updateLock, portMAX_DELAY);
unitSHT30.update();
xSemaphoreGive(_updateLock);
// If measurement data is available, acquire it and pass it to the UI.
// If the UI is locked, skip and continue.
if (!unitSHT30.empty()) {
if (env3SmallUI.lock(ui_take_wait)) {
mcnt += unitSHT30.available();
auto latest = unitSHT30.latest();
env3SmallUI.sht30_push_back(latest.temperature(), latest.humidity()); // Gets the latest data
unitSHT30.flush(); // Discard all data
env3SmallUI.unlock();
}
}
// std::this_thread::yield();
m5::utility::delay(1);
++fcnt;
auto now = m5::utility::millis();
if (now >= start_at + 1000) {
mps = fcnt;
M5_LOGD("SHT30:%u (%u)", mps, mcnt);
fcnt = mcnt = 0;
start_at = now;
}
}
}
// Task for QMP6988(ENV3)
void update_qmp6988(void*)
{
static uint32_t fcnt{}, mps{}, mcnt{};
static unsigned long start_at{};
for (;;) {
// Exclusive control of TwoWire access and unit updates
xSemaphoreTake(_updateLock, portMAX_DELAY);
unitQMP6988.update();
xSemaphoreGive(_updateLock);
// If measurement data is available, acquire it and pass it to the UI.
// If the UI is locked, skip and continue.
if (!unitQMP6988.empty()) {
if (env3SmallUI.lock(ui_take_wait)) {
mcnt += unitQMP6988.available();
auto latest = unitQMP6988.latest();
env3SmallUI.qmp6988_push_back(latest.temperature(), latest.pressure()); // Gets the latest data
unitQMP6988.flush(); // Discard all data
env3SmallUI.unlock();
}
}
// std::this_thread::yield();
m5::utility::delay(1);
++fcnt;
auto now = m5::utility::millis();
if (now >= start_at + 1000) {
mps = fcnt;
M5_LOGD("QMP6988:%u (%u)", mps, mcnt);
fcnt = mcnt = 0;
start_at = now;
}
}
}
// Task for HEART
void update_heart(void*)
{
static uint32_t fcnt{}, mps{}, mcnt{};
static unsigned long start_at{};
for (;;) {
// Exclusive control of TwoWire access and unit updates
xSemaphoreTake(_updateLock, portMAX_DELAY);
unitHeart.update();
xSemaphoreGive(_updateLock);
// If measurement data is available, acquire it and pass it to the UI.
// If the UI is locked, skip and continue.
if (!unitHeart.empty()) {
if (heartSmallUI.lock(ui_take_wait)) {
mcnt += unitHeart.available();
while (unitHeart.available()) {
heartSmallUI.push_back(unitHeart.ir(), unitHeart.red());
unitHeart.discard();
}
heartSmallUI.unlock();
}
}
m5::utility::delay(1);
// std::this_thread::yield();
++fcnt;
auto now = m5::utility::millis();
if (now >= start_at + 1000) {
mps = fcnt;
M5_LOGD("Heart:%u (%u)", mps, mcnt);
fcnt = mcnt = 0;
start_at = now;
}
}
}
void drawUI(LovyanGFX& dst, const uint32_t x, const uint32_t yoffset)
{
vmeterSmallUI.push(&dst, 0, 0 + yoffset);
tvocSmallUI.push(&dst, lcd.width() >> 1, 0 + yoffset);
env3SmallUI.push(&dst, 0, (lcd.height() >> 1) + yoffset);
heartSmallUI.push(&dst, lcd.width() >> 1, (lcd.height() >> 1) + yoffset);
}
} // namespace
void setup()
{
M5.begin();
lcd.startWrite();
lcd.clear(TFT_DARKGRAY);
//
strip_height = lcd.height() / SPLIT_NUM;
uint32_t cnt{};
for (auto&& spr : strips) {
spr.setPsram(false);
spr.setColorDepth(lcd.getColorDepth());
cnt += spr.createSprite(lcd.width(), strip_height) ? 1 : 0;
}
assert(cnt == 2 && "Failed to create sprite");
prepare();
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.begin(pin_num_sda, pin_num_scl, 400000U);
if (!unitPaHub.add(unitVmeter, 0) /* Connect Vmeter to PaHub2 ch:0 */
|| !unitPaHub.add(unitTVOC, 1) /* Connect TVOC to PaHub2 ch:1 */
|| !unitPaHub.add(unitENV3, 2) /* Connect ENV3 to PaHub2 ch:2 */
|| !unitPaHub.add(unitHeart, 3) /* Connect HEART to PaHub2 ch:3 */
|| !Units.add(unitPaHub, Wire) /* Connect PaHub2 to Core */
|| !Units.begin() /* Begin UnitUnified */
) {
M5_LOGE("Failed to begin");
lcd.clear(TFT_RED);
while (true) {
m5::utility::delay(10000);
}
}
lcd.clear(TFT_DARKGREEN);
M5_LOGI("M5UnitUnified has been begun");
M5_LOGI("%s", Units.debugInfo().c_str());
M5_LOGI("CPP %ld", __cplusplus);
M5_LOGI("ESP-IDF Version %d.%d.%d", (ESP_IDF_VERSION >> 16) & 0xFF, (ESP_IDF_VERSION >> 8) & 0xFF,
ESP_IDF_VERSION & 0xFF);
M5_LOGI("BOARD:%X", M5.getBoard());
M5_LOGI("Heap: %u", esp_get_free_heap_size());
//
_updateLock = xSemaphoreCreateBinary();
xSemaphoreGive(_updateLock);
xTaskCreateUniversal(update_vmeter, "vmeter", 8192, nullptr, 2, nullptr, PRO_CPU_NUM);
xTaskCreateUniversal(update_tvoc, "tvoc", 8192, nullptr, 1, nullptr, PRO_CPU_NUM);
xTaskCreateUniversal(update_sht30, "sht30", 8192, nullptr, 1, nullptr, PRO_CPU_NUM);
xTaskCreateUniversal(update_qmp6988, "qmp6988", 8192, nullptr, 1, nullptr, PRO_CPU_NUM);
xTaskCreateUniversal(update_heart, "heart", 8192, nullptr, 1, nullptr, PRO_CPU_NUM);
}
void loop()
{
static uint32_t fpsCnt{}, fps{};
static unsigned long start_at{};
++fpsCnt;
auto now = m5::utility::millis();
if (now >= start_at + 1000) {
fps = fpsCnt;
M5_LOGD("FPS:%u", fps);
fpsCnt = 0;
start_at = now;
}
M5.update();
// All units do their own updates, so there is no need to call for a unit-wide update here.
// xSemaphoreTake(_updateLock, portMAX_DELAY);
// unitTVOC.update();
// xSemaphoreGive(_updateLock);
tvocSmallUI.update();
vmeterSmallUI.update();
heartSmallUI.update();
env3SmallUI.update();
static uint32_t current{};
int32_t offset{};
uint32_t cnt{SPLIT_NUM};
while (cnt--) {
auto& spr = strips[current];
spr.clear();
drawUI(spr, 0, offset);
spr.pushSprite(&lcd, 0, -offset);
current ^= 1;
offset -= strip_height;
}
}

View file

@ -0,0 +1,100 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@brief Configrate time
@file config_time.cpp
*/
#include "config_time.hpp"
#include <M5Unified.h>
#include <M5Utility.h>
#include <WiFi.h>
#include <esp_sntp.h>
#include <random>
#include <algorithm>
namespace {
// NTP server URI
constexpr char ntp0[] = "ntp.nict.jp";
constexpr char ntp1[] = "ntp.jst.mfeed.ad.jp";
constexpr char ntp2[] = "time.cloudflare.com";
const char* ntpURLTable[] = {ntp0, ntp1, ntp2};
constexpr char defaultPosixTZ[] = "JST-9"; // Asia/Tokyo
auto rng = std::default_random_engine{};
} // namespace
bool isEnabledRTC()
{
// Check RTC if exists
if (M5.Rtc.isEnabled()) {
auto dt = M5.Rtc.getDateTime(); // GMT
if (dt.date.year > 2016) {
M5_LOGV("RTC time already set. (GMT) %04d/%02d/%2d %02d:%02d:%02d", dt.date.year, dt.date.month,
dt.date.date, dt.time.hours, dt.time.minutes, dt.time.seconds);
return true;
}
M5_LOGW("RTC is not set to the correct time");
}
return false;
}
void setTimezone(const char* posix_tz)
{
setenv("TZ", posix_tz ? posix_tz : defaultPosixTZ, 1);
tzset();
}
bool configTime(const char* posix_tz, const char* ssid, const char* password)
{
M5_LOGI("Configrate time");
// WiFi connect
if (ssid && password) {
WiFi.begin(ssid, password);
} else {
// Connect to credential in Hardware. (ESP32 saves the last WiFi connection)
WiFi.begin();
}
int32_t retry{10};
while (WiFi.status() != WL_CONNECTED && --retry >= 0) {
M5_LOGI(".");
m5::utility::delay(1000);
}
if (WiFi.status() != WL_CONNECTED) {
M5_LOGE("Failed to connect WiFi");
return false;
}
std::shuffle(std::begin(ntpURLTable), std::end(ntpURLTable), rng);
configTzTime(posix_tz ? posix_tz : defaultPosixTZ, ntpURLTable[0], ntpURLTable[1], ntpURLTable[2]);
// Waiting for time synchronization
retry = 10;
sntp_sync_status_t st{};
while (((st = sntp_get_sync_status()) == SNTP_SYNC_STATUS_RESET) && --retry >= 0) {
M5_LOGI("Time synchronization in progress");
m5::utility::delay(1000);
}
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
std::tm discard{};
if ((st != SNTP_SYNC_STATUS_COMPLETED) || !getLocalTime(&discard, 10 * 1000 /* timeout */)) {
M5_LOGE("Failed to sync time");
return false;
}
// Set RTC if exists
if (M5.Rtc.isEnabled()) {
time_t t = time(nullptr) + 1;
while (t > time(nullptr)) {
/* Nop */
}
M5.Rtc.setDateTime(std::gmtime(&t));
}
return true;
}

View file

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@brief Configrate time by NTP
@file config_time.cpp
*/
#ifndef CONFIG_TIME_HPP
#define CONFIG_TIME_HPP
bool isEnabledRTC();
void setTimezone(const char* posix_tz = nullptr);
bool configTime(const char* ssid = nullptr, const char* password = nullptr);
#endif

View file

@ -0,0 +1,128 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_bar_meter.cpp
@brief Bar meter
*/
#include "ui_bar_meter.hpp"
#include <M5Utility.h>
namespace {
constexpr float table0[] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
constexpr float table1[] = {0.125f, 0.125f * 3, 0.125f * 5, 0.125f * 7};
} // namespace
namespace m5 {
namespace ui {
void BarMeterH::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
{
dst->setClipRect(x, y, width(), height());
const auto t = height() >> 3;
const auto gy = y + t * 6;
const auto gw = width() - 1;
const auto gh0 = t;
const auto gh1 = gh0 >> 1;
dst->fillRect(x, y, width(), height(), TFT_BLUE);
// gauge
dst->drawFastHLine(x, gy, width(), gaugeColor());
for (auto&& e : table0) {
dst->drawFastVLine(x + gw * e, gy - gh0, gh0, gaugeColor());
}
for (auto&& e : table1) {
dst->drawFastVLine(x + gw * e, gy - gh1, gh1, gaugeColor());
}
// needle
dst->drawFastVLine(x + gw * ratio(val), y, height(), needleColor());
dst->clearClipRect();
}
void BarMeterV::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
{
dst->setClipRect(x, y, width(), height());
const auto t = width() >> 3;
const auto gx = x + t * 6;
const auto gh = height() - 1;
const auto gw0 = t;
const auto gw1 = gw0 >> 1;
// gauge
dst->drawFastVLine(gx, y, height(), gaugeColor());
for (auto&& e : table0) {
dst->drawFastHLine(gx - gw0, y + gh * e, gw0, gaugeColor());
}
for (auto&& e : table1) {
dst->drawFastHLine(gx - gw1, y + gh * e, gw1, gaugeColor());
}
// needle
dst->drawFastHLine(x, y + gh * (1.0f - ratio(val)), width(), needleColor());
dst->clearClipRect();
}
void ColorBarMeterH::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
{
const auto w = width();
const auto t = height() >> 3;
const auto h = t << 2;
auto left = x;
auto top = y + height() / 2 - height() / 4;
auto gw = width() - 1;
dst->setClipRect(x, y, width(), height());
// gauge
if (!_crange.empty()) {
dst->fillRect(left, top, w, h, _crange.back().clr);
for (auto it = _crange.crbegin() + 1; it != _crange.crend(); ++it) {
int32_t ww = w * ratio(it->lesseq);
dst->fillRect(left, top, ww, h, it->clr);
}
} else {
dst->fillRect(left, top, w, h, backgroundColor());
}
dst->drawRect(left, top, w, h, gaugeColor());
// needle
dst->drawFastVLine(left + gw * ratio(val), y, height(), needleColor());
dst->clearClipRect();
}
void ColorBarMeterV::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
{
const auto h = height();
const auto w = width() >> 1;
auto top = y;
auto left = x + width() / 2 - width() / 4;
auto gh = height() - 1;
dst->setClipRect(x, y, width(), height());
// gauge
if (!_crange.empty()) {
dst->fillRect(left, top, w, h, _crange.back().clr);
for (auto it = _crange.crbegin() + 1; it != _crange.crend(); ++it) {
int32_t hh = h * ratio(it->lesseq);
dst->fillRect(left, top + height() - hh, w, hh, it->clr);
}
} else {
dst->fillRect(left, top, w, h, backgroundColor());
}
dst->drawRect(left, top, w, h, gaugeColor());
// needle
dst->drawFastHLine(x, y + gh * (1.0f - ratio(val)), width(), needleColor());
dst->clearClipRect();
}
} // namespace ui
} // namespace m5

View file

@ -0,0 +1,109 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_scale_meter.hpp
@brief Bar meter
*/
#ifndef UI_PARTS_BAR_METER_HPP
#define UI_PARTS_BAR_METER_HPP
#include "ui_base.hpp"
#include <initializer_list>
namespace m5 {
namespace ui {
/*!
@class BarMeterH
@brief Horizontal bar meter
*/
class BarMeterH : public Base {
public:
BarMeterH(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid, const int32_t hgt)
: Base(parent, minimum, maximum, wid, hgt)
{
}
virtual ~BarMeterH()
{
}
protected:
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
};
/*!
@class BarMeterH
@brief Vertical bar meter
*/
class BarMeterV : public Base {
public:
BarMeterV(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid, const int32_t hgt)
: Base(parent, minimum, maximum, wid, hgt)
{
}
virtual ~BarMeterV()
{
}
protected:
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
};
struct ColorRange {
int32_t lesseq;
m5gfx::rgb565_t clr;
};
class ColorBarMeterH : public Base {
public:
ColorBarMeterH(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid,
const int32_t hgt, std::initializer_list<ColorRange> init = {})
: Base(parent, minimum, maximum, wid, hgt), _crange(init.begin(), init.end())
{
}
virtual ~ColorBarMeterH()
{
}
void setColorRange(std::initializer_list<ColorRange> init)
{
_crange = std::vector<ColorRange>(init);
}
protected:
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
private:
std::vector<ColorRange> _crange{};
};
class ColorBarMeterV : public Base {
public:
ColorBarMeterV(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid,
const int32_t hgt, std::initializer_list<ColorRange> init = {})
: Base(parent, minimum, maximum, wid, hgt), _crange(init.begin(), init.end())
{
}
virtual ~ColorBarMeterV()
{
}
void setColorRange(std::initializer_list<ColorRange> init)
{
_crange = std::vector<ColorRange>(init);
}
protected:
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
private:
std::vector<ColorRange> _crange{};
};
} // namespace ui
} // namespace m5
#endif

View file

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_base.cpp
@brief Base class for UI
*/
#include "ui_base.hpp"
#include <M5Utility.h>
namespace m5 {
namespace ui {
void Base::animate(const int32_t val, const elapsed_time_t dur)
{
if (_to != val && _min != _max) {
_from = _value;
_to = std::min(std::max(val, _min), _max);
_start_at = m5::utility::millis();
_duration = dur;
}
}
bool Base::update()
{
if (_start_at) {
auto now = m5::utility::millis();
if (now >= _start_at + _duration) {
_start_at = 0;
_value = _to;
} else {
float t = (now - _start_at) / (float)_duration;
_value = _from + (_to - _from) * t;
}
return true;
}
return false;
}
} // namespace ui
} // namespace m5

View file

@ -0,0 +1,129 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_base.hpp
@brief Base class for UI
*/
#ifndef UI_BASE_HPP
#define UI_BASE_HPP
#include <M5GFX.h>
#include <algorithm>
#include <cmath>
namespace m5 {
namespace ui {
class Base {
public:
using elapsed_time_t = unsigned long;
Base(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const int32_t wid, const int32_t hgt)
: _parent(parent),
_min{minimum},
_max{maximum},
_value{minimum},
_from{minimum},
_to{minimum},
_wid{wid},
_hgt{hgt}
{
}
virtual ~Base()
{
}
inline int32_t value() const
{
return _value;
}
inline int32_t valueTo() const
{
return _to;
}
inline int32_t width() const
{
return _wid;
}
inline int32_t height() const
{
return _hgt;
}
inline int32_t range() const
{
return _max - _min;
}
inline m5gfx::rgb565_t needleColor() const
{
return _needleClr;
}
inline m5gfx::rgb565_t gaugeColor() const
{
return _gaugeClr;
}
inline m5gfx::rgb565_t backgroundColor() const
{
return _bgClr;
}
template <typename T>
void setNeedleColor(const T& clr)
{
_needleClr = clr;
}
template <typename T>
void setGaugeColor(const T& clr)
{
_gaugeClr = clr;
}
template <typename T>
void setBackgroundColor(const T& clr)
{
_bgClr = clr;
}
virtual bool update();
///@name Control
///@{
virtual void animate(const int32_t val, const elapsed_time_t dur);
inline void set(const int32_t val)
{
animate(val, 0U);
}
///@}
///@name Push
///@{
inline void push(const int32_t x, const int32_t y)
{
push(_parent, x, y);
}
virtual void push(LovyanGFX* dst, const int32_t x, const int32_t y)
{
render(dst, x, y, _value);
}
///@}
protected:
inline float ratio(const int32_t val)
{
return range() > 0 ? (std::min(std::max(val, _min), _max) - _min) / (float)range() : 0.0f;
}
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
{
}
private:
LovyanGFX* _parent{};
int32_t _min{}, _max{}, _value{}, _from{}, _to{}, _wid{}, _hgt{};
elapsed_time_t _start_at{}, _duration{};
m5gfx::rgb565_t _needleClr{TFT_WHITE}, _gaugeClr{TFT_DARKGRAY}, _bgClr{TFT_BLACK};
};
} // namespace ui
} // namespace m5
#endif

View file

@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_gauge_meter.cpp
@brief Gauge meter
*/
#include "ui_gauge_meter.hpp"
#include <M5Utility.h>
#include <cassert>
namespace m5 {
namespace ui {
GaugeMeter::GaugeMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg,
const float maxDeg, const int32_t wid, const int32_t hgt, const int32_t thickness)
: GaugeMeter(parent, minimum, maximum, minDeg, maxDeg, wid, hgt, (wid >> 1) - 1, (hgt >> 1) - 1,
std::min(wid >> 1, hgt >> 1) - 1, thickness)
{
}
GaugeMeter::GaugeMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg,
const float maxDeg, const int32_t wid, const int32_t hgt, const int32_t cx, const int32_t cy,
const int32_t radius, const int32_t thickness)
: Base(parent, minimum, maximum, wid, hgt),
_cx(cx),
_cy(cy),
_radius(radius),
_thickness{thickness},
_minDeg{minDeg},
_maxDeg{maxDeg}
{
}
void GaugeMeter::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
{
dst->setClipRect(x, y, width(), height());
int32_t r0{_radius}, r1{_radius - _thickness};
float sdeg{std::fmin(_minDeg, _maxDeg)};
float edeg{std::fmax(_minDeg, _maxDeg)};
// float sdeg{_minDeg};
// float edeg{_maxDeg};
dst->fillArc(x + _cx, y + _cy, r0, r1, sdeg, edeg, backgroundColor());
float deg = _minDeg + (_maxDeg - _minDeg) * ratio(val);
dst->fillArc(x + _cx, y + _cy, r0, r1, sdeg, deg, needleColor());
dst->drawArc(x + _cx, y + _cy, r0, r1, sdeg, edeg, gaugeColor());
dst->clearClipRect();
}
} // namespace ui
} // namespace m5

View file

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_gauge_meter.hpp
@brief Gauge meter
*/
#ifndef UI_GAUGE_METER_HPP
#define UI_GAUGE_METER_HPP
#include "ui_base.hpp"
namespace m5 {
namespace ui {
class GaugeMeter : public Base {
public:
GaugeMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg, const float maxDeg,
const int32_t wid, const int32_t hgt, const int32_t thickness);
GaugeMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg, const float maxDeg,
const int32_t wid, const int32_t hgt, const int32_t cx, const int32_t cy, const int32_t radius,
const int32_t thickness);
virtual ~GaugeMeter()
{
}
protected:
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
private:
int32_t _cx{}, _cy{}, _radius{}, _thickness{};
float _minDeg{}, _maxDeg{};
};
} // namespace ui
} // namespace m5
#endif

View file

@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_lgfx_extesion.hpp
@brief M5GFX lgfx extensions
*/
#ifndef UI_LGFX_EXTENSION_HPP
#define UI_LGFX_EXTENSION_HPP
#include <M5GFX.h>
namespace m5 {
namespace lgfx {
/*!
@brief Push sprite partialty
@param dst Push target
@param dx Destination X coordinate for push
@param dy Destination Y coordinate for push
@param width Width of partial rectangle
@param height Height of partial rectangle
@param src Source sprite
@param sx Source X coordinate for push
@param sy Source Y coordinate for push
@warning If you have already set clip rect to dst, save and set it again on your own.
@warning After this function call, the clip rectangle in dst is cleared.
*/
inline void pushPartial(LovyanGFX* dst, const int32_t dx, const int32_t dy, const int32_t width, const int32_t height,
LGFX_Sprite* src, const int32_t sx, const int32_t sy)
{
dst->setClipRect(dx, dy, width, height);
src->pushSprite(dst, dx - sx, dy - sy);
dst->clearClipRect();
}
/*!
@copybrief pushPartial(LovyanGFX* dst, const int32_t dx, const int32_t dy, const int32_t width, const int32_t height,
LGFX_Sprite* src, const int32_t sx, const int32_t sy)
@copydoc pushPartial(LovyanGFX* dst, const int32_t dx, const int32_t dy, const int32_t width, const int32_t height,
LGFX_Sprite* src, const int32_t sx, const int32_t sy)
@param transp Color/Palette for transparent
@tparam T Color/Palettetype
*/
template <typename T>
void pushPartial(LovyanGFX* dst, const int32_t dx, const int32_t dy, const int32_t width, const int32_t height,
LGFX_Sprite* src, const int32_t sx, const int32_t sy, const T& transp)
{
dst->setClipRect(dx, dy, width, height);
src->pushSprite(dst, dx - sx, dy - sy, transp);
dst->clearClipRect();
}
} // namespace lgfx
} // namespace m5
#endif

View file

@ -0,0 +1,166 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_plotter.cpp
@brief Plotter
*/
#include "ui_plotter.hpp"
#include <algorithm>
namespace m5 {
namespace ui {
Plotter::Plotter(LovyanGFX* parent, const size_t maxPlot, const int32_t wid, const int32_t hgt,
const int32_t coefficient)
: _parent(parent), _wid{wid}, _hgt{hgt}, _coefficient(coefficient), _data(maxPlot), _autoScale{true}
{
}
Plotter::Plotter(LovyanGFX* parent, const size_t maxPlot, const int32_t minimum, const int32_t maximum,
const int32_t wid, const int32_t hgt, const int32_t coefficient)
: _parent(parent),
_min{minimum},
_max{maximum},
_wid{wid},
_hgt{hgt},
_coefficient(coefficient),
_data(maxPlot),
_autoScale{false}
{
}
void Plotter::update()
{
if (_cb && _autoScale && _cb->size() >= 2) {
auto it = std::minmax_element(_cb->cbegin(), _cb->cend());
_min = *(it.first);
_max = *(it.second);
if (_min == _max) {
++_max;
}
}
}
void Plotter::push_back(const float val)
{
push_back((int32_t)(val * _coefficient));
}
void Plotter::push_back(const int32_t val)
{
auto v = _autoScale ? val : std::min(std::max(val, _min), _max);
_data.push_back(v);
if (_autoScale && _data.size() >= 2) {
#if 0
if (_min == _max) {
auto it = std::minmax_element(_data.cbegin(), _data.cend());
_min = *(it.first);
_max = *(it.second);
} else {
if (v < _min) {
_min = v;
}
if (v > _max) {
_max = v;
}
}
#else
auto it = std::minmax_element(_data.cbegin(), _data.cend());
_min = *(it.first);
_max = *(it.second);
if (_min == _max) {
++_max;
}
#endif
}
}
void Plotter::assign(m5::container::CircularBuffer<int32_t>& cb)
{
_cb = &cb;
if (_autoScale && _cb->size() >= 2) {
auto it = std::minmax_element(_cb->cbegin(), _cb->cend());
_min = *(it.first);
_max = *(it.second);
}
}
void Plotter::push(LovyanGFX* dst, const int32_t x, const int32_t y)
{
dst->setClipRect(x, y, width(), height());
// gauge
dst->drawFastHLine(x, y, _wid, _gaugeClr);
dst->drawFastHLine(x, y + (_hgt >> 1), _wid, _gaugeClr);
dst->drawFastHLine(x, y + (_hgt >> 2), _wid, _gaugeClr);
dst->drawFastHLine(x, y + (_hgt >> 2) * 3, _wid, _gaugeClr);
dst->drawFastHLine(x, y + _hgt - 1, _wid, _gaugeClr);
if (_data.size() >= 2) {
auto it = _cb ? _cb->cbegin() : _data.cbegin();
auto itend = _cb ? --_cb->cend() : --_data.cend();
auto sz = _cb ? _cb->size() : _data.size();
const float range{(float)_max - _min};
const int32_t hh{_hgt - 1};
int32_t left{x};
// plot latest
if (sz > _wid) {
auto cnt{sz - _wid};
while (cnt--) {
++it; // Bidirectional iterator, so only ++/-- is available.
}
}
if (sz < _wid) {
left += _wid - sz;
}
while (it != itend) {
int32_t s{*it}, e{*(++it)};
dst->drawLine(left, y + hh - hh * (s - _min) / range, left + 1, y + hh - hh * (e - _min) / range,
_needleClr);
++left;
}
}
//
auto f = dst->getFont();
auto td = dst->getTextDatum();
dst->setFont(&fonts::Font0);
dst->setTextColor(TFT_WHITE);
dst->setTextDatum(_tdatum);
int32_t tx{x}; // left
switch (_tdatum & 0x03) {
case 1: // center
tx = x + (_wid >> 1);
break;
case 2: // right:
tx = x + _wid;
break;
default:
break;
}
auto s = m5::utility::formatString("%d%s", _min / _coefficient, _ustr ? _ustr : "");
dst->drawString(s.c_str(), tx, y + _hgt - 8);
if (_min != _max) {
auto s = m5::utility::formatString("%d%s", _max / _coefficient, _ustr ? _ustr : "");
dst->drawString(s.c_str(), tx, y);
if (_max - _min > 1) {
s = m5::utility::formatString("%d%s", (_min + ((_max - _min) >> 1)) / _coefficient, _ustr ? _ustr : "");
dst->drawString(s.c_str(), tx, y + _hgt / 2 - 4);
}
}
dst->setTextDatum(td);
dst->setFont(f);
dst->clearClipRect();
}
} // namespace ui
} // namespace m5

View file

@ -0,0 +1,109 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_plotter.hpp
@brief Plotter
*/
#ifndef UI_PLOTTER_HPP
#define UI_PLOTTER_HPP
#include <M5GFX.h>
#include <M5Utility.h>
namespace m5 {
namespace ui {
class Plotter {
public:
Plotter(LovyanGFX* parent, const size_t maxPlot, const int32_t wid, const int32_t hgt,
const int32_t coefficient = 1);
Plotter(LovyanGFX* parent, const size_t maxPlot, const int32_t minimum, const int32_t maximum, const int32_t wid,
const int32_t hgt, const int32_t coefficient = 1);
void update();
inline int32_t width() const
{
return _wid;
}
inline int32_t height() const
{
return _hgt;
}
inline int32_t minimum() const
{
return _min;
}
inline int32_t maximum() const
{
return _max;
}
template <typename T>
void setNeedleColor(const T& clr)
{
_needleClr = clr;
}
template <typename T>
void setGaugeColor(const T& clr)
{
_gaugeClr = clr;
}
template <typename T>
void setBackgroundColor(const T& clr)
{
_bgClr = clr;
}
inline void setUnitString(const char* s)
{
_ustr = s;
}
inline void setGaugeTextDatum(const textdatum_t datum)
{
_tdatum = datum;
}
void push_back(const float val);
void push_back(const int32_t val);
void assign(m5::container::CircularBuffer<int32_t>& cb);
inline void push(const int32_t x, const int32_t y)
{
push(_parent, x, y);
}
virtual void push(LovyanGFX* dst, const int32_t x, const int32_t y);
protected:
m5gfx::rgb565_t needleColor() const
{
return _needleClr;
}
m5gfx::rgb565_t gaugeColor() const
{
return _gaugeClr;
}
m5gfx::rgb565_t backgroundColor() const
{
return _bgClr;
}
protected:
private:
LovyanGFX* _parent{};
int32_t _min{}, _max{}, _wid{}, _hgt{}, _coefficient{};
m5::container::CircularBuffer<int32_t> _data;
m5::container::CircularBuffer<int32_t>* _cb{};
m5gfx::rgb565_t _needleClr{TFT_WHITE}, _gaugeClr{TFT_DARKGRAY}, _bgClr{TFT_BLACK};
textdatum_t _tdatum{textdatum_t::top_left};
const char* _ustr{};
bool _autoScale{};
};
} // namespace ui
} // namespace m5
#endif

View file

@ -0,0 +1,109 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_rotary_couter.cpp
@brief Rotary counter
*/
#include "ui_rotary_counter.hpp"
#include <M5Utility.h>
#include <cassert>
namespace m5 {
namespace ui {
RotaryCounter::Number::Number(LGFX_Sprite* src, const uint8_t base) : _src{src}, _base{base}
{
assert(_src && "Source must be NOT nullptr");
_height = _src->height() / (_base + 1);
}
void RotaryCounter::Number::animate(const uint8_t num, const uint32_t dur)
{
_duration = dur;
auto n = num % _base;
const int32_t shgt{_height * _base};
if (_to != n) {
// _y = _fy = _ty % (_height * _base);
_fy = _y % shgt;
_ty = n * _height;
if (_ty < _fy) {
_ty += shgt;
}
_start_at = m5::utility::millis();
_to = n;
// printf("==> %d >Y ;%d -> %d\n", _to, _fy, _ty);
}
}
bool RotaryCounter::Number::update(const unsigned long now)
{
const int32_t shgt{_height * _base};
if (_start_at) {
if (now >= _start_at + _duration) {
_start_at = 0;
_ty %= shgt;
_y = _fy = _ty;
} else {
float t = (now - _start_at) / (float)_duration;
_y = (int16_t)(_fy + (_ty - _fy) * t) % shgt;
// printf(">>> [%d] %d: (%d - %d) %f\n", _to, _y, _fy, _ty, t);
}
return true;
}
return false;
}
RotaryCounter::RotaryCounter(LovyanGFX* parent, const size_t maxDigits, LGFX_Sprite* src, const uint8_t base)
: _parent(parent), _base(base)
{
_numbers.resize(maxDigits);
if (src) {
construct(src);
}
}
void RotaryCounter::construct(LGFX_Sprite* src)
{
assert(src != nullptr && "src must be NOT nullptr");
for (auto& n : _numbers) {
n = Number(src, _base);
}
}
bool RotaryCounter::update()
{
bool updated{};
if (!_pause) {
auto now = m5::utility::millis();
for (auto&& n : _numbers) {
updated |= n.update(now);
}
}
return updated;
}
void RotaryCounter::animate(const uint32_t val, const unsigned long dur)
{
uint32_t v{val};
for (auto it = _numbers.rbegin(); it != _numbers.rend(); ++it) {
it->animate(v % 10, dur);
v /= 10;
}
}
void RotaryCounter::animate(const size_t digit, const uint8_t val, const unsigned long dur)
{
if (digit >= _numbers.size()) {
M5_LIB_LOGE("Illegal digit %zu/%zu", digit, _numbers.size());
return;
}
_numbers[digit].animate(val, dur);
}
} // namespace ui
} // namespace m5

View file

@ -0,0 +1,194 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_rotary_couter.hpp
@brief Rotary counter
*/
#ifndef UI_ROTARY_COUNTER_HPP
#define UI_ROTARY_COUNTER_HPP
#include <M5GFX.h>
#include <vector>
#include "ui_lgfx_extension.hpp"
namespace m5 {
namespace ui {
/*!
@class RotaryCounter
@brief Rotary counter with any digits
*/
class RotaryCounter {
public:
using elapsed_time_t = unsigned long;
// For each number
class Number {
public:
Number()
{
}
Number(LGFX_Sprite* src, const uint8_t base = 10);
inline LGFX_Sprite* sprite()
{
return _src;
}
inline int32_t sourceY() const
{
return _y;
}
inline uint32_t width() const
{
return _src ? _src->width() : 0U;
}
inline uint32_t height() const
{
return _height;
}
inline void set(const uint8_t num)
{
animate(num, 0);
}
void animate(const uint8_t num, const uint32_t dur);
bool update(const elapsed_time_t now);
private:
LGFX_Sprite* _src{};
int32_t _height{}, _fy{}, _ty{}, _y{};
elapsed_time_t _start_at{}, _duration{};
uint8_t _base{};
uint8_t _to{};
};
using vector_type_t = std::vector<Number>;
/*!
@brief Constructor
@param parent Push target
@param src Source sprite
@param digits Number of digits
@param base How many decimal digits?
@note The source sprite should consist of the following
[0...9] [0...5] [0...2]
+---+ +---+ +---+
| 0 | | 0 | | 0 |
| 1 | | 1 | | 1 |
| 2 | | 2 | | 2 |
| 3 | | 3 | | 0 |
| 4 | | 4 | +---+
| 5 | | 5 |
| 6 | | 0 |
| 7 | +---*
| 8 |
| 9 |
| 0 |
+---+
*/
RotaryCounter(LovyanGFX* parent, const size_t digits, LGFX_Sprite* src = nullptr, const uint8_t base = 10);
virtual ~RotaryCounter()
{
}
//! @brief Construct with source sprite
void construct(LGFX_Sprite* src);
//!@brief Update all numbers
virtual bool update();
///@Properties
///@{
const vector_type_t& numbers() const
{
return _numbers;
}
vector_type_t& numbers()
{
return _numbers;
}
///@}
///@name Control
///@{
/*!@brief Pause/Resume */
inline void pause(const bool paused)
{
_pause = paused;
}
//! @brief Pause
inline void pause()
{
pause(true);
}
//! @brief Resume
inline void resume()
{
pause(false);
}
//! @brief Animate and change values (all)
void animate(const uint32_t val, const elapsed_time_t dur);
//! @brief Set value (all)
inline void set(const uint32_t val)
{
animate(val, 0U);
}
//! Animate and change values (partial)
void animate(const size_t digit, const uint8_t val, const elapsed_time_t dur);
//! @brief Set value (partial)
inline void set(const size_t digit, const uint8_t val)
{
animate(digit, val, 0U);
}
///@}
///@warning If you have already set clip rect to dst, save and set it again on your own.
///@warning After this function call, the clip rectangle in dst is cleared.
///@name Push
///@{
inline void push(const int32_t x, const int32_t y)
{
push(_parent, x, y);
}
void push(LovyanGFX* dst, const int32_t x, const int32_t y)
{
int32_t left{x};
for (auto&& n : _numbers) {
m5::lgfx::pushPartial(dst, left, y, n.width(), n.height(), n.sprite(), 0, n.sourceY());
left += n.width();
}
}
template <typename T>
inline void push(const int32_t x, const int32_t y, const T& transp)
{
push(_parent, x, y, transp);
}
template <typename T>
void push(LovyanGFX* dst, const int32_t x, const int32_t y, const T& transp)
{
int32_t left{x};
if (!_fit) {
fit();
_fit = true;
}
for (auto&& n : _numbers) {
m5::lgfx::pushPartial(dst, left, y, n.width(), n.height(), n.sprite(), 0, n.sourceY(), transp);
left += n.width();
}
}
///@}
protected:
void fit();
LovyanGFX* _parent{};
int32_t _height{};
vector_type_t _numbers{};
uint8_t _base{};
bool _fit{}, _pause{};
};
} // namespace ui
} // namespace m5
#endif

View file

@ -0,0 +1,67 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_scale_meter.cpp
@brief Scale meter
*/
#include "ui_scale_meter.hpp"
#include <M5Utility.h>
#include <cassert>
namespace {
constexpr float table0[] = {0.0f, 0.25f, 0.5f, 0.75f, 1.0f};
constexpr float table1[] = {0.125f, 0.125f * 3, 0.125f * 5, 0.125f * 7};
} // namespace
namespace m5 {
namespace ui {
void ScaleMeter::render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val)
{
dst->setClipRect(x, y, width(), height());
auto rad = _radius - 1;
// gauge
int32_t r0{rad}, r1{rad - 1};
float sdeg{std::fmin(_minDeg, _maxDeg)};
float edeg{std::fmax(_minDeg, _maxDeg)};
dst->fillArc(x + _cx, y + _cy, r0, r1, sdeg, edeg, gaugeColor());
const auto w = _maxDeg - _minDeg;
constexpr float deg_to_rad = 0.017453292519943295769236907684886f;
for (auto&& e : table0) {
const float f = _minDeg + w * e;
const float cf = std::cos(f * deg_to_rad);
const float sf = std::sin(f * deg_to_rad);
int32_t sx = rad * cf;
int32_t sy = rad * sf;
int32_t ex = (rad - 4) * cf;
int32_t ey = (rad - 4) * sf;
dst->drawLine(x + _cx + sx, y + _cy + sy, x + _cx + ex, y + _cy + ey, gaugeColor());
}
for (auto&& e : table1) {
const float f = _minDeg + w * e;
const float cf = std::cos(f * deg_to_rad);
const float sf = std::sin(f * deg_to_rad);
int32_t sx = rad * cf;
int32_t sy = rad * sf;
int32_t ex = (rad - 2) * cf;
int32_t ey = (rad - 2) * sf;
dst->drawLine(x + _cx + sx, y + _cy + sy, x + _cx + ex, y + _cy + ey, gaugeColor());
}
// needle
float deg = _minDeg + (_maxDeg - _minDeg) * ratio(val);
int32_t tx = rad * std::cos(deg * deg_to_rad);
int32_t ty = rad * std::sin(deg * deg_to_rad);
dst->drawLine(x + _cx, y + _cy, x + _cx + tx, y + _cy + ty, needleColor());
dst->clearClipRect();
}
} // namespace ui
} // namespace m5

View file

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_scale_meter.hpp
@brief Scale meter
*/
#ifndef UI_SCALE_METER_HPP
#define UI_SCALE_METER_HPP
#include "ui_base.hpp"
namespace m5 {
namespace ui {
class ScaleMeter : public Base {
public:
ScaleMeter(LovyanGFX* parent, const int32_t minimum, const int32_t maximum, const float minDeg, const float maxDeg,
const int32_t wid, const int32_t hgt, const int32_t cx, const int32_t cy, const uint32_t radius)
: Base(parent, minimum, maximum, wid, hgt), _cx(cx), _cy(cy), _radius(radius), _minDeg{minDeg}, _maxDeg{maxDeg}
{
}
virtual ~ScaleMeter()
{
}
protected:
virtual void render(LovyanGFX* dst, const int32_t x, const int32_t y, const int32_t val) override;
private:
int32_t _cx{}, _cy{}, _radius{};
float _minDeg{}, _maxDeg{};
};
} // namespace ui
} // namespace m5
#endif

View file

@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file sprite.cpp
@brief Shared sprite
*/
#include "sprite.hpp"
struct NumberSprite {
const lgfx::IFont* font;
LGFX_Sprite* sprite;
const int32_t width, height;
const uint32_t base;
};
// For rotary counter
LGFX_Sprite number10_6x8;
LGFX_Sprite number10_8x16;
LGFX_Sprite number6_6x8;
LGFX_Sprite number6_8x16;
void make_shared_sprites()
{
NumberSprite table[] = {
{&fonts::Font0, &number10_6x8, 6, 8, 10},
{&fonts::Font2, &number10_8x16, 8, 16, 10},
{&fonts::Font0, &number6_6x8, 6, 8, 6},
{&fonts::Font2, &number6_8x16, 8, 16, 6},
};
for (auto&& e : table) {
e.sprite->setPsram(false);
e.sprite->setColorDepth(1);
e.sprite->createSprite(e.width + 2, e.height * (e.base + 1));
e.sprite->setPaletteColor(0, TFT_BLACK);
e.sprite->setPaletteColor(1, TFT_WHITE);
e.sprite->setTextColor(1, 0);
e.sprite->setFont(e.font);
for (int i = 0; i <= e.base; ++i) {
e.sprite->setCursor(1, i * e.height + 1);
e.sprite->printf("%d", i % e.base);
}
// e.sprite->drawFastVLine(0, 0, e.sprite->height(), 1);
// e.sprite->drawFastVLine(e.sprite->width() - 1, 0, e.sprite->height(), 1);
}
}

View file

@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file sprite.hpp
@brief Shared sprite
*/
#ifndef SPRITE_HPP
#define SPRITE_HPP
#include <M5GFX.h>
void make_shared_sprites();
// For rotary counter
extern LGFX_Sprite number10_6x8;
extern LGFX_Sprite number10_8x16;
extern LGFX_Sprite number6_6x8;
extern LGFX_Sprite number6_8x16;
#endif

View file

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitBase.cpp
@brief UI for UnitBase
*/
#include "ui_UnitBase.hpp"
UnitUIBase::UnitUIBase(LovyanGFX* parent) : _parent(parent)
{
_sem = xSemaphoreCreateBinary();
xSemaphoreGive(_sem);
}
UnitUIBase::~UnitUIBase()
{
xSemaphoreTake(_sem, portMAX_DELAY);
vSemaphoreDelete(_sem);
}
bool UnitUIBase::lock(portTickType bt)
{
return xSemaphoreTake(_sem, bt) == pdTRUE;
}
void UnitUIBase::unlock()
{
xSemaphoreGive(_sem);
}

View file

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitBase.hpp
@brief UI for UnitBase
*/
#ifndef UI_UNIT_BASE_HPP
#define UI_UNIT_BASE_HPP
#include <freertos/FreeRTOS.h>
#include <M5GFX.h>
#include <vector>
class UnitUIBase {
public:
explicit UnitUIBase(LovyanGFX* parent);
virtual ~UnitUIBase();
bool lock(portTickType bt = portMAX_DELAY);
// TickType_t
void unlock();
virtual void construct() = 0;
virtual void update() = 0;
virtual void push(LovyanGFX* dst, const int32_t x, const int32_t y) = 0;
inline void push(const int32_t x, const int32_t y)
{
push(_parent, x, y);
}
protected:
LovyanGFX* _parent{};
int32_t _wid{}, _hgt{};
private:
volatile SemaphoreHandle_t _sem{};
};
#endif

View file

@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitCO2.cpp
@brief UI for UnitCO2
*/
#include "ui_UnitCO2.hpp"
#include <M5Unified.h>
#include <M5Utility.h>
#include <iterator>
namespace {
constexpr int32_t GAP{2};
constexpr float COEFF{100.0f};
constexpr float COEFF_RECIPROCAL{1.0f / COEFF};
constexpr int32_t min_co2{0};
constexpr int32_t max_co2{6000};
constexpr int32_t min_temp{-10};
constexpr int32_t max_temp{40};
m5gfx::rgb565_t temp_chooseColor(const int32_t val)
{
return val > 0 ? m5gfx::rgb565_t(0xfe, 0xcb, 0xf2) : m5gfx::rgb565_t(0xb8, 0xc2, 0xf2);
}
constexpr std::initializer_list<m5::ui::ColorRange> co2_color_table = {
{1000, m5gfx::rgb565_t(TFT_GREEN)},
{1500, m5gfx::rgb565_t(TFT_GOLD)},
{2500, m5gfx::rgb565_t(TFT_ORANGE)},
{6000, m5gfx::rgb565_t(TFT_RED)},
};
m5gfx::rgb565_t co2_chooseColor(const int32_t val)
{
for (auto&& e : co2_color_table) {
if (val <= e.lesseq) {
return e.clr;
}
}
return (std::end(co2_color_table) - 1)->clr;
}
} // namespace
void UnitCO2SmallUI::construct()
{
auto& lcd = M5.Display;
_wid = lcd.width() >> 1;
_hgt = lcd.height() >> 1;
int32_t wh = std::max(_wid / 2, _hgt / 2) - GAP * 2;
_tempMeter.reset(new m5::ui::GaugeMeter(_parent, min_temp * COEFF, max_temp * COEFF, 90.0f + 45.0f,
90.0f - 45.0f + 360.f, wh, wh, 10));
_tempMeter->set(0);
_co2Meter.reset(
new m5::ui::ColorBarMeterH(_parent, min_co2, max_co2, _wid - GAP * 2, _hgt - wh - GAP * 2, co2_color_table));
}
void UnitCO2SmallUI::push_back(const int32_t co2, const float temp)
{
_tempMeter->animate(temp * COEFF, 1000);
_co2Meter->animate(co2, 1000);
}
void UnitCO2SmallUI::update()
{
_tempMeter->update();
_tempMeter->setNeedleColor(temp_chooseColor(_tempMeter->value()));
_co2Meter->update();
}
void UnitCO2SmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
{
auto left = x;
auto right = x + _wid - 1;
auto top = y;
auto bottom = y + _hgt - 1;
auto w = right - left + 1;
auto h = bottom - top + 1;
// BG
dst->fillRoundRect(x, y, _wid, _hgt, GAP * 2, TFT_MAGENTA);
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
auto tcx = left + GAP;
auto tcy = top + GAP;
_tempMeter->push(dst, x + GAP, y + GAP);
_co2Meter->push(dst, x + GAP, tcy + _tempMeter->height() + GAP * 2);
//
auto f = dst->getFont();
dst->setFont(&fonts::Font0);
dst->setTextColor(TFT_WHITE);
auto td = dst->getTextDatum();
dst->setTextDatum(textdatum_t::middle_center);
auto s = m5::utility::formatString("%3.2fC", _tempMeter->value() * COEFF_RECIPROCAL);
dst->drawString(s.c_str(), tcx + _tempMeter->width() / 2, tcy + _tempMeter->height() / 2);
dst->drawString("TEMP", tcx + _tempMeter->width() / 2, tcy + _tempMeter->height() / 2 + 10);
dst->setTextDatum(textdatum_t::top_right);
dst->drawString("CO2", right - GAP, top + _tempMeter->height() + GAP - 10);
auto sw = dst->drawString("ppm", right - GAP, top + _tempMeter->height() + GAP);
dst->setTextColor((uint16_t)co2_chooseColor(_co2Meter->value()));
s = m5::utility::formatString("%d", _co2Meter->value());
dst->drawString(s.c_str(), right - GAP - sw, top + _tempMeter->height() + GAP);
dst->setFont(&fonts::Font2);
dst->setTextColor(TFT_WHITE);
dst->setTextDatum(textdatum_t::middle_center);
dst->drawString("UnitCO2", left + w / 4 * 3, top + h / 4);
dst->setTextDatum(td);
dst->setFont(f);
}

View file

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitCO2.hpp
@brief UI for UnitCO2
*/
#ifndef UI_UNIT_CO2_HPP
#define UI_UNIT_CO2_HPP
#include "parts/ui_rotary_counter.hpp"
#include "parts/ui_scale_meter.hpp"
#include "parts/ui_gauge_meter.hpp"
#include "parts/ui_bar_meter.hpp"
#include "ui_UnitBase.hpp"
#include <memory>
class UnitCO2SmallUI : public UnitUIBase {
public:
explicit UnitCO2SmallUI(LovyanGFX* parent = nullptr) : UnitUIBase(parent)
{
}
void push_back(const int32_t co2, const float temp);
virtual void construct() override;
virtual void update() override;
virtual void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
private:
LovyanGFX* _parent{};
int32_t _wid{}, _hgt{};
std::unique_ptr<m5::ui::GaugeMeter> _tempMeter{};
std::unique_ptr<m5::ui::ColorBarMeterH> _co2Meter{};
};
#endif

View file

@ -0,0 +1,192 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitENV3.cpp
@brief UI for UnitENV3
*/
#include "ui_UnitENV3.hpp"
#include <M5Unified.h>
#include <M5Utility.h>
#include <cassert>
namespace {
constexpr int32_t GAP{2};
constexpr float COEFF{100.0f};
constexpr float COEFF_RECIPROCAL{1.0f / COEFF};
constexpr m5gfx::rgb565_t hum_needle_color{32, 147, 223};
constexpr m5gfx::rgb565_t pres_needle_color{161, 54, 64};
constexpr int32_t min_temp{-10};
constexpr int32_t max_temp{40};
constexpr int32_t min_hum{0};
constexpr int32_t max_hum{100};
constexpr int32_t min_pres{0};
constexpr int32_t max_pres{1500};
m5gfx::rgb565_t temp_chooseColor(const int32_t val)
{
return val > 0 ? m5gfx::rgb565_t(0xfe, 0xcb, 0xf2) : m5gfx::rgb565_t(0xb8, 0xc2, 0xf2);
}
} // namespace
void UnitENV3SmallUI::construct()
{
auto& lcd = M5.Display;
_wid = lcd.width() >> 1;
_hgt = lcd.height() >> 1;
// auto left = 0 + GAP;
// auto right = _wid - GAP - 1;
// auto top = 0;
// auto bottom = _hgt - 1;
auto w = _wid / 5;
auto h = _hgt / 2 - GAP * 2;
auto rad = std::min(w, h / 2);
auto wh = (std::min(_wid, _hgt) >> 1) - GAP * 2;
_tempMeterSHT.reset(
new m5::ui::GaugeMeter(_parent, min_temp * COEFF, max_temp * COEFF, 25.0f, -25.0f + 360.f, wh, wh, 10));
_tempMeterQMP.reset(
new m5::ui::GaugeMeter(_parent, min_temp * COEFF, max_temp * COEFF, 25.0f, -25.0f + 360.f, wh, wh, 10));
_humMeter.reset(
new m5::ui::ScaleMeter(_parent, min_hum * COEFF, max_hum * COEFF, 360.0f + 90.0f, 270.0f, w, h, 0, h / 2, rad));
_presMeter.reset(new m5::ui::ScaleMeter(_parent, min_pres * COEFF, max_pres * COEFF, 360.0f + 90.0f, 270.0f, w, h,
0, h / 2, rad));
_humMeter->setNeedleColor(hum_needle_color);
_presMeter->setNeedleColor(pres_needle_color);
}
void UnitENV3SmallUI::sht30_push_back(const float tmp, const float hum)
{
_tempMeterSHT->animate(tmp * COEFF, 10);
_humMeter->animate(hum * COEFF, 10);
}
void UnitENV3SmallUI::qmp6988_push_back(const float tmp, const float pa)
{
_tempMeterQMP->animate(tmp * COEFF, 10);
_presMeter->animate(pa * COEFF * 0.01f, 10); // pa to hPa
}
void UnitENV3SmallUI::update()
{
lock();
_tempMeterSHT->update();
_tempMeterQMP->update();
_tempMeterSHT->setNeedleColor(temp_chooseColor(_tempMeterSHT->value()));
_tempMeterQMP->setNeedleColor(temp_chooseColor(_tempMeterQMP->value()));
_humMeter->update();
_presMeter->update();
unlock();
}
void UnitENV3SmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
{
auto left = x;
auto right = x + _wid - 1;
auto top = y;
auto bottom = y + _hgt - 1;
auto w = right - left + 1;
auto h = bottom - top + 1;
auto sx = left + GAP;
auto sy = top + GAP;
auto qx = left + GAP;
auto qy = top + h / 2 + GAP;
auto hx = right - (_humMeter->width() + GAP);
auto hy = top + GAP;
auto px = right - (_presMeter->width() + GAP);
auto py = top + h / 2 + GAP;
auto f = dst->getFont();
dst->setFont(&fonts::Font0);
dst->setTextColor(TFT_WHITE);
auto td = dst->getTextDatum();
// BG
dst->fillRoundRect(x, y, _wid, _hgt, GAP, TFT_GREEN);
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
// meters
_tempMeterSHT->push(dst, sx, sy);
_humMeter->push(dst, hx, hy);
_tempMeterQMP->push(dst, qx, qy);
_presMeter->push(dst, px, py);
// text
dst->setTextDatum(textdatum_t::middle_left);
auto s = m5::utility::formatString("T:%3.2fC", _tempMeterSHT->value() * COEFF_RECIPROCAL);
dst->drawString(s.c_str(), sx + 16, sy + _tempMeterSHT->height() / 2);
s = m5::utility::formatString("T:%3.2fC", _tempMeterQMP->value() * COEFF_RECIPROCAL);
dst->drawString(s.c_str(), qx + 16, qy + _tempMeterQMP->height() / 2);
dst->setTextDatum(textdatum_t::middle_right);
s = m5::utility::formatString("H:%3.2f%%", _humMeter->value() * COEFF_RECIPROCAL);
dst->drawString(s.c_str(), hx, hy + _humMeter->height() / 2);
s = m5::utility::formatString("P:%4.0f", _presMeter->value() * COEFF_RECIPROCAL);
dst->drawString(s.c_str(), px, py + _presMeter->height() / 2);
#if 0
auto s = m5::utility::formatString("%dC", max_temp);
dst->drawString(s.c_str(), sx, sy);
dst->drawString(s.c_str(), qx, qy);
s = m5::utility::formatString("%dC", (max_temp - min_temp) / 2);
dst->setTextDatum(textdatum_t::middle_left);
dst->drawString(s.c_str(), sx, sy + _tempMeterSHT->height() / 2);
dst->drawString(s.c_str(), qx, qy + _tempMeterSHT->height() / 2);
dst->setTextDatum(textdatum_t::bottom_left);
s = m5::utility::formatString("%dC", min_temp);
dst->drawString(s.c_str(), sx, sy + _tempMeterSHT->height());
dst->drawString(s.c_str(), qx, qy + _tempMeterSHT->height());
#endif
dst->setTextDatum(textdatum_t::top_right);
s = m5::utility::formatString("%d%%", max_hum);
dst->drawString(s.c_str(), right - GAP, hy);
dst->setTextDatum(textdatum_t::bottom_right);
s = m5::utility::formatString("%d%%", min_hum);
dst->drawString(s.c_str(), right - GAP, hy + _humMeter->height());
dst->setTextDatum(textdatum_t::top_right);
s = m5::utility::formatString("%dhPa", max_pres);
dst->drawString(s.c_str(), right - GAP, py);
dst->setTextDatum(textdatum_t::bottom_right);
s = m5::utility::formatString("%dhPa", min_pres);
dst->drawString(s.c_str(), right - GAP, py + _presMeter->height());
//
#if 0
dst->setTextDatum(textdatum_t::top_left);
dst->drawString("SHT30", sx + _tempMeterSHT->width() + GAP, sy + 10);
s = m5::utility::formatString(" T: %3.2f C", _tempMeterSHT->valueTo() * COEFF_RECIPROCAL);
dst->drawString(s.c_str(), sx + _tempMeterSHT->width() + GAP, sy + 10 * 2);
s = m5::utility::formatString(" H: %3.2f RH", _humMeter->valueTo() * COEFF_RECIPROCAL);
dst->drawString(s.c_str(), sx + _tempMeterSHT->width() + GAP, sy + 10 * 3);
dst->setTextDatum(textdatum_t::bottom_right);
dst->drawString("QMP6988", right - (_tempMeterSHT->width() + GAP), bottom - GAP - 10 + 1);
s = m5::utility::formatString(" T: %4.2f C", _tempMeterQMP->valueTo() * COEFF_RECIPROCAL);
dst->drawString(s.c_str(), right - (_tempMeterSHT->width() + GAP), bottom - GAP - 10 * 2);
s = m5::utility::formatString(" P: %4.2f hPa", _presMeter->valueTo() * COEFF_RECIPROCAL);
dst->drawString(s.c_str(), right - (_tempMeterSHT->width() + GAP), bottom - GAP - 10 * 3);
#endif
dst->setTextDatum(textdatum_t::top_center);
dst->drawString("SHT30", x + w / 2, top + GAP);
dst->setTextDatum(textdatum_t::bottom_center);
dst->drawString("QMP6988", x + w / 2, bottom - GAP);
dst->setFont(&fonts::Font2);
dst->setTextColor(TFT_GREEN);
dst->setTextDatum(textdatum_t::middle_center);
dst->drawString("UnitENVIII", left + w / 2, top + h / 2);
dst->setTextDatum(td);
dst->setFont(f);
}

View file

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitENV3.hpp
@brief UI for UnitENV3
*/
#ifndef UI_UNIT_ENV3_HPP
#define UI_UNIT_ENV3_HPP
#include "parts/ui_scale_meter.hpp"
#include "parts/ui_gauge_meter.hpp"
#include "ui_UnitBase.hpp"
#include <memory>
class UnitENV3SmallUI : public UnitUIBase {
public:
explicit UnitENV3SmallUI(LovyanGFX* parent) : UnitUIBase(parent)
{
}
void construct() override;
void sht30_push_back(const float tmp, const float hum); // SHT30
void qmp6988_push_back(const float tmp, const float pres); // QMP6988
void update() override;
void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
private:
std::unique_ptr<m5::ui::GaugeMeter> _tempMeterSHT{};
std::unique_ptr<m5::ui::GaugeMeter> _tempMeterQMP{};
std::unique_ptr<m5::ui::ScaleMeter> _humMeter;
std::unique_ptr<m5::ui::ScaleMeter> _presMeter;
};
#endif

View file

@ -0,0 +1,115 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitHEART.cpp
@brief UI for UnitHEART
*/
#include "ui_UnitHEART.hpp"
#include <M5Unified.h>
#include <M5Utility.h>
#include <cassert>
namespace {
constexpr int32_t GAP{2};
constexpr float COEFF{100.0f};
constexpr float COEFF_RECIPROCAL{1.0f / COEFF};
constexpr m5gfx::rgb565_t ir_gauge_color{161, 54, 54};
constexpr m5gfx::rgb565_t spo2_gauge_color{38, 41, 64};
constexpr int32_t min_spo2{90};
constexpr int32_t max_spo2{100};
constexpr char spO2ustr[] = "%";
} // namespace
void UnitHEARTSmallUI::construct()
{
auto& lcd = M5.Display;
_wid = lcd.width() >> 1;
_hgt = lcd.height() >> 1;
auto gw = _wid - GAP * 2;
auto gh = (_hgt >> 1) - (GAP * 2 + 16);
_irPlotter.reset(new m5::ui::Plotter(_parent, gw, gw, gh));
_irPlotter->setGaugeColor(ir_gauge_color);
_spO2Plotter.reset(new m5::ui::Plotter(_parent, gw, min_spo2 * COEFF, max_spo2 * COEFF, gw, gh, COEFF));
_spO2Plotter->setGaugeColor(spo2_gauge_color);
_spO2Plotter->setUnitString(spO2ustr);
_spO2Plotter->setGaugeTextDatum(textdatum_t::top_right);
}
void UnitHEARTSmallUI::push_back(const int32_t ir, const int32_t red)
{
_intermediateBuffer.emplace_back(Data{ir, red});
}
void UnitHEARTSmallUI::update()
{
if (_beatCounter > 0) {
--_beatCounter;
}
lock();
for (auto&& e : _intermediateBuffer) {
_monitor.push_back(e.ir, e.red);
_monitor.update();
beat(_monitor.isBeat());
_bpm = _monitor.bpm();
// _irPlotter->push_back(e.ir);
_irPlotter->push_back(_monitor.latestIR());
_spO2Plotter->push_back(_monitor.SpO2());
}
_intermediateBuffer.clear();
unlock();
_irPlotter->update();
_spO2Plotter->update();
}
void UnitHEARTSmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
{
auto f = dst->getFont();
dst->setFont(&fonts::Font0);
dst->setTextColor(TFT_WHITE);
auto td = dst->getTextDatum();
auto left = x;
auto right = x + _wid - 1;
auto top = y;
auto bottom = y + _hgt - 1;
auto w = right - left + 1;
auto h = bottom - top + 1;
// BG
dst->fillRoundRect(x, y, _wid, _hgt, GAP, TFT_YELLOW);
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
_irPlotter->push(dst, x + GAP, y + GAP);
_spO2Plotter->push(dst, x + GAP, y + _hgt - GAP - _spO2Plotter->height());
auto s = m5::utility::formatString("HR:%3dbpm", _bpm);
dst->drawString(s.c_str(), left + GAP * 2, top + GAP * 2 + _irPlotter->height());
dst->setTextDatum(textdatum_t::bottom_right);
s = m5::utility::formatString("SpO2:%3.2f%%", _monitor.SpO2());
dst->drawString(s.c_str(), right - GAP, bottom - _spO2Plotter->height() - GAP);
constexpr int32_t radius{4};
dst->fillCircle(right - radius * 2 - GAP, top + GAP * 2 + _irPlotter->height() + radius, radius,
_beatCounter > 0 ? TFT_RED : TFT_DARKGRAY);
dst->setFont(&fonts::Font2);
dst->setTextDatum(textdatum_t::middle_center);
dst->setTextColor(TFT_YELLOW);
dst->drawString("UnitHEART", left + w / 2, top + h / 2);
dst->setTextDatum(td);
dst->setFont(f);
}

View file

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitHEART.hpp
@brief UI for UnitHEART
*/
#ifndef UI_UNIT_HEART_HPP
#define UI_UNIT_HEART_HPP
#include "parts/ui_plotter.hpp"
#include "ui_unitBase.hpp"
#include <M5UnitUnifiedHEART.h>
#include <memory>
class UnitHEARTSmallUI : public UnitUIBase {
public:
explicit UnitHEARTSmallUI(LovyanGFX* parent = nullptr) : UnitUIBase(parent)
{
}
inline m5::heart::PulseMonitor& monitor()
{
return _monitor;
}
inline void beat(bool beated)
{
if (beated) {
_beatCounter = 4;
}
}
void push_back(const int32_t ir, const int32_t red);
void construct() override;
void update() override;
void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
private:
std::unique_ptr<m5::ui::Plotter> _irPlotter{};
std::unique_ptr<m5::ui::Plotter> _spO2Plotter{};
m5::heart::PulseMonitor _monitor{100.0f};
int32_t _beatCounter{}, _bpm{};
struct Data {
int32_t ir, red;
};
std::vector<Data> _intermediateBuffer{};
};
#endif

View file

@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitTVOC.cpp
@brief UI for UnitTVOC
*/
#include "ui_UnitTVOC.hpp"
#include <M5Unified.h>
#include <cassert>
namespace {
const int32_t GAP{2};
constexpr m5gfx::rgb565_t co2_gauge_color{38, 41, 64};
constexpr m5gfx::rgb565_t tvoc_gauge_color{64, 48, 26};
constexpr char co2ustr[] = "ppm";
constexpr char tvocustr[] = "ppb";
constexpr std::initializer_list<m5::ui::ColorRange> tvocGauge = {
{220, m5gfx::rgb565_t(TFT_GREEN)}, {660, m5gfx::rgb565_t(TFT_GOLD)}, {1430, m5gfx::rgb565_t(TFT_ORANGE)},
{2000, m5gfx::rgb565_t(TFT_RED)}, {3300, m5gfx::rgb565_t(TFT_VIOLET)}, {5500, m5gfx::rgb565_t(TFT_PURPLE)},
};
} // namespace
void UnitTVOCSmallUI::construct()
{
auto& lcd = M5.Display;
_wid = lcd.width() >> 1;
_hgt = lcd.height() >> 1;
auto bw = _wid / 6 - GAP * 2;
auto bh = (_hgt >> 1) - GAP * 2 - 8;
auto pw = _wid / 6 * 5 - GAP * 2;
auto ph = (_hgt >> 1) - GAP * 2 - 8;
_co2Bar.reset(new m5::ui::BarMeterV(_parent, 400, 6000, bw, bh));
_tvocBar.reset(new m5::ui::ColorBarMeterV(_parent, 0, 5500, bw, bh, tvocGauge));
_co2Plotter.reset(new m5::ui::Plotter(_parent, pw, pw, ph));
_co2Plotter->setGaugeColor(co2_gauge_color);
_co2Plotter->setUnitString(co2ustr);
_co2Plotter->setGaugeTextDatum(textdatum_t::top_right);
_tvocPlotter.reset(new m5::ui::Plotter(_parent, pw, pw, ph));
_tvocPlotter->setGaugeColor(tvoc_gauge_color);
_tvocPlotter->setUnitString(tvocustr);
_intermediateBuffer.reserve(pw);
_intermediateBuffer.clear();
}
void UnitTVOCSmallUI::push_back(const int32_t co2, const int32_t tvoc)
{
_co2Bar->animate(co2, 10);
_tvocBar->animate(tvoc, 10);
_intermediateBuffer.emplace_back(Data{co2, tvoc});
}
void UnitTVOCSmallUI::update()
{
lock();
for (auto&& e : _intermediateBuffer) {
_co2Plotter->push_back(e.co2);
_tvocPlotter->push_back(e.tvoc);
}
_intermediateBuffer.clear();
unlock();
_co2Bar->update();
_tvocBar->update();
_co2Plotter->update();
_tvocPlotter->update();
}
void UnitTVOCSmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
{
auto left = x;
auto right = x + _wid - 1;
auto top = y;
auto bottom = y + _hgt - 1;
auto w = right - left + 1;
auto h = bottom - top + 1;
auto f = dst->getFont();
dst->setFont(&fonts::Font0);
dst->setTextColor(TFT_WHITE);
auto td = dst->getTextDatum();
// BG
dst->fillRoundRect(x, y, _wid, _hgt, GAP, TFT_BLUE);
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
_co2Bar->push(dst, left + GAP, y + GAP);
_co2Plotter->push(dst, right - _co2Plotter->width() - GAP, y + GAP);
_tvocPlotter->push(dst, left + GAP, bottom - _tvocPlotter->height() - GAP);
_tvocBar->push(dst, right - _tvocBar->width() - GAP, bottom - _tvocPlotter->height() - GAP);
dst->drawString("CO2eq", left + GAP * 3 + _co2Bar->width(), y + GAP);
dst->setTextDatum(textdatum_t::bottom_right);
dst->drawString("TVOC", right - (GAP * 2 + _tvocBar->width()), bottom);
dst->setFont(&fonts::Font2);
dst->setTextColor(TFT_BLUE);
dst->setTextDatum(textdatum_t::middle_center);
dst->drawString("UnitTVOC", x + w / 2, y + h / 2);
dst->setTextDatum(td);
dst->setFont(f);
}
// current valiue

View file

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitTVOC.hpp
@brief UI for UnitTVOC
*/
#ifndef UI_UNIT_TVOC_HPP
#define UI_UNIT_TVOC_HPP
#include "parts/ui_bar_meter.hpp"
#include "parts/ui_plotter.hpp"
#include "ui_UnitBase.hpp"
#include <memory>
class UnitTVOCSmallUI : public UnitUIBase {
public:
explicit UnitTVOCSmallUI(LovyanGFX* parent) : UnitUIBase(parent)
{
}
void push_back(const int32_t co2, const int32_t tvoc);
void construct() override;
void update() override;
void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
private:
std::unique_ptr<m5::ui::Plotter> _co2Plotter{};
std::unique_ptr<m5::ui::BarMeterV> _co2Bar{};
std::unique_ptr<m5::ui::Plotter> _tvocPlotter{};
std::unique_ptr<m5::ui::ColorBarMeterV> _tvocBar{};
struct Data {
int32_t co2, tvoc;
};
std::vector<Data> _intermediateBuffer{};
};
#endif

View file

@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitVmeter.cpp
@brief UI for UnitVmeter
*/
#include "ui_UnitVmeter.hpp"
#include <M5Unified.h>
#include <M5Utility.h>
#include <cassert>
namespace {
constexpr int32_t GAP{2};
constexpr m5gfx::rgb565_t voltage_gauge_color{129, 134, 80};
constexpr char vustr[] = "mV";
} // namespace
void UnitVmeterSmallUI::construct()
{
auto& lcd = M5.Display;
_wid = lcd.width() >> 1;
_hgt = lcd.height() >> 1;
auto pw = _wid - GAP * 2;
// auto ph = (_hgt >> 2) * 3 - GAP * 2;
auto ph = (_hgt >> 1) - GAP * 2 - 4;
_voltagePlotter.reset(new m5::ui::Plotter(_parent, pw, pw, ph));
_voltagePlotter->setUnitString(vustr);
_voltagePlotter->setGaugeColor(voltage_gauge_color);
_intermediateBuffer.reserve(pw);
_intermediateBuffer.clear();
}
void UnitVmeterSmallUI::push_back(const float mv)
{
_intermediateBuffer.emplace_back(mv);
}
void UnitVmeterSmallUI::update()
{
lock();
for (auto&& e : _intermediateBuffer) {
_voltagePlotter->push_back(e);
}
_intermediateBuffer.clear();
unlock();
_voltagePlotter->update();
}
void UnitVmeterSmallUI::push(LovyanGFX* dst, const int32_t x, const int32_t y)
{
auto left = x;
auto right = x + _wid - 1;
auto top = y;
auto bottom = y + _hgt - 1;
auto w = right - left + 1;
auto h = bottom - top + 1;
auto f = dst->getFont();
dst->setFont(&fonts::Font0);
dst->setTextColor(TFT_WHITE);
auto td = dst->getTextDatum();
// BG
dst->fillRoundRect(x, y, _wid, _hgt, GAP, TFT_RED);
dst->fillRoundRect(x + GAP, y + GAP, _wid - GAP * 2, _hgt - GAP * 2, GAP, TFT_BLACK);
_voltagePlotter->push(dst, x + GAP, y + GAP);
auto s = m5::utility::formatString("MIN:%5dmV", _voltagePlotter->minimum());
dst->drawString(s.c_str(), x + GAP * 2, y + GAP * 2 + _voltagePlotter->height() + 16);
s = m5::utility::formatString("MAX:%5dmV", _voltagePlotter->maximum());
dst->drawString(s.c_str(), x + GAP * 2, y + GAP * 2 + _voltagePlotter->height() + 16 + 10 * 1);
dst->setFont(&fonts::Font2);
dst->setTextColor(TFT_RED);
dst->setTextDatum(textdatum_t::middle_center);
dst->drawString("UnitVmeter", left + w / 2, top + h / 2);
dst->setTextDatum(td);
dst->setFont(f);
}

View file

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file ui_UnitVmeter.hpp
@brief UI for UnitVmeter
*/
#ifndef UI_UNIT_VMETER_HPP
#define UI_UNIT_VMETER_HPP
#include "parts/ui_plotter.hpp"
#include "ui_UnitBase.hpp"
#include <memory>
class UnitVmeterSmallUI : public UnitUIBase {
public:
explicit UnitVmeterSmallUI(LovyanGFX* parent) : UnitUIBase(parent)
{
}
void push_back(const float mv);
void construct() override;
void update() override;
void push(LovyanGFX* dst, const int32_t x, const int32_t y) override;
private:
std::unique_ptr<m5::ui::Plotter> _voltagePlotter{};
std::vector<float> _intermediateBuffer{};
};
#endif

View file

@ -0,0 +1,31 @@
{
"name": "M5UnitUnified",
"description": "M5UnitUnified is a library for unified handling of various M5 units products.(Alpha version)",
"keywords": "M5UnitUnified",
"authors": {
"name": "M5Stack",
"url": "http://www.m5stack.com"
},
"repository": {
"type": "git",
"url": "https://github.com/m5stack/M5UnitUnified.git"
},
"dependencies": {
"m5stack/M5Utility": "*",
"m5stack/M5HAL": "*"
},
"version": "0.1.4",
"frameworks": [
"arduino"
],
"platforms": [
"espressif32"
],
"headers": "M5UnitUnified.h",
"license": "MIT",
"export": {
"exclude": [
"docs/html"
]
}
}

View file

@ -0,0 +1,11 @@
name=M5UnitUnified
version=0.1.4
author=M5Stack
maintainer=M5Stack
sentence=M5UnitUnified is a library for unified handling of various M5 units products. (Alpha version)
paragraph=
category=Device Control
url=https://github.com/m5stack/M5UnitUnified.git
architectures=esp32
includes=M5UnitUnified.h
depends=M5Utility,M5HAL

View file

@ -0,0 +1,41 @@
{
"build": {
"arduino": {
"memory_type": "qio_opi",
"ldscript": "esp32s3_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_M5STACK_ATOMS3R",
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "dio",
"mcu": "esp32s3",
"variant": "m5stack_atoms3"
},
"connectivity": [
"bluetooth",
"wifi"
],
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stack AtomS3R",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 460800
},
"url": "https://docs.m5stack.com/en/core/AtomS3R",
"vendor": "M5Stack"
}

View file

@ -0,0 +1,33 @@
{
"build": {
"core": "esp32",
"extra_flags": [
"-DARDUINO_M5STACK_NANOC6"
],
"f_cpu": "160000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"mcu": "esp32c6",
"variant": "esp32c6"
},
"connectivity": [
"wifi"
],
"debug": {
"openocd_target": "esp32c6.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stack NanoC6",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194384,
"require_upload_port": true,
"speed": 460800
},
"url": "https://docs.m5stack.com/en/core/M5NanoC6",
"vendor": "M5Stack"
}

View file

@ -0,0 +1,40 @@
{
"build": {
"arduino":{
"ldscript": "esp32_out.ld",
"partitions": "default_8MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DM5STACK_M5STICK_CPLUS2",
"-DBOARD_HAS_PSRAM",
"-mfix-esp32-psram-cache-issue",
"-mfix-esp32-psram-cache-strategy=memw",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "dio",
"mcu": "esp32",
"variant": "m5stick_c"
},
"connectivity": [
"wifi",
"bluetooth"
],
"frameworks": [
"arduino",
"espidf"
],
"name": "M5Stick-CPlus2",
"upload": {
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
"require_upload_port": true,
"speed": 1500000
},
"url": "https://docs.m5stack.com/en/core/M5StickC%20PLUS2",
"vendor": "M5Stack"
}

View file

@ -0,0 +1,21 @@
# Remove compile target
Import("env")
EXCLUDE_FILES = (env.GetProjectOption("custom_exclude_src_files") or []).split(' ')
def skip_from_build(node):
src_path = node.get_abspath()
#filename = os.path.basename(src_path)
#print(EXCLUDE_FILES)
#print('>>>> {}'.format(src_path))
# for ef in EXCLUDE_FILES:
# if 'I2C_Class.cpp' in src_path and 'I2C_Class.cpp' in ef:
# print('>>>> {} {}'.format(src_path, ef))
if any(ef in src_path for ef in EXCLUDE_FILES):
return None
return node
env.AddBuildMiddleware(skip_from_build, "*.cpp")

View file

@ -0,0 +1,274 @@
;-----------------------------------------------------------------------
; For UnitTest and examples using M5UnitUnified local sources
;-----------------------------------------------------------------------
[platformio]
src_dir = ../src
[env]
build_flags = -Wall -Wextra -Wreturn-local-addr -Werror=format -Werror=return-local-addr
test_framework = googletest
test_build_src = true
; My local sources
lib_extra_dirs = ./lib
../
lib_ldf_mode = deep
lib_deps=m5stack/M5Unified
m5stack/M5Utility
m5stack/M5HAL
m5stack/M5Unit-ENV
m5stack/M5Unit-METER
m5stack/M5Unit-HUB
m5stack/M5Unit-GESTURE
m5stack/M5Unit-HEART
m5stack/M5Unit-TOF
m5stack/M5Unit-WEIGHT
m5stack/M5Unit-ANADIG
m5stack/M5Unit-COLOR
m5stack/M5Unit-THERMO
m5stack/M5Unit-EXTIO
m5stack/M5Unit-INFRARED
m5stack/M5Unit-CRYPTO
https://github.com/m5stack/M5Unit-RFID
https://github.com/m5stack/M5Unit-DISTANCE
https://github.com/m5stack/M5Unit-KEYBOARD
; https://github.com/m5stack/M5Unit-KEYBOARD#develop
; boschsensortec/BME68x Sensor library@1.2.40408
; boschsensortec/bsec2@1.8.2610
; Ignore DL'd M5UnitUnified, using local!
lib_ignore = M5UnitUnified
; --------------------------------
[m5base]
monitor_speed = 115200
monitor_filters = esp32_exception_decoder, time
upload_speed = 1500000
test_speed = 115200
test_filter= embedded/test_update
test_ignore= native/*
;Arduino-esp 2.0.4 (Changed Wire) at 5.1.0
platform = espressif32 @6.8.1
;platform = espressif32 @ 5.0.0
framework = arduino
[Core]
extends = m5base
board = m5stack-grey
;m5stack-core-esp32-16M ;;6.8.0 or later
;m5stack-core-esp32
lib_deps = ${env.lib_deps}
[Core2]
extends = m5base
board = m5stack-core2
lib_deps = ${env.lib_deps}
[CoreS3]
extends = m5base
board = m5stack-cores3
lib_deps = ${env.lib_deps}
[Fire]
extends = m5base
board = m5stack-fire
lib_deps = ${env.lib_deps}
[StampS3]
;include M5Capsule, DinMeter
extends = m5base
board = m5stack-stamps3
lib_deps = ${env.lib_deps}
[Dial]
extends = m5base
board = m5stack-stamps3
lib_deps = ${env.lib_deps}
m5stack/M5Dial
[AtomMatrix]
extends = m5base
board = m5stack-atom
lib_deps = ${env.lib_deps}
[AtomS3]
extends = m5base
board = m5stack-atoms3
lib_deps = ${env.lib_deps}
; Using ./boards/m5stack-atoms3r.json
[AtomS3R]
extends = m5base
board = m5stack-atoms3r
lib_deps = ${env.lib_deps}
; Using ./boards/m5stack-nanoc6.json
[NanoC6]
extends = m5base
board = m5stack-nanoc6
platform = https://github.com/platformio/platform-espressif32.git
platform_packages =
platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.7
platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1
board_build.partitions = default.csv
lib_deps = ${env.lib_deps}
lib_ignore = ${env.lib_ignore}
bsec2
[StickCPlus]
extends = m5base
board = m5stick-c
lib_deps = ${env.lib_deps}
; Using ./boards/m5stick-cplus2.json
[StickCPlus2]
extends = m5base
board = m5stick-cplus2
lib_deps = ${env.lib_deps}
[Paper]
extends = m5base
board = m5stack-fire
lib_deps = ${env.lib_deps}
[CoreInk]
extends = m5base
board = m5stack-coreink
lib_deps = ${env.lib_deps}
[sdl]
build_flags = -O3 -xc++ -std=c++14 -lSDL2
-arch arm64 ; for arm mac
-I"/usr/local/include/SDL2" ; for intel mac homebrew SDL2
-L"/usr/local/lib" ; for intel mac homebrew SDL2
-I"${sysenv.HOMEBREW_PREFIX}/include/SDL2" ; for arm mac homebrew SDL2
-L"${sysenv.HOMEBREW_PREFIX}/lib" ; for arm mac homebrew SDL2
platform = native
test_filter= native/*
test_ignore= embedded/*
lib_deps = ${env.lib_deps}
; --------------------------------
;Choose build options
[option_release]
build_type=release
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=3
-DLOG_LOCAL_LEVEL=3
-DAPP_LOG_LEVEL=3
-DM5_LOG_LEVEL=3
[option_log]
build_type=release
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=5
-DLOG_LOCAL_LEVEL=5
-DAPP_LOG_LEVEL=5
[option_debug]
build_type=debug
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=5
-DLOG_LOCAL_LEVEL=5
-DAPP_LOG_LEVEL=5
-DDEBUG
[option_map]
build_type=release
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=3
-DLOG_LOCAL_LEVEL=3
-DAPP_LOG_LEVEL=3
-DM5_LOG_LEVEL=0
-Wl,-Map,output.map
; Require at leaset C++14 after 1.13.0
[test_fw]
lib_deps = google/googletest@1.12.1
; --------------------------------
; [env:test_native]
; extends = sdl, option_release
; build_flags = ${sdl.build_flags} ${option_release.build_flags}
; lib_deps = ${sdl.lib_deps}
; ${test_fw.lib_deps}
; ;lib_compat_mode= off
; ; Removal of out-of-M5UnitUnified files that cause errors
; extra_scripts = pre:custom_script.py
; custom_exclude_src_files=bme68xLibrary.cpp bsec2.cpp BMP280.cpp DHT12.cpp M5Unit-ENV/I2C_Class.cpp SCD4X.cpp SHT3X.cpp M5Unit-ENV/QMP6988.cpp SHT4X.cpp utility.cpp M5UnitKmeterISO.cpp M5UnitWeightI2C.cpp
; lib_ignore = M5UnitUnified
; bsec2
; BME68x Sensor library
[env:test_Core]
extends=Core, option_release
lib_deps = ${Core.lib_deps}
${test_fw.lib_deps}
[env:test_Core2]
extends=Core2, option_release
lib_deps = ${Core2.lib_deps}
${test_fw.lib_deps}
[env:test_CoreS3]
extends=CoreS3, option_release
lib_deps = ${CoreS3.lib_deps}
${test_fw.lib_deps}
[env:test_Fire]
extends=Fire, option_release
lib_deps = ${Fire.lib_deps}
${test_fw.lib_deps}
[env:test_StampS3]
extends=StampS3, option_release
lib_deps = ${StampS3.lib_deps}
${test_fw.lib_deps}
[env:test_Dial]
extends=Dial, option_release
lib_deps = ${Dial.lib_deps}
${test_fw.lib_deps}
[env:test_AtomMatrix]
extends=AtomMatrix, option_release
lib_deps = ${AtomMatrix.lib_deps}
${test_fw.lib_deps}
[env:test_AtomS3]
extends=AtomS3, option_release
lib_deps = ${AtomS3.lib_deps}
${test_fw.lib_deps}
[env:test_AtomS3R]
extends=AtomS3R, option_release
lib_deps = ${AtomS3R.lib_deps}
${test_fw.lib_deps}
[env:test_NanoC6]
extends=NanoC6, option_release
lib_deps = ${NanoC6.lib_deps}
${test_fw.lib_deps}
[env:test_StickCPlus]
extends=StickCPlus, option_release
lib_deps = ${StickCPlus.lib_deps}
${test_fw.lib_deps}
[env:test_StickCPlus2]
extends=StickCPlus2, option_release
lib_deps = ${StickCPlus2.lib_deps}
${test_fw.lib_deps}
[env:test_Paper]
extends=Paper, option_release
lib_deps = ${Paper.lib_deps}
${test_fw.lib_deps}
[env:test_CoreInk]
extends=CoreInk, option_release
lib_deps = ${CoreInk.lib_deps}
${test_fw.lib_deps}

View file

@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
main for UnitTest on native
*/
#include <gtest/gtest.h>
// C++ version
#if __cplusplus >= 202002L
#pragma message "C++20 or later"
#elif __cplusplus >= 201703L
#pragma message "C++17 or later"
#elif __cplusplus >= 201402L
#pragma message "C++14 or later"
#elif __cplusplus >= 201103L
#pragma message "C++11 or later"
#else
#error "Need C++11 or later"
#endif
// Compiler
#if defined(__clang__)
#pragma message "Clang"
#elif defined(_MSC_VER)
#pragma message "MSVC"
#elif defined(__BORLANDC__)
#pragma message "BORLANDC"
#elif defined(__MINGW32__) || defined(__MINGW64__)
#pragma message "MINGW"
#elif defined(__INTEL_COMPILER)
#pragma message "ICC"
#elif defined(__GNUG__)
#pragma message "GCC"
#else
#pragma message "Unknown compiler"
#endif
/*
For native test, this main() is used.
If the Arduino framework is used, the framework library main is used.
*/
#if !defined(ARDUINO)
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
#ifdef GTEST_FILTER
::testing::GTEST_FLAG(filter) = GTEST_FILTER;
#endif
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
RUN_ALL_TESTS();
#pragma GCC diagnostic pop
// Always return zero-code and allow PlatformIO to parse results
return 0;
}
#endif

View file

@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
main for UnitTest on embedded
*/
#include <gtest/gtest.h>
#include <M5Unified.h>
#include <esp_system.h>
#pragma message "Embedded setup/loop"
#if __has_include(<esp_idf_version.h>)
#include <esp_idf_version.h>
#else // esp_idf_version.h has been introduced in Arduino 1.0.5 (ESP-IDF3.3)
#define ESP_IDF_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(3, 2, 0)
#endif
namespace {
auto& lcd = M5.Display;
} // namespace
void test()
{
lcd.fillRect(0, 0, lcd.width() >> 1, lcd.height(), RUN_ALL_TESTS() ? TFT_RED : TFT_GREEN);
}
void setup()
{
delay(1500);
M5.begin();
M5_LOGI("CPP %ld", __cplusplus);
M5_LOGI("ESP-IDF Version %d.%d.%d", (ESP_IDF_VERSION >> 16) & 0xFF, (ESP_IDF_VERSION >> 8) & 0xFF,
ESP_IDF_VERSION & 0xFF);
M5_LOGI("BOARD:%X", M5.getBoard());
M5_LOGI("Heap: %u", esp_get_free_heap_size());
lcd.clear(TFT_DARKGRAY);
::testing::InitGoogleTest();
#ifdef GTEST_FILTER
::testing::GTEST_FLAG(filter) = GTEST_FILTER;
#endif
}
void loop()
{
test();
#if 0
delay(1000);
esp_restart();
#endif
while (true) {
delay(10000);
}
}

View file

@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for M5UnitComponet
*/
#include <gtest/gtest.h>
#include <M5UnitComponent.hpp>
#include <M5UnitUnified.hpp>
#include "unit_dummy.hpp"
#include <Wire.h>
using namespace m5::unit;
TEST(Component, Update)
{
UnitUnified units;
UnitDummy u;
EXPECT_FALSE(u.isRegistered());
{
auto cfg = u.component_config();
EXPECT_FALSE(cfg.self_update); // false as default
EXPECT_EQ(u.count, 0U);
EXPECT_TRUE(units.add(u, Wire));
units.update(); // Dont call u.update() because unit was not begun.
EXPECT_EQ(u.count, 0U);
EXPECT_TRUE(units.begin());
units.update(); // Call u.update()
EXPECT_EQ(u.count, 1U);
cfg.self_update = true;
u.component_config(cfg);
cfg = u.component_config();
EXPECT_TRUE(cfg.self_update);
units.update(); // Don't call u.update()
EXPECT_EQ(u.count, 1U);
u.update(); // If component_config.self_update is true, you have to call it yourself
EXPECT_EQ(u.count, 2U);
}
}

View file

@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for M5UnitComponet
*/
#include <gtest/gtest.h>
#include <M5UnitComponent.hpp>
#include "unit_dummy.hpp"
TEST(Component, Children)
{
m5::unit::UnitDummy u0, u1, u2, u3;
//
EXPECT_FALSE(u0.hasParent());
EXPECT_FALSE(u0.hasSiblings());
EXPECT_FALSE(u0.hasChildren());
EXPECT_EQ(0U, u0.childrenSize());
EXPECT_FALSE(u0.add(u1, 0));
// add 1
auto cfg = u0.component_config();
cfg.max_children = 1;
u0.component_config(cfg);
EXPECT_LT(u1.channel(), 0);
EXPECT_TRUE(u0.add(u1, 0));
EXPECT_FALSE(u0.add(u1, 1));
EXPECT_FALSE(u0.add(u2, 1));
EXPECT_FALSE(u0.hasParent());
EXPECT_FALSE(u0.hasSiblings());
EXPECT_TRUE(u0.hasChildren());
EXPECT_EQ(1U, u0.childrenSize());
EXPECT_EQ(0, u1.channel());
EXPECT_TRUE(u1.hasParent());
EXPECT_FALSE(u1.hasSiblings());
EXPECT_FALSE(u1.hasChildren());
// add 2
cfg = u0.component_config();
cfg.max_children = 2;
u0.component_config(cfg);
EXPECT_LT(u2.channel(), 0);
EXPECT_FALSE(u0.add(u2, 0)); // same channel (failed)
EXPECT_LT(u2.channel(), 0);
EXPECT_TRUE(u0.add(u2, 3));
EXPECT_FALSE(u0.hasParent());
EXPECT_FALSE(u0.hasSiblings());
EXPECT_TRUE(u0.hasChildren());
EXPECT_EQ(2U, u0.childrenSize());
EXPECT_TRUE(u1.hasParent());
EXPECT_TRUE(u1.hasSiblings());
EXPECT_FALSE(u1.hasChildren());
EXPECT_TRUE(u2.hasParent());
EXPECT_TRUE(u2.hasSiblings());
EXPECT_FALSE(u2.hasChildren());
EXPECT_EQ(3, u2.channel());
EXPECT_LT(u3.channel(), 0);
EXPECT_FALSE(u0.add(u3, 2)); // max = 2 (failed)
EXPECT_LT(u3.channel(), 0);
// iteration
m5::unit::UnitDummy* ptr[] = {&u1, &u2};
size_t i = 0;
for (auto it = u0.childBegin(); it != u0.childEnd(); ++it) {
EXPECT_EQ(&(*it), ptr[i++]);
// printf("[%u]:%s\n", it->port(), (*it).deviceName());
}
EXPECT_EQ(i, u0.childrenSize());
}

View file

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for M5UnitComponent
*/
#include "unit_dummy.hpp"
#include <M5Utility.hpp>
using namespace m5::utility::mmh3;
namespace m5 {
namespace unit {
const char UnitDummy::name[] = "UnitDummy";
const m5::unit::types::uid_t UnitDummy::uid{"UnitDummy"_mmh3};
const m5::unit::types::attr_t UnitDummy::attr{0};
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for M5UnitComponent
*/
#ifndef M5_UNIT_COMPONENT_TEST_UNIT_DUMMY_HPP
#define M5_UNIT_COMPONENT_TEST_UNIT_DUMMY_HPP
#include <M5UnitComponent.hpp>
namespace m5 {
namespace unit {
// DummyComponent for UnitTest
class UnitDummy : public m5::unit::Component {
M5_UNIT_COMPONENT_HPP_BUILDER(UnitDummy, 0x00);
public:
UnitDummy() : Component(0x01)
{
}
virtual ~UnitDummy()
{
}
virtual bool begin() override
{
return true;
}
virtual void update(const bool force = false) override
{
++count;
}
uint32_t count{};
};
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,169 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for M5UnitUnified
*/
#include <gtest/gtest.h>
#include <M5Unified.h>
#include <M5UnitComponent.hpp>
#include <M5UnitUnified.hpp>
#include <M5UnitUnifiedENV.h>
#include <M5UnitUnifiedMETER.h>
#include <M5UnitUnifiedHUB.h>
#include <M5UnitUnifiedGESTURE.h>
#include <M5UnitUnifiedHEART.h>
#include <M5UnitUnifiedTOF.h>
#include <M5UnitUnifiedWEIGHT.h>
#include <M5UnitUnifiedANADIG.h>
#include <M5UnitUnifiedCOLOR.h>
#include <M5UnitUnifiedTHERMO.h>
#include <M5UnitUnifiedDISTANCE.h>
#include <M5UnitUnifiedEXTIO.h>
#include <M5UnitUnifiedINFRARED.h>
#include <M5UnitUnifiedCRYPTO.h>
#include <M5UnitUnifiedRFID.h>
#include <M5UnitUnifiedKEYBOARD.h>
#include <algorithm>
#include <utility>
namespace {
#if 0
// Get the equivalent of a unique type name without RTTI
template <typename U>
const char* TypeName() {
return __PRETTY_FUNCTION__;
}
#endif
std::vector<m5::unit::Component*> vec;
template <class U>
void each_unit_test()
{
SCOPED_TRACE(U::name);
U* unit = new U();
EXPECT_TRUE((bool)unit);
M5_LOGI(">>%02XH %08X [%s]", U::DEFAULT_ADDRESS, U::uid, U::name);
// Are the values the same via class and via instance?
EXPECT_EQ(+U::DEFAULT_ADDRESS, unit->address());
EXPECT_STREQ(U::name, unit->deviceName());
EXPECT_EQ(U::uid, unit->identifier());
EXPECT_EQ(U::attr, unit->attribute());
// Identical IDs exist?
for (auto&& e : vec) {
EXPECT_NE(unit->identifier(), e->identifier()) << unit->deviceName() << " / " << e->deviceName();
}
// Move
{
U tmp;
// Move constructor
U mc(std::move(tmp));
// assign by move
U mc2;
mc2 = std::move(mc);
}
vec.push_back(unit);
}
} // namespace
// Check each value and check duplicate uid
TEST(UnitUnified, EachUnit)
{
// ENV
each_unit_test<m5::unit::UnitSCD40>();
each_unit_test<m5::unit::UnitSCD41>();
each_unit_test<m5::unit::UnitSHT30>();
each_unit_test<m5::unit::UnitQMP6988>();
each_unit_test<m5::unit::UnitENV3>();
each_unit_test<m5::unit::UnitBME688>();
each_unit_test<m5::unit::UnitSGP30>();
each_unit_test<m5::unit::UnitSHT40>();
each_unit_test<m5::unit::UnitBMP280>();
each_unit_test<m5::unit::UnitENV4>();
// METER
each_unit_test<m5::unit::UnitADS1113>();
each_unit_test<m5::unit::UnitADS1114>();
each_unit_test<m5::unit::UnitADS1115>();
each_unit_test<m5::unit::meter::UnitEEPROM>();
each_unit_test<m5::unit::UnitAmeter>();
each_unit_test<m5::unit::UnitVmeter>();
each_unit_test<m5::unit::UnitKmeterISO>();
each_unit_test<m5::unit::UnitDualKmeter>();
// HUB
each_unit_test<m5::unit::UnitPCA9548AP>();
// GESTURE
each_unit_test<m5::unit::UnitPAJ7620U2>();
// HEART
each_unit_test<m5::unit::UnitMAX30100>();
each_unit_test<m5::unit::UnitMAX30102>();
// TOF
each_unit_test<m5::unit::UnitVL53L0X>();
each_unit_test<m5::unit::UnitVL53L1X>();
// WEIGHT
each_unit_test<m5::unit::UnitWeightI2C>();
each_unit_test<m5::unit::UnitMiniScales>();
// ANADIG
each_unit_test<m5::unit::UnitMCP4725>();
each_unit_test<m5::unit::UnitGP8413>();
each_unit_test<m5::unit::UnitADS11XX>();
each_unit_test<m5::unit::UnitADS1110>();
each_unit_test<m5::unit::UnitADS1100>();
// COLOR
each_unit_test<m5::unit::UnitTCS34725>();
// THERMO
each_unit_test<m5::unit::UnitMLX90614>();
each_unit_test<m5::unit::UnitMLX90614BAA>();
each_unit_test<m5::unit::UnitNCIR2>();
// DISTANCE
each_unit_test<m5::unit::UnitRCWL9620>();
// EXTIO
each_unit_test<m5::unit::UnitExtIO2>();
// INFRARED
each_unit_test<m5::unit::UnitSTHS34PF80>();
// CRYPTO
each_unit_test<m5::unit::UnitATECC608B>();
each_unit_test<m5::unit::UnitATECC608B_TNGTLS>();
// RFID
each_unit_test<m5::unit::UnitMFRC522>();
each_unit_test<m5::unit::UnitWS1850S>();
// KEYBOARD
each_unit_test<m5::unit::UnitKeyboard>();
each_unit_test<m5::unit::UnitKeyboardBitwise>();
each_unit_test<m5::unit::UnitCardKB>();
each_unit_test<m5::unit::UnitFacesQWERTY>();
for (auto&& e : vec) {
delete e;
}
vec.clear();
}

View file

@ -0,0 +1,353 @@
;-----------------------------------------------------------------------
; For examples
;-----------------------------------------------------------------------
[platformio]
[env]
build_flags = -Wall -Wextra -Wreturn-local-addr -Werror=format -Werror=return-local-addr
test_framework = googletest
test_build_src = true
lib_ldf_mode = deep
lib_deps=m5stack/M5Unified
m5stack/M5Utility
m5stack/M5HAL
; --------------------------------
[m5base]
monitor_speed = 115200
monitor_filters = esp32_exception_decoder, time
upload_speed = 1500000
test_speed = 115200
test_filter= embedded/test_update
test_ignore= native/*
;Arduino-esp 2.0.4 (Changed Wire) at 5.1.0
platform = espressif32 @6.8.1
;platform = espressif32 @ 5.0.0
framework = arduino
[Core]
extends = m5base
board = m5stack-grey
;m5stack-core-esp32-16M ;;6.8.0 or later
;m5stack-core-esp32
lib_deps = ${env.lib_deps}
[Core2]
extends = m5base
board = m5stack-core2
lib_deps = ${env.lib_deps}
[CoreS3]
extends = m5base
board = m5stack-cores3
lib_deps = ${env.lib_deps}
[Fire]
extends = m5base
board = m5stack-fire
lib_deps = ${env.lib_deps}
[StampS3]
;include M5Capsule, DinMeter
extends = m5base
board = m5stack-stamps3
lib_deps = ${env.lib_deps}
[Dial]
extends = m5base
board = m5stack-stamps3
lib_deps = ${env.lib_deps}
m5stack/M5Dial
[AtomMatrix]
extends = m5base
board = m5stack-atom
lib_deps = ${env.lib_deps}
[AtomS3]
extends = m5base
board = m5stack-atoms3
lib_deps = ${env.lib_deps}
; Using ./boards/m5stack-atoms3r.json
[AtomS3R]
extends = m5base
board = m5stack-atoms3r
lib_deps = ${env.lib_deps}
; Using ./boards/m5stack-nanoc6.json
[NanoC6]
extends = m5base
board = m5stack-nanoc6
platform = https://github.com/platformio/platform-espressif32.git
platform_packages =
platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.7
platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-libs.git#idf-release/v5.1
board_build.partitions = default.csv
lib_deps = ${env.lib_deps}
[StickCPlus]
extends = m5base
board = m5stick-c
lib_deps = ${env.lib_deps}
; Using ./boards/m5stick-cplus2.json
[StickCPlus2]
extends = m5base
board = m5stick-cplus2
lib_deps = ${env.lib_deps}
[Paper]
extends = m5base
board = m5stack-fire
lib_deps = ${env.lib_deps}
[CoreInk]
extends = m5base
board = m5stack-coreink
lib_deps = ${env.lib_deps}
[sdl]
build_flags = -O3 -xc++ -std=c++14 -lSDL2
-arch arm64 ; for arm mac
-I"/usr/local/include/SDL2" ; for intel mac homebrew SDL2
-L"/usr/local/lib" ; for intel mac homebrew SDL2
-I"${sysenv.HOMEBREW_PREFIX}/include/SDL2" ; for arm mac homebrew SDL2
-L"${sysenv.HOMEBREW_PREFIX}/lib" ; for arm mac homebrew SDL2
platform = native
test_filter= native/*
test_ignore= embedded/*
lib_deps = ${env.lib_deps}
; --------------------------------
;Choose build options
[option_release]
build_type=release
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=3
-DLOG_LOCAL_LEVEL=3
-DAPP_LOG_LEVEL=3
-DM5_LOG_LEVEL=3
[option_log]
build_type=release
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=5
-DLOG_LOCAL_LEVEL=5
-DAPP_LOG_LEVEL=5
[option_debug]
build_type=debug
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=5
-DLOG_LOCAL_LEVEL=5
-DAPP_LOG_LEVEL=5
-DDEBUG
[option_map]
build_type=release
build_flags = ${env.build_flags}
-DCORE_DEBUG_LEVEL=3
-DLOG_LOCAL_LEVEL=3
-DAPP_LOG_LEVEL=3
-DM5_LOG_LEVEL=0
-Wl,-Map,output.map
[example]
lib_deps=${env.lib_deps}
m5stack/M5Unit-ENV
;examples Simple
[env:Simple_Core]
extends=Core, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_Core2]
extends=Core2, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_CoreS3]
extends=CoreS3, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_Fire]
extends=Fire, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_StampS3]
extends=StampS3, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_Dial]
extends=Dial, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_AtomMatrix]
extends=AtomMatrix, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_AtomS3]
extends=AtomS3, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_AtomS3R]
extends=AtomS3R, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_NanoC6]
extends=NanoC6, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_StickCPlus]
extends=StickCPlus, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_StickCPlus2]
extends=StickCPlus2, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_Paper]
extends=Paper, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
[env:Simple_CoreInk]
extends=CoreInk, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/Simple>
;examples SelfUpdate
[env:SelfUpdate_Core]
extends=Core, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_Core2]
extends=Core2, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_CoreS3]
extends=CoreS3, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_Fire]
extends=Fire, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_StampS3]
extends=StampS3, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_Dial]
extends=Dial, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_AtomMatrix]
extends=AtomMatrix, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_AtomS3]
extends=AtomS3, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_AtomS3R]
extends=AtomS3R, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_NanoC6]
extends=NanoC6, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_StickCPlus]
extends=StickCPlus, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_StickCPlus2]
extends=StickCPlus2, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_Paper]
extends=Paper, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
[env:SelfUpdate_CoreInk]
extends=CoreInk, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/SelfUpdate>
;examples ComponentOnly
[env:ComponentOnly_Core]
extends=Core, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_Core2]
extends=Core2, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_CoreS3]
extends=CoreS3, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_Fire]
extends=Fire, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_StampS3]
extends=StampS3, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_Dial]
extends=Dial, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_AtomMatrix]
extends=AtomMatrix, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_AtomS3]
extends=AtomS3, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_AtomS3R]
extends=AtomS3R, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_NanoC6]
extends=NanoC6, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_StickCPlus]
extends=StickCPlus, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_StickCPlus2]
extends=StickCPlus2, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_Paper]
extends=Paper, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
[env:ComponentOnly_CoreInk]
extends=CoreInk, option_release, example
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/Basic/ComponentOnly>
;demo MultipleUnits
[demo]
lib_deps=${env.lib_deps}
m5stack/M5Unit-HUB
m5stack/M5Unit-ENV
m5stack/M5Unit-METER
m5stack/M5Unit-HEART
[env:MultipleUnits_Core]
extends=Core, option_release, demo
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/demo/MultipleUnits>
[env:MultipleUnits_Core2]
extends=Core2, option_release, demo
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/demo/MultipleUnits>
[env:MultipleUnits_CoreS3]
extends=CoreS3, option_release, demo
build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/demo/MultipleUnits>

View file

@ -0,0 +1,395 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitComponent.cpp
@brief Base class for Unit Component
*/
#include "M5UnitComponent.hpp"
#include <M5Utility.hpp>
#include <algorithm>
#include <array>
using namespace m5::unit::types;
namespace m5 {
namespace unit {
const char Component::name[] = "";
const types::uid_t Component::uid{};
const types::attr_t Component::attr{};
Component::Component(const uint8_t addr) : _adapter{new Adapter()}, _addr{addr}
{
}
size_t Component::childrenSize() const
{
size_t sz{};
auto it = childBegin();
while (it != childEnd()) {
++sz;
++it;
}
return sz;
}
bool Component::existsChild(const uint8_t ch) const
{
if (!_child) {
return false;
}
return std::any_of(childBegin(), childEnd(), [&ch](const Component& c) { return ch == c.channel(); });
}
bool Component::canAccessI2C() const
{
return attribute() & attribute::AccessI2C;
}
bool Component::canAccessGPIO() const
{
return attribute() & attribute::AccessGPIO;
}
bool Component::add(Component& c, const int16_t ch)
{
if (childrenSize() >= _component_cfg.max_children) {
M5_LIB_LOGE("Can't connect any more");
return false;
}
if (existsChild(ch)) {
M5_LIB_LOGE("Already connected an other unit at channel:%u", ch);
return false;
}
if (isRegistered()) {
M5_LIB_LOGE(
"As the parent unit is already registered with the UnitUnified, no additional children can be added");
return false;
}
if (c.isRegistered()) {
M5_LIB_LOGE("Children already registered with UnitUnified cannot be added");
return false;
}
if (!add_child(&c)) {
return false;
}
c._channel = ch;
return true;
}
bool Component::add_child(Component* c)
{
if (!c || c->_parent || c->_prev || c->_next) {
M5_LIB_LOGE("Invalid child [%s] %p / %p / %p", c ? c->deviceName() : "null", c ? c->_parent : nullptr,
c ? c->_next : nullptr, c ? c->_prev : nullptr);
return false;
}
// Add to tail
if (!_child) {
_child = c;
} else {
auto last = _child;
while (last->_next) {
last = last->_next;
}
last->_next = c;
c->_prev = last;
}
c->_parent = this;
return true;
}
Component* Component::child(const uint8_t ch) const
{
auto it = childBegin();
while (it != childEnd()) {
if (it->channel() == ch) {
return const_cast<Component*>(&*it);
}
++it;
}
return nullptr;
}
bool Component::assign(m5::hal::bus::Bus* bus)
{
if (_addr) {
_adapter = std::make_shared<AdapterI2C>(bus, _addr, _component_cfg.clock);
}
return static_cast<bool>(_adapter);
}
bool Component::assign(TwoWire& wire)
{
if (canAccessI2C() && _addr) {
_adapter = std::make_shared<AdapterI2C>(wire, _addr, _component_cfg.clock);
}
return static_cast<bool>(_adapter);
}
bool Component::assign(const int8_t rx_pin, const int8_t tx_pin)
{
if (canAccessGPIO()) {
_adapter = std::make_shared<AdapterGPIO>(rx_pin, tx_pin);
}
return static_cast<bool>(_adapter);
}
bool Component::selectChannel(const uint8_t ch)
{
bool ret{true};
if (hasParent()) {
ret = _parent->selectChannel(channel());
}
return ret && (select_channel(ch) == m5::hal::error::error_t::OK);
}
m5::hal::error::error_t Component::readWithTransaction(uint8_t* data, const size_t len)
{
selectChannel(channel());
auto r = adapter()->readWithTransaction(data, len);
return r;
}
m5::hal::error::error_t Component::writeWithTransaction(const uint8_t* data, const size_t len, const uint32_t exparam)
{
selectChannel(channel());
return adapter()->writeWithTransaction(data, len, exparam);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
m5::hal::error::error_t Component::writeWithTransaction(const Reg reg, const uint8_t* data, const size_t len,
const bool stop)
{
selectChannel(channel());
return adapter()->writeWithTransaction(reg, data, len, stop);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::readRegister(const Reg reg, uint8_t* rbuf, const size_t len, const uint32_t delayMillis,
const bool stop)
{
if (!writeRegister(reg, nullptr, 0U, stop)) {
M5_LIB_LOGE("Failed to write");
return false;
}
m5::utility::delay(delayMillis);
return (readWithTransaction(rbuf, len) == m5::hal::error::error_t::OK);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::readRegister8(const Reg reg, uint8_t& result, const uint32_t delayMillis, const bool stop)
{
return readRegister(reg, &result, 1, delayMillis, stop);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::read_register16E(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop,
const bool endian)
{
uint8_t tmp[2]{};
auto ret = readRegister(reg, tmp, 2, delayMillis, stop);
if (ret) {
result = (tmp[!endian] << 8) | tmp[endian];
}
return ret;
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::read_register32E(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop,
const bool endian)
{
uint8_t tmp[4]{};
auto ret = readRegister(reg, tmp, 4, delayMillis, stop);
if (ret) {
result = (tmp[0 + 3 * endian] | (tmp[1 + endian] << 8) | (tmp[2 - endian] << 16)) | (tmp[3 - 3 * endian] << 24);
}
return ret;
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::writeRegister(const Reg reg, const uint8_t* buf, const size_t len, const bool stop)
{
return (sizeof(Reg) == 2
? writeWithTransaction(reg, buf, len, stop)
: writeWithTransaction((uint8_t)(reg & 0xFF), buf, len, stop)) == m5::hal::error::error_t::OK;
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::writeRegister8(const Reg reg, const uint8_t value, const bool stop)
{
return writeRegister(reg, &value, 1, stop);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::write_register16E(const Reg reg, const uint16_t value, const bool stop, const bool endian)
{
uint8_t tmp[2]{};
tmp[endian] = value & 0xFF;
tmp[!endian] = (value >> 8) & 0xFF;
return writeRegister(reg, tmp, 2, stop);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type>
bool Component::write_register32E(const Reg reg, const uint32_t value, const bool stop, const bool endian)
{
uint8_t tmp[4]{};
tmp[0 + endian * 3] = value & 0xFF;
tmp[1 + endian] = (value >> 8) & 0xFF;
tmp[2 - endian] = (value >> 16) & 0xFF;
tmp[3 - endian * 3] = (value >> 24) & 0xFF;
return writeRegister(reg, tmp, 4, stop);
}
bool Component::generalCall(const uint8_t* data, const size_t len)
{
return adapter()->generalCall(data, len) == m5::hal::error::error_t::OK;
}
bool Component::pinModeRX(const gpio::Mode m)
{
return adapter()->pinModeRX(m) == m5::hal::error::error_t::OK;
}
bool Component::writeDigitalRX(const bool high)
{
return adapter()->writeDigitalRX(high) == m5::hal::error::error_t::OK;
}
bool Component::readDigitalRX(bool& high)
{
return adapter()->readDigitalRX(high) == m5::hal::error::error_t::OK;
}
bool Component::writeAnalogRX(const uint16_t v)
{
return adapter()->writeAnalogRX(v) == m5::hal::error::error_t::OK;
}
bool Component::readAnalogRX(uint16_t& v)
{
return adapter()->readAnalogRX(v) == m5::hal::error::error_t::OK;
}
bool Component::pulseInRX(uint32_t& duration, const int state, const uint32_t timeout_us)
{
return adapter()->pulseInRX(duration, state, timeout_us) == m5::hal::error::error_t::OK;
}
bool Component::pinModeTX(const gpio::Mode m)
{
return adapter()->pinModeTX(m) == m5::hal::error::error_t::OK;
}
bool Component::writeDigitalTX(const bool high)
{
return adapter()->writeDigitalTX(high) == m5::hal::error::error_t::OK;
}
bool Component::readDigitalTX(bool& high)
{
return adapter()->readDigitalTX(high) == m5::hal::error::error_t::OK;
}
bool Component::writeAnalogTX(const uint16_t v)
{
return adapter()->writeAnalogTX(v) == m5::hal::error::error_t::OK;
}
bool Component::readAnalogTX(uint16_t& v)
{
return adapter()->readAnalogTX(v) == m5::hal::error::error_t::OK;
}
bool Component::pulseInTX(uint32_t& duration, const int state, const uint32_t timeout_us)
{
return adapter()->pulseInTX(duration, state, timeout_us) == m5::hal::error::error_t::OK;
}
bool Component::changeAddress(const uint8_t addr)
{
if (canAccessI2C() && m5::utility::isValidI2CAddress(addr)) {
auto ad = asAdapter<AdapterI2C>(Adapter::Type::I2C);
if (ad) {
M5_LIB_LOGI("Change to address %x", addr);
_addr = addr;
ad->setAddress(addr);
return true;
}
}
M5_LIB_LOGE("Failed to change, %u, %x", canAccessI2C(), addr);
return false;
}
std::string Component::debugInfo() const
{
std::string tmp{};
switch (_adapter->type()) {
case Adapter::Type::I2C:
tmp = m5::utility::formatString("%p:%u ADDR:%02X", _adapter.get(), _adapter.use_count(),
asAdapter<AdapterI2C>(Adapter::Type::I2C)->address());
break;
case Adapter::Type::GPIO:
tmp = m5::utility::formatString("%p:%u RX:%d TX:%d", _adapter.get(), _adapter.use_count(),
asAdapter<AdapterGPIO>(Adapter::Type::GPIO)->rx_pin(),
asAdapter<AdapterGPIO>(Adapter::Type::GPIO)->tx_pin());
break;
default:
tmp = m5::utility::formatString("%p:%u Type:%d", _adapter.get(), _adapter.use_count(), _adapter->type());
break;
}
return m5::utility::formatString("[%s]:ID{0X%08x}:%s CH:%d parent:%u children:%zu/%u", deviceName(), identifier(),
tmp.c_str(), channel(), hasParent(), childrenSize(), _component_cfg.max_children);
}
// Explicit template instantiation
template bool Component::readRegister<uint8_t>(const uint8_t, uint8_t*, const size_t, const uint32_t, const bool);
template bool Component::readRegister<uint16_t>(const uint16_t, uint8_t*, const size_t, const uint32_t, const bool);
template bool Component::readRegister8<uint8_t>(const uint8_t, uint8_t&, const uint32_t, const bool);
template bool Component::readRegister8<uint16_t>(const uint16_t, uint8_t&, const uint32_t, const bool);
template bool Component::read_register16E<uint8_t>(const uint8_t, uint16_t&, const uint32_t, const bool, const bool);
template bool Component::read_register16E<uint16_t>(const uint16_t, uint16_t&, const uint32_t, const bool, const bool);
template bool Component::read_register32E<uint8_t>(const uint8_t, uint32_t&, const uint32_t, const bool, const bool);
template bool Component::read_register32E<uint16_t>(const uint16_t, uint32_t&, const uint32_t, const bool, const bool);
template bool Component::writeRegister<uint8_t>(const uint8_t, const uint8_t*, const size_t, const bool);
template bool Component::writeRegister<uint16_t>(const uint16_t, const uint8_t*, const size_t, const bool);
template bool Component::writeRegister8<uint8_t>(const uint8_t, const uint8_t, const bool);
template bool Component::writeRegister8<uint16_t>(const uint16_t, const uint8_t, const bool);
template bool Component::write_register16E<uint8_t>(const uint8_t, const uint16_t, const bool, const bool);
template bool Component::write_register16E<uint16_t>(const uint16_t, const uint16_t, const bool, const bool);
template bool Component::write_register32E<uint8_t>(const uint8_t, const uint32_t, const bool, const bool);
template bool Component::write_register32E<uint16_t>(const uint16_t, const uint32_t, const bool, const bool);
template m5::hal::error::error_t Component::writeWithTransaction<uint8_t>(const uint8_t reg, const uint8_t* data,
const size_t len, const bool stop);
template m5::hal::error::error_t Component::writeWithTransaction<uint16_t>(const uint16_t reg, const uint8_t* data,
const size_t len, const bool stop);
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#ifndef M5_UNIT_COMPONENT_H
#define M5_UNIT_COMPONENT_H
#ifdef __cplusplus
#include "M5UnitComponent.hpp"
#else
#error M5UnitComponent requires a C++ compiler, please change file extension to .cc or .cpp
#endif
#endif

View file

@ -0,0 +1,754 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitComponent.hpp
@brief Main header of M5UnitComponent
*/
#ifndef M5_UNIT_COMPONENT_HPP
#define M5_UNIT_COMPONENT_HPP
#include "m5_unit_component/types.hpp"
#include "m5_unit_component/adapter.hpp"
#include <cstdint>
#include <vector>
#include <algorithm>
#include <iterator>
#include <type_traits>
#include <memory>
class TwoWire;
namespace m5 {
namespace unit {
class UnitUnified;
class Adapter;
/*!
@class m5::unit::Component
@brief Base class of unit component
*/
class Component {
public:
/*!
@struct component_config_t
@brief Component basic settings for begin
*/
struct component_config_t {
//! Clock for communication (default as 100000)
uint32_t clock{100000};
//! Maximum number of periodic measurement data to be stored
uint32_t stored_size{1};
//! Does the user call Unit's update? (default as false)
bool self_update{false};
//! Maximum number of units that can be connected (default as 0)
uint8_t max_children{0};
};
///@warning Define the same name and type in the derived class.
///@name Fixed parameters for class
///@{
static const types::uid_t uid; //!< @brief Unique identifier
static const types::attr_t attr; //!< @brief Attributes
static const char name[]; //!< @brief Device name string
///@}
///@warning COPY PROHIBITED
///@name Constructor
///@{
explicit Component(const uint8_t addr = 0x00); // I2C address
Component(const Component&) = delete;
Component(Component&&) noexcept = default;
///@}
///@warning COPY PROHIBITED
///@name Assignment
///@{
Component& operator=(const Component&) = delete;
Component& operator=(Component&&) noexcept = default;
///@}
virtual ~Component() = default;
///@name Component settings
///@{
/*! @brief Gets the common configurations in each unit */
inline component_config_t component_config()
{
return _component_cfg;
}
//! @brief Set the common configurations in each unit
inline void component_config(const component_config_t& cfg)
{
_component_cfg = cfg;
}
///@}
///@name Functions that must be inherited
///@{
/*!
@brief Begin unit
@details Initiate functions based on component config and unit config
*/
virtual bool begin()
{
return true;
}
/*!
@brief Update unit
@param force Forced communication for updates if true
*/
virtual void update(const bool force = false)
{
(void)force;
}
///@}
///@name Properties
///@{
/*! @brief Gets the device name */
inline const char* deviceName() const
{
return unit_device_name();
}
//! @brief Gets the identifier
inline types::uid_t identifier() const
{
return unit_identifier();
}
//! @brief Gets the attributes
inline types::attr_t attribute() const
{
return unit_attribute();
}
//! @brief Gets the category
inline types::category_t category() const
{
return unit_category();
}
//! @brief Gets the registered order (== 0 means not yet)
inline uint32_t order() const
{
return _order;
}
//! @brief Gets the channel if connected to another unit
inline int16_t channel() const
{
return _channel;
}
//! @brief Is the unit registered with the manager?
inline bool isRegistered() const
{
return _manager != nullptr;
}
//! @brief Address used to I2C access the device
inline uint8_t address() const
{
return _addr;
}
/*!
@brief Gets the access adapter
@warning Ownership is retained by the unit and should not be released
*/
inline Adapter* adapter() const
{
return _adapter.get();
}
//! @brief Gets the access adapter
template <class T>
inline auto asAdapter(const Adapter::Type t) ->
typename std::remove_cv<typename std::remove_pointer<T>::type>::type*
{
using U = typename std::remove_cv<typename std::remove_pointer<T>::type>::type;
static_assert(std::is_base_of<Adapter, U>::value, "T must be derived from Adapter");
return (_adapter->type() == t) ? static_cast<U*>(_adapter.get()) : nullptr;
}
template <class T>
inline auto asAdapter(const Adapter::Type t) const -> const
typename std::remove_cv<typename std::remove_pointer<T>::type>::type*
{
using U = typename std::remove_cv<typename std::remove_pointer<T>::type>::type;
static_assert(std::is_base_of<Adapter, U>::value, "T must be derived from Adapter");
return (_adapter->type() == t) ? static_cast<const U*>(_adapter.get()) : nullptr;
}
///@}
///@name Attributes
///@{
bool canAccessI2C() const;
bool canAccessGPIO() const;
///@}
///@name Periodic measurement
///@{
/*! @brief In periodic measurement? */
inline bool inPeriodic() const
{
return in_periodic();
}
//! @brief Periodic measurement data updated?
inline bool updated() const
{
return _updated;
}
/*!
@brief Time elapsed since start-up when the measurement data was updated in update()
@return Updated time (Unit: ms)
*/
inline types::elapsed_time_t updatedMillis() const
{
return _latest;
}
/*!
@brief Gets the periodic measurement interval
@return interval time (Unit: ms)
*/
inline types::elapsed_time_t interval() const
{
return _interval;
}
///@}
///@name Bus assignment
///@{
/*! @brief Assgin m5::hal::bus */
virtual bool assign(m5::hal::bus::Bus* bus);
/*! @brief Assgin TwoWire */
virtual bool assign(TwoWire& wire);
/*! @brief Assgin GPIO */
virtual bool assign(const int8_t rx_pin, const int8_t tx_pin);
///@}
///@note For daisy-chaining units such as hubs
///@name Parent-children relationship
///@{
/*! @brief Has parent unit? */
inline bool hasParent() const
{
return _parent != nullptr;
}
//! @brief Are there any other devices connected to the same parent unit besides yourself?
inline bool hasSiblings() const
{
return (_prev != nullptr) || (_next != nullptr);
}
//! @brief Are there other devices connected to me?
inline bool hasChildren() const
{
return _child;
}
//! @brief Number of units connected to me
size_t childrenSize() const;
//! @brief Is there an other unit connected to the specified channel?
bool existsChild(const uint8_t ch) const;
//! @brief Gets the parent unit
inline Component* parent()
{
return _parent;
}
#if 0
//! @brief Gets the parent unit
inline const Component* parent() const
{
return _parent;
}
#endif
//! @brief Gets the device connected to the specified channel
Component* child(const uint8_t chhanle) const;
//! @brief Connect the unit to the specified channel
bool add(Component& c, const int16_t channel);
//! @brief Select valid channel if exists
bool selectChannel(const uint8_t ch = 8);
///@}
///@cond 0
template <typename T>
class iterator {
public:
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
explicit iterator(Component* c = nullptr) : _ptr(c)
{
}
reference operator*() const
{
return *_ptr;
}
pointer operator->() const
{
return _ptr;
}
iterator& operator++()
{
_ptr = _ptr ? _ptr->_next : nullptr;
return *this;
}
iterator operator++(int)
{
auto tmp = *this;
++(*this);
return tmp;
}
friend bool operator==(const iterator& a, const iterator& b)
{
return a._ptr == b._ptr;
}
friend bool operator!=(const iterator& a, const iterator& b)
{
return a._ptr != b._ptr;
}
private:
Component* _ptr;
};
using child_iterator = iterator<Component>;
using const_child_iterator = iterator<const Component>;
inline child_iterator childBegin() noexcept
{
return child_iterator(_child);
}
inline child_iterator childEnd() noexcept
{
return child_iterator();
}
inline const_child_iterator childBegin() const noexcept
{
return const_child_iterator(_child);
}
inline const_child_iterator childEnd() const noexcept
{
return const_child_iterator();
}
///@endcond
/*! @brief General call for I2C*/
bool generalCall(const uint8_t* data, const size_t len);
//! @brief Output information for debug
virtual std::string debugInfo() const;
// I2C R/W
///@cond 0
m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool readRegister(const Reg reg, uint8_t* rbuf, const size_t len, const uint32_t delayMillis,
const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool readRegister8(const Reg reg, uint8_t& result, const uint32_t delayMillis, const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool readRegister16BE(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop = true)
{
return read_register16E(reg, result, delayMillis, stop, true);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool readRegister16LE(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop = true)
{
return read_register16E(reg, result, delayMillis, stop, false);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool readRegister32BE(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop = true)
{
return read_register32E(reg, result, delayMillis, stop, true);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool readRegister32LE(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop = true)
{
return read_register32E(reg, result, delayMillis, stop, false);
}
m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len, const uint32_t exparam = 1);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
m5::hal::error::error_t writeWithTransaction(const Reg reg, const uint8_t* data, const size_t len,
const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool writeRegister(const Reg reg, const uint8_t* buf = nullptr, const size_t len = 0U, const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool writeRegister8(const Reg reg, const uint8_t value, const bool stop = true);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool writeRegister16BE(const Reg reg, const uint16_t value, const bool stop = true)
{
return write_register16E(reg, value, stop, true);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool writeRegister16LE(const Reg reg, const uint16_t value, const bool stop = true)
{
return write_register16E(reg, value, stop, false);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool writeRegister32BE(const Reg reg, const uint32_t value, const bool stop = true)
{
return write_register32E(reg, value, stop, true);
}
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
inline bool writeRegister32LE(const Reg reg, const uint32_t value, const bool stop = true)
{
return write_register32E(reg, value, stop, false);
}
// GPIO
bool pinModeRX(const gpio::Mode m);
bool writeDigitalRX(const bool high);
bool readDigitalRX(bool& high);
bool writeAnalogRX(const uint16_t v);
bool readAnalogRX(uint16_t& v);
bool pulseInRX(uint32_t& duration, const int state, const uint32_t timeout_us = 1000000);
bool pinModeTX(const gpio::Mode m);
bool writeDigitalTX(const bool high);
bool readDigitalTX(bool& high);
bool writeAnalogTX(const uint16_t v);
bool readAnalogTX(uint16_t& v);
bool pulseInTX(uint32_t& duration, const int state, const uint32_t timeout_us = 1000000);
///@endcond
#if defined(DOXYGEN_PROCESS)
// There is a problem with the Doxygen output of templates containing std::enable_if,
// so we need a section for Dxygen output
///@name Read/Write
///@{
//! @brief Read any data with transaction
m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len);
//! @brief Read any data with transaction from register
template <typename Reg>
bool readRegister(const Reg reg, uint8_t* rbuf, const size_t len, const uint32_t delayMillis,
const bool stop = true);
//! @brief Read byte with transaction from register
template <typename Reg>
bool readRegister8(const Reg reg, uint8_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Read word in big-endian order with transaction from register
template <typename Reg>
bool readRegister16BE(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Read word in little-endian order with transaction from register
template <typename Reg>
bool readRegister16LE(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Read dword in big-endian order with transaction from register
template <typename Reg>
bool readRegister32BE(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Read dword in little-endian order with transaction from register
template <typename Reg>
bool readRegister32LE(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop = true);
//! @brief Write any data with transaction
m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len, const bool stop = true);
//! @brief Write any data with transaction to register
template <typename Reg>
m5::hal::error::error_t writeWithTransaction(const Reg reg, const uint8_t* data, const size_t len,
const bool stop = true);
//! @brief Write any data with transaction to register
template <typename Reg>
bool writeRegister(const Reg reg, const uint8_t* buf = nullptr, const size_t len = 0U, const bool stop = true);
//! @brief Write byte with transaction to register
template <typename Reg>
bool writeRegister8(const Reg reg, const uint8_t value, const bool stop = true);
//! @brief Write word in big-endian order with transaction from register
template <typename Reg>
bool writeRegister16BE(const Reg reg, const uint16_t value, const bool stop = true);
//! @brief Write word in little-endian order with transaction from register
template <typename Reg>
bool writeRegister16LE(const Reg reg, const uint16_t value, const bool stop = true);
//! @brief Write dword in big-endian order with transaction from register
template <typename Reg>
bool writeRegister32BE(const Reg reg, const uint32_t value, const bool stop = true);
//! @brief Write dword in little-endian order with transaction from register
template <typename Reg>
bool writeRegister32LE(const Reg reg, const uint32_t value, const bool stop = true);
///@}
#endif
protected:
// Proper implementation in derived classes is required
virtual const char* unit_device_name() const = 0;
virtual types::uid_t unit_identifier() const = 0;
virtual types::attr_t unit_attribute() const = 0;
inline virtual types::category_t unit_category() const
{
return types::category_t::None;
}
inline virtual bool in_periodic() const
{
return _periodic;
}
inline virtual std::shared_ptr<Adapter> ensure_adapter(const uint8_t /*ch*/)
{
return _adapter; // By default, offer my adapter for sharing
}
// Select valid channel if exists(PaHub etc...)
inline virtual m5::hal::error::error_t select_channel(const uint8_t)
{
return m5::hal::error::error_t::OK;
}
inline size_t stored_size() const
{
return _component_cfg.stored_size;
}
bool add_child(Component* c);
// I2C
bool changeAddress(const uint8_t addr); // Functions for dynamically addressable devices
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool read_register16E(const Reg reg, uint16_t& result, const uint32_t delayMillis, const bool stop,
const bool endian);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool write_register16E(const Reg reg, const uint16_t value, const bool stop, const bool endifan);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool read_register32E(const Reg reg, uint32_t& result, const uint32_t delayMillis, const bool stop,
const bool endian);
template <typename Reg,
typename std::enable_if<std::is_integral<Reg>::value && std::is_unsigned<Reg>::value && sizeof(Reg) <= 2,
std::nullptr_t>::type = nullptr>
bool write_register32E(const Reg reg, const uint32_t value, const bool stop, const bool endifan);
protected:
// For periodic measurement
types::elapsed_time_t _latest{}, _interval{};
bool _periodic{}; // During periodic measurement?
bool _updated{};
private:
UnitUnified* _manager{};
std::shared_ptr<m5::unit::Adapter> _adapter{};
uint32_t _order{};
component_config_t _component_cfg{};
int16_t _channel{-1}; // valid [0...]
uint8_t _addr{};
bool _begun{};
// for chain
Component* _parent{};
Component* _next{};
Component* _prev{};
Component* _child{};
friend class UnitUnified;
};
/*!
@class PeriodicMeasurementAdapter
@brief Interface class for periodic measurement (CRTP)
@details Common interface for accumulated periodic measurement data
@details Provide a common interface for periodic measurements for each unit
@tparam Derived Derived class
@tparam MD Type of the measurement data group
@warning MUST IMPLEMENT some functions (NOT VERTUAL)
- MD Derived::oldest_periodic_data() const;
- MD Derived::latestt_periodic_data() const;
- bool Derived::start_periodic_measurement(any arguments);
- bool Derived::stop_periodic_measurement():
@warning MUST ADD std::unique_ptr<m5::container::CircularBuffer<MD>> _data{}
in Derived class
@warning This class is an interface class and should not have any data
@note See also M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER
*/
template <class Derived, typename MD>
class PeriodicMeasurementAdapter {
public:
///@name Periodic measurement
///@{
/*!
@brief Start periodic measurement
@tparam Args Optional arguments
@return True if successful
@note Call Derived::start_periodic_measurement
*/
template <typename... Args>
inline bool startPeriodicMeasurement(Args&&... args)
{
// Prepare for future common initiation preprocessing needs
return static_cast<Derived*>(this)->start_periodic_measurement(std::forward<Args>(args)...);
}
/*!
@brief Stop periodic measurement
@tparam Args Optional arguments
@return True if successful
@note Call Derived::stop_periodic_measurement
*/
template <typename... Args>
inline bool stopPeriodicMeasurement(Args&&... args)
{
// Prepare for future common stopping preprocessing needs
return static_cast<Derived*>(this)->stop_periodic_measurement(std::forward<Args>(args)...);
}
///@}
///@name Data
///@{
//! @brief Gets the number of stored data
inline size_t available() const
{
return available_periodic_measurement_data();
}
//! @brief Is empty stored data?
inline bool empty() const
{
return empty_periodic_measurement_data();
}
//! @brief Is stored data full?
inline bool full() const
{
return full_periodic_measurement_data();
}
//! @brief Retrieve oldest stored data
inline MD oldest() const
{
return static_cast<const Derived*>(this)->oldest_periodic_data();
}
//! @brief Retrieve latest stored data
inline MD latest() const
{
return static_cast<const Derived*>(this)->latest_periodic_data();
}
//! @brief Discard the oldest data accumulated
inline void discard()
{
discard_periodic_measurement_data();
}
//! @brief Discard all data
inline void flush()
{
flush_periodic_measurement_data();
}
///@}
protected:
///@note Must implement in derived class
///@name Pure virtual functions
///@{
virtual size_t available_periodic_measurement_data() const = 0;
virtual bool empty_periodic_measurement_data() const = 0;
virtual bool full_periodic_measurement_data() const = 0;
virtual void discard_periodic_measurement_data() = 0;
virtual void flush_periodic_measurement_data() = 0;
///@}
};
} // namespace unit
} // namespace m5
// Helper for creating derived classes from Component
///@cond
#define M5_UNIT_COMPONENT_HPP_BUILDER(cls, reg) \
public: \
constexpr static uint8_t DEFAULT_ADDRESS{(reg)}; \
static const types::uid_t uid; \
static const types::attr_t attr; \
static const char name[]; \
\
cls(const cls&) = delete; \
\
cls& operator=(const cls&) = delete; \
\
cls(cls&&) noexcept = default; \
\
cls& operator=(cls&&) noexcept = default; \
\
protected: \
inline virtual const char* unit_device_name() const override \
{ \
return name; \
} \
inline virtual types::uid_t unit_identifier() const override \
{ \
return uid; \
} \
inline virtual types::attr_t unit_attribute() const override \
{ \
return attr; \
}
// Helper for creating derived class from PeriodicMeasurementAdapter
#define M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER(cls, md) \
protected: \
friend class PeriodicMeasurementAdapter<cls, md>; \
\
inline md oldest_periodic_data() const \
{ \
return !_data->empty() ? _data->front().value() : md{}; \
} \
inline md latest_periodic_data() const \
{ \
return !_data->empty() ? _data->back().value() : md{}; \
} \
inline virtual size_t available_periodic_measurement_data() const override \
{ \
return _data->size(); \
} \
inline virtual bool empty_periodic_measurement_data() const override \
{ \
return _data->empty(); \
} \
inline virtual bool full_periodic_measurement_data() const override \
{ \
return _data->full(); \
} \
inline virtual void discard_periodic_measurement_data() override \
{ \
_data->pop_front(); \
} \
inline virtual void flush_periodic_measurement_data() override \
{ \
_data->clear(); \
}
///@endcond
#endif

View file

@ -0,0 +1,150 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitUnified.cpp
@brief class UnitUnified
*/
#include "M5UnitUnified.hpp"
#include <M5Utility.hpp>
namespace m5 {
namespace unit {
uint32_t UnitUnified::_registerCount{0};
bool UnitUnified::add(Component& u, m5::hal::bus::Bus* bus)
{
if (u.isRegistered()) {
M5_LIB_LOGW("Already added");
return false;
}
if (!bus) {
M5_LIB_LOGE("Bus null");
return false;
}
M5_LIB_LOGD("Add [%s]:0x%02x", u.deviceName(), u.address());
u._manager = this;
if (u.assign(bus)) {
u._order = ++_registerCount;
_units.emplace_back(&u);
return add_children(u);
}
M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessI2C());
return false;
}
bool UnitUnified::add(Component& u, TwoWire& wire)
{
if (u.isRegistered()) {
M5_LIB_LOGW("Already added");
return false;
}
M5_LIB_LOGD("Add [%s] addr:%02x children:%zu", u.deviceName(), u.address(), u.childrenSize());
u._manager = this;
if (u.assign(wire)) {
u._order = ++_registerCount;
_units.emplace_back(&u);
return add_children(u);
}
M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessI2C());
return false;
}
bool UnitUnified::add(Component& u, const int8_t rx_pin, const int8_t tx_pin)
{
if (u.isRegistered()) {
M5_LIB_LOGW("Already added");
return false;
}
M5_LIB_LOGD("Add [%s] rx:%d tx:%d %zu", u.deviceName(), rx_pin, tx_pin, u.childrenSize());
u._manager = this;
if (u.assign(rx_pin, tx_pin)) {
u._order = ++_registerCount;
_units.emplace_back(&u);
return add_children(u);
}
M5_LIB_LOGE("Failed to assign %s:%u", u.deviceName(), u.canAccessGPIO());
return false;
}
// Add children if exists
bool UnitUnified::add_children(Component& u)
{
auto it = u.childBegin();
while (it != u.childEnd()) {
auto ch = it->channel();
M5_LIB_LOGV("%s child:%s channel:%u", u.deviceName(), it->deviceName(), ch);
if (it->isRegistered()) {
M5_LIB_LOGE("Already registered %s", it->deviceName());
return false;
}
it->_manager = this;
it->_adapter = u.ensure_adapter(ch);
M5_LIB_LOGD(" Shared:%u %u", u._adapter.use_count(), it->_adapter.use_count());
it->_order = ++_registerCount;
_units.emplace_back(&*it);
if (!add_children(*it)) {
return false;
}
++it;
}
return true;
}
bool UnitUnified::begin()
{
return !std::any_of(_units.begin(), _units.end(), [](Component* c) {
M5_LIB_LOGV("Try begin:%s", c->deviceName());
bool ret = c->_begun = c->begin();
if (!ret) {
M5_LIB_LOGE("Failed to begin: %s", c->debugInfo().c_str());
}
return !ret;
});
}
void UnitUnified::update(const bool force)
{
// Order of registration
for (auto&& u : _units) {
if (!u->_component_cfg.self_update && u->_begun) {
u->update(force);
}
}
}
std::string UnitUnified::debugInfo() const
{
std::string s = m5::utility::formatString("\nM5UnitUnified: %zu units\n", _units.size());
for (auto&& u : _units) {
if (!u->hasParent()) {
s += make_unit_info(u, 0);
}
}
return m5::utility::trim(s);
}
std::string UnitUnified::make_unit_info(const Component* u, const uint8_t indent) const
{
std::string s = m5::utility::formatString("%*c%s\n", indent * 4, ' ', u->debugInfo().c_str());
if (u->hasChildren()) {
s += make_unit_info(u->_child, indent + 1);
}
u = u->_next;
return u ? s += make_unit_info(u, indent) : s;
}
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitUnified.h
*/
#ifndef M5_UNIT_UNIFIED_H
#define M5_UNIT_UNIFIED_H
#ifdef __cplusplus
#include "M5UnitUnified.hpp"
#else
#error M5UnitUnified requires a C++ compiler, please change file extension to .cc or .cpp
#endif
#endif

View file

@ -0,0 +1,114 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file M5UnitUnified.hpp
@brief Main header of M5UnitUnified
@mainpage M5UnitUnified
M5UnitUnified is a library for unified handling of various M5 unit products.
- Unified APIs
- Unified Connections
- Unified Licensing
*/
#ifndef M5_UNIT_UNIFIED_HPP
#define M5_UNIT_UNIFIED_HPP
#include "M5UnitComponent.hpp"
#include <M5HAL.hpp>
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#else
#include <driver/rmt.h>
#endif
#include <vector>
#include <string>
class TwoWire;
/*!
@namespace m5
@brief Top level namespace of M5stack
*/
namespace m5 {
/*!
@namespace unit
@brief Unit-related namespace
*/
namespace unit {
class Component;
/*!
@class m5::unit::UnitUnified
@brief For managing and leading units
*/
class UnitUnified {
public:
using container_type = std::vector<Component*>;
///@warning COPY PROHIBITED
///@name Constructor
///@{
UnitUnified() = default;
UnitUnified(const UnitUnified&) = delete;
UnitUnified(UnitUnified&&) noexcept = default;
///@}
///@warning COPY PROHIBITED
///@name Assignment
///@{
UnitUnified& operator=(const UnitUnified&) = delete;
UnitUnified& operator=(UnitUnified&&) noexcept = default;
///@}
///@name Add unit
///@{
/*!
@brief Adding unit to be managed (I2C)
@param u Unit Component
@param wire TwoWire to be used
@return True if successful
*/
bool add(Component& u, TwoWire& wire);
/*!
@brief Adding unit to be managed (GPIO)
@param u Unit Component
@param rx_pin Pin number to be used for RX
@param tx_pin Pin number to be used for TX
@return True if successful
*/
bool add(Component& u, const int8_t rx_pin, const int8_t tx_pin);
/*!
@brief Adding unit to be managed (M5HAL)
@param u Unit Component
@param bus Bus to be used
@return True if successful
*/
bool add(Component& u, m5::hal::bus::Bus* bus);
///@}
//! @brief Begin of all units under management
bool begin();
//! @brief Update of all units under management
void update(const bool force = false);
//! @brief Output information for debug
std::string debugInfo() const;
protected:
bool add_children(Component& u);
std::string make_unit_info(const Component* u, const uint8_t indent = 0) const;
protected:
container_type _units{};
private:
static uint32_t _registerCount;
};
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file test_helper.hpp
@brief Helper for testing UnitComponent
@note Depends on GoogleTest
*/
#ifndef M5_UNIT_COMPONENT_GOOGLETEST_HELPER_HPP
#define M5_UNIT_COMPONENT_GOOGLETEST_HELPER_HPP
#include <M5Utility.hpp>
#include <thread>
#include <cassert>
namespace m5 {
namespace unit {
namespace googletest {
template <class U>
uint32_t test_periodic_measurement(U* unit, const uint32_t times, const uint32_t tolerance,
const uint32_t timeout_duration, void (*callback)(U*), const bool skip_after_test)
{
static_assert(std::is_base_of<m5::unit::Component, U>::value, "U must be derived from Component");
auto interval = unit->interval();
decltype(interval) avg{}, avgCnt{};
uint32_t cnt{times};
auto prev = unit->updatedMillis();
auto timeout_at = m5::utility::millis() + timeout_duration;
while (cnt && m5::utility::millis() <= timeout_at) {
unit->update();
if (unit->updated()) {
--cnt;
auto um = unit->updatedMillis();
if (prev) {
auto duration = um - prev;
++avgCnt;
avg += duration;
// M5_LOGI("dur:%ld", duration);
// EXPECT_LE(duration, interval + 1);
}
prev = um;
if (callback) {
callback(unit);
}
}
std::this_thread::yield();
}
if (!skip_after_test) {
EXPECT_EQ(cnt, 0U);
EXPECT_EQ(avgCnt, times - 1);
if (avgCnt) {
avg /= avgCnt;
EXPECT_LE(avg, decltype(interval)(interval + tolerance));
}
return avg;
}
return 0U;
}
template <class U>
uint32_t test_periodic_measurement(U* unit, const uint32_t times = 8, const uint32_t tolerance = 1,
void (*callback)(U*) = nullptr, const bool skip_after_test = false)
{
static_assert(std::is_base_of<m5::unit::Component, U>::value, "U must be derived from Component");
auto timeout_duration = (unit->interval() * 2) * times;
return test_periodic_measurement(unit, times, tolerance, timeout_duration, callback, skip_after_test);
}
template <class U>
uint32_t test_periodic_measurement(U* unit, const uint32_t times = 8, void (*callback)(U*) = nullptr,
const bool skip_after_test = false)
{
static_assert(std::is_base_of<m5::unit::Component, U>::value, "U must be derived from Component");
auto timeout_duration = (unit->interval() * 2) * times;
return test_periodic_measurement(unit, times, 1, timeout_duration, callback, skip_after_test);
}
} // namespace googletest
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,115 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file test_template.hpp
@brief Helper for testing UnitComponent
@note Depends on GoogleTest
*/
#ifndef M5_UNIT_COMPONENT_GOOGLETEST_TEMPLATE_HPP
#define M5_UNIT_COMPONENT_GOOGLETEST_TEMPLATE_HPP
#include "../M5UnitComponent.hpp"
#include <type_traits>
#include <Wire.h>
#include <esp32-hal-i2c.h>
namespace m5 {
namespace unit {
/*!
@namespace googletest
@brief For GoogleTest
*/
namespace googletest {
/*!
@class GlobalFixture
@brief Overall test environment configuration
@tparam FREQ TwoWire operating frequency
@tparam WNUM TwoWire number to be used (0 as default)
*/
template <uint32_t FREQ, uint32_t WNUM = 0>
class GlobalFixture : public ::testing::Environment {
static_assert(WNUM < 2, "Wire number must be lesser than 2");
public:
void SetUp() override
{
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
#if defined(CONFIG_IDF_TARGET_ESP32C6)
TwoWire* w[1] = {&Wire};
#else
TwoWire* w[2] = {&Wire, &Wire1};
#endif
if (WNUM < m5::stl::size(w) && i2cIsInit(WNUM)) {
M5_LOGW("Already inititlized Wire %d. Terminate and restart FREQ %u", WNUM, FREQ);
w[WNUM]->end();
}
w[WNUM]->begin(pin_num_sda, pin_num_scl, FREQ);
}
};
/*!
@class ComponentTestBase
@brief UnitComponent Derived class for testing
@tparam U m5::unit::Component-derived classes to be tested
@tparam TP parameter type for testing. see also INSTANTIATE_TEST_SUITE_P
*/
template <typename U, typename TP>
class ComponentTestBase : public ::testing::TestWithParam<TP> {
static_assert(std::is_base_of<m5::unit::Component, U>::value, "U must be derived from Component");
protected:
virtual void SetUp() override
{
unit.reset(get_instance());
if (!unit) {
FAIL() << "Failed to get_instance";
GTEST_SKIP();
return;
}
ustr = m5::utility::formatString("%s:%s", unit->deviceName(), is_using_hal() ? "HAL" : "Wire");
if (!begin()) {
FAIL() << "Failed to begin " << ustr;
GTEST_SKIP();
}
}
virtual void TearDown() override
{
}
virtual bool begin()
{
if (is_using_hal()) {
// Using M5HAL
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl);
m5::hal::bus::I2CBusConfig i2c_cfg;
i2c_cfg.pin_sda = m5::hal::gpio::getPin(pin_num_sda);
i2c_cfg.pin_scl = m5::hal::gpio::getPin(pin_num_scl);
auto i2c_bus = m5::hal::bus::i2c::getBus(i2c_cfg);
return Units.add(*unit, i2c_bus ? i2c_bus.value() : nullptr) && Units.begin();
}
// Using TwoWire
return Units.add(*unit, Wire) && Units.begin();
}
//!@brief Function returning true if M5HAL is used (decision based on TP)
virtual bool is_using_hal() const = 0;
//! @brief return m5::unit::Component-derived class instance (decision based on TP)
virtual U* get_instance() = 0;
std::string ustr{};
std::unique_ptr<U> unit{};
m5::unit::UnitUnified Units;
};
} // namespace googletest
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter.cpp
@brief Adapters to treat M5HAL and any connection in the same way
@note Currently handles Arduino directly, but will handle via M5HAL in the future
*/
#include "adapter.hpp"
#include <cassert>
#if defined(ARDUINO)
#include <Wire.h>
#endif
#include <M5HAL.hpp>
#include <M5Utility.hpp>
#include <soc/gpio_struct.h>
#include <soc/gpio_sig_map.h>
namespace m5 {
namespace unit {
// Adapter
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter.hpp
@brief Adapters to treat M5HAL and any connection in the same way
@note Currently handles Arduino directly, but will handle via M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_HPP
#define M5_UNIT_COMPONENT_ADAPTER_HPP
#include "adapter_base.hpp"
#include "adapter_i2c.hpp"
#include "identify_functions.hpp"
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include "adapter_gpio_v2.hpp"
#else
#include "adapter_gpio_v1.hpp"
#endif
#endif

View file

@ -0,0 +1,233 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_base.hpp
@brief Adapter base
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_BASE_HPP
#define M5_UNIT_COMPONENT_ADAPTER_BASE_HPP
#include <cstdint>
#include <cstddef>
#include <memory>
#include <M5HAL.hpp>
#include "types.hpp"
namespace m5 {
namespace unit {
/*!
@class m5::unit::Adapter
@brief Adapter base class to treat M5HAL and TwoWire,GPIO,Serial,SPI... in the same way
*/
class Adapter {
public:
enum class Type : uint8_t {
Unknown,
I2C,
GPIO,
UART,
SPI,
};
class Impl {
public:
Impl() = default;
virtual ~Impl()
{
}
// I2C R/W
virtual m5::hal::error::error_t readWithTransaction(uint8_t*, const size_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t*, const size_t, const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t, const uint8_t*, const size_t,
const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeWithTransaction(const uint16_t, const uint8_t*, const size_t,
const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t generalCall(const uint8_t*, const size_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
// GPIO R/W
virtual m5::hal::error::error_t pinModeRX(const gpio::Mode)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeDigitalRX(const bool)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t readDigitalRX(bool&)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeAnalogRX(const uint16_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t readAnalogRX(uint16_t&)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t pulseInRX(uint32_t&, const int, const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t pinModeTX(const gpio::Mode)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeDigitalTX(const bool)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t readDigitalTX(bool&)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t writeAnalogTX(const uint16_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t readAnalogTX(uint16_t&)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
virtual m5::hal::error::error_t pulseInTX(uint32_t&, const int, const uint32_t)
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
};
explicit Adapter() : _impl{new Impl()}
{
}
protected:
Adapter(const Type t, Impl* impl) : _type{t}, _impl{impl}
{
}
public:
Adapter(const Adapter&) = delete;
Adapter(Adapter&&) noexcept = default;
Adapter& operator=(const Adapter&) = delete;
Adapter& operator=(Adapter&&) noexcept = default;
virtual ~Adapter() = default;
inline Type type() const
{
return _type;
}
virtual Adapter* duplicate(const uint8_t /*addr*/)
{
return new Adapter();
}
// I2C R/W
inline m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len)
{
return _impl->readWithTransaction(data, len);
}
inline m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t exparam = 1)
{
return _impl->writeWithTransaction(data, len, exparam);
}
inline m5::hal::error::error_t writeWithTransaction(const uint8_t reg, const uint8_t* data, const size_t len,
const uint32_t exparam = 1)
{
return _impl->writeWithTransaction(reg, data, len, exparam);
}
inline m5::hal::error::error_t writeWithTransaction(const uint16_t reg, const uint8_t* data, const size_t len,
const uint32_t exparam = 1)
{
return _impl->writeWithTransaction(reg, data, len, exparam);
}
inline m5::hal::error::error_t generalCall(const uint8_t* data, const size_t len)
{
return _impl->generalCall(data, len);
}
// GPIO R/W
inline m5::hal::error::error_t pinModeRX(const gpio::Mode m)
{
return _impl->pinModeRX(m);
}
inline m5::hal::error::error_t writeDigitalRX(const bool high)
{
return _impl->writeDigitalRX(high);
}
inline m5::hal::error::error_t readDigitalRX(bool& high)
{
return _impl->readDigitalRX(high);
}
inline m5::hal::error::error_t writeAnalogRX(const uint16_t v)
{
return _impl->writeAnalogRX(v);
}
inline m5::hal::error::error_t readAnalogRX(uint16_t& v)
{
return _impl->readAnalogRX(v);
}
inline m5::hal::error::error_t pulseInRX(uint32_t& duration, const int state, const uint32_t timeout_us)
{
return _impl->pulseInRX(duration, state, timeout_us);
}
inline m5::hal::error::error_t pinModeTX(const gpio::Mode m)
{
return _impl->pinModeTX(m);
}
inline m5::hal::error::error_t writeDigitalTX(const bool high)
{
return _impl->writeDigitalTX(high);
}
inline m5::hal::error::error_t readDigitalTX(bool& high)
{
return _impl->readDigitalTX(high);
}
inline m5::hal::error::error_t writeAnalogTX(const uint16_t v)
{
return _impl->writeAnalogTX(v);
}
inline m5::hal::error::error_t readAnalogTX(uint16_t& v)
{
return _impl->readAnalogTX(v);
}
inline m5::hal::error::error_t pulseInTX(uint32_t& duration, const int state, const uint32_t timeout_us)
{
return _impl->pulseInTX(duration, state, timeout_us);
}
private:
Type _type{Type::Unknown};
protected:
std::unique_ptr<Impl> _impl{};
};
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,507 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio.cpp
@brief Adapters to treat M5HAL and RMT in the same way
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#include "adapter_gpio.hpp"
#include <driver/gpio.h>
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#pragma message "Using RMT v2,Oneshot"
#include <esp_adc/adc_oneshot.h>
#else
#pragma message "Using RMT v1"
#include <driver/adc.h>
#endif
#if defined(SOC_DAC_SUPPORTED) && SOC_DAC_SUPPORTED
#pragma message "DAC supported"
#if __has_include(<driver/dac_common.h>)
#include <driver/dac_common.h>
#define USING_RMT_CHANNNE_T
#endif
#endif
#if !defined(USING_RMT_CHANNNE_T) && defined(ARDUINO)
#include <esp32-hal-dac.h>
#endif
#if SOC_ADC_SUPPORTED
#pragma message "ADC supported"
#else
#pragma message "ADC Not supported"
#endif
#include <esp_timer.h>
namespace {
constexpr gpio_config_t gpio_cfg_table[] = {
{
// Input
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// Output
.pin_bit_mask = 0,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// Pullup
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// InputPullup
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// Pulldown
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// InputPulldown
.pin_bit_mask = 0,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// OpenDrain
.pin_bit_mask = 0,
.mode = GPIO_MODE_OUTPUT_OD,
.pull_up_en = GPIO_PULLUP_ENABLE, //
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// OutputOpenDrain,
.pin_bit_mask = 0,
.mode = GPIO_MODE_OUTPUT_OD,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
{
// Analog
.pin_bit_mask = 0,
.mode = GPIO_MODE_DISABLE,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
#if defined(CONFIG_IDF_TARGET_ESP32P4)
.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE,
#endif
},
};
#if CONFIG_IDF_TARGET_ESP32
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ 11, // ADC2_CHANNEL_1
/* 1 */ -1,
/* 2 */ 12, // ADC2_CHANNEL_2
/* 3 */ -1,
/* 4 */ 10, // ADC2_CHANNEL_0
/* 5 */ -1,
/* 6 */ -1,
/* 7 */ -1,
/* 8 */ -1,
/* 9 */ -1,
/* 10 */ -1,
/* 11 */ -1,
/* 12 */ 15, // ADC2_CHANNEL_5
/* 13 */ 14, // ADC2_CHANNEL_4
/* 14 */ 16, // ADC2_CHANNEL_6
/* 15 */ 13, // ADC2_CHANNEL_3
/* 16 */ -1,
/* 17 */ -1,
/* 18 */ -1,
/* 19 */ -1,
/* 20 */ -1,
/* 21 */ -1,
/* 22 */ -1,
/* 23 */ -1,
/* 24 */ -1,
/* 25 */ 18, // ADC2_CHANNEL_8
/* 26 */ 19, // ADC2_CHANNEL_9
/* 27 */ 17, // ADC2_CHANNEL_7
/* 28 */ -1,
/* 29 */ -1,
/* 30 */ -1,
/* 31 */ -1,
/* 32 */ 4, // ADC1_CHANNEL_4
/* 33 */ 5, // ADC1_CHANNEL_5
/* 34 */ 6, // ADC1_CHANNEL_6
/* 35 */ 7, // ADC1_CHANNEL_7
/* 36 */ 0, // ADC1_CHANNEL_0
/* 37 */ 1, // ADC1_CHANNEL_1
/* 38 */ 2, // ADC1_CHANNEL_2
/* 39 */ 3 // ADC1_CHANNEL_3
};
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ -1,
/* 1 */ 0, // ADC1_CHANNEL_0
/* 2 */ 1, // ADC1_CHANNEL_1
/* 3 */ 2, // ADC1_CHANNEL_2
/* 4 */ 3, // ADC1_CHANNEL_3
/* 5 */ 4, // ADC1_CHANNEL_4
/* 6 */ 5, // ADC1_CHANNEL_5
/* 7 */ 6, // ADC1_CHANNEL_6
/* 8 */ 7, // ADC1_CHANNEL_7
/* 9 */ 8, // ADC1_CHANNEL_8
/* 10 */ 9, // ADC1_CHANNEL_9
/* 11 */ 10, // ADC2_CHANNEL_0
/* 12 */ 11, // ADC2_CHANNEL_1
/* 13 */ 12, // ADC2_CHANNEL_2
/* 14 */ 13, // ADC2_CHANNEL_3
/* 15 */ 14, // ADC2_CHANNEL_4
/* 16 */ 15, // ADC2_CHANNEL_5
/* 17 */ 16, // ADC2_CHANNEL_6
/* 18 */ 17, // ADC2_CHANNEL_7
/* 19 */ 18, // ADC2_CHANNEL_8
/* 20 */ 19, // ADC2_CHANNEL_9
};
#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ 0, // ADC1_CHANNEL_0
/* 1 */ 1, // ADC1_CHANNEL_1
/* 2 */ 2, // ADC1_CHANNEL_2
/* 3 */ 3, // ADC1_CHANNEL_3
/* 4 */ 4, // ADC1_CHANNEL_4
/* 5 */ 10, // ADC2_CHANNEL_0
};
#elif CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32P4 || \
CONFIG_IDF_TARGET_ESP32C5 || CONFIG_IDF_TARGET_ESP32C61
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ 0, // ADC1_CHANNEL_0
/* 1 */ 1, // ADC1_CHANNEL_1
/* 2 */ 2, // ADC1_CHANNEL_2
/* 3 */ 3, // ADC1_CHANNEL_3
/* 4 */ 4, // ADC1_CHANNEL_4
/* 5 */ 5, // ADC1_CHANNEL_5
/* 6 */ 6, // ADC1_CHANNEL_6
};
#elif CONFIG_IDF_TARGET_ESP32P4
constexpr int8_t gpio_to_adc_table[] = {
/* 0 */ -1,
/* 1 */ -1,
/* 2 */ -1,
/* 3 */ -1,
/* 4 */ -1,
/* 5 */ -1,
/* 6 */ -1,
/* 7 */ -1,
/* 8 */ -1,
/* 9 */ -1,
/* 10 */ -1,
/* 11 */ -1,
/* 12 */ -1,
/* 13 */ -1,
/* 14 */ -1,
/* 15 */ -1,
/* 16 */ 0, // ADC1_CHANNEL_0
/* 17 */ 1, // ADC1_CHANNEL_1
/* 18 */ 2, // ADC1_CHANNEL_2
/* 19 */ 3, // ADC1_CHANNEL_3
/* 20 */ 4, // ADC1_CHANNEL_4
/* 21 */ 5, // ADC1_CHANNEL_5
/* 22 */ 6, // ADC1_CHANNEL_6
/* 23 */ 7, // ADC1_CHANNEL_7
/* 24 */ -1,
/* 25 */ -1,
/* 26 */ -1,
/* 27 */ -1,
/* 28 */ -1,
/* 29 */ -1,
/* 30 */ -1,
/* 31 */ -1,
/* 32 */ -1,
/* 33 */ -1,
/* 34 */ -1,
/* 35 */ -1,
/* 36 */ -1,
/* 37 */ -1,
/* 38 */ -1,
/* 39 */ -1,
/* 40 */ -1,
/* 41 */ -1,
/* 42 */ -1,
/* 43 */ -1,
/* 44 */ -1,
/* 45 */ -1,
/* 46 */ -1,
/* 47 */ -1,
/* 48 */ -1,
/* 49 */ 10, // ADC2_CHANNEL_0
/* 50 */ 11, // ADC2_CHANNEL_1
/* 51 */ 12, // ADC2_CHANNEL_2
/* 52 */ 13, // ADC2_CHANNEL_3
/* 53 */ 14, // ADC2_CHANNEL_4
/* 54 */ 15, // ADC2_CHANNEL_5
};
#else
#error Invalid target
#endif
// 0-9: ADC1 10-:ADC2 (ESP-IDF 4.x)
// 0-9, 10- : ADC (ESP-IDF 5.x)
int8_t gpio_to_adc_channel(const int8_t pin)
{
if (pin < 0 || pin >= m5::stl::size(gpio_to_adc_table)) {
return -1;
}
auto v = gpio_to_adc_table[pin];
return (v < 10) ? v : v - 10;
}
#if 0
// -1:ivalid 0:ADC1 1:ADC2
int gpio_to_adc12(const int8_t pin)
{
return (pin >= 0 && pin < m5::stl::size(gpio_to_adc_table))
? (gpio_to_adc_table[pin] < 0 ? -1 : (gpio_to_adc_table[pin] >= 10 ? 1 : 0))
: -1;
}
#endif
} // namespace
namespace m5 {
namespace unit {
namespace gpio {
uint8_t calculate_rmt_clk_div(uint32_t apb_freq_hz, uint32_t tick_ns)
{
if (tick_ns == 0) {
return 1;
}
uint64_t clk_div = (static_cast<uint64_t>(apb_freq_hz) * tick_ns + 500) / 1000000000UL;
clk_div = std::min<uint64_t>(255, std::max<uint64_t>(0, clk_div));
return static_cast<uint8_t>(clk_div);
}
uint32_t calculate_rmt_resolution_hz(uint32_t apb_freq_hz, uint32_t tick_ns)
{
if (tick_ns == 0) {
return apb_freq_hz;
}
uint64_t target_hz = 1000000000UL / tick_ns;
uint32_t clk_div = std::max<uint32_t>(1, (apb_freq_hz + target_hz / 2) / target_hz);
clk_div = std::min<uint32_t>(clk_div, 255U);
return apb_freq_hz / clk_div;
}
} // namespace gpio
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::pin_mode(const gpio_num_t pin, const gpio::Mode m)
{
if (m < gpio::Mode::RmtRX) {
gpio_config_t cfg = gpio_cfg_table[m5::stl::to_underlying(m)];
cfg.pin_bit_mask = 1ULL << pin;
gpio_config(&cfg);
return m5::hal::error::error_t::OK;
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::write_digital(const gpio_num_t pin, const bool high)
{
gpio_set_level(pin, high);
return m5::hal::error::error_t::OK;
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::read_digital(const gpio_num_t pin, bool& high)
{
high = true;
high = gpio_get_level(pin);
return m5::hal::error::error_t::OK;
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::write_analog(const gpio_num_t pin, const uint16_t value)
{
if (pin != 25 && pin != 26) {
// DAC output can 25 or 26
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
#if defined(USING_RMT_CHANNNE_T)
dac_channel_t ch = (pin == 25) ? DAC_CHANNEL_1 : DAC_CHANNEL_2;
dac_output_enable(ch);
dac_output_voltage(ch, static_cast<uint8_t>(value & 0xFF)); // 0〜255
return m5::hal::error::error_t::OK;
#else
analogWrite(pin, value & 0xFF);
return m5::hal::error::error_t::OK;
// return m5::hal::error::error_t::NOT_IMPLEMENTED;
#endif
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::read_analog(uint16_t& value, const gpio_num_t pin)
{
value = 0;
const auto ch = gpio_to_adc_channel(pin);
if (ch < 0) {
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
#if !defined(SOC_ADC_PERIPH_NUM) || SOC_ADC_PERIPH_NUM <= 1
if (ch >= 10) {
M5_LIB_LOGE("Not support ADC2");
return m5::hal::error::error_t::NOT_IMPLEMENTED;
}
#endif
#if defined(M5_UNIT_UNIFIED_USING_ADC_ONESHOT)
// ESP-IDF 5.x
adc_unit_t unit = (ch < 10) ? ADC_UNIT_1 : ADC_UNIT_2;
adc_channel_t channel = static_cast<adc_channel_t>((ch < 10) ? ch : (ch - 10));
adc_oneshot_unit_handle_t adc_handle{};
#if defined(CONFIG_IDF_TARGET_ESP32C6)
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = unit, .clk_src = ADC_DIGI_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE};
#else
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = unit, .clk_src = ADC_RTC_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE};
#endif
if (adc_oneshot_new_unit(&init_config, &adc_handle) != ESP_OK) {
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12, // 0~3.3V
.bitwidth = ADC_BITWIDTH_DEFAULT // 12bit
};
auto ret = m5::hal::error::error_t::UNKNOWN_ERROR;
if (adc_oneshot_config_channel(adc_handle, channel, &chan_config) == ESP_OK) {
int raw{};
if (adc_oneshot_read(adc_handle, channel, &raw) == ESP_OK) {
value = static_cast<uint16_t>(raw);
ret = m5::hal::error::error_t::OK;
}
}
adc_oneshot_del_unit(adc_handle);
return ret;
#else
// ESP-IDF 4.x
// ADC2
if (ch >= 10) {
#if SOC_ADC_SUPPORTED && SOC_ADC_PERIPH_NUM > 1
adc2_channel_t channel = static_cast<adc2_channel_t>(ch - 10);
int v = 0;
if (adc2_get_raw(channel, ADC_WIDTH_BIT_12, &v) != ESP_OK) {
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
value = static_cast<uint16_t>(v);
return m5::hal::error::error_t::OK;
#endif
}
// ADC1
adc1_channel_t channel = static_cast<adc1_channel_t>(ch);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(channel, ADC_ATTEN_DB_12);
value = static_cast<uint16_t>(adc1_get_raw(channel));
return m5::hal::error::error_t::OK;
#endif
}
m5::hal::error::error_t AdapterGPIOBase::GPIOImpl::pulse_in(uint32_t& duration, const gpio_num_t pin, const int state,
const uint32_t timeout_us = 30000)
{
duration = 0;
auto start = esp_timer_get_time();
auto now = start;
// Wait for any previous pulse to end
while (gpio_get_level(pin) == state) {
now = esp_timer_get_time();
if (now - start > timeout_us) {
return m5::hal::error::error_t::TIMEOUT_ERROR;
}
}
// Wait for the pulse to start
while (gpio_get_level(pin) != state) {
now = esp_timer_get_time();
if (now - start > timeout_us) {
return m5::hal::error::error_t::TIMEOUT_ERROR;
}
}
auto pulse_start = esp_timer_get_time();
// Wait for the pulse to end
while (gpio_get_level(pin) == state) {
now = esp_timer_get_time();
if (now - pulse_start > timeout_us) {
return m5::hal::error::error_t::TIMEOUT_ERROR;
}
}
auto pulse_end = esp_timer_get_time();
duration = static_cast<uint32_t>(pulse_end - pulse_start);
return m5::hal::error::error_t::OK;
}
AdapterGPIOBase::AdapterGPIOBase(GPIOImpl* impl) : Adapter(Adapter::Type::GPIO, impl)
{
}
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,163 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio.hpp
@brief Adapters to treat M5HAL and GPIO in the same way
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_GPIO_HPP
#define M5_UNIT_COMPONENT_ADAPTER_GPIO_HPP
#include "identify_functions.hpp"
#include "types.hpp"
#include "adapter_base.hpp"
namespace m5 {
namespace unit {
namespace gpio {
/*!
@brief Calculate clk_div from desired tick time (nanoseconds)
@param apb_freq_hz Current APB clock (Hz)
@param tick_ns Desired tick time (ns)
@return clk_div for RMT v1
*/
uint8_t calculate_rmt_clk_div(const uint32_t apb_freq_hz, const uint32_t tick_ns);
/*!
@brief Calculate resolution from desired tick time
@param apb_freq_hz Current APB clock (Hz)
@param tick_ns Desired tick time (ns)
@return resoution for RMT v2
*/
uint32_t calculate_rmt_resolution_hz(const uint32_t apb_freq_hz, const uint32_t tick_ns);
} // namespace gpio
// Base class for AdapterGPIO
class AdapterGPIOBase : public Adapter {
public:
class GPIOImpl : public Adapter::Impl {
public:
GPIOImpl() = default;
GPIOImpl(const int8_t rx_pin, const int8_t tx_pin) : _rx_pin{(gpio_num_t)rx_pin}, _tx_pin{(gpio_num_t)tx_pin}
{
}
inline gpio_num_t rx_pin() const
{
return _rx_pin;
}
inline gpio_num_t tx_pin() const
{
return _tx_pin;
}
inline gpio::adapter_config_t adapter_config() const
{
return _adapter_cfg;
}
inline virtual bool begin(const gpio::adapter_config_t& cfg)
{
return false;
}
//
inline virtual m5::hal::error::error_t pinModeRX(const gpio::Mode m) override
{
return pin_mode(rx_pin(), m);
}
inline virtual m5::hal::error::error_t writeDigitalRX(const bool high) override
{
return write_digital(rx_pin(), high);
}
inline virtual m5::hal::error::error_t readDigitalRX(bool& high) override
{
return read_digital(rx_pin(), high);
}
inline virtual m5::hal::error::error_t writeAnalogRX(const uint16_t v) override
{
return write_analog(rx_pin(), v);
}
inline virtual m5::hal::error::error_t readAnalogRX(uint16_t& v)
{
return read_analog(v, rx_pin());
}
inline virtual m5::hal::error::error_t pulseInRX(uint32_t& duration, const int state,
const uint32_t timeout_us) override
{
return pulse_in(duration, rx_pin(), state, timeout_us);
}
//
inline virtual m5::hal::error::error_t pinModeTX(const gpio::Mode m) override
{
return pin_mode(tx_pin(), m);
}
inline virtual m5::hal::error::error_t writeDigitalTX(const bool high) override
{
return write_digital(tx_pin(), high);
}
inline virtual m5::hal::error::error_t readDigitalTX(bool& high) override
{
return read_digital(tx_pin(), high);
}
inline virtual m5::hal::error::error_t writeAnalogTX(const uint16_t v) override
{
return write_analog(tx_pin(), v);
}
inline virtual m5::hal::error::error_t readAnalogTX(uint16_t& v)
{
return read_analog(v, tx_pin());
}
inline virtual m5::hal::error::error_t pulseInTX(uint32_t& duration, const int state,
const uint32_t timeout_us) override
{
return pulse_in(duration, tx_pin(), state, timeout_us);
}
protected:
m5::hal::error::error_t pin_mode(const gpio_num_t pin, const gpio::Mode m);
m5::hal::error::error_t write_digital(const gpio_num_t pin, const bool high);
m5::hal::error::error_t read_digital(const gpio_num_t pin, bool& high);
m5::hal::error::error_t write_analog(const gpio_num_t pin, const uint16_t value);
m5::hal::error::error_t read_analog(uint16_t& value, const gpio_num_t pin);
m5::hal::error::error_t pulse_in(uint32_t& duration, const gpio_num_t pin, const int state,
const uint32_t timeout_us);
protected:
gpio_num_t _rx_pin{(gpio_num_t)-1}, _tx_pin{(gpio_num_t)-1};
gpio::adapter_config_t _adapter_cfg{};
};
//
explicit AdapterGPIOBase(GPIOImpl* impl);
inline GPIOImpl* impl()
{
return static_cast<GPIOImpl*>(_impl.get());
}
inline const GPIOImpl* impl() const
{
return static_cast<GPIOImpl*>(_impl.get());
}
inline gpio_num_t rx_pin() const
{
return impl()->rx_pin();
}
inline gpio_num_t tx_pin() const
{
return impl()->tx_pin();
}
inline bool begin(const gpio::adapter_config_t& cfg)
{
return impl()->begin(cfg);
}
};
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,168 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio_v1.cpp
@brief Adapters to treat M5HAL and GPIO in the same way using RNT v1
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#include "adapter_gpio_v1.hpp"
#if !defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <esp_clk.h>
using namespace m5::unit::gpio;
namespace {
uint32_t using_rmt_channel_bits{};
rmt_channel_t retrieve_available_rmt_channel()
{
for (int_fast8_t ch = 0; ch < RMT_CHANNEL_MAX; ++ch) {
if (((1U << ch) & using_rmt_channel_bits) == 0) {
return (rmt_channel_t)ch;
}
}
return RMT_CHANNEL_MAX;
}
bool declrare_use_rmt_channel(const int ch)
{
if (ch >= 0 && ch < RMT_CHANNEL_MAX && ((1U << ch) & using_rmt_channel_bits) == 0) {
using_rmt_channel_bits |= (1U << ch);
return true;
}
return false;
}
rmt_config_t to_rmt_config_tx(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
{
rmt_config_t out{};
out.rmt_mode = RMT_MODE_TX;
out.mem_block_num = cfg.tx.mem_blocks;
out.clk_div = calculate_rmt_clk_div(apb_freq_hz, cfg.tx.tick_ns);
out.tx_config.carrier_en = false;
out.tx_config.idle_output_en = cfg.tx.idle_output;
out.tx_config.idle_level = cfg.tx.idle_level_high ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW;
out.tx_config.loop_en = cfg.tx.loop_enabled;
return out;
}
#if 0
rmt_config_t to_rmt_config_rx(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
{
rmt_config_t out{};
out.rmt_mode = RMT_MODE_RX;
out.mem_block_num = cfg.tx.mem_blocks;
out.clk_div = calculate_rmt_clk_div(apb_freq_hz, cfg.rx.tick_ns);
out.rx_config.filter_en = true;
out.rx_config.filter_ticks_thresh = 30;
out.rx_config.idle_threshold = 300;
return out;
}
#endif
} // namespace
namespace m5 {
namespace unit {
class GPIOImplV1 : public AdapterGPIOBase::GPIOImpl {
public:
GPIOImplV1() : AdapterGPIOBase::GPIOImpl(-1, -1)
{
}
GPIOImplV1(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase::GPIOImpl(rx_pin, tx_pin)
{
}
virtual bool begin(const gpio::adapter_config_t& cfg) override
{
_adapter_cfg = cfg;
if (_tx_config.clk_div || _rx_config.clk_div) {
M5_LIB_LOGD("Already begun");
return true;
}
// RMT TX
if (cfg.mode == gpio::Mode::RmtTX || cfg.mode == gpio::Mode::RmtRXTX) {
rmt_channel_t ch = retrieve_available_rmt_channel();
if (ch >= RMT_CHANNEL_MAX) {
M5_LIB_LOGE("RMT(v1) No room on channel");
return false;
}
declrare_use_rmt_channel(ch);
M5_LIB_LOGI("Retrive RMT(v1) %u", ch);
_tx_config = to_rmt_config_tx(cfg, esp_clk_apb_freq());
_tx_config.channel = ch;
_tx_config.gpio_num = tx_pin();
auto err = rmt_config(&_tx_config);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to configurate %d:%s", err, esp_err_to_name(err));
return false;
}
err = rmt_driver_install(_tx_config.channel, 0, 0);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to install %d:%s", err, esp_err_to_name(err));
return false;
}
}
// RMT RX
if (cfg.mode == gpio::Mode::RmtRX || cfg.mode == gpio::Mode::RmtRXTX) {
// TODO
}
return true;
}
m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len, const uint32_t waitMs) override
{
if (_adapter_cfg.mode == Mode::RmtTX || _adapter_cfg.mode == Mode::RmtRXTX) {
auto err = rmt_write_items(_tx_config.channel, (gpio::m5_rmt_item_t*)data, len, true);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to write %d:%s", err, esp_err_to_name(err));
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
if (err == ESP_OK && waitMs) {
err = rmt_wait_tx_done(_tx_config.channel, pdMS_TO_TICKS(50));
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to wait %d:%s", err, esp_err_to_name(err));
}
}
return err == ESP_OK ? m5::hal::error::error_t::OK : m5::hal::error::error_t::UNKNOWN_ERROR;
}
M5_LIB_LOGE("Failed invalid config");
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
void copy_from(GPIOImplV1* ptr)
{
_rx_pin = ptr->_rx_pin;
_tx_pin = ptr->_tx_pin;
_adapter_cfg = ptr->_adapter_cfg;
_rx_config = ptr->_rx_config;
_tx_config = ptr->_tx_config;
}
protected:
rmt_config_t _rx_config{}, _tx_config{};
};
AdapterGPIO::AdapterGPIO(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase(new GPIOImplV1(rx_pin, tx_pin))
{
}
//
} // namespace unit
} // namespace m5
#endif
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2) && defined(RMT_CHANNEL_0)
#error "RMT v1 is mixed in with RMT v2 even though RMT v2 is used"
#endif

View file

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio_v1.hpp
@brief Adapters to treat M5HAL and GPIO in the same way using RNT v1
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_GPIO_V1_HPP
#define M5_UNIT_COMPONENT_ADAPTER_GPIO_V1_HPP
#include "identify_functions.hpp"
#include "types.hpp"
#include "adapter_gpio.hpp"
#if !defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <driver/rmt.h>
namespace m5 {
namespace unit {
/*!
@class m5::unit::AdapterGPIO
@brief GPIO access adapter
*/
class AdapterGPIO : public AdapterGPIOBase {
public:
AdapterGPIO(const int8_t rx_pin, const int8_t tx_pin);
};
} // namespace unit
} // namespace m5
#endif
#endif

View file

@ -0,0 +1,116 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio_v2.cpp
@brief Adapters to treat M5HAL and GPIO in the same way using RMT v2
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#include "adapter_gpio_v2.hpp"
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <esp_private/esp_clk.h>
using namespace m5::unit::gpio;
namespace {
rmt_encoder_handle_t copy_encoder{};
rmt_tx_channel_config_t to_rmt_tx_config(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
{
rmt_tx_channel_config_t out{};
out.clk_src = RMT_CLK_SRC_DEFAULT;
out.mem_block_symbols = cfg.tx.mem_blocks * 16;
out.resolution_hz = calculate_rmt_resolution_hz(apb_freq_hz, cfg.tx.tick_ns);
out.trans_queue_depth = 4;
out.flags.with_dma = cfg.tx.with_dma;
return out;
}
#if 0
rmt_rx_channel_config_t to_rmt_rx_config(const adapter_config_t& cfg, const uint32_t apb_freq_hz)
{
rmt_rx_channel_config_t out{};
return out;
}
#endif
} // namespace
namespace m5 {
namespace unit {
//
class GPIOImplV2 : public AdapterGPIO::GPIOImpl {
public:
GPIOImplV2(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase::GPIOImpl(rx_pin, tx_pin)
{
}
bool begin(const gpio::adapter_config_t& cfg)
{
// RMT TX
if (!_tx_handle && (cfg.mode == gpio::Mode::RmtTX || cfg.mode == gpio::Mode::RmtRXTX)) {
_tx_config = to_rmt_tx_config(cfg, esp_clk_apb_freq());
_tx_config.gpio_num = tx_pin();
auto err = rmt_new_tx_channel(&_tx_config, &_tx_handle);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to rmt_new_tx_channel pin:%d", tx_pin());
return false;
}
err = rmt_enable(_tx_handle);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to rmt_enable");
return false;
}
}
// RMT RX
if (!_rx_handle && (cfg.mode == gpio::Mode::RmtRX || cfg.mode == gpio::Mode::RmtRXTX)) {
// TODO
}
if (!copy_encoder) {
rmt_copy_encoder_config_t enc_cfg{};
auto err = rmt_new_copy_encoder(&enc_cfg, &copy_encoder);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to rmt_new_copy_encoder");
return false;
}
}
return true;
}
m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len, const uint32_t waitMs) override
{
rmt_transmit_config_t tx_config = {};
auto err = rmt_transmit(_tx_handle, copy_encoder, (gpio::m5_rmt_item_t*)data, len * sizeof(gpio::m5_rmt_item_t),
&tx_config);
if (err == ESP_OK && waitMs) {
err = rmt_tx_wait_all_done(_tx_handle, waitMs);
if (err != ESP_OK) {
M5_LIB_LOGE("Failed to wait %d:%s", err, esp_err_to_name(err));
}
}
return err == ESP_OK ? m5::hal::error::error_t::OK : m5::hal::error::error_t::UNKNOWN_ERROR;
}
protected:
rmt_channel_handle_t _rx_handle{}, _tx_handle{};
rmt_rx_channel_config_t _rx_config{};
rmt_tx_channel_config_t _tx_config{};
};
AdapterGPIO::AdapterGPIO(const int8_t rx_pin, const int8_t tx_pin) : AdapterGPIOBase(new GPIOImplV2(rx_pin, tx_pin))
{
}
} // namespace unit
} // namespace m5
#endif
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2) && defined(RMT_CHANNEL_0)
#error "RMT v1 is mixed in with RMT v2 even though RMT v2 is used"
#endif

View file

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_gpio_v2.hpp
@brief Adapters to treat M5HAL and GPIO in the same way using RMT v2
@note Currently handles GPIO directly, but will handle via M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_GPIO_V2_HPP
#define M5_UNIT_COMPONENT_ADAPTER_GPIO_V2_HPP
#include "identify_functions.hpp"
#include "types.hpp"
#include "adapter_gpio.hpp"
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <driver/rmt_tx.h>
#include <driver/rmt_rx.h>
#include <driver/rmt_encoder.h>
#include <driver/gpio.h>
namespace m5 {
namespace unit {
/*!
@class m5::unit::AdapterGPIO
@brief GPIO access adapter
*/
class AdapterGPIO : public AdapterGPIOBase {
public:
AdapterGPIO(const int8_t rx_pin, const int8_t tx_pin);
};
} // namespace unit
} // namespace m5
#endif
#endif

View file

@ -0,0 +1,396 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_i2c.cpp
@brief Adapter for I2C to treat M5HAL and TwoWire in the same way
@note Currently handles TwoWire directly, but will handle via M5HAL in the future
*/
#include "adapter.hpp"
#include <cassert>
#include <Wire.h>
#include <M5HAL.hpp>
#include <M5Utility.hpp>
#include <soc/gpio_struct.h>
#include <soc/gpio_sig_map.h>
#if defined(ARDUINO)
namespace {
int16_t search_pin_number(const int peripheral_sig)
{
int16_t no{-1};
#if defined(CONFIG_IDF_TARGET_ESP32C6)
// C6
no = (peripheral_sig >= 0 && peripheral_sig < m5::stl::size(GPIO.func_in_sel_cfg))
? GPIO.func_in_sel_cfg[peripheral_sig].in_sel
: -1;
#elif defined(CONFIG_IDF_TARGET_ESP32P4)
// P4
#pragma message "ESP32P4 was not support"
#else
// Others
no = (peripheral_sig >= 0 && peripheral_sig < m5::stl::size(GPIO.func_in_sel_cfg))
? GPIO.func_in_sel_cfg[peripheral_sig].func_sel
: -1;
#endif
return (no < GPIO_NUM_MAX) ? no : -1;
}
int8_t idx_table[][2] = {
#if !defined(CONFIG_IDF_TARGET_ESP32P4)
{I2CEXT0_SDA_IN_IDX, I2CEXT0_SCL_IN_IDX}, // Wire
#if !defined(CONFIG_IDF_TARGET_ESP32C6)
{I2CEXT1_SDA_IN_IDX, I2CEXT1_SCL_IN_IDX}, // Wire1
#else
{I2CEXT0_SDA_IN_IDX, I2CEXT0_SCL_IN_IDX}, // Same as Wire
#endif
#else
// ESP32-P4 Not support yet
{-1, -1},
{-1, -1},
#endif
};
} // namespace
namespace m5 {
namespace unit {
// Impl for TwoWire
AdapterI2C::WireImpl::WireImpl(TwoWire& wire, const uint8_t addr, const uint32_t clock)
: AdapterI2C::I2CImpl(addr, clock), _wire(&wire)
{
uint32_t w = (&wire != &Wire);
_sda = search_pin_number(idx_table[w][0]);
_scl = search_pin_number(idx_table[w][1]);
M5_LIB_LOGI("I2C SDA:%d, SCL:%d", _sda, _scl);
}
bool AdapterI2C::WireImpl::begin()
{
return _wire->begin();
}
bool AdapterI2C::WireImpl::end()
{
#if defined(WIRE_HAS_END)
return _wire->end();
#else
return false;
#endif
}
m5::hal::error::error_t AdapterI2C::WireImpl::readWithTransaction(uint8_t* data, const size_t len)
{
assert(_addr);
if (data && _wire->requestFrom(_addr, len)) {
auto count = std::min(len, (size_t)_wire->available());
for (size_t i = 0; i < count; ++i) {
data[i] = (uint8_t)_wire->read();
}
return (count == len) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR;
}
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
m5::hal::error::error_t AdapterI2C::WireImpl::writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t stop)
{
return write_with_transaction(_addr, data, len, stop);
}
m5::hal::error::error_t AdapterI2C::WireImpl::writeWithTransaction(const uint8_t reg, const uint8_t* data,
const size_t len, const uint32_t stop)
{
assert(_addr);
_wire->setClock(_clock);
_wire->beginTransmission(_addr);
_wire->write(reg);
if (data && len) {
_wire->write(data, len);
}
auto ret = _wire->endTransmission(stop);
if (ret) {
M5_LIB_LOGE("%d endTransmission stop:%d", ret, stop);
}
return (ret == 0) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR;
}
m5::hal::error::error_t AdapterI2C::WireImpl::writeWithTransaction(const uint16_t reg, const uint8_t* data,
const size_t len, const uint32_t stop)
{
assert(_addr);
_wire->setClock(_clock);
m5::types::big_uint16_t r(reg);
_wire->beginTransmission(_addr);
_wire->write(r.data(), r.size());
if (data && len) {
_wire->write(data, len);
}
auto ret = _wire->endTransmission(stop);
if (ret) {
M5_LIB_LOGE("%d endTransmission stop:%d", ret, stop);
}
return (ret == 0) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR;
}
AdapterI2C::I2CImpl* AdapterI2C::WireImpl::duplicate(const uint8_t addr)
{
return new WireImpl(*_wire, addr, _clock);
}
m5::hal::error::error_t AdapterI2C::WireImpl::generalCall(const uint8_t* data, const size_t len)
{
return write_with_transaction(0x00, data, len, true);
}
m5::hal::error::error_t AdapterI2C::WireImpl::wakeup()
{
return write_with_transaction(_addr, nullptr, 0, true);
}
m5::hal::error::error_t AdapterI2C::WireImpl::write_with_transaction(const uint8_t addr, const uint8_t* data,
const size_t len, const uint32_t stop)
{
_wire->setClock(_clock);
_wire->beginTransmission(addr);
if (data) {
_wire->write(data, len);
}
auto ret = _wire->endTransmission(stop);
if (ret) {
M5_LIB_LOGE("%d endTransmission stop:%d", ret, stop);
}
return (ret == 0) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR;
}
#endif
// Impl for M5HAL
AdapterI2C::BusImpl::BusImpl(m5::hal::bus::Bus* bus, const uint8_t addr, const uint32_t clock)
: AdapterI2C::I2CImpl(addr, clock), _bus(bus)
{
_access_cfg.i2c_addr = addr;
_access_cfg.freq = clock;
}
AdapterI2C::I2CImpl* AdapterI2C::BusImpl::duplicate(const uint8_t addr)
{
return new BusImpl(_bus, addr, _clock);
}
m5::hal::error::error_t AdapterI2C::BusImpl::readWithTransaction(uint8_t* data, const size_t len)
{
if (_bus && data) {
auto acc = _bus->beginAccess(_access_cfg);
if (acc) {
auto trans = acc.value();
auto result = trans->startRead().and_then([&trans, &data, &len]() {
return trans->read(data, len).and_then([&trans](size_t&&) { return trans->stop(); });
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterI2C::BusImpl::writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t stop)
{
if (_bus) {
auto acc = _bus->beginAccess(_access_cfg);
if (acc) {
auto trans = acc.value();
auto result = trans->startWrite().and_then([&trans, &data, &len, &stop]() {
return trans->write(data, len).and_then([&trans, &stop](size_t&&) {
return stop ? trans->stop() : m5::stl::expected<void, m5::hal::error::error_t>();
});
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterI2C::BusImpl::writeWithTransaction(const uint8_t reg, const uint8_t* data,
const size_t len, const uint32_t stop)
{
assert(_addr);
if (_bus) {
auto acc = _bus->beginAccess(_access_cfg);
if (acc) {
auto trans = acc.value();
auto result = trans->startWrite().and_then([&trans, &reg, &data, &len, &stop]() {
return trans->write(&reg, 1).and_then([&trans, &data, &len, &stop](size_t&&) {
return ((data && len) ? trans->write(data, len)
: m5::stl::expected<size_t, m5::hal::error::error_t>((size_t)0UL))
.and_then([&trans, &stop](size_t&&) {
return stop ? trans->stop() : m5::stl::expected<void, m5::hal::error::error_t>();
});
});
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterI2C::BusImpl::writeWithTransaction(const uint16_t reg, const uint8_t* data,
const size_t len, const uint32_t stop)
{
assert(_addr);
if (_bus) {
auto acc = _bus->beginAccess(_access_cfg);
if (acc) {
m5::types::big_uint16_t r(reg);
auto trans = acc.value();
auto result = trans->startWrite().and_then([&trans, &r, &data, &len, &stop]() {
return trans->write(r.data(), r.size()).and_then([&trans, &data, &len, &stop](size_t&&) {
return ((data && len) ? trans->write(data, len)
: m5::stl::expected<size_t, m5::hal::error::error_t>((size_t)0UL))
.and_then([&trans, &stop](size_t&&) {
return stop ? trans->stop() : m5::stl::expected<void, m5::hal::error::error_t>();
});
});
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
m5::hal::error::error_t AdapterI2C::BusImpl::generalCall(const uint8_t* data, const size_t len)
{
m5::hal::bus::I2CMasterAccessConfig gcfg = _access_cfg;
gcfg.i2c_addr = 0x00;
return write_with_transaction(gcfg, data, len, true);
}
m5::hal::error::error_t AdapterI2C::BusImpl::wakeup()
{
return write_with_transaction(_access_cfg, nullptr, 0, true);
}
m5::hal::error::error_t AdapterI2C::BusImpl::write_with_transaction(const m5::hal::bus::I2CMasterAccessConfig& cfg,
const uint8_t* data, const size_t len,
const uint32_t stop)
{
if (_bus) {
auto acc = _bus->beginAccess(cfg);
if (acc) {
auto trans = acc.value();
auto result =
trans->startWrite()
.and_then([&trans, &data, &len]() {
return ((data && len) ? trans->write(data, len)
: m5::stl::expected<size_t, m5::hal::error::error_t>((size_t)0UL));
})
.and_then([&trans, &stop](size_t&&) {
return stop ? trans->stop() : m5::stl::expected<void, m5::hal::error::error_t>();
});
// Clean-up must be called
auto eresult = this->_bus->endAccess(std::move(trans));
return result.error_or(eresult);
}
return acc.error();
}
return m5::hal::error::error_t::INVALID_ARGUMENT;
}
// Adapter
#if defined(ARDUINO)
AdapterI2C::AdapterI2C(TwoWire& wire, const uint8_t addr, const uint32_t clock)
: Adapter(Adapter::Type::I2C, new AdapterI2C::WireImpl(wire, addr, clock))
{
assert(_impl);
}
#else
#pragma message "Not support TwoWire"
AdapterI2C::AdapterI2C(TwoWire& wire, const uint8_t addr, const uint32_t clock) : Adapter()
{
(void)wire;
(void)addr;
(void)clock;
assert(_impl);
M5_LIB_LOGE("Not support TwoWire");
}
#endif
AdapterI2C::AdapterI2C(m5::hal::bus::Bus* bus, const uint8_t addr, const uint32_t clock)
: Adapter(Adapter::Type::I2C, new AdapterI2C::BusImpl(bus, addr, clock))
{
assert(_impl);
}
Adapter* AdapterI2C::duplicate(const uint8_t addr)
{
auto ptr = new AdapterI2C();
if (ptr) {
ptr->_impl.reset(impl()->duplicate(addr));
if (ptr->_impl) {
return ptr;
}
delete ptr;
}
M5_LIB_LOGE("Failed to duplicate");
return nullptr;
}
bool AdapterI2C::pushPin()
{
#if defined(ARDUINO)
if (_backupSCL.getPin() < 0 && _backupSDA.getPin() < 0) {
_backupSCL.setPin(scl());
_backupSCL.backup();
_backupSDA.setPin(sda());
_backupSDA.backup();
M5_LIB_LOGD(">>Push SCL:%u SDA:%u", _backupSCL.getPin(), _backupSDA.getPin());
return true;
}
return false;
#else
return false;
#endif
}
bool AdapterI2C::popPin()
{
#if defined(ARDUINO)
if (_backupSCL.getPin() >= 0 && _backupSDA.getPin() >= 0) {
M5_LIB_LOGD("<<Pop SCL:%u SDA:%u", _backupSCL.getPin(), _backupSDA.getPin());
_backupSCL.restore();
_backupSDA.restore();
_backupSCL.setPin(-1);
_backupSDA.setPin(-1);
return true;
}
return false;
#else
return false;
#endif
}
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,247 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file adapter_i2c.hpp
@brief Adapter for I2C to treat M5HAL and TwoWire in the same way
*/
#ifndef M5_UNIT_COMPONENT_ADAPTER_I2C_HPP
#define M5_UNIT_COMPONENT_ADAPTER_I2C_HPP
#include "adapter_base.hpp"
#include "pin.hpp"
class TwoWire;
namespace m5 {
namespace unit {
/*!
@class m5::unit::AdapterI2C
@brief I2C access adapter
*/
class AdapterI2C : public Adapter {
public:
class I2CImpl : public Adapter::Impl {
public:
I2CImpl() = default;
I2CImpl(const uint8_t addr, const uint32_t clock) : Adapter::Impl(), _addr(addr), _clock(clock)
{
}
virtual ~I2CImpl() = default;
inline uint8_t address() const
{
return _addr;
}
inline void setAddress(const uint8_t addr)
{
_addr = addr;
}
inline uint32_t clock() const
{
return _clock;
}
inline virtual void setClock(const uint32_t clock)
{
_clock = clock;
}
//
virtual int16_t scl() const
{
return -1;
}
virtual int16_t sda() const
{
return -1;
}
//
virtual bool begin()
{
return false;
}
virtual bool end()
{
return false;
}
virtual m5::hal::error::error_t wakeup()
{
return m5::hal::error::error_t::UNKNOWN_ERROR;
}
//
virtual I2CImpl* duplicate(const uint8_t addr)
{
return new I2CImpl(addr, _clock);
}
virtual TwoWire* getWire()
{
return nullptr;
}
virtual m5::hal::bus::Bus* getBus()
{
return nullptr;
}
protected:
uint8_t _addr{};
uint32_t _clock{100 * 1000U};
};
//
#if defined(ARDUINO)
class WireImpl : public I2CImpl {
public:
WireImpl(TwoWire& wire, const uint8_t addr, const uint32_t clock);
inline virtual TwoWire* getWire() override
{
return _wire;
}
inline virtual int16_t scl() const override
{
return _scl;
}
inline virtual int16_t sda() const override
{
return _sda;
}
virtual bool begin() override;
virtual bool end() override;
virtual m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t reg, const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint16_t reg, const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual I2CImpl* duplicate(const uint8_t addr) override;
virtual m5::hal::error::error_t generalCall(const uint8_t* data, const size_t len) override;
virtual m5::hal::error::error_t wakeup() override;
protected:
m5::hal::error::error_t write_with_transaction(const uint8_t addr, const uint8_t* data, const size_t len,
const uint32_t stop);
private:
TwoWire* _wire{};
int16_t _sda{}, _scl{};
};
#endif
class BusImpl : public I2CImpl {
public:
BusImpl(m5::hal::bus::Bus* bus, const uint8_t addr, const uint32_t clock);
inline virtual m5::hal::bus::Bus* getBus() override
{
return _bus;
}
inline virtual void setClock(const uint32_t clock) override
{
I2CImpl::setClock(clock);
_access_cfg.freq = clock;
}
virtual I2CImpl* duplicate(const uint8_t addr) override;
virtual m5::hal::error::error_t readWithTransaction(uint8_t* data, const size_t len) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint8_t reg, const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t writeWithTransaction(const uint16_t reg, const uint8_t* data, const size_t len,
const uint32_t stop) override;
virtual m5::hal::error::error_t generalCall(const uint8_t* data, const size_t len) override;
virtual m5::hal::error::error_t wakeup() override;
protected:
m5::hal::error::error_t write_with_transaction(const m5::hal::bus::I2CMasterAccessConfig& cfg,
const uint8_t* data, const size_t len, const uint32_t stop);
private:
m5::hal::bus::Bus* _bus{};
m5::hal::bus::I2CMasterAccessConfig _access_cfg{};
};
#if defined(ARDUINO)
AdapterI2C(TwoWire& wire, uint8_t addr, const uint32_t clock);
#endif
AdapterI2C(m5::hal::bus::Bus* bus, const uint8_t addr, const uint32_t clock);
AdapterI2C(m5::hal::bus::Bus& bus, const uint8_t addr, const uint32_t clock) : AdapterI2C(&bus, addr, clock)
{
}
inline I2CImpl* impl()
{
return static_cast<I2CImpl*>(_impl.get());
}
inline const I2CImpl* impl() const
{
return static_cast<I2CImpl*>(_impl.get());
}
inline uint8_t address() const
{
return impl()->address();
}
inline void setAddress(const uint8_t addr)
{
impl()->setAddress(addr);
}
inline uint32_t clock() const
{
return impl()->clock();
}
inline void setClock(const uint32_t clock)
{
impl()->setClock(clock);
}
inline int16_t scl() const
{
return impl()->scl();
}
inline int16_t sda() const
{
return impl()->sda();
}
virtual Adapter* duplicate(const uint8_t addr) override;
/// @warning Functionality required for a specific unit
/// @warning Will be improved when integrated with M5HAL
/// @name Temporary API
///@{
inline bool begin()
{
return impl()->begin();
}
inline bool end()
{
return impl()->end();
}
bool pushPin();
bool popPin();
///@}
protected:
AdapterI2C() : Adapter(Adapter::Type::I2C, new I2CImpl())
{
}
protected:
gpio::pin_backup_t _backupSCL{-1}, _backupSDA{-1};
};
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file identify_functions.hpp
@brief Identification of functions to be used
*/
#ifndef M5_UNIT_UNIFIED_IDENTIFY_FUNCTIONS_HPP
#define M5_UNIT_UNIFIED_IDENTIFY_FUNCTIONS_HPP
// Detect ESP-IDF version
#if __has_include(<esp_idf_version.h>)
#include <esp_idf_version.h>
#else // esp_idf_version.h has been introduced in Arduino 1.0.5 (ESP-IDF3.3)
#define ESP_IDF_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(3, 2, 0)
#endif
// RMT
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define M5_UNIT_UNIFIED_USING_RMT_V2
#define M5_UNIT_UNIFIED_USING_ADC_ONESHOT
#endif
#endif

View file

@ -0,0 +1,106 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file pin.hpp
@brief PIN settings save/restore
@todo Will be transferred to M5HAL in the future
*/
#include "pin.hpp"
#include <M5Utility.hpp>
#include <cstddef>
#include <driver/i2c.h>
#include <soc/gpio_struct.h>
#include <soc/gpio_periph.h>
namespace m5 {
namespace unit {
namespace gpio {
pin_backup_t::pin_backup_t(int pin_num) : _pin_num{static_cast<gpio_num_t>(pin_num)}
{
if (pin_num >= 0) {
backup();
}
}
void pin_backup_t::backup(void)
{
#if !defined(CONFIG_IDF_TARGET_ESP32P4)
auto pin_num = (size_t)_pin_num;
if (pin_num < GPIO_NUM_MAX) {
_io_mux_gpio_reg = *reinterpret_cast<uint32_t*>(GPIO_PIN_MUX_REG[pin_num]);
_gpio_pin_reg = *reinterpret_cast<uint32_t*>(GPIO_PIN0_REG + (pin_num * 4));
_gpio_func_out_reg = *reinterpret_cast<uint32_t*>(GPIO_FUNC0_OUT_SEL_CFG_REG + (pin_num * 4));
#if defined(GPIO_ENABLE1_REG)
_gpio_enable = (bool)((*reinterpret_cast<uint32_t*>(((pin_num & 32) ? GPIO_ENABLE1_REG : GPIO_ENABLE_REG)) &
(1U << (pin_num & 31))) != 0);
#else
_gpio_enable = (bool)((*reinterpret_cast<uint32_t*>(GPIO_ENABLE_REG) & (1U << (pin_num & 31)) != 0);
#endif
_in_func_num = -1;
size_t func_num = ((_gpio_func_out_reg >> GPIO_FUNC0_OUT_SEL_S) & GPIO_FUNC0_OUT_SEL_V);
if (func_num < sizeof(GPIO.func_in_sel_cfg) / sizeof(GPIO.func_in_sel_cfg[0])) {
_gpio_func_in_reg = *reinterpret_cast<uint32_t*>(GPIO_FUNC0_IN_SEL_CFG_REG + (func_num * 4));
if (func_num == ((_gpio_func_in_reg >> GPIO_FUNC0_IN_SEL_S) & GPIO_FUNC0_IN_SEL_V)) {
_in_func_num = func_num;
M5_LIB_LOGV("backup pin:%d : func_num:%d", pin_num, _in_func_num);
}
}
}
#else
#pragma message "ESP32P4 was not support"
#endif
}
void pin_backup_t::restore(void)
{
#if !defined(CONFIG_IDF_TARGET_ESP32P4)
auto pin_num = (size_t)_pin_num;
if (pin_num < GPIO_NUM_MAX) {
if ((uint16_t)_in_func_num < 256) {
GPIO.func_in_sel_cfg[_in_func_num].val = _gpio_func_in_reg;
M5_LIB_LOGV("pin:%d in_func_num:%d", (int)pin_num, (int)_in_func_num);
}
M5_LIB_LOGV("restore pin:%d ", pin_num);
M5_LIB_LOGV("restore IO_MUX_GPIO0_REG :%08x -> %08x ",
*reinterpret_cast<uint32_t*>(GPIO_PIN_MUX_REG[pin_num]), _io_mux_gpio_reg);
M5_LIB_LOGV("restore GPIO_PIN0_REG :%08x -> %08x ",
*reinterpret_cast<uint32_t*>(GPIO_PIN0_REG + (pin_num * 4)), _gpio_pin_reg);
M5_LIB_LOGV("restore GPIO_FUNC0_OUT_SEL_CFG_REG:%08x -> %08x ",
*reinterpret_cast<uint32_t*>(GPIO_FUNC0_OUT_SEL_CFG_REG + (pin_num * 4)), _gpio_func_out_reg);
*reinterpret_cast<uint32_t*>(GPIO_PIN_MUX_REG[_pin_num]) = _io_mux_gpio_reg;
*reinterpret_cast<uint32_t*>(GPIO_PIN0_REG + (pin_num * 4)) = _gpio_pin_reg;
*reinterpret_cast<uint32_t*>(GPIO_FUNC0_OUT_SEL_CFG_REG + (pin_num * 4)) = _gpio_func_out_reg;
#if defined(GPIO_ENABLE1_REG)
auto gpio_enable_reg = reinterpret_cast<uint32_t*>(((pin_num & 32) ? GPIO_ENABLE1_REG : GPIO_ENABLE_REG));
#else
auto gpio_enable_reg = reinterpret_cast<uint32_t*>(GPIO_ENABLE_REG);
#endif
uint32_t pin_mask = 1 << (pin_num & 31);
uint32_t val = *gpio_enable_reg;
M5_LIB_LOGV("restore GPIO_ENABLE_REG:%08x", (int)*gpio_enable_reg);
if (_gpio_enable) {
val |= pin_mask;
} else {
val &= ~pin_mask;
}
*gpio_enable_reg = val;
}
#else
#pragma message "ESP32P4 was not support"
#endif
}
} // namespace gpio
} // namespace unit
} // namespace m5

View file

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file pin.hpp
@brief PIN settings save/restore
@todo Will be transferred to M5HAL in the future
*/
#ifndef M5_UNIT_COMPONENT_PIN_HPP
#define M5_UNIT_COMPONENT_PIN_HPP
#include <cstdint>
namespace m5 {
namespace unit {
namespace gpio {
// From M5GFX
class pin_backup_t {
public:
explicit pin_backup_t(int pin_num = 1);
inline void setPin(int pin_num)
{
_pin_num = pin_num;
}
inline int8_t getPin(void) const
{
return _pin_num;
}
void backup(void);
void restore(void);
private:
uint32_t _io_mux_gpio_reg{};
uint32_t _gpio_pin_reg{};
uint32_t _gpio_func_out_reg{};
uint32_t _gpio_func_in_reg{};
int16_t _in_func_num{-1};
int8_t _pin_num{-1}; // GPIO_NUM_NC
bool _gpio_enable{};
};
} // namespace gpio
} // namespace unit
} // namespace m5
#endif

View file

@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*!
@file types.hpp
@brief Type and enumerator definitions
*/
#ifndef M5_UNIT_COMPONENT_TYPES_HPP
#define M5_UNIT_COMPONENT_TYPES_HPP
#include <cstdint>
#include <type_traits>
#include "identify_functions.hpp"
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
#include <driver/rmt_types.h>
#else
#include <soc/rmt_struct.h>
#endif
#include <driver/gpio.h>
namespace m5 {
namespace unit {
/*!
@namespace types
@brief Type and enumerator definitions
*/
namespace types {
/*!
@enum category_t
@brief Unit category (used for static class determination)
*/
enum class category_t {
None,
UnitLED, //!< Derived from UnitLED
};
using uid_t = uint32_t; //!< @brief Component unique identifier
using attr_t = uint32_t; //!< @brief Component attribute bits
using elapsed_time_t = unsigned long; //!< @brief Elapsed time unit (ms)
namespace attribute {
///@name Attribute
///@{
constexpr attr_t AccessI2C = 0x00000001; //!< I2C Accessible Unit
constexpr attr_t AccessGPIO = 0x00000002; //!< GPIO Accessible Unit
///@}
} // namespace attribute
} // namespace types
namespace gpio {
/*!
@enum Mode
@brief Pin mode
*/
enum class Mode : uint8_t {
Input,
Output,
Pullup,
InputPullup,
Pulldown,
InputPulldown,
OpenDrain,
OutputOpenDrain,
Analog,
// RMT access
RmtRX = 0x80,
RmtTX,
RmtRXTX,
};
/*!
@struct m5::unit::gpio::adapter_config_t
@brief Common pinMode, RMT v1 and v2 settings
*/
struct adapter_config_t {
struct config_t {
gpio_num_t gpio_num{};
uint32_t tick_ns{};
uint8_t mem_blocks{};
bool idle_output{};
bool idle_level_high{};
bool with_dma{};
bool loop_enabled{};
};
Mode mode{}; // Mode
config_t rx{}; // For RMT
config_t tx{}; // For RMT
};
#if defined(M5_UNIT_UNIFIED_USING_RMT_V2)
using m5_rmt_item_t = rmt_symbol_word_t;
#else
using m5_rmt_item_t = rmt_item32_t;
#endif
} // namespace gpio
} // namespace unit
} // namespace m5
#endif