2009年11月22日

USBaspLoader 移植メモ

USBaspLoader の移植についての覚書。

だいぶ出来てきたので、ブートローダ関係についてまとめておこうと思う。(USBaspのプロトコルについては前記事でまとめる)

ブートローダの基本動作



ブートローダ領域をサポートしたAVR で、ブートローダを有効にすると、リセットベクタが、ブートローダ領域 に移動する。

USBaspLoader では、PORTD の BIT7 を pull-up した後、チェックして H レベル bit_is_set(PIND,JUMPER_BIT) なら、0 番地にジャンプして アプリケーションを実行するようになっている。また、リセットボタンでリセットしたとき以外 -- bit_is_clear(MCUCSR,EXTRF) のときも アプリケーションを実行する。

ブートローダを実行するときは、set_bit(MCUCR,IVSEL) することで、割り込みベクタ全部をブートローダ領域 に移動する。こういう機能があるおかげで、VUSB のような割り込みを使う機能をブートローダでも使えるわけだ。

bit_is_set(PIND,JUMPER_BIT)の状態になれば、ブートローダを終了して、アプリケーションを実行するようになっている。

bootloaderconfig.h で、BOOTLOADER_CAN_EXIT を 1 にしておくと、USBasp への USBASP_FUNC_DISCONNECT コマンドで ブートローダを終了することはできる。

  • Atmel DFU ブートローダとの違い

    リセットボタンでリセットしたときに、PORTD の BIT7 が Lだったときのみ ブートローダが動作するのは同じ。ただし、DFU ブートローダは、H になったからといって ただちに抜けるわけではない。

    BOOTLOADER_CAN_EXIT を 1 にして ブートローダの実行条件bootLoaderCondition()を 開始用と終了用の 2 つに分ければ Atmel DFU ブートローダに近くなるのかも。ただ、avrdude を 実行するたびに リセットボタンでリセットしないといけないかも。
    -- 実験してみて仕様を決めよう。

  • アプリケーションからのブートローダ実行

    USBaspLoader では、アプリケーションからのブートローダ実行も考慮しているようだ。

    実行すること自体は簡単で ブートローダ領域の先頭アドレスに jump すれば良い。

    ただし、ブートローダの実行条件をクリアしないとリセットと同じになってしまう。

    具体的には、JUMPER をセットした後、

       bit_set(MCUCSR,EXTRF) ;

    して jump 。JUMPER_BIT を 出力にして L レベルにする手もあるが、そうすると今度はブートローダからリセットなしに抜けられなくなる。


ブートローダのビルドについて



ブートローダは、開始アドレスがベクター領域ごとずれる。そういうものをどうやってビルドするかについて いろんなやり方があって、参考にしたものはみんな違う。


  • USBaspLoader の方法

    普通にコンパイルして、リンクのときに 以下のオプションでアドレスを指定。

    -Wl,--section-start=.text=3800
    または、
    -Ttext 0x3800


    hex ファイルは、まったく同じになるようだ。なら昔からある -Ttext 0x3800 で良さそうなものだが、なぜか誰も使っていない。

    AT90USB162 では、--section-start がエラーになる。..と書いたが間違い。ちゃんと使えた。

  • BOOTLOADER_SECTION を使う方法

    ソースコードで関数の宣言のときに例えば、

       void bootloader(uint8_t sync) BOOTLOADER_SECTION
               __attribute__((noreturn));

    という風に BOOTLOADER_SECTION を付ける。関数定義自体にも付ける必要があるようだ。

    リンクでのアドレス指定は次のようにする。

    -Wl,--section-start=.bootloader=3800


    そういうやりかたが出来るというのは分ったのだが、こうしてしまうとライブラリが使えないように思えるし、メリットは良くわからない。


ブートローダ エミュレーション



ブートローダ領域をサポートしない AVR で、ブートローダを作るには、どうすれば良いのだろう?

実用上はともかく、これができるとデバッグするのが楽。なぜなら、本物のブートローダが使えるから。

    www.mikrocontroller.net の "Bootloader" for ATTiny2313に 置いてある ATTiny2313_bootloader が 代表的なもの。ちなみに、上のリンクには、”48-instruction one-wire bootloader for ATTiny13/24/45/85” なんて話題もある。


まず、違うアドレスから実行するようにしないといけない。これは上で説明した。

次にリセットで、ブートローダに制御を渡すように 0 番地に本物のベクタを置かないといけない。

