STM32 + HALのSPIでMPU-6500と通信する
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としました。
20197/24日 追記: MPU6500では CPHL : High, CPHA : 2 Edgeに設定をする必要があります。データシートを読んでいたらわかりました。
Project Managerの設定で、それぞれのペリフェラルの.c,.hファイルを生成するというところのチェックボックスにチェックを入れてください。そうすることでペリフェラルごとにファイルが分かれるのでプログラムが見やすくなると思います。
Generateボタンを押してファイルを生成してもらいましょう。
Visual studio code を実行して、makefile,Inc,Srcなどのディレクトリに移動します。
ファイル→フォルダを開くから移動が可能です。
“Ctr + Shift + p"でターミナルを開き、bashでmakeと実行するとコンパイルができると思います。
通信の内容を確認する
今回は、ジャイロのz軸を2000deg/secで読むプログラムを書きたいと考えています。
それを踏まえて、データを読むまでの流れは次のようになりました。
- WHO_AM_Iを読み取る。
- 1.でちゃんとデータが読めているか確認する
- パワーをオンにする
- mpu6500のコンフィグを設定する
- ジャイロの取得スケールの設定をする
- 値を取得する
しかし、MPU6500のどのアドレスに対して書き込みや読み込みをすればいいのかわからないのでデータシートを見ていきたいと思います。
前回同様最低限必要だと思ったところのMPU6500のレジスタマップが書いてあるデータシートを抜粋して見ていきます。説明がないところはデータシート各自読んでください。
1. WHO_AM_I : 0x75 に対して、read_byteする。
2. 読み取った値が0x70ならOK
3. PWR_MGMT_1 : 0x6Bにwrite_byteする。
データシートより、0x00を設定すれば、20Mhzで動作をするようになるので、0x00を設定しました。
4. CONFIG : 0x1Aにwrite_byteする。
私がチェックする回路ではFSYNC pinはNCなので、これらの機能を使用しません。
そのため、0x00を書き込みます5。
5. GYRO_CONFIG : 0x1Bにwrite_byteする
2000dpsで取得するために、0x18を書き込みました。
6. z軸のデータは16bitで上位ビットと下位ビットに分かれているので、アクセスをしなければならないアドレスは2つあります。
データシートより、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は色々と設定できるようなのでデータシートを読んでやってみてください。
マイクロマウス競技では、センサの値を一定周期で取得、読み取った値の初期のオフセットがあるのが嫌なので、ソフトでできるだけオフセットがなくなるように対策をしています。
また、加速度を取得して衝突検知などもしています。
是非考えてみてください。
知見のある方で何かありましたらご指摘をしていただきたいです。
参考文献
MPU-6500Product SpecificationRevision 1.1
MPU-6500Register Map and DescriptionsRevision 2.1