airpocketのアイコン画像
airpocket 2022年04月14日作成 (2022年05月17日更新)
セットアップや使用方法 セットアップや使用方法 閲覧数 2323
airpocket 2022年04月14日作成 (2022年05月17日更新) セットアップや使用方法 セットアップや使用方法 閲覧数 2323

Spresenseで高速振動サンプリング

Spresenseで高速振動サンプリング

はじめに

SPRESENSEを使ってエッジAIについて勉強しようと思ったのですが、テーマとして振動解析を行いたいと思います。
私の知っている範囲で安価かつ最も高速な加速度サンプリングできるセンサーはMPU6886でしたので、M5StackのUnit IMUを使って3kHz程度での加速度サンプリングを行います。

動作の様子

ここに動画が表示されます

画面上に「335」と表示されているのがサンプリング周期の平均値(usec)です。概ね3kHzで測定できています。
測定は随時行っていますが、設定値以上の加速度を検出すると50回さかのぼった時点からの値を320個記録します。
描画を同時に行うとサンプリング周期を上げられないため、320個のデータ記録後にグラフ描画しています。

開発環境

プログラミングはArduino IDEで行います。
こちらのサイトなどを参考にArduino IDEにSpresenseのボードライブラリを導入してください。
測定した加速度はディスプレイにグラフ表示します。ディスプレイにはSpresenseでは定番のILI9341(SPI接続版)を使いました。
ディスプレイへの表示は、LovyanGFXのdevelopブランチのライブラリを使用しています。TwitterでつぶやいたところLovyanさんが即対応してくださいました。感謝します。

MPU6886

MPU6886用のライブラリはM5Stack用ライブラリをちょびっと改変して使用しています。
githubに公開している4つのファイルをダウンロードして、inoファイルと同じフォルダに保存してください。
変更内容はI2Cのインスタンス名をWire1からWireに変更した点と、処理の高速化のためZ軸データのみ読み込む関数を追加した点です。

高速化の手法

デフォルトの設定では、数百ヘルツでのサイクルでしたデータを取得できませんが、レジストリを書き換えて設定を変更することで高速化を実現しています。

測定したデータをすべて使用する。

デフォルト設定では測定したデータの内、5回に1回だけを使用して残り4回は使用していませんのですべてのデータを使用するようにします。

LPFをバイパスする。

LPFを使用すると処理に時間がかかりますので、LPFはバイパスします。

Z軸方向のデータのみ読み取る

これはライブラリの関数の問題ですが、標準のライブラリでは3軸の加速度すべてを読み取る設計となっています。解析したい振動の種類にもよりますが、1軸のみの振動でも良いのであれば、残りの2軸のデータを読み取る時間がロスになります。
今回は1軸データのみを使用する予定ですのでZ軸データのみを読み取る関数を追加しました。

以上の方法をすべて採用すると一秒間に3000回のデータを読み取れます。

使用したプログラム

MPU6886を制御するライブラリが必要です。M5Stack用のライブラリをちょっぴり変更しています。
https://github.com/airpocket-soundman/MPU6886_spresense/tree/main/src
こちらの4つのファイルをinoファイルと同じフォルダに保存しておきます。
今回使用したプログラムは次の通りです。
雑な作りですがとりあえず動きます。

#include <Wire.h>
#include <LGFX_SPRESENSE_sample.hpp>
#include "MPU6886.h"

#define SAMPLE_SIZE   320       //
#define PLAY_BACK     50
#define SAMPLE_CYCLE  333    

#define X0 5                  // 横軸の描画開始座標
#define MINZ -16000           // 縦軸の最小値
#define MAXZ 16000            // 縦軸の最大値

static unsigned long timeLog[SAMPLE_SIZE];    //サンプリング周期記録用
static unsigned long startTime;
static unsigned long finishTime;
static unsigned long timeNow;
float ax[SAMPLE_SIZE], ay[SAMPLE_SIZE], az[SAMPLE_SIZE];    // 加速度データを読み出す変数
static unsigned long counter = PLAY_BACK;
unsigned int delayTime = 100;

boolean serialOut  = true;
boolean graphOut   = true;

static LGFX lcd;
MPU6886 IMU;

void serialOutput(int i, float* az, long* timeLog);
void output(float* az, long* timeLog);
void drawGraphLine(int i, float* az);
unsigned int popAppend();

void setup() {

  lcd.init();
  lcd.fillScreen(TFT_BLACK);
  lcd.setCursor(0,0);
  lcd.setFont(&fonts::Font0);
  lcd.setTextColor(TFT_WHITE, TFT_BLACK);
  lcd.setTextSize(2);
  lcd.setRotation(1);

  Wire.begin();
  Wire.setClock(500000);
  Serial.begin(115200);
  Serial.println("start");
  IMU.Init();
  timeLog[SAMPLE_SIZE - 1] = micros();
}

void loop() {
  int i = 0;

  popAppend();
  if (az[SAMPLE_SIZE - 1] > 1.5 && counter >= SAMPLE_SIZE) {
    counter = PLAY_BACK;
    startTime = micros();
  }
  if (counter == SAMPLE_SIZE && micros() > 3000000){
    finishTime = micros();
    lcd.fillScreen(TFT_BLACK);  // 画面をクリア
    lcd.setCursor(0, 0);
    lcd.println((finishTime - startTime)/(SAMPLE_SIZE - PLAY_BACK));
    output(az, timeLog);
  }
}

void drawGraphLine(int i, float* az){
  if (i > 1) {
      int y0 = map((int)(az[i - 1] * 1000), MINZ, MAXZ, lcd.height(), 0);
      int y1 = map((int)(az[i] * 1000), MINZ, MAXZ, lcd.height(), 0);
      lcd.drawLine(i - 1 + X0, y0, i + X0, y1, TFT_GREEN);
  }
}

void serialOutput(int i, float* az, long* timeLog){
  Serial.print("cycle(usec):");
  Serial.print(timeLog[i + 1] - timeLog[i]);
  Serial.print(" , Zaxis accelerration(mG):");
  Serial.print(az[i]);
  Serial.print(" , sample num:");
  Serial.println(i); 
}

void output(float* az, long* timeLog){
  for (int i = 0; i < SAMPLE_SIZE; i++){
    if (serialOut == true) serialOutput(i, az, timeLog);
    if (graphOut == true) drawGraphLine(i, az); 
  }
}


unsigned int popAppend(){
  float azNow = 0;
  while ((micros() - timeLog[SAMPLE_SIZE - 1]) < SAMPLE_CYCLE - 20) delayMicroseconds(2);
  timeNow = micros();
  IMU.getAccelZData(&azNow);
  for (int i = 0; i < SAMPLE_SIZE-1; i++){
    az[i] = az[i + 1];
    timeLog[i] = timeLog[i + 1];
  }
  timeLog[SAMPLE_SIZE - 1] = timeNow;
  az[SAMPLE_SIZE -1] = azNow;
  counter++;
  return SAMPLE_CYCLE;

}

このプログラムではデータをディスプレイに表示しておしまいですが、AIの学習用データにしなければならないのでラベリング情報とセットでMicroSDカードに保存する予定です。

4
airpocketのアイコン画像
電子工作、プログラミング、AI、DIY、XR、IoT M5Stack / Raspberry Pi / Arduino / spresense / K210 / ESP32 / Maix / maicro:bit / oculus / Jetson Nano / minipupper etc
ログインしてコメントを投稿する