RXマイコンのソフト開発(8)磁気式エンコーダーAS5047

RXマイコン(RX631)のソフト開発の8回目です。今回は、磁気式エンコーダーのAS5047を動作させたいと思います。AS5047によって、タイヤの回転角度を求めます。

環境

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

AS5047の周辺回路

作成したAS5047の周辺回路図は図1です。xxxxxx_Hxxxxxx_HRはコネクタでつながっています。

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

今回使用するネットの RX631 への接続先は以下です。今回は右側のエンコーダーしか使いません。

  • CS1 → PB5
  • RSPCKA → PC5/RSPCKA
  • MOSIA → PC6/MOSIA
  • MISOA → PC7/MISOA

はじめ、SPI通信で初期設定をして、R_ENC_AR_ENC_BからA相とB相の矩形波を受け取ろうと思ったのですが、SPI通信だけでも角度情報を受け取れるようなので、R_ENC_AR_ENC_Bは使いません。

補足:初期状態でA相とB相の矩形波が出力されるように設定されている(オシロスコープで出力を確認)ので、SPIの通信線がなくても角度情報は受け取れるそうです。このため、レジスタ設定をしない人は、SPIの通信線を省略してみてもいいかもしれません。

作成した機体の足回り

足回りの構造は図2のようになっています。

図2:足回りの構造
図2:足回りの構造

ギアホイールにシャフトを圧入して、カラーにシャフトを圧入して、カラーの中に磁石を入れることで、ギアホイールと磁石が同じように回転します。磁気式エンコーダーで磁石の角度がわかることで、ギアホイールの回転角度もわかります。

プログラムフロー

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

図3:プログラムのフロー
図3:プログラムのフロー

クロックの初期化とCMT0、SCI1の初期化を行って、RSPI0を初期化します。その後、メインルーチンに入り、1ms経過したら、磁石の角度を取得して、UARTで送信します。

プログラム

磁気式エンコーダーに関係するプログラム部分だけ示します。sci.csci.h を除いた全てのソースコードGitHubの「8_as5047」フォルダの中にあります。

メイン関数

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){
    unsigned short rd_data;
    unsigned short data;

    init_rx631();       // Overall Initialization

    while(1){
        if(g_flag){
            g_flag = 0;  // Clear flag

            // Read angular velocity
            rd_data = SPI_ReadAS5047(AS5047_ANGLECOM);
            data = (rd_data & 0x3FFF) * 360 / 16384;
            sci_printf("%u\r\n", data);
        }
    }
}

メイン関数では、RX631の初期化を行い、メインルーチンに入ります。CMT0の割り込み関数によって1ms 経過フラグ(g_flag)が立ったら、フラグを下げて、角度を読み、その値をsci_printfでUART送信します。

補足:角度データが入っているレジスタANGLEANGLECOMがありますが、今回はエラー補正したANGLECOMから角度データを取り出します。

RSPI0の初期化

初期化関数のソースコードが以下です。RSPI に関してはユーザーズマニュアルの p.1619「38. シリアルペリフェラルインタフェース(RSPI)」に記述があります。ほとんどRXマイコンのソフト開発(6)RSPIでMPU6000から角速度取得の初期化と同じです。

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);

/*---- 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();

    SYSTEM.PRCR.WORD = 0xA500;  // Reprotect
}

~略~

/*---- RSPI0 Initialization ----*/
static void init_rspi0(void){

    MSTP(RSPI0) = 0;               //  RSPI0 Module Stop Release

    RSPI0.SPBR = 5;                //  521 kbps 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 = 0  : RSPCK at idle is LOW
    // BRDV = 3  : 8 divisions of the base bit rate
    // LSBF = 0  : MSB First
    // SPB  = 15 : Data length = 16bit
    RSPI0.SPCMD0.WORD = 0x0F0D;

    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
    PORTB.PDR.BIT.B5      = 1;     // CS1: OUT
    PORTB.PODR.BIT.B5     = 1;     // CS1: HIGH

    RSPI0.SPCR.BIT.SPMS = 1;       // Clock synchronous operation
    RSPI0.SPCR.BIT.MSTR = 1;       // Master Mode
}

MPU6000のときと変わっている点は、SPCMD0レジスタのCPOLビットを"0"にしている点だけです。これで、アイドル時にRSPCKAが"L"になります。

補足:今回は、ANGLECOMレジスタから角度データを取り出すので、AS5047の初期設定はしませんでした。ANGLEレジスタから角度データを取り出す場合は、SETTINGS1レジスタDAECDESビットやDataselectビットを設定したほうがいいかもしれません。

SPIの送受信

SPIで読み書きするソースコードとヘッダーファイルが以下です。こっちはMPU6000のときと少し変わっています。

spi.c:SPI関連のソースコード

#include "define_wakaba.h"
#include "spi.h"
#include "sci.h"

static u16 SPI_SendRecvAS5047(u32 packet);

