Arduinoでデジタル加速度センサーの情報を取得

組み込みエンジニア

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

ArduinoのWire(SPI)を使用すると加速度センサー(ADXL345)の加速度やタップ情報などを取得することができます。ADXL345のライブラリを流用して加速度センサーの情報をFIFO機能を使用して取得する方法をまとめました。

ADXL345モジュール(秋月電子)を使用して加速度を取得しています。ライブラリは一部ソースコードを追加して使用しています。

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

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

加速度センサーのライブラリを使用する

ADXL345の製造元のアナログデバイセズのHPに提供されているライブラリを使用してもよいのですが、今回はseeeduino用に有志が提供している「Accelerometer ADXL345」ライブラリを使用します。seeeduino用ですが、Arduino環境で作られているためUNOでも使用することができます。

ADXL345のライブラリについて

Arduino IDEを使ってライブラリをインストールして追加します。Arduino IDEのスケッチ欄からライブラリをインクルードを選択するとライブラリを管理の項目が表示されるのでクリックしてライブラリマネージャに遷移します。

ライブラリマネージャの検索欄にADXLを入力すると候補としてADXLに関するライブラリが表示されます。「Accelerometer ADXL345」を選択してインストールします。ライブラリ関数で設定できる項目については下記記事にまとめています。

Seeeduino XIAOでデジタル加速度センサーの情報を取得

ADXL345のFIFOを使用する

ADXL345の特徴でもあるFIFOを使用します。FIFOモードは32レベル(32個のデータを保管できる)機能でホストプロセッサの負荷を低減することができます。詳細はデータシートで確認できます。

アナログデバイセズ:MEMS加速度センサーADXL345

FIFOモードには4つのモードがあります。

  モード    説明
バイパスFIFOは動作せず空のまま(データを保管しない)
FIFO測定データがFIFOに格納される。
FIFO_CTLレジスタのSamplesビットで指定した値以上になるとウォーターマーク割り込みの条件となり満杯(32個のデータ)になった時点でデータの収集を停止する。
ストリーム測定データがFIFOに格納される。
FIFO_CTLレジスタのSamplesビットで指定した値以上になるとウォーターマーク割り込みの条件となる。FIFOが満杯(32個のデータ)の時は古いデータを破棄して保管する。
トリガFIFOは指定された割り込みピンの状態と連動して32個の測定サンプルを保持する。FIFO_CTLレジスタのTriggerビットによって選択されたピンがセットされるとSamplesビットで指定した値の分のデータを保存してFIFO動作して満杯でない限りデータを保管する。
ADXL345のFIFOモード

本記事ではストリームモードを実装しています。ウォーターマーク割り込みが発生したとき保管したデータの平均値をとり加速度のデータとして採用するようにしています。

ライブラリの一部を追加する

FIFOモードの実装はスケッチ例のADXL345_demo_codeでは使用されていない機能です。FIFO_CTLとFIFO_STATUSレジスタはライブラリのADXL345.hに定義されているものの実装いない(気づいていないだけかもしれません)ため追加実装します。

追加例にはadd-1とadd-2をコメントで示していますがadd-1はseeeduino XIAOで動作確認したときに追加したものです。add-2は今回の実装で追加した部分となります。add-1については下記記事を参考にしてください。

Seeeduino XIAOでデジタル加速度センサーの情報を取得

ADXL345.hの内容を表示するためにはVsCode上で関数にカーソルを合わせて右クリックしたときに表示される宣言へ移動を選択すると表示できます。

//ADXL345.hに追加する内容
#define ADXL345_FIFO_MODE_BYPASS    0x00
#define ADXL345_FIFO_MODE_FIFO      0x40
#define ADXL345_FIFO_MODE_STREAM    0x80
#define ADXL345_FIFO_MODE_TRG       0xC0

#define部分にFIFOのモードを設定するための宣言をしています。FIFO_CTLレジスタを設定する際に引数として指定する際に使用します。

