2012年08月14日

USB HUB IC

ちょっと USB HUB IC について気になったのでメモ。

以前目をつけた HUB に、

Quadrate USB 2.0 4-Port Hub with USB Cable (Color Assorted) $4.14
Quadrate USB 2.0 4-Port Hub with USB Cable (Translucent Orange) $4.20

というのがある。どこに目を付けたかというと...

  • 透明なので、外観でどういう構造なのか分かる。改造しやすそうな感じ。片側 2 port のコネクタを取って 外部 2 port + 内部向けみたいなことも出来そう。

  • IC が SSOP28 で、電子工作として扱いやすそう。-- IC が単体で手に入らなくとも HUB から外してしまえば良い。再設計した基板に載せかえれば、自由な形態にできる。

まぁこういうことなのだ。これを、入手したので、ID を見てみた。

さて、しばらくぶりに FE1.1s を ググってみれば、この IC に目を付けたひとがいた。

仕切りなおしのA8
Hubあぶり焼き
空きパターンのアレ


回路図も作ってくれているので、見たところ ... すごくシンプル。SSOP28 だし、なにか、電子工作ネタに良さそうな感じ。


    3.3V と 1.8V のレギュレータがオンチップになっている。 どれだけ電流を流せるのか分からないが、場合によっては、このためだけに使うのもアリか?

記事中にあるが、このひとが入手したのは、上海問屋の これ。今でも 299 円で入手できる安物 HUB 。ケーブルが断線したりして、壊れやすそうだが、壊れても捨ててはいけない。バラして電子工作ネタにするのだ。

基板が小さいので、そのままでも なにかに組み込んだりできそうだし、基板を起こして PIC32MX ボードのマザーボードみたいにするのも楽しいような気がしている。
posted by すz at 22:19| Comment(0) | TrackBack(0) | 日記

2012年08月07日

ブートローダの検討

PIC32MX-PINGUINO-MX220 のブートローダを壊してしまった。要するにライタの方をなんとかしないと先に進めなくなった。それはさておき、自分でブートローダをビルドしたい。

とにかくいまのやつは気に入らないのだ。なにが気に入らないのか?というと

・ サイズがでかい。(プログラム・フラッシュを 12KB 専有する)
・ スタートアドレス のカスタマイズが出来ない。
・ ブートボタン、LED のカスタマイズが出来ない。

    サイズについては、ブートフラッシュ領域を有効に使うようにすると、8KB かうまくすれば 4KB に抑えられる。 で、jzlib の場合 ロードしたアドレスに JUMP してくれれば、ベクタ領域の 4KB をさらに節約できる。32KB しかない PIC32MX220 で この差は大きい。16KB しか使えなかったものが、24KB (うまくすれば 28KB) 使えるようになるのだ。

    あと 、カスタマイズ機能も欲しい。LED のポートを変更するだけで、再ビルドはしたくないのだ。さらに言うと フラッシュ領域の保護機能も欲しい。コンフィグで設定できるとは言え、チェックしておいた方がより安全だ。

それに、自製のブートローダも作ってみたい。自製の USB デバイスライブラリを使うもので一番シンプルなのが ブートローダだったりするのだ。( 要するにデバッグが目的なので、実用的に使えるのかどうかは気にしない。)

オリジナルのコードについて

    RetroBSD のコードをベースにして、PIC32MX220/PIC32MX250 に対応する。調べて見たが、ビルドするために必要なコードは揃っている。これを jzlib にできるだけ 合わせるように改造した上で、コンフィグ機能を付ける。

    retrobsd-src-r561-20110729.tar.gz

    参考用に取った、このスナップショットをベースにする。

ブートフラッシュ領域を有効に使う案

    jzlib で、割り込みベクタを割り当てない リンク・スクリプトと スタートアップを先に作っている。これを元にブートローダ専用の リンク・スクリプトを仕上げるのだ。

    さて、これのメモリマップの(一部)を説明すると

     program flash + 0x4000 exception_mem size 0x180
    .app_reset (_start)
     program flash + 0x4180 kseg0_program_mem
    .text

    こんな感じにしている。jzlib では exception_mem は、もともとスタートアップと割り込みベクタ領域で、1KB これを 384B に切り詰めた。その後に kseg0_program_mem が続く。これには .text すなわちプログラムコードが含まれる。 ブートローダでは、

     boot flash + 0x0000 exception_mem size 0xBE0
    .app_reset (_start)
    .boot
     program flash + 0x0000 kseg0_program_mem size 8K
    .text

    こんな風に変える。普通にビルドすると、コードは全部 .text になるから、プログラム・フラッシュに行ってしまう。コードを選んで .boot セクションに持って行くと ブートローダが専有する プログラム・フラッシュが減るようになる。

    .boot セクションに持って行くには、

     #define BOOT_SECTION __attribute__((section(".boot")))

     int BOOT_SECTION main(void)
     {
         :

    こんな風に関数を宣言する。ひとつひとつ 宣言することになるが、まぁたいした手間ではない。

    ついでに書いておくと、全部をブートフラッシュ領域に持っていくには、別の方法を使う。普通に戻して、kseg0_program_mem 自体を 移動するのだ。440 なんかでは、12KB もあるから それが可能になる。220 のように 3KB しかなければ頑張っても無理だと分かった。boot flash 3KB + program flash 4KB が精々。

カスタマイズの方法

    リンク・スクリプトには、もともと コンフィグ領域の 指定をする仕組みが仕込まれている。コンフィグ領域は、ブート・フラッシュの 最後 16バイト。これに倣って、その下 に 16Bの ブートローダ・コンフィグ領域を作る。
     
    MEMORY(追加)
    bl_config3 : ORIGIN = 0xBFC00BE0, LENGTH = 0x4
    bl_config2 : ORIGIN = 0xBFC00BE4, LENGTH = 0x4
    bl_config1 : ORIGIN = 0xBFC00BE8, LENGTH = 0x4
    bl_config0 : ORIGIN = 0xBFC00BEC, LENGTH = 0x4

    SECTIONS
    .bl_config3 : {
    KEEP(*(.bl_config3))
    } > bl_config3
    .bl_config2 : {
    KEEP(*(.bl_config2))
    } > bl_config2
    .bl_config1 : {
    KEEP(*(.bl_config1))
    } > bl_config1
    .bl_config0 : {
    KEEP(*(.bl_config0))
    } > bl_config0
    }

    こんな記述を リンク・スクリプトに追加するのだ。

    使う方は、

    #define BLCFG3 (*(unsigned *)0xBFC00BE0)
    #define BLCFG2 (*(unsigned *)0xBFC00BE4)
    #define BLCFG1 (*(unsigned *)0xBFC00BE8)
    #define BLCFG0 (*(unsigned *)0xBFC00BEC)

    こんな風に定義して

    * BLCFG0 アプリケーション領域の先頭(kseg1 address)
    * BLCFG1 アプリケーション領域の最後(kseg1 address) + 1
    * BLCFG2 スタートアドレス (kseg1 address, 省略可)
    * BLCFG3 ボタンとLED のコンフィグ

    struct blcfg3 {
    unsigned REDLED_BIT:4;
    unsigned REDLED_PORT:3; /* 0 A/1 B/... */
    unsigned reserved1:1; /* 0 */

    unsigned GRNLED_BIT:4;
    unsigned GRNLED_PORT:3;
    unsigned reserved2:1; /* 0 */

    unsigned BUT_BIT:4;
    unsigned BUT_PORT:3;
    unsigned reserved3:1; /* 0 */

    unsigned REDLED_TYPE:2; /* 0: LAT_SET 1: LAT_CLR
    2: TRIS_SET 3: TRIS_CLR */
    unsigned GRNLED_TYPE:2;
    unsigned BUT_TYPE:1; /* 0: NOT-PULLUP 1 : PULLUP */
    unsigned reserved:3; /* 000 */
    };

    こう使おうかと思う。

スタートアドレスのサーチ

    jzlib でシングルベクターモードにして コードを節約したのに、ブートローダを使うと アプリケーション領域の先頭 + 4K に JUMP してくる。しょうがないので、4KB 後ろにずらして使っている。これが一番気に入らない。

    でも、アプリケーション領域の先頭に JUMP するようにしたら互換性がなくなってしまう。どうしたものかと思ったのだが、a.out を ディスアセンブルすると 必ず先頭が

    9d004000: 3c1a9d00 lui k0,0x9d00

    これで始まっている。k0 に 上位アドレスをロードしているのだが、これは決まりきった手順のようなもの。MichroChip 由来の crt0.S を使っていればこうなるし、特に 変える理由もない。

    あと、スタートアドレスは、4KB にアラインされる。2KB を超えることもある割り込みベクタが先頭にあるから当然なのだ。

    つまり、アプリケーション領域の先頭から 4KB 単位でインクリメントして、0x3c1a9d00 を探せば良い。こうすることで、jzlib では、先頭から使えるようになる。

最初のバージョン

     ・ jzlib-0.2d.tar.gz

    jzlib に依存するように作り替えているので、jzlib に同梱することにした。jzlib をインストールして、hidboot ディレクトリで make すればビルドできる。

    ただし、まだ、全然テストしていない。バイナリを置くのはまだ先になる。

    text data bss dec hex filename
    9056 52 1187 10295 2837 usbboot.elf

    サイズは、9KB 弱。これを 振り分けて 8KB からロードできるようにした。7KB 以下にしないと 4KB からのロードはできないので、これで手をうった。(頑張って小さくするつもりはない。これを参考にして自製の USBASP ブートローダを作るのだ。)

      ------------------------------------------------
      : 02 0000 04 9D00 5D
      : 10 0000 00 8800F07700000000FFFF001000000000 F3
      :
      :
      : 04 1A90 00 000088BF 0B
      ------------------------------------------------
      : 02 0000 04 9FC0 9B
      : 10 0000 00 C09F1A3C10005A270800400300000000 5F
      :
      :
      : 10 08D0 00 05977C6730F044DB00EF0363000200A0 63
      ------------------------------------------------
      : 02 0000 04 BFC0 7B
      : 04 0BE0 00 0A1F170E C3 (Red RA10 (0A), Grn RB15(1F) , BUT RB7(17) )
      : 04 0BE4 00 FFFFFFFF 11 (START_ADDR : auto search)
      : 04 0BE8 00 FFFFFFFF 0D (APP_FLASH_END +1: auto)
      : 04 0BEC 00 002000BD 28 (APP_FLASH_START : 0xbd00_2000 )
      : 04 0BF0 00 FFFFFF0F F5
      : 04 0BF4 00 D979F9FF B3
      : 04 0BF8 00 5BCE60FF 71
      : 04 0BFC 00 EEDFFF7E AB
      ------------------------------------------------
      : 04 0000 05 9FC00000 98
      : 00 0000 01 FF

    さて、HEX ファイルはこうなっている。領域内に収まっていることを確認した。あと最後の部分がコンフィグ。ここを編集すれば ビルドしなくともカスタマイズできる。

    DEVCFG の方は、BWP=0(ブート領域保護) と PWP(8KB 保護)にしてある。JTAGEN は Enable だが、

    /* Disable JTAG port, to use it for i/o. */
    DDPCON = 0;

    こんなコードが入っていて、ブートローダの先頭で Disable にしている。こうすることで、AVR の ISP のように 書き込みするときだけ、JTAG を使えるようにしている。 (注) 1xx/2xx では DDPCON をCFGCON に読み替える。

    ちなみに、Makefile で DIP_MX220 を選択できるようにしているが、LED の設定しか変わらない。また 250 でも同じものが使える(220で動けばの話だが)。PINGUINO_MICRO の設定は、440 用のブートローダで 大きく違う点は、全体が ブート・フラッシュに置かれること。これによって プログラムフラッシュの先頭から使えるようになる。当然ながらこれも未テスト。

USBASP ブートローダの検討

    まだまだなのだが、どんなものになるかちょっと触れておこうと思う。

    PIC32 用のライタソフトとして、avrdude を使っているものがある。ちゃんと調べていないのだが、arduino と互換にしているらしい。それがありならば、USBASP でもなんとかできるのかも知れない。自製の USB device ライブラリ向けに移植した USBASP ブートローダのコードがあるし、USB device ライブラリを PIC32MX に移植したいと思っていたので検討してみることにした。

    まず、USBASP ブートローダは割り込みを使わずに実装できている。また、USB シリアル プロトコルの CDC とは違って 基本の機能しか使っていない。... どうもこれでデバッグするのが良さそうなのだ。

    このブートローダは、サイズが小さいのが特徴のひとつで AVR では 2KB で実装できている。PIC32MX では、コードが大きくなってしまって、3KB の領域には収まりそうにない。3KB を超えると 次は 7KB 以内が目標になる。それで、機能を充実させる方向で 検討を進めている。

    text data bss dec hex filename
    5016 64 873 5953 1741 asp162bl.elf

    いまのサイズは 5KB ぐらいで、ブートローダ自体の機能は一応入っている。上で説明したブートローダ・コンフィグ機能なども全部入れた。ただし、USB device ライブラリのほうがまだで、これよりちょっと大きくなる予定。それでも 7KB には収まるだろうし、収める。こうすれば、HID ブートローダより使えるメモリが増えてメリットが出る。

      text data bss dec hex filename
      3924 64 873 4861 12fd asp162bl.elf

      嘘のような話なのだが、ポートをコンフィグ可能にするだけで 1KB 使っている。上は、define で指定したケース。

    さて、機能として最も重要なのが、シグネチャー。ライターソフトは、これを見てどういう動作をするか決める。だが、AVR に合わせないと、avrdude で使うのは厳しい。

    AVR の場合、第一バイトが 0x1E で ベンダーのコードになっている。第二が FLASH サイズで、第三がそのサイズでの識別番号。 で、PIC32MX にも当然デバイスの ID はある。こちらは、1word - 4 バイト。下位 8bit がベンダーコードで 0x53 。その後がどうなるかちょっと調べてみた。

    [1][2][3]
    * 00 A0 04 220 B 32
    * 10 A0 04 210 B 16
    * 20 A0 04 220 C 32
    * 30 A0 04 210 C 16
    * 40 A0 04 220 D 32
    * 50 A0 04 210 D 16

    * 00 D0 04 250 B 128
    * 10 D0 04 230 B 64
    * 20 D0 04 250 C 128
    * 30 D0 04 230 C 64
    * 40 D0 04 250 D 128
    * 50 D0 04 230 D 64

    * 20 94 -- 420 H 32
    * D0 94 -- 440 H 128
    * 20 95 -- 440 H 256
    * 60 95 -- 440 H 512

    * D0 96 -- 440 L 128
    * 80 97 -- 460 L 512
    * 40 97 -- 460 L 256

    一応、第二、第三バイトでデバイスを特定できるようだ。2xx は、規則正しい。5xx/6xx/7xx は、沢山ある上に良くわかっていないのでパス。4xx もリストしただけ。ベンダーコードさえ違えば重ならないから、なんとかなりそう。これをシグネチャの定義にしてしまおうと思う。

    これだけなんとかなれば、後は アドレスを指定してメモリを読み書きする仕組みがあれば、事足りる。USBASP には、上位 16bit を指定する機能と、下位 16 bit でアドレスを指定して ブロックを読み書きする機能がある。

    アドレスの範囲をチェックして、余計なところを読み書きできないようにすることは必要だが、新たなプロトコルの定義はいらない。ちなみに、HID ブートローダには、メモリを読み出す機能がない。製品としては、セキュリティ上必要なんだろうけれども、趣味で使う分には不便でしかたがない。書き込みは、アプリケーション領域のみ。読み込みは、フラッシュ全域のみということにしようかと。こうしておけば、使っているブートローダをバックアップできるし、コンフィグがどうなっているかも分かる。

    ソースコード (USBASP ブートローダの方)

     ・angel_loader-p32mx-wk05.tar.gz

    全然動くことが期待できるものではないが、一応置いておく。周りは固めたのだが、肝心の USB device ライブラリがまだまだ。

関連記事
posted by すz at 19:32| Comment(0) | TrackBack(0) | PIC32MX

2012年07月31日

PIC32MX2xx用基板

開発用に Olimex PIC32-PINGUINO-MX220 を使っているのだが、結構使い勝手がよく気に入った。

とはいえ、失敗したらチップを壊してしまうようなテストには使いたくない。そこで、DIP の PIC32MX220F032B を使った専用基板を作ることにした。これだと、壊してもチップを入れ替えればすむ。.... なんてのは、ただの口上で 、設計してみたかっただけ。

    よくわかっていないのだが、JTAG も ICSP も Disable にしてしまうと 復活できないんじゃないかと思う。ちゃんとしたツールを使えば避けられるはずだが、そのツールを作ろうとしているわけで、壊す可能性は結構ある。

基板は、Fusion PCB とか IteadStudio(新サイト)に発注するのを前提にする。安く済ませるためには、5cm x 5cm でないといけない。

で設計してみたのが、これ



    PIC32-PINGUINO-MX220をお手本にしたのだが、5cm x 5cm では、Arduino のコネクタは入れることができない。そのため左側ははずした。右側もかざりで、配線していない。それ以前にピンが足りないために、A0-A1 , D4-D7 が配線できていない。

    Arduino 互換にしようという意図ではなく、どうせなら PINGUINO に近くしておこうという意図。PINGUINO はブートローダがあるので、ブートローダ起動用 ボタン(BUT) と LED1 は 同じにしておいた方が良さそう。あと、PGEx3 を使うとか ... 合わせておいた方が 良いと判断した。

回路図



    PINGUINO の回路図を見ながら作った。適当にしてしまったところもあるが、まぁ。

    ちょっと工夫したのは、ICSP 用コネクタ。リセットのところ MCLR と A5(RB3) を切り替えられるようにした。(ソフト次第で)プログラマにすることができるかも知れない。

回路図の注意点

    結構いいかげんなものなので、説明しておこうと思う。

    USB コネクタの接続

    ID ピンまで GND にしている ... だけでなく Frame GND も GND に接続。ここは、お手本とは違う。今までそれで困ったことにはなっていないので、適当に処理している。

    AVDD の接続

    これは、VDD(3.3V) から 10 Ωを直列に 入れて からつなぐのが推奨されているのだが、直結している。ちなみに お手本では、抵抗パターンがあるが、0 Ωにしている。パスコンは、AVDD - AGND に近いところに一応配置した。

    MCLR のプルアップ

    10 kΩ以下でないといけない。お手本では 4.7 kΩとなっていた。

    BUT ボタン(RB7)、LED1 (RB15)

    HID ブートローダを使うなら、接続すべき。特に BUT ボタンがないと使えないことに。

    ICSP の選択

    お手本と同じ PGEx3 にしている。

    D0/D1 と D2/D3

    お手本では、RB8/RB9 が D0/D1 だったのだが、D2/D3 にしている。代わりに RA4/RB4 が D0/D1 。配線が楽なのを選んだらこうなってしまった。回路図には、それぞれ UxTX/UxRX と書いてあるが、どちらもほかのピンに割り当て可能。ただし ... PPS の割り当ては 1 回しか許さない というコンフィグになっている可能性があり、しかも ブートローダで割り当てているかも知れない。注意が必要。

    D9=LED2(RED) (RA10 → RB14)

    RA10 のピンはないので、RB14 に振り替えている。LED1(GRN) は同じ RB15。

    UEXT
    3V3 (1) (2) GND
    U1RX RA4 (3) (4) RB4 U1TX
    (PU) SCL1 RB8 (5) (6) RB9 SDA1 (PU)
    SDI1 RA8 (7) (8) RA9 SDO1
    SCK1 RB14 (9)(10) RA7 CS (PU)

    ちょっと 10pin の拡張コネクタである UEXT の割り当てをメモしておこう。ここで使われているピンを だいたい Ax/Dx に持って行っていて、元のピンとの振り替えになる 。
    水晶の 並列記述

    回路図には、2 つの水晶をパラレルに接続するような形になっているが、もちろんどちらかだけを使う。右側は、FA238 となっているが、シンボルを借りただけで、実は MA-506 。これを裏につけられるようにしてみた。使い道がなかったのをこれ幸いとばかりに つけようと考えたのだが、これだけ裏につけるのでは見栄えも使い勝手も悪くなる。せめて表に付けられるようにすべきだった。(一応 作ってみた。最後のほう参照)

    コネクタ

    基本 0.8mm φ にした。ピンヘッダだと 細ピンヘッダしか入らない。サイドのやつは、それで良いと思うのだが、ICSP コネクタまで 0.8 mm にしたのは失敗かも。

主要パーツ

    主要パーツは、秋月で揃えられるように配慮している。

    3.3V レギュレータ: NJM2866F33 または TAR5SB33

      似た名前のチップがあるが、ピン配置が違うので間違わないよう注意。NJM2866F33 を使う場合は、(4) ピンのコンデンサは不要。TAR5SB33 では 0.01uF 推奨。0.1uF だと電圧が立ち上がるのに時間がかかるが、なんとかなると思う。

    水晶 : 8MHz

      今回は、HC-49/S(8MHz) が標準 。表面実装タイプ(8MHz)もピンを伸ばせば使えるはず。


      ついでに、格安の MA-506(8MHz)も使えるようにしておいた。ただし、これだけが 裏面実装になる。あまりお薦めではないのだが、以前買ってしまったのもあるし。



      コンフィグを変えることで、12 MHz や 16 Mhz でも対応できることを後で知った。(後述)

      12 MHz で良いのであれば、DealExtreme の格安のタイプも使えそうだ。

      追記: 水晶は、aitendo の クリスタル(HC49S)各種(10個入) が格安で出ている。

    表面実装タクトスイッチ : LS6JM-T



      以前 2 種類あったのだが ... 1つは在庫がなくなったらしい。新しく別のタイプが出ている。使えそうな感じ。

      以前記事にした、中華のタクトスイッチ TD-85XU も使える。このタイプは 2端子だが、これが片側を使って実装できるようだ。あと、6mm x 6mm の TD-27XAもいけそうな気がする。は、ダメだった。


        追記: TD-27XA は OK なのを確認はしたのだが、後でパターンを変更したので、5 番目のピンが 干渉するようになってしまった。これは 浮かすなり、カットするなりする必要がある。 勘違い? かなり無理がある。

    裏面実装2色LED : SEC2764C

      SEC2764C は、方向を間違えないし、ハンダ付けが楽なので愛用しているが、これでないと使えないというわけではない。

      1608 タイプを 2 個載せることは可能で、SEC2764C がなくなったとしても、なんとかなる。

    USB ミニB コネクタ : MUSB-5BF02AS-N



      たぶん使えるとは思うのだが、フットプリントが変わったとか書いてあって不安ではある。実は沢山買った 中華製のを使おうかと ...


      (この 2 つは、格安に買ったもの。ほとんど違いがわからない。ただ、耐久性とかに差があるかも知れない。そればかりは使ってみないと分からないし、違いが分かるほど使い込むかどうかも分からない)

    スライドスイッチ



      ライタにする目論見で、ICSP の RST を切り替えられるようにした。 秋月の SS12D01G4 あたりを J1 に付けようかと。

    LCR

    ピンヘッダ、ピンフレーム

      ピンフレームは、『分割ロングピンソケット』が良いと思う。Arduino 流に ベースの方に上向きに付けるのが標準的だが、どう使っても良い。ピンヘッダを付ける場合は、『細ピンヘッダ』しか付かない -- ちょっとゆるいような ...。『L型』は、ICSP コネクタとか、UART を 外に出す場合に使う。

ブートローダとコンフィグについて

    ブートローダは、Pinguino に含まれているようだ。extra/bootloaders/32bit に

    PIC32-Pinguino_HIDBoot_MX220.hex

    というファイルがある。これが DIPでも使える。注意しなくてはいけないのは、BUT(PB7) のボタンを プルアップして付けること。あと出来れば LED1(RB15) を付ける。(回路図参照)

    ちなみに code.google.com の方では、

    PIC32MX250F128B_-_HID_Bootloader.hex

    というファイルがある。どうも 128K 版も存在するようだ。これらのブートローダは、ユーザフラッシュの 先頭から 12KB 占有する。次の 4KB はベクタエリアなので、実質 16KB は使えない。32KB あっても 半分しかコードが入らないので 注意が必要。

    さて、これらのコンフィグは、hex ファイルに含まれている。抜粋すると

      PIC32-Pinguino_HIDBoot_MX220.hex
      :040bf400d979f9ffb3
      :040bf8005bce60ff71
      :040bfc00eeffff7f8a
      PIC32MX250F128B_-_HID_Bootloader.hex
      :040bf000ffffffcf35
      :040bf400d979f9ffb3
      :040bf8005bde74ff4d
      :040bfc00e3ffff7f95

    読み方はこう

      PIC32-Pinguino_HIDBoot_MX220.hex
       config2 : 0xfff979d9
      config1 : 0xff60ce5b
      config0 : 0x7fffffee
      PIC32MX250F128B_-_HID_Bootloader.hex
      config3 : 0xcfffffff
      config2 : 0xfff979d9
      config1 : 0xff74de5b
      config0 : 0x7fffffe3

    この部分だが、合わせて

      :040bf000ffffffcf35
      :040bf400d979f9ffb3
      :040bf8005bde74ff4d
      :040bfc00e3ffff7f95

    にするのが良いのではないかと思う。250 用の 頭の CF は

      #define CONFIG_FUSBIDIO 0 /* USBID is port */
      #define CONFIG_FVBUSONIO 0 /* Vbuson is port */

    という意味で、この設定をしないと 2 ピンを USB 用に使われてしまう。

    あと、JTAG が enable になっている。enable にすることで ピンが使われてしまうのならば (実は良く知らないのだ)

      :040BFC00EAFFFF7F8E

    最後の行はこうした方が良い。

      追記: ついに分かった

      DDPCON = 0;

      ブートローダにこういうコードが入っていて、JTAG を disable している。リセット との併用で、JTAG を使っての書き込みは出来るが、プログラムを動かすときは disable ということだった。(注) DDPCON は 1xx/2xx で名称が CFGCON になったが、アドレスは同じ

    セカンダリ・オシレータは、もとから OFF なので この設定で 全部のピンが使えると思う。

    水晶は、8 MHz でしか使えないとばかり思っていたのだが、自由度はそれなりに高かった。16/20/24/40/48 MHz に対応できるようだ。

      8 MHz :040bf400d979f9ffb3
      12 Mhz :040BF400DA7AF9FFB1
      16 MHz :040BF400DB7BF9FFAF

    たとえば、このように 2行目 を入れ替えることで、12 Mhz / 16 MHz に対応できる(はず)。

      #define CONFIG_FPLLIDIV IDIV_3
      #define CONFIG_UPLLIDIV IDIV_3

    入力側の分周比しか変更していないから、動かなくなることはないと思う。

    ところで、この HEX ファイルを書き込むには、ライタが必要。
     ・ pic32prog
    を使うと pikkit2 でも書き込めるらしい。svn でダウンロードすることになっているが、browse から pic32prog.exe だけをダウンロードすることも可能。

    FT232R などの Bitbang で書き込めるようにしたいとは思っているが、コードを書いただけ。
     ・ pic32prog-r51-ftdi-02.zip
    に置いてはあるが、全然テストしていない。

    追記: 『ブートローダの検討』の記事で紹介しているのだが、自分でビルドすることにした。これまた未テストだが、近いうちに使えるようにするつもり。

    追記: pic32prog-r62-ftdi-05.zip で、Synchronous BitBang を使った ICSP 書き込みができた。テストしたのは、UM232H のみだが、AE-UM232R などでも可能なはず。ただ、Synchronous BitBang では外付けトランジスタが必要になる.
    (DTC144 可) 。わずか 2 個のパーツ追加だが、それも面倒なら、UM232H で MPSSE (の BitBang) を使うと結線のみで済む。

この基板の eagle ファイル

     ・ PIC32MX-typeB.zip

    一応、置いておく。ろくに検証もしていないのだが、 IteadStudioに発注してしまった。送料は $3.9 だったので 全部で 1100 円ぐらい。送料をけちってるので、3 週間ぐらい待つことになる。

      追記 8/7 : 送付したというメールが来た。あと 10 日ぐらいか。実をいうと 下記の TypeBr2 も発注していて、これも送付された。多分同時に受け取ることになる。

      8月9日 9:51 引受
      8月10日 18:04 国際交換支店から発送 SHENZHEN
      8月13日 14:22 国際交換支店に到着
      8月14日 9:00 通関手続中
      8月14日 10:06 国際交換支店から発送

      最初のやつだけ、国内まで来た。メールは同時に受け取ったが、実際には時間差があるようで typeBr2 は 全然。... と思ったらトラッキングできていたのは、後のやつだった。前のは不明 -- 番号の登録を間違えたか、ニコイチにされた?

      8/16 受け取り! 受け取ったのは、TypeBr2 のみ。10 枚ぴったりだった。前のは? 行方不明?
      8/16 来ていないと文句を言ったら、中国ポストで 15日付けで引受になった。まぁ、ありがち。

    typeB というのは、末尾 B = DIP のこと。適当な名前をつけてしまった。

     ・ PIC32MX-typeBr2-02.zip (更新版)

    とりあえず、MA-506 水晶のパターンを裏から表に移した版。一応 49/S をつけてもパターンが干渉しないように配慮はしている。

追記: PIC32MX-typeBr2



    上で載せた typeBr2 だが、今はこうなっている。

    回路図はほぼ同じ。C9 だけ追加(VUSB3V3 は、USB のドライバ用の電源だったのだが、パスコンを付け忘れた。)

    変更点

    まず、MA-506 を 表にもってきたのだが、秋月には、pF クラスのコンデンサが少ない。以前は 大きめながら 10pF の表面実装品を扱っていたので それ向けのパターンに変更した。.. のだが、今みてみたらすでに扱ってなかった。... リード 10pF ぐらいか。まぁ ランドを大きくしたので、リード部品も付けられるはず。 マルツなら 2012 22pF があるのだが .. あと、共立には、1608 22pFがある。

      20pF 前後を使うのが普通。10pF では、発振しない恐れがあるが、たぶん大丈夫。10 pF を製品で使っている例を見たことがある。それどころか、一般には浮遊容量(5pF ぐらい?)だけで発振したりするらしい。いままで付けなかったことはなかったので、一度試してみよう。



    あと、タクトスイッチ周りを変更した。TD-85XU とか 横向きの TD-26XA まで付くようにした。ついでに 端まで寄せて、基板固定用の 穴を 1つ増やした。



      ついでに書いておくと、端まで寄せたのは、TD-26XA の都合。USB コネクタ用のランドが内側まで来ているのは、端を基板内に納めるため。

    ICSP 用は 普通のピンヘッダが付くように径を大きなものに変更。

    忘れていた。LED は、この方向で左が LED1(緑 or 黄) 右が LED2(赤) 。お手本とは 左右が逆で色の方を合わせた。

    こうやって、直してはみたものの、後のまつり。最初のやつを発注済み。最初のやつが気に入らなかったら、これも発注してみるかも知れないが、ずいぶんと先になりそう。

    結局これを発注したわけだが、前の基板より先に到着。



      MA-506 のパターンが、49/S の水晶と干渉する。正確にはぎりぎり干渉しないのだが、やはりショートの恐れがある。絶縁するか、表面実装の 台付き を利用する。

      USB A オス は (ポッチを削ると)きっちり裏面に付けられるようだ。手持ちの A メス は、表面実装型なのだが、これもなんとか付くようだ。miniB は、やはり絶縁が必要。A オス でいいかという気がだんだんしてきた。

      6mmx6mm の表面実装のタクトスイッチが付くと書いたのだが、全然厳しい。チェックしたつもりだったのだが ... なにか勘違いしたようだ。

      再度の 0.8mm Φは、やっぱり狭い。普通のピンヘッダが付かないし良くなかったかも。普通のピンヘッダとは何だろう? 0.64mm 角 (対角 0.9mm)のものや、0.7mm 角(対角 1.0mm) あるいはそれ以上のものがある。

        手持ちの L 型 ピンヘッダ (多分 0.64mm 角)は、ストレスなく差し込めた。

      うまくすると Type A オス(ウラ) と miniB が両方付く ような ... どちらかしか使えないが、やっぱり OK 。ただし、VBUS ピンの干渉にだけは、要注意。

この基板のつかいかた

    実を言うとあまり考えていなかったりする。まぁ最初は、ライブラリとかライタの デバッグ用に使うつもり。USB から電源を取れるし、JTAG や ICSP の接続も 楽なはずで、ブレッドボードと比べれば、格段に使いやすいはず。

    デバッグが終わったら、USB デバイスの 開発ツールにでも仕立てようと思う。あとは、気軽につかえそうだから、ADC を使ってみるとか ... ぐらい。

    USB HOST は、2 枚スタック?

    電源 やら デバッグのための通信やら を考えると 2 枚スタックが良さそうな気がする。HOST は、正規でない MINI-B だが、中華アンドロイドは皆 この形式なので HOST ケーブルは手にはいる。(後で HOST 用 A コネクタも付くように変更)

    RA0/G1 o AREF PGED3
    o GND
    RB15/G1 x D13 LED1
    RB13/G3 o D12
    RST x RB5/G2 o D11 TMS SD11/SDO1
    3V3 x RA1/G2 o D10 PGEC3 SDO1/SDI1
    5V o RB14/G4 o D9 LED2 SCK1
    GND o RB7/G1 o D8 BUT/TDI SS1
    GND o
    -- o -- o D7
    -- o D6
    A0 o -- -- o D5
    A1 o -- -- o D4
    A2 o G4/RB0 RB9/G4 o D3 SDA1 U2TX/TDO
    A3 o G2/RB1 RB8/G2 o D2 SCL1 U2RX/TCK
    A4 o G3/RB2 RB4/G1 x D1 U1TX
    A5 o G1/RB3 RA4/G3 x D0 U1RX

    スタックと言っても全結線するわけにはいかない。

    まず左上の POWER コネクタ。5V はつなぐ。HOST にするのだから当然こうなる。両方とも レギュレータと分かっているから、3V3 , 5V ともにつないでも ... という気はしなくはない。RST だけはつないではダメ。

    左下 A0-A4 は 、使わない前提でつなぐ。A5 は、J1 で RST に 接続できる。これで HOST 側のリセットを制御。

    右の D8 から上だが、LED1 はブートローダーで 出力するので まずい。それ以外はつなぐ。調べてみたのだが、SPI1 は、SDI/SDO を入れ替えたピン設定が可能で、並列に接続しても SPI 通信ができそう。

    D0-D7 についてだが、D4-D7 は基板固定用に つなけた方が良さそう。あと D2-D3 に移した PB8/PB9 だが、I2C にもなるからこれも。TX/RX のピンは入れ替えられない。UART で接続するならクロスにせざるを得ない。.. というわけで D2-D7 をつなぐ。

    さて、こうやってスタックにすると書き込み をどうするかが次の問題。テストするのに 基板を外すのは 当然やらない。USB HOST のテストをするのに HID USB BootLoader も使わない。これも わざわざケーブルを付け替えることになるからだ。

    選択枝は、たぶん 2 つ。ひとつは、ICSP での書き込み。もうひとつは、UART 1 が接続されていることを利用しての ブートローダ書き込み。AN1388 ブートローダは、シリアルであろうと HID であろうと プロトコルは同じようだ。ならば、pic32prog を改造することで対応できるかも知れない。ただし、PC と接続する方に、USB シリアル変換ファームウェアを入れなければならない。

      おなじようなものとして、Arduino と互換のブートローダもあるらしい。こちらは avrdude を使う。

    こうやってスタックして、上のボードを開発できるようにするのが第一歩か。で、UART や I2C , SPI の通信プログラムの開発が全部できる。最後に USB HOST 。やるべきことと優先順位が見えてきた。

    ところで、基板をアップデートした。HOST 評価向けに、USB TypeA コネクタ (メス)を付けられるようにした。オス コネクタ用ではないので注意。ディメンジョンが違うので付かないような気がするが、付いたとしても 1-4 が反転している。どうしても付けたいならば、裏面に付ける。

      メスコネクタといっても、かならずしも HOST 専用とは限らない。オスーオスケーブルがあれば、デバイスとして付けられるし、中華アンドロイドの OTG は大概 mini-B なので、デバイスとして 付けるときに普通のケーブルが使える。

    この変更にともない、mini-B の方が具合が悪くなった。#1 の スルーホールが シールドと接触するはず。5V と GND がショートすることになるので、絶縁が必要になった。注意。

    あと スタックするのなら、ボタンは 横向き(TD-26XA) にしようかと思う。

    注意)
    ここまで書いて ... やっぱり直接つなげるのは、どうか? という気がしてきた。左側は基本的に問題ない。A5 をリセットに使うだけだし。

    問題は右側。右側にはもう一列あるので、100 Ωぐらいの抵抗を入れてつないだほうが良さそうな気がする。

Arduino ピン配置との関係

    Arduino の ピン配置は無視したのだが、お手本の方が互換性を持たせるように設計されている。ちょっとメモしておこう。

    A0-A5

    A0 o --
    A1 o --
    A2 o G4/RB0
    A3 o G2/RB1
    SDA SDA2 A4 o G3/RB2
    SCL SCL2 A5 o G1/RB3

    もとよりピンが足りないのはおいておいて .. A4/A5 には、I2C2 のピンが割りあたっている。一応互換性はある ... のだが A5 は、リセット制御に使うつもりなので、スタックしたときは ここは使わない。

    D0 - D7

    -- o D7
    -- o D6
    -- o D5
    -- o D4
    RB9/G4 o D3 SDA1 U2TX
    RB8/G2 o D2 SCL1 U2RX
    RB4/G1 x D1 U1TX TX
    RA4/G3 x D0 U1RX RX

    お手本とはピンを変えたのだが、TX/RX の関係は同じにしている。( お手本は U2TX/U2RX )

    D8-D13 , AREF

    RA0/G1 o AREF PGED3 AREF
    o GND
    RB15/G1 x D13 LED1 SCK SCK2
    RB13/G3 o D12 MISO SDI2
    RB5/G2 o D11 SD11/SDO1 MOSI SDO2
    RA1/G2 o D10 PGEC3 SDO1/SDI1 SS
    RB14/G4 o D9 LED2 SCK1 SS2
    RB7/G1 o D8 BUT SS1

    AREF は PIC32MX でも AREF ピンを割り当てている。(こちらはポートにも使える)
    問題は SPI 。一応 SPI2 を使うと マスターモードで SCK/MISO/MOSI は 互換性がある。ただし、SS2 は G4 でないとダメで ずれている。たぶん マスターモードでは SS は ポートで良いのだろう。スレーブモードまでは互換性がないわけだが、それではスタックしたときに困るのだ。スタックで、通信できるようにするためには、SPI1 を使わないといけない。

基板組立て + ライター基板づくり

    まずは、TypeBr2 を 2 つ組み立てた 。

    一号機



      スイッチに横押しタイプの TD-26XA を使ってみた。ほんのわずかだが、基板の端より奥になってしまい。少し押しにくい。

      USB は、ノーマルに miniB 。今回は超安物の中華製。不具合が出るものかどうか、試してみるのだ。安もののポリプロピレンテープ(普通の透明タイプ)で絶縁してみたが、問題なさそう。水晶は、aitendo で安く買えた 49/S 。水晶周りも同じように絶縁。

      LED の抵抗は、赤 1.5K Ω、黄色 1.0K Ωにしてみたが、黄色はやや暗い。

      あとは普通に ピンフレームにした。あとで気がついたが、DIP の IC の取り外しは結構厳しいかも。

    ニ号機



      スイッチには、小型の TD-85XU を使ってみた。これで、使えるものなのかどうか調子を見たい。

      一号機と変えたのは、あと、ピンフレームを細ピンヘッダにしたこと。HOST ではないが、スタック用を想定。ピンヘッダで間引いたのは、RST と D0/D1 。あとは、5V/3.3V ともに接続。5V は、問題ないとして 3.3V のレギュレータ出力も 接続してしまった。たぶん問題は出ないはず。

    ライタ基板

      ちゃんとライタソフトが動くまでは、接触不良の心配をしなくてよいように基板を作ることにした。

      AE-UM232R と 上記の基板をマウントできるように して、JTAG 用の 信号を結線。あと ICSP 用コネクタを付けて、こちらも結線。ICSP は、基板に直接配線できるのだが、不採用。

      電源は、基本両方 USB につなぐようにするから、お互い独立。... のつもりだったのだが、スイッチを付けて 5V だけ AE-UM232R から供給可能にした。

      ICSPの 3.3V は、PGEDのプルアップのみに使用。

      ピンヘッダとピンソケットの両方を付けたのだが ... 干渉して 一号機は付かないことがあとで分かった。( Olimex PIC-PINGUINO-MX220 は付いた。) うっかりしていた。

      まぁ ICSP での書き込みが動きだせば、問題ない。二号機を常時マウントして、ライターソフトのデバッグ専用に使うことにしよう。

      3) JTAG using sync bitbang

      D5/DSR --- R --- TCK
      D4/DTR --- R --- TDI
      D3/CTS --- R --- TDO
      D2/RTS --- R --- TMS
      D7/RI --- R --- /MCLR

      syncbb-jtag or syncbb-jtag-54327

      4) ICSP using sync bitbang

      D0/TXD ------ R ----------------- PGEC
      3V3
      |
      D6/DCD --+----------+ Rpull
      | | |
      Rpull +---|>o----+------ PGED
      | | |
      GND GND |
      |
      D1/RXD ------ R ----------+

      D7/RI ------ R ---------------- /MCLR

      syncbb-icsp or syncbb-icsp-0617

      結線は、こんな風にした。TXD/RXD を使わないつもりだったのだが、ピンヘッダに出しておくと 通信に使えて便利かもしれないと思い直した。また JTAG と 信号線を共通にするのは、具合がわるい。ICSP を使う時に基板を外さないといけなくなってしまう。

      さらに、ICSP コネクタを付けたままにすると 通信が (一応)可能なのだ。RXD は UART1 / TXD は UART2 という変則な接続になるが、 できないよりマシだろう。

      ライタのデバッグの準備はできた。続きは、『pic32progの改造』の追記に書く予定。

      追記 2012/8/21 : syncbb-icsp , mpsse-icsp 共に動作した。分かりにくいかも知れないが,上記の記事を参照。いずれまとめの記事を書こうとは思う。

追記 2012/9/2: typeB-R3



    PIC32MX-typeBr3-01.zip eagle ソース

    発注するつもりは、いまのところないのだが、少々変更。

    まず、ICSP コネクタ を見なおした。PGEx3 だったのを PGEx1 に変更した上で 抵抗を入れるようにした。ついでに、PGED1 をプルアップできるようにもしている。

    USB でのブートローダが動けば、ICSPで書き込むことは、まずない。1 回しか使わないかも知れないのだ。そして、その1回目は PGEx1 に接続しなくてはならない。... 要するに PGEx3 では、あまり意味が無い。さらに、PGEx1 だと PGED1 = G4/RB0 = U2TX, PGEC1 = G2/RB1 = U2RX に割り当てることが出来るというメリットがある。あと、プルアップ は、FT232R で書き込むときに部品点数を減らせるようにするため。これで、外付け部品は、デジトラ 1 個になった。

    あと変更したのは、水晶周り。MA-506 は、もういいやということで外した。22pF にリード部品を使えるように配慮(VIA を付けただけなので、難があるかも)。これで一応は、秋月の部品で完結するようになった。

    基板を変更するのは、これで終わりにしようと思う。そろそろ、使う側を充実させないと。

追記 2012/9/18: PIC32MX-UM



    PIC32MX-typeBr3-03.zip eagle ソース

    ボード追加。FT231X (QFN) を使った AE-UM232R (のような)ボード と FTDI basic breakout (のような)ボード を追加した。

    さらに、PIC32MX-2XX (QFN-28) を使った AE-UM232R (のような)ボードを設計してみた。ブートローダのための RST/BUTボタンや 確認用 LED は、なんと 裏面に配置することに。

    まぁ、設計してみたかっただけなので、サンプルということで。FT231X ボードも PIC32MX-UM ボードも未チェックなので要注意。

    ピン配置を付録に記載した。DSR/DCD は、N.C. なのに、VCC や PU1/PU2 まで信号線を割り当てているのは、配線が苦しいため。 DSR/DCD が必要になったら VCC からジャンパするつもり。

    同じ機能のボードがあるから、この基板の出番はずっと先になるはず。なにかの装置に組み込んで、単なる USB HOST/Device インターフェイスとして使ったりを想定している。

    追記: ちょっと変更

      ピン配置とか、シルクを入れてみた。あと、水晶に FA238を使っているのだが、ハンダ付けが苦手。ランドを広くすべしということらしいので、パターン見直し。ちなみに、16 MHz を使うつもり。CONFIG は 8 MHz 用ではダメなので注意。

付録: ピン配置

    PMD7 RA0/G1 o AREF PGED3
    o GND
    PMCS1 RB15/G1 o D13 LED1
    PMRD RB13/G3 o D12
    RST o RB5/G2 o D11 TMS SD11/SDO1
    3V3 o PMD6 RA1/G2 o D10 PGEC3 SDO1/SDI1
    5V o RB14/G4 o D9 LED2 SCK1
    GND o PMD5 RB7/G1 o D8 BUT/TDI SS1
    GND o
    -- o -- o D7
    -- o D6
    A0 o -- -- o D5
    A1 o -- -- o D4
    A2 o G4/RB0 PMD0 PMD3 RB9/G4 o D3 SDA1 U2TX/TDO
    A3 o G2/RB1 PMD1 PMD4 RB8/G2 o D2 SCL1 U2RX/TCK
    A4 o G3/RB2 PMD2 RB4/G1 o D1 U1TX
    A5 o G1/RB3 PMWR PMA1 RA4/G3 o D0 U1RX


    ____________ PIC32MX-UM ___________
    | |
    | __ UM232R ____ |
    ================ | | =============
    U1TX RA4 G3 TXD GND
    TMS RB5 G2 DTR CB0 G1 RB3 SCL2
    TDO/SDA1 RB9 G4 RTS CB1 G3 RB2 SDA2
    3V3 VIO VCC G2 RB1 PGEC1
    U1RX RB4 G1 RXD RST ~MCLR
    TDI/BUT PB7 G1 RI 3V3
    GND CB3 G4 RB0 PGED1
    N.C. DSR PU1 G2 RA1 PGEC3
    N.C. DCD _____ PU2 G1 RA0 PGED3
    TCK/SCL1 PB8 G2 CTS | | VCC G1 RB15 LED/SCK2
    RB13 G3 CB4 | | 5V
    SCK1 RB14 G4 CB2 |_____| GND

関連記事
posted by すz at 07:05| Comment(0) | TrackBack(0) | PIC32MX

2012年07月29日

jzlibの使い方

まだ完成はしていないのだが、説明を書き始めた。ソースコードは最後にリンクを提示

jzlib とは

    PIC32MX 用の libc ライブラリで、AVR-libc のように使えることを目指したもの。もともと 中華PMP などに使われていた JZ47xx 用のライブラリとして作っていたもので放置していたのだが、PIC32MX 用に作り直すことにした。名前は、もともと jzlib だったので継承している。

      現状はまだ未完成。特に割り込みが動いていないのが致命的。... ではあるが、まぁなんとかなるだろう。

    基本的にこのライブラリは、AVR-libc である。実際に AVR-libc のソースコードを元にしている。AVR-libc には、アセンブラで書かれたものがかなりある。その部分は、NetBSD のソースを借用して なんとか した(つもり)。基本的には、AVR-libc なのだが、デバイス系の ライブラリは ほとんどない。あるのは、デバイスのレジスタの定義 と 割り込みサポート と タイマー 程度。要するに余計なものと思われるものは、一切ない。ただし、割り込みだけは別。MIPS はすごくめんどくさいのだ。それだけは、AVR-libc のように簡単に使えるものを目指して提供している。

    タイマーというのは、OS では基本的な機能。時刻管理用の ostimer と 定期的に callback を呼び出す timer の 2 つの機能を提供することにした。 これらは、割り込みと違って使わないという選択もできる。

    あとは、core 用の シンプルなライブラリとマクロ。割り込み禁止・許可をする cli()/sei() , irq_disable_irq()/irq_restore() とか。ディレイ関係。関数版 は基本 mips16e 用。mips16e では、使える命令に制限があるし、ディレイも定義したクロック数(1ループ 2CLK) にならない。

対応チップ

    対応予定のチップ
     110 120 130 150 210 220 230 250 の B/C/D
     (320 340 360 ?) 420 440 の H/L
    確認可能なチップ (所持しているもの)
    220 B/D , 250 B , 440 H

    実際のところ、スタックのアドレス以外は同じもの。3xx/4xx は、ostimer/timer が変わるが、シンボル名を変えることで対応している。( 後は 1xx/2xx と同じ )

    5xx/6xx/7xx は、データシート未読のため、対応を考えていない。

インストール方法

    MIPS の gcc は、Pinguino の環境を借用できるようにした。Pinguino は、Windows/Linux/Mac 用の環境があるので、あとは shell とか sed とかがあれば良い。

      借用しているのは、gcc の コンパイラのみ。なので、mips-elf-gcc を自分でビルドしても良い。
      Windows では、MSYS を使っている。

     ・ msys-1.0.10
     ・ Pinguino downloads

    このあたりがインストールされていることを前提にして、インストール方法を説明する。

    Pinguino をインストールすると たとえば /c/PinguinoX.3/win32/p32/bin/ に mips-gcc がある。例えば Windows だと 次のようにする。

    $ export PATH=$PATH:/c/PinguinoX.3/win32/p32/bin
    $ export CROSS_COMPILE=mips-
    $ sh install.sh

    ( gcc の prefix は Windows では mips- 、Linux や Mac , 野良ビルドならば、mips-elf- )
    ( 注意 : Pinguino は最後に追加すること。make が含まれていて、ちょっと具合が悪い )

    install.sh では、${CROSS_COMPILE}gcc があるディレクトリを基点にして、../jzlib に 環境を インストール し、p32mx-gcc という コマンド(シェル) を インストールする。

    (付録)インストールされるファイル一覧

    jzlib/lib/2xx/
    crt2xx.o libc.a

    jzlib/lib/4xx/
    crt4xx.o libc.a

    jzlib/lib/ldscripts/
    p32mxb12k.x p32mxb3k.b p32mxb3k.bu p32mxb3k.x

    jzlib/include/
    alloca.h ctype.h inttypes.h math.h setjmp.h stdio.h string.h
    assert.h errno.h limits.h p32mx stdint.h stdlib.h

    jzlib/include/p32mx/
    interrupt.h p32mx_capt.h p32mx_i2c.h p32mx_spi.h p32mx_wdt.h
    ostimer.h p32mx_clkc.h p32mx_intc.h p32mx_sys.h timer.h
    p32mx_2xx_regs.h p32mx_core.h p32mx_nvm.h p32mx_timer.h wdt.h
    p32mx_4xx_regs.h p32mx_devcfg.h p32mx_port.h p32mx_uart.h
    p32mx_adc.h p32mx_dmac.h p32mx_rtc.h p32mx_usbotg.h

PIC32MX 用 の プログラム の ビルド方法。

    #include <p32mx/p32mx_core.h>
    #include <p32mx/p32mx_port.h>

    // Olimex PIC32-PINGUINO-MX220
    // LED1 (Green) D13 = RB15 Active H (1: Light on)
    // LED2 (Red ) D9 = RA10 Active H (1: Light on)
    // BUT D8 = RB7 Active L (0 : Pressed)

    int main() {
    TRIS_CLR(1) = 1 << 15;
    TRIS_CLR(0) = 1 << 10;
    LAT_CLR(1) = 1 << 15;
    LAT_SET(0) = 1 << 10;
    for (;;) {
    delay_us(500000);
    LAT_INV(1) = 1 << 15;
    }
    }

    これは、Lチカのプログラム例だが、これを test.c という名前で作ったとする。

    $ p32mx-gcc -mmcu=p32mx220-bu -O2 test.c -lc
    $ mips-objcopy -O ihex a.out test.hex

    これで、AN1388 ブートローダ用 HEX ファイルが出来上がる。

    mips16e 用のコードの場合は、次のようにする。

    $ p32mx-gcc -mmcu=p32mx220-bu -mips16 -O2 test.c -lc
    $ mips-objcopy -O ihex a.out test.hex

     (注意:野良ビルドの mips-elf-gcc だと objcopy がエラーになる。i386 用なら問題ないので、そちらを使う。)

    -mmcu= で指定する MCU は、p32m[1234][2345]0 。f032b と続けても良いがなにも変わらない。 例外は、320f064/320f128 。これだけは ここまで指定する必要がある。

      実際のところ、crtXX.o (スタートアップファイル) も libc.a も 1xx/2xx では、共通。違いはなにかというと
       define : -D__32MX1XX__ , -D__32MX2XX__ , など
       stack 位置 : -Wl,--defsym,__stack=0xa0002000 など。

      RAM サイズは、x10 が 4K , x20 が 8K , x30 が 16K , x40 と x50 が 32K としている。320 だけはこのルールに会わないので、f064/f128 まで指定する必要がある。

      3xx/4xx は、crtXXX.o libc.a を別にしたのだが、今は同じもの。ただし、今後は変わる可能性がある。
      タイマーなどは、シンボル名(の プレフィックス)を変えて対応したので、両方 libc.a に含まれている。あと、リンカスクリプトは別になる。違いは Config が置かれるアドレス値のみ。

    -mmcu= のサフィックスの -bu は、ブートローダ用の リンカスクリプトの指定

      program addr 1xx/2xx 3xx/4xx
      -bu 0x9d004000 p32mxb3k.bu p32mxb12k.bu
      -b 0x9d001000 p32mxb3k.b p32mxb12k.b
      (-x) 0x9d000000 p32mxb3k.x p32mxb12k.x

    一般的には、最初の 4KB には ベクタが置かれるらしく、スタートアドレスは +0x1000 。jzlib では、シングルベクタモードを使うので、ベクタ領域は 1KB 。スタートアップは、ベクタ領域の先頭にしていて オフセットは 0 。というわけで、4KB ずらして 作れば対応できる。(-b を指定する)

    220 用の AN1388 ブートローダは、ブート領域に入りきらず ユーザ領域の先頭 12KB を占有している。ベクタ領域とあわせて +0x4000 (16KB !) ずらすことで対応する。(-bu を指定する)

    コード領域を節約したければ、先頭からにする。一応 ブート領域の 先頭のリセットアドレスには、スタートアップのアドレスへの JMP 命令を置いたので、ブート領域が書き込み可能であれば、動くはず。(書き込み不可なら -b を指定する)

    -lc をつけると、リンカのための オプションが付く。

    付録) AN1388 ブートローダでの書き込み。

      $ pic32prog -S test.hex

      pic32prog r55 以降を使うと書き込みができる。ただし べりファイがうまくないので、-S が必要。
       ・ pic32prog-r56-20120725.tar.gz

      スナップショットを取ったものを 置いておく。Windows で使えるように、pic32prog.exe を ビルドしたものと入れ替えた、

    付録) オプション例(コンパイル・リンク時)

    -mips32r2 -EL -D__32M4KCORE__ -D__32MX2XX__ \
    -nostdinc -I/c/PinguinoX.3/win32/p32/bin/../jzlib/include

    オプション例(リンク時のみ)

    -Xlinker -T /c/PinguinoX.3/win32/p32/bin/../jzlib/lib/ldscripts/p32mxb3k.x \
    -Wl,--defsym,__stack=0xa0002000 \
    /c/PinguinoX.3/win32/p32/bin/../jzlib/lib/2xx/crt2xx.o \
    -nostdlib -L/c/PinguinoX.3/win32/p32/bin/../jzlib/lib/2xx

API 概要(1) デバイスのレジスタ

    libc は、置いておくとして、ヘッダーファイルの 定義は MicroChip とまったく互換性がない。そもそもレジスタの定義以外の API はほとんどない。

    まず デバイスのレジスタの定義だが、データシートに書いてある名前 に _ を付けたものを 一応は用意している。

    p32mx/p32mx_2xx_regs.h -- 1xx/2xx 用
    p32mx/p32mx_4xx_regs.h -- 3xx/4xx 用

    これには、2 つの使い方がある。

    -D_USE_SFRS_BASE_ADDR

    とすると、ベースアドレス相対でアクセスする。define しないと 絶対アドレスでのアクセスになる。

    レジスタは、2 つの 64KB 領域に分類される。64KB 以内であれば、1 命令での ロード・ストアが可能で効率が良いはず。なお、ほかの ヘッダファイルも ベースアドレス相対が可能なものがいくつかある。

    デバイス毎のヘッダーファイル

    p32mx/p32mx_adc.h
    p32mx/p32mx_capt.h
    p32mx/p32mx_clkc.h
    p32mx/p32mx_dmac.h
    p32mx/p32mx_i2c.h
    p32mx/p32mx_nvm.h
    p32mx/p32mx_port.h
    p32mx/p32mx_rtc.h
    p32mx/p32mx_spi.h
    p32mx/p32mx_sys.h
    p32mx/p32mx_timer.h
    p32mx/p32mx_uart.h
    p32mx/p32mx_usbotg.h
    p32mx/p32mx_wdt.h

    デバイス毎の定義を作っていて、こちらを使うようにしている。レジスタの名前や フィールドの名前を 一定の規則で変えている。

    規則は次のとおり

      ・ U1OTGCON など デバイス番号があるものは 、数字を _ に変更して U_OTGCON などとする。
      ・ SPI など 複数のデバイスがあるものは、SPI_CON(0) といった指定をする。
      ・ SET/CLR/INV のサフィックスは _ をつける SPI_CON_CLR(0) など。
      ・ PORT の場合 LATACLR などと定義されるが、A-G は _ に変更。
       たとえば LAT_CLR(1) は B を指す。
      ・ いくつか例外がある。DCH_CON(x) など。

    フィールドの定義は、これら デバイス単位の ヘッダファイルにしか定義されていない。デバイス名_フィールド名で、bit 位置 を 示す。bit 幅の定義はない。どうせデータシートを読まないとコードは書けないから なくても問題ないと判断した。データシートを検索するためのフィールド名が 1ヶ所あればよい。

    あと、PORTなど bit 位置が明らかなものの、フィールド名は 定義しないようにした。

    注意) これらの定義は、基本的に 1xx/2xx 向け。3xx/4xx での違いは 一応対応したが、あまり信用ならない。

    補足) PPS -- Peripheral Pin Select の使い方

      p32mx/p32mx_sys.h

      PIC32MX は デバイスの 入出力 の ピンの割り当てを 設定する機能がある。これを使わないと UART とかは使えない。便利でもあり不便でもある。PPS の定義は、p32mx_sys.h で、ほかの雑多なものと 一緒にまとめてしまっている。定義名は、ちょっと変えた。グループ 1-4 に整理してあるので、このヘッダファイルの中を見てほしい。

      なお、3xx/4xx には、PPS はない。定義する側だと、やっかいなものがなくて 少し対応が楽になった。5xx/6xx/7xx に対応するには、上で示してきたヘッダファイルを 全部チェックして必要なものは、追加しないといけない。すごく面倒なので、とにかく後回し。

API 概要(2) CPU

    p32mx/p32mx_core.h

    これに、core 関係の定義を集約している。asm を使った定義もこれだけには存在する。

      static inline unsigned long __irq_disable();
      static inline void __irq_restore(unsigned long c);
      static inline void __nop();
      static inline void __cli();
      static inline void __sei();
      static inline void __delay_loop_1(unsigned int loops);
      extern void delay_loop_1(unsigned int loops);

      extern unsigned long irq_disable();
      extern void irq_restore(unsigned long c);
      extern void nop();
      extern void cli();
      extern void sei();
      extern void delay_loop_1(unsigned int loops);

      extern void delay_us(unsigned int us);
      extern void delay_ms(unsigned int ms);

      __ext_bits(c, pos, width);
      __ins_bits(d, c, pos, width);

    沢山定義されているが、一般的に使うのは、これぐらい。

    irq_disable/irq_restore と cli/sei の使い分けだが、ネストして使いたいときに irq_disable/irq_restore を使う。

    あと、delay_us/delay_ms だが、OSCC の レジスタを見て CPU クロックを推定している。ただ、8MHz 以外の クロックではうまくないかも。USB を使う前提なら 必ず 8MHz の水晶になるはずで、大きな問題はないはず。

    __ext_bits/__ins_bits は、ビットフィールドの 取り出し・結合の命令 EXT/INS に展開する。使用上の注意だが、mips16e では使えず、pos / width は、定数でなければならない。

API 概要(3) 割り込み とタイマー (未完了)

    p32mx/p32mx_intc.h
    p32mx/interrupt.h
    p32mx/ostimer.h
    p32mx/timer.h

    これらに定義してある。

    割り込みを有効にするには、まず interrupt_enable() を call する必要がある。チップやら core やらいろいろ設定しなければならないのだ。似た名前の機能に irq_enable() というのがあったりするが、これは 関係ない。

    SIGNAL(JZ_OSTIMER_IRQ)
    {
    __intc_ack_irq(JZ_OSTIMER_IRQ);
    current_cycle_high++;
    return IRQ_HANDLED;
    }

    これは ostimer.c の一部だが 、こんな風に使う。まず、SIGNAL(IRQ番号) で関数を定義する。AVR-libc と似ているが、多重割り込みは許可しないので、オプションはない。

    __intc_ack_irq(IRQ番号) は、割り込みフラグ ( IFS(x) ) をクリアする。一部のデバイスでは、下位に多数の割り込み要因があり、それらをクリアしてからでないと、割り込みフラグをクリアできないケースがある。このため、割り込み関数側で __intc_ack_irq() を call しなければならないとしている。

    また、戻り値が 0 のとき、割り込みマスク ( IEC(x) ) を クリアしない。IRQ_HANDLED をリターンしないと、次の割り込みが来ないことになる。ただし、割り込みマスクの 制御は別途可能。

    __intc_mask_irq(IRQ番号)
    __intc_unmask_irq(IRQ番号)

    という API がある。

API 概要(4) コンフィグ(未完了)

    MicroChip の コンパイラだと #pragma が定義されていて、コンフィグを設定できる。だが、汎用の gcc でそれは無理。うまい方法はないかと 考えて 一応作ってみた。

    p32mx/p32mx_devcfg.h

    がその定義。

      #define _BUILD_CONFIG
      #include <p32mx/p32mx_devcfg.h>
      #define CONFIG_FPLLIDIV IDIV_4
      #define CONFIG_UPLLIDIV IDIV_4
      #define CONFIG_JTAGEN 0 /* Disabled */
      #include <p32mx/p32mx_devcfg_build.h>

    こんな C のコード を作ってコンパイルすると ...

    struct devcfg0 __attribute__((section(".config_BFC00BFC"))) _config0;
    struct devcfg1 __attribute__((section(".config_BFC00BFC"))) _config1;
    struct devcfg2 __attribute__((section(".config_BFC00BFC"))) _config2;
    struct devcfg3 __attribute__((section(".config_BFC00BFC"))) _config3;

    といった、section 指定のデータが作られて、リンク時に

    config3 : ORIGIN = 0xBFC00BF0, LENGTH = 0x4
    config2 : ORIGIN = 0xBFC00BF4, LENGTH = 0x4
    config1 : ORIGIN = 0xBFC00BF8, LENGTH = 0x4
    config0 : ORIGIN = 0xBFC00BFC, LENGTH = 0x4

    という風に config データが置かれるアドレスに 情報が格納される。#define は省略時デフォルト値が 入る。

    ちなみに、値を確認するには、

    $ mips-objdump -D a.out |less

    などとすると良い。

    付録)デフォルト値

    #define CONFIG_USERID 0xffff
    #define CONFIG_PMDL1WAY 0 /* Multiple Reconfiguration is Allowed */
    #define CONFIG_IOL1WAY 0 /* Multiple Reconfiguration is Allowed */
    #define CONFIG_FUSBIDIO 0 /* USBID is port */
    #define CONFIG_FVBUSONIO 0 /* Vbuson is port */

    #define CONFIG_FPLLIDIV IDIV_2 /* 8 MHz -> 4 MHz */
    #define CONFIG_FPLLMUL MUL_20 /* 4 MHz -> 80 MHz */
    #define CONFIG_FPLLODIV ODIV_2 /* 80 MHz -> 40 MHz */
    #define CONFIG_UPLLIDIV IDIV_2 /* 8 MHz -> 4 MHz */
    #define CONFIG_UPLLEN 0 /* Eable USB PLL */

    #define CONFIG_FWDTWINSZ WISZ_25 /* 25 % */
    #define CONFIG_FWDTEN 0 /* Watchdog Timer is disabled */
    #define CONFIG_WINDIS 1 /* Watchdog Timer is non-Window mode */
    #define CONFIG_WDTPS 0 /* 1/1 */
    #define CONFIG_FCKSM CSDCMD /* Clock Switching Disabled */
    #define CONFIG_FPBDIV 0 /* PBCLK = SYSCLK */
    #define CONFIG_OSCIOFNC 1 /* CLKO output Disabled */
    #define CONFIG_POSCMOD 2 /* Primary Osillator is HS mode */
    #define CONFIG_IESO 0 /* Switchover mode is Disbled */
    #define CONFIG_FSOSCEN 0 /* Secondary Osillator is Disabled */
    #define CONFIG_FNOSC POSCPLL /* Pri osc + PLL */

    #define CONFIG_DEBUG 2 /* DEBUG OFF */
    #define CONFIG_JTAGEN 1 /* Enabled */
    #define CONFIG_ICSEL ICS_PGx3 /* PGEx3 */
    #define CONFIG_PWP PWP_SIZE(0) /* All Writable */
    #define CONFIG_BWP 1 /* Writable */
    #define CONFIG_CP 1 /* No protection */
    #define CONFIG_DEBUG 0x3 /* OFF */
    #define CONFIG_JTAGEN 1 /* Enabled */
    #define CONFIG_ICSEL 3 /* PGEx1 */
    #define CONFIG_PWP 0x3f /* OFF */
    #define CONFIG_BWP 1 /* OFF */
    #define CONFIG_CP 1 /* OFF */


API 概要(5) ヒープ(未完了)

    char *__malloc_heap_start = &__heap_start;
    char *__malloc_heap_end = &__heap_end;

    これは、stdlib/malloc.c での定義。この __head_start/__heap_end がどうなっているかというと。リンク・スクリプトで次のように定義している。

    __heap_start = . ;
    .heap :
    {
    *(.heap)
    } >kseg1_data_mem
    . = ALIGN(4) ;
    __heap_end = . ;
    _end = . ;
    _bss_end = . ;

    .heap セクションに 配列を定義すると その分の領域が取られる。

    char __attribute__((section(".heap"))) head_area[1024];

    例えばこういう風にすると、head_area のサイズ分が確保される。( 変数名はなんでも良い )

    似たものとして、.bdt セクションがある。これは、USB を使う場合に必要な BDT -- Buffer Descriptor Table を置く専用のセクションで 512B アラインという制限から .data の前に置かれる。(注意: この領域はリセットでクリアされない)

    あと、.head_nv セクションも作ってみた。これは .bss の後ろに配置され、

    __heap_nv_stgart
    __heap_nv_end

    で領域を知ることができる。リセットでクリアされない領域なので、便利が使い方ができるかも知れない。

API 概要(6) 一般例外ベクタ

    プログラムが不正な命令を実行したりすると、EBase + 0x180 の 一般例外ベクタに飛んでくる。デフォルトでは、単に ERET して 知らないふりをする。が、これではまずいだろうということで、対応できるようにした。

    void
    __attribute__((section(".gen_handler0")))
    never_return()
    {
    int i;
    for (i=0;;i++) {
    __delay_loop_1(2000000);
    LAT_INV(0) = 1 << 10;
    }
    }

    このように、.gen_handler0 セクションにプログラムを置くと それが実行される。エラーである以上 リターンする必要がないだろうということで、このように C で書いてしまうこともできる。関数名はなんでも良く。セクションだけが重要。

    ただし、スタック は使わないほうが良い。こんな風に 単純な LED 点滅ルーチンにしておいた方が無難。

gcc 野良ビルドの方法 (メモ)

    binutils-2.20.1

    ./configure --prefix=/d/MIPS32_Toolchain --target=mips-elf --disable-shared \
    --disable-threads --enable-languages=c --disable-dssi \
    --disable-plugin

    gcc-4.4.3

    ./configure --target=mips-elf --prefix=/d/MIPS32_Toolchain --enable-languages=c \
    --with-dwarf2 --disable-doc --disable-shared --disable-libada \
    --disable-libssp --disable-nls --disable-fixed-point \
    --with-build-time-tools=/d/MIPS32_Toolchain/mips-elf/bin \
    --disable-tui --disable-gdbtk --disable-threads --disable-bootstrap \
    --enable-multilib --enable-sim --with-float=soft --without-headers

    メモだけ。古めだが、最新のものでも同じオプションでいけるはず。ちなみに mips-elf-objcopy は、最新版でもダメだった。

現状と 今後の予定

    今は、Olimex PIC32-PINGUINO-MX220 (Muser でも買える 1572円) でデバッグしている。やはり ブートローダがあると 便利すぎるぐらい便利。ただ、これでは、コンフィグとかのテストはできない。

    割り込みのところが、かなり難航していて、うまくいっていない。これをクリアできたら、秋月で安価に買える PIC32MX220F032B に移ろうかと思う。といっても、PIC32-PINGUINO-MX220 と同じプログラムが動くはずなので、主に pin32prog の 改造のデバッグ と コンフィグ関係。

    それとは別に実用的なプログラムを作って、ライブラリが使えるものなのかどうか確認したい。USB デバイスライブラリ と 応用プログラムを予定している。これも以前 AVR 向けに作ったものがあるので、移植ということになる。こちらのデバッグも PIC32-PINGUINO-MX220 を使うつもり。USB デバイスでは、割り込みを使わない予定なので、割り込みが動いていなくとも 作ることはできそう。

    なお、割り込みのデバッグ状況だが、ostimer を使ってデバッグしている。1 回割り込みを起こすことができて 割り込み関数に飛んでくることは確認できた。だが、割り込みからの復帰で 一般例外を起こしている。アドレスは、アセンブラの __intent らしい。一般例外ベクタで ERET だけすると、平タスクに復帰してくる。たぶんスタックがおかしいか、実行モードがおかしいか そんなところ。あと、シャドウレジスタを使った版があるのだが、こちらも全然だめ。これもなんとかしたいが、レジスタのセーブ・リストアをする版の方が先。

    retrobsd

      PIC32MX で動くらしいのだが、これの ソースコードも参考にしようかと思う。
       ・ retrobsd-src-r561-20110729.tar.gz ( 参考用のスナップショット )

      USB device とか SDカードのドライバもある。MicroChip のコードがベースだが、参考にしたい。

ソースコード

     ・ jzlib-0.1.tar.gz

    割り込みが動いていないという重大が制限があるが、とりあえず。0.1 とした。

     ・ jzlib-0.1a.tar.gz

    __ext()/__ins() など、いくつか修正を入れた。割り込みはやっぱりまだ。あと examples を入れた。簡単なプログラムだが、ビルドの仕方とか の参考用に。

     ・ jzlib-0.2.tar.gz

    printf を使えるようにする準備。一応 examples/hello.c がビルドできた。

      シリアルの送信は、割り込みなしでも 一応はできる。FIFO は 4 段 + 送信バッファで、5 バイトまで一気に送ることも可能。それは良いのだが、stdio (FILE *) を使うと一気にサイズが増える。さらに printf でまた増える。hello.hex が 9.5KB とか。

        G _sbss_begin
        G _sbss_end
        B __iob
        G __malloc_heap_end
        G __malloc_heap_start
        G __malloc_margin
        S __brkval
        S __flp
        S _sbss_begin
        S _sbss_end
        T _2xx_serial0_init
        T calloc
        T clkc_get_pbclk
        T fdevopen
        T free
        T irq_disable
        T irq_restore
        T jzcon_append
        T jzcon_setup
        T malloc
        T memset
        b __putfunc
        s __putfunc_num
        t __jzcon_putc
        t __serial_putc
        t smallclr

        FILE * を使うと malloc を使う。こういうので結構コード量が増える。

        R __clz_tab
        T __udivdi3
        T __umoddi3
        T fputc
        T memchr
        T printf
        T strlen
        T vfprintf
        r blanks.1661
        r zeroes.1662

        こちらは、printf で増えたもの。vfprintf が本体なのだが、NetBSD のコードを使っている。%f とかの変換は OFF にしているのにでかい。AVR のものが使えなかったのは、アセンブラのコードを一部使っているため。RetoroBSD のライブラリが小さければ、入れ替えるつもり。

      USB device ライブラリの移植を始めているのだが、これがまたサイズがでかい。

      AVR:

      text data bss dec hex filename
      1026 0 412 1438 59e cdc_main.o
      601 0 0 601 259 cdcmsc162.o
      165 0 0 165 a5 desc_cdcmsc.o
      1326 0 18 1344 540 usb162.o
      3368 0 446 3814 ee6 serial162.elf

      MIPS32R2
      text data bss dec hex filename
      1980 0 412 2392 958 cdc_main.o
      976 0 0 976 3d0 cdcmsc162.o
      156 24 0 180 b4 desc_cdcmsc.o
      1800 512 20 2332 91c usb162.o
      7976 544 540 9060 2364 serial162.elf

      MIPS16
      text data bss dec hex filename
      1560 0 412 1972 7b4 cdc_main.o
      692 0 0 692 2b4 cdcmsc162.o
      156 24 0 180 b4 desc_cdcmsc.o
      1304 512 20 1836 72c usb162.o
      6788 544 540 7872 1ec0 serial162.elf

      だいぶ埋めたから、これから大きくは増えないものの、こんな差になっている。MIPS16 使っても AVR の 1.5 倍は覚悟しないとダメそうだ。もともと print + USB serial でデバッグしようなんて考えていたのだが、この調子だと肝心のプログラムが入らなさそうになっている。(USB HID ブートローダ使うと 16KB 取られる)

シリアル出力の確認

    #include <stdio.h>
    #include <p32mx/p32mx_core.h>
    #include <p32mx/p32mx_port.h>
    #include <p32mx/p32mx_uart.h>
    #include <p32mx/p32mx_sys.h>
    #include <p32mx/console.h>

    // Olimex PIC32-PINGUINO-MX220
    // LED1 (Green) D13 = RB15 Active H (1: Light on)
    // LED2 (Red ) D9 = RA10 Active H (1: Light on)
    // BUT D8 = RB7 Active L (0 : Pressed)

    uint8_t heap_area[256] HEAP_SECTION;

    #define UART_CH 0
    static inline void serial_setbrg(const long baud) {
    U_BRG(UART_CH) = (clkc_get_pbclk()/16/baud - 1 ) & 0xffff;
    }

    int main() {
    int i;
    regx_clr(TRIS , 1, 15);
    regx_clr(TRIS , 0, 10);
    regx_clr(LAT , 1, 15);
    regx_clr(LAT , 0, 10);

    for (i=0;i<5;i++) {
    __delay_loop_1(10000000);
    regx_inv(LAT, 0, 10);
    }
    serial_init(0);
    serial_setbrg(50); // veri slow rate
    jzcon_setup();
    for (i=0;i<4;i++) {
    __delay_loop_1(10000000);
    regx_inv(LAT, 1, 15);
    }
    RPB15R = PPS1_OUT_U1TX;; // assign PB15
    printf("hello world\n");
    regx_clr(LAT , 1, 15);
    RPB15R = PPS1_OUT_NONE;; // assign PB15
    for (i=0;i<5;i++) {
    __delay_loop_1(10000000);
    regx_inv(LAT, 0, 10);
    }
    for (i=0;i<5;i++) {
    __delay_loop_1(10000000);
    regx_inv(LAT, 1, 15);
    }
    for (;;) {
    }
    }

    hello.c -fno-builtin-printf を付けないでコンパイルすると、この printf は、gcc の機能でputs になる。余計なことを ... と思ったが、puts でないと今のところ動かないようだ。

    さて、ボーレートを 50 まで落として確認してみたところ、LED の光り具合で なにか送信していることを目視で確認できた。

    RPB15R = PPS1_OUT_U1TX;
    RPB15R = PPS1_OUT_NONE;

    というのが、U1TX を PB15 に割り当てたり元に戻したりするためのコード。出力については同一機能を複数の ピンに割り当てられる。だから本来どこに割り当たっているか気にせず、LED に割り当てられる。( 入力は逆で、同一のピンを複数の機能に割り当てられる。)

    あと、ポートの制御だが、regx_set/_clr/_inv という風にできるようにした。それぞれ _SET/_CLR/_INV のレジスタに ビットをセットする。regx は、REGx のようにインデックスが着くレジスタの場合に使用し(0 オリジン)、インデックスがないレジスタには、reg_ を使う。

    clkc_get_pbclk() は PBCLK の周波数を推定する関数。特に USB を使うようなチップでは、PLL に 4MHz を入力することになっているため、それを基準にして PLL の倍数 や ポストスケーラーの設定から 周波数を 決めている。あと Fast RC オシレータは 8MHz と分かっているのでそこから計算。対になる機能として、clkc_get_sysclk() がある。こちらも同様だが、CPU のクロックを推定する機能。

    ところで、 -fno-builtin-printf を付けたら動かないと書いたが、初期化前の LED の点滅さえ動かなくなる。ライタに問題があったり、リンクスクリプトに問題があったりするのかも知れない。printf がバグっているかどうか以前の問題なのだ。

jzlib-0.2a

     ・ jzlib-0.2a.tar.gz

    ぜんぜん問題は解決していないが、0.2a にした。つけた機能は FLASH の書き込み のコード。あと割り込みベクタをまったく使わない リンクの サポート。-bu としていたところを -nvbu とする予定 (一部のみ対応)

    あまり関係ないが、RetroBSD も少し見ている。MicroChip のコード由来だが、USB HID ブートローダのコード(sys/pic32/usbboot)も入っていた。見たいコードが一式入っているかんじで参考になる。

jzlib-0.2b

     ・ jzlib-0.2b.tar.gz

    printf を使うとサイズがひどいことになっていた原因は、ll とか long long をサポートしているためだと分かった。サポートしないようにしたところ、4KB 以上も減った。あと、割り込みベクタを生成しないオプション を作ったところ、1KB 以上減った。結局 printf で 2KB 強増えるだけで済むようにできた。

    text data bss dec hex filename
    9468 276 112 9856 2680 hello.elf (0.2a) printf
    5624 20 368 6012 177c hello.elf (0.2b) printf
    4392 20 368 4780 12ac hello.elf (0.2b) printf , no vector
    2044 20 368 2432 980 hello.elf (0.2b) puts, no vector

    割り込みベクタを生成しないオプションは、デバッグで違いを見るためと ブートローダを作れるようにする準備。で、HEX ファイルを見ていて気がついたのだが、heap 領域の初期値が、HEX ファイルに出てくる。どうも (NOLOAD) を指定しないとだめらしい。

    HEX ファイルはチェックしないとまずい。Pinguino IDE に付属の MPHidFlash を試しに使ったら、ブートローダを飛ばしてしまった。いまのところ安全なのは、pic32prog 。これは書き込む領域をチェックしてくれているみたい。

HEX ファイルの確認

    まずは、簡単に構造を説明しておくと ..

     : サイズ(2) オフセット(4) タイプ(2) データ(サイズx2) チェックサム(2)
      (x) は桁数
    タイプ
    00 データ
    01 EOF
    04 拡張アドレス
    05 スタートアドレス (通常使われないが、objcopy で出てくる)

    こうなっている。zjlib で 典型的なのは、こんな感じ。

     :020000049D005D
     :10400000009D1A3C10405A270800400300000000A1
      :
     :04414000000000007B
     :10418000009D1A3C10405A27080040030000000020
     :10420000009D1A3C10425A2708004003000000009D
      :
     :02000004BFC07B
     :10000000009D1A3C10405A270800400300000000E1
     :040000059D0040001A
     :00000001FF

    スペースを入れるとこう。

     :02 0000 04 9D00 5D
     :10 4000 00 009D1A3C10405A270800400300000000 A1
      :
     :04 4140 00 00000000 7B
     :10 4180 00 009D1A3C10405A270800400300000000 20
     :10 4200 00 009D1A3C10425A270800400300000000 9D
      :
     :02 0000 04 BFC0 7B
     :10 0000 00 009D1A3C10405A270800400300000000 E1
     :04 0000 05 9D004000 1A
     :00 0000 01 FF

    随分見やすくなった。意味は、

      0x9d00_0000 (プログラムフラッシュの先頭) 〜
       オフセット 0x4000 (スタートアドレス) からのデータ
       オフセット 0x4180 (一般例外ベクタ) からのデータ
       オフセット 0x4200 (割り込みベクタ) からのデータ
      0xbfc0_0000 (ブートフラッシュの先頭) 〜
       オフセット 0x0000 (リセットアドレス) からのデータ
      スタートアドレス (0x9d004000)

    リセットアドレスに ジャンプ命令を入れているのだが、ブートローダを使う場合には保護されているはずだし、ツールもはじいてくれるから ... と思って入れていたのだ。

    だが、ちょっとまずい。最後の EOF を除く 3 行は削除した方が安心できる。

    コンフィグデータを生成すると (examples/config-sample.hex)

     :04 0BF0 00 FFFFFF0F F5
     :04 0BF4 00 D979F9FF B3
     :04 0BF8 00 5BCE60FF 71
     :04 0BFC 00 EEFFFF7F 8A

    このようなデータが 最後の方に追加される。これも pic32prog でははじいてくれるが、うっかり書き込むと危険。

    さて、PINGUINO IDE に付属の PIC32-Pinguino_HIDBoot_MX220.hex をちょっと見てみよう。 :02000004 のデータ が 各エリアの先頭を示すはずだから grep してみる。

     :02 0000 04 0000 fa
     :02 0000 04 1fc0 1b
     :02 0000 04 0000 fa
     :02 0000 04 1fc0 1b
    :
     :02 0000 04 0000 fa
     :02 0000 04 1d00 dd
     :02 0000 04 0000 fa
     :02 0000 04 1d00 dd
     :02 0000 04 0000 fa
     :02 0000 04 1d00 dd
     :02 0000 04 0000 fa
     :02 0000 04 1d00 dd

    随分沢山出てくるのだった。アドレス 0000 のものは、続いて 1fc0 , 1d00 の行が出てくるから、無視して良さそう。で、1fc0 , 1d00 が何かというと 物理アドレス。上で出てくる BFC0 , 9D00 は仮想アドレス。仮想アドレスの 上位 2 bit を 0 にすると物理アドレスになる。

    pic32prog では仮想アドレスでも 問題ないのだが ... 正しくは物理アドレスを渡さないといけない。 ちなみに沢山出てくるのは、連続していないと とりあえず出力しているためのようだ。

     :04 0bf4 00d979f9ff b3
     :04 0bf8 005bce60ff 71
     :04 0bfc 00eeffff7f 8a

    ちなみに、このようなコンフィグデータが含まれている。:040bf で grep すれば見つかる。

    もう少し詳しくみてみる。

     : 02 0000 04 0000 fa
     : 02 0000 04 1fc0 1b
     : 04 0bf4 00 d979f9ff b3
     : 02 0000 04 0000 fa
     : 02 0000 04 1fc0 1b
     : 04 0bf8 00 5bce60ff 71
     : 02 0000 04 0000 fa
     : 02 0000 04 1fc0 1b
     : 04 0bfc 00 eeffff7f 8a
     : 02 0000 04 0000 fa
     : 02 0000 04 1fc0 1b
     : 10 0000 00 c0bf1a3c10005a270800400300000000 3f
     : 10 0010 00 00601a40c0045a7f0500401300000000 31
      :
     : 10 01e0 00 0000a530009d083c402f082508000001 b4
     : 04 01f0 00 00000000 0b
     : 02 0000 04 0000 fa
     : 02 0000 04 1fc0 1b
     : 10 0380 00 009d1a3c5c2f5a270800400300000000 23
     : 02 0000 04 0000 fa
     : 02 0000 04 1d00 dd
     : 10 0180 00 009d1a3c2c295a270800400300000000 5b
     : 02 0000 04 0000 fa
     : 02 0000 04 1d00 dd
      :

    最初の方にコンフィグデータがあり、次にブートフラッシュの先頭にもどる。0000 はリセットアドレスで、スタートアップが入っている。0380 と 0180 は、ブート時の割り込みベクタ。この短さだと 実際には割り込みを使っていないのかも知れない。で、どうもこれだけのようだ。3KB あるが、スタートアップと コンフィグにしか使われていない感じ。プログラム本体は、0a00 から始まっていて、2f90 まで続いている。( ちなみに、アプリケーションプログラムは、0x3000 から ベクター領域になっていて 0x4000 がスタートアドレスになる )

    PIC32MX は、プログラムフラッシュも書き込み保護ができる。 保護されていたら ブートローダを飛ばすこともなかったのだが ...

jzlib-0.2d

     ・ jzlib-0.2d.tar.gz

    ブートローダの検討』で紹介した ブートローダのソースコードを同梱したのが変更の目玉。ただし、テストも初めていない。コードを書いてみて API を早めに修正したいのだ。(動く動かないより重要だと考えてえいる)。ブートローダが必要になったので、テストの優先順位は高いが 先のはなし。

    API として p32mx/bootloader.h を追加している。自製のブートローダも作るつもりで、共通化できるところを入れている。

    あと、p32mx_core.h も結構変更している。HID ブートローダ の移植で 変更が必要になった。あと p32mx_usbotg.h も。

    さらに、5xx/6xx/7xx に対応可能かすこし調べだしている。見ていると UART とか SPI とか数が増えていて、番号とアドレスの関係が ややこしいことになっている。で SPI4 とか UART6 とか PORTG を index の意味で 定義することにした。

ポート制御の簡潔な記法

    #include <p32mx/p32mx_core.h>
    #include <p32mx/p32mx_port.h>

    int main() {
    port_clr(TRIS , PB15);
    port_clr(TRIS , PA10);
    port_set(LAT , PB15);
    port_clr(LAT , PA10);
    for (;;) {
    __delay_loop_1(10000000);
    port_inv(LAT , PB15);
    }
    }

    こういう書き方が出来ることが分かった。
    実体は、

    #define port_clr(...) regx_clr(__VA_ARGS__)
    #define PB15 1,15

    NG regx_clr(TRIS , PB15);
    OK port_clr(TRIS , PB15);

    どうも一回 可変引数の マクロを通すと これができるらしい。次の版でいれよう。

     ・ jzlib-0.2e.tar.gz

    とりあえず次の版を置いておく。hidboot の コードを変更。作成中の USBASP ブートローダのコードをマージ。あと usb_device.c をちょっと見ている。使っていない関数を statc に変更して整理。

      なんというか、usb_device.c がでかい。(ブートローダでは)絶対動かないコードが多数含まれているんじゃないかと思える。でも if 文で直接 call されているようで切り離せない。とりあえず、static にしてみた。(すこし小さくなる)。あと、ブートローダ・コンフィグのコードは最適化して小さくするようにしたので、マージ。

      text data bss dec hex filename
      7968 52 535 8555 216b usbboot.elf

      結果 1KB ほど小さくなった。


    ところで、printf を使うと全く動かなる件。やっぱり分からない。

    < ok.out: file format elf32-littlemips
    ---
    > bad.out: file format elf32-littlemips

    < 9d0040d4: 2508483c addiu t0,t0,18492
    ---
    > 9d0040d4: 25085164 addiu t0,t0,20836

    < 9d0041e8: 77401147 jalx 9d00451c <_2xx_serial0_init>
    ---
    > 9d0041e8: 77401355 jalx 9d004d54 <_2xx_serial0_init>

    a.out を objdump で見ると、頭の部分の違いは、こんな感じ。40d4 の違いは、.data の初期値 の アドレスで問題ない。で、_2xx_serial0_init まで同じだとすると ... LED が点灯しないはずはないのだ。FLASH に期待どうりに書きこまれていないのではないか? というのが今の疑い。ひょっとして、HEX ファイルの作り方が正しくない?

    で、pic32prog のソースコードを見ているのだが、読み込みは基本的には問題なさそう。

      内部は、フラッシュ用データとブート用データに分けて管理している。で、読み込んできたデータを それぞれのバッファに埋め込むような処理になっている。そして、1KB 毎にどこのデータが変更されたかも管理していて、変更のあったところを書き込むようだ。

      読み込むのは、物理アドレス(1D00_0000 , 1FC0_0000) と 仮想アドレス (9D00_0000 , 9FC0_0000) に対応していてどちらでも良い。ただし kseg1 アドレスは、範囲外にされてしまう。コンフィグ情報は、kseg1 アドレス (BFC0_0000) にしているので、そのままでは書き込めない。あと、リセットアドレスに埋め込んでいるジャンプコードも。

      ついでに書いておくとスタートアドレスは、単に無視する。あってもなくても良い。

printf が動かない理由

    少し進展があった。まず、一般例外が起きているはずで、これが起きたら LED を高速点滅させることで分かるようにした。言うは易しで、割り込みをサポートしないつもりのタイプで これを出来るようにするのは結構面倒だった。また、この機能はブートローダでも使いたい。... これで結構なやんだのだが 次のようにすることにした。

    で、printf というか FILE 構造体を扱う場合は calloc が呼ばれていたのだが ... よくよくコードを見たら calloc を使わないような造りができるのだった。それを直したら、かなり現象がシンプルになった。最初から全く動かないという現象はなくなって、動いたり動かなかったりになった。動かない場合でも、途中までは動く。で、動かなくなるところは、jzcon_apped という関数。

    あまりに単純な関数なので、何故動かないかなかなか分からなかったのだが、hello.elf の逆アセンブルを眺めていてようやく気がついた。

    単純化したとは言え printf とかいろいろ関数がある中で、この関数だけが gp を使っている。

      オプションなしだと、gp は、グローバル変数のアクセスに使われる。使いかたは、

      9d004ed4: 8f828010 lw v0,-32752(gp)

      9d004f58: af828010 sw v0,-32752(gp)

      gp には、常に 0xa0007FF0 が入っていて、この場合、0xa0000000 の変数をアクセスするはずなのだ。どうも全ての変数がこのようなアクセスをするわけではないようで、配列とかは、絶対値をロードして使っている。auto 変数にも gp は当然使わない。ライブラリも グローバル変数はあまり使わない。malloc は当然使うので、ややこしい現象になっていた(らしい)。

      例外を起こすから、gp の値がおかしいとしか思えないのだが ... どうするのが正しいのか良くわかっていない。

      そう言えば、以前も Jz47xx のライブラリとして使った時も相当悩んだのだった。ここさえクリアすればいろんなものが動くようになりそう。

    BMX

      いままで、関係無いだろうと思って見ていなかったのだが ... いろいろと機能があった。

      BMXCON_BMXERRIS: Bus Error from CPU Instruction Access bit
      BMXCON_BMXERRDS: Bus Error from CPU Data Access bit
      BMXCON_BMXERRDMA: Bus Error from DMA bit
      BMXCON_BMXERRICD: Enable Bus Error from ICD Debug Unit bit
      BMXCON_BMXERRIXI: Enable Bus Error from IXI bit

      例外をそもそも起こさないというモードが設定できた。これらは、Reset で 1 なので 0 にすると例外を起こさなくなる。

      BMXCON_BMXWSDRM: CPU Instruction or Data Access from Data RAM Wait State bit

      ブートローダで唯一クリアしているビット。0 ウエイトにしている。

      BMXDRMSZ: DATA RAM SIZE REGISTER
      BMXPFMSZ: PROGRAM FLASH (PFM) SIZE REGISTER
      BMXBOOTSZ: BOOT FLASH (IFM) SIZE REGISTER

      RAM やフラッシュの サイズが見える レジスタもあった。これを元にスタックの値を設定したほうが良いかも知れない。フラッシュのサイズは、ブートローダで使える。

      BMXDKPBA: DATA RAM KERNEL PROGRAM BASE ADDRESS REGISTER
      BMXDUDBA: DATA RAM USER DATA BASE ADDRESS REGISTER
      BMXDUPBA: DATA RAM USER PROGRAM BASE ADDRESS REGISTER

      (BMXDKPBA > BMXDUDBA > BMXDUPBA)

      User Mode /Kernel Mode に RAM を割り当てる機能らしい。どうも 簡単な 論理→物理変換機能で、この値だけ物理アドレスを上位にずらすらしい。 User Mode は分かるのだが、Kernel Mode までずらせてなにが嬉しいのだろう? このキーワードでググればなにかヒントが出てくるのかも。それはともかく間違えて設定するとまずい。要注意のようだ。

      /* Step 1. */
      xfer_instruction (a, 0x3c04bf88); // lui a0, 0xbf88
      xfer_instruction (a, 0x34842000); // ori a0, 0x2000 - address of BMXCON
      xfer_instruction (a, 0x3c05001f); // lui a1, 0x1f
      xfer_instruction (a, 0x34a50040); // ori a1, 0x40 - a1 has 001f0040
      xfer_instruction (a, 0xac850000); // sw a1, 0(a0) - BMXCON initialized

      /* Step 2. */
      xfer_instruction (a, 0x34050800); // li a1, 0x800 - a1 has 00000800
      xfer_instruction (a, 0xac850010); // sw a1, 16(a0) - BMXDKPBA initialized

      /* Step 3. */
      xfer_instruction (a, 0x8c850040); // lw a1, 64(a0) - load BMXDMSZ
      xfer_instruction (a, 0xac850020); // sw a1, 32(a0) - BMXDUDBA initialized
      xfer_instruction (a, 0xac850030); // sw a1, 48(a0) - BMXDUPBA initialized

      pic32prog を見ていたらこんなコードがあった。割り込みを受けたくないなら、BMXCONを 設定するのが普通なのkも知れない。あと、BMXDKPBA, BMXDUPBA, BMXDUDBA をちゃんと設定している。この場合は BMXDKPBA=2K だからなにか理由があって使っているのだろうけれども、設定した方がよいのかも。

    試してみた。

      アドレスで例外を起こさないようにしたら、今度は、 Reserved instruct になった。関数ポインタ使っているから当然か。コンパイルオプションで -G 0 を付けると gp を使わなくなる。でも結果は同じ。

printf が動いた。

    jzcon が悪いのなら使わなければ良い。もともと 大したことはしていなくて、ただの FILE の分配機能 -- printf とすると serial にも出力するし、console にも出力する みたいなことをするものなのだ。

    で、USB シリアルに接続して、表示させたところ見事に出力された。

      hello world 5 5 000003b0
      DEVCFG3 = 0x3fffffff
      DEVCFG2 = 0xfff979d9
      DEVCFG1 = 0xff60cedb
      DEVCFG0 = 0x7fffffeb

    今もっとも見たいのは、DEVCFG で JTAG が disable されているかどうか。残念なことに disable されていた。また、書き込み保護を一切していない。これだと壊れる場合も出てくるのは当然か。

    それはともかく、これで知りたい情報が いくらでも見れる。jzlib のデバッグが進むはず。

      gp a0007ff0
      sp a0001fd8
      c0_status 10100000
      c0_cause 18800028
      c0_prid 00018769
      c0_intctl 00000020
      c0_srsctl 00000000
      c0_ebase 9d004000
      c0_compare ffffffff

    次にいろいろな レジスタ。とりあえず p32mx_core.h にある read_xx 関数を片っ端から表示させた。c0_srsctl が意外。これだと シャドウレジスタが存在しないことになる。c0_intctl は、VS=1 という設定だけが入っている。これはひとつの 割り込みベクタが 32B という意味。( マルチベクタモードでは、64 個あるから 全部で 2KB 。)

      a0000000: 00000000, 00000000, 00000000, 00000000
      a0000010: 00000000, 00000000, 00000000, 00000000
      a0000020: 00000200, 00000000, 00000190, 9d004220
      a0000030: 00000000, 00000000, 00000000, 00000200

    次にメモリダンプ。グローバル変数は、stdout などが入る __iob[] の 1 つしか使っていない。そのデータ。

      ここで、ブートローダの ダンプも取った。これで 、完全に元に戻せる。一安心。

    さて、 malloc 関係のデータのアドレスを参照してみる(参照だけで call しない)と ...

      __malloc_mergin addr =
      : , , ,
      : , , ,
      : , , ,
      : , , ,

    ... これはそのまま貼り付けてみたもの。こういう風に %08x の部分のみ 文字化けする。これはなにを意味しているのか? なぜこうなるかを調べることで原因がわかるはず。

    まず、%x の処理を見てみることにした。


      case 'X':
      xdigs = "0123456789ABCDEF";
      goto hex;
      case 'x':
      xdigs = "0123456789abcdef";
      hex: _uintmax = UARG();
      :
      :
      :

      case HEX:
      do {
      *--bp = xdigs[(size_t)
      (_uintmax & 15)];
      _uintmax >>= 4;
      } while (_uintmax);
      break;

      こんな感じ。xdigs に入れるアドレスが 変わってしまえば、文字化けするかも知れないが ... ほかの理由はあまり考え付かない。とりあえず 上半分が アセンブラでどうなっているか追跡。

      $L149:
      move $16,$4
      $L151:
      li $4,1
      b $L62
      $L34:
      lw $5,$L160
      sw $2,60($sp)
      sw $4,56($sp)
      sw $5,88($sp)
      b $L23
      $L127:
      lw $2,$L162
      sw $2,88($sp)
      $L23:

      $L160:
      .word $LC2
      $L161:
      .word $LC0
      $L162:
      .word $LC1

      $LC1:
      .ascii "0123456789ABCDEF\000"
      .align 2
      $LC2:
      .ascii "0123456789abcdef\000"
      .text

      データを置いて、そのアドレスも置く。で lw で アドレスを取ってきている。これが a.out で どうなったか見てみる。

      304: 6c01 li a0,1
      306: 1023 b 34e <vfprintf+0x34e>
      308: b5ba lw a1,5f0 <vfprintf+0x5f0>
      30a: d20f sw v0,60(sp)
      30c: d40e sw a0,56(sp)
      30e: d516 sw a1,88(sp)
      310: 1002 b 316 <vfprintf+0x316>
      312: b2ba lw v0,5f8 <vfprintf+0x5f8>
      314: d216 sw v0,88(sp)
      316: 6aff li v0,255

      9d004d40: 5014
      9d004d42: 9d00
      9d004d44: 4ff8
      9d004d46: 9d00
      9d004d48: 5000
      9d004d4a: 9d00
      9d004d4c: 5048
      9d004d4e: 9d00

      9d004d50: 5038
      9d004d52: 9d00
      9d004d54: 5038
      9d004d56: 9d00
      9d004d58: 5028
      9d004d5a: 9d00

      アドレスのテーブルは合っていて、それへのアクセスも合っているように見える。...ただ、lw が関数の先頭からの相対になっている。実をいうと MIPS の命令にそのようなものはない。ルールがあって、関数の先頭が入っているレジスタが決まっているのだ。call 先アドレスを そこに入れて call する。このルールどおりでないと、ローカルなデータアクセスも 正しくできない。... というのは間違いだった。PC 相対ができるみたいだ。

      グローバル変数を使うとこのルールが破られる? ... どうも腑に落ちないが そういうことでもない限りこういう現象にならないような気がする。

      見落としていたが、%d は正しく表示できている。... ということは、やはり %x での string のアドレスが 間違っていることになる。

      で、やっぱり PC 相対で lw している。a.out の vfprintf の 部分を比べてみたが、fputc とかの 関数 call のアドレス以外 まったく同一。そして上で示したように テーブルのアドレス値もあっているのだ。もうなにがなんだか。

        __malloc_mergin addr = a0000000
        a0000000: 00000200, a0000070, a0000270, ffffffff
        a0000010: 00000000, 00000000, 00000000, 00000000
        a0000020: 00000000, 00000000, 00000000, 00000000
        a0000030: 00000000, 00000200, 00000000, 000001e2

      原因ははっきりしたので、printf だけは直せる。static 変数を使わないようにすれば良いわけだ。問題は解決していないのだが、printf が動かないことには、情報が取れない。

      直したらこのように動くようになった。サイズが小さくなるというおまけまでついた。

      text data bss dec hex filename
      4060 12 612 4684 124c hello.elf (org)
      3856 12 612 4480 1180 hello.elf (new)

      ただ、現象がどのようになるのか見るのに printf は使えなくなった。代わりのものでデバッグしなくては。

      ちょっと進展というか ...

      __malloc_mergin addr = a0000000
      a0000000: 00000200, a0000070, a0000270, 00000000
      a0000010: 00000000, 00000000, 00000000, 00000000
      a0000020: 00000000, 00000000, 00000000, 00000000
      a0000030: 00000000, 00000000, 00000200, 00000000
      a0000040: 00000214, 9d004220, 00000000, 00000000
      a0000050: 00000000, 00000200, 00000000, 00000000
      a0000060: 9d004220, 00000000, 00000000, 00000000
      a0000070: 00000000, 00000000, 00000000, 00000000

      たとえばこういう風にメモリがちゃんと初期化されて見える場合がある。

      __malloc_mergin addr = a0000000
      a0000000: ffffffff, ffffffff, ffffffff, 00000000
      a0000010: 00000000, 00000000, 00000000, 00000000
      a0000020: 00000000, 00000000, 00000000, 00000000
      a0000030: 00000000, 00000000, 00000200, 00000000
      a0000040: 00000214, 9d004220, 00000000, 00000000
      a0000050: 00000000, 00000200, 00000000, 00000000
      a0000060: 9d004220, 00000000, 00000000, 00000000
      a0000070: 00000000, 00000000, 00000000, 00000000
      a0000080: 00000000, 00000000, 00000000, 00000000
      a0000090: 00000000, 00000000, 00000000, 00000000
      __malloc_mergin addr = a0000000 val = 00001234
      a0000000: 00001234, ffffffff, ffffffff, 00000000
      a0000010: 00000000, 00000000, 00000000, 00000000
      a0000020: 00000000, 00000000, 00000000, 00000000

      一方 メモリへの書き込みがうまくいくが、メモリの初期化がおかしい場合がある。3 ワード だけ all 1 になっているが、3 ワードしかデータがないのだ。そして初期値を入れるのは、スタートアップだけ。アドレスが間違っていればこうなるが、la で とってきているので、間違いはない。

      ... これは! なぜなのか? FLASH がちゃんと読めない場合があるということではないのだろうか?

      c0_config a4210582

      C0_CONFIG の値をとっていなかったので、メモしておく。キャッシュは OFF ( LSB から 3bit が 2 / ON なら 3)

jzlib-0.3c

     ・ jzlib-0.3c.tar.gz

    そんなわけで、メモリを読んで ダンプし、書き込んだものと比較することにした。

    書き込んだものとは ... objcopy で作った ihex フォーマット。これと比較するには、プログラム自体も 比較可能な形式での ihex を出力するのが便利だと考えた。

    で、新規に作ったのが、util/ihex_dump.h -- 全部 inline 関数。なので、HOST 側でも少しの手直しで使える。

      本来手直しなど不要のはずだったのだが、... MIPS32 と MIPS16 を混ぜた時、gcc のバグ?で不具合が生じた。関数の最後で 関数を call すると j 命令でジャンプするようになっている。だが、MIPS32 から MIPS16 に j 命令では移行できない。これがリンク時にエラーになる。しょうがないので、inline 関数の __nop() を最後に入れている。

       ・ ihex_conv.c

      ついでに例として ihex ファイルを整形する プログラムを置いておく。


    あと、リンク・スクリプトを RetroBSD の usbboot のものをベースに つくり変えた。こちらの方がシンプルで改造しやすい。

    で、結果なのだが、ちゃんと同じものが書けているようだ。ただし、ちゃんと動いたケースに限ったはなし。途中でおかしくなるものがどうなるかは確認できていない。

    ところで、PIC32MX は、CRC を計算する機能を持っている。どこにあるかというと DMAC の中らしい。CRC16 と PIC32MX で検索したら サンプルコードを みつけた。→ ここ 。DMAC の使い方の練習にもなるかも知れない。メモ。

      DCRCDATA=get_non_direct_seed(seed);// seed the CRC generator
      DCRCXOR=0x1021; // Use the standard CCITT CRC 16 polynomial: X^16+X^12+X^5+1

      DCRCCON=0x00000FC0; // CRC enabled, polynomial length 16, append mode,
      // CRC attached to the DMA channel 0.

      良くわかっているわけではないが、どうもこのあたりのレジスタが CRC 自体の設定らしい。

      DMACONSET=0x00008000; // enable the DMA controller

      // do something else while the CRC calculation takes place
      // poll to see that the transfer was done

      DCH_CON(0)=0x03; // channel off, priority 3, no chaining
      DCH_ECON(0)=0; // no start irqs, no match enabled

      // program channel transfer
      DCH_SSA(0) = vtop(buffer); // transfer source physical address
      DCH_DSA(0)= vtop(&tmp); // transfer destination physical address
      DCH_SSIZ(0)=size+2; // source size
      DCH_DSIZ(0)=4; // dst size
      DCH_CSIZ(0) =size+2; // 200 bytes transferred per event
      DCH_INTCLR(0)=0x00ff00ff; // DMA0: clear events, disable interrupts
      DCH_INTCLR(1)=0x00ff00ff; // DMA1: clear events, disable interrupts

      DCH_CONSET(0)=0x80; // channel 0 on
      DCH_ECONSET(0)=0x00000080; // initiate a transfer

      DMA のチャネル制御を jzlib 用に書き直すとこう。 なかなか長い呪文だ。それはともかく、CRC モジュールはひとつで、使う DMA チャネルを選ぶという使い方のようだ。

関連記事
posted by すz at 20:39| Comment(0) | TrackBack(0) | PIC32MX

2012年07月21日

PIC32MX用ライブラリの検討(1)

FT232R ベースのライタは、デバッグ前まで来た。たぶんなんとかなるので、ライブラリについての検討を始めようと思う。

    もう一度書くが、ベースとするのは、
    USBBOOTプログラム用ライブラリ』の記事の usbboot-wk19.tar.gz

    Ingenic の MIPS プロセッサ 用として作っていたのだが、放置中。ライブラリの名前は jzlib 。AVR-libc ようなものを目指していて MichroChip のライブラリと全然違うもの。PIC32 とか名前に入れずに jzlib の PIC32MX 対応版という位置づけにしようかと思う。

    ライブラリ自体は、AVR-libc から取ってきたり、NetBSD から取ってきたりしてでっち上げている。mips16e には対応できていないが、mips32 なら コンパイルするだけで済む。課題は、

     ・ スタートアップ
     ・ 割り込みエントリ
     ・ リンカ・スクリプト
     ・ ヘッダファイル
     ・ libm

    こんなところで、PIC32MX の環境に対応することなのだ。あ、libm は単に後回しになっているだけ。

ディレクトリ体系

    ディレクトリ体系とか ファイル名をどうするか -- こういうところから始めないといけない。さて、どうしよう。
    まずは、手本となる AVR-libc がどうなっているのかの確認から。

      TOP/avr/include/

      alloca.h ctype.h inttypes.h setjmp.h stdio.h string.h
      assert.h errno.h math.h stdint.h stdlib.h

      TOP/avr/include/util/
      atomic.h crc16.h delay.h delay_basic.h
      parity.h setbaud.h twi.h

      TOP/avr/include/avr/io.h
      TOP/avr/include/avr/iousbxx2.h
      TOP/avr/include/avr/iousb162.h

      TOP/avr/lib/avr3/crtusb162.o
      TOP/avr/lib/avr3/libc.a
      TOP/avr/lib/avr3/libm.a
      TOP/avr/lib/ldscripts/avr3.x

    おおざっぱに書いてしまうと、AVR は avr3 とかのカテゴライズがあって、その中での空間定義は同じ。libc とか libm ライブラリは共通になっている。

    スタートアップファイルはチップの種類毎に違う。レジスタの定義ファイルも別で、crtXXX.o と ioXXX.h の関係になっている。ただし、ヘッダファイルは、共通部分が別にあったりする。

    これをもとに jzlib のディレクトリ体系を考えてみる。

      TOP/mips-elf/include/
      alloca.h ctype.h inttypes.h limits.h setjmp.h stdio.h string.h
      assert.h errno.h math.h stdint.h stdlib.h

      TOP/mips-elf/include/p32mx/interrupt.h

      TOP/mips-elf/include/p32mx/io.h
      TOP/mips-elf/include/p32mx/io2xx.h (?)
      TOP/mips-elf/include/p32mx/io220.h (?)

    インクルードは、こんな感じでどうだろう。p32 は pinguino とか 名前がぶつかるので避けて p32mx で作る。レジスタの定義は、とにかく io.h をインクルードするルール。

      #if defined(__32M4KCORE__)
        :
      #if defined(__32MX220F032B__)
      :

    define は、こういう決まりがあるみたいだから踏襲するが、

      #if defined(__32MX1XX__) || defined(__32MX2XX__)
      :

    こういうコードは使いたい。

      TOP/mips-elf/lib/p32mx/crt220.o
      TOP/mips-elf/lib/p32mx/libc.a
      TOP/mips-elf/lib/p32mx/libm.a

    ライブラリは、スタートアップ以外は共通にする。

      TOP/mips-elf/lib/ldscripts/p32mxb3k.x
      TOP/mips-elf/lib/ldscripts/p32mxb12k.x

    ldscripts は 2種類。ブートローダ の最後に コンフィグ情報があるので、ブートローダのサイズによって空間が変わる。メモリのサイズや 割り込みが空間として定義されなければ、これだけの違い。

インクルードファイル

    pinguino に添付されている p32mx220f032b.h p32mx250f128b.h をチェックしたのだが、 220 B と 250 B は本質的な違いがない。 220 D と 250 D も同様。だが、 220 B と 220 D には、850 行もの違いがあった。調べてみると PORTC の追加によってこれだけの違いが出ている。

    220 B と 120B も比較してみたのだが、400 行ぐらいの差分で、USB レジスタの有無が違いだった。

    どうも 220 D 用を基準にすると、後の 1xx/2xx は、そのサブセットになるようだ。とりあえず 220 D 用を作ってあとは、おいおいやっていくことにしよう。

    さて、 p32mx220f032d.h だが、14278 lines もある。ここから使うものだけ抜き出して 再定義していくつもり。

    まず XXX_BASE_ADDRESS に注目して抜き出す。これは、42 個。機能ブロックが 42 個もあるということ。-- 相当面倒な予感がする。

    次に .extern の行が レジスタ名の定義らしいので抜き出すと ...

      .extern WDTCONINV /* 0xBF80000C */
      .extern RTCCON /* 0xBF800200 */
      .extern RTCCONCLR /* 0xBF800204 */
      .extern RTCCONSET /* 0xBF800208 */

    こんな感じ。これは、1139 個もある。AVR なんかと比べると相当に多い。幸いなことに アドレスが入っている。

      #define WDTCONINV (*(volatile unsigned *)0xBF80000C)
      #define _WDTCONINV 0xBF80000C

    これなら、スクリプトで変換できる。

    次は、レジスタの中のフラグ名。恐ろしいことになりそうだ。

      #define _WDTCON_WDTCLR_POSITION 0x00000000
      #define _WDTCON_WDTCLR_MASK 0x00000001
      #define _WDTCON_WDTCLR_LENGTH 0x00000001

    こんな感じで定義されていた。_POSITION に注目すると 1994 個。だが、_DEVCFG1_w_POSITION みたいな変なのが混じっている。これは不要なので抜くと 1861 個。

      #define _WDTCON_WDTCLR 0x00

    こんな風に変換はできるのだが、どうも気に入らない。整理して特徴を掴まないと使いこなせる気がしない。

    だが、整理するのにも時間がかかる。

PORT の定義

    /* x=0:PORTA , x=1:PORTB , x=2:PORTC */
    #define ANSELx(x) (*(volatile unsigned *)(0xBF886000 + (x)*0x100))
    #define ANSELxCLR(x) (*(volatile unsigned *)(0xBF886004 + (x)*0x100))
    #define ANSELxSET(x) (*(volatile unsigned *)(0xBF886008 + (x)*0x100))
    #define ANSELxINV(x) (*(volatile unsigned *)(0xBF88600C + (x)*0x100))
            :
    他に、TRISx, PORTx, LATx, ODCx, CNPUx, CNPDx, CNCONx, CNENx, CNSTAT

    #define CNCON_SIDL 0x0D
    #define CNCON_ON 0x0F

    どうも、こういうことらしい。 レジスタが base としてあって、CLR 用, SET 用, 反転用の Write-Only レジスタが規則正しく並ぶ。CLR 用といっても PORT出力 を L にするわけではない。単にレジスタの操作として 定義されていて、どのレジスタにも CLR がある。

    そして、0x100 おきに PORTA , PORTB, PORTC と並んでいる。

    オリジナルでは、それぞれの ビットの定義に数千行を費やしているのだが、あっさり切る。

      /* x=0:PORTA , x=1:PORTB , x=2:PORTC */
      #define ANSEL(x) (*(volatile unsigned *)(0xBF886000 + (x)*0x100))
      #define ANSEL_CLR(x) (*(volatile unsigned *)(0xBF886004 + (x)*0x100))
      #define ANSEL_SET(x) (*(volatile unsigned *)(0xBF886008 + (x)*0x100))
      #define ANSEL_INV(x) (*(volatile unsigned *)(0xBF88600C + (x)*0x100))
              :

    他のレジスタなども見てたのだが、CLE/SET/INV の前に _ を付けることにする。ANSELx としたのは、正式名が ANSELA などで x は 置き換わるという意味。だが、このルールで統一すると I2C1STAT_CLR は、 I2CxSTAT_CLR となって見栄えが悪い。 I2C_STAT_CLR のほうがすっきりする。

    というわけで、x はやめて、_ にする。末尾が _ になったり __ と重なる場合は、省略。

    こうやって モジュール単位でまとめ p32mx/port.h にしようと思う。p32mx/io.h にまとめるのは、ヤメ。

    実を言うともうひとつ課題がある。全部 32bit でとりあえず作っていくが、MIPS は 32bit の即値は効率が悪い。これだと アドレスも データも 32bit になってしまう。

    8bit アクセスや 16bit アクセス のマクロなども必要に応じて作っていこうかと思う。たぶんレジスタ名 + b が 8bit , レジスタ名 + w が 16bit になると思うが、実際にコードを書き始めてから決める。

     ・ p32mx-inc-wk001.tar.gz

    なんか、出来た。

      152 include/p32mx/adc.h (ADC & camparator & CTMU)
      53 include/p32mx/capt.h (Input & Output Capture)
      70 include/p32mx/clkc.h (Oscillator Control)
      60 include/p32mx/devcfg.h
      123 include/p32mx/dmac.h
      70 include/p32mx/i2c.h
      109 include/p32mx/intc.h
      31 include/p32mx/nvm.h
      57 include/p32mx/port.h
      134 include/p32mx/pps.h
      67 include/p32mx/rtc.h
      66 include/p32mx/spi.h
      300 include/p32mx/sys.h (PPS - Peripheral Pin Select 他)
      30 include/p32mx/timer.h
      61 include/p32mx/uart.h
      220 include/p32mx/usbotg.h
      42 include/p32mx/wdt.h (Watch Dog Timer & Reset Control)
      1511 total

    1500 行だから、だいぶコンパクトになった。どれかに振り分けているから、不足はないはず。
    振り分けについては、アナログ関係は、adc.h にまとめ、リセット関係は wdt.h にまとめた。そして、良くわからないものは、全部 sys.h 。ただの define のかたまりだし、それぞれ独立。

    あと、jzlib では、スタートアップ と 割り込みに必要なものだけ使う。

    ちょっと 220 と 440 を比べて見たのだが随分違う。当面 1xx/2xx 専用ということにする。

    追記: ファイル名変更。
    チップのデバイスの ヘッダファイルは、p32mx_ を頭に付けることにした。ライブラリで定義する機能用のヘッダファイルと同名になってしまうのを避けるため。timer.h / wdt.h が実際に同名になってしまった。

      あまり大した機能を提供するつもりはないのだが、wdt.h は、AVR-libc タイプの API にするもの。timer.h は、割り込みのテスト用みたいなもの。あと時刻用の ostimer.h もある。

    追記: ファイル追加
    1つ忘れていた。デバイス関係ではなく、CPU コア関係で、p32mx_core.h

    read_c0_status() とか CP0 へのアクセス関数が主。割り込み禁止/許可も これに入っている irq_disable()/irq_restore() を使う。

    シャドウレジスタの制御とかも CP0 を使うことがわかった。使い方は、
     ・ MicroChip 製品ページ
    のリンクにある、リファレンス・マニュアル 第二章 CPU に書いてあった。

    キーワードは、IntCtl とか SRSCtl とか。( M14K のみとあるのは、使えないので注意。) これらのアクセス関数も p32mx_core.h に追加している。

割り込み

    PIC32MX は、シングルベクターモードにすると、ユーザプログラムに来る割り込みは、たったひとつになる。

      Flush:
      0xBD00_0000 (EBase)
      0xBD00_0200 Single Vector Mode (BEV==0)
      0xBD00_0200 Multi Vector Mode (BEV==0)
      + vector_no * 32 * VS(VS==1)

      Boot:
      0xBFC0_0000 Reset,Soft Reset,NMI
      0xBFC0_0200 EJTAG Debug (EJTAG ProbEn==1)
      0xBFC0_0380 all generic interrupt (BEV==1)
      0xBFC0_0480 EJTAG Debug (EJTAG ProbEn==0)

    0xBD00_0200 に飛び込んでくるわけなのだが、ここは kseg1 。キャッシュが効く kseg0 に同じメモリイメージがあるので、速やかに移動したほうが良い。

      kseg0 kseg1

      Flash 0x9D00_00000 0xBD00_0000
      Boot 0x9FC0_0000 0xBFC0_0000
      Config 0x9FC0_0BF0 0xBFC0_0BF0

    次に、レジスタのセーブだが、シャドウレジスタを最終的に使うつもり。割り込みも多段割り込みはサポートしない。どうせレジスタのアクセスは割り込み禁止にするのだ。一気に走り切った方が良いだろう。... これで随分簡単な話になる。

    その次に C 関数に飛び 割り込みを振り分けて ベクターを call する。振り分けるのは、IFS0/IFS1 に割り込み要因が入っているので、その bit 位置を ベクター番号(IRQ)にする。IRQ は、マルチベクターモードでのベクター番号とは違うもの。ベクターもただの関数テーブル。

    IRQ と 割り込み要因の割付は、p32mx/intc.h で定義した。

    #define IRQ_U2RX 0x36
    #define IRQ_U2TX 0x37

    こんな感じで 1xx/2xx では、きっちり 64 個ある。あと調べてみたのだが、3xx/4xx は、これより少ない。5xx/6xx/7xx は、IFS2 もあって 64 個を超える。

    さて、ベクターの登録だが、こんな風にする。

    (p32mx/interrupt.h)
    #define _VECTOR(irq) __vector_ ## irq
    #define SIGNAL(irq) int _VECTOR(irq) ()

    (user program)
    SIGNAL(IRQ_U2RX)
    {
    :
    __intc_ack_irq(IRQ_U2RX);
    return IRQ_HANDLED;
    }

    戻り値 IRQ_HANDLED(非ゼロ) は、割り込みのブロック解除。多重割り込みをサポートしないつもりだから、割り込み関数内で解除しても良いのだが、一応仕様上はこういうルールにしておく。

    割り込み要求は、呼ばれた関数が __intc_ack_irq() を実行して解除しないといけない。IRQ のフラグだけ解除しても多段になっていたりして、すぐに 1 になってしまうかも知れないので、原因を先にクリアしないとダメなのだ。

    uint32_t intent(uint32_t c0_status, uint32_t c0_cause, uint32_t c0_epc)
    {
    uint32_t isr;
    int v,r;

    c0_status &= ~0x1e;
    write_c0_status(c0_status & ~0x1); /* irq_disable */
    do {
    isr = IFS(0);
    v = ffs(isr);
    if (v) {
    v--;
    __intc_mask_irq(v & 0x1f);
    r = (__vectors[v])();
    if (r) {
    __intc_unmask_irq(v & 0x1f);
    }
    }
    } while (isr);
    do {
    isr = IFS(1);
    v = ffs(isr);
    if (v) {
    v--;
    __intc_mask_irq(v & 0x1f | 0x20);
    r = (__vectors[v | 0x20])();
    if (r) {
    __intc_unmask_irq(v & 0x1f | 0x20);
    }
    }
    } while (isr);

    write_c0_cause(0);
    return c0_status;
    }

    割り込みエントリの intent は、こんな感じ。前に作ったのを改造しただけなので、細いことは、思い出さないといけない。

    #define __intc_mask_irq(v) { \
    if ((v) & 0x20) IEC_CLR(1) = (1 << ((v) & 0x1f); \
    else IEC_CLR(0) = (1 << ((v) & 0x1f); \
    }
    #define __intc_unmask_irq(v) { \
    if ((v) & 0x20) IEC_SET(1) = (1 << ((v) & 0x1f); \
    else IEC_SET(0) = (1 << ((v) & 0x1f); \
    }
    #define __intc_ack_irq(v) { \
    if ((v) & 0x20) IFS_CLR(1) = (1 << ((v) & 0x1f); \
    else IFS_CLR(0) = (1 << ((v) & 0x1f); \
    }

    mask/unmask/ack は、こうした。(p32mx/intc.h 更新)

    さて、C やヘッダの部分は、改造するだけで良いのだが、アセンブラのエントリは、設計しないと。.... その前にリンカ・スクリプトとメモリマップの設計がいる。

リンカ・スクリプトとメモリマップ

    これは、普通意識していないのだが、作る立場だと定義から始めないといけない。定義するにしてもゼロから作るわけにもいかないので、いろいろ見ている。

    まず、gcc をビルドしたときに作られるものが、すべてのベースのようだ。だが、これは UNIX などのプログラム向け。次に MicroChip のブートローダのソースに添付されていたもの。これは、Flash や Boot に割り当てるような対応とか PIC32 への対応とか。最後に pinguino に添付されているもの。これは、MicroChip のものをベースに これまたいろいろ変更していて、一部 gcc 版に戻しているようなところもある。

      OUTPUT_FORMAT("elf32-tradlittlemips" → "elf32-littlemips")
      OUTPUT_ARCH(pic32mx → mips)

      pinguino は、冒頭が変わっている。これでないと普通の gcc が使えないのかも。

    まぁリンカ・スクリプトは、メモリへの割り当てを指示するものだから、マップに整理してみよう。

    Flash:
    exception_mem : ORIGIN = 0x9D000000, LENGTH = 0x1000
    .app_excpt _GEN_EXCPT_ADDR(0x180)
    .vector_0 _ebase_address + 0x200
    :
    .vector_63 _ebase_address + 0x200 + 32 * 63
    kseg0_program_mem (rx) : ORIGIN = 0x9D001000, LENGTH = 0x4FFF
    .text
    .rodata
    .sdata2
    .sbss2

    ユーザプログラムは、4KB オフセットして始まる。最初の 4KB は、exception_mem という領域に取られる。ここにマルチベクターモード用のベクターが置かれるが、2xx だと 2KB 。だいぶガラガラのようだ。

    まずは、exception_mem を詰めないと。シングルベクターモードにする意味が無い。.vector_1 - .vector_63 を削除して、kseg0_program_mem の ORIGIN を 0x9D000400 あたりにしてしまおう。

    アセンブラの割り込みエントリは .vector_0 を使うことにして、スタートアップのコードは、どうしよう。exception_mem の先頭 から 0x200(0x180 ?)を使いたいのだが、新たなセクションを作らないと、そこを使うすべがない。.startup とか .reset とか の名前を使いたいのだが ... .app_reset にするか。

    Boot:
    kseg1_boot_mem : ORIGIN = 0xBFC00000, LENGTH = 0x490
    .reset _RESET_ADDR
    .bev_excpt _BEV_EXCPT_ADDR :
    debug_exec_mem : ORIGIN = 0x9FC00490, LENGTH = 0x760
    kseg0_boot_mem : ORIGIN = 0x9FC00490, LENGTH = 0x0
    .startup ORIGIN(kseg0_boot_mem) :
    Config:
    config3 : ORIGIN = 0xBFC00BF0, LENGTH = 0x4
    .config_BFC00BF0
    config2 : ORIGIN = 0xBFC00BF4, LENGTH = 0x4
    .config_BFC00BF4
    config1 : ORIGIN = 0xBFC00BF8, LENGTH = 0x4
    .config_BFC00BF8
    config0 : ORIGIN = 0xBFC00BFC, LENGTH = 0x4
    .config_BFC00BFC
    configsfrs : ORIGIN = 0xBFC00BF0, LENGTH = 0x10

    RAM:
    kseg1_data_mem (w!x) : ORIGIN = 0xA0000000, LENGTH = 0x2000
    :
    .sdata
    _sdata_begin = . ;
    :
    _sdata_end = . ;
    .lit8
    :
    .lit4
    :
    _data_end = . ;
    _bss_begin = . ;
    .sbss
    _sbss_begin = . ;
    :
    _sbss_end = . ;
    .bss
    :
    _end = . ;
    _bss_end = . ;

    これは、スタートアップ作成のためのメモ。これを使うのは少し先。

    ところで、普通の GCC は、config のための #pragma など記述できない。だが、 .config_BFC00BF0 といったセクション内にデータを作れば良さそう。

    unsigned devcfg3 __attribute__((section(".config_BFC00BF0"))) = 0xXXXXXXXX;

    KEEP になっているから バイナリ(elf)には、出力されるはず。構造体を使って省略なしに きっちり記述するのでも良い。

    あとメモリは、kseg1 (キャッシュ Off) を使う。どうせ データ用のキャッシュはないし、関係ないのだろう。

ostimer.c を 移植してみる。

    あまり大したものではないのだが、もともと作っていた ostimer というものを PIC32MX 向けに書きなおしてみた。

    timer2/3 , timer4/5 は 2 つを合わせて 32bit タイマーにできる。時刻用なので、これでも足りず 割り込みで 上位 32bit をインクリメントして 64bit タイマーにする。

    思ったものが、作れるかどうかの確認が目的。ビルドは出来たのだが、結構バグがあった。デバイスの定義も 適当に変換したものがベースなので、いろいろ不満があったり、バグもあったり。やはり使ってみないと 完成度が上がらないようだ。

    それはともかく ... コードがでかい。AVR だと 2KB もあれば相当なことが出来るのだが、この程度のもので 632
    バイトも使っている。MIPS 自体も効率が悪いのだろうが、レジスタのアクセスの仕方も悪いのかも知れない。ちょっとサイズを減らすことを考えないと。

mips16 の検討

      text data bss dec hex filename
      14880 16 76 14972 3a7c others16.o (68 %)
      21948 136 76 22160 5690 others32.o
      2668 84 0 2752 ac0 stdio16.o (59 %)
      4512 84 0 4596 11f4 stdio32.o
      3168 20 0 3188 c74 stdlib16.o (66 %)
      4768 20 0 4788 12b4 stdlib32.o
      2328 0 4 2332 91c string16.o (72 %)
      3216 0 4 3220 c94 string32.o

    ちょっと行き詰まったので、mips16e を 試してみることにした。とりあえず、libc 部分はこんな感じ。.o をサブ・ディレクトリ毎にまとめた。(ld -r) アセンブラコードも含まれているのだが、その部分は全部 -mips32。

    string はアセンブラが多いのでこんなものか。others が巨大なのは、strtod のせい。

    これら stdio, stdli, string , others は、全部 CFLAGS に -mips16 を設定してビルドできる。アセンブラは、コード中に

    .set nomips16

    と書けば mips32 にしてくれる。問題は、C の方で、インラインアセンブラが効かない .. ではなく CP0 アクセス命令がないこと。これがないので割り込み禁止にするコードでエラーになる。... まぁ関数にしてしまえば良いのだが
     ・ jzlib-wk002.tar.gz

    とりあえず、ここまでの成果。

スタートアップの検討

    crtXXX.o をどうするか、悩んでいたのだが、そろそろ手をつけることにした。幸いなことに、pinguino に MicroChip 由来のソースコードがあった。これをもとに、AVR-libc のコードもみながら、作ろうとおもう。

    まずは、なにを作るのか整理。

      pinguino 添付のものは、完全なスタンドアローン用みたいだ。_reset から始まって 最終的に main を call 。万が一リターンしてきたら無限ループ。

      こういうものだから、ブートローダー領域にあるはずのコードまで含まれる。これを ユーザフラッシュの先頭から実行するようなものに変える。

      あと、不要な機能は、全部削り スタックやヒープの初期化は、AVR libc のやりかたに合わせる。

      最後にベクタ。これは今まで書いてきた通りのものを crtXXX.o に含めるようにする。

    MicroChip 由来のコードの調査

      見ているのだが、削るところは、まぁはっきりしている。ベクタはとにかく全部削る。あと ramfunc ? この機能はないし作るつもりもないので、あっさり削る。

      結局は、CP0 レジスタの初期化 スタック/ヒープの設定、あと gp の設定 と RAM の初期化 ぐらいが残った。

      CP0 レジスタの初期化は、これを見ないと作れないと思うレベル。シャドウレジスタの gp の初期化が必要だとか分からないし、やりかたも知らない。gp は、(elf の )PIC のコードで使う。-fno-pic で進めてきたが、PIC を使った方が良いかも知れない。

    AVR-libc の調査

      AVR とどう関係あるのかと言うと、jzlib の もとが AVR-libc なのだ。まず、スタック/ヒープの設定の仕方は 合わせるべき。.weak 使っていたりして、ユーザレベルで設定変更が可能になっていて、そこは継承したい。

      あと、初期化でフックを入れられるようになっている。.init0 , .init1 ,... とセクションを並べていて、セクション指定で コードを書くとそこに挟まるようになっている。これも対応したいものだが、後回しで良いだろう。

    なんとなく分かったような気がするので、とにかく作ってみよう。

    一応はできた。

      まず crt0.S では、-mips32r2 を指定しなければならなかった。次に デフォルトが big-endian になってしまっていた。他のものも含めて -EL オプションの追加が必要。

      これ自体は、4xx とかでも共通っぽいのだが、libc.a を共通にするために、チップ依存の部分を引きこまないといけない。割り込みベクタと 上記の intent は、例えば 4xx と IRQの数が違うから 引き込む。アセンブラの エントリ関数は、共通だがこれも。実を言うと 各レジスタのベースアドレスを変数にしていて、これもチップ依存みたいなので引きこむ。

      引き込むと書いているが、ld の -r オプションを使って 1 つにまとめる方法にした。ld にも -EL オプションが必要だった。

    一応はできたから リンク

    $ mips-elf-ld crt220.o main.o -T p32mxb3k.x -Map tmp.map

    map 抜粋:
    .app_reset 0x000000009d000000 0x108
    *(.app_reset)
    .app_reset 0x000000009d000000 0x108 crt220.o
    0x000000009d000000 _start

    .app_excpt 0x000000009d000180 0x4
    *(.gen_handler)
    .gen_handler 0x000000009d000180 0x4 crt220.o

    .vector_0 0x000000009d000200 0x100
    *(.vector_0)
    .vector_0 0x000000009d000200 0x100 crt220.o
    0x000000009d000200 __intent

    .startup
    *(.startup)

    .text 0x000000009d000400 0x2f0
    0x000000009d000400 _text_begin = .

    動くものなのかどうか、どうやって動かすか課題は多いのだが、最初のバイナリが出来た。

    0 - 0x180 までに スタートアップを入れるつもりだったが、0x108 で少し余裕がある。0x180 に割り込みが来るかも知れないので一応空ける。0x200 からは、アセンブラの 割り込みエントリ。レジスタセーブ版で 0x100 に収まっている。0x400 からの .text には、(擬似)割り込みベクタと C の intent などが入り 0x2f0 を専有している。

    最低限度のもので 2KB 弱というわけだ。まぁ 残りの 30KB は自由に使えるわけなので良しとしよう。

    まずは、どうやって動かすか についてしらべないと。

      ・なんらかのブートローダがあったとして、ユーザー FLASH の先頭に Jump してくれるものなのかどうか?
      ・なにもない場合はブートローダのスタブが必要だが これも考えておかないと。
      ・あと pinguino は手に入れたが、どうやって動かすのか? pinguino に合わせた スタートアップとリンカ・スクリプトが必要そうな気がする。

    次は動くものなのかどうか?とにかく一式用意できたわけだが、検証していない。

      ・コンパイルオプションをどうするのか? PIC にするかどうかがポイント。
      ・割り込み関係、シャドウレジスタの扱いも含めて 整理しないと動くような気がしない。

    ... 考えてみれば、すべてを自前で用意している。コンパイラ - ライブラリ - ライタ。そのすべてが動いたという実績がない ... だけでなく ライブラリ - ライタ は かなりの部分作り込んでいる。これは、最初に main で L チカできるまで、結構厳しい道のりになりそう。

MIPS32r2 について

    ちょっと脱線。スタートアップの コンパイルで wrpgpr や ext , ins という見慣れない命令でエラーになったため、-mips32r2 にしてみたのだが、調べて見ると便利な命令が増えている。

    di/ei

      AVR でいうところの cli/sei 命令。1 命令で 禁止許可ができることが分かったので、インラインアセンブラで __cli()/__sei() を作ることにした。mips16 では使えないので、関数 cli()/sei() も作る。

      また、di/ei は 変更前の CP0_STATUS を戻すので、irq_disable() を作るのに複数命令必要だったのが、1 命令で出来るようになった。( irq_restore() は、もともと 1 命令 )

    ext/ins

      ext は、レジスタから offset n bit , size m bit のフィールドを取り出す命令。 ins は、その逆で フィールドを埋め込む。デバイスのレジスタの操作いかにも便利そうな命令。

    clz

      これは、MSB から 0 の数をカウントしてくれる命令。最初のビット位置を見つけるのに便利な命令で、割り込みで ビットマスクを IRQ 番号に変換するのに使える。

    他にも 積和演算とか あるようだ。gcc は、これらの命令を有効に使ってくれるのだろうか? もしダメなら マクロを用意しないと。

割り込み制御の検証

    CP0 STATUS レジスタが、どういう風に設定されるかを中心に チェックしてみた。

    4 3 2 1 0
    CP0_STATUS ... UM x ERL EXL IE

    下位 5 bit のこのフィールドがとりあえず重要。IE=1 は割り込み許可。.. なのだが ERL/EXL のいずれかが 1 だと割り込み処理実行中を意味して、割り込みが許可されない。UM=1 は、ユーザモードを意味する。組み込みプロセッサで ユーザモードなど使えないだろうと思っていたのだが、なんと使えるのだ。

      BMXDUDBA (DATA RAM USER DATA BASE ADDRESS REGISTER) というレジスタで USER 空間のメモリを指定するらしい。使う気はないが、割り込みは一応考慮しておく。

      あと SR, NMI, BEV といった bit がある。SR/NMI は 通常のリセット以外が発生したかどうかの 情報。BEV=1 は、ブートローダで割り込みを使うという意味で良い。

    スタートアップ:

    CP0_STATUS ... UM x ERL EXL IE
    0 0 0 0

    この状態で main が call される。SR, NMI, BEV bit はそのまま。

    interrupt_enable() 後

    jzlib で割り込みを使うには、interrupt_enable() を call する。


    CP0_STATUS ... UM x ERL EXL IE
    0 0 0 1

    これ以外では、SR, NMI , BEV をクリアし、IPL=2 にする。また、IECx を all 1 にしてすべての IRQ を許可する。

    割り込みが来ると ...

    CP0_STATUS ... UM x ERL EXL IE
    X 0 1 1

    たぶんこの状態? ERL/EXL いずれかが 1 なら IE=1 でも 割り込みは禁止されている。UM も無視される。

    C の 割り込みハンドラ intent では、もとの状態をセーブして

    CP0_STATUS ... UM x ERL EXL IE
    0 0 0 0

    この状態にして、割り込み関数を順番に実行。

    最後に CP0_CAUSE を 0 にして、レジスタを復帰して eret 直前に C0_STATUS を元に戻す(ERL/EXL はクリア)。

    検証しながら直したのだが、動きそうな気がしてきた。

a.out の検証

    できあがった 実行形式 (ELF) をチェックしてみた。


    Disassembly of section .app_reset:

    9d000000 <_start>:
    9d000000: 3c1a9d00 lui k0,0x9d00
    9d000004: 275a0010 addiu k0,k0,16
    9d000008: 03400008 jr k0
    9d00000c: 00000000 nop

    9d000010 <_start_k0>:
    9d000010: 3c1d0000 lui sp,0x0
    9d000014: 27bd0000 addiu sp,sp,0
    9d000018: 3c1ca000 lui gp,0xa000
    9d00001c: 279c7ff0 addiu gp,gp,32752

    まずは、新規に作った .app_reset が先頭で そこからスタートアップが始まる。kseg1 の 0xBD00_0000 に 飛んできても、kseg0 に jump している 。アドレスも正しい。_start_k0 は、MicroChip のスタートアップベースでいろいろ初期化をする。

    9d000100: 01000008 jr t0
    9d000104: 00000000 nop

    Disassembly of section .app_excpt:

    9d000180 <_gen_exception>:
    9d000180: 00000000 nop

    Disassembly of section .vector_0:

    9d000200 <__intent>:
    9d000200: 3c1a9d00 lui k0,0x9d00
    9d000204: 275a0210 addiu k0,k0,528
    9d000208: 03400008 jr k0
    9d00020c: 00000000 nop

    9d000210 <__intent_k0>:
    9d000210: 40046000 mfc0 a0,c0_status
    9d000214: 40056800 mfc0 a1,c0_cause
    9d000218: 40067000 mfc0 a2,c0_epc
    9d00021c: 3c199d00 lui t9,0x9d00
    9d000220: 27390594 addiu t9,t9,1428
    9d000224: 0320f809 jalr t9
    9d000228: 0040d021 move k0,v0
    9d00022c: 409a6000 mtc0 k0,c0_status
    9d000230: 42000018 eret
    ...

    Disassembly of section .text:

    スタートアップは、0x104 で終わり、0x180 からは、.app_excpt 。
    _gen_exception はなにもないが、call されないはず。ただ、今のままだと コード を入れたくとも入れられない。AVR-libc の .initN のようなもので、コードを挿入可能にしたい。

    0x200 の __intent は、__intent_k0 にまず飛ぶ。これはスタートアップと同じ。次にいきなり c0_status などを 引数に入れて intent を call 。戻り値を c0_status に入れて eret 。

    シャドウレジスタの設定が出来ていれば、これで良いはず。

    9d000400 <_main_entry>:
    9d000400: 774001c4 jalx 9d000710 <main>
    9d000404: 00000000 nop
    9d000408: 1000ffff b 9d000408 <_main_entry+0x8>
    9d00040c: 00000000 nop
    ...

    9d000420 <__vectors>:
    9d000420: 9d000520 0x9d000520
    9d000424: 9d000520 0x9d000520
    9d000428: 9d000520 0x9d000520
    9d00042c: 9d000520 0x9d000520
    :
    9d00047c: 9d000520 0x9d000520
    9d000480: 9d000789 0x9d000789
    9d000484: 9d000520 0x9d000520
    :
    9d000520 <_bad_interrupt>:
    9d000520: 00001021 move v0,zero
    9d000524: 03e00008 jr ra
    9d000528: 00000000 nop
    :
    9d000788 <__vector_24>:
    9d000788: 675c move v0,gp
    9d00078a: f010 9a70 lw v1,-32752(v0)
    9d00078e: 4b01 addiu v1,1
    9d000790: f010 da70 sw v1,-32752(v0)
    9d000794: b203 lw v0,9d0007a0 <__vector_24+0x18>
    9d000796: b304 lw v1,9d0007a4 <__vector_24+0x1c>
    9d000798: da60 sw v1,0(v0)
    9d00079a: e820 jr ra
    9d00079c: 6a01 li v0,1
    9d00079e: 6500 nop

    ベクターのチェック。0x528 は、_bad_interrupt で、0 をリターンする。こうすると、二度と call されなくなる。0x789 は mips16 を call することを意味する。実際に 0x788 から ostimer.c で定義した 割り込みハンドラが入っている。gp を使っているから PIC (Position Independent Code) になっている。

    コードを見たかぎり、なんだか動きそうな気がしてきた。

    ただし、ユーザフラッシュの先頭に飛んできてくれないと、ダメ。このあたりはどうなっているのだろうか?

     ・ jzlib-wk005.tar.gz

    ここまでのものを wk005 として置いた。ちゃんとした スペックファイルがないので 全部明示的に指定しないといけないのだが、

    $ mips-elf-gcc -mips32r2 -EL -fpic -msoft-float -O2 \
    -I/d/MIPS32_Toolchain/mips-elf/include \
    -c xxx.c
    $ mips-elf-ld -T /d/MIPS32_Toolchain/lib/ldscripts/p32mxb3k.x \
    -o xxx /d/MIPS32_Toolchain/lib/p32mx/crt220.o \
    xxx.o -L /d/MIPS32_Toolchain/lib/p32mx -lc -lgcc

    こういう風に指定することで、ビルドできるようにしようと思う。

    -fpic -msoft-float は、たぶん省略可。-EL は必要。-mips32r2 の代わりに -mips16 とすると mips16e のコードが出る。混在は可能。

ブートローダとの連携

    MicroChip が出している。AN1388 のブートローダのコードを見てみたのだが ...


    Include/BootLoader.h:
    #define USER_APP_RESET_ADDRESS (0x9D006000 + 0x1000 + 0x970)

    void JumpToApp(void)
    {
    void (*fptr)(void);
    fptr = (void (*)(void))USER_APP_RESET_ADDRESS;
    fptr();
    }

    こんな風になっていた。これでは動くはずはない。ただ、Flash から 0x7970 オフセットされているから 32KB の終わりのほう。このアドレスに jump コードを入れておけばあるいは、いけるのかも知れない。

    一方 pinguino のほう。p32/lkr/PIC32_PINGUINO_220/procdefs.ld を見ると

    _ebase_address = 0x9D003000;
    _RESET_ADDR = 0x9D004000;

    こんな風になっている。オフセット 16KB のところに ジャンプしてくるということか。ただし、オフセット 12KB に ebase を設定しているから、標準から 12KB オフセットされているとも言える。

    kseg1_boot_mem : ORIGIN = 0x9D004000, LENGTH = 0x490
    kseg0_boot_mem : ORIGIN = 0x9D004490, LENGTH = 0x570
    kseg0_program_mem (rx) : ORIGIN = 0x9D004A00, LENGTH = 0x3600

    ただ、こういう設定もあるのだった。どうやら 本来ブートにあるべきものが、0x9D004000 から入っていて、0x9D004A00 に飛んでも 動く。ひょっとしたら こっちがエントリアドレスかも知れない。


    _ebase_address = 0x9D004000;
    _RESET_ADDR = 0xBFC04000;

    exception_mem : ORIGIN = 0x9D004000, LENGTH = 0x400
    kseg0_program_mem (rx) : ORIGIN = 0x9D004A00, LENGTH = 0x1FC00

    p32mxb3k.x に対して、上記を変更すれば、対応できるような気はする。

    ただ、なんだか無駄だらけ。32KB もあっても 随分余計なものに喰われてしまっている印象がある。

gcc の specs ファイル

    mips-elf-gcc -dumpspecs > specs
    mv specs /d/MIPS32_Toolchain/lib/gcc/mips-elf/4.4.3/

    例えばこういう風にすることで、gcc の動作を定義する specs ファイルを読みこませることができる。

    mips-elf-gcc -v として確認してみれば、

    Reading specs from d:/mips32_toolchain/bin/../lib/gcc/mips-elf/4.4.3/specs

    と表示され、確かに 読み込まれている。それは、良いのだが、悲しいことにどう変更すれば良いのやら分からないのであった。

    まず分かる範囲でちょっと。

      *endian_spec:
      -%{!EL:%{!mel:-EB}} %{EL|mel:-EL}
      +%{!EB:%{!mel:-EL}} %{EB|mel:-EB}

      EB と EL を入れ替える。

      *endfile:
      -crtend%O%s crtn%O%s
      +

      *startfile:
      -crti%O%s crtbegin%O%s
      +crt220%O%s

      endfile の指定を削除、startfile 変更。

    こうしておいて、

    TOP/mips-elf/include ( jzlib の include をコピー)
    TOP/mips-elf/lib/libc.a
    TOP/mips-elf/lib/crt220.o

    こんな風にインストールすると ...

    $ mips-elf-gcc -D__32M4KCORE__ -D__32MX2XX__ -D__32MX220F032B_ \
    -mips32r2 -O2 -Xlinker -T p32mxb3k.x main.c -lc

    こういうオプションで ビルドまでは行く。ただ、p32mxb3k.x は手元に置いておかないとならない。define と リンカ・スクリプトのサーチは、難しい。

    それに、startfile をチップ別に作ったり、mips-elf/lib に サブディレクトリを作り、libc.a を そこに置きたい。AVR でこういうことが出来ているのは、gcc 自体にそういう機能を追加しているためだから、spec だけでは無理かも知れない。

    適切な gcc のオプションを作ってくれるプリプロセッサを作るのが近道かも知れない。

インストールとかビルドとかの方法

    一切説明してこなかったのだが、少々まともになってきたし、少し説明しておこう。

    まずコンパイラは、自分でビルドしたものを使っている。だが Pinguino IDE のコンパイラと gcc のバージョンは違うものの config の方法はほぼ同じ。Pinguino の gcc を借りて説明しよう。

    Pinguino をインストールすると たとえば /c/PinguinoX.3/win32/p32/bin/ に mips-gcc がある。ここに PATH を通して

    $ export CROSS_COMPILE=mips-

    とする。... のではあるが、肝心のことを忘れていた。環境は MSYS を使っている。

      msys-1.0.10 いまだに、この古い 1.0.10 をベースにして使い続けている。この msys-1.0.10 と Pinguino だけ使うことを前提にする。

     ・ jzlib-wk006.tar.gz
    最新ソースはこれで、TOP ディレクトリで make を実行すると

    libc.a
    p32mx/crt2xx.o

    この 2 つが作られる。

    で次にどうインストールするか... p32/mips-elf/include は、Pinguino に使われてしまっている。

    p32/jzlib に置くことにしよう。

    $ mkdir -p /c/PinguinoX.3/win32/p32/jzlib/lib
    $ cp libc.a p32mx/crt2xx.o /c/PinguinoX.3/win32/p32/jzlib/lib
    $ mkdir /c/PinguinoX.3/win32/p32/jzlib/lib/ldscripts
    $ cp p32mx/p32mxb* /c/PinguinoX.3/win32/p32/jzlib/lib/ldscripts/
    $ cp -r include /c/PinguinoX.3/win32/p32/jzlib

    これで一応 OK だが、指定が面倒なので、
     ・ p32mx-gcc.gz
    これを gunzip したものを /c/PinguinoX.3/win32/p32/bin に置いて使う。

    p32mx-gcc は、スクリプトで 面倒な指定 (特にディレクトリ関係) を省略できるようにしたもの。基本的には、足りないオプションを 追加して、mips-gcc を 呼び出すような仕組みとする。ただし、その足りないオプションもカスタマイズしたいので、専用のオプションを新設。

    専用のオプションとは、

    -mmcu=p32mx1xx
    -mmcu=p32mx2xx
    -mmcu=p32mx4xx
    -mmcu=p32mx2xx-b
    -mmcu=p32mx2xx-bu

    こんなもの。-b/-bu といったものは何かというと デフォルトの リンカスクリプトの拡張子。通常は、

    TOP/jzlib/lib/ldscripts/p32mxb3k.x (1xx/2xx の場合)
    TOP/jzlib/lib/ldscripts/p32mxb12k.x (3xx/4xx の場合)

    を使うが、-XXX で リンカスクリプトを変えることができるようにする。指定しないと -mmcu=p32mx2xx が指定されたとみなす。
    あと、-mmcu の指定で以下の define を追加する。

    -D__32M4KCORE__ -D__32MX2XX__ (2xx の場合)

    その他、標準オプション でいくつか省略可能にした。

    -mips32r2
    -EL

    -EL は強制的に付ける。-mips32r2 は -mips16 など -mips* が指定されない場合のみ。

    -Xlinker XXX

    あと、リンカオプションを明示的に指定した場合、標準のリンカスクリプトは指定しないようにしている。

    次、このスクリプトは、リンクモード を意識する。"-lc" が含まれると リンクモード 。その場合、crtXXX.o ファイル と リンカスクリプト を 指定するようにしている。

    結局のところ動作がどうなるかというと

    p32mx-gcc -O2 main.c -lc

    とすると

    mips-gcc -mips32r2 -EL -D__32M4KCORE__ -D__32MX2XX__ \
    -nostdinc -I/c/PinguinoX.3/win32/p32/bin/../jzlib/include \
    -Xlinker -T /c/PinguinoX.3/win32/p32/bin/../jzlib/lib/ldscripts/p32mxb3k.x \
    /c/PinguinoX.3/win32/p32/bin/../jzlib/lib/crt2xx.o main.c -lc

    こんな風にして、mips-elf-gcc を実行する。

    p32mx-gcc -mips16 -O2 -c main.c

    だと

    mips-gcc -EL -D__32M4KCORE__ -D__32MX2XX__ \
    -nostdinc -I/c/PinguinoX.3/win32/p32/bin/../jzlib/include \
    -mips16 -O2 -c main.c

    こう。ただ、適当なスクリプトなので、ディレクトリ名に スペースが入ったりすると具合が悪い。エスケープで指定しても 無効。

プログラムを書いてみる

    # include <p32mx/p32mx_core.h>
    # include <p32mx/p32mx_port.h>
    int main() {
    int i;
    LAT_CLR(1) = 1 << 7; // PORTB bit7 : 0
    for (i=0; i< 1000; i++) {
    __delay_loop_1(4000000);
    TRIS_INV(1) = 1 << 7; // PORTB bit7 : output
    }
    }

    実を言うと PORT の使い方がよく分かっていないのだが、こんな感じ?
    LAT_CLR() のパラメータ 1 は、2 番目の PORT すなわち PORTB の指定。TRIS レジスタは 0 で 出力にするようなので、そちらをトグルするようにしてみた。(LED は L で点灯するようにする )
    __delay_loop_1 は、2 クロックで 1 ループ。(mips32r2 の場合) 4000000 だから もし 8MHz ならば、1 秒毎に反転するはず。
    初期化が少々不安。PORT と 組み込み機能の選択だとか いろいろあるようなので、設定が他に必要かも知れない。

    これがうまくいくようなら、delay_us() を試してみる。delay_us() は、OSCCON からクロックの設定を読み出して、それに合わせてループ回数を計算するようにしているのだが、出来が悪いかも。

    p32mx-gcc -mmcu=p32mx2xx -O2 main.c -lc -o main.bin

    これで main.bin が出来るはずなのだが、動かす環境によって -b とかのオプションの指定が必要。( 予定 : まだ確認できていない )

コードが動いた。

    Olimex PIC32-PINGUINO-MX220 を入手したので、これを使ってデバッグ開始。書き込みには、pic32prog (r55) を使った。

    まずは、こんな Lチカのコード

    #include <p32mx/p32mx_core.h>
    #include <p32mx/p32mx_port.h>

    // Olimex PIC32-PINGUINO-MX220
    // LED1 (Green) D13 = RB15 Active H (1: Light on)
    // LED2 (Red ) D9 = RA10 Active H (1: Light on)
    // BUT D8 = RB7 Active L (0 : Pressed)

    int main() {
    TRIS_CLR(1) = 1 << 15;
    TRIS_CLR(0) = 1 << 10;
    LAT_CLR(1) = 1 << 15;
    LAT_SET(0) = 1 << 10;
    for (;;) {
    __delay_loop_1(10000000);
    LAT_INV(1) = 1 << 15;
    }
    }

    をコンパイル。__delay_loop_1() は 2 クロックで 1ループなので 40 MHz なら 1秒周期で点滅するはず。

    Programmer for Microchip PIC32 microcontrollers, Version 1.51M
    Copyright: (C) 2011-2012 Serge Vakulenko
    Adapter: HID Bootloader
    Program area: 1d003000-1d007fff
    Processor: Bootloader (id DEAFB00B)
    Flash memory: 20 kbytes

    ブートローダモードにして pic32prog (最新版 : r55)をオプションなしで起動すると、こういうメッセージがでる。 (物理アドレス) 0x1d003000 からがプログラムエリアだが、最初はベクタなので 0x1d004000 に jump してくる。だから 0x4000 ずらした リンカ・スクリプト p32mxb3k.bu を使う。

    $ p32mx-gcc -mmcu=p32mx2xx-bu -O2 test1.c -lc -o test1.elf

    まずは、これを試す。

    問題は HEX ファイルの作り方だった。

    $ objcopy -O ihex test1.elf test1.hex

    mips 版は、何故かエラーになるのだ。mingw の i386 だと問題なく作れたり。
    書き込みには、

    $ pic32prog -S test1.hex

    として、ベリファイをスキップさせる。ベリファイさせようとすると止まってしまう。... もともと AN1388 HID ブートローダ には読み込みの機能はないのだが ...

    で動かしてみると、赤が点灯し、緑が1秒周期で点滅したのだった。

    $ p32mx-gcc -mmcu=p32mx2xx-bu -O2 test1.c -lc -o test1.elf \
    -D_USE_SFRS_BASE_ADDR

    次はこれを試す。ちょっとバグがあって、修正する必要があったが、これも動いた。

    何を確認したかというと、グローバル変数のアクセス。

    $ p32mx-gcc -mmcu=p32mx2xx-bu -O2 test1.c -lc -o test1.elf \
    -mips16

    これは、動かなかった。__delay_loop_1() は、ディレイドスロット前提のコードなのだが、mips16 には ディレイドスロットが無いのだった。

    しょうがないので、delay_loop_1() を -mips32r2 で関数として作った。で、試してみると ... 全然動かない。赤 LED も点灯しない。-mips16 をやめても同じ現象。

    調べてむると、これが最初にスタックを使うコードだった。main の頭で レジスタセーブしているので、全然動かなかったのだ。

    .set __stack , _stack

    というのが、crt0.S に必要だった。ちなみに スタックは bss の終わりから 1KB 。サイズは __stack を定義することで、プログラム側で変更可能。

    これで関数呼び出しも OK 。-mips16 も OK になった。
    ただ、相変わらず __delay_loop_1 が使えてしまう。pic32mx-gcc スクリプトでは、-mips16 があったら

    -D_MIPS16

    と定義しようかと思う。

    さて、ようやく次の段階に入れる。次とは、割り込みハンドラの確認。これさえ動けば随分完成に近づく。... というか、チップ対応の機能として、ostimer と timer しか提供するつもりはなかったりする。API とかディレクトリ構成だとかを見直して完成にするつもり。

    あと、動くプログラムが作れたので、『pic32progの改造』も進められる。

    コードは、まだ公開しない。割り込みが動いたら 新記事で。

割り込み関係のメモ

    割り込みを動かすのは難航している。組み込み用に拡張された MIPS の割り込み制御はなかなか難しい。

    とりあえず メモだけ。

    シャドウレジスタの扱い

      割り込みが発生すると、シャドウレジスタセット(SRS) を切り替える。使わないにしても 使わないように設定する必要がある。

      面倒なのは、カレントの SRS (C0_SRSCTL_CSS で示される) は、簡単には切り替えられないこと。レジスタの値がいきなり変わってしまうと困るから当然かもしれない。切り替えるには、割り込みからの復帰命令である eret を使う。eret では、 C0_STATUS_BEV = 0 で C0_STATUS_ERL = 0 のとき C0_SRSCTL_PSS で指定されている SRS に 切り替える。

      スタートアップでは、C0_SRSCTL_CSS が 0 とは限らない という 前提にして 0 に切り替えることにした。そうしないと、ユーザプログラムが 1 で動き続けたりして 面倒な話になる可能性がある。

    一般例外と EIC モード

      MIPS では 割り込み や 例外は C0_EBASE + 0x180 に飛ぶことになっている。だが、PIC32MX など組み込み用では、外部 割り込みコントローラ(EIC: External Interrupt Controller) を使用することで、 C0_EBASE + 0x200 + ベクタに飛ぶこと (も) できる。

      ややこしいのは、C0_STATUS_BEV の存在。BEV は Bootstrap Exception Vector モードで、ROM 領域の 割り込みベクタ(アドレス固定) を使う。組み込み用では 両方ROM だが Boot 領域を指す。

      C0_EBASE + 0x200 が使われるには、BEV = 0 と C0_CAUSE_IV = 1 である必要がある。また、C0_EBASE + 0x180 を使うことは (推奨)されていない。

    シングルベクタモード

      INTCON_MVEC = 0 とすると シングルベクタモードになる。INTCON_SS0=1 とすると、シングルベクタモードでの割り込み時 シャドウレジスタセット(SRS) を 1 にする。( INTCON_SS0 = 0 では、SRS を 0 にする )

      上がたぶん正しいとおもわれるのだが、CPU の定義では、C0_CAUSE_IV = 1(EIC モード) で、C0_INTCTL_VS(割り込みベクタ1 つのサイズ) が 0 のとき シングルベクターモードで、C0_SRSCTL_EICSS に切り替えることになっている。

      一般例外ベクタ (C0_EBASE + 0x180) を使う場合には、 C0_SRSCTL_ESS で指定された番号になる。マルチベクタモードのときは、もっとややこしい。C_SRSMAP に割り込みレベル毎の 番号を指定されている。

      他に INTCON_VS というのがあって、これがあるプロセッサでは、C0_INTCTL_VS は使うなと書いてある。.. 正直良くわからないのだが、P32MX2XX (P32MX4XX も) INTCON_VS は存在せず INTCON_SS0 が存在する。 たぶん C0_INTCTL_VS は 0 にしておけば良いのだろう 。

    割り込みレベル と 実行レベル

      C0_STATUS_IPL に CPU が実行しているレベルが 格納されている。この値より 高い 割り込みレベルの割り込みしか発生しない。割り込みを許可しても この条件がクリアされないとダメなのだ。

      C0_CAUSE_RIPL に 発生した割り込みレベルが格納されている。一般的には、これを C0_STATUS_IPL にコピーするらしい。が、守る必要はない。

      さてこの 割り込みレベルだが、PC1-IPC10 で 割り込みベクタ毎に指定する。シングルベクターモードの場合は、割り込みベクタ 0 のみを使用する。... のだが INTSTAT_RIPL というのもあって シングルベクタモードで使うことになっている。この関係が良くわかっていない。

      さて、実行レベルをどうするのか? これは決めなのだが、常に 0 としようかと思う。で、割り込みレベルは、1 。この 2 つしか使わない。

割り込み関係のデバッグの状況

    まず、割り込みのデバッグを行うには、割り込みを発生させなければならない。

    これに ostimer を使っている。ostimer は Timer 4/5 を 1 つにまとめて 32bit タイマーにする機能を使って実装している。32bit もあれば、割り込み間隔を (40MHz で) 2分弱まで 伸ばせるのだ。もっとも そんなに待てないので、1/16 にして 6.7 秒で 最初の割り込みが来るようにした。ostimer は、単に時刻用で 64 bit の値を返すような機能にしている。call back などは本来ないのだが、デバッグ用に call back 機能を付けて、LED を点灯させたりしている。

    割り込み処理なのだが、従来のプロセッサ用のコード と SRS を使うコード (-D_USE_SRS で指定) の 2 種類を作ったのだが、SRS を使う方は、全然だめ。まずは、従来コードのデバッグをやって、その後 SRS のコードに移るつもり。だいたい、割り込み処理を C で書いているので、C の分の レジスタのセーブ・リストアが結構ある。ここもアセンブラ化しないと SRS を使うメリットが出ない。

    で、従来用のコードだが、割り込みを 1 回発生させて、復帰する。平タスクはちゃんと動き続けるところまでは来た。だが、2 回目の割り込みが発生しない。理由は不明だが、どうも C0_STATUS 関係っぽい。

    ちなみに、従来用のコードでは、ERET は使っていない。ERET は、MIPS32 からの命令で、MIPS32R2 で SRS の切り替え機能が付いた。ターゲットの 1 つとして考えている Jz47xx (4770 以前) は、MIPS32 ですらないので、ERET 命令を持っていないのだ。

閑話休題


    text data bss dec hex filename
    608 0 0 608 260 interrupt.o (OLD)
    580 0 0 580 244 interrupt.o (NEW)

    ちょっとヘッダファイルを変更したら、サイズが小さくなった。

    extern const void * _sfrs_base_addr_1;
    #define _INTC_ADDR ( _sfrs_base_addr_1 + 0x1000 )
    #define INTCON (*(volatile uint32_t *)( 0x00 + _INTC_ADDR)) (OLD)
    #define INTCON (*(volatile uint32_t *)(_INTC_ADDR + 0x00 )) (NEW)

    たぶん、オフセットを ポインタの先に書くか後に書くかの違い。編集の都合で 先に書いてしまっていたのだが、interrupt.c の出力アセンブラを見てみたら、レジスタのアクセスが全部独立したポインタになっていた。ちゃんと後で書くと、_sfrs_base_addr_1 + オフセットの形でアクセスしてくれるようになった。サイズが多少小さくなるだけなのだが、小さくなったのは、多分 割り込みハンドラ intent での GPR レジスタの セーブ・リストアなのだ。

割り込み関係のデバッグの現状(2)

    割り込み処理が 1 回しか動かなくて困っていたのだが、どうも割り込み処理の中で例外を起こしているようだ。

    void
    __attribute__((section(".gen_handler0")))
    never_return()
    {
    for (;;) {
    __delay_loop_1(2000000);
    LAT_INV(0) = 1 << 10;
    }
    }

    こんな風に .gen_handler0 にコードを置くと EBase + 0x180 に飛んできたら LED を高速点滅させるようにしたのだが、最初の割り込みと同時に 高速点滅 。

    だからといって、原因がすぐ判明するわけではないが、一歩前進した。

割り込み関係のデバッグの現状(3) (2012/07/29)

     ・ retorobsd startup.S
    をみていたら、HI,LO レジスタのセーブ・リストアをしているのに気がついた。関係なかったが、バグなので修正。

    ところで retorobsd 存在も知っていたし、PIC32MX で動くことも。だが、そのときはただ感心しただけだった。『たけおか ぼちぼち日記: retroBSD on PIC32マイコン』を見て 再発見。相当に参考になりそうなので、ソースコードをちゃんと眺めてみたい。

     ・ retrobsd-src-r561-20110729.tar.gz

    とりあえずソースコード(一部)を 取り出したものをここに置く。
posted by すz at 09:09| Comment(0) | TrackBack(0) | PIC32MX

2012年07月18日

pic32progの改造

PIC32MX のライタは、純正の pickit3 を使うのが普通らしい。だが、pic32prog を使うと pickit2 が使えるばかりではなく、FT2232 系の Olimex ARM-USB-TINY Jtag ケーブルが使えるらしい。ソースコードも公開されているので、改造すればいろんなものに対応できそう。

秋月で USB OTG 対応の PIC32MX220F032B が、220円 と低価格で売られているが、ライタが高価ではなかなか薦められない。そこで、950 円の AKI-UM232R あたりで書き込めるようにならないか検討することにした。最初の書き込みさえできれば、PIC32MX220F032自身を使ったライタも作れるだろうから、書き込み速度はあまり気にしないことにする。
機能の検討

    さて、PIC32MX の書き込み方法には、2通りある。ひとつは JTAG もうひとつは 2-Wire 式の ICSP 。pickit3 も ICSP らしく、どうも こちらが標準らしい。PIC32MX220F032B を使う場合、JTAG は Disable にすると思う。となると、やはり ICSP を使いたくなる。

    ただ、ICSP は非常に入出力の切り替えの頻度が高く、Synchronous BitBang(以下 SYNCBB) と相性が悪い。付加回路なしだと限度を超える遅さになるはず。そこでこんな風にしようかと思う。



    要するに 出力を I2C のように pull-up / L にする。これだと PGD 出力を負論理にしなければならないが、外付け回路が トランジスタ+抵抗で済む。これでも部品が多いと思うなら デジトラを使えば 2 つ抵抗を減らせる。あと 100 Ωは 保護用で直結の場合でも入れた方が良いもの。

    あと MPSSE が使える 上に Hi-Speed の UM232Hも 1680円と比較的安価に買えるようになった。こちらも 使えるようにしておきたい。

    SYNCBB を使うならば、同様の付加回路で実現できるのだが、MPSSE だと 入出力を頻繁に切り替えることが可能で、直結することが出来る。MPSSE でも ICSP に対応したいもの。

    これに加えて SYNCBB での JTAG にも対応したい。MPSSE では、ICSP との兼ね合いで 本来の性能が出ないやりかたになりそう。(要するに MPSSE の BITBANG というべき機能を使う)

標準ピン配置 (未確定)

    syncbb-icsp
    dbus_tck PGC D5/DSR
    dbus_tdi PGD_OUT D6/DCD
    dbus_tdo PGD D3/CTS
    dbus_sysrst ~MCLR D7/RI
    mpsse-icsp
    dbus_tck PGC ACBUS0
    dbus_tdi PGD ACBUS1
    dbus_sysrst ~MCLR ACBUS7
    syncbb-jtag
    dbus_tck TCK D5/DSR
    dbus_tdi TDI D2/RTS
    dbus_tdo TDO D3/CTS
    dbus_tms TMS D4/DTR
    dbus_sysrst ~MCLR D7/RI
    mpsse-jtag
    dbus_tck TCK ACBUS0
    dbus_tdi TDI ACBUS1
    dbus_tdo TDO ACBUS2
    dbus_tms TMS ACBUS3
    dbus_sysrst ~MCLR ACBUS7

    こういう風に決めた。UM232H などでは、この 4 つのどれも使える。FT232R だとケーブルによっては、4 つしか ピンがなく JTAG は使えない。(多分: 実は良くわからないのだ)

      訂正: MPSSE では、ADBUS 0-3 は、BITMANG として扱えなかった。ACBUS に移動。
      注意:出力専用線は、TXD , RTS, DTR だが、これらを PGC/PGD/PGD_OUT あと TDO には割り当てないように。できるだけ ~MCLR に割り当てる。やむを得ない場合は、PGD_OUTに 割り当てた方がよい。

      さらに訂正: ADBUS 0-3 は、BITMANG として扱える。LOWBYTE のアクセスで、bit0-3 が GPIOL0-L3 だと思ったら違った。GPIOL0-L3 は、bit4-7 。紛らわしいのに説明を見つけられなかったので誤解していた。

    この 4 つをどうやって選択するのか? ピン配置をカスタマイズするのはどうしたら良いのか? 実は悩んだ。pic32prog では、使用可能なものを自動的に選ぶだけで、オプションすらないのだ。

    しょうがないので -t 文字列 というのを追加することにした。この文字列に syncbb-icsp とかを入れることで、標準的なピン配置は使えるようにする。

    次にカスタマイズだが、コンフィグファイルというものもない。ややこしいのだが、syncbb-icsp-5637 とか 上記のエントリの順番でビット番号を指定していくことにした。

改造の方針

    さきに 使用可能なものを自動的に選ぶようになっていると書いたが、MPSSE の場合 Olimex の VID のみに対応する。FTDI の VID には対応しないのだ。

    これを逆に利用して、別のソースコードにしようと思う。オリジナルのコピーして改造版を別のライタ用として作りなおすわけだ。オリジナルは、ftd2xx を使っていなかったりするのだが、使った方が結局は楽そうなので、そこも変えるつもり。

    あと、FTDI のケーブルが複数あると困ったことになる。syncbb-icsp と指定すると書いたが、ft0-syncbb-icsp とか フレフィックスを付けることで対応しようかと思う。

      調べてみたのだが、ftd2xx は、デフォルトでは、FTDI 社の VID:PID のみしか対応しない。FT_SetVIDPID で VID:PID を あらかじめ設定 しておくことで 他のものに対応できる。

      プレフィックスは、
       (1) 見つかる順番に ft0 , ft1 ... ft9
       (2) seriall number (- が含まれないこと)
       (3) description (- が含まれないこと)
      ということにしたのだが、VID:PID (4桁 16進数 :0xは付けない) もサポートしようかと思う。

      FT232R 対応の avrdude では困らなかったのだが、MPSSE だと普通 1384:6010 とか JtagKey 互換にしているはず。ただこれだけでは、対応できない。

      基本的に SYNCBB では、AD0-AD7 のみ。MPSSE(BitBang) では、AC0-AC7 + AD4-AD7 のみの操作しかできないのだ。バッファの出力制御のコンフィグをどうするかの問題もある。 真っ当な JTAG ケーブルの ICSP 対応は SYNCBB のみということにして、バッファの出力制御のコンフィグの方法を検討するに留めたい。



      多分こういう回路になる。指定は、syncbb-icsp-01234 。最後の 4 が新設で、初期化時に 出力 L に設定する。

    ちなみに、スナップショットを取った r51 版 : pic32prog-r51-20120714.tar.gz をベースに改造していくことにする。最新版への追従は別途考える。

(補足)MPSSE の BITBANG

    MPSSEめも』の記事で MPSSE について調べたのだが、

      Set Data bits LowByte 0x80 Value Direction
      Set Data bits HighByte 0x82 Value Direction
      Read Data bits LowByte 0x81
      Read Data bits HighByte 0x83

    こんな機能があるのだ。入出力の方向と出力値を同時に指定できる。そして、必要な時だけ 入力値を読み込める。SYNCBB だと方向はあらかじめ決めておかなければならない。そして常に読み込むのだ。この違いは大きく処理的にも随分違う。

    追記: LowByte は、TCK/TDI/TDO/TMS , GPIOL0-4 で、HighByte は GPIOH0-7 。

    ビットレートがどうなるかは、不明。内部クロックは 60MHz(2232D は 12MHz) で これを分周する設定があるのだが、TCK の分周を規定しているだけで、エンジンそのもののクロックが変わるとは書いてない。

(補足)JTAG と ICSP

    ICSP は、JTAG の操作にマップできる。基本は、4 倍遅い 4-PHASE 。一部 2 CLK で済む 2-PHASE が使えるらしいのだが、面倒なので全部 4-PHASE で行くつもり。
    シーケンスはこう。

    BITBANG ICSP

    PGC 1 0 1 0 1 0 1 0
    PGD_OUT ~TDI ~TMS 0 0 0 0
    PGD/TDI - - - - - - X -

    MPSSE ICSP
    PGC 1 0 1 0 1 0 1 0 (idle 0)
    PGD DIR 1 1 1 1 0 0 0 0
    PGD TDI TMS 1 1 1 1
    PGD(READ) - - - - - - X -

    わかりづらいと思うがこういう操作。

    あと、ICSP は、リセット中に特別な操作をすることで 使用可能になる。

    Entering ICSP (MPSSE)

    SYSRST 0 1 0 .. 0 .............0 0 ... 0 1 (idle 1)
    PGC 0 0 0 .. 0 1 0 1 0 1 0 1 0 0
    PGD 0 0 0 .. b31 b30 ... b00 0 0 0

    key_data: 0x4D434850

    これもわかりづらいと思うが一応メモしておく。

作ってみた

開発ターゲット購入

    実を言うと PIC32MX も UM232H もまだ持っていない。

    Mouser で PINGUINO があるのを知ったので、PIC32-PINGUINO-MX220 と一緒に PIC32MX250F128B(DIP) , UM232H も 購入することにした。

    当面は、PIC32MX250F128B + UM232H の組み合わせのみで デバッグしていこうと思う。( PIC32MX220F032B が真のターゲットだが 大は小を兼ねる -- はず )

    PIC32MX440F256H の PINGUINO-MICRO も購入することにしたが、これを使う予定はまだない。

ベースバージョン変更

    pic32prog-r62-20120804.tar.gz ベース(r62)
    pic32prog-r62-ftdi-02.zip ftdi-02版 を r62 ベースにしたもの。

    )jzlibの使い方』の記事で書いているように大分ライブラリも出来上がってきた。

    あいかわらず、割り込みは動いていないのだ。UART に printf できる環境を作って、デバッグしようかと思う。そのためにはまず、FTDI シリアル を使う。そうなると、DIP を使って 書き込みもできるようにしたほうが便利。

    そろそろ 再始動する時期が来たようだ。まずは、最新版とのマージから。

ブレットボードでライタを



    どんな感じになるか検討してみた。... のだが、IEC801 とか、30 行?しかない。AKI-UM232R が 12 ピン分、PIC32MX2xx が 14 ピン分使うから 残り 4 ピン分の空きしかない。ここに タクトスイッチを入れる。あとは LED か。

    LED はどうしても収まりが悪い。小基板使うしかないような気がする。もしくは抵抗入り LED を作るか。

    AKI-UM232Rは、これでも まだ良いのだ。本命は、UM-232H なのだが、これは 28 ピン。PIC32MX-DIP と並べると 2 ピンしか空きがない。

    リセットスイッチは、27 ピンの AGND を使って 付けられそう。だが、付加する部品がある。これは、DTC144 ( IN - OUT - GND というピン配置 ) を使って、IN を AD6 に OUT を AD7 に割り当てるしかなさそう。

    o-------o
    | |
    | TACT |
    | -SW |
    --o---o---o o-------o
    |
    | o---o---o
    UM232H | |
    | x x | PIC23MX220F032B
    | |
    | o---o---o
    --o---o---o

    o o o DTC144
    IN OUT GND

    位置関係を書くとこうなる。(UM232H の端 は AD6 - AD7 )

    syncbb-icsp-5673
    dbus_tck PGC AD5/DSR
    dbus_tdi PGD_OUT AD6/DCD
    dbus_tdo PGD AD7/RID
    dbus_sysrst ~MCLR AD3/CTS

    こういう風にしよう。

再始動

    jzlib』の方なかなかうまくいかないので、飽きてきた。こっちの方に手を付けることにした。どうせ両方仕上げないと自由には使えないのだ。『基板』の方もそろそろ送られて来るし急がないと。

    さて、本家の『pic32prog』だが、しばらく r62 で更新が止まっている。Windows 版は、バグを入れたまま放置のようだ。

      adapter-hidboot.c で、hid_read_timeout を使うようになったのだが、Windows では、hid_read_timeout 自体が変。元に戻して -S (ベリファイをスキップ) で 使った方がマシ。


    FTDI 版の状況だが、UM232H でデバッグしている。SYNCBB と MPSSE(BitBang) の両方をデバッグできて便利なのだ。ターゲットは、ブートローダを壊してしまった PIC-PINGUINO-MX220 。実は 2つ買ったので、jzlib の開発には支障がないのだが、基板も出来てくるし、書き込み方法を確立しておきたい。

    まずは、前の版の『pic32prog-r62-ftdi-02.zip』を動かしてみたのだが、ターゲットに接続する以前のレベルのバグがいくつかあった。

    上位レイヤーは動いているはずなのだから、どんな信号を出そうとしているのかだけ、クリアすれば良いはず。このことに注力して、メッセージを入れてターゲットに接続しないでテスト中。

    もっとも単純なのは、MPSSE(BitBang) での JTAG (以下 mpsse-jtag) 。ちょっとメッセージを紹介

      $ ./pic32prog -DD -t mpsse-jtag
      Programmer for Microchip PIC32 microcontrollers, Version 1.exported
      Copyright: (C) 2011-2012 Serge Vakulenko
      FTDI adapter: found (UM232H) mode (mpsse-jtag)
      mpsse-jtag: divisor: 11
      mpsse-jtag: latency timer: 1 usec
      mpsse-jtag: speed 500000 samples/sec
      write : to_write 3 to_drain 0 to_read 0 v 0 rlen 3
      write : to_write 1 to_drain 0 to_read 0 v 0 rlen 1
      drain : to_drain 0 rlen 0
      ## ftdi_reset 0
      write : to_write 6 to_drain 0 to_read 0 v 0 rlen 6
      drain : to_drain 0 rlen 0
      ## ftdi_reset 1
      write : to_write 6 to_drain 0 to_read 0 v 0 rlen 6
      drain : to_drain 0 rlen 0
      drain : to_drain 0 rlen 0
      ## mpsse_send :TTTTT0
      to_write 36 to_drain 0 to_read 0 v 0
      ## mpsse_send :TT000010TT0
      to_write 102 to_drain 0 to_read 0 v 0
      ## mpsse_send :TT001110TT0
      to_write 168 to_drain 0 to_read 0 v 0
      ## mpsse_send :T000111111TT0
      to_write 246 to_drain 0 to_read 0 v 0
      ## mpsse_send :T000000000TT0
      to_write 332 to_drain 0 to_read 8 v 8
      write : to_write 332 to_drain 0 to_read 8 v 8 rlen 332
      drain : to_drain 0 rlen 0
      read : to_read 8 v 8 rlen 8
      :74 74 74 74 74 74 74 74
      mpsse-jtag: status 00ff
      mpsse-jtag: invalid status = 00ff
      No target found.

    ## mpsse_send :T000000000TT0 は、大分下のレベルで、なにを送ろうとしているかをダンプしたもの。T が TMS を H にする意味 で確かに合っている。write : と drain: は、バッファ管理のメッセージで、貯めこんで write している。drain は余計なデータを読み飛ばす処理で、write からさらに遅延させている。だが、mpsse では、必要なときだけ READ の要求を出すので、余計なデータは常にゼロ。ここが単純だと言っているところ。
    read も 読み込んだバイトデータの TDO bit を変換するだけで楽。

    同じ処理を SYNCBB でやるとどうなるか

      ## ftdi_reset 0
      write : to_write 1 to_drain 1 to_read 0 v 0 rlen 1
      drain : to_drain 1 rlen 1
      ## ftdi_reset 1
      write : to_write 1 to_drain 1 to_read 0 v 0 rlen 1
      drain : to_drain 1 rlen 1
      drain : to_drain 0 rlen 0
      ## mpsse_send :TTTTT0
      to_write 12 to_drain 12 to_read 0 v 0
      ## mpsse_send :TT000010TT0
      to_write 34 to_drain 34 to_read 0 v 0
      ## mpsse_send :TT001110TT0
      to_write 56 to_drain 56 to_read 0 v 0
      ## mpsse_send :T000111111TT0
      to_write 82 to_drain 82 to_read 0 v 0
      ## mpsse_send :T000000000TT0
      to_write 108 to_drain 88 to_read 20 v 8
      write : to_write 108 to_drain 88 to_read 20 v 8 rlen 108
      drain : to_drain 88 rlen 88
      read : to_read 20 v 8 rlen 20
      :4b 4b 4b 4b 4b 4b 4b 4b
      syncbb-jtag: status 00ff
      syncbb-jtag: invalid status = 00ff
      No target found.

    write するバイト数は減るのだが、読み飛ばすデータが圧倒的に増える。で、読み込む場合も 20 バイトのうち 8 バイトだけが有効なデータ。最初はバッファ管理でずれが出ていたのだが、直せたようだ。

    さて、ICSP が本命。mpsse-icsp もあるが、複雑な syncbb-icsp の方を紹介する。

      ## ftdi_reset 1
      write : to_write 1 to_drain 1 to_read 0 v 0 rlen 1
      drain : to_drain 1 rlen 1
      ## ftdi_reset 0
      write : to_write 1 to_drain 1 to_read 0 v 0 rlen 1
      drain : to_drain 1 rlen 1
      ## ftdi_reset 1
      write : to_write 1 to_drain 1 to_read 0 v 0 rlen 1
      ## ftdi_send_key 0x4d434850
      write : to_write 65 to_drain 66 to_read 0 v 0 rlen 65
      drain : to_drain 66 rlen 66
      ## ftdi_reset 0
      write : to_write 1 to_drain 1 to_read 0 v 0 rlen 1
      drain : to_drain 1 rlen 1
      drain : to_drain 0 rlen 0
      ## mpsse_send :TTTTT0
      to_write 48 to_drain 48 to_read 0 v 0
      ## mpsse_send :TT000010TT0
      to_write 136 to_drain 136 to_read 0 v 0
      ## mpsse_send :TT001110TT0
      to_write 224 to_drain 224 to_read 0 v 0
      ## mpsse_send :T000111111TT0
      to_write 328 to_drain 328 to_read 0 v 0
      ## mpsse_send :T000000000TT0
      to_write 432 to_drain 352 to_read 80 v 8
      write : to_write 432 to_drain 352 to_read 80 v 8 rlen 432
      drain : to_drain 352 rlen 352
      read : to_read 80 v 8 rlen 80
      :9f 9f 9f 9f 9f 9f 9f 9f
      syncbb-icsp: status 00ff
      syncbb-icsp: invalid status = 00ff
      No target found.

    まず、ICSP では、SYSRST = L にした後 KEY を送って、SYSRST = H にする必要がある。(JTA では、SYSRST = L にするだけ)

    mpsse_send だが、送受信ともに 4 倍になる。で、受信データも 8 bit のために 80 バイト受け取る。UM232H は、480M bps だから、こんな処理でも遅さを感じないんじゃないかと思う。FT232R では心配だが、何分もかかるものではないと思っている。遅いのが困るなら JTAG にする手もあるし、まぁなんとかなるだろう。。

    pic32prog-r62-ftdi-03.zip ftdi-03版

    まだまだなのだが、一旦スナップショット。exe ファイルも同梱している。HID BOOT の方は -S オプションで使える。
    pic32prog-r62-ftdi-04.zip ftdi-04版 (置き換え)

    あまり変わっていないが、いくつか気がついたので直した。ICSP モードに入るとき 最後の TCK の立下りと /MCLK の立ち上がりのタイミングがシビア。多分同時に変化させれば良いのだと思うが自信なし。また、ICSP に入る前に /MCLR を L → H → L にするのだが、H の期間が 500us 以下でないといけない。ftdi_reset() 関数を 2 回 call するのでは、これを保証できないので 専用の関数を作成した。タイミングがシビアなのは、この2つだけの模様。

    いろいろ試しているのだが、全然だめ。ひょっとして、JTAG が Disable されていたりしないか? 配線が長過ぎないのか? とか疑心暗鬼状態。PGEx3 が使えるのは確実だと思うのだが、ICSP モードの方が難しいし、悩ましい。
    懸案事項を列挙すると

    • ビットレートがどうなるか不明なこと。内部クロックは 60MHz(2232D は 12MHz) で これを分周する設定があるのだが、TCK の分周を規定しているだけで、エンジンそのもののクロックが変わるとは書いてない。GPIO の設定 を全力でされると 1/3 の 20 MHz で信号が切り替わるかも知れない。

      ICSP では、10 MHz が上限なので、それ以上になる不安がある。2 倍のデータを送れば レートを半分に落とせるものの、ICSP が耐えても ケーブルが適当ではダメかもしれない。

      USB の転送レートとは別の話なので、確認するには、ロジアナが必要になりそう。あと、SYNCBB だと、ルールが良くわからないものの、分周の設定に比例して変わっていた。MPSSE も同じようなものだと期待したい。

    • JTAG が Disable されている可能性。コンフィグが分れば良いのだが ... シリアルを用意したし、動いている PINGUINO で確認したいのだが、まだ printf が動かない。

    • 接触不良の可能性や 接続を間違えている可能性。動くと分かっていれば、これのチェックに注力すれば良いのだが ...

    pic32prog-r62-ftdi-04.zip を置き換えた。

    ロジック自体は、進展がない。bit clk の設定とメッセージ関係の修正。メッセージを眺めているのだが、動いてもおかしくなさそうな ... 特に syncbb-jtag は動きそうな気がする。

      $ ./pic32prog -DD -t ft0-syncbb-jtag
      Programmer for Microchip PIC32 microcontrollers, Version 1.exported
      Copyright: (C) 2011-2012 Serge Vakulenko
      syncbb-jtag Pin Assignment :
      TCK (PGC) 5
      TDI (PGD) 2
      TDO (PGD_OUT) 3
      TMS 7
      /MCLR 4
      FTDI adapter: found (UM232H) mode (syncbb-jtag)
      syncbb-jtag: latency timer: 2 msec
      syncbb-jtag: requested bit_clk: 500000 Hz
      syncbb-jtag: set baud 115200
      drain : to_drain 0 rlen 0
      ## ftdi_reset 0
      write : to_write 1 to_drain 1 to_read 0 v 0 rlen 1
      drain : to_drain 1 rlen 1
      ## ftdi_reset 1
      write : to_write 1 to_drain 1 to_read 0 v 0 rlen 1
      drain : to_drain 1 rlen 1
      drain : to_drain 0 rlen 0
      ## mpsse_send :TTTTT0
      to_write 12 to_drain 12 to_read 0 v 0
      ## mpsse_send :TT000010TT0
      to_write 34 to_drain 34 to_read 0 v 0
      ## mpsse_send :TT001110TT0
      to_write 56 to_drain 56 to_read 0 v 0
      ## mpsse_send :T000111111TT0
      to_write 82 to_drain 82 to_read 0 v 0
      ## mpsse_send :T000000000TT0
      to_write 108 to_drain 88 to_read 20 v 8
      write : to_write 108 to_drain 88 to_read 20 v 8 rlen 108
      drain : to_drain 88 rlen 88
      read : to_read 20 v 8 rlen 20
      :4b 6b 4b 6b 4b 6b 4b 6b 4b 6b 4b 6b 4b 6b 4b eb
      syncbb-jtag: status 00ff
      syncbb-jtag: invalid status = 00ff
      No target found.

    これは、なにもつなげずに 動かした結果。

    ## mpsse_send :T00[0000000T]T0

    [] の部分が、read の dump に対応する。2 バイト目は、前に書いたデータの結果で TCK=1 になっている。最後だけ eb で TMS=1 。

    このように、自分がどう出力したかは確認できた。baudrate も 115200 で問題ない。 やっぱり JTAG が Disable されていなければ動くと思える。

    read : to_read 16 v 8 rlen 16
    :74 75 74 75 74 75 74 75 74 75 74 75 74 75 74 7d

    では、mpsse-jtag ではどうなのか? mpsse では余計なデータを読まないので、コードを追加してやってみたところ同じようになった。... あとは、データが変化する周期がどうなのか確認できれば、syncbb-jtag と同じレベルになる。

    おなじように、ICSP の場合も結果を見られるようにしておいた。これは syncbb-icsp の結果

    :9f df df 9f 9f df df 9f 9f df df 9f 9f df df 9f
    :9f df df 9f 9f df df 9f 9f df df 9f 9f df 9f 9f

    もうなんだか分からなくなってくる。

    :9f df df 9f : 9f df df 9f : 9f df df 9f : 9f df df 9f
    :9f df df 9f : 9f df df 9f : 9f df df 9f : 9f df [9f] 9f

    ううむ。

    :fd fd fe fe : fd fd fe fe : fd fd fe fe : fd fd fe fe
    :fd fd fe fe : fd fd fe fe : fd fd fe fe : fd ff fe fe

    こちらは、mpsse-icsp 。

    まぁいいか。今回はここまでにしておこう。次は、基板がきた時。

    ちょっと進展というか残念な 情報が得られた。

    hello world 5 5 000003b0
    DEVCFG3 = 0x3fffffff
    DEVCFG2 = 0xfff979d9
    DEVCFG1 = 0xff60cedb
    DEVCFG0 = 0x7fffffeb

    だいぶごまかして、printf を動かし、DEVCFG を表示させた。これを見ると DEVCFG0 の最後 が 1011 になっている。すなわち JTAG は Disabled 。残念だが ICSP でなんとかするしかなくなった。 まぁ、原因がわかっただけでもすっきりした。

進捗状況 (8/21)

    なんと、mpsse-icsp で、最初のところだけ動いた。jtag より先に進んでしまったのだ。

      ./pic32prog -t mpsse-icsp-012 -D
      Programmer for Microchip PIC32 microcontrollers, Version 1.exported
      Copyright: (C) 2011-2012 Serge Vakulenko
      mpsse-icsp-012 Pin Assignment :
      TCK (PGC) 0
      TDI (PGD) 1
      TDO -1
      TMS -1
      /MCLR 2
      FTDI adapter: found (UM232H) mode (mpsse-icsp-012)
      mpsse-icsp-012: latency timer: 2 msec
      mpsse-icsp-012: requested bit_clk: 500000 Hz
      mpsse-icsp-012: set divisor 3
      mpsse-icsp-012: idcode 04a00053
      mpsse-icsp-012:open status 008a
      Adapter: mpsse-icsp-012
      Processor: MX220F032B (id 04A00053)
      Flash memory: 32 kbytes
      Boot memory: 3 kbytes
      mpsse-icsp-012: enter serial execution
      mpsse-icsp-012:serial_execution(1) status 008a
      mpsse-icsp-012:serial_execution(2) status 008a
      mpsse-icsp-012: read word at 1fc00bf0 -> ffffffff
      mpsse-icsp-012: read word at 1fc00bf4 -> ffffffff
      mpsse-icsp-012: read word at 1fc00bf8 -> ffffffff
      mpsse-icsp-012: read word at 1fc00bfc -> 7fffffff

    jtag の方も Boot memory の表示までは行く。どうも変なバグを入れてしまったようだ。よく無限ループに落ちいる。

    ところで、デフォルトのコンフィグは、PGEC1/PGED1 を使うようになっているのだが、ICSP のコネクタは PGEC3/PGED3 にしている。PIC32-PINGUINO-MX220 と同じにしたわけだ。で、コンフィグを書き換えてみたところ、 PGEC3/PGED3に切り替わった。一応1回は書けたわけだ。だが、その後 無限ループに落ちいるようになってしまった。

    あと、PIC32-PINGUINO-MX220 のコンフィグは、PGEC1/PGED1 のままだった。コネクタまで付けているのにひどい話だ。(間違い --- 確認したところ PGEC3/PGED3 の設定になっていた。で、この設定では PGEC1/PGED1 も使えたので誤解していた。)

    この無限ループさえなんとかすれば、使えるようになるかも知れない。ただ、最も使われるであろう syncbb-icsp は、まだ動いていない。付加回路の部分がまずいのか、単なるバグかはまだ不明。いずれにしても 解決はすぐだろう。

    pic32prog-r62-ftdi-05.zip

    PIC32-PINGUINO-MX220 からダウンロードした HID ブートローダを試しにそのまま書いてみたら書けてしまった。... 壊してしまった PIC32-PINGUINO-MX220 が復活し、自作 PIC32MX-typeB 基板も HID ブートローダが使えてしまった。いまのところ mpsse-icsp しか実績がないが、一応公開しておく。

    syncbb-icsp も動いたら 別途公開するつもり。

    追記: 上記 05版 そのままで、 syncbb-icsp は動いた。確認は UM232H で行ったが、AE-UM232R や 一般の FT235R で 問題ないはず。 付加回路は、秋月で買った DTC144 に 4.7KΩで OUT をプルアップしたもの。保護に使った抵抗は 120 Ω。結局テスト用に用意した AE-UM232R は、ボロボロだったようで役に立たなかった。基板まで作ったのに、ブレッドボード + UM232H だけでのテストになった。

    後は JTAG だが、動きそうな気がする。



    ちょっと説明。例えば AE-UM232R だとこういう接続で良いはずなのだ。追加パーツは、4.7K と デジトラ。結線は 3 本のみ。GND の結線も不要 (USB 側でつながっている場合)
    使い方は、
     pic32prog -t syncbb-icsp-0175 -DD

    これでいけるはず。

付録) シーケンス表

    SENDING SEQUENCE (MEMO)

    SYNCBB_JTAG ( 2 bytes out / 2 bytes in) x bits

    +------+------+
    TCK | 1 | 0 |
    +------+------+
    TDI | TDI | TDI |
    +------+------+
    TDO | TDO | Z |
    +------+------+
    TMS | TMS | TMS |
    +------+------+
    SYSRST | | |
    (/MCLR) +------+------+

    MPSSE_JTAG ( 6 or 7 bytes out / 1 byte in) x bits

    READ
    +------+------+------+
    TCK | - | 1 | 0 |
    +------+------+------+
    TDI | - | TDI | TDI |
    +------+------+------+
    TDO | TDO | Z | Z |
    +------+------+------+
    TMS | - | TMS | TMS |
    +------+------+------+
    SYSRST | | | |
    (/MCLR) +------+------+------+

    SYNCBB_ICSP ( 8 bytes out / 8 bytes in) x bits

    +------+------+------+------+------+------+------+------+
    TCK | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
    (PGC) +------+------+------+------+------+------+------+------+
    TDI | ~TDI |~TDI |~TMS |~TMS | 0 | 0 | 0 | 0 |
    (PGD_OUT) +------+------+------+------+------+------+------+------+
    TDO | Z | Z | Z | Z | Z | Z | TDO | Z |
    (PGD) +------+------+------+------+------+------+------+------+
    SYSRST | | | | | | | | |
    (/MCLR) +------+------+------+------+------+------+------+------+

    MPSSE_ICSP ( 24 or 25 bytes out / 1 byte in) x bits
    READ
    +------+------+------+------+------+------+------+------+------+
    TCK | 1 | 0 | 1 | 0 | 1 | 0 | - | 1 | 0 |
    (PGC) +------+------+------+------+------+------+------+------+------+
    TDI | TDI | TDI | TMS | TMS | Z | Z | TDO | Z | Z |
    (PGD) +------+------+------+------+------+------+------+------+------+
    SYSRST | | | | | | | - | | |
    (/MCLR) +------+------+------+------+------+------+------+------+------+


    --------------------------------------------------------------
    SEND KEY SEQUENCE (ICSP only)

    SYNCBB_ICSP ( 65 bytes out / 65 bytes in)


    +------+------+ +------+------+------+
    TCK | 0 | 1 | | 0 | 1 | 0 |
    (PGC) +------+------+ +------+------+------+
    TDI |~MSB |~MSB | ..... | ~LSB |~LSB | 1 |
    (PGD_OUT) +------+------+ +------+------+------+
    TDO | Z | Z | | Z | Z | Z |
    (PGD) +------+------+ +------+------+------+
    SYSRST | 0 | 0 | | 0 | 0 | 1 |
    (/MCLR) +------+------+ +------+------+------+

    MPSSE_ICSP ( 195 bytes out / 0 byte in)

    +------+------+ +------+------+------+
    TCK | 0 | 1 | | 0 | 1 | 0 |
    (PGC) +------+------+ ------+------+------+
    TDI | MSB | MSB | ..... | LSB | LSB | 0 |
    (PGD) +------+------+ ------+------+------+
    SYSRST | 0 | 0 | | 0 | 0 | 1 |
    (/MCLR) +------+------+ +------+------+------+

付録) ピンアサイン 表

    AE-UM232R と UM232H
     

    PIC32MX-TypeB / PIC32-PINGUINO-MX220

    RA0 (x)AREF PGED3
    o GND
    o D13
    o D12
    RST(o) RB5 (o)D11 TMS
    3V3 o RA1 (x)D10 PGEC3
    5V o o D9
    GND o RB7 (o)D8 TDI
    GND o
    -- o -- o D7
    -- o D6
    A0 o -- -- o D5
    A1 o -- -- o D4
    A2 o RB0 RB9 (o)D3 TDO (*1)
    A3 o RB1 RB8 (o)D2 TCK (*1)
    A4 o RB2 o D1 TDO (*2)
    A5 o RB3 o D0 TCK (*2)

    (*1) PIC32MX-TypeB (my bourd)
    (*2) PIC32-PINGUINO-MX220 (Olimex)

関連記事
posted by すz at 21:50| Comment(1) | TrackBack(0) | PIC32MX

2012年07月13日

PIC32MX220F032B メモ

CPLD のカテゴリで延々と記事を書いているのだが、ちょっと脱線。

USB OTG コントローラ付きの PIC32MX PIC32MX220F032B が、秋月で 220円という低価格で売られるようになった。この値段だと、単なる USB コントローラより安い。AVR の代わりとして使う気はないのだが、なにかの専用ICとして仕立て使ってみたい気がしている。

    興味があるのは、とりあえず USB ホスト機能。USB BlueTooth ドングルは妙に安いようだから、これでシリアルやSPIに変換できたらと思っている。

というわけで、ちょっと下調べ。

28 pin DIP だが、自由に使えるピンは少ないようだ。

~MCLR (1) AVdd
PGED3 RA0 G1 AVss
PGEC3 RA1 G2 G1 RB15/SCK2 G1 SS1
PGED1 RB0 G4 G4 RB14/VBUSON/SCK1 G2 SDI1 SDO1 SDO2
PGEC1 RB1 G2 G3 RB13 G3 SDI2
SDA2/RB2 G3 Vusb3v3 G4 SS2
SCL2/RB3 G1 G2 RB11/D- PGEC2
Vss G4 RB10/D+ PGED2
CLKI Vcap
CLKO Vss
SOSCI RB4 G1 G4 RB9/SDA1 TDO
SOSCO RA4 G3 G2 PB8/SCL1 TCK
Vdd G1 PB7 TDI
TMS/USBID RB5 G2 Vbus

とりあえずピン配置をメモ。JTAG を使うと 11 pin しか自由に使えない。ただ、コンフィグで JTAG は Disable に出来る。そうすると 15 pin 使えるようになるらしい。

補足: デフォルトでは、セカンダリオシレータ(32.768 Hz 水晶) が ON になっているそうだ。さらに、USBID と VUSBON の設定も コンフィグにあり、これも ON になっているとポートとして使えない。また、PPS -- Periphral Pin Assign という機能があって、UART などのピンをいくつかのピンから選択できるのだが、この選択を 1 回しかできなくする設定も コンフィグにある。とにかくコンフィグはよくよく調べておかないとならない。

補足2: JTAG は コンフィグでは Enable にしておいて、ブートローダやプログラムの先頭で

CFGCON = 0; // 1xx/2xx 以外では DDPCON = 0;

とやって Disable にするものらしい。こうすると、ファームウェアを書き込むときにだけ JTAG を使うことができる。

    RA0 o AREF PGED3
    GND
    RB4 U1TX RB15 o D13 LED1
    RA4 U1RX RB13 o D12
    RB14 SCK1 RB5 o D11 TMS
    RA1 o D10 PGEC3
    RA10 x D9 LED2
    RB7 o D8 BUT/TDI

    RC7 x D7
    RC6 x D6
    A0 x RC0 RC5 x D5
    A1 x RC1 RC4 x D4
    A2 o RB0 RC3 x D3
    A3 o RB1 RC2 x D2
    A4 o RB2 RB9 o D1 U2TX/TDO
    A5 o RB3 RB8 o D0 U2RX/TCK

    ちょっと PIC32-PINGUINO-MX32 のピン配置を調べてみた。これは、同じ PIC32MX220F032 だが 44pin の D を使っている。B ではピンが足りなすぎて Arduino もどきにするには、厳しそうだ。

組み込みの機能のピンは、割り当てが最初から決まっているものと、割り当てを変えられるものがある。ここで載せたのは、最初から決まっているもので、興味があるもの。( ADC 関係は面倒なので載せていない。)
どうも I2C と SPI の SCK は配置を変えられないようだ。

    ピン A0 B3 B4 B15 B7
    出力 U1TX U2RTS SS1 OC1 C2OUT
    入力 INT4 T2CK IC4 SS1 REFCLK1

    ピン RA1 RB5 RB1 RB11 RB8
    出力 SDO1 SDO2 OC2
    入力 INT3 T3CK IC3 U1CTS U2RX SDI1

    ピン RB6 RA4 RB13 RB2
    出力 SDO1 SDO2 OC4 OC5 REFCLKO
    入力 INT2 T4CK IC1 IC5 U1RX U2CTS SDI2 OCFB

    ピン RB14 RB0 RB10 RB9
    出力 U1RTS U2TX SS2 OC3 C1OUT
    入力 INT1 T5CK IC2 SS2 OCFA

割当てを変えられるものは、4 つのグループに分けられていて、その中では自由に割り当てられるようだ。

あといくつか

    フラッシュの書き込みは、OpenOCD が使えるとのこと。
    ~MCLR (リセット) にはプルアップが必要。
    レジスタへの 書き込みは割り込み禁止で

デジキーでの値段とか

    QFN28 や SSOP28 パッケージもあり、デジキーでは 300円ぐらい。10 個買えば、秋月価格に近くなるみたいだ。

      QFN28:
      1 315.00000 315
      10 246.00000 2,460
      25 225.80000 5,64
      SSOP28
      1 300.00000 300
      10 236.30000 2,363
      25 216.12000 5,403

    ピンを増やしたいなら TQFP44 がある。QFN もあるけど。値段は 少し高くなる。

    44pin は、PIC32MX220F032D とか末尾が D。
    メモリが多いのは、型番が違う。

    PIC32MX220F032B/D Flash 32KB RAM 8KB
    PIC32MX230F064B/D Flash 64KB RAM 16KB
    PIC32MX250F128B/D Flash 128KB RAM 32KB

    SSOP28 128KB
    1 431.00000 431
    10 338.20000 3,382
    25 310.12000 7,753
    DIP28 128KB
    1 457.00000 457
    10 359.30000 3,593
    25 329.48000 8,237

    これでも安いといえば安いが、使いこなせなければ意味ないし気軽に買えるわけでもない。

USBコントローラ

    PIC32MXの USB インターフェイス について 秋月の URL にあるデータシートを見れば、説明が載っているわけだが、全部英語。で、レジスタの名前 例えば U1OTGIR で検索すると ... PIC24F の 日本語ドキュメント がヒットする。

    構成図を見ると、PIC32MX と PIC24F の USB モジュールは実に似ている。どうもほとんど同じようだ。概要を知りたい程度なら、PIC24Fの方で済みそう。

    PIC には、USB Framework というのがあるらしいが、32KB でも厳しいらしい。gcc のくせに最適化を使わせないというのは、どうかと思うし、このチップを使うなら、全部自前で用意した開発ツールでビルドできるようにしたい。

    ... というわけで、使いたくはあるが、なかなか手がつけられないのだった。

ブートローダ

    AVR の ATmega32u2 用に自前の USB ライブラリ で USBasp プロトコル ブートローダ を作っている。 PIC32MX も 3KB の ブートローダ 領域を持っている。移植できたりしないかな。ヒューズビット等の関係があるから、avrdude が使えるかどうか分からないが ...

    あと、AVR と同様に割り込みを一切使わずに ブートローダ を作れるなら、mips16e が使えるかも知れない。

    ブートローダは、後述している Pinguino プロジェクト のを使うのが吉らしい。自分で作る必要はなかったか。

     ・ Section 5 : Flash Programming -- Run-Time Self-Programming(RTSP) の仕様書 (pdf)
     ・ PIC32MX Flash Programming Specification -- JTAG , ICSP の仕様書 (pdf)

      JTAG を使った書き込みには、2 種類あるらしい。ひとつは、Programming Executive(PE) を RAM に送り込んで それを使って Flash を操作するやりかたで 多分これが標準的。もうひとつは、直接 RTSP 用レジスタを操作して書き込むやりかた?

参考になるサイト

     ・ 『マイコン風雲録: 秋月でPIC32MX 28pin DIP版出る

    ここが詳しい。Pinguino プロジェクト というのがキーワードっぽい。

    Pinguino プロジェクト

      ブートローダや gcc まで 含まれた開発ツール Pinguino X.3 を 配布している。

      gcc/binutils のパッチ や config方法 、ブートローダのソースコードだけ見たいのだが どこにあるんだろう?

      そもそも gcc のバージョンは? 4.6.2 と 4.5.2 があるようだが。 自製 AVR_Toolchain は、gcc-4.4.3 を使っているのだが、これで済ませられないかな?

      追記: svn から最新のものをダウンロードしてみた。(リビジョン 543)

        Configured with: ../configure --target=mips-elf --program-prefix=mips- \
        --enable-languages=c --prefix=/home/jpm/pcompiler --disable-nls \
        --disable-tui --disable-gdbtk --disable-shared --enable-static --disable-threads \
        --disable-bootstrap --with-dwarf2 --enable-multilib --enable-sim \
        --with-float=soft --without-headers --with-lib-path=: \
        --with-pkgversion='Pinguino C Compiler for PIC32 v4.5'
        Thread model: single
        gcc version 4.5.2 (Pinguino C Compiler for PIC32 v4.5)

      -v で確認するとこういうオプションで config されていることは分かった。

      MIPS は AVR のように 新しいチップに対応するために コンパイラを変更したりしない。たぶんパッチなしなのだろう。変更がないのなら、gcc-4.4.3 でも構わなさそう。ちょっとビルドしてみよう。

      ところで、バイナリのダウンロードは、
       http://code.google.com/p/pinguino32/downloads/list
      ここから出来るのだが、win32 版だけ小さい。これはたぶん g++ がない。svn の方にも win版だけ g++ がなかった。

    pic32prog: PIC32MX220をPickit2/Pickkit3で書き込む

      これは ... ソースコード を GPLv2 で配布しているではないか。MPSSE(Olimex ARM-USB-Tiny JTAG adapter) 用のコードもある。
      ちょっと見てみよう。FT232RL に対応できるかも知れないし、逆に 自製の JTAG ツールで対応できるかも知れない。(対応できた。追記参照)

    Olomex PIC32-PINGUINO-MX220



      Olimex のボード 。結構安い。9.95EUR。送料はいくらだろう?

      リンクも参考になりそう。

      arduio と ピン互換になるようにしていて、外側内側には ユニバーサル基板を使えるようにもう一列追加されている。

      追記: Mouser で PINGUINO を扱っている

      PIC32-PINGUINO-MX220 1:\1,572
      PINGUINO-MICRO (PIC32MX440F256H) 1:\1,660

      本末転倒なのかも知れないが、pickit3 や PIC32MX250F128B/PIC32MX220F220B あるいは UM232H なんかを一緒に買うと 7500 円は超えそうだし、Mouser で買うのも悪くなさそう。

      追記: デジキーでも PINGUINO を扱いはじめた。

      PIC32-PINGUINO-MX220 1:\1,418
      PINGUINO-MICRO (PIC32MX440F256H) 1:\1,418

      こっちの方が安い。

    エラッタ : レジスタに書き込んでるときに 割り込みが起きると 割り込みからの戻りで 再度書き込む。

    割り込みが起きた以上そうなるのは、MIPS の特性。割り込みが起きないようにしていないというのがバグ?

PIC32MX 用 gcc のビルド(1)

    以前、『AVR Toolchain (その2)』という記事で avr-gcc をビルドした。この バージョンの gcc を PIC32MX 向けにビルドしようと思う。

    binutils-2.20.1


    ./configure --prefix=/d/MIPS32_Toolchain --target=mips-elf --disable-shared \
    --disable-threads --enable-languages=c,c++ --disable-dssi \
    --disable-plugin


    --enable-languages など関係ないはずだが、prefix と target だけ変更してビルド。

    make ; make install

    gcc-4.4.3


    ./configure --target=avr --prefix=/c/AVR_Toolchain --enable-languages=c,c++ \
    --with-dwarf2 --disable-doc --disable-shared --disable-libada \
    --disable-libssp --disable-nls --enable-fixed-point \
    --with-build-time-tools=/c/AVR_Toolchain/avr/bin

    AVR ではこういう指定をした。

    ./configure --target=mips-elf --prefix=/d/MIPS32_Toolchain --enable-languages=c \
    --with-dwarf2 --disable-doc --disable-shared --disable-libada \
    --disable-libssp --disable-nls --disable-fixed-point \
    --with-build-time-tools=/d/MIPS32_Toolchain/mips-elf/bin \
    --disable-tui --disable-gdbtk --disable-threads --disable-bootstrap \
    --enable-multilib --enable-sim --with-float=soft --without-headers

    それを元に Pinguino で指定しているオプションを追加。C のみに限れば 比較的簡単だろう。

    オプションは、有効でないものもおそらく指定している。だが、確認するのが面倒なので気にしない。

    make は通った。

    mips-elf-gcc -Os -mips16

    も使えるようだ。


    ls /d/MIPS32_Toolchain/bin/
    libiconv-2.dll mips-elf-gcc-4.4.3.exe mips-elf-objdump.exe
    libintl-8.dll mips-elf-gcc.exe mips-elf-ranlib.exe
    mips-elf-addr2line.exe mips-elf-gccbug mips-elf-readelf.exe
    mips-elf-ar.exe mips-elf-gcov.exe mips-elf-size.exe
    mips-elf-as.exe mips-elf-ld.exe mips-elf-strings.exe
    mips-elf-c++filt.exe mips-elf-nm.exe mips-elf-strip.exe
    mips-elf-cpp.exe mips-elf-objcopy.exe

    ls x.3/win32/p32/bin/
    grep.exe mips-as.exe mips-ld.bfd.exe mips-strings.exe
    libiconv-2.dll mips-c++filt.exe mips-ld.exe mips-strip.exe
    libiconv2.dll mips-cpp.exe mips-nm.exe mphidflash.exe
    libintl-8.dll mips-elf-gcc-4.5.2.exe mips-objcopy.exe pcre3.dll
    libintl3.dll mips-elfedit.exe mips-objdump.exe regex2.dll
    make.exe mips-gcc.exe mips-ranlib.exe
    mips-addr2line.exe mips-gccbug mips-readelf.exe
    mips-ar.exe mips-gcov.exe mips-size.exe

    作ったものの実行ファイル一覧と Pinguino (win版)比較。

    dll は、AVR_Toolchain と同じものを入れている。

    Pinguino では、make と grep が添付されている。binutils は target=mips でビルドしているのか。gcc もリネームしている。

    さて、これだけで、PIC32MX220F032B で動くものが作れるかというと ダメ。足りないものがある。

    Pinguino をチェックすると

      p32/include/non-free/cp0defs.h
      p32/include/non-free/p32xxxx.h
      p32/include/non-free/regdef.h

      p32/include/non-free/proc/p32mx220f032b.h
      p32/include/non-free/proc/p32mx220f032d.h
      p32/include/non-free/proc/p32mx250f128b.h
      p32/include/non-free/proc/p32mx440f256h.h
      p32/include/non-free/proc/p32mx460f512l.h
      p32/include/non-free/proc/p32mx795f512l.h
      p32/include/non-free/proc/p32mxgeneric.h
      p32/include/non-free/proc/ppic32mx.h

      p32/obj/non-free/crt0.S

      p32/lkr/ISRwrapper.S
      p32/lkr/elf32pic32mx.x
      p32/lkr/procdefs.ld
      p32/lkr/PIC32_PINGUINO_220/ISRwrapper.S
      p32/lkr/PIC32_PINGUINO_220/elf32pic32mx.x
      p32/lkr/PIC32_PINGUINO_220/procdefs.ld

    多分これらが必要。include のものは、レジスタの定義が主。non-free となっているのは、MicroChip 提供のコード。crt0.S はスタートアップ。p32/lkr は、リンカスクリプトだと思うが、ISRwrapper.S が何なのかは未チェック。

    これらを使って main を call するところまで 行くのが最初のステップ。

    ライブラリは、AVR-libc 風のものを 以前に作ったのがあるから、これを使えるようにしたい。これが次のステップ。

      記事『USBBOOTプログラム用ライブラリ』にどういうものかについて記載している。usbboot-wk19.tar.gz がその最後の版で 未完成のまま放置中。

      記事を見直すと、-fno-pic ,-fno-pic -mno-abicalls , -fpic といったオプションのどれを使うべきかで悩んでいた。これもチェックしておかないと。

      PIC32MX は、シャドウ・レジスタを持っている。対応できると 割り込みで、レジスタのセーブ・リストアが要らなくなる。ちなみに AVR-libc 風ライブラリは、(適当なものだが)割り込みベクタもサポートしている。

      リンカスクリプトは、チップの種類毎に ユーザプログラム用、ブートローダ用の 2 つ。中を見てみると割り込みベクタまでいちいち定義している。AVR-libc だとベクタは アーキテクチャとして定義されている。のだが、ライブラリの中でサポートしていて、リンカスクリプトは、関知しない。MIPS は、割り込み要因毎のベクタは アーキテクチャとして定義されていない。にも関わらずリンカスクリプトに細かく定義されている。

      これは相当に気に入らない。どうせ自製のライブラリにするんだから、リンカスクリプトは 一種類で済ませたい。

      メモリマップを調べてみた。kseg0 だけ書くと

      Start Size
      Config 9FC00BF0 10
      Boot 9FC00000 BF0
      Flash 9D000000 16KB - 128KB
      RAM 80000000 4KB - 32KB

      1xx , 2xx は、こんな風になっていて、スタートアドレスは同じ。Flash と RAM のサイズが違うのみ。

      あと、PIC32MX は、M4K アーキテクチャで、割り込みベクタは、ハードウェアでサポートされているらしい。そうだとしても AVR-libc のように .text セクションの先頭に 置けばよいだけのはず。ただ、エントリが細分化されると レジスタのセーブ・リストアが面倒。シャドウレジスタを使いたくなる。使い方を調べないと...
       ・ Breaf Introduction for M4K Shadow Registers
      これか?

      リンカスクリプトを見てみたが 割り込みベクタは 1エントリにつき 32バイト(8命令分)使う。で、1xx/2xx は 32 エントリ -- 1KB も消費するわけだ。データシートの割り込みコントローラの説明を見たところ マルチベクターモードと シングルベクターモードがあることが分かった。シングルベクターモードでは、シャドウレジスタを使うか使わないかの設定もある。あと、INTSTAT レジスタに 割り込み番号が入っているが、CPU に通知した番号らしい。もし、シングルベクターモードでは 常に 0 になるなら使えない。( IFSx という ビットマップから番号を作れはするが、重くなる。)

    その次が、自製の USB デバイスライブラリの移植。

      こっちは、『Teensy(1.0)互換ボード』に記載した angel_loader-1.4c.zip が最新。API を共通にしたいところ。

      USB デバイスライブラリは、割り込みを使用していない。PIC32MX でも同じように出来ると良いのだが ...

    ここまで動かせたら、USB HOST のプログラミングに入れる。なかなかに道のりは遠い。

    ライタソフトも FT232R 対応のものを用意したい。これは別途検討するが、動かそうとする段階で欲しくなる。

      MachXO2コンフィグめも』の記事の rtavr_tools-0.10.tar.gz が公開している 自製 JTAG ツールの最新版なのだが、それから変更した版がある。

      自製 JTAG ツールだと、もともと FT232R 用で、JTAG レベルの手順が分れば、PIC32MX に対応できる。HEX ファイルの読み込みも 一応ある。

      pic32prog に FT232R 用のコードを追加する手もあるだろう。どっちが楽かというと 多分自製ツールでの対応。でも、元が MPSSE に対応しているなら、 FT232R のコード追加は、それほど難しくはない。ただし、ピンアサインの自由度がなくなるかも知れない。

ライタの検討(1)

     ・ PIC32MX Flash Programming Specification -- JTAG , ICSP の仕様書 (pdf)

    まずは、どういう手順で操作するのか知るためにこれを読んでみた。

    FLASH をプログラミングするには、2 つの手段がある。ひとつは、JTAG でもうひとつは、ICSP(2-Wire) 。ICSP は、PGECx/PGEDx という 2 つの 信号線と ~MCLR を使うもので、PIC32MX220F032B には、3 セットもある。そのうちのひとつは、D-/D+ に割り当てられている。USB を使うのが前提であれば、このピンは必ず空いている。相当便利なような気がしてきた。

    ICSP は、PGECx をクロックにして、PGEDx でデータを送受信する。これを JTAG の操作にマッピングしている。だから 下位レイヤを変更することで対応できる。それは良いのだが、JTAG だと 1 CLK で済むところを、標準的には 4 CLK かける 。そして、4 CLK の間で 出力と入力を切り替える。MPSSE だと、高度な I/O ができるので問題ないのだが、FT232R だと 出力を Hi-Z にするための付加回路が必要になりそう。チップの機能で 入出力を切り替えると とてつもなく遅くなる。

    あと、ICSP を使うためには、手続きがある。3 セットもあるわけだし、どれを使うか 手続きなしに決められるわけもない。一方 JTAG は手続きなしらしい。... ということは Disable にしてしまったら、どうなるのだろう? ひょっとしたら使えないのかも知れない。

      追記: だいぶ分かってきた。PGEx は、どれを使うかコンフィグで設定する。00 を設定してしまうと、使えなくなってしまう。JTAG も コンフィグで Disable できるが、プログラムでもできる。Enable にしておいて、プログラムの頭で Disable にする使い方が良い。(ブートローダを使っている場合は、既に Disableにされているから プログラムではなにもしなくて良い。)こうすることで、最悪 JTAG を使って復活できる。

      もうひとつの懸案は、クロック。AVR だとクロックの設定を失敗すると面倒なことになった。JTAG/ICSP では、クロックは関係無いようなことが書いてある。そうであれば、チップイレーズすることで、なんとでもなるのだろう。

    ... というわけで、ICSP を中心に検討した方が良さそうな感じ。

ライタの検討(2)

    ICSP を中心にするといっても、操作の基本は JTAG と変わらない。では、それはどういうものなのか?

     ・SetMode
     ・SendCommand
     ・XferData
     ・XferFastData
     ・XferInstruction

    これら 5 つのオペレーションを使うらしい。最後の XferInstruction は、基本操作ではなく、上の 4 つを使って実装する。で、名前から想像できるように 命令列を送り込んで実行させるもののようだ。

    どのような命令を送り込むのかについて、詳しく書いてある。また、PE -- Program Executive という プログラムを RAM に送りこんで 高度な操作をさせることも可能になっている。PE を使うやりかたは標準的で、pic32prog でも採用しているようだ。

    それは良いのだが ... こんな手続きだと 非常に面倒。自製の ツールに組み込むのは止めにして、pic32prog を改造する方針で検討する。

    pic32prog を使う利点はほかにもあった。
     ・ PIC32MX220F032B 製品ページ
    ここに、いろいろリンクがあるのだが、AN1388 -- PIC32 Bootloader の HID プロトコルに対応しているのだ。そうであれば、pic32prog に 機能をまとめてしまった方が便利だろう。

    さて、pic32prog だが、svn から チェックアウトしたものを
     ・ pic32prog-r51-20120714.tar.gz
    に置いた。

    これを見ると .. MPSSE を使うのに ftd2xx.dll を使っていない。なんと libusb を使って直接アクセスしている。これは GPL をクリアするためなのだろうか? ただ、遅いはずだし、ちょっと改造がやりにくい。といっても Synchronos BitBang Mode はモードが違うだけで、READ/WRITE を基本にするところは同じ。ICSP に対応するにしても MPSSE のコマンドが違うだけの話。なんとかなりそうな気はする。

    あと、対応している VID:PID が OLIMEX_ARM_USB_TINY/OLIMEX_ARM_USB_TINY_H だけになっている。ジェネリックな FTDI の ID に対応していない。

    行数は全部で、985 行。libftdi 相当が含まれていると思えば、かなり小さいともいえる。処理をちょっと見てみたのだが、

     ・ MPSSE としての機能しか使っていない。TCK/TMS/TDI/TDO のみの操作ということ。
     ・ mpsse_send / mpsse_recv が基本操作。MPSSE だと recv 不要の操作がある。ICSP の対応 や Synchrons BitBang では、必ず READ が入るから 少々対応が面倒。

割り込みアドレスの調査

    話は飛ぶのだが、ライブラリを作るうえで どんな割り込みアドレスがあるのか知っておかないといけない。ちょっと調べてみた。

    その前に メモリマップ

    kseg1 base Memory Map
    1xx/2xx 3xx/4xx 5xx/6xx/7xx
    Start Size Start Size Start Size
    Config BFC00BF0 10 BFC02FF0 10 BFC02FF0 10
    Boot BFC00000 BF0 BFC00000 2FF0 BFC00000 2FF0
    Flash BD000000 16KB - 128KB BD000000 BD000000
    RAM A0000000 4KB - 32KB A0000000 A0000000

    kseg1 内のメモリマップを作った。1xx/2xx 以外も調べてみたが、Boot 領域 のサイズが違う。Boot 領域の最後 16 バイトは、Config 領域で、AVR での ヒューズビット・ロックビットに相当する重要な情報が入っている。ここが 1xx/2xx でずれるというのは覚えておかないと。
    ただ、スタートアドレスはみな同じ。リンカースクリプトはかなり共通化できそう。

    さて、割り込みだが、

    0xBFC0_0000 Reset,Soft Reset,NMI
    0xBFC0_0200 EJTAG Debug (EJTAG ProbEn==1)
    0xBFC0_0380 all generic interrupt (BEV==1)
    0xBFC0_0480 EJTAG Debug (EJTAG ProbEn==0)

    0xBD00_0000 (EBase)
    0xBD00_0200 Single Vector Mode (BEV==0)
    0xBD00_0200 Multi Vector Mode (BEV==0)
    + vector_no * 32 * VS(VS==1)

    どうも ユーザコードにくる割り込みは、0xBD00_0200 から始まる割り込みベクタのみらしい。シングルベクタモードを使うと、たった一つ。

    あと、ブート領域になにかコードが入っていないと、ユーザコードは実行されない。それに 最初のエントリは、どこなのか? ブートローダによって決まるような?

追記、回路図を作ってみた



    PIC32MX2xx用基板』用の回路図の焼き直しなのだが、ユニバーサル基板とかで作るときの参考用として作った。



    いつも水晶の収まりが悪いわけだが、MA-506などはどうだろう?足を伸ばすことで、ブレッドボードなんかに挿せるような気がする。

    追記) ブレッドボードで 、ライタのテスト用配置を考えているのだが、なかなか厳しい。PIC32MX032B は、28 pin で 14 列を使う。予定している UM232H も 28 pin で これまた 14 列。そして、EIC-801 (BB-801) などは 30 列しかない。2 列しか 余裕がないのだ。で、この 2 列で入るものとして、リセットスイッチと DTC144 を考えている。

    o-------o
    | |
    | TACT |
    | -SW |
    --o---o---o o-------o
    |
    | o---o---o
    UM232H | |
    | x x | PIC23MX220F032B
    | |
    | o---o---o
    --o---o---o

    o o o DTC144
    IN OUT GND

    たぶん、こういう位置関係以外は無理。UM232H の重なっている部分は、AD6, AD7 で IN/OUT ともに接続する設定にすれば OK 。LED は、ストレートに 接続するしかない。そのためには、小基板を使ったりして 抵抗 + LED のパーツを作る必要がある。

    AE-UM232R だと 24 pin で 2 列増える。だが、DTC144 は重ねられないから 位置関係はあまり変わらない。

Arduino がライタになるらしい。

    ardupic32』というのを発見した。これは、JTAG で書き込む。

    遅いらしいのだが、ブートローダさえ書き込めさえすれば、あとは快適になるはず。幸いなことに、JTAG のピンは 5V トレラントになっているので、5V 版の Arduino が使える。

    同じようなものは、USB デバイスのコードが自由に書けるようになりさえすれば、作れるはずで いずれは検討したい。

追記 : BitBang で 書き込みが出来た。

    pic32prog を改造して、FT232R や FT232H で 書き込みできるようになった。(テストは、UM232H のみ)

    ものは、
    pic32prog-r62-ftdi-05.zip

    バイナリも同梱しているので、ftd2xx.dll を別途用意すれば使える。『pic32progの改造』の記事で説明はしているのだが、経緯込みで書いているので、イマイチわかりづらいと思う。いずれまとめの記事を起こしたいとは思う。

    もう少し詳しく書くと、Synchronous BitBang を使った CISP (外付けトランジスタが必要) と MPSSE での BitBang を使った CISP (結線のみ) の動作が確認できた。JTAG は、まだ動いていない。

関連記事
posted by すz at 20:51| Comment(0) | TrackBack(0) | PIC32MX

2012年07月07日

USBコントローラの設計(2)

前記事で、QFN32 の MachXO2-256 に入る USBコントローラを設計してみた。次は、これをどうやって使うのかソフト編に入りたい。... が結局どうなったのか混乱するので、仕様の整理から。(対応するのは、qfn32samples-12)

BUS版と SPI版

    HOST とのインターフェイスは 2 種類ある。LCD で良く使われる 8080 バス仕様と SPI 仕様。

    BUS 版。自作モジュールのピン配置を示す。

    // XO2-256 UDRV XO2-256
    // 27 DP 1 24 VCC
    // 28 DM 2 23 nBUSY 25
    // 29 nRD 3(TMS) 22 nCRC_ERROR 23
    // 30 nWR 4(TCK) 21 CLK_OUT 21
    // 32 DB0 5(TDI) 20 CLK_4X 20
    // 1 DB1 6(TDO) 19 nDATA_FULL 17
    // 4 DB2 7 18 nDATA_EMPTY 16
    // 5 DB3 8 17 N.C. (JTAGENB)
    // 8 DB4 9 16 nDATA_RDY 14
    // 9 DB5 10 15 nCS 13
    // 10 DB6 11 14 RS 12
    // GND 12 13 DB7 11

    (JTAGENBを除いて)1つのピンも余らない。コンフィグデータを書き込むときには、JTAGENB を H にして、JTAG ポートから書き込む。

    // _ ____________________________________ _____
    // RS _><____________________________________><_____ // // _____ _______ // nCS |________________________________| // // ______________ _____________ // nWR/nRD |_____________| // // WRITE: _________________ // DB[7:0] ----------------<_________________>--------
    // |---| CLK_4X (MAX)
    // |---------------------| CLK_4X x 2 (MIN)
    //
    // READ: _____________
    // DB[7:0] --------------<_____________>----------
    //
    // |-| gate level delay

    アクセスのタイミング。こうはしたが、RS は、nWR/nRD を L にしたときに確定していれば良い。nCS は、単なる出力コントロールなので、nRD と 同時に変化させても構わない。

    nWR は、普通 posedge で採取するものなのだが、内部クロックに同期して採取しているので無理。negedge から間をおいて採取になる。negedge から CLK_4X の 1T (Full-speed なら 20.8 ns) 以内に WRITE データを確定させないといけない。そこから 最小 1T 確定させておかなければならない。 Low-Speed なら 1T が 167 ns にもなる。注意が必要。

    RS は、A0 とも表記される レジスタ選択。L:データ/H:コントロール

    BUS 版を 一応は作ってみたが、実際に使うことは想定していない。

    実際に使うのは、SPI 版

    // XO2-256 UDRV SPI XO2-256
    // 27 DP 1 24 VCC
    // 28 DM 2 23 nBUSY 25
    // 29 nCS(2) 3(TMS) 22 nCRC_ERROR 23
    // 30 SCK(2) 4(TCK) 21 CLK_OUT 21
    // 32 MOSI(2) 5(TDI) 20 CLK_4X 20
    // 1 MISO(2) 6(TDO) 19 nDATA_FULL 17
    // 4 7 18 nDATA_EMPTY 16
    // 5 8 17 N.C. (JTAGENB)
    // 8 9 16 nDATA_RDY 14
    // 9 MISO 10 15 nCS 13
    // 10 MOSI 11 14 RS 12
    // GND 12 13 SCK 11

    ピン配置は、こうした。(2) と書いてあるのは、第二の選択。

    JTAG のポートを空けるならば、オリジナルのピン配置にする。ただ、SPI を通してコンフィグもやりたい(できる)なら、(2) の方が便利。

    SPI自体のビットレートには制限はあまりない。FIFO とのハンドシェークが制限になるだけ。SPI では、FIFO を CLK (12MHz) で駆動している。ハンドシェークに 4 CLK かけるとすると 24 MHz が上限。FIFO のクロックを CLK_4X にすれば、96 MHz までいく。(FPGA の SCK の上限はもっと高い)

共通のピンの説明

    DP/DM

      USB の D+/D- 。Low-Speed デバイスは、DMを 1.5 K でプルアップする。Full-Speed デバイスは DP の方。FPGA の ピン設定では、抵抗値が高く役に立たないので注意。

      HOST 向けでは、両方をプルダウンする。こちらは、FPGA の ピン設定で良い。現状では、Low-Speed/Full-Speed の両方には対応できない。

    CLK_4X/CLK_OUT

      Full-Speed では、48 MHz , Low-Speed では 6MHz を CLK_4X に入力する。CLK_OUT は、反転出力で水晶を直付け可能なように配慮。

    nDATA_RDY

      受信データがあるときに L になる。受信ハンドシェーク用で、割り込み線として使う。

    nBUSY/nCRC_ERROR/nDATA_FULL/nDATA_EMPTY

      RS=1 で READ すれば、同じ情報が読めるので必須ではない。特に nDATA_EMPTY 。nDATA_RDY と論理とタイミングが違うだけなので、信号すら不要。これらは、LED とかでのモニタ用として一応 残してある。

      信号自体は、後で説明。

define と parameter

    変更可能だと思っているものについて説明しておく。

    (define) USE_SPI

    SPI にするか BUS にするかの選択。( USE_BUS は常に define で構わない )

    (parameter) SUPPORT_CRC5S=1

    デバイスでは、CRC-5 を使う トークンパケットを出さないので、CRC-5 生成は不要。
    SUPPORT_CRC5S=0 とすると僅かに規模が減る。

    Full-Speed 用
    (parameter) FS=1
    (parameter) FIFO_DEPTH = 5

    Low-Speed 用
    (parameter) FS=0
    (parameter) FIFO_DEPTH = 4

    Low-Speed では、SOP のみの送信をサポートする。規模が増えるので、FIFO を減らすことで対処する。

    Full-Speed 用の FIFO は 32 バイト。最大パケットサイズ 32 を想定しているが、MCU 側の 応答時間+転送速度によっては、もっと大きなパケットにも対応可能。逆に 32バイトのパケット全部は FIFO に入らない。送信時にちょっと細工がいる。

データ送受信

    BUS 版では、

      RS=0 としてデータを書けば FIFO に入り、読めば FIFO のデータを読める。

      FIFO が溢れれば nDATA_FULL が L になり、FIFO をリセットするまで FULL 状態が維持される。
      データがないのに読めば、前のデータが読める。nDATA_RDY を見てのハンドシェークが必要。

    SPI では、

      (送受信モード = 0 のとき) RS=0 として データを書けば FIFO に入る。DATA_FULL の動作は同じ。
      (送受信モード = 1 のとき) RS=0 として データを読めば FIFO のデータを読める。nDATA_RDY との関係は同じ。

    送受信モード, FIFO リセット については、次で説明する。

コントロールレジスタ

    RS=0 にして書き込むことで 以下の状態を制御する。

      0x80 送受信モードを 0 (受信モード)
      0x82 送受信モードを 1 (送信モード)
      0x81 送信要求を 1 (送信モード+送信スタート)

    RS=0 にして読み込むと以下の状態が読める。

      bit7: CRC_ERROR
      bit6: DATA_FULL
      bit5: DATA_EMPTY
      bit4: DATA_RDY
      bit3: RECV_EN (受信中)
      bit2: SEND_EN (送信中)
      bit1: 送受信モード (0: 送信 / 1: 受信)
       bit0: 送信要求 (1: スタート / 0: 完了)

    送信では、送受信モード=0 にしてから、FIFO にデータを送る。ある程度送ったら 送信要求=1 。残りのデータを FIFO にデータを送る。
    FIFO のサイズを大きく超えるデータ量だと、この制御は難しい。規模に余裕があるので、DATA_EMPTY をフロー制御用の信号に置き換えるべき。


      assign I_DATA_EMPTY = (i_addr == o_addr);

      wire [DEPTH-2:0] data_len = (i_addr[DEPTH-1:1] - o_addr[DEPTH-1:1]);
      assign I_DATA_EMPTY = (data_len < 2**(DEPTH-2) );

      誤差 -1 だから 引き算の結果は、±1 。FIFO のデータ量が
      0 〜 14 なら 1
      (15 〜 16 なら どちらになるか分からない)
      17 〜 31 なら 0

      という制御なら入った。後日変更しよう。

    (まとめ終わり・本題が続く)

V-USB

    V-USB は、AVR のみで Low-Speed デバイスにするドライバで、商業利用でないなら、GPL v2 のもとに利用できる。(USB の ID を利用するのは、また別)

    AVR のみで動くのならば、必要な処理すべてが入っているわけだ。一部置き換えることで、作ったコントローラ用になるはず。

    一体どこをどう変えるのか? ... 目星は付けた。

    V-USB は、アセンブラと C に分かれている。インターフェイスを見たがどうも、アセンブラを置き換えるだけで済むようだ。C を変更することになったとしても変更は僅かになりそう。

    コンフィグは、実にややこしくなっている。ここは変更すべきところが出てくる。

    まずは、この方針で V-USB というものを理解する。

V-USB のアセンブラコード・インターフェイス(1)

    udbdrv.c: usbPoll() をまず見よう。受信後どんなデータを見ているのかが分かる。

    uchar usbRxToken;
    uchar usbCurrentTok;
    volatile schar usbRxLen;
    uchar usbInputBufOffset;
    uchar usbRxBuf[2*USB_BUFSIZE];
    :
    len = usbRxLen - 3;
    if(len >= 0){
    usbProcessRx(usbRxBuf + USB_BUFSIZE + 1 - usbInputBufOffset, len);
    usbRxLen = 0;
    }

    usbProcessRx:
    if(usbRxToken < 0x10){
    usbFunctionWriteOut(data, len);
    return;
    }
    if(usbRxToken == (uchar)USBPID_SETUP){
    :
    if(type != USBRQ_TYPE_STANDARD){
    replyLen = usbFunctionSetup(data);
    }else{
    :

    大胆にロジックを削った。

    受信データがある場合、usbRxLen に PID , CRC-16 を含めたデータサイズが格納される。
    ただし、0 オフセットではなく、USB_BUFSIZE - usbInputBufOffset から格納。

    受信データとは何かというと DATA0/1 パケット。このパケットの前には、SETUP か OUT のトークンが来ている。それは、usbRxToken に格納され、後で処理の切り分けに使う。OUT でのエンドポイント番号は、usbCurrentTok に格納されるらしい。

    まずは、ここまで。

    どうも usbRxLen がカナメらしい。これをセットすることで上位レイヤーの受信処理が動き出す。

    自作 USBコントローラ(以下 udrv) で対応させる場合、ルールさえ守れば後は自由にして良さそうだ。

    まず、どの契機で動かすか ... Full-Speed では、次々にデータが来るから nDATA_RDY=L を割り込みの契機にする。一度フレームが来たら、1 フレーム分 全部処理し終わるまで 割り込みから抜けない。というもので良さそう。

      ただ、Low-Speed でそれでは面白くない。余裕があるからだ。受信完了を契機にしたいところ。nBUSY は、送信完了で H になるのだが、L になるようセットしておけば、受信完了でも H になる。

      Full-Speed では、FIFO が溢れる恐れがあるし、連続受信で、L になるようセットするのが間に合わないケースが出てくる。Low-Speed では問題ないようにできるはず。

      Full-Speed でも、FIFO が 半分を超えるか、受信完了で 割り込むという手がある。送信の場合は、FIFO が 半分を切れば追加のデータを送るのが良さそう。送信完了自体は検出する必要はない。そういう信号線(IRQ)が作れないか検討してみよう。

      DATA_EMPTY はなくしても良いし、nCRC_ERROR と nDATA_FULL はエラーが起きたという意味にして 1 つにまとめても良いし、なくしてさえ良い。nBUSY は動いているのを目視したいから残したいが、送信中+受信中に変えたい。

      //assign nBUSY = r_start;
      assign nBUSY = ~(RECV_EN | SEND_EN);
      assign nIRQ = ~r_irq;
      always @(posedge CLK)
      begin
      r_irq &= ~UNDER_HALF ^ r_start ^ i_recv_mode;
      end

      こんな風に変えるのなら可能だった。(UNDER_HALF は、変更後の I_DATA_EMPTY )

      送信モードでは、nIRQ の 最初の状態は H 。
      r_start == 0 すなわち 送信データ詰め込み中ならば、半分埋まったところで L になる。
      r_start = 1 すなわち 送信をスタートさせると nIRQ は反転。
      半分以上埋めているなら、半分を切れば L になる。(半分以下なら、r_start=1 で L )
      送信完了後は、受信モードになる。最初は 空 だから L のまま。
      ここで r_start = 1にすると H に反転。
      受信データが半分を超えるか、受信が完了すれば L になる。

      受信完了後、自動で r_start = 1にすることは可能だった。

      if (r_se0)
      begin
      `ifdef AUTO_RECV_AFTER_SEND
      if (~r_recv_mode) r_recv_mode <= 1;
      `endif
      r_start <= 1'b0;
      end
      else if (~r_recv_en1 & r_recv_en)
      r_start <= 1'b1;

      上記(2ヶ所ある)の最後の else が追加分。

      そこまでやると、H → L の変化だけが重要になる。送信完了後にやるべきことはないのだ。

        送信で重要なのは、途切れずにデータを送り込むこと。
        半分を切れば割り込みが起きるから、16 バイト+αを送り込む。送り込む速度が 12Mbps よりかなり速くないと 64 バイトのパケット送信は無理だが。送り込んでしまえば、あとは受信待ち。

        受信で重要なのは、FIFO を溢れさせないことと 確実な 受信完了の受け取り。両方 H → L 。

    さて、受信データは、usbRxBuf に貯めこんでいく。usbRxLen が 0 になっていたら、貯めるインデックスを 0 にする。負の値にならないようにするなら、最大パケット長に 128 以上は取れない。Full-speed でも 64 が精々。

    受信データの前に SETUP か OUT が来ている。これは、PID を usbRxToken に格納して、廃棄? -- データをそのまま残しても良いかも知れない。

V-USB のアセンブラコード・インターフェイス(2)

    1 パケットの受信完了で何をしているか ... アセンブラを眺めてみると

    受け取ったパケットのPID によって次の処理を行う。

      DATA0/1 の場合 : handleData
      ( usbDeviceAddr を見て チェック : ignorePacket )
      IN の場合 : handleIn
      OUT の場合 : handleSetupOrOut
      SETUP の場合 : handleSetupOrOut
      どれでもない場合 : ignorePacket

    handleSetupOrOut では、一旦リターンする。(思っていたのと違った)

    handleIn は、送るべきデータが用意されていなければ NAK を送ってリターン
    ある場合は、usbSendAndReti これが送信ルーチンだが、終了後 usbNewDeviceAddr を見て usbDeviceAddr を更新している。

    udrv の場合、usbSendAndReti に相当する部分は、かなり簡略化できる。データをそのまま送れば良いからだ。

    どうすれば良いか大分イメージできた。次は、送信データを受け取るところを見てみよう。あと、ハンドシェーク。ACK/NAK/STALL をどういう場合に送っているか(あるいは送らないか)もチェックしよう。

V-USB のアセンブラコード・インターフェイス(3)

    typedef struct usbTxStatus{
    volatile uchar len;
    uchar buffer[USB_BUFSIZE];
    }usbTxStatus_t;

    extern usbTxStatus_t usbTxStatus1, usbTxStatus3;
    #define usbTxLen1 usbTxStatus1.len
    #define usbTxBuf1 usbTxStatus1.buffer
    #define usbTxLen3 usbTxStatus3.len
    #define usbTxBuf3 usbTxStatus3.buffer

    volatile uchar usbTxLen = USBPID_NAK;
    uchar usbTxBuf[USB_BUFSIZE];

    usbBuildTxBlock:

    usbTxBuf[0] ^= USBPID_DATA0 ^ USBPID_DATA1; /* DATA toggling */
    len = usbDeviceRead(usbTxBuf + 1, wantLen);
    if(len <= 8){
    usbCrc16Append(&usbTxBuf[1], len);
    len += 4; /* length including sync byte */
    }else{
    len = USBPID_STALL;
    }
    usbTxLen = len;

    usbDeviceRead:
    if(usbMsgFlags & USB_FLG_USE_USER_RW){
    len = usbFunctionRead(data, len);
    }else{
    if(usbMsgFlags & USB_FLG_MSGPTR_IS_ROM){ /* ROM data */
    :
    }else{ /* RAM data */
    :
    usbSetInterrupt:
    usbGenericSetInterrupt(data, len, &usbTxStatus1);

    usbSetInterrupt3:
    usbGenericSetInterrupt(data, len, &usbTxStatus3);

    とりあえず、送信データは、3 種類ある。usbTxBuf と usbTxBuf1/usbTxBuf3。 これらのフォーマットはすべて同じで、PID と CRC-16 を含むデータ。usbTxLen には、NAK , STALL などの PID が入る場合がある。 そうでない場合は、SYNC の分を含む 1 多い値。

    IN がきた時 endpoint が 0 なら usbTxBuf を使う。USB_CFG_EP3_NUMBER に一致すれば usbTxBuf3 , それ以外なら usbTxBuf1を使う。

    udrv に対応させる場合、CRC-16 は余計なのだが、インターフェイスを変えない。内部で -2 すれば良いだけの話だからだ。オーバヘッドが大きければ、CRC-16 の計算を止める。

    こうしておくと udrv の送信で CRC-16 の計算をサポートしないという選択枝が出来る。

    ついでに気がついたのでメモ: データサイズが 8 バイト以内だという のがハードコーディングされている所がある。ここは、Full-Speed 対応で変更しないと。

パケットのフロー制御

    パケットが来たら ACK/NAK/STALL のどれかを送ったり、あるいは送らないという処理が必要。

    いったい V-USB ではどうしているのだろう。

    まずは、予備知識

      HOST | device | HOST
      ---------------+--------------------------+----------
      SETUP DATA0 | ACK/(none) |
      | |
      OUT DATA0/1 | ACK/NAK/STALL/(none) |
      | |
      IN | DATA0/1 or NAK/STALL | ACK/(none)

    代表的なのは、仕様上こういうことらしい。だが、実際のコードはもっと細い。どういう風になっているか まとめておかないと、コードが作れないのだ。

    SETUP/OUT の後 の DATA0/1
    NAK : (usbRxLun != 0) のとき 受信バッファが空いていないので受け取らない。
    ACK : 0 バイト受信のとき 、受信バッファの状態を変更せず ACK
    STALL : 該当ケースなし。
    (none) : 前が SETUP/OUT でないとき
    IN
    NAK : (usbRxLun != 0) のとき 受信バッファが空いていないので送らない。
    ( すぐに ACK が来るが受け取れない )
    送信するデータがないとき。
    上位から指定されたとき
    STALL : 上位から指定されたとき

    (その他)
    SETUP/OUT (none)
    受信バッファオーバーフロー (none)
    想定外のトークン / エンドポイント 範囲外 (none)

    どうも、これだけのようだ。CRC エラーはないが、たぶん想定外と同じ。

    受信バッファが空いていないので受け取らない。というのは、若干面倒。FIFO には受け取った上で NAK を送信すれば良いのだが、受信完了を待つのが ちょっと。

    IN での NAK は、自力では、思いつかなさそう。
    あと、SETUP の後、NAK は送らないのだが ... 送るケースがあるような ...これは、そういうことがないようにしている?

    usbCurrentTok にストアするタイミング

    SETUP/OUT (none)
    受信バッファオーバーフロー (none)
    想定外のトークン / エンドポイント 範囲外 (none)

    これらは、ACK/NAK を返さないだけでなく、usbCurrentTok を更新している。ちなみに DATA0/1 の後の (none) では、更新しない。わざわざそうしているから、理由があるのかも。

ドライバーの設計

    ようやく本題に入る。ドライバーをどうやって作るのか検討を始めよう。

    その前に 仕様が大分変わった。
     ・ udrv-13-spec.txt
    変更案をいろいろ書いたが、このスペックをもとにする。

    ISR(INT0_vect) {
    static uint8_t recv_offset = 0;
    static int8_t send_len = 0;
    static uint8_t *send_ptr;
    uint8_t i;

    まずは、C で全部記述することにする。AVR だとこんな感じで割り込みを定義できる。
    初期化で、negedge nIRQ で INT0割り込みが 起きるようにしておく。

    if (send_len > 0) {
    :
    return;
    }

    送信で、送りそこなった分があれば送るコードが必要かも知れない。割り込みは、FIFO が半分になったとき起きる。ただ、SPI が十分速くないと最大パケットサイズを大きくできない。遅いと 最大パケットサイズ を 32 にせざるを得ないが、そうなると、送信をキックするところで全部送れてしまう。たぶん必要ないので、これは後回しにしよう。

    次、受信だが、割り込みは、受信完了か FIFO が半分を超えたところで起きる。

    if (bit_is_clear(nDATA_RDY_PIN, nDATA_RDY_BIT)) {
    re_recv:
    do {
    if (recv_offset < USB_BUFSIZE) {
    usbRxBuf[recv_offset++] = spi_read();
    } else
    spi_read();
    } while (bit_is_clear(nDATA_RDY_PIN, nDATA_RDY_BIT));
    }
    i = ctrl_write(CTRL_READ_STATUS);
    if (i & ST_BUSY) return; // RECV in progress
    if (i & ST_DATA_RDY) goto re_recv;
    if (i & ST_CRC_ERROR) {
    recv_offset = 0;
    return;
    }
    if (i & ST_DATA_FULL) {
    recv_offset = 0;
    ctrl_write(CTRL_SEND_MODE);
    ctrl_write(CTRL_RECV_MODE);
    return;
    }

    nDATA_RDYが L で buffer に入る限りは、とにかく入れる。H になれば、FIFO が空ということ。ループを抜けてチェックにはいる。ctrl_write() でステータスが読めるとしておく。

    ST_BUSY なら、まだ受信が続いているということ。割り込みを一旦抜けて、次の割り込みを待つ。
    受信が終了したとしても、僅かな期間で 最後のバイトが受信されたかも知れない。ST_DATA_RDY ならば、もう一度 読み込む。

    エラーが起きたら、無視することになっている。ST_DATA_FULLだと、一旦 送信モードにしないとステータスが消えない。

    ここで バッファに受信データが入った。次はパケットの解析。

    usbRxToken = usbRxBuf[0];
    if ((usbRxToken == USBPID_SETUP) | (usbRxToken == USBPID_OUT)) {
    if ( recv_offset < 4 ) {
    if (usbRxLen) recv_offset = USB_BUFSIZE + 1;
    return; // RECV DATA0/1
    }
    if (recv_offset == USB_BUFSIZE + 1) { // buffer busy
    recv_offset = 0;
    goto send_nak;
    }
    if (recv_offset == USB_BUFSIZE ) { // buffer overrun
    recv_offset = 0;
    return;
    }
    // data top : USB_BUFSIZE - usbInputBufOffset
    usbInputBufOffset = USB_BUFSIZE - 3;
    usbRxLen = recv_offset - 3;
    recv_offset = 0;
    send_handshake(USBPID_ACK);
    return;

    SETUP か OUT の場合、次に DATA0/1 パケットが来る。続けて 読み込んで バッファにくっつけてしまう。ただし、usbRxLen が 0 でなければ、受け取った後、NAK を送る。recv_offset = USB_BUFSIZE + 1 として後で分かるようにしておく。DATA0/1 パケットを受け取った後、バッファが溢れていたら NAK は送らない。

    このコードで DATA0/1 トークンのチェックはしていない。後で入れておく必要がある。
    あと、RxLen が 0 でないときでも、DATA パケットが開始されるまでに SETUP/OUT を受け取らないと、DATA パケットを受け取ってしまう。ここは直すの面倒。

    最後に、usbRxLen をセットして、ACK を送り割り込みを抜ける。

    IN が来たら送信。usbRxLen が 0 以外なら NAK を送るのは、SETUP/OUT と同じ。アドレスが違うときも NAK 。
    次に エンドポイント番号によって、送信バッファを選択。send_len には、SYNC と CRC-16が含まれるので udrv では、-3 する。これで送信の準備は出来た。ちなみに、これ以外のパケットが来ても何もしない。

    } else if ( usbRxToken == USBPID_IN ) {
    if ( usbRxLen < 0 ) goto send_nak;
    i = (usbRxBuf[1] >> 1);
    if (i & (i != usbDeviceAddr)) goto send_nak;
    i = (usbRxBuf[1] << 3) & 0x08 | (usbRxBuf[2] >> 5) & 0x07;
    if (i == 0) {
    send_len = usbTxLen;
    send_ptr = usbTxBuf;
    usbTxLen = USBPID_NAK;
    } else if ( i == USB_CFG_EP3_NUMBER) {
    send_len = usbTxLen3;
    send_ptr = usbTxBuf3;
    usbTxLen3 = USBPID_NAK;
    } else {
    send_len = usbTxLen1;
    send_ptr = usbTxBuf1;
    usbTxLen1 = USBPID_NAK;
    }
    if (send_len > 3) send_len -= 3; // remove SYNC , CRC-16
    usbDeviceAddr = usbNewDeviceAddr;
    (送信処理)
    } else {
    recv_offset = 0;
    return;
    }

    いよいよ送信にはいる。まず、send_len が負の場合 ACK などのハンドシェークの PID を送る仕様。0 なら、送るものがないので NAK 。
    いよいよ送信だが、まずは、送信モードにする。最初は FIFO が空で nIRQ は H 。詰めていって 半分を超えると L になる。ここまで詰めたら送信スタート。半分に満たない場合は、スタートと共に nIRQ が L になる。

    割り込み要求ビットが立っているので、送信スタートの直後にクリア。次にまだまだ詰められるので 20 を上限に詰める。
    最後に 全部詰められたら、割り込み要求ビットをクリア。

    if (send_len < 0) {
    send_handshake(send_len);
    send_len = 0;
    return;
    }
    if (send_len == 0) goto send_nak;
    ctrl_write(CTRL_SEND_MODE);
    bit_clear(nCS_PORT, nCS_BIT);
    do {
    spi_write(*send_ptr++);
    while ( (--send_len) && bit_is_set(nIRQ_PIN, nIRQ_BIT) );
    while (spi_busy())
    ;
    bit_set(nCS_PORT, nCS_BIT);
    ctrl_write(CTRL_SEND_START);
    bit_set(EIFR, INTF0); // clear INT0 req
    re_send:
    if (send_len > 0) {
    bit_clear(nCS_PORT, nCS_BIT);
    i = 20;
    if (send_len < i) i = send_len + 1;
    while (--i) {
    spi_write(*send_ptr++);
    --send_len;
    }
    while (spi_busy())
    ;
    }
    bit_set(nCS_PORT, nCS_BIT);
    if (send_len == 0)
    bit_set(EIFR, INTF0); // clear INT0 req
    return;

    だいたいこんな感じだが、送信はちょっとグダグダ。SPI が遅ければどうせ全部詰めることになるのだ。nIRQ など見ないで 24 など 規定数詰めたらスタート 。残りを順次詰めて、最後に 割り込み要求ビットをクリアで良いと思う。

    まぁ AVR 以外も想定してロジックを考えてみたのだが、24MHz 以上とかの 高速なSPI とかじゃないと意味なさそう。... Low-Speed を忘れていたが、こちらは最大パケット長が 8 なので余裕。全部詰めてスタートで良い。

追記: バグがいくつか

    送信で、NAK などハンドシェークパケットを送る際に Length の方にセットする仕様なのだが、変更する必要があった。
    ハンドシェーク の PID は以下のとおり

    /* handshake */
    #define USBPID_ACK 0xd2 /* 1101 0010 */
    #define USBPID_NAK 0x5a /* 0101 1010 */
    #define USBPID_STALL 0x1e /* 0001 1110 */

    Low-Speed では、DATA パケットを送る場合 の Length の最大値は 12 なのだ。V-USB では、Length の bit4 が 1 だと ハンドシェークと判断する。この仕様だと 32 バイトのパケットサイズすら対応できない。

    PID の bit0 が 0 なのを利用して、0x80 | (PID >> 1) とすることにした。

    ただ、こうやって仕様変更すると usbdrv.c の方を結構修正しないといけない。10 ヶ所ぐらいある。あと usbTxLen & 0x10 などとして判断しているところもあってこれも 0x80 に変更しないといけない。

    次に受信のほう。パケットを受けられるだけ受けるというのは、どうも無理がありそうだ。

      SOF → SETUP → DATA0

    例えばこんな風にパケットが来たらくっつけてしまうやりかただと対応が面倒。

    どうも 受信中にパケットの切れ目を判断してパケット毎に処理しないとダメそうだ。判断は簡単だが、ループの内側に if 分が 2 つほど入ることになる。ちょっと面白くないが、とりあえずは、やむを得ないということにしよう。

    IN (device → host) のデータ再送未対応。

    V-USB も分かっていながら未対応にしている。作っているのは、サイズの制限など気にしてないので、対応させておきたい。

    バスリセット 未対応

    ホストがバスリセットすると、たぶんおかしくなる。

ACK や NAK を返すタイミングについて

    どうも気になって ググってみると ... ACK や NAK を返すタイミングは相当にタイトらしい。Low-Speed で 18 クロックだとか。( USB の仕様書を見てみたが、 Full-Speed も同じだった)

    いつまでも待っていたら HOST が次を始められない。それはそうだ。

    データがぶつかるのは、IN の後 デバイスから送られる DATA0/1 と NAK/STALL ハンドシェーク。HOST が DATA0/1 を送った後の デバイスから送られる ACK/NAK/STALL ハンドシェーク。

    ... となると いままで作ってきた udrv の仕様では全然ダメだ。全然間に合わない。変更するにも規模が足りない。

    DATA0/1 を受けとれない状態なら NAK か STALL。受け取った後 エラーが起きなければ ACK 。こういうものだけなら、規模がもうすこしあれば行けそうな気がする。だが、DATA を送信するのは一体どうするのだろう?最初からデータを入れとかないと無理なのでは?

      あと、Low-Speed 専用ならどうだろう? 1.5 MHz で 18 clk ということは、20 MHz で 240 clk 。受信完了し続いての動作だから、可能なやりかたはありそうだ。

      だが、12MHz で 18clk は、20 MHz で 30 clk 。SPI では 1バイトを送る時間もなさそう。これは諦める。対応する余裕はない。

      Full-Speed device に対応するには、エンドポイント毎の バッファを持ったモジュールを MCU の代わりに入れて、高レベルのコントローラにするしかないような気がする。いま作っているのは、単なる物理層だから無駄にはならない。だが、高レベルのコントローラまで設計するつもりは今はない。

      ただ、Low-Speed でも 受信完了してから 受信データを受け取り ACK を返すのでは遅すぎる。受信したら即受け取りでないと ダメかも知れない。

    逆に HOST 専用ならどうだろう? HOST が制御しているのだから、送信同士がぶつからないように制御はできる。USB の仕様書でも、送信同士がぶつかるケースについてだけ言及しているように見える。

    ところで、device 側から見ると、ACK が来なかったら、(たぶん)次の要求は再送。このあたり、V-USB ではどうなっているのだろう? 気になってきた。

      ; Comment about when to set usbTxLen to USBPID_NAK:
      ; We should set it back when we receive the ACK from the host. This would
      ; be simple to implement: One static variable which stores whether the last
      ; tx was for endpoint 0 or 1 and a compare in the receiver to distinguish the
      ; ACK. However, we set it back immediately when we send the package,
      ; assuming that no error occurs and the host sends an ACK. We save one byte
      ; RAM this way and avoid potential problems with endless retries. The rest of
      ; the driver assumes error-free transfers anyway.

    なんかわざわざコメントが書いてあった。やっぱり 送信後 バッファを捨ててしまうのはまずそうな ... たぶん ACK が来たときに usbTxLen を更新してバッファを捨てれば良い。

    さて、device では タイムアウト が必要ということではなさそうだ。デバイス依存になるかも知れないが、Full-Speed の HOST は可能かも知れない。

ホストの検討

    なかなか問題が多そうだが、デバイス側はどうすれば良いか分かってきた。では、ホストはどうしたら良いのだろう。これも問題がありそうだが、強引に検討だけはしておこう。

      実現できたとしても、実用上の利点というのは、ほとんどなさそう。デバイス側が USB の勉強になったように、これもまた勉強。

      作るためには、ホストの制御を基本から理解する必要がある。逆にこれが使えるぐらいの知識が出来たら、他の OTG 付き MCU で制御できるようになるはず。V-USB の HOST 版というものもたぶん作れるようになる。

      OTG 付きの MCU というと、秋月の PIC32MX220F032B がわずか 220 円で手に入るようになった。これを使いこなせば実にいろいろできそうな気がする。

    さて、HOST にしてなにを制御するのか? 汎用的なものは一切考えない。それは PC でやるべきことだ。一応電子工作として テーマを 2 つ

      (1) キーボード や マウスの制御

      これができるようになると、PS/2 変換器が作れそう。ただ、V-USB のような 直接制御が可能そうなので、実際には udrv で作る必要はないだろう。あと、HUB には対応するのは、面倒そうだが ... いったいどうするのか 後回しにするがいずれ検討したい。

      (2) BlueTooth ドングル

      USB BlueTooth ドングルは、妙に安いので これが作れると嬉しいような気がする。実際に作るなら、PIC32MX220F032B で変換器を作るのが最も安くあがりそうだが ... まずは udrv で検討する。

      はたして、可能なのかどうか? 最初の壁はパケットサイズ。MaxPS=64 みたいなので、結構厳しいものがある。

    目標はできたが、どのように設計するのかについて条件がある。まずはそこから。

      (1) V-USB のアセンブラコードを最小限度の変更で利用できるようなインターフェイスにする。

      Low-Speed の HOST なら ピンを直接制御しても 作れそう。ただ、送信・受信をするコードを 一から作れるような気はしないので、V-USB のコードを流用するにはどうしたら良いかについて、最初から考慮しておく。

      (2) PIC32MX220F032B が最終ターゲット

      AVR にも HOST 付きのものはあるが、どうも 最近は device only のものばかりになっている。XMEGA にも USB 付きが出てきているが device ばかり。

      まだ、AVR を中心に使おうとは思っているのだが、PIC32MX を専用IC にしてしまうような使い方なら 使ってみたいとは思っている。なにしろ値段が安い。220 円ならホストインターフェイスを買うより安くなってしまう。

      (3) MicroChip の USB Framework は使わない。

      これを使うと MicroChip 以外のものを扱うとき困ってしまう。API も合わせない。作りたいのは、上記の 2 つぐらいだし、理解するために試しに作ってみるだけなので、いきなり高い完成度のものは狙わない。

      もっと言うと、MicroChip が出している開発ツールを一切使わないで開発できるようにしたい。なかなか厳しそうではあるが、使いたい MIPS チップは他にもある。ちなみに、書き込みは、 OpenOCD が既に対応しているそうだ。問題なのはライブラリだが、(他のMIPSチップ向けだが)作りかけたものがある。

    こういう条件で作り出すのは良いのだが、さてどうしよう。

    最初に PIC32MXの USB インターフェイスがどういうものなのか、あたりを付けないといけないだろう。
     ・ 秋月の PIC32MX220F032B
    ここにデータシートがあるので 見てみる。で、レジスタの名前 例えば U1OTGIR で検索すると ... PIC24F の 日本語ドキュメント がヒットする。

    構成図を見ると、PIC32MX と PIC24F の USB モジュールは実に似ている。どうもほとんど同じようだ。なら、調査は、PIC24Fの方で済ますことにする。

    つらつらと眺めてみたが、HOST の場合、それほど高機能ではなさそう。エンドポイントも 1 つだけを使う。なら udrv と大差ないんじゃないかと言う気がしてきた。udrv 向けに普通に作れば対応可能だろう。

      あと気になったのは、U1SOF レジスタ。HOST のみの機能で、送信同士のぶつかりを防ぐためのタイムアウト値を格納する。最大値は 255 。なるほど、変更できるかどうか別にして、HOST では、タイムアウトが必ずあるものだと思った方が良さそうだ。逆に、デバイスで ACK 待ちのタイムアウト値はレジスタとしてはない。デバイスでは、タイムアウトという概念はなくても良いのかも知れない。

    次に、HOST がどうやって送信するのか? V-USB の device のコードは、見てきたわけだが、HOST から来る 指令に対応するだけのものなのだ。だからこの知識だけでは、HOST が どのような送信の仕方をするのかは全然分からない。

    ぐぐってみると PICFUN さんのところの、
     ・ http://www.picfun.com/usb03.html
    が引っかかった。随分前にも見たことがあるのだが、理解する必要もなかったので忘れていた。

       ・ 概念的には、SOF で始まるフレームがある。
       ・ フレームのなかには、SETUP - DATA0 - ACK とか IN - DATA0/1 - ACK とか一連の シーケンスからなる トランザクションがある。
       ・ バルク転送というのは、本来 フレーム内で必要なトランザクションを行ったあと、余った帯域で 行うもの。(優先度が低い)
       ・ インタラプト転送というのは、優先度が高いが、頻繁に行うものではない。
       ・ フレームについて概念的にと書いたのは、デバイスから見ればフレームがあってもなくても処理に大差ないため。今まで作ったもののレベルだと、リセットするのを防ぐとかその程度の意味しかないような感じ。

    今の理解はこんな感じ。分からないのは、

       ・ Low-Speed では、EOP のみを送るルールがある。
       ・ そうなるとフレームはなくても良さそうなもの。
       ・ SOF は送らなくとも良いのか or 送ってはダメなのか ?

ホストの設計

    随分前置きが長くなった。さぁ、設計をしてみよう。

    Low-Speed では、必ずしもフレームを構成しなくても良さそうなのだが、定期的に EOP を送らなければならない。そこから考えると、1ms 周期のタイマ割り込みを作りそこから、一連の 送受信処理につなげていく 構造が良さそうだ。

    Full-Speed では、最初に SOF を送る。Low-Speed では、SOF は送らない? 結局送信すべきものがないと EOP だけ送る。

    トランザクションの途中では、かならず受信待ちが入るわけだが、ここで割り込みを一旦抜ける。返ってこなかったら、次の 1ms のタイマ割り込みで タイムアウトの処理を行うことにする。これで良いのか? という気もするのだが、動作ポイントは、タイマ割り込みと 受信割り込みということで進めよう。

    次にバッファの受け渡し。受信待ちに入れば、タスクの方が動作可能になる。まず送信について考えてみると、そこで 次のデータを準備したりできるわけだ。だが、ACK が返らない限り バッファが開放できない。送信では(少なくとも)ダブルバッファが必要そうに思える。

    そもそも 1 フレームで 1 つしか送信できないとすると、MaxPS=64 でも 64KB/sec にしかならない。BlueTooth のことを考えると これじゃ帯域が低すぎるような気がする。ダブルバッファでは足りず FIFO にしないといけないかも知れないが、まずは ダブルバッファということにしておこう。

    では受信ではどうなのか? データを受け取り ACK を返せば受信成功だ。で、すぐに次の受信待ちになる。このときに、タスクで受信処理をできるかも知れない。やっぱりダブルバッファは必要そう。

    バッファの構造はどうしよう。

      V-USB では、受信バッファに 全部収めて、さらに usbRxLen / usbRxToken とかの情報を設定するルールだった。ここは、ダブルバッファにするが、同じようなものにすればよさそうだ。

      送信は、3 つのバッファがあって、IN を受信すると エンドポイントに対応したバッファを選んで送信する仕組みだった。送るデータは ACK/NAK などを送るか、バッファに格納されたデータを送るかの 2 種類。

      HOST の場合、ACK を 受信完了で送ることはあるが、NAK/STALL を送信することはない。そのかわり SETUP/OUT に続いて DATA0/1 を送ることになる。また、IN/OUT/SETUP のトークンを送るには、アドレスとエンドポイントの情報が必要。ついでに CRC-5 を生成しないといけなくなった。

    V-USB の送信コードを有効に使うならば、Length とバッファのアドレスを指定して送信する機能と ACK を送信する機能を使うことになる。どちらも 割り込みから抜けてしまうことになっているが、そこは 戻ってきてもらわないと困る。

    あと、SETUP/OUT + DATA で 送信バッファを 2 つ使うのではバッファの効率が悪い。1 つのバッファに詰めることにする。トークンより大きいデータが入っていたら 2 回に分けて送信するようなイメージ。call したい ところは 2 ヵ所だが、サブルーチンにする必要はないかもしれない。

    一方 ACK を返すのは、たったの一ヶ所。こちらは無条件ジャンプで事足りる。

    ここまでがドライバの仕事。要するにバッファの受け渡しで 事が進む。タスク側の処理がこれに加えて必要になる。あと、接続しただとか、切断しただとか状態管理も別途必要で、これについては後で検討しよう。まずは、中核となる部分だが、ここのイメージはできた。

状態監視とか

    接続後の動作しか検討していなかったのだが、気になってきた。今の udrv で 接続や切断、リセットに対応できるのだろうか?

    なにも接続されていない状態では、D+/D- ともに L で SE0 状態。接続されていれば idle 状態。

      この状態をホストがどのように検出できるか?

      受信をスタートさせると SE0 なのですぐに bit0: 送受信要求 が 0 になる。これは、nIRQ が L になることでも検出できる。続けて 2 回実行しても同じ結果なら、接続されていないことが分かる。

      受信をスタートさせても、nIRQ が H なら、接続はされていて idle 状態。

      これを定期的にポーリングしてやれば良いのだろう。

    逆に、ホストがリセットしたい場合、50ms 以上 SE0 状態にする。

      device 側が、これを検出する仕組みは、ホストと同じ。だが、ホスト側で 強制的に SE0 状態にする仕組みを作っていない。

      実をいうと -13 版では、コントロールレジスタに 0x84 を書き込むことで、ENABLE ピンを L にする 仕組みを入れた。このピンでプルアップすることで、device では、切断状態を作り出せる。

      ホストでは、0x88 を書き込むことで、D+ , D- ともに L 出力ということにしようかと思う。ただ、この程度の変更すら規模的に厳しい。ただ、既に SUPPORT_CRC5S=0 としていてそのおかげで入った。要するに ホストで使うトークンや SOF に対して CRC-5 は自動生成しない。生成済みのデータを送る必要があるが、僅か 11 bit 分の CRC 計算だし重くはないだろう。

関連記事
posted by すz at 15:51| Comment(0) | TrackBack(0) | CPLD

2012年06月28日

USBコントローラの設計

USBコントローラを設計してみたい。-- これは、FPGA を始めたときから思っていたこと。そろそろ、それが出来るレベルになったかも。少なくともどうやって設計すべきなのか見当も付かなかったのが、今は見当ぐらいは付くようになった。

最初は、規模が小さい MachXO2-256 で作れるものとして検討してみよう。もちろん、規模が小さいわけだから、一般的なコントローラの機能は作れない。では何を作るのか?

    レベル 0)
    パケットを送出し、受け取る。そういうものならまずは可能だろう。ホストから来た 0/1 のシーケンスをそのまま送るのは、難しくなさそうだ。

    受信を考えると、まず相手のクロックに同期させなければならない。4 倍クロックを使って、最初の 信号の立ち上がりに合わせようかと思う。ここのところは、少々難しそうだが、USART などと似た様なものだから、それほどでもないだろう。

      パケットの開始(SOP)は、送信側からみると、入力状態から、アイドル状態と同じ値を一旦出力することらしい。アイドル状態は、出力しない状態で Low-Speed では、D- がプルアップされているわけだから D- = H , D+ = L 。Full-Speed では、逆になる。

      続いて SYNC データを送出する。データは LSB first で 0x80 。これを NRZI (Non Return Zero Invert) エンコードすると H-L-H-L-H-L-H-H (Low-Speed での D+/Full-Speed D-)。 最初に L → H と変わるから ここを拾って 受信クロックを同期させるわけだ。 クロックが同期してなければ、1 つ遅延させる。 6 回チャンスがあるから、最後の H-H までに同期するはず。

      パケットの終了(EOP)は D- , D+ 共に L 出力 x 2クロックの後 、アイドル状態と同じ値を一旦出力して、入力状態。受信では、D- , D+ 共に Lを検出したら、1 クロック待って 受信を終了させれば良い。

    あと難しそうなのが、FIFO が必須ということ。途切れなく 送受信しなければならないので、データが来たからといって、すぐに 送信すれば良いものでもない。受信では、FULL にならないようにする配慮も必要。

    ちなみに、Full-Speed での最大パケットサイズは、512 Bytes だったと思う。MachXO2-256では、合計で 64B ぐらいしか取れそうにない。なかなか考えどころ。

    レベル1)
    USB では、NRZI (Non Return Zero Invert) エンコーディング というのを行なっている。レベル1では、エンコード/デコードする 。後で説明するが、NRZI を 論理レベルに変換すると、MCU 側の処理が楽になる。コントローラ側でも 送受信の単位が バイト単位になって FIFO まわりが少し楽になる。

    レベル2)
    パケットの形式を知った上で、いくつかの機能をサポート。パケット ID(PID) が正しいかどうかの検出や、CRC のチェック/生成など。

    このレベルより高度なものは規模的に無理。このレベルでも怪しい。一応目標にはするが、レベル1になるかも。

さて、こういうものを作るとして、MCU との 送受信インターフェイスはどうしよう。FIFO を使うから基本は、8bit パラレル。

ただ、パラレルでは、ピンを消費する上に AVR なんかだと SPI より遅くなる。やはり SPI かと考えている。まぁこれは、出来た後でも良い。

ところで、USB PHY のインターフェイスは、UTMI というものが定義されているらしい。SMSC では、これのピン数を減らした ULPI( UTMI + Low Pin Interface) の PHY を製品化している。これに合わせることは、考えていないが、出来た後、再度検討してみよう。まずは、上記のものを設計しなければ。

USBでの信号の扱い

    基本は、差動信号だが、上で出てきたように例外がある。(両方 L レベル)

    FPGA は一般に 差動出力も持っているのだが、これをサポートするため、シングルエンド x 2 で操作すれば良いだろう。受信側も 同じ事情。差動入力もできるが、シングルエンドも必要。FPGA は 1つのポートで 複数の入力の仕方ができるものなのだろうか? その上 出力にも切り替えられないといけないのだが ...

    多分無理だと考えて、差動入力をパラレルに接続することにしよう。ポートが勿体なければ、シングルエンドで 値を読むことにしても良い。

    さて、デバイスとして構成するなら 1.5K のプルアップが必要だ。ポートが余っていれば 2つのポートに抵抗を付けることで、設定可能にしても良いだろう。ホスト側は、15K のプルダウン。これは、FPGA のプルダウン機能でいけそう。

    こういうことなので、最大は D-,D+ にそれぞれ 3 つづつ 計 6 個のポートが必要。

LRZI について

    簡単に説明してしまうと 論理 0 出力は反転、論理 1 出力は 状態保持。
    そして、同じ状態が 6 クロック続くと 次に 0 を挿入する。-- 要するに論理 11111 は、0 または 1 の状態が 6 クロック続くので 0 が挿入される。

    Full-Speed は、物理的な差動出力が逆になるわけだが、受信側から見ると、同じ値になる。初期状態が違うだけで、反転-保持の関係は変わらないからだ。

以上の理解で、レベル1は設計できるはず。さあ設計してみよう。

クロックジェネレータ

    48 MHz を入力して、12 MHz を作る。Low-Speed の場合は、6MHz 入力で 1.5 MHz を作る。

    PLL のない MachXO2-256 で 48 MHz をどうするのか? という問題があるが、とりあえず考えない。あとクロックは、送信中、受信中のみ 生成することにしよう。

    module udrv_clkgen (
    input CLK_4X
    , input DP
    , input SYNC_REQ
    , output SYNC_ACK
    , output CLK
    );
    reg [1:0] clk_ph = 0;
    reg r_dp = 1'b0;
    reg r_done = 1'b0;

    assign CLK = clk_ph[1];
    assign SYNC_ACK = r_done;

    always @(posedge CLK_4X)
    begin
    r_dp <= DP;
    if (~SYNC_REQ | ~(r_dp ^ DP) | (clk_ph == 3) )
    clk_ph <= clk_ph + 1;
    if (~SYNC_REQ)
    r_done <= 1'b0;
    else if ((r_dp ^ DP) & (clk_ph == 3))
    r_done <= 1'b1;
    clk_ph <= clk_ph + 1;
    end
    endmodule

    // _______
    // CLK |_______|
    //
    // clk_ph 3 | 0 | 1 | 2 | 3 | 0
    // _____________
    // DP >< ><
    //
    // _________
    // SYNC_ACK __|
    //

    SYNC_REQ は、上位モジュールからの受信要求で、SYNC_ACK が 1 になると 受信準備が出来たということで、受信モジュールを enable するつもり。

    (clk_ph == 3) 以外で DP が変化すると 1/4 クロック遅らせる。OK になれば、SYNC_ACK が 1 になるので、(上位モジュールが) SYNC_REQ を 0 にする。

受信モジュール

    DP 以外に SE0 という入力信号を作ることにした。これは、DP-,DP+ を シングルエンドとして見て共に 0 のとき 1 にする。RECV_EN で受信開始だが、SYNC の途中なので、1 - 1 または 0 - 0 が来た次から 採取開始。データは、FIFO に入れることにする。FIFO が溢れても関知しない。

    module udrv_reciever (
    input CLK
    , input RECV_EN
    , input DP
    , input SE0
    , output [7:0] RECV_DATA
    , output RECV_DATA_RDY
    , input RECV_DATA_ACK
    );

    reg recv_stat = 0;
    reg r_dp = 0;
    reg [7:0] data_out;
    reg data_rdy = 0;
    reg [7:0] r_data;
    reg [2:0] data_count = 0;
    reg [2:0] one_count = 0;

    assign RECV_DATA[7:0] = r_data[7:0];
    assign RECV_DATA_RDY = data_rdy;

    always @(posedge CLK)
    begin
    r_dp <= DP;
    if (~RECV_EN | SE0)
    begin
    recv_stat <= 0;
    data_count <= 0;
    r_data <= 0;
    one_count <= 0;
    end
    else if (recv_stat == 0)
    begin
    if (~(r_dp ^ DP)) // end of SYNC
    recv_stat <= 1;
    end
    else
    begin
    if (~(r_dp ^ DP)) one_count <= one_count + 1;
    else one_count <= 0;

    if (one_count <= 5)
    begin
    r_data <= { ~(r_dp ^ DP), r_data[7:1] };
    data_count <= data_count + 1;

    if (data_count == 7)
    data_out <= { ~(r_dp ^ DP), r_data[7:1] };
    end
    end

    if ( RECV_EN & ~SE0 & (recv_stat == 1)
    & (one_count <= 5) & (data_count == 7) )
    data_rdy <= 1'b1;
    else if (RECV_DATA_ACK)
    data_rdy <= 1'b0;

    end
    endmodule

送信モジュール

    SEND_EN のとき送信開始。まず SYNC を送出し、次からデータを送る。データが途切れると送信終了。
    送信側は、シングルエンドとしてのポート状態を生成する。ちょっと変なのだが、(SEND_DONE != 0) のとき DP,DM を出力することを想定している。

    一応 受信モジュールとのペアで、パラレルの通信ができるようにした。送信が途切れると パケットを終了し、再開すると自動的に SYNC が付く。CRC などはないから、再送できないので実用的ではないが、ロジックのデバッグには役立ちそうだ。

    send_stat は、6 状態になった。シーケンスがあるので、そんなものだろう。

      send_stat = 0 : アイドル
      send_stat = 1 :
      send_stat = 2 : SYNC とデータ
      send_stat = 3 : SOP 1 (SE0)
      send_stat = 4 : SOP 2 (SE0)
      send_stat = 5 : SOP 3

    module sender (
    input CLK
    , input SEND_EN
    , output SEND_DONE
    , output DP
    , output DM
    , input [7:0] SEND_DATA
    , input SEND_DATA_RDY
    , output SEND_DATA_ACK
    );

    reg r_dp;
    reg r_dm;
    reg [2:0] send_stat = 0;
    reg r_out = 0;
    reg [6:0] data_in;
    reg data_ack = 0;
    reg [7:0] data_count = 0;
    reg [7:0] one_count = 0;

    assign DP = r_dp;
    assign DM = r_dm;
    assign SEND_DONE = (send_stat == 0);
    assign SEND_DATA_ACK = data_ack;

    always @(posedge CLK)
    begin
    if (~SEND_EN)
    begin
    send_stat <= 0;
    end
    if ( (send_stat == 0) & SEND_EN )
    begin
    send_stat <= 1;
    data_count <= 7;
    data_in[7:0] <= 7'b1000000;
    r_out <= 1'b0;
    end
    else if (send_stat == 1)
    send_stat <= 2;
    else if ( (send_stat == 2) & (one_count <= 5) & (data_count == 0) )
    if (SEND_DATA_RDY)
    begin
    data_count <= 7;
    data_in[6:0] <= SEND_DATA[7:1];
    r_out <= SEND_DATA[0];
    end
    else // exit sending
    send_stat <= 3;
    else if ( (send_stat == 2) & (one_count <= 5) )
    begin
    data_count <= data_count - 1;
    data_in[6:0] <= { 1'b0, data_in[6:1] };
    r_out <= data_in[0];
    end
    else if (send_stat == 3) send_stat <= 4;
    else if (send_stat == 4) send_stat <= 5;
    else if (send_stat == 5) send_stat <= 0;

    if ( (send_stat == 3) | (send_stat == 4) ) // SE0
    begin
    one_count <= 0;
    r_dp <= 1'b0;
    r_dm <= 1'b0;
    end
    else if (send_stat == 2)
    begin
    if ( (~r_out) | (one_count > 5) )
    begin
    one_count <= 0;
    r_dp <= ~r_dp;
    r_dm <= r_dp;
    end
    else one_count <= one_count + 1;
    end
    else // if ( (send_stat == 1) | (send_stat == 5) )
    begin
    one_count <= 0;
    r_dp <= (FS) ? 1'b1 : 1'b0;
    r_dm <= (FS) ? 1'b0 : 1'b1;
    end

    data_ack <= SEND_EN & SEND_DATA_RDY & ( (send_stat == 1)
    & (one_count <= 5) & (data_count == 0) );
    end
    endmodule

FIFOモジュール

    FIFOモジュールも自分で作る。Dual PORT の分散RAM ベース。32 バイトで 30 スライス 、64 バイトだと 50 スライス。たぶん 32 バイトしか選べない。

      32 バイト増えて 20 スライス増えている。2 バイトあたり 1 スライス だから 16bit x 1bit が 2 LUT の計算。Dual PORT ならこの値が適切。

      送受信は、同時には行わない。FIFO を 2 つ持つのは、もったいないので可能なら共用にしたい。

    module udrv_fifo # (
    parameter DEPTH = 5
    // parameter DEPTH = 6
    ) (
    input CLK
    , input RST
    , input [7:0] I_DATA
    , input I_DATA_RDY
    , output I_DATA_ACK
    , output I_DATA_FULL
    , output [7:0] O_DATA
    , output O_DATA_RDY
    , input O_DATA_ACK
    );
    reg [5:0] mem [0:2**DEPTH-1];

    reg [DEPTH-1:0] i_addr = 0;
    reg [DEPTH-1:0] o_addr = 0;
    wire [DEPTH-1:0] i_next = i_addr + 1;
    wire [DEPTH-1:0] o_next = o_addr + 1;
    reg i_ack = 0;
    reg o_rdy = 0;
    assign I_DATA_FULL = (i_addr != o_next);
    assign I_DATA_ACK = i_ack;
    assign O_DATA_RDY = o_rdy;

    always @(negedge CLK)
    begin
    if (RST)
    begin
    i_addr <= 0;
    o_addr <= 0;
    i_ack <= 1'b0;
    o_rdy <= 1'b0;
    end
    else if (I_DATA_RDY & ~i_ack)
    begin
    if (~I_DATA_FULL)
    begin
    i_addr <= i_next;
    mem[i_next] <= I_DATA;
    end
    i_ack <= 1'b1;
    end
    else
    i_ack <= 1'b0;

    if ( O_DATA_ACK & (i_addr != o_next))
    begin
    o_addr <= o_next;
    o_rdy <= 1'b1;
    end
    else if (i_addr != o_next)
    o_rdy <= 1'b1;
    else if ( O_DATA_ACK )
    o_rdy <= 1'b0;
    end
    assign O_DATA = mem[o_addr];

    endmodule

    以上が今回作ったもの。シミュレーションとかデバッグは未だ。あとインターフェイスに SPI を付けなければならない。そうしないとピンが多すぎて、QFN32 に収まらないのだ。

    で、専用の SPI モジュールを作ったのだが、問題がある。パケットより小さな FIFO で、Full-Speed に対応するには、12 M bps 前後で通信できないといけない。なかなかに厳しい条件だが、それは置いておく。問題は、SPI の SCK が CLK より早い場合にも対応しないといけないところ。FIFO との ハンドシェークが鍵なのだが、うまく行っていないかも。

    とりあえず QFN32 に収めるために、8bit データを inout に変更するのが良さそう。

    ところで、作ったは良いがどうやって使うのか? ... V-USB のコードを改造すればうまく制御できるのではないかと思っている。V-USB のなかで見たくもない部分は、通信のところで、アセンブラになっている。これをまるごと置き換えてしまえば 良さそうなのだ。ただ、Full-Speed に対応するには、48 MHz が必要になってしまう。Low-Speed で良いなら V-USB で良いし。結局のところ 規模の大きいFPGA のコアで使うぐらいか。

    なかなか使いドコロがないが、CRC のコードも作りたいし、それだけ追加してみて、シミュレータでチェックして、ひとまずの完成としよう。

    この後、送信をシミュレータでデバッグした。


    Full-Speed(FS) の例。FS では、D+(DP) がプルアップされているわけだから DP = H がアイドル。Hi-Z から DP = H なって、LHLHLHLL と SYNC コードを送出している。次に 0x33 を送るのだが、 ちゃんと LLHLLLHL となっている。

    終わりは、DP/DM 共に L を 2 クロックで、次に アイドルと同じ DP = H で最後に Hi-Z 。

    結構バグがあったが、なんとかデータを送ってくれるようになった。上で貼り付けたコードは、バグがあるままなので注意。正しいのは、最新のソースコードのみである。

      送信でインターフェイスを若干変更。ひとつは、送信STARTを指示するフラグ。FIFO にパケットを全部送ってから、送信というのが妥当だと思えたので。-- 以前に AT90USB162 のコードを作ったが 最大パケットサイズは、32 か 64 にしか出来なかった。それで十分でもあった。その程度ならば FIFOに全部入るのだ。

      もうひとつは、DP/DM を出力にするのを指示するフラグ。SEND 中というフラグで代用しようと思っていたのだが 1 クロックずれる。

    次は受信をデバッグしよう。送信データを受信側とつなぐのだが、CLK がずれるので、ちょっと工夫がいる。


    受信も一応動かせた。受信要求を出すと SYNC_REQ が 1 になって、CLK をずらす。確かに 180°ずれて SYNC が完了しているのだが、SYNC_REQ が ON/OFF を繰り返していて、ぎりぎりで 受信開始になっている。... まずそう。
    それは、おいおい直すとして ... 今回は、送信用、受信用 2 つのモジュールを接続したのだが、pull-up/pull-down が分からなかったので論理レベルで接続することにした。このため、LB_TEST という define を追加。


    0x33,0x34,0x35,0x36 を送信しているのだが、その 4つをちゃんと受け取って、受信終了。その後、RECV_REQ が ON/OFF を繰り返している。(受信前は、SYNC_REQ)。一応動きはしたが、やっぱりまずそうだ。

CRC について(基礎編)

    まずは基礎から。

    USBでは、CRC-5 と CRC-16 の2通りの CRC を使っている。CRC-5 は SYNC 込み 32bit のパケット専用で、それより大きいパケットでは CRC-16 を使う。CRC-16 と言っても、計算式はひと通りしかないわけではないらしい。USB での方法を知らないといけないのだ。

    (16bit) x^15 + x^14 + x^2 + 1
    (5bit) x^5 + x^2 + 1

    生成多項式にはこれを使う。初期値には all 1 を使い、出力は ビット反転した上で LSB と MSB も反転。

    コードで示した方が早そうだ。

    CRC-16:
    unsigned int r_crc = 0xffff;
    unsigned int In;
    for (i=0; i< XX; i++) {
    w = (r_crc ^ In) & 1;
    r_crc = (r_crc >> 1) & 0x7fff;
    if (w) {
    r_crc ^= (1<<0);
    r_crc ^= (1<<13);
    r_crc ^= (1<<15); // w
    }
    In = (In >> 1);
    printf("%04x r %04x\n", r_crc, ~r_crc & 0xffff);
    }
    CRC-5:
    unsigned int r_crc = 0x1f;
    unsigned int In;

    for (i=0; i< XX; i++) {
    w = (r_crc ^ In) & 1;
    r_crc = (r_crc >> 1) & 0xf;
    if (w) {
    r_crc ^= (1<<4);
    r_crc ^= (1<<2);
    }

    In = (In >> 1);
    printf("%02x r %02x\n", r_crc, ~r_crc & 0x1f);
    }

    出力は、~r_crc (bit反転したもの)なので注意。

    さて、受信で CRC値が含まれているが、これも含めて CRC 計算してしまうと ... 特定の値になる。CRC-5 では 5'b11001 , CRC-16 では 16'h4FFE 。(bit反転前の値(r_crc) なので注意)

CRC について(応用編)

    応用編というより実装編。

    module udrv_crc # (
    parameter WIDTH=16
    // parameter WIDTH=5
    , parameter SEQUENTIAL_MATCHING=0
    ) (
    input CLK
    , input [1:0] SEL
    , input I // LSB first
    , output O // LSB first
    , output MATCH
    , output [WIDTH-1:0] CRC
    );

    SEL で 動作を指定するのだが、基本的に 2 つのモードがある。ひとつは、01 で CRC 計算。もうひとつは、10 で、計算結果をビットストリームにして出力 -- 送信などで使う。
    00 は、なにもしない。-- USB では、0 が挿入される場合があるから、これが必要なのだ。あと、11 は、初期値にする 。
    パラレルの CRC は、シミュレータのチェック用で使わないのが基本。

    同時に CRC の一致検出 もつけた。やりかたには 2 通りある。ひとつは、計算結果を出力して、入力と比べるというやりかた(SEQUENTIAL_MATCHING=1)。もうひとつは、最後まで計算して特定の値になったかどうか チェックするやりかた(SEQUENTIAL_MATCHING=0)。計算結果を出力してしまうと、以降計算はできないので両立はできない。

    (SEQUENTIAL_MATCHING=0) は、受信での CRC-16 チェックに使う。USB ではパケットを受け取り終わってはじめて、2 バイト前が CRC だったと分かる。だから、これを使う以外にはない。

    (SEQUENTIAL_MATCHING=1) は、CRC-5 に使おうかと思っている。

    reg [WIDTH-1:0] r_crc = (-1);
    wire I2 = (r_crc[0] ^ I);
    assign CRC [WIDTH-1:0] = ~r_crc[WIDTH-1:0] ;
    assign O = ~r_crc[0];
    reg r_match = 0;
    assign MATCH = (SEQUENTIAL_MATCHING) ? r_match
    : (WIDTH == 5) ? ( ~r_crc == 5'b11001 )
    : (WIDTH == 16) ? ( ~r_crc == 16'h4FFE )
    : 0;

    always @(negedge CLK)
    begin
    if ( SEL == 2'b11 ) // RST
    begin
    r_crc <= (-1);
    r_match <= 1;
    end
    else if ( SEL == 2'b10 ) // Extract bitstream
    begin
    r_match <= (I ^ r_crc[0]) & r_match;
    r_crc <= { 1'b0, r_crc[WIDTH-1:1] };
    end
    else if ( SEL == 2'b01 ) // Calc.
    if (WIDTH == 16)
    r_crc <= { I2 , r_crc[15]
    , ( r_crc[14] ^ I2) , r_crc[13:2]
    , ( r_crc[ 1] ^ I2) } ;
    else if (WIDTH == 5)
    r_crc <= { I2, r_crc[4]
    , ( r_crc[ 3] ^ I2), r_crc[2:1] };
    end
    endmodule

    実装はこれ。短いとみると長いとみるか...

    これを 送受信に組み込むことは成功した。送信・受信でモジュールを兼用。-- わずか 3 スライスほどだが、節約できた。合計の規模は、110 になった。ただし、FIFO のサイズを 32 バイト にしたときの話。これで FIFO のサイズを 64 バイトにするのは無理になった。

    実は、ピンの数の方が切実。JTAGENB を使うようにすれば、21 pin 使えるのだが、既に 19 pin 使っている。受信では CRC エラーの出力でさらに 1pin 増えた。送信では、CRC を生成するかどうかの 設定を 2 種類入れたかったのだが、無理なので自動で 生成することにした。

      CRC-5 は、計算結果を入れるかどうかの判断をするタイミング(3バイト目 残り 5bit)で、次のデータがあるかどうか を見て なければ、入れる。そこできちんと終わらせないといけない。次のデータがあれば CRC-16 を入れることにする。( データを受け取り終わったら CRC-16 の 2 バイトが挿入される。)

    こういうロジックを入れて 110 スライスなのだ。CRC をサポートせず生のデータを送信・受信するなら 79 スライスになった。31 スライスも使っているが、CRC 自体は小さい。制御が大変なのだ。

    さて、入れたいメカニズムは入ったのだが ... 入出力をどうするのか まだ。
    SPI はひとつの案だが、詳細は詰めないと。それに今の 入出力 。これも一般的な仕様にする必要がある。

    ところで、48 MHz の件、 『クリスタルオシレータ(48MHz) SG-8002DC(3.3V)』 -- PLL なしだとこれを使うしかないようだ。

入出力のハンドシェーク

    これまで、全部が CLK に同期して動いていた。だが、別クロック系統のものと接続するには、具合が悪い。パラレルで MCU と接続する場合も今のままではダメだし、SPI と接続する場合も同様。

    まずは、パラレルのところの整備から始める。

    // ______________________ _______
    // DATA <______________________><_______ // // _______ _____ // RDY ________| |______________| // // ________ // ACK _____________| |________________ // // // CLK | | | | | //

    今考えているのを図にしてみた。
     1. ACK が 0 のとき DATA をセットできる。セットしたら RDY を 0 にする。
     2. DATA が確定したので、RDY を 1 にする。
     3. 受け取る方は、受信後、ACK を 1 にする。
    4. ACK が 1 になったら、RDY を 0 にする。
     5. 受け取る方は、RDY が 0 になったら ACK を 0 にする。
     最短 3 クロック。

    このルールにしたがうようにまず変更した。規模は 110 で変わらない。

    さて、BUS を使うなら、一般的なデバイス -- RD/WR で制御できるもの と同じようにしておきたい。ついでにコントロールレジスタも付けて ピンも減らしたい。

    RDについては、

    assign RECV_DATA_ACK = ~RD;

    で制御できる。データがあれば、ACK の前後で 安定している。なければ、前のデータが 読めるだけ。RD が H になった後 次のデータがセットされるので、読み込みサイクルが早すぎるとマズイ。なんなら FIFO のクロックを 4倍速にしたり マスター側に合わせて良い。それが出来るように ハンドシェークを見直したのだ。

    RECV_DATA_RDY は、BUS に接続するなら コントロールレジスタから読めると便利。だが、AVR などでポート制御するなら、ピンに出しておいた方が良い。

    assign DB[7:0] = (~RD & ~RS) ? RECV_DATA[7:0]
    : (~RD & RS) ? CTRL_REG[7:0]
    : 8'bz;
    assign SEND_DATA[7:0] = DB[7:0];

    出力制御は、こんな風にする。

    次に WRのほう。

    assign SEND_DATA_RDY = ~WR;

    WR を L にしたときデータが安定しているという条件なら、これでも良い。ACK は無視。勝手に L→ H→ L になる。不安なら、WR を遅延させるのも良いかも知れない。

    ここまで作ったところ 115 スライスになった。わずかな増加だが、これを元に SPI を付けるのだ。残り はわずか 13 スライス。

SPI インターフェイス

    これで、 SCK が CLK より早かろうが遅かろうが問題なくなった。いよいよ最後の機能として SPI を付ける。

    module udrv_spi (
    input MOSI
    , input SCK
    , input CS
    , output MISO

    , input [7:0] SPI_DATA_I
    , output SPI_DATA_I_ACK
    , input SPI_DATA_I_RDY

    , output [7:0] SPI_DATA
    , output SPI_DATA_RDY
    , input SPI_DATA_ACK
    );

    モジュール単体は、こう。SPI を パラレルに変換しているだけ。CLK もない。
    続いて本体

    reg [3:0] r_count = 0;
    reg [7:0] r_spi;
    reg [7:0] spi_out;
    reg r_mosi = 1'b0;

    reg o_data_ack = 1'b0;
    reg o_data_set = 1'b0;
    reg o_data_rdy = 1'b0;
    assign SPI_DATA_RDY = o_data_rdy;

    reg i_data_ack = 1'b0;
    assign SPI_DATA_I_ACK = i_data_ack;

    assign SPI_DATA = spi_out[7:0];
    assign MISO = r_spi[7];

    always @(posedge SCK)
    begin
    r_mosi <= MOSI;
    if (r_count == 1)
    i_data_ack <= 1'b1;
    else if (~SPI_DATA_I_RDY)
    i_data_ack <= 1'b0;
    end

    always @(CS , negedge SCK)
    begin
    if (CS) // LOAD
    begin
    r_count <= (-1);
    r_spi <= SPI_DATA_I;
    end
    else if (r_count == 7) // LOAD
    begin
    r_count <= 0;
    r_spi <= SPI_DATA_I;
    spi_out <= {r_spi[6:0], r_mosi };
    end
    else // SHIFT
    begin
    r_count <= r_count + 1;
    r_spi <= {r_spi[6:0], r_mosi };
    end

    o_data_ack <= SPI_DATA_ACK;
    o_data_set <= (~CS & (r_count == 7));
    if (o_data_set)
    o_data_rdy <= 1'b1;
    else if (~o_data_ack & SPI_DATA_ACK)
    o_data_rdy <= 1'b0;
    end
    endmodule

    悩ましいのは、動作のタイミングが足りないところ。

      通常、最後のタイミングは、negedge SCK で (r_count == 7) 。ここでデータ自体は受け取れる。o_data_set を 1 にすることも出来る。だが、o_data_rdy を 1 に出来ない。
      CS の変化も条件に入れてようやく 1 にできる。ただし、0 には出来ない。

      これでもシミュレータでは動く。だが、always @(CS , negedge SCK) という記述を期待通りにやってくれるかどうか? 不安だったりする。前に Xilinx で試したときは、ダメだったような ...

    それも問題だが、上位レイヤーでどう使うか?

    コントロールレジスタをサポートしたいのだが、どういうプロトコルにするか ... RS が邪魔なのだ。まぁ、余裕もなさそうだから、ピンから入力することにしよう。

    送信の場合、RS=0 にして、パケット分のデータを 送り、RS=1 にして、コントロールレジスタに書き込むことで、SEND_START を 1 にする 。FIFO が空になれば、一定時間後に 送信完了。

    受信が問題。通常は受信モード。FIFO が空でなければ、受信中。一定時間後に 受信は完了する。... なんだか苦しいが、ピンもロジックも足りない。内部ロジックには、送信中、受信中というのがあるので、コントロールレジスタをポーリングすれば、分かるようにしておこう。

    こんなところか。で、これを実際に作りこむと .. 124 スライスにまでなった。

      128 スライスなど小さいと思っていたのだが、udrv.v は、なんと 859 行もある。コメントはそれなりにはあるが、コード自体に冗長な部分はない。メモリを使わずにコードだけで埋めるのは結構しんどい。



    シミュレータで、送信側を確認。

      シミュレータでは、negedge CS , posedge CS が動作ポイントに入るのは確認できた。
      r_count は、4bit 。最初に negedge CS で動いたときに 7 にするのは まずい。F にしている。
      negedge CS で F それから 0 1 ... 7 でデータ出力。最後は、negedge SCK で データ出力できている。posedge CS でかろうじて o_data_rdy を 1 。これでデータは受け取ってもらえる。ACK は半端な状態だが、次に送信するときに、 続きから処理される。

      o_data_rdy を 1 クロック早めるのは、データを確実に渡せない気がする。ただ、FIFO 側で 遅延して受け取るような変更なら出来る。決めたルールに例外を持ち込むことになるが、このようなケースなら止むを得ないか。

      追記: 間違っていた。


      always @(CS , negedge SCK)
      begin
      if (CS) // LOAD
      begin
      r_count <= (-1);
      r_spi <= SPI_DATA_I;

      この部分なのだが、先頭の negedge CS で条件が成立していない。
      受信の動作確認で分かった。

      reg r_cs = 0;
      always @(CS or negedge SCK)
      begin
      r_cs <= CS;
      if (r_cs) // LOAD
      begin
      r_count <= 0;
      r_spi <= SPI_DATA_I;

      こう変えたら動いたのだが、どうも釈然としない。 negedge SCK だけの場合は、変更前の SCK が見えたのに、こう書くと変更後?



      受信しているところ。送信側も SPI だから両方ちゃんと動いている。

      FIFO に遅延読み込みのパラメータを付け o_data_rdy を 1 SCK クロック 前にした。

    入れたい機能は入れた。QFN32 の 128 スライスという条件をほぼフルに使った。もう満足だったりする。随分と勉強にはなったが、この後どうしよう?

    Full-Speed/Low-Speed の切り替えを付けたい。あと差動入力を使うオプション。だが、これ以上のロジック追加は無理だろう。今回は、MachXO2-256 でどこまで出来るか? というテーマなので、いずれ別の機会にしよう。

      思い出したので、メモ。
      FIFO は、Dual-Port の RAM を使っている。いま想定している使い方だとパケットを受け取り終わってから、READ することになりそう。送信の場合も同じ。なら Single-Port でも良さそうな気がする。制御ロジックが似た様なものなら 2 倍の容量が使える。FIFO まわりを差し替えるだけで済むなら検討してみたい。
       ... やってみたが、却って規模が増えた。.. 失敗。

      あと FIFO は、同期 FIFO を非同期で使えるようにしている。非同期 FIFO というものもある。それと差し替えるとどうなるか試してみたい。 (参考: 『FPGAの部屋:同期FIFOと非同期FIFO』)

    残っているものとしては、JTAG 通信と ADC 。DAC は一応 設計してみたのだから、ADC もその程度のものはやりたい。

検討もれ

    ひといきつきたいのだが、検討もれがあるかないかが気になっている。マズそうなものをリストアップしていこう。ただし、すぐには直さないかも。

    (EOP だけのパケット)

      Low-Speed では、これがあるそうだ。

      まずこれを送るインターフェイスがない。送信では、SYNC が自動で付加される。0 バイトを送出することもできない。最短は、SYNC - PID - EOP 。

      受信でも検出できない。だけでなく、マズイケースがありそうだ。受信中にまで行くと EOP でともかく状態を抜けるのだが、少々不安。あと、受信は完了したが、0 バイトというインターフェイスがいる。

      (タイムアウト)
      反応が遅すぎてタイムアウトになる可能性がある。 3ms バスが アイドル状態だとサスペンドだそうだ。
      心配だったのだが、AVR の USB だと ACK/NAK/STALL を返すのは、上位レイヤーだったことを思い出した。... たぶん大丈夫ではないかと思う。

      ただちょっと確認。1 ms というのは、1.5 MHz クロックだとして 1500 クロック。8 バイトのパケットは、SYNC , PID , CRC を含まないそうだから 1(SOP) + 14 x 8 + 3(EOP) の 96 クロックが 最短。Low-Speed だとしても 1ms に対して 十分に短い 感じ。反応が悪いと性能が出ない。

      ところで、FIFO は 32 バイトだが、PID + データ を収めないといけない。だが 32 バイトのパケットとは、データのサイズが 32 バイトのようだ。なら収まらない。これもなんとか対処する方法を考えないと。
      幸い、受信部分と FIFO の間には、1 バイトのバッファがある。最後のデータを待たせればなんとか。送信も SPI だとバッファがある。

追記:使い方編

    使い方を考えながら、インターフェイスを見なおした。

    まずは、BUS の信号

    // _ ____________________________________ _____
    // RS _><____________________________________><_____
    //
    // _____ _______
    // nCS |________________________________|
    //
    // ______________ _____________
    // nWR/nRD |_____________|
    //
    // _______________
    // DB[7:0] --------------<_______________>----------
    //

    LCD とかのタイミングを参考にするとこんな感じか。信号名も nWR とか nXX にしてみた。

    READ は、これで問題ない。WRITE はどうしたものか。どうも posedge nWR でデータ採取しているみたいに見えるのだが ... ここは negedge nWR から遅延させて採取にしたい。

    SPI は、普通のタイミング ... だが 、最後のデータは、CS を H にしたとき 、上位に 受け取られる。

    とりあえずは、これでいこうと思っている。

      ところで、FIFO 付きの BUS/SPI コードを作ったことになる。DAC で欲しかったものだ。DAC もこれをくっつけようと思う。

    さて、どう使うかまとめてみた。

    ステータスレジスタ

      bit7: CRC_ERROR
      bit6: DATA_FULL
      bit5: DATA_EMPTY
      bit4: DATA_RDY
      bit3: RECV_EN (受信中)
      bit2: SEND_EN (送信中)
      bit1: r_recv_mode (送受信モード ) (0: 送信 / 1: 受信)
       bit0: r_start (送受信スタート) (1: スタート / 0: 完了)

      内部の状態をステータスレジスタとして見せる。この内いくつかは ピンにも出力している。

      対応するピン出力( 負論理 )
      nCRC_ERROR
      nDATA_EMPTY
      nDATA_FULL
      nDATA_RDY
      nBUSY ( ~r_start )

    信号の説明

      r_recv_mode (送受信モード )

       状態は、0: 送信モード か 1: 受信モードのどちらかに大別される。
       どちらでもない状態は存在しない。

      r_start/BUSY (送受信スタート)

       1 にすると 送信モードでは送信要求、受信モードでは 受信要求。
       送信が完了すると、続いて受信スタートになる。(r_recv_mode = 1)
      受信が完了 すると 0 になる。

      CRC_ERROR :
       受信完了後有効で、1 になると CRC_ERROR が起きたことを示す。

      DATA_FULL :
       1 で、FIFO が溢れたことを示す。(データロスト)
       FIFO をリセットするまで有効

      DATA_EMPTY :
      1 で FIFO が空であることを示す。データが入れば 0 になる。

      DATA_RDY :
      1 で 受信データがあることを示す。
       ハンドシェーク用で、DATA_EMPTY とは変化のタイミングが異なる。

      ※) FIFO は、受信モード→送信モードに切り替えたときと、受信中になったタイミングでリセットされる。

      ※) ステータスレジスタの読み込み:
      (SPI) RS=1 で 0x00 を書き込む。
      (BUS) RS=1 で読み込む

    コントロールレジスタ

      bit7: 書き込み要求
      bit1: r_recv_mode (送受信モード ) (0: 送信 / 1: 受信)
       bit0: r_start (送受信スタート) (1: スタート / 0: 完了)

      動作をさせる信号は、コントロールレジスタとした。書き込み要求は、SPI のみで必須だが、インターフェイスを合わせるために、BUS でも必須ということにしておく。

      ※) コントロールレジスタの書き込み:
       1)送信 モード
      (SPI) RS=1 で 0x80 を書き込む。
      (BUS) RS=1 で 0x80 を書き込む。
      2)送信スタート
      (SPI) RS=1 で 0x81 を書き込む。
      (BUS) RS=1 で 0x81 を書き込む。
       3) 受信スタート
      (SPI) RS=1 で 0x83 を書き込む。
      (BUS) RS=1 で 0x83 を書き込む。

      サポートするのは上記の 3 つ。

    送信方法

       1) 送信 モードに する
       2) PID+データを書き込む。(合計 31 バイトまで)
       3) 送信 スタート にする
       4) (タイミングを見て) 追加のデータを書き込む。

      やはり、規模的に これが限界。最大パケットサイズを 32 バイトにすれば、追加のデータは、高々 2 バイト。すぐに送信が始まることは保証できるので、ステータスを見なくても タイミングは計れるはず。空になる前に送り込めるだろう。



      シミュレータで確認したが、こんな感じ。



      続いて送信されるわけだが、FIFO が空になると CRC を付けて 送信完了。CRC-5 を使う場合も自動で元データと CRC を入れ替える。

      ※ シミュレータ結果で i_recv_mode が 1 になっているが、過渡のものなので気にしないで欲しい。

    受信方法

       1) 受信 スタート にする



      データが来ると nDATA_RDY が 0 になるので データを受け取る。nBUSY が 0 になった後は、新たなデータは来ない。 nBUSY = 0 かつ nDATA_EMPTY = 0 を確認して終わる。

      終わるときに、nDATA_FULL = 0 か nCRC_ERROR = 0 なら、エラーにする。



      FIFO が溢れなければ良いので、高々 2 バイト余分に受け取ることは可能だろう。

        nDATA_RDY=0 で割り込みをかけるとする。ここから 12 MHz で 8 x 31 クロックもある。この間に SPI で 2 バイト 受け取れば溢れない。AVR なら問題なさそうに思える。

    以上だが、パケットの間隔というのが気になる。送信→受信、受信→受信で、データを受け取り損ねるとマズイ。特に受信→受信。このケースには、SETUP や OUT がある。

    まずいなら、仕様を変更しないとならない。... どうもその必要がありそうだ。

    ソースコードは、qfn32samples-11.zip の予定(準備中)。

追記: ソースコードのブラッシュアップとバグ修正

    仕様を変更するにも、まずは規模を減らさないといけない。無駄なところがないかチェックしてみた。

    そうしたら、... 送信での CRC 生成がエンバグしていた。
    後で use_i16 , use_i5 という レジスタを作ったのだが、全く不要なばかりか、バグの原因になっていた。

    次、CRC のモジュールは 送信・受信で共有している。特に CRC-5 は、配線の切り替えの方が重いような気がしたので、2 つに分けてみた .... これは 1 スライスの増加に終わった。残念。

    次、受信での CRC の制御。こっちもバグっていた。まず、CRC-5 か CRC-16 のどちらの結果を取るか示す MATCH_SEL というレジスタがあるのだが、完了でリセットされる。これではまずいので 状態を残すように修正。

    次、MATCH_SEL 自体を良くみたら 受信 4 バイト以上という条件だったので、data_bytes の bit を 1 つ増やして data_bytes[2] に変更。

    次、FIFO リセットの条件を 受信モード・送信モードを切り替えたときに変更。受信→受信に対応するベースにする。FIFO_FULL は、受信モードでずっと残るが、その方が都合が良いようにも思える。その上で、受信モードは常に受信待ちということにした。r_start は、1つめを受信したときに 0 になる。完了が必要ならば、r_startを再度 1 にする。

    さて、 送信→受信のパターン。DATA0/DATA1 を送ったあとの ACK が相当するようだ。タイミングによって ACK を取りこぼしてしまう。やはり送信後すかさず受信モードにすることにした。r_start は、0 になるので、次の受信の完了は検出できない。あと、送信時の FIFO_FULL も検出できない。

    ここまでやった。そして ... なんとか 128 スライスに収まった。

まだ続く

    もう FIX したいのだが、まだ手直しするところが ...

    どれぐらいのクロックで動くのかの確認をするついでに、DCMA モジュールというのを入れてみた。これは、クロックセレクタで、ここを通すとグローバルクロックにつなげられる。で、やってみると DCMA に入力できない。... ピン配置を変えると OK みたいなので、ピン配置を見なおした。

    ついでに RS ... データとコントロールレジスタの選択だが、一般に RS=0 が コントロールレジスタみたいなので論理を逆転。

    あと見直していたら、CRC-5 を 2 個使うようになっていた。これで問題なさそうなので 2 個使う USE_DUAL_CRC5
    を標準にした。

    ついでに SUPPORT_SEND_EOP というのを作りかけていたのだが、ヤメ。コード削除。

    で、動作周波数の確認。... ボトルネックを見ていたら、r_recv_mode1 が引っかかった。1 クロック遅延させるつもりが、半クロックになっていたので変更。さらに FIFO のクロックを CLK_4X にしていたのだが、SPI では必要ないので通常の CLK に。

    最終的に CLK_4X は、67.7 MHz になった。本来 48 MHz だが、これぐらい余裕があれば安心。 規模は 127 スライスでぎりぎり OK。

    あと、CLK_OUT を付けた。CLK_4X の反転出力。水晶があれば、発振させられるかも。ソースコードは、-11 を上書き。

ソフト側の構想

    V-USB を改造すればいけるんじゃないかと上で書いたが、少し確認してみよう。本格的にやる場合は別記事にするが、とりあえず、あたりを付けよう。

    extern usbRxBuf, usbDeviceAddr, usbNewDeviceAddr, usbInputBufOffset
    extern usbCurrentTok, usbRxLen, usbRxToken, usbTxLen
    extern usbTxBuf, usbTxStatus1, usbTxStatus3
    extern usbSofCount

    ; extern unsigned usbCrc16(unsigned char *argPtr, unsigned char argLen);
    ; extern unsigned usbCrc16Append(unsigned char *data, unsigned char len);
    ; extern unsigned usbMeasurePacketLength(void);

    アセンブラで extern を grep すれば、こういうのが見つかる。
    下の 3 つは、アセンブラから呼び出すサブルーチンで無視して良い。CRC などは機能にあるし、usbMeasurePacketLength というのも NRZI 関係だろう。

    さて、上は全部変数。この変数を制御しているのが、usbdrv.c 。同じインターフェイスにすれば、わずかな変更でいけそうだ。たぶんアセンブラは INT0 割り込みで動作する。udrv では、なにか受信すると DATA_RDY が L になる。この変化を INT0(か 1) で拾えば同じような処理ができるはず。

    受信は割り込みベース、送信はポーリングでも良いかも。12 MHz でデータが来るが、16 バイト受信ぐらいまでに応答するとすれば、10 us 以内。20 MHz なら 200 クロックもある。これなら受信ルーチンも C で良いかも知れない。

    memo : 受信側

    uchar usbRxBuf[2*USB_BUFSIZE];
    uchar usbInputBufOffset;
    volatile schar usbRxLen;
    uchar usbCurrentTok;
    uchar usbRxToken;

    このあたりが受信で重要そう。

    len = usbRxLen - 3;
    if(len >= 0){
    usbProcessRx(usbRxBuf + USB_BUFSIZE + 1 - usbInputBufOffset, len);

    こういうコードがある。usbRxLen には、先頭の PID と 最後の CRC-16 が含まれるから -3 する。
    PID の場所は、usbRxBuf + USB_BUFSIZE - usbInputBufOffset ということらしい。

    CRC-16があるということは、DATA0/1 パケット。その前に SETUP か OUT が来ているはずで、それが、その前に入っているのかも。この udrv でも同じ仕様が都合が良さそう。

    usbRxToken は、OUT or SETUP (の PID)が入っている。usbCurrentTok は、OUT だった場合に エンドポイント番号が格納される。

    あと、受信では 1 フレームの送受信を一気にやってしまうようだ。IN が来たら予め用意されたデータを送る。OUT or SETUP では、ACK などの応答を返してしまう。

なんと、まだ続く

    V-USB のコードを眺めていたら CRC-16 は、DATA0/1 パケットに使うことが分かった。サイズは関係ない -- 誤解してた。あと、CRC-5 の送信側のコードがない。.... デバイスは 指示パケットを送信しないのであった。

    仕様を誤解していたのだから、まずは修正。CRC-5 の送信側のフラグは、crc5_extract というものなのだが、ややこしいので、整理したら規模が少し減った。さらにデバイス専用にも出来るようにして、少し規模を削れるように parameter を導入したところ、論理が変わらないのに規模が減った。今は 123 スライス。デバイス専用だと 121 スライス。

    ついでなので、0 バイト送信で SOP のみを送る機能は、Low-speed のみなので FS パラメータ(1 で Full-speed) に連動するように変更。さらに DP , SE0 状態を negedge CLK でサンプリングするよう変更。こうしておかないと 受信毎にクロックを 180°ずらすことになる。ソースコードは、-12 版準備中。

    最終的な仕様を記録しないとわけが分からなくなりそうだ。新記事を起こそう。

関連記事

     ・ 『QFN32の FPGA
     ・ 『TTL ALU 74281
     ・ 『FPGA時計の設計
     ・ 『MCPU -- A Minimal 8 Bit CPU
     ・ 『DACを設計してみよう
     ・ 『USBコントローラの設計

    最新ソースコード (2012/07/1)
     ・ qfn32samples-10.zip

      qfn32samples-10.zip で一応の完成とした。最終的な規模を記録しておこう。GSR は、1 つしかないリソースで、SPI で使った非同期リセットで 使われたらしい。

      BUS SPI
      Number of registers: 117 126
      PFU registers: 115 123
      PIO registers: 2 4
      Number of SLICEs: 116 122 /128
      SLICEs(logic/ROM): 32 32 /32
      SLICEs(logic/ROM/RAM): 84 90 /96
      As RAM: 12 12 /96
      As Logic/ROM: 72 78 /96
      Number of logic LUT4s: 200 214
      Number of distributed RAM: 12 12
      Total number of LUT4s: 224 238
      Number of PIO sites used: 20 14 /22
      Number of GSRs: 0 1 /1

    最新ソースコード (2012/07/05)
     ・ qfn32samples-11.zip

    最新ソースコード (2012/07/06)
     ・ qfn32samples-12.zip

      今度こそ.. ちなみに、実際に動かそうと思えるレベルかどうかが、『一応の完成』の基準。(動かせばバグは出るだろう)
      規模を記録しておく。僅かながら変更する余裕ができた。SUPPORT_CRC5S=0 とすると、送信時 CRC-5 は生成しない(device では不要) が、-2 スライスになる。

      BUS SPI (CRC 分)
      Number of registers: 123 148 45
      PFU registers: 121 145 45
      PIO registers: 2 3
      Number of SLICEs: 112 123 /128 27
      SLICEs(logic/ROM): 32 32 /32
      SLICEs(logic/ROM/RAM): 80 91 /96 33
      As RAM: 12 12 /96
      As Logic/ROM: 68 79 /96
      Number of logic LUT4s: 197 218 64
      Number of distributed RAM: 12 12
      Total number of LUT4s: 221 242 64
      Number of PIO sites used: 21 14 /22
      Number of GSRs: 0 1 /1


参考文献(データシート等)
 ・ 『MachXO2 ファミリデータシート(日本語 pdf)
 ・ 『MachXO2 テクニカルノート(日本語)
 ・ 『Lattice Diamond 1.4マニュアル
posted by すz at 00:24| Comment(0) | TrackBack(0) | CPLD

2012年06月21日

DACを設計してみよう

QFN32 パッケージの MachXO2-256 という FPGA でなにが出来るのか、検討しているのだが。... DAC が作れそうな気がする。FPGA は大概、コア用の電源と IO 用の電源に分かれている。この QFN32 も 例外ではないどころか、VCCIO が 4 つもある。ちゃんと電源を設計すれば、ノイズ的に有利かも知れない。ただ、オーディオDAC に匹敵するようなものには、できないだろう。どこまでのものが作れるかも追求しない。256 に入れられて 検証できるものを目指す。

AVR など PWM を持った MCU は 多いわけだが、ΣΔ変調の DAC を持っているものは、殆ど無い。同じ bit幅でも、ΣΔ変調だと ノイズを減らせるそうだ。ちょっと試してみたい。

ΣΔ変調の原理

    1 bit の ON/OFF だけで DAC を作ると 256 クロック使って ようやく 8bit 幅になる。このことは、PWM と変わらない。ノイズを減らせるのは、どうしてかというと PWM では 0 が続いたあと 1 にするだけのところを、1 を分散させて ノイズの周波数を 上げるためらしい。

    どうやって そういう 0/1 を作るのだろう?

    なかなか理解できなかったのだが、3bit の DAC を使って 8bit 出力することを考えてみたら、分かったような気がする。

    例えば、8'b01011010 を 3bit の DAC で出力するには ... 上位 3bit の 3'b010 をまず出力して 時々 3'b011 も出力することで 中間値を表現する。

    3'b010 を DACで出力すれば 5'b11010 が出力されなかった分として残る。次も 3'b010 なら 5'b11010 + 5'b11010 が残るのだが ... 桁上がりして、6'b110100 になる。だから 3'b011 を DAC で出力して、5'b10100を出力されなかった分として 覚え直す。

    要するに、ずっと 5'b11010 を加算していって、桁上がりしたときに 3'b011 を DACに出力すれば良いのだ。

    では、0/1 だけで表現するには ... 8'b01011010 を ずっと 加算していって、桁上がりしたときに 1 を出力すれば良い。256 回加算すれば、16'b 01011010 00000000 になるから、確かに 8'b01011010 個の 1 を出力したことになる。

    フィルタがどうとか に囚われてよく分からなかったのだが、どうも原理は簡単な話のようだ。

作ってみよう

    module dac # (
    parameter WIDTH = 8
    , parameter WITH_LATCH = 0
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output A_OUT
    );

    reg r_stb = 1'b0;
    reg [WIDTH-1:0] r_data = 0;
    reg [WIDTH-1:0] fraction = 0;
    reg [WIDTH-1:0] count = 0;
    reg r_out = 1'b0;

    always @(posedge CLK)
    begin
    r_stb <= STB;
    if (&count | (STB & ~r_stb))
    begin
    count <= 0;
    fraction <= I_DATA;
    r_out <= 0;
    if (WITH_LATCH)
    r_data <= I_DATA;
    end
    else
    begin
    { r_out, fraction } <= { 1'b0 ,fraction }
    + { 1'b0, (WITH_LATCH ? r_data : I_DATA) };
    count <= count + 1;
    end
    end

    assign A_OUT = r_out;

    endmodule

    取り敢えず書いてみたのがこれ。ラッチあり/なし を選べたり ビット幅を指定できるようにしてみた。そうそう、新しい呪文を覚えた。&count は、count の各ビットを AND する意味で all 1 のとき 1 になる。

    書いてみて思ったのだが、毎回 Nbit の加算をしている。これはクロックを上げられないのではないか?

    例えば 16 bit の出力 を 50kHz でするとする。65536 倍のクロック が必要で 計算すると 3.2768 GHz で駆動することに。とりあえず 3bit の DAC を使うとすれば ... 409.6 MHz 。これでも無理な話だが、周波数が減る上に 加算する bit 数も減る。

    さて、この 3bit の DAC もまた、ΣΔで作ってやれば、どうなるのだろう? 同じ 1bit のΣΔでも最大周波数が上がるのではないだろうか?

N bit ΣΔ

    module dac5 # (
    parameter AWIDTH = 5
    , parameter WIDTH = 16
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output [AWIDTH-1:0] A_OUT
    );

    reg r_stb = 1'b0;
    reg [WIDTH-1:0] r_data = 0;
    reg [WIDTH-AWIDTH-1:0] fraction = 0;
    reg [WIDTH-AWIDTH-1:0] count = 0;
    reg [AWIDTH-1:0] r_out = 0;

    wire [WIDTH-AWIDTH-1:0] WK = 0;
    wire [AWIDTH-1:0] A_ZERO = 0;
    wire [AWIDTH-1:0] A_WK = ~A_ZERO -1;
    wire [WIDTH-1:0] I_MAX = { A_WK , ~WK };
    wire [WIDTH-1:0] I_DATA2 = (&I_DATA[WIDTH-1:WIDTH-AWIDTH])
    ? I_DATA : I_MAX;

    always @(posedge CLK)
    begin
    r_stb <= STB;
    if (&count | (STB & ~r_stb))
    begin
    count <= 0;
    r_data <= I_DATA2;
    fraction <= I_DATA2[WIDTH-AWIDTH-1:0];
    r_out <= I_DATA2[WIDTH-1:WIDTH-AWIDTH];
    end
    else
    begin
    { r_out, fraction } <= { A_ZERO , fraction } + r_data;
    count <= count + 1;
    end
    end

    assign A_OUT = r_out;

    endmodule

    例えば 3bit DAC では、3'b111 より大きな値は、出力できないので 上限値を設けてやる。後は最初に説明したとおり。

    すなおに、N bit の R-2R ラダー DAC を使っても良いのだが、これと 1bit ΣΔを組み合わせてみたい。

    module dacx # (
    parameter WIDTH = 8
    ) (
    input CLK
    ,input STB // active high
    ,input [WIDTH-1:0] I_DATA
    ,output A_OUT
    );

    reg [2:0] hi_clk;
    wire [2:0] hi_out;
    wire lo_stb = CLK;

    always @(negedge CLK)
    begin
    hi_clk <= hi_clk + 1;
    end

    dac #( .WIDTH(3) , .WITH_LATCH(0) ) dac_lo ( .CLK(CLK), .STB(lo_stb)
    , .I_DATA(hi_out), .A_OUT(A_OUT) );
    dac5 #( .AWIDTH(3) , .WIDTH(WIDTH-3) ) dac_hi ( .CLK(hi_clk[2])
    , .STB(STB), .I_DATA(I_DATA) , .A_OUT(hi_out) );

    endmodule

    こうかな?

    ところで、ラッチなしにして、2 ** N 回クロックを待たないで、適時値を変更しても、それなりに追従する。dac5 の方もラッチなしに対応しておいた方が応用が効きそうだ。

    あとは、フィルタがどうとか ... だが、クロックはたっぷりある。簡単なものなら加算だけで作れそうな気がする。これはまた別途考えてみたい。

    それとは別に、データをどうやって受け取るかという問題もある。パラレルは無理だしなにより面倒。やっぱり SPI ? 実は、JTAG 経由の通信というのも検討中。

R-2R ラダー DAC

    簡単に説明すると

    A_OUT_2 --- R ---+----- Analog
    |
    2R
    |
    A_OUT_1 --- R ---+
    |
    2R
    |
    A_OUT_0 --- R ---+
    |
    2R
    |
    GND

    こんな回路。抵抗が 2 種類しかいらないが、値が厳密。あと電流は取れないから、オペアンプを使ってボルテージ・フォロワを組む。

      FPGA 出力には、(数十Ωの)抵抗分がある。これにより R と 2R の比が崩れるので注意。また、出力電流を 4段階ぐらいで調整できる(たぶん 抵抗分が変わる)ので、チューニングに使えるかも。

    秋月で 1608 をリール売りしているから、1K/2K , 1.2K/2.4K , 1.5K/3K とかの組みを選ぶと良さそう。2500 個もあるから、選別しほうだい。

    選別には、SMDテスターが便利そう。3000 カウントだから 3K は避けたほうが良いか。

動作周波数について

    2 つの モジュールにすることで、周波数を上げられないか? やってみたのだが、4bit でも 16bit でも 150MHz ぐらいで、あまり変わらない。2 つの モジュールにしても、やっぱり あまり変わらない。要するに CLK 自体が ボトルネック。150MHz ということは、12bit で 36.6 kHz が上限。3bit の R-2R DAC + 10 bit ΣΔあたりが妥当?

    ところで、100 MHz で 駆動したとして 例えば 010101 というパターンが出てきたとき素直に駆動して良いものなのだろうか? 1bit ならともかく、多ビットだと 周波数を落としたくなる。

    PWM だと 周期が低くなりすぎるし ... PWM + ΣΔ が良いのだろうか? PWM でも Phase Correct MODE というのが AVR にある。UP していって TOP まで行ったら 逆に DOWN していく。こうすると スイッチング周波数は、普通の PWM の 1/2 。例えば 4bit Phase Correct MODE PWM なら CLK が 100 MHz でも スイッチング周波数は、3 MHz ぐらいまで落とせる。ちょっとこれを検討してみたい。2 つの モジュール は、既に出来ているから、片方を PWM にするだけ -- 難しいことはない。

    3bit の R-2R DAC で PWM を使い 、さらに上位を ΣΔにするのは、ちょっと難しい。これも 挑戦してみよう。

    PWM を使うのとは別に 010101 というパターンを 例えば 000111 というのに変換してしまうのはどうだろう?

    考えたのは、次のやりかた。

      今出力しているのが 1 だとする。次に出力するのが、01 だと分かっているなら 10 にしてしまうのだ。(0 についても同様)

      1 01 → 1 10
      0 10 → 0 01

      要するに 単独の 1/0 を出ないようにしてしまうわけだから 絶対に 2bit は続くわけだ。3 bit に拡張するなら、上記に加えて

      1 001 → 1 100
      0 110 → 0 011

      とする。これで 3 つ以上連続することが保証される。

      ただ、この処理には問題がある。例えば 2 を出力する場合 、わざわざ離れた位置に 1 を配置するわけだが、これを くっつけてしまう。連続した数を数えておいて 、くっつける処理をキャンセルする必要がある。

      なにやら面倒な話になってきた。やはり PWM の方が良さそうな。

SPI 通信の例

    秋月で売っている DAC MCP4922 や ADC の MCP3002 は、SPI で通信する。プロトコルを調べてみた。

    DAC :
     ・ 1 データ 送受信の度に CS を 上げ下げする必要がある。
     ・ 1 データは、MSB first , 16 bit 単位

    bit15 ~ A/B チャネル選択
    bit14:12 config
    bit11:0 データ[11:0]

    ADC:
     ・ 1 データ 送受信の度に CS を 上げ下げする必要がある。
     ・ 1 データは、MSB first , 24 bit 単位

    bit23 START (1)
    bit22:20 config (チャネル含む)
    (ADC → Master)
    bit19 NULL (0)
    bit18:9 データ[9:0]
    bit8:0 データ[1:9]

     ・ START を 遅らせて 16 bit 単位 にすることも可能

    bit15 (0)
    bit14 START (1)
    bit13:11 config
    (ADC → Master)
    bit10 NULL (0)
    bit9:0 データ[9:0]

    CS を 上げ下げする必要があるのは、FPGA 側としては、都合が良いかも。SPI というからには、MSB first にした方が自然。バイト単位になるような配慮も必要。変換タイミングは、正確にしたいだろうから 別途ということになりそう。(AVR だと PWM を使う)

    もっと大きな問題があった。クロックをどうしよう。変換タイミングを完全に正確にするには、送信側から CLK をもらうか、DAC 側のタイミングに 合わせてもらうか ...

    送信側から CLK をもらうにしても 50 MHz 〜 100 MHz というのは厳しい。規模が大きい FPGA だと PLL があるが、MachXO2-256 にはない。

    今回のは、DAC 側のタイミングに 合わせてもらうことにしよう。それにしても 問題が。AVR を使うならなんとかなるのだろうが、PC と通信する場合は、どうしよう。

    普通にシリアルで CTS/RTS 制御で良いのかな? 115Kbps だと 11.5 Kbytes/sec になってしまって 性能が足りない。1M bps とか できたら 3M bps とか欲しいところだが ... 途切れなく送り込めるのかどうかが問題。

    DAC を設計するなら MachXO2-1200 ぐらいは 欲しいところで、PC とのインターフェイスに FT2232H を使いたいということになりそうだ。 そういうものとして、『MachXO2 Breakout ボード』があるのだが ... 今回は 256 を使いこなすのがテーマなのでパス。とにかく AVR で使えるものだけを考えよう。

    ここまでの検討で
     ・ SPI を使う。
     ・ データリクエストを DAC 側が出す。
     ・ 16bit 単位
     ・ DAC データは、12bit-14bit の範囲
    ということに決めた。

    プロトコルは、別途。

ソースコード

関連記事
posted by すz at 22:55| Comment(0) | TrackBack(0) | CPLD