仮想 IoT デバイスを Athrill で作る！

#仮想環境で IoT 組込み開発
組込みマイコン開発では実機を使ってドライバ/アプリのデバッグをするのが普通のスタイルだと思います．PoCであれば，ラズパイやWio LTE等を使えばそんなに苦労なく開発ができることでしょう．

たぶん，この記事をチラ見された方は，「IoTで仮想環境なにそれ？おいしいの？」と感じておられるのではないかと思いますが，あえて仮想環境で IoT 開発の話をさせていただきます！

#デモ
まずは，この動画をご覧ください．
![demo1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/81486110-a56a-6383-7484-f66662a5b6e4.gif)

画面左は，GPS位置情報を Google Map と紐づけてリアルタイムに位置表示する Unity アプリです(自作)．画面右は，TOPPERS/箱庭 単体ロボット向けシミュレータです．

デモの内容としては，「ロボットの位置情報をGPSアプリに通知して表示する」という単純なものなのですが，その作りは結構奥深いです．

#プログラム構成
プログラム構成はこんな感じです．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/dcbfbb71-b810-1ad7-1c91-42f8719f8cdb.png)

ご覧の通り，クラウド/サーバー介さずに１個のパソコン(Windows/WSL)上で，GPS位置情報の動作確認テストができています．さらに注目すべきポイントは，マイコン側のプログラムは，マイコン・シミュレータ([Athrill](https://www.toppers.jp/athrill.html))上で動作しており，マイコン上で動作するアプリケーションおよびドライバ開発がこの仮想環境上で出来てしまうのです！

つまり，クラウド側のアプリとマイコン側のアプリ/ドライバが同じ仮想環境上で開発できてしまうのですよ．

#Athrill
ここから本題です．こんな素敵な開発を実現してくれる仮想化技術の紹介です．
[Athrill](https://www.toppers.jp/athrill.html) は，[TOPPERS](https://www.toppers.jp/) から一般公開されているオープンソースベースのマイコン・シミュレータです．どなたでも自由に利用いただけますし，ソース改修も自由です．

また，Athrill は，マイコン単体のシミュレーションに留まらず，様々な要素技術([Unity](https://unity.com/ja) や [RDBOX](https://github.com/rdbox-intec/rdbox)，[mROS](https://github.com/tlk-emb/mROS)等)と連携したシミュレーションもできます．


余談ですが，「IoT/クラウド時代に求められるシミュレーションとは何か？」の問いに応えてくれた有志が集まって，[箱庭](https://toppers.github.io/hakoniwa/)というTOPPERSワーキンググループができました．箱庭という次世代シミュレーション構想の中で，Athrill はその要素技術の一つでもあります．

#デバイスプラットフォーム Athrill
そんな Athrill ですが，次世代シミュレーション環境として求められるであろう機能を検討したところ，IoT のような開発で求められるのは様々な周辺デバイス(温度センサ, 加速度センサ，GPS, LTE,...)を手軽に試せるシミュレーション環境ではないか？と思い至りました． 

そのために必要な Athrill 機能とは何かというと，様々なデバイスを手軽に開発できて，デバッグ/実行できるようなプラットフォームだと気づきました．つまり，こんなプラットフォームです．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/3c86f697-2c7c-f2db-3afd-e27ad31e7124.png)

今回のデモで作成したデバイスは，GPSとLTEです．GPSデバイスについては，以下で一般公開しております．

https://github.com/toppers/athrill-device/tree/main/device/gps

なお，プラグインとして開発できるようにするために，一般的なデバイスが求める共通機能は Athrill 側でデバイス共通機能として利用できるようにしています．デバイス側は，これらの共通機能を利用しながら，開発対象デバイス側で必要な機能実装をするというわけです．

#サンプルデバイスで Hello World
具体的なデバイス作成方法の理解を深めるために，Hollo World やってみましょう．

##準備
まず，以下のリポジトリをクローンしましょう．

```bash
$ cd <work_dir※作業フォルダを作ってください>
$ git clone https://github.com/toppers/athrill.git
$ git clone https://github.com/toppers/athrill-target-v850e2m.git
$ git clone https://github.com/toppers/athrill-device.git
```

次に，athrillをビルドします．

```bash
$ cd athrill-target-v850e2m/build_linux
$ make timer32=true serial_fifo_enable=true
```

ビルド成功すると，athrill2 というバイナリができていますので，以下のコマンドで確認してください．

```bash
$ ls -l ../../athrill/bin/linux/athrill2
```

##デバイス作成方法
さて，ここから athrill に追加するデバイスの作成方法です．
以下に，単純なサンプルデバイスを用意していますので，そのプログラムを見ながら説明します(100行程度です)．

```bash
athrill-device/device/sample/sample_device.c
```

このファイルの中で，athrill のプラグインとしてデバイス登録するためのデータが以下です．athrill は，**athrill_ex_device** というシンボル名を検索し，そのデータの妥当性チェック後，問題なければ，デバイス初期化関数の呼び出しおよび定期的なクロック供給を行います．

```C
/**************************************
 * START: external symbols
 **************************************/
AthrillExDeviceType athrill_ex_device = {
                .header.magicno = ATHRILL_EXTERNAL_DEVICE_MAGICNO,
                .header.version = ATHRILL_EXTERNAL_DEVICE_VERSION,
                .header.memory_size = EX_DEVICE_MEMORY_SIZE, /* KB */
                .datap = ex_device_memory_data,
                .ops = &ex_device_memory_operation,
                .devinit = ex_device_init,
                .supply_clock = ex_device_supply_clock,
};
/**************************************
 * END: external symbols
 **************************************/
```

初期化関数は，`devinit` メンバに登録します．
クロック供給関数は，`supply_clock` メンバに登録します．
また，デバイスレジスタの I/O ですが，`datap` でアクセス領域のメモリ割り当てし，I/O用のフック関数を `ops` メンバに登録します．

##サンプルデバイス実装例
では，それぞれの登録関数の実装例を見ていきましょう．

## devinit 関数
まず，初期化関数 `devinit` の実装例です．
本初期化関数は，athrill 起動時に１回だけ呼び出されます．このとき，athrill が提供する共通機能を `athrill_ops` で渡していますので，デバイス処理実装時に利用できるように，グローバル変数(athrill_ex_devop )にアドレスコピーしています．
 
```C
static void ex_device_init(MpuAddressRegionType *region, AthrillExDevOperationType *athrill_ops)
{
        athrill_ex_devop = athrill_ops;
        printf("SAMPLE_DEVICE: init\n");
        return;
}
```

## supply_clock 関数
次に supply_clock の実装例です．
本関数は，athrill のシミュレーション時間が 1 clock 進むたびに呼び出されるものです．本実装例では，10,000 クロック後に，Hello World を出力していますね．

```C
static void ex_device_supply_clock(DeviceClockType *dev_clock)
{
        if (dev_clock->clock == 10000) {
                printf("SAMPLE_DEVICE: Hello World!\n");
        }
        return;
}
```

## デバイスレジスタ I/O 関数
デバイスレジスタへの I/O 関数には，サイズ(1byte, 2byte, 4byte)毎に read関数/write関数を登録します．

この例では，1byte のwrite 関数の実装例です．本デバイスレジスタへの書き込みがあった場合，そのアドレスと書き込みしたデータの値を表示するようにしています．


```C
static Std_ReturnType ex_sampledev_put_data8(MpuAddressRegionType *region, CoreIdType core_id, uint32 addr, uint8 data)
{
        uint32 off = (addr - region->start);
        *((uint8*)(&region->data[off])) = data;
        printf("SAMPLE_DEVICE: put8() addr=0x%x data=0x%x(%c)\n", addr, data, data);
        return STD_E_OK;
}
```

##サンプルデバイスのビルド
このサンプルデバイスのビルドは make を叩くだけです．
成功すると devsample.so ファイルができます．

```bash
$ make clean;make
rm -f sample_device.o devsample.so
gcc -O3 -c -g -Wall -Wunknown-pragmas -Wimplicit-int -Wtrigraphs -std=gnu99 -mtune=native -march=native -mfpmath=both  -I../../../athrill/src/inc -I../../../athrill/src/cpu -I../../../athrill/src/bus -I../../../athrill/src/device/mpu -I../../../athrill/src/device/peripheral/serial/fifo -I../../../athrill/src/lib -fPIC -DOS_LINUX -DATHRILL_EXT_DEVICE sample_device.c
gcc -shared -fPIC  -o devsample.so sample_device.o
```

##サンプルプログラム作成
さて，デバイスができましたので，そのデバイスを動かすマイコン側のプログラムを作成しましょう．場所は，以下にあります．

```
athrill-device/demo/sample/v850/main.c
```

サンプルプログラムの内容は以下の通りです．デバイスレジスタアドレスに，"Hello World" を書き込み，その書いたアドレスの内容をシリアルで出力させています．

```C
#define SAMPLE_DEV_ADDR ((volatile char*)0xFF100000)
int main(void)
{
        volatile char *p = SAMPLE_DEV_ADDR;

        p[0] = 'H';
        p[1] = 'e';
        p[2] = 'l';
        p[3] = 'l';
        p[4] = 'o';
        p[5] = ' ';
        p[6] = 'W';
        p[7] = 'o';
        p[8] = 'r';
        p[9] = 'l';
        p[10] = 'd';
        p[11] = '\n';
        while (TRUE) {
                test_print((const char*)SAMPLE_DEV_ADDR);
                do_halt();
        }
        return 0;
}
```

このプログラムのビルドは make 実行するだけす．成功すると，test_main.elf ファイルが作成されます．

##サンプルプログラム実行
では，サンプログラムを実行してみましょう．
その前に，athrill に渡すメモリ配置構成定義ファイル(memory.txt)に，サンプルデバイスのレジスタアドレス先頭番地(0xFF100000)と，先ほど作成したサンプルデバイス(devsample.so)のパスを追加しておきます．

```
DEV,  0xFF100000, ../../../device/sample/devsample.so
```

athrill 実行コマンドは以下の通りです．

```bash
$ ../../../../athrill/bin/linux/athrill2 -t -1 -c1 -m memory.txt -d device_config.txt test_main.elf
```

実行すると以下のような起動ログが出力されます．

```bash
：
RAM : START=0x5ff7000 SIZE=512
DEV : START=0xff100000 SIZE=2
：
SAMPLE_DEVICE: init
```

上記の通り，デバイスのアドレスが認識され，サンプルデバイスの初期化が行われていることがわかります．

そして，サンプルデバイスの Hello World が出力されました．

```bash
SAMPLE_DEVICE: Hello World!
```

また，サンプルプログラムの実行した Hello World のレジスタ書き込みは，以下のようにサンプルデバイス内でフック検出できています．

```bash
SAMPLE_DEVICE: put8() addr=0xff100000 data=0x48(H)
SAMPLE_DEVICE: put8() addr=0xff100001 data=0x65(e)
SAMPLE_DEVICE: put8() addr=0xff100002 data=0x6c(l)
SAMPLE_DEVICE: put8() addr=0xff100003 data=0x6c(l)
SAMPLE_DEVICE: put8() addr=0xff100004 data=0x6f(o)
SAMPLE_DEVICE: put8() addr=0xff100005 data=0x20( )
SAMPLE_DEVICE: put8() addr=0xff100006 data=0x57(W)
SAMPLE_DEVICE: put8() addr=0xff100007 data=0x6f(o)
SAMPLE_DEVICE: put8() addr=0xff100008 data=0x72(r)
SAMPLE_DEVICE: put8() addr=0xff100009 data=0x6c(l)
SAMPLE_DEVICE: put8() addr=0xff10000a data=0x64(d)
SAMPLE_DEVICE: put8() addr=0xff10000b data=0xa(
)
```

ここで書き込みした値を，参照⇒シリアル出力した結果は以下の通りで，期待したものです！

```bash
Hello World
```

#今後について
様々なIoTデバイスを手軽に追加できるデバイスプラットフォームとしてのマイコンシミュレータ Athrill の紹介をさせていただきました．

今後は，これらのデバイスの数を増やしていくと共に，Athrill として備えるべき共通機能の拡充を図りたいと考えています．乞うご期待ください！