LinuxでAVR開発 (ビルド編)

ずいぶん前に、「LinuxでAVRの開発環境を構築する」というエントリを書いて放置していたのですが、 id:rdera さんから続編希望との有難いお言葉を頂きましたので、本日はその続きです。

今日のエントリでは、ビルドの概要からMakeを使った手軽なビルド方法までを解説します。

用意するもの

今回はビルド編です。焼き込みまでは行かないので、用意するのは必要なツールがインストールされた Linux PC だけになります。

前回エントリから関連するものを再掲しましたので Ubuntu の人は、以下のパッケージをインストールしておいてください。 (Ubuntu でない人は、各自相当する物を準備してください。)

Ubuntuのパッケージ名 説明
gcc-avr avr用GCC(インストールされるコマンド名は"avr-gcc"と語順が違うので注意)
binutils-avr avr-as, avr-objcopy などコンパイラの裏で使われるツール群
avr-libc 標準Cライブラリ。ヘッダなど(レジスタ定義なども含まれているので実質必須)
make ビルド作業を楽にするためのツール。 AVR用というわけではなく、Linuxでよく使われる一般のツールです

また、ソースコードを書くためのエディタの準備もしておきましょう。makeを使う関係上、タブ文字をきちんと入力できるものが必要です。(後述)

ビルド作業とは

実際のビルドの仕方に入るまえに、ビルドについての前提知識を整理しておきましょう。

この場におけるビルドとは「ソースコードを元に、マイコンに書き込めるバイナリを生成する」作業です。 *1

今回はAVRをターゲットにCでプログラミングしますので、「ソースコード」は拡張子 .c のテキストファイルとなります。また、「マイコンで書き込めるバイナリ」ですが、これは書き込みソフト(プログラマ)次第でフォーマットが決まります。とはいえ、私が使用する avrdude や dfu-programmer は、いずれも大抵のフォーマットに対応していますので、今回はとりあえず両者が対応していて最も一般的なフォーマットの一つである Intel Hex フォーマットを使うことにします。

さて、頭とお尻が決まったら、あとはその間をどう埋めていくかということになりますね。その答えを図示するとこうなります。

まず、 avr-gcc を使ってCのソースコード(時には複数。asmも可)をコンパイルし、オブジェクトファイル(拡張子は .o)を生成します。そして、出来たオブジェクトを結合(リンク)してELF(実行可能形式)ファイル(拡張子は無し)を生成します。最後に、ELFファイルから、実際にマイコンに書き込む部分を Intel Hex 形式で抜きだします。最後のステップはAVR等のマイコンに特有の話ですが、前の2ステップはPCのソフトでも同様の一般的な話ですね。

「うへぇ。面倒くさそう」 そういう考え方はプログラマに必要な素質だと偉い人が言ってました。この面倒くさい手順を楽するために make というツールを使います。具体的な使い方は以下で今日エントリの最後に説明します。

サンプルソースコード

それでは、実際の手順に入っていきましょう。まずはソースコードを準備します。

いきなりご自分で作成されたソースコードで試すのもアリですが、うまく行かなかった時にどこが悪いかわからなくなる可能性があります。とりあえず確実に動くサンプルを用意しましたので、まずはこれで試してみてください。

#include <avr/io.h>
#include <util/delay.h>

#define BBLED 3

int main(void)
{
    DDRB  = _BV(BBLED);

    for(;;){
        PORTB ^= _BV(BBLED);
        _delay_ms(250);
    }
}

マイコン界の Hello World、LEDをチカチカさせるサンプルです。250ms ごとに ON と OFFを反転させるので、1秒間に2回光る計算になります。ポートBの3番に出力しますので、そこにLEDと制限抵抗を直列に繋ぐことになります。(冒頭に掲載した写真のような感じです。)

コマンド直打ちでビルド

make を使った手抜きをするまえに、まずは直接 gcc などを使ってビルドしてみましょう。

ビルドの概要で示した3ステップの処理は、具体的には以下の通りです。(ソースを led.c。 ターゲットを led.ihex。対象となるマイコンを ATTiny13 と仮定します。 )

$ avr-gcc -g -O2 -mmcu=attiny13 -DF_CPU=1000000UL    -c -o led.o led.c
$ avr-gcc -g -O2 -mmcu=attiny13  led.o   -o led
$ avr-objcopy -j .text -j .data -O ihex led led.ihex

それぞれのオプション(と、上では使っていないけどよく使うオプション)の意味を以下に示しますが、調整が必要な物というわけでも無いので、深く理解する必要は無いと思います。

