AVR 開発用の makefile を書くだけ

# この記事は？

AVR 等組み込みプログラムのコンパイル、及び書き込みは、PC 上で動作する C言語プログラムのコンパイルと異なり、必要なオプションや手順が多いです。
デバイス指定オプションなんかが必要ということもあり、コンパイルや書き込みのコマンド 1つも短くないので、毎回コマンドを打つのは手間で非効率的です。
ビルド関係は、やはり`makefile`にまとめておいてしまうのが効率的です。

この記事では、`avr-gcc`と`avrdude`で、C言語の AVR プログラムのコンパイル、及びマイコン書き込みができる`makefile`の作成を行います。
(`makefile`の詳しい説明は、ここではそこまで深く扱っておりません。)

# 用意するもの

使うコマンドは以下の 4つ。

* `avr-gcc` - C言語を AVR 用実行ファイルへコンパイル
* `avr-objcopy` - 実行ファイルから HEX ファイルの作成
* `avrdude` - HEX ファイルの AVR マイコンへの書き込み
* `make` - `makefile`に書かれたスクリプトの実行

avr-gcc でのコンパイルをするためには、AVR 用 Cライブラリ`avr-libc`も必要です。
ひとまず、以下のコマンドで全てインストールできます。(APT の場合)

```sh
sudo apt install gcc-avr avr-libc avrdude make
```

あとは、お好みのコードエディター等を任意で。

# ビルド対象のソースコード

Lチカプログラムです。
ポートの初期化を`AvrInit.c`で、LED の点灯切り替えを`PBToggle.c`で行っています。
この程度の規模なら`main.c`にまとめても問題無いですが、複数ファイルのソースのビルドをする`makefile`を作るため、あえて分割しています。

```main.c
#define F_CPU 1000000

#include <avr/io.h>
#include <util/delay.h>
#include "PBToggle.h"
#include "AvrInit.h"

int main() {
  // Setup
  AvrInit();

  // Main loop
  while(1) {
    PBToggle();
    _delay_ms(250);
  }

  return 0;
}
```

```AvrInit.h
#ifndef AVRINIT_H
#define AVRINIT_H

#include <avr/io.h>

int AvrInit(void);

#endif /* AVRINIT_H */
```

```AvrInit.c
#include "AvrInit.h"

int AvrInit(void) {
  DDRB = DDRB | 0b11111111; // Set PBx to output
  PORTB = 0b00000000; // Set PBx output to LOW
  return 0;
}
```

```PBToggle.h
#ifndef PBTOGGLE_H
#define PBTOGGLE_H

#include <avr/io.h>

int PBToggle(void);

#endif /* PBTOGGLE_H */
```

```PBToggle.c
#include "PBToggle.h"

int PBToggle(void) {
  PORTB = ~PORTB;
  return 0;
}
```

# make 無しでのコンパイル & 書き込み

とりあえず、make を使わない場合でのコンパイル、及び書き込みのコマンドを揃えます。

1. `avr-gcc`で、ソースから実行ファイル生成
2. `avr-objcopy`で、実行ファイルから HEX ファイル生成
3. `avrdude`で、HEX ファイルを AVR マイコンへ書き込み

```sh
avr-gcc -Wall -Os -mmcu=atmega328p AvrInit.c PBToggle.c main.c -o Blink
## -Wall        : 詳細な警告を全て表示するようにする
## -Os          : バイナリサイズが小さくなるよう最適化をする
## -mmcu=<Name> : プログラムを動かすマイコンを指定

avr-objcopy -F ihex Blink Blink.hex
## -F : 出力形式指定 (Intel HEX に指定)

avrdude -c avrispmkII -P usb -p atmega328p -U flash:w:Blink.hex:i
## -c : 書き込み機器 (ライタ) の指定 (avrispmkII を使用)
## -P : 書き込み機器の接続ポートの指定 (USB ポートを使用)
## -p : 書き込み先のマイコンを指定 (atmega328p が書き込み先)
## -U <memtype>:r|w|v:<filename>[:format]
##    : 書き込み先メモリタイプ:書き込みor読み込み:書き込むファイル:ファイルフォーマット を指定
```

`avr-gcc`について、「全ての Cソースを指定して一発で実行ファイルまで」でも動かなくはないです。
ただ、せっかく make を使うので、オブジェクトファイル生成をさせるプロセスを挟んで、利口な方法にします。

```sh
avr-gcc -Wall -Os -mmcu=atmega328p -c AvrInit.c -o AvrInit.o
avr-gcc -Wall -Os -mmcu=atmega328p -c PBToggle.c -o PBToggle.o
avr-gcc -Wall -Os -mmcu=atmega328p -c main.c -o main.o
avr-gcc -Wall -Os -mmcu=atmega328p AvrInit.o PBToggle.o main.o -o Blink
```

