あいまいまいんの生物学

あいまいまいんの生物学

生物学が好き。勉強したり遊んだり。

日頃感じたこと思ったこと、出来事など

勉強して面白かった話

授業で使えそうな生物学の知識・雑談・小ネタ

などなどを紹介していきたいと思います

コメント大歓迎!気軽にコメントして下さい

ゲームサイトはこちら

M5Stackで心拍表示する 2nd try

私が作ったものはゴミ性能だった

前回↓
i-my-mine.hatenablog.com

作ってから色々試してみたんですけど、やっぱり駄目ね…という感じが強く、前回作ったものはポンコツだったな感が強まってきた。
いや分かってけど…

まず心拍上がった状態で測ると速攻で死ぬ。
BPMが0になる。
やめて…
平均を範囲動かさずにとってくせいでちょっとでも心拍の落ちるタイミングとかが平均とる範囲とずれていくと駄目になってしまう。
これは使えない。

しかもあれだよ、やっぱり上限と下限がずれていくと一回の失敗が大反響しちゃってもうがったがたになっちゃう。
波形的に死んでる。
これはいかんで、ということで二つのトライをしてみた。
①データをとって上限下限の設定のし直し
移動平均の実装


とりあえず様子を見るためのデータ取り

データをとれるようにシリアルモニタでlastMaxとlastMinがどう遷移していっていってるかを確認してみた。

/*
    Install MAX30100lib Library first.

    MAX30100_RawData.ino
*/
  
#include <M5Stack.h>
#include <Wire.h>
#include "MAX30100.h"

#define SAMPLING_RATE   MAX30100_SAMPRATE_100HZ
#define IR_LED_CURRENT  MAX30100_LED_CURR_50MA
#define RED_LED_CURRENT MAX30100_LED_CURR_27_1MA
// set HIGHRES_MODE to true only
// when setting PULSE_WIDTH to MAX30100_SPC_PW_1600US_16BITS
#define PULSE_WIDTH MAX30100_SPC_PW_1600US_16BITS
#define HIGHRES_MODE    true

// new a object
MAX30100 sensor;

void setup() {
    M5.begin();
    Serial.begin(115200);
    Serial.print("Initializing MAX30100..");
    if (!sensor.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }
    sensor.setMode(MAX30100_MODE_SPO2_HR);
    sensor.setLedsCurrent(IR_LED_CURRENT, RED_LED_CURRENT);
    sensor.setLedsPulseWidth(PULSE_WIDTH);
    sensor.setSamplingRate(SAMPLING_RATE);
    sensor.setHighresModeEnabled(HIGHRES_MODE);
}


const int LCD_WIDTH = 320;
const int LCD_HEIGHT = 240;
const int DOTS_DIV = 30;
#define GREY 0x7BEF

void DrawGrid() {
    for (int x = 0; x <= LCD_WIDTH; x += 2) { // Horizontal Line
        for (int y = 0; y <= LCD_HEIGHT; y += DOTS_DIV) {
            M5.Lcd.drawPixel(x, y, GREY);
        }
        if (LCD_HEIGHT == 240) {
            M5.Lcd.drawPixel(x, LCD_HEIGHT - 1, GREY);
        }
    }
    for (int x = 0; x <= LCD_WIDTH; x += DOTS_DIV) { // Vertical Line
        for (int y = 0; y <= LCD_HEIGHT; y += 2) {
            M5.Lcd.drawPixel(x, y, GREY);
        }
    }
}

#define REDRAW 20 // msec

int lastMin = 65000, lastMax = 62000;
int minS= 63000, maxS = 60000;
int lastY = 63000;
int x = 0;
int count1 = 0;
int count2 = 0;
int p = 0;
int lastp = 0;
int bpm;



