本記事の概要
前回作成したLED点滅回路では、GPIO(汎用I/O)からLEDに向けて信号を出力するようにしています。
そして、アプリケーションプロジェクト上で、”AXI GPIO”のレジスタに直接値を書き込むことによってLEDを点滅させました。
今回はアプリケーションプロジェクトにおけるmain関数を変更し、”AXI GPIO”をインスタンスとして扱って、ドライバAPIを活用してみようと思います。
これを例に、インスタンスの概念と”AXI GPIO”のドライバAPIの使用方法について学んでいきたいと思います。
それでは、興味のある方はぜひ最後までご覧ください!
本記事の目標
統合ソフトウェア開発環境VitisとFPGA設計環境Vivadoにおける
インスタンスの概念とGPIOのドライバAPIの使用方法について理解する
インスタンスの概念について
GPIOのインスタンス
XSAファイルを読み込んで作成したVitisのプラットフォームプロジェクトには、xgpio.hというヘッダファイルが含まれています。
xgpio.hでは次の2種類の構造体が定義されています。
インスタンス 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を代入しています。
インスタンスを使うポイントは、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を使うことも可能です。
しかし、機能が複雑になってくると、インスタンスとして抽象化し、より機能を限定かつ明確にした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
}
前回の記事では、以下のように定義した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が点滅することを確認できました。
まとめ
以上をまとめると次の通りです:
ここまで読んでいただき、ありがとうございました。
コメント