マイコンの記録

今はとある組込系開発の活動記録です。

ジャイロをつなごう(I2C基本編)

いま開発を嗜んでいるマイコンは簡単にいうと、車載してコースを走るときの車両の状況を記録しつつ、四駆の電気自動車?なので、各モーターの回転数を車両の姿勢にあわせて微調整する、というも役目も担っている。ようやく、I2C通信もわかってきて周辺デバイスも制御できるようになってきたので、ちょっとまとめておこうと思う。今回はジャイロ。こういう感じのものでアマゾンなんかでも買える。しかも結構安い。最初の基板でなんだか動かなくて、依頼主に「なんか基板の調子が」って言ったら、直ぐにもう2枚送ってくれた。でも基板の調子はすこぶる良かったみたい。

f:id:loopsketch:20190303123908j:plain
GY-521

このボードは、MPU-6050というデバイスが搭載されていて加速度と角速度が取れる6軸のもの。基板上にx軸、y軸の向きが書いてあるので、丸い穴がある方が前で端子がある方が後ろ向きに搭載すれば、なんとなく姿勢が取れそうだ。arduinoの記事は良くみかけるが LPC-1347 ではなかなか見つからなかったので、I2Cデバイスを制御する流れをまとめておこう。まずは初期化。LPC-1347ではLPCOpenというライブラリを使って開発するが、そのI2Cモジュールを設定し、クロックは400kHzとした。

void I2CInit() {
	Chip_SYSCTL_DeassertPeriphReset(1);
	Chip_Clock_EnablePeriphClock(5);
	Chip_I2CM_ResetControl(LPC_I2C);
	Chip_I2CM_SetBusSpeed(LPC_I2C, 400000);
}


続いて、ジャイロの基板とI2C通信してみる。I2Cはデバイスを制御したい側をマスタ、されるデバイス側がスレーブとしてその間で通信を行う規格。読込指定でアドレスを送信するとデバイスからデータが返されたり、書込指定でアドレスとデータを送信すると、デバイスにデータを送れたりできる。各デバイスにはスレーブアドレスという、予め決められたデバイスを指定するためのアドレスがあり、それがあることで複数のI2Cデバイスが同じバス上に接続できる。LPCOpenではだいたいこんな感じに書く。

I2CM_XFER_T xfer;
uint8_t gyro_buff[16];

uint32_t test_gyroscope() {
	gyro_buff[0] = 0x75; // WHO_AM_I
	gyro_buff[1] = 0x00;
	xfer.slaveAddr = 0x68;
	xfer.txSz = 1;
	xfer.txBuff = &gyro_buff[0];
	xfer.rxSz = 1;
	xfer.rxBuff = &gyro_buff[1];
	uint32_t res = Chip_I2CM_XferBlocking(LPC_I2C, &xfer);
	if (!res || gyro_buff[1] != 0x68) {
		DEBUGPRT("failed WHO_AM_I %x\r\n", gyro_buff[1]);
		return 0;
	}
      :
      :
    return 1;
}

I2CM_XFER_Tという構造体は、LPCOpenでI2C通信するときに使う情報を登録する構造体で、Chip_I2CM_XferBlocking()関数が、I2C通信をする関数だ。Blockingという関数名だけあって、ちゃんと通信が終わるまで帰って来ない。それもなんかアレなので、この関数を丸々コピーして、タイムアウトするようなバージョンを実際は使っていて、タイムアウトした場合はリトライしたり、以降亡き者として処理できるように工夫する必要はあると思う。なお、スレーブアドレスは幾つか、とか、どのアドレスがどのような機能か、などはメーカのサイトや部品購入時にデータシートという資料があって、そこにまとめられている。このジャイロ基板の場合は0x68というスレーブアドレスで通信できると、メーカのサイトにあるレジスターマップという資料に書いてある。また、0x75というアドレスを読込指定で送信すると自身のスレーブアドレスを返すWHO_AM_Iという機能があり、今回はtxSz/txBuffで指定したポインタに0x75を書いておき、rxSz/rxBuffで指定したポインタに、うまく通信ができると0x68が書き込まれる。ので、13行目で、それを判定し、0x68以外ならエラーとしている。

