STM32 + HALのSPIでMPU-6500と通信する 

2020年6月11日

STM32マイコンのペリフェラル関連記事を一覧にまとめました。

こんにちは。

STM32F405RGT(STMicroelectronics)のHALドライバを使用して、MPU6500(TDK Invensense)とSPI通信1を使用して通信をするプログラムの作成までを書いていきたいと思います。

データシートは一部抜粋で載せていくので、細かいところは各自データシートに目を通しながら進めていただければと思います。

 




通信について確認する

SPI通信で通信することは決まっていますが、通信規格や通信読度の設定がわからないので

プログラム・マイコンの初期設定をする前に、MPU6500のデータシートを読んでいきたいと思います。

まずは、SPI通信の書き込み、読み込みのできる最高速度、MSB2,LSB3のどちらからデータが返ってくるかということを見ていきたいと思います。

MPU-6500Product SpecificationRevision 1.1より

MPU-6500Product SpecificationRevision 1.1よりMPU-6500Product SpecificationRevision 1.1より

データシートから読み取れたことは、

  • 書き込み、読み込みの速度の最大は1±10%MHz
  • 読み込み専用の時は20±10%MHzまで可能ということがわかりました。
  • データはMSB FirstでLSB last
  • 一回の通信が16クロックのサイクル。
  • アドレスを送った後にデータを送る(読み取る)ことができる4
  • アドレスフォートマットの最上位ビットが1/0でREAD/WRITEが設定できる。
  • 通信をするときはcsPinをLowにする。

これで、マイコンの設定の仕方がわかりました。

 

STM32CubeMxの設定

STM32CubeMxを開いて、使用するマイコン、ボードを検索してプロジェクトを作成しましょう。冒頭で述べた通り、今回のサンプルはstm32f405rgt + makefileでやっていきます。

SPI通信と、ターミナルに文字列の表示を行いたいのでSPIとUSARTを有効、SPI通信のCSピン用に1ポートGPIO OUTPUTに設定し、ラベルをgyro_csとしました。

STM32CubeMx SPI通信の設定

20197/24日 追記: MPU6500では CPHL : High, CPHA : 2 Edgeに設定をする必要があります。データシートを読んでいたらわかりました。

STM32CuveMx USARTの設定

Project Managerの設定で、それぞれのペリフェラルの.c,.hファイルを生成するというところのチェックボックスにチェックを入れてください。そうすることでペリフェラルごとにファイルが分かれるのでプログラムが見やすくなると思います。

Generateボタンを押してファイルを生成してもらいましょう。

 

Visual studio code を実行して、makefile,Inc,Srcなどのディレクトリに移動します。

ファイル→フォルダを開くから移動が可能です。

“Ctr + Shift + p"でターミナルを開き、bashでmakeと実行するとコンパイルができると思います。

 

通信の内容を確認する

今回は、ジャイロのz軸を2000deg/secで読むプログラムを書きたいと考えています。

それを踏まえて、データを読むまでの流れは次のようになりました。

  1. WHO_AM_Iを読み取る。
  2. 1.でちゃんとデータが読めているか確認する
  3. パワーをオンにする
  4. mpu6500のコンフィグを設定する
  5. ジャイロの取得スケールの設定をする
  6. 値を取得する

しかし、MPU6500のどのアドレスに対して書き込みや読み込みをすればいいのかわからないのでデータシートを見ていきたいと思います。

前回同様最低限必要だと思ったところのMPU6500のレジスタマップが書いてあるデータシートを抜粋して見ていきます。説明がないところはデータシート各自読んでください。

1. WHO_AM_I : 0x75 に対して、read_byteする。

2. 読み取った値が0x70ならOK

3. PWR_MGMT_1 : 0x6Bにwrite_byteする。

MPU-6500Register Map and DescriptionsRevision 2.1より

データシートより、0x00を設定すれば、20Mhzで動作をするようになるので、0x00を設定しました。

4. CONFIG : 0x1Aにwrite_byteする。

MPU-6500Register Map and DescriptionsRevision 2.1より

私がチェックする回路ではFSYNC pinはNCなので、これらの機能を使用しません。

そのため、0x00を書き込みます5

5. GYRO_CONFIG : 0x1Bにwrite_byteする

MPU-6500Register Map and DescriptionsRevision 2.1より