void loop()
{
    M5.update();
    delay(REDRAW);
    uint16_t ir, red;
    sensor.update();
    while(sensor.getRawValues(&ir, &red)){
      //Serial.println(ir);
      //Serial.print('\t');
      //Serial.println(red);
    };
    uint16_t y = red;
    if (y < minS) minS = y;
    if (maxS < y) maxS = y;
    if (x > 0) {
        y = (int)(LCD_HEIGHT - (float)(y - lastMin) / (lastMax - lastMin) * LCD_HEIGHT);
        M5.Lcd.drawLine(x - 1, lastY, x, y, WHITE);
        lastY = y;
        p += y;
        count2++;
        if(count2 == 15){
          p/=15;
          if(lastp > p+(LCD_HEIGHT/8)) ++count1;
          lastp = p;
          p = 0;
          count2 = 0;  
        }
    }
    //Serial.print("minS: ");
    //Serial.print(minS);
    //Serial.print(" maxS: ");
    //Serial.print(maxS);
    //Serial.print(" y: ");
    //Serial.println(y);
    if (++x > LCD_WIDTH) {
        x = 0;
        M5.Lcd.fillScreen(BLACK);
        DrawGrid();
        lastMin = minS - 20;
        lastMax = maxS + 20;
        minS = 63000;
        maxS = 60000;
        bpm = (float)count1/6.5 * 60;
        M5.Lcd.setCursor(0, 0);
        M5.Lcd.setTextSize(4);
        M5.Lcd.printf("BPM: %d", bpm);
        count1 = 0;
        Serial.print("lastmin: ");
        Serial.print(lastMin);
        Serial.print("   lastmax: ");
        Serial.println(lastMax);
    }
}


シリアルモニタで出てきた値はこんな感じだった。
lastmin: -8 lastmax: 65555
lastmin: 62633 lastmax: 64932
lastmin: 61831 lastmax: 63631
lastmin: 60875 lastmax: 62621
lastmin: 60031 lastmax: 61655
lastmin: 59639 lastmax: 60937
lastmin: 59217 lastmax: 60638
lastmin: 58777 lastmax: 60020
lastmin: 58648 lastmax: 60020
lastmin: 56636 lastmax: 60020
lastmin: 55482 lastmax: 60020
lastmin: 55611 lastmax: 60020
lastmin: 55262 lastmax: 60020
lastmin: 58584 lastmax: 61697
lastmin: 55789 lastmax: 65555
lastmin: 55413 lastmax: 60020
lastmin: 54854 lastmax: 60020
lastmin: 54067 lastmax: 65180
lastmin: 58596 lastmax: 60020
lastmin: 56288 lastmax: 60020
lastmin: 54920 lastmax: 60020
lastmin: 51183 lastmax: 60020
lastmin: 50659 lastmax: 60020
lastmin: 56110 lastmax: 60020
lastmin: 58426 lastmax: 60036
lastmin: 58589 lastmax: 60061 ←ここらへんがよかった
lastmin: 58636 lastmax: 60365


最初の-8とか悲劇でしかない。
55000あたりの数字もだいぶやばかった。もうめっちゃ画面の上の方で波打つの。
よってなんとなくlastMax=60100くらい、lastMin=58000くらいが適正値なのではないか、という気がしてくる。
ただし自分だけでしかこれは見ていないのでわからない。
lastMaxとlastMinがあまりにも人依存で変化するなら、従来通り刻々と変化していくようにしなければいけないけれど、
もし人によってそう変化なくこれくらいの値になるのであればもはやlastMaxとlastMinは固定でもよい。と思う。

取り敢えずまぁまだ更新入れておきつつ、
int lastMin = 57000, lastMax = 59000;
int lastY = 57000;
で試していくことにした。
色んな人で試してここの値は調整しよう。
あとは、指を離した時の所謂0とかマイナスとかの値がminSとしてlastMinに反映されたり、強く押し込んだ時の高すぎる値がmaxSおよびlastMaxに反映されるのも嫌だったので、lastMinとlastMaxはあからさまにおかしい値が入りそうになったときにはブロックかけられるようにifで上限下限を設定した。



やるぜ移動平均

