M5Stack Baステークカジノ リップルcで通信機能付き心拍・血中酸素モニタを作る

2020.04.28

コロナウィルスの蔓延により、医療崩壊や軽度感染者の自宅待機が新たな問題を引き起こステークカジノ リップルきています(2020年4月26日現在)。なぜ、助けを呼べなかったのか?なぜ、周りの人達は異変に気付かなかったのだろうか?おそらく、人との接触を避ける今、病状が急変した時には取り返しのつかない状態になっているのだと思います。
世の中には、パルスオキシメーター(ステークカジノ リップル・血中酸素モニタ)はありますが、家庭用で使うものには、変化を通知するための通信機能がありません。
そこで、軽度感染者の変化にいろんな人が気付けるよう、身の回りに転がっているIoT部品で通信機能付きパルスオキシメーターを作ってみました。

img-20200428-technical-01.png

用意するもの

身の回りにないかもしれませんが、下記を用意ステークカジノ リップルください。

M5Stack Baステークカジノ リップルc

LCD表示必要なければArduステークカジノ リップルo Nano等でも大丈夫です。

パルス酸素濃度計および心拍数センサーIC(ステークカジノ リップルX3010x)

私は、GAOHOU(中国製)のステークカジノ リップルX30100開発ボードセンサを購入しましたが、Groveコネクタで接続可能なステークカジノ リップルStack用心拍センサユニットの方が便利かもしれません。

img-20200428-technical-02.png

ステークカジノ リップルgfox Breakout board(BRKWS01)

BRKWS01はステークカジノ リップルgfox Devkitとして1年間の無料通信回線※1 込みでお使いいただけます。

※1 2022年7月1日をもって、ステークカジノ リップルvkit対応デバイスのステークカジノ リップルvkit登録(無償回線利用)が終了いたしました。
ステークカジノ リップルgfox回線をご利用の際には、ステークカジノ リップルgfox Buyにて有償回線をご購入ください。
ステークカジノ リップルgfox Buy お申込みからご利用開始までの流れは
こちら

img-20200428-technical-03.png

他の通信モジュールを使っても結構です。今回は、M5Stack Basicでステークカジノ ランクアップfox RFモニタを作るで作ったプロトタイプをベースにパルスオキシメーターを追加ステークカジノ リップルいきます。

MAX3010xとステークカジノ リップルStackを接続する

心拍センサMAX3010xとステークカジノ リップルStackとは、I2Cで接続することとなります。

img-20200428-technical-04.png

MAX30100のVステークカジノ リップルをM5Stackの5V出力端子に、GNDをGNDに、SCLとSDAはそのまま、M5Stack側のSCL、SDAに接続します。
ステークカジノ リップルgfoxモジュールとの接続はシリアル(Serial2)用のG16,17がそのまま使えます。

ステークカジノ リップルX3010x用プログラム

ステークカジノ リップルX3010x用ライブラリのインストール

Arduステークカジノ リップルo IDEのライブラリマネージャ([ツール]_[ライブラリを管理]メニュー)からSparkFun MAX3010x Pulステークカジノ リップル and Proximity ステークカジノ リップルnsor Libraryをインストールします。

img-20200428-technical-05.png

ソースコード

下記の通りとなります。

pulステークカジノ リップルoximeter.c
#include
#include
#include "MAX30105.h"
#include "spo2_algorithm.h"
#include "xbm.h" //my bitmap

MAX30105 particleSensor;

uint32_t irBuffer[50]; //infrared LED sensor data
uint32_t redBuffer[50];  //red LED sensor data

int32_t bufferLength; //data length
int32_t spo2; //SPO2 value
int8_t validSPO2; //indicator to show if the SPO2 calculation is valid
int32_t heartRate; //heart rate value
int8_t validHeartRate; //indicator to show if the heart rate calculation is valid

bool doStart = false; // flag true if senステークカジノ リップルng should be started

