SNSへはこちら

AVRマイコンでブートローダーを自作(1) - お試しブートローダー作成

AVR マイコンをもっと知りたい!ということでこのシリーズを立ち上げます。

訂正(2021/8/10):
コードにアセンブリコードに誤りがありました。訂正いたします。

AVRにおけるブートローダー

最近の 32bit マイコンとかはマスク ROM か何かに USB ブートローダーや UART ブートローダーが搭載されています。
一方で 8bit マイコンである AVR マイコンは SPI による書き込みが可能ですが、これとは別に設定によってブートローダーを自作することが可能です!本記事シリーズではオレオレ仕様のブートローダーを作成し、そこ経由でプログラムを書き込むことを目的にまったりやっていきたいと思います。

その前に

予備知識です。

  • ブートローダー領域は最大でも 2k words (4k bytes)
    • 機能を盛り盛りにして多機能なブートローダーを作ろうとすると多分きついです。
    • アセンブリで記述する分には良いと思います。C言語だとひょっとしたらサイズが足りないかも...
  • プログラム領域(Flash)は16ビット幅
    • 例えば、0x0000 というアドレスには 16bit 分のデータが書ける。
    • 一般的なマイコンや PC とは異なっているのに注意(通常はあるメモリ番地にあるデータは 8bit)
  • AVR の各領域(SRAM, Flash, EEPROM, I/O Register)は分離されていて、それぞれ別のメモリ空間に位置する
    • 例えば現代のマイコンのように「0x0000」と言ってもそれはどの領域の先頭番地なのかわからない

Fuse Bitによるブートの差異

色々と説明すべきことはあるのですが、今回はブートローダープログラムへジャンプするのに必要な Fuse Bit の設定のみ述べます。
ただし、Fuse Bit をいじるのは値を間違えたら最悪二度とマイコンが書き込みを受け付けなくなる場合があるということに注意してください。まあぶっ壊したら新しいのに交換すれば良いんだけどね。(でも昨今は半導体不足で秋月に在庫がないらしい)

BOOTSZ - ブートローダーサイズを決定

High Byte にある BOOTSZ ビット(2ビット幅)を設定すればブートローダーサイズを決めることができます。
詳しくはデータシート参照ですが、ここでは NRWW (後に述べます)領域の関係で BOOTSZ == 00 とします。

これにて、ブートローダー開始アドレスが 0x3800 になりました。

BOOTRST - リセット後のジャンプ先を決定

マイコンリセット後にどのプログラムメモリ番地にジャンプするかを決定するビットです。

ここを 0 にすると、ブートローダーにジャンプします。上の場合は 0x3800 ですね。逆に 1 の場合は通常のブートアドレス 0x0000 にジャンプするはず。
今回はここの値を弄ることで挙動を変えてみようとするお試しブートローダー(もどき)を作成します

なお、Arduino では必ずブートローダーにジャンプしているものだと考えられますArduino UNO の回路図によると、PC から書き込み処理を行うときに DTR として必ずリセット信号が入っているものだと考えられます。
そのリセット後にはブートローダーが起動し、おそらく次の処理をブートローダープログラムが実行しているのではないでしょうか:

  • ブートローダー起動
  • PC から特定のデータ(「これからデータを書くよ」的な定数値)が送られるまで待機
  • 送られたら引き続きブートローダーの内容を実行
  • 送られずに一定時間が経過したら、ユーザープログラムの先頭番地である 0x0000 にジャンプし、通常のプログラムを実行

こうやって、ブートローダーを仕込んだときは通常のプログラム実行 or ブートローダー続行を分岐する内容に必ずします。

試しに書いてみよう

それではブートローダーもどきを作ってみましょう。ですが今回は入門ということでブートローダーらしからぬ L チカプログラムとします。

僕は AVR マイコンを C 言語で書く方法を知らないので、AVR アセンブリします。

; boot_test.asm

.include "../m328pdef.inc"
.device atmega328p

.def tmp = r16

.cseg
.org 0x0000

sbi DDRB, 1
sbi PORTB, 1
loop1:
jmp loop1

.org 0x3800

    ldi tmp, (0b11 << COM1A0) | (0b10 << WGM10) ; fast PWM for PB1, High on match
    sts TCCR1A, tmp
    ldi tmp, (0b100 << CS10) | (0b11 << WGM12) ; division by 256, match by OCR1A
    sts TCCR1B, tmp

    ; when counter matches OCR1A, PB1 is set.
    ldi tmp, high(6250 - 1) ; per 200msec
    sts OCR1AH, tmp ; higher
    ldi tmp, low(6250 - 1) ; per 200msec
    sts OCR1AL, tmp ; lower

    ; when counter matches ICR1H, V=1 and counter will be reset in the next clock.
    ldi tmp, high(15625 - 1) ; per 500msec
    sts ICR1H, tmp ; higher
    ldi tmp, low(15625 - 1) ; per 500msec
    sts ICR1L, tmp ; lower

    sbi DDRB, PB1
loop2:
    jmp loop2

軽く説明します。

まず、通常のユーザープログラムコードのリセットアドレスは 0x0000 です。ここで通常のブートをした際は PB1 が High になります。すなわち LED が点くということです。
続いてブートローダーを Fuse Bit で有効化した場合は 0x3800 にブートします。このときは LED がチカチカします。Duty 50% じゃないけどね。もっというとこれ、タイマーで PWM(CTC) モードを使用しています

...このようにブートローダーがちゃんと起動しているか確認する意味で挙動を変えただけのバイナリを書き込みます

動作条件・書き込み

ところで皆さんのお持ちの AVR ATmega328P はどうなっていますか??まずはその条件を確認しておきます。

  • 内部 RC オシレータ 8MHz 動作
    • 内部分周なし
  • BOD 無効
  • メモリ保護無効

というガバガバ動作条件で行きます。動作スピードとかこの際関係ないよ。
また、このときの書き込みアダプタは秋月の FT232RL モジュールを使用していて、詳しい結線はこちらを参照ください。

その前に、まず以上の条件を盛り込んだデフォルトの Fuse Bits を書き込みましょう。

$ avrdude -c ft232r -p m328p -U lfuse:w:0xC2:m -U hfuse:w:0xD9:m -U efuse:w:0xFF:m -U lock:w:0xFF:m

これでヨシ!文鎮化はしないはずです。

それでは上に書いたアセンブリ言語をバイナリにして書き込みましょう!!

$ avra boot_test.asm
$ avrdude -c ft232r -p m328p -U flash:w:boot_test.hex

これで LED が点くだけの動作になっていれば所望どおりです。BOOTRST は HFUSE の 0 ビット目なので、たしかに通常のブートアドレスにジャンプしています

ここで、BOOTRST を 0 にしてブートローダーもどきを起動してみましょう。

$ avrdude -c ft232r -p m328p -U hfuse:w:0xD8:m

これで LED がチカチカするはず!

以上で Fuse Bit によるブートローダーリセットの挙動を確認しました。
会社生活がドタバタして忙しいですが、ゆっくりとオレオレブートローダーの開発を勧めていきたいと思います。