【LED点滅編(3)】インスタンスの概念とGPIOのドライバAPI関数の使用方法

Vitis
スポンサーリンク

本記事の概要

概要

Zynq内のCPUにて動作するLED点滅アプリケーションの作成を通じて以下のポイントを解説:
① インスタンスという概念について
② GPIO(General-Purpose I/O;汎用I/O)を制御するドライバAPI(Application Programming Interface)の使用方法

前回作成したLED点滅回路では、GPIO(汎用I/O)からLEDに向けて信号を出力するようにしています。

そして、アプリケーションプロジェクト上で、”AXI GPIO”のレジスタに直接値を書き込むことによってLEDを点滅させました。
今回はアプリケーションプロジェクトにおけるmain関数を変更し、”AXI GPIO”をインスタンスとして扱って、ドライバAPIを活用してみようと思います。
これを例に、インスタンスの概念と”AXI GPIO”のドライバAPIの使用方法について学んでいきたいと思います。

この記事の対象読者
  • FPGAプログラミングの初学者
  • FPGAの開発に興味のある学生
  • Vitisを用いたアプリケーション開発は未経験のエンジニア
ひがし
ひがし

それでは、興味のある方はぜひ最後までご覧ください!

本記事の目標

統合ソフトウェア開発環境VitisとFPGA設計環境Vivadoにおける
インスタンスの概念とGPIOのドライバAPIの使用方法について理解する

インスタンスの概念について

GPIOのインスタンス

XSAファイルを読み込んで作成したVitisのプラットフォームプロジェクトには、xgpio.hというヘッダファイルが含まれています。
xgpio.hでは次の2種類の構造体が定義されています。

xgpio.hで定義される2つの構造体
  • XGpio:インスタンス
  • XGpio_Config:コンフィグレーション

インスタンス XGpio

xgpio.hにおける2つの構造体のうち、構造体XGpioにより宣言された変数は「インスタンス」と呼ばれています。
ドライバAPIにおける関数では、XGpioで宣言された変数をあたかもブロックデザイン上で定義したAXI GPIOのIPコアであるかのように、アプリケーション上での一つの対象として扱っています。
このようにIPコアをアプリケーション上でも仮想的に扱うために宣言した変数は、実際のデバイス上で実体を伴った対象であることを考慮し、「インスタンス(実体)」と呼ばれています
以下では、具体例を見ながら、インスタンスの概念と取り扱い方について確認していきましょう。

ちなみに、構造体XGpio_Configはデバイスのハードウェア設定を一つにまとめています。
インスタンスとコンフィグレーションに含まれる変数は重複することも多いですが、ドライバAPI上では用途によって引数を区別しているようです。

インスタンスの具体例

ドライバAPIにおける各機能をもつ関数の引数にインスタンスを設定することによって、実際のIPコアを制御することができます。
例えば、以下のコードを見ていきましょう。

XGpio LEDInst;

int main()
{
    int status;

    status = XGpio_Initialize(&LEDInst, XPAR_AXI_GPIO_0_DEVICE_ID);
    XGpio_SetDataDirection(&LEDInst, 1, 0x00);

  /*略*/
}

まず、main関数外で、LEDInstというXGpioのインスタンスを宣言しておきます。
しかし、宣言しただけでは、まだ実際にデバイス上に存在するIPコアとはなんの対応もついていません。

そこで、main関数内では、宣言したインスタンスLEDinstと実際にデバイス上で使用しているAXI GPIOのベースアドレスをセットにして、API関数XGpio_Initializeを実行します。

この初期化関数により、インスタンスLEDinstとデバイスにおけるIPコアAXI GPIOとを紐付け、アプリケーション上ではLEDInstをあたかも仮想的にAXI GPIO IPコアとして扱うことができるようになります。

実際にインスタンスを使ってIPコアを制御するイメージを、XGpio_SetDataDirectionというAPI関数を例に確認してみましょう。

XGpio_SetDataDirection(&LEDInst, 1, 0x00);

