M5Stack Baステークカジノ リップルcで通信機能付き心拍・血中酸素モニタを作る
2020.04.28
コロナウィルスの蔓延により、医療崩壊や軽度感染者の自宅待機が新たな問題を引き起こステークカジノ リップルきています(2020年4月26日現在)。なぜ、助けを呼べなかったのか?なぜ、周りの人達は異変に気付かなかったのだろうか?おそらく、人との接触を避ける今、病状が急変した時には取り返しのつかない状態になっているのだと思います。
世の中には、パルスオキシメーター(ステークカジノ リップル・血中酸素モニタ)はありますが、家庭用で使うものには、変化を通知するための通信機能がありません。
そこで、軽度感染者の変化にいろんな人が気付けるよう、身の回りに転がっているIoT部品で通信機能付きパルスオキシメーターを作ってみました。
用意するもの
身の回りにないかもしれませんが、下記を用意ステークカジノ リップルください。
M5Stack Baステークカジノ リップルc
LCD表示必要なければArduステークカジノ リップルo Nano等でも大丈夫です。
パルス酸素濃度計および心拍数センサーIC(ステークカジノ リップルX3010x)
私は、GAOHOU(中国製)のステークカジノ リップルX30100開発ボードセンサを購入しましたが、Groveコネクタで接続可能なステークカジノ リップルStack用心拍センサユニットの方が便利かもしれません。
ステークカジノ リップルgfox Breakout board(BRKWS01)
BRKWS01はステークカジノ リップルgfox Devkitとして1年間の無料通信回線※1 込みでお使いいただけます。
※1 2022年7月1日をもって、ステークカジノ リップルvkit対応デバイスのステークカジノ リップルvkit登録(無償回線利用)が終了いたしました。
ステークカジノ リップルgfox回線をご利用の際には、ステークカジノ リップルgfox Buyにて有償回線をご購入ください。
ステークカジノ リップルgfox Buy お申込みからご利用開始までの流れはこちら
他の通信モジュールを使っても結構です。今回は、M5Stack Basicでステークカジノ ランクアップfox RFモニタを作るで作ったプロトタイプをベースにパルスオキシメーターを追加ステークカジノ リップルいきます。
MAX3010xとステークカジノ リップルStackを接続する
心拍センサMAX3010xとステークカジノ リップルStackとは、I2Cで接続することとなります。
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をインストールします。
ソースコード
下記の通りとなります。
#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ファイルに配列データを書いています。
ちなみに、今回は、こんなと画像を用意しました。
心拍・血中酸素をステークカジノ リップルgfoxで送信する
取得した心拍(HeartRate)、血中酸素(SpO2)をsendステークカジノ リップルgfoxMessage関数内でデータ送信しています。ステークカジノ リップル$SFコマンドでメッセージ送信をステークカジノ リップルいるだけですので、細かい説明は割愛します。
本当は、ステークカジノ リップル濃度が一定の値を下回ったらメッセージ送信するという仕組みにすれば、もう少し実用っぽいプログラムになるかもしれませんが。
動作確認
指をMAX30100の赤く光っている部分に当て、Aボタンを押せばセンシングが開始されます。数秒後に結果が画面上に表示されます。Cボタンを押せば、その結果をステークカジノ リップルgfoxメッセージとして送信します。
反省点
今回、ステークカジノ リップルX3010xの開発用ボードをそのまま使ったため、測定時に指がセンサ部に接触してしまい有効値を取得するのに苦労しました。直接センサ部に触れてはだめだし、離れすぎてもだめ。
3Dプリンタがあれば、Arduステークカジノ リップルo Project Hubのサンプルのように指が安定するようなものができるのでしょうが。。。