M5Stack ( https://ja.wikipedia.org/wiki/M5Stack ) と心拍センサーを使ってリアルタイムに血中酸素飽和度と心拍数を測定・表示、おまけ機能として心拍パルスを可視化するプログラムを作成してみました。
M5Stackが5200円くらい、心拍センサーが1300円くらい。必要なソフトウェアはすべて無償で入手できる物なので6500円ほどで血中酸素飽和度と心拍数を計測できる装置が自作できます。
このプログラムを使うとパルスオキシメーターのような機能を使うことができますが、医療機器認証を受けた医療機器ではないので精度や信頼性・安定性が保証されません。
プログラムの作成時、作者が日常的に使用しているdretec社のOX-101 パルスオキシメーター(医療機器認証を受けた医療機器。医療機器認証番号: 第227AKBZX00100000号)で計測した値と比較して近い値が取得できていることを確認できましたが、センサーまたはセンサーを制御するライブラリに起因していると思われる事象により数値が安定しないケースを確認しています。
また、PCにUSB接続した状態でプログラムを実行すると高確率でデータの読み落としなどの事象が発生することも確認しています。
パルスオキシメーターについては下記のサイトで詳しく説明されていますのでご一読ください。
このプログラムは、心拍センサーから取得した酸素飽和度(SpO2)と心拍数(PR)をM5StackのLCD画面に表示するプログラムです。
酸素飽和度の値に応じて背景色・文字色を変え状態把握をしやすくしています。
センサーが心拍を検知したタイミングでビープ音を鳴らし、心拍パルス値をLCD画面下部にグラフ表示しています。
※心拍センサーからの値を取得するために使用しているArduinoライブラリ『Arduino-MAX30100』では心拍パルス値を外部から取得することができませんが、グラフ表示のためライブラリを修正しパルス値を取得できるようにしています。
スイッチサイエンスさんは、14時までに注文すると当日発送対応、各種支払い対応なのでとても便利でおすすめです。
上記機材のほかにM5Stack本体に給電をするためのUSB-CケーブルやUSBアダプタなどが必要になります。
ほかにVisual Studio Codeを動かすためのWindowsやmacOSなどが必要になります。
Visual Studio CodeやPlatformIO IDEのセットアップ手順については、インターネットで検索すると詳しい手順を解説しているサイトがたくさんあるので、お使いのOSにあわせた解説サイトを参照してください。
ダウンロードしたソースコードをVisual Studio Codeで読み込み、PlatformIO IDE上でビルド、M5Stack本体にアップロードしてください。
M5Stack本体のポートAとセンサーをGroveケーブルで接続します。
M5Stackシリーズでは、USB-Cポートのとなりについている差し込み口がポートAになります。
Groveケーブルは端子に凹凸がついているのでポート側の凹凸にあわせて差し込んでください。
接続後、M5Stackの電源を入れるとプログラムが起動し計測した血中酸素飽和度(SpO2/単位:%)、心拍数(PR/単位:bpm)、心拍パルスを表示します。
血中酸素飽和度により表示の色分けを行います。
SpO2(%) |
背景色 |
文字色 |
96以上 |
黒 |
白 |
90~95 |
黄色 |
黒 |
75~89 |
ピンク |
黒 |
74以下 |
赤 |
白 |
※SpO2値が0や100より大きな値など無効な場合は、背景色:黒、文字色:白で表示されます。
心拍数が正常値の範囲外の値の場合、心拍数の隣に「(!)」を表示します。正常値の範囲はプログラム内のマクロで定義しているので年齢や性別などにあわせて調整してください。
/* M5Stackと心拍センサユニットで酸素飽和度と心拍数、心拍パルスを取得し可視化するプログラム Copyright (C) 2021 SHIRAFU Makoto <shirafu@gmail.com> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. ■必要なハードウェア ・M5Stack シリーズ ESP32 Basic Core IoT Development Kit ESP32 Basic Core IoT Development Kit ・M5Stack用心拍センサユニット Mini Heart Rate Unit (MAX30100) Pulse Oximeter Mini Heart Rate Unit (MAX30100) Pulse Oximeter ■必要なソフトウェア ・Visual Studio Code https://code.visualstudio.com ・PlatformIO https://platformio.org/install/ide?install=vscode ■使用しているArduinoライブラリ ・M5Stack Library https://github.com/m5stack/M5Stack ・Arduino-MAX30100 https://github.com/oxullo/Arduino-MAX30100 ■ソースの構成 ・main.cpp ... 可視化するプログラム本体 ・MAX30100_PulseOximeter.cpp ... Arduino-MAX30100より心拍パルスをとれるように修正 ・MAX30100_PulseOximeter.h ... Arduino-MAX30100より心拍パルスをとれるように修正 */ #include <Arduino.h> #include <M5Stack.h> #include <Wire.h> #include "MAX30100_PulseOximeter.h" // 酸素飽和度と心拍数の更新間隔(ms) #define STATUS_INTERVAL_MS 1000 // 心拍グラフの更新間隔(ms) #define GRAPH_INTERVAL_MS 15 // 心拍数を正常とする下限値 #define NORMAL_HEART_RATE_LOWER 60 // 心拍数を正常とする上限値 #define NORMAL_HEART_RATE_UPPER 100 // 液晶の横サイズ #define LCD_WIDTH 320 // 液晶の縦サイズ #define LCD_HEIGHT 240 // ステータス表示の横サイズ #define IMG_WIDTH 320 // ステータス表示の縦サイズ #define IMG_HEIGHT 140 // パルスオキシメータ PulseOximeter pox; // 酸素飽和度 uint8_t spo2 = 0; // 心拍数 uint8_t hr = 0; // 心拍パルス値 float pulse; // 直前のステータス表示更新 uint32_t tsLastReport = 0; // 直前のグラフ表示更新 uint32_t tsLastPulse = 0; // 心拍グラフの描画位置(X) int cx = 0; // 心拍グラフの直前の描画位置(X) int px = -1; // 心拍グラフの直前の描画位置(Y) int py = -1; // ステータス表示のオフスクリーンバッファ TFT_eSprite statusimg = TFT_eSprite(&M5.Lcd); // // ステータス(酸素飽和度と心拍数)の更新 // void updateStatus() { // 背景色 uint32_t bg; // 前景色 uint32_t fg = TFT_WHITE; // SPO2の値から色を決定 // 96 以上: 黒 / 白 // 90 ~ 95: 黄色 / 黒 // 75 ~ 89: ピンク / 黒 // 74以下: 赤 / 白 if (spo2 >= 96) { bg = TFT_BLACK; } else if (spo2 >= 90) { bg = TFT_YELLOW; fg = TFT_BLACK; } else if (spo2 >= 75) { bg = TFT_PINK; fg = TFT_BLACK; } else if (spo2 > 0) { bg = TFT_RED; } else { bg = TFT_BLACK; } // オフスクリーンバッファを背景色で塗りつぶして前景色を設定 statusimg.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT, bg); statusimg.setTextColor(fg); // 見出しを描画 statusimg.setCursor(0, 0); statusimg.setTextSize(3); statusimg.print("SPO2(%)"); statusimg.setCursor(0, 110); statusimg.setTextSize(3); statusimg.print("PR(bpm)"); // 酸素飽和度と心拍数を描画 if (spo2 > 0 && spo2 <= 100) { statusimg.setCursor(120, 40); statusimg.setTextSize(7); statusimg.print(spo2); statusimg.setCursor(160, 110); statusimg.setTextSize(3); statusimg.print(hr); // 心拍数の警告 if (hr < NORMAL_HEART_RATE_LOWER || hr > NORMAL_HEART_RATE_UPPER) { statusimg.setCursor(250, 110); statusimg.print("(!)"); } } else { // 酸素飽和度が無効の場合、酸素飽和度と心拍数を表示しない statusimg.setCursor(120, 40); statusimg.setTextSize(7); statusimg.print("--"); statusimg.setCursor(160, 110); statusimg.setTextSize(3); statusimg.print("--"); } // ステータスの表示更新 statusimg.pushSprite(0, 0, TFT_TRANSPARENT); } // // 心拍グラフの更新 // void updateGraph(float value) { // 以前の描画を消して M5.Lcd.drawLine(cx, 141, cx, LCD_HEIGHT, TFT_BLACK); // 心拍パルスの値を適当な位置に描画するよう算出(今回座標のY) int cy = 220 - (value + 50) / 5; if (cy <= 141) { cy = 141; } // 前回座標(X)がマイナス値の場合、前回座標(Y)を今回座標(Y)で上書き if (px < 0) { py = cy; } // 前回座標から今回座標まで新しい線を描画 M5.Lcd.drawLine(px, py, cx, cy, CYAN); // 描画座標を待避 px = cx++; py = cy; if (cx >= LCD_WIDTH) { cx = 0; px = 0; } } // // 心拍検知ハンドラ // void beatDetectedHandler() { // 心拍検知したときにBEEP M5.Speaker.tone(1200, 10); updateStatus(); } // // セットアップ // void setup() { M5.begin(); Serial.begin(115200); // オフスクリーンバッファ初期化 statusimg.setColorDepth(4); statusimg.createSprite(IMG_WIDTH, IMG_HEIGHT); statusimg.setSwapBytes(false); // センサーの初期化 if (!pox.begin()) { M5.Speaker.beep(); M5.Lcd.println("Sensor initialization failed"); for(;;); } // センサーのLED設定(デフォルト値の50mAではうまくとれない) pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA); // センサーの心拍検知ハンドラ設定 pox.setOnBeatDetectedCallback(beatDetectedHandler); M5.Lcd.drawLine(0, 140, LCD_WIDTH, 140, TFT_WHITE); } // // メインループ // void loop() { // M5Stackとセンサーの状態を更新 // (M5Stackの状態更新をしないとBEEPが止まらなくなる) m5.update(); pox.update(); uint32_t now = millis(); // 前回更新から STATUS_INTERVAL_MS ミリ秒分経過していたらステータス更新 if (now - tsLastReport > STATUS_INTERVAL_MS) { hr = (uint8_t)pox.getHeartRate(); spo2 = pox.getSpO2(); updateStatus(); tsLastReport = now; } // 前回更新から GRAPH_INTERVAL_MS ミリ秒分経過していたらグラフ更新 if (now - tsLastPulse > GRAPH_INTERVAL_MS) { pulse = pox.getFilteredPulseValue(); updateGraph(pulse); tsLastPulse = now; } }
[…] 2021/08/23に公開した『M5Stackと心拍センサーを使って血中酸素飽和度と心拍数を見える化するプロ…』(いわゆるパルスオキシメーター)を改良し、計測したデータをネットワーク上のMQTTサーバを介してモニタリングできるようにしました。 […]