/* Write to AS5047 register */
void SPI_WriteAS5047(u16 address, u16 data){
    u16 packet;
    u8  i;
    u8  parity=0;

    CS_AS5047 = ASSERT;  // Assert AS5047

    /* make packet */
    packet = address;
    for(i=0;i<15;i++)
        parity += (packet >> i) & 1;
    packet += (parity % 2) << 15;

    SPI_SendRecvAS5047(packet);  // Send Address

    CS_AS5047 = NEGATE;  // Negate AS5047

    for(i=0;i<10;i++)   // Wait 700ns
        __nop();

    CS_AS5047 = ASSERT;  // Assert AS5047

    /* make packet */
    packet = data;
    parity = 0;
    for(i=0;i<15;i++)
        parity += (packet >> i) & 1;
    packet += (parity % 2) << 15;

    SPI_SendRecvAS5047(packet);  // Send Data

    CS_AS5047 = NEGATE;  // Negate AS5047
}

/* Read AS5047 register */
u16 SPI_ReadAS5047(u16 address){
    u16 packet=0;
    u8  i;
    u8  parity=0;
    u16 data=0;

    CS_AS5047 = ASSERT;  // Assert AS5047

    /* make packet */
    packet = 0x4000 + address;
    for(i=0;i<15;i++)
        parity += (packet >> i) & 1;
    packet += (parity % 2) << 15;

    SPI_SendRecvAS5047(packet);  // Send Address

    CS_AS5047 = NEGATE;  // Negate AS5047

    for(i=0;i<10;i++)   // Wait 700ns
        __nop();

    CS_AS5047 = ASSERT;  // Assert AS5047

    /* make packet */
    packet = 0xC000;

    data = SPI_SendRecvAS5047(packet);  // Receive Data

    CS_AS5047 = NEGATE;  // Negate AS5047

    return data;
}

/* Sending/receiving data */
static u16 SPI_SendRecvAS5047(u32 packet){
    u16 data;

    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

    return data;
}

spi.h:SPI関連のヘッダーファイル

#ifndef SPI_H_
#define SPI_H_

#include "iodefine.h"
#include "define_wakaba.h"

#define LENGTH_16BIT  15
#define LENGTH_24BIT   1

#define AS5047_ERRFL     0x0001
#define AS5047_ZPOSM     0x0016
#define AS5047_ZPOSL     0x0017
#define AS5047_SETTINGS1 0x0018
#define AS5047_SETTINGS2 0x0019
#define AS5047_ANGLE     0x3FFE
#define AS5047_ANGLECOM  0x3FFF

#define ASSERT 0
#define NEGATE 1

#define CS_AS5047  PORTB.PODR.BIT.B5

void SPI_WriteAS5047(u16 address, u16 data);
u16 SPI_ReadAS5047(u16 address);

#endif /* SPI_H_ */

メイン関数ではSPI_WriteAS5047は使っていないので、SPI_ReadAS5047だけ説明したいと思います。SPI_SendRecvAS5047についてはSPI_SendRecvMPU6000と変わっていません。

SPI通信でAS5047のレジスタを読む方法は図4のようになります。

図4:SPI通信によるレジスタの読みかた(AS5047Pのデータシートから引用)
図4:SPI通信によるレジスタの読みかた(AS5047Pのデータシートから引用)

コマンドフレーム(Command)の構造は以下の表のようになります。

ビット 名前 説明
15 PARC 14:0 ビットで計算された偶数パリティビット
14 R/W 0:書き込み、1:読み込み
13:0 ADDR 読み書きするアドレス

パリティビットを作る必要があるので、SPI_ReadAS5047関数の中でパリティビットを作成しています。

データフレーム(Data)の構造は以下の表のようになります。

ビット 名前 説明
15 PARD 14:0 ビットで計算された偶数パリティビット
14 EF 0:コマンドフレームでエラーなし、1:エラーあり
13:0 DATA データ

15:14 ビットについてはデータではないので、メイン関数でマスクをして、データだけ取り出しています。

また、アドレス送信とデータ受信の間に350ns以上、CSを"H"にする必要があるので、NOPを10回実行しています。オシロスコープで測定したところ、NOPを10回実行することで、700nsの間、CSが”H”になっていました。

データを受信するときは、NOPレジスタ(Address=0x0000)を読み込むためのコマンドフレーム(0xC000)を同時送信しています。

プログラムの実行

プログラムを実行して、タイヤを左右に動かすと図5のようになりました(取得したデータから初期値を引いています)。

図5:取得した回転角度
図5:取得した回転角度

図5を見たところ、回転角度は問題なく取得できてそうです。

おわりに

今回で磁気式エンコーダーのAS5047からタイヤの回転角度を取得できました。これまでで、ほとんどのデバイスを動作させることができましたが、赤外線センサーだけ上手く動作できなかったので、もう一度基板を作りなおそうかと思います。

参考文献

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

© 2021 Setoti All rights reserved.