SpeedStudioの加速度センサボード。アナログデバイセズ製のADXL345という加速度センサを実装してます。今回は、このボードをM5Stamp picoで読ませてみたいと思います。
センサADXL345のIC自体のデータシートは以下です。
自分がもっているのはSpeedStudio製ですが、このICをつかった評価ボードは他にも沢山あります。
つなぎ方
加速度センサはI2Cなので、M5STAMP picoとは、下のようにつなげます。
せっかくセンサーボードにGroveコネクタが付いているので、下のような自作の変換基板をつくり、コネクタをそのまま差し込めるようにしました。
I2Cのデバイスアドレスを調べる
まず、通信チェックのため、デバイスアドレスを確認します。I2CはWire.hというライブラリで簡単に通信できます。まず、ボードに加速度センサをつないで、アドレスを検索してみます。
この時のスケッチは以下のとおり。
#include <Arduino.h>
#include <Wire.h>
int device_add;
int DevSearch(){
int address;
int error;
Serial.printf("\nscanning Address [HEX]\n");
for(address = 1; address < 127; address++ )
{
Wire.beginTransmission(address);
//Data transmission to the specified device address starts.
error = Wire.endTransmission();
/*Stop data transmission with the slave.
0: success.
1: The amount of data exceeds the transmission buffer capacity limit.
2: Received NACK when sending address.
3: Received NACK when transmitting data.
4: Other errors. */
Serial.print(".");
if(error==0)
return address;
}
return 0;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Wire.begin(21, 22);
delay(3000);
int device_add = DevSearch();
if (0!=device_add)
{
Serial.println("");
Serial.print("Device was found ! address is 0x");
Serial.println(device_add,HEX);
}
else
{
Serial.println("");
Serial.println("Device was not found !");
}
}
void loop() {
}
下が実際の実行結果です。アドレスは0x53で認識されました。とりあえず、接続はOK。
ADXL345のライブラリ
いくつかライブラリがありますが、Speed Studioのものをリンクはっておきます。
このままだと、センサの中身があんまりよくわからないので、ライブラリなしでコードを書いていきます。ちなみに上ライブラリの動作チェックはしていません。M5Stamp Picoの場合は、もしかしたら、コードの微修正などが必要かもしれません…
基本的な考え方
I2C経由でセンサからデータを取得するには、測定前に必要なセンサの設定をおこない、計測をスタートして、指定されたアドレスからデータをひろっていく流れになります。
下の絵のように基本は、受け取りたい値もしくは、設定したい値が定義されているレジスタのアドレスを投げ、それから値を受け取るなり、値を書き込むなりします。
レジスタリスト
データシートでは、こんな感じでそれぞれのレジスタが定義されています。これに従い、指定のアドレスに設定を書き込んだり、読み込んだりする訳です。
センサの主な機能
データシートを読み込む前にまず、ざっくりと主な機能を把握しておくと、内容が掴みやすいので、簡単に説明しておきます。
割り込み機能について
下の写真のようにSpeedStudioのボードには、INT1とINT2とかかれた端子があります。これは割り込み用の端子で、例えば、加速度センサがデータをゲットした瞬間にINT1をHighにするとか、ボードが静止状態になったら、INT2をHighにするとかADXL345のIC側で用意された割り込み機能に応じて、そのイベントの発生をH/Lの信号で確認できます。
ちなみに割り込み機能は全部で8個あるようです。
- Over run
- Watermark
- Free Fall
- Inactive
- Active
- Double Tap
- Single Tap
- Data Ready
それぞれの機能は以下のとおり。
Over run
センサが受信した信号をマイコンが受け取る前に次の信号がきて、上書きされてしまった場合にイベントが発生します。(FIFO設定時に主に使います。FIFOは、32段のバッファがあるので、マイコンが何もしないと33個データ受信するとイベントが発生するっていうことです)
Watermark
FIFO設定時、指定のサンプル数を超すと発火します。サンプル数はレジスタで変更できます。
Free Fall
自由落下すると発火します。加速度センサは、ボードが動くと、センサの値は当然、大きくなりますが、ボードが静止していても、重力加速度の1Gは検出しつづけます。ただ、自由落下する時は、この重力の影響がキャンセルされるため、出力は0に近くなります。Free Fallは、これを利用して、自由落下判定を行う機能です。実際はレジスタで、その判定しきい値とデバンス時間をうまく設定してやる必要があります。
Inactive
センサが静止した状態を判定します。x, y, z軸それぞれの値に対して、しきい値を設定し、その値を一定時間下回れば、Inactiveイベントが発生します。条件はx,y,zのANDです。
Active
Inactiveの逆で、センサの動作を検出します。x, y, z軸それぞれの値に対して、しきい値を設定し、その値を一定時間上回れば、activeイベントが発生します。条件はORです。
Single Tap
外からタップした時の動作を判定します。Activeと同じように判定しきい値と時間で判定します。
Double Tap
Single Tapのイベントが発生した後に、一定時間内(Tap Window)にもう一度、Single Tapの条件を判定したときにDouble Tapイベントが発生します。ダブルクリック的な動作を検出するためのものでしょうか。使ったことないので、よくわかりません。
Data Ready
文字どおりです。新しいデータがきたら、イベントが発生します。
Sleep mode
ADXL345には、Sleepモードというのがあり、Sleepモード中は、サンプリング周期を減らし、消費電流を抑えることができます。Sleepモードと通常モードの遷移は、上で説明したInactiveイベントとActiveイベントで制御されます。
レジスタの書き方、読み方
以前、DPS310という気圧センサをI2C経由でデータをとったことがあり、今回はその時に作成したレジスタの読み込み、書き込みの関数をつかうことにしました。
M5Stamp pico でADC(温度センサ)とI2C(高精度気圧センサDSP310)をつかう
書き込み側
//指定のアドレスに値を書き込む関数
void WriteReg(uint8_t addrs, uint8_t val)
{
Wire.beginTransmission(ADXL345_I2CADDR_DEFAULT);
Wire.write(addrs);
Wire.write(val);
Wire.endTransmission();
}
読み込み側
//指定のアドレスから値を読む関数(lengthで読み込むバイト数を指定する)
void ReadReg(uint8_t addrs,uint8_t * buf,uint8_t length)
{
Wire.beginTransmission(ADXL345_I2CADDR_DEFAULT);
Wire.write(addrs);
Wire.endTransmission();
Wire.requestFrom(ADXL345_I2CADDR_DEFAULT,length,true);
for(uint8_t i =0;i<length;i++)
{
buf[i] = Wire.read();
}
}
データのとり方
ADXL345では、下のように加速度の測定レンジをレジスタで変えることができます。測定レンジの設定によっては、加速度センサの出力フォーマットが変わるので、注意です。
データシートによれば、加速度センサの値は、レジスタ0x32 ~ 0x37に保存されます。下がx軸加速度の出力フォーマットの例です。フォーマットには、10bit 固定モードとFull-resolutionモードの二つのモードがあり、10bitモードの場合は、加速度レンジの設定に関わらず、データ長は10bitの固定ですが、Full-resolutionモードの場合は、加速度レンジの設定に従い、1 LSB= 4mgをキープするため、データ長が可変します。
今回はとりあえず、±16gレンジのfull-resolutionモードで設定してみました。
符号ビットの処理などは、上で紹介した記事に書いてあるので、省略します。
サンプルコード
サンプルコードをとりあえず、つくってみました。測定するだけなら、レジスタの設定はほぼデフォルトでいけますが、以下の設定だけは変更しています。
POWER_CTLレジスタのbit3を1に設定し、測定モードをオンにした
FIFO_CTLレジスタのbit6-7を0に設定し、FIFOモードをオフにした(たぶん不要)
DATA_FORMATレジスタのbit3を1に設定し、full-resolutionモードにする。bit0-1を0b11にセットして、レンジを±16gにした
#include <Arduino.h>
#include <Wire.h>
//device address
#define ADXL345_I2CADDR_DEFAULT (0x53)
//Register list
#define ADXL345_DEVID 0x00
#define ADXL345_THRESH_TAP 0x1d
#define ADXL345_OFSX 0x1e
#define ADXL345_OFSY 0x1f
#define ADXL345_OFSZ 0x20
#define ADXL345_DUR 0x21
#define ADXL345_ACT_INACT_CTL 0x27
#define ADXL345_BW_RATE 0x2c
#define ADXL345_POWER_CTL 0x2d
#define ADXL345_INT_SOURCE 0x30
#define ADXL345_DATA_FORMAT 0x31
#define ADXL345_DATAX0 0x32
#define ADXL345_DATAX1 0x33
#define ADXL345_DATAY0 0x34
#define ADXL345_DATAY1 0x35
#define ADXL345_DATAZ0 0x36
#define ADXL345_DATAZ1 0x37
#define ADXL345_FIFO_CTL 0x38
//Range Bits
typedef enum {
Range2g = 0b00,
Range4g = 0b01,
Range8g = 0b10,
Range16g = 0b11
} adxl345_range_t;
uint8_t buf[6];
float acc[3];
float kgain = 0.004;
//指定のアドレスに値を書き込む関数
void WriteReg(uint8_t addrs, uint8_t val)
{
Wire.beginTransmission(ADXL345_I2CADDR_DEFAULT);
Wire.write(addrs);
Wire.write(val);
Wire.endTransmission();
}
//指定のアドレスから値を読む関数(lengthで読み込むバイト数を指定する)
void ReadReg(uint8_t addrs,uint8_t * buf,uint8_t length)
{
Wire.beginTransmission(ADXL345_I2CADDR_DEFAULT);
Wire.write(addrs);
Wire.endTransmission();
Wire.requestFrom(ADXL345_I2CADDR_DEFAULT,length,true);
for(uint8_t i =0;i<length;i++)
{
buf[i] = Wire.read();
}
}
//2の補数計算(マイナス値を算出する)
int16_t two_complement(uint16_t val,uint8_t bits)
{
int16_t val_out;
if ((val) & ((uint16_t)1 << (bits - 1))) {
val_out = val - (1U<<bits);
}
else{
val_out = val;
}
return val_out;
}
void setup(){
Serial.begin(115200);
Wire.begin(21, 22); //I2C端子
delay(3000);
//測定モードON
uint8_t val = 1 << 3U;
WriteReg(ADXL345_POWER_CTL,val);
/*
//confirmation
ReadReg(ADXL345_POWER_CTL,buf,1);
Serial.print("POWER_CTL:");
Serial.println(buf[0]);
*/
//FIFO OFF (Bypass設定)
val = 0;
WriteReg(ADXL345_FIFO_CTL,val);
/*
//confirmation
ReadReg(ADXL345_FIFO_CTL,buf,1);
Serial.print("FIFO_CTL:");
Serial.println(buf[0]);
*/
//測定レンジ±16g Full resolutionモード設定
val = (uint8_t)Range16g | 1<<3U;
WriteReg(ADXL345_DATA_FORMAT,val);
/*
//confirmation
ReadReg(ADXL345_DATA_FORMAT,buf,1);
Serial.print("DATA_FORMAT:");
Serial.println(buf[0]);
*/
}
void loop(){
//データ6バイト分一気読み
ReadReg(ADXL345_DATAX0,buf,6);
//符号ビットを考慮
int16_t temp = two_complement((buf[1]<<8U | buf[0])&0x1FFF,13);
//物理値変換
acc[0]=temp*kgain;
temp = two_complement((buf[3]<<8U | buf[2])&0x1FFF,13);
acc[1]=temp*kgain;
temp = two_complement((buf[5]<<8U | buf[4])&0x1FFF,13);
acc[2]=temp*kgain;
Serial.println("*********************************");
Serial.print("accx = ");
Serial.print(acc[0]);
Serial.println(" g");
Serial.print("accy = ");
Serial.print(acc[1]);
Serial.println(" g");
Serial.print("accz = ");
Serial.print(acc[2]);
Serial.println(" g");
delay(1000);
}
下が結果です。最新バージョンのarduino IDEでシリアルプロットが出せるって最近しりましたので、アップデートして、出力してみました。
問題なく、計測できてそうな感じです。
今回のサンプルでは単純に逐次センサデータを取得しにいってるだけですが、FIFOを使えば、実際のマイコンの動作状況に合わせて、最適なタイミングでデータを取りに行ったり、前述した割り込み機能などもあわせて、結構いろいろな動作のさせ方ができそうです。
とりあえず、今回は目標は達成したので、ここまでです。
ADXL345ばんざい。