Merge branch 'develop'

This commit is contained in:
GOB
2026-04-03 10:37:55 +09:00
49 changed files with 1723 additions and 1832 deletions
@@ -12,28 +12,28 @@ on:
branches:
- '*'
paths:
- 'src/unit/**.cpp'
- 'src/unit/**.hpp'
- 'src/unit/**.h'
- 'src/unit/**.c'
- 'src/**.cpp'
- 'src/**.hpp'
- 'src/**.h'
- 'src/**.c'
- 'examples/UnitUnified/**.ino'
- 'examples/UnitUnified/**.cpp'
- 'examples/UnitUnified/**.hpp'
- 'examples/UnitUnified/**.h'
- 'examples/UnitUnified/**.c'
- '**arduino-esp-v2-build-check.yml'
- '.github/workflows/arduino-esp-v2-build-check.yml'
pull_request:
paths:
- 'src/unit/**.cpp'
- 'src/unit/**.hpp'
- 'src/unit/**.h'
- 'src/unit/**.c'
- 'src/**.cpp'
- 'src/**.hpp'
- 'src/**.h'
- 'src/**.c'
- 'examples/UnitUnified/**.ino'
- 'examples/UnitUnified/**.cpp'
- 'examples/UnitUnified/**.hpp'
- 'examples/UnitUnified/**.h'
- 'examples/UnitUnified/**.c'
- '**arduino-esp-v2-build-check.yml'
- '.github/workflows/arduino-esp-v2-build-check.yml'
workflow_dispatch:
defaults:
@@ -46,13 +46,13 @@ concurrency:
jobs:
build:
name: ${{ matrix.unit }}:${{ matrix.sketch }}:${{matrix.board}}@${{matrix.platform-version}}
name: ${{ matrix.build-properties }}:${{ matrix.sketch }}:${{matrix.board}}@${{matrix.platform-version}}
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 12
strategy:
fail-fast: false
#max-parallel: 1
max-parallel: 20
matrix:
platform-url:
- https://espressif.github.io/arduino-esp32/package_esp32_index.json
@@ -60,8 +60,8 @@ jobs:
sketch:
- PlotToSerial
unit:
- UnitHeart
build-properties:
- "-DUSING_UNIT_HEART"
board:
- m5stack-atom
@@ -84,29 +84,54 @@ jobs:
include:
# Specific sketches
- sketch: GraphicalMeter
unit: UnitHeart
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 2.0.17
board: m5stack-core-esp32
build-properties: "-DUSING_UNIT_HEART"
- sketch: PlotToSerial
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 2.0.17
board: m5stack-coreink
build-properties: "-DUSING_HAT_HEART"
- sketch: DualSensor
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 2.0.17
board: m5stack-coreink
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
# Build
- name: Prepare libraries list
id: libs
run: |
{
echo "yaml<<EOF"
echo "$REQUIRED_LIBRARIES" | tr ',' '\n' | while read -r lib; do
echo "- name: $lib"
done
echo "- source-path: ./"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Compile examples
uses: ArminJo/arduino-test-compile@master
uses: arduino/compile-sketches@v1
with:
arduino-board-fqbn: ${{ matrix.platform }}:${{ matrix.archi }}:${{ matrix.board }}
arduino-platform: ${{ matrix.platform }}:${{ matrix.archi }}@${{ matrix.platform-version }}
platform-url: ${{ matrix.platform-url }}
required-libraries: ${{ env.REQUIRED_LIBRARIES }}
extra-arduino-cli-args: ${{ matrix.cli-args }}
#build-properties: ${{ toJson(matrix.build-properties) }}
sketch-names: ${{ matrix.sketch }}.ino
sketch-names-find-start: ${{ env.SKETCH_NAMES_FIND_START }}/${{ matrix.unit }}
#sketches-exclude: ${{ matrix.sketches-exclude }}
fqbn: ${{ matrix.platform }}:${{ matrix.archi }}:${{ matrix.board }}
platforms: |
- name: ${{ matrix.platform }}:${{ matrix.archi }}
source-url: ${{ matrix.platform-url }}
version: ${{ matrix.platform-version }}
libraries: ${{ steps.libs.outputs.yaml }}
sketch-paths: |
- ${{ env.SKETCH_NAMES_FIND_START }}/${{ matrix.sketch }}
cli-compile-flags: |
- --build-property
- build.extra_flags=${{ matrix.build-properties }}
@@ -12,28 +12,28 @@ on:
branches:
- '*'
paths:
- 'src/unit/**.cpp'
- 'src/unit/**.hpp'
- 'src/unit/**.h'
- 'src/unit/**.c'
- 'src/**.cpp'
- 'src/**.hpp'
- 'src/**.h'
- 'src/**.c'
- 'examples/UnitUnified/**.ino'
- 'examples/UnitUnified/**.cpp'
- 'examples/UnitUnified/**.hpp'
- 'examples/UnitUnified/**.h'
- 'examples/UnitUnified/**.c'
- '**arduino-esp-v3-build-check.yml'
- '.github/workflows/arduino-esp-v3-build-check.yml'
pull_request:
paths:
- 'src/unit/**.cpp'
- 'src/unit/**.hpp'
- 'src/unit/**.h'
- 'src/unit/**.c'
- 'src/**.cpp'
- 'src/**.hpp'
- 'src/**.h'
- 'src/**.c'
- 'examples/UnitUnified/**.ino'
- 'examples/UnitUnified/**.cpp'
- 'examples/UnitUnified/**.hpp'
- 'examples/UnitUnified/**.h'
- 'examples/UnitUnified/**.c'
- '**arduino-esp-v3-build-check.yml'
- '.github/workflows/arduino-esp-v3-build-check.yml'
workflow_dispatch:
defaults:
@@ -46,13 +46,13 @@ concurrency:
jobs:
build:
name: ${{ matrix.unit }}:${{ matrix.sketch }}:${{matrix.board}}@${{matrix.platform-version}}
name: ${{ matrix.build-properties }}:${{ matrix.sketch }}:${{matrix.board}}@${{matrix.platform-version}}
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 12
strategy:
fail-fast: false
#max-parallel: 1
max-parallel: 20
matrix:
platform-url:
- https://espressif.github.io/arduino-esp32/package_esp32_index.json
@@ -60,19 +60,21 @@ jobs:
sketch:
- PlotToSerial
unit:
- UnitHeart
build-properties:
- "-DUSING_UNIT_HEART"
board:
- arduino_nesso_n1
- m5stack_atom
- m5stack_atoms3
- m5stack_capsule
# - m5stack_cardputer
- m5stack_cardputer
- m5stack_core
- m5stack_core2
- m5stack_coreink
- m5stack_cores3
- m5stack_dial
- m5stack_dinmeter
- m5stack_fire
- m5stack_nanoc6
- m5stack_paper
@@ -84,13 +86,14 @@ jobs:
# - m5stack_stickc
- m5stack_stickc_plus
- m5stack_stickc_plus2
- m5stack_tab5
# - m5stack_timer_cam
# - m5stack_tough
# - m5stack_unit_cam
# - m5stack_unit_cams3
platform-version:
- 3.0.4
- 3.3.6
platform:
- esp32
@@ -100,70 +103,84 @@ jobs:
include:
# Specific sketches
# HAT
- sketch: PlotToSerial
unit: HatHeart
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 3.0.4
platform-version: 3.3.6
board: m5stack_stickc_plus
build-properties: "-DUSING_HAT_HEART"
- sketch: PlotToSerial
unit: HatHeart
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 3.0.4
platform-version: 3.3.6
board: m5stack_stickc_plus2
- sketch: GraphicalMeter
unit: UnitHeart
build-properties: "-DUSING_HAT_HEART"
- sketch: PlotToSerial
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 3.0.4
platform-version: 3.3.6
board: m5stack_coreink
build-properties: "-DUSING_HAT_HEART"
- sketch: PlotToSerial
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 3.3.6
board: arduino_nesso_n1
build-properties: "-DUSING_HAT_HEART"
#GraphicalMeter
- sketch: GraphicalMeter
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 3.3.6
board: m5stack_core
build-properties: "-DUSING_UNIT_HEART"
- sketch: GraphicalMeter
unit: HatHeart
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 3.0.4
board: m5stack_stickc_plus
- sketch: GraphicalMeter
unit: HatHeart
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 3.0.4
platform-version: 3.3.6
board: m5stack_stickc_plus2
build-properties: "-DUSING_HAT_HEART"
- sketch: DualSensor
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 3.0.4
board: m5stack_stickc_plus
- sketch: DualSensor
platform-url: https://espressif.github.io/arduino-esp32/package_esp32_index.json
platform: esp32
archi: esp32
platform-version: 3.0.4
platform-version: 3.3.6
board: m5stack_stickc_plus2
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
# Build
- name: Prepare libraries list
id: libs
run: |
{
echo "yaml<<EOF"
echo "$REQUIRED_LIBRARIES" | tr ',' '\n' | while read -r lib; do
echo "- name: $lib"
done
echo "- source-path: ./"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Compile examples
uses: ArminJo/arduino-test-compile@master
uses: arduino/compile-sketches@v1
with:
arduino-board-fqbn: ${{ matrix.platform }}:${{ matrix.archi }}:${{ matrix.board }}
arduino-platform: ${{ matrix.platform }}:${{ matrix.archi }}@${{ matrix.platform-version }}
platform-url: ${{ matrix.platform-url }}
required-libraries: ${{ env.REQUIRED_LIBRARIES }}
extra-arduino-cli-args: ${{ matrix.cli-args }}
#build-properties: ${{ toJson(matrix.build-properties) }}
sketch-names: ${{ matrix.sketch }}.ino
sketch-names-find-start: ${{ env.SKETCH_NAMES_FIND_START }}/${{ matrix.unit }}
#sketches-exclude: ${{ matrix.sketches-exclude }}
fqbn: ${{ matrix.platform }}:${{ matrix.archi }}:${{ matrix.board }}
platforms: |
- name: ${{ matrix.platform }}:${{ matrix.archi }}
source-url: ${{ matrix.platform-url }}
version: ${{ matrix.platform-version }}
libraries: ${{ steps.libs.outputs.yaml }}
sketch-paths: |
- ${{ env.SKETCH_NAMES_FIND_START }}/${{ matrix.sketch }}
cli-compile-flags: |
- --build-property
- build.extra_flags=${{ matrix.build-properties }}
+86 -54
View File
@@ -12,28 +12,28 @@ on:
branches:
- '*'
paths:
- 'src/unit/**.cpp'
- 'src/unit/**.hpp'
- 'src/unit/**.h'
- 'src/unit/**.c'
- 'src/**.cpp'
- 'src/**.hpp'
- 'src/**.h'
- 'src/**.c'
- 'examples/UnitUnified/**.ino'
- 'examples/UnitUnified/**.cpp'
- 'examples/UnitUnified/**.hpp'
- 'examples/UnitUnified/**.h'
- 'examples/UnitUnified/**.c'
- '**arduino-m5-build-check.yml'
- '.github/workflows/arduino-m5-build-check.yml'
pull_request:
paths:
- 'src/unit/**.cpp'
- 'src/unit/**.hpp'
- 'src/unit/**.h'
- 'src/unit/**.c'
- 'src/**.cpp'
- 'src/**.hpp'
- 'src/**.h'
- 'src/**.c'
- 'examples/UnitUnified/**.ino'
- 'examples/UnitUnified/**.cpp'
- 'examples/UnitUnified/**.hpp'
- 'examples/UnitUnified/**.h'
- 'examples/UnitUnified/**.c'
- '**arduino-m5-build-check.yml'
- '.github/workflows/arduino-m5-build-check.yml'
workflow_dispatch:
defaults:
@@ -46,13 +46,13 @@ concurrency:
jobs:
build:
name: ${{ matrix.unit }}:${{ matrix.sketch }}:${{matrix.board}}@${{matrix.platform-version}}
name: ${{ matrix.build-properties }}:${{ matrix.sketch }}:${{matrix.board}}@${{matrix.platform-version}}
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 12
strategy:
fail-fast: false
# max-parallel: 1
max-parallel: 20
matrix:
platform-url:
- https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
@@ -60,15 +60,17 @@ jobs:
sketch:
- PlotToSerial
unit:
- UnitHeart
build-properties:
- "-DUSING_UNIT_HEART"
board:
- arduino_nesso_n1
- m5stack_atom
- m5stack_atoms3
- m5stack_atoms3r
- m5stack_capsule
# - m5stack_cardputer
- m5stack_cardputer
# - m5stack_chain_dualkey
- m5stack_core
- m5stack_core2
- m5stack_coreink
@@ -76,22 +78,30 @@ jobs:
- m5stack_dial
- m5stack_dinmeter
- m5stack_fire
- m5stack_nano_c6
# - m5stack_nano_h2
- m5stack_paper
- m5stack_papers3
# - m5stack_poe_cam
# - m5stack_powerhub
# - m5stack_stamp_c3
# - m5stack_stamp_pico
- m5stack_stamp_s3
# - m5stack_stamplc
# - m5stack_station
# - m5stack_stickc
- m5stack_stickc
- m5stack_stickc_plus
- m5stack_stickc_plus2
- m5stack_sticks3
- m5stack_tab5
# - m5stack_timer_cam
# - m5stack_tough
# - m5stack_unit_c6l
# - m5stack_unit_cam
# - m5stack_unit_cams3
platform-version:
- 2.1.2
- 3.2.5
platform:
- m5stack
@@ -101,71 +111,93 @@ jobs:
include:
# Specific sketches
# HAT
- sketch: PlotToSerial
unit: HatHeart
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 2.1.2
platform-version: 3.2.5
board: m5stack_stickc_plus
build-properties: "-DUSING_HAT_HEART"
- sketch: PlotToSerial
unit: HatHeart
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 2.1.2
platform-version: 3.2.5
board: m5stack_stickc_plus2
- sketch: GraphicalMeter
unit: UnitHeart
build-properties: "-DUSING_HAT_HEART"
- sketch: PlotToSerial
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 2.1.2
platform-version: 3.2.5
board: m5stack_sticks3
build-properties: "-DUSING_HAT_HEART"
- sketch: PlotToSerial
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 3.2.5
board: m5stack_coreink
build-properties: "-DUSING_HAT_HEART"
- sketch: PlotToSerial
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 3.2.5
board: arduino_nesso_n1
build-properties: "-DUSING_HAT_HEART"
# GraphicalMeter
- sketch: GraphicalMeter
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 3.2.5
board: m5stack_core
build-properties: "-DUSING_UNIT_HEART"
- sketch: GraphicalMeter
unit: HatHeart
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 2.1.2
board: m5stack_stickc_plus
- sketch: GraphicalMeter
unit: HatHeart
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 2.1.2
platform-version: 3.2.5
board: m5stack_stickc_plus2
build-properties: "-DUSING_HAT_HEART"
# DualSensor
- sketch: DualSensor
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 2.1.2
board: m5stack_stickc_plus
- sketch: DualSensor
platform-url: https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json
platform: m5stack
archi: esp32
platform-version: 2.1.2
platform-version: 3.2.5
board: m5stack_stickc_plus2
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
# Build
- name: Prepare libraries list
id: libs
run: |
{
echo "yaml<<EOF"
echo "$REQUIRED_LIBRARIES" | tr ',' '\n' | while read -r lib; do
echo "- name: $lib"
done
echo "- source-path: ./"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Compile examples
uses: ArminJo/arduino-test-compile@master
uses: arduino/compile-sketches@v1
with:
arduino-board-fqbn: ${{ matrix.platform }}:${{ matrix.archi }}:${{ matrix.board }}
arduino-platform: ${{ matrix.platform }}:${{ matrix.archi }}@${{ matrix.platform-version }}
platform-url: ${{ matrix.platform-url }}
required-libraries: ${{ env.REQUIRED_LIBRARIES }}
extra-arduino-cli-args: ${{ matrix.cli-args }}
#build-properties: ${{ toJson(matrix.build-properties) }}
sketch-names: ${{ matrix.sketch }}.ino
sketch-names-find-start: ${{ env.SKETCH_NAMES_FIND_START }}/${{ matrix.unit }}
#sketches-exclude: ${{ matrix.sketches-exclude }}
fqbn: ${{ matrix.platform }}:${{ matrix.archi }}:${{ matrix.board }}
platforms: |
- name: ${{ matrix.platform }}:${{ matrix.archi }}
source-url: ${{ matrix.platform-url }}
version: ${{ matrix.platform-version }}
libraries: ${{ steps.libs.outputs.yaml }}
sketch-paths: |
- ${{ env.SKETCH_NAMES_FIND_START }}/${{ matrix.sketch }}
cli-compile-flags: |
- --build-property
- build.extra_flags=${{ matrix.build-properties }}
+3 -5
View File
@@ -17,7 +17,7 @@ on:
- '**.h'
- '**.c'
- '**.inl'
- '**clang-format-check.yml'
- '.github/workflows/clang-format-check.yml'
- '**.clang-format'
pull_request:
paths:
@@ -27,7 +27,7 @@ on:
- '**.h'
- '**.c'
- '**.inl'
- '**clang-format-check.yml'
- '.github/workflows/clang-format-check.yml'
- '**.clang-format'
workflow_dispatch:
@@ -55,11 +55,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Run clang-format style check for C/C++/Protobuf programs.
uses: jidicula/clang-format-action@v4.10.2 # Using include-regex 10.x or later
uses: jidicula/clang-format-action@v4.16.0
with:
clang-format-version: '13'
check-path: ${{ matrix.path['check'] }}
+3 -1
View File
@@ -1,8 +1,10 @@
name: Deploy Doxygen docuemnt on GitHub Pages
name: Deploy Doxygen document on GitHub Pages
on: [release, workflow_dispatch]
# branches:
# - main
# - master
permissions:
contents: write
defaults:
run:
shell: bash
+44 -58
View File
@@ -8,30 +8,30 @@ on:
branches:
- '*'
paths:
- 'src/unit/**.cpp'
- 'src/unit/**.hpp'
- 'src/unit/**.h'
- 'src/unit/**.c'
- 'src/**.cpp'
- 'src/**.hpp'
- 'src/**.h'
- 'src/**.c'
- 'examples/UnitUnified/**.ino'
- 'examples/UnitUnified/**.cpp'
- 'examples/UnitUnified/**.hpp'
- 'examples/UnitUnified/**.h'
- 'examples/UnitUnified/**.c'
- '/platformio-build-check.yml'
- '**platformio.ini'
- '.github/workflows/platformio-build-check.yml'
- 'platformio.ini'
pull_request:
paths:
- 'src/unit/**.cpp'
- 'src/unit/**.hpp'
- 'src/unit/**.h'
- 'src/unit/**.c'
- 'src/**.cpp'
- 'src/**.hpp'
- 'src/**.h'
- 'src/**.c'
- 'examples/UnitUnified/**.ino'
- 'examples/UnitUnified/**.cpp'
- 'examples/UnitUnified/**.hpp'
- 'examples/UnitUnified/**.h'
- 'examples/UnitUnified/**.c'
- '**/platformio-build-check.yml'
- '**platformio.ini'
- '.github/workflows/platformio-build-check.yml'
- 'platformio.ini'
workflow_dispatch:
defaults:
@@ -46,11 +46,11 @@ jobs:
build:
name: ${{ matrix.unit }}:${{ matrix.example }}@${{ matrix.board }}:${{ matrix.framework }}:${{ matrix.espressif32 }}
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 12
strategy:
fail-fast: false
#max-parallel: 1
max-parallel: 20
matrix:
example:
@@ -66,55 +66,28 @@ jobs:
- Fire
- StampS3
- Dial
- AtomMatrix
- Atom
- AtomS3
- AtomS3R
- NanoC6
- StickCPlus
- StickCPlus2
- StickS3
- Paper
- CoreInk
- Cardputer
- Tab5
- NessoN1
framework:
- Arduino
espressif32:
- latest
# - '5_4_0'
# - '4_4_0'
# exclude:
# - board: CoreS3
# espressif32: '5_4_0'
# - board: CoreS3
# espressif32: '4_4_0'
# - board: StampS3
# espressif32: '5_4_0'
# - board: StampS3
# espressif32: '4_4_0'
# - board: AtomS3
# espressif32: '5_4_0'
# - board: AtomS3
# espressif32: '4_4_0'
# - board: Dial
# espressif32: '5_4_0'
# - board: Dial
# espressif32: '4_4_0'
# - board: NanoC6
# espressif32: '5_4_0'
# - board: NanoC6
# espressif32: '4_4_0'
# - board: StickCPlus
# espressif32: '5_4_0'
# - board: StickCPlus
# espressif32: '4_4_0'
# - board: Paper
# espressif32: '5_4_0'
# - board: Paper
# espressif32: '4_4_0'
include:
# Specific sketches
# Hat
- example: PlotToSerial
unit: HatHeart
board: StickCPlus
@@ -125,36 +98,49 @@ jobs:
board: StickCPlus2
framework: Arduino
espressif32: latest
- example: PlotToSerial
unit: HatHeart
board: StickS3
framework: Arduino
espressif32: latest
- example: PlotToSerial
unit: HatHeart
board: CoreInk
framework: Arduino
espressif32: latest
- example: PlotToSerial
unit: HatHeart
board: NessoN1
framework: Arduino
espressif32: latest
# GraphicalMeter
- example: GraphicalMeter
unit: UnitHeart
board: Core
framework: Arduino
espressif32: latest
- example: GraphicalMeter
unit: HatHeart
board: StickCPlus
framework: Arduino
espressif32: latest
- example: GraphicalMeter
unit: HatHeart
board: StickCPlus2
framework: Arduino
espressif32: latest
- example: DualSensor
board: StickCPlus
framework: Arduino
espressif32: latest
# DualSensor
- example: DualSensor
board: StickCPlus2
framework: Arduino
espressif32: latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
ref: ${{ github.event.pull_request.head.sha }}
python-version: '3.10'
- name: Install intelhex
run: pip install intelhex
- name: Build examples
uses: karniv00l/platformio-run-action@v1
+21
View File
@@ -30,3 +30,24 @@
*.exe
*.out
*.app
# Doxygen
docs/html/
# PlatformIO
.pio/
# IDE
.vscode/
# macOS
.DS_Store
# Temporary
tmp/
# Secrets
.env*
*.pem
*.key
*.cert
+22 -11
View File
@@ -2,26 +2,20 @@
## Overview
Library for UnitHEART [M5UnitUnified](https://github.com/m5stack/M5UnitUnified).
Library for UnitHEART using [M5UnitUnified](https://github.com/m5stack/M5UnitUnified).
M5UnitUnified is a library for unified handling of various M5 units products.
### SKU:U029
HEART is built using the MAX30100 chipset.
MAX30100 is a complete pulse oximetry and heart-rate sensor system solution designed for the demanding requirements of wearable devices.
The MAX30100 provides very small total solution size without sacrificing optical or electrical performance. Minimal external hardware components are needed for integration into a wearable device.
Unit Heart is a blood oxygen and heart rate sensor. Integrated MAX30100, it offers a complete pulse oximeter and heart rate sensor system solution. This is a non-invasive blood oxygen and heart rate sensor, integrating two infrared light-emitting diodes and a photodetector. The detection principle is to use infrared LEDs to illuminate and detect the proportion of red blood cells carrying oxygen versus those not carrying oxygen, thereby obtaining the oxygen content.
### SKU:U118
HEART RATE HAT is a blood oxygen heart rate sensor. Integrate MAX30102 to provide a complete pulse oximeter and heart rate sensor system solution. This is a non-pluggable blood oxygen heart rate sensor. The sensor uses I2C communication interface, internally integrates infrared light-emitting diodes, photo-detectors, optical components and low-noise electronic equipment. A certain amount of ambient light suppression function can make the measurement results more accurate. .
Hat Heart is a blood oxygen and heart rate sensor. It integrates MAX30102, providing a complete pulse oximeter and heart rate sensor system solution. This is a non-invasive blood oxygen and heart rate sensor. Its detection principle is based on infrared LED light illumination, which detects the ratio of oxygenated to deoxygenated red blood cells to determine blood oxygen levels. The sensor uses an I2C communication interface and integrates infrared LEDs, photodetectors, optical components, and low-noise electronics, with some ambient light suppression for more accurate measurements.
## Related Link
See also examples using conventional methods here.
- [Unit Heart & Datasheet](https://docs.m5stack.com/ja/unit/heart)
- [Unit Heart & Datasheet](https://docs.m5stack.com/en/unit/heart)
- [Hat Heart & Datasheet](https://docs.m5stack.com/en/hat/hat_heart_rate)
## Required Libraries:
@@ -32,12 +26,29 @@ See also examples using conventional methods here.
## License
- [M5Unit-HEART- MIT](LICENSE)
- [M5Unit-HEART - MIT](LICENSE)
## Examples
See also [examples/UnitUnified](examples/UnitUnified)
### For ArduinoIDE settings
You must choose a define symbol for the unit you will use.
(Rewrite source or specify with compile options)
- PlotToSerial / GraphicalMeter
```cpp
// *************************************************************
// Choose one define symbol to match the unit you are using
// *************************************************************
#if !defined(USING_UNIT_HEART) && !defined(USING_HAT_HEART)
// For UnitHeart (U029)
// #define USING_UNIT_HEART
// For HatHeart (U118)
// #define USING_HAT_HEART
#endif
```
## Doxygen document
[GitHub Pages](https://m5stack.github.io/M5Unit-HEART/)
+1 -1
View File
@@ -42,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = M5Unit-HEART
PROJECT_NAME = "M5Unit-HEART"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# Please execute on repositry root
# Please execute on repository root
## Get version from library.properties
## Get git rev of HEAD
@@ -10,6 +10,7 @@
#include <M5UnitUnified.h>
#include <M5UnitUnifiedHEART.h>
#include <Wire.h>
#include <M5HAL.hpp>
#include "../src/view.hpp"
namespace {
@@ -19,6 +20,30 @@ m5::unit::UnitHeart unit;
m5::unit::HatHeart hat;
View* view[2]{};
bool is_epd_panel{};
struct I2cPins {
int sda;
int scl;
};
I2cPins get_hat_i2c_pins(const m5::board_t board)
{
switch (board) {
case m5::board_t::board_M5StickC:
case m5::board_t::board_M5StickCPlus:
case m5::board_t::board_M5StickCPlus2:
return {0, 26};
case m5::board_t::board_M5StickS3:
return {8, 0};
case m5::board_t::board_M5StackCoreInk:
return {25, 26};
case m5::board_t::board_ArduinoNessoN1:
return {6, 7};
default:
return {-1, -1};
}
}
} // namespace
@@ -32,39 +57,73 @@ void setup()
m5cfg.internal_imu = false; // Disable internal IMU
m5cfg.internal_rtc = false; // Disable internal RTC
M5.begin(m5cfg);
M5.setTouchButtonHeightByRatio(100);
is_epd_panel = lcd.isEPD();
auto board = M5.getBoard();
if (board != m5::board_t::board_M5StickCPlus && board != m5::board_t::board_M5StickCPlus2) {
M5_LOGE("Example for StickCPlus/CPlus2");
lcd.clear(TFT_RED);
while (true) {
m5::utility::delay(10000);
}
}
const auto board_type = M5.getBoard();
// The screen shall be in landscape mode
if (lcd.height() > lcd.width()) {
lcd.setRotation(1);
}
// Setup required to use HatHEART
pinMode(25, INPUT_PULLUP);
pinMode(26, OUTPUT);
const auto pins = get_hat_i2c_pins(board_type);
M5_LOGI("getHatPin: SDA:%u SCL:%u", pins.sda, pins.scl);
if (pins.sda < 0 || pins.scl < 0) {
M5_LOGE("Hat port not exists");
lcd.fillScreen(TFT_RED);
while (true) {
m5::utility::delay(10000);
}
}
// Wire settings
// Setup required to use HatHEART
pinMode(pins.scl, OUTPUT);
// HatHeart settings
bool hat_ready{};
if (board_type == m5::board_t::board_ArduinoNessoN1) {
// Using wire1
Wire1.end();
Wire1.begin(pins.sda, pins.scl, 400 * 1000U);
hat_ready = Units.add(hat, Wire1);
} else {
// SoftwareI2C for Hat
m5::hal::bus::I2CBusConfig hat_i2c_cfg;
hat_i2c_cfg.pin_sda = m5::hal::gpio::getPin(pins.sda);
hat_i2c_cfg.pin_scl = m5::hal::gpio::getPin(pins.scl);
auto hat_i2c_bus = m5::hal::bus::i2c::getBus(hat_i2c_cfg);
hat_ready = Units.add(hat, hat_i2c_bus ? hat_i2c_bus.value() : nullptr);
}
// UnitHeart settings
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);
Wire.end();
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
bool unit_ready{};
if (board_type == m5::board_t::board_ArduinoNessoN1) {
// For NessoN1 GROVE
// Wire is used internally, so SoftwareI2C handles the unit
pin_num_sda = M5.getPin(m5::pin_name_t::port_b_out);
pin_num_scl = M5.getPin(m5::pin_name_t::port_b_in);
M5_LOGI("getPin(NessoN1): SDA:%u SCL:%u", pin_num_sda, pin_num_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);
M5_LOGI("Bus:%d", i2c_bus.has_value());
unit_ready = Units.add(unit, i2c_bus ? i2c_bus.value() : nullptr);
} else {
// UnitHeart on Wire
M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
Wire.end();
Wire.begin(pin_num_sda, pin_num_scl, 400 * 1000U);
unit_ready = Units.add(unit, Wire);
}
Wire1.end();
Wire1.begin(0, 26, 400 * 1000U);
// UnitHeart connected to GROOVE with Wire
// HatHeart connected to PIN sockect with Wire1
if (!Units.add(unit, Wire) || !Units.add(hat, Wire1) || !Units.begin()) {
M5_LOGE("Failed to begin");
lcd.clear(TFT_RED);
if (!hat_ready || !unit_ready || !Units.begin()) {
M5_LOGE("Failed to begin %u/%u", hat_ready, unit_ready);
M5_LOGE("%s", Units.debugInfo().c_str());
lcd.fillScreen(TFT_RED);
while (true) {
m5::utility::delay(10000);
}
@@ -73,14 +132,14 @@ void setup()
M5_LOGI("M5UnitUnified has been begun");
M5_LOGI("%s", Units.debugInfo().c_str());
lcd.startWrite();
view[0] = new View(lcd.width() >> 1, lcd.height(), true);
view[1] = new View(lcd.width() >> 1, lcd.height(), false);
view[0]->_monitor.setSamplingRate(unit.caluculateSamplingRate());
view[1]->_monitor.setSamplingRate(hat.caluculateSamplingRate());
view[0]->push(&lcd, lcd.width() >> 1, 0);
view[1]->push(&lcd, 0, 0);
view[0]->_monitor.setSamplingRate(unit.calculateSamplingRate());
view[1]->_monitor.setSamplingRate(hat.calculateSamplingRate());
if (!is_epd_panel) {
view[0]->push(&lcd, lcd.width() >> 1, 0);
view[1]->push(&lcd, 0, 0);
}
}
void loop()
@@ -88,34 +147,66 @@ void loop()
M5.update();
Units.update();
if (!is_epd_panel) {
lcd.startWrite();
}
if (unit.updated()) {
if (unit.overflow()) {
M5_LOGW("OVERFLOW U:%u", unit.overflow());
}
bool beat{};
while (unit.available()) {
view[0]->push_back(unit.ir(), unit.red());
const auto ir = unit.ir();
const auto red = unit.red();
view[0]->push_back(ir, red);
view[0]->update();
if (is_epd_panel) {
M5.Log.printf(">UIR:%u\n>URED:%u\n", static_cast<uint32_t>(ir), static_cast<uint32_t>(red));
M5.Log.printf(">UMIR:%f\n", view[0]->_monitor.latestIR());
}
beat |= view[0]->_monitor.isBeat();
unit.discard();
}
view[0]->render();
view[0]->push(&lcd, lcd.width() >> 1, 0);
if (is_epd_panel) {
M5.Log.printf(">UBPM:%f\n>USpO2:%f\n>UBEAT:%u\n", view[0]->_monitor.bpm(), view[0]->_monitor.SpO2(), beat);
}
if (!is_epd_panel) {
view[0]->render();
view[0]->push(&lcd, lcd.width() >> 1, 0);
}
}
if (hat.updated()) {
if (hat.overflow()) {
M5_LOGW("OVERFLOW H:%u", hat.overflow());
}
bool beat{};
while (hat.available()) {
view[1]->push_back(hat.ir(), hat.red());
const auto ir = hat.ir();
const auto red = hat.red();
view[1]->push_back(ir, red);
view[1]->update();
if (is_epd_panel) {
M5.Log.printf(">HIR:%u\n>HRED:%u\n", static_cast<uint32_t>(ir), static_cast<uint32_t>(red));
M5.Log.printf(">HMIR:%f\n", view[1]->_monitor.latestIR());
}
beat |= view[1]->_monitor.isBeat();
hat.discard();
}
view[1]->render();
view[1]->push(&lcd, 0, 0);
if (is_epd_panel) {
M5.Log.printf(">HBPM:%f\n>HSpO2:%f\n>HBEAT:%u\n", view[1]->_monitor.bpm(), view[1]->_monitor.SpO2(), beat);
}
if (!is_epd_panel) {
view[1]->render();
view[1]->push(&lcd, 0, 0);
}
}
if (M5.BtnA.wasClicked()) {
view[0]->clear();
view[1]->clear();
}
if (!is_epd_panel) {
lcd.endWrite();
}
}
@@ -12,12 +12,15 @@
class Meter {
public:
Meter(const uint32_t left, const uint32_t top, const uint32_t wid, const uint32_t hgt, m5gfx::rgb565_t tcolor)
: _left(left), _top(top), _theme_color(tcolor)
Meter(const uint32_t left, const uint32_t top, const uint32_t wid, const uint32_t hgt, m5gfx::rgb565_t tcolor,
m5gfx::rgb565_t gcolor = TFT_DARKGRAY, m5gfx::rgb565_t bcolor = TFT_BLACK)
: _left(left), _top(top), _theme_color(tcolor), _gauge_color(gcolor), _background_color(bcolor)
{
_plotter = new m5::ui::Plotter(nullptr, wid, wid, hgt);
_plotter->setGaugeTextDatum(textdatum_t::top_right);
_plotter->setLineColor(_theme_color);
_plotter->setGaugeColor(_gauge_color);
_plotter->setBackgroundColor(_background_color);
}
inline void push_back(const float value)
@@ -25,7 +28,7 @@ public:
_plotter->push_back(value);
}
inline void push(LovyanGFX* target, const uint32_t x, const uint32_t y)
inline void push(LovyanGFX* target)
{
_plotter->push(target, _left, _top);
}
@@ -39,5 +42,7 @@ private:
uint32_t _left{}, _top{};
m5::ui::Plotter* _plotter{};
m5gfx::rgb565_t _theme_color{};
m5gfx::rgb565_t _gauge_color{TFT_DARKGRAY};
m5gfx::rgb565_t _background_color{TFT_BLACK};
};
#endif
@@ -83,7 +83,8 @@ void Plotter::push(LovyanGFX* dst, const int32_t x, const int32_t y)
left += _wid - sz;
}
while (it != itend && left < _wid) {
const int32_t right = x + _wid;
while (it != itend && left < right) {
int32_t s{*it}, e{*(++it)};
dst->drawLine(left, y + hh - hh * (s - _min) / range, left + 1, y + hh - hh * (e - _min) / range, _lineClr);
++left;
+19 -4
View File
@@ -11,9 +11,17 @@
struct View {
View(const uint32_t wid, const uint32_t hgt, bool unit) : _type{unit}
{
_theme_color = unit ? TFT_BLUE : TFT_YELLOW;
_sprite.setPsram(false);
_sprite.setColorDepth(2);
_sprite.createSprite(wid, hgt);
_sprite.setPaletteColor(palette_black, TFT_BLACK);
_sprite.setPaletteColor(palette_white, TFT_WHITE);
_sprite.setPaletteColor(palette_theme, _theme_color);
_sprite.setPaletteColor(palette_gauge, TFT_DARKGRAY);
_sprite.setTextColor(palette_white, palette_black);
_sprite.setFont(&fonts::FreeSansBold9pt7b);
_meter = new Meter(0, hgt * 2 / 3, wid, hgt / 3, unit ? TFT_BLUE : TFT_YELLOW);
_meter = new Meter(0, hgt * 2 / 3, wid, hgt / 3, palette_theme, palette_gauge, palette_black);
}
inline void push_back(const float ir, const float red)
@@ -33,13 +41,13 @@ struct View {
void render()
{
_sprite.clear();
_sprite.clear(palette_black);
_sprite.drawString(_type ? "Unit" : "Hat", 0, 0);
_sprite.setCursor(0, 24);
_sprite.printf("BPM: %3.2f\nSpO2:%3.2f", _monitor.bpm(), _monitor.SpO2());
_sprite.fillCircle(_sprite.width() - 12, 24 * 3, 7, _beat ? TFT_RED : TFT_WHITE);
_sprite.fillCircle(_sprite.width() - 12, 24 * 3, 7, _beat ? palette_theme : palette_white);
_meter->push(&_sprite, 0, _sprite.height() >> 1);
_meter->push(&_sprite);
}
void clear()
@@ -53,10 +61,17 @@ struct View {
_sprite.pushSprite(target, x, y);
}
enum : uint8_t {
palette_black = 0,
palette_white = 1,
palette_theme = 2,
palette_gauge = 3,
};
LGFX_Sprite _sprite{};
m5::heart::PulseMonitor _monitor{100, 2};
Meter* _meter{};
uint32_t _left{}, _top{}, _beat{};
bool _type{};
m5gfx::rgb565_t _theme_color{};
};
#endif
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Graphical meter example for UnitHeart / HatHeart
*/
// *************************************************************
// Choose one define symbol to match the unit you are using
// *************************************************************
#if !defined(USING_UNIT_HEART) && !defined(USING_HAT_HEART)
// For UnitHeart (U029)
// #define USING_UNIT_HEART
// For HatHeart (U118)
// #define USING_HAT_HEART
#endif
#include "main/GraphicalMeter.cpp"
@@ -0,0 +1,193 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Graphical meter example for UnitHeart / HatHeart
The core must be equipped with LCD
*/
#include <M5Unified.h>
#include <M5UnitUnified.h>
#include <M5UnitUnifiedHEART.h>
#include <Wire.h>
#include <M5HAL.hpp>
#include "../src/view.hpp"
// *************************************************************
// Choose one define symbol to match the unit you are using
// *************************************************************
#if !defined(USING_UNIT_HEART) && !defined(USING_HAT_HEART)
// #define USING_UNIT_HEART
// #define USING_HAT_HEART
#endif
#if defined(USING_UNIT_HEART)
#pragma message "Using UnitHeart"
using namespace m5::unit::max30100;
#elif defined(USING_HAT_HEART)
#pragma message "Using HatHeart"
using namespace m5::unit::max30102;
#else
#error Please choose unit!
#endif
namespace {
auto& lcd = M5.Display;
m5::unit::UnitUnified Units;
#if defined(USING_UNIT_HEART)
m5::unit::UnitHeart heart;
#elif defined(USING_HAT_HEART)
m5::unit::HatHeart heart;
#endif
View* view{};
#if defined(USING_HAT_HEART)
struct I2cPins {
int sda;
int scl;
};
I2cPins get_hat_i2c_pins(const m5::board_t board)
{
switch (board) {
case m5::board_t::board_M5StickC:
case m5::board_t::board_M5StickCPlus:
case m5::board_t::board_M5StickCPlus2:
return {0, 26};
case m5::board_t::board_M5StickS3:
return {8, 0};
case m5::board_t::board_M5StackCoreInk:
return {25, 26};
case m5::board_t::board_ArduinoNessoN1:
return {6, 7};
default:
return {-1, -1};
}
}
#endif
} // namespace
void setup()
{
auto m5cfg = M5.config();
#if defined(USING_HAT_HEART)
m5cfg.pmic_button = false; // Disable BtnPWR
m5cfg.internal_imu = false; // Disable internal IMU
m5cfg.internal_rtc = false; // Disable internal RTC
#endif
M5.begin(m5cfg);
M5.setTouchButtonHeightByRatio(100);
auto board = M5.getBoard();
// The screen shall be in landscape mode (skip for M5Tab5)
if (board != m5::board_t::board_M5Tab5 && lcd.height() > lcd.width()) {
lcd.setRotation(1);
}
#if defined(USING_HAT_HEART)
const auto pins = get_hat_i2c_pins(board);
M5_LOGI("getHatPin: SDA:%u SCL:%u", pins.sda, pins.scl);
if (pins.sda < 0 || pins.scl < 0) {
M5_LOGE("Illegal pin number");
lcd.fillScreen(TFT_RED);
while (true) {
m5::utility::delay(10000);
}
}
// Setup required to use HatHEART
pinMode(pins.scl, OUTPUT);
auto& wire = (board == m5::board_t::board_ArduinoNessoN1) ? Wire1 : Wire;
wire.end();
wire.begin(pins.sda, pins.scl, 400 * 1000U);
if (!Units.add(heart, wire) || !Units.begin()) {
M5_LOGE("Failed to begin");
lcd.fillScreen(TFT_RED);
while (true) {
m5::utility::delay(10000);
}
}
#else
// NessoN1: Arduino Wire (I2C_NUM_0) cannot be used for GROVE port.
// Wire is used by M5Unified In_I2C for internal devices (IOExpander etc.).
// Wire1 exists but is reserved for HatPort — cannot be used for GROVE.
// Reconfiguring Wire to GROVE pins breaks In_I2C, causing ESP_ERR_INVALID_STATE in M5.update().
// Solution: Use SoftwareI2C via M5HAL (bit-banging) for the GROVE port.
// NanoC6: Wire.begin() on GROVE pins conflicts with m5::I2C_Class registered by Ex_I2C.setPort()
// on the same I2C_NUM_0, causing sporadic NACK errors.
// Solution: Use M5.Ex_I2C (m5::I2C_Class) directly instead of Arduino Wire.
bool unit_ready{};
if (board == m5::board_t::board_ArduinoNessoN1) {
// NessoN1: GROVE is on port_b (GPIO 5/4), not port_a (which maps to Wire pins 8/10)
auto pin_num_sda = M5.getPin(m5::pin_name_t::port_b_out);
auto pin_num_scl = M5.getPin(m5::pin_name_t::port_b_in);
M5_LOGI("getPin(M5HAL): SDA:%u SCL:%u", pin_num_sda, pin_num_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);
M5_LOGI("Bus:%d", i2c_bus.has_value());
unit_ready = Units.add(heart, i2c_bus ? i2c_bus.value() : nullptr) && Units.begin();
} else if (board == m5::board_t::board_M5NanoC6) {
// NanoC6: Use M5.Ex_I2C (m5::I2C_Class, not Arduino Wire)
M5_LOGI("Using M5.Ex_I2C");
unit_ready = Units.add(heart, M5.Ex_I2C) && Units.begin();
} else {
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.end();
Wire.begin(pin_num_sda, pin_num_scl, 400000U);
unit_ready = Units.add(heart, Wire) && Units.begin();
}
if (!unit_ready) {
M5_LOGE("Failed to begin");
lcd.fillScreen(TFT_RED);
while (true) {
m5::utility::delay(10000);
}
}
#endif
M5_LOGI("M5UnitUnified has been begun");
M5_LOGI("%s", Units.debugInfo().c_str());
lcd.fillScreen(0);
#if defined(USING_UNIT_HEART)
view = new View(lcd.width(), lcd.height(), true);
#else
view = new View(lcd.width(), lcd.height(), false);
#endif
view->_monitor.setSamplingRate(heart.calculateSamplingRate());
view->push(&lcd, 0, 0);
M5_LOGI("periodic:%d", heart.inPeriodic());
}
void loop()
{
M5.update();
Units.update();
if (heart.updated()) {
if (heart.overflow()) {
M5_LOGW("OVERFLOW:%u", heart.overflow());
}
while (heart.available()) {
view->push_back(heart.ir(), heart.red());
view->update();
heart.discard();
}
view->render();
view->push(&lcd, 0, 0);
}
if (M5.BtnA.wasClicked()) {
view->clear();
}
}
@@ -12,12 +12,15 @@
class Meter {
public:
Meter(const uint32_t left, const uint32_t top, const uint32_t wid, const uint32_t hgt, m5gfx::rgb565_t tcolor)
: _left(left), _top(top), _theme_color(tcolor)
Meter(const uint32_t left, const uint32_t top, const uint32_t wid, const uint32_t hgt, m5gfx::rgb565_t tcolor,
m5gfx::rgb565_t gcolor = TFT_DARKGRAY, m5gfx::rgb565_t bcolor = TFT_BLACK)
: _left(left), _top(top), _theme_color(tcolor), _gauge_color(gcolor), _background_color(bcolor)
{
_plotter = new m5::ui::Plotter(nullptr, wid, wid, hgt);
_plotter->setGaugeTextDatum(textdatum_t::top_right);
_plotter->setLineColor(_theme_color);
_plotter->setGaugeColor(_gauge_color);
_plotter->setBackgroundColor(_background_color);
}
inline void push_back(const float value)
@@ -25,7 +28,7 @@ public:
_plotter->push_back(value);
}
inline void push(LovyanGFX* target, const uint32_t x, const uint32_t y)
inline void push(LovyanGFX* target)
{
_plotter->push(target, _left, _top);
}
@@ -39,5 +42,7 @@ private:
uint32_t _left{}, _top{};
m5::ui::Plotter* _plotter{};
m5gfx::rgb565_t _theme_color{};
m5gfx::rgb565_t _gauge_color{TFT_DARKGRAY};
m5gfx::rgb565_t _background_color{TFT_BLACK};
};
#endif
@@ -83,7 +83,8 @@ void Plotter::push(LovyanGFX* dst, const int32_t x, const int32_t y)
left += _wid - sz;
}
while (it != itend && left < _wid) {
const int32_t right = x + _wid;
while (it != itend && left < right) {
int32_t s{*it}, e{*(++it)};
dst->drawLine(left, y + hh - hh * (s - _min) / range, left + 1, y + hh - hh * (e - _min) / range, _lineClr);
++left;
@@ -0,0 +1,124 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#ifndef M5_UNIT_HEART_EXAMPLE_VIEW_HPP
#define M5_UNIT_HEART_EXAMPLE_VIEW_HPP
#include "meter.hpp"
struct View {
View(const uint32_t wid, const uint32_t hgt, bool unit) : _type{unit}
{
_theme_color = unit ? TFT_BLUE : TFT_YELLOW;
_screen_w = wid;
_screen_h = hgt;
_plot_top = hgt * 2 / 3;
_plot_hgt = hgt / 3;
const uint32_t spr_w = wid;
const uint32_t spr_h = _plot_hgt;
_plot_sprite.setPsram(false);
_plot_sprite.setColorDepth(2);
_plot_sprite.createSprite(spr_w, spr_h);
_plot_sprite.setPaletteColor(palette_black, TFT_BLACK);
_plot_sprite.setPaletteColor(palette_white, TFT_WHITE);
_plot_sprite.setPaletteColor(palette_theme, _theme_color);
_plot_sprite.setPaletteColor(palette_gauge, TFT_DARKGRAY);
_plot_sprite.setTextColor(palette_white, palette_black);
_plot_sprite.setFont(&fonts::FreeSansBold9pt7b);
_text_sprite.setPsram(false);
_text_sprite.setColorDepth(2);
_text_sprite.setPaletteColor(palette_black, TFT_BLACK);
_text_sprite.setPaletteColor(palette_white, TFT_WHITE);
_text_sprite.setPaletteColor(palette_theme, _theme_color);
_text_sprite.setPaletteColor(palette_gauge, TFT_DARKGRAY);
_text_sprite.setTextColor(palette_white, palette_black);
_text_sprite.setFont(&fonts::FreeSansBold9pt7b);
const int32_t line_h = _text_sprite.fontHeight();
const int32_t title_w = _text_sprite.textWidth("Unit");
const int32_t bpm_w = _text_sprite.textWidth("BPM: 000.00");
const int32_t spo2_w = _text_sprite.textWidth("SpO2:100.00");
_text_w = std::max(title_w, std::max(bpm_w, spo2_w));
_text_h = line_h * 3;
_text_sprite.createSprite(_text_w, _text_h);
_text_sprite.setPaletteColor(palette_black, TFT_BLACK);
_text_sprite.setPaletteColor(palette_white, TFT_WHITE);
_text_sprite.setPaletteColor(palette_theme, _theme_color);
_text_sprite.setPaletteColor(palette_gauge, TFT_DARKGRAY);
_text_sprite.setTextColor(palette_white, palette_black);
_text_sprite.setFont(&fonts::FreeSansBold9pt7b);
_beat_size = 16;
_beat_x = _screen_w - _beat_size - 2;
_beat_y = (line_h * 2) + (line_h / 2) - (_beat_size / 2);
_beat_sprite.setPsram(false);
_beat_sprite.setColorDepth(2);
_beat_sprite.createSprite(_beat_size, _beat_size);
_beat_sprite.setPaletteColor(palette_black, TFT_BLACK);
_beat_sprite.setPaletteColor(palette_white, TFT_WHITE);
_beat_sprite.setPaletteColor(palette_theme, _theme_color);
_beat_sprite.setPaletteColor(palette_gauge, TFT_DARKGRAY);
_meter = new Meter(0, 0, spr_w, spr_h, palette_theme, palette_gauge, palette_black);
}
inline void push_back(const float ir, const float red)
{
_monitor.push_back(ir, red);
}
void update()
{
if (_beat) {
--_beat;
}
_meter->push_back(_monitor.latestIR());
_monitor.update();
_beat += _monitor.isBeat() * 8;
}
void render()
{
_plot_sprite.clear(palette_black);
_meter->push(&_plot_sprite);
_text_sprite.clear(palette_black);
_text_sprite.drawString(_type ? "Unit" : "Hat", 0, 0);
_text_sprite.setCursor(0, _text_sprite.fontHeight());
_text_sprite.printf("BPM: %3.2f\nSpO2:%3.2f", _monitor.bpm(), _monitor.SpO2());
_beat_sprite.clear(palette_black);
_beat_sprite.fillCircle(_beat_size / 2, _beat_size / 2, 7, _beat ? palette_theme : palette_white);
}
void clear()
{
_monitor.clear();
_meter->clear();
_plot_sprite.clear(palette_black);
_text_sprite.clear(palette_black);
_beat_sprite.clear(palette_black);
}
void push(LovyanGFX* target, const uint32_t x, const uint32_t y)
{
_text_sprite.pushSprite(target, x, y);
_beat_sprite.pushSprite(target, x + _beat_x, y + _beat_y);
_plot_sprite.pushSprite(target, x, y + _plot_top);
}
enum : uint8_t {
palette_black = 0,
palette_white = 1,
palette_theme = 2,
palette_gauge = 3,
};
LGFX_Sprite _plot_sprite{};
LGFX_Sprite _text_sprite{};
LGFX_Sprite _beat_sprite{};
m5::heart::PulseMonitor _monitor{100, 2};
Meter* _meter{};
uint32_t _left{}, _top{}, _beat{};
uint32_t _screen_w{}, _screen_h{}, _plot_top{}, _plot_hgt{};
int32_t _text_w{}, _text_h{}, _beat_x{}, _beat_y{}, _beat_size{};
bool _type{};
m5gfx::rgb565_t _theme_color{};
};
#endif

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