移動平均についてはいやだいやだと逃げていたけどもうもはや逃げられないなという気持ちなので実装することにした。
そこで考えた一番ストレートな方法がこれ。
①めちゃでかコンテナを用意して毎回出てくるyをがんがん入れていくスタイル
コンテナがkだとして、
データをとった順番iだとするとk[i]=yでどんどん更新して入れていく。
で、p + k[i] – k[i-n](nは移動平均をとるときの平均をとるデータ数)にして、
Lastpとpを比較後lastp=pにして更新

なんかもっさりしているなと思ったのでもう一個、もうちょっと見栄えが良くスタイリッシュなものを考えた。
②modを使っていくタイプ
コンテナがkだとして、
データをとった順番iだとするとk[i]=yでどんどん更新してまずはn個入れていく。
で、p + y – k[i%n](nは移動平均をとるときの平均をとるデータ数)にして、
その後k[i%n] = yに更新する。
Lastpとpを比較後lastp=pにして更新

modを使って出していく方法は個人的には凄くいいものが自分で思いつけた!と大満足だったんだけど
旦那さんにこのアイデアを話したら「それは〇〇という手法だね」と言われ(何かは忘れた)、すでに存在している&確立している方法としてあるらしい…残念。

なにはともあれとりあえず実装である。

/*
    Install MAX30100lib Library first.

    MAX30100_RawData.ino
*/
  
#include <M5Stack.h>
#include <Wire.h>
#include "MAX30100.h"

#define SAMPLING_RATE   MAX30100_SAMPRATE_100HZ
#define IR_LED_CURRENT  MAX30100_LED_CURR_50MA
#define RED_LED_CURRENT MAX30100_LED_CURR_27_1MA
// set HIGHRES_MODE to true only
// when setting PULSE_WIDTH to MAX30100_SPC_PW_1600US_16BITS
#define PULSE_WIDTH MAX30100_SPC_PW_1600US_16BITS
#define HIGHRES_MODE    true

// new a object
MAX30100 sensor;

void setup() {
    M5.begin();
    Serial.begin(115200);
    Serial.print("Initializing MAX30100..");
    if (!sensor.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }
    sensor.setMode(MAX30100_MODE_SPO2_HR);
    sensor.setLedsCurrent(IR_LED_CURRENT, RED_LED_CURRENT);
    sensor.setLedsPulseWidth(PULSE_WIDTH);
    sensor.setSamplingRate(SAMPLING_RATE);
    sensor.setHighresModeEnabled(HIGHRES_MODE);
}


const int LCD_WIDTH = 320;
const int LCD_HEIGHT = 240;
const int DOTS_DIV = 30;
#define GREY 0x7BEF

void DrawGrid() {
    for (int x = 0; x <= LCD_WIDTH; x += 2) { // Horizontal Line
        for (int y = 0; y <= LCD_HEIGHT; y += DOTS_DIV) {
            M5.Lcd.drawPixel(x, y, GREY);
        }
        if (LCD_HEIGHT == 240) {
            M5.Lcd.drawPixel(x, LCD_HEIGHT - 1, GREY);
        }
    }
    for (int x = 0; x <= LCD_WIDTH; x += DOTS_DIV) { // Vertical Line
        for (int y = 0; y <= LCD_HEIGHT; y += 2) {
            M5.Lcd.drawPixel(x, y, GREY);
        }
    }
}

#define REDRAW 20 // msec

int lastMin = 57000, lastMax = 59000;
int minS= 60000, maxS = 57000;
int lastY = 57000;
int x = 0;
int count1 = 0;
int p = 0;
int lastp = 0;
int k[10];
int i = 0;
int bpm;



void loop()
{
    M5.update();
    delay(REDRAW);
    uint16_t ir, red;
    sensor.update();
    while(sensor.getRawValues(&ir, &red)){
      //Serial.println(ir);
      //Serial.print('\t');
      //Serial.println(red);
    };
    uint16_t y = red;
    if (y < minS) minS = y;
    if (maxS < y) maxS = y;
    ++i;
    p += y;
if(i<=8){
k[i-1]=y; //0~7の8要素に値を入れる
if(i==8) lastp = p;
else {
  p -= k[(i-1)%8];
  k[(i-1)%8]=y;
  if(lastp>p) ++count1;
  lastp = p;
}
        

    if (x > 0) {
        y = (int)(LCD_HEIGHT - (float)(y - lastMin) / (lastMax - lastMin) * LCD_HEIGHT);
        M5.Lcd.drawLine(x - 1, lastY, x, y, WHITE);
        lastY = y;
    }

    if (++x > LCD_WIDTH) {
        x = 0;
        M5.Lcd.fillScreen(BLACK);
        DrawGrid();
        if(minS>50000) lastMin = minS - 20;
        if(maxS<65000) lastMax = maxS + 20;
        minS = 60000;
        maxS = 57000;
        bpm = (float)count1/6.4 * 60;
        M5.Lcd.setCursor(0, 0);
        M5.Lcd.setTextSize(4);
        M5.Lcd.printf("BPM: %d", bpm);
        count1 = 0;
        i = 0;
        Serial.print("lastmin: ");
        Serial.print(lastMin);
        Serial.print("   lastmax: ");
        Serial.println(lastMax);
    }
}

結果、BPM爆上がりですよ…これは試す時にドキドキしたからじゃないですよね…???(キレ
count1が足されるたびにプロッタに通知が表示されるような形を実装して追跡してみたら、一回の谷だけで5,6回分countしてた。そりゃ爆上がりしますよ。通常の人間の5倍くらいの心拍やで。
というわけで、所謂c++のboolにあたるものがあるのかよくわからなかったので、適当にflagをint型で作って、谷の入り始め…すなわち、一度でもpがlastpを下回ってcount1が足されたら、それ以降の連続するlastp>pの場合にはカウントしていかない仕組みを入れた。雑だけどまぁやってみよう。

/*
    Install MAX30100lib Library first.

    MAX30100_RawData.ino
*/
  
#include <M5Stack.h>
#include <Wire.h>
#include "MAX30100.h"

#define SAMPLING_RATE   MAX30100_SAMPRATE_100HZ
#define IR_LED_CURRENT  MAX30100_LED_CURR_50MA
#define RED_LED_CURRENT MAX30100_LED_CURR_27_1MA
// set HIGHRES_MODE to true only
// when setting PULSE_WIDTH to MAX30100_SPC_PW_1600US_16BITS
#define PULSE_WIDTH MAX30100_SPC_PW_1600US_16BITS
#define HIGHRES_MODE    true

// new a object
MAX30100 sensor;

void setup() {
    M5.begin();
    Serial.begin(115200);
    Serial.print("Initializing MAX30100..");
    if (!sensor.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }
    sensor.setMode(MAX30100_MODE_SPO2_HR);
    sensor.setLedsCurrent(IR_LED_CURRENT, RED_LED_CURRENT);
    sensor.setLedsPulseWidth(PULSE_WIDTH);
    sensor.setSamplingRate(SAMPLING_RATE);
    sensor.setHighresModeEnabled(HIGHRES_MODE);
}


const int LCD_WIDTH = 320;
const int LCD_HEIGHT = 240;
const int DOTS_DIV = 30;
#define GREY 0x7BEF

void DrawGrid() {
    for (int x = 0; x <= LCD_WIDTH; x += 2) { // Horizontal Line
        for (int y = 0; y <= LCD_HEIGHT; y += DOTS_DIV) {
            M5.Lcd.drawPixel(x, y, GREY);
        }
        if (LCD_HEIGHT == 240) {
            M5.Lcd.drawPixel(x, LCD_HEIGHT - 1, GREY);
        }
    }
    for (int x = 0; x <= LCD_WIDTH; x += DOTS_DIV) { // Vertical Line
        for (int y = 0; y <= LCD_HEIGHT; y += 2) {
            M5.Lcd.drawPixel(x, y, GREY);
        }
    }
}

#define REDRAW 20 // msec

int lastMin = 57000, lastMax = 59000;
int minS= 60000, maxS = 57000;
int lastY = 57000;
int x = 0;
int count1 = 0;
int p = 0;
int lastp = 0;
int k[10];
int i = 0;
int bpm;
int flag = 0;



void loop(){
    M5.update();
    delay(REDRAW);
    uint16_t ir, red;
    sensor.update();
    while(sensor.getRawValues(&ir, &red)){
      //Serial.println(ir);
      //Serial.print('\t');
      //Serial.println(red);
    };
    uint16_t y = red;
    if (y < minS) minS = y;
    if (maxS < y) maxS = y;
    ++i;
    p += y;
    if(i<=8){
    k[i-1]=y; //0~7の8要素に値を入れる
    if(i==8) lastp = p;
    } else {
      p -= k[(i-1)%8];
      k[(i-1)%8]=y;
      if(lastp>p+200){
        flag++;
        if(flag==1) ++count1;
      } else flag = 0;
      lastp = p;
      //Serial.print("   lastp: ");
      //Serial.println(lastp);
    }
        

    if (x > 0) {
        y = (int)(LCD_HEIGHT - (float)(y - lastMin) / (lastMax - lastMin) * LCD_HEIGHT);
        M5.Lcd.drawLine(x - 1, lastY, x, y, WHITE);
        lastY = y;
    }

    if (++x > LCD_WIDTH) {
        x = 0;
        M5.Lcd.fillScreen(BLACK);
        DrawGrid();
        if(minS>50000) lastMin = minS - 20;
        if(maxS<65000) lastMax = maxS + 20;
        minS = 60000;
        maxS = 57000;
        bpm = (float)count1/6.4 * 60;
        M5.Lcd.setCursor(0, 0);
        M5.Lcd.setTextSize(4);
        M5.Lcd.printf("BPM: %d", bpm);
        count1 = 0;
        i = 0;
        Serial.print("lastmin: ");
        Serial.print(lastMin);
        Serial.print("   lastmax: ");
        Serial.println(lastMax);
    }
}

なかなかいいんじゃね?と思ってやってみたが…

M5StackとMAX30100で心拍とBPMと表示する2-1
1つ目に、「連続してlastp>pになるとき」しか検出していかないので、一個でも飛ぶともうだめ。カウントしちゃう。
結果BPMがやばくなる。
これは安定させるこっち側の技術が必要。

2つ目に、ちょっと時間を置いてやってみたんだけども…

M5StackとMAX30100で心拍とBPMと表示する2-2
なんか低空飛行じゃない???????????????????????
モニタで追ってみると、なんか心拍弱まってるよ。時間によって弱まるのかも…
これだとやはり沿わせていく技術が必要だし、今の設定値もちょっと限定的すぎるみたいだなぁ。
もっと幅広く受け止められるものにしなければ…


ということで次の改善目標はあれですね、

  • カウント制度をなんとかしてBPMをより正確化できたらいいな
  • 色んな人、色んなタイミングで使えるものにしたいな
  • ほとんどできあがってきた感があるので(?)どうせならボタンを押すと何かができるとか、その時のBPMデータをふっとばしてどこかに保存するみたいな仕組みを作り(タイムスタンプと一緒に)、後から一日の変動を見られるような仕組みにできないかなor何人かの心拍を比較する仕組みをボタンを利用してできないかな

というところですかね。
とにかくまずは今夜RingFit Adventureやってから心拍測定に使ってみまーす!

ほかにやりたいこと

  • 動くロボをつくる
  • 加速器やx,y,z座標を検出しグラフで出す(画面をボタンで切り替えられる)やつをつくる
  • 温度検出記録器(wifi機能使いたい、Excelに記録してほしい)つくる
  • センサーで赤外線感知?だっけ 物の通過回数を記録するやつつくる
  • ビデオにとる(何かを、タイムラプスでも化)やつつくるor変化回数を記録するor変化検出するやつを作る

明日明後日は外出なので難しそうですが、できれば年始のお休みの間にもう一つくらい開発したいね…