VR空間移動用いす型デバイスを作成する⑧椅子の回転角度をジャイロセンサーで読み取る

秋月電子のジャイロセンサモジュールを購入しました。
これで椅子の回転を読み取れるようにします（エンコーダの代替）。
エンコーダを使用したときと同じように回転角度を5度刻みで出力させます。
角度の算出方法は以下のサイトに詳しいようです。
https://garchiving.com/angular-from-angular-acceleration/

#目次
１．ジャイロセンサについて
２．回路の作成
３．WHO_AM_I
４．回転角度の検知

#１．ジャイロセンサについて
ジャイロセンサとは角速度を読み取るセンサです。
時間当たりの回転角度の変化を出力します。これを使用し、椅子の回転を読み取ります。
これまでは椅子の軸部にエンコーダをつけていましたが、これを取り外し、椅子内部に回転検出用のセンサを内蔵してやります。
いすの高さ調整ができるようになったり、組み立てが簡単になったりとかメリットあります。
<img width=25% src=https://qiita-image-store.s3.amazonaws.com/0/184089/910fad93-894c-a16e-7ac7-a60cc26aae60.png>
センサは秋月電子の「ＳＴマイクロＬ３ＧＤ２０使用　３軸ジャイロセンサーモジュール」を購入しました。
椅子の回転のみなら一軸取れれば十分なのですが、このセンサは出力がデジタル（I2C）なので便利と思い買ってみました。

#２．回路の作成
Arduino UNOで出力を読み取るために以下の回路図の回路を組みます。
<img width=75% src=https://qiita-image-store.s3.amazonaws.com/0/184089/73f4ea8b-3ea6-7bd2-c74c-f1f4d69acec3.png>
ジャイロセンサの駆動電圧範囲が2.4V～3.6Vになっていますので、Arduinoの3.3V出力をセンサの電源に供給します。
センサのCSを3.3Vに接続するとI2Cモードになります。また、SA0を3.3VまたはGNDのどちらかに接続することでセンサのスレーブアドレスを選択できます。3.3Vにつないでいるので、アドレスは1101011になります。

そのままでつなぐとセンサ側は3.3V、Arduino側は5Vで駆動となり電圧が合わなくなります。間に「Ｉ２Ｃバス用双方向電圧レベル変換モジュール」を挟みます。
<img width=25% src=https://qiita-image-store.s3.amazonaws.com/0/184089/d202c826-1e8c-c9e2-d094-9afdd10bd5fa.png>
電圧の異なるI2C信号をそれぞれの電圧に変換してくれます。秋月電子で買えます。
Arduino UNOにはSCL、SDAと書かれたピンがあるのでここにI2Cの信号線をつなぎます。実際にはこのピンの正体はアナログ出力のA5、A4ピンなのですが（デジマルとかで確認すると導通しているのがわかる）、どちらにつないでもよいかと。

#３．WHO_AM_I
まずは通信できるのかを確認するため、センサの固有ID（通称WHO_AM_I）を読めと説明書にありました。
以下のようにArduinoのスケッチに書き込んでみました。

```arduino:GyroSensor.ino
#include <Wire.h>		//I2C Arduino Library
#define address 0x6B	//1101011b, I2C 7bit address of L3GD20

void setup() {
  Serial.begin(9600);
  Wire.begin();
  delay(500);
  Wire.beginTransmission(address);
  Wire.write(0x0F);					//WHO_AM_I -> 0x0F
  Wire.endTransmission(); 
  Wire.requestFrom(address, 1);
  Serial.println(Wire.read());
}

void loop() {

}
```
Arduinoのツールよりシリアルモニタを起動し結果を見てみます。
<img width=75% src=https://qiita-image-store.s3.amazonaws.com/0/184089/a4443002-d7b6-26c7-59c2-9f1e23acb99b.png>

ちょっと解説です。
<font color="DeepSkyBlue">ⅰ．Arduinoのプログラムについて</font>
・Wire.h
I2C通信を行うため、Wireライブラリを読み込みます。
・address 0x6B
センサのスレーブアドレス1101011（=16進数の0x6B）をaddressと定義します。
・Serial.begin(9600)
PCへデータを送信するため、シリアルポートを9600bpsの速度で開きます。
・Wire.write(0x0F)
固有IDはセンサのレジスタアドレス00001111（=16進数の0x0F）にあります。
・Serial.println(Wire.read())
読込んだレジスタアドレス00001111の中の数値（固有ID）をPCに送ります。

<font color="DeepSkyBlue">ⅱ．結果について</font>
シリアルモニタに212と表示されました。
固有IDの11010100（=16進数の0xD4）（=10進数の212）が読み取れました。

#４．回転角度の検知
組んだ回路を椅子の座面上に接着します。
<img width=25% src=https://qiita-image-store.s3.amazonaws.com/0/184089/bc81c0f7-10b3-a45c-9606-776418f51c1a.png>

椅子の回転角度を検知するプログラムを作成し、スケッチに書き込みます。
回転角度とそれを５度ごとに要約した値を出力します。

