加速度センサーMPU6050について

データシート
https://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf

レジスタマップ
https://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf

購入元
https://www.amazon.co.jp/gp/product/B010RYGIRM


回路としては



vccは5v、SDA,SCLにI2Cをつなぐ(A4->SDA,A5->SCL)。使われているMPU6050はSPIとして使う機能はないため、I2C only注意!
たぶん、5vでSDA,SCL動かして大丈夫だと思う。(センサー側でプルアップされているため)

XDA,XCLは外部のセンサーをつなぐことで内部のFIFOメモリを使ったりできる感じだと思う。(使わないので私はずっとOPENにしてる。)

AD0はI2Cでのアドレスをセットできる。
0->0x68,1 -> 0x69 となる。

データの通信の仕方としては、レジスタマップに加速度、角速度などが随時保存されるため、I2Cで取得していく形になると思う。

注意点:最初に レジスタアドレス0x6B の Bit6 の SLEEP を LOW にしないと動作しない。(デフォルトで0x6B-> 0x40 のため、最初はSLEEPがついている。)

Arudino での I2C のプログラム方法
まず、Wire.begin();でWireを使えるようにする。

書き込む場合ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
Wire.beginTransmission(デバイスのアドレス->0x68,0x69);

Wire.write(書き込みたいレジスタのアドレス);

Wire.write(書き込みたいデータ);

Wire.endTransmission();
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

読み込む場合ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
Wire.beginTransmission(デバイスのアドレス->0x68,0x69);

Wire.write(読みたいレジスタのアドレス);

Wire.endTransmission();

Wire.requestFrom(デバイスのアドレス, 1); //1byte読みたい

変数=Wire.read();
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー


void writem(byte add, byte data) {
  Wire.beginTransmission(0x68);
  Wire.write(add);
  Wire.write(data);
  Wire.endTransmission();

}
byte readm(byte add) {
  byte k;
  Wire.beginTransmission(0x68);
  Wire.write(add);
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  while (Wire.available()) {
    k = Wire.read();
  }
  return k;
}

レジスタマップについて
ABCD
1
Register Address内容具体的内容注意点
2
0x19サンプリング周波数を決める8khz / (このレジスタで設定した数値)ローパスフィルタなどの機能を付与すると8khz ではなくなる。
3
0x1Bジャイロセンサの読み取る数値の最大値を決めるBit3,4で設定(bit0,1,2,3,4)とbit0もある↓ 範囲、感度
4
0x1B0±250°/s ,131LSB/(°/s )
5
0x1B1±500°/s ,65.5LSB/(°/s )
6
0x1B2±1000°/s ,32.8LSB/(°/s )
7
0x1B3±2000°/s ,16.4LSB/(°/s )


LSBとは求まる数値の1bitの変位による、対象物の変位つまり、感度を表すために使う
131LSB/(°/s )とは
°/s  = 131bit
つまり、得られたデータが262(0x106)ならば 2°/s の角加速度ということ

ABCD
1
Register Address内容具体的内容
2
0x1cselfcheckと 加速度センサーの範囲感度±2g16384LSB/g
3
範囲と感度はBIT4,3で制御する。±4g8192LSB/g
4
上から0,1,2,3となる±8g4096LSB/g
5
±16g2048LSB/g

出力結果のレジスタマップ
となる。

実際の制作
http://tocknlab.hatenablog.com/entry/2017/03/11/182703
この方の説明がわかりやすく、よいプログラム。

今後
実際やってみると、I2cの速度は
の通り1byte読むのに約450usかかってしまうため、ArduinoでFIFOを使うことはできないだろう。カルマンフィルター、相補フィルターを理解し、raspberry Pi でやってみたいと思う。また、倒立ロボを作ってみたい。


2018 3/29 追記 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
ステッピングモーターを使い、倒立振子製作成功しました。
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄

#include <Wire.h>

#define MPU6050_ADDR         0x68 // MPU-6050 device address
#define MPU6050_SMPLRT_DIV   0x19 // MPU-6050 register address
#define MPU6050_CONFIG       0x1a
#define MPU6050_GYRO_CONFIG  0x1b
#define MPU6050_ACCEL_CONFIG 0x1c
#define MPU6050_WHO_AM_I     0x75
#define MPU6050_PWR_MGMT_1   0x6b

#define  switch1 5 //5pinで安定時の角度を指定する
#define  moter1 3
#define  moter2 4
#define  led 13

double offsetX = 0, offsetY = 0, offsetZ = 0;
double gyro_angle_x = 0, gyro_angle_y = 0, gyro_angle_z = 0;
float angleX, angleY, angleZ;
float interval, preInterval;
float acc_x, acc_y, acc_z, acc_angle_x, acc_angle_y;
float gx, gy, gz, dpsX, dpsY, dpsZ;

float anglexp = 0;
int key = 0;

void culcRotation();

//I2c書き込み
void writeMPU6050(byte reg, byte data) {
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(reg);
  Wire.write(data);
  Wire.endTransmission();
}

//i2C読み込み
byte readMPU6050(byte reg) {
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(reg);
  Wire.endTransmission(true);
  Wire.requestFrom(MPU6050_ADDR, 1/*length*/);
  byte data =  Wire.read();
  return data;
}

void setup() {
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, INPUT_PULLUP);
  pinMode(13, OUTPUT);
  digitalWrite(moter1 ,LOW);
  digitalWrite(moter2,LOW);

  Wire.begin(0x68);
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x6B); // PWR_MGMT_1 register
  Wire.write(0); // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);

  Serial.begin(9600);
  delay(100);

  //正常に接続されているかの確認
  if (readMPU6050(MPU6050_WHO_AM_I) != 0x68) {
    Serial.println("\nWHO_AM_I error.");
    while (true) ;
  }

  //設定を書き込む
  writeMPU6050(MPU6050_SMPLRT_DIV, 0x00);   // sample rate: 8kHz/(7+1) = 1kHz
  writeMPU6050(MPU6050_CONFIG, 0x00);       // disable DLPF, gyro output rate = 8kHz
  writeMPU6050(MPU6050_GYRO_CONFIG, 0x08);  // gyro range: ±500dps
  writeMPU6050(MPU6050_ACCEL_CONFIG, 0x00); // accel range: ±2g
  writeMPU6050(MPU6050_PWR_MGMT_1, 0x01);   // disable sleep mode, PLL with X gyro

  //キャリブレーション
  Serial.print("Calculate Calibration");
  for (int i = 0; i < 3000; i++) {

    int16_t raw_acc_x, raw_acc_y, raw_acc_z, raw_t, raw_gyro_x, raw_gyro_y, raw_gyro_z ;

    Wire.beginTransmission(MPU6050_ADDR);
    Wire.write(0x3B);
    Wire.endTransmission(false);
    Wire.requestFrom(MPU6050_ADDR, 14, true);

    raw_acc_x = Wire.read() << 8 | Wire.read();
    raw_acc_y = Wire.read() << 8 | Wire.read();
    raw_acc_z = Wire.read() << 8 | Wire.read();
    raw_t = Wire.read() << 8 | Wire.read();
    raw_gyro_x = Wire.read() << 8 | Wire.read();
    raw_gyro_y = Wire.read() << 8 | Wire.read();
    raw_gyro_z = Wire.read() << 8 | Wire.read();
    dpsX = ((float)raw_gyro_x) / 65.5;
    dpsY = ((float)raw_gyro_y) / 65.5;
    dpsZ = ((float)raw_gyro_z) / 65.5;
    offsetX += dpsX;
    offsetY += dpsY;
    offsetZ += dpsZ;
    if (i % 1000 == 0) {
      Serial.print(".");
    }
  }
  Serial.println();

  offsetX /= 3000;
  offsetY /= 3000;
  offsetZ /= 3000;

  digitalWrite(led, HIGH);

}

void loop() {

  calcRotation();
  if (key == 0) {
    if (digitalRead(switch1) == 0) {
      anglexp = angleX;
      Serial.print("point");
      Serial.println(anglexp);
      key = 1;
    }
  }
  if(key==1){
 
    Serial.println(angleX);
    if((angleX-anglexp)>=0){
   
      digitalWrite(moter2,LOW);
      digitalWrite(moter1,HIGH);
    }else{
   
      digitalWrite(moter1,LOW);
      digitalWrite(moter2,HIGH);
    }
  }

}

//加速度、ジャイロから角度を計算
void calcRotation() {


  int16_t raw_acc_x, raw_acc_y, raw_acc_z, raw_t, raw_gyro_x, raw_gyro_y, raw_gyro_z ;

  //レジスタアドレス0x3Bから、計14バイト分のデータを出力するようMPU6050へ指示
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(MPU6050_ADDR, 4);


  //出力されたデータを読み込み、ビットシフト演算
  raw_acc_x = Wire.read() << 8 | Wire.read();
  raw_acc_y = Wire.read() << 8 | Wire.read();

  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(MPU6050_ADDR, 4);
  raw_acc_z = Wire.read() << 8 | Wire.read();
  raw_t = Wire.read() << 8 | Wire.read();

  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(MPU6050_ADDR, 4);
  raw_gyro_x = Wire.read() << 8 | Wire.read();
  raw_gyro_y = Wire.read() << 8 | Wire.read();

  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(MPU6050_ADDR, 2);
  raw_gyro_z = Wire.read() << 8 | Wire.read();

  //単位Gへ変換
  acc_x = ((float)raw_acc_x) / 16384.0;
  acc_y = ((float)raw_acc_y) / 16384.0;
  acc_z = ((float)raw_acc_z) / 16384.0;


  //加速度センサーから角度を算出
  acc_angle_y = atan2(acc_x, acc_z + abs(acc_y)) * 360 / -2.0 / PI;
  acc_angle_x = atan2(acc_y, acc_z + abs(acc_x)) * 360 / 2.0 / PI;

  dpsX = ((float)raw_gyro_x) / 65.5; // LSB sensitivity: 65.5 LSB/dps @ ±500dps
  dpsY = ((float)raw_gyro_y) / 65.5;
  dpsZ = ((float)raw_gyro_z) / 65.5;

  //前回計算した時から今までの経過時間を算出
  interval = millis() - preInterval;
  preInterval = millis();


  //数値積分
  gyro_angle_x += (dpsX - offsetX) * (interval * 0.001);
  gyro_angle_y += (dpsY - offsetY) * (interval * 0.001);
  gyro_angle_z += (dpsZ - offsetZ) * (interval * 0.001);

  //相補フィルター
  angleX = (0.996 * gyro_angle_x) + (0.004 * acc_angle_x);
  angleY = (0.996 * gyro_angle_y) + (0.004 * acc_angle_y);
  angleZ = gyro_angle_z;
  gyro_angle_x = angleX;
  gyro_angle_y = angleY;
  gyro_angle_z = angleZ;
}

というかんじで、先に紹介したブログのプログラムをほぼ利用してやってみたが、時々フリーズしたかのように
void calcRotation() {


  int16_t raw_acc_x, raw_acc_y, raw_acc_z, raw_t, raw_gyro_x, raw_gyro_y, raw_gyro_z ;

  //レジスタアドレス0x3Bから、計14バイト分のデータを出力するようMPU6050へ指示
  Wire.beginTransmission(MPU6050_ADDR);
  Wire.write(0x3B);
  Wire.endTransmission();
  Wire.requestFrom(MPU6050_ADDR, 4);

ここのどこかで、whileループでもしているかのように戻ってこなくなった。



8/12 追記
I2Cはモータのノイズに弱いため(オープンドレインでHIGHのため)。SCLがHIGHの間にSDAが変化しているからかもしれない。
NACK(Not Acknow bit)が出ている。

L3G4200はSPIで信号に強うそうなためこちらについて研究していきたいと思う。


//11/19追記

IvanSenserはDMPについての情報を公開していない(レジスタアドレス0x39DMP_INT_STATUSなど)ため、DMPを使うためには、解析してくれている、下記のライブラリを理解するかだと思う。しかし、ライブラリを使った方が、速い気がする。
https://www.i2cdevlib.com/devices/mpu6050#source

ライブラリ内のMPU6050_DMP6.ino
では10msごとに、加速度データを送っていた(ロジックアナライザで読んだのみ)

コメント