diff --git a/.github/workflows/arduino-esp-v2-build-check.yml b/.github/workflows/arduino-esp-v2-build-check.yml index cb55c34..66fcf6b 100644 --- a/.github/workflows/arduino-esp-v2-build-check.yml +++ b/.github/workflows/arduino-esp-v2-build-check.yml @@ -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<> "$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 }} diff --git a/.github/workflows/arduino-esp-v3-build-check.yml b/.github/workflows/arduino-esp-v3-build-check.yml index 345d533..fe2a9e8 100644 --- a/.github/workflows/arduino-esp-v3-build-check.yml +++ b/.github/workflows/arduino-esp-v3-build-check.yml @@ -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<> "$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 }} diff --git a/.github/workflows/arduino-m5-build-check.yml b/.github/workflows/arduino-m5-build-check.yml index f779b63..4c8126e 100644 --- a/.github/workflows/arduino-m5-build-check.yml +++ b/.github/workflows/arduino-m5-build-check.yml @@ -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<> "$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 }} diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml index 0c93b67..1fbe231 100644 --- a/.github/workflows/clang-format-check.yml +++ b/.github/workflows/clang-format-check.yml @@ -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'] }} diff --git a/.github/workflows/doxygen-gh-pages.yml b/.github/workflows/doxygen-gh-pages.yml index 83c1c96..59c8172 100644 --- a/.github/workflows/doxygen-gh-pages.yml +++ b/.github/workflows/doxygen-gh-pages.yml @@ -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 diff --git a/.github/workflows/platformio-build-check.yml b/.github/workflows/platformio-build-check.yml index 6332066..8f8714c 100644 --- a/.github/workflows/platformio-build-check.yml +++ b/.github/workflows/platformio-build-check.yml @@ -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 diff --git a/.gitignore b/.gitignore index 259148f..cd234a6 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,24 @@ *.exe *.out *.app + +# Doxygen +docs/html/ + +# PlatformIO +.pio/ + +# IDE +.vscode/ + +# macOS +.DS_Store + +# Temporary +tmp/ + +# Secrets +.env* +*.pem +*.key +*.cert diff --git a/README.md b/README.md index 657f0cf..776548d 100644 --- a/README.md +++ b/README.md @@ -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/) diff --git a/docs/Doxyfile b/docs/Doxyfile index c820920..232301f 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -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 diff --git a/docs/doxy.sh b/docs/doxy.sh index 064edd0..d4492c9 100755 --- a/docs/doxy.sh +++ b/docs/doxy.sh @@ -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 diff --git a/examples/UnitUnified/DualSensor/main/DualSensor.cpp b/examples/UnitUnified/DualSensor/main/DualSensor.cpp index 49c923b..b8b8150 100644 --- a/examples/UnitUnified/DualSensor/main/DualSensor.cpp +++ b/examples/UnitUnified/DualSensor/main/DualSensor.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #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(ir), static_cast(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(ir), static_cast(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(); + } } diff --git a/examples/UnitUnified/DualSensor/src/meter.hpp b/examples/UnitUnified/DualSensor/src/meter.hpp index 1f8b506..bc56477 100644 --- a/examples/UnitUnified/DualSensor/src/meter.hpp +++ b/examples/UnitUnified/DualSensor/src/meter.hpp @@ -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 diff --git a/examples/UnitUnified/DualSensor/src/ui_plotter.cpp b/examples/UnitUnified/DualSensor/src/ui_plotter.cpp index 603d8e2..a3d0a79 100644 --- a/examples/UnitUnified/DualSensor/src/ui_plotter.cpp +++ b/examples/UnitUnified/DualSensor/src/ui_plotter.cpp @@ -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; diff --git a/examples/UnitUnified/DualSensor/src/view.hpp b/examples/UnitUnified/DualSensor/src/view.hpp index 0186581..ba8f418 100644 --- a/examples/UnitUnified/DualSensor/src/view.hpp +++ b/examples/UnitUnified/DualSensor/src/view.hpp @@ -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 diff --git a/examples/UnitUnified/GraphicalMeter/GraphicalMeter.ino b/examples/UnitUnified/GraphicalMeter/GraphicalMeter.ino new file mode 100644 index 0000000..f2fa438 --- /dev/null +++ b/examples/UnitUnified/GraphicalMeter/GraphicalMeter.ino @@ -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" diff --git a/examples/UnitUnified/GraphicalMeter/main/GraphicalMeter.cpp b/examples/UnitUnified/GraphicalMeter/main/GraphicalMeter.cpp new file mode 100644 index 0000000..a346956 --- /dev/null +++ b/examples/UnitUnified/GraphicalMeter/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 +#include +#include +#include +#include +#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(); + } +} diff --git a/examples/UnitUnified/HatHeart/GraphicalMeter/src/meter.hpp b/examples/UnitUnified/GraphicalMeter/src/meter.hpp similarity index 64% rename from examples/UnitUnified/HatHeart/GraphicalMeter/src/meter.hpp rename to examples/UnitUnified/GraphicalMeter/src/meter.hpp index 1f8b506..bc56477 100644 --- a/examples/UnitUnified/HatHeart/GraphicalMeter/src/meter.hpp +++ b/examples/UnitUnified/GraphicalMeter/src/meter.hpp @@ -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 diff --git a/examples/UnitUnified/UnitHeart/GraphicalMeter/src/ui_plotter.cpp b/examples/UnitUnified/GraphicalMeter/src/ui_plotter.cpp similarity index 96% rename from examples/UnitUnified/UnitHeart/GraphicalMeter/src/ui_plotter.cpp rename to examples/UnitUnified/GraphicalMeter/src/ui_plotter.cpp index 603d8e2..a3d0a79 100644 --- a/examples/UnitUnified/UnitHeart/GraphicalMeter/src/ui_plotter.cpp +++ b/examples/UnitUnified/GraphicalMeter/src/ui_plotter.cpp @@ -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; diff --git a/examples/UnitUnified/HatHeart/GraphicalMeter/src/ui_plotter.hpp b/examples/UnitUnified/GraphicalMeter/src/ui_plotter.hpp similarity index 100% rename from examples/UnitUnified/HatHeart/GraphicalMeter/src/ui_plotter.hpp rename to examples/UnitUnified/GraphicalMeter/src/ui_plotter.hpp diff --git a/examples/UnitUnified/GraphicalMeter/src/view.hpp b/examples/UnitUnified/GraphicalMeter/src/view.hpp new file mode 100644 index 0000000..60bc7aa --- /dev/null +++ b/examples/UnitUnified/GraphicalMeter/src/view.hpp @@ -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 diff --git a/examples/UnitUnified/HatHeart/GraphicalMeter/GraphicalMeter.ino b/examples/UnitUnified/HatHeart/GraphicalMeter/GraphicalMeter.ino deleted file mode 100644 index b9fe561..0000000 --- a/examples/UnitUnified/HatHeart/GraphicalMeter/GraphicalMeter.ino +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -/* - Graphical meter example for HatHeart -*/ -#include "main/GraphicalMeter.cpp" diff --git a/examples/UnitUnified/HatHeart/GraphicalMeter/main/GraphicalMeter.cpp b/examples/UnitUnified/HatHeart/GraphicalMeter/main/GraphicalMeter.cpp deleted file mode 100644 index c99a4cd..0000000 --- a/examples/UnitUnified/HatHeart/GraphicalMeter/main/GraphicalMeter.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -/* - Graphical meter example for HatHeart -*/ -#include -#include -#include -#include -#include "../src/view.hpp" - -namespace { -auto& lcd = M5.Display; -m5::unit::UnitUnified Units; -m5::unit::HatHeart hat; -m5::heart::PulseMonitor monitor{}; - -constexpr bool using_wire1{false}; // Using Wire1 if true - -View* view{}; - -} // namespace - -using namespace m5::unit::max30102; - -void setup() -{ - // Configuration if using Wire1 - auto m5cfg = M5.config(); - if (using_wire1) { - m5cfg.pmic_button = false; // Disable BtnPWR - m5cfg.internal_imu = false; // Disable internal IMU - m5cfg.internal_rtc = false; // Disable internal RTC - } - M5.begin(m5cfg); - - auto board = M5.getBoard(); - if (board != m5::board_t::board_M5StickCPlus && board != m5::board_t::board_M5StickCPlus2) { - M5_LOGE("HatHeart for StickCPlus/CPlus2"); - lcd.clear(TFT_RED); - while (true) { - m5::utility::delay(10000); - } - } - - // 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); - - // Using TwoWire - if (using_wire1) { - Wire1.end(); - Wire1.begin(0, 26, 400 * 1000U); - if (!Units.add(hat, Wire1) || !Units.begin()) { - M5_LOGE("Failed to begin"); - lcd.clear(TFT_RED); - while (true) { - m5::utility::delay(10000); - } - } - } else { - Wire.end(); - Wire.begin(0, 26, 400 * 1000U); - if (!Units.add(hat, Wire) || !Units.begin()) { - M5_LOGE("Failed to begin"); - lcd.clear(TFT_RED); - while (true) { - m5::utility::delay(10000); - } - } - } - - M5_LOGI("M5UnitUnified has been begun"); - M5_LOGI("%s", Units.debugInfo().c_str()); - - lcd.clear(0); - - view = new View(lcd.width(), lcd.height(), false); - view->_monitor.setSamplingRate(hat.caluculateSamplingRate()); - view->push(&lcd, 0, 0); -} - -void loop() -{ - M5.update(); - Units.update(); - - if (hat.updated()) { - if (hat.overflow()) { - M5_LOGW("OVERFLOW:%u", hat.overflow()); - } - while (hat.available()) { - view->push_back(hat.ir(), hat.red()); - view->update(); - hat.discard(); - } - view->render(); - view->push(&lcd, 0, 0); - } - - if (M5.BtnA.wasClicked()) { - view->clear(); - } -} diff --git a/examples/UnitUnified/HatHeart/GraphicalMeter/src/ui_plotter.cpp b/examples/UnitUnified/HatHeart/GraphicalMeter/src/ui_plotter.cpp deleted file mode 100644 index 603d8e2..0000000 --- a/examples/UnitUnified/HatHeart/GraphicalMeter/src/ui_plotter.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -/*! - @file ui_plotter.cpp - @brief Plotter - */ -#include "ui_plotter.hpp" -#include -#include - -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::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) { - auto it = std::minmax_element(_data.cbegin(), _data.cend()); - _min = *(it.first); - _max = *(it.second); - if (_min == _max) { - ++_max; - } - } -} - -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 = _data.cbegin(); - auto itend = --_data.cend(); - auto sz = _data.size(); - const float range{_max > _min ? (float)_max - _min : 1.0f}; - 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 && left < _wid) { - 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; - } - } - dst->clearClipRect(); -} - -} // namespace ui -} // namespace m5 diff --git a/examples/UnitUnified/HatHeart/GraphicalMeter/src/view.hpp b/examples/UnitUnified/HatHeart/GraphicalMeter/src/view.hpp deleted file mode 100644 index 0186581..0000000 --- a/examples/UnitUnified/HatHeart/GraphicalMeter/src/view.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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} - { - _sprite.createSprite(wid, hgt); - _sprite.setFont(&fonts::FreeSansBold9pt7b); - _meter = new Meter(0, hgt * 2 / 3, wid, hgt / 3, unit ? TFT_BLUE : TFT_YELLOW); - } - - 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() - { - _sprite.clear(); - _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); - - _meter->push(&_sprite, 0, _sprite.height() >> 1); - } - - void clear() - { - _monitor.clear(); - _meter->clear(); - } - - void push(LovyanGFX* target, const uint32_t x, const uint32_t y) - { - _sprite.pushSprite(target, x, y); - } - - LGFX_Sprite _sprite{}; - m5::heart::PulseMonitor _monitor{100, 2}; - Meter* _meter{}; - uint32_t _left{}, _top{}, _beat{}; - bool _type{}; -}; -#endif diff --git a/examples/UnitUnified/HatHeart/PlotToSerial/PlotToSerial.ino b/examples/UnitUnified/HatHeart/PlotToSerial/PlotToSerial.ino deleted file mode 100644 index ee0887f..0000000 --- a/examples/UnitUnified/HatHeart/PlotToSerial/PlotToSerial.ino +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -/* - Example using M5UnitUnified for HatHeart -*/ -#include "main/PlotToSerial.cpp" diff --git a/examples/UnitUnified/HatHeart/PlotToSerial/main/PlotToSerial.cpp b/examples/UnitUnified/HatHeart/PlotToSerial/main/PlotToSerial.cpp deleted file mode 100644 index b7ccc55..0000000 --- a/examples/UnitUnified/HatHeart/PlotToSerial/main/PlotToSerial.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -/* - Example using M5UnitUnified for HatHeart -*/ -#include -#include -#include -#include - -namespace { -auto& lcd = M5.Display; -m5::unit::UnitUnified Units; -m5::unit::HatHeart hat; -m5::heart::PulseMonitor monitor{}; - -constexpr bool using_multi_led_mode{false}; // Using multiLED mode if true -constexpr bool using_wire1{false}; // Using Wire1 if true - -} // namespace - -using namespace m5::unit::max30102; - -void setup() -{ - // Configuration if using Wire1 - auto m5cfg = M5.config(); - if (using_wire1) { - m5cfg.pmic_button = false; // Disable BtnPWR - m5cfg.internal_imu = false; // Disable internal IMU - m5cfg.internal_rtc = false; // Disable internal RTC - } - M5.begin(m5cfg); - - auto board = M5.getBoard(); - if (board != m5::board_t::board_M5StickCPlus && board != m5::board_t::board_M5StickCPlus2) { - M5_LOGE("HatHeart for StickCPlus/CPlus2"); - lcd.clear(TFT_RED); - while (true) { - m5::utility::delay(10000); - } - } - - // Setup required to use HatHEART - pinMode(25, INPUT_PULLUP); - pinMode(26, OUTPUT); - - // Using MultiLED mode - // In MultiLED mode, you need to set and start them yourself - if (using_multi_led_mode) { - auto cfg = hat.config(); - cfg.start_periodic = false; // Ignore auto start - hat.config(cfg); - } - - // Using TwoWire - if (using_wire1) { - Wire1.end(); - Wire1.begin(0, 26, 400 * 1000U); - if (!Units.add(hat, Wire1) || !Units.begin()) { - M5_LOGE("Failed to begin"); - lcd.clear(TFT_RED); - while (true) { - m5::utility::delay(10000); - } - } - } else { - Wire.end(); - Wire.begin(0, 26, 400 * 1000U); - if (!Units.add(hat, Wire) || !Units.begin()) { - M5_LOGE("Failed to begin"); - lcd.clear(TFT_RED); - while (true) { - m5::utility::delay(10000); - } - } - } - - M5_LOGI("M5UnitUnified has been begun"); - M5_LOGI("%s", Units.debugInfo().c_str()); - - // In MultiLED mode, you need to set and start them yourself - if (using_multi_led_mode) { - hat.writeMode(Mode::MultiLED); - hat.writeSpO2Configuration(ADC::Range4096nA, Sampling::Rate400, LEDPulse::Width411); - hat.writeFIFOConfiguration(FIFOSampling::Average4, true, 15); - // hat.writeMultiLEDModeControl(Slot::Red, Slot::IR); // (A) - hat.writeMultiLEDModeControl(Slot::IR, Slot::Red); // (B) - hat.writeLEDCurrent(0, 0x1F); // Red if (A), IR if (B) - hat.writeLEDCurrent(1, 0x1F); // IR if (A), Red if (B) - hat.startPeriodicMeasurement(); - } - lcd.clear(TFT_DARKGREEN); - - monitor.setSamplingRate(hat.caluculateSamplingRate()); -} - -void loop() -{ - M5.update(); - Units.update(); - - if (hat.updated()) { - // WARNING - // If overflow is occurring, the sampling rate should be reduced because the processing is not up to par - if (hat.overflow()) { - M5_LOGW("OVERFLOW:%u", hat.overflow()); - } - - bool beat{}; - // MAX30102 is equipped with a FIFO, so multiple data may be stored - while (hat.available()) { - M5.Log.printf(">IR:%u\n>RED:%u\n", hat.ir(), hat.red()); - monitor.push_back(hat.ir(), hat.red()); // Push back the oldest data - M5.Log.printf(">MIR:%f\n", monitor.latestIR()); - monitor.update(); - beat |= monitor.isBeat(); - hat.discard(); // Discard the oldest data - } - M5.Log.printf(">BPM:%f\n>SpO2:%f\n>BEAT:%u\n", monitor.bpm(), monitor.SpO2(), beat); - } - - // Measure tempeature - if (M5.BtnA.wasClicked()) { - TemperatureData td{}; - if (hat.measureTemperatureSingleshot(td)) { - M5.Log.printf(">Temp:%f\n", td.celsius()); - } - } -} diff --git a/examples/UnitUnified/PlotToSerial/PlotToSerial.ino b/examples/UnitUnified/PlotToSerial/PlotToSerial.ino new file mode 100644 index 0000000..71c8dd8 --- /dev/null +++ b/examples/UnitUnified/PlotToSerial/PlotToSerial.ino @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +/* + Example using M5UnitUnified 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/PlotToSerial.cpp" diff --git a/examples/UnitUnified/PlotToSerial/main/PlotToSerial.cpp b/examples/UnitUnified/PlotToSerial/main/PlotToSerial.cpp new file mode 100644 index 0000000..f7e027b --- /dev/null +++ b/examples/UnitUnified/PlotToSerial/main/PlotToSerial.cpp @@ -0,0 +1,219 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ +/* + Example using M5UnitUnified for UnitHeart / HatHeart +*/ +#include +#include +#include +#include +#include // For NessoN1 + +// ************************************************************* +// 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 unit; +#elif defined(USING_HAT_HEART) +m5::unit::HatHeart unit; +#endif + +m5::heart::PulseMonitor monitor; + +#if defined(USING_HAT_HEART) +constexpr bool using_multi_led_mode{false}; // Using multiLED mode if true + +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); + + // The screen shall be in landscape mode + if (lcd.height() > lcd.width()) { + lcd.setRotation(1); + } + + auto board = M5.getBoard(); + +#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); + + // Using MultiLED mode + // In MultiLED mode, you need to set and start them yourself + if (using_multi_led_mode) { + auto cfg = unit.config(); + cfg.start_periodic = false; // Ignore auto start + unit.config(cfg); + } + auto& wire = (board == m5::board_t::board_ArduinoNessoN1) ? Wire1 : Wire; + wire.end(); + wire.begin(pins.sda, pins.scl, 400 * 1000U); + if (!Units.add(unit, 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(unit, 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(unit, 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, 400 * 1000U); + unit_ready = Units.add(unit, 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()); + +#if defined(USING_HAT_HEART) + // In MultiLED mode, you need to set and start them yourself + if (using_multi_led_mode) { + M5_LOGI("MultiLED mode"); + unit.writeMode(Mode::MultiLED); + unit.writeSpO2Configuration(ADC::Range4096nA, Sampling::Rate400, LEDPulse::Width411); + unit.writeFIFOConfiguration(FIFOSampling::Average4, true, 15); + // unit.writeMultiLEDModeControl(Slot::Red, Slot::IR); // (A) + unit.writeMultiLEDModeControl(Slot::IR, Slot::Red); // (B) + unit.writeLEDCurrent(0, 0x1F); // Red if (A), IR if (B) + unit.writeLEDCurrent(1, 0x1F); // IR if (A), Red if (B) + unit.startPeriodicMeasurement(); + } +#endif + + monitor.setSamplingRate(unit.calculateSamplingRate()); + lcd.fillScreen(TFT_DARKGREEN); +} + +void loop() +{ + M5.update(); + Units.update(); + + if (unit.updated()) { + // WARNING + // If overflow is occurring, the sampling rate should be reduced because the processing is not up to par + if (unit.overflow()) { + M5_LOGW("OVERFLOW:%u", unit.overflow()); + } + + bool beat{}; + // MAX30100/02 is equipped with a FIFO, so multiple data may be stored + while (unit.available()) { + M5.Log.printf(">IR:%u\n>RED:%u\n", unit.ir(), unit.red()); + monitor.push_back(unit.ir(), unit.red()); // Push back the oldest data + M5.Log.printf(">MIR:%f\n", monitor.latestIR()); + monitor.update(); + beat |= monitor.isBeat(); + unit.discard(); // Discard the oldest data + } + M5.Log.printf(">BPM:%f\n>SpO2:%f\n>BEAT:%u\n", monitor.bpm(), monitor.SpO2(), beat); + } + + // Measure temperature + if (M5.BtnA.wasClicked()) { + TemperatureData td{}; + if (unit.measureTemperatureSingleshot(td)) { + M5.Log.printf(">Temp:%f\n", td.celsius()); + } + } +} diff --git a/examples/UnitUnified/UnitHeart/GraphicalMeter/GraphicalMeter.ino b/examples/UnitUnified/UnitHeart/GraphicalMeter/GraphicalMeter.ino deleted file mode 100644 index 1784b04..0000000 --- a/examples/UnitUnified/UnitHeart/GraphicalMeter/GraphicalMeter.ino +++ /dev/null @@ -1,10 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -/* - Graphical meter example for UnitHeart - The core must be equipped with LCD -*/ -#include "main/GraphicalMeter.cpp" diff --git a/examples/UnitUnified/UnitHeart/GraphicalMeter/main/GraphicalMeter.cpp b/examples/UnitUnified/UnitHeart/GraphicalMeter/main/GraphicalMeter.cpp deleted file mode 100644 index b3e9706..0000000 --- a/examples/UnitUnified/UnitHeart/GraphicalMeter/main/GraphicalMeter.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -/* - Graphical meter example for UnitHeart - The core must be equipped with LCD -*/ -#include -#include -#include -#include -#include "../src/view.hpp" - -namespace { -auto& lcd = M5.Display; -m5::unit::UnitUnified Units; -m5::unit::UnitHeart unit; -View* view{}; - -} // namespace - -using namespace m5::unit::max30102; - -void setup() -{ - M5.begin(); - - // The screen shall be in landscape mode - if (lcd.height() > lcd.width()) { - lcd.setRotation(1); - } - - 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); - if (!Units.add(unit, Wire) || !Units.begin()) { - M5_LOGE("Failed to begin"); - lcd.clear(TFT_RED); - while (true) { - m5::utility::delay(10000); - } - } - - M5_LOGI("M5UnitUnified has been begun"); - M5_LOGI("%s", Units.debugInfo().c_str()); - - lcd.clear(0); - - view = new View(lcd.width(), lcd.height()); - view->_monitor.setSamplingRate(unit.caluculateSamplingRate()); - view->push(&lcd, 0, 0); -} - -void loop() -{ - M5.update(); - auto touch = M5.Touch.getDetail(); - - Units.update(); - - if (unit.updated()) { - if (unit.overflow()) { - M5_LOGW("OVERFLOW:%u", unit.overflow()); - } - while (unit.available()) { - view->push_back(unit.ir(), unit.red()); - view->update(); - unit.discard(); - } - view->render(); - view->push(&lcd, 0, 0); - } - - if (M5.BtnA.wasClicked() || touch.wasClicked()) { - view->clear(); - } -} diff --git a/examples/UnitUnified/UnitHeart/GraphicalMeter/src/meter.hpp b/examples/UnitUnified/UnitHeart/GraphicalMeter/src/meter.hpp deleted file mode 100644 index 1f8b506..0000000 --- a/examples/UnitUnified/UnitHeart/GraphicalMeter/src/meter.hpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -#ifndef M5_UNIT_HEART_EXAMPLE_METER_HPP -#define M5_UNIT_HEART_EXAMPLE_METER_HPP - -#include -#include "ui_plotter.hpp" -#include - -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) - { - _plotter = new m5::ui::Plotter(nullptr, wid, wid, hgt); - _plotter->setGaugeTextDatum(textdatum_t::top_right); - _plotter->setLineColor(_theme_color); - } - - inline void push_back(const float value) - { - _plotter->push_back(value); - } - - inline void push(LovyanGFX* target, const uint32_t x, const uint32_t y) - { - _plotter->push(target, _left, _top); - } - - inline void clear() - { - _plotter->clear(); - } - -private: - uint32_t _left{}, _top{}; - m5::ui::Plotter* _plotter{}; - m5gfx::rgb565_t _theme_color{}; -}; -#endif diff --git a/examples/UnitUnified/UnitHeart/GraphicalMeter/src/ui_plotter.hpp b/examples/UnitUnified/UnitHeart/GraphicalMeter/src/ui_plotter.hpp deleted file mode 100644 index f504cf9..0000000 --- a/examples/UnitUnified/UnitHeart/GraphicalMeter/src/ui_plotter.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ - -#ifndef M5_UNIT_HEART_EXAMPLE_UI_PLOTTER_HPP -#define M5_UNIT_HEART_EXAMPLE_UI_PLOTTER_HPP -#include -#include - -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); - ~Plotter() - { - } - - 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; - } - inline uint32_t size() const - { - return _data.size(); - } - inline int32_t latest() const - { - return !_data.empty() ? *(--_data.cend()) : 0; - } - - template - void setLineColor(const T& clr) - { - _lineClr = clr; - } - template - void setGaugeColor(const T& clr) - { - _gaugeClr = clr; - } - template - void setBackgroundColor(const T& clr) - { - _bgClr = clr; - } - - inline void setUnitString(const char* s) - { - _ustr = s; - } - inline void setGaugeTextDatum(const textdatum_t datum) - { - _tdatum = static_cast(m5::stl::to_underlying(datum) & 0x03); // only horizon - } - - void push_back(const float val); - void push_back(const int32_t val); - inline void clear() - { - _data.clear(); - } - - 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); - -protected: - m5gfx::rgb565_t lineColor() const - { - return _lineClr; - } - 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 _data; - - m5gfx::rgb565_t _lineClr{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 diff --git a/examples/UnitUnified/UnitHeart/GraphicalMeter/src/view.hpp b/examples/UnitUnified/UnitHeart/GraphicalMeter/src/view.hpp deleted file mode 100644 index 6d4201f..0000000 --- a/examples/UnitUnified/UnitHeart/GraphicalMeter/src/view.hpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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) - { - constexpr RGBColor palettes[4] = {RGBColor(0, 0, 0), RGBColor(0, 0, 255), RGBColor(255, 0, 0), - RGBColor(255, 255, 255)}; - _sprite.setColorDepth(2); // 4 colors - _sprite.createSprite(wid, hgt); - _sprite.setFont(&fonts::FreeSansBold9pt7b); - auto pal = _sprite.getPalette(); - for (auto&& p : palettes) { - *pal++ = p; - } - _meter = new Meter(0, hgt * 2 / 3, wid, hgt / 3, 1); - } - - 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() - { - _sprite.clear(); - _sprite.drawString("Unit", 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 ? 2 : 3); - - _meter->push(&_sprite, 0, _sprite.height() >> 1); - } - - void clear() - { - _monitor.clear(); - _meter->clear(); - } - - void push(LovyanGFX* target, const uint32_t x, const uint32_t y) - { - _sprite.pushSprite(target, x, y); - } - - LGFX_Sprite _sprite{}; - m5::heart::PulseMonitor _monitor{100, 2}; - Meter* _meter{}; - uint32_t _left{}, _top{}, _beat{}; - bool _type{}; -}; -#endif diff --git a/examples/UnitUnified/UnitHeart/PlotToSerial/PlotToSerial.ino b/examples/UnitUnified/UnitHeart/PlotToSerial/PlotToSerial.ino deleted file mode 100644 index e97160d..0000000 --- a/examples/UnitUnified/UnitHeart/PlotToSerial/PlotToSerial.ino +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -/* - Example using M5UnitUnified for UnitHeart -*/ -#include "main/PlotToSerial.cpp" diff --git a/examples/UnitUnified/UnitHeart/PlotToSerial/main/PlotToSerial.cpp b/examples/UnitUnified/UnitHeart/PlotToSerial/main/PlotToSerial.cpp deleted file mode 100644 index 615713a..0000000 --- a/examples/UnitUnified/UnitHeart/PlotToSerial/main/PlotToSerial.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD - * - * SPDX-License-Identifier: MIT - */ -/* - Example using M5UnitUnified for UnitHeart -*/ -// #define USING_M5HAL // When using M5HAL - -#include -#include -#include -#if !defined(USING_M5HAL) -#include -#endif - -namespace { -auto& lcd = M5.Display; -m5::unit::UnitUnified Units; -m5::unit::UnitHeart unit; -m5::heart::PulseMonitor monitor; - -} // namespace - -using namespace m5::unit::max30100; - -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); - -#if defined(USING_M5HAL) -#pragma message "Using M5HAL" - // Using M5HAL - 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()); - if (!Units.add(unit, i2c_bus ? i2c_bus.value() : nullptr) || !Units.begin()) { - M5_LOGE("Failed to begin"); - lcd.clear(TFT_RED); - while (true) { - m5::utility::delay(10000); - } - } -#else -#pragma message "Using Wire" - // Using TwoWire - Wire.begin(pin_num_sda, pin_num_scl, 400000U); - if (!Units.add(unit, Wire) || !Units.begin()) { - M5_LOGE("Failed to begin"); - lcd.clear(TFT_RED); - while (true) { - m5::utility::delay(10000); - } - } -#endif - - M5_LOGI("M5UnitUnified has been begun"); - M5_LOGI("%s", Units.debugInfo().c_str()); - - monitor.setSamplingRate(unit.caluculateSamplingRate()); - - lcd.clear(TFT_DARKGREEN); -} - -void loop() -{ - M5.update(); - Units.update(); - if (unit.updated()) { - // WARNING - // If overflow is occurring, the sampling rate should be reduced because the processing is not up to par - if (unit.overflow()) { - M5_LOGW("OVERFLOW:%u", unit.overflow()); - } - - bool beat{}; - // MAX30100 is equipped with a FIFO, so multiple data may be stored - while (unit.available()) { - M5.Log.printf(">IR:%u\n>RED:%u\n", unit.ir(), unit.red()); - monitor.push_back(unit.ir(), unit.red()); // Push back the oldest data - M5.Log.printf(">MIR:%f\n", monitor.latestIR()); - monitor.update(); - beat |= monitor.isBeat(); - unit.discard(); // Discard the oldest data - } - M5.Log.printf(">BPM:%f\n>SpO2:%f\n>BEAT:%u\n", monitor.bpm(), monitor.SpO2(), beat); - } - - // Measure tempeature - if (M5.BtnA.wasClicked()) { - TemperatureData td{}; - if (unit.measureTemperatureSingleshot(td)) { - M5.Log.printf(">Temp:%f\n", td.celsius()); - } - } -} diff --git a/library.json b/library.json index 06d796b..afd05d4 100644 --- a/library.json +++ b/library.json @@ -1,19 +1,19 @@ { "name": "M5Unit-HEART", "description": "Library for M5Stack UNIT HEART Using M5UnitUnified", - "keywords": "M5Stack UNIT HEART, M5UnitUnified", + "keywords": "m5stack, m5unitunified, heart, max30100, max30102, pulse-oximeter, heart-rate, spo2, i2c", "authors": { "name": "M5Stack", - "url": "http://www.m5stack.com" + "url": "https://www.m5stack.com" }, "repository": { "type": "git", "url": "https://github.com/m5stack/M5Unit-HEART.git" }, "dependencies": { - "m5stack/M5UnitUnified": ">=0.1.0" + "m5stack/M5UnitUnified": ">=0.4.4" }, - "version": "0.2.0", + "version": "0.2.1", "frameworks": [ "arduino" ], diff --git a/library.properties b/library.properties index c86bc25..6b1f3e8 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=M5Unit-HEART -version=0.2.0 +version=0.2.1 author=M5Stack maintainer=M5Stack sentence=Library for M5Stack UNIT HEART using M5UnitUnified -paragraph= +paragraph=Supports MAX30100 (UnitHeart, SKU:U029) and MAX30102 (HatHeart, SKU:U118) for pulse oximetry and heart rate monitoring. category=Device Control url=https://github.com/m5stack/M5Unit-HEART architectures=esp32 diff --git a/platformio.ini b/platformio.ini index 6d85f7d..d09b5c4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,21 +10,18 @@ lib_ldf_mode = deep test_framework = googletest test_build_src = true lib_deps=m5stack/M5Unified - m5stack/M5UnitUnified@>=0.1.0 + m5stack/M5UnitUnified@>=0.4.4 -; -------------------------------- [m5base] monitor_speed = 115200 monitor_filters = esp32_exception_decoder, time upload_speed = 1500000 test_speed = 115200 -test_ignore= native/* +test_ignore= native/* [Core] extends = m5base -board = m5stack-grey -;m5stack-core-esp32-16M ;;6.8.0 or later -;m5stack-core-esp32 +board = m5stack-core-esp32-16M lib_deps = ${env.lib_deps} [Core2] @@ -54,18 +51,21 @@ board = m5stack-stamps3 lib_deps = ${env.lib_deps} m5stack/M5Dial -[AtomMatrix] +[Atom] +;include AtomMatrix,AtomLite,AtomU extends = m5base board = m5stack-atom lib_deps = ${env.lib_deps} [AtomS3] +;include AtomEchoS3R,AtomS3Lite,AtomS3U extends = m5base board = m5stack-atoms3 lib_deps = ${env.lib_deps} ; Using ./boards/m5stack-atoms3r.json [AtomS3R] +;include AtomEchoS3R extends = m5base board = m5stack-atoms3r lib_deps = ${env.lib_deps} @@ -74,12 +74,8 @@ lib_deps = ${env.lib_deps} [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} +board_build.partitions = default.csv [StickCPlus] extends = m5base @@ -92,6 +88,19 @@ extends = m5base board = m5stick-cplus2 lib_deps = ${env.lib_deps} +[StickS3] +extends = m5base +board = esp32-s3-devkitc-1 +board_build.arduino.partitions = default_8MB.csv +board_build.arduino.memory_type = qio_opi +build_flags = + -DESP32S3 + -DBOARD_HAS_PSRAM + -mfix-esp32-psram-cache-issue + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 +lib_deps = ${env.lib_deps} + [Paper] extends = m5base board = m5stack-fire @@ -102,6 +111,31 @@ extends = m5base board = m5stack-coreink lib_deps = ${env.lib_deps} +[Cardputer] +extends = m5base +board = esp32-s3-devkitc-1 +build_flags = + -DESP32S3 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 +lib_deps = ${env.lib_deps} + +[Tab5] +extends = m5base +board = esp32-p4-evboard +board_build.mcu = esp32p4 +board_build.flash_mode = qio +build_flags = + -DBOARD_HAS_PSRAM + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_USB_MODE=1 +lib_deps = ${env.lib_deps} + +[NessoN1] +extends = m5base +board = arduino_nesso_n1 +lib_deps = ${env.lib_deps} + [sdl] build_flags = -O3 -xc++ -std=c++14 -lSDL2 -arch arm64 ; for arm mac @@ -117,33 +151,20 @@ lib_deps = ${env.lib_deps} ; -------------------------------- ;Choose framework [arduino_latest] -platform = espressif32 @ 6.8.1 +platform = espressif32 @ 6.12.0 framework = arduino -[arduino_6_6_0] -platform = espressif32 @ 6.6.0 +[nanoc6_latest] +platform = espressif32 @ 6.12.0 +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 framework = arduino -[arduino_6_0_1] -platform = espressif32 @ 6.0.1 +[pioarduino_latest] +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.36/platform-espressif32.zip framework = arduino -[arduino_5_4_0] -platform = espressif32 @ 5.4.0 -framework = arduino - -[arduino_4_4_0] -platform = espressif32 @ 4.4.0 -framework = arduino - -;[arduino_3_5_0] -;platform = espressif32 @ 3.5.0 -;framework = arduino - -[esp-idf] -platform = espressif32 @ 6.8.1 -framework = espidf - ; -------------------------------- ;Choose build options [option_release] @@ -178,7 +199,7 @@ build_flags = ${env.build_flags} -DM5_LOG_LEVEL=0 -Wl,-Map,output.map -; Require at leaset C++14 after 1.13.0 +; Require at least C++14 after 1.13.0 [test_fw] lib_deps = google/googletest@1.12.1 @@ -222,9 +243,9 @@ lib_deps = ${Dial.lib_deps} ${test_fw.lib_deps} test_filter= embedded/test_max30100 -[env:test_UnitHeart_AtomMatrix] -extends=AtomMatrix, option_release, arduino_latest -lib_deps = ${AtomMatrix.lib_deps} +[env:test_UnitHeart_Atom] +extends=Atom, option_release, arduino_latest +lib_deps = ${Atom.lib_deps} ${test_fw.lib_deps} test_filter= embedded/test_max30100 @@ -241,7 +262,7 @@ lib_deps = ${AtomS3R.lib_deps} test_filter= embedded/test_max30100 [env:test_UnitHeart_NanoC6] -extends=NanoC6, option_release, arduino_latest +extends=NanoC6, option_release, nanoc6_latest lib_deps = ${NanoC6.lib_deps} ${test_fw.lib_deps} test_filter= embedded/test_max30100 @@ -258,6 +279,14 @@ lib_deps = ${StickCPlus2.lib_deps} ${test_fw.lib_deps} test_filter= embedded/test_max30100 +[env:test_UnitHeart_StickS3] +extends=StickS3, option_release, arduino_latest +build_flags = ${StickS3.build_flags} + ${option_release.build_flags} +lib_deps = ${StickS3.lib_deps} + ${test_fw.lib_deps} +test_filter= embedded/test_max30100 + [env:test_UnitHeart_Paper] extends=Paper, option_release, arduino_latest lib_deps = ${Paper.lib_deps} @@ -270,6 +299,28 @@ lib_deps = ${CoreInk.lib_deps} ${test_fw.lib_deps} test_filter= embedded/test_max30100 +[env:test_UnitHeart_Cardputer] +extends=Cardputer, option_release, arduino_latest +build_flags = ${Cardputer.build_flags} + ${option_release.build_flags} +lib_deps = ${Cardputer.lib_deps} + ${test_fw.lib_deps} +test_filter= embedded/test_max30100 + +[env:test_UnitHeart_Tab5] +extends=Tab5, option_release, pioarduino_latest +build_flags = ${Tab5.build_flags} + ${option_release.build_flags} +lib_deps = ${Tab5.lib_deps} + ${test_fw.lib_deps} +test_filter= embedded/test_max30100 + +[env:test_UnitHeart_NessoN1] +extends=NessoN1, option_release, pioarduino_latest +lib_deps = ${NessoN1.lib_deps} + ${test_fw.lib_deps} +test_filter= embedded/test_max30100 + ; HatHeart [env:test_HatHeart_StickCPlus] extends=StickCPlus, option_release, arduino_latest @@ -283,100 +334,155 @@ lib_deps = ${StickCPlus2.lib_deps} ${test_fw.lib_deps} test_filter= embedded/test_max30102 +[env:test_HatHeart_StickS3] +extends=StickS3, option_release, arduino_latest +build_flags = ${StickS3.build_flags} + ${option_release.build_flags} +lib_deps = ${StickS3.lib_deps} + ${test_fw.lib_deps} +test_filter= embedded/test_max30102 + +[env:test_HatHeart_CoreInk] +extends=CoreInk, option_release, arduino_latest +lib_deps = ${CoreInk.lib_deps} + ${test_fw.lib_deps} +test_filter= embedded/test_max30102 + +[env:test_HatHeart_NessoN1] +extends=NessoN1, option_release, pioarduino_latest +lib_deps = ${NessoN1.lib_deps} + ${test_fw.lib_deps} +test_filter= embedded/test_max30102 + + + ;-------------------------------- ;Examples by M5UnitUnified ;-------------------------------- ;-------------------------------- ;UnitHeart ;-------------------------------- -;PlotToSerail +;PlotToSerial [env:UnitHeart_PlotToSerial_Core_Arduino_latest] extends=Core, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> - -[env:UnitHeart_PlotToSerial_Core_Arduino_5_4_0] -extends=Core, option_release, arduino_5_4_0 -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> - -[env:UnitHeart_PlotToSerial_Core_Arduino_4_4_0] -extends=Core, option_release, arduino_4_4_0 -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_Core2_Arduino_latest] extends=Core2, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> - -[env:UnitHeart_PlotToSerial_Core2_Arduino_5_4_0] -extends=Core2, option_release, arduino_5_4_0 -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> - -[env:UnitHeart_PlotToSerial_Core2_Arduino_4_4_0] -extends=Core2, option_release, arduino_4_4_0 -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_CoreS3_Arduino_latest] extends=CoreS3, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_StampS3_Arduino_latest] extends=StampS3, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART -[env:UnitHeart_PlotToSerial_AtomMatrix_Arduino_latest] -extends=AtomMatrix, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +[env:UnitHeart_PlotToSerial_Atom_Arduino_latest] +extends=Atom, option_release, arduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_AtomS3_Arduino_latest] extends=AtomS3, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_AtomS3R_Arduino_latest] extends=AtomS3R, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_Dial_Arduino_latest] extends=Dial, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_NanoC6_Arduino_latest] -extends=NanoC6, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +extends=NanoC6, option_release, nanoc6_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_StickCPlus_Arduino_latest] extends=StickCPlus, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_StickCPlus2_Arduino_latest] extends=StickCPlus2, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART + +[env:UnitHeart_PlotToSerial_StickS3_Arduino_latest] +extends=StickS3, option_release, arduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${StickS3.build_flags} + ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_Paper_Arduino_latest] extends=Paper, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_CoreInk_Arduino_latest] extends=CoreInk, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART [env:UnitHeart_PlotToSerial_Fire_Arduino_latest] extends=Fire, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART -[env:UnitHeart_PlotToSerial_Fire_Arduino_5_4_0] -extends=Fire, option_release, arduino_5_4_0 -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +[env:UnitHeart_PlotToSerial_Cardputer_Arduino_latest] +extends=Cardputer, option_release, arduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${Cardputer.build_flags} + ${option_release.build_flags} + -D USING_UNIT_HEART -[env:UnitHeart_PlotToSerial_Fire_Arduino_4_4_0] -extends=Fire, option_release, arduino_4_4_0 -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/PlotToSerial> +[env:UnitHeart_PlotToSerial_Tab5_Arduino_latest] +extends=Tab5, option_release, pioarduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${Tab5.build_flags} + ${option_release.build_flags} + -D USING_UNIT_HEART + +[env:UnitHeart_PlotToSerial_NessoN1_Arduino_latest] +extends=NessoN1, option_release, pioarduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART ;GraphicalMeter -; For thisg samples, please refer to PlotToSerial to create env for your own environment -; こののサンプルは、PlotToSerial を参考にして、自分の環境にあった env を作成してください -; +; For this sample, please refer to PlotToSerial to create env for your own environment +; このサンプルは、PlotToSerial を参考にして、自分の環境にあった env を作成してください [env:UnitHeart_GraphicalMeter_Core_Arduino_latest] extends=Core, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/GraphicalMeter> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/GraphicalMeter> +build_flags = ${option_release.build_flags} + -D USING_UNIT_HEART + ;-------------------------------- ;HatHeart @@ -384,28 +490,68 @@ build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/UnitHeart/Gr ;PlotToSerial [env:HatHeart_PlotToSerial_StickCPlus_Arduino_latest] extends=StickCPlus, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/HatHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_HAT_HEART [env:HatHeart_PlotToSerial_StickCPlus2_Arduino_latest] extends=StickCPlus2, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/HatHeart/PlotToSerial> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_HAT_HEART + +[env:HatHeart_PlotToSerial_StickS3_Arduino_latest] +extends=StickS3, option_release, arduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${StickS3.build_flags} + ${option_release.build_flags} + -D USING_HAT_HEART + +[env:HatHeart_PlotToSerial_CoreInk_Arduino_latest] +extends=CoreInk, option_release, arduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_HAT_HEART + +[env:HatHeart_PlotToSerial_NessoN1_Arduino_latest] +extends=NessoN1, option_release, pioarduino_latest +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/PlotToSerial> +build_flags = ${option_release.build_flags} + -D USING_HAT_HEART ;GraphicalMeter -[env:HatHeart_GraphicalMeter_StickCPlus_Arduino_latest] -extends=StickCPlus, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/HatHeart/GraphicalMeter> - +; For this sample, please refer to PlotToSerial to create env for your own environment +; このサンプルは、PlotToSerial を参考にして、自分の環境にあった env を作成してください [env:HatHeart_GraphicalMeter_StickCPlus2_Arduino_latest] extends=StickCPlus2, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/HatHeart/GraphicalMeter> +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/GraphicalMeter> +build_flags = ${option_release.build_flags} + -D USING_HAT_HEART -;DualSensor +; [env:HatHeart_GraphicalMeter_StickS3_Arduino_latest] +; extends=StickS3, option_release, arduino_latest +; build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/GraphicalMeter> +; build_flags = ${option_release.build_flags} +; -D USING_HAT_HEART + +;-------------------------------- +;DualSensor (Using Unit and Hat) +; For this sample, please refer to PlotToSerial to create env for your own environment +; このサンプルは、PlotToSerial を参考にして、自分の環境にあった env を作成してください ;Example of using M5UnitUnified to connect both UnitHeart and HatHeart -[env:DualSensor_StickCPlus_Arduino_latest] -extends=StickCPlus, option_release, arduino_latest -build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/DualSensor> - [env:DualSensor_StickCPlus2_Arduino_latest] extends=StickCPlus2, option_release, arduino_latest build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/DualSensor> +; [env:DualSensor_StickS3_Arduino_latest] +; extends=StickS3, option_release, arduino_latest +; build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/DualSensor> + +; [env:DualSensor_NessoN1_Arduino_latest] +; extends=NessoN1, option_release, pioarduino_latest +; build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitUnified/DualSensor> + + + + + diff --git a/src/M5UnitUnifiedHEART.h b/src/M5UnitUnifiedHEART.h index 070a00e..eb33c1c 100644 --- a/src/M5UnitUnifiedHEART.h +++ b/src/M5UnitUnifiedHEART.h @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT */ /*! - @file M5UnitHEART.h + @file M5UnitUnifiedHEART.h */ #ifndef M5_UNIT_UNIFIED_HEART_H #define M5_UNIT_UNIFIED_HEART_H diff --git a/src/M5UnitUnifiedHEART.hpp b/src/M5UnitUnifiedHEART.hpp index 21909e8..dab524e 100644 --- a/src/M5UnitUnifiedHEART.hpp +++ b/src/M5UnitUnifiedHEART.hpp @@ -19,7 +19,7 @@ /*! @namespace m5 - @brief Top level namespace of M5stack + @brief Top level namespace of M5Stack */ namespace m5 { diff --git a/src/unit/unit_MAX30100.cpp b/src/unit/unit_MAX30100.cpp index d4adb20..4728bce 100644 --- a/src/unit/unit_MAX30100.cpp +++ b/src/unit/unit_MAX30100.cpp @@ -55,7 +55,7 @@ inline bool is_allowed_settings(const Mode mode, const Sampling rate, const LEDP } // Calculate the interval per data -inline uint32_t caluculate_interval_time(const Sampling rate) +inline uint32_t calculate_interval_time(const Sampling rate) { return std::floor(1000.f / sr_table[m5::stl::to_underlying(rate)]); } @@ -163,7 +163,7 @@ const types::attr_t UnitMAX30100::attr{attribute::AccessI2C}; bool UnitMAX30100::begin() { auto ssize = stored_size(); - assert(ssize >= max30100::MAX_FIFO_DEPTH && "stored_size must be greater than MAX_FIFO_DEPT"); + assert(ssize >= max30100::MAX_FIFO_DEPTH && "stored_size must be greater than MAX_FIFO_DEPTH"); if (ssize != _data->capacity()) { _data.reset(new m5::container::CircularBuffer(ssize)); if (!_data) { @@ -179,14 +179,10 @@ bool UnitMAX30100::begin() return false; } -#if 0 - // Clear interrupt status - uint8_t it{}; - if (!read_register8(READ_INTERRUPT_STATUS, it)) { - M5_LIB_LOGE("Failed to read INTERRUPT_STATUS"); + if (!reset()) { + M5_LIB_LOGE("Failed to reset"); return false; } -#endif return _cfg.start_periodic ? startPeriodicMeasurement(_cfg.mode, _cfg.rate, _cfg.width, _cfg.ir_current, _cfg.high_resolution, _cfg.red_current) @@ -201,7 +197,6 @@ void UnitMAX30100::update(const bool force) if (force || !_latest || at >= _latest + _interval) { _updated = read_FIFO(); if (_updated) { - // _latest = at; _latest = m5::utility::millis(); } } @@ -219,7 +214,7 @@ bool UnitMAX30100::start_periodic_measurement() _periodic = writeShutdownControl(false) && resetFIFO(); if (_periodic) { _latest = 0; - _interval = caluculate_interval_time(rate); + _interval = calculate_interval_time(rate); // M5_LIB_LOGE(">>>>R: Rate:%u IT:%u", rate, _interval); // _mask = adc_resolution_bits_table[m5::stl::to_underlying(width)]; return true; @@ -433,9 +428,10 @@ bool UnitMAX30100::reset() auto timeout_at = m5::utility::millis() + 1000; do { if (read_register8(MODE_CONFIGURATION, mc.value) && !mc.reset()) { - _periodic = false; - _mode = mc.mode(); - _retrived = _overflow = 0; + _periodic = false; + _mode = mc.mode(); + _retrieved = _overflow = 0; + m5::utility::delay(10); // Wait for registers to settle after POR return true; } m5::utility::delay(1); @@ -448,7 +444,7 @@ bool UnitMAX30100::reset() bool UnitMAX30100::read_FIFO() { uint8_t wptr{}, rptr{}; - _retrived = _overflow = 0; + _retrieved = _overflow = 0; if (!read_register8(FIFO_WRITE_POINTER, wptr) || !read_register8(FIFO_READ_POINTER, rptr) || !read_register8(FIFO_OVERFLOW_COUNTER, _overflow)) { @@ -465,7 +461,6 @@ bool UnitMAX30100::read_FIFO() assert(readCount <= MAX_FIFO_DEPTH); -#if 1 if (readCount) { uint8_t reg{FIFO_DATA_REGISTER}; if (writeWithTransaction(®, 1) != m5::hal::error::error_t::OK) { @@ -485,7 +480,7 @@ bool UnitMAX30100::read_FIFO() return false; } - for (uint_fast8_t i = 0; i < batch_count; ++i) { + for (uint32_t i = 0; i < batch_count; ++i) { Data d; // Unlike MAX30102, the length of data per session does not change even in HROnly memcpy(d.raw.data(), rbuf + 4 * i, 4); @@ -493,21 +488,9 @@ bool UnitMAX30100::read_FIFO() } left -= batch_len; } - _retrived = readCount; + _retrieved = readCount; } - -#else - while (readCount--) { - Data d{}; - if (!read_register(FIFO_DATA_REGISTER, d.raw.data(), d.raw.size())) { - M5_LIB_LOGE("Failed to read"); - return false; - } - _data->push_back(d); - ++_retrived; - } -#endif - return (_retrived != 0); + return (_retrieved != 0); } bool UnitMAX30100::read_measurement_temperature(max30100::TemperatureData& td) @@ -521,11 +504,11 @@ bool UnitMAX30100::readRevisionID(uint8_t& rev) return read_register8(READ_REVISION_ID, rev); } -uint32_t UnitMAX30100::caluculateSamplingRate() +uint32_t UnitMAX30100::calculateSamplingRate() { Sampling rate{}; if (readSpO2SamplingRate(rate)) { - return 1000 / caluculate_interval_time(rate); + return 1000 / calculate_interval_time(rate); } return 0; } diff --git a/src/unit/unit_MAX30100.hpp b/src/unit/unit_MAX30100.hpp index a597133..7bafc95 100644 --- a/src/unit/unit_MAX30100.hpp +++ b/src/unit/unit_MAX30100.hpp @@ -64,13 +64,13 @@ enum class LEDPulse : uint8_t { @brief LED current control */ enum class LED : uint8_t { - Current0_0, //!< 0,0 mA - Current4_4, //!< 4,4 mA + Current0_0, //!< 0.0 mA + Current4_4, //!< 4.4 mA Current7_6, //!< 7.6 mA Current11_0, //!< 11.0 mA Current14_2, //!< 14.2 mA Current17_4, //!< 17.4 mA - Current20_8, //!< 20,8 mA + Current20_8, //!< 20.8 mA Current24_0, //!< 24.0 mA Current27_1, //!< 27.1 mA Current30_6, //!< 30.6 mA @@ -89,11 +89,13 @@ constexpr uint8_t MAX_FIFO_DEPTH{16}; //!< @brief FIFO depth @brief Measurement data group */ struct Data { - std::array raw{}; // [0...1]:IR [2...3]:Red + std::array raw{}; //!< Raw data [0...1]:IR [2...3]:Red + //! @brief Gets the IR value inline uint16_t ir() const { return m5::types::big_uint16_t(raw[0], raw[1]).get(); } + //! @brief Gets the Red value inline uint16_t red() const { return m5::types::big_uint16_t(raw[2], raw[3]).get(); @@ -105,7 +107,7 @@ struct Data { @brief Measurement data group for temperature */ struct TemperatureData { - std::array raw{0xFF, 0xFF}; // [0]:integer [1]:fraction + std::array raw{0x80, 0x00}; // [0]:integer [1]:fraction sentinel:0x80(-128) is outside operating range //! @brief Temperature (Celsius) inline float temperature() const { @@ -114,9 +116,10 @@ struct TemperatureData { //! @brief Temperature (Celsius) inline float celsius() const { - return (raw[0] != 0xFF) ? (int8_t)raw[0] + raw[1] * 0.0625f : std::numeric_limits::quiet_NaN(); + return (raw[0] != 0x80) ? static_cast(raw[0]) + raw[1] * 0.0625f + : std::numeric_limits::quiet_NaN(); } - //! @brief temperature (Fahrenheit) + //! @brief Temperature (Fahrenheit) inline float fahrenheit() const { return celsius() * 9.0f / 5.0f + 32.f; @@ -182,6 +185,8 @@ public: m5::unit::max30100::LED red_current{max30100::LED::Current27_1}; }; + /*! @brief Constructor + @param addr I2C address */ explicit UnitMAX30100(const uint8_t addr = DEFAULT_ADDRESS) : Component(addr), _data{new m5::container::CircularBuffer(max30100::MAX_FIFO_DEPTH)} { @@ -194,17 +199,19 @@ public: { } + //! @brief Begin the unit virtual bool begin() override; + //! @brief Update the unit virtual void update(const bool force = false) override; ///@name Settings for begin ///@{ - /*! @brief Gets the configration */ + /*! @brief Gets the configuration */ inline config_t config() { return _cfg; } - //! @brief Set the configration + //! @brief Set the configuration inline void config(const config_t& cfg) { _cfg = cfg; @@ -229,9 +236,17 @@ public: accumulated @sa available() */ - inline uint8_t retrived() const + inline uint8_t retrieved() const { - return _retrived; + return _retrieved; + } + /*! + @brief Deprecated alias of retrieved() + @deprecated Use retrieved() instead. + */ + [[deprecated("Please use retrieved()")]] inline uint8_t retrived() const + { + return retrieved(); } /*! @brief The number of samples lost @@ -248,7 +263,15 @@ public: @return >= 0 Sampling rate @note Calculate by SpO2 sampling rate */ - uint32_t caluculateSamplingRate(); + uint32_t calculateSamplingRate(); + /*! + @brief Deprecated alias of calculateSamplingRate() + @deprecated Use calculateSamplingRate() instead. + */ + [[deprecated("Please use calculateSamplingRate()")]] inline uint32_t caluculateSamplingRate() + { + return calculateSamplingRate(); + } ///@name Periodic measurement ///@{ @@ -391,7 +414,7 @@ public: } //! @brief Write the sampling rate bool writeSpO2SamplingRate(const max30100::Sampling rate); - //! @brief Write LED pulse width + //! @brief Read LED pulse width inline bool readSpO2LEDPulseWidth(max30100::LEDPulse& width) { bool enabled{}; @@ -403,11 +426,11 @@ public: ///@} ///@warning In the heart-rate only mode, the red LED is inactive - // @warning and only the IR LED is used to capture optical data and determine the heart rate + ///@warning and only the IR LED is used to capture optical data and determine the heart rate ///@name LED Configuration ///@{ /*! - @brief Read the LED curremt + @brief Read the LED current @param[out] ir_current IR current @param[out] red_current Red current @return True if successful @@ -425,12 +448,12 @@ public: ///@name Measurement temperature ///@{ /*! - @brief Measure tempeature single shot + @brief Measure temperature single shot @param[out] td TemperatureData @return True if successful @warning Blocking until measured about 29 ms @warning Does not work in power-save mode - @sa m5::unit::MAX30100::readShutdownControl + @sa m5::unit::UnitMAX30100::readShutdownControl */ bool measureTemperatureSingleshot(max30100::TemperatureData& td); ///@} @@ -509,7 +532,7 @@ protected: protected: max30100::Mode _mode{max30100::Mode::None}; - uint8_t _retrived{}, _overflow{}; + uint8_t _retrieved{}, _overflow{}; std::unique_ptr> _data{}; config_t _cfg{}; diff --git a/src/unit/unit_MAX30102.cpp b/src/unit/unit_MAX30102.cpp index 5185397..13472f1 100644 --- a/src/unit/unit_MAX30102.cpp +++ b/src/unit/unit_MAX30102.cpp @@ -12,6 +12,7 @@ #include // NaN #include #include +#include using namespace m5::utility::mmh3; using namespace m5::unit::types; @@ -138,9 +139,10 @@ constexpr uint32_t adc_resolution_bits_table[] = { }; // Calculate the interval per data -inline uint32_t caluculate_interval_time(const FIFOSampling avg, const Sampling rate) +inline uint32_t calculate_interval_time(const FIFOSampling avg, const Sampling rate) { - float freq = sampling_rate_table[m5::stl::to_underlying(rate)] / (float)average_table[m5::stl::to_underlying(avg)]; + float freq = sampling_rate_table[m5::stl::to_underlying(rate)] / + static_cast(average_table[m5::stl::to_underlying(avg)]); // M5_LIB_LOGE(">>>>>>>>>> avg:%u %u rate:%u %u => %f %f", avg, average_table[m5::stl::to_underlying(avg)], rate, // sampling_rate_table[m5::stl::to_underlying(rate)], freq, std::ceil(1000.f / freq)); @@ -261,7 +263,7 @@ void UnitMAX30102::update(const bool force) if (inPeriodic()) { auto at = m5::utility::millis(); if (force || !_latest || at >= _latest + _interval) { - _updated = (read_FIFO() && _retrived); + _updated = (read_FIFO() && _retrieved); if (_updated) { _latest = m5::utility::millis(); } @@ -287,7 +289,7 @@ bool UnitMAX30102::start_periodic_measurement() writeShutdownControl(false) && resetFIFO(); if (_periodic) { _latest = 0; - _interval = caluculate_interval_time(avg, rate); + _interval = calculate_interval_time(avg, rate); _mask = adc_resolution_bits_table[m5::stl::to_underlying(width)]; // M5_LIB_LOGI(">>> AVG:%u SR:%u => interval:%u WID:%u => mask:%0x", avg, rate, _interval, width, _mask); @@ -464,7 +466,7 @@ bool UnitMAX30102::read_led_current(const uint8_t idx, float& mA) bool UnitMAX30102::write_led_current(const uint8_t idx, const uint8_t raw) { - return (idx < 2) ? writeRegister8((uint8_t)(LED_CONFIGURATION_1 + idx), raw) : false; + return (idx < 2) ? writeRegister8(static_cast(LED_CONFIGURATION_1 + idx), raw) : false; } bool UnitMAX30102::write_led_current(const uint8_t idx, const float mA) @@ -473,7 +475,7 @@ bool UnitMAX30102::write_led_current(const uint8_t idx, const float mA) M5_LIB_LOGE("Valid range 0.0 - 51.0 (0.2 increments) %f", mA); return false; } - uint8_t raw = (uint8_t)(mA * 5); // / 0.2f + uint8_t raw = static_cast(std::lround(mA * 5)); // / 0.2f return write_led_current(idx, raw); } @@ -606,7 +608,7 @@ bool UnitMAX30102::reset_FIFO(const bool circling_read_ptr) bool UnitMAX30102::read_FIFO() { uint8_t rptr{}, wptr{}; - _retrived = _overflow = 0; + _retrieved = _overflow = 0; if (!readFIFOReadPointer(rptr) || !readFIFOWritePointer(wptr) || !readFIFOOverflowCounter(_overflow)) { M5_LIB_LOGE("Failed to read ptrs"); @@ -622,7 +624,6 @@ bool UnitMAX30102::read_FIFO() assert(readCount <= MAX_FIFO_DEPTH); -#if 1 uint32_t dlen = (_mode == Mode::HROnly) ? 3 : (_mode == Mode::SpO2) ? 6 : (_mode == Mode::MultiLED) ? 3 * ((_slot[0] != Slot::None) + (_slot[1] != Slot::None)) @@ -649,7 +650,7 @@ bool UnitMAX30102::read_FIFO() return false; } - for (uint_fast8_t i = 0; i < batch_count; ++i) { + for (uint32_t i = 0; i < batch_count; ++i) { Data d; d.mask = _mask; switch (_mode) { @@ -673,21 +674,9 @@ bool UnitMAX30102::read_FIFO() } left -= batch_len; } - _retrived = readCount; + _retrieved = readCount; } -#else - while (readCount--) { - Data d{}; - if (!read_register(FIFO_DATA_REGISTER, d.raw.data(), d.raw.size())) { - M5_LIB_LOGE("Failed to read"); - return false; - } - _data->push_back(d); - ++_retrived; - } - -#endif - return (_retrived != 0); + return (_retrieved != 0); } bool UnitMAX30102::reset() @@ -698,10 +687,11 @@ bool UnitMAX30102::reset() auto timeout_at = m5::utility::millis() + 1000; do { if (read_register8(MODE_CONFIGURATION, mc.value) && !mc.reset()) { - _periodic = false; - _mode = mc.mode(); - _retrived = _overflow = 0; + _periodic = false; + _mode = mc.mode(); + _retrieved = _overflow = 0; _slot[0] = _slot[1] = Slot::None; + m5::utility::delay(10); // Wait for registers to settle after POR return true; } m5::utility::delay(1); @@ -716,7 +706,7 @@ bool UnitMAX30102::readRevisionID(uint8_t& rev) return read_register8(READ_REVISION_ID, rev); } -uint32_t UnitMAX30102::caluculateSamplingRate() +uint32_t UnitMAX30102::calculateSamplingRate() { FIFOSampling avg{}; bool rollover{}; @@ -724,7 +714,7 @@ uint32_t UnitMAX30102::caluculateSamplingRate() Sampling rate{}; if (readFIFOConfiguration(avg, rollover, almostFull) && readSpO2SamplingRate(rate)) { - return 1000 / caluculate_interval_time(avg, rate); + return 1000 / calculate_interval_time(avg, rate); } return 0; } diff --git a/src/unit/unit_MAX30102.hpp b/src/unit/unit_MAX30102.hpp index 8097d5a..9eb1348 100644 --- a/src/unit/unit_MAX30102.hpp +++ b/src/unit/unit_MAX30102.hpp @@ -106,17 +106,19 @@ constexpr uint8_t MAX_FIFO_DEPTH{32}; //!< @brief FIFO depth @brief Measurement data group */ struct Data { - std::array raw{}; // [0...2]:Red [3...5]:IR - uint32_t mask{0x3FFFF}; // Valid bits based on ADC range - //! IR value + std::array raw{}; //!< Raw data [0...2]:Red [3...5]:IR + uint32_t mask{0x3FFFF}; //!< Valid bits based on ADC range + //! @brief Gets the IR value inline uint32_t ir() const { - return mask & (((uint32_t)raw[3] << 16) | ((uint32_t)raw[4] << 8) | ((uint32_t)raw[5])); + return mask & ((static_cast(raw[3]) << 16) | (static_cast(raw[4]) << 8) | + static_cast(raw[5])); } - //! Red value + //! @brief Gets the Red value inline uint32_t red() const { - return mask & (((uint32_t)raw[0] << 16) | ((uint32_t)raw[1] << 8) | ((uint32_t)raw[2])); + return mask & ((static_cast(raw[0]) << 16) | (static_cast(raw[1]) << 8) | + static_cast(raw[2])); } }; @@ -125,7 +127,7 @@ struct Data { @brief Measurement data group for temperature */ struct TemperatureData { - std::array raw{0xFF, 0xFF}; // [0]:integer [1]:fraction + std::array raw{0x80, 0x00}; // [0]:integer [1]:fraction sentinel:0x80(-128) is outside operating range //! @brief Temperature (Celsius) inline float temperature() const { @@ -134,9 +136,10 @@ struct TemperatureData { //! @brief Temperature (Celsius) inline float celsius() const { - return (raw[0] != 0xFF) ? (int8_t)raw[0] + raw[1] * 0.0625f : std::numeric_limits::quiet_NaN(); + return (raw[0] != 0x80) ? static_cast(raw[0]) + raw[1] * 0.0625f + : std::numeric_limits::quiet_NaN(); } - //! @brief temperature (Fahrenheit) + //! @brief Temperature (Fahrenheit) inline float fahrenheit() const { return celsius() * 9.0f / 5.0f + 32.f; @@ -191,7 +194,7 @@ public: /*! @struct config_t @brief Settings for begin - @warning Note that some combinations of sampling_rate and pulse_width are invalid. See alse SpO2 configuration + @warning Note that some combinations of sampling_rate and pulse_width are invalid. See also SpO2 configuration */ struct config_t { /*! @@ -216,6 +219,8 @@ public: max30102::FIFOSampling fifo_sampling_average{max30102::FIFOSampling::Average4}; }; + /*! @brief Constructor + @param addr I2C address */ explicit UnitMAX30102(const uint8_t addr = DEFAULT_ADDRESS) : Component(addr), _data{new m5::container::CircularBuffer(max30102::MAX_FIFO_DEPTH)} { @@ -228,17 +233,19 @@ public: { } + //! @brief Begin the unit virtual bool begin() override; + //! @brief Update the unit virtual void update(const bool force = false) override; ///@name Settings for begin ///@{ - /*! @brief Gets the configration */ + /*! @brief Gets the configuration */ inline config_t config() { return _cfg; } - //! @brief Set the configration + //! @brief Set the configuration inline void config(const config_t& cfg) { _cfg = cfg; @@ -262,9 +269,17 @@ public: @note The number of data retrieved by the latest update, not all data accumulated @sa available() */ - inline uint8_t retrived() const + inline uint8_t retrieved() const { - return _retrived; + return _retrieved; + } + /*! + @brief Deprecated alias of retrieved() + @deprecated Use retrieved() instead. + */ + [[deprecated("Please use retrieved()")]] inline uint8_t retrived() const + { + return retrieved(); } /*! @brief The number of samples lost @@ -281,7 +296,15 @@ public: @return >= 0 Sampling rate @note Calculate by FIFO average and SpO2 sampling rate */ - uint32_t caluculateSamplingRate(); + uint32_t calculateSamplingRate(); + /*! + @brief Deprecated alias of calculateSamplingRate() + @deprecated Use calculateSamplingRate() instead. + */ + [[deprecated("Please use calculateSamplingRate()")]] inline uint32_t caluculateSamplingRate() + { + return calculateSamplingRate(); + } ///@name Periodic measurement ///@{ @@ -383,7 +406,7 @@ public: ///@{ /*! @brief Read the SpO2 configuration - @param[out] range ADC rRange + @param[out] range ADC range @param[out] rate Sampling rate @param[out] width LED pulse width @return True if successful @@ -412,7 +435,7 @@ public: } /*! @brief Write the SpO2 configuration - @param range ADC rRange + @param range ADC range @param rate Sampling rate @param width LED pulse width @return True if successful @@ -474,7 +497,7 @@ public: template ::value, std::nullptr_t>::type = nullptr> inline bool writeLEDCurrent(const uint8_t slot, const T mA) { - return write_led_current(slot, (float)mA); + return write_led_current(slot, static_cast(mA)); } ///@} @@ -483,7 +506,7 @@ public: ///@name Multi-LED Mode Control ///@{ /*! - @brief Read the the MultiLED Mode form Slot 1-2 + @brief Read the MultiLED Mode from Slot 1-2 @param[out] slot1 Slot1 mode @param[out] slot2 Slot2 mode @return True if successful @@ -503,12 +526,12 @@ public: ///@name Measurement temperature ///@{ /*! - @brief Measure tempeature single shot + @brief Measure temperature single shot @param[out] td TemperatureData @return True if successful @warning Blocking until measured about 29 ms @warning Does not work in power-save mode - @sa m5::unit::MAX30102::readShutdownControl + @sa m5::unit::UnitMAX30102::readShutdownControl */ bool measureTemperatureSingleshot(max30102::TemperatureData& td); ///@} @@ -518,7 +541,7 @@ public: /*! @brief Read the FIFO configuration @param[out] avg FIFO sampling average - @param[out] rolllover FIFO Rolls on Full if true + @param[out] rollover FIFO Rolls on Full if true @param[out] almostFull FIFO Almost Full Value for interrupt @return True if successful */ @@ -526,7 +549,7 @@ public: /*! @brief Write the FIFO configuration @param avg FIFO sampling average - @param rolllover FIFO Rolls on Full if true + @param rollover FIFO Rolls on Full if true @param almostFull FIFO Almost Full Value for interrupt @return True if successful @warning During periodic detection runs, an error is returned @@ -618,7 +641,7 @@ protected: protected: std::unique_ptr> _data{}; max30102::Mode _mode{}; - uint8_t _retrived{}, _overflow{}; + uint8_t _retrieved{}, _overflow{}; uint32_t _mask{}; // Valid bits based on ADC range max30102::Slot _slot[2]{}; config_t _cfg{}; diff --git a/src/utility/pulse_monitor.cpp b/src/utility/pulse_monitor.cpp index 362ac6a..79380f6 100644 --- a/src/utility/pulse_monitor.cpp +++ b/src/utility/pulse_monitor.cpp @@ -20,7 +20,7 @@ void PulseMonitor::setSamplingRate(const uint32_t samplingRate) return; } _sampling_rate = (float)samplingRate; - _max_samples = (size_t)samplingRate * _range; + _max_samples = static_cast(samplingRate) * _range; _filterIR.setSamplingRate(5.0f, samplingRate); clear(); @@ -30,7 +30,7 @@ void PulseMonitor::clear() { _dataIR.clear(); _beat = false; - _bpm = _SpO2 = 0.0f; + _bpm = _spo2 = 0.0f; _count = 0; _avered = _aveir = _sumredrms = _sumirrms = 0; @@ -53,10 +53,17 @@ void PulseMonitor::push_back(const float ir, const float red) _aveir = _aveir * 0.95f + ir * (1.0f - 0.95f); _sumredrms += (red - _avered) * (red - _avered); _sumirrms += (ir - _aveir) * (ir - _aveir); - if (++_count == (uint32_t)_sampling_rate) { - float R = (std::sqrt(_sumredrms) / _avered) / (std::sqrt(_sumirrms) / _aveir); - _SpO2 = -23.3f * (R - 0.4f) + 100; - _SpO2 = std::fmax(std::fmin(100.0f, _SpO2), 80.0f); // clamp 80-100 + if (++_count == static_cast(_sampling_rate)) { + const float eps = 1e-6f; + if (std::fabs(_avered) < eps || std::fabs(_aveir) < eps) { + _sumredrms = _sumirrms = 0; + _count = 0; + return; + } + float R = (std::sqrt(_sumredrms) / _avered) / (std::sqrt(_sumirrms) / _aveir); + // Empirical SpO2 approximation from the red/IR RMS to DC ratio. + _spo2 = -23.3f * (R - 0.4f) + 100; + _spo2 = std::fmax(std::fmin(100.0f, _spo2), 80.0f); // clamp 80-100 _sumredrms = _sumirrms = 0; _count = 0; } @@ -69,6 +76,10 @@ void PulseMonitor::update() float PulseMonitor::calculate_bpm() { + if (_dataIR.size() < 3) { + _beat = false; + return 0.0f; + } std::vector peaks; float threshold = 50.f; bool negatived{}; diff --git a/src/utility/pulse_monitor.hpp b/src/utility/pulse_monitor.hpp index 86cb60c..65483ed 100644 --- a/src/utility/pulse_monitor.hpp +++ b/src/utility/pulse_monitor.hpp @@ -18,8 +18,8 @@ namespace m5 { /*! - @namepsace heart - @brief Unit-HEART releated + @namespace heart + @brief Unit-HEART related */ namespace heart { @@ -29,15 +29,21 @@ namespace heart { */ class EMA { public: + /*! @brief Constructor + @param factor Smoothing factor (0.0 - 1.0) */ explicit EMA(float factor) : _alpha(factor) { } + //! @brief Clear the stored value inline void clear() { _ema_value = std::numeric_limits::quiet_NaN(); } + /*! @brief Update with a new value and return the smoothed result + @param new_value New input value + @return Smoothed value */ inline float update(float new_value) { if (!std::isnan(_ema_value)) { @@ -58,22 +64,32 @@ private: */ class Filter { public: + /*! @brief Constructor + @param cutoff Cutoff frequency in Hz + @param sampling_rate Sampling rate in Hz */ Filter(const float cutoff, const float sampling_rate) { setSamplingRate(cutoff, sampling_rate); } + /*! @brief Set the sampling rate and reset filter state + @param cutoff Cutoff frequency in Hz + @param sampling_rate Sampling rate in Hz */ void setSamplingRate(const float cutoff, const float sampling_rate) { + constexpr float pi{3.14159265358979323846f}; _cutoff = cutoff; _samplingRate = sampling_rate; _prevIn = _prevOut = 0.0f; auto dt = 1.0f / _samplingRate; - auto RC = 1.0f / (2.0f * M_PI * _cutoff); + auto RC = 1.0f / (2.0f * pi * _cutoff); _alpha = RC / (RC + dt); _ema.clear(); } + /*! @brief Process a sample through the filter + @param value Input sample + @return Filtered and inverted output */ float process(const float value) { float out = _ema.update(_alpha * (_prevOut + value - _prevIn)); @@ -96,17 +112,17 @@ private: class PulseMonitor { public: /*! - @brief Costructor + @brief Constructor @param samplingRate sampling rate @param sec Seconds of data to be stored */ explicit PulseMonitor(const uint32_t samplingRate = 100, const uint32_t sec = 5) : _range{sec}, _sampling_rate{(float)samplingRate}, - _max_samples{(size_t)samplingRate * sec}, + _max_samples{static_cast(samplingRate) * sec}, _filterIR(5.0f, samplingRate) { - assert(sec >= 1 && "sec must be greater or eaual than 1"); + assert(sec >= 1 && "sec must be greater or equal than 1"); assert(samplingRate >= 1.0f && "SamplingRate must be greater or equal than 1.0f"); } @@ -126,7 +142,7 @@ public: */ inline float SpO2() const { - return _SpO2; + return _spo2; } /*! @@ -145,20 +161,20 @@ public: @brief Push back IR and RED @param ir IR data @param red RED data - @note Calclate SpO2 + @note Calculate SpO2 */ void push_back(const float ir, const float red); /*! @brief Update status - @note Calclate BPM + @note Calculate BPM */ void update(); //! @brief Clear inner data void clear(); - //! @brief Filterd latest ir value + //! @brief Filtered latest ir value inline float latestIR() const { return !_dataIR.empty() ? _dataIR.back() : std::numeric_limits::quiet_NaN(); @@ -177,7 +193,7 @@ private: bool _beat{}; float _bpm{}; - float _SpO2{}; + float _spo2{}; uint32_t _count{}; float _avered{}, _aveir{}; diff --git a/test/embedded/embedded_main.cpp b/test/embedded/embedded_main.cpp index 28f708c..84427b0 100644 --- a/test/embedded/embedded_main.cpp +++ b/test/embedded/embedded_main.cpp @@ -30,7 +30,11 @@ void test() void setup() { - M5.begin(); + auto m5cfg = M5.config(); + m5cfg.pmic_button = false; // Disable BtnPWR + m5cfg.internal_imu = false; // Disable internal IMU + m5cfg.internal_rtc = false; // Disable internal RTC + M5.begin(m5cfg); M5_LOGI("CPP %ld", __cplusplus); M5_LOGI("ESP-IDF Version %d.%d.%d", (ESP_IDF_VERSION >> 16) & 0xFF, (ESP_IDF_VERSION >> 8) & 0xFF, @@ -38,7 +42,7 @@ void setup() M5_LOGI("BOARD:%X", M5.getBoard()); M5_LOGI("Heap: %u", esp_get_free_heap_size()); - lcd.clear(TFT_DARKGRAY); + lcd.fillScreen(TFT_DARKGRAY); ::testing::InitGoogleTest(); #ifdef GTEST_FILTER diff --git a/test/embedded/test_max30100/max30100_test.cpp b/test/embedded/test_max30100/max30100_test.cpp index 7283f81..567b660 100644 --- a/test/embedded/test_max30100/max30100_test.cpp +++ b/test/embedded/test_max30100/max30100_test.cpp @@ -15,18 +15,13 @@ #include #include #include -#include -#include using namespace m5::unit::googletest; using namespace m5::unit; using namespace m5::unit::max30100; using namespace m5::unit::max30100::command; -using m5::unit::types::elapsed_time_t; -const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture<400000U>()); - -class TestMAX30100 : public ComponentTestBase { +class TestMAX30100 : public I2CComponentTestBase { protected: virtual UnitMAX30100* get_instance() override { @@ -37,18 +32,8 @@ protected: } return ptr; } - virtual bool is_using_hal() const override - { - return GetParam(); - }; }; -// INSTANTIATE_TEST_SUITE_P(ParamValues, TestMAX30100, -// ::testing::Values(false, true)); -// INSTANTIATE_TEST_SUITE_P(ParamValues, TestMAX30100, -// ::testing::Values(true)); -INSTANTIATE_TEST_SUITE_P(ParamValues, TestMAX30100, ::testing::Values(false)); - namespace { constexpr uint32_t STORED_SIZE{6}; @@ -61,7 +46,7 @@ constexpr uint8_t hr_table[] = { 0x0F, 0x0F, 0x07, 0x07, 0x03, 0x03, 0x03, 0x03, }; constexpr uint8_t none_table[] = { - // LSB:200 MSB::1600 + // LSB:200 MSB:1600 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; constexpr const uint8_t* allowed_setting_table[] = {none_table, none_table, hr_table, spo2_table}; @@ -128,14 +113,15 @@ void test_spo2_config(UnitMAX30100* unit, const Mode mode) LEDPulse width2{}; EXPECT_TRUE(unit->readSpO2Configuration(resolution2, rate2, width2)); EXPECT_EQ(resolution2, resolution); - EXPECT_EQ(rate2, rate2); - EXPECT_EQ(width2, width2); + EXPECT_EQ(rate2, rate); + EXPECT_EQ(width2, width); } } } } } +// Verify individual SpO2 parameter read/write APIs (roundtrip) void test_spo2_config_each(UnitMAX30100* unit, const Mode mode) { auto s = m5::utility::formatString("Mode:%u", mode); @@ -144,99 +130,88 @@ void test_spo2_config_each(UnitMAX30100* unit, const Mode mode) EXPECT_TRUE(unit->writeMode(mode)); EXPECT_TRUE(unit->writeSpO2Configuration(false, Sampling::Rate50, LEDPulse::Width200)); + // Resolution for (auto& res : res_table) { - auto s = m5::utility::formatString("RES:%u", res); - SCOPED_TRACE(s); - bool resolution{}; EXPECT_TRUE(unit->writeSpO2HighResolution(res)); EXPECT_TRUE(unit->readSpO2HighResolution(resolution)); EXPECT_EQ(resolution, res); + } - for (auto&& sr : sr_table) { - auto s = m5::utility::formatString("Rate:%u", sr); - SCOPED_TRACE(s); - - if (is_allowed_settings(mode, sr, LEDPulse::Width200)) { - Sampling rate{}; - - EXPECT_TRUE(unit->writeSpO2SamplingRate(sr)); - - EXPECT_TRUE(unit->readSpO2SamplingRate(rate)); - EXPECT_EQ(rate, sr); - - for (auto&& pw : pw_table) { - auto s = m5::utility::formatString("Width:%u", pw); - SCOPED_TRACE(s); - LEDPulse width{}; - - if (is_allowed_settings(mode, sr, pw)) { - EXPECT_TRUE(unit->writeSpO2LEDPulseWidth(pw)); - - EXPECT_TRUE(unit->readSpO2LEDPulseWidth(width)); - EXPECT_EQ(width, pw); - } else { - LEDPulse width2{}; - EXPECT_TRUE(unit->readSpO2LEDPulseWidth(width)); - - EXPECT_FALSE(unit->writeSpO2LEDPulseWidth(pw)); - - EXPECT_TRUE(unit->readSpO2LEDPulseWidth(width2)); - EXPECT_EQ(width2, width); - } - } - EXPECT_TRUE(unit->writeSpO2LEDPulseWidth(LEDPulse::Width200)); - } else { - Sampling rate{}, rate2{}; - EXPECT_TRUE(unit->readSpO2SamplingRate(rate)); - - EXPECT_FALSE(unit->writeSpO2SamplingRate(sr)); - - EXPECT_TRUE(unit->readSpO2SamplingRate(rate2)); - EXPECT_EQ(rate2, rate); - } + // Sampling rate (allowed settings only) + for (auto&& sr : sr_table) { + if (is_allowed_settings(mode, sr, LEDPulse::Width200)) { + Sampling rate{}; + EXPECT_TRUE(unit->writeSpO2SamplingRate(sr)); + EXPECT_TRUE(unit->readSpO2SamplingRate(rate)); + EXPECT_EQ(rate, sr); + } + } + EXPECT_TRUE(unit->writeSpO2SamplingRate(Sampling::Rate50)); + + // LED pulse width (allowed settings only) + for (auto&& pw : pw_table) { + if (is_allowed_settings(mode, Sampling::Rate50, pw)) { + LEDPulse width{}; + EXPECT_TRUE(unit->writeSpO2LEDPulseWidth(pw)); + EXPECT_TRUE(unit->readSpO2LEDPulseWidth(width)); + EXPECT_EQ(width, pw); } - EXPECT_TRUE(unit->writeSpO2Configuration(res, Sampling::Rate50, LEDPulse::Width200)); } } template -elapsed_time_t test_periodic(U* unit, const uint32_t times, const uint32_t measure_duration = 0) +void collect_and_verify(U* unit, uint32_t count, bool expect_ir, bool expect_red) { - auto tm = unit->interval(); - auto timeout_at = m5::utility::millis() + 10 * 1000; + auto ad = unit->template asAdapter(m5::unit::Adapter::Type::I2C); + bool is_bus = ad && ad->implType() == m5::unit::AdapterI2C::ImplType::Bus; + uint32_t limit = is_bus ? 3U : 1U; + auto timeout = std::max(unit->interval() * (count + 1) * 2, 2000); + auto result = collect_periodic_measurements(unit, count, timeout); - do { - unit->update(); - if (unit->updated()) { - break; - } - std::this_thread::yield(); - } while (!unit->updated() && m5::utility::millis() <= timeout_at); - // timeout - if (!unit->updated()) { - return 0; + EXPECT_TRUE(unit->stopPeriodicMeasurement()); + EXPECT_FALSE(unit->inPeriodic()); + EXPECT_FALSE(result.timed_out); + EXPECT_EQ(result.update_count, count); + EXPECT_LE(result.median(), result.expected_interval + limit); + + EXPECT_GE(unit->available(), count); + EXPECT_FALSE(unit->empty()); + + uint32_t cnt{unit->available() / 2}; + uint32_t left = unit->available() - cnt; + uint32_t air{}, ared{}; + while (cnt-- && unit->available()) { + air += unit->ir(); + ared += unit->red(); + EXPECT_EQ(unit->oldest().ir(), unit->ir()); + EXPECT_EQ(unit->oldest().red(), unit->red()); + EXPECT_FALSE(unit->empty()); + unit->discard(); } + if (expect_ir) { + EXPECT_NE(air, 0); + } else { + EXPECT_EQ(air, 0); + } + if (expect_red) { + EXPECT_NE(ared, 0); + } else { + EXPECT_EQ(ared, 0); + } + + EXPECT_EQ(unit->available(), left); + EXPECT_FALSE(unit->empty()); + EXPECT_FALSE(unit->full()); + unit->flush(); + EXPECT_EQ(unit->available(), 0); + EXPECT_TRUE(unit->empty()); + EXPECT_FALSE(unit->full()); - // - uint32_t measured{}; - auto start_at = m5::utility::millis(); - timeout_at = start_at + (times * (tm + measure_duration) * 2); - - do { - unit->update(); - measured += unit->updated() ? 1 : 0; - if (measured >= times) { - break; - } - // std::this_thread::yield(); - m5::utility::delay(1); - - } while (measured < times && m5::utility::millis() <= timeout_at); - return (measured == times) ? m5::utility::millis() - start_at : 0; - // return (measured == times) ? unit->updatedMillis() - start_at : 0; + EXPECT_EQ(unit->ir(), 0); + EXPECT_EQ(unit->red(), 0); } void test_periodic_spo2(UnitMAX30100* unit) @@ -279,52 +254,7 @@ void test_periodic_spo2(UnitMAX30100* unit) SCOPED_TRACE(s); EXPECT_TRUE(unit->startPeriodicMeasurement(Mode::SpO2, rate, width, LED::Current27_1, res, LED::Current27_1)); - auto it = unit->interval() ? unit->interval() : 1; - - auto elapsed = test_periodic(unit, STORED_SIZE, it); - - EXPECT_TRUE(unit->stopPeriodicMeasurement()); - EXPECT_FALSE(unit->inPeriodic()); - - EXPECT_NE(elapsed, 0); - EXPECT_GE(elapsed, STORED_SIZE * unit->interval()); - // M5_LOGI(">>> %s>elapsed: %ld/%u retrived:%u overflow:%u", s.c_str(), elapsed, STORED_SIZE * unit->interval(), - // unit->retrived(), unit->overflow()); - - EXPECT_GE(unit->available(), STORED_SIZE); // Check GE not EQ! (because FIFO) - EXPECT_FALSE(unit->empty()); - if (unit->available() == MAX_FIFO_DEPTH) { - EXPECT_TRUE(unit->full()); - } else { - EXPECT_FALSE(unit->full()); - } - - uint32_t cnt{unit->available() / 2}; - uint32_t left = unit->available() - cnt; - uint32_t air{}, ared{}; - while (cnt-- && unit->available()) { - air += unit->ir(); - ared += unit->red(); - EXPECT_EQ(unit->oldest().ir(), unit->ir()); - EXPECT_EQ(unit->oldest().red(), unit->red()); - - EXPECT_FALSE(unit->empty()); - unit->discard(); - } - EXPECT_NE(air, 0); - EXPECT_NE(ared, 0); - - EXPECT_EQ(unit->available(), left); - EXPECT_FALSE(unit->empty()); - EXPECT_FALSE(unit->full()); - - unit->flush(); - EXPECT_EQ(unit->available(), 0); - EXPECT_TRUE(unit->empty()); - EXPECT_FALSE(unit->full()); - - EXPECT_EQ(unit->ir(), 0); - EXPECT_EQ(unit->red(), 0); + collect_and_verify(unit, STORED_SIZE, true, true); } } @@ -369,58 +299,13 @@ void test_periodic_hr(UnitMAX30100* unit) SCOPED_TRACE(s); EXPECT_TRUE(unit->startPeriodicMeasurement(Mode::HROnly, rate, width, LED::Current27_1, res)); - auto it = unit->interval() ? unit->interval() : 1; - - auto elapsed = test_periodic(unit, STORED_SIZE, it); - - EXPECT_TRUE(unit->stopPeriodicMeasurement()); - EXPECT_FALSE(unit->inPeriodic()); - - EXPECT_NE(elapsed, 0); - EXPECT_GE(elapsed, STORED_SIZE * unit->interval()); - // M5_LOGI(">>> %s>elapsed: %ld/%u retrived:%u overflow:%u", s.c_str(), elapsed, STORED_SIZE * unit->interval(), - // unit->retrived(), unit->overflow()); - - EXPECT_GE(unit->available(), STORED_SIZE); // Check GE not EQ! (because FIFO) - EXPECT_FALSE(unit->empty()); - if (unit->available() == MAX_FIFO_DEPTH) { - EXPECT_TRUE(unit->full()); - } else { - EXPECT_FALSE(unit->full()); - } - - uint32_t cnt{unit->available() / 2}; - uint32_t left = unit->available() - cnt; - uint32_t air{}, ared{}; - while (cnt-- && unit->available()) { - air += unit->ir(); - ared += unit->red(); - EXPECT_EQ(unit->oldest().ir(), unit->ir()); - EXPECT_EQ(unit->oldest().red(), unit->red()); - - EXPECT_FALSE(unit->empty()); - unit->discard(); - } - EXPECT_NE(air, 0); - EXPECT_EQ(ared, 0); - - EXPECT_EQ(unit->available(), left); - EXPECT_FALSE(unit->empty()); - EXPECT_FALSE(unit->full()); - - unit->flush(); - EXPECT_EQ(unit->available(), 0); - EXPECT_TRUE(unit->empty()); - EXPECT_FALSE(unit->full()); - - EXPECT_EQ(unit->ir(), 0); - EXPECT_EQ(unit->red(), 0); + collect_and_verify(unit, STORED_SIZE, true, false); } } } // namespace -TEST_P(TestMAX30100, Mode) +TEST_F(TestMAX30100, Mode) { constexpr bool bool_table[] = {true, false}; @@ -457,7 +342,7 @@ TEST_P(TestMAX30100, Mode) } } -TEST_P(TestMAX30100, SpO2Configuration) +TEST_F(TestMAX30100, SpO2Configuration) { SCOPED_TRACE(ustr); @@ -474,12 +359,17 @@ TEST_P(TestMAX30100, SpO2Configuration) test_spo2_config_each(unit.get(), Mode::HROnly); } -TEST_P(TestMAX30100, LEDCurrent) +TEST_F(TestMAX30100, LEDCurrent) { SCOPED_TRACE(ustr); - for (auto&& ir : cur_table) { - for (auto&& red : cur_table) { + EXPECT_TRUE(unit->stopPeriodicMeasurement()); + EXPECT_FALSE(unit->inPeriodic()); + + // Boundary values: min, mid, max + constexpr LED boundary[] = {LED::Current0_0, LED::Current24_0, LED::Current50_0}; + for (auto&& ir : boundary) { + for (auto&& red : boundary) { auto s = m5::utility::formatString("IR:%u Red:%u", ir, red); SCOPED_TRACE(s); EXPECT_TRUE(unit->writeLEDCurrent(ir, red)); @@ -492,7 +382,7 @@ TEST_P(TestMAX30100, LEDCurrent) } } -TEST_P(TestMAX30100, Temperature) +TEST_F(TestMAX30100, Temperature) { SCOPED_TRACE(ustr); @@ -538,7 +428,20 @@ TEST_P(TestMAX30100, Temperature) } } -TEST_P(TestMAX30100, Revision) +TEST_F(TestMAX30100, TemperatureDataSentinel) +{ + TemperatureData td{}; + + td.raw = {0x80, 0x00}; + EXPECT_FALSE(std::isfinite(td.celsius())); + EXPECT_FALSE(std::isfinite(td.fahrenheit())); + + td.raw = {0xFF, 0x00}; + EXPECT_FLOAT_EQ(td.celsius(), -1.0f); + EXPECT_FLOAT_EQ(td.fahrenheit(), 30.2f); +} + +TEST_F(TestMAX30100, Revision) { SCOPED_TRACE(ustr); @@ -548,7 +451,7 @@ TEST_P(TestMAX30100, Revision) // M5_LOGI("Rev:%02X", rev); } -TEST_P(TestMAX30100, Reset) +TEST_F(TestMAX30100, Reset) { SCOPED_TRACE(ustr); @@ -598,7 +501,7 @@ TEST_P(TestMAX30100, Reset) EXPECT_EQ(cnt, 0U); } -TEST_P(TestMAX30100, Periodic) +TEST_F(TestMAX30100, Periodic) { SCOPED_TRACE(ustr); @@ -618,7 +521,7 @@ TEST_P(TestMAX30100, Periodic) } while (!unit->updated() && m5::utility::millis() - start_at <= 1000); EXPECT_TRUE(unit->updated()); - // M5_LOGW("%u %u", unit->retrived(), unit->available()); + // M5_LOGW("%u %u", unit->retrieved(), unit->available()); EXPECT_FALSE(unit->full()); EXPECT_FALSE(unit->empty()); @@ -637,8 +540,8 @@ TEST_P(TestMAX30100, Periodic) EXPECT_TRUE(unit->updated()); EXPECT_GE(unit->available(), 10U); - auto retrived = unit->retrived(); - EXPECT_GT(retrived, 0U); + auto retrieved = unit->retrieved(); + EXPECT_GT(retrieved, 0U); EXPECT_FALSE(unit->full()); EXPECT_FALSE(unit->empty()); @@ -649,7 +552,7 @@ TEST_P(TestMAX30100, Periodic) unit->flush(); EXPECT_EQ(unit->available(), 0U); - EXPECT_EQ(unit->retrived(), retrived); // Not clear on flush + EXPECT_EQ(unit->retrieved(), retrieved); // Not clear on flush EXPECT_FALSE(unit->full()); EXPECT_TRUE(unit->empty()); @@ -660,7 +563,7 @@ TEST_P(TestMAX30100, Periodic) EXPECT_TRUE(unit->updated()); EXPECT_EQ(unit->available(), MAX_FIFO_DEPTH); - EXPECT_EQ(unit->retrived(), MAX_FIFO_DEPTH); + EXPECT_EQ(unit->retrieved(), MAX_FIFO_DEPTH); EXPECT_TRUE(unit->full()); EXPECT_FALSE(unit->empty()); EXPECT_GT(unit->overflow(), 0U); @@ -675,7 +578,7 @@ TEST_P(TestMAX30100, Periodic) } } -TEST_P(TestMAX30100, Periodic_SPO2) +TEST_F(TestMAX30100, Periodic_SPO2) { SCOPED_TRACE(ustr); @@ -686,7 +589,7 @@ TEST_P(TestMAX30100, Periodic_SPO2) test_periodic_spo2(unit.get()); } -TEST_P(TestMAX30100, Periodic_HR) +TEST_F(TestMAX30100, Periodic_HR) { SCOPED_TRACE(ustr); diff --git a/test/embedded/test_max30102/max30102_test.cpp b/test/embedded/test_max30102/max30102_test.cpp index 91d8cac..45dd326 100644 --- a/test/embedded/test_max30102/max30102_test.cpp +++ b/test/embedded/test_max30102/max30102_test.cpp @@ -11,46 +11,57 @@ #include #include #include +#include #include #include #include -#include +#include using namespace m5::unit::googletest; using namespace m5::unit; using namespace m5::unit::max30102; using namespace m5::unit::max30102::command; -using m5::unit::types::elapsed_time_t; -#if !defined(M5STACK_M5STICK_CPLUS2) && !defined(ARDUINO_M5Stick_C) -#error This test for M5StckCPlus or M5StckCPlus2 -#else namespace hat { -template -class GlobalFixture : public ::testing::Environment { - static_assert(WNUM < 2, "Wire number must be lesser than 2"); - -public: - void SetUp() override - { - // Setup required to use HatHEART - pinMode(25, INPUT_PULLUP); - pinMode(26, OUTPUT); - - TwoWire* w[2] = {&Wire, &Wire1}; - 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(0, 26, FREQ); - } +struct I2cPins { + int sda; + int scl; }; -} // namespace hat -const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new hat::GlobalFixture<400000U>()); -#endif -class TestMAX30102 : public ComponentTestBase { +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 hat + +class TestMAX30102 : public I2CComponentTestBase { protected: + virtual bool begin() override + { + auto board = M5.getBoard(); + const auto pins = hat::get_hat_i2c_pins(board); + // NessoN1: Wire is used by M5Unified In_I2C; use Wire1 for Hat port + auto& wire = (board == m5::board_t::board_ArduinoNessoN1) ? Wire1 : Wire; + pinMode(pins.scl, OUTPUT); + wire.end(); + wire.begin(pins.sda, pins.scl, unit->component_config().clock); + return Units.add(*unit, wire) && Units.begin(); + } + virtual UnitMAX30102* get_instance() override { auto ptr = new m5::unit::UnitMAX30102(); @@ -60,18 +71,10 @@ protected: } return ptr; } - virtual bool is_using_hal() const override - { - return GetParam(); - }; }; -// INSTANTIATE_TEST_SUITE_P(ParamValues, TestMAX30102, ::testing::Values(false, true)); -// INSTANTIATE_TEST_SUITE_P(ParamValues, TestMAX30102, ::testing::Values(true)); -INSTANTIATE_TEST_SUITE_P(ParamValues, TestMAX30102, ::testing::Values(false)); - namespace { -auto rng = std::default_random_engine{}; +// esp_random() used instead of std::default_random_engine constexpr uint32_t STORED_SIZE{4}; @@ -185,14 +188,15 @@ void test_spo2_config(UnitMAX30102* unit, const Mode mode) LEDPulse width2{}; EXPECT_TRUE(unit->readSpO2Configuration(range2, rate2, width2)); EXPECT_EQ(range2, range); - EXPECT_EQ(rate2, rate2); - EXPECT_EQ(width2, width2); + EXPECT_EQ(rate2, rate); + EXPECT_EQ(width2, width); } } } } } +// Verify individual SpO2 parameter read/write APIs (roundtrip) void test_spo2_config_each(UnitMAX30102* unit, const Mode mode) { auto s = m5::utility::formatString("Mode:%u", mode); @@ -201,99 +205,90 @@ void test_spo2_config_each(UnitMAX30102* unit, const Mode mode) EXPECT_TRUE(unit->writeMode(mode)); EXPECT_TRUE(unit->writeSpO2Configuration(ADC::Range2048nA, Sampling::Rate50, LEDPulse::Width69)); + // ADC range for (auto& rg : range_table) { - auto s = m5::utility::formatString("Range:%u", rg); - SCOPED_TRACE(s); - ADC range{}; EXPECT_TRUE(unit->writeSpO2ADCRange(rg)); EXPECT_TRUE(unit->readSpO2ADCRange(range)); EXPECT_EQ(range, rg); + } - for (auto&& sr : sr_table) { - auto s = m5::utility::formatString("Rate:%u", sr); - SCOPED_TRACE(s); - - if (is_allowed_settings(mode, sr, LEDPulse::Width69)) { - Sampling rate{}; - - EXPECT_TRUE(unit->writeSpO2SamplingRate(sr)); - - EXPECT_TRUE(unit->readSpO2SamplingRate(rate)); - EXPECT_EQ(rate, sr); - - for (auto&& pw : pw_table) { - auto s = m5::utility::formatString("Width:%u", pw); - SCOPED_TRACE(s); - LEDPulse width{}; - - if (is_allowed_settings(mode, sr, pw)) { - EXPECT_TRUE(unit->writeSpO2LEDPulseWidth(pw)); - - EXPECT_TRUE(unit->readSpO2LEDPulseWidth(width)); - EXPECT_EQ(width, pw); - } else { - LEDPulse width2{}; - EXPECT_TRUE(unit->readSpO2LEDPulseWidth(width)); - - EXPECT_FALSE(unit->writeSpO2LEDPulseWidth(pw)); - - EXPECT_TRUE(unit->readSpO2LEDPulseWidth(width2)); - EXPECT_EQ(width2, width); - } - } - EXPECT_TRUE(unit->writeSpO2LEDPulseWidth(LEDPulse::Width69)); - } else { - Sampling rate{}, rate2{}; - EXPECT_TRUE(unit->readSpO2SamplingRate(rate)); - - EXPECT_FALSE(unit->writeSpO2SamplingRate(sr)); - - EXPECT_TRUE(unit->readSpO2SamplingRate(rate2)); - EXPECT_EQ(rate2, rate); - } + // Sampling rate (allowed settings only) + for (auto&& sr : sr_table) { + if (is_allowed_settings(mode, sr, LEDPulse::Width69)) { + Sampling rate{}; + EXPECT_TRUE(unit->writeSpO2SamplingRate(sr)); + EXPECT_TRUE(unit->readSpO2SamplingRate(rate)); + EXPECT_EQ(rate, sr); + } + } + EXPECT_TRUE(unit->writeSpO2SamplingRate(Sampling::Rate50)); + + // LED pulse width (allowed settings only) + for (auto&& pw : pw_table) { + if (is_allowed_settings(mode, Sampling::Rate50, pw)) { + LEDPulse width{}; + EXPECT_TRUE(unit->writeSpO2LEDPulseWidth(pw)); + EXPECT_TRUE(unit->readSpO2LEDPulseWidth(width)); + EXPECT_EQ(width, pw); } - EXPECT_TRUE(unit->writeSpO2Configuration(rg, Sampling::Rate50, LEDPulse::Width69)); } } template -elapsed_time_t test_periodic(U* unit, const uint32_t times, const uint32_t measure_duration = 0) +void collect_and_verify(U* unit, uint32_t count, bool expect_ir, bool expect_red, uint32_t mask = 0) { - auto tm = unit->interval(); - auto timeout_at = m5::utility::millis() + 10 * 1000; + constexpr uint32_t limit{3U}; + auto timeout = std::max(unit->interval() * (count + 1) * 2, 2000); + auto result = collect_periodic_measurements(unit, count, timeout); - do { - unit->update(); - if (unit->updated()) { - break; + EXPECT_TRUE(unit->stopPeriodicMeasurement()); + EXPECT_FALSE(unit->inPeriodic()); + EXPECT_FALSE(result.timed_out); + EXPECT_EQ(result.update_count, count); + EXPECT_LE(result.median(), result.expected_interval + limit); + + EXPECT_GE(unit->available(), count); + EXPECT_FALSE(unit->empty()); + + uint32_t cnt{unit->available() / 2}; + uint32_t left = unit->available() - cnt; + uint32_t air{}, ared{}; + while (cnt-- && unit->available()) { + air += unit->ir(); + ared += unit->red(); + if (mask) { + EXPECT_LE(unit->ir(), mask); + EXPECT_LE(unit->red(), mask); } - std::this_thread::yield(); - } while (!unit->updated() && m5::utility::millis() <= timeout_at); - // timeout - if (!unit->updated()) { - return 0; + EXPECT_EQ(unit->oldest().ir(), unit->ir()); + EXPECT_EQ(unit->oldest().red(), unit->red()); + EXPECT_FALSE(unit->empty()); + unit->discard(); } + if (expect_ir) { + EXPECT_NE(air, 0); + } else { + EXPECT_EQ(air, 0); + } + if (expect_red) { + EXPECT_NE(ared, 0); + } else { + EXPECT_EQ(ared, 0); + } + + EXPECT_EQ(unit->available(), left); + EXPECT_FALSE(unit->empty()); + EXPECT_FALSE(unit->full()); + unit->flush(); + EXPECT_EQ(unit->available(), 0); + EXPECT_TRUE(unit->empty()); + EXPECT_FALSE(unit->full()); - // - uint32_t measured{}; - auto start_at = m5::utility::millis(); - timeout_at = start_at + (times * (tm + measure_duration) * 2); - - do { - unit->update(); - measured += unit->updated() ? 1 : 0; - if (measured >= times) { - break; - } - // std::this_thread::yield(); - m5::utility::delay(1); - - } while (measured < times && m5::utility::millis() <= timeout_at); - return (measured == times) ? m5::utility::millis() - start_at : 0; - // return (measured == times) ? unit->updatedMillis() - start_at : 0; + EXPECT_EQ(unit->ir(), 0); + EXPECT_EQ(unit->red(), 0); } void test_periodic_spo2(UnitMAX30102* unit) @@ -354,57 +349,9 @@ void test_periodic_spo2(UnitMAX30102* unit) auto s = m5::utility::formatString("SPO2 RNG:%u SR:%u WID:%u AVG:%u", range, rate, width, avg); SCOPED_TRACE(s); - EXPECT_TRUE(unit->startPeriodicMeasurement(Mode::SpO2, range, rate, width, avg, 0x1f, 0x1f)); - auto it = unit->interval() ? unit->interval() : 1; - - auto elapsed = test_periodic(unit, STORED_SIZE, it); - - EXPECT_TRUE(unit->stopPeriodicMeasurement()); - EXPECT_FALSE(unit->inPeriodic()); - - EXPECT_NE(elapsed, 0); - EXPECT_GE(elapsed, STORED_SIZE * unit->interval()); - // M5_LOGI(">>> %s>elapsed: %ld/%u retrived:%u overflow:%u", s.c_str(), elapsed, STORED_SIZE * unit->interval(), - // unit->retrived(), unit->overflow()); - - EXPECT_GE(unit->available(), STORED_SIZE); // Check GE not EQ! (because FIFO) - EXPECT_FALSE(unit->empty()); - if (unit->available() == MAX_FIFO_DEPTH) { - EXPECT_TRUE(unit->full()); - } else { - EXPECT_FALSE(unit->full()); - } - uint32_t mask = adc_resolution_bits_table[m5::stl::to_underlying(width)]; - - uint32_t cnt{unit->available() / 2}; - uint32_t left = unit->available() - cnt; - uint32_t air{}, ared{}; - while (cnt-- && unit->available()) { - air += unit->ir(); - ared += unit->red(); - EXPECT_LE(unit->ir(), mask); - EXPECT_LE(unit->red(), mask); - EXPECT_EQ(unit->oldest().ir(), unit->ir()); - EXPECT_EQ(unit->oldest().red(), unit->red()); - - EXPECT_FALSE(unit->empty()); - unit->discard(); - } - EXPECT_NE(air, 0); - EXPECT_NE(ared, 0); - - EXPECT_EQ(unit->available(), left); - EXPECT_FALSE(unit->empty()); - EXPECT_FALSE(unit->full()); - - unit->flush(); - EXPECT_EQ(unit->available(), 0); - EXPECT_TRUE(unit->empty()); - EXPECT_FALSE(unit->full()); - - EXPECT_EQ(unit->ir(), 0); - EXPECT_EQ(unit->red(), 0); + EXPECT_TRUE(unit->startPeriodicMeasurement(Mode::SpO2, range, rate, width, avg, 0x1f, 0x1f)); + collect_and_verify(unit, STORED_SIZE, true, true, mask); } } @@ -472,57 +419,9 @@ void test_periodic_hr(UnitMAX30102* unit) auto s = m5::utility::formatString("HR RNG:%u SR:%u WID:%u AVG:%u", range, rate, width, avg); SCOPED_TRACE(s); - EXPECT_TRUE(unit->startPeriodicMeasurement(Mode::HROnly, range, rate, width, avg, 0x1f, 0x1f)); - auto it = unit->interval() ? unit->interval() : 1; - - auto elapsed = test_periodic(unit, STORED_SIZE, it); - - EXPECT_TRUE(unit->stopPeriodicMeasurement()); - EXPECT_FALSE(unit->inPeriodic()); - - EXPECT_NE(elapsed, 0); - EXPECT_GE(elapsed, STORED_SIZE * unit->interval()); - // M5_LOGI(">>> %s>elapsed: %ld/%u retrived:%u overflow:%u", s.c_str(), elapsed, STORED_SIZE * unit->interval(), - // unit->retrived(), unit->overflow()); - - EXPECT_GE(unit->available(), STORED_SIZE); // Check GE not EQ! (because FIFO) - EXPECT_FALSE(unit->empty()); - if (unit->available() == MAX_FIFO_DEPTH) { - EXPECT_TRUE(unit->full()); - } else { - EXPECT_FALSE(unit->full()); - } - uint32_t mask = adc_resolution_bits_table[m5::stl::to_underlying(width)]; - - uint32_t cnt{unit->available() / 2}; - uint32_t left = unit->available() - cnt; - uint32_t air{}, ared{}; - while (cnt-- && unit->available()) { - air += unit->ir(); - ared += unit->red(); - EXPECT_LE(unit->ir(), mask); - EXPECT_LE(unit->red(), mask); - EXPECT_EQ(unit->oldest().ir(), unit->ir()); - EXPECT_EQ(unit->oldest().red(), unit->red()); - - EXPECT_FALSE(unit->empty()); - unit->discard(); - } - EXPECT_NE(air, 0); - EXPECT_EQ(ared, 0); - - EXPECT_EQ(unit->available(), left); - EXPECT_FALSE(unit->empty()); - EXPECT_FALSE(unit->full()); - - unit->flush(); - EXPECT_EQ(unit->available(), 0); - EXPECT_TRUE(unit->empty()); - EXPECT_FALSE(unit->full()); - - EXPECT_EQ(unit->ir(), 0); - EXPECT_EQ(unit->red(), 0); + EXPECT_TRUE(unit->startPeriodicMeasurement(Mode::HROnly, range, rate, width, avg, 0x1f, 0x1f)); + collect_and_verify(unit, STORED_SIZE, true, false, mask); } } @@ -543,73 +442,16 @@ void test_periodic_multi(UnitMAX30102* unit) SCOPED_TRACE(s); EXPECT_TRUE(unit->writeMultiLEDModeControl(slot1, slot2)); - EXPECT_TRUE(unit->startPeriodicMeasurement()); - auto it = unit->interval() ? unit->interval() : 1; - - auto elapsed = test_periodic(unit, STORED_SIZE, it); - - EXPECT_TRUE(unit->stopPeriodicMeasurement()); - EXPECT_FALSE(unit->inPeriodic()); - - EXPECT_NE(elapsed, 0); - EXPECT_GE(elapsed, STORED_SIZE * unit->interval()); - // M5_LOGI(">>> %s>elapsed: %ld/%u retrived:%u overflow:%u", s.c_str(), elapsed, STORED_SIZE * unit->interval(), - // unit->retrived(), unit->overflow()); - - EXPECT_GE(unit->available(), STORED_SIZE); // Check GE not EQ! (because FIFO) - EXPECT_FALSE(unit->empty()); - if (unit->available() == MAX_FIFO_DEPTH) { - EXPECT_TRUE(unit->full()); - } else { - EXPECT_FALSE(unit->full()); - } - - uint32_t mask = 0x3FFFF; - - uint32_t cnt{unit->available() / 2}; - uint32_t left = unit->available() - cnt; - uint32_t air{}, ared{}; - while (cnt-- && unit->available()) { - air += unit->ir(); - ared += unit->red(); - EXPECT_LE(unit->ir(), mask); - EXPECT_LE(unit->red(), mask); - EXPECT_EQ(unit->oldest().ir(), unit->ir()); - EXPECT_EQ(unit->oldest().red(), unit->red()); - - EXPECT_FALSE(unit->empty()); - unit->discard(); - } - if (slot1 == Slot::IR || slot2 == Slot::IR) { - EXPECT_NE(air, 0); - } else { - EXPECT_EQ(air, 0); - } - - if (slot1 == Slot::Red || slot2 == Slot::Red) { - EXPECT_NE(ared, 0); - } else { - EXPECT_EQ(ared, 0); - } - - EXPECT_EQ(unit->available(), left); - EXPECT_FALSE(unit->empty()); - EXPECT_FALSE(unit->full()); - - unit->flush(); - EXPECT_EQ(unit->available(), 0); - EXPECT_TRUE(unit->empty()); - EXPECT_FALSE(unit->full()); - - EXPECT_EQ(unit->ir(), 0); - EXPECT_EQ(unit->red(), 0); + bool has_ir = (slot1 == Slot::IR || slot2 == Slot::IR); + bool has_red = (slot1 == Slot::Red || slot2 == Slot::Red); + collect_and_verify(unit, STORED_SIZE, has_ir, has_red, 0x3FFFF); } } } // namespace -TEST_P(TestMAX30102, Mode) +TEST_F(TestMAX30102, Mode) { constexpr bool bool_table[] = {true, false}; @@ -645,7 +487,7 @@ TEST_P(TestMAX30102, Mode) } } -TEST_P(TestMAX30102, SpO2Configuration) +TEST_F(TestMAX30102, SpO2Configuration) { SCOPED_TRACE(ustr); @@ -665,11 +507,19 @@ TEST_P(TestMAX30102, SpO2Configuration) test_spo2_config_each(unit.get(), Mode::MultiLED); } -TEST_P(TestMAX30102, LEDCurrent) +TEST_F(TestMAX30102, LEDCurrent) { SCOPED_TRACE(ustr); - for (uint16_t cur = 0; cur < 256; ++cur) { + EXPECT_TRUE(unit->stopPeriodicMeasurement()); + EXPECT_FALSE(unit->inPeriodic()); + + // Boundary values: min, near-min, mid, near-max, max + constexpr uint8_t boundary[] = {0, 1, 127, 128, 254, 255}; + for (auto cur : boundary) { + auto s = m5::utility::formatString("cur:%u", cur); + SCOPED_TRACE(s); + EXPECT_TRUE(unit->writeLEDCurrent(0, cur)); EXPECT_TRUE(unit->writeLEDCurrent(1, cur)); @@ -690,6 +540,7 @@ TEST_P(TestMAX30102, LEDCurrent) EXPECT_FLOAT_EQ(f, mA); } + // Out-of-range (float) EXPECT_FALSE(unit->writeLEDCurrent(0, -0.01f)); EXPECT_FALSE(unit->writeLEDCurrent(1, -0.01f)); @@ -697,7 +548,7 @@ TEST_P(TestMAX30102, LEDCurrent) EXPECT_FALSE(unit->writeLEDCurrent(1, 51.01f)); } -TEST_P(TestMAX30102, MultiLEDMode) +TEST_F(TestMAX30102, MultiLEDMode) { SCOPED_TRACE(ustr); @@ -716,7 +567,6 @@ TEST_P(TestMAX30102, MultiLEDMode) EXPECT_TRUE(unit->writeMultiLEDModeControl(slots[0], slots[1])); EXPECT_TRUE(unit->readMultiLEDModeControl(slot1, slot2)); - ; EXPECT_EQ(slot1, slots[0]); EXPECT_EQ(slot2, slots[1]); } @@ -734,7 +584,7 @@ TEST_P(TestMAX30102, MultiLEDMode) EXPECT_EQ(s2, slot2); } - // All invalid (slots can be set for MutiLED mode only). + // All invalid (slots can be set for MultiLED mode only). constexpr Mode m_table[] = { Mode::SpO2, Mode::HROnly, @@ -768,7 +618,7 @@ TEST_P(TestMAX30102, MultiLEDMode) } } -TEST_P(TestMAX30102, FIFOConfiguration) +TEST_F(TestMAX30102, FIFOConfiguration) { SCOPED_TRACE(ustr); @@ -779,8 +629,8 @@ TEST_P(TestMAX30102, FIFOConfiguration) EXPECT_FALSE(unit->inPeriodic()); for (auto&& fs : fs_table) { - bool ro = rng() % 1; - uint8_t af = rng() % 0x0F; + bool ro = esp_random() % 2; + uint8_t af = esp_random() % 0x0F; auto s = m5::utility::formatString("FS:%u RO:%u AF:%u", fs, ro, af); SCOPED_TRACE(s); EXPECT_TRUE(unit->writeFIFOConfiguration(fs, ro, af)); @@ -795,7 +645,7 @@ TEST_P(TestMAX30102, FIFOConfiguration) } } -TEST_P(TestMAX30102, Temperature) +TEST_F(TestMAX30102, Temperature) { SCOPED_TRACE(ustr); @@ -840,7 +690,20 @@ TEST_P(TestMAX30102, Temperature) } } -TEST_P(TestMAX30102, Revision) +TEST_F(TestMAX30102, TemperatureDataSentinel) +{ + TemperatureData td{}; + + td.raw = {0x80, 0x00}; + EXPECT_FALSE(std::isfinite(td.celsius())); + EXPECT_FALSE(std::isfinite(td.fahrenheit())); + + td.raw = {0xFF, 0x00}; + EXPECT_FLOAT_EQ(td.celsius(), -1.0f); + EXPECT_FLOAT_EQ(td.fahrenheit(), 30.2f); +} + +TEST_F(TestMAX30102, Revision) { SCOPED_TRACE(ustr); @@ -849,7 +712,7 @@ TEST_P(TestMAX30102, Revision) EXPECT_NE(rev, 0); } -TEST_P(TestMAX30102, Reset) +TEST_F(TestMAX30102, Reset) { SCOPED_TRACE(ustr); @@ -915,7 +778,7 @@ TEST_P(TestMAX30102, Reset) EXPECT_EQ(cnt, 0U); } -TEST_P(TestMAX30102, Periodic) +TEST_F(TestMAX30102, Periodic) { SCOPED_TRACE(ustr); @@ -952,8 +815,8 @@ TEST_P(TestMAX30102, Periodic) EXPECT_TRUE(unit->updated()); EXPECT_GE(unit->available(), 10U); - auto retrived = unit->retrived(); - EXPECT_GT(retrived, 0U); + auto retrieved = unit->retrieved(); + EXPECT_GT(retrieved, 0U); EXPECT_FALSE(unit->full()); EXPECT_FALSE(unit->empty()); @@ -964,7 +827,7 @@ TEST_P(TestMAX30102, Periodic) unit->flush(); EXPECT_EQ(unit->available(), 0U); - EXPECT_EQ(unit->retrived(), retrived); // Not clear on flush + EXPECT_EQ(unit->retrieved(), retrieved); // Not clear on flush EXPECT_FALSE(unit->full()); EXPECT_TRUE(unit->empty()); @@ -975,7 +838,7 @@ TEST_P(TestMAX30102, Periodic) EXPECT_TRUE(unit->updated()); EXPECT_EQ(unit->available(), MAX_FIFO_DEPTH); - EXPECT_EQ(unit->retrived(), MAX_FIFO_DEPTH); + EXPECT_EQ(unit->retrieved(), MAX_FIFO_DEPTH); EXPECT_TRUE(unit->full()); EXPECT_FALSE(unit->empty()); EXPECT_GT(unit->overflow(), 0U); @@ -990,7 +853,7 @@ TEST_P(TestMAX30102, Periodic) } } -TEST_P(TestMAX30102, Periodic_SPO2) +TEST_F(TestMAX30102, Periodic_SPO2) { SCOPED_TRACE(ustr); @@ -1001,7 +864,7 @@ TEST_P(TestMAX30102, Periodic_SPO2) test_periodic_spo2(unit.get()); } -TEST_P(TestMAX30102, Periodic_HR) +TEST_F(TestMAX30102, Periodic_HR) { SCOPED_TRACE(ustr); @@ -1012,7 +875,7 @@ TEST_P(TestMAX30102, Periodic_HR) test_periodic_hr(unit.get()); } -TEST_P(TestMAX30102, Periodic_MultiLED) +TEST_F(TestMAX30102, Periodic_MultiLED) { SCOPED_TRACE(ustr);