概要
- 新型コロナの影響でヘルスケア関連のデバイスの需要が高まっている
- maxim integrated社から出ているMAX3010xシリーズの最新版を試してみた
- まず開発キット(MAXREFDES117)を入手し、動作を確かめてみる
用意するもの
- Raspberry Pi4 (3でもおそらく大丈夫)
- Raspberry Pi用のケース
- Raspberry Pi用のヒートシンク
- Raspberry Pi4 対応 電源セット(5V 3.0A)
- MAX30102開発キット(MAXREFDES117)
https://www.maximintegrated.com/jp/design/reference-design-center/system-board/6300.html - ブレッドボード
- ジャンパーワイヤー各種
環境構築
- Raspberry PiのOSをセットアップし、I2Cを有効化する
https://www.raspberrypi.org/downloads/
https://www.indoorcorgielec.com/resources/raspberry-pi/raspberry-pi-i2c/
組み立て
Raspberry Piと開発キット(MAXREFDES117)を以下のように接続する
※Raspberry Piのピン配置は割愛します。
プログラミング
- まず動作確認のため、既存のリポジトリを使って動かしてみた
https://github.com/vrano714/max30102-tutorial-raspberrypi
メインプログラム(GitHubより転載)
maxim integrated社の公式サイトにArduino用のサンプルコードがある。それをもとに開発された模様。
(サンプルコードのダウンロードは公式サイトにアカウント作成、ユーザー登録を行う必要がある)
/home/pi/guest/max30102.py
# -*-coding:utf-8-*-
# this code is currently for python 2.7
from __future__ import print_function
from time import sleep
import RPi.GPIO as GPIO
import smbus
# i2c address-es
# not required?
I2C_WRITE_ADDR = 0xAE
I2C_READ_ADDR = 0xAF
# register address-es
REG_INTR_STATUS_1 = 0x00
REG_INTR_STATUS_2 = 0x01
REG_INTR_ENABLE_1 = 0x02
REG_INTR_ENABLE_2 = 0x03
REG_FIFO_WR_PTR = 0x04
REG_OVF_COUNTER = 0x05
REG_FIFO_RD_PTR = 0x06
REG_FIFO_DATA = 0x07
REG_FIFO_CONFIG = 0x08
REG_MODE_CONFIG = 0x09
REG_SPO2_CONFIG = 0x0A
REG_LED1_PA = 0x0C
REG_LED2_PA = 0x0D
REG_PILOT_PA = 0x10
REG_MULTI_LED_CTRL1 = 0x11
REG_MULTI_LED_CTRL2 = 0x12
REG_TEMP_INTR = 0x1F
REG_TEMP_FRAC = 0x20
REG_TEMP_CONFIG = 0x21
REG_PROX_INT_THRESH = 0x30
REG_REV_ID = 0xFE
REG_PART_ID = 0xFF
# currently not used
MAX_BRIGHTNESS = 255
class MAX30102():
# by default, this assumes that physical pin 7 (GPIO 4) is used as interrupt
# by default, this assumes that the device is at 0x57 on channel 1
def __init__(self, channel=1, address=0x57, gpio_pin=7):
print("Channel: {0}, address: 0x{1:x}".format(channel, address))
self.address = address
self.channel = channel
self.bus = smbus.SMBus(self.channel)
self.interrupt = gpio_pin
# set gpio mode
GPIO.setmode(GPIO.BOARD)
GPIO.setup(self.interrupt, GPIO.IN)
self.reset()
sleep(1) # wait 1 sec
# read & clear interrupt register (read 1 byte)
reg_data = self.bus.read_i2c_block_data(self.address, REG_INTR_STATUS_1, 1)
# print("[SETUP] reset complete with interrupt register0: {0}".format(reg_data))
self.setup()
# print("[SETUP] setup complete")
def shutdown(self):
"""
Shutdown the device.
"""
self.bus.write_i2c_block_data(self.address, REG_MODE_CONFIG, [0x80])
def reset(self):
"""
Reset the device, this will clear all settings,
so after running this, run setup() again.
"""
self.bus.write_i2c_block_data(self.address, REG_MODE_CONFIG, [0x40])
def setup(self, led_mode=0x03):
"""
This will setup the device with the values written in sample Arduino code.
"""
# INTR setting
# 0xc0 : A_FULL_EN and PPG_RDY_EN = Interrupt will be triggered when
# fifo almost full & new fifo data ready
self.bus.write_i2c_block_data(self.address, REG_INTR_ENABLE_1, [0xc0])
self.bus.write_i2c_block_data(self.address, REG_INTR_ENABLE_2, [0x00])
# FIFO_WR_PTR[4:0]
self.bus.write_i2c_block_data(self.address, REG_FIFO_WR_PTR, [0x00])
# OVF_COUNTER[4:0]
self.bus.write_i2c_block_data(self.address, REG_OVF_COUNTER, [0x00])
# FIFO_RD_PTR[4:0]
self.bus.write_i2c_block_data(self.address, REG_FIFO_RD_PTR, [0x00])
# 0b 0100 1111
# sample avg = 4, fifo rollover = false, fifo almost full = 17
self.bus.write_i2c_block_data(self.address, REG_FIFO_CONFIG, [0x4f])
# 0x02 for read-only, 0x03 for SpO2 mode, 0x07 multimode LED
self.bus.write_i2c_block_data(self.address, REG_MODE_CONFIG, [led_mode])
# 0b 0010 0111
# SPO2_ADC range = 4096nA, SPO2 sample rate = 100Hz, LED pulse-width = 411uS
self.bus.write_i2c_block_data(self.address, REG_SPO2_CONFIG, [0x27])
# choose value for ~7mA for LED1
self.bus.write_i2c_block_data(self.address, REG_LED1_PA, [0x24])
# choose value for ~7mA for LED2
self.bus.write_i2c_block_data(self.address, REG_LED2_PA, [0x24])
# choose value fro ~25mA for Pilot LED
self.bus.write_i2c_block_data(self.address, REG_PILOT_PA, [0x7f])
# this won't validate the arguments!
# use when changing the values from default
def set_config(self, reg, value):
self.bus.write_i2c_block_data(self.address, reg, value)
def read_fifo(self):
"""
This function will read the data register.
"""
red_led = None
ir_led = None
# read 1 byte from registers (values are discarded)
reg_INTR1 = self.bus.read_i2c_block_data(self.address, REG_INTR_STATUS_1, 1)
reg_INTR2 = self.bus.read_i2c_block_data(self.address, REG_INTR_STATUS_2, 1)
# read 6-byte data from the device
d = self.bus.read_i2c_block_data(self.address, REG_FIFO_DATA, 6)
# mask MSB [23:18]
red_led = (d[0] << 16 | d[1] << 8 | d[2]) & 0x03FFFF
ir_led = (d[3] << 16 | d[4] << 8 | d[5]) & 0x03FFFF
return red_led, ir_led
def read_sequential(self, amount=100):
"""
This function will read the red-led and ir-led `amount` times.
This works as blocking function.
"""
red_buf = []
ir_buf = []
for i in range(amount):
while(GPIO.input(self.interrupt) == 1):
# wait for interrupt signal, which means the data is available
# do nothing here
pass
red, ir = self.read_fifo()
red_buf.append(red)
ir_buf.append(ir)
return red_buf, ir_buf
検証
- 心拍数と血中酸素濃度(SpO2)が同時に取得できている
- 発光面に指を触れると読み取りエラーとなる
- ジャンパーワイヤを曲げてスペーサーとし、触れないようにしてみると安定した数値が取得できた
- 人差し指以外でも値は取得できたが、指の置き方でエラーになりやすい
- 周囲の明るさの影響を受けるので、パルスオキシメーターのようなカバーリングは必要
次回に向けて
- Arduino nanoとOLED液晶を使い、小型化・表示装置を搭載した単体のデバイスにできないか検証する