この後必要に応じてデバイスの初期設定を行う。この基板は、電源オンになるとまずSLEEPモードに入りその間に初期化を行い、SLEEPを解除するとデータが収集されるようになる。SLEEPを解除するためには、PWR_MGMT_1というアドレスにパラメータを設定する。尚、スレーブアドレスは同じなので、xferはそのまま使い回している。gyro_buffも使い回しているが、サイズや内容は通信内容に応じて都度変更する。書込だけで読込が無い場合は、rxSz=0 / rxBuff=NULLとする。

      :
      :
    gyro_buff[0] = 0x6B; // PWR_MGMT_1
    gyro_buff[1] = 0x00;
    xfer.txSz = 2;
    xfer.txBuff = &gyro_buff[0];
    xfer.rxSz = 0;
    xfer.rxBuff = NULL;
    res = Chip_I2CM_XferBlocking(LPC_I2C, &xfer);
    if (!res) {
        DEBUGPRT("failed set PWR_MGMT_1 %x\r\n", res);
        return 0;
    }
      :
      :

最初、xferやgyro_buffをグローバルに置かずにローカル変数にしていたが、誤動作して無駄に悩んだのでグローバルに置いて使い回すようにした。正しい書き方や振る舞いが判る人がいれば教えて欲しいところ。別の案件でもスタックは小さいから気をつけろ、とか、特殊ルールみたいなのがあったので、なんとなくそういうことなのかな〜と思ったけど、ちょっとポインタ引数はドキドキする。今回は半分趣味なので気楽だけど。では最後に、姿勢データを取り出すところ。0x3Bから14バイト読み込むと、それが、加速度、気温、角速度データになっている。

      :
      :
    int16_t raw[7];
    gyro_buff[0] = 0x3B;
    xfer.txSz = 1;
    xfer.txBuff = &gyro_buff[0];
    xfer.rxSz = 14;
    xfer.rxBuff = &gyro_buff[1];
    uint32_t res = Chip_I2CM_XferBlocking(LPC_I2C, &xfer);
    if (res) {
        raw[0] = (int16_t)((((uint16_t)gyro_buff[ 1]) << 8) | gyro_buff[ 2]);
        raw[1] = (int16_t)((((uint16_t)gyro_buff[ 3]) << 8) | gyro_buff[ 4]);
        raw[2] = (int16_t)((((uint16_t)gyro_buff[ 5]) << 8) | gyro_buff[ 6]);
        raw[3] = (int16_t)((((uint16_t)gyro_buff[ 7]) << 8) | gyro_buff[ 8]);
        raw[4] = (int16_t)((((uint16_t)gyro_buff[ 9]) << 8) | gyro_buff[10]);
        raw[5] = (int16_t)((((uint16_t)gyro_buff[11]) << 8) | gyro_buff[12]);
        raw[6] = (int16_t)((((uint16_t)gyro_buff[13]) << 8) | gyro_buff[14]);
    }
      :
      :

各データは、ビックエンディアンで並んでいる16bitのshortの値。キャストの仕方とか、もっと簡潔に書けるかもしれないので、誰か教えてください。また、取れるのはrawデータなので、この後加速度や気温、角速度に変換する必要がありますが、細かいところはレジスターマップを見よう。このジャイロ基板は様々な機能があって、まだうまくいってないんだけど、いろいろやると結構正確にデータを取れそうな気もする。このやり方だと、常に最新値を取るだけなので、場合によってはデータが欠落してて実際の姿勢よりも少なく出るんじゃないかと思うし、arudino界隈で使われてるソースを参考に、FIFOの辺りとかをもうちょっと調査しようと思っている。