2000dpsで取得するために、0x18を書き込みました。

6. z軸のデータは16bitで上位ビットと下位ビットに分かれているので、アクセスをしなければならないアドレスは2つあります。

    GYRO_ZOUT_H : 0x47 , GYRO_ZOUT_H : 0x48
上位ビットを8bitシフトをして、下位ビットと結合すれば値が取れそうだということがわかりました。
また、dpsでは物理量ではないため、deg/secに変換する必要があります。

データシートより、2000dpsのときは16.4で1deg/secであることがわかりました。したがって、取得した値を16.4で割ってあげればdeg/secに変換できるということが読み取れます。

ソースコードを実装

SPI通信のread,write関数を実装する

通信の条件は、

  • 8bitずつでアドレスを送ったのちに書き込みデータまたは、データ読み込みをすればいい。
  • 書き込みの場合はアドレスの最上位ビットを0。読み込みの場合は最上位ビットを1にする。
  • 通信するときはcsピンをLowにする。

ということがわかっているのでこれをもとに実装していきます。

20197/24日 追記: read,write関数の変更。readByte,WriteByteどちらかの初回動作時に正常に動かないことがある。そのため、起動時にwho_am_iをダミーリードするとよさそう。2回目以降はミスがありませんでした。

uint8_t readByte(uint8_t reg)
{
  uint8_t rx_data[2];
  uint8_t tx_data[2];

  tx_data[0] = reg | 0x80;
  tx_data[1] = 0x00;  // dummy

  HAL_GPIO_WritePin(gyro_cs_GPIO_Port, gyro_cs_Pin, RESET);
  HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, 2, 1);
  HAL_GPIO_WritePin(gyro_cs_GPIO_Port, gyro_cs_Pin, SET);

  return rx_data[1];
}

void writeByte(uint8_t reg, uint8_t data)
{
  uint8_t rx_data[2];
  uint8_t tx_data[2];

  tx_data[0] = reg & 0x7F;
  tx_data[1] = data;  // write data

  HAL_GPIO_WritePin(gyro_cs_GPIO_Port, gyro_cs_Pin, RESET);
  HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, 2, 1);
  HAL_GPIO_WritePin(gyro_cs_GPIO_Port, gyro_cs_Pin, SET);
}

 

MPU-6500の読み込み書き込みのプログラム

通信内容を確認するを踏まえて実装すると次のようになりました。

/*
 * @breif initialize mpu 6500
*/

void mpu6500_init( void )
{
  uint8_t who_am_i;
  
  HAL_Delay( 100 ); // wait start up
  who_am_i = read_byte( WHO_AM_I ); // 1. read who am i 
  printf( "rn0x%xrn",who_am_i ); // 2. check who am i value
  // 初回に失敗するときがあるので、もう一度動かしてみる
  HAL_Delay( 10 )
  who_am_i = read_byte( WHO_AM_I ); // 1. read who am i 
  printf( "rn0x%xrn",who_am_i ); // 2. check who am i value

  // 2. error check
  if ( who_am_i != 0x70 ){
    while(1){
      printf( "gyro_errorr");
    }
  }

  HAL_Delay( 50 ); // wait

  write_byte( PWR_MGMT_1, 0x00 ); // 3. set pwr_might

  HAL_Delay( 50 );

  write_byte( MPU6500_RA_CONFIG, 0x00 ); // 4. set config

  HAL_Delay( 50 );

  write_byte( GYRO_CONFIG, 0x18 ); // 5. set gyro config
  
  HAL_Delay( 50 );

}

/*
 * @breif read z axis
 * @return float gyro_z deg/sec
*/
float mpu6500_read_gyro_z( void )
{
  int16_t gyro_z;
  float omega;

  // H:8bit shift, Link h and l
  gyro_z = (int16_t)( (int16_t)(read_byte(GYRO_ZOUT_H) << 8 ) | read_byte(GYRO_ZOUT_L) );

  omega = (float)( gyro_z / GYRO_FACTOR ); // dps to deg/sec

  return omega;
}

これで、値の取得ができるようになりました。

プログラム全体

今回実装したプログラムをまとめたものになります。
main.c追記データ