class ADXL345
{
public:
    byte isTapSourcebyte(); //add-1
    void ADXL345::setFiFobyte(byte val); //add-2
    void ADXL345::setFiFobyte(byte mode, byte smp); //add-2 引数で指定して設定する
    void ADXL345::setFiFobyte(byte mode, byte smp, byte trg); //add-2 トリガ割り込みの指定込み
    byte ADXL345::getFiFobyte(); //FIFO_CTLのレジスタ内容を読み込む
    byte ADXL345::getFiFoStatusbyte();
    void ADXL345::getAcceleration2(double* accelxyz, int* xyz);
};

追加する関数を宣言しています。宣言した後はADXL345.cppに処理を追加します。

void ADXL345::setFiFobyte(byte val){  //設定方法1
    writeTo(ADXL345_FIFO_CTL, val); 
}
void ADXL345::setFiFobyte(byte mode, byte smp){  //設定方法2
    byte fifo_ctl;

    fifo_ctl |= mode & 0xC0;
    fifo_ctl |= smp & 0x1F;    

    writeTo(ADXL345_FIFO_CTL, fifo_ctl); 
}

void ADXL345::setFiFobyte(byte mode, byte smp,byte trg){  //設定方法3
    byte fifo_ctl;

    fifo_ctl |= mode & 0xC0;
    fifo_ctl |= trg & 0x20;
    fifo_ctl |= smp & 0x1F;

    writeTo(ADXL345_FIFO_CTL, fifo_ctl); 
}

FIFO_CTLレジスタを設定は3つの設定方法で行えます。設定方法1ではレジスタ値で設定したいビットを確認して1バイトデータを生成して入力します。設定方法2と3は引数に指定した値の論理和を計算してWireで送信しています。

byte ADXL345::getFiFobyte(){
    byte val;
    readFrom(ADXL345_FIFO_CTL, 1, &val);
    return val; 
}

byte ADXL345::getFiFoStatusbyte(){
    byte val;
    readFrom(ADXL345_FIFO_STATUS, 1, &val);
    return val; 
}

FIFO_CTL及びFIFO_STATUSレジスタをリードする際に使用する関数です。どちらの関数もレジスタのデータ値をWireで1バイトデータとして受信して戻り値で返しています。追加した関数の内容をまとめると以下の通りになります。

追加関数説明
setFiFobyte(引数)引数に1バイトデータを指定する。
setFiFobyte(引数1,引数2)引数1にFIFOモードに関する定義を指定する。
追加したADXL345_FIFO_MODE_STREAMなどを指定。
引数2にFIFO_CTL内のsamplesの値を指定する。
setFiFobyte(引数1,引数2,引数3)引数1と2は上記と同様。
引数3にTriggerの値(0または1)を指定する。
getFiFobyte()FIFO_CTLレジスタの内容を戻り値で返す。
getFiFoStatusbyte()FIFO_STATUSレジスタの内容を戻り値で返す。
追加した関数と説明

FIFO使用時のデータを換算する

FIFOを使用すると測定データを読み込むまで保管することができるのがメリットです。例えば8回分のデータの平均値を使って加速度データに換算するようなことができるようになります。

スケッチ例のADXL345_demo_codeで使用しているgetAcceleration()は瞬時値を換算するようになっているためFIFOのように複数回取得したデータを変換するのには使いにくい部分があります。

データシートの「2.5V以外の電圧での動作」を確認すると感度の変化による影響が記載されています。データシートをもとに加速度のデータを生成する関数を追加しました。

void ADXL345::getAcceleration2(double* accelxyz, int* xyz){

    accelxyz[0] = ( xyz[0] * 3.9 * ((double)256 / 265)) / 1000;
    accelxyz[1] = ( xyz[1] * 3.9 * ((double)256 / 265)) / 1000;
    accelxyz[2] = ( xyz[2] * 3.9 * (1)) / 1000;
}

どのレンジにおいても3.9mg/LSBであることや3.3Vの電源電圧で動作するときは2.5V時の256LSB/gが265LSB/gに変化することを考慮して換算しています。

FIFOの使用例

FIFOモードをストリームに設定して8回分のデータをストックして平均値を計算してから加速度のデータに変換します。

