Arduinoの互換機っぽい物を作ろうと、「ATmega328P-PU(ブートローダ付きARDUINO UNO R3に適合)」という単体LSIを購入しました。
一応、商品名には、「ブートローダ付き」を謳っているので、例えば、いつも使っているUNOのATmega328P-PUをソケットから引っこ抜いて、新たに購入したこのLSIを代わりに挿し込めば、普通にUNOとして使えるはず……、でした……。
結論から言うと、そう上手くは行かなかったのです……。今回は、そんな奮戦記です。
さすがに、いきなり「UNOのLSIを引っこ抜き、新しいLSIを挿して試してみる」のは、あまりに乱暴なので、まずは、「ちゃんとブートプログラム、書かれてますね?」から、確かめて行こうと思いました。そこで、ATmega328のデータシートに記載のあった、シリアルプログラミングモードでフラッシュの中身を読み出してみようと考えました。
シリアルプログラミングモードは、ATmega328のRESET端子をLOWにアサート中に、SPI端子を使ってATmega328の内部情報(ヒューズビットやフラッシュ、EEPROMなど)へアクセスする仕組みのことで、通常は、AVRプログラマを使ってアクセスします。
AVRプログラマを検索する(別ウインドウが開きます)
もし、AVRプログラマをお持ちなら、本ページの作業は、ほぼ、不要なので、読み飛ばしてしまってください(1^^;)。一々AVRプログラマとか、購入していられないので、自分でサブセットを作ってしまおう、と言うのが本ページの趣旨となります。
そう、これまでにも何度か使ってきたSPI通信なので、Arduinoがあればアクセス可能なのです。
ここから先は、手持ちのArduinoをAVRプログラマ代わりに使用して、対象のLSIにシリアルプログラミングモードでアクセスしてみます。役割を明確にするため、以下の様に呼称します。
後ほど、実験する時に結線を示しますが、基本的に繋ぐだけですので、早速、プログラムを設計して行きます。
以下、今回のプログラム(ser_prog.ino)の目標仕様をまとめておきます。
後は、ATmega328データシートの「Memory Programming」章の「Serial Downloading」節に従って、コーディングして行くだけだったので、ポイントだけ、箇条書きにしておきます。
SPIの使用方法等については、「逆引き索引」から、SPIを取り扱った過去記事を参照してください。上述以上の細かい解説は省略します。以下にリンクする完成プログラムを参照してください。
完成プログラム:ser_prog.ino
今回は、プログラマにArduino Pro Miniを使用するので、プログラム(ser_prog.ino)をコンパイルして書き込んでおきます。シリアルモニタから、以下のコマンドを受け付けるようになっています。
全て、16進数のa〜fは小文字です。
購入したATmega328P-PUの中身を覗きたい気持ちをぐっと抑えて、Arduino UNOに搭載されているATmega328の内部情報にアクセスしてみます。この情報が「期待値」となります。
以下の様に結線します。
プログラマ (Pro Mini) |
接続 |
ターゲット Arduino UNO |
VCC | × | Vcc |
GND | ⇔ | GND |
D10 | → | RESET |
D11(MOSI) | → | D11(MOSI) |
D12(MISO) | ← | D12(MISO) |
D13(SCK) | → | D13(SCK) |
※各々、USBなどでPCと接続して、電源を確保してください。よって、Vcc同士は接続してはいけません。ターゲットとプログラマの動作電圧が異なる場合には、適切にL/Sなどを挿入してください。
※ターゲットには、D11,D12,D13を出力にしていないような適切なプログラムを書き込んでおいてください。
ターゲットが、常用しているArduino UNOとなるので、プログラマとしてはPro Miniを使いました(下図左)。Pro Miniには、USB I/Fが付いていないので、USB⇔UART変換モジュール(FTDI)でインターフェイスしました(下図右)。
UNOを2機お持ちの方は、プログラマ側もUNOで大丈夫です。
以下、実験中の様子です。
ターゲットとなるUNOの方は、ICSPピンヘッダを使って接続しています(上図)。ICSPピンヘッダは、AVRプログラマとの接続に使う端子が以下の様な配列でまとまって配置された端子です。
端子名 | ピン番号 | 端子名 | |
D12(MISO) | 1 | 2 | VCC(5V) |
D13(SCK) | 3 | 4 | D11(MOSI) |
RESET | 5 | 6 | GND |
UNOには、2つのICSPピンヘッダがあり、一方はUSB⇔UART変換チップATmega16U2用です。メインMCUであるATmega328用のICSPピンヘッダに接続します。フォームファクタが統一されているとは言え、互換機の場合には、設計次第で多少配置が異なる場合も考えられますので、良く確認してから使いましょう。不明な場合は、明快なD11、D12と言ったピンソケットに接続するのが確実です。
プログラマとターゲットを接続したら、プログラマ(Pro Mini)のシリアルモニタを開きます。
実験サンプル:【 】内はシリアルモニタからの送信データ
OK: serial programming mode
OK: ATmega328P detected
RDY
【dmpb】
OK+ fuse: 0xfd 0xde 0xff
OK+ lock: 0xff
OK+ cali: 0x96
OK: sign: 0x1e 0x95 0x0f 0x00
各設定データが読み出せました。
ヒューズビットについては、ビット毎に意味が定義されているので、細かく見ておきます。
Arduino UNO(互換機)搭載ATmega328のヒューズ値 | |||||
Fuse | data | bit | name | value | memo |
E | 0xfd | 7 | − | 1 | 未定義 |
6 | − | 1 | |||
5 | − | 1 | |||
4 | − | 1 | |||
3 | − | 1 | |||
2 | BODLEVEL2 | 1 |
BOD:Brown-Out Detection (低電圧検出機能) BODLEVEL=2.7V(TYP) |
||
1 | BODLEVEL1 | 0 | |||
0 | BODLEVEL0 | 1 | |||
H | 0xde | 7 | RSTDISBL | 1 | 外部RESET端子有効 |
6 | DWEN | 1 | デバッグワイヤ無効 | ||
5 | SPIEN | 0 | シリアルプログラム有効 | ||
4 | WDTON | 1 | WDTCSRで制御 | ||
3 | EESAVE | 1 | チップイレースでEEPROM消去 | ||
2 | BOOTSZ1 | 1 | ブート領域は256×16ビット | ||
1 | BOOTSZ0 | 1 | |||
0 | BOOTRST | 0 | ベクタはブート領域 | ||
L | 0xff | 7 | CKDIV8 | 1 | システムクロックを分周しない |
6 | CKOUT | 1 | クロック出力禁止 | ||
5 | SUT1 | 1 | 水晶発振子、Slowly rising power | ||
4 | SUT0 | 1 | |||
3 | CKSEL3 | 1 | 8〜16[MHz]低電力水晶発振モード | ||
2 | CKSEL2 | 1 | |||
1 | CKSEL1 | 1 | |||
0 | CKSEL0 | 1 |
買って来たATmega328P-PUを使って、UNOの互換機っぽい物を作ろうとするなら、上の設定値が期待値となります。
いよいよ、購入したATmega328P-PUの内部情報にアクセスしてみます。プログラマには、前節から引き続き、Pro Miniを使用します。
生のATmega328P-PUと接続するには、以下の様な簡単な回路を構成する必要があります。
プログラム(ser_prog.ino)に戻りますが、8ビットタイマ(TC2)を使って、8MHzのPWM(デューティ50%)を出力しています。TC2の設定は以下の通りです。
レジスタ | アドレス | 設定値 | 備考 |
TCCR2A | 0x00b0 | 0x23 |
fast pwm OCR2A mode 7, OC2B=clear OCR2B & set bottom |
TCCR2B | 0x00b1 | 0x09 |
fast pwm OCR2A mode 7, fcpu/1 |
OCR2A | 0x00b3 | 0x01 | 周期(=1/周波数)、周波数8MHz |
OCR2B | 0x00b4 | 0x00 | デューティ50% |
ATmega328で出力できる最速のPWMとなります。これを8MHzのクロックとして使います。
※このクロック出力、単体のATmega328P-PUに対して、シリアルプログラミングモードで簡単にアクセスするための小技だったのですが、後で(ブートローダの書き込みをする時に)失敗に気が付きます……。まあ、致命的な失敗ではないので、このまま進めます。
では、実験してみましょう。以下、実験中の様子です。
プログラム(ser_prog.ino)を書き込んだPro Miniのシリアルモニタで、以下の様に実験を進めます。
実験サンプル:【 】内はシリアルモニタからの送信データ
【dmpb】
OK+ fuse: 0xff 0xd9 0x62
OK+ lock: 0xff
OK+ cali: 0x9c
OK: sign: 0x1e 0x95 0x0f 0x00
内蔵RC発振器のキャリブレーション値(cali)は、0x9cでした。チップ毎に異なる可能性のあるビットですので、やはり、さっきのUNO搭載ATmega328(0x96だった)とは違う値が読めましたね。
ん?ヒューズ値が全くUNOとは異なっています。シグネチャビットは正しく読み出せている様なので、回路やプログラム(ser_prog.ino)に間違いはなさそうです。UNOの時と同様に、ビット値を読み解いてみましょう。
ATmega328工場出荷時のデフォルトヒューズ値 | |||||
Fuse | data | bit | name | value | memo |
E | 0xff | 7 | − | 1 | 未定義 |
6 | − | 1 | |||
5 | − | 1 | |||
4 | − | 1 | |||
3 | − | 1 | |||
2 | BODLEVEL2 | 1 | BOD無効 | ||
1 | BODLEVEL1 | 1 | |||
0 | BODLEVEL0 | 1 | |||
H | 0xd9 | 7 | RSTDISBL | 1 | 外部RESET端子有効 |
6 | DWEN | 1 | デバッグワイヤ無効 | ||
5 | SPIEN | 0 | シリアルプログラム有効 | ||
4 | WDTON | 1 | WDTCSRで制御 | ||
3 | EESAVE | 1 | チップイレースでEEPROM消去 | ||
2 | BOOTSZ1 | 0 | ブート領域は2,048×16ビット | ||
1 | BOOTSZ0 | 0 | |||
0 | BOOTRST | 1 | ベクタはアプリケーション領域 | ||
L | 0x62 | 7 | CKDIV8 | 0 | システムクロックを1/8分周 |
6 | CKOUT | 1 | クロック出力禁止 | ||
5 | SUT1 | 1 | Slowly rising power | ||
4 | SUT0 | 0 | |||
3 | CKSEL3 | 0 | 内蔵RC発振器8MHz | ||
2 | CKSEL2 | 0 | |||
1 | CKSEL1 | 1 | |||
0 | CKSEL0 | 0 |
※UNOのヒューズ値と異なっている箇所に着色(ピンク)してあります。
なるほど。データシートと突き合わせてわかりました。これ、ATmega328P-PUの工場出荷時デフォルト値です。何だか嫌な予感が……。ブートローダが書き込まれているかどうか、Flashメモリをダンプしてみます。
実験サンプル:【 】内はシリアルモニタからの送信データ
【dmpf,0,7ffff】
addr: +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f
0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
0030: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
:
: (中略)
:
7fc0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
7fd0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
7fe0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
7ff0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
OK: dump flash 0x0000~0x7fff
やってくれました。見事に、all 0xffでした。ブートローダ、書き込まれていません……。「ブートローダ書き込み済み」って書いて売ってたじゃない!嘘つき!w(1x🐽x)w
結果、購入したATmega328P-PUは、真っ新なデフォルト出荷状態で、ブートローダも書き込んでないし、ヒューズ値すらデフォルト状態だとわかりました。騙された……。まあ、いいや、ブートローダくらい、簡単に書き込めるから(1^3^;)。
ここから先は、先輩たちの記事が沢山ありますので、検索してみてください。多分、そちらの方がスマートなはずです。
ブートローダの書き込みを検索する(別ウインドウが開きます)
本ページでは、上で示した回路図でブートプログラムを書き込んでみます。この回路だと、クロック発振回路が付いていないので、上で検索したようなスマートな方法では、書き込めないのですよ……。こうなるなら、ちゃんとクロック発振回路を作り込んでおけば良かったです。
まあ、進めましょう。
以下、ブートローダの書き込み手順です。すぐ上で検索した先輩たちの記事の方がスマートなので、皆様にはそちらを推奨します。よって、ここでは、簡単に手順だけ書きます。
さて、確認です。Pro Miniにプログラム(ser_prog.ino)を書き込み直して、ATmega328P-PUの内部情報にアクセスしてみましょう。ボード設定を「UNO」に変更してしまったはずなので、忘れずに「Pro or Pro Mini」に設定してからコンパイル&書き込みます。
実験サンプル:【 】内はシリアルモニタからの送信データ
【dmpf,0】
addr: +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f
0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
OK: dump flash 0x0000~0x000f
Flashメモリのアプリケーション領域は何も書き込まれていないことを示すall 0xffみたいです。
【dmpf,7e00】
addr: +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f
7e00: 11 24 84 b7 14 be 81 ff f0 d0 85 e0 80 93 81 00
OK: dump flash 0x7e00~0x7e0f
UNOのブート領域は0x7e00〜0x7fff番地(512バイト)なので、その先頭アドレス付近を読み出しました。何かプログラムが書き込まれています。先ほどのブートローダの書き込みが成功しているなら、ブートローダの一部でしょう。良かった。続けます。
【dmpb】
OK+ fuse: 0xfd 0xde 0xff
OK+ lock: 0xcf
OK+ cali: 0x9c
OK: sign: 0x1e 0x95 0x0f 0x00
おっと!ブートローダを書き込んだら、ちゃんとヒューズ値も設定してくれた様です。ありがたや。
ロックビット(lock)が0xcfになっているので、ブート領域が再プログラム(再書き込み)できないようにロックされています。ただ、シリアルプログラミングモード(AVRプログラマやArduinoISP.ino等)で「チップイレース」を実行すると、消去して再書き込みできてしまうので、普段は気にする必要はありません。恐らく、フィールドで誤って消去してしまわないようにするためのビットだと思われます。
※ブートローダの書き込みで、ヒューズビットを設定してもらえたので、今回ser_prog.inoに実装した、ヒューズへの書き込みコマンド(【wrfe】/【wrfh】/【wrfl】)は未使用でした。ざっと動作検証はしたので、使えると思います。
今回のお買い物は、以下の通りです。
上に記載したのが、商品名でした。中華ショップで購入したので、日本語が若干怪しいです。「ブートローダ付き」 ⇒ 「ブートローダ書き込み済み」と、勝手に読み替えたのだけど、結果としては、真っ新な工場出荷状態のLSIでした。まあ、安かったし、配送料無料で届けてもらったので、「全然OK」ではあります。
ただ、嘘はいかんよ、嘘は……。もし、自分にとって「ここは譲れない」ってところで嘘をつかれて、届いた商品が違っていたら、どうします?普通は返品ですよね?でも、中国向け着払いの荷物って、どこでどうやって送るのか??たぶんひと手間かかりますよ。
中華ショップは国内のショップに比べると安いけど、トラブルが起きた時の対処はとっても面倒なので、心して使わないとダメですね。まして、他ショップと比べて値段が高くなると、もう、魅力がありません。前から使っていた、「EasyWordMall」というショップ(#4-1,#4-7,#7-4、等)は、国内配送に切り替わったり、配送料が一律上乗せされたりして価格が高くなったので、今回は別なショップを使いました。
もうちょっと楽ができる予定だったのだけど、何だか、ずいぶんと手間をかけさせられてしまいました。「ブートローダ書き込み済み」に騙されました。お陰で、今回はここ止まりです。次は、UNO互換機を最小構成で構築してみたいと思います。