概要
JIS配列のキーボードをPro MicroにつなげてほぼNICOLA(親指シフト)キーボード化するプログラムを作っている中で、誤って動作周波数の違うプロセッサ用のスケッチを書き込んでしまった。結果としてUSBデバイスとして認識されなくなったので修復したお話。
発端
ほぼNICOLAなキーボードを実現するアダプタを作るため、以下の2種類のPro Microを使っていた。
- USBキーボード版
+3.3V動作のmini UHSボードを使うので+3.3V/8MHz版のPro Microを使う(上の写真)。 - PS/2キーボード版
+5Vを必要とするPS/2キーボードを直接接続するので、+5V/16MHz版のPro Microを使う(下の写真)
2つのPro Microの相違は、ボード上のLDOの出力電圧と発振器の周波数だけである。
USB版とPS/2版がそれなりに動くようになり、両者に共通する修正を入れてスケッチを書き直しているとき、Arduino IDEのツール/プロセッサを ATmega32u4(+3.3V, 8MHz)にしたまま+5V版に書き込んでしまった。同じPCにPro Microが2つも3つもつながっていたので、ついシリアルポートの選択を忘れてしまったということ。
この結果、対象のPro MicroはPCからUSBデバイスとして認識されなくなってしまった。なので、HIDキーボードとしてもシリアルデバイスとしても機能しなくなった。こうなってしまうと、Arduino IDEからスケッチを書き込むことができなくなるから、よく言われる文鎮化、煉瓦化した一口羊羹程度の不動のマイコンボードになってしまった。
これら2種類のボードは、(少なくとも自分には)肉眼で識別できないから、いつかまた同じことをするかもしれない。今後にそなえて修復した記録を残しておくことにする。
なぜ動かないのか
まずは、動かなくなった理由を考えてみた。
ブートローダは壊れていないはず
スケッチの大きさはさほどでもないので、ブートローダ領域を壊してしまった、ということはないだろう。
Sparkfun社がPro Micro用に公開しているcaterinaブートローダは、プログラムメモリ(フラッシュメモリ)空間の 0x7000 から約4KBytesに渡って格納されているのに対し、ビルド中のスケッチのテキスト領域のサイズは14Kバイトにも満たない。最大アドレスは0x368aだった。
Pro Microのスケッチやブートローダについてちょっと調べてまとめた。
フラッシュ内でのプログラム最終アドレス
Arduino IDEでビルドすると以下のようにスケッチの大きさが表示される。
1 2 |
最大28672バイトのフラッシュメモリのうち、スケッチが13962バイト(48%)を使っています。 最大2560バイトのRAMのうち、グローバル変数が573バイト(22%)を使っていて、ローカル変数で1987バイト使うことができます。 |
これは、avr-size というコマンドの出力をまとめたもので、例えばPS/2キーボード版のスケッチ( ps2_hobo_nicola.ino)をビルドし、得られた.elfファイルをこのコマンドにかけると、以下のように出力される。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
C:\Users\arduino\AppData\Local\Temp\arduino_build_898012>avr-size -A ps2_hobo_nicola.ino.elf ps2_hobo_nicola.ino.elf : section size addr .data 310 8388864 .text 13652 0 .bss 263 8389174 .comment 17 0 .note.gnu.avr.deviceinfo 64 0 .debug_aranges 280 0 .debug_info 40571 0 .debug_abbrev 8256 0 .debug_line 10651 0 .debug_frame 2788 0 .debug_str 9044 0 .debug_loc 23095 0 .debug_ranges 1576 0 Total 110567 |
Arduino IDEがフラッシュメモリの使用量(スケッチのサイズ)として表示したのは、このうちの、.textと.dataの2つのセクションの合計値で、RAMの使用量は.dataと.bssの合計値になっている。
.dataセクションには初期値をもつ変数データが格納されるので、あらかじめ各変数の初期値はフラッシュに入っており、スケッチの開始に先立ってRAMにコピーされる。
したがって、フラッシュ内での最終アドレスは、.textと.dataの2つのセクションの合計値をHEXに直して、0x368a番地ということになる。
あるいは、avr-nmやavr-objdumpといったコマンドによってもデータやプログラムの配置(アドレス)情報を得ることができる。
ブートローダの開始アドレス
caterinaブートローダの開始アドレスはMakefileを参考にすると以下のようなシェルスクリプトで得ることができる。
1 2 3 4 5 |
#!/bin/sh FLASH_SIZE_KB=32 BOOT_SECTION_SIZE_KB=4 BOOT_START=`echo "obase=16; ($FLASH_SIZE_KB - $BOOT_SECTION_SIZE_KB) * 1024" | bc</code> echo '0x'$BOOT_START |
実行すると0x7000 となるから、この後ろまでスケッチが被さったということはなさそうである。なお、上記のBOOT_SECTION_SIZE_KBは、FUSEに書かれている内容によって変更する必要がある。
ブートローダの大きさはFUSEで規定
ブートローダ用セクションのサイズはATmega32u4のHFUSE (FUSEハイバイト)に書かれていて、データシートに書かれているデフォルト値と、SparkFun Pro Micro用のHFUSEは以下のようになっている。
1 2 3 4 |
bit7 bit0 OCDEN JTAGEN SPIEN WDTON EESAVE BOOTSZ1 BOOTSZ0 BOOTRST Default 1 0 0 1 1 0 0 1 SparkFun 1 1 0 1 1 0 0 0 |
SparkFunのPro Micro用HFUSEの設定は以下のようになっている。
- OCDは禁止
- JTAGは禁止
- SPIによるプログラミングは許可
- WDT常時許可は禁止(ウォッチドッグタイムアウトで常時リセットは禁止)。
- EESAVEは許可(チップイレース時にEEPROM内容を保存)。
- BOOTSZ1-0が”00”なので、 ブートローダサイズは2K ワード(4KBytes)。
- BOOTRSTが0なので、リセット時のブートローダ起動が許可。
このように設定されているので、ブートローダのサイズや開始アドレスは以下のように決まる(データシートの、27.7.14 Boot Loader Parameters を参照)。
- アプリケーション領域のアドレス範囲:0x0000~0x6fff (28KBytes)
- ブートローダ領域のアドレス範囲:0x7000~0x7fff (4KBytes)
- ブートリセットアドレス:0x7000
そしてBOOTRSTがプログラムされているので、ハードウェアリセットやウォッチドッグリセットがかかると、0番地ではなく0x7000番地のコードに制御が移りブートローダが開始する。
ブートローダ内での処理が終わったり、所定の時間内に所定の通信データを得られないときには、0番地にジャンプしてユーザーのスケッチに制御を移すようになっている。
ブートローダが不要ならば、BOOTRSTを1にしてやればよいだろう。そうした場合、リセットがかかると0番地から実行が開始することになっている。
USB接続を初期化できないことが原因
単純に考えると、スケッチとリンクされて書き込まれたUSBインタフェース用のプログラムは、システムクロックが8MHzのつもりでハードウェアを初期化したり、プログラムでタイミングをとったりするのに、実際にマイコンにつながっている発振器は16MHzである。通信用のクロックさえも正しく設定できないだろう。つまり、書き込まれたプログラムではUSBの通信が成り立たない。
スケッチ転送の仕組み
書き込んだスケッチ内にはUSB通信用の機能がリンクされており、そこには仮想的なシリアルポートを作り出すCDCプロトコルを処理する機能が入っている。CDC.cppには、ある特定の条件(1200bpsでのオープン/クローズ)を検出したとき、ブートローダに制御を移すためのウォッチドッグリセットをかけるような仕組みがある。
ふつうにスケッチを転送しようとするときには、Arduino IDEは仮想シリアルポートを制御してPro Microにブートローダを開始させる。ブートローダのプログラムがUSB接続を初期化しCDCプロトコルを確立すると、やがてスケッチの転送が開始しブートローダによる書き込みが行われる。
残念ながらスケッチが実行を開始すると正しく初期化できず、USB接続できなくなるわけだから、USB経由で何かする仕組みは一切無効、全部ダメ、ということになる。
ブートローダモードにして修復
今回の場合、よく言われるような「ブートローダを入れ直す」ことが解決策ではないようである。問題があるのは書き込んでしまった(前提となるクロック周波数の異なる)スケッチプログラムであってブートローダではないのだから。ブートローダ領域には、もともと正しいクロック周波数用にビルドされたプログラムが残っているはず。
で、SparkFun社のFAQページに書いてあるように、ハードウェアリセット(RESETをGNDに接続)することで、修復することにした。Pro MicroにはリセットスイッチはついていないがRESET端子はあるから、ブレッドボード用のワイヤを使ってGNDにショートすることでハードウェアリセットをかけることができる。
RESET端子をすばやく2回GNDに接続してやることで、約8秒間にわたってブートローダモードが維持され、フラッシュ内のスケッチに制御が移らない。その間は、ブートローダプログラム(caterina) によってUSB接続が確立されCDCプロトコルが有効になるので、その8秒間に正しいスケッチを書き込んでやる。GNDへ接続するのが一度だけ(つまり、通常のリセット操作やパワーオン時)だと、750msecでブートローダは終了し、ユーザープログラム(スケッチ)に制御が移ってしまう。
Arduino IDEに対する書き込みの指示と、Pro Microに対してRESETを2回かけるタイミングが難しいものの、何度かやったらうまくいった。そのときのArduino IDEに表示された出力ログは以下の通り。ファイル/環境設定で より詳細な情報を表示する をチェックしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
.....(ビルドが終了) 最大28672バイトのフラッシュメモリのうち、スケッチが13962バイト(48%)を使っています。 最大2560バイトのRAMのうち、グローバル変数が573バイト(22%)を使っていて、ローカル変数で1987バイト使うことができます。 PORTS {COM19, COM29, } / {COM19, COM29, } => {} PORTS {COM19, COM29, } / {COM19, COM29, } => {} PORTS {COM19, COM29, } / {COM19, COM24, COM29, } => {COM24, } Found upload port: COM24 C:\Program Files (x86)\Arduino\hardware\tools\avr/bin/avrdude -CC:\Program Files (x86)\Arduino\hardware\tools\avr/etc/avrdude.conf -v -patmega32u4 -cavr109 -PCOM24 -b57600 -D -Uflash:w:C:\Users\arduino\AppData\Local\Temp\arduino_build_701908/ps2_hobo_nicola.ino.hex:i avrdude: Version 6.3-20171130 Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/ Copyright (c) 2007-2014 Joerg Wunsch System wide configuration file is "C:\Program Files (x86)\Arduino\hardware\tools\avr/etc/avrdude.conf" Using Port : COM24 Using Programmer : avr109 ... |
このログを見ると、PORTS { } の中にCOM24が出現した時点でアップロード先のポートを見つけ、avrdudeによる書き込みが開始しているのがわかる。COM24は、ブートローダモードのPro MicroによるUSB接続(CDCモード)をWindowsが検出して作成している。
Arduino IDEでスケッチ/マイコンボードに書き込む を実行するとビルドされて書き込みが始まるが、RESET操作はビルドがけっこう進んだあたりでやるとうまくいく。
何度か失敗を繰り返していたとき、ツール/シリアルポートにCOM24は現れたり消えたりしたわけだが、とりあえずシリアルボートがブランクの状態で書き込みを開始してやり、リンクが始まったあたりでRESETを2回GNDに接続してやると、タイミングとしてちょうどよかったようだ。転送が成功してスケッチが開始するとCOM24は消えて、スケッチ内のCDC機能による別のCOMポートが出現した。
クロック間違え程度ならば、ISPを使うことなく修復できることが確認できた。
きょうのまとめ
- プロセッサ(周波数)の選択間違いによってPro Microが認識されなくなった場合は、RESET端子とGNDを接続してやることで修復できる。
- 同様のトラブルにあった場合、ブートローダだけを書き直しても意味がない。Arduino ISPを利用するならばavrdudeに -e オプションを与えてフラッシュのクリアを実行してやるべきだろう。当然ながら、その後ブートローダとスケッチをフラッシュに転送してやる必要がある。
- あるいは、avrdudeを使ったブートローダ書き込み時に -D オプションを指定しないことで、フラッシュをクリアしてからブートローダプログラムの書込みを行うようにする。
Arduino IDEからのスケッチの書込み時( avrdudeが起動している )には -D オプションが指定されているので、ブートローダは消えない。 - 実のところ、Arduino ISP と avrdude を使った修復も実施した。その後調べているうちに、RESET端子を2回接地する方が簡単なことが分かったのでわざわざそれを確認した、というお話でした。