Search code examples
gccmakefilegnu-makeavr-gcc

How to let avr-gcc output *.o to separate folder in Makefile with `%.o: %.c`?


As showed in first screenshot, my ideal AVR project structure is that:

  1. *.o, *.elf and *.hex files are in build folder.
  2. PomoScheler.c and pinDefines.h as main files are in root folder, while other *.c and *.h are in src folder.

But *.o are always generated at the same folder as *.c like showed in second screenshot, no matter how.

(I attached my endeavors and whole Makefile below the screenshots)

enter image description here enter image description here

Firstly, I tried build/ before $@, in vain. The terminal still the same.

# My first Makefile endeavor
%.o: %.c $(HEADERS)
    $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o build/$@
# Terminal command generated by Makefile (Look at the end: *.o path still same as *.c)
avr-gcc -Os -g -std=gnu99 -Wall -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums  -ffunction-sections -fdata-sections  -DF_CPU=1000000UL   -DBAUD=9600UL -I. -I~/Developer/bin/avr8-gnu-toolchain-darwin_x86_64/avr/include -mmcu=atmega328p -c -o src/RotaryEncoder.o src/RotaryEncoder.c

Secondly, I tried to add mv $@ build to explicitly move it to build folder. But nothing happened. Even echo are not displayed in Terminal.

# My second Makefile endeavor
%.o: %.c $(HEADERS)
    $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
    mv $@ build
    echo ---------Hello---------

Thirdly, I delete $(HEADERS) and replace $< with $^ just to have a try. The mv is executed. But it cannot find *.o file this time even though I have VPATH = src:build in Makefile.

# My third Makefile endeavor
%.o: %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $^ -o $@
    mv $@ build
# Terminal error
Assembler messages:
Fatal error: can't create build/src/RotaryEncoder.o: No such file or directory

And my whole Makefile is here. Please help me out.

# My whole Makefile

MCU   = atmega328p
F_CPU = 1000000UL  
BAUD  = 9600UL

LIBDIR = ~/Developer/bin/avr8-gnu-toolchain-darwin_x86_64/avr/include

PROGRAMMER_TYPE = usbtiny
PROGRAMMER_ARGS =   

CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
AVRSIZE = avr-size
AVRDUDE = avrdude

##########------------------------------------------------------##########

VPATH = ./src:./build

TARGET = $(lastword $(subst /, ,$(CURDIR)))

SOURCES=$(wildcard *.c src/*.c $(LIBDIR)/*.c)
OBJECTS=$(SOURCES:.c=.o)
HEADERS=$(SOURCES:.c=.h)

CPPFLAGS = -DF_CPU=$(F_CPU) -DBAUD=$(BAUD) -I. -I$(LIBDIR)
CFLAGS = -Os -g -std=gnu99 -Wall
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums 
CFLAGS += -ffunction-sections -fdata-sections 
LDFLAGS = -Wl,-Map,build/$(TARGET).map 
LDFLAGS += -Wl,--gc-sections 
TARGET_ARCH = -mmcu=$(MCU)

%.o: %.c $(HEADERS)
    $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o build/$@

$(TARGET).elf: $(OBJECTS)
    $(CC) $(LDFLAGS) $(TARGET_ARCH) $^ $(LDLIBS) -o build/$@

%.hex: %.elf
    $(OBJCOPY) -j .text -j .data -O ihex build/$< build/$@

all: $(TARGET).hex 

size:  $(TARGET).elf
    $(AVRSIZE) -C --mcu=$(MCU) $(TARGET).elf

clean:
    rm -f $(TARGET).elf $(TARGET).hex $(TARGET).obj \
    $(TARGET).o $(TARGET).d $(TARGET).eep $(TARGET).lst \
    $(TARGET).lss $(TARGET).sym $(TARGET).map $(TARGET)~ \
    $(TARGET).eeprom

flash: $(TARGET).hex 
    $(AVRDUDE) -c $(PROGRAMMER_TYPE) -p $(MCU) $(PROGRAMMER_ARGS) -U flash:w:$<

Solution

  • This is clearly not right:

    OBJECTS = $(SOURCES:.c=.o)
    

    because the object files you want to create are not foo.o etc. which is what this will expand to; the object files are build/foo.o etc. So this has to be:

    OBJECTS = $(patsubst %.c,build/%.o)
    

    All of your attempts to trick make by telling it your recipe will build one target (foo.o) but actually building a totally different target (build/foo.o) are doomed to fail, regardless of whether you have the compiler do it directly, you use mv, or any other method.

    If you just tell make what your recipe actually does, you will have a much simpler time of it:

    build/%.o: %.c
            mkdir -p $(@D)
            $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $^ -o $@
    

    Probably you have similar issues with the other rules that you want to put into other directories.

    You can't do this by trying to hide it from make. Make has to know where the files actually are.

    BTW, VPATH cannot help here. VPATH is for finding source files, it cannot be used for finding generated files. So you could use VPATH to find your .c files but not your .o files.