ブートローダ実行で、0 番地にアプリケーションのベクタが書かれた場合、元のデータを変更しないようにして、別のところに置くようにする。READ も対応して透過的にしないと ベリファイでエラーになる。

あと、自分自身を書き換えないように保護する必要もある。

ちなみに、AT90USB162 は割込みを一切使わなくとも USB の処理が出来る。リセットベクタだけ保護すれば良いので大分楽。また、ベクタは、2 Word なので、相対アドレスのRJMPではなく、絶対アドレスの JMP 命令を使う。これも処理が楽になる。

もっとも重要なことを見逃していた。ブートローダー領域でプログラム実行中でないと、SPM 命令でのフラッシュの書き込みが禁止される。アプリケーションから ブートローダー領域への call はできるようなので、いんちきは出来るかもしれない。ただ、そのいんちきをするためには、自作ブートローダーが動作していないといけないので、デバッグにはあまり役立たない。

    ついでに書いておくと、AT90USB162では、後ろの 4KB が ブートローダー領域のサイズにかかわらず NRWW 領域となってて、ここに書き込む場合は、CPU が停止する。AT90USB162はあまり関係ないのだが、3,4 ms 停止するので、応答性が重要な VUSB では問題が出るかも知れない。

    ATmega88p , ATmega88pa のデータシートを見てみたが、88/168 は、NRWW = 最大ブートローダ領域は、2KB 。328 はそれが 4KB になっているようだ。VUSB では 2KB でも厳しいし普通は NRWW = ブートローダ領域と決めてしまえば良いのだろう。

    この問題、解決するかも。↓で記載。



おまけ:gawk で作る 0 番地に置くベクタを作るツール



0 番地のベクタを ビルドしたプログラムから作りたいのだが、どうしたら良いのだろう?

MinGW を前提にすれば gcc も使えるので簡単なのだが..

WinAVR の utils\bin には、いろんな UNIX 系コマンドがある。perl はないが、gawk はあるので、これでなんとかしてみることにした。ちょっと長いがスクリプトだし、参考になる時もあるかも ... ということで載せておく。


BEGIN{
hex[0] = 0; hex[1] = 1; hex[2] = 2; hex[3] = 3;
hex[4] = 4; hex[5] = 5; hex[6] = 6; hex[7] = 7;
hex[8] = 8; hex[9] = 9; hex["A"] = 10; hex["B"] = 11;
hex["C"] = 12; hex["D"] = 13; hex["E"] = 14; hex["F"] = 15;
}
{
sum = 0;
len = hex2dec($1, 2);
sum += len;
x = hex2dec($1, 4);
sum += x;
addr = hex2dec($1, 6);
sum += addr;
addr += x * 256;
rtype = hex2dec($1, 8);
sum += rtype;
for (i=0; i<len; i++) {
data[i] = hex2dec($1, 10+i*2);
sum += data[i];
}
isum = hex2dec($1, 10+len*2);
sum += isum;
sum = sum % 256;
if (sum != 0) {
printf("line %d: ihex format error!\n",NR);
exit(1);
}
if (NR == 1) {
if (data[0] == 12 && data[1] == 9 * 16 + 4) {
addr = 0;
} else {
printf("line %d: data error!\n",NR);
exit(1);
}
}
if (NR == 1 || rtype == 1) {
print_ihex(rtype, addr, data, len);
}
}

function hex2dec(str, i, x) {
x = hex[substr(str,i,1)] * 16 + hex[substr(str,i+1,1)];
return x;
}

function print_ihex(rtype, addr, data, len, sum) {
sum = 0;
printf(":%02X%04X%02X",len, addr, rtype);
sum += len;
sum += addr;
sum += int(addr/256);
sum += rtype;
for (i=0; i<len; i++) {
printf("%02X", data[i]);
sum += data[i];
}
sum = sum % 256;
printf("%02X\r\n", 256 - sum);
return sum;
}


おまけ:DFU ブートローダ 1.05 の サブルーチン



逆アセンブルしたら、SPM を使う関数一式が見つかった。

このアドレスに直接 CALL することで DEBUG できそう。
で、自作するときは、最後の方の固定のアドレスに置いておく。

... というか、最後に JUMP テーブルがあるから、なにか公開された API かも。ただ、gcc 規約ではないので注意。

    gccの規約は、ELM: アセンブラ関数の書き方 (avr-gcc)が分りやすい。引数のレジスタが違うし、r16,r17 を使っているので、ラッパーで push/pop しないといけない。あと、0 にしておく r1 を壊している。0 に再設定しないと。

