Zynqでプログラミング(2) - GPIO -

# 開発環境
開発環境は以下の通り。

## 開発用PC
* Windows10 64bit
* メモリ：32GB
* SSD：512GB

## 環境
ZYBO Z7-20
参考サイト：[XILINX公式 ZYBO Z7-20](https://japan.xilinx.com/products/boards-and-kits/1-pukio3.html)

* Vivado 2018.2
* TeraTerm ver4.90

# ZYBOの汎用I/O(GPIO)
ZYBOには、以下のような入出力素子が搭載されている。これらをPL部のFPGAとPS部のアプリケーションを組み合わせて使えるようにしていく。

* 入力(項目：接続されている場所)
 * スイッチ(SW0～SW3)：PL部
 * ボタン(BTN0～BTN3)：PL部
 * ボタン(BTN4, BTN5)：PS部(MIO)

* 出力(項目：接続されている場所)
 * LED(LD0～LD3)：PL部
 * LED(LD4)：PS部(MIO)

# スイッチ操作でのLEDの点灯
PL部のスイッチ(SW0～SW3)とLED(LD0～LD3)使うアプリケーションを作成し、実行する。
AXIバスや、PS部からドライバ使用するプロジェクトなので、これから非常に重要になる。
参考サイト：[ZYBO (Zynq) 初心者ガイド (4) PLのAXI GPIOでPSからLチカ](https://qiita.com/iwatake2222/items/a3b935a4e4dbd41593e1)

## 1.　プロジェクトの作成～ビットストリーム生成まで
[Zynqでプログラミング(1) - Helloworld -](https://qiita.com/lea22ther21/items/4bd6316f9d580c42aa1f)と同様の方法で、Vivadoプロジェクトを「gpio」という名前で作成し、ブロックデザインを作成する。
![fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512428/a1d7b45e-f713-fcb8-bd9d-286721928d25.png)
ブロックデザインに、「ZYNQ7 Processing System」のIPコアを追加し、「Run Block Automation」しておく。
さらに、「AXI GPIO」のIPコアを追加して、そのIPコアをダブルクリックして設定を開く。
GPIO：sws 4bits／GPIO2：leds 4bitsに設定し、「OK」。これでFPGAとSW、LEDが繋がる。
![fig2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512428/f1d85586-0f55-37e6-a8de-fea3b62e542b.png)
「Run Connection Automation」でウィンドウ左のチェックを全て入れ、「OK」すると以下のようになる。
![fig3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512428/ab7eb735-4331-ff90-1d4e-228a65cb154c.png)
よく見ると、勝手にIPコアが追加されている。「Processor System Reset」はその名の通りPS部のリセットに使用される。特に触る必要もないし、今後もあまり触らないと思う。
肝心なのが、**「AXI Interconnect」**。AXIバスという、**PL部(FPGA)とPS部(CPU)のデータの橋渡し機能**を持つIPコアである。詳細については、かな～り長くなるので、Google先生に聞いてもらいたい。とりあえず今は、PL部とPS部を繋ぐアドレスバスとだけ覚えておいてほしい。

「Create HDL Wrapper」でwrapperファイルを作成し、「Generate Bitstream」でビットストリームを生成する。これでPL部(FPGA)は作成完了。

ビットストリームができるのを待っている間に、「Diagram」タブから「Address Editor」タブに切り替える。「processing_system7_0」＞「Data」＞「axi_gpio_0」のOffset Addressが**0x4120_0000**に設定されていることを覚えておく。
![fig4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512428/ff368d00-220b-e6bc-ad4c-ff0e59fff8b5.png)

「File」＞「Export」＞「Export Hardware」でhdfファイルをエクスポート(「Include bitstream」にチェックを入れる)し、「File」＞「launch SDK」よりSDKを起動する。

## 2.　アプリケーションの作成
SDKのメニュー「File」＞「New」＞「Application Project」よりアプリケーション(App)プロジェクトを作成する。
Appプロジェクト名は「gpio_app」とし、「Hello World」を選択して「Finish」。
![fig5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/512428/170112e2-8224-182d-5962-8e07114075e4.png)
AXI_GPIOのドライバを使用して、PS部からPL部を介してSWとLEDに接続する。
helloworld.cを編集し、ZYBO上のスイッチとLEDを操作するソースコードを追加する。

```c:helloworld.c
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xparameters.h"	/* FPGA設定のヘッダ */
#include "xgpio.h"			/* AXI_GPIOドライバのヘッダ */

/* GPIOで設定したCH */
#define SW_CHANNEL 1
#define LD_CHANNEL 2

/* AXI_GPIOドライバのインスタンス定義 */
XGpio gpio_0;

int main()
{
    int status;

    init_platform();

    print("Hello World\n\r");

    u32 sw_state;

    /* AXI_GPIOドライバの初期化 */
    status = XGpio_Initialize(&gpio_0, XPAR_GPIO_0_DEVICE_ID);
    if(status != XST_SUCCESS)
    {
        xil_printf("XPAR_GPIO_0_DEVICE_ID initialization failed.\r\n");
        return XST_FAILURE;
    }

    /* 入出力方向の設定 0:出力 1:入力 */
    XGpio_SetDataDirection(&gpio_0, SW_CHANNEL, 0xFFFFFFFF);	/* CH1(SW)は入力設定 */
    XGpio_SetDataDirection(&gpio_0, LD_CHANNEL, 0x00000000);	/* CH2(LD)は出力設定 */

    while(1)
    {
        /* SWの値を読み出し */
        sw_state = XGpio_DiscreteRead(&gpio_0, SW_CHANNEL);

        /* SWの状態をLDに反映 */
        XGpio_DiscreteWrite(&gpio_0, LD_CHANNEL, sw_state);
    }

    cleanup_platform();
    return 0;
}
```

###アプリケーション解説
アプリケーションの気になると所を解説していく。XILINXのドライバに関しては、ヘッダファイルやソースファイルに(英語だが)関数の機能や引数の詳細が書かれているため、そちらも参考にしてほしい。

* **XGpio gpio_0;**
AXI_GPIOドライバを使用するために必要で、「XGpio」は xgpio.hに定義されている。

* **XGpio_Initialize(&gpio_0, XPAR_GPIO_0_DEVICE_ID);**
指定されたインスタンスと、デバイスIDを紐づけ。XPAR_GPIO_0_DEVICE_IDはxparameters.hに定義されている。
xparameters.hを見ると、XPAR_GPIO_0_DEVICE_IDは**0x41200000**。Vivadoで確認したアドレスと同じ。
もし、Vivadoでもう1つAXI_GPIOを追加したら、XPAR_GPIO_1_DEVICE_IDが生成される。

* **XGpio_SetDataDirection(&gpio_0, SW_CHANNEL, 0xFFFFFFFF);**
引数の2番目に設定されたCHを入力／出力設定する。引数の3番目のbit単位で0:出力／1:入力。
なので、SWは全て1(入力)の0xFFFFFFFF、LEDは全て0(出力)の0x00000000。

* **XGpio_DiscreteRead(&gpio_0, SW_CHANNEL);**
引数2番目のCHから値を読み出し、返り値(u32(unsigned int))にする。

* **XGpio_DiscreteWrite(&gpio_0, LD_CHANNEL, sw_state);**
引数2番目のCHに、引数3番目の値を書き込む。SWがONになっていればsw_stateの値が変化し、LEDも点灯する。

## 3.　アプリケーションの実行
ZYBOの電源を入れ、「Program FPGA」でFPGAを焼きこみ、アプリケーションを実行する。

* TeraTermを開いていれば、「Hello World」が表示される
* ZYBO上のSW0～SW3をONにするとLD0～LD3が点灯。SWをOFFにすると消灯。

# 最後に
これで、XILINX標準のIPコアのドライバを使うところまではできた。
次は、タイマのIPコアを使って、割り込みを作っていく。