void setup()
{
  Serial.begin(115200); // to PC via USB
  Serial2.begin(9600, SERIAL_8N1, 16, 17);   // to ステークカジノ リップルgfox module

  M5.begin();
  M5.Power.begin();
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextステークカジノ リップルze(2);

  // Initialize sensor
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
  {
    M5.Lcd.setTextColor(RED);
    M5.Lcd.println("MAX30105 was not found. Please check wiring/power.");
    while (1);
  }

  M5.Lcd.setTextColor(BLUE);
  M5.Lcd.println("A: start measuring");
  M5.Lcd.println("C: send message");
}

void loop()
{
  M5.update();  //update button state
  if (doStart) {
    startSense();
  }
  if (M5.BtnA.wasReleased()) {
    doStart = true;
  }
}

void startSense()
{
  byte ledBrightness = 55; //Options: 0=Off to 255=50mA
  byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32
  byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
  byte sampleRate = 200; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 411; //Options: 69, 118, 215, 411
  int adcRange = 4096; //Options: 2048, 4096, 8192, 16384

  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings 

  bufferLength = 50; //buffer length of 50 stores 4 seconds of samples running at 25sps

  //read the first 50 samples, and determine the ステークカジノ リップルgnal range
  for (byte i = 0 ; i < bufferLength ; i++)
  {
    while (particleSensor.available() == false) //do we have new data?
      particleSensor.check(); //Check the sensor for new data

    redBuffer[i] = particleSensor.getRed();
    irBuffer[i] = particleSensor.getIR();
    particleSensor.nextSample(); //We're finished with this sample so move to next sample

    Serial.print("red="); Serial.print(redBuffer[i], DEC);
    Serial.print(", ir=");  Serial.println(irBuffer[i], DEC);
  }

  //calculate heart rate and SpO2 after first 50 samples (first 4 seconds of samples)
  maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

  //Continuously taking samples from MAX30102. Heart rate and SpO2 are calculated every 1 second
  while (1)
  {
    //dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top
    for (byte i = 25; i < bufferLength; i++)
    {
      redBuffer[i - 25] = redBuffer[i];
      irBuffer[i - 25] = irBuffer[i];
    }

    //take 25 sets of samples before calculating the heart rate.
    for (byte i = 25; i < bufferLength; i++)
    {
      while (particleSensor.available() == false) //do we have new data?
        particleSensor.check(); //Check the sensor for new data

      redBuffer[i] = particleSensor.getRed();
      irBuffer[i] = particleSensor.getIR();
      particleSensor.nextSample(); //We're finished with this sample so move to next sample

      //send samples and calculation result to terminal program through UART
      Serial.print("red="); Serial.print(redBuffer[i], DEC);
      Serial.print(", ir=");  Serial.print(irBuffer[i], DEC);

      Serial.print(", HR="); Serial.print(heartRate, DEC);
      Serial.print(", HRvalid="); Serial.print(validHeartRate, DEC);
      Serial.print(", SPO2="); Serial.print(spo2, DEC);
      Serial.print(", SPO2Valid="); Serial.println(validSPO2, DEC);

      checkButton();
    }

    maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
    printToDisplay();
  }
}

void printToDisplay()
{
  //M5.Lcd.clear(BLACK);
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextステークカジノ リップルze(4);
  //M5.Lcd.setCursor(0,0);
  if(validSPO2 && validHeartRate) {
    M5.Lcd.drawXBitmap(0, 5, hb2_bmp, 64, 32, TFT_RED);
    M5.Lcd.setCursor(0,60);
    M5.Lcd.print("HR: "); M5.Lcd.println(heartRate, DEC);
    M5.Lcd.print("SPO2: "); M5.Lcd.println(spo2, DEC);
  } else {
    M5.Lcd.drawXBitmap(0, 5, hb1_bmp, 64, 32, TFT_WHITE);
    M5.Lcd.setCursor(0,60);
    M5.Lcd.print("Not valid");
  }
}

void checkButton()
{
  M5.update();
  if (M5.BtnC.wasReleased()) {
    sendステークカジノ リップルgfoxMessage();
  }
}

void sendステークカジノ リップルgfoxMessage()
{
  if(validSPO2 && validHeartRate) {
    String msg = "AT$SF=" + String(heartRate, HEX) + String(spo2, HEX);
    Serial2.println(msg);
    M5.Lcd.println(msg);
    delay(10000);
  }
}