XGpio_SetDataDirectionは、AXI GPIOにおける、「AXI GPIO 3-State Control Register (GPIOx_TRI)」と呼ばれる制御レジスタを書き換える関数です。
GPIOx_TRIレジスタでは、GPIO信号を入力にするか出力にするかを切り替えています。
前回レジスタを直接変更した場合の、コントロールレジスタに対応しています。
例えば、レジスタに0を代入するとGPIOのI/Oピンは出力方向を向き、1を代入すると入力方向を向きます。
LED点滅アプリケーションの例では、LEDに信号を出力させるわけなので、レジスタには0を代入します。

前回、コントロールレジスタ[LED_ctrl]に値[0x0]を直接格納しましたが、XGpio_SetDataDirectionという関数を使えば同じことを行うことが可能です。
API関数XGpio_SetDataDirectionをインスタンスに作用させ、AXI GPIOのチャネル番号1と入出力方向0x00をパラメータとして代入します。
※AXI GPIOには2個の入出力I/Oを設けることが可能で、その2つをチャネルと呼んで区別しています。チャネル番号は1と2を選択できますが、今回は1つしか使っていないので1を代入しています。

XGpio_SetDataDirection()

機能:
 ある特定のGPIOチャネルの個々の端子における信号の入出力方向を設定する。
パラメータ:
 ・InstancePtr:GPIOインスタンスへのポインタ
 ・Channel:設定したいチャネル番号(1 or 2)
 ・DirectionMask:どの端子を入力(1)、出力(0)にするかを定めるビットマスク。
  例)LED[3:0]のうち、仮に上位2ビットを入力、下位2ビットを出力にしたい場合は
   DirectionMaskに2進数で[1100]、つまり16進数で0x0cを代入する。
返り値:なし

インスタンスを使うポイントは、API関数XGpio_SetDataDirectionを使うときに、デバイス側のID情報などのハードウェアに関する特性を、アプリケーション開発者が直接は参照していない点です。
ベースアドレスはインスタンスに含まれており、API関数を使えばハードウェアのどのレジスタを叩けばよいかということは意識する必要がなくなります。
アプリケーションを作成するときに、デバイスにおけるAXI GPIOというIPコアを意識せずに、API関数だけで制御できるのが、インスタンスという概念でデバイスを抽象化するメリットになっています。

ドライバAPIの中を見てみよう

ひがし
ひがし

ただ、筆者はハードウェアがどうやって動いているのか気になってしまい、隠されているドライバAPI関数の中を関数を使う前に覗くことが多いです。今回もせっかくですので見てみましょう。

Xilinxから提供されるソースコードの中を参照しました。
ソースコードはgithubからお借りしています。

void XGpio_SetDataDirection(XGpio *InstancePtr, unsigned Channel,
			    u32 DirectionMask)
{
	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(InstancePtr->IsReady == XIL_COMPONENT_IS_READY);
	Xil_AssertVoid((Channel == 1) ||
		     ((Channel == 2) && (InstancePtr->IsDual == TRUE)));

	XGpio_WriteReg(InstancePtr->BaseAddress,
			((Channel - 1) * XGPIO_CHAN_OFFSET) + XGPIO_TRI_OFFSET,
			DirectionMask);
}

主に2つの関数Xil_AssertVoidとXGpio_WriteRegが使われているようですね。
Xil_AssertVoidは引数がfalseの場合に警告を発する関数で、ドライバAPIの関数が正しく使用されているかを監視しています。

XGpio_WriteRegはGPIOの特定のレジスタに値を書き込む関数です。
実際にどんどん階層を深くたどっていくと、結局前回の記事でやったのと同じようにレジスタを直接叩いている関数であることがわかります。

XGpio_WriteReg()

機能:
 GPIOレジスタに値を書き込む。
パラメータ:
 ・BaseAddress:GPIOデバイスのベースアドレス
 ・RegOffset:ベースアドレスから書き込むレジスタのアドレスまでのオフセット
 ・Data:レジスタに書き込むデータ
返り値:なし

ひがし
ひがし

