Makefile을 사용하여 펌웨어를 빌드하는 방법

발행: (2025년 12월 21일 오후 09:46 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

위에 제공된 소스 링크 외에 번역할 텍스트가 보이지 않습니다. 번역을 원하는 본문 내용을 알려주시면 한국어로 번역해 드리겠습니다.

프로젝트 레이아웃

$ tree
.
├── bin/
├── drivers/
   ├── CMSIS/
   ├── STM32F446RETX_FLASH.ld
   └── STM32F4xx_HAL_Driver/
├── lib/
├── Makefile
└── src/
    ├── it.c
    ├── main.c
    ├── main.h
    └── peripheral.c

필수 라이브러리 및 도구

  • HAL library – 주변 장치 드라이버.
  • CMSIS library – Cortex‑M 코어 지원.
  • Linker script – MCU의 메모리 레이아웃.
  • Startup / system code – 어셈블리 리셋 핸들러 및 시스템 초기화.
  • arm‑none‑eabi‑* 툴체인 (gcc, objcopy, size, …).

필요한 모든 라이브러리와 스크립트는 drivers 디렉터리에 위치합니다. 다음 명령으로 가져올 수 있습니다:

git clone --recurse-submodules https://github.com/STMicroelectronics/STM32CubeF4.git
sudo apt install gcc-arm-none-eabi

HAL 라이브러리는 주변 장치를 프로그래밍하는 데 사용되며, CMSIS는 코어 정의를 제공합니다.

Source:

Makefile Walk‑through

아래는 전체 Makefile이며, 섹션별로 설명합니다.

1. Toolchain 정의

CC      := arm-none-eabi-gcc
OBJCOPY := arm-none-eabi-objcopy
SIZE    := arm-none-eabi-size
RM      := rm -f
FILE    := file

2. MCU‑특정 정의

DEVICE_FAMILY  := STM32F4xx
DEVICE_MODEL   := STM32F446xx
DEVICE_VARIANT := STM32F446RETx

3. 컴파일러 및 링커 플래그

CORTEX_FLAGS := -mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard
COMMON_FLAGS := -g3 -Os -ffunction-sections -fdata-sections -Wall
AS_FLAGS     := -x assembler-with-cpp

설명

  • -mthumb      Thumb 명령어 집합을 사용합니다.
  • -mcpu=cortex-m4  Cortex‑M4 코어를 대상으로 합니다.
  • -mfpu=fpv4-sp-d16-mfloat-abi=hard 하드웨어 부동소수점 연산을 활성화합니다.

4. 포함 경로

MAIN_INC      := ./src
CMSIS_INC     := ./drivers/CMSIS/Include
CMSIS_DEV_INC := ./drivers/CMSIS/Device/ST/STM32F4xx/Include
HAL_INC       := ./drivers/STM32F4xx_HAL_Driver/Inc
CMSIS_DSP_INC := ./drivers/CMSIS/DSP/Include

이 디렉터리들은 소스 코드에서 참조하는 헤더 파일들을 포함합니다.

5. 소스 및 특수 파일

MAIN_SRC      := $(wildcard ./src/*.c)
HAL_SRC       := $(wildcard ./drivers/STM32F4xx_HAL_Driver/Src/*.c)
SYSTEM_SRC    := ./drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/system_stm32f4xx.c
STARTUP_CODE  := ./drivers/CMSIS/Device/ST/STM32F4xx/Source/Templates/gcc/startup_stm32f446xx.S
LINKER_SCRIPT := ./drivers/STM32F446RETX_FLASH.ld

6. 약어 및 빌드 정의

DEFINES := -D$(DEVICE_FAMILY) -D$(DEVICE_MODEL) -D$(DEVICE_VARIANT) \
           -DUSE_HAL_DRIVER

SOURCES  := $(MAIN_SRC) $(HAL_SRC) $(SYSTEM_SRC)
OBJECTS  := $(notdir $(patsubst %.c,%.o,$(SOURCES))) startup_stm32f446xx.o
INCLUDES := -I$(MAIN_INC) -I$(CMSIS_DEV_INC) -I$(CMSIS_INC) -I$(HAL_INC) \
            -I$(CMSIS_DSP_INC)

CFLAGS  := $(CORTEX_FLAGS) $(COMMON_FLAGS) $(DEFINES) $(INCLUDES)
AFLAGS  := $(CORTEX_FLAGS) $(AS_FLAGS) $(DEFINES) $(INCLUDES)
LDFLAGS := $(CORTEX_FLAGS) -T $(LINKER_SCRIPT) \
           -Wl,--gc-sections,--relax --specs=nano.specs --specs=nosys.specs \
           -Wl,--start-group -lc -lm -lnosys -Wl,--end-group

핵심 포인트

  • SOURCES는 모든 .c 파일을 모으며, OBJECTS는 이를 .o 이름으로 변환합니다.
  • CFLAGS, AFLAGS, LDFLAGS는 각각 컴파일, 어셈블, 링크에 사용됩니다.
  • LDFLAGSnewlib‑nano C 라이브러리(-lc), 수학 라이브러리(-lm), 그리고 nosys 스텁 라이브러리(-lnosys)를 링크합니다. 이는 펌웨어가 베어 메탈(운영체제 없음) 환경에서 실행되기 때문입니다.

7. 출력 파일

FIRMWARE_ELF := firmware.elf
FIRMWARE_BIN := firmware.bin
  • ELF – 기계 코드, 디버그 정보, 심볼 테이블 등을 포함하며, 플래싱 및 디버깅에 사용됩니다.
  • BIN – 플래시를 직접 프로그래밍하기에 적합한 순수 바이너리 이미지입니다.

8. 빌드 규칙

.PHONY: all clean flash

all: $(FIRMWARE_ELF) $(FIRMWARE_BIN)

$(FIRMWARE_ELF): $(OBJECTS)
	$(CC) $(LDFLAGS) -o $@ $^

$(FIRMWARE_BIN): $(FIRMWARE_ELF)
	$(OBJCOPY) -O binary $< $@

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

startup_stm32f446xx.o: $(STARTUP_CODE)
	$(CC) $(AFLAGS) -c -o $@ $<

clean:
	$(RM) *.o $(FIRMWARE_ELF) $(FIRMWARE_BIN)

# Example flash target (adjust the programmer/command as needed)
flash: $(FIRMWARE_BIN)
	st-flash write $(FIRMWARE_BIN) 0x08000000

How It Works

  1. Compilation – 각 .c 파일이 $(CFLAGS)와 함께 객체 파일로 컴파일됩니다.
  2. Assembly – 시작 어셈블리 파일이 $(AFLAGS)와 함께 어셈블됩니다.
  3. Linking – 모든 객체가 $(LDFLAGS)와 제공된 링커 스크립트를 사용해 링크되어 firmware.elf를 생성합니다.
  4. Binary conversionobjcopy가 ELF를 원시 바이너리(firmware.bin)로 변환합니다.
  5. Flashing – 선택적인 flash 타깃은 MCU를 프로그래밍하는 방법을 보여줍니다(여기서는 st-flash 사용; 필요에 따라 프로그래머를 교체하세요).

결론

  • 컴파일하고, 링크하며, ELF와 원시 바이너리 이미지를 모두 생성합니다.
  • 빌드 프로세스를 완전히 투명하게 유지합니다—숨겨진 IDE 매직이 없습니다.
  • 파일 목록, 컴파일러 플래그, 또는 대상 MCU를 쉽게 조정할 수 있습니다.

각 단계를 이해하면 빌드 프로세스를 완전히 제어할 수 있게 되며, 이는 저수준 문제를 디버깅하거나 전체 IDE를 사용할 수 없는 환경에서 작업할 때 매우 귀중합니다. 즐거운 해킹 되세요!

머신 코드
두 파일의 크기를 보면 ELF 실행 파일이 훨씬 크다는 것을 알 수 있습니다!

빌드 레시피

.PHONY: all

all:
	@echo "-------------------------------------"
	@echo "----- Building the source files -----"
	@echo "-------------------------------------"
	@$(CC) $(AFLAGS) -c $(STARTUP_CODE)
	@$(CC) $(CFLAGS) -c $(SOURCES)

	@echo "\n------------------------------------"
	@echo "----- Linking the object files -----"
	@echo "------------------------------------"
	@$(CC) $(LDFLAGS) $(OBJECTS) -o $(FIRMWARE_ELF)
	@$(OBJCOPY) -O binary $(FIRMWARE_ELF) $(FIRMWARE_BIN)
	@$(RM) $(OBJECTS)

	@echo "\n-------------------------------------"
	@echo "----- The firmware memory usage -----"
	@echo "-------------------------------------"
	@$(SIZE) $(FIRMWARE_ELF)

	@echo "\n-------------------------------------"
	@echo "----- The firmware binary format ----"
	@echo "-------------------------------------"
	@$(FILE) $(FIRMWARE_ELF)

펌웨어 빌드

$ make

그게 전부입니다 🥳🥳🥳. firmware.elffirmware.bin 두 파일을 모두 빌드했습니다.

펌웨어 플래시

$ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
    -c "program firmware.elf verify reset exit"

이제 빌드된 펌웨어를 마이크로컨트롤러에 플래시할 준비가 되었습니다 (ST‑Link 연결 또는 유사한 도구를 사용).

Back to Blog

관련 글

더 보기 »

Rust와 함께 풀스택을 넘어

!‘Going beyond full stack with Rust’의 커버 이미지 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev...