- 1回目: 開発環境の準備
- 2回目: Hello Worldプロジェクト
- 3回目: PSのGPIOでLチカ
- 4回目: PLのAXI GPIOでPSからLチカ
- 5回目: PLだけでLチカ
- 6回目: 自作IPでLチカ
- 7回目: ブートイメージを作る
- 8回目: Linux起動する
- 9回目: Linuxカーネルを少しカスタマイズする
- 10回目: LinuxのRootFSをカスタマイズする / PythonでHello World
- 11回目: LinuxユーザアプリケーションでLチカ <--- 今回の内容
- 12回目: LinuxカーネルモジュールでLチカ
- 13回目: LAN(Ethernet 0)を使う
- 14回目: Linuxユーザアプリをデバッグする / RootFSに取り込む
- 15回目: Linux起動時にアプリケーションを自動実行させる
- 16回目: Linuxから自作IPをUIOで制御する
- 17回目: Linuxで自作IPのデバイスドライバを作る
- 18回目: IoT化してスマホからLチカ
環境
- 開発用PC: Windows 10 64-bit
- Vivado 2017.4 WebPACKライセンス
- Xilinx SDK 2017.4 <- 今回は使わない
- 開発用PC (Linux): Ubuntu 16.04 本家 (日本語版じゃない) (on VirtualBox 5.2.4)
- PetaLinux 2017.4
- ターゲットボード: ZYBO (Z7-20)
Windows環境は1回目、Linux環境は8回目を参照。
使用するハードウェア(hdf)は8回目を参照。
LinuxユーザアプリケーションでLチカ
PetaLinuxツールで作られるLinux内に、ユーザアプリケーションを追加します。そのアプリケーションでLED(LD4)をチカチカさせます。内容的には、3回目: PSのGPIOでLチカと同じことを、Linuxユーザアプリからやります。
Linuxユーザアプリケーションの作り方は、基本的には、ug1144-petalinux-tools-reference-guide.pdfのガイド通りです。ただし、このガイドにも書いてあるのですが、アプリケーション自体は他の環境(XilinxSDK等)で作成して、完成したら、ここに示す手順でプロジェクトに取り込む、というのが推奨フローのようです。実際、ここで示す手順だと、非常に効率が悪いです。が、現状僕の環境だとLANが使えず、まともなデバッグもできない状況なので、ひとまずこの記事の手順でやろうと思います。
プロジェクトの準備
新規PetaLinuxプロジェクトの作成からやります。とりあえず、下記操作でBOOT.binとimage.ubを作成して、ちゃんと起動できることを確認します。これはいつも通りの手順で、8回目と同じです。
cd ~/work/peta
petalinux-create --type project --template zynq --name SimplePS
cd SimplePS/
petalinux-config --get-hw-description=../project_1.sdk
petalinux-build
petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/design_1_wrapper.bit --u-boot
シェルからLEDチカチカ
この状態で既に、GPIOにアクセスするためのsysfsインターフェースが用意されています。今回は、PSのGPIOに接続されているLED(LD4)を制御します。これはMIO7に接続されています。もともと、/sys/class/gpio/gpiochip906
が用意されているので、この906を基準に、906+7=913がMIO7用のGPIO番号になるようです。(https://stackoverflow.com/questions/40131771/sysfs-interface-i-cant-export-gpio-pins-in-a-zybo-board)
echo 913 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio913/direction
echo 1 > /sys/class/gpio/gpio913/value
echo 0 > /sys/class/gpio/gpio913/value
または、レジスタ(メモリ)に直書きでも制御できます。理由は後述しますが、GPIOへのクロック供給をONにするために、echo 913 > /sys/class/gpio/export
は必要です。レジスタと設定値については3回目をご参考ください。
echo 913 > /sys/class/gpio/export
devmem 0xE000A204 32 0x0080
devmem 0xE000A208 32 0x0080
devmem 0xE000A040 32 0x0080
devmem 0xE000A040 32 0x0000
ユーザアプリケーションを作成する
先ほどシェルからやった、LEDチカチカ制御を行うアプリケーション(Cプログラム)を作ります。下記コマンドで、myappというLinuxのユーザ空間で動くアプリケーションを作成します。コードは、project-spec/meta-user/recipes-apps/myapp/
に作られます。後で編集しますが、ひとまずはこのままにします。petalinux-build -x package
でこのアプリケーションを含むimage.ub
が再作成されます。
petalinux-create -t apps --template c --name myapp --enable
petalinux-build -x package
作成されたimage/linux/image.ub
をSDカードに上書きコピーしてZyboを起動します。(BOOT.binはそのまま)
すると、/usr/bin/myappがインストールされており、実行するとHello Worldが出力されていることが分かります。
root@SimplePS:~# ls /usr/bin/myapp
/usr/bin/myapp
root@SimplePS:~# myapp
Hello World!
コードを編集して、再ビルドする
コードを編集して、再度ビルドするには、以下のようにします(エディタは適当に置き換えてください)。どういうコードにするかは後述。
code project-spec/meta-user/recipes-apps/myapp/files/myapp.c &
petalinux-build -c myapp -x do_clean
petalinux-build -c myapp -x do_install
petalinux-build -x package
ug1144-petalinux-tools-reference-guide.pdfには、最初に一度だけ、petalinux-build -x do_populate_sysroot
とpetalinux-build -c rootfs
をやるように書いてありましたが、無くても問題ありませんでした。(影響は不明)
メモ
再ビルドする前にpetalinux-build -c myapp -x do_clean
をしないと、ビルドエラーが発生しました(原因不明)。
なお、petalinux-build -c myapp -x do_install
を省略して、petalinux-build -x package
だけでも自動的にmyappをビルドしてくれるようです。
メモ
今回は、毎回、パッケージ化されたimage/linux/image.ub
を置き換えて動作確認します。後々、myappのバイナリだけを差し替えて開発できるようにしたいと思っています(TCFやNFSなど)。その時は、petalinux-build -c myapp -x do_install
だけやればmyappのバイナリ作成が完了します。(petalinux-build -x package
もそれなりに時間がかかるので、だいぶ時間を節約できます。)
myappのバイナリ自体は、下記の場所に作られていました。ファイルが異なるものが2つ出来ていたのですが、おそらくstripされているかどうかだと思います。9回目にやったように、RootFSをSDカードにしている場合は、このバイナリをSDカードに書き込むことでも動作確認ができると思います。
- build/tmp/work/cortexa9hf-neon-xilinx-linux-gnueabi/myapp/1.0-r0/myapp
- build/tmp/work/cortexa9hf-neon-xilinx-linux-gnueabi/myapp/1.0-r0/package/usr/bin/myapp
レジスタ直叩きでLチカ
3回目でやったように、PS GPIO(MIO7)のレジスタを直叩きして制御します。コードはほとんど同じですが、Linuxユーザ空間からのアクセスになるので、mmapが必要になります。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#define REG(address) *(volatile unsigned int*)(address)
#define GPIOPS_BASE (0xE000A000)
#define GPIOPS_DATA_0 (0x0040)
#define GPIOPS_DIRM_0 (0x0204)
#define GPIOPS_OEN_0 (0x0208)
int main(int argc, char **argv)
{
printf("Hello World!\n");
int address; /* GPIOレジスタへの仮想アドレス(ユーザ空間) */
int fd;
/* メモリアクセス用のデバイスファイルを開く */
if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
perror("open");
return -1;
}
/* ARM(CPU)から見た物理アドレス → 仮想アドレスへのマッピング */
address = (int)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIOPS_BASE);
if (address == MAP_FAILED) {
perror("mmap");
close(fd);
return -1;
}
/* Set MIO7 as output */
REG(address + GPIOPS_DIRM_0) |= 1 << 7;
REG(address + GPIOPS_OEN_0) |= 1 << 7;
while(1) {
/* Set MIO7 as High */
REG(address + GPIOPS_DATA_0) |= 1 << 7;
usleep(1*1000*1000);
/* Set MIO7 as Low */
REG(address + GPIOPS_DATA_0) &= ~(1 << 7);
usleep(1*1000*1000);
}
/* 使い終わったリソースを解放する */
munmap((void*)address, 0x1000);
close(fd);
return 0;
}
image.ubを再作成して、SDカードに入れて起動します。そして、MIO7に相当するGPIOを有効にしてから、myapp
を実行します。すると、LED(LD4)が1秒間隔でチカチカするはずです。
root@SimplePS:~# echo 913 > /sys/class/gpio/export
root@SimplePS:~# myapp
Hello World!
1点注意点ですが、myapp
を実行する前には、echo 913 > /sys/class/gpio/export
でGPIOを有効化しておく必要があります。これは、デフォルトだと省電力のために、GPIOのクロックがOFFになっているためだそうです。(https://forums.xilinx.com/t5/Embedded-Linux/GPIO-access-via-mmap/td-p/672045)
ここではレジスタ直叩きしてみましたが、実際には、デバイスドライバが用意したデバイスファイル/SysFSインターフェースを使うか(次に紹介)、UIOというものを使うのが一般的なようです。
GPIOのSysFSインターフェースでLチカ
以前、他の記事で紹介しましたが、以下のようなコードでGPIOのSysFSにアクセスします。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
int main(int argc, char **argv)
{
printf("Hello World!\n");
int fd;
/* 1. GPIO913を使用可能にする */
fd = open("/sys/class/gpio/export", O_WRONLY);
write(fd, "913", 3);
close(fd);
/* 2. GPIO913を出力設定する */
fd = open("/sys/class/gpio/gpio913/direction", O_WRONLY);
write(fd, "out", 3);
close(fd);
/* 3. GPIO913に1(High)を出力する */
fd = open("/sys/class/gpio/gpio913/value", O_WRONLY);
write(fd, "1", 1);
while(1) {
write(fd, "0", 1);
usleep(1*1000*1000);
write(fd, "1", 1);
usleep(1*1000*1000);
}
/* 4. 使い終わったので閉じる */
close(fd);
return 0;
}
image.ubを再作成して、SDカードに入れて起動します。myapp
を実行すると、LED(LD4)が1秒間隔でチカチカするはずです。このコードの場合は、手動でGPIOを有効にする必要はありません。
root@SimplePS:~# myapp
Hello World!
アプリケーションを取り除く
このユーザアプリケーションを、作成するLinuxイメージ(image.ub)に含めたくないときは、petalinux-config -c rootfs
のapps設定で、myappのチェックを外します。
メモ: PL GPIOの場合
PL側のGPIOに接続されたLED(M14、M15、G14、D18)を制御する場合の方法です。
ハードウェアは、4回目で作ったものと同じ、AXI GPIOをLED(M14、M15、G14、D18)に接続したものがあるとします。AXI GPIOのBase Addressを0x41200000
とします。これをベースにPetaLinuxで作成したLinuxを起動します。
まずは、レジスタ直叩きバージョンです。PLのAXI GPIOに関しては、OSで電源管理されていないので、レジスタを叩くだけで使えます。
# set GPIO as output
devmem 0x41200004 32 0x0000
# Output High to GPIO (4-bit)
devmem 0x41200000 32 0x000F
# Output Low to GPIO (4-bit)
devmem 0x41200000 32 0x0000
SysFS経由の場合です。/sys/class/gpio/gpiochip1020
が用意されているので、それがAXI GPIOになります。念のため、more /sys/class/gpio/gpiochip1020/label
で中身を確認してみてください。オフセットをつける必要がないので、1020番をそのまま使えます。
echo 1020 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio1020/direction
echo 1 > /sys/class/gpio/gpio1020/value
echo 0 > /sys/class/gpio/gpio1020/value
アドレスや番号が違うだけで、プログラムからはPS GPIOと同様に制御できます。
メモ: ライブラリを作りたい場合
petalinux-create -t libs --template c --name mylib --enable