心拍センサ + Raspberry Pi(エッジ検出)

 前回まで心拍センサを Arduino 互換の Seeduino で使ってきましたが、今回は Raspberry Pi で心拍センサを使ってみたいと思います。スクリプトは Python で実装し、 GPIO からの入力を検知したら心拍数の計算等を実行します。

回路構成

 Raspberry Pi には Grove コネクタがついていないので、前々回のケースと同様に、 Grove コネクタに直接ジャンパコードを挿し、 Raspberry Pi の GPIO ピンに接続しました。 Raspberry Pi 側は 3.3V、GND、GPIO17 のピンに接続します。

f:id:akanuma-hiroaki:20180728164820j:plain

f:id:akanuma-hiroaki:20180728164853j:plain

f:id:akanuma-hiroaki:20180728164923j:plain

Raspberry Pi での割り込み処理

 今回スクリプトは Python で実装しますが、内容としては前々回の c++ のコードと同様です。心拍モニタが心拍を検知すると GPIO の17番ピンが High になるので、この入力を待ち受けるようにします。

 電気信号が Low -> High もしくは High -> Low に切り替わる瞬間のことをエッジ(edge)と言い、今回はエッジ検出のために GPIO.wait_for_edge() メソッドを使いました。引数には対象のピンの番号と、検出したいエッジの種類を指定します。エッジの種類には、 Low -> High(立ち上がりエッジ)、 High -> Low(立ち下がりエッジ)、そしてその両方の3種類があり、今回は立ち上がりエッジを検出したいので、 GPIO.RISING を指定しています。このメソッドを実行すると、エッジが検出されるまで待ち受け状態になりますので、無限ループの中でこのメソッドを実行し、エッジが検出されたら処理を行うようにしています。

while True:
    GPIO.wait_for_edge(self.INTERRUPT_PIN, GPIO.RISING)
    self._interrupt()

Queue の使用

 前回までの c++ のスクリプト内では心拍数の計算のために配列を使用していましたが、今回は単純なリストではなく、 Queue として使用したかったので、 Python の collections.deque を使ってみました。普通のリストでも append() と pop() を使うことで同様の処理を行うことができますが、リストの先頭の要素を pop すると、全ての要素の移動処理が行われるため、処理効率は悪いようです。

 まずは下記のように deque をインポートします。

from collections import deque

 queue の初期化は deque() に初期配列を渡します。

self.detected_times = deque([])

 要素の追加は普通のリストと同様に append() で行います。

self.detected_times.append(time.time())

 先頭要素の削除は popleft() で行うことができます。

self.detected_times.popleft()

スクリプト実装

 今回のスクリプト全体は下記のように実装しました。心拍が検知される(エッジが検出される)とその時刻を Queue に格納し、 20回を越えるとその差分から心拍数を計算して出力します。エラーハンドリングは考慮していないので、実際に使用する場合はエラーハンドリングのコードを追加することになると思います。

#!/usr/bin/env python

import time
from collections import deque

import RPi.GPIO as GPIO

class HeartRateMonitor:
    INTERRUPT_PIN = 17
    MAX_DETECTED_TIMES_COUNT = 20
    MAX_PULSE_INTERVAL = 2.0

    def __init__(self):
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.INTERRUPT_PIN, GPIO.IN)

        self._init_array()

    def _init_array(self):
        self.detected_times = deque([])

    def _calc_heart_rate(self):
        return 1200.0 / (self.detected_times[-1] - self.detected_times[0])

    def _interrupt(self):
        self.detected_times.append(time.time())

        if(len(self.detected_times) == 1):
            return

        interval = self.detected_times[-1] - self.detected_times[-2]
        heart_rate = -1
        if interval > self.MAX_PULSE_INTERVAL:
            print('Heart rate measure error. Monitoring will restart!')
            self._init_array()
            return

        if(len(self.detected_times) >= self.MAX_DETECTED_TIMES_COUNT):
            heart_rate = self._calc_heart_rate()
            self.detected_times.popleft()

        print("HeartRate: {heart_rate}, Interval: {interval}".format(heart_rate = heart_rate, interval = interval))

    def execute(self):
        print('Please ready your heart rate monitor.')
        time.sleep(3)

        while True:
            GPIO.wait_for_edge(self.INTERRUPT_PIN, GPIO.RISING)
            self._interrupt()

if __name__ == '__main__':
    monitor = HeartRateMonitor()
    monitor.execute()

動作確認

 上記のコードを実行すると、ターミナルに下記のように心拍数の計算結果が出力されます。

$ ./heart_rate_monitor.py
Please ready your heart rate monitor.
HeartRate: -1, Interval: 0.8302757740020752
HeartRate: -1, Interval: 0.808060884475708
HeartRate: -1, Interval: 0.7631323337554932
HeartRate: -1, Interval: 0.7801287174224854
HeartRate: -1, Interval: 0.798215389251709
HeartRate: -1, Interval: 0.8021731376647949
HeartRate: -1, Interval: 0.7605118751525879
HeartRate: -1, Interval: 0.7294180393218994
HeartRate: -1, Interval: 0.7548770904541016
HeartRate: -1, Interval: 0.8093967437744141
HeartRate: -1, Interval: 0.8305354118347168
HeartRate: -1, Interval: 0.8199899196624756
HeartRate: -1, Interval: 0.8051409721374512
HeartRate: -1, Interval: 0.8412587642669678
HeartRate: -1, Interval: 0.8870439529418945
HeartRate: -1, Interval: 0.9384407997131348
HeartRate: -1, Interval: 0.933556079864502
HeartRate: -1, Interval: 0.9079298973083496
HeartRate: 76.52472304674038, Interval: 0.8811209201812744
HeartRate: 76.12490143070653, Interval: 0.9126362800598145
HeartRate: 75.64256806120363, Interval: 0.9085769653320312
HeartRate: 75.02947229026323, Interval: 0.8927640914916992
HeartRate: 74.78054308152043, Interval: 0.8333685398101807
HeartRate: 74.81021338673118, Interval: 0.7918510437011719
HeartRate: 74.72784839482473, Interval: 0.8198530673980713
HeartRate: 74.24507270335705, Interval: 0.8649301528930664
HeartRate: 73.53067103751506, Interval: 0.8864498138427734
HeartRate: 73.00755517148106, Interval: 0.8718116283416748
HeartRate: 72.93730864636744, Interval: 0.8252270221710205
HeartRate: 72.89795075840209, Interval: 0.8394181728363037
HeartRate: 72.71868699404068, Interval: 0.860569953918457
HeartRate: 72.42184236861083, Interval: 0.8727796077728271
HeartRate: 72.46996284628561, Interval: 0.830256462097168
HeartRate: 73.06855132933228, Interval: 0.7513935565948486
HeartRate: 74.17623306838857, Interval: 0.6931953430175781

まとめ

 Raspberry Pi は Arduino 系のマイコン等を使用した場合と比べて省電力が大きいので、ボタン電池等で小型化してウェアラブルデバイスを作るというようなことには向いていませんが、個人的には c++ よりも Python の方が馴染みがあるということもあり色々柔軟に扱えて取っつきやすいので、電源を確保できるような環境での使用であればとてもプロトタイピングしやすいですね。 GPIO のエッジ検出も簡単にできたので、今後色々な用途を検討してみたいと思います。

 今回はこちらのサイトを参考にさせていただきました。

ag.hatenablog.com