ところで、rww_set_enable() 相当らしき関数が、よくわからない。11 = 1011b を書いているが、データシートには記載なし。

あと、erase してwrite というのは、何? 先に fill しておいて、atomic にやるということ?

それはともかく、erase と write は、アクセスできるようになったら戻るようになっている。アプリーケーションから使っても問題なさそう。

1FF2 : 940C 1E70 JMP L1E70 ; erase and write
1FF4 : 940C 1E94 JMP L1E94 ; LPM なので略
1FF6 : 940C 1E9C JMP L1E9C ; LPM なので略
1FF8 : 940C 1EA4 JMP L1EA4 ; page_fill(addr, data);
1FFA : 940C 1E75 JMP L1E75 ; page_write(addr);
1FFC : 940C 1E86 JMP L1E86 ; page_erase(addr);
1FFE : 940C 1EAC JMP L1EAC ; lock_bits_set(lock_bits)

;page_write(addr);
1E75 : D03C L1E75: RCALL L1EB2 ; spm_busy_wait();
1E76 : 2FF1 MOV ZH,R17 ;
1E77 : 2FE0 MOV ZL,R16 ;
1E78 : E045 LDI R20,$05 ; WRITE
1E79 : BF47 OUT SPMCSR,R20 ;
1E7A : 95E8 SPM ;
1E7B : D036 RCALL L1EB2 ; spm_busy_wait();
1E7C : D012 RCALL L1E8F ; rww_set_enable();
1E7D : 9508 RET ;

; page_erase(addr);
1E86 : D02B L1E86: RCALL L1EB2 ;spm_busy_wait();
1E87 : 2FF1 MOV ZH,R17 ;
1E88 : 2FE0 MOV ZL,R16 ;
1E89 : E043 LDI R20,$03 ; ERASE
1E8A : BF47 OUT SPMCSR,R20 ;
1E8B : 95E8 SPM ;
1E8C : D025 RCALL L1EB2 ;spm_busy_wait();
1E8D : D001 RCALL L1E8F ;rww_set_enable();
1E8E : 9508 RET ;

;rww_set_enable();
1E8F : D022 L1E8F: RCALL L1EB2 ;spm_busy_wait();
1E90 : E141 LDI R20,$11 ;
1E91 : BF47 OUT SPMCSR,R20 ;
1E92 : 95E8 SPM ;
1E93 : C01E RJMP L1EB2 ;spm_busy_wait();

; page_fill(addr, data);
1EA4 : 2FF3 L1EA4: MOV ZH,R19 ;
1EA5 : 2FE2 MOV ZL,R18 ;
1EA6 : 2E01 MOV R0,R17 ;
1EA7 : 2E10 MOV R1,R16 ;
1EA8 : E041 LDI R20,$01 ; FILL
1EA9 : BF47 OUT SPMCSR,R20 ;
1EAA : 95E8 SPM ;
1EAB : C006 RJMP L1EB2 ;

; lock_bits_set(lock_bits)
1EAC : D005 L1EAC: RCALL L1EB2 ;
1EAD : 2E00 MOV R0,R16 ;
1EAE : E029 LDI R18,$09 ;BLBSET | 1
1EAF : BF27 OUT SPMCSR,R18 ;
1EB0 : 95E8 SPM ;
1EB1 : C000 RJMP L1EB2 ;


; spm_busy_wait();
1EB2 : 2E02 L1EB2: MOV R0,R18 ;
1EB3 : B727 IN R18,SPMCSR ;
1EB4 : FD20 SBRC R18,0 ;SPMEN
1EB5 : CFFC RJMP L1EB2 ;

1EB6 : 2D20 MOV R18,R0 ;
1EB7 : 9508 RET ;

; rww_busy_wait();
1EB8 : 2E02 L1EB8: MOV R0,R18 ;
1EB9 : B727 IN R18,SPMCSR ;
1EBA : FD26 SBRC R18,6 ;RWWSB
1EBB : CFFC RJMP L1EB8 ;

1EBC : 2D20 MOV R18,R0 ;
1EBD : 9508 RET ;
posted by すz at 17:19| Comment(0) | TrackBack(0) | USBasp関係
この記事へのコメント
コメントを書く
お名前: [必須入力]

メールアドレス: [必須入力]

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/33798299
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック