【M5StickCPlus】パルスオキシメーターを自作する

# はじめに
こんにちは

**Advent Calendar 2022**

先日は東京の生ビールの価格がいかに高いかを調査する記事を投稿しておりました。

https://qiita.com/Miyayan/items/9ff35ac4ce46e17affed

ありがたいことに**TLB Enjoy Developers Advent Calendar 2022**も全日程が埋まり、盛り上がってまいりました。

**では11日目、行きましょう！**

:::note info
この記事は [TLB Enjoy Developers Advent Calendar 2022](https://qiita.com/advent-calendar/2022/tlb-enjoy) の11日目の記事です。
:::

# 目次
- [はじめに](#はじめに)
- [ある日のこと](#ある日のこと)
- [環境](#環境)
- [実装](#実装)
    - [ライブラリインストール](#ライブラリインストール)
    - [Lチカ](#Lチカ)
    - [Hello World](#hello-world)
    - [パルスオキシメータ](#パルスオキシメータ)
        - [ライブラリインストール](#ライブラリインスール)
- [完成](#完成)
- [おわりに](#おわりに)
- [参考文献](#参考文献)

# ある日のこと
いつものごとく職場の同僚とアキバを散策していたところ

同僚「最近マイコン欲が激上がりしている。」

ワイ「は？」

同僚「**最近！マイコン欲が！！激上がりしていルゥ！！！！**」
同僚「**マルツに行くぞ！！！！！！！！**」

〜マルツにて〜

ワイ「マイコンとかほんま**ラズパイ**って名前くらいしか知らんなぁ・・・。」
同僚「有名どころはラズパイだが、**M5Stack**というのが注目を浴びていてぇ。特にM5Stackはモジュールやセンサーが数多く作られていて色いr...」
ワイ「**お！時計バンド付きのやつとかあるやんけ！おもろそうやんけ！！買お！！！（脳死）**」

で、入手したのがこちら
![20221204_194740.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/873426/6b1503f3-6b32-fab4-a2ce-4db8216ca3e6.jpeg)


[M5StickC Plus(腕時計アクセサリー付き)【M5STACK-K016-H】](https://www.marutsu.co.jp/pc/i/2232114/)
[M5Stack用心拍センサユニット【M5STACK-HEARTRATE-UNIT】](https://www.marutsu.co.jp/pc/i/1556078/)
[M5Stack用GROVE互換ケーブル(20cm、5個入り)【M5STACK-CABLE-20】](https://www.marutsu.co.jp/pc/i/1526317/)
※付属のケーブル短そうなんで、いるかなと思って買ったこの互換ケーブルは
**まさかの付属のケーブルと同じ長さでした（草）**

# 概要

本記事ではM5StickCPlusを用いて持ち運べるパルスオキシメーターを自作するところまでを目標とします。


# 環境
- VSCode v1.73.1
    - PlatformIO IDE v2.5.5

方法としては**Arduino IDE**がメジャーらしいですが、
同僚がVSCodeの**PlatformIO**をオススメしてくれたのでそちらで開発を進めます。
個人的には**micro Python**での開発をしてみたかったのですが、
いろいろ調べたところ結構ハードル高かったので、C++で頑張るます・・・

# 実装

**M5StickCPlus**はすでに6軸ジャイロ、RTC、マイク、IRなどを用いた5つの画面が用意されており、
それぞれディスプレイ下の「**M5**」と彫られたAボタンを押して切り替えができるようです。

また、これらのソースコードは「**FactoryTest**」としてオープンソースとしてあるみたいなので、
こちらを参考に実装を進めていこうと思います。

https://github.com/m5stack/M5StickC-Plus/blob/master/examples/FactoryTest/FactoryTest.ino

なるほど

**わからん。**

### ライブラリインストール
PIO Homeのサイドメニュー"Libraries"から**M5StickCplus**をインストールしておきます。

![スクリーンショット 2022-12-11 13.31.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/873426/2bac4cfa-76d3-e9b0-d522-57284a8e837f.png)

```ini
[env:m5stick-c]
platform = espressif32
board = m5stick-c
framework = arduino
lib_deps = 
	m5stack/M5StickCPlus@^0.0.8 // これが追加される
```


## Lチカ
どうも電子工作でははじめの一歩としてLチカから行うようです。

https://dic.nicovideo.jp/a/l%E3%83%81%E3%82%AB


自分自身こういった電子工作は全くの初めてなので全てが新鮮ですね。

```c++
#include <M5StickCPlus.h>

#define LED_PIN 10 // G10

void setup() {
    M5.begin();
    pinMode(LED_PIN, OUTPUT);
}

void loop() {
    digitalWrite(LED_PIN, LOW); // LED点灯
    delay(1000); // 1秒ごとに切り替え
    digitalWrite(LED_PIN, HIGH); // LED消灯
    delay(1000);
}
```

![LチカGIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/873426/4ab9db23-3951-8f5c-6449-0f3a404c6927.gif)

**感動**

自分でコードを書いて、それをビルド→アップロードして機器に実際に反映されるところを見ると既に**達成感でいっぱいです。**

ここまでみてくださった方々、
**本当にありがとうございました！**


## Hello World

というのは冗談です。ごめんなさい。
流石にもう少しつづけます。

ハードはLチカからでしたが、ソフトはやっぱりHello World
画面表示も試してみます。

```c++
#include <M5StickCPlus.h>

void setup() {
    M5.begin();
    M5.Lcd.print("Hello World");
}

void loop() {
}
```

![20221210_175211 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/873426/f401788f-4163-d568-34b1-dd892c5d862d.jpeg)

**ちっさぁ...**

ちなみに最初に**Hello World**を間違えて**シングルクォーテーション**で囲ってしまって表示がバグったのをしばらくハマってたのは内緒です。

![20221211_041110 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/873426/c5f5b26c-519b-82a4-912e-5a7107b76927.jpeg)

## パルスオキシメータ
### ライブラリインストール
追加でPlatformIOにて下記ライブラリのインストールをしておきます

```ini
[env:m5stick-c]
platform = espressif32
board = m5stick-c
framework = arduino
lib_deps = 
	m5stack/M5StickCPlus@^0.0.8
	oxullo/MAX30100lib@^1.2.1
	sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@^1.1.2
```

https://github.com/oxullo/Arduino-MAX30100?utm_source=platformio&utm_medium=piohome

https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library?utm_source=platformio&utm_medium=piohome


それではいよいよメインディッシュです。
看護師時代はイヤというほどみてきたパルスオキシメータ
「酸素の値測りますねぇ〜」
「98%...**ヨシ！**」
「94%...ちょっと深呼吸してみましょか〜」 94%..93%..94%..95% 「**ヨシ！！！！！！**」
「92%...えーーAさんの既往は...特に目立つものなしか、**ヨシ！！！！！！！！！**」

そんな自分がまさかパルスオキシメータを作るとは過去の自分は思いもしませんでしょうね。


まずは素直に公式のスクリプトをコピペしてきます。

https://github.com/m5stack/M5Stack/blob/master/examples/Unit/HEART_MAX30100/MAX30100_RawData/MAX30100_RawData.ino

```c++
/*
*******************************************************************************
* Copyright (c) 2022 by M5Stack
*                  Equipped with M5Core sample source code
*                          配套  M5Core 示例源代码
* Visit for more information: https://docs.m5stack.com/en/core/gray
* 获取更多资料请访问: https://docs.m5stack.com/zh_CN/core/gray
*
* Describe: Heart Rate.  心率
* Date: 2021/8/16
*******************************************************************************
  Please connect to Port A, HEART Unit obtains the original value of the heart
rate detection and displays it on the screen. The user can also use the Arduino
IDE Serial Plotter to view the line graph output. 请连接端口A,HEART
Unit获取心率检测的原始值并显示在屏幕上。用户还可以使用Arduino IDE Serial
Plotter查看线图输出。
*/

#include <M5StickCPlus.h>
#include <FS.h>
#include <SPI.h>
#include <Wire.h>

#include "MAX30100.h"

#define SAMPLING_RATE   MAX30100_SAMPRATE_100HZ
#define IR_LED_CURRENT  MAX30100_LED_CURR_24MA
#define RED_LED_CURRENT MAX30100_LED_CURR_27_1MA
#define PULSE_WIDTH     MAX30100_SPC_PW_1600US_16BITS
#define HIGHRES_MODE    true

MAX30100
sensor;  // Instantiate a MAX30100 sensor class.  实例化一个MAX30100传感器类

void setup() {
    M5.begin();        // Init M5Stack.  初始化M5Stack
    M5.Axp.begin();  // Init power  初始化电源模块
    Serial.print("Initializing MAX30100..");

    while (!sensor.begin()) {  // Initialize the sensor.  初始化传感器
        M5.Lcd.setTextFont(4);
        M5.Lcd.setCursor(50, 100, 4);
        M5.Lcd.println("Sensor not found");
        delay(1000);
    }
    M5.Lcd.fillScreen(BLACK);
    // Set up the wanted parameters.  设置所需的参数
    sensor.setMode(MAX30100_MODE_SPO2_HR);
    sensor.setLedsCurrent(IR_LED_CURRENT, RED_LED_CURRENT);
    sensor.setLedsPulseWidth(PULSE_WIDTH);
    sensor.setSamplingRate(SAMPLING_RATE);
    sensor.setHighresModeEnabled(HIGHRES_MODE);
}

void loop() {
    uint16_t ir, red;
    sensor.update();  //更新传感器读取到的数据
    while (sensor.getRawValues(&ir, &red)) {  //如果获取到数据
        M5.Lcd.setTextFont(4);
        M5.Lcd.setCursor(100, 100, 4);
        M5.Lcd.printf("IR:%d               ", ir);
        M5.Lcd.setCursor(100, 130, 4);
        M5.Lcd.printf("RED:%d              ", red);
    }
}
```

include文を**M5StickCPlus**用に変更しています
```c++
#include <M5StickCPlus.h>
#include <FS.h>
#include <SPI.h>
#include <Wire.h>
```

Wire.hより下は、**platform.ini**を下記のように追記してもよさそうですね。
```ini
[env:m5stick-c]
platform = espressif32
board = m5stick-c
framework = arduino
lib_deps = 
	m5stack/M5StickCPlus@^0.0.8
	oxullo/MAX30100lib@^1.2.1
	sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@^1.1.2
	Wire
	FS
	SPI
```

**M5StackはPowerがある**ようですが、**StickCではAxp**のようなのでsetupも変えておきます
```c++
M5.Axp.begin();  // Init power  初始化电源模块
```

![IRGIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/873426/826dfdd0-0a91-5379-d417-02c5aa983bf2.gif)

おもてたんとちゃう。

なんかIRとREDという値がひたすらに動き回ってます。

SpO2は赤色光（RED）と赤外光（IR）の２種類の光の透過率で酸化Hbの割合を算出しているみたいですね。
コニカミノルタの知恵袋が非常に参考になりました。

https://www.konicaminolta.jp/healthcare/knowledge/details/principle.html

看護師時代では脳死で指に挟めばわかるのであんまし原理とか気にしたことなかったので、
今改めて原理がわかると非常に面白いもんですね。

さて、いろいろ計算するのも面倒なので、今回はMAX30100libのMAX30100_PulseOximeter.hのPluseOximeterクラスを使用します

```c++
float PulseOximeter::getHeartRate()
{
    return beatDetector.getRate();
}

uint8_t PulseOximeter::getSpO2()
{
    return spO2calculator.getSpO2();
}
```

ライブラリで既に関数が用意されてるので非常に便利ですね。
下記に全ソースを格納しておきます。

<details><summary>main.cpp</summary><div>

```c++:main.cpp
#include <M5StickCPlus.h>
#include "MAX30100_PulseOximeter.h"

#define LED_PIN 10 // G10
#define REPORTING_PERIOD_MS 1000
#define MAX_SPO2 100

PulseOximeter pox;
uint32_t tsLastReport = 0;

void pulseOximeter()
{
		pox.update();

		// 心拍数と酸化度を非同期でシリアルにダンプ
		// どちらも0は "無効 "
		if (millis() - tsLastReport > REPORTING_PERIOD_MS) {
				M5.Lcd.fillScreen(BLACK);
				M5.Lcd.setCursor(3, 5, 1);
				M5.Lcd.print("SpO2 \n");
				M5.Lcd.print((pox.getSpO2() > MAX_SPO2)? MAX_SPO2 : pox.getSpO2() );
				M5.Lcd.println(" %\n");
				M5.Lcd.print("PR \n");
				M5.Lcd.print(pox.getHeartRate());
				M5.Lcd.println(" bpm");
				
				tsLastReport = millis();
		}
}

// パルス検出時Lチカ
void onBeatDetected()
{
    digitalWrite(LED_PIN, LOW);
    delay(1);
    digitalWrite(LED_PIN, HIGH);
}

void setup()
{
    M5.begin();
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(LED_PIN, HIGH);
    M5.Axp.ScreenBreath(10);
    M5.Lcd.setRotation(3);
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setTextSize(3);
    M5.Lcd.print("MAX30100");
    M5.Lcd.setCursor(0, 40);
    
    Serial.begin(115200);
    Serial.print("Initializing pulse oximeter");

    // パルスオキシメータインスタンスの初期化
    if (!pox.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }

    // IR LEDのデフォルトの電流は50mAですが、以下の行をコメントアウト解除することで変更可能
	// 利用可能なすべてのオプションについては、MAX30100_Registers.hを確認
    // pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);

    // パルス検出用コールバック
    pox.setOnBeatDetectedCallback(onBeatDetected);
		M5.Lcd.fillScreen(BLACK);
}

void loop()
{
		pulseOximeter();

		M5.update();
    delay(50);
}
```

</div></details>

とりあえず前述の関数でSpO2とPR（脈拍数）は取得できました。
しかしSpO2が100%を超えることがあり、医療現場でそれはあり得ないので、下記のように条件式を追記しておきました。

```c++
M5.Lcd.print((pox.getSpO2() > MAX_SPO2)? MAX_SPO2 : pox.getSpO2() );
```


# 完成

完成品は私のTwitterにて動画を上げております。

https://twitter.com/miyaengineer/status/1601907637830160389?s=20

付属の時計バンドは**過去に経験したことのない固さ**
Groveケーブルは間違えて購入したので、長さが足りず**絶対にジャンケンでパーを出せない仕様**

そういやセンサーどうやって指につけようと考えた結果、
**便利そうな穴が２つ空いてたので家にあった結束バンドで対処**

あらゆる面で便利すぎる現代への**反骨精神**が垣間見られて、
逆張りで**オシャレなシロモノ**が出来上がりました。


# おわりに

ここまでお読みいただきありがとうございました！！
マイコン、初めてのチャレンジですが、機器が動くのを見るとやはり少なからず達成感はありますし、面白いですね。
課題は結構ありますが、今回の記事ではここまでとしたいと思います。

今回紹介したハードも買った時は安い！と思ってたんですが、
**販売当初は数百〜数千円安かった**らしいです・・・。
半導体不足解消でまた値段が落ち着いたら色々なモジュール買って遊びたいですね。


とりあえず、

**ナースリーでパルスオキシメータ買いましょうか**

https://www.nursery.co.jp/cat/goods/oximeter

**明日は@ko27 さんの記事です！お楽しみに！！**

# 参考文献

https://github.com/m5stack/M5Stack/blob/master/examples/Unit/HEART_MAX30100/MAX30100_RawData/MAX30100_RawData.ino

https://docs.m5stack.com/en/core/m5stickc_plus

https://www.konicaminolta.jp/healthcare/knowledge/details/principle.html

https://101010.fun/iot/m5stickc-plus-firststep.html

https://kinneko.fanbox.cc/posts/991824