IchigoJamからISPで自分のファームウェアを書き込む

# 何をするか

IchigoJam (Rではない) のシリアル通信機能を利用し、
プログラムを実行中の石に書き込まれているファームウェアを別のIchigoJam (Rではない) 互換機に書き込みます。

:::note warn
「ファームウェア」と書いていますが、今回はユーザプログラムを含むFlash全体の内容をコピーします。
コピー先に既にIchigoJamのユーザプログラムが保存されている場合、消去・上書きされます。
:::

# Flash書き込みの手順

IchigoJam (Rではない) のFlashに書き込む方法は、『LPC111x/LPC11Cxx User manual』(UM10398) の
Chapter 26: LPC111x/LPC11Cxx Flash programming firmware に載っています。

これを参考に、以下の手順でIchigoJam (Rではない) Flashに書き込むことができます。
なお、ここで「改行」はCRLFを表します。
また、数値は十進数で送受信します。

1. ハードウェアを準備する
   1. 電源とシリアル通信を接続する
   2. `PIO0_1`ピンをLOWにする
2. 通信を初期化する
   1. 起動する (電源投入 or リセット解除)
   2. `?` を送信する (通信速度は自動で設定される)
   3. `Synchronized` と改行を受信する
   4. `Synchronized` と改行を送信し、`OK` と改行を受信する
   5. クロック周波数(例えば`12000`)と改行を送信し、`OK` と改行を受信する
3. Flashへの書き込みを許可させる
   1. `U 23130` と改行を送信し、`0`と改行を受信する (Unlock)
4. Flashの内容(全て)を消去する
   1. `P 0 7` と改行を送信し、`0`と改行を受信する (Prepare sector(s) for write operation)
   2. `E 0 7` と改行を送信し、`0`と改行を受信する (Erase sector(s))
