005 makefileの中身の解説
ここではmakefileの説明をしようと思いますが、ここはわりと玄人向けの説明になってしまいそうで頭が痛いところです.
なぜか?
eclipse+CDTで構築したC言語開発環境は、一見普通の統合開発環境に見えますけど、実体はかなり違います.
それは、コンパイラとかリンカのコマンドをプログラマが全てmakefileに記述しなくちゃいけないからです.
だから、projectにソースファイルを追加するときには、makefileにも追記しなくちゃいけません.
普通の統合開発環境だと、あなたがそういうめんどくさいことをする必要はありません.
ソースファイルをドラッグ&ドロップするだけでOKだったりするので簡単です.
このお手軽さは、総合開発環境があなたの代わりにmakefileを生成して裏で動かしてくれてるからなんです.
だから、eclipse+CDTで構築したC言語開発環境は、コマンドウインドウでバッチファイルでビルドする開発環境と正体は同じだといっても過言ではありません.
make allなどとコマンドウインドウで打つ代わりに、allというボタンをクリックしているだけってことです.
eclipse+CDTがコマンドウインドウよりもマシところは、エディターが美しくて見やすいからGOODだとは言えますがな.
ただ、こういうことを書いていてすら、90%の方には話が通じてないんじゃないかと心配になります.
なぜなら、いまどきコマンドウインドウでバッチファイルでビルドする開発環境の経験者って、読者の10%ぐらいしかいないんじゃないだろうか?って思うからです.
だとすると、以下でmakefileの中身を解説しても、わかる素地がある人は10%しかいないってことになりますなぁ. う~ん、、、、、
makeとmakefileだけで本が一冊書けてしまいますので、makeとはなにかという解説をする気にはならんしなぁ......
仕方がないので簡単にはこう思ってください.
●makefileとは、自動でビルドするためのバッチファイルみたいなもんです.
●makefileには、複数のエントリポイントを記述できます.そのエントリポイントの一つをxxxとすると、make xxxというコマンドでxxx以下を走らせることができます.
たったこれだけ理解しただけで、以下のmakefileの解説を読んでいただくことにします.
以下のmakefileは、マイコン徹底入門の開発環境のmakefileをわたしがいろいろと改竄したものです.
★あなたがカスタマイズする可能性があるところ
●おまじないだと思って放置していてよいところ
●SHELL = cmd.exe
ウインドウスのコマンドプロンプト画面をだすコマンドです.
何に使われているのか知りませんが、削除せずに残してあります.
削除してもよいのかもしれない.
●TARGET_ARCH = -mcpu=cortex-m3 -mthumb
STM32は、ARMのCPUです.様々なARM CPUのラインナップのうち、Cortex-M3というCPUです.
それをここで指定しています.thumbっていうのは何なのかまだ知りません.
STM32-DISCOVERYを使っているうちは、この設定を変更することはないです.
★INCLUDE_DIRS = -I lib/STM32F10x_StdPeriph_Driver/inc \
-I lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x \
-I lib/CMSIS/CM3/CoreSupport \
-I lib2
インクルードファイルがどこのフォルダにあるかを一覧表的に記述してINCLUDE_DIRSという変数に代入しています.
行末が\になってるのは、次行に続くの意味ですので、これらは一行の文字列だとmakeはみなします.
なお、フォルダ名は相対パス表示です.projectフォルダがカレントフォルダです.
あたらしいインクルードファイルを記述して、あたらしいフォルダにそれを置いたら、そのフォルダ名をここに追記しなくてはいけません.
●STARTUP_DIR = lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/gcc_ride7/
CPUの最もハードウエア寄りのレイヤーを記述した startup_stm32f10x_md_vl.s というアセンブラソースファイルがあります.
このファイルのフォルダが深いところにあって、記述するときにウザいので、STARTUP_DIRという変数で代用させています.
●BOARD_OPTS = -DHSE_VALUE=((uint32_t)8000000) -DSTM32F10X_MD_VL \
-DUSE_STM32_VLD -DSYSCLK_FREQ_24MHz=24000000
-Dほにゃらら という記述は、コンパイラに対して #define ほにゃらら と同じ意味があります.
HSE_VALUE=8000000は、外部水晶発振子の周波数が8MHzの意味です.
STM32F10X_MD_VLは、STM32の品種を表す記号です.Middle Density Value Lineの頭頭文字.
STM32_VLDは、STM32の品種を表す記号です.Value Line Discoveryの頭文字.
SYSCLK_FREQ_24MHz=24000000は、システムクロック周波数が24MHzの意味です.
●FIRMWARE_OPTS = -DUSE_STDPERIPH_DRIVER
STMicro社のライブラリを使うという宣言です.
●COMPILE_OPTS = -fsigned-char -Wall -fmessage-length=0 $(INCLUDE_DIRS) $(BOARD_OPTS) $(FIRMWARE_OPTS)
コンパイルオプションを設定しています.
前半はよくわからないおまじないです.
後半は、上で定義した INCLUDE_DIRS,BOARD_OPTS,FIRMWARE_OPTS を連結しています.
★TOOLDIR = ../../yagarto/bin/
コンパイラコマンドの本体が置かれたフォルダを指定しています.
コンパイラコマンドの本体を置くフォルダをあなたが変更した場合は、この行を書きかえる必要があります.
●CC = $(TOOLDIR)arm-none-eabi-gcc
コンパイラのパスを設定しています.arm-none-eabi-gcc.exeがコンパイラです.
●AS = $(CC)
アセンブラのパスを設定しています.
●LD = $(CC)
リンカのパスを設定しています.
●AR = $(TOOLDIR)arm-none-eabi-ar
ライブラリを作るコマンドのパスを設定しています.
●OBJCOPY = $(TOOLDIR)arm-none-eabi-objcopy
コピーコマンドのパスを設定しています.
●CFLAGS = -std=gnu99 $(COMPILE_OPTS)
コンパイルオプションは最終的にCFLAGSです.
●ASFLAGS = -x assembler-with-cpp -c $(TARGET_ARCH) $(COMPILE_OPTS)
アセンブラのオプションは最終的にASFLAGです.
★LDFLAGS = -Wl,--gc-sections,-Map=bin\main.map,-cref -T lib2/128_8.ld $(INCLUDE_DIRS) -L ./lib
リンカのオプションは最終的にLDFLAGSです.
128_8.ldはリンカスクリプトです.リンカスクリプトの内容は知りません.
128_8.ldのフォルダを変えたりしたら、この行も変えなくちゃいけません.
●all: lib/lib.a bin\main.hex
makeエントリです.
もしもコマンドラインからmake allと打つと、このエントリが起動されます.
すると、lib.a と main.hex を生成しようとします.
●bin\main.hex: main.o lib/lib.a $(STARTUP_DIR)startup_stm32f10x_md_vl.o
$(LD) $(LDFLAGS) $(TARGET_ARCH) $^ -o bin\main.elf
$(OBJCOPY) -O ihex bin\main.elf bin\main.hex
main.hexエントリです.
allエントリがmain.hexを生成しようとすると、このエントリが参照されます.
ここにはmain.hexをどうやって生成するかが完全に記述されています.
まず、main.hexを生成する素材は、main.o,lib.a,startup_stm32f10x_md_vl.o だと定義しています.
つぎに、LDすなわちリンカで main.elf を生成すると定義しています.$^はmain.o,lib.a,startup_stm32f10x_md_vl.oを表しています.
つぎに、コピーコマンドで、main.elf を main.hex に変換しています.
これで、main.hex を生成するコマンドが定義されたわけです.
ちなみに、芋づる式に、main.o,lib.a,startup_stm32f10x_md_vl.oを生成しようとします.
この芋づる性がmakeの賢いところなんです.
なお、startup_stm32f10x_md_vl.o をlib.aの中に含めてしまい、main.elfの生成ルールから除外するのはやめた方がいいみたいです.
なぜなら、.elfあるいは.hexを生成するときに重要な情報がstartup_stm32f10x_md_vl.oに入っているのが透過しないことがあるみたいなので.
★LIB_OBJS = $(sort \
$(patsubst %.c,%.o,$(wildcard lib/*.c)) \
$(patsubst %.c,%.o,$(wildcard lib2/*.c)) \
$(patsubst %.c,%.o,$(wildcard lib/STM32F10x_StdPeriph_Driver/src/*.c)) \
$(patsubst %.c,%.o,$(wildcard lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x/*.c)))
LIB_OBJSには、ライブラリソースのオブジェクトファイル xxx.o がたくさん並んだリストです.
そのために、ライブラリフォルダの中にあるソースファイル xxx.cやxxx.s をサーチして、そのsuffixを.oに変換することで、オブジェクトファイルxxx.oのリストを作っています.
その操作のために、この行ではgcc makeのコマンドがいろいろ使われています.
sortコマンドは、ABC順にオブジェクトファイルを並べかえます.
patsubstコマンドは、.cを.oに変換しています.
wildcardコマンドは、そのフォルダにある全てのxxx.cのリストを返しています.
よって、あなたが、新しいライブラリファイルを記述して、どこか別のフォルダに置いたら、そのフォルダをここに追記しなければなりません.
●lib/lib.a: $(LIB_OBJS)
$(AR) cr lib/lib.a $(LIB_OBJS)
allエントリがlib.aを生成しようとすると、このエントリが参照されます.
arコマンドで、たくさんのオブジェクトファイルを連結したlib.aを生成するコマンドがここで定義されています.
★$(LIB_OBJS): \
$(wildcard lib/STM32F10x_StdPeriph_Driver/inc/*.h) \
$(wildcard lib/STM32F10x_StdPeriph_Driver/src/*.c) \
$(wildcard lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x/*.h) \
$(wildcard lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x/*.c) \
$(wildcard lib2/*.h) \
$(wildcard lib2/*.c) \
makefile
たくさんのオブジェクトファイル xxx.o のリストであるLIB_OBJSの生成コマンドについて定義されています.
まず、LIB_OBJSたちがどのソースファイルたちから生成されるのかが定義されています.
そのソースファイルたちはwildcardの働きによってxxx.hやxxx.cやxxx.sのリストが作られます.
つぎに、LIB_OBJSたちを生成するコマンドが定義されているかと思いきや、そういう定義はされていません.
ここで、makeの高等技法であるサフィックスルールが一役買っています.
xxx.cからxxx.oを生成するコマンドが定義されていない場合は、$(CC)と$(CFLAG)を活用して、makeがよしなにxxx.oを生成してくれる.
それがサフィックスルールです.
だから、xxx.oの生成ルールは記述されていません.
あなたが、新しいライブラリファイルを記述して、どこかのフォルダに置いた場合は、ここにそのフォルダを追加しなければなりません.
★all_clean:
del /f /q *.o *.s bin\* lib\*.a lib\*.o lib2\*.o
del /f /q lib\STM32F10x_StdPeriph_Driver\src\*.o
del /f /q lib\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\gcc_ride7\*.o
xxx.oやlib.aは、コンパイルの中間生成物に過ぎす、これらを一気に削除したいことがあります.
そのためのエントリです.
コマンドウインドウからmake all_cleanと打つと、このエントリが起動され、中間生成ファイルを消去してくれます.
消去したいファイルが増えたら、このエントリに追加するとよいでしょう.
以上がmakefileの全てでしたが、こういう風にmakefileを読むと、consoleペインにダーッと表示されるコンパイル経過の表示の意味がわかってきます.
●C:\eclipse\yagarto-tools-20100703\bin\make all
make allが起動されました.
●../../yagarto/bin/arm-none-eabi-gcc -x assembler-with-cpp -c -mcpu=cortex-m3 -mthumb -fsigned-char -Wall -fmessage-length=0 -I lib/STM32F10x_StdPeriph_Driver/inc -I lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x -I lib/CMSIS/CM3/CoreSupport -I lib2 -DHSE_VALUE=((uint32_t)8000000) -DSTM32F10X_MD_VL -DUSE_STM32_VLD -DSYSCLK_FREQ_24MHz=24000000 -DUSE_STDPERIPH_DRIVER -o lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/gcc_ride7/startup_stm32f10x_md_vl.o lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/gcc_ride7/startup_stm32f10x_md_vl.s
この長いのが一つのコマンドです.
分解して整形すると意味がわかります.長いフォルダ名は省略しますと、下記になります.
①arm-none-eabi-gcc コンパイルコマンド
②-x assembler-with-cpp -c -mcpu=cortex-m3 -mthumb -fsigned-char -Wall -fmessage-length=0 コンパイルオプション
③-I lib/STM32F10x_StdPeriph_Driver/inc インクルードファイルが置かれたフォルダの指定
-I lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x
-I lib/CMSIS/CM3/CoreSupport
-I lib2
④-DHSE_VALUE=((uint32_t)8000000) 様々な#define
-DSTM32F10X_MD_VL
-DUSE_STM32_VLD
-DSYSCLK_FREQ_24MHz=24000000
-DUSE_STDPERIPH_DRIVER
⑤-o lib/...../startup_stm32f10x_md_vl.o -oは出力ファイルの指定
⑥lib/....../startup_stm32f10x_md_vl.s 入力ファイルの指定
●../../yagarto/bin/arm-none-eabi-ar cr lib/lib.a lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/gcc_ride7/startup_stm32f10x_md_vl.o lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x/system_stm32f10x.o lib/STM32F10x_StdPeriph_Driver/src/misc.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_adc.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_bkp.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_can.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_cec.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_crc.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_dac.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_dbgmcu.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_dma.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_exti.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_flash.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_fsmc.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_gpio.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_i2c.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_iwdg.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_pwr.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_rcc.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_rtc.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_sdio.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_spi.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_tim.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_usart.o lib/STM32F10x_StdPeriph_Driver/src/stm32f10x_wwdg.o lib2/boardinit.o lib2/remap.o
この超長いのも一つのコマンドです.
オブジェクトファイルを連結して、一つのlib.aを作っています.
●../../yagarto/bin/arm-none-eabi-gcc -Wl,--gc-sections,-Map=bin\main.map,-cref -T lib2/128_8.ld -I lib/STM32F10x_StdPeriph_Driver/inc -I lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x -I lib/CMSIS/CM3/CoreSupport -I lib2 -L ./lib -mcpu=cortex-m3 -mthumb main.o lib/lib.a lib/CMSIS/CM3/DeviceSupport/ST/STM32F10x/startup/gcc_ride7/startup_stm32f10x_md_vl.o -o bin\main.elf
最終段階のコマンドです.オブジェクトファイル.oとlib.aは全て作り終えました.
その後に、リンカでひとまとめにして main.elf というバイナリファイルを作ります.
●../../yagarto/bin/arm-none-eabi-objcopy -O ihex bin\main.elf bin\main.hex
そして、一番最後に、FLASHに焼くためのmain.hexを生成しています.