- 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)のソフト開発の8回目です。今回は、磁気式エンコーダーのAS5047を動作させたいと思います。AS5047によって、タイヤの回転角度を求めます。
環境
- パソコン: Windows10 64 bit
- ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)
AS5047の周辺回路
作成したAS5047の周辺回路図は図1です。xxxとxxx_H、xxxとxxx_HRはコネクタでつながっています。
今回使用するネットの RX631 への接続先は以下です。今回は右側のエンコーダーしか使いません。
- CS1 → PB5
- RSPCKA → PC5/RSPCKA
- MOSIA → PC6/MOSIA
- MISOA → PC7/MISOA
はじめ、SPI通信で初期設定をして、R_ENC_AとR_ENC_BからA相とB相の矩形波を受け取ろうと思ったのですが、SPI通信だけでも角度情報を受け取れるようなので、R_ENC_AとR_ENC_Bは使いません。
補足:初期状態でA相とB相の矩形波が出力されるように設定されている(オシロスコープで出力を確認)ので、SPIの通信線がなくても角度情報は受け取れるそうです。このため、レジスタ設定をしない人は、SPIの通信線を省略してみてもいいかもしれません。
作成した機体の足回り
足回りの構造は図2のようになっています。
ギアホイールにシャフトを圧入して、カラーにシャフトを圧入して、カラーの中に磁石を入れることで、ギアホイールと磁石が同じように回転します。磁気式エンコーダーで磁石の角度がわかることで、ギアホイールの回転角度もわかります。
プログラムフロー
プログラムの処理の流れは図3です。
クロックの初期化とCMT0、SCI1の初期化を行って、RSPI0を初期化します。その後、メインルーチンに入り、1ms経過したら、磁石の角度を取得して、UARTで送信します。
プログラム
磁気式エンコーダーに関係するプログラム部分だけ示します。sci.c と sci.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送信します。
補足:角度データが入っているレジスタはANGLEとANGLECOMがありますが、今回はエラー補正した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のようになります。
コマンドフレーム(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を見たところ、回転角度は問題なく取得できてそうです。
おわりに
今回で磁気式エンコーダーのAS5047からタイヤの回転角度を取得できました。これまでで、ほとんどのデバイスを動作させることができましたが、赤外線センサーだけ上手く動作できなかったので、もう一度基板を作りなおそうかと思います。