avr-gcc のオプション 説明
-g 変数名や関数名などのデバッグ情報を ELF に埋め込む。gdb を使ったり、逆アセンブルして確認する場合には便利。ihexにまで残るわけではなく、実行時に悪影響は無いので、常時指定しておくのが吉。
-O2 変な副作用が出ない程度に最適化をかける。最適化オプションを一切指定しないと無駄なコードが出力されてメモリ的に厳しいので、普通は常時つけておくのが良い。例外は下記 -Osオプションを使うとき。
-Os 可能な限り出力コードが小さくなるようにコンパイルする。実行速度などは多少犠牲となるが、ROMが少ないAVRを使っているとたまにお世話になるオプション。上記 -O2 とは排他利用なので、普段は O2 にしておき、どうしてもROMに乗りきらないときにはOsに切り替える、が吉。
-mmcu=[avr-name] [avr-name]で指定したAVR向けのコードを出力する。AVR専用のオプション。AVRは石によって対応している命令等にバリエーションがあるので、このオプションでどの石を使うかを指定する。なお avr-name に書ける名前のリストは avr-as --help で表示できる。
-D[name]=[value] マクロ [name][value] として定義する。これはつまり、ソースコードの先頭に #define [name] [value] と書いたのと同じ効果となる。上記の例では、 delay.h の _delay_ms() 関数が内部で参照するための動作周波数を与えるために使用している。
-c コンパイルのみを行い、リンクはしない。
-o [file] コンパイルやリンクの結果を [file] に出力する。
avr-objcopy のオプション 説明
-j [section] [section]で指定したセクションを出力する。 .text にはプログラムのコードが、 .data には文字列定数や構造体変数の初期値などが収められている。
-O [format] 出力するファイルのフォーマットを指定する。なお、対応フォーマットのリストは avr-objdump --help で表示できる

詳細や、その他のオプションについてもっと知りたい人は、man gcc で表示されるマニュアルや、gccのオンラインヘルプ( avr-gcc -v --help など)を参照してください。

手抜きな make 入門

さて、前節で示したコマンドを毎回手打ちしていたのでは面倒なので、makeというツールを使います。

まずは理論を抜きにして、以下のテキストをコピペして、 "Makefile" という名前でソースコードと同じディレクトリに保存してください。
ただし、最後の方に2ヶ所ある字下げに関しては、必ずスペースではなくタブ文字で字下げを行ってください。

PROG=led
OBJS=${PROG}.o
MCU=attiny13
F_CPU=1000000UL # internal 8MHz clock & CKDIV8 fuse bit enabled
#--------

ELF=$(PROG)
IHEX=$(PROG).ihex

CC=avr-gcc
CFLAGS=-g -O2 -mmcu=$(MCU) -DF_CPU=$(F_CPU)
LDFLAGS=-g -O2 -mmcu=$(MCU)
OBJCOPY=avr-objcopy

all: $(IHEX)

$(IHEX): $(ELF)

$(ELF): $(OBJS)

%.ihex: %
	$(OBJCOPY) -j .text -j .data -O ihex $< $@

clean:
	-$(RM) $(ELF) $(IHEX) $(OBJS)

.PHONY: all clean

このファイルが置かれたディレクトリで make と打つと、先ほど紹介した3行のコマンドが実行され、led.ihex ファイルが生成されます。また、 make clean と打つことで、ビルドによって生成された各種ファイルを削除することもできます。

Makefile の文法については後日(気が向いたら)書くとして、なんとなく理解するための要点だけ挙げると以下のようになります。

  • コメント
    • "#" があると、それ以降のその行はコメントとして扱われ、無視される
  • 変数
    • 変数名=値 で変数定義
    • 使用時は "$(変数名)" などと書いて値を参照する
  • 依存関係
    • "ターゲット : ソース" と書くと 「ターゲットを作るにはソースが必要」という依存関係を記述したことになる
  • 実際のコマンド
    • 依存関係の行の直後に、先頭がタブ文字で字下げされた行を記述すると、その行に書いたコマンドがターゲットの生成に使用される

まあ、これも、基本はコピペして使えばよいだけなので、無理して理解する必要はないです。おもしろそうだと思ったら調べてみてください。

この Makefile は、ある程度汎用に作りましたので、最初の4個の変数定義だけ書き換えてやれば、他のプログラムを作る際にも使いまわせるようになっています。複数のソースファイルから成るプログラムをビルドする場合は、そのソースファイルに対応する .o ファイルを OBJS に列挙すればOKです。

例: mytest を作るのに mytest.c の他に lcd.c と usb.c を使う場合

PROG=mytest
OBJS=${PROG}.o lcd.o usb.o

さらに、ここにちょっと追加すれば、マイコンへの焼き込みも出来るようになります。……と、いったところで、次回「焼き込み編」へ続く。

*1:ビルドの定義の話は、厳密な事を言い出すと宗教論争にまで発展する話題なので、ここではこの定義で納得してください。