void setup(){
    adxl.setFiFobyte(ADXL345_FIFO_MODE_STREAM, G_DATA_MAX); //FIFO_CTLの設定(ストリーム)
}

void loop(){
    if( digitalRead(PIN_DI2) ){ //INT2 DATA_READ
        int2src = adxl.getInterruptSource();

        if( int2src & INT_WATERMARK_BIT_MASK){ //ウォーターマーク割り込みが発生
            Adxl345Rcv();
        }
    } 
}

FIFO_CTLレジスタにストリームモードで動作するように初期設定を行っています。FIFOモードではウォーターマーク割り込み発生によって指定した回数分のデータが格納されたことが分かります。

void Adxl345Rcv(){
    int xyz[3];
    int sumxyz[3];
    byte i;
    byte cnt;

    cnt = adxl.getFiFoStatusbyte() & 0x3F;  //ウォーターマーク割り込み発生時のデータ数

    sumxyz[0] = 0;
    for( i=0;i < cnt; i++){
        adxl.readAccel(&xyz[0]);
        sumxyz[0] += xyz[0]; //平均をとるために合計を計算
    }

    sumxyz[0] = sumxyz[0] >> 3; //3回シフト8で割るのと同じ
    adxl.getAcceleration2( &xyzData[0], &sumxyz[0]); //XYZの加速度の取得
}

ウォーターマーク割り込みが発生する条件を8回としているため割り込みが発生するタイミングはデフォルトであれば100Hz毎に測定するため80ms経過後になります。上の例ではレジスタ値を読み込んでcnt値分だけ合計値を計算した後でシフト演算で平均値を算出しています。

合計を右に3回シフトすることは8で割るのと同じことです。ビットシフトを使用することで処理を高速化することができます。そのため2の倍数を指定することが有効です。

平均値を算出した後は追加したgetAcceleration2()を使用して加速度のデータに変換しています。

動作確認

ArduinoでADXL345の動作を確認
ArduinoでADXL345の動作を確認

ArduinoとADXL345の配線例を示しています。ADXL345はDC2.5V(DC3.3V)の電源で動作するモジュールであるためArduinoの5Vを直接印加することができません。ArduinoからADXL345に向かう信号についてはレベル変換IC(FXMA2102)を使用しています。

Arduino IDEのシリアルモニタにはXYZの軸の加速度の表示するようにしています。ウォータマーク割り込みが発生した段階(80ms毎)で表示が更新されてしまうため、タップ情報などの確認する際は表示部分をコメントアウトして確認しました。

ADXL345のイベント確認
ADXL345のイベント確認

Arduino IDEのシリアルプロッタを使用して加速度の変化が分かるように表示しました。シリアルモニタとシリアルプロッタは同時には使用できないためシリアルプロッタを使用する場合はソースコード全体の#define MONITER_USEをコメントアウトすると表示できます。

シリアルプロッタによる加速度の動作確認
シリアルプロッタによる加速度の動作確認

シリアルプロッタによる結果を確認すると加速度センサーを動かしているとXYZ軸の値が変化していることが分かります。机において動かさないようにすると加速度の動きが安定していることからうまく検出できていることが分かりました。

ソースコード全体

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

#include <Wire.h>
#include <ADXL345.h>

#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 2
#define PIN_DI2 3
#define G_DATA_MAX 8

//#define MONITER_USE //シリアルプロッタを使用する場合はコメントアウト

// application use
ADXL345 adxl;
uint16_t cnt;
uint16_t cnt2;
uint8_t di1;
uint8_t di2;
byte intsrc;    //割り込みイベント
byte intsrc2;   //アクティブまたはタップイベントの内容
byte int2src;
byte fifostatus;
byte test;
double xyzData[3]; //XYZの加速度のデータ

/*** Local function prototypes */
void InitAdxl345();
void Adxl345Rcv();
void Adxl345Data();

