SNSへはこちら

FT90マイコンを使ってみよう(1) - 導入と解析

お久しぶりです。毎回言っている気がしますねぇ。
皆さん、FTDI ってご存知でしょうか?そうそう、FT232RL というチップの会社です。これを小さな PCB 上に実装したものが「USB-シリアル変換モジュール」という名前で禾火月電子で売られていますよね。

そして最近、この会社が作っているマイコン、FT90 シリーズをいじり始めました。最新のものではなくひと世代前のものですが、それでもまあ32ビットなのでそれなりに楽しめそうです。

情報が少ない...

このマイコン、法人向けのものらしくて、個人でいじっている人が全くいないという状態です。しかもアセンブリ情報はなんと秘密保持契約を結ばないと提供してくれないという。ゾクゾクしてきましたね

一方でユーザーマニュアルは公開されていて、ペリフェラルの挙動やレジスタマップが分かるというのが救いです。そしてこのアーキテクチャ(FT32)は gcc でサポートされている(多分今も)ので、開発環境の整備が容易です。

そして、以下の理由によって逐一開発手順を示すことはしません。ざっくりと記述していく予定です。

  • まずこのマイコンで遊ぶ人なんていない
    • いても超上級者なので、自分でなんとかできるっしょ
    • 需要?そんなの知るか
  • FTDI 社に忖度する
    • 秘密保持契約があるくらいですからね...

「公開しろ!」との要望があれば遠慮なくコメントにどうぞ。

対象

このマイコンに決めます。理由は MOUSER で買えるから。そして QFN48。地獄を見そうな感じですが、まあイケるイケる

  • FT932Q
  • FT32 32bit RISC Architecture
    • FT32B, the latest variant
  • プログラム書き込みは USB DFU で行けるっぽい
    • 公式でも dfu-util を使う手順が書かれていた(なお Windows)
    • Windows で行けるなら Mac でも行けるという過信

取り敢えずリバースエンジニアリング

普通だったらスタートアップコードを書いて色々やるんですけど、前述のようにアセンブリの命令がわからないので、リバースエンジニアリングから始めます

やりかた

至って普通です。適当な関数を C で書いて、それをコンパイルするだけ。至って普通。

$ cat main.c
int main(void){

    return 0;
}

int returnMePlus1_int(int n) {
    return n + 1;
}

short returnMePlus1_short(short n) {
    return n + 1;
}

char returnMePlus1_char(char n) {
    return n + 1;
}

int plus(int a, int b) {
    return a + b;
}

int pointer(int *a, int n) {
    return a[n];
}
$ ft32-elf-gcc -mft32b -mcompress -Os -Wall main.c # Compile
$ ft32-elf-objdump -d # Disassembly
...

ちなみに、.elf バイナリを作らなくても通常はオブジェクトファイルで objdump をかければ良いのですが、自分の環境では全ての即値オペランドが 0 と解釈されてしまったので、そこだけは要注意でした。

結果

取り敢えず逆アセンブルの結果を示します。

00000400 <returnMePlus1_int>:
 400:   08 01 ba 13     13ba0108 add.l $r0,$r0,1 ; return !

00000404 <returnMePlus1_short>:
 404:   00 01 84 10     10840100 bexts.l $r0,$r0,0 ; add.l $r0,$r0,1!
 408:   00 00 00 a0     a0000000 return

0000040c <returnMePlus1_char>:
 40c:   1c 04 84 10     1084041c bexts.l $r0,$r0,256 ; add.l $r0,$r0,1!
 410:   00 00 00 a0     a0000000 return

00000414 <plus>:
 414:   14 00 ba 13     13ba0014 add.l $r0,$r0,$r1 ; return !

00000418 <pointer>:
 418:   2c 01 0a 18     180a012c ashl.l $r1,$r1,2 ; add.l $r0,$r0,$r1!
 41c:   00 00 00 ac     ac000000 ldi.l $r0,$r0,0
 420:   00 00 00 a0     a0000000 return

00000424 <main>:
 424:   e8 05 ba 13     13ba05e8 ldk.l $r0,0 ; return !

また、次は _start です。