5. Flashにデータを書き込む (これを繰り返す)
   1. RAMに、Flashに書き込むデータを書き込む
      1. `W (書き込み先) (書き込むバイト数)` と改行を送信し、`0`と改行を受信する
      2. 書き込むデータを[uuencode](https://ja.wikipedia.org/wiki/Uuencode)して送信する
         * 1行の長さは61バイト以下 (改行文字は除く)
         * 20行送信するか最後まで送信したら、送信したバイトの合計と改行を送信し、`OK`と改行を受信する
   2. `P (セクタ番号) (セクタ番号)` と改行を送信し、`0`と改行を受信する
   3. `C (書き込み先) (書き込むRAMのアドレス) (書き込むバイト数)` と改行を送信し、`0`と改行を受信する  
      (Copy RAM to flash)

# 実装

タイトルと、接続の方法を書いておきます。

```
10 ' ﾌｧｰﾑｳｪｱ ｶｷｺﾐ
20 'OUT1->RESET,OUT2->PIO0_1
```

自分のFlashの内容を読み取るためのマシン語を用意します。

<details><summary>アセンブリコード</summary><div>

```
' R0 で指定したアドレスから256バイトを、#780 にコピーする
R1 = @DATA
R2 = 0
@LOOP
R3 = [R0 + 0]L
[R1 + 0]L = R3
R0 += 4
R1 += 4
R2 += 1
R2 - 64
IF CC GOTO @LOOP
RET

ORGR #80
@DATA
```

</div></details>

```
30 POKE#700,31,161,0,34,3,104,11,96,4,48,4,49,1,50,64,42,248,211,112,71
```

CRLFを送信します。

```
130 ?CHR$(13)
```

データを`E+1`行受信します。
ただし、1行の受信に1.5秒以上かかった時は、受信を打ち切ります。

```
140 P=0:CLT
150 K=INKEY():IFK>31POKE#740+P,K:P=P+1ELSEIFK=10IFEE=E-1:GOTO140ELSERETURN
160 IFTICK()>89E=0:RETURNELSEGOTO150
```

データを受信し、それが期待したものかをチェックします。
170行目では、受信したデータが`OK`かをチェックします。
180行目では、受信したデータが`0`かをチェックします。
データが期待したものでなかった場合、書き込み対象をリセットし、`Failed!`を出力して終了します。

```
170 GOSUB130:IFP=2ANDPEEK(#740)=79ANDPEEK(#741)=75:RETURNELSEGOTO190
180 GOSUB130:IFP=1ANDPEEK(#740)=48:RETURNELSEGOTO190
190 OUT1,0:?"Failed!"
```

`?`を送信し、1行読み込みます。
`Synchronized`が送られてくるはずですが、1.0.0で実際に試してみると途中までしか読み込めませんでした。

```
40 OUT1,0:OUT2,0:OUT1,1?"?";:E=0:GOSUB140:
```

`Synchronized`と改行を送信し、2行読み込みます。
ここでは送信した内容もそのまま送り返されてくるので、その分を含めた2行となっています。
`OK`が送られてくるはずですが、うまく読み込めなかったため、チェックを無効にしています。
また、`Synchronized`を一気に送信してしまうとうまくいかなかったため、ウェイトを挟んでいます。
(本当はソフトウェアフロー制御(XON/XOFF)に対応しないといけないとされていますが、サボっています)

```
?"Synchr";:WAIT9:?"onized";:E=1:GOSUB130:
```

`12000`(クロック周波数)と改行を送信し、2行読み込みます。
ここでは`OK`がうまく読み込めるようだったので、チェックを有効にしています。
ただし、そのままだと1.3.1で`12000`と`OK`が1行として読み込まれてしまい失敗したので、
ここにもウェイトを挟んでいます。

```
?"12000";:WAIT9:E=1:GOSUB170:
```

Echoコマンドを送信し、送信した内容が送り返されないようにします。
ここまでは送信した内容が送り返されるので、その分を含め2行読み込みます。
このコマンドの実行後は1行読み込めばよく、読み込み処理の後は`E=0`となるため`E`の設定を省略できます。

```
?"A 0";:E=1:GOSUB180
```

Flashへの書き込みを許可させ、全部のデータを消去します。

```
50 ?"U 23130";:GOSUB180:?"P 0 7";:GOSUB180:?"E 0 7";:GOSUB180:
```

Flashに書き込むデータを自分のFlashから読み込み、転送していきます。
読み込みは256バイト単位で行うことにしました。
転送は、256の約数であり、かつ1行で転送できる32バイト単位で行うことにしました。

```
FORI=0TO#7F00STEP#100:X=USR(#700,I):FORJ=0TO#E0STEP32
```

データのRAMへの転送を開始します。
転送先のアドレスの下位の数字として`J`を使うと`J`が小さい時桁数が少なくなって失敗するので、
`J+100`を用いて桁数が変わらないようにしました。

```
60 ?"W 268436";J+100;" 32";:GOSUB180:
```

uuencodeの送信を開始します。
uuencodeでは、3バイトをひとかたまりとして送信します。
行の最初の`@`は、その行に32バイトのデータがあることを表します。

```
?"@";:S=0:FORK=0TO30STEP3
```

uuencodeへのエンコードを行い、データを送信します。
データは`# 780`～`# 880`に書き込まれており、配列・変数領域の前半部分も用いているので、
作業用にはそれと被らない配列の後ろの方を用います。
70行目で1個ずつ`# 780`を足したり、80行目で1個ずつ`63`とのANDを取ったりすると長くなってしまうので、
1箇所にまとめています。

```
70 M=#780+J+K:A=PEEK(M):B=PEEK(M+1):C=PEEK(M+2)*(K<30):S=S+A+B+C
80 [64]=A/4:[65]=A<<4|B>>4:[66]=B*4|C>>6:[67]=C
90 FORL=64TO67:V=[L]&63:?CHR$(32+V+64*!V);:NEXT
```

送信したバイトの合計を送信し、`OK`を受信します。
データの送信を繰り返し、256バイト転送します。

```
100 NEXT:?CHR$(13):?S;:GOSUB170:NEXT
```

転送したデータをFlashに書き込みます。

```
110 T=I/#1000:?"P ";T;" ";T;:GOSUB180:?"C ";I;" 268436100 256";:GOSUB180
```

データの読み取り・転送・書き込みを繰り返します。
最後まで書き込んだら、`Done!`を出力し、書き込み先をリセットして起動します。

```
120 NEXT:OUT1,0:?"Done!":OUT2,1:OUT1,1:END
```

# コード全体

```
10 ' ﾌｧｰﾑｳｪｱ ｶｷｺﾐ
20 'OUT1->RESET,OUT2->PIO0_1
30 POKE#700,31,161,0,34,3,104,11,96,4,48,4,49,1,50,64,42,248,211,112,71
40 OUT1,0:OUT2,0:OUT1,1:?"?";:E=0:GOSUB140:?"Synchr";:WAIT9:?"onized";:E=1:GOSUB130:?"12000";:WAIT9:E=1:GOSUB170:?"A 0";:E=1:GOSUB180
50 ?"U 23130";:GOSUB180:?"P 0 7";:GOSUB180:?"E 0 7";:GOSUB180:FORI=0TO#7F00STEP#100:X=USR(#700,I):FORJ=0TO#E0STEP32
60 ?"W 268436";J+100;" 32";:GOSUB180:?"@";:S=0:FORK=0TO30STEP3
70 M=#780+J+K:A=PEEK(M):B=PEEK(M+1):C=PEEK(M+2)*(K<30):S=S+A+B+C
80 [64]=A/4:[65]=A<<4|B>>4:[66]=B*4|C>>6:[67]=C
90 FORL=64TO67:V=[L]&63:?CHR$(32+V+64*!V);:NEXT
100 NEXT:?CHR$(13):?S;:GOSUB170:NEXT
110 T=I/#1000:?"P ";T;" ";T;:GOSUB180:?"C ";I;" 268436100 256";:GOSUB180
120 NEXT:OUT1,0:?"Done!":OUT2,1:OUT1,1:END
130 ?CHR$(13)
140 P=0:CLT
150 K=INKEY():IFK>31POKE#740+P,K:P=P+1ELSEIFK=10IFEE=E-1:GOTO140ELSERETURN
160 IFTICK()>89E=0:RETURNELSEGOTO150
170 GOSUB130:IFP=2ANDPEEK(#740)=79ANDPEEK(#741)=75:RETURNELSEGOTO190
180 GOSUB130:IFP=1ANDPEEK(#740)=48:RETURNELSEGOTO190
190 OUT1,0:?"Failed!"
```

# 実行

以下のように接続します。

|書き込む側|書き込まれる側|
|---|---|
|RXD|TXD|
|TXD|RXD|
|GND|GND|
|OUT1|RESET|
|OUT2|ISP|

![書き込み用の接続](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/9f5bc54a-495b-befd-73ad-a0620e87b3a4.jpeg)

1.0.0 で書き込みプログラムを走らせた結果、約14分で書き込みが完了しました。
書き込みが完了後、書き込んだファームウェアを実行した結果、
バージョン情報が送られてきて、書き込む側でSyntax errorになっています。
さらに、`?VER()`を送信し、出力されるバージョン情報を`INPUTZ`で受け取ることができています。

![書き込み完了 (1.0.0)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/b1e97df9-4748-54eb-5e11-30e5b85edb70.png)

1.3.1では、約7分で書き込みが完了しました。
なぜかSyntax errorが大量に出てきてしまいましたが、シリアル通信の接続を抜いたら止まりました。

![書き込み完了 (1.3.1)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/47a7a307-520d-4f81-0de6-bba27ab5a23f.png)

# おわりに

書き込んだファームウェアの扱いは、必要な契約をせずに譲渡目的で使用しないなど、
[利用規約](https://ichigojam.net/download.html)に準じるべきであると推測できます。
(あくまで推測です。確定させるには権利者に問い合わせるのがいいでしょう)

IchigoJamはjig.jpの登録商標です。