Orange Pi Zero Armbian導入後の設定とmmapなLチカ

# 導入
Orange Pi ZeroとArmbianのインストールについては「[Orange Pi Zero に Armbian をインストールしてみた](http://qiita.com/mabomabo4/items/e7e22fd62999bf181ab5)」を参考にしました。使ったイメージはUbuntu Xenial（Armbian_5.25_Orangepizero_Ubuntu_xenial_default_3.4.113.img）です。

当初は[本家](http://www.orangepi.org/downloadresources/)のLubuntuあたりを入れるつもりだったんですが、Google Driveのリンクが壊れてる、残されたBaidu Cloudからのダウンロードもファイルサイズが大きいからと専用ソフトのインストールを迫ってくる、という状況（注）で、流石に嫌だな、と思いArmbianを選びました。

（注）Armbianインストール中に、実はLinuxからBaidu Cloudにいけばツールのインストールなしにダウンロードできる事がわかりました。ここで得られたリンクはreferrerさえ与えれば別のマシンからも……つまりダBaidu CloudのダウンロードページからDevTools開いてlocation.hrefを書き換えてダウンロード開始できました。

# 最初のログイン
## UART
公式の写真みると、LANポートの脇に「Debug serial port」と書かれたスペースがあり、ここにピンが3本立ってます。これがGND/TX/RXであろう事は想像できるのですが、どれがどのピンか見極めるには少し時間がかかりました。

結論から言えば、外側からGND/RX/TXの順に並んでいます。

公式のmechanical drawingデータ（dwgファイル）をみると、大きく拡大するとこっそり端子名がコメントされてました。あと[linux-sunxi](http://linux-sunxi.org/Xunlong_Orange_Pi_Zero)にも情報あり。

電源投入と同時に115200bpsでU-Bootのログが流れていき、そのままLinuxのログインまでいけます。Linuxのブートが始まる前にキー入力を入れればU-Bootのモニタに落ちて遊べます。ここで起動してるU-BootはSDカード内の決め打ちセクタに入ってる物がロードされるらしいのでArmbian付属のU-Bootという事になります。例によってU-Boot Applicationの実行とかも難しくなさそうです。

## micro USB
デバイスをmicro USBでPCに繋いでいれば、Armbian起動後にはOrange Pi Zero本体がUSBシリアルとして認識されるようです。うちのmacOSでは`/dev/cu.usbmodem******`という名前で見えていました。後ろの`*`6個は実際には数字です。何を基準にしてつく数字か不明なため、とりあえず念のために伏せ字。起動するまで使えないのでトラブルシュートには使えませんが、特別な設備なしに接続できるのは便利。シリアルで繋げば普通にログインできます。

# ネットワーク設定
WIFIはついているのですが、技適を通ってないので普通に使うのはNG。ちなみに、/etc/network/interfacesに書かれているとおり、ネットワーク周りの設定はnetwork-managerが管理しているので、ヘッドレス運用の場合はnmtuiを使えば簡単に設定できます。僕は最初にいじった際はいきなりpanicでrebootされた。micro USBから入ってると突然反応なくなって何が置きたかわからないので注意。あと、高負荷（高音）時に不安定になってpanicするのは主にWIFI周りな気がしていて。ここは清く正しく殺しておくのが正義かと思います。

# 少しハードなLチカを試す
## その前に清く正しい方法についておさらい
普通にGPIOを操作する分には、ドライバを経由して叩けばOK。LEDなんかは`/sys/class/leds/`、それ以外の用途でも`/sys/class/gpio_sw/`に見えているデバイスファイルを叩けば、だいたい用は足りるはず。

RasPiでも使われているWiringOPも移植されているようですが、特にZero対応をしているわけでもないようなので、Orange Pi PCと互換性ある範囲でしか正しく動かないかも。

## Bare Metalに近い方法
### 概要
openocdにはRasPiのGPIOを使ってソフトでJTAGを駆動するドライバがあるんだけど、中を見たら面白い事をやってました。基本方針は`/dev/mem`を開いてmmapでpio空間を直接ユーザ空間にマップして叩く、それだけ。なんてお行儀の悪い！速度重視の場合や、リスク承知で色々と試したい時にはアリかもしれません。この手のIoTデバイスならありかなー、って事でOrange Piでも似たような事を試してみました。

### LEDの配線について調べる
まずはLEDがどこに繋がっているのか調査。公式の回路図を見てみたんだけど、ところどころ間違いがあったので正誤表。

まず、GPIO ASSIGNMENTのページ。PA15: STATUS-LED、PA17: SPDIF、PL10: PWR-LEDとなっていますが、STATUS-LEDはPA15ではなくPA17です。CPUのピン配置を図示したページにはGPIOのところに`STATUS-LED <<---- C15 ---- PA16/SPDIF_OUT/PA_EINT17`と書かれていました。ありゃ、最終的にSPDIFは潰されちゃったって事ですかね。

また、LEDの図には
`PWR-LED <<------- LED1 ----|> D7 LED-R`
`STATUS-LED <<---- LED2 ----|> D7 LED-G`
と書かれているのですが、実際にはPWR-LEDが緑で、STATUS-LEDが赤です。

### レジスタについて
Allwinner H3のデータシートを見れば書かれています。Zeroに乗っているCPUはH2+ですが、これはH3のvariantだそうです。HDMI周りの機能を落とした省電力版みたいな事がどこかに書いてありました。それってH3-じゃないのか。

本来、機能割り当てや入出力設定、プルアップ・ダウンの設定などが必要ですが、ここでは一応裏でドライバが動いている事ですし、正しく設定済みと仮定してピンの値を上げ下げするのみに留めて置きます。

PAからPGまでのGPIOについては0x01c20800から0x24バイトずつ設定用レジスタが並んでいます。PLについては少し離れた場所にあり、0x01f02c00からの0x24バイトになります。各設定レジスタはC的に書くと以下のような構造になります。

```c
struct pio_cfg {
  volatile uint32_t cfg[4];
  volatile uint32_t dat;
  volatile uint32_t drv[2];
  volatile uint32_t pul[2];
};
```

設定済みのGPIOに対して値を書き換えるだけなら`dat`に書き込むだけで対応できます。mmapする際の注意点としてページ単位でマップしないといけないため、純粋に設定レジスタの値からマップする事はできません。具体的なコードは以下のようになります。

```c
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>

#define GPIO_PA_BASE 0x01c20000
#define GPIO_PA_OFFSET 0x0800
#define GPIO_PL_BASE 0x01f02000
#define GPIO_PL_OFFSET 0x0c00

struct pio_cfg {
  volatile uint32_t cfg[4];
  volatile uint32_t dat;
  volatile uint32_t drv[2];
  volatile uint32_t pul[2];
};

int main(int argc, char** argv) {
  int mem = open("/dev/mem", O_RDWR | O_SYNC);
  if (mem < 0) {
    perror("open /dev/mem");
    return -1;
  }

  char *pa_base =
      mmap(NULL, sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE, MAP_SHARED,
           mem, GPIO_PA_BASE);
  struct pio_cfg* pa = (struct pio_cfg*)&pa_base[GPIO_PA_OFFSET];

  char *pl_base =
      mmap(NULL, sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_WRITE, MAP_SHARED,
           mem, GPIO_PL_BASE);
  struct pio_cfg* pl = (struct pio_cfg*)&pl_base[GPIO_PL_OFFSET];

  for (;;) {
    pa->dat |= (1 << 17);  // PA17 STATUS-LED ON
    pl->dat |= (1 << 10);  // PL10 PWR-LED ON
    usleep(500000);
    pa->dat &= ~(1 << 17);  // PA17 STATUS-LED OFF
    pl->dat &= ~(1 << 10);  // PL10 PWR-LED OFF
    usleep(500000);
  }

  return 0;
}
```

実行にはroot権限が必要ですが、うまく動いているようです。ドライバ側と整合性を取るにはドライバ側のトリガ設定をnoneにしてあげないとかな、とか思ってたんですが、外部で書き換えるとドライバ側の設定は自動的にnoneになるようです。わりと良く出来てた。

という事で、似たような感じでOrange PiのGPIOもopenocd対応できるんじゃないかなー、とか思いつつあります。USBで刺したらそのままターミナルソフトから入ってopenocdを起動、とか。わりと便利そうじゃないですか。