色々やって、取り敢えず分かったことを適当に。

  • 後ろについている .l は多分ビット長
  • コンパイルオプションに -mcompress をつけるとコード圧縮が入る
    • 並列計算ができている!
    • 興奮してきた
  • memset, memcpy, stpcpy, strcmp, strlen とかいうC言語ベッタリな便利命令がある
    • え??これ RISC コアだよね???
    • CISC 的命令ですね。クロック数どうなってしまうのか...
  • CPU レジスタ
    • r0 〜 r28
    • fp, sp, cc, pc
    • fp は float 計算用?不明
    • cc はマジで不明
  • ベクタテーブルは ARM Cortex-M とは異なり、ジャンプ命令を記載する
  • 命令のヒントは gdb-8.3/opcodes/

GDBで動かしてみる

ここまで来たら、GDB のお出番です。ペリフェラルに関しては流石に実機がなきゃ分からないのでやりません。

今回は print デバッグをやってみたいと思います。ブレークポイントを張って見たりするのも良いのですが、つまらないので、コンソール上に出します。

準備

プロジェクトフォルダに、gcc ツールチェインの中から sim.ld を持ってきます。そして、コンパイル時にエラーが出ないようにチョチョイと編集したら準備完了です。簡単でしょう。

print デバッグの出力先

通常 print デバッグと言ったら printf 等でコンソールに出すものを指しますが、今回は違います。gdb の仕様として、あるアドレスにデータを書き込むとそれを画面に出してくれるというものがあるので、今回はそれを使用します。

で、その出力先アドレスはアーキテクチャによって異なり、ドキュメント化されていない(多分)ので、自分で探る必要があります。そこで、gdb のソースコードを見てみました。

gdb-8.3/sim/ を見ると、アーキテクチャの名前がついたディレクトリが存在することが分かります。
gdb-8.3/sim/ft32/interp.c を見てみると、cpu_mem_write にそれっぽい記述が見つかります。

static void cpu_mem_write (SIM_DESC sd, uint32_t dw, uint32_t ea, uint32_t d)
{
  sim_cpu *cpu = STATE_CPU (sd, 0);
  ea &= 0x1ffff;
  if (ea & 0x10000)
    {
      /* Simulate some IO devices */
      switch (ea)
    {
    case 0x10000:
      /* Console output */
      putchar (d & 0xff);
      break;
    case 0x1fc80:
...

というか、ベタ書きです。ea って実行アドレス(effective address)なんですかね。ともかく、ea == 0x10000 だと Console output らしいので、実際にそういうコードを書いてみましょうか。

#define output (*(volatile unsigned int *)0x10000)

int main(void) {
    const char message[] = "Hello, FT90 world!\n";

    const char *p = message;

    while( *p != '\0' ) {
        output = *p;
        p++;
    }

}

はいはい、コンパイル。

$ ft32-elf-gcc -mft32b -mcompress -Os main.c -o output.elf

これでいいでしょう。あとは実行するだけです。

$ ft32-elf-gdb output.elf
GNU gdb (GDB) 8.3
なんかいろいろ
(gdb) tar sim
Connected to the simulator.
(gdb) lo
Loading section .text, size 0x5e0 lma 0x0
Loading section .tors, size 0x10 lma 0x800000
Loading section .data, size 0xf4 lma 0x5e0
Loading section .eh_frame, size 0x4 lma 0x6f4
Start address 0x0
Transfer rate: 14144 bits in <1 sec.
(gdb) r
Starting program: /Users/yuki/Dropbox/Micom_Projs/FT32_Proj/sim/output.elf
Hello, FT90 world!
[Inferior 1 (process 42000) exited normally]

こんな感じで、コンソール出力ができましたね。

ちなみにこの 0x10000 が使えるのはあくまでもシミュレータ上のみです。実機に書き込む時は全く使えないのでご注意を。

で、本来はここからが本番なのですが、残念ながら前述の通り細かくは載せられません。すみません。実際にやった作業はというと...

  • 実機用にリンカスクリプトをコピー&編集
  • スタートアップコードを書き書き
    • その前にアセンブリコードの解読が必要
    • C言語で使えるように、スタックポインタ初期化、 .bss セクションクリア、.data セクションのコピー
    • main をコール
    • あとは適当にループしとく

ですね。実際、これで上と同じようなコンソール出力を実現することができました。

ここまで来たら実機が欲しくなってきますね。ということで、お金に余裕ができたら MOUSER で個人輸入したいと思います。