2014年6月15日日曜日

加速度+ジャイロのGY-521(MPU-6050)を使ってみた -1-




安く購入できた3軸加速度センサー+3軸ジャイロセンサーのGY-521を使ってみました。
MPU-6050が基板に乗っていて、使用例もたくさんありセンサーとしてはポピュラーのようです。
arduinoのplaygroundにMPU-6050の解説、使用基板ごとの違い、スケッチもあります。

arduinoとの通信はI2CなのでVCC、GND、SCL、SDAの接続だけで6軸+温度センサーの値を取得できます。
スケッチでスタートの合図になるのでINTとarduinoの外部割込み0(D2)を接続。

他にもINT端子は、落下検知や振動検知にも使われるようです。
GY-521には電源レギュレーターが乗っているので5V電源OK。
しかしI2Cは3.3Vで、本当は5V-3.3Vのレベルシフトをしないといけないのですが、変換しなくても大丈夫らしい情報もあるので、ここでは直結。
お約束:真似して壊れても自己責任
双方向の電圧変換モジュールもありますし、FETでの電圧変換もあるので、心配な方はその方法を使ってください。
次の部品購入時には変換モジュール買うつもりですが。

I2Cで通信しレジスタを書き換えることで、感度など様々な設定を変えることができるようですが、ひとまずデフォルトで使いました。

秋月のaruduino用ユニバーサル基板に乗せたところ。

I2C用のSCL、SDAのピンは半ピッチずれたところに出ていて、この基板はその穴に対応していません。(AREFまでしか穴が開いていない)

ちょっと焦りましたが、arduinoUNOの回路図を見ると、
SCL <-> A5
SDA <-> A4 と接続されていたのでそちらと接続、一安心。


サンプルスケッチに若干の追加と大量の削除。
デフォルト設定での使用前提で、大量の#defineとコメント行を削除しましたので、詳しい解説はオリジナルスケッチを読むのをおすすめします。
http://playground.arduino.cc/Main/MPU-6050

・加速度計の生データから加速度Gへの変換、傾斜角への変換をしています。
・ジャイロの生データから角速度への変換をしています。
参考計算式:建築発明工作ゼミ2008  Arduino 加速度センサ




シリアルに出力している計測結果は見やすいように桁数を少なくしています。
スケッチ内部ではfloat型の変数で計算しているので必要に応じて調整すればいいかと。
出力している項目は順番に、
エラー番号、温度、X,Y,Zの加速度(G)、X,Y,Zの傾斜角度、ジャイロからX,Y,Zの角速度(度/s)。

XYZ軸の方向は基板のシルクスクリーンに合わせたつもりで、写真の置き方を基準にしています。
机においた状態で、天井方向がZ軸です。

arduino playgroundのスケッチを元にしています。

// MPU-6050 Accelerometer + Gyro
#include <Wire.h>

#define MPU6050_ACCEL_XOUT_H       0x3B   // R  
#define MPU6050_WHO_AM_I           0x75   // R
#define MPU6050_PWR_MGMT_1         0x6B   // R/W
#define MPU6050_I2C_ADDRESS 0x68

typedef union accel_t_gyro_union{
  struct{
    uint8_t x_accel_h;
    uint8_t x_accel_l;
    uint8_t y_accel_h;
    uint8_t y_accel_l;
    uint8_t z_accel_h;
    uint8_t z_accel_l;
    uint8_t t_h;
    uint8_t t_l;
    uint8_t x_gyro_h;
    uint8_t x_gyro_l;
    uint8_t y_gyro_h;
    uint8_t y_gyro_l;
    uint8_t z_gyro_h;
    uint8_t z_gyro_l;
  } 
  reg;
  struct{
    int16_t x_accel;
    int16_t y_accel;
    int16_t z_accel;
    int16_t temperature;
    int16_t x_gyro;
    int16_t y_gyro;
    int16_t z_gyro;
  } 
  value;
};

void setup(){
  Wire.begin();
  int error;
  uint8_t c;
  Serial.begin(115200);
  Serial.print("InvenSense MPU-6050");
  Serial.print("June 2012");
  error = MPU6050_read (MPU6050_WHO_AM_I, &c, 1);
  Serial.print("WHO_AM_I : ");
  Serial.print(c,HEX);
  Serial.print(", error = ");
  Serial.println(error,DEC);
  error = MPU6050_read (MPU6050_PWR_MGMT_1, &c, 1);
  Serial.print("PWR_MGMT_1 : ");
  Serial.print(c,HEX);
  Serial.print(", error = ");
  Serial.println(error,DEC);
  MPU6050_write_reg (MPU6050_PWR_MGMT_1, 0);
}

void loop(){
  int error;
  float dT;
  accel_t_gyro_union accel_t_gyro;
  error = MPU6050_read (MPU6050_ACCEL_XOUT_H, (uint8_t *) &accel_t_gyro, sizeof(accel_t_gyro));
  Serial.print(error,DEC);
  Serial.print("\t");

  uint8_t swap;
#define SWAP(x,y) swap = x; x = y; y = swap
  SWAP (accel_t_gyro.reg.x_accel_h, accel_t_gyro.reg.x_accel_l);
  SWAP (accel_t_gyro.reg.y_accel_h, accel_t_gyro.reg.y_accel_l);
  SWAP (accel_t_gyro.reg.z_accel_h, accel_t_gyro.reg.z_accel_l);
  SWAP (accel_t_gyro.reg.t_h, accel_t_gyro.reg.t_l);
  SWAP (accel_t_gyro.reg.x_gyro_h, accel_t_gyro.reg.x_gyro_l);
  SWAP (accel_t_gyro.reg.y_gyro_h, accel_t_gyro.reg.y_gyro_l);
  SWAP (accel_t_gyro.reg.z_gyro_h, accel_t_gyro.reg.z_gyro_l);

  dT = ( (float) accel_t_gyro.value.temperature + 12412.0) / 340.0;
  Serial.print(dT, 1);
  Serial.print("\t");

  float acc_x = accel_t_gyro.value.x_accel / 16384.0; //FS_SEL_0 16,384 LSB / g
  float acc_y = accel_t_gyro.value.y_accel / 16384.0;
  float acc_z = accel_t_gyro.value.z_accel / 16384.0;

  Serial.print(acc_x, 2);
  Serial.print("\t");
  Serial.print(acc_y, 2);
  Serial.print("\t");
  Serial.print(acc_z, 2);
  Serial.print("\t");

  float acc_angle_x = atan2(acc_x, acc_z) * 360 / 2.0 / PI;
  float acc_angle_y = atan2(acc_y, acc_z) * 360 / 2.0 / PI;
  float acc_angle_z = atan2(acc_x, acc_y) * 360 / 2.0 / PI;

  Serial.print(acc_angle_x, 2);
  Serial.print("\t");
  Serial.print(acc_angle_y, 2);
  Serial.print("\t");
  Serial.print(acc_angle_z, 2);
  Serial.print("\t");

  float gyro_x = accel_t_gyro.value.x_gyro / 131.0;  //FS_SEL_0 131 LSB / (°/s)
  float gyro_y = accel_t_gyro.value.y_gyro / 131.0;
  float gyro_z = accel_t_gyro.value.z_gyro / 131.0;

  Serial.print(gyro_x, 2);
  Serial.print("\t");
  Serial.print(gyro_y, 2);
  Serial.print("\t");
  Serial.print(gyro_z, 2);
  Serial.println("");
}

// MPU6050_read
int MPU6050_read(int start, uint8_t *buffer, int size){
  int i, n, error;
  Wire.beginTransmission(MPU6050_I2C_ADDRESS);
  n = Wire.write(start);
  if (n != 1)
    return (-10);
  n = Wire.endTransmission(false);    // hold the I2C-bus
  if (n != 0)
    return (n);
  // Third parameter is true: relase I2C-bus after data is read.
  Wire.requestFrom(MPU6050_I2C_ADDRESS, size, true);
  i = 0;
  while(Wire.available() && i<size){
    buffer[i++]=Wire.read();
  }
  if ( i != size)
    return (-11);
  return (0);  // return : no error
}

// MPU6050_write
int MPU6050_write(int start, const uint8_t *pData, int size){
  int n, error;
  Wire.beginTransmission(MPU6050_I2C_ADDRESS);
  n = Wire.write(start);        // write the start address
  if (n != 1)
    return (-20);
  n = Wire.write(pData, size);  // write data bytes
  if (n != size)
    return (-21);
  error = Wire.endTransmission(true); // release the I2C-bus
  if (error != 0)
    return (error);
  return (0);         // return : no error
}

// MPU6050_write_reg
int MPU6050_write_reg(int reg, uint8_t data){
  int error;
  error = MPU6050_write(reg, &data, 1);
  return (error);
}


追記:秋月AE-ATmega基板を使ったarduino互換機+USBシリアル変換モジュールAE-UM232R(FT232RL)で上記スケッチを実行したところ、115200bpsではスムーズな受信ができませんでした。互換機などを使っていておかしい時は9600bpsなど速度を落としてみてください。
レイテンシの影響のようです。
デバイスマネージャで該当するシリアルポートのプロパティ「ポートの設定」「詳細設定」にある「待ち時間」(レイテンシ)を16から1に変更すると、115200でも問題なく動きました。

このセンサーに限らずジャイロセンサーのドリフトがあるので何らかの方法で補正しないといけないようで、ネットにはそれにトライしている記事がたくさんあります。


環境:arduinoUNO、arduinoIDE1.0.5、win7(64)



3 件のコメント:

  1. MPU6050はSPIできず、I2cの場合アドレスはAD0で決められます。AD0=0->0x68,AD0=1->0x69となる。サンプルコードの40行 Wire.begin(); をWire.beign(0x68);のように修正しないと通信が始まらないので初心者の方は注意(投稿者ではなく私は少し戸惑ったので、私のような初心者のかたへ)!

    返信削除
  2. ちな、XDA,XCLは外部のセンサーをつなぐための物なので、ArduinoなどプロセッサーはSDA,SCLにつなぎましょう。

    返信削除
  3. I2CはLOW,ハイインピーダンスの二択で通信するので、HIGH状態の電圧がデバイスで違っても問題ないんだと思います(ハイインピーダンスではセンサー側でプルアップされた電圧が入力され琉はず)。にわかです

    返信削除