※本ブログは現時点での情報です、最新情報はM5StickC非公式日本語リファレンスを参照してください。
昔に作ったものをM5StickCで作り直しました。
概要
M5StickCにGROVE接続のアナログセンサーを取り付けて、そのデータを0.5秒間隔でBluetoothSerialで送信します。
PCからBluetoothSerialに接続して、そのデータをProcessingを使って受信して、グラフ化とCSV出力を行います。
機材一覧
- M5StickC
- GROVE – GSRセンサ
- PC(Windows or Linux or Mac OS X)
Processingとは?
Arduino IDEの元の元になったアプリケーションで、主にグラフィカルな処理を得意としている開発環境です。
Javaで動作しているので、Arduino IDEとは開発言語が違いますが、比較的簡単にマルチプラットフォームで動くプログラムを作ることが可能です。
利用ライブラリ
- controlP5
プルダウンやグラフの描画が簡単にできるcontrolP5を利用していますので、事前にライブラリに追加しておいてください。追加方法はほぼArduino IDEと同じなのですぐにわかると思います。
上記のページにProcessingやcontrolP5の追加などを、わかりやすくまとめられています。
GSR(Galvanic Skin Response)センサとは?
皮膚への電流の流れやすさを測定するセンサで、汗などの影響によって測定値が変動します。手元にあったので利用しただけで、実際のところあまり使い所がありません。
M5StickC側のコード
#include <M5StickC.h> #include <BluetoothSerial.h> BluetoothSerial SerialBT; uint64_t chipid; char chipname[256]; void setup() { chipid = ESP.getEfuseMac(); sprintf( chipname, "M5StickC_%04X", (uint16_t)(chipid >> 32)); M5.begin(); M5.Lcd.setRotation(3); M5.Lcd.fillScreen(BLACK); M5.Lcd.printf("Bluetooth: %s\n", chipname); M5.Lcd.printf("Ver: %s %s\n", __DATE__, __TIME__); M5.Lcd.println(); M5.Lcd.printf("Val:"); SerialBT.begin(chipname); } void loop() { int val = analogRead(33); Serial.println(val); SerialBT.println(val); M5.Lcd.setCursor(8*4, 8*3); M5.Lcd.printf("%4d", val); delay(500); }
chipidを取得して、SerialBTの待受名を指定しているので、複数のM5StickCがある場合でも個別に選択して接続が可能です。
同じ名前の方が好ましい場合はSerialBT.begin()の待受名を固定してください。
内容はGROVE端子のアナログ入力(IO33)を取得して、画面への表示とSerialとSerialBTにデータを送信しています。
SerialBTしか使わないのであれば、Serialに送信しないほうがバッテリーが持つかもしれません。
delay(500)なので、約0.5秒間隔での送信になります。どんどんずれていくので、厳密に送信したい場合には時刻などを見て、インターバルで送信する必要があります。
ちなみに、このコードで約25分の動作が可能です。LCDのバックライトを暗くするとか、ボタンで消せるようにするなどで、もう少し動作時間を伸ばすことは可能だと思います。
Processing側のコード
import processing.serial.*; import controlP5.*; import java.util.*; ControlP5 cp5; Chart myChart; int sizeW = 640; // 画面横幅 640以上 int sizeH = 480; // 画面縦幅 480以上 int samplingCount = 10; // 未選択時のサンプリング数 int serialSpeed = 115200; // 未選択時の速度 int minValue = 0; // グラフの最小値 Serial myPort; int lf = 10; int miliSec; PrintWriter fp; float maxValue; Slider slider; ArrayList<Integer> samplingList = new ArrayList<Integer>(); List<String> ls = new ArrayList<String>(); List<String> lc = new ArrayList<String>(); void settings() { size(sizeW, sizeH); } void setup() { int oldSec = second(); for (int i = 0; i < 3000; i++) { // 起動時間からのミリ秒と時刻のミリ秒の差分を探す if ( oldSec != second() ) { miliSec = millis() % 1000; break; } delay(1); } cp5 = new ControlP5(this); // sampling lc.add("10"); lc.add("20"); lc.add("30"); lc.add("40"); lc.add("50"); lc.add("100"); lc.add("500"); lc.add("1000"); cp5.addScrollableList("sampling_count") .setPosition(sizeW-200, 0) .setType(ControlP5.LIST) .setSize(200, 200) .setBarHeight(20) .setItemHeight(20) .addItems(lc) ; // speed ls.add("9600"); ls.add("19200"); ls.add("38400"); ls.add("57600"); ls.add("115200"); cp5.addScrollableList("serial_speed") .setPosition(sizeW-200, 190) .setType(ControlP5.LIST) .setSize(200, 200) .setBarHeight(20) .setItemHeight(20) .addItems(ls) ; List l = Arrays.asList(Serial.list()); cp5.addScrollableList("serial_port") .setPosition(sizeW-200, 320) .setType(ControlP5.LIST) .setSize(200, 200) .setBarHeight(20) .setItemHeight(20) .addItems(l) ; String fileName = createFileName(); fp = createWriter(fileName); myChart = cp5.addChart("") .setPosition(0, 00) .setSize(sizeW-250, sizeH) .setRange(0, 1) .setView(Chart.LINE); myChart.getColor().setBackground(color(255, 100)); myChart.addDataSet("val"); myChart.setColors("val", color(255, 255, 255), color(255, 0, 0)); myChart.setData("val", new float[1000]); myChart.addDataSet("avg"); myChart.setColors("avg", color(0, 255, 0), color(255, 0, 0)); myChart.setData("avg", new float[1000]); myChart.addDataSet("min"); myChart.setColors("min", color(0, 0, 255), color(255, 0, 0)); myChart.setData("min", new float[1000]); myChart.setStrokeWeight(1.5); slider = cp5.addSlider("Value", 0, 1, 50, sizeW-250, 0, 10, sizeH); } float peek = 0; float lastpeek = 0; int mode = 0; int lastvalue = 0; void draw() { // 画面クリア background(0); // 何かデータを受信したら if ( myPort != null && myPort.available() > 0) { String data = myPort.readStringUntil(lf); // 文字列を受信 if ( data != null ) { float value = float(trim(data)); String timeStr = year()+"/"+month()+"/"+day()+" "+hour()+":"+minute()+":"+second()+"."; int milisec = (millis()-miliSec)%1000; if ( milisec < 10 ) { timeStr = timeStr + "00"; } else if ( milisec < 100 ) { timeStr = timeStr + "0"; } timeStr = timeStr + milisec; fp.print( timeStr+","); fp.println(int(trim(data))); fp.flush(); // 最大値更新 if ( maxValue < value ) { maxValue = value; myChart.setRange(minValue, maxValue); slider.setRange(minValue, maxValue); } // 測定値追加 samplingList.add((int)value); myChart.push("val", value); // 規定サンプル数以上の場合先頭削除 if ( samplingCount < samplingList.size() ) { samplingList.remove(0); } // 移動平均計算 int sum = 0; for ( int i = 0; i < samplingList.size(); i++ ) { sum += samplingList.get(i); } myChart.push("avg", sum / samplingList.size()); slider.setValue(int(value)); if ( lastvalue + 2 < ( sum / samplingList.size() ) ) { if ( mode == 1 ) { int j = myChart.getDataSet( "min" ).size() - 3; while ( 0 <= j && myChart.getDataSet("min").get( j ).getValue() == -1 ) { // 最後の値を探す j--; } println(j); if ( 0 <= j ) { lastpeek = peek; peek = sum / samplingList.size(); j += 1; int jstart = j; println(j); for (; j < myChart.getDataSet( "min" ).size(); j++ ) { myChart.getDataSet("min").get( j ).setValue( lastpeek + ( ( peek - lastpeek) / ( 1000 - jstart ) ) * ( j - jstart ) ); } } mode = 0; myChart.push("min", peek ); } else { myChart.push("min", -1 ); } } else { myChart.push("min", -1 ); if( ( sum / samplingList.size() ) + 20 < lastvalue ){ mode = 1; } } lastvalue = sum / samplingList.size(); } } } void serial_port(int n) { println(Serial.list()[n]); if ( myPort != null ) { myPort.stop(); } myPort = new Serial(this, Serial.list()[n], serialSpeed); } void serial_speed(int n) { serialSpeed = Integer.parseInt( ls.get(n) ); } void sampling_count(int n) { samplingCount = Integer.parseInt( lc.get(n) ); samplingList.clear(); } String createFileName() { String fileName= nf(year(), 2) + nf(month(), 2) + nf(day(), 2) +"-"+ nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2); fileName += ".csv"; return fileName; }

白が測定値で、緑が移動平均です。BluetoothSerialの場合には速度は何を選択しても通信が可能で、シリアルポートを選択すると受信を開始します。
プログラムのあるディレクトリにCSVファイルが出力されますので、そこに受信時間と受信した値が保存されます。
まとめ
比較的簡単にM5StickCを使うとGROVEセンサーの値をリアルタイムでグラフ化と保存が可能でした。
何も考えずに使っても25分は動きました。体に固定して、ワイヤレスでデータ取得するデバイスとしても使いやすそうですね。
コメント