SNSへはこちら

FT90マイコンを使ってみよう(3) - ブートシーケンス&その他めも

前回 FT90 くんが(ソフト的に)死にましたが、死んでいる間に起動シーケンスについて調査しました。
巷に蔓延るいかがでしたか系記事に比べれば内容はあると思いますよ。

書き込み方法

このマイコンに挑戦される方は、ここだけを見て自作プログラムで実行に移さないでください!!死にますよ

書き込みは dfu-util で行います。その前に DFU サフィックスをバイナリに付ける必要があります。詳細は不明なので次回以降の記事を見てください。分かり次第書きます。取り敢えず CRC が必要になるらしい...?

書き込み自体は至極単純で、以下のコマンドを実行するだけ。

$ dfu-util -d 0403:0fcf -a 0 -D output.bin

ただ、前回の記事に載せた DFU と、自作プログラム書き込み時にエントリーする DFU ではちょっと仕様が違うようです。後者では書き込みに失敗しますが、結局フォールバックで(CPU が「Flash 死んどるな」と検知して)前回の記事の DFU くんがブートしてくれます。この時に書き込みを再度行うと成功しますw
現に、Lチカはすでに成功しています。皆様にお見せできず悔しいです。だってその後色々いじっていたら死んでしまったのですもん。

バイナリ仕様

FT930 シリーズ限定で話をします。書き込むバイナリサイズは 124 KiB + 16Bytes で固定です。
ユーザープログラムは 124 KiB まで上限で許されていて、それ以上はダメです。それ未満のサイズの場合は残りを 0xFF で埋める必要があります。ということで、なんとなくそのプログラムを書いてみました。実際上手く動きますよ。

#include <stdio.h>

int main(int argc, char **argv) {
    if( argc != 2 ) {
        fprintf(stderr, "Argument Error\n");
        return 1;
    }

    FILE *fp = fopen(argv[1], "ab+"); // for adding binary data
    if( fp == NULL ) {
        fprintf(stderr, "Invalid Path or File State\n");
        return 1;
    }

    fpos_t fpos;
    fgetpos(fp, &fpos);
    if( fpos > 124 * 1024 ) {
        fprintf(stderr, "Designated binary size is too large.\n");
        fprintf(stderr, "Is it for FT900?\n");
        return 1;
    }

    for(int i = 0; i < 124 * 1024 - fpos; i++) {
        char buf = 0xFF;
        fwrite(&buf, 1, 1, fp);
    }

    fclose(fp);

    return 0;
}

また、どうやらフラッシュ領域の 0x3F000〜0x3FFFF にはブートローダーがはじめから書き込まれていて、この領域にデータが書き込まれるとデバイスが起動不能に陥るらしいです。124KiB と言う指定は、Flash 領域 128KiB から末尾に配置されているブートローダー分 4KiB を差し引いたサイズということなのですね。
前回はこれではなく、ブートローダーが呼んだユーザープログラムに問題があったのでした。ってか書き込むと死ぬブートローダーって...

復帰できるコマンドか何かが用意されていればいいですが、現状では死んだら 1wire debug を使うほかないです。要注意。
まあ素直に FTDI 公式の開発ツールを使い続ければいいのですが、僕はへそ曲がりなので Mac やら Linux で自力でなんとかしたいというわけです。というかそれ自身が苦しみでもあり楽しみなんですよ(今はすっごく苦しい)。

起動シーケンス

以下はツールチェインマニュアルを大いに参考にしました。

マイコンはリセット時に 0x00000 から実行されます。そしてツールチェインはブートローダーに入ります(詳細はこの下で)。ブートローダーはユーザープログラムの正当性をチェックし、0x8c(ユーザープログラムのエントリポイント)にジャンプすることでプログラムが始まります。

実際に出来上がったバイナリを逆アセンブルしたものの一部はこちら。

$ ft32-elf-objdump -d output.elf | head

output.elf:     file format elf32-ft32


Disassembly of section .text:

00000000 <_start>:
       0:   ff ff 30 00     0030ffff jmp 3fffc <EXITEXIT+0x20000>
       4:   ab 00 30 00     003000ab jmp 2ac <interrupt_33>
       8:   48 00 30 00     00300048 jmp 120 <interrupt_0>