#include <stdio.h>
// __io_putcharとか
// 省略
int main()
{
// initは省略

/* USER CODE BEGIN 2 */
mpu6500_init();
setbuf( stdout, NULL );
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
printf( "gyro_z = %f \r",mpu6500_read_gyro_z() );
/* USER CODE BEGIN 3 */

}
/* USER CODE END 3 */

}

mpu6500.h

#ifndef __MPU6500_H
#define __MPU6500_H

#include <stdint.h>

uint8_t read_byte( uint8_t reg );

void write_byte( uint8_t reg, uint8_t val );

void mpu6500_init( void );

float mpu6500_read_gyro_z( void );

#endif /* __MPU6500_H */

mpu6500.c

#include "mpu6500.h"

#include "main.h"

#include "spi.h"

#define WHO_AM_I 0x75
#define PWR_MGMT_1 0x6B
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B


/*
 @brief spi : read 1 byte
 @param uint8_t Register
 @return read 1byte data
*/
uint8_t read_byte( uint8_t reg )
{
  uint8_t rx_data[2];
  uint8_t tx_data[2];

  tx_data[0] = reg | 0x80;
  tx_data[1] = 0x00;  // dummy

  HAL_GPIO_WritePin(gyro_cs_GPIO_Port, gyro_cs_Pin, RESET);
  HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, 2, 1);
  HAL_GPIO_WritePin(gyro_cs_GPIO_Port, gyro_cs_Pin, SET);

  return rx_data[1];
}

/*
 @brief spi : write 1 byte
 @param uint8_t Register
 @param uint8_t Write Data
*/
void write_byte( uint8_t reg, uint8_t val )
{
  uint8_t rx_data[2];
  uint8_t tx_data[2];

  tx_data[0] = reg & 0x7F;
  tx_data[1] = data;  // write data

  HAL_GPIO_WritePin(gyro_cs_GPIO_Port, gyro_cs_Pin, RESET);
  HAL_SPI_TransmitReceive(&hspi1, tx_data, rx_data, 2, 1);
  HAL_GPIO_WritePin(gyro_cs_GPIO_Port, gyro_cs_Pin, SET);
}

/*
 * @breif initialize mpu 6500
*/

void mpu6500_init( void )
{
  uint8_t who_am_i;
  
  HAL_Delay( 100 ); // wait start up
  who_am_i = read_byte( WHO_AM_I ); // 1. read who am i 
  printf( "\r\n0x%x\r\n",who_am_i ); // 2. check who am i value

  // 2. error check
  if ( who_am_i != 0x70 ){
    while(1){
      printf( "gyro_error\r");
    }
  }

  HAL_Delay( 50 ); // wait

  write_byte( PWR_MGMT_1, 0x00 ); // 3. set pwr_might

  HAL_Delay( 50 );

  write_byte( MPU6500_RA_CONFIG, 0x00 ); // 4. set config

  HAL_Delay( 50 );

  write_byte( GYRO_CONFIG, 0x18 ); // 5. set gyro config
  
  HAL_Delay( 50 );

}

/*
 * @breif read z axis
 * @return float gyro_z deg/sec
*/
float mpu6500_read_gyro_z( void )
{
  int16_t gyro_z;
  float omega;

  // H:8bit shift, Link h and l
  gyro_z = (int16_t)( (int16_t)(read_byte(GYRO_ZOUT_H) << 8 ) | read_byte(GYRO_ZOUT_L) );

  omega = (float)( gyro_z / GYRO_FACTOR ); // dps to deg/sec

  return omega;
}

さいごに

mpu6500は色々と設定できるようなのでデータシートを読んでやってみてください。

マイクロマウス競技では、センサの値を一定周期で取得、読み取った値の初期のオフセットがあるのが嫌なので、ソフトでできるだけオフセットがなくなるように対策をしています。

また、加速度を取得して衝突検知などもしています。

是非考えてみてください。

知見のある方で何かありましたらご指摘をしていただきたいです。

 

参考文献

UM1725 User Manual

MPU-6500Product SpecificationRevision 1.1

MPU-6500Register Map and DescriptionsRevision 2.1

 

  1. Serial Peripheral Interface : 同期式シリアル通信のひとつ
  2. Most Significant Bit : 最上位ビット
  3. least Significant Bit : 最下位ビット
  4. マイコン側からみたとき
  5. 初期数値が0x00ですが念のため設定をしています。