どうもこんにちは。
「M5Stackいじりてぇ~」と言いながら言うだけで何もせずのんべんだらりだった私です。
一念発起して「っしゃM5Stackいじるか~」って重い腰を先日やってあげました。へぇへぇ。
さ~て何日ぶりのM5Stack進捗なのかな~??
過去記事曰く…
i-my-mine.hatenablog.com i-my-mine.hatenablog.com
3ヶ月経っとるやんけ!!!!!!!!
どんだけサボってるんだこいつは。
仕方ないっすね。波があるからね。人間だもの。
ということで進捗を報告します。
目次をつけたいしなんか色んな機能を統合したい
如何せん前回やったのが3ヶ月前なので記憶が埃を被っていてなーんにも覚えてない。
なんにもとは言わないけれどほっとんど覚えていないので、自分が何をしたいか何が継続課題だったかも分からない。
で、大抵の放っておいたことっていうのは「過去の自分がなんかいい感じにして終わった気がする…」という謎の妄想を産むんですよね。
だから「もう心拍何もいじらなくてよくね?もっとエキサイティングなことしたいわ」みたいな気持ちになってきた。
過去記事を見ればよかったんだけどな。全く見ずに出発進行!
今回自分が思ったのは「目次を作って機能をボタンで切り替えられるようにする」というもの。
ボタンがついてるのに全く使わずに今まで来ているので、どうせなら使いたい。
あとついでにgyroセンサや温度センサもついてるのに無視して心拍だけ阿呆みたいに写し続けるのはなんかもったいない。どうせなら全部の機能をフルで使いたい。もったいないから。
ということで、目次にgyroだの温度だの心拍だのを設定して、ボタンでぴぴぴと選べるようにして、ボタン押したら機能を使える…みたいな感じにしたいなと思った。
目次難しくないですか?
まー目次なんで余裕でしょーと思ったんだけど、ところがどっこいこれが実はちょっと面倒で難しい。
まず目次だから目次を提示しなければいけない。
そしてその目次をカーソルで選べるようにしなきゃいけない。カーソルの表示と目次が対応していないといけない。カーソルはボタンを押す度に決まった場所へ動いていかなきゃいけない。
そしてそのカーソルがある目次のところに来てなんらかのボタンを押すとある機能のプログラムにふっとばされないといけない。つまりカーソルの位置対応なのか、指す目次対応なのかは分からないがとにかく紐付けなきゃいけない。
…考えている内にどんどん面倒になってきた。
素人が作るしょっぱいものよりなんかあるんじゃないか?と調べてみた。
あったわ。
天才が作った天才のものがあった。しかもカッコイイ‼‼‼‼‼‼
githubを見たらMITライセンスだ。個人利用なら大丈夫。
取り敢えずしばらくの間お貸ししていただこうということで、心の中で土下座しながらsimpleなやつを使わせて貰った。
本当ありがとうございます…
これすっごい使いやすいし、かっこいいし、動きも良いし、便利…
目次と中身のリンクをする
目次ができたら次はボタンをポチッと押せばプログラムが起動する、みたいなものを作らねばならないわけだが、これはプロである旦那さんからアイデアを貰った。
update_funcという関数を作っておいて、それを最初空にしておく。
update_funcが空のうちは目次を回し続ける。
で、目次でポチするとupdate_funcに飛びたいプログラムの関数が入る。そしてsetupして、個別で作ったloopに入って…みたいな感じだ。
個別で作ったloopの中に、「ボタンAが押されたらupdate_funcを空にして戻る」という指示を入れたので、これでプログラムから目次に戻ることもできるようになった。
以下、実際に心拍とジャイロと気温と環境のすごく素朴な表示が切り替えられるようになった統合版のコード。ただし目次はまだ増やす可能性がかなり大きいのでコメント型にして残してある。非常に見づらくなってしまった…
#include <vector> #include <M5Stack.h> #include <M5TreeView.h> #include <Wire.h> #include "utility/MPU9250.h" #include "MAX30100.h" #include "DHT12.h" #include "Adafruit_Sensor.h" #include <Adafruit_BMP280.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 MPU9250 IMU; MAX30100 sensor; M5TreeView treeView; DHT12 dht12; Adafruit_BMP280 bme; typedef std::vector<MenuItem*> vmi; void (*update_func)() = NULL; //update_funcの中身の有無でスイッチさせる //heartrate void switch_to_heatrbeat(MenuItem* sender) { update_func = H_loop; M5.Lcd.clear(); 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); } } } int lastMin = 70000, lastMax = 50000; int minS= 70000, maxS = 50000; int lastY = 50000; int x = 0; int count1 = 0; int p = 0; int lastp = 0; int k[10]; int i = 0; int bpm; int flag = 0; #define REDRAW 20 // msec void H_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<70000) lastMax = maxS + 20; minS = 70000; maxS = 50000; 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); } if (M5.BtnA.isPressed()) { M5.Lcd.clear(); update_func = NULL; } } //gyro void switch_to_gyromode(MenuItem* sender) { update_func = G_loop; } void G_loop() { if (IMU.readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01) { M5.update(); M5.Lcd.clear(); M5.Lcd.setCursor(0, 0); IMU.readAccelData(IMU.accelCount); IMU.getAres(); // get accelerometer scales saved to "aRes" IMU.ax = (float)IMU.accelCount[0] * IMU.aRes; // - accelBias[0]; IMU.ay = (float)IMU.accelCount[1] * IMU.aRes; // - accelBias[1]; IMU.az = (float)IMU.accelCount[2] * IMU.aRes; // - accelBias[2]; M5.Lcd.print("X-acceleration: "); M5.Lcd.print(1000 * IMU.ax); M5.Lcd.println(" mg "); M5.Lcd.print("Y-acceleration: "); M5.Lcd.print(1000 * IMU.ay); M5.Lcd.println(" mg "); M5.Lcd.print("Z-acceleration: "); M5.Lcd.print(1000 * IMU.az); M5.Lcd.println(" mg "); IMU.tempCount = IMU.readTempData(); IMU.temperature = ((float) IMU.tempCount) / 333.87 + 21.0; M5.Lcd.print("MPU9250 Temperature is "); M5.Lcd.print(IMU.temperature, 1); Serial.print(IMU.ax); Serial.print(","); Serial.print(IMU.ay); Serial.print(","); Serial.println(IMU.az); if (M5.BtnA.isPressed()) { M5.Lcd.clear(); update_func = NULL; } delay(200); } } //temperature void switch_to_temperature(MenuItem* sender) { update_func = T_loop; } void T_loop(){ M5.update(); M5.Lcd.clear(); M5.Lcd.setCursor(0, 0); IMU.tempCount = IMU.readTempData(); IMU.temperature = ((float) IMU.tempCount) / 333.87 + 21.0; M5.Lcd.setCursor(0, 0); M5.Lcd.setTextColor(WHITE); M5.Lcd.setTextSize(1); M5.Lcd.println("MPU9250 Temperature is "); M5.Lcd.setTextColor(YELLOW); M5.Lcd.setTextSize(6); M5.Lcd.setCursor(100, 100); M5.Lcd.print(IMU.temperature, 1); if (M5.BtnA.isPressed()) { M5.Lcd.clear(); update_func = NULL; } delay(500); } //environment void switch_to_environment(MenuItem* sender) { update_func = E_loop; M5.Lcd.clear(); M5.Lcd.setBrightness(10); Serial.println(F("ENV Unit(DHT12 and BMP280) test...")); while (!bme.begin(0x76)){ Serial.println("Could not find a valid BMP280 sensor, check wiring!"); M5.Lcd.println("Could not find a valid BMP280 sensor, check wiring!"); } M5.Lcd.clear(BLACK); M5.Lcd.println("ENV Unit test..."); } void E_loop(){ M5.update(); if (M5.BtnA.isPressed()) { update_func = NULL; M5.Lcd.clear(); } float tmp = dht12.readTemperature(); float hum = dht12.readHumidity(); float pressure = bme.readPressure(); Serial.printf("Temperatura: %2.2f*C Humedad: %0.2f%% Pressure: %0.2fPa\r\n", tmp, hum, pressure); M5.Lcd.setCursor(0, 0); M5.Lcd.setTextColor(WHITE, BLACK); M5.Lcd.setTextSize(3); M5.Lcd.printf("Temp: %2.1f \r\nHumi: %2.0f%% \r\nPressure:%2.0fPa\r\n", tmp, hum, pressure); delay(200); } void setup() { M5.begin(); Wire.begin(); Serial.begin(115200); IMU.initMPU9250(); IMU.calibrateMPU9250(IMU.gyroBias, IMU.accelBias); IMU.initAK8963(IMU.magCalibration); treeView.useFACES = true; treeView.useCardKB = true; treeView.useJoyStick = true; treeView.usePLUSEncoder = true; treeView.useFACESEncoder= true; treeView.clientRect.x = 2; treeView.clientRect.y = 10; treeView.clientRect.w = 316; treeView.clientRect.h = 216; treeView.setItems(vmi { new MenuItem("heartbeat", switch_to_heatrbeat) // { new MenuItem("sub 1-1", 11, vmi // { new MenuItem("sub 1-1-1", 111) // } ) // } ) , new MenuItem("gyro", switch_to_gyromode) // { new MenuItem("sub 2-1", vmi // { new MenuItem("sub 2-1-1", 211) // , new MenuItem("sub 2-1-2", 212) // } ) // , new MenuItem("sub 2-2", vmi // { new MenuItem("sub 2-2-1", 221) // , new MenuItem("sub 2-2-2", 222) // } ) // } ) , new MenuItem("temperature", vmi { new MenuItem("temperature", switch_to_temperature) , new MenuItem("more environment", switch_to_environment) // { new MenuItem("sub 3-1-2-1", 3121) // , new MenuItem("sub 3-1-2-2", 3122) // , new MenuItem("sub 3-1-2-3", 3123) // } ) // , new MenuItem("sub 3-1-3", vmi // { new MenuItem("sub 3-1-3-1", 3131) // , new MenuItem("sub 3-1-3-2", 3132) // , new MenuItem("sub 3-1-3-3", 3133) // } ) // } ) // , new MenuItem("sub 3-2", vmi // { new MenuItem("sub 3-2-1", vmi // { new MenuItem("sub 3-2-1-1", 3211) // , new MenuItem("sub 3-2-1-2", 3212) // , new MenuItem("sub 3-2-1-3", 3213) // } ) // , new MenuItem("sub 3-2-2", 322) // , new MenuItem("sub 3-2-3", 323) // } ) // , new MenuItem("sub 3-3", vmi // { new MenuItem("sub 3-3-1", vmi // { new MenuItem("sub 3-3-1-1", 3311) // , new MenuItem("sub 3-3-1-2", 3312) // , new MenuItem("sub 3-3-1-3", 3313) // } ) // , new MenuItem("sub 3-3-2", 332) // , new MenuItem("sub 3-3-3", vmi // { new MenuItem("sub 3-3-3-1", 3331) // , new MenuItem("sub 3-3-3-2", 3332) // , new MenuItem("sub 3-3-3-3", 3333) // } ) // } ) } ) } ); treeView.begin(); } void loop() { if (update_func == NULL) { MenuItem* mi = treeView.update(); if (mi != NULL) { M5.Lcd.fillRect(0,0,320,8,0); M5.Lcd.setTextColor(0xffff,0); M5.Lcd.setTextSize(1); M5.Lcd.drawString("menu:" + mi->title + " / tag:" + mi->tag, 15, 0, 1); } return; } update_func(); }
実際動かしてみると、ちゃんと動く!感動した。
でもまだまだ問題点が山積みである。
問題点
幾つか思い当たったものだけでも書いておく。
- 心拍と環境はセンサを取り付けないとできないので、センサが抜けているとどうなるか?というのを試したらとんでもないことになった。センサがない時に「センサを付けて」→ついたら実行、みたいなプログラムを一つ噛ませなければいけない
- 心拍がエグい。過去記事にもあるがBPMが死んでるし画面も補正に時間がかかる。そもそも案外時間や状況で値がだいぶ変わっているらしいので最初の数秒で大体の値の範囲を図って目印を作って上下を決めて…みたいなのが必要か?
- ジャイロに見せる気が全く感じられない。見せるものに変える。まずは文字の大きさ。あと見せ方。グラフが本当はいいけれど中々大変なんだろうな~。あるボタンを押した時にその時の測定値を固定して見せておいて、隣に今の状況(変化していくもの)を表示して見比べられる、みたいなの作れないかな
- 環境もジャイロも心拍も全部そうだけど記録ができるようにしたい。経時変化が全く見えずに今の状況しか分からない。勝手にパソコンにデータが送られてってほしい。
- 環境画面を開いた後目次に戻ると背景が謎の状況になる。おかしい。直したい。
- もっと機能を搭載したい。
- 実験に実際どういうものを作れば使えるか?というのを考えながら一つ複雑な実験特化のものを作りたい…けど今の所あまり想像ができない。生物実験で使えるのってどんな機能だろう…
- 心拍センサと環境センサが同じ接続場所を取り合うので逐一付け替えなきゃいけないので面倒だ。どうすれば解決する?
ということで沢山の問題点があるけれど、取り敢えず心拍はちょっと距離を置いて考えたい。実装の仕組みを考えないと小手先でできるほど熟練じゃないので…
直近の次の目標としては、手元にある他のセンサたちを一回試して使えそうならどんどん実装すること、そしてwifiの使い方確認とお試しをすることかなぁという風に思っている。今の所wifiに完全にびびって何もできていないので、このままだとポンコツを作って終わってしまう(今の時点でも相当ポンコツだけど)
あとこれはただの野望だけど無駄に走るロボットを作りたい。車輪のテストもします。
最近思ってきたけど、やってみる前から考えて分かることは勿論あって、考えて動かなきゃいけない所もあるけど
同時にやってみた後でしか分からないことも沢山あって、それらは絶対相容れないんだろうなと。
つまりやる前とやった後両方で見えるものがあるから、やらずに考えるだけ、考えずにやるだけ、どっちもだめなんだろうなという気持ちがある。
やるという壁を乗り越えなければならない。たとえそれがポンコツでもうんこを生み出す行為でもだ。やるぞ。これは三連休中にはやるからな!!!