```arduino:GyroSensor.ino
#include <Wire.h>			//I2C Arduino Library
#define address 0x6B		//1101011b, I2C 7bit address of L3GD20
int outZH,outZL,angleIn5;
long timeNow,timePre;
float degree,angle,addTime;

void setup() {
	Serial.begin(9600);
	Wire.begin();
	Wire.beginTransmission(address);
	Wire.write(0x20);		//CTRL_REG1
	Wire.write(0x0F);		//power-down mode -> normal mode
	Wire.endTransmission();
}

void loop() {
	delay(50);
	Wire.beginTransmission(address);
	Wire.write(0x2C);
	Wire.endTransmission(); 
	Wire.requestFrom(address, 1);
	outZL = Wire.read();
	Wire.beginTransmission(address);
	Wire.write(0x2D);
	Wire.endTransmission(); 
	Wire.requestFrom(address, 1);
	outZH = Wire.read();
	degree = 0.00875 * ((outZH<<8) + outZL);
	
	timeNow = micros();
	if(timeNow > timePre) addTime = (timeNow - timePre) / 1000;
	timePre = timeNow;
	degree = degree * addTime / 1000;
	if( (degree>0.05) || (degree<-0.05)) angle += degree;
	if(angle>360) angle = angle-360;
	if(angle<0) angle = 360-angle;
	angleIn5 = (angle/2.5+1)/2;
	angleIn5 = angleIn5 * 5;
	if(angleIn5 == 360) angleIn5 = 0;

	Serial.print(" angle ");
	Serial.print(angle);
	Serial.print(" = ");
	Serial.println(angleIn5);
}
```
Arduinoのツールよりシリアルモニタを起動し椅子を回転してみます。

<img width=75% src=https://qiita-image-store.s3.amazonaws.com/0/184089/cc9e8c27-b5fd-b652-efc4-7169ed5b3275.png>

椅子を回転すると値が変化しました。

ちょっと解説です。
<font color="DeepSkyBlue">ⅰ．Arduinoのプログラムについて</font>
・void setup() 内
起動時はパワーダウンモードのため、ノーマルモードにします。取説に従って0x0Fと書き込みました、
XYZ軸すべてが読み取れる設定になります。必要なのはZ軸だけですが、気にしない。
他の設定は触っていません。
この場合は分解能は0.00875dps、データレートは95Hz（=1秒間に95回測定）になります。
・delay(50)
50ms毎に動作します。人が椅子を回転する速度が50msの間に大きく変化はしないと考えますので、この数値は妥当かと。
・Wire.beginTransmission(address)～degree = 0.00875 * ((outZH<<8) + outZL)まで
Z軸の回転速度を読み取ります。レジスタアドレス0x2Cに下位8bit、0x2Dに上位8bitが入っています。合わせて16bitにして、分解能0.00875dpsを掛けます。
順に0x2C、0x2Dに一つずつアクセスします。Wire.requestFrom(address, 2)と書き込めば、連続してアクセスできるはずなのですが、なぜか上手くいかない（2回とも同じ数値が出てきました）。面倒だが、一つずつアクセスするしかないのか・・・。
・timeNow = micros()
精度を出すため、測定毎の経過時間を確認します。micros()を使用すると、Arduino起動からの経過時間がマイクロ秒単位で得られます。
・if(timeNow > timePre) addTime = (timeNow - timePre) / 1000
micros()は70分くらいで0に数値が戻ります。０をまたいだ時は何もしないようにします。たまにしか起こらないことなので問題ないかと。前回の経過時間との差をとります。ついでにマイクロ秒からミリ秒に直します。
・degree = (degree * addTime / 1000)
センサの出力である時間当たりの回転角度の変化に対し、実際の時間をかけると角度変化となります。addTimeの単位がここではミリ秒なので、秒単位にするため、1000で割ってます。
・if( (degree>0.05) || (degree<-0.05)) angle += degree
変化量を加算することで現在の角度が出てきます。
ただ、センサに何もしなくても小さい数値が出力されるようです。小さい値でも加算していくと、大きな誤差になっていきますので、小さい値はif（）内にあるように無視するようにします。
・if(angle>360) angle = angle-360～if(angleIn5 == 360) angleIn5 = 0まで
360度を越えた時や0度を下回った時に角度を0～360までの数値に戻してやります。また、±2.5度の範囲にある角度を5度刻みの角度に直します。

<font color="DeepSkyBlue">ⅱ．結果について</font>
・Serial.print(" angle ")～Serial.println(angleIn5)まで
椅子の角度と5度刻みの値を順に出しています。angleは椅子の細かい数値までの角度、angleIn5は5度刻みに直した数値です。椅子を回転すると数値が変化します。うまいこと、5度刻みの値に直しているかと。
どうしても回転させていくうちに1度、2度と角度にずれが発生しているようなのですが、5度ごとの値として出していたり、実際に椅子を回転させるとそれほど気にならなかったりで精度は十分そうです。

以上になります。

UnityとかのPCとのデータの受け渡しとかまでは載せていません。