ESP32-WROOM-32Eで加速度の変化をグラフ表示する

組み込みエンジニア

こんにちは、ENGかぴです。

ESP32-WROOM-32EのWire(SPI)を使用すると加速度センサー(ADXL345)の加速度やタップ情報などを取得することができます。加速度情報を取得しながらイベント発生で加速度データを保管してグラフ表示する方法をまとめています。

ESP32-WROOM-32Eの開発環境はArduino IDEを使用しています。またESP32-WROOM-32E開発ボード(秋月電子)及びADXL345モジュール(秋月電子)を使用しています。

ESP32-WROOM-32Eで動作確認したことについてリンクをまとめています。

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

加速度データをグラフで表示する

グラフ表示はChart.jsを使っています。Chart.jsを使ってブラウザー上にグラフ表示する方法を下記記事まとめています。

Chart.jsを使ってデータをブラウザー上でグラフ表示する

ChartライブラリをESP32-WROOM-32EのFlashに書き込む

ChartライブラリをESP32-WROOM-32EのFlashに書き込んで読み出して使用しています。Flashに書き込むためにSPIFFSプラグインをダウンロードしてArduino IDEのツールに「ESP32 Sketch Data Upload」を追加する方法などは下記記事を参考にしてください。

ESP32-WROOM-32Eで温湿度データをグラフで表示する

グラフ表示するまでのポイントを以下に示しています。

ESP32でグラフ表示するまでの手順
  • 手順1
    ESP32 Sketch Data Uploadを追加
  • 手順2
    ChartライブラリをSPIFFSに書き込む
  • 手順3
    SPIFFSからChartライブラリを読み込みHTMLデータに組み込む

グラフ表示用のデータを準備する

加速度データをグラフ表示するためのデータを準備します。グラフ表示する目安としてADXL345のイベントを使います。今回はFREE FALLがイベントを検出した前後250msの加速度情報を保存して表示します。

加速度情報の保存の考え方
加速度情報の保存の考え方

加速度情報を保管するバッファはリング構成にすることでxyzdata[]配列のサイズをオーバーしないようにデータを更新することができます。wpはxyzdata[]配列におけるデータの格納場所を示すポインタです。

FREE FALLイベントが発生すると250ms分の加速度情報を取得してからwave[]配列にイベント発生時の加速度情報を格納します。

int16_t rp;
uint16_t i;
      
  rp = getdata.wp;
  if( ++rp >= CHART_SZ ){
    rp = 0;
  }
  //rpを更新してデータを時系列にデータを並び替えてwave[]に格納      
  for( i = 0; i < CHART_SZ; i++){
    wave[0][i] = getdata.xyz[0][rp];
    wave[1][i] = getdata.xyz[1][rp];
    wave[2][i] = getdata.xyz[2][rp];

    if( ++rp >= CHART_SZ ){
      rp = 0;
    }
}

getdata.xyz[]配列に格納している加速度情報を時系列でグラフで表示するために並び替えしながら格納します。wpはgetdata[]において次にデータを格納する場所を示すポインタなのでwp+1のデータが時系列でみた時getdata[]内の最古のデータになります。

最古のデータの位置をrpの初期値としてgetdata[]配列のサイズ分順に取得して格納することで時系列で整理したデータになります。

FREE FALLイベント発生直後からデータを取得するのではなくイベント発生以前のデータを含めて取得することでイベント発生の様子を確認することができます。

HTMLデータの生成

  str += "       datasets: [{";
  str += "         label: 'X',";
  str += "         fill: false,";
  str += "         borderColor: 'blue',";
  str += "         borderWidth: 1,";
  str += "         pointRadius: 0,";
  str += "         pointHoverBorderWidth: 10,";
  str += "         data: [";
                     for(uint16_t i = 0; i< CHART_SZ; i++ ){
                       str += wave[0][i]; 
                       if(i != CHART_SZ-1 )str += ",";
                     }
  str += "         ]";
  str += "       }, {";

Chart.jsでX軸やY軸のデータを配列名で指定することでグラフの表示ができますが、HTML上の<script>と温湿度情報を格納している変数の関連付けができないため温湿度情報の配列名をそのまま指定することができません。

配列に値を入れて文字列を生成する方法でグラフデータをセットする必要があります。配列のサイズ分for分で値をdata[]に追加してHTMLデータを生成しています。

