私が作ったものはゴミ性能だった
作ってから色々試してみたんですけど、やっぱり駄目ね…という感じが強く、前回作ったものはポンコツだったな感が強まってきた。
いや分かってけど…
まず心拍上がった状態で測ると速攻で死ぬ。
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やってから心拍測定に使ってみまーす!