M5Stackで心拍数モニター

Heart rate monitor ・ 心率监测

指先にセンサーを当てて心拍数と血中酸素濃度を測定します。

この構成をエディタで開く・編集する →

部品リスト(BOM)

部品役割ポートリンク・価格
M5Stack Core2Controllergrove_i2c, grove_analog, grove_uart, mbusofficial page — $46.9
Unit HeartHeart rate / SpO2 sensorgrove_i2cofficial page — $9.95

配線

作り方

M5Stack Core2とUnit Heart(MAX30100)で作る、指先で心拍数と血中酸素濃度(SpO2)を測定する装置です。センサーに指を置くだけで、リアルタイムに測定値を画面に表示します。

  1. 部品を用意する — 用意するもの:M5Stack Core2本体、Unit Heart(MAX30100センサーユニット)、Groveケーブル(4ピン、赤色コネクタのI2C用)、Core2とパソコンをつなぐUSB-Cケーブル。
  2. Groveケーブルでつなぐ — Unit HeartはI2C接続です。Groveケーブルの一端をUnit HeartのGroveポートに差し込みます。もう一端をCore2の赤いPort.A(I2Cポート)に差し込みます。コネクタは一方向にしか入らないので、無理に押し込まないでください。
  3. パソコンにつないでMicroPythonを書き込む — USB-CケーブルでCore2をパソコンにつなぎます。UIFlowやThonnyなどのツールで、Core2にMicroPythonファームウェアが入っていることを確認し、下のコードをデバイスにコピーして実行します。
  4. 指を置いて測定する — コードを実行すると、画面に「Place finger」と表示されます。Unit Heartのセンサー部分(LEDが光る窓)に指をそっと置きます。数秒待つと、心拍数(BPM)とSpO2(%)が表示されます。
  5. 測定値を確認する — 画面に表示される数値が安定するまで指を置き続けます。心拍数は通常60~100、SpO2は95%以上が正常範囲です。指を離すと「No finger」と表示され、測定が停止します。

サンプルコード(micropython)

# M5Stack Core2 + Unit Heart (MAX30100) fingertip heart rate & SpO2 monitor
# Wiring: Unit Heart -> Core2 red Port.A (I2C)
import time
from machine import Pin, I2C
from m5stack import lcd

# Initialize I2C on Port.A (Core2: SDA=21, SCL=22)
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)

# MAX30100 I2C address is 0x57
MAX30100_ADDR = 0x57

# Register addresses
REG_INTR_STATUS_1 = 0x00
REG_INTR_STATUS_2 = 0x01
REG_INT_ENABLE_1 = 0x02
REG_INT_ENABLE_2 = 0x03
REG_FIFO_WR_PTR = 0x04
REG_OVF_COUNTER = 0x05
REG_FIFO_RD_PTR = 0x06
REG_FIFO_DATA = 0x07
REG_MODE_CONFIG = 0x09
REG_SPO2_CONFIG = 0x0A
REG_LED_CONFIG = 0x0C
REG_TEMP_INTEGER = 0x16
REG_TEMP_FRACTION = 0x17
REG_REVISION_ID = 0xFE
REG_PART_ID = 0xFF

def write_reg(reg, value):
    i2c.writeto_mem(MAX30100_ADDR, reg, bytes([value]))

def read_reg(reg):
    return i2c.readfrom_mem(MAX30100_ADDR, reg, 1)[0]

def read_fifo():
    data = i2c.readfrom_mem(MAX30100_ADDR, REG_FIFO_DATA, 4)
    # data[0:2] = IR, data[2:4] = Red
    ir = (data[0] << 8) | data[1]
    red = (data[2] << 8) | data[3]
    return ir, red

def setup_sensor():
    # Reset
    write_reg(REG_MODE_CONFIG, 0x40)
    time.sleep(0.1)
    # Set mode to SpO2/HR (0x03)
    write_reg(REG_MODE_CONFIG, 0x03)
    # Configure SPO2: 100Hz, 1600us pulse width, 14.2mA LED current
    write_reg(REG_SPO2_CONFIG, 0x27)  # 0b00100111
    # Enable all interrupts
    write_reg(REG_INT_ENABLE_1, 0xE0)  # A_FULL, PPG_RDY, ALC_OVF
    write_reg(REG_INT_ENABLE_2, 0x00)
    # Set LED currents: IR=50mA, Red=50mA
    write_reg(REG_LED_CONFIG, 0x7F)  # 0b01111111
    # Clear FIFO
    write_reg(REG_FIFO_WR_PTR, 0x00)
    write_reg(REG_OVF_COUNTER, 0x00)
    write_reg(REG_FIFO_RD_PTR, 0x00)

def read_temp():
    integer = read_reg(REG_TEMP_INTEGER)
    fraction = read_reg(REG_TEMP_FRACTION)
    return integer + fraction * 0.0625

# Simple heart rate detection (basic peak detection)
class HeartRateMonitor:
    def __init__(self):
        self.buffer = []
        self.bpm = 0
        self.spo2 = 0
        self.last_beat = 0
        self.ir_prev = 0
        self.threshold = 50000  # adjust based on signal
        
    def process(self, ir, red):
        # Simple peak detection
        if ir > self.threshold and self.ir_prev <= self.threshold:
            now = time.ticks_ms()
            if self.last_beat > 0:
                interval = now - self.last_beat
                if 300 < interval < 2000:  # 30-200 BPM
                    self.bpm = 60000 // interval
            self.last_beat = now
        self.ir_prev = ir
        # SpO2 estimation (very basic)
        if ir > 0 and red > 0:
            ratio = red / ir
            self.spo2 = 110 - 25 * ratio  # rough approximation
            if self.spo2 > 100:
                self.spo2 = 100
            if self.spo2 < 0:
                self.spo2 = 0
        else:
            self.spo2 = 0

setup_sensor()
lcd.clear()
lcd.print('Place finger...', 10, 20)
monitor = HeartRateMonitor()

while True:
    # Check interrupt status
    status = read_reg(REG_INTR_STATUS_1)
    if status & 0x40:  # PPG_RDY
        # Read all available samples (up to 32)
        for _ in range(32):
            ir, red = read_fifo()
            if ir > 0 or red > 0:
                monitor.process(ir, red)
        # Display
        lcd.clear()
        if monitor.bpm > 0:
            lcd.print('HR: %d BPM' % monitor.bpm, 10, 20)
            lcd.print('SpO2: %d %%' % monitor.spo2, 10, 60)
        else:
            lcd.print('No finger', 10, 20)
        # Clear interrupt
        write_reg(REG_INTR_STATUS_1, 0xFF)
    time.sleep(0.01)

コツ・安全上の注意

この作り方はAIが構成から生成したものです(未検証)。通電前に配線とコードをご確認ください。

この構成はAIが実在するM5Stack部品から設計し、ポート互換をサーバで検証したものです。編集・共有できます。

エディタで開く →

UnitKit は M5Stack® 製品向けの非公式ツール。作りたいものを書くと、部品リストと配線図が出ます。