SPI通信で搭載した6軸IMUのTDK InvenSense社ICM-20602と通信できるかテストしてみます。
参考サイトです。



回路図
マイコンとSPI接続する推奨回路をそのまま使っています。
STM32F446側は、SPI2が使えるポートに接続しています。
ここは初めて回路作成するときによく間違うところのようで、ししかわさんも、inukaiさんもやられており、UARTのTx、Rxの接続ミスと同じ位の頻度だと思われます。私も、青木さんのレビューで気づいてもらえなければ、やられてました。シンボルが略語になっているので、まずは意味を知りましょう。
- MOSIは「Master Out Slave In」の略
- MISOは「Master In Slave Out」の略
- SDIは「Serial Data In」の略
- SDOは「Serial Data Out」の略
ということは、シリアルデータはどういう流れでどうつなぐかと言うと、
- MOSI → SDI
- MISO ← SDO
となるわけです。
他のピンは、そのまま対応するピンに接続します。
- CSは「Chip Select」の略
- SCLKは「Serial CLocK」の略
STM32CubeMX
CubeMXの設定です。クロック設定とUSART設定は以前と同様。
SPIでは最初に、Pinout viewでそれぞれ回路接続しているピンにSPI2の割当をしていきます。ですが、CSのみGPIO_Outにしており、ハードウェア割当は行いません。
CSのLOWからSCLKまでの時間が3clk?と非常に短いので、対応できるスレーブICが少なく、だいたいはCSはソフトウェア制御にします。更に、STMのSPIのハードウェアCSはSPI1つにつき1つしか制御できないようで、複数のスレーブICを制御するにはソフトウェアにするしかないようです。マイコン側をスレーブ設定にしたいときに、ハードウェア割当を行うようです。ラベル名だけ回路図ラベルに合わせてSPI2_SSEL(Slave SELect)としておきます。
SPI Mode and Configurationの設定では、以下のようにします。
- Full-Duplex Master (受信と送信を同時に行えるモード)
- Hardware NSS Signalは、Disable (ソフト的にCSピンを使う)
つぎは、Parmeter Settingsになるわけですが、ここからはSPIで接続するICのデータシートを参照する必要があります。ICM-20602のデータシートの必要な部分を抜き出してみます。
画像引用:TDK InvenSense社ICM-20602データシート
翻訳すると、
「SPIは、2本の制御線と2本のデータ線を使用する4線式同期シリアルインターフェースです。ICM-20602は、標準のマスタースレーブSPI動作中は常にスレーブデバイスとして動作します。マスターに関しては、シリアルクロック出力(SPC)、シリアルデータ出力(SDO)、およびシリアルデータ入力(SDI)がスレーブデバイス間で共有されます。各SPIスレーブデバイスには、マスターからの独自のチップセレクト(CS)ラインが必要です。CSは、送信の開始時にロー(アクティブ)になり、終了時にハイ(非アクティブ)に戻ります。一度にアクティブになるCS回線は1つだけであり、常に1つのスレーブのみが選択されるようにします。選択されていないスレーブデバイスのCSラインはハイに保持され、SDOラインはハイインピーダンス(high-z)状態のままになり、アクティブなデバイスに干渉しません。
SPIの運用機能
1.データはMSBが最初に、LSBが最後に配信されます
2.データはSPCの立ち上がりエッジでラッチされます
3.データはSPCの立ち下がりエッジで遷移する必要があります
4.SPCの最大周波数は10MHzです
5. SPIの読み取りおよび書き込み操作は、16クロックサイクル以上(2バイト以上)で完了します。最初のバイトにはSPIアドレスが含まれ、次のバイトにはSPIデータが含まれます。最初のバイトの最初のビットには読み取り/書き込みビットが含まれ、読み取り(1)または書き込み(0)操作を示します。次の7ビットにはレジスタアドレスが含まれています。複数バイトの読み取り/書き込みの場合、データは2バイト以上です。」
ということで、First Bitは、MSB Firstのままに、Prescalerは8に設定して、Baud Rateが10Mbpsを超えないように(ここでは5.626 MBits/s)設定します。
CPOLはclockの初期レベルのことです。Clock Polarity(CPOL)をHighにすると、Lowになったときを1 Edeg、次にhighになった時を2Edegとカウントします。なので、立ち上がりエッジでラッチする場合は、Clock Phase(CPHA)を2にします。
GPIO output level (GPIOピンの初期状態) はHighにしておきます。CSピンはアイドル状態がHigh、選択状態がLowとなるので、鉄鼠はSPIを1つしか使っていないので、ここはしなくても良いのですが、他のセンサを増設して1つのSPIで複数のICとCSで切り替えて使う場合は設定が必要となります。
ICM-20602のアドレス&データ
SPI通信は、送り場所のアドレス指定と、データの送信、返ってくるデータの受信が必要です。各ICのデータシートを参照します。
IMUの回路が正常に動いているかどうかの確認のため、「WHO AM I」で双方向通信を行ってみます。まずは、こういったICは省エネモード等がデフォルトで設定されているので、それらの資料を読んでみます。
画像引用:TDK InvenSense社ICM-20602データシート
翻訳すると、
「以下のレジスタ以外のすべてのレジスタのリセット値は0x00です。また、セルフテストレジスタには事前にプログラムされた値が含まれており、リセット後は0x00にはなりません。
•レジスタ26(0x80)CONFIG
•レジスタ107(0x41)Power Management 1
•レジスタ117(0x12)WHO_AM_I」
CONFIGとPower Management 1、WHI_AM_Iのデータシートの記述を見てみます。
CONFIG
CONFIGの記述を見てみます。
画像引用:TDK InvenSense社ICM-20602データシート
アドレスは、十進法で表すと26、十六進法で表すと0x1A。
BIT[7]は、設定したい場合は0に切り替える必要がありそうです。設定ロック機能と思います。
BIT[6]は、FIFOをどうするか。
BIT[5:3]は、FSYNCピンデータのサンプリングを有効にするかどうか
BIT[2:0]は、フィルタの設定のようです。
WHO AM Iを取得するだけなら、初期設定のままで良さそう。
Power Management 1
次は、Power Management 1の記述を見てみます。
画像引用:TDK InvenSense社ICM-20602データシート
アドレスは、十進法で表すと107、十六進法で表すと0x68。
BIT[7]は、デフォルトにリセットしたい場合は、1に変えるとのこと。工場リセットかな?
BIT[6]は、SLEEPモードのようです。1でSLEEP、0で起動。
BIT[5]は、センサ値の循環設定のようです。
BIT[4]は、ジャイロの低電力モードのようです。
BIT[3]は、温度センサの有効/無効切り替えのようです。
BIT[2:0]は、クロックソースの切り替えです。
初期値は0x41(0b01000001)なので、予想通りスリープ状態のようです。ここは、0x01に書き換える必要があります。
WHO AM I
最後に、WHO AM Iの記述を見てみます。
画像引用:TDK InvenSense社ICM-20602データシート
アドレスは、十進法で表すと117、十六進法で表すと0x75。下の文を翻訳するとこんな感じ。
「このレジスタは、デバイスのIDを確認するために使用されます。WHOAMIの内容は8ビットのデバイスIDです。レジスタのデフォルト値は0x12です。これは、アプリケーションプロセッサによってスレーブI2Cコントローラに表示されるデバイスのI2Cアドレスとは異なります。ICM-20602のI2Cアドレスは、AD0ピンで駆動される値に応じて0x68または0x69です。」
SPI接続なので、0x12がデータとして返ってくるようです。
プログラム
STM32F446のプログラムに入ります。SPIの通信は、HAL_SPI_Transmit関数とHAL_SPI_Receive関数を使います。
データを送信する場合は、HAL_SPI_Transmit関数を使います。
画像引用: Description of STM32F4 HAL and low-layer drivers
- 第一引数は、SPIハンドラ
- 第二引数は、データバッファ
- 第三引数は、データバッファのサイズ(バイト)
- 第四引数は、タイムアウトの時間(ms)
データを受信する場合は、HAL_SPI_Receive()関数を使用します。
画像引用: Description of STM32F4 HAL and low-layer drivers
- 第一引数は、SPIハンドラ
- 第二引数は、データバッファ
- 第三引数は、データバッファのサイズ(バイト)
- 第四引数は、タイムアウトの時間(ms)
プログラムです。
/* USER CODE BEGIN 2 */ setbuf(stdout, NULL); setbuf(stdin, NULL ); printf("Started!\n\r"); uint8_t address = 0x00; uint8_t data = 0x00; // POWER MANAGEMENT 1 address = 0x6B & 0b01111111; // write data = 0x01; HAL_GPIO_WritePin(SPI2_SSEL_GPIO_Port, SPI2_SSEL_Pin, GPIO_PIN_RESET); // CS = 0 HAL_SPI_Transmit(&hspi2, &address, 1, 100); HAL_SPI_Transmit(&hspi2, &data, 1, 100); HAL_GPIO_WritePin(SPI2_SSEL_GPIO_Port, SPI2_SSEL_Pin, GPIO_PIN_SET); // CS = 1 HAL_Delay(50); // WHO AM I address = 0x75 | 0b10000000; // read HAL_GPIO_WritePin(SPI2_SSEL_GPIO_Port, SPI2_SSEL_Pin, GPIO_PIN_RESET); // CS = 0 HAL_SPI_Transmit(&hspi2, &address, 1, 100); HAL_SPI_Receive(&hspi2, &data, 1, 100); HAL_GPIO_WritePin(SPI2_SSEL_GPIO_Port, SPI2_SSEL_Pin, GPIO_PIN_SET); // CS = 1 HAL_Delay(50); printf("who am i = %x\n\r", data); /* USER CODE END 2 */
SPIの説明のところで「最初のバイトの最初のビットには読み取り/書き込みビットが含まれ、読み取り(1)または書き込み(0)操作を示します。次の7ビットにはレジスタアドレスが含まれています。」とあるので、送る1バイトアドレスデータのBIT[7]にあたるところを送信or受信で切り替えるため、&と|の論理演算子で書き換えます。
データ書き込みのときは、アドレスをTransmitしたあと、次はデータをTransmitします。データ読み込みのときは、アドレスをTransmitしたあと、データが来るのでReceiveします。
実行
シリアルターミナルで確認します。
0x12が上手く受信できました。回路は正常に動いているようです。次は、センサデータを取得します。