startステークカジノ リップルnステークカジノ リップル()関数内がPulステークカジノ リップル Oximeterセンサ処理部分になります。
getRed()、getIR()メソッドで、赤色光(R)と赤外光(IR)の値を取得し、maxim_heステークカジノ リップルt_rate_and_oxygen_saturation関数内でR / IRの比率により、heステークカジノ リップルtRate(心拍)とspo2(血中酸素)を計算ステークカジノ リップルくれます。
赤色光(R)を血液に当てると、ヘモグロビンと酸素がより多く結びついている場合、多くの光が指を通り抜けるそうです。逆に赤外光(ステークカジノ リップル)は酸素との結びつきに係わらず、血液を通り抜けるため、R / ステークカジノ リップル比率を調べることにより、血中酸素量をセンシングできます。
コニカミノルタさんのページにパルスオキシメーターの原理が分かりやすく解説されています。

ステークカジノ リップルStack上にビットマップ表示

画面上に心拍(Heステークカジノ リップルtRate)と血中酸素(SpO2)を表示するだけでは味気ないので、測定中を表す画像を表示してみます。
M5Stackでは、SDカードに保存した画像ファイルを読み込み表示する方法の他に、1ドットを1ビットで表した配列データ(XBitMap[XBM])をLCD表示する方法もあります。drawXBitmap関数に画像配置左上座標(x,y)と画像サイズ(width, ステークカジノ リップルight)、塗りつぶしする色(color)に加え、XBitMap配列データを渡します。
M5.Lcd.drawXBitmap(ステークカジノ リップルt16_t x, ステークカジノ リップルt16_t y, const uステークカジノ リップルt8_t *bitmap, ステークカジノ リップルt16_t width, ステークカジノ リップルt16_t height, uステークカジノ リップルt16_t color)

XBitステークカジノ リップルpの作成方法

お手持ちの画像ファイルをConvert Iステークカジノ リップルge to XBMサイトでステークカジノ リップルMに変換できます。変換された配列は、

static chステークカジノ リップル xxx_bits[] = {
  0x00, 0x00, 0x00, (中略), 0x00, 0x00, 0x00,
  };

といった形でダウンロードできますので、その配列をdrawXBitステークカジノ リップルp関数に渡すこととなります。今回のソースコードでは、xbm.hファイルに配列データを書いています。
ちなみに、今回は、こんなimg-20200428-technical-06.pngimg-20200428-technical-07.png画像を用意しました。

心拍・血中酸素をステークカジノ リップルgfoxで送信する

取得した心拍(HeartRate)、血中酸素(SpO2)をsendステークカジノ リップルgfoxMessage関数内でデータ送信しています。ステークカジノ リップル$SFコマンドでメッセージ送信をステークカジノ リップルいるだけですので、細かい説明は割愛します。

本当は、ステークカジノ リップル濃度が一定の値を下回ったらメッセージ送信するという仕組みにすれば、もう少し実用っぽいプログラムになるかもしれませんが。

動作確認

指をMAX30100の赤く光っている部分に当て、Aボタンを押せばセンシングが開始されます。数秒後に結果が画面上に表示されます。Cボタンを押せば、その結果をステークカジノ リップルgfoxメッセージとして送信します。

img-20200428-technical-08.png

反省点

今回、ステークカジノ リップルX3010xの開発用ボードをそのまま使ったため、測定時に指がセンサ部に接触してしまい有効値を取得するのに苦労しました。直接センサ部に触れてはだめだし、離れすぎてもだめ。
3Dプリンタがあれば、Arduステークカジノ リップルo Project Hubのサンプルのように指が安定するようなものができるのでしょうが。。。

img-20200428-technical-09.png

著者ステークカジノ リップル

Products and Marketステークカジノ リップルg Department 日比 学

京セラコミュニケーションシステム株式会社(KCCS)よりステークカジノ リップルgfox社(フランス)に出向中の元自称ステークカジノ リップルgfoxエバンジェリスト

  • KCCSから追い出され、フランスの片田舎で余生を過ごステークカジノ リップルいます
Twitter:https://twitter.ステークカジノ リップルm/ghibi
ステークカジノ リップル