- RXマイコンのソフト開発(1)クロック設定とLチカ
- RXマイコンのソフト開発(2)スイッチのチャタリング対策
- RXマイコンのソフト開発(3)スピーカーから音を出す
- RXマイコンのソフト開発(4)UART送信でHelloWorld
- RXマイコンのソフト開発(5)ADCでバッテリーの電圧測定
- RXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得
- RXマイコンのソフト開発(7)モータドライバーDRV8836の動作
- RXマイコンのソフト開発(8)磁気式エンコーダーAS5047
RXマイコン(RX631)のソフト開発の6回目です。今回は、RSPIを使ってジャイロ(MPU6000)から角速度を取得して、計算した角度をTeraTermで確認したいと思います。情報が少なかったので、実装するのに苦労しました。ユーザーズマニュアルや本、他の人のブログ、MPU6000のデータシートを参考に実装を行いました。
以下の本を少しだけ参考にしました。ただ、基本的にはユーザーズマニュアルや他の人のブログを参考にしています。
環境
- パソコン: Windows10 64 bit
- ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)
ジャイロの回路
作成したMPU6000の周辺回路図は図1です。
各ネットの RX631 への接続先は以下です。
- MOSIA → PC6/MOSIA
- RSPCKA → PC5/RSPCKA
- CS2 → P16
- MISOA → PC7/MISOA
MPU6000とはSPIとI2Cの通信方法でデータをやり取りできますが、作成した回路はSPIで通信する回路となっています。また、RX631の端子機能であるSSLAxを使わず、汎用入出力でCSを制御しています。
MPU6000の読み書き方法
MPU6000のデータシートを読んでも、読み書きの仕方がいまいちわからなくて苦労しました。そのため、読み書きの仕方を説明します。
1バイトのデータを読む
1バイトのデータを読むときは図2のようにデータをやり取りします。
CS2を”L”にして、最初に送信する1ビットを"1"とすることで、読み込み動作となります。読み込むレジスタは次に送信する7ビットのアドレス [A6]-[A0] で指定できます。アドレス送信後、 指定したアドレスのデータが MPU6000 から送信されますので、これを受信することで1バイト読むことができます。
アドレス送信後に送信するデータ [D7]-[D0] はダミーデータで、RSPCKAを動作させるためのデータです。RSPIの仕様上必要となります。ダミーデータの値はなんでもいいですが、私は 0x00 を送信しています。
1バイトのデータを書く
1バイトのデータを書くときは図3のようにデータを送信します。
CS2を”L”にして、最初に送信する1ビットを"0"とすることで、書き込み動作となります。書き込むレジスタは次に送信する7ビットのアドレス [A6]-[A0] で指定できます。アドレス送信後、 書き込むデータ [D7]-[D0] を送信することで、指定したアドレスのレジスタにその値が書き込まれます。
2バイトのデータを読む
角速度のデータは2バイトの符号付整数のため、2バイトを一気に読み込む必要があります。1バイトずつ読み込んだ場合、上位8ビットと下位8ビットが異なる時間の値となってしまいます。
2バイトのデータを読むときは図4のようにデータをやり取りします。
1バイトのデータを読み込んだあとに、再びダミーデータを送信することで、指定したアドレスの次のアドレスのデータがMPU6000から送信されます。
3バイト以上を読むときも、ダミーデータを何度も送信することでさらに次のアドレスのデータが送信されて、読むことができます。
注意:SPIを使用するデバイスがMPU6000だけの場合、CSをずっと”L”にしておけばいいと思っていましたが、上記のバースト転送の仕様のため、SPI送信後にCSを"H"にしないといけないみたいです。ずっと”L”の場合、時間を空けたとしても、前回受信した次のアドレスのデータがMPU6000から送信されてしまいます。CSを”H”から"L"にして送信した最初のデータがR/Wとアドレスとなるようです。これに気づかなくて、最初に読みだした1バイトだけ正しい現象が起きて、数時間悩みました。
プログラムフロー
プログラムの処理の流れは図5です。
クロックの初期化とCMT0、SCI1の初期化を行って、RSPIとMPU6000を初期化します。その後、メインルーチンに入り、1ms 経過するたびに角速度をMPU6000から取得して、足し合わせていきます。また、100ms経過するたびに足し合わせた値を角度(deg)に変換して、UART 送信します。
プログラム
MPU6000に関係するプログラム部分だけ示します。sci.c と sci.h を除いた全てのソースコードはGitHubの「6_mpu6000」フォルダの中にあります。
メイン関数
main.c:メイン関数のソースコード
#include "sci.h" #include "spi.h" #include "init_rx631.h" #include "define_wakaba.h" extern volatile unsigned char g_flag; void main(void){ int angle_Z=0; u16 cnt=0; int send_data; short rd_data; init_rx631(); // Overall Initialization while(1){ if(g_flag){ g_flag = 0; // Clear flag // Read angular velocity rd_data = SPI_ReadMPU6000(MPU6000_GYRO_ZOUT_H); angle_Z += rd_data; cnt++; if(cnt==100){ cnt=0; send_data = angle_Z / 16384; // (angle_Z*2000) / (32768*1000) sci_printf("angle=%l\r\n", send_data); } } } }
メイン関数では、RX631の初期化を行い、メインルーチンに入ります。CMT0の割り込み関数によって1ms 経過フラグ(g_flag)が立ったら、フラグを下げて、角速度を読み、いままでの値に足し合わせ、カウント(cnt)をインクリメントします。100msが経過(cnt==100)したら、カウントをクリアして、角度を計算して、その値をsci_printfでUART送信します。
角度(ANGLE)については、長方形近似による数値積分で以下のように求めています。
angle_Z は読み取った値の総和、FS はフルスケールの値に対応する角速度です。MPU6000から読み取るデータは2バイトの符号付整数なので、フルスケールの値は32768となります。また、1ms ごとにサンプリングしているため、1000で割っています。
MPU6000の初期化で、 としているため、約分してソースコードの計算式になります。
RSPI0の初期化
初期化関数のソースコードが以下です。RSPI に関してはユーザーズマニュアルの p.1619「38. シリアルペリフェラルインタフェース(RSPI)」に記述があります。
init_rx631.c:初期化関数のソースコード
#include "init_rx631.h" #include "iodefine.h" #include "spi.h" /*** Function Declaration ***/ static void init_clock(void); static void init_cmt0(void); static void init_sci1(void); static void init_rspi0(void); static void init_mpu6000(void); /*---- RX631 Initialization ----*/ void init_rx631(void){ SYSTEM.PRCR.WORD = 0xA503; // Unprotect // MainCLK, SUBCLK and RTC Initialization init_clock(); // CMT0 Initialization init_cmt0(); // SCI1 Initialization init_sci1(); // RSPI0 Initialization init_rspi0(); // MPU6000 Initialization init_mpu6000(); SYSTEM.PRCR.WORD = 0xA500; // Reprotect } ~略~ /*---- RSPI0 Initialization ----*/ static void init_rspi0(void){ MSTP(RSPI0) = 0; // RSPI0 Module Stop Release RSPI0.SPBR = 5; // 521kbps when BRDV=3 RSPI0.SPSCR.BIT.SPSLN = 0; // Sequence length = 1 RSPI0.SPDCR.BIT.SPFC = 0; // Frame Num = 1 RSPI0.SPDCR.BIT.SPLW = 1; // Long Word Access /* RSPI Command Register0 */ // CPHA = 1 : Data change at odd edges, data sample at even edges // CPOL = 1 : RSPCK at idle is High // BRDV = 3 : 8 divisions of the base bit rate // LSBF = 0 : MSB First // SPB = 15 : Data length = 16bit RSPI0.SPCMD0.WORD = 0x0F0F; MPC.PWPR.BIT.B0WI = 0; // PFSWE bit write enable MPC.PWPR.BIT.PFSWE = 1; // PmnPFS Register write enable MPC.PC7PFS.BIT.PSEL = 13; // Set to MISOA MPC.PC6PFS.BIT.PSEL = 13; // Set to MOSIA MPC.PC5PFS.BIT.PSEL = 13; // Set to RSPCKA MPC.PWPR.BYTE = 0x80; // Reprotect PORTC.PMR.BIT.B7 = 1; // set to Peripheral PORTC.PMR.BIT.B6 = 1; // set to Peripheral PORTC.PMR.BIT.B5 = 1; // set to Peripheral PORT1.PDR.BIT.B6 = 1; // CS2: OUT PORT1.PODR.BIT.B6 = 1; // CS2: HIGH RSPI0.SPCR.BIT.SPMS = 1; // Clock synchronous operation RSPI0.SPCR.BIT.MSTR = 1; // Master Mode } /*---- MPU6000 Initialization ----*/ static void init_mpu6000(void){ // Sleep mode Release SPI_WriteMPU6000(MPU6000_PWR_MGMT_1, 0x00); // Disable I2C interface SPI_WriteMPU6000(MPU6000_USER_CTRL, 0x10); // The full scale range of the gyroscope = +-2000 dps SPI_WriteMPU6000(MPU6000_GYRO_CONFIG, 0x18); }
RSPI0の初期化では、モジュールストップ状態の解除と端子機能の変更を行います。
その後、以下のRSPI0の設定を行います。
- RSPI0.SPBR=5 で、ベースビットレートを4.16Mbpsに設定
- RSPI0.SPSCR.BIT.SPSLN=0 で、シーケンス長を1に設定
- RSPI0.SPDCR.BIT.SPFC=0 で、フレーム数を1に設定
- RSPI0.SPDCR.BIT.SPLW=1 で、SPDR レジスタへのアクセス幅を32ビットに設定
- RSPI0.SPCMD0.WORD = 0x0F0F で、送信の設定をいろいろする
- RSPI0.SPCR.BIT.SPMS = 1 で、クロック同期式動作に設定
- RSPI0.SPCR.BIT.MSTR = 1 で、マスタモードに設定
シーケンス長を1にすることで、SPCMD0で設定した送信の方法でしか、送信しないようにします。SPIで送信するデバイスが複数ある場合は、デバイスの数だけシーケンス長を設定するようです。
SPCMD0を設定することで、送信の仕方を以下のように変更します。
- CPOLビットを”1”にして、アイドル時のRSPCKをHIGHにする
- CPHAビットを”1”にして、CPOL=1 のときRSPCKの立ち上がりでサンプリング(図2のような波形にするために、ユーザーズマニュアルのp.1660「38.3.5.2 CPHA ビット= 1 の場合」の図 38.24 にあるように、CPOL=1、CPHA=1とします)
- BRDVビットを3にして、ベースビットレートを8分周する(4.16Mbpsを8分周することで、512kbpsにします。MPU6000の絶対定格ではSCLKの周波数が1MHzなので、1MHz以下になるように512kbpsにしています。)
- LSBFビットを”0”にして、MSBファーストで送信
- SPBビットを15にして、16ビット送信する(送信するたびに変更しますが、初期設定は16ビットで送信するようにします。)
SPCMD0の設定が終わった後は、SSLは使わないので、クロック同期式動作にします。また、マスタモードに設定することで、RXマイコンから送信開始するようにします。
以上でRSPI0の設定は終わりです。
つづいて、MPU6000の初期化を行います。初期設定で以下のことをします。
- PWR_MGMT_1(アドレス107)のSLEEPビットを"0"にして、スリープモードを解除
- USER_CTRL(アドレス106)の I2C_IF_DIS ビットを"1"にして、I2Cを無効にしてSPIを有効にする(設定しなくてもSPI通信はできるので、設定する必要はないかもしれない)
- GYRO_CONFIG(アドレス27)の FS_SELビットを3にして、フルスケールの値に対応する角速度を2000dpsにする
SPIの送受信
SPIで読み書きするソースコードとヘッダーファイルが以下です。ソースコードは主にユーザーズマニュアルのp.1676「(a) 送信処理フロー」とp.1677「(b) 受信処理フロー」を参考に書きました。
spi.c:SPI 関係のソースコード
#include "define_wakaba.h" #include "spi.h" #include "sci.h" static u16 SPI_SendRecvMPU6000(u32 packet); /* Write to MPU6000 register */ void SPI_WriteMPU6000(u8 address, u8 data){ u16 packet; /* make packet */ packet = address << 8; packet |= data; /* Number of data to send = 16bit */ RSPI0.SPCMD0.BIT.SPB = LENGTH_16BIT; SPI_SendRecvMPU6000(packet); // Write } /* Read MPU6000 register */ u16 SPI_ReadMPU6000(u8 address){ u16 data; u32 packet; /* make packet */ packet = 0x800000; packet |= address << 16; /* Number of data to send = 24bit */ RSPI0.SPCMD0.BIT.SPB = LENGTH_24BIT; data = SPI_SendRecvMPU6000(packet); // Read return data; } /* Sending/receiving data to/from MPU6000 */ static u16 SPI_SendRecvMPU6000(u32 packet){ u16 data; CS_MPU6000 = ASSERT; // Assert MPU6000 CS RSPI0.SPCR.BIT.SPTIE = 1; // Enable transmission IRQ RSPI0.SPCR.BIT.SPE = 1; // Enable RSPI /* Wait for the send buffer to be empty */ while(IR(RSPI0, SPTI0)==0); IR(RSPI0, SPTI0)=0; // Clear Flag RSPI0.SPDR.LONG = packet; // Send data RSPI0.SPCR.BIT.SPTIE = 0; // Disable transmission IRQ RSPI0.SPCR2.BIT.SPIIE = 1; // Enable idle IRQ RSPI0.SPCR.BIT.SPRIE = 1; // Enable receive IRQ /* Wait for RSPI to idle */ while(IR(RSPI0, SPII0)==0); IR(RSPI0, SPII0)=0; // Clear Flag /* Wait for the receive buffer to be written */ while(IR(RSPI0, SPRI0)==0); IR(RSPI0, SPRI0)=0; // Clear Flag data = RSPI0.SPDR.LONG & 0xFFFF; RSPI0.SPCR2.BIT.SPIIE = 0; // Enable idle IRQ RSPI0.SPCR.BIT.SPRIE = 0; // Enable receive IRQ RSPI0.SPCR.BIT.SPE = 0; // Disable RSPI CS_MPU6000 = NEGATE; // Negate MPU6000 CS return data; }
spi.h:SPI 関係のヘッダーファイル
#ifndef SPI_H_ #define SPI_H_ #include "iodefine.h" #include "define_wakaba.h" #define MPU6000_GYRO_CONFIG 27 #define MPU6000_GYRO_ZOUT_H 71 #define MPU6000_GYRO_ZOUT_L 72 #define MPU6000_USER_CTRL 106 #define MPU6000_PWR_MGMT_1 107 #define MPU6000_WHO_AM_I 117 #define LENGTH_16BIT 15 #define LENGTH_24BIT 1 #define ASSERT 0 #define NEGATE 1 #define CS_MPU6000 PORT1.PODR.BIT.B6 void SPI_WriteMPU6000(u8 address, u8 data); u16 SPI_ReadMPU6000(u8 address); #endif /* SPI_H_ */
書き込み関数 SPI_WriteMPU6000 と読み込み関数 SPI_WriteMPU6000 はほとんど違いはなく、以下の手順でプログラムが実行されます。
- 送信するパケットの作成
- 送信するデータ数を設定
- SPIで送受信を行う
読み書きで異なる点は以下です。
- 送信するパケットのMSBが、読みでは"1"、書きでは"0"
- 読みではダミーデータを送信、書きでは書き込みデータを送信
- 送信データ数が、読みでは24ビット、書きでは16ビット
SPIの送受信関数SPI_SendRecvMPU6000では以下の手順でプログラムが実行されます。
- MPU6000のCSをアサート("L"にする)
- 送信割り込み要求の許可とRSPI機能を有効化する
- 送信バッファが空になったら、フラグを下げる
- 送受信バッファSPDRにパケットを書き、SPI送信する
- 送信割り込み要求を禁止して、アイドル割り込み要求と受信割り込み要求を許可する
- アイドル状態になったら、フラグを下げる
- 受信バッファに書き込まれたら、フラグを下げる
- 送受信バッファSPDRから、受信データを取り出す
- 全ての割り込み要求を禁止して、RSPI機能を無効化する
- MPU6000のCSをネゲート("H"にする)
プログラムの実行
プログラムを実行して、基板の角度をだいたい 度から 度に変えたところ、図6のようにデータを取得できました。
上手くできてそうです。
おわりに
今回でMPU6000のデータを取得できました。今回の記事は赤外線LEDと赤外線センサーについて書こうと思ったのですが、所望の値が得られなかったため記事が書けませんでした。ハードウェアの問題の可能性が高いので、もう一度基板を作りなおすかもしれません。次回はモータドライバー(DRV8836)の記事を書こうと思います。