📝

ATmega328PをArduinoなしで使ってみる

2021/03/13に公開
2

PICをやっていた感覚でArduinoで遊んでいると、いろいろ便利な仕組みが整備されているので、細かいことを考えなくてもいい反面、地に足がついてないというか、「これはマイコンなんだっけ」という根本的なところの理解があやふやな自分に気づく。

たとえば、Arduino Unoに載っているATmega328Pというマイコンは、原理的にはArduino Unoに載せずに単体で動かせるはずだけど、どうやったら動かせるのかよくわからない。

さらにいうと、Arduino IDEやArduino言語を使わずにプログラムを書けるはずだけど、どこから調べ始めればいいかよくわからない。

というわけで、ATmega328Pをスタンドアロンでやっていくメモ。

ATmega328PはAVRマイコンである

根本的なところからはじめると、ATmega328PはAVRマイコンなので、AVRの開発環境でプログラミングし、コンパイルしたバイナリをマイコンに書き込めばよい。だから、コンパイルしたhexファイルをマイコンにフラッシュする装置を用意し、それを動かすソフトウェアをパソコンにインストールすれば、ATmega328P上で動くプログラムを開発できる。なお、これらの装置とソフトは一般に「ROMライター」とか「ライター」などと呼ばれている。

ここで面白いのは、Arduinoでは、ライターを使わずにマイコン上で動くようにコンパイルしたプログラムをマイコンに書き込めるところだ。つまり、「コンパイルしたhexファイルをマイコンにフラッシュする装置」も、そのためのソフトウェアも必要なく、USBケーブルでパソコンとつなぐだけでAVRマイコン用のプログラムを転送できる。これがArduinoの気軽さであり、すごいところだといえる。

この気軽さは、Arduinoからマイコンを始めると、べつだん不思議なことではない。しかし、「マイコンとしてのATmega328PはArduino Unoなしでも動く」といった根本的な話は、むしろこの気軽さによってわかりにくくなっているようにも思う。もちろん、それがわからなくてもマイコンを使った何かを開発できるのがまさにArduinoのすごいところなので、これがわかりにくくなっていることは、当たり前といえば当たり前である。

Arduinoのブートローダー

というわけで、マイコンの勉強にとってArduinoがわかりにくいのは、その気軽さを支える「コンパイルしたhexファイルをマイコンにフラッシュする装置が必要ない」という特徴と表裏一体だ。なぜ、ArduinoをUSBケーブルでパソコンにつなぐだけで、AVRマイコン用のプログラムがマイコンに書き込めるのだろう?

これは、Arduinoに載っているAVRマイコンに、ブートローダーと呼ばれるプログラムがあらかじめ書き込まれているからだ。Arduinoのブートローダーは、「USB経由でパソコンから送られてくるAVRマイコン用のプログラムをマイコンに書き込み、さらにそのプログラムを先頭アドレスから実行する」、という機能を提供する。この機能のおかげで、ライターを使わずとも、パソコンからUSB経由でAVR用のプログラムを取り込んで気軽に実行できるようになっている。

Arduinoのブートローダーは、ちょっと特別な感じはあるけれど、やはりAVRマイコンに書き込んで使うふつうのプログラムである。ソースは以下で読める。いろいろな種類があるけど違いはよくわかっていない。

Arduino Unoを買ったときに付属してくるATmega328Pにも、Arduinoのブートローダーがあらかじめ書き込まれた状態になっている。が、素のATmega328PにはArduinoのブートローダーは書きこまれていないので、単独で手に入れたATmega328PをArduino Unoに指しても、USB経由でプログラムを取り込むことはできず、なにもできない。素のATmega328Pにプログラムを書きこむには、やはりArduinoとは別にライターが必要になる。

AVRマイコンにプログラムを書き込む2つの手段

ここまでの話を整理すると、AVRマイコンで動くプログラムは、以下のいずれかの方法でマイコンに書き込んで実行できるということがわかる。

  1. 専用のライターでプログラムを書き込む(マイコンとしてはふつうの方法)
  2. すでにブートローダーが書き込まれたマイコンに、USB経由でプログラムを転送する(Arduinoとかの方法)

ライターがあるなら、Arduinoのブートローダーは不要だといえる。ただし、ライターと一口にいってもいろいろな種類があるので、書き込みの方法はそれぞれ微妙に異なる。ふつうは購入したライターで指示された仕組みを使えばいいことだけど、ライターを自作する場合には、書き込みの実行に必要なソフトウェアも自分で書く必要があるはず。

逆に、ライターをもっていなくても、Arduinoさえあれば、その上のマイコンに組み込まれているブートローダーを利用することで、USBケーブル一本でプログラムをマイコンに書き込める。これがArduinoのすごいところなんだけど、ここまでの話で想像できるように、何が起こっているのかを理解しようと思うとややこしさは増える。あと、起動もブートローダー経由になるので、直接書き込むときよりちょっと立ち上がりが遅くなるかもしれない。

ATmega328Pの内部クロック

マイコンは、動作するためにクロックを必要とする。Arduino Unoの場合は、基板の上に発振子が実装されていて、これで16MHzのクロックを生成している。

だが、実はATmega328Pの内部にもクロックは実装されている。これを使えば、スタンドアローンで動かすときに発振子を回路上に載せる必要がなくなる。

どのクロックを使うかの切り替えは、lfuseビットというフラグをセットする。必要な設定は、マイコンを使う回路に合わせて、データシートの"13.2. Clock Sources"あたりを見て自分で考えるしかない。

内部クロックを使うときは、lfuseの値を0xF3もしくは0x73あたりにすればよさそう、とわかる。

回路とプログラム

以上の知見をLチカで試してみる。まずはプログラムから。

#include <avr/io.h>
#define F_CPU 1000000UL
#include <util/delay.h>

int main(void) {
  DDRD = 0b00000100;
  PORTD = 0b00000000;
  while(1) {
    PORTD = 0b00000100;
    _delay_ms(100);
    PORTD = 0b00000000;
    _delay_ms(100);
  }
}

このコードは以下のサイトで紹介されているものに周波数の設定(F_CPU)を追加しただけ。

AVRマイコンのプログラムの基本は avr/io.h の読み込みになる。DDRD により、Dポートたちを出力として使えるようになる。PORTD に3ビットめを立てた8ビットの値を代入することで、Dポートの3つめ、つまりATmega328Pであれば「PD2」がオンになる。これを while により繰り返すことでPD2に接続したLEDをオンオフしようというプログラム。

これを led.c として保存し、以下のコマンドでコンパイルする。

$ avr-gcc -mmcu=atmega328p -Os -Wall -g -o main.bin led.c

さらにマイコンへ書き込むためにhexに変換する。

$ avr-objcopy -j .text -j .data -O ihex main.bin main.hex

生成された main.hex をATmega328Pに転送しすると、スタンドアローンで動かせるようになる。

転送は上記いずれの方法でもいいが、ライターを利用できる場合には前述のようにlfuseビットを 0xF3 にすると内部クロックで動かせる。Arduinoのブートローダーを使ってUSBで転送する場合は、ATmega328PのlfuseビットがArduinoのデフォルト設定である 0xFF になっているので、回路に外部クロックを実装するしかないだろう。lfuseビットの書き込みにはライターが必要なので、あきらめるしかない。

以下は、ATmega328Pを電源につなぎ、PD2に抵抗を介してLEDを接続しただけの回路。ライターを使って転送し、lfuseビットを0xF3にしたので、内部クロックで動いている。これでちゃんとLチカする。楽しい。

Discussion

だめぽだめぽ

F_CPU の定義は #include の前に書くべきです。今回は <util/delay.h> で定義されているデフォルト値と同じ値なのでコンパイル&動作するかと思いますが、 F_CPU の値を変えると期待通りの動作をしなくなるかと思います。