初めての電子工作: Arduinoで6軸センサを動かしてみる


なんとなく思い立って初めての電子工作。


# 用意したもの

- [Arduino エントリーキット(Uno版）](http://amzn.to/2fW4J1y)
- [MPU-6050 ３軸ジャイロスコープ ３軸加速度センサー (2個)](http://amzn.to/2g3S4JY)
- [Arduinoをはじめよう 第3版 (Make:PROJECTS)](http://amzn.to/2gLQWMX)

## Arduino Uno R3

購入したのは [Arduino Uno R3](https://www.arduino.cc/en/Main/ArduinoBoardUno) で電子工作のプロトタイプによく使われている。ぐぐると分かるけど、回路図やソフトウェアもたくさん公開されている。最近売られているものはアクリルの土台がついてくるので底面を気にせず置けて便利。

![2016-12-24 15.01.44.jpg](https://qiita-image-store.s3.amazonaws.com/0/29860/734707f1-b76a-2a95-29a2-f4f5fac89521.jpeg "2016-12-24 15.01.44.jpg")

AVRマイコンが載ってて足にピンソケット経由でアクセスできる。専用のIDEからUSBケーブルで接続して、＃っぽい何か（よく分かってない）をコンパイルして書き込むことができる。I/O電圧は5Vらしいので、3.3V機器とアクセスする際には注意が必要？

UnoのI2Cバスは、SDA, SCLに専用のピンが用意されているみたい（I2C使ってるときはA4, A5は使えないみたい）。

## GY-521モジュール

搭載されている6軸センサは [InvenSense MPU-6050](https://www.invensense.com/products/motion-tracking/6-axis/mpu-6050/)というもの。加速度が3軸、角速度が3軸の合計6軸。いわゆる磁気コンパスは搭載されていない（磁気コンパス必要な方はInvenSenseのMPU-9250搭載モジュールをどうぞ）。ピンヘッダが別でついてくるのではんだ付けした。

![2016-12-24 15.18.30.jpg](https://qiita-image-store.s3.amazonaws.com/0/29860/b530a961-b93d-0ed2-a797-1a56b7de3816.jpeg "2016-12-24 15.18.30.jpg")


内部に搭載されたDSPで6軸の値を演算することで、マイコンの支援なしにタップや歩行検出などを行うことができる、らしい。

レギュレータが入っているので5Vを入力しても大丈夫。消費電力は加速度、角速度、DMPを有効にして3.9mA。

- [MPU-6050 DataSheet](https://www.invensense.com/wp-content/uploads/2015/02/MPU-6000-Datasheet1.pdf)
- [MPU-6050 Register Map](https://store.invensense.com/Datasheets/invensense/RM-MPU-6000A.pdf)

# 回路図

フリーの回路CADであるところの[Fritzing](http://fritzing.org/)を使った。GUIで部品をおいたり線を引っ張って設計できる。ブレッドボードにジャンパ挿して組み立てると、次に使うとき忘れてしまうのでメモがてらに作った。

Unoは電源とGND, I2Cバスだけ使う。こんなに乱暴に接続して大丈夫なのか、よくわからない。

- Uno 3.3V -> GY-521 VCC
- GY-521 GND -> Uno GND
- GY-521 SDA -> Uno 16
- GY-521 SCL -> Uno 17

![mpu6050_ブレッドボード.png](https://qiita-image-store.s3.amazonaws.com/0/29860/37332cc9-46d5-294e-def0-8da7a6bf0722.png "mpu6050_ブレッドボード.png")

# ソースコード

[Arduino Playbookのソース](http://playground.arduino.cc/Main/MPU-6050)を参考にしているという[加速度+ジャイロのGY-521（MPU-6050）を使ってみた -1-](http://cranberrytree.blogspot.jp/2014/06/gy-521mpu-6050.html)を使わせていただきました。ソース内のコメントは適当に追記。

流れとしては、

1. 通信周りの初期設定
2. デバイスを初期化する
3. デバイスをアクティブにする
4. デバイスレジスタからセンサ値を読み出し
5. 一定時間待つ
6. 4に戻る

というかんじ。

```arduino
// MPU-6050 Accelerometer + Gyro

// I2CにアクセスするためにWireライブラリを使用
#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() {
  int error;
  uint8_t c;

  Wire.begin();
  
  // ボーレートを115200bpsにセット
  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動作開始
  MPU6050_write_reg(MPU6050_PWR_MGMT_1, 0);
}

void loop() {
  int error;
  float dT;
  accel_t_gyro_union accel_t_gyro;
  
  // 加速度、角速度の読み出し
  // 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");

  // 取得できるデータはビッグエンディアンなので上位バイトと下位バイトの入れ替え（AVRはリトルエンディアン）
  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");

  // 取得した加速度値を分解能で割って加速度(G)に変換する
  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");

  // 取得した角速度値を分解能で割って角速度(degrees per sec)に変換する
  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);
  Serial.print("error = ");
  Serial.println(error);
  return (error);
};

```

# 動かしてみる

ブレッドボードを組み立てる。特に電源のプラス・マイナスを間違えるとデバイスが壊れることがあるので気を付ける。

![2016-12-24 15.42.34.jpg](https://qiita-image-store.s3.amazonaws.com/0/29860/dbdc61ba-2fde-3c5d-0377-22c80e2ba161.jpeg "2016-12-24 15.42.34.jpg")

Arduino IDEに上記プログラムを打ち込んで *スケッチ→マイコンボードに書き込む* を選択すると、Unoにプログラムがアップロードされて実行される。ログ出力はシリアルモニタで確認できる。 *ツール→シリアルモニタ* を確認すると次のように表示される。

```text
InvenSense MPU-6050June 2012WHO_AM_I : 68, error = 0
PWR_MGMT_1 : 0, error = 0
error = 0
0	27.0	0.01	-0.02	0.97	0.42	-1.11	159.36	-3.07	-0.93	-0.62
0	27.0	-0.00	-0.02	0.97	-0.17	-1.27	-172.23	-2.95	-1.09	-0.53
0	27.1	-0.00	-0.01	0.97	-0.10	-0.75	-172.33	-3.05	-0.88	-0.54
```

# 気づき

- とりあえずセンサを動かすだけなら簡単
	- 中高生でもヨユーでできるレベル
	- Arduino以外にもブレッドボードやジャンパが必要なのでキット購入が楽
	- このエントリには出てこないけど書籍があると分かりやすい
- Arduino IDEはお花畑
	- 取り扱えない文字が入っててもハイライトしてくれないし
	- シンタクスハイライトも貧弱だし
	- コードフォーマッティングに自由度がないし