もちろん、レジスタに値を書き込むだけの単純な処理であれば、直接アドレスに値を書いたり、レジスタを意識したXGpio_WriteRegを使うことも可能です。
しかし、機能が複雑になってくると、インスタンスとして抽象化し、より機能を限定かつ明確にしたXGpio_SetDataDirectionという関数を使ったほうが、アプリケーション設計者にとって可読性が高く理解しやすいコードを書けるようになります。

ドライバAPIを用いたLED点滅アプリケーション

コード例

以下は、前回作成した”LED点滅”アプリケーションを、GPIOのインスタンスを用いて書き換えたコード例です。
このコード例を見ながら、関連するドライバAPI関数の機能を確認していきましょう。

#include "xparameters.h"
#include "xgpio.h"

XGpio LEDInst;

int main()
{
    int status;

    // Init GPIO
    status = XGpio_Initialize(&LEDInst, XPAR_AXI_GPIO_0_DEVICE_ID);
    if (status != XST_SUCCESS) {
        return XST_FAILURE;
    }

    XGpio_SetDataDirection(&LEDInst, 1, 0x00);

    int i, j;
    while(1) {
        for ( i=0; i<2; i++ ) {
        //switching on and off
        if(i==0){
            XGpio_DiscreteWrite(&LEDInst, 1, 1); //on
        }else{
            XGpio_DiscreteWrite(&LEDInst, 1, 0); //off
        }

            for ( j=0; j<100000000; j++); //Latency
        }
    }
   return 0;
}

コードの解説

このコード例で、新しく追加したドライバAPI関数はXGpio_DiscreteWriteです。
この関数もレジスタに値を書き込む関数ですが、XGpio_SetDataDirectionとの違いはデータレジスタに値を書き込む点です。

        if(i==0){
            XGpio_DiscreteWrite(&LEDInst, 1, 1); //on
        }else{
            XGpio_DiscreteWrite(&LEDInst, 1, 0); //off
        }
XGpio_DiscreteWrite()

機能:
 
ある特定のGPIOチャネルの、個々のデータレジスタに値を書き込む。
パラメータ:
 
・InstancePtr:GPIOインスタンスへのポインタ
 ・Channel:設定したいチャネル番号(1 or 2)
 ・Data:端子に書き込む値。
  例)LED[3:0]のうち、仮に上位2ビットをHigh(1)、下位2ビットをLow(0)にしたい場合は
   Dataに2進数で[1100]、つまり16進数で0x0cを代入する。
返り値:
なし

前回の記事では、以下のように定義したLEDというデータアドレスに直接値を代入していました。

#define LED      *((volatile unsigned int*) (XPAR_GPIO_0_BASEADDR + 0x00))

int main()
{
	/*略*/
  if(i==0){
    LED = 0x1; //on
  }else{
    LED = 0x0; //off
  }
  /*略*/
  return 0;
}

全く同じことをやっていますが、XGpio_DiscreteWriteというAPI関数を用いたほうが、どういった処理を行っているかがハードウェアの中身を知らない設計者にとっても理解しやすくなります。

ひがし
ひがし

ちなみに、このソースコードでも前回の記事と同様に、LEDが点滅することを確認できました。

まとめ

以上をまとめると次の通りです:

概要

Zynq内のCPUにて動作するLED点滅アプリケーションの作成を通じて以下のポイントを解説:
① インスタンスとは、IPコアをアプリケーション上でも仮想的に扱うために宣言した変数。
② アプリケーションを作成するときに、デバイスを意識せずにAPI関数だけで制御できるのが、インスタンスという概念でデバイスを抽象化するメリット。

ひがし
ひがし

ここまで読んでいただき、ありがとうございました。

次回の記事へのリンク

参考:コードの動作を確認したシステムの構成

開発環境

環境
  • 開発用PC: Windows 10, 64bit
    • Vivado Design Suite – HLx Edition – 2020.2
    • Vitis コア開発キット – 2020.2
  • 開発用基板: Zybo Zynq-7010評価ボード(Board Rev.4)
    • Zynq XC7Z010-1CLG400C

開発ボード Zybo Zynq-7010評価ボード

Vivadoのブロックデザイン

参考文献

gpio: xgpio.c File Reference
301 Moved Permanently

コメント