void setup(){

    Serial.begin(115200);
    pinMode(PIN_DI1,INPUT);
    pinMode(PIN_DI2,INPUT);

    InitAdxl345();
}

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(7); //(5 - 9) recommended - 62.5mg per increment
    adxl.setFreeFallDuration(45); //(20 - 70) recommended - 5ms per increment 45

    adxl.setRangeSetting(2); //4g Range
    adxl.setFiFobyte(ADXL345_FIFO_MODE_STREAM, G_DATA_MAX); //FIFO_CTLの設定(追加)
    
    //setting all interrupts to take place on int pin 1
    //I had issues with int pin 2, was unable to reset it
    adxl.setInterruptMapping( ADXL345_DATA_READY,   ADXL345_INT2_PIN );
    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 );
 
    //register interrupt actions - 1 == on; 0 == off  
    adxl.setInterrupt( ADXL345_DATA_READY, 1);
    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);

    memset(&xyzData,0, sizeof(xyzData));
    delay(100); //初期時にデータを格納してスタート
    dmy = adxl.getInterruptSource(); //ダミーで割り込みビットをクリア

}

void loop(){

    if( digitalRead(PIN_DI1) ){ //INT1
        intsrc2 = adxl.isTapSourcebyte();
        intsrc = adxl.getInterruptSource();

        #ifdef  MONITER_USE
        if( intsrc & INT_SINGLE_TAP_BIT_MASK){
            Serial.println("SINGLE_TAP");
        }
        /*  追加した関数を使用しない場合
        if( adxl.isActivitySourceOnX()){ 
            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( intsrc & INT_WATERMARK_BIT_MASK){
            Serial.println("WATERMARK");
            Adxl345Rcv();
        }
        */

        if( intsrc2 & ACT_TAP_ACT_X ){
            Serial.println("ACT X");
        }

        if( intsrc2 & ACT_TAP_ACT_Y ){
            Serial.println("ACT Y");
        }

        if( intsrc2 & ACT_TAP_ACT_Z ){
            Serial.println("ACT Z");
        }

        if( intsrc2 & ACT_TAP_TAP_X ){
            Serial.println("TAP X");
        }        

        if( intsrc2 & ACT_TAP_TAP_Y ){
            Serial.println("TAP Y");
        } 

        if( intsrc2 & ACT_TAP_TAP_Z ){
            Serial.println("TAP Z");
        }
        #endif
    }

    if( digitalRead(PIN_DI2) ){ //INT2
        int2src = adxl.getInterruptSource();

        if( int2src & INT_WATERMARK_BIT_MASK){ //ウォーターマーク割り込みが発生
            Adxl345Rcv();
        }
    }   
}

void Adxl345Rcv(){
    int xyz[3];
    int sumxyz[3];
    byte i;
    byte cnt;

    cnt = adxl.getFiFoStatusbyte() & 0x3F; //ウォーターマーク割り込み発生時のデータ数
    if( cnt > G_DATA_MAX){
        cnt = G_DATA_MAX;
    }

    sumxyz[0] = 0;
    sumxyz[1] = 0;
    sumxyz[2] = 0;

    for( i=0;i < cnt; i++){
        adxl.readAccel(&xyz[0]);
        sumxyz[0] += xyz[0]; //平均をとるために総数を計算
        sumxyz[1] += xyz[1];
        sumxyz[2] += xyz[2];
    }

    sumxyz[0] = sumxyz[0] >> 3; //3回シフト8で割るのと同じ
    sumxyz[1] = sumxyz[1] >> 3;
    sumxyz[2] = sumxyz[2] >> 3;

    adxl.getAcceleration2( &xyzData[0], &sumxyz[0]); //XYZの加速度の取得

    #ifndef  MONITER_USE
    Serial.print("X:");
    Serial.print(xyzData[0]);
    Serial.print(",");
    Serial.print("Y:");
    Serial.print(xyzData[1]);
    Serial.print(",");
    Serial.print("Z:");
    Serial.print(xyzData[2]);
    Serial.println("");
    #endif
}

ライブラリの一部を追加するで説明している関数をADXL345ライブラリに追加することで上記ソースコードが有効になります。

関連リンク

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

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

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

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

TECH::CAMPプログラミング教養【無料体験会】

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

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