AXI Quad SPIを使ったSPI Master (MicroBlaze編)

## 概要
FPGAで外部のICとSPI通信をしたいケースは多くあると思います。
単純にコントロールレジスタを設定するような、通信のタイミングを厳密に一定にする必要はない場合、
HDLでSPIコントローラを自分で作成しなくても、MicroBlazeとAXI Quad SPIを使ってソフトウェアで開発すると便利です。
今回は**MicroBlazeにAXI Quad SPIを接続し、SPI Master Modeで書き込みと読み出しを行う方法**について紹介します。


## 環境
* Vivado 2018.3

## Block Designの作成
[こちらの記事](https://qiita.com/s_nkgw/items/81a6f8e468fb5584584c)のデザインをベースに作成していきます。
基本的にはInterrupt Controllerが接続されたMicroBlazeがあれば問題ありません。
![000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410452/3d1d2a2e-602f-07d8-9e14-514296e0bd34.png)


### AXI Quad SPIを追加
AXI Quad SPIを追加し、IPの設定は下記画像のようにしました。
SPIは**Master Mode**で動作させ、データ幅は**8bit**とします。
Slaveには2個のICを接続することを想定し、**No. of Slavesを2**に設定しています。

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410452/bb278a70-398f-2e62-1961-78c3e3035b06.png)

**SPIバス**は直接FPGAのピンに出力し、**ip2intc_irpt**はMicroBlazeの割り込み入力に接続しています。
**ext_spi_clk**は100MHzのクロックを入力しています。
![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410452/24d49af7-77a2-246b-0dcb-e87631a54be8.png)

SCLKの周波数は次式のようになります。

```math
f_{SCLK} = \frac{f_{in}}{D_{Frequency Ratio}} = \frac{100 \rm MHz}{16 \times 1} = 6.25 {\rm MHz}
```

全体的なデザインは次のようになりました。
![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410452/494c461a-bbc7-01d2-2190-83af184f59f1.png)


## サンプルコード
**Hello Worldテンプレート**をベースとして作成し、下記のコードをコピーしてください。

```c
#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でも基本的にはこのプロトコルでも問題ありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410452/da58daaa-11d9-3ad1-6c92-6bb0a29aac60.png)

次に、転送する長さを1としてみました。
転送終了後にChip Selectが上がっているのが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410452/49e45607-5267-981a-8b69-10fe8259ecda.png)

SCLKの周波数は設定どおり6.25MHzとなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410452/4e3a3416-10b9-677d-8d61-1b24c56556b1.png)


## 参考
https://github.com/Xilinx/embeddedsw/tree/master/XilinxProcessorIPLib/drivers/spi/examples