IMU383を使う

こんなブログにでもコンスタントにアクセスを頂いています。主にElecrow発注記事が参照されているようで、何を隠そう私自身も活用しています。毎週発注するわけでもないですから、忘れてしまうのは仕方がないことですね。

 

最近は半導体不足もあって、あまり工作が進んでいません。

最近のトピックとして、Aceinnaという会社のIMUであるIMU383を入手しました。

www.aceinna.com

Homebrew界?で有名なIMUといえばIMU6000やその後継機種になるかと思いますが、こちらはもう少しハイエンドよりです。ジャイロや加速度計の詳細なスペックの読み方は優れた記事が国内外にありますので割愛します(語るだけの知識がありません…)が、mouserで個人が1個から購入できる製品は他に見当たらないので、導入してみました。このセンサについて書かれた日本語記事(英語記事すらも)はほぼ存在しないようでしたので、せっかくだから備忘録を残そうと思ったのが今回のモチベーションです。

※本ブログの内容を実践して損害が生じても補償はできません。必ずご自身の手で設計検証を行ってください。

 

■本記事のスコープ

SPI通信のポーリングでセンサからデータを取得する。初めて使うセンサなので、Who am I(0x57)の取得を目指す。IMU383と書いてますが、データシートを読む限り381についても同じ方法で行けると思います。

 

■HW編

今回はセンサの評価という側面が大きいので、まずは手持ちのマイコンボード(STM32F7 Nucleo)にドッキングできるよう治具を起こしました。将来的にはマイコンボードとセットでHWを起こす予定です。(要望があれば治具も共有します)

センサ側のコネクタが少し特殊で、メス側のコネクタを用意するのに少し苦労しました。SAMTECのCLM-110-02-L-D-P-TRという型番のものですが、Mouserで1個600円以上します。代替品を探してみたものの、ちょうどよい製品は存在しないようです。

接続は次のようにしました。初期化をしっかり行う必要があるので、8pinは必ずGPIOにつなぎます。7pinについてはGNDに落とすとUSART接続になりますが、今回はSPIで使用するためPB12につないでマイコン側でpull-upしています。

f:id:Mami_pero_pero:20211008235247p:plain

 

■SW編(初期設定編)

SW開発にはCubeIDEを使っています。mbedのクラウド環境なども使えそうですが、Cubeにもボード別のテンプレートが用意されているので活用しましょう。以下ポイントだけ列挙します。

 

<System Clockの設定>

割とどうでもいいと思いますが、SPI1に供給されるクロックを覚えておきます。

 

<SPIの設定>

今回はSPI1を使いました。Full-duplex Masterで、NSSはSoftware制御にします。

Data Sizeは8bit、MSB Firstです。

Prescalerはデータシート指示の通り1000kB/sとなるよう設定します。これはSystem Clockで設定したSPIクロックをスケーラで除算した値になります。

極性はデータシートによればクロックのアイドル時はHigh、立ち上がりエッジでのサンプリングです。CPOL=High、CPHA=2 Edgeを選択します。

 

<GPIOの設定>

PA4: NSSとして割当。GPIO Outputに設定。Output LevelはHigh。

PB12: DRとして割当。SPIを使用するのでGPIO Inputに設定。pull-upを指定。

PB13: nRSTとして割当。GPIO Outputに設定。Output LevelはHigh。

 

なお、GPIOの項目からAlternative機能を割り当てたピンの挙動を設定することができます。

今回はSPIですが、これらのピンのMax output speedをLowに指定しました。

理由は以下の記事を発見したためです。

blog.boochow.com

実は今回は結構ハマってしまって、オシロ/ロジアナで通信を眺めていて同じような事象に遭遇したためです。このエラッタを踏んでいるのかはきちんと検証できていないのですが、おまじない程度のつもりで設定しています。

 

■SW編(通信編)

実際に通信してゆきます。

 

<初期化>

意外と?初期化ロジックは繊細なようです。大体データシートに書かれている通りで、Delayなどは若干コンサバに書きました。

HAL_Delay(100);

HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);

HAL_Delay(100);

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); // RESET

HAL_Delay(900);

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); // 

HAL_Delay(500);

 

__HAL_SPI_ENABLE(&hspi1) 

最後の一行はおまじないで、なくても動くと思います。何をしているかの説明は不要ですね。概ねデータシート通りリセットをしています。注意点として、MX_SPI1_Init()を実行したあとに書くようにしてください。私はUSER CODE BEGIN 2という位置に書いています。

 

<byte読み込み>

実際にデータを読んでいきましょう。

void read_single(uint8_t reg, uint8_t* buf){

    uint8_t send[2] = {reg, 0x00};

    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // select device

    HAL_Delay(1);

    while(!(SPI1->SR & SPI_SR_TXE));

    HAL_SPI_TransmitReceive(&hspi1, send, buf, 2, HAL_MAX_DELAY);

    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);  // 

    HAL_Delay(1);

}

NSSを自分で制御しているので、頭とお尻にそのための処理があります。

Whileループはなぜかベアメタル風ですが、以前書いたものを引っ張ってきただけで深い意味はありません。送信バッファに何も残っていないことを確認しています。

データシートを読むと、データは16bit単位?でやり取りし、一度送受信するたびにNSSを上げ下げする必要があります。つまり、0x57から値を読み出そうとすると、

(1)読み出しアドレス{0x57, 0x00}を送信

(2)ダミーデータ{0x00, 0x00}を送信&データを受信

という2回の送受信が発生し、この間はいちいちNSSを操作します。SPIは全二重通信ですから、なにか送ればなにか返ってきます。一回目の送信でもゴミデータが返ってくる可能性はありますが、これは捨ててしまっていいでしょう。(2)が本命です。

というわけで次のように書きます。main関数の無限ループの中に書きました。

uint8_t buf[2];

read_single(0x57, buf);

read_single(0x00, buf);

buf[1]の値を見てみて、0x38が返ってくれば成功です!

お疲れさまでした。