概要
FPGAで外部のICとSPI通信をしたいケースは多くあると思います。
単純にコントロールレジスタを設定するような、通信のタイミングを厳密に一定にする必要はない場合、
HDLでSPIコントローラを自分で作成しなくても、MicroBlazeとAXI Quad SPIを使ってソフトウェアで開発すると便利です。
今回はMicroBlazeにAXI Quad SPIを接続し、SPI Master Modeで書き込みと読み出しを行う方法について紹介します。
環境
- Vivado 2018.3
Block Designの作成
こちらの記事のデザインをベースに作成していきます。
基本的にはInterrupt Controllerが接続されたMicroBlazeがあれば問題ありません。
AXI Quad SPIを追加
AXI Quad SPIを追加し、IPの設定は下記画像のようにしました。
SPIはMaster Modeで動作させ、データ幅は8bitとします。
Slaveには2個のICを接続することを想定し、No. of Slavesを2に設定しています。
SPIバスは直接FPGAのピンに出力し、ip2intc_irptはMicroBlazeの割り込み入力に接続しています。
ext_spi_clkは100MHzのクロックを入力しています。
SCLKの周波数は次式のようになります。
f_{SCLK} = \frac{f_{in}}{D_{Frequency Ratio}} = \frac{100 \rm MHz}{16 \times 1} = 6.25 {\rm MHz}
サンプルコード
Hello Worldテンプレートをベースとして作成し、下記のコードをコピーしてください。
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xintc.h"
#include "xspi.h"
// 送受信バッファサイズ
#define BUFFER_SIZE 32
// デバイスドライバのインスタンス
static XIntc Intc;
static XSpi Spi;
// SPI通信中フラグ
volatile static int TransferInProgress;
// 割り込み処理中に起きたエラーをカウント
int Error;
// SPI送受信バッファ
u8 WriteBuffer[BUFFER_SIZE];
u8 ReadBuffer[BUFFER_SIZE];
// SPI割り込みハンドラ
void SpiHandler(void *CallBackRef, u32 StatusEvent, unsigned int ByteCount)
{
// 通信中フラグを降ろす
TransferInProgress = FALSE;
// 通信完了割り込み以外はエラー
if (StatusEvent != XST_SPI_TRANSFER_DONE) {
Error++;
}
}
int main()
{
int Status;
XSpi_Config *ConfigPtr;
init_platform();
// SPIドライバの初期化
ConfigPtr = XSpi_LookupConfig(XPAR_SPI_0_DEVICE_ID);
if (ConfigPtr == NULL) return XST_DEVICE_NOT_FOUND;
Status = XSpi_CfgInitialize(&Spi, ConfigPtr, ConfigPtr->BaseAddress);
if (Status != XST_SUCCESS) return XST_FAILURE;
// SPIコントローラが正しく動作しているかセルフテストを行う
Status = XSpi_SelfTest(&Spi);
if (Status != XST_SUCCESS) return XST_FAILURE;
// XIntcの初期化
Status = XIntc_Initialize(&Intc, XPAR_INTC_0_DEVICE_ID);
if(Status != XST_SUCCESS) return XST_FAILURE;
// SPIの割り込みハンドラを指定
Status = XIntc_Connect(&Intc, XPAR_INTC_0_SPI_0_VEC_ID,
(XInterruptHandler)XSpi_InterruptHandler,
(void *)&Spi);
if(Status != XST_SUCCESS) return XST_FAILURE;
//XIntcをhardware interrupts onlyで開始
Status = XIntc_Start(&Intc, XIN_REAL_MODE);
if(Status != XST_SUCCESS) return XST_FAILURE;
// SPIの割り込みを有効化
XIntc_Enable(&Intc, XPAR_INTC_0_SPI_0_VEC_ID);
// MicroBlazeの割り込み設定を初期化
Xil_ExceptionInit();
// MicroBlazeにXIntcからの割り込みを登録
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XIntc_InterruptHandler,
&Intc);
// MicroBlazeの割り込みを有効化
Xil_ExceptionEnable();
// SPI割り込みハンドラによって呼び出される関数を指定
XSpi_SetStatusHandler(&Spi, &Spi, (XSpi_StatusHandler)SpiHandler);
// SPI動作モードを設定
// XSP_MASTER_OPTION: マスターモードで動作
// XSP_MANUAL_SSELECT_OPTION: SSピンマニュアル選択
Status = XSpi_SetOptions(&Spi, XSP_MASTER_OPTION |
XSP_MANUAL_SSELECT_OPTION);
if (Status != XST_SUCCESS) return XST_FAILURE;
// スレーブを選択する
Status = XSpi_SetSlaveSelect(&Spi, 1);
if (Status != XST_SUCCESS) return XST_FAILURE;
// SPIドライバをスタートし、デバイスと割り込みを有効にする
XSpi_Start(&Spi);
// 送受信バッファの初期化
for(int i=0; i<BUFFER_SIZE; i++){
WriteBuffer[i] = i+0xA0;
ReadBuffer[i] = 0;
}
// 10回送信する
for(int i=0; i<10; i++){
// 通信中フラグを立てる
TransferInProgress = TRUE;
// 送信開始
XSpi_Transfer(&Spi, WriteBuffer, ReadBuffer, BUFFER_SIZE);
// SPI割り込みが入るまで待つ
while (TransferInProgress);
}
xil_printf("SPI Transfer Finished! (Error Count=%d", Error);
cleanup_platform();
return 0;
}
動作波形
データが連続で送信されました。BUFFER_SIZE分の長さが連続で送信されています。
BlockDesignでTransaction Widthを8bitに設定したので下位8bitだけにデータがのっていますね。
SPIは単純なシフトレジスタですので、上位24bit分は溢れて使われないので、
8bitタイプのSPIでも基本的にはこのプロトコルでも問題ありません。
次に、転送する長さを1としてみました。
転送終了後にChip Selectが上がっているのが確認できます。
参考