# makefile に書く

## 変数無しで (とりあえず上記ほぼコピペ)

以上のコマンドが動くように、`makefile`を書いていきます。
以下のような動作をするように仕上げます。

* `make`と打つと、実行ファイル (`Blink`) 及び HEX ファイル (`Blinl.hex`) が作られる
* `make write`と打つと、HEX ファイルがマイコンに書き込まれる (HEX ファイルが無ければ作る)
* `make clean`と打つと、オブジェクトファイルと実行ファイルが削除される (オブジェクトファイル等が邪魔になる際に片付けできる)

```makefile
# 実行ファイル及び HEX ファイル作成 (各 Cソースのオブジェクトファイルが必要)
Blink.hex: AvrInit.o PBToggle.o main.o
	avr-gcc -Wall -Os -mmcu=atmega328p AvrInit.o PBToggle.o main.o -o Blink
	avr-objcopy -F ihex Blink Blink.hex

# 各 Cソースのオブジェクトファイル作成
%.o: %.c AvrInit.h PBToggle.h
	avr-gcc -Wall -Os -mmcu=atmega328p -c $< -o $@

# HEX ファイル書き込み (Blink.hex が必要)
write: Blink.hex
	avrdude -c avrispmkII -P usb -p atmega328p -U flash:w:Blink.hex:i

# オブジェクトファイルと実行ファイル及び HEX ファイル削除
.PHONY: clean
clean:
	-rm *.o
	-rm Blink*
```

オブジェクトファイルの生成のルールは、`%.o: %.c`で簡潔にします。
これにより、Cソースが増えても、ルールを新しく追加する手間が省けます。
`%.o: %.c`を使わない方法で同様の動作を書くと、以下のような面倒な書き方になります。

```makefile
AvrInit.o: AvrInit.c AvrInit.h PBToggle.h
	avr-gcc -Wall -Os -mmcu=atmega328p -c AvrInit.c -o AvrInit.o

PBToggle.o: PBToggle.c AvrInit.h PBToggle.h
	avr-gcc -Wall -Os -mmcu=atmega328p -c PBToggle.c -o PBToggle.o
```

ここで、オブジェクトファイル作成のルールについてですが、依存ファイルにヘッダを全て記してしまっています。
これでも動くには動きますが、`AvrInit.o`は`PBToggle.h`を使いません。
よって、この部分の依存関係の記述は正確とは言えません。
(より正確にできる方法もあるとは思いますが･･･`makefile`マスターでない私にはこれが限界です･･･。)

## 変数を使ってスマートにする

さて、上記の`makefile`を変数を使って記述し、メンテナンス性を高めます。
ソースコードの構成が変わっても、変数を変えるだけで済むようにします。

```makefile
# 変数宣言
## ソース情報
TARGET = Blink
HEDS = AvrInit.h PBToggle.h
SRCS = AvrInit.c PBToggle.c main.c
OBJS = $(SRCS:.c=.o) # AvrInit.o PBToggle.o main.o
## AVR デバイス情報
AVR_CHIP = atmega328p
AVR_WRIT = avrispmkII
AVR_PORT = usb
## コンパイラ情報
CC = avr-gcc
CFLAGS = -Wall -Os -mmcu=$(AVR_CHIP)
## 他
RM = -rm

# 実行ファイル及び HEX ファイル作成 (各 Cソースのオブジェクトファイルが必要)
$(TARGET).hex: $(OBJS)
	$(CC) $(CFLAGS) $(OBJS) -o $(TARGET)
	avr-objcopy -F ihex $(TARGET) $(TARGET).hex

# 各 Cソースのオブジェクトファイル作成
%.o: %.c $(HEDS)
	$(CC) $(CFLAGS) -c $< -o $@

# HEX ファイル書き込み (Blink.hex が必要)
write: $(TARGET).hex
	avrdude -c $(AVR_WRIT) -P $(AVR_PORT) -p $(AVR_CHIP) -U flash:w:$(TARGET).hex:i

# オブジェクトファイルと実行ファイル及び HEX ファイル削除
.PHONY: clean
clean:
	$(RM) *.o
	$(RM) $(TARGET) $(TARGET).hex
```

`avr-objcopy`や`avrdude`も変数にしても良いのですが、一度しか登場しない上、仮に変更することになったらどのみちコマンド引数を変えることになると思うので、ここは直書きにしました。

これで`makefile`の完成です。
ソースコードが増えたら、`HEDS`と`SRCS`を変えてやることですぐに対応できます。