GroveBeginnerKit を、C++(Arduino)とRuby(ラズパイ,Rboard) で使う　I2C - 加速度センサー(LIS3DHTR) 編

[しまねソフト研究開発センター](https://www.s-itoc.jp)（略称 ITOC）にいます、東です。

[Grove Beginner Kit for Arduino を使ってみる記事](https://qiita.com/HirohitoHigashi/items/dc43ea72d572b21a6823)の第4回、今回から I<sup>2</sup>C 接続のセンサーを題材にします。
このキットに付属している I<sup>2</sup>C 接続のセンサーは、以下の3種類です。

 * 3軸加速度センサー
 * 気圧センサー
 * 温湿度センサー

その中でも今回は、3軸加速度センサーを題材にします。

このレポートでは、

<b>「メーカーのデータシートを見て、センサICを直接コントロールをすること」</b>

を、方針とします。
もちろん、プログラムを書く上で既存のライブラリ等の実装を参考にすることは良い事です。しかしながらこのレポートは、I2C バスやセンサ IC の扱い方、データシートの読み方を示す事も目的としているため、できるだけ低レイヤーでの説明を行います。

ターゲットは、以下の通り。
* Arduino　- 付属の Arduino Uno 互換機
* Ruby - Raspberry Pi + [Grove Base HAT for Raspberry Pi](https://www.seeedstudio.com/Grove-Base-Hat-for-Raspberry-Pi.html)
* mruby/c - [RBoard](https://www.sjc-inc.co.jp/service/rboard)


# 3軸加速度センサー (3-Axis Digital Accelerometer)

| 外観 | 回路図 |
|:-:|:-:|
| ![grove_Accelerometer.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504019/1163c7c8-2798-d698-5ee8-91a31547af84.jpeg) | （センサーがGrove端子に接続されてるだけなので省略） |

（[メーカーWiki: Grove - 3-Axis Digital Accelerometer (LIS3DHTR)](https://wiki.seeedstudio.com/Grove-3-Axis-Digital-Accelerometer-LIS3DHTR/) より引用）

搭載されているセンサーICは、STマイクロエレクトロニクスの LIS3DHTR です。


# データシートの確認

[ICメーカー製品ページ](https://www.st.com/en/mems-and-sensors/lis3dh.html) で、データシートと必要に応じてアプリケーションノートなどを確認します。
プログラム作成に必要な情報は以下ぐらいでしょうか。
 * X,Y,Z の3軸方向の加速度センサー
 * フルスケールは、重力加速度を単位として ±2, 4, 8, 16g に切り替え可能
 * 測定値は、10bit を標準とし、8bit や 12bit に切り替え可能。よって、分解能は ±2g,12bit モードの、約 1mg
 * 出力は、いずれも 16bit　左詰で、2の補数表現で出力される
 * FIFO を持っている
 * I2Cアドレスは 0x18 もしくは 0x19 で、内部レジスタを 1バイトで指定する
 * レジスタの連続アクセスモードを持っており、その場合は MSB を 1 にして内部レジスタを指定する
 * 起動時はパワーダウンモードなので、動作モードの変更が必要

また、SPI接続などもできますが、この Grove モジュールは、I2C接続のみで作られており、アドレスは　0x19 です。

レジスタマップとその機能を確認して、サンプルプログラムの作成は、以下の方針で行います。

 * X,Y,Z 方向それぞれの値を、約0.5秒おきに画面表示する
 * フルスケール ±2g、12bit モードにする
 * FIFOを使わず、プログラムのタイミングでデータを読み込む

任意のタイミングで値を読み込む場合は、Block data update (BDU) の continuous update を外すことが強く推奨されると書いてあるので、それに従います。


## ICの初期化

### CTRL_REG1
![lis3dh-p35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504019/0007e3bd-7a83-b8ad-7c25-5a71528c5ebb.png)

データシートより、コントロールレジスタ CTRL_REG1(20h) の ODRビットでパワーダウンを解除するとともに、測定周波数を設定することが分かります。その他はデフォルト値でよさそうなので、ここでは 100Hzの測定モードにすることにして、0b0101_0111 を指定すれば良いことになります。

### CTRL_REG4
![lis3dh-p37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504019/f148fae5-ca32-9b49-b02b-9ab9ba96f74b.png)

同様に、コントロールレジスタ　CTRL_REG4(23h) に関し、以下の設定にすることとして 0b1000_1000 を設定します。
 * BDU ビットを 1 にして、continuous update モードを外す
 * FS ビットを、00 にして、±2g モードにする（これはデフォルト値）
 * HR ビットを 1 にして、ハイレゾモード (12bit) にする


## データの読み込み

![lis3dh-p39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/504019/40203ae5-6edd-9038-13d0-742b1e92e372.png)

測定が終わり測定値の出力準備が終わったら、ステータスレジスタ (STATUS_REG) の、\*DA (data available) ビットが 1になると書いてあります。
今回は XYZ 3軸のデータを取得するので、ZYXDA ビットが 1 になったら、OUT_\* レジスタを読んで値を取得します。


# サンプルプログラム

## Arduino

センサーを、`I2C` 端子に接続します。

```c++:I2C_Accelerometer.ino
#include <Wire.h>

const int I2C_ADRS = 0x19;

void setup() {
  Wire.begin();
  Serial.begin(9600);

  // IC初期化
  Wire.beginTransmission( I2C_ADRS );
  Wire.write( 0x20 );  // CTRL_REG1
  Wire.write( 0x57 );  // = 0101_0111
  Wire.endTransmission();

  Wire.beginTransmission( I2C_ADRS );
  Wire.write( 0x23 );  // CTRL_REG4
  Wire.write( 0x88 );  // = 1000_1000
  Wire.endTransmission();
}

void loop() {
  int sts;
  byte data[6];

  // STATUS_REG:XYXDA ビットが 1 になるのを待つ
  do {
    Wire.beginTransmission( I2C_ADRS );
    Wire.write( 0x27 );  // STATUS_REG
    Wire.endTransmission( false );
    Wire.requestFrom( I2C_ADRS, 1 );
    sts = Wire.read();
  } while( bitRead( sts, 3) == 0 );

  // データを読み込む
  Wire.beginTransmission( I2C_ADRS );
  Wire.write( 0x28 | 0x80 );  // OUT_X_L .. OUT_Z_H
  Wire.endTransmission( false );
  Wire.requestFrom( I2C_ADRS, 6 );
  for( int i = 0; i < 6; i++ ) {
    data[i] = Wire.read();
  }

  // 重力加速度に換算する
  double fs_div = 0x4000;  // 2g=0x4000, 4g=0x2000...
  double x = (int16_t)((data[1] << 8) | data[0]) / fs_div;
  double y = (int16_t)((data[3] << 8) | data[2]) / fs_div;
  double z = (int16_t)((data[5] << 8) | data[4]) / fs_div;

  // 表示
  Serial.print("X:");
  Serial.print(x, 3);
  Serial.print(" Y:");
  Serial.print(y, 3);
  Serial.print(" Z:");
  Serial.println(z, 3);

  delay( 500 );
}
```

Arduino は、C++ ベース（だと聞いている）ので、class を使おうと思いましたが、[Arduino 公式 Language Reference](https://www.arduino.cc/reference/en/) の範囲内で書くべきかと思いなおし、クラスは使いませんでした。
センサーからの読み込み値から、測定値 (重力加速度 g) に計算するため、定数 0x4000 で割り算をしています。これは、signed 16bit のフルスケールを 2g とみなすため、FS(0x8000) / 2 = 0x4000(=16384) を使っています。
ところが、Seeed 社の作ったライブラリを見ると　16000 で割り算をしており、0x4000(=16384) とは多少違いがあります。何故なのかデータシートを見ても分かりませんでした。それどころか、アプリケーションノート(AN3308) では、（換算が必要ですが） 16000、16384 両方の数値が使われていて、どちらが正しいのか分かりませんでした。



## Raspberry Pi (CRuby)

センサーを、`I2C` 端子に接続します。

```ruby:Accelerometer.rb
require "mruby/i2c"

class LIS3DHTR
  I2C_ADRS = 0x19

  #
  # init instance
  #
  def initialize( i2c_bus, address = I2C_ADRS )
    @bus = i2c_bus
    @address = address
  end

  #
  # init sensor
  #
  def init()
    # CTRL_REG1(20): Normal mode 100Hz, enable XYZ-axis
    @bus.write( @address, 0x20, 0b0101_0111 )
    # CTRL_REG4(23): Block data update disabled, fs=2g, HighResolution
    @bus.write( @address, 0x23, 0b1000_1000 )
  end


  #
  # meas
  #
  def meas()
    while true
      data = @bus.read( @address, 1, 0x27 )     # STATUS_REG(27)
      break  if data.getbyte(0)[3] == 1         # check ZYXDA bit enable
    end

    # read OUT_X_L(28) - OUT_Z_H(2D) continuously
    data = @bus.read( @address, 6, 0x28 | 0x80 ).bytes
    fs_div = 0x4000                             # 2g=0x4000, 4g=0x2000...
                                                # 16000, 7282, 3968, 1280 ?
    x = to_int16( data[1], data[0] ).to_f / fs_div
    y = to_int16( data[3], data[2] ).to_f / fs_div
    z = to_int16( data[5], data[4] ).to_f / fs_div

    return {:x=>x, :y=>y, :z=>z}
  end

  #
  # convert 2 byte to int16 value
  #
  def to_int16( b1, b2 )
    return (b1 << 8 | b2) - ((b1 & 0x80) << 9)
  end
end

sensor = LIS3DHTR.new( I2C.new )
sensor.init
while true
  data = sensor.meas
  printf("X:%5.3f Y:%5.3f Z:%5.3f\n", data[:x], data[:y], data[:z] )
  sleep 0.5
end
```

こちらはクラスを使います。init メソッドで初期化を、meas メソッドで測定値の取得を行います。`0b` を使って2進数が直接記述でき、しかも `_` で区切りを入れられて見やすくできるので、便利ですね。


## RBoard (mruby/c)

RaspberryPiと同じプログラムから、require をコメントアウトしたプログラムがそのまま動きます。


# おわりに

当レポート執筆のための調査中に、MEMS加速度センサーの性能比較を行っている方の記事を発見しました。良記事です。

地震観測を目的とした加速度センサ5種の性能比較

https://qiita.com/compo031/items/e62d0a0e1425c5e1efe8

今回は、Grove モジュールのコントロールのみに焦点をあてた記事で、測定周期も規定せず、とりあえず使ってみるという事を重視したものです。実用的な用途では、測定周期の指定やデータ抜けを防ぐための FIFO の利用などが必要になると思います。

データシートを読んでその機能と使い方が理解できるようになっていれば、既存のライブラリを使うだけではわからなかったセンサICの機能、性能を100%生かせますから、もっとたくさんの事ができるようになると思いますし、トラブルシュート時も、より早く原因にたどりつけることと思います。

次回は、同じく I2C を使った気圧センサーを試してみたいと思います。