例)文字列追加の様子
配列の一番目を追加:data[0.03,
配列の二番目を追加:data[0.03,0.04,
配列のN番目を追加:data[0.03,0.04,・・・,0.47,
配列の追加が完了:data[0.03,0.04,・・・,0.47,・・・,1.64]

一文字でも間違うと表示できなくなるためテキストファイルなどでHTML形式で文章を生成しGoogle Chromeで開いて表示できることを確認しておくとよいでしょう。

ESP32のString型は65535バイト分(2バイト)までしか対応していないため文字の増やし過ぎには注意が必要です。

動作確認

動作確認用の回路図
動作確認用の回路図

ESP32-WROOM-32EとADXL345の配線例を示しています。回路図の番号はESP32 -WROOM-32Eの左上を1ピンとした時反時計回りにピンを数えた場合の番号としています。ピン番号横の()内の番号はシルク印刷されているピンの名称です。

加速度情報のグラフ表示(FREE FALL発生時)
加速度情報のグラフ表示(FREE FALL発生時)

スマホのGoogle ChromeでIPアドレス「192.168.11.2」を入力しリクエストを送るとESP32-WROOM-32Eからスマホに返信しFREE FALLが発生した時のデータの様子を示すグラフ表示されました。サンプル数250の時のFREE FALLが発生しています。(XYZ軸の値がほとんど0になっていることが分かります。)

ブラウザーの環境によってはWiFi接続が切れることがありました。ノートPCのWiFiやiPhoneでは接続が切断しませんでしたがAndroidでは切断することがありました。

通信なので接続が切断しても復帰できる仕組みを検討しておくことが理想です。シリアルプロッタでのグラフ表示を行ってFREE FALL発生時のデータと一致するかを確かめました。

シリアルプロッタでの確認
シリアルプロッタでの確認

シリアルプロッタのグラフとスマホのグラフを比較すると一致していることが分かりました。位置がずれているのはシリアルプロッタをスクリーンショットするタイミングがわずかに異なっているからです。

今回はFREE FALL発生をトリガ(起点)にしてデータを取得してグラフ表示しましたが、計測したデータを任意のタイミングで取得しイベントが発生する場合のデータの様子を確認する方法は応用範囲が広いと思います。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

#include <Wire.h>
#include <WiFi.h>
#include <WebServer.h>
#include <SPIFFS.h>
#include <ADXL345.h>

#define SLAVE_ADRS 0x45
#define POLYNOMIAL 0x31
#define PIN_DI  22
#define CHART_SZ 500
#define CHART_HALF_SZ (CHART_SZ/2)
#define TIME_OFF -1 //タイマーを使用しない場合
#define TIME_UP 0 //タイムアップ
#define BASE_CNT 10 //ベースタイマカウント値
#define TIME_ADXL_MAX 1
#define TIME_EVENT_WAIT_MAX 600

#define INT_DATA_READY_BIT_MASK 0x80
#define INT_SINGLE_TAP_BIT_MASK 0x40
#define INT_DOUBLE_TAP_BIT_MASK 0x20
#define INT_ACTIVITY_BIT_MASK   0x10
#define INT_INACTIVITY_BIT_MASK 0x08
#define INT_FREE_FALL_BIT_MASK  0x04
#define INT_WATERMARK_BIT_MASK  0x02
#define INT_OVERRUNY_BIT_MASK   0x01

#define ACT_TAP_ACT_X 0x40
#define ACT_TAP_ACT_Y 0x20
#define ACT_TAP_ACT_Z 0x10
#define ACT_TAP_TAP_X 0x04
#define ACT_TAP_TAP_Y 0x02
#define ACT_TAP_TAP_Z 0x01

#define PIN_DI1 35
//#define MONITER_USE

typedef struct{
    uint16_t wp;
    uint16_t rp;
    double xyz[3][CHART_SZ];
}DATA_RING;

const char *ssid = "EngKapi1"; //SSID
const char *pass = "22223333"; //password
const IPAddress ip(192,168,11,2); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク
  
/* 変数宣言 */
WebServer Wserver(80);
uint32_t beforetimCnt = millis();
ADXL345 adxl;
byte intsrc;    //割り込みイベント
byte intsrc2;   //アクティブまたはタップイベントの内容
byte int2src;
int16_t  timAdxl345get;
int16_t  timEventWait = TIME_OFF;
volatile bool intboo1 = false;
double wave[3][CHART_SZ];
double xyzdata[3][CHART_SZ];
DATA_RING getdata;
uint16_t evescnt;

/* プロトタイプ宣言 */
void mainApp(void);
void HtmlSet(void);
void handleNotFound(void);
void charDataSet(void);
void InitAdxl345();
void Adxl345Rcv();
void IRAM_ATTR irt1(void);

void setup() {

  Serial.begin(115200);
  pinMode(PIN_DI1,INPUT_PULLDOWN);
  attachInterrupt(PIN_DI1,irt1,RISING);
  Wire.begin();
  InitAdxl345();

  WiFi.softAP(ssid, pass); //WiFiのアクセスポイントの設定
  WiFi.softAPConfig(ip, ip, subnet); //アクセスポイントのIP及びサブネットマスク
  SPIFFS.begin();
  Wserver.serveStatic("/myfavicon.ico",SPIFFS,"/myfavicon.ico");
  Wserver.serveStatic("/chart.min.js",SPIFFS, "/chart.min.js");
  
  Wserver.on("/", HTTP_GET, HtmlSet); //URLを指定して処理する関数を指定
  Wserver.onNotFound(handleNotFound); //URLが存在しない場合の処理する関数を指定
  Wserver.begin(); //Webサーバーの開始
}

void loop() {

  mainTimer();
  mainApp();
  Wserver.handleClient();
}
/* タイマ管理 */
void mainTimer(void){

  if ( millis() - beforetimCnt >= BASE_CNT ){
    beforetimCnt = millis();

    if( timAdxl345get > TIME_UP ){
      --timAdxl345get;
    }
    if( timEventWait > TIME_UP ){
      --timEventWait;
    }
  }
}
/* DIがRISINGして割込み処理 */
void IRAM_ATTR irt1(void){
    intboo1 = true;
}
/* メイン処理関数 */
void mainApp(void){
    
  if( intboo1 ){ //INT1
    intboo1 = false;
    intsrc = adxl.getInterruptSource();
    #ifdef MONITER_USE
        if( intsrc & INT_SINGLE_TAP_BIT_MASK){
            Serial.println("SINGLE_TAP");
        }
        if( intsrc & INT_DOUBLE_TAP_BIT_MASK){
            Serial.println("DOUBLE_TAP");
        }
        if( intsrc & INT_ACTIVITY_BIT_MASK){
            Serial.println("Activity");
        }
        if( intsrc & INT_INACTIVITY_BIT_MASK){
            Serial.println("inactivity");
        }
        if( intsrc & INT_FREE_FALL_BIT_MASK){
            Serial.println("FREE_FALL");
        }
        if( adxl.isActivitySourceOnX()){
            Serial.println("ACT X");
        }
        if( adxl.isActivitySourceOnY()){
            Serial.println("ACT Y");
        }
        if( adxl.isActivitySourceOnZ()){
            Serial.println("ACT Z");
        }
        if(adxl.isTapSourceOnX()){
            Serial.println("TAP X");
        }        
        if(adxl.isTapSourceOnY()){
            Serial.println("TAP Y");
        } 
        if(adxl.isTapSourceOnZ()){
            Serial.println("TAP Z");
        }
    #endif
    if( intsrc & INT_FREE_FALL_BIT_MASK){
      if( timEventWait == TIME_OFF ){
        timEventWait = TIME_EVENT_WAIT_MAX;
      }
    }
  }

  if( timAdxl345get == TIME_UP ){
    timAdxl345get = TIME_ADXL_MAX;
    Adxl345Rcv();
  }
  if( timEventWait == TIME_UP ){
    timEventWait = TIME_OFF;
  }
}
/* 加速度情報の取得 */
void Adxl345Rcv(){
  double xyz[3];
  double ax,ay,az;

  adxl.getAcceleration(xyz); //XYZの加速度の取得
  ax = xyz[0];
  ay = xyz[1];
  az = xyz[2];

  getdata.xyz[0][getdata.wp] = ax;
  getdata.xyz[1][getdata.wp] = ay;
  getdata.xyz[2][getdata.wp] = az;

  if(++getdata.wp >= CHART_SZ ){
      getdata.wp = 0;
  }

  if( timEventWait > TIME_UP ){
    if( ++evescnt >= CHART_HALF_SZ ){ //イベント発生から250ms経過
      int16_t rp;
      uint16_t i;
      
      rp = getdata.wp;
      if( ++rp >= CHART_SZ ){
        rp = 0;
      }
        
      for( i = 0; i < CHART_SZ; i++){
        wave[0][i] = getdata.xyz[0][rp];
        wave[1][i] = getdata.xyz[1][rp];
        wave[2][i] = getdata.xyz[2][rp];

        if( ++rp >= CHART_SZ ){
          rp = 0;
        }
      }
      timEventWait = TIME_UP;
      evescnt = 0;
    }
  }
  #ifndef  MONITER_USE
    Serial.print("X:");
    Serial.print(ax);
    Serial.print(",");
    Serial.print("Y:");
    Serial.print(ay);
    Serial.print(",");
    Serial.print("Z:");
    Serial.print(az);
    Serial.println("");
  #endif
}
/* クライアントに返信するhtmlデータを生成 */
void HtmlSet(void){
  String str = "";

  str += "<html lang=\"ja\">";
  str += "<head>";
  //str += "<meta http-equiv=\"refresh\" content=\"5\">";
  str += "<meta charset=\"UTF-8\">";
  str += "<title>Sensor SHT35</title>";
  str += "<link rel='shortcut icon' href='/myfavicon.ico' />";
  str += "<script src = '/chart.min.js'></script>"; 
  str += "</head>";
  str += "<body>";
  str += "<h1>ESP32-ADXL345加速度センサ</h1>";
  str += "<h2>WebServerライブラリを使用</h2>";
  str += "<div style='height: 600px; width: 800px; margin: auto;'>";
  str += "<canvas id='ChartID'></canvas>";
  str += "</div>";
  str += "<script>";
  str += "var ctx = document.getElementById('ChartID').getContext('2d');";
  str += "var myChart = new Chart(ctx, {";
  str += "   type: 'line',";
  str += "   data: {";
  str += "     labels: [ ";
                 for(uint16_t i = 0; i< CHART_SZ; i++ ){
                   str += i; 
                   if(i != CHART_SZ-1 )str += ",";
                 }   
  str += "     ],";
  str += "       datasets: [{";
  str += "         label: 'X',";
  str += "         fill: false,";
  str += "         borderColor: 'blue',";
  str += "         borderWidth: 1,";
  str += "         pointRadius: 0,";
  str += "         pointHoverBorderWidth: 10,";
  str += "         data: [";
                     for(uint16_t i = 0; i< CHART_SZ; i++ ){
                       str += wave[0][i]; 
                       if(i != CHART_SZ-1 )str += ",";
                     }
  str += "         ]";
  str += "       }, {";
  str += "         label: 'Y',";
  str += "         fill: false,";
  str += "         borderColor: 'red',";
  str += "         borderWidth: 1,";
  str += "         pointRadius: 0,";
  str += "         pointHoverBorderWidth: 10,";
  str += "         pointStyle: 'rect',";
  str += "         data: [";
                     for(uint16_t i = 0; i< CHART_SZ; i++ ){
                       str += wave[1][i]; 
                       if(i != CHART_SZ-1 )str += ",";
                     }
  str += "         ]";
  str += "       }, {";
  str += "         label: 'Z',";
  str += "         fill: false,";
  str += "         borderColor: 'green',";
  str += "         borderWidth: 1,";
  str += "         pointRadius: 0,";
  str += "         pointHoverBorderWidth: 10,";
  str += "         pointStyle: 'triangle',";
  str += "         yAxisID: 'y',";
  str += "         data: [";
                     for(uint16_t i = 0; i< CHART_SZ; i++ ){
                       str += wave[2][i]; 
                       if(i != CHART_SZ-1 )str += ",";
                     }
  str += "         ]";
  str += "       }]";
  str += "     },";
  str += "     options: {";
  str += "       responsive: true,";
  str += "       plugins: {";
  str += "         title: {";
  str += "           display: true,";
  str += "           text: 'ADXL345から取得した加速度',";
  str += "           font: {";
  str += "             size: 18,";
  str += "           },";
  str += "         },";
  str += "       },";
  str += "       scales: {";
  str += "         x: {";
  str += "           display: true,";
  str += "           stacked: false,";
  str += "           title: {";
  str += "             display: true,";
  str += "             text: 'サンプル数',";
  str += "             font: {";
  str += "               size: 16,";
  str += "             },";      
  str += "           },";
  str += "         },";
  str += "         y: {";
  str += "           min: -4,";
  str += "           max: 4,";
  str += "           title: {";
  str += "             display: true,";
  str += "             text: '加速度[g]',";
  str += "             color: 'black',";
  str += "             font: {";
  str += "               size: 16,";
  str += "             },";
  str += "           },";
  str += "           ticks:{";
  str += "             display: true,";          
  str += "             color: 'black',";
  str += "           },";
  str += "         },";
  str += "       },";
  str += "     },";
  str += "})";
  str += "</script>";
  str += "</body>";
  str += "</html>";
  detachInterrupt(PIN_DI1);//htmlデータ送信時に割り込み禁止
  Wserver.send(200,"text/html", str); 
  //HTTPレスポンス200でhtmlデータとして送信
  attachInterrupt(PIN_DI1,irt1,RISING); //割り込み復帰
}
/* URLが存在しない場合の処理 */
void handleNotFound(void) {

  String message = "File Not Found\n\n";
  message += "URI: ";
  message += Wserver.uri();
  message += "\nMethod: ";
  message += (Wserver.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += Wserver.args();
  message += "\n";

  for (uint8_t i = 0; i < Wserver.args(); i++) {
    message += " " + Wserver.argName(i) + ": " + Wserver.arg(i) + "\n";
  }
  Wserver.send(404, "text/plain", message); //テキストファイルであることを示している。
}
/* 加速度センサーの初期化 */
void InitAdxl345(){
    byte dmy;

    adxl.powerOn();
    //set activity/ inactivity thresholds (0-255)
    adxl.setActivityThreshold(75); //62.5mg per increment 75
    adxl.setInactivityThreshold(75); //62.5mg per increment
    adxl.setTimeInactivity(10); // how many seconds of no activity is inactive?
    //look of activity movement on this axes - 1 == on; 0 == off 
    adxl.setActivityX(1);
    adxl.setActivityY(1);
    adxl.setActivityZ(1);
    //look of inactivity movement on this axes - 1 == on; 0 == off
    adxl.setInactivityX(1);
    adxl.setInactivityY(1);
    adxl.setInactivityZ(1);
    //look of tap movement on this axes - 1 == on; 0 == off
    adxl.setTapDetectionOnX(0);//0
    adxl.setTapDetectionOnY(0);//0
    adxl.setTapDetectionOnZ(1);//1
    //set values for what is a tap, and what is a double tap (0-255)
    adxl.setTapThreshold(50); //62.5mg per increment
    adxl.setTapDuration(15); //625us per increment
    adxl.setDoubleTapLatency(80); //1.25ms per increment
    adxl.setDoubleTapWindow(200); //1.25ms per increment
    //set values for what is considered freefall (0-255)
    adxl.setFreeFallThreshold(5); //(5 - 9) recommended - 62.5mg per increment 7
    adxl.setFreeFallDuration(20); //(20 - 70) recommended - 5ms per increment 45
    
    adxl.setFullResBit(1);
    adxl.setRangeSetting(4); //4g Range
  
    adxl.setInterruptMapping( ADXL345_INT_SINGLE_TAP_BIT,   ADXL345_INT1_PIN );
    adxl.setInterruptMapping( ADXL345_INT_DOUBLE_TAP_BIT,   ADXL345_INT1_PIN );
    adxl.setInterruptMapping( ADXL345_INT_FREE_FALL_BIT,    ADXL345_INT1_PIN );
    adxl.setInterruptMapping( ADXL345_INT_ACTIVITY_BIT,     ADXL345_INT1_PIN );
    adxl.setInterruptMapping( ADXL345_INT_INACTIVITY_BIT,   ADXL345_INT1_PIN );
 
    adxl.setInterrupt( ADXL345_INT_SINGLE_TAP_BIT, 1);
    adxl.setInterrupt( ADXL345_INT_DOUBLE_TAP_BIT, 1);
    adxl.setInterrupt( ADXL345_INT_FREE_FALL_BIT,  1);
    adxl.setInterrupt( ADXL345_INT_ACTIVITY_BIT,   1);
    adxl.setInterrupt( ADXL345_INT_INACTIVITY_BIT, 1);

    dmy = adxl.getInterruptSource(); //ダミーで割り込みビットをクリア
}

本ソースコードでグラフ表示するためにChart.jsライブラリをESP32 Sketch Data Uploadを使ってフラッシュ領域に書き込んでおく必要があります。

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

Code Camp完全オンラインのプログラミング個人レッスン【無料体験】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました