RXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得

RXマイコン(RX631)のソフト開発の6回目です。今回は、RSPIを使ってジャイロ(MPU6000)から角速度を取得して、計算した角度をTeraTermで確認したいと思います。情報が少なかったので、実装するのに苦労しました。ユーザーズマニュアルや本、他の人のブログ、MPU6000のデータシートを参考に実装を行いました。

以下の本を少しだけ参考にしました。ただ、基本的にはユーザーズマニュアルや他の人のブログを参考にしています。

環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)

ジャイロの回路

作成したMPU6000の周辺回路図は図1です。

図1:MPU6000の周辺回路図
図1:MPU6000の周辺回路図

各ネットの RX631 への接続先は以下です。

  • MOSIA → PC6/MOSIA
  • RSPCKA → PC5/RSPCKA
  • CS2 → P16
  • MISOA → PC7/MISOA

MPU6000とはSPIとI2Cの通信方法でデータをやり取りできますが、作成した回路はSPIで通信する回路となっています。また、RX631の端子機能であるSSLAxを使わず、汎用入出力でCSを制御しています。

MPU6000の読み書き方法

MPU6000のデータシートを読んでも、読み書きの仕方がいまいちわからなくて苦労しました。そのため、読み書きの仕方を説明します。

1バイトのデータを読む

1バイトのデータを読むときは図2のようにデータをやり取りします。

図2:1バイトの読み方
図2:1バイトの読み方

CS2を”L”にして、最初に送信する1ビットを"1"とすることで、読み込み動作となります。読み込むレジスタは次に送信する7ビットのアドレス [A6]-[A0] で指定できます。アドレス送信後、 指定したアドレスのデータが MPU6000 から送信されますので、これを受信することで1バイト読むことができます。

アドレス送信後に送信するデータ [D7]-[D0] はダミーデータで、RSPCKAを動作させるためのデータです。RSPIの仕様上必要となります。ダミーデータの値はなんでもいいですが、私は 0x00 を送信しています。

1バイトのデータを書く

1バイトのデータを書くときは図3のようにデータを送信します。

図3:1バイトの書き方
図3:1バイトの書き方

CS2を”L”にして、最初に送信する1ビットを"0"とすることで、書き込み動作となります。書き込むレジスタは次に送信する7ビットのアドレス [A6]-[A0] で指定できます。アドレス送信後、 書き込むデータ [D7]-[D0] を送信することで、指定したアドレスのレジスタにその値が書き込まれます。

2バイトのデータを読む

角速度のデータは2バイトの符号付整数のため、2バイトを一気に読み込む必要があります。1バイトずつ読み込んだ場合、上位8ビットと下位8ビットが異なる時間の値となってしまいます。

2バイトのデータを読むときは図4のようにデータをやり取りします。

図4:2バイトの読み方
図4:2バイトの読み方

1バイトのデータを読み込んだあとに、再びダミーデータを送信することで、指定したアドレスの次のアドレスのデータがMPU6000から送信されます。

3バイト以上を読むときも、ダミーデータを何度も送信することでさらに次のアドレスのデータが送信されて、読むことができます。

注意:SPIを使用するデバイスがMPU6000だけの場合、CSをずっと”L”にしておけばいいと思っていましたが、上記のバースト転送の仕様のため、SPI送信後にCSを"H"にしないといけないみたいです。ずっと”L”の場合、時間を空けたとしても、前回受信した次のアドレスのデータがMPU6000から送信されてしまいます。CSを”H”から"L"にして送信した最初のデータがR/Wとアドレスとなるようです。これに気づかなくて、最初に読みだした1バイトだけ正しい現象が起きて、数時間悩みました。

プログラムフロー

プログラムの処理の流れは図5です。

図5:プログラムのフローチャート
図5:プログラムのフローチャート

クロックの初期化とCMT0、SCI1の初期化を行って、RSPIとMPU6000を初期化します。その後、メインルーチンに入り、1ms 経過するたびに角速度をMPU6000から取得して、足し合わせていきます。また、100ms経過するたびに足し合わせた値を角度(deg)に変換して、UART 送信します。

プログラム

MPU6000に関係するプログラム部分だけ示します。sci.csci.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)については、長方形近似による数値積分で以下のように求めています。

\displaystyle{
ANGLE=\frac{angle\_Z \times {\rm FS} }{ 32768 \times  1000 } 
}

angle_Z は読み取った値の総和、FS はフルスケールの値に対応する角速度です。MPU6000から読み取るデータは2バイトの符号付整数なので、フルスケールの値は32768となります。また、1ms ごとにサンプリングしているため、1000で割っています。

MPU6000の初期化で、{\rm FS}=2000\ {\rm dps} としているため、約分してソースコードの計算式になります。

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 はほとんど違いはなく、以下の手順でプログラムが実行されます。

  1. 送信するパケットの作成
  2. 送信するデータ数を設定
  3. SPIで送受信を行う

読み書きで異なる点は以下です。

  • 送信するパケットのMSBが、読みでは"1"、書きでは"0"
  • 読みではダミーデータを送信、書きでは書き込みデータを送信
  • 送信データ数が、読みでは24ビット、書きでは16ビット

SPIの送受信関数SPI_SendRecvMPU6000では以下の手順でプログラムが実行されます。

  1. MPU6000のCSをアサート("L"にする)
  2. 送信割り込み要求の許可とRSPI機能を有効化する
  3. 送信バッファが空になったら、フラグを下げる
  4. 送受信バッファSPDRにパケットを書き、SPI送信する
  5. 送信割り込み要求を禁止して、アイドル割り込み要求と受信割り込み要求を許可する
  6. アイドル状態になったら、フラグを下げる
  7. 受信バッファに書き込まれたら、フラグを下げる
  8. 送受信バッファSPDRから、受信データを取り出す
  9. 全ての割り込み要求を禁止して、RSPI機能を無効化する
  10. MPU6000のCSをネゲート("H"にする)

プログラムの実行

プログラムを実行して、基板の角度をだいたい-45 度から45 度に変えたところ、図6のようにデータを取得できました。

図6:取得した角度データ
図6:取得した角度データ

上手くできてそうです。

おわりに

今回でMPU6000のデータを取得できました。今回の記事は赤外線LEDと赤外線センサーについて書こうと思ったのですが、所望の値が得られなかったため記事が書けませんでした。ハードウェアの問題の可能性が高いので、もう一度基板を作りなおすかもしれません。次回はモータドライバー(DRV8836)の記事を書こうと思います。

参考文献

お問い合わせフォーム プライバシーポリシー

© 2021 Setoti All rights reserved.