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


開発環境

開発環境は以下の通り。

開発用PC

  • Windows10 64bit
  • メモリ:32GB
  • SSD:512GB

環境

ZYBO Z7-20
参考サイト:XILINX公式 ZYBO Z7-20

  • 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チカ

1. プロジェクトの作成~ビットストリーム生成まで

Zynqでプログラミング(1) - Helloworld -と同様の方法で、Vivadoプロジェクトを「gpio」という名前で作成し、ブロックデザインを作成する。

ブロックデザインに、「ZYNQ7 Processing System」のIPコアを追加し、「Run Block Automation」しておく。
さらに、「AXI GPIO」のIPコアを追加して、そのIPコアをダブルクリックして設定を開く。
GPIO:sws 4bits/GPIO2:leds 4bitsに設定し、「OK」。これでFPGAとSW、LEDが繋がる。

「Run Connection Automation」でウィンドウ左のチェックを全て入れ、「OK」すると以下のようになる。

よく見ると、勝手に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に設定されていることを覚えておく。

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

2. アプリケーションの作成

SDKのメニュー「File」>「New」>「Application Project」よりアプリケーション(App)プロジェクトを作成する。
Appプロジェクト名は「gpio_app」とし、「Hello World」を選択して「Finish」。

AXI_GPIOのドライバを使用して、PS部からPL部を介してSWとLEDに接続する。
helloworld.cを編集し、ZYBO上のスイッチとLEDを操作するソースコードを追加する。

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コアを使って、割り込みを作っていく。