こんにちは、ENGかぴです。
ArduinoのWire(SPI)を使用すると加速度センサー(ADXL345)の加速度やタップ情報などを取得することができます。ADXL345のライブラリを流用して加速度センサーの情報をFIFO機能を使用して取得する方法をまとめました。
ADXL345モジュール(秋月電子)を使用して加速度を取得しています。FIFO機能を使用するため一部関数を追加しています。Arduino UNOを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
加速度センサーのライブラリを使用する
ADXL345の製造元のアナログデバイセズのHPに提供されているライブラリを使用してもよいのですが、今回はSeeed Studioが提供している「Accelerometer ADXL345」ライブラリを使用します。seeeduino用ですが、Arduino環境で作られているためUNOでも使用することができます。
ADXL345のライブラリについて
Arduino IDEを使ってライブラリをインストールして追加します。Arduino IDEのスケッチ欄からライブラリをインクルードを選択するとライブラリを管理の項目が表示されるのでクリックしてライブラリマネージャに遷移します。
ライブラリマネージャの検索欄にADXLを入力すると候補としてADXLに関するライブラリが表示されます。「Accelerometer ADXL345」を選択してインストールします。ライブラリ関数で設定できる項目については下記記事にまとめています。
Seeeduino XIAOでデジタル加速度センサーの情報を取得
ADXL345のFIFOを使用する
ADXL345のFIFOモードは32レベル(32個のデータを保管できる)機能でホストプロセッサの負荷を低減することができます。詳細はデータシートで確認できます。
FIFOモードには4つのモードがあります。
モード | 説明 |
---|---|
バイパス | FIFOは動作せず空のまま(データを保管しない) |
FIFO | 測定データがFIFOに格納される。 FIFO_CTLレジスタのSamplesビットで指定した値以上になるとウォーターマーク割り込みの条件となり満杯(32個のデータ)になった時点でデータの収集を停止する。 |
ストリーム | 測定データがFIFOに格納される。 FIFO_CTLレジスタのSamplesビットで指定した値以上になるとウォーターマーク割り込みの条件となる。FIFOが満杯(32個のデータ)の時は古いデータを破棄して保管する。 |
トリガ | FIFOは指定された割り込みピンの状態と連動して32個の測定サンプルを保持する。FIFO_CTLレジスタのTriggerビットによって選択されたピンがセットされるとSamplesビットで指定した値の分のデータを保存してFIFO動作して満杯でない限りデータを保管する。 |
本記事ではストリームモードを実装しています。ウォーターマーク割り込みが発生したとき保管したデータの平均値をとり加速度のデータとして採用するようにしています。
FIFOモードを操作する関数を実装する
FIFOモードの実装はスケッチ例のADXL345_demo_codeでは使用されていない機能です。FIFO_CTLとFIFO_STATUSレジスタはADXL345.hに実装されていないため追加で実装します。(ライブラリの更新で実装される可能性があります。)
Arduinoファイルに次の定義を追加します。
#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 ADXL345_DEVICE (0x53) // ADXL345 device address
#define部分にFIFOのモードを設定するための宣言をしています。FIFO_CTLレジスタを設定する際に引数として指定する際に使用します。ADXL345_DEVICEはWireで指定するスレーブアドレスです。追加実装する関数は以下の通りです。
追加実装する関数 | 説明 |
---|---|
getAcceleration2(引数1,引数2) | 加速度の計算と補正 引数1に計算後の加速度データを格納するアドレスを指定する。 引数2に補正する前の加速度データのアドレス |
getdataread(引数1,引数2,引数3) | 指定したアドレスに対しデータを読み込む 引数1に読み込み対象のアドレスを指定する。 引数2に読み込むデータ数を指定する。 引数3に読み込んだデータを格納するアドレスを指定する。 |
getFiFobyte() | FIFO_CTLレジスタの内容を戻り値で返す。 |
getFiFoStatusbyte() | FIFO_STATUSレジスタの内容を戻り値で返す。 |
getTapSourcebyte() | ACT_TAP_STATUSレジスタの内容を戻り値で返す。 |
setFiFobyte(引数1,引数2) | 引数1にFIFOモードに関する定義を指定する。 追加したADXL345_FIFO_MODE_STREAMなどを指定する。 引数2にFIFO_CTL内のsamplesの値を指定する。 |
Arduinoファイルに処理を追加します。
void getdataread(uint8_t adr, uint8_t sz, uint8_t* data){
Wire.beginTransmission(ADXL345_DEVICE); // start transmission to device
Wire.write(adr); // sends address to read from
Wire.endTransmission(); // end transmission
Wire.requestFrom(ADXL345_DEVICE, sz); // request 6 bytes from device
for( uint8_t i=0; i < sz; i++ ){
*data = Wire.read(); //len分だけデータをリードする
++data;
}
}
//使用例
/* FiFoデータの読み出し */
uint8_t getFiFobyte(void){
uint8_t val;
getdataread(ADXL345_FIFO_CTL, 1, &val);
return val;
}
レジスタの値を読み込む際に共通して使用するgetdataread()関数について説明します。Wireライブラリのメンバー関数であるbeginTransmisson()関数で初期化を行いスレーブアドレスをセットします。write()関数でレジスタのアドレスをセットします。endTransmisson()関数でスタート・コンディションからストップ・コンディションまでを含めたデータを送信します。
requestFrom()関数でスレーブから指定した数のデータを取得します。read()関数で取得したデータを読み込みます。
getFiFobyte()を使用例として説明します。getdataread()の第1引数にFIFO_CTLレジスタのアドレスを指定します。第2引数に読み込むデータ数を指定しますが、レジスタは1バイトなので1を指定します。第3引数に読み込んだデータを格納するアドレスを指定します。第3引数で指定した変数に読み込んだデータが格納されるので戻り値にセットしています。
void setFiFobyte(byte mode, byte smp){
uint8_t fifo_ctl;
fifo_ctl |= mode & 0xC0;
fifo_ctl |= smp & 0x1F;
Wire.beginTransmission(ADXL345_DEVICE); // start transmission to device
Wire.write(ADXL345_FIFO_CTL);
Wire.write(fifo_ctl);
Wire.endTransmission();
}
FIFOレジスタを操作するためにsetFiFobyte()関数を実装します。第1引数はモード選択のビット値(#defineで定義しているADXL345_FIFO_MODE_STREAMなど)を指定します)第2引数はFIFOで保管するデータ数を指定します。
FIFO_CTLレジスタにおいて必要なビット部分を変更するために引数で指定した値が他のビットに影響しないように論理積を取ってマスクしてから論理和をとっています。
Wireライブラリのメンバー関数であるbeginTransmisson()関数で初期化を行いスレーブアドレスをセットします。write()関数でレジスタのアドレスをセットします。続けて任意のデータをwrite()関数でセットし、最後にendTransmission()関数でスタート・コンディションからストップ・コンディションまでを含めたデータを送信します。
FIFO使用時のデータを換算する
FIFOを使用すると測定データを読み込むまで保管することができるのがメリットです。例えば8回分のデータの平均値を使って加速度データに換算するようなことができるようになります。
スケッチ例のADXL345_demo_codeで使用しているgetAcceleration()は瞬時値を換算するようになっているためFIFOのように複数回取得したデータを変換するのには使いにくい部分があります。
データシートの「2.5V以外の電圧での動作」を確認すると感度の変化による影響が記載されています。データシートをもとに加速度のデータを補正する関数を追加しました。
void 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(){
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の配線例を示しています。ADXL345はDC2.5V(DC3.3V)の電源で動作するモジュールであるためArduinoの5Vを直接印加することができません。ArduinoからADXL345に向かう信号についてはレベル変換IC(FXMA2102)を使用しています。ADXL345モジュールはプルアップ抵抗が実装されているためFXMA2102間のプルアップ抵抗は必要ありません。
Arduino IDEのシリアルモニタにはXYZの軸の加速度の表示するようにしています。ウォータマーク割り込みが発生した段階(80ms毎)で表示が更新されてしまうため、タップ情報などの確認する際は表示部分をコメントアウトして確認しました。
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 ADXL345_FIFO_MODE_BYPASS 0x00
#define ADXL345_FIFO_MODE_FIFO 0x40
#define ADXL345_FIFO_MODE_STREAM 0x80
#define ADXL345_FIFO_MODE_TRG 0xC0
#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 ADXL345_DEVICE (0x53) // ADXL345 device address
#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);
void Adxl345Rcv(void);
void Adxl345Data(void);
void getAcceleration2(double* accelxyz, int* xyz);
void getdataread(uint8_t adr, uint8_t sz, uint8_t* data);
uint8_t getFiFobyte(void);
uint8_t getFiFoStatusbyte(void);
uint8_t getTapSourcebyte(void);
void setFiFobyte(byte mode, byte smp);
void setup(){
Wire.begin();
Serial.begin(115200);
pinMode(PIN_DI1,INPUT);
pinMode(PIN_DI2,INPUT);
InitAdxl345();
}
void InitAdxl345(void){
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
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(); //ダミーで割り込みビットをクリア
Serial.println("ok");
}
void loop(){
if( digitalRead(PIN_DI1) ){ //INT1
intsrc2 = getTapSourcebyte();
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( 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(void){
int xyz[3];
int sumxyz[3];
byte i;
byte cnt;
cnt = 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;
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
}
/* 加速度の計算 */
void 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;
}
/* FiFoの設定 */
void setFiFobyte(byte mode, byte smp){
uint8_t fifo_ctl;
fifo_ctl |= mode & 0xC0;
fifo_ctl |= smp & 0x1F;
Wire.beginTransmission(ADXL345_DEVICE); // start transmission to device
Wire.write(ADXL345_FIFO_CTL);
Wire.write(fifo_ctl);
Wire.endTransmission();
}
/* データの読み出し */
void getdataread(uint8_t adr, uint8_t sz, uint8_t* data){
Wire.beginTransmission(ADXL345_DEVICE); // start transmission to device
Wire.write(adr); // sends address to read from
Wire.endTransmission(); // end transmission
Wire.requestFrom(ADXL345_DEVICE, sz); // request 6 bytes from device
for( uint8_t i=0; i < sz; i++ ){
*data = Wire.read(); //len分だけデータをリードする
++data;
}
}
/* FiFoデータの読み出し */
uint8_t getFiFobyte(void){
uint8_t val;
getdataread(ADXL345_FIFO_CTL, 1, &val);
return val;
}
/* FiFoステータスの読み出し */
uint8_t getFiFoStatusbyte(void){
uint8_t val;
getdataread(ADXL345_FIFO_STATUS, 1, &val);
return val;
}
/* TAP情報の読み出し */
uint8_t getTapSourcebyte(void){
uint8_t val;
getdataread(ADXL345_ACT_TAP_STATUS, 1, &val);
return val;
}
関連リンク
Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。
Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方
Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方
ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方
最後まで、読んでいただきありがとうございました。