...
0000008c <codestart>:
      8c:   25 00 30 00     00300025 jmp 94 <init>

00000090 <_exithook>:
      90:   00 00 00 a0     a0000000 return

00000094 <init>:
      94:   00 00 f1 65     65f10000 ldk.l $r31,65536
      98:   00 00 11 c4     c4110000 lda.l $r1,810000 <_end+0xfb58>
      9c:   49 c1 10 44     4410c149 lshr.l $r1,$r1,20
      a0:   02 c9 e0 5d     5de0c902 cmp.l $r1,144
      a4:   e3 00 11 64     641100e3 ldk.l $r1,65763
      a8:   2c 00 28 00     0028002c jmpc z,b0 <init+0x1c>
      ac:   23 01 11 64     64110123 ldk.l $r1,65827
      b0:   80 00 40 64     64400080 ldk.l $r4,128
      b4:   00 00 12 b0     b0120000 sti.b $r1,0,$r4
      b8:   20 2c 40 64     64402c20 ldk.l $r4,11296
      bc:   c0 2d 10 64     64102dc0 ldk.l $r1,11712
      c0:   00 00 20 64     64200000 ldk.l $r2,0
      c4:   36 00 30 00     00300036 jmp d8 <.dscopy>
...

確かに 0x00 では 0x3fffc にジャンプしますね。この部分はコンパイラに付属している crt0.o に含まれています。
そうして 0x8ccodestart では init を呼んでスタートしています。

その他めも

これ以外の雑多な内容をメモしておきます。なにか気づいたことがある方はコメントどうぞ。

本当にDFUがユーザープログラム実行中に呼ばれないのか??

検証をした。具体的には GO_DFU() を呼ぶタイミングをずらしたと言った程度。GO_DFU() はアプリケーション中で DFU モード(?)にエントリーするための関数である。
これまで実行したプログラムはLED点灯後早々に DFU にエントリーするものだった。そこで、起動後 10sec 待ってその後に DFU にエントリーするプログラムを作った。もし自動的に DFU が有効になるのなら、この10秒の間に何らかの USB DFU デバイスとしてマイコンが認識されるはずである。
書いたプログラムはこちら。

#include <ft900.h>
#include <ft900_startup_dfu.h>

#define LED_GPIO_NUM 13
int main(void) {
    gpio_dir(LED_GPIO_NUM, pad_dir_output);
    gpio_write(LED_GPIO_NUM, 1);
    delayms(1000 * 10);
    gpio_write(LED_GPIO_NUM, 0);
    GO_DFU();
}

結果、特にその10秒間で DFU デバイスは表れず。つまりアプリケーション中でも自分で GO_DFU() を呼んでエントリーする必要があるっぽい。

あと、どうやらブートローダーに含まれる DFU とユーザープログラムにある DFU は仕様が異なるみたい。これも要調査。

Windows用公式FTDIプログラマのコマンド

FT90Prog.exe というのがプログラマであるが、どうやらその本体は jar ファイル。ということは、逆コンパイル出来るじゃん...つまりソースコードをそのまま復元できるということ。

$ brew install jadx
$ jadx -ds ftprogram_decompiled /Volumes/BOOTCAMP/Program\ Files\ \(x86\)/Bridgetek/FT9xx\ Toolchain/Toolchain/programmer/dist/FT900ProgGUI.jar

これでコードを覗ける。見てみると、次の処理をしていた。

  1. DFU で書き込み指定しているバイナリの DFU サフィックスがなければ、以下のコマンドで付加する。
    • .\FT900Prog.exe -g <BinFileName> -o <OutputPath> -D 1
      • <OutputPath> には ~/AppData/Roaming/Bridgetak/.../dfuSuffix.dat が指定。
      • -D 1非常に大事。これがないと FT900 シリーズ用の大容量バイナリが生成されて、FT930 シリーズのブートローダーを破壊する
  2. 書き込みを実行。
    • .\dfu-util.exe -S <Serial> -vRD <OutputPath>
      • -S <Serial> は無視可能なので、ここでは気にしない。

ここで FT900Prog.exe が Windows 用のプログラムなのが鬼門。実際のところ dfu-suffix で代用出来ると考えているが、両者で付加されるサフィックス値が微妙に異なるのが不安点。