6軸IMUセンサ BMI160 を使ってみる

6軸IMUセンサの "Bosch BMI160" を動かした時のメモになります。
Bosch社が提供している BMI160 sensor API (https://github.com/BoschSensortec/BMI160_driver) を使っています。

## 必要なもの
* Bosch BMI160 センサ
私はAmazonで１つ300円程度のブレークアウトボードを購入しました。
センサモジュールの値段に対して安すぎるので動かない可能性もありそうですが、私の買ったものは大丈夫でした。
Accel,Gyroの6軸、16bitの分解能、最大1600Hz と値段の割にかなりのスペックだと思います。

* Arduinoボード
私は ESP32-DevKitC を使用しています。

## 公式ドキュメントへのリンク
* BMI160のデータシート
https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMI160-DS000.pdf
BMI160の機能や使用方法で困った時はこれを見ます。

* BMI160のAPI
https://github.com/BoschSensortec/BMI160_driver
今回はこのAPIを使用してセンサを動かします。

## 準備
### BMI160 sensor API の取得と配置
https://github.com/BoschSensortec/BMI160_driver からソースコードを取得します。
Arduino IDE で使用するので ~/Arduino/libraries ディレクトリに配置します。

```
Arduino/
 +-- libraries/
      +-- BMI160_driver/   <-- ここに配置します
           +-- bmi160.c
           +-- bmi160.h
           +-- bmi160_defs.h
```

## 実行
### サンプルコード
以下は、I2Cインターフェースを使って Accel, Gyro を取得するコードになります。

* ESP32用に書いてあるのでSerialボーレートやピン番号は置き換えてください。
* 私の使用しているBMI160ブレークアウトボードでは、I2Cアドレスが0x69だったので ``BMI160_I2C_ADDR + 1`` のようにしています。

```c++:bmi160_example1.ino
#include <Wire.h>
#include "bmi160.h"

static const uint8_t I2C_SDA = 21;
static const uint8_t I2C_SCL = 22;
static const uint32_t I2C_FREQUENCY = 400000;  // 400kHz (fast mode)

struct bmi160_dev sensor;

void setup() {
  Serial.begin(115200);
  while (!Serial);

  Wire.begin(I2C_SDA, I2C_SCL, I2C_FREQUENCY);

  int8_t ret = BMI160_OK;

  // BMI160 センサの初期化.
  sensor.id = BMI160_I2C_ADDR + 1;  // 0x68 or 0x69
  sensor.interface = BMI160_I2C_INTF;
  sensor.read = user_i2c_read;      // [1] (※後述）
  sensor.write = user_i2c_write;    // [2]
  sensor.delay_ms = user_delay_ms;  // [3]
  ret = bmi160_init(&sensor);
  Serial.print("bmi160_init: ");
  Serial.println(ret);

  // Accel の設定.
  sensor.accel_cfg.odr = BMI160_ACCEL_ODR_100HZ;
  sensor.accel_cfg.range = BMI160_ACCEL_RANGE_2G;
  sensor.accel_cfg.bw = BMI160_ACCEL_BW_NORMAL_AVG4;
  sensor.accel_cfg.power = BMI160_ACCEL_NORMAL_MODE;
  // Gyro の設定.
  sensor.gyro_cfg.odr = BMI160_GYRO_ODR_100HZ;
  sensor.gyro_cfg.range = BMI160_GYRO_RANGE_2000_DPS;
  sensor.gyro_cfg.bw = BMI160_GYRO_BW_NORMAL_MODE;
  sensor.gyro_cfg.power = BMI160_GYRO_NORMAL_MODE;
  ret = bmi160_set_sens_conf(&sensor);
  Serial.print("bmi160_set_sens_conf: ");
  Serial.println(ret);

  Serial.println("========================================");
  Serial.println("accl.x\taccl.y\taccl.z\tgyro.x\tgyro.y\tgyro.z");
}

void loop() {
  struct bmi160_sensor_data accel;
  struct bmi160_sensor_data gyro;

  // Accel, Gyro の取得.
  bmi160_get_sensor_data(
    (BMI160_ACCEL_SEL | BMI160_GYRO_SEL),
    &accel, &gyro, &sensor);

  // シリアルへの出力.
  Serial.print(accel.x);
  Serial.print("\t");
  Serial.print(accel.y);
  Serial.print("\t");
  Serial.print(accel.z);
  Serial.print("\t");
  Serial.print(gyro.x);
  Serial.print("\t");
  Serial.print(gyro.y);
  Serial.print("\t");
  Serial.print(gyro.z);
  Serial.println();

  delay(100);
}

// [1] I2C read access 
int8_t user_i2c_read(uint8_t dev_addr, uint8_t reg_addr, uint8_t* data, uint16_t len) {
  // note: arduino-esp32 v1.0.3 のWireライブラリでは
  //       I2Cバッファサイズが 128 bytes であるため、
  //       lenが128以上になる場合(FIFO機能など)はそのままでは使えない
  Wire.beginTransmission(dev_addr);
  Wire.write(reg_addr);
  Wire.endTransmission(false);
  Wire.requestFrom((int)dev_addr, len);
  Wire.readBytes(data, len);
  return 0;
}

// [2] I2C write access
int8_t user_i2c_write(uint8_t dev_addr, uint8_t reg_addr, uint8_t* data, uint16_t len) {
  Wire.beginTransmission(dev_addr);
  Wire.write(reg_addr);
  Wire.write(data, len);
  Wire.endTransmission();
  return 0;
}

// [3]
void user_delay_ms(uint32_t period) {
  delay(period);
}
```

このコードを動かしてAccel, Gyroのセンサ値(16bit)が取れていれば成功です。

### 解説
BMI160 sensor API は、SPIとI2Cの2つのインターフェースが使用できます。
I2Cインターフェースを使用する場合、環境に合わせて以下の3つの関数をユーザ側で用意する必要があります。

```c++
// [1]
int8_t user_i2c_read(uint8_t, uint8_t, uint8_t*, uint16_t)
// [2]
int8_t user_i2c_write(uint8_t, uint8_t, uint8_t*, uint16_t)
// [3]
void user_delay_ms(uint32_t)
```

user_i2c_read, user_i2c_write 関数では、データシートの P.94 〜 95 の信号を送る実装になっています。
bmi160_*** APIを呼び出すと、これら3つの関数が自動的に呼ばれてレジスタ制御が行わる仕組みになっています。