Jetson nano + .NET Core 3.0 でLチカを行う

##はじめに
Microsoftより今後の.NETは.NET Coreに統合されていくことがアナウンスされました。
WindowsでもLinuxでもx86でもARM64でも動作するプラットフォームは大変強力です。

クロスプラットフォームといえばPythonやJavaScriptがありますが、
動作速度が早いことや開発のしやすさ、過去の資産が豊富などC#には別の魅力があります。

今回はJetson nano に .NET Core 3.0 を導入してI/Oの制御を行ってみたいと思います。

##.Net Core 3.0実行環境の準備

### インストール
githubの**[issue](https://github.com/dotnet/announcements/issues/82)** とか **[gist](https://gist.github.com/richlander/467813274cea8abc624553ee72b28213)** に情報があります。
以下のコマンドでインストールできます。

```bash
#依存パッケージのインストール
sudo apt update
sudo apt upgrade
sudo apt -y install libc6 libgcc1  libgssapi-krb5-2 libicu60 liblttng-ust0 libssl1.0.0 libstdc++6 zlib1g

#.NetCore本体のインストール(最新版が入る)
curl -SL -o dotnet.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/Sdk/master/dotnet-sdk-latest-linux-arm64.tar.gz
sudo mkdir -p /usr/share/dotnet
sudo tar -zxf dotnet.tar.gz -C /usr/share/dotnet
sudo ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet

#インストールしたバージョンを確認
dotnet --list-sdks
```
先ほどのgithubのissueを見てみると
>Initial support for ARM64 was added in the .NET Core 2.1 release. The team is not maintaining .NET Core 2.x branches with respect to ARM64. All ARM64 improvements will be made in the .NET Core 3.0 branch (currently master).

となっているので、ARM64勢は最新版の.NET Core 3.0を使うのが良さそうです。
とは言っても上のコマンドで入るのはPreview版です。利用は自己責任ですね。(2019.5.23時点)

.NET Core 3.0は強力です。([新機能の説明](https://docs.microsoft.com/ja-jp/dotnet/core/whats-new/dotnet-core-3-0))
C#8.0対応なのも嬉しいですし、LinuxでSerialPortが使えるようになりました。
Jetson nanoとは関係ないですが、Raspberry PiのGPIOをサポートしてたり、IoT向けの用途を意識しています。

ちなみに、Raspberry Piにおいては前提になるパッケージは少し異なります。
(コマンド：```sudo apt-get install curl libunwind8 gettext apt-transport-https```)
本体インストールのコマンドはファイル名の"arm64"のところを"arm"に変更してインストールできます。


###動作確認
コンソールアプリケーションを作成して実行してみます

```bash
dotnet new console -o app
cd app
dotnet run
```
JITコンパイラが動作するため起動が若干もたつきますが、
「Hello World!」と表示されれば成功です。

###開発環境について
ここはぜひともVSCodeを使いたいところです。
がしかし、
>[WARNING]: Processor architecture 'aarch64' is not currently supported by the .NET Core debugger. Debugging will not be available.

悲しみ。

サポートされるまでは根性でデバッグするしか無いようです。
ハードウェア依存部を切り離して大半をWindows環境で開発するなどデバッグ負担を減らす工夫が必要ですね。
複雑なことをやろうとするとアレですが、まぁ、IoT機器として使うレベルの単純なハードウェア制御ならなんとかなるんじゃないでしょうか。

ちなみにですが、最近insiderリリースされたVSCodeのリモート開発機能もx86/x64しかサポートしていないのでJetsonやRPiでは使えません。

##IO制御方法について
IO制御方法はパッと思いつく方法は以下の2パターンです。

 - CPUのIO制御レジスタのアドレスを調べて直接アクセスする
 - Raspberry Piと同様に仮想ファイルでIOを制御する

このうち前者はCPUのハードウェアマニュアルが無料公開されてい様子。断念します。

###仮想ファイルによるIO制御方法の確認

以下のディレクトリ
```/sys/class/gpio/``` 
にGPIOの仮想ファイルを作成してアクセスします。
最初は、仮想ファイルは無く、exportなどのディレクトだけがある状態です。

```bash
$ ls /sys/class/gpio/
export gpiochip0 unexport
```
exportにGPIOのピン番号を書き込むと、仮想ファイルが生成されます。
GPIO12に出力する場合の例は以下の通り（実行時には、管理者権限が必要になる）

```bash
$ sudo echo 12 > /sys/class/gpio/export                 # GPIO12の仮想ファイルを生成
$ sudo echo out > /sys/class/gpio/gpio12/direction      # GPIO12を出力に設定
$ sudo echo 0 > /sys/class/gpio/gpio12/value            # GPIO12に0出力(Lo)
$ sudo echo 1 > /sys/class/gpio/gpio12/value            # GPIO12に1出力(Hi)
$ sudo echo 12 > /sys/class/gpio/unexport               # GPIO12の仮想ファイルを破棄
```

GPIO番号が異なっている以外は Raspberry Piと同じですね。


###C#によるIO制御

.NET Core 3.0ではRaspberry PiのGPIO制御がサポートされているらしいですが、GPIO番号が異なるためおそらく使えない（未検証 2019.5.23時点）ので、今回は別の方法で行います。

Raspberry Piと一緒ということは、上記のコマンドライン操作を
丸々C#で模擬することで制御できそうです。
軽く調べてみると、やはり先達がいらっしゃいます
**[【C#】.NET Core 2.0でRaspberryPi 3のGPIOを操作してLチカ！](https://qiita.com/logikuma/items/36caab945d17f78ce746)**

上記の実装を参考にGPIOクラスを軽く作ります。

詳細な実装は**[github](https://github.com/nkoppe/Jetson-RPi_GPIO_Controller)**の方を参照ください。
ポイントになる部分のみ抜き出して解説します。

####ポートの作成(コンストラクタ)
ポートの作成はコンストラクタの処理の中で実施します

```C#
public GPIO(int ioNum, Direction d = Direction.In, State s = State.Lo, bool forceCreate = false, string name = "")
{
  string DirPath = "/sys/class/gpio/gpio" + ioNum.ToString();
  if (Directory.Exists(DirPath))
  { /*ポートが存在していた時の処理(省略)*/ }
  
  //exportに番号を書き込んで仮想ディレクトリを作成する
  File.WriteAllText("/sys/class/gpio/export", ioNum.ToString());
}
```

####信号方向の設定
GPIOフォルダの中のdirectionに信号の方向を設定します

```C#
public void SetDirection(Direction d)
{ //Direction型は自分で定義した列挙型
  switch(d)
  {
    case Direction.In:
      File.WriteAllText(DirPath + "/direction", "in");
      break;
    case Direction.Out:
      File.WriteAllText(DirPath + "/direction", "out");
      break;
  }
}
```

####I/Oの端子状態を設定
端子状態を設定します

```C#
public void SetPinState(State s)
{ //State型は自分で定義した列挙型
  switch(s)
  {
    case State.Hi:
      File.WriteAllText(DirPath + "/value", "1");
      break;
    case State.Lo:
      File.WriteAllText(DirPath + "/value", "0");
      break;
  }
}
```

ここまででLチカくらいなら可能になります。
処理を色々追加すればクラスが完成です。
→**[実装例](https://github.com/nkoppe/Jetson-RPi_GPIO_Controller/blob/master/gpio.cs)**

####動作確認
以下のコードで正しく動いているか確認します。

```C#
 static void Main(string[] args)
{
  //GPIOインスタンスを作成（＝ポートを開く）
  GPIO testPin = new GPIO(12, GPIO.Direction.Out, GPIO.State.Lo, true,"test");

  //Lチカのためのタスクを作成
  var task = Task.Run(async () =>
  {
    //とりあえず10回ON/OFF
    foreach(var i in Enumerable.Range(1,50))
    {
      testPin.ToggleState();   //端子状態を反転する
      await Task.Delay(200);    //指定時間のwait
    }
  });

  //タスクを実行、完了まで待機
  task.Wait();
}
```

##最後に
次はラッパークラスを作って既存のLinuxライブラリ、できればCUDAやNEONを扱うようなものをC#から扱いたいと思います。（いつやるかは未定）