マイクロコントローラMAXQ3180でのSPI (Serial Peripheral Interface)の使用方法
要約
マイクロコントローラMAXQ3180は、電力メータ用の多相アナログフロントエンドです。最新の多機能な電力計測に必要となるすべての機能を搭載しています。MAXQ3180は、シリアル方式の周辺相互接続バスであるSPI™ (Serial Peripheral Interface)バスを使用して、ホストマイクロコントローラに計測値を伝達します。このアプリケーションノートでは、このインタフェースがどのように行われるかを説明し、設計者が通信メカニズムを実装する手助けとなるサンプルコードを提示します。
SPIの概要
SPI (Serial Peripheral Interface)は、チップ間の高速同期式全二重通信を提供するデバイス間バスプロトコルです。1個のデバイス(マスタ)が同期クロックを駆動して、複数のスレーブの中から対象を選択します。各SPI周辺装置は単一のシフトレジスタと制御回路で構成され、対象となるシリアル周辺インタフェースSPI周辺装置が送信と受信を同時に行う仕組みになっています。
SPI通信では、4本の独立した回路が使用されます。
- SCLK:すべてのデバイスによって使用される同期クロックです。マスタがこのクロックを駆動して、スレーブがクロックを受信します。SCLKはゲート制御が可能であり、SPIトランザクション間では駆動の必要がないことに注意してください。
- MOSI:マスタアウト、スレーブイン。マスタによってSPIバス上のすべてのスレーブに対して駆動されるメインのデータ線です。選択されたスレーブだけがMOSIからデータをクロックインします。
- MISO:マスタイン、スレーブアウト。選択されたスレーブによってマスタに対して駆動されるメインのデータ線です。選択されたスレーブだけがこの回路を駆動することができます。実際には、SPIのバス構成の中で、スレーブによる駆動が許される唯一の回路です。
- SSEL:この信号は、各スレーブに固有です。アクティブ(一般的にはロー)の場合、選択されたスレーブはMISOを駆動する必要があります。
この議論に関して、「SPI周辺装置は送信と受信を同時に行う」という点に注意することが極めて重要です。マスタが常に1バイトを送信して1バイトを受信するということを覚えておくと便利です。
一部のSPI周辺装置は、速度を犠牲にして半二重動作をシミュレートしています。マイクロコントローラMAXQ3180は真の全二重SPIスレーブであり、これには該当しません。
このアプリケーションノートの残りの部分では、SPIバス上でMAXQ3180の接続を行い、適切に使用する方法について説明します。
MAXQ3180の通信の概要
マスタ(ホスト)にとって、MAXQ3180はRAMとROMの両方で構成されたメモリアレイのように見えます。これは、MAXQ3180内のROMファームウェアが動作パラメータをRAMから読み取り、結果をRAMに格納するためです。したがって、単純にRAMロケーションへのブロック書込みを実行するだけでMAXQ3180の設定を行うことができます。
MAXQ3180の一部の「メモリ」ロケーションは、デバイス内で電力計測の結果を「オンザフライ」で計算する動作を行わせるトリガの役割を果たします。これらのロケーションへの書込みは「nop (無動作)」として扱われます。RAMおよび仮想ROMの各ロケーションが持つ具体的な機能および目的は、この記事の範囲を超えます。ここで重要なのは、このマイクロコントローラが実際に備えているSPI通信のための動作が、読取りと書込みの2つだけであるという事実です。
MAXQ3180のすべてのトランザクションは、コマンド(読取りまたは書込み)、アクセス先のアドレス、およびアクセスするバイト数を格納した2バイトをマスタが送信することによって開始されます。前述の通り、すべてのSPI周辺装置が、受信1バイトに対して1バイトを返信します。したがって、MAXQ3180は最初のコマンドバイトを受信した後に0xC1を返して、第2のコマンドバイトを受信した後に0xC2を返します。このプロトコルを、次の図2に示します。
マスタが1バイト以上の読取りを行う場合、ダミーのバイトを送信する必要があります。マスタは、何かを送信しない限りスレーブから何も受け取ることができない、すなわち、1バイト受け取るためには1バイト送信する必要があることを思い出してください。しかし、コマンドを受信した後で、MAXQ3180が結果を計算する必要がある可能性があり、そのためにマスタがダミーバイトを送信した時点ではデータが準備できていない可能性があります。こうした理由から、MAXQ3180はデータを送信する前に、常に0バイト以上のNAKキャラクタ(0x4E、ASCIIの「N」)に続けて1バイトのACKキャラクタ(0x41、ASCIIの「A」)を送信します。
マスタが1バイト以上の書込みを行う場合、コマンドを送信した直後に、書込むデータを送信します。MAXQ3180はデータ1バイトごとにACK (0x41)を返します。その後、書込みサイクルが完了するまでNAK (0x4E)を返し、書込み完了後に最終的なACKを返します。
最後のACKの直後から、MAXQ3180は次のトランザクションを開始する準備ができており、それ以上他のイベントを待つ必要がないことに注意してください。次のトランザクションを開始するためにSSELをトグルさせる必要さえありません。MAXQ3180は最初のトランザクションが終わったことを理解して、次のトランザクションに備えています。
何らかの理由で、マスタとMAXQ3180の間の通信をリセットする必要が生じた場合は(たとえば、通信の同期が取れなくなった場合など)、マスタが最初のコマンドバイトから200ms待って通信を再開するだけでリセット可能です。200msの遅延によって、直前のトランザクションをマスタが放棄しようとしていることがMAXQ3180に伝達されます。
コマンドバイト
コマンドバイトは、MAXQ3180に以下の内容を伝えます。
- 要求されるトランザクションがREADかWRITEか
- トランザクションの長さ
- 書き換えるRAMのアドレス(または読み取る仮想ROMのアドレス)
第1のコマンドバイト(図3)は、要求されるトランザクションがREADかWRITEか、およびトランザクションの長さをMAXQ3180に伝えます。コマンドバイトでは、次の対応表が使用されます。
Length Code | Data Length |
0b00 | 1 byte |
0b01 | 2 bytes |
0b10 | 4 bytes |
0b11 | 8 bytes |
コマンドバイト1の残り部分およびコマンドバイト2の全体で、アクセス対象となるRAM内のバイトのアドレス(または仮想ROM機能のID)を示します。
ホストのソフトウェア設計
MAXQ3180はハードウェアSPIコントローラを内蔵していますが、個々のメッセージバイトは、ROMファームウェアに含まれているソフトウェアルーチンで処理されます。そのため、連続するバイト間に遅延時間が必要になります。最新バージョンのMAXQ3180の場合、信頼性の高い動作のためには、この遅延を100µs以上にする必要があります。図4および5をご覧ください。
コードのリスト
SPIマスタを内蔵したマイクロコントローラMAXQ2000と、MAXQ3180とのインタフェースを行うためのコードを示します。他のマイクロコントローラのユーザは独自のSPIプリミティブを用意する必要があり、場合によっては高水準サブルーチンの修正も必要になる可能性があります。
次のリストの中で、dly_usサブルーチンは、指定されたマイクロ秒の間プログラムスレッドの実行を停止させるものです。定数SPI_TIMEOUTは、1文字のタイムアウトより長くなるように定義されています。
高水準サブルーチンでは、ENUMを使用することによってレジスタを名前で選択します。これは配列register_lookup_tableアレイの添字になっており、この配列には特に重要な項目としてMAXQ3180の各レジスタの長さが格納されています。図6、7、および8をご覧ください。
unsigned char Send_SPI(unsigned char x) { unsigned char y = 0; int z; int error = 0; SPICN = 3; /* MSTSM, SPIEN */ z = 0; while ((SPICN_bit.STBY) && (++z < SPI_TIMEOUT)); if (z == SPI_TIMEOUT) error = 1; SPICN_bit.SPIC = 0; /* Clear transfer complete flag */ SPIB = x; z = 0; while ((!SPICN_bit.SPIC) && (++z < SPI_TIMEOUT)); if (z == SPI_TIMEOUT) error = 1; y = SPIB; SPICN_bit.SPIC = 0; dly_us(100); if (error) return 0; return y; }
long Read_AFE(enum METER_REGISTER_RECORD reg, uint16 reg_addr) { extern unsigned char record[8]; unsigned long x = 0; unsigned char i, regadd, command_code = 0; for(i=0; i<8; i++) record[i] = 0; switch(register_lookup_table[reg].register_length) { case 2: command_code |= 0x10; break; case 4: command_code |= 0x20; break; case 8: command_code |= 0x30; break; } command_code |= reg_addr >> 8; regadd = reg_addr & 0xff; /* Disable SPI to reset it */ SPICN_bit.SPIEN = 0; for(x=0; x<300; x++); SPICN_bit.SPIEN = 1; SPI_SELECT_0; i = 0; while((Send_SPI(command_code)!= 0xC1)&&(++i < SPI_COMMAND_RETRIES)) spi_comm_timeout(); x = 0xffffffff; if (i == SPI_COMMAND_RETRIES) goto spierror; Send_SPI(regadd); i = 0; while((Send_SPI(0) != 'A') && (++i < SPI_RETRIES)); if (i == SPI_RETRIES) goto spierror; x = 0; for(i=0; i<register_lookup_table[reg].register_length; i++) { record[i] = Send_SPI(0); x |= ((uint32)record[i]) << (i * 8); } spierror: SPI_DESELECT_0; return (int32)x; }
void Write_AFE(enum METER_REGISTER_RECORD reg, uint16 reg_addr, uint32 data) { uint8 i, regadd, command_code = 0x80; int x; switch(register_lookup_table[reg].register_length) { case 2: command_code |= 0x10; break; case 4: command_code |= 0x20; break; case 8: command_code |= 0x30; break; } command_code |= reg_addr >> 8; regadd = reg_addr & 0xff; /* Disable SPI hardware to reset it */ SPICN_bit.SPIEN = 0; for(x=0; x<300; x++); SPICN_bit.SPIEN = 1; SPI_SELECT_0; i = 0; while((Send_SPI(command_code)!=0xC1)&&(++i < SPI_COMMAND_RETRIES)) spi_comm_timeout(); if (i == SPI_COMMAND_RETRIES) goto spierror; Send_SPI(regadd); for(i=0; i<register_lookup_table[reg].register_length; i++) Send_SPI((data >> (i * 8)) & 0xff); i = 0; while((Send_SPI(0) != 'A') && (++i < SPI_RETRIES)); spierror: SPI_DESELECT_0; }