インフルエンザ流行に備える – M5StickCでパルスオキシメーターを自作した話

みなさん、こんにちは。

2026年があけていきなりの冬本番。ここ数日、寒さがかなり増してきました。今年のインフルエンザは例年以上に猛威を振るっていますよね。
新年の初詣に足を運んだ際も、境内には咳き込む人の姿が散見され、人混みの中で「もし自分が今、ウイルスをもらっていたら……」と少し不安がよぎることもありました。

そこでふと思いついたのが、「自分の健康状態を客観的なデータとして把握する手段」を自前で用意することでした。

 


 

医療現場の知恵を、デスクトップに

 

呼吸器疾患の重症度を測るうえで、今や医療現場の必需品となっているのがパルスオキシメーターです。指先を挟むだけで、血液中の酸素飽和度(SpO2)と心拍数を瞬時に算出してくれるこのデバイス。

もし高熱や激しい咳に見舞われた際、自分の肺がどれだけ効率的に酸素を取り込めているかを知ることは、受診のタイミングを判断する重要な指標になります。

実はこのプロジェクト、思い立ったのはコロナ禍が始まった2020年でした。当時はデバイスが市場から枯渇していたため、「ないなら作ろう」と決意したのですが、早いものであれから5年。半分忘れていたのですが、先日ようやくセンサーを確保し、実装に漕ぎ着けました。

今さら感は否めませんが(笑)、その有用性は2026年の今も変わりません。

 


 

ハードウェア構成 – M5StickC × 心拍センサユニット

 

今回の選定パーツは、エンジニアにはお馴染みの構成です。

  • マイコンボード:M5StickC
    ESP32ベースのArduino互換ボードです。このサイズ感でカラー液晶、バッテリーまで内蔵されているため、ウェアラブルな試作には最適です。
  • センサーユニット:MAX30100搭載 心拍センサユニット(U029)
    赤色光と赤外光の反射を利用して血中のヘモグロビン状態を測定するGROVE互換端子搭載のセンサユニットです。医療機器に採用されているものと同等の原理で動作します。

年末のうちにSwitchScienceでセンサーを調達しましたが、コストは2,000円程度。市販の医療機器グレードの製品と比較すれば、圧倒的に安価に、そして「ハッカブル」な環境が手に入ります。

 


 

実装プロセス – GitHub Copilotとのペアプロ

 

開発環境はArduino IDE。かつてはMAX30100のレジスタ制御やノイズ除去アルゴリズムの実装に骨が折れることもありましたが、既にM5Stack用のサンプルコードが公開されており、なおかつGitHub Copilotという強力な味方もいます。

実装の大まかなフローは以下の通りです。

  1. I2C通信の初期化:MAX30100をスキャンし、通信を確立。
  2. RAWデータの取得:IR(赤外線)とRED(赤色光)の反射値をサンプリング。
  3. 演算処理:ライブラリを介して心拍数とSpO2を算出。
  4. UI表示:M5StickCのTFT液晶にリアルタイムに描画。

デバッグ中に2、3箇所の修正を挟みましたが、Copilotとの対話によって、ものの数十分でメインロジックが完成しました。2026年の開発体験は、本当にスムーズになったと感じます。

 


 

動作検証 – 数値の妥当性

 

ビルドして実機に流し込み、実際に指を置いてみます。数秒のキャリブレーションを経て、液晶に浮かび上がったのは「HR: 53 / SpO2: 96%」の文字。

  • 心拍数:53bpm
  • 酸素飽和度:96%

数値の時々取りこぼしが時々発生するものの、まあまあ安定しており、私の健康状態をそれなりに反映しているようです。もちろん医療用機器ほどの精度は望むべくもありませんが、「普段の自分のベースライン」を知るためのツールとしては十分な性能を発揮してくれています。

 


 

「自作」がもたらす健康管理の新しい形

 

現在、この自作パルスオキシメーターはデスクの片隅に常駐しています。

単に数値を測るだけでなく、体調に変化を感じた時にその推移をデータとして追える。そうした「自分の健康を自分でモニタリングする習慣」は、これからの時代、さらに重要になってくるはずです。

もしArduinoプログラミングやIoTにご興味があれば、ぜひ挑戦してみてください。コードはGitHubに公開していますが、簡単なコードなのでこちらにも全行公開しておきます。

PulseOximeter_StickC.ino

/*
Arduino-MAX30100 oximetry / heart rate integrated sensor library
Copyright (C) 2016  OXullo Intersecans <x@brainrapers.org>
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 <http://www.gnu.org/licenses/>.
*/

#include <Wire.h>
#include "MAX30100_PulseOximeter.h"
#include <M5StickC.h>

#define REPORTING_PERIOD_MS     1000

// PulseOximeter is the higher level interface to the sensor
// it offers:
//  * beat detection reporting
//  * heart rate calculation
//  * SpO2 (oxidation level) calculation
PulseOximeter pox;

uint32_t tsLastReport = 0;

// Callback (registered below) fired when a pulse is detected
void onBeatDetected()
{
    Serial.println("Beat!");
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.println("Beat!");
}

void setup()
{
    Serial.begin(115200);
    M5.begin();
    M5.Lcd.setRotation(3);
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setTextColor(WHITE);

    Serial.print("Initializing pulse oximeter..");

    // Initialize the PulseOximeter instance
    // Failures are generally due to an improper I2C wiring, missing power supply
    // or wrong target chip
    if (!pox.begin()) {
        Serial.println("FAILED");
        M5.Lcd.setTextSize(2);
        M5.Lcd.setCursor(10, 30);
        M5.Lcd.println("FAILED");
        delay(1000);
    } else {
        Serial.println("SUCCESS");
    }

    // The default current for the IR LED is 50mA and it could be changed
    //   by uncommenting the following line. Check MAX30100_Registers.h for all the
    //   available options.
    // pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);

    // Register a callback for the beat detection
    pox.setOnBeatDetectedCallback(onBeatDetected);
}

void loop()
{
    // Make sure to call update as fast as possible
    pox.update();

    // Asynchronously dump heart rate and oxidation levels to the serial
    // For both, a value of 0 means "invalid"
    if (millis() - tsLastReport > REPORTING_PERIOD_MS) {
        M5.Lcd.fillScreen(BLACK);
        M5.Lcd.setTextSize(2);
        M5.Lcd.setCursor(0, 20);
        M5.Lcd.printf("HR:%03.0f\n", pox.getHeartRate());
        M5.Lcd.setCursor(0, 40);
        M5.Lcd.printf("SpO2:%03d%%", pox.getSpO2());
        tsLastReport = millis();
    }
}

今後の拡張案としては、TVOC/eCO2 ガスセンサーユニットを追加してお部屋の二酸化炭素濃度を監視したり、測定値をクラウドに飛ばして時系列グラフ化するのも面白いかもしれませんね。

技術を楽しみつつ、健康も守る。そんな「エンジニアらしい冬の過ごし方」はいかがでしょうか。

 

本日も最後までお読みいただきありがとうございました。

それでは、よいIoTライフを!

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール