とある科学の備忘録

とある科学の備忘録

CやPythonのプログラミング、Arduino等を使った電子工作をメインに書いています。また、木製CNCやドローンの自作製作記も更新中です。たまに機械学習とかもやってます。

【Arduino】MPU6050とI2C通信して、加速度とジャイロのデータを取得

題名の通り、今回から6軸センサーの「MPU-6050」を使用していきます。

f:id:pythonjacascript:20190216151131j:plain

この記事では、MPU6050から加速度とジャイロの計測データを取得して、それをシリアルモニターに表示するところまでを行います。
それ以降の内容(角度算出etc.)は、別の記事に書いていくつもりです。

1.下準備(反田付け)

私は、ここからMPU-6050のモジュールを購入しました。Amaozonさんです。


購入すると、このようにピンヘッダが2種類付属していたので、曲がっている方をとりあえず半田付けします。
f:id:pythonjacascript:20190216151456j:plain


半田付け後、裏面の様子です。
f:id:pythonjacascript:20190216151537j:plain

2.回路図

以下のような回路を組んでください。
f:id:pythonjacascript:20190216153145j:plain


Arduino内部で線がつながっているので、このように繋いでもOKです。
f:id:pythonjacascript:20190216162859j:plain


本当は、Arduinoの入出力ピンが5V、MPU-6050の入出力が3.3Vなので、電圧変換モジュールをはさんだ方がよいと思います。
上のように組んでも一応動きます。

3.サンプルプログラム

以下のプログラムをArduinoに書き込んでください。

#include <Wire.h>

// MPU-6050のアドレス、レジスタ設定値
#define MPU6050_WHO_AM_I     0x75  // Read Only
#define MPU6050_PWR_MGMT_1   0x6B  // Read and Write
#define MPU_ADDRESS  0x68


// デバイス初期化時に実行される
void setup() {
  Wire.begin();

  // PCとの通信を開始
  Serial.begin(115200); //115200bps
 
  // 初回の読み出し
  Wire.beginTransmission(MPU_ADDRESS);
  Wire.write(MPU6050_WHO_AM_I);  //MPU6050_PWR_MGMT_1
  Wire.write(0x00);
  Wire.endTransmission();

  // 動作モードの読み出し
  Wire.beginTransmission(MPU_ADDRESS);
  Wire.write(MPU6050_PWR_MGMT_1);  //MPU6050_PWR_MGMT_1レジスタの設定
  Wire.write(0x00);
  Wire.endTransmission();
  
}


void loop() {
  Wire.beginTransmission(0x68);
  Wire.write(0x3B);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68, 14, true);
  while (Wire.available() < 14);
  int16_t axRaw, ayRaw, azRaw, gxRaw, gyRaw, gzRaw, Temperature;

  axRaw = Wire.read() << 8 | Wire.read();
  ayRaw = Wire.read() << 8 | Wire.read();
  azRaw = Wire.read() << 8 | Wire.read();
  Temperature = Wire.read() << 8 | Wire.read();
  gxRaw = Wire.read() << 8 | Wire.read();
  gyRaw = Wire.read() << 8 | Wire.read();
  gzRaw = Wire.read() << 8 | Wire.read();

  // 加速度値を分解能で割って加速度(G)に変換する
  float acc_x = axRaw / 16384.0;  //FS_SEL_0 16,384 LSB / g
  float acc_y = ayRaw / 16384.0;
  float acc_z = azRaw / 16384.0;

  // 角速度値を分解能で割って角速度(degrees per sec)に変換する
  float gyro_x = gxRaw / 131.0;//FS_SEL_0 131 LSB / (°/s)
  float gyro_y = gyRaw / 131.0;
  float gyro_z = gzRaw / 131.0;

  Serial.print(acc_x);  Serial.print(",");
  Serial.print(acc_y);  Serial.print(",");
  Serial.print(acc_z);  Serial.print(",");
  Serial.print(gyro_x); Serial.print(",");
  Serial.print(gyro_y); Serial.print(",");
  Serial.print(gyro_z); Serial.println("");
}

 


4.実行

プログラムを書き込んだら、ArduinoをUSBでPCに接続し、シリアルモニターを開きます。
通信速度は115200bpsに設定しておいてください。

f:id:pythonjacascript:20190216170934j:plain
電源が供給されると、MPU-6050の基盤に実装されたチップLEDが赤く光ります。


下のようにデータが送られてきたら成功です。
f:id:pythonjacascript:20190216153627j:plain
 

5.簡単に解説

まず、MPU-6050とArduinoの通信には、I2Cという通信規格が用いられています。
I2C通信を行うには、「Wire.h」というライブラリを使用する必要があります。

では、このプログラムの動作を考えていきます。

(1)MPU-6050のセットアップ

まず、MPU-6050の初期設定を行います。

Wire.begin();

の命令で、MPU-6050とのI2C通信を開始します。



次に、MPU6050のデバイス確認と設定を行います。

(2) 通信相手の確認

  // 初回の読み出し
  Wire.beginTransmission(MPU_ADDRESS);
  Wire.write(MPU6050_WHO_AM_I);
  Wire.write(0x00);
  Wire.endTransmission();

の4行は、MPU-6050の通信アドレスを確認しています。


正確には、MPU-6050の中の「WHO_AM_I」レジスタを読み込んでいます。
「WHO_AM_I」レジスタのメモリ番地は0x75なので、

#define MPU6050_WHO_AM_I     0x75  // R

と書いているのです。

このレジスタはデバイスの識別情報を確認するために使用されます。 WHO_AM_Iの内容は、MPU-60X0の7ビットI2Cアドレスの上位6ビットであり、デフォルト値は0x68です。


WHO_AM_Iレジスタの中身です。(MPU-6050 Register Mapより)
f:id:pythonjacascript:20190216155542j:plain


この4行は通信相手の動作確認のために書いたので、削除しても動作します。



(3) 動作モードの読み出し

#define MPU6050_PWR_MGMT_1   0x6B  // Read and Write
(中略)
// 動作モードの読み出し
Wire.beginTransmission(MPU_ADDRESS);
Wire.write(MPU6050_PWR_MGMT_1);  //PWR_MGMT_1レジスタの設定
Wire.write(0x00);
Wire.endTransmission();

この4行で、MPU-6050の動作モードを読み込みます。

0x6B とは、MPU-6050の「PWR_MGMT_1レジスタの番地を表しています。

PWR_MGMT_1レジスタは、電力モードとクロックソースの設定を行うためのものです。

また、デバイス全体をリセットするためのビットと、温度センサーを無効にするためのビットもあります。

PWR_MGMT_1レジスタの中身です。
f:id:pythonjacascript:20190216155526j:plain


次の表に従ってクロックソースを選択できます。
f:id:pythonjacascript:20190216160430j:plain
 

この4行は、MPU-6050の重要な設定が含まれているので、削除すると動かなくなります。

(4) 値を取得

以下の部分で値を取得します。

  Wire.beginTransmission(0x68);
  Wire.write(0x3B);
  Wire.endTransmission(false);
  Wire.requestFrom(0x68, 14, true);
  while (Wire.available() < 14);
  int16_t axRaw, ayRaw, azRaw, gxRaw, gyRaw, gzRaw, Temperature;

  axRaw = Wire.read() << 8 | Wire.read();
  ayRaw = Wire.read() << 8 | Wire.read();
(以下も同じ形式なので略)

int16_t とは、符号付き整数を格納する16ビットの変数型です。
ここでは使用しませんが、uint16_t にすると、正の整数の16ビットになります。

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

は、「今からデータを送って下さい!!」という命令を送信しています。
因みに、0x3Bは、ACCEL_XOUT_H(x軸加速度データの上位バイト)が格納されているレジスタの番地です。

「ACCEL_XOUT_Hレジスタから14ビット分連続でレジスタの値を送信してください」という命令です。

ちなみに、ACCEL_XOUT_Hレジスタ以降の値は以下の表のとおりです。
f:id:pythonjacascript:20190216161555j:plain

加速度のほかにも、角速度、温度のデータを送っていることがわかります。


MPU-6050から送られてくる加速度、角速度のデータは16ビットですが、I2C通信で一回で送ることのできる値は8ビットです。
そこで、一つのデータ(数値)あたり2回のI2C読み込み処理を行って、特定の変数に格納する作業が

  axRaw = Wire.read() << 8 | Wire.read();

の部分で行われています。


(5) 値を補正

送られてきた値を補正します。

というのも、PWR_MGMT_1でセンサー感度を設定したとき、それと同時に測定値の数値のScalability(数値の示す大きさ)を変更したことになります。そこで、下のように数値を乗除して正しい値にしているのです。

  // 加速度値を分解能で割って加速度(G)に変換する
  float acc_x = axRaw / 16384.0;  //FS_SEL_0 16,384 LSB / g
  float acc_y = ayRaw / 16384.0;
  float acc_z = azRaw / 16384.0;

  // 角速度値を分解能で割って角速度(degrees per sec)に変換する
  float gyro_x = gxRaw / 131.0;
  float gyro_y = gyRaw / 131.0;
  float gyro_z = gzRaw / 131.0;

これらの値は、データシートに基づいています。
(加速度の設定)
f:id:pythonjacascript:20190216162202j:plain
上のプログラムの場合、「AFS-SEL」は0に設定されているため、16384で割っています。

(ジャイロの設定)
f:id:pythonjacascript:20190216162205j:plain
上のプログラムの場合、「FS-SEL」は0に設定されているため、131で割っています。


(6) 送信

あとは、補正した値をシリアル通信で送信するだけです。

  Serial.print(acc_x);  Serial.print(",");
  Serial.print(acc_y);  Serial.print(",");
  Serial.print(acc_z);  Serial.print(",");
  Serial.print(gyro_x); Serial.print(",");
  Serial.print(gyro_y); Serial.print(",");
  Serial.print(gyro_z); Serial.println("");

これらの命令達をループ関数内で繰り返し行うことで、連続して値を取得することができます。