素のZynqでLチカしてみよう

前回はVivadoを使ってPSだけの素のZynqの回路を生成しました。これを使って最初のLEDチカチカを行います。

Xilinx SDKを使ってプログラムを作りますが、アプリの作成にはそれなりに手順が必要となります。プログラムとしては標準のGPIOに出力すればいいので簡単なハズですが・・・。

Xilinx SDKを起動する

前回はVivadoでビットストリームを生成して、ハードウェアをエクスポートするところまでやりました。ここからXilinx SDK(以降SDKとします)へとつながっていきますので、Vivadoを終了していたら起動してください。

SDKは単独で起動できるのですが、最初にVivadoから起動すればSDKに必要なプロジェクトを作ってくれます。VivadoからSDKを起動するにはFileメニューの「Launch SDK」を実行します。

SDKを起動する

エクスポート先とワークスペースの出力先の確認がありますので、デフォルトを受け入れてOKを押します。

SDKを起動する2

そうしますと、SDKが起動しハードウェア定義のインポートが自動的に開始されます。

SDKでハードウェア定義のインポート

それが終わると、SDKの起動画面が次のように表示されます。

アプリのひな型を作る

この段階ではZynqのハードウェアの初期化ルーチンのみ登録されていますので、まだ何もできない状態です。ここから、「ボードサポートパッケージ」と「アプリケーションプロジェクト」を追加していきます。

まず、FileメニューのNewから「Board Support Package」を実行します。

BSP作成

ボードサポートパッケージの設定画面が表示されますので、「Project name」はデフォルトとし、「Board Support Package OS」は「standalone」とします。

BSP設定

さらに詳細な設定画面が表示されますので、stdin/stdoutのUARTの設定を「ps7_uart_1」にしておきます。本編では使いませんが、この設定にしておくと、USBシリアルでターミナルの入出力ができるようになります。

BSP設定2

完了するとProject Explorerに「standalone_bsp_0」が追加されます。

BSP設定3

ボードサポートパッケージにはZynqを使うための基本的な関数が用意されています。GPIOにアクセスするための関数も用意されていますので、それを使えば簡単(?)にプログラムが作れます。

次はアプリのひな型を作ります。FileメニューのNewから「Application Project」を実行します。

アプリケーションプロジェクト実行

設定画面が表示されますので、Project nameは「Test」としておきます。Board Support Packageの設定は必ず「Use existing」を選択し、さっき作った「standalone_bsp_0」を指定します。

アプリケーションプロジェクト設定

Nextボタンを押すと、どんなアプリを作るか聞いてきます。ここでは「Hello World」を選択しておきます。

アプリケーションプロジェクト設定2

Finishボタンを押せば、これでようやくアプリを作るベースができました。

SDKの設定完了

Lチカプログラムを書く

「helloworld.c」がメインルーチンとなっていますので、これを開いてLチカのプログラムに書き換えます。

helloworld.cを開く

SDKのターミナルを設定すればprint文で「Hello World」が表示されるのですが、それは無視してLチカへと突き進みます。ここで、LEDについて調べてみましょう。

MiniZedのハードウェアガイドの「4.1 PS pin Allocations」辺りを見ると、LEDがMIOピンの52と53番に割り当てられており、GPIOに接続されていることがなんとなく理解できます。このポートに出力すればLEDが制御できるはずです。

ここで、GPIOの制御をどうするかですが、実はボードサポートパッケージの中に「xgpiops.c」というソースファイルが含まれており、GPIO制御関数が用意されています。時短のためサクッと主要関数を説明します。

XGpioPs_Config *XGpioPs_LookupConfig(u16 devid)
  デバイスIDからGPIOのコンフィグレーションを返します。
s32 XGpioPs_CfgInitialize(XGpioPs *insptr, XGpioPs_Config *cfgptr, u32 addr)   GPIOのコンフィグレーションを初期化します。
void XGpioPs_SetDirectionPin(XGpioPs *insptr, u32 pin, u32 dir)   GPIOの入出力を指定します。
void XGpioPs_SetOutputEnablePin(XGpioPs *insptr, u32 pin, u32 ena)   GPIOの出力を許可します。
void XGpioPs_WritePin(XGpioPs *insptr, u32 pin, u32 data)   GPIOにデータを出力します。

なお、詳しい説明は最初にSDKで表示されていた「system.mss」の「ps7_gpio_0」「Documentation」をクリックするればオンラインドキュメントが開くようになっています。また、「Import Examples」でGPIOのサンプルをゲットすることができますので、そちらも参考にしてください。

それでは、いよいよプログラムを書いてみましょう。helloworld.cを以下のように変更します。

#include <stdio.h>
#include "platform.h"
#include "sleep.h"
#include "xgpiops.h"

int main()
{
    XGpioPs_Config *cfg;
    XGpioPs ins;

    init_platform();
    cfg = XGpioPs_LookupConfig(XPAR_XGPIOPS_0_DEVICE_ID);
    XGpioPs_CfgInitialize(&ins, cfg, cfg->BaseAddr);

    XGpioPs_SetDirectionPin(&ins, 52, 1);
    XGpioPs_SetOutputEnablePin(&ins, 52, 1);

    while(1) {
        XGpioPs_WritePin(&ins, 52, 1);
        sleep(1);
        XGpioPs_WritePin(&ins, 52, 0);
        sleep(1);
    }

    cleanup_platform();
    return 0;
}

APIを使うことでハードの固有なところが隠蔽されるため、汎用化されて、おしゃれな感じになります。逆にI/O一つ書き込むのに遠回りな手続きが必要となりますが・・・。

では、このプログラムについて、簡単に説明しておきます。まず、GPIOにアクセスするAPIのヘッダファイルをインクルードします。

#include "xgpiops.h"

次はZynqの初期化と終了処理です。実際には無限ループで回りますので終了処理には来ません。

    init_platform();
        :
        :(本体)
        :
    cleanup_platform();

次はGPIOのドライバIを初期化するための呼び出しです。

    cfg = XGpioPs_LookupConfig(XPAR_XGPIOPS_0_DEVICE_ID);
    XGpioPs_CfgInitialize(&ins, cfg, cfg->BaseAddr);

「XPAR_XGPIOPS_0_DEVICE_ID」を指定するとcfgのBaseAddrにGPIOのベースアドレスが返ってきます。それを使って、XGpioPs_CfgInitializeでGPIOドライバを初期化しています。初期化後はinsという変数がGPIOのアクセスハンドルとなり、以降のGPIOの設定や読み書きはinsを使って行うことができます。

XPAR_XGPIOPS_0_DEVICE_IDについては「xparameter.h」に定義されているのですが、この定義を使えという記述が見当たりませんでした(Exampleで使われていたので発見できました)。まあ、汎用化のためにこれをそのまま使うことにします。

次のAPI呼び出しは、GPIOの入出力の方向を決め、出力を許可する指定です。

    XGpioPs_SetDirectionPin(&ins, 52, 1);
    XGpioPs_SetOutputEnablePin(&ins, 52, 1);

XGpioPs_CfgInitializeで取得したinsによりGPIOを指定します。52はMIOの番号を示しています。XGpioPs_SetDirectionPinで最後のパラメータを1にすることでこのポートを出力に指定します。0だと入力になります。XGpioPs_SetOutputEnablePinは最後のパラメータが1の場合に出力許可となります。

そして以下の操作を行うことでLEDが点滅します。

    XGpioPs_WritePin(&ins, 52, 1);
    sleep(1);
    XGpioPs_WritePin(&ins, 52, 0);
    sleep(1);

52番のポートに1を出力することでLEDがONとなり、0を出力することでOFFになります。sleep(1)を入れておくことで1秒間隔となります。sleep(1)はBSPで用意されている関数ですが、わざわざタイマーを作らなくてもいいので便利ですね。

それでは、実際に動かしてみましょう。ソースファイルを保存すると、自動的にビルドが動き出してコンパイルが完了します。

MiniZedボードの接続と動作確認

MiniZedボードで実際に動作させるために、DIPスイッチをJ側に変更します。この設定をおこなうと、USB経由でオンボードのJTAGエミュレータが動作します。ちなみに、このDIPスイッチは購入時にプラスチックカバーが貼ってありますので、それをはがす必要があります。

MiniZed DIPSW変更

次に付属のUSBケーブルを接続し、パソコンと接続します。電源が供給されパワーLEDが点灯します。

MiniZed USB接続

ここでようやくVivadoで作成した、ハードウェアをFPGAに書き込む時が来ました。書き込み方法はツールバーの「Program FPGA」を押します。

Program FPGA

すると以下の画面が表示されますのでそのままProgramを押します。

Program FPGA設定

正しく書き込めると、パワーLEDの隣にある青色LEDがまぶしく点灯します。この青色LEDはFPGAがちゃんと書き込めたかどうかを示すLEDとなっています。

次に虫のマークのデバッグボタンを押せば、Lチカプログラムがボードにダウンロードされ、実行できる状態になります。

デバッグボタン

この時、初めて虫マークのボタンを押した場合は、以下の画面が表示されますので、「Launch on Hardware (System Debugger)」を選択してOKを押してください。

デバッグ設定

画面がデバッグしやすい構成に切り替わり、main関数の先頭で止まります。

デバッグ画面

Resumeボタンを押せば、プログラムが実行されます。

実行開始ボタン

プログラムが正しく動けば、PS LEDの赤色LEDが1秒間隔で点滅するはずです。

実はこのLEDは2色発光が可能なLEDです。今回は赤色LEDだけを使用しましたが、MIOの53番には緑色LEDが接続されています。興味がありましたら同じように53番に出力するコードを書いて試してみてください。

 


ここまででPSだけの機能でGPIOの制御をやってみました。本当はAPIではなくガチガチなポートアクセスをやってみたいのですが、それはまたの機会とします。

MiniZedボードにはPL LEDというのがありまして、こちらはPLで制御できるようになっています。次回はPL側にGPIOを作って、PL LEDをチカチカさせてみたいと思います。