ZYBO (Zynq) 初心者ガイド (18)(完) IoT化してスマホからLチカ

- [1回目: 開発環境の準備](https://qiita.com/take-iwiw/items/966f252f6ca954aff08b)
- [2回目: Hello Worldプロジェクト](https://qiita.com/take-iwiw/items/24a8a94741fdbb80f62a)
- [3回目: PSのGPIOでLチカ](https://qiita.com/take-iwiw/items/39d2ed00fa88a1204fce)
- [4回目: PLのAXI GPIOでPSからLチカ](https://qiita.com/take-iwiw/items/a3b935a4e4dbd41593e1)
- [5回目: PLだけでLチカ](https://qiita.com/take-iwiw/items/efcdfca1866b9941fd2a)
- [6回目: 自作IPでLチカ](https://qiita.com/take-iwiw/items/b0f2ce7b4653bc6f32b9)
- [7回目: ブートイメージを作る](https://qiita.com/take-iwiw/items/ece0cdf83e6e1908fad8)
- [8回目: Linux起動する](https://qiita.com/take-iwiw/items/6e6915f7318689818368)
- [9回目: Linuxカーネルを少しカスタマイズする](https://qiita.com/take-iwiw/items/6f2644d16cbfdb021d07)
- [10回目: LinuxのRootFSをカスタマイズする / PythonでHello World](https://qiita.com/take-iwiw/items/94a90047978664159681)
- [11回目: LinuxユーザアプリケーションでLチカ](https://qiita.com/take-iwiw/items/5e4ab9a106acfda2c866)
- [12回目: LinuxカーネルモジュールでLチカ](https://qiita.com/take-iwiw/items/e7efc48d92d7b979efdc)
- [13回目: LAN(Ethernet 0)を使う](https://qiita.com/take-iwiw/items/ac489acaca1ab54d7ce8)
- [14回目: Linuxユーザアプリをデバッグする / RootFSに取り込む](https://qiita.com/take-iwiw/items/8134ca7098622219b177)
- [15回目: Linux起動時にアプリケーションを自動実行させる](https://qiita.com/take-iwiw/items/50065df6cdd1ba8bcbd6)
- [16回目: Linuxから自作IPをUIOで制御する](https://qiita.com/take-iwiw/items/da91ce4dc2a8a8df3c0a)
- [17回目: Linuxで自作IPのデバイスドライバを作る](https://qiita.com/take-iwiw/items/05eb1204cb51ce0a5c03)
- __[18回目: IoT化してスマホからLチカ](https://qiita.com/take-iwiw/items/e9311292c5078d785699) <--- 今回の内容__


# 環境
- 開発用PC: Windows 10 64-bit 
	- Vivado 2017.4 WebPACKライセンス
	- Xilinx SDK 2017.4 (C アプリケーションの実装)
	- Visual Studio Code (Pythonスクリプトの実装)
- 開発用PC (Linux): Ubuntu 16.04 本家 (日本語版じゃない) (on VirtualBox 5.2.4)
	- PetaLinux 2017.4
- ターゲットボード: ZYBO (Z7-20) 

# IoT化してスマホからLチカ
今回は最終回として、これまでやってきた内容を組み合わせて、スマートフォンからZYBO上のLEDやボタンを制御してみます。ラズパイでよくある、こういうやつです([ラズパイ + Bottleでお手軽にIoT環境を作る](https://qiita.com/take-iwiw/items/3f86281312646fd9d893))。

![01.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/6350614e-ddb9-adab-465f-6d15064aa691.jpeg)

デモ動画と作り方: https://youtu.be/z613J8RVpog
(Xilinxのブログで紹介されました！！ (https://forums.xilinx.com/t5/Xcell-Daily-Blog/Hands-on-Watch-YouTube-video-maker-takeshi-i-create-an-IoT/ba-p/822967 ))

## 全体像
全体像は以下の通りです。

![02.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/f416de8f-36ab-5576-5448-29699acafc25.jpeg)

下から見ていきます。ZYBO Z7-20ボード上には、PLに接続されたLED x 4、ボタン x 4、スイッチ x 4、カラーLED x 2と、PSに接続されたLED x 1、ボタン x 2があります。PL側のLED、ボタン、スイッチの制御用に、`myip`というIPを作ります。これは、AXI GPIOとほぼ同等ですが、練習のために自作IPとしました。また、カラーLEDのPWM制御用に`mypwm`というIPを作成します。PS側のLEDはOSの機能でそのまま制御します。ボタンは今回は使いません。(1時間くらハマったけど、ボタンのGPIO956/957の制御が出来なかった)。

自作IPである`myip`と`mypwm`は、User space IO(UIO)として制御します。それぞれ、`/dev/uio0`、`/dev/uio1`になります。これは、デバイスツリーで設定します。

自作IPの制御用に、Cアプリケーション(`myip_controller`)を作成します。入出力として名前付きパイプを持ちます。入力パイプで、LED制御コマンド(ON/OFFやカラーLEDのDuty比)を受け付けます。出力パイプで、ボタンとスイッチの状態を出力します。 

最上位層にPythonスクリプト(`ServerIoT`)がいます。`ServerIoT`は、bottleを使用してサーバーとして動作します。WebAPIとして、`setStatus`と`getStatus`をPOST(JSON)形式で提供します。`setStatus`が呼ばれたら、PLデバイスの制御コマンドをパイプ経由で`myip_controller`に出力します。PSのLEDは直接制御します。`getStatus`が呼ばれたら、パイプ経由で`myip_controller`からボタンとスイッチの状態を取得して、呼び元にJSON形式で返します。また、公開ルートパスにアクセスされたら、`index.html`を出力します。この`index.html`がユーザにとってのビューになります。`index.html`内にボタンなどが配置されており、ボタンが押されるなどのイベントに応じて、`main.js`というJavaScriptを呼び出し、このJavaScriptが先ほどのWebAPIを呼び出します。

### ポイント
実は、Cアプリケーションを省略して、Pythonだけで`/dev/uio0,1`の制御をすることも可能です。今回は練習のために、あえてCアプリケーションで実装しました。

CとPythonの連携については、Boost.Pythonやctypesを使用することもできます。この場合、C側は共有ライブラリ(.so)になります。個人的にこのパターンは以前やったことがあるのと、開発が面倒(soのテストアプリが必要)なので、今回はやめました。
代わりに、名前付きパイプを使用して、文字列でやり取りします。これによって、Python側とCアプリケーションがバイナリ的に疎結合になり、テストが非常にやりやすくなります。C側の開発をするときは、入出力がパイプなので、コンソールからのcatやechoで動作確認できます。Python側も同様です。さらに、入出力がパイプになることで、ハードウェア制御が無くなり、ホストPC上で開発/確認ができます。(ただし、パイプを使うので、Windows コマンドプロンプトだと不可。また、PS GPIOの確認はできない)

### 各ブロックの責務と詳細
- ServerIoT
	- Pythonスクリプト。WindowsPCで開発。デバッグはmsysとZYBO実機
	- サーバーとして、WebAPIとビューを提供する
	- WebAPI(setStatus)で指示されたとおりに、名前付きパイプにLED制御コマンドを出力。PS GPIOは自分で制御する
	- WebAPI(getStatus)を受けたら、名前付きパイプからボタンとスイッチの状態を取得する。その後、JSON形式にして呼び元(main.js)に返す
- myip_controller
	- C++アプリケーション。WindowsPC上のXilinx SDKで開発/デバッグ
	- 自作IP(`/dev/uio0`、`/dev/uio1`)の制御
- Linuxイメージ (カーネル/RootFS)
	- Ubuntu上のPetaLinuxで作成
	- PythonとC++ライブラリをインストール
	- ServerIoTとmyip_controllerを含む。また、これらを起動デーモンとして開始する
	- デバイスツリー設定(`system-user.dtsi`)によって、自作IPをUIOデバイスとして認識させる
- hdf
	- ハードウェア情報 (PSの設定と、PLのビットストリーム)。
	- Windows PC上のVivadoで開発

## ソースコード
- Vivadoプロジェクト
	- https://github.com/take-iwiw/ZYBO_IoT_Vivado
- Xilinx SDKプロジェクト (`myip_controller`)
	- https://github.com/take-iwiw/ZYBO_IoT_SDK
- PetaLinux プロジェクト
	- https://github.com/take-iwiw/ZYBO_IoT_PetaLinux
	- `project-spec/meta-user/recipes-apps/myinit/files/`内に、以下ファイルを配置
		- Xilinx SDKで作成済みの`myip_controller`のバイナリ
		- `ServerIoT`用のPythonスクリプトとhtml,JavaScriptファイル一式
		- init.dスクリプト

### Build方法
ハードウェア(hdf)やビルド済みのバイナリは、既にPebaLinuxプロジェクトに含まれています。そのため、ビルドするだけであれば、PetaLinux プロジェクト (https://github.com/take-iwiw/ZYBO_IoT_PetaLinux) だけあれば大丈夫です。

```sh:開発PCのターミナル
git clone https://github.com/take-iwiw/ZYBO_IoT_PetaLinux.git
cd ZYBO_IoT_PetaLinux/PjIoT
# 下記ファイルの`SERVER_URL`にZYBOのIPアドレスを設定する。コミットされているのは僕の環境の値 (192.168.1.87)
code project-spec/meta-user/recipes-apps/myinit/files/ServerIoT/static/js/main.js &
petalinux-build
petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/design_iot_wrapper.bit --u-boot
```
出来上がった、`images/linux/BOOT.bin`と`images/linux/image.ub`をFAT32フォーマットのSDカードに書き込みます。その後、同じネットワーク内にあるブラウザから`http://192.168.1.87:8080/`にアクセスします。


Pythonスクリプトを変更したい場合は、`project-spec/meta-user/recipes-apps/myinit/files/ServerIoT/`を直接編集します。

Cアプリケーション(`myip_controller`)を変更したい場合は、https://github.com/take-iwiw/ZYBO_IoT_SDK を取得して、Xilinx SDKで変更/デバッグします。出来上がったバイナリ(`myip_controller.elf`)をPetaLinuxの`project-spec/meta-user/recipes-apps/myinit/files/`にコピーします。

ハードウェアデザインを変更したい場合は、https://github.com/take-iwiw/ZYBO_IoT_Vivado を取得して、変更後、hdfを出力します。その後、PetaLinux側にコピーして、`petalinux-config --get-hw-description=.`した後、再ビルドします。



# 本プロジェクトの作り方
一からプロジェクトを始める際の、作業の流れを記載します。コードは省略します。代わりに、GitHubへのリンクを載せるようにします。

成果物ベースでのフローは以下のようになります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/214268/9dc9564c-7b36-040f-a485-af84264ffa77.png)

## 1. ハードウェアの作成  (Vivado on Windows)
Vivadoで新規プロジェクトを作成します。以下のようなハードウェアを作り、ビットストリーム付きのhdfをエクスポートしておきます。
以後、ブロックデザイン名は`design_iot`とします。そのため、出来上がるhdfは`design_iot_wrapper.hdf`として説明/コマンドなどを記載します。

- PSを配置 (Ethernet0のMDIO接続を修正)
- `myip`を作成して、配置
	- コードは、https://github.com/take-iwiw/ZYBO_IoT_Vivado/tree/master/project_iot/ip_repo/myip_1.0/hdl
	- 4-bit出力 x 1 (MY_OUT)と、4-bit入力 x 2 (MY_IN0、MY_IN1)
		- `MY_OUT = slv_reg0[3:0];`
		- `slv_reg1[3:0] <= MY_IN0;`
		- `slv_reg2[3:0] <= MY_IN1;`
	- 名前はmyip_0
	- I/O Ports設定 (制約ファイル)で、MY_OUTはLED、MY_IN0はボタン、MY_IN1はスイッチに接続する
- `mypwm`を作成して、配置
	- コードは、https://github.com/take-iwiw/ZYBO_IoT_Vivado/tree/master/project_iot/ip_repo/mypwm_1.0/hdl
	- 出力 x 6 (PWM_OUT)
	- slv_reg0[7:0] ~ slv_reg5[7:0]で各出力のDuty比を設定 (0 ~ 255)
	- 名前はmypwm_0
	- I/O Ports設定 (制約ファイル)で、PWM_OUTはカラーLEDのRGBに接続する (3色 x 2)

![03.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/9de9f3e3-c305-b5cb-f289-5611519b2f75.jpeg)

必要に応じて、この時点でシミュレーションを回して、ハードウェアが期待通りに動いているかを確認します。僕は面倒だったので、SDK上でstandaloneプロジェクトを作り、デバッグ画面でレジスタに読み書きして、期待通りの動作をしているかを確認しました。

完成した`design_iot_wrapper.hdf`を、Ubuntu側にコピーしておきます。

## 2. ベースとなるLinuxイメージの作成 (PetaLinux on Ubuntu)
Ubuntu上で、新たにPetaLinuxプロジェクトを作ります。先ほど作成した`design_iot_wrapper.hdf`を、`~/work/peta/PjIoT`にコピーしておきます。そして、このハードウェア情報を基に、プロジェクトのコンフィグを行います。(ビルドはまだ)

```sh:開発PCのターミナル
cd ~/work/peta
petalinux-create --type project --template zynq --name PjIoT
cd PjIoT/
petalinux-config --get-hw-description=.
```

ビルドに進む前に、いくつかやることがあります。
まず、デバイスツリーを変更します。`myip`と`mypwm`をUIOデバイスとします。

```txt:project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
/include/ "system-conf.dtsi"
/ {
    chosen {
        bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
    };
};

&myip_0 {
    compatible = "generic-uio";
};

&mypwm_0 {
    compatible = "generic-uio";
};
```

続いて、RootFSのコンフィグを変えて、必要なツールやライブラリをインストールします。menuconfig上で、pythonとcurlとlibstdc++をインストールします。念のためPythonは、全てのパッケージを入れておきます。(本当は、bottleが使っているのだけを入れるべき)。
設定が終わったら、ビルドします。

```sh:開発PCのターミナル
petalinux-config -c rootfs
#	Filesystem Packages  → devel  → python  → python
#	Filesystem Packages  → console  → network  → curl
#	Filesystem Packages  → misc  → gcc-runtime -> libstdc++

petalinux-build
petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/design_iot_wrapper.bit --u-boot
```

![04.jpg](https://qiita-image-store.s3.amazonaws.com/0/214268/0493611e-f9a0-c199-9b28-ad0e658f66a7.jpeg)


完成したイメージ(`images/linux/BOOT.bin`と`images/linux/image.ub`)をSDカード(FAT32)に入れて、ZYBOを起動します。pythonがインストールされていることを確認します。また、ifconfigでIPアドレスを確認しておきます。

## 3-a. Cアプリケーションの開発 (Xilinx SDK on Windows)
`myip`(`/dev/uio0`)と`mypwm`(`/dev/uio1`)を制御するアプリケーションを開発します。僕はクラスを使いたかったので、実際にはC++アプリケーションになります。
Xilinx SDK上で、メニューバー -> File -> New -> Application Projectで、以下のプロジェクトを作ります。

- ProjectName = myip_controller
- OS Platform = linux
- Processor Type = ps7_cortexa9
- Language = C++
- Linux System Root / Linux Toolchain = 非チェック

ここ(https://github.com/take-iwiw/ZYBO_IoT_SDK/tree/master/myip_controller )にあるようなコードを実装/ビルドします。その後、TCFでデバッグします。デバッグ方法についてはこちら(https://qiita.com/take-iwiw/items/8134ca7098622219b177 )を参考にしてください。

デバッグ開始前に、ZYBO上で、下記コマンドで名前付きパイプを作っておきます。

```sh:ZYBO上のターミナル
mkfifo pipe_c2p
mkfifo pipe_p2c
chmod 666 pipe_p2c
chmod 666 pipe_c2p
```

パイプによるインターフェースは以下のような文字列にします。

- pipe_p2c (Python → CアプリへのLEDの制御コマンド)
	- "ON,OFF,ON,OFF,100,100,100,100,100,100"
	- ↑はそれぞれ、LD0,LD1,LD2,LD3,R0,G0,B0,R1,G1,B1、に対応。Duty比は0～100
- pipe_c2p (Cアプリ → Pythonへのボタンとスイッチの状態通知)
	- "{"btn0":"OFF", "btn1":"OFF", "btn2":"OFF", "btn3":"OFF", "sw0":"OFF", "sw1":"OFF", "sw2":"OFF", "sw3":"OFF"}"

そのため、ZYBOのターミナル上で、以下のようなコマンドによって動作確認をします。

```sh:ZYBO上のターミナル(デバッグコマンド例)
echo "ON,OFF,ON,OFF,100,100,100,100,100,100" > pipe_p2c
cat pipe_c2p
```

このように、名前付きパイプと文字列によるシンプルなインターフェースを採用したため、Python側のコーディングの前に、ほぼ本番と同じ状態でのテストが可能となりました。


## 3-b. Pythonアプリケーションの開発
WebサーバーとなるPythonスクリプトを開発します。開発場所はどこでもいいです。僕は、最初はWindows上で開発していましたが、途中から動作確認は直絶ZYBOで行いました。Windows上でVSCodeでコーディング → SFTP(拡張機能)でZYBOに自動コピー → ZYBOで実行、という流れです。ただし、ZYBOを再起動したらファイルが消えるのでご注意ください。

コード一式は、こちら(https://github.com/take-iwiw/ZYBO_IoT_PetaLinux/tree/master/PjIoT/project-spec/meta-user/recipes-apps/myinit/files/ServerIoT )にあります。WebサーバやPythonなどの詳細はこの記事(https://qiita.com/take-iwiw/items/3f86281312646fd9d893 )をご参考にしてください。

見た目をよくするために、Bootstrapを取り込んでいます。また、Bottleは、bottle.pyをファイルとして直接配置しています。これは、ZYBO上で追加パッケージのinstallなどをしたくないためです(だから、1ファイルで済むbottleを使用することにしました)。

ある程度出来上がったら、先ほどのCアプリケーションを実行して、結合確認をしてみます。スマホやPCのブラウザから、http://192.168.1.87:8080/ にアクセスして、LED等の制御ができたらOKです。

## 4. 全てを含んだLinuxイメージの作成 (PetaLinux on Ubuntu)
ここまでの開発では、CアプリケーションはelfバイナリをTCFでデバッグ、PythonスクリプトはSFTPでアップロードしていました。今使用しているイメージでは、RootFSはinitramfsとして、DDR上にマウントされるので、再起動のたびに消えてしまいます。また、実行するのに、毎回手間がかかります。

必要なバイナリやスクリプトを含み、かつ、それらを起動時に自動実行するようにします。なお、Cアプリケーションについては、PetaLinuxでビルドするのではなく、Xilinx SDKでビルド済みの実行ファイルを取り込むことにします。
基本的には、[15回目: Linux起動時にアプリケーションを自動実行させる](https://qiita.com/take-iwiw/items/50065df6cdd1ba8bcbd6)、の通りです。以下コマンドで、myinitという起動用のinit.dデーモンを作ります。レシピと、shスクリプトを編集する必要があります。中身は、(https://github.com/take-iwiw/ZYBO_IoT_PetaLinux/tree/master/PjIoT/project-spec/meta-user/recipes-apps/myinit )をご確認ください。


```sh:開発PCのターミナル
petalinux-create -t apps --template install -n myinit --enable
code project-spec/meta-user/recipes-apps/myinit/myinit.bb &
code project-spec/meta-user/recipes-apps/myinit/files/myinit &
code project-spec/meta-user/recipes-apps/myinit/files/myinit_run.sh &
```

`petalinux-create`で`myinit`を作ったら、Xilinx SDKでビルド済みの`myip_controller.elf`と、Pythonスクリプト一式をフォルダ(ServerIoT)ごと、`project-spec/meta-user/recipes-apps/myinit/files/`にコピーしておきます。

レシピと、shスクリプトの編集と、ファイルのコピーが終わったら、下記コマンドで、image.ubを再作成します。

```sh:開発PCのターミナル
petalinux-build -c rootfs
petalinux-build -x package
```

### メモ
本来、Cアプリケーション(自作IPの制御)とPythonスクリプト(サーバー)は、別々のデーモンとして動かすべきです。が、今回は横着をして、[同一のスクリプト(`myinit_run.sh`)](https://github.com/take-iwiw/ZYBO_IoT_PetaLinux/blob/master/PjIoT/project-spec/meta-user/recipes-apps/myinit/files/myinit_run.sh) 内で、Cアプリケーション(自作IPの制御)をバックグラウンドとして動かすことにしました。
なお、名前付きパイプの作成も、`myinit_run.sh`内で行っています。

Pythonスクリプト(サーバー)の方は、1ファイルだけではなく、htmlなど、複数のファイルを含むディレクトリをインストールする必要があります。そのため、レシピ内で少し工夫が必要でした。(https://github.com/take-iwiw/ZYBO_IoT_PetaLinux/blob/master/PjIoT/project-spec/meta-user/recipes-apps/myinit/myinit.bb )。ただ、この方法で正しいかは不明。


## 動作確認する
作成したimage.ubを、SDカードに上書きコピーして、ZYBOを起動します。

すると、電源ONするだけで、http://192.168.1.87:8080/ のページが有効になり、LED制御などが出来るはずです。


# おわりに
今回は最終回として、今までやってきた内容をほぼ全て詰め込んでみました。また、ZYBOをIoTっぽく使うときのベースとなるプロジェクトにできたと思います。

本連載はこれで終わりです。ツールのインストールから始まり、IPの作成、ベアメタル実装、Linuxまで、一通りの内容を抑えることが出来たと思います。