MPU9250で地磁気センサを使う方法(SPIで)

はじめに

数年前から,やれIoTだなんだという言葉がバズワードっぽく流行っている.
そのおかげか知らないが,安価で小型なセンサが数多く出回るようになり,
ロボット製作を目指すものとしてはうれしい限りである.
そんな中でも一番需要の高いセンサと言えばIMU(完成計測装置)であろう.

なんせ,二足歩行させるにも,ドローンを飛ばすにも姿勢の安定は最重要課題である.
そのため,姿勢を計測できるセンサの精度は非常に重要である.
当然,私が以前作っていた倒立二輪型のロボットでも,この手のセンサは最重要部品の一つと言えるだろう.

数ヶ月前,Aliexpressで9軸のIMUを見つけ,思わず購入してしまった. ja.aliexpress.com

このモジュールに載っているのはInvenSense社(現在はTDKが買収)のMPU9250というチップ.
前に使っていたMPU6050の後継機種ともいえるもので,最大の特徴はMPU6050が6軸(ジャイロ・加速度)だったのに対し, MPU9250は9軸(6軸+地磁気)となったこと,そして通信方式がSPIとI2Cの両対応になったことだろう. 1個あたり400円しない額で,9軸のデータが取れるのは非常に便利である.

また倒立振子やドローンのような,センサに応答速度が求められる分野に関して,SPIはかなり便利である.
(一般にI2Cは100k~400kbps前後なのに対して,SPIは1~10Mbps程度の通信速度が出せる)

MPU9250をSPIで使ってみよう(加速度・ジャイロ編)

MPU9250の前身のMPU6050は,Arduino向けのセンサモジュールとしてそこそこの数が出荷されていた. そのため,Arduino上のフォーラムでは,かなりのユーザーがI2Cを利用したMPU6050のライブラリを公開している.
MPU6050の後継であるMPU9250も,同じ会社の同じ種類の製品のため,内部構造は酷似・・・というかほとんど同じである.
そのせいなのか,MPU9250に関するライブラリもI2Cを使用したものが多く,SPIを使ったものはあまり多くはないようだ.

とはいえ,SPIは基本的にI2Cと同じ要領で扱うことができるように設計されている.
レジスタのアドレスや書き込む/読み込むべきデータはまったく同じのようだ.
したがって,SPIを使用する場合は,レジスタへの書き込み方法と読み込み方法さえ書き換えればよい.
I2Cでは,

レジスタ書き込み

I2C

  • 書き込みモードでスレーブアドレス送信
  • レジスタアドレス送信
  • 書き込むデータ送信

SPI

  • レジスタアドレス送信
  • 書き込むデータを送信

レジスタ読み込み

I2C

  • 書き込みモードでスレーブアドレス送信
  • レジスタアドレス送信
  • 読み込みモードでスレーブアドレス送信
  • データ読み込み

SPI

  • レジスタアドレスを受信モード(最上位ビットを1に)送信
  • データを読み込み(空データ送信)

という形になる.むしろシンプルになってわかりやすいが 読み込み時にレジスタアドレスの最上位ビットを1にすることを忘れないようにしなければならない.

MPU9250をSPIで使ってみよう(地磁気編)

次は地磁気なのだが,これはかなり厄介である.
なぜかというと,MPU9250は内部的に
6軸センサ+3軸地磁気センサという構成になっているからだ.
というか,地磁気センサは旭化成のAK8963がそのまま入っているようである.
要するに,I2Cバスでつながっている2つのデバイス
無理やり1つのチップに収めたと言うことのようだ.
その証拠に,I2Cで通信する場合6軸センサと地磁気センサでは,
スレーブアドレスもレジスタアドレスもまったく異なっており,
地磁気センサのそれはAK8963とまったく同じようだ.

では,これをSPIで読むにはどうすればよいのだろう?
SPIも基本的にはバス接続が可能
(ただしチップセレクトだけはデバイスの分だけ用意する必要がある),
であればチップのどこかに地磁気センサ用のチップセレクトがあるのか?
しかし,上のデバイスには該当するようなピンはない.

6軸センサと地磁気センサはお互いにI2Cでつながっているが, SPIは6軸センサにしかつながっていない.
(ただし,両者が同じチップ内に存在するので,
外から見ると「どちらのセンサにもつながっている」ようにしか見えない)

こうなれば答えは一つ,
SPIを通じて6軸センサを操り,6軸センサのI2C機能を利用して地磁気センサから情報を得る
であろう.

実は,MPU9250のレジスタマップには

  • I2C_SLVx_ADDR
  • I2C_SLVx_REG
  • I2C_SLVx_CTRL
  • I2C_SLVx_DO

という4つのレジスタがある(ただしx=0~4である) これは,6軸センサをI2Cのマスタとして,

  • スレーブデバイスI2C_SLVx_ADDR
  • スレーブのレジスタアドレス:I2C_SLVx_REG
  • レジスタに書き込むデータ:I2C_SLVx_DO
  • 送信許可:I2C_SLVx_CTRL

とするI2C通信を行うためのレジスタである.
SPIを使ってI2C通信するというなんか奇妙なシステムになっているが,この際仕方ないだろう.
また,この機能(SPI経由でI2Cを間接的に使用する機能)をコントロールするためのレジスタとして USER_CTRLI2C_MST_CTRLの2つのレジスタの設定が必要になるようだ.

また,この時スレーブデバイスのアドレスを指定するレジスタI2C_SLVx_ADDRの最上位ビットを1にすると 読み込みモードでの通信になる.このあたりはI2Cのアドレス指定とは逆になるので注意. (I2Cではスレーブアドレスを1ビット左シフトして,最下位ビットを1にする.) さらに,読み込み/書き込みバイト数はI2C_SLVx_CTRLの下位4ビットで指定できる. (もっとも,書き込みの場合I2C_SLVx_DOが8bit幅なので最大1バイトしかできないが) この際,受信したデータはEXT_SENS_DATA_00

AK8963で地磁気を読み込むには?

さて,MPU9250のSPI→I2Cブリッジ通信機能を使えば,
内臓地磁気センサであるAK8963にアクセスすることが可能であることがわかった.
では,実際にはどのレジスタに何を書き込めばよいのか?
AK8963のデータシートを見るとこのあたりは結構楽そうである.
手順は以下の通りである.

  1. CNTL2に0x01を書き込んでデバイスをリセットする(たぶん不要な手順だが安定のため.)
  2. CNTL1に動作モードを書き込んでセンサを起こす
  3. 計測値がHXL以下6バイト分に格納されるので読み込む
  4. 手順2において,動作モードが連続測定モードの場合,ST2レジスタを読み捨てる
    (こうしないと次の計測値が入ってこない)

これで良い様だ.レジスタマップ上ではHXL以下6バイト分の計測データと,
ST2レジスタが連続しているため,手順3において7バイト読めばよいようだ.
(最後の1バイトはST2なので読み捨て)

まとめ

そんなわけで,MPU9250の地磁気センサをSPIを使って読む場合の手順は以下の通り.
(連続計測モード100Hzを想定)

初期化関数

  • USER_CTRLI2C_MST_CTRLの2つのレジスタを設定して,
    SPI経由のI2C通信を有効にする
    USER_CTRL:0x30(機能を有効化)
    USER_CTRL:0x0D(I2Cクロックを400kHzに設定)
  • I2C_SLVx以下を設定して地磁気センサをリセット
    I2C_SLVx_ADDR:0x0C(AK8963のアドレス)
    I2C_SLVx_REG:0x0B(CNTL2) I2C_SLVx_DO:0x01(リセット有効)
    I2C_SLVx_CTRL:0x81(1バイトのデータ送信)
  • I2C_SLVx以下を設定して地磁気センサを連続送信モードに
    I2C_SLVx_ADDR:0x0C(AK8963のアドレス)
    I2C_SLVx_REG:0x0A(CNTL1) I2C_SLVx_DO:0x16(連続計測モード100Hz)
    I2C_SLVx_CTRL:0x81(1バイトのデータ送信)

計測関数

  • I2C_SLVx以下を設定して地磁気センサをリセット
    I2C_SLVx_ADDR:0x8C(AK8963のアドレス.受信なので最上位ビットが1になっている)
    I2C_SLVx_REG:0x0B(HXL) I2C_SLVx_CTRL:0x87(7バイトのデータ受信)
  • EXT_SENS_DATA_00から7バイト分受信
    (先頭6バイトが地磁気データ)

※受信したデータは下位8ビットが先行しているので,16ビットデータに直す際は注意が必要
※当然だが地磁気は100Hzの計測周期なので,
毎回地磁気を取得しているとせっかくSPIが高速なのにその利点を生かせない

それでは,また.