Making Build Scripts
Last time I talked about the basic structure of a makefile. If you just have a file or two, it is easy enough to just write a basic file. However, a complex project gets burdensome after awhile. I usually start with a generic template that has grown with inputs from several Internet sources, and try to come up with a generic makefile for a particular platform. Then, in theory, I should be able to just modify a few variables at the front of a copy of the file and be ready to go.
The common makefile I use has a few predefined targets:
In addition, I will usually have targets for specific output formats I want to convert the resulting build into (e.g., hex, elf, or coff).
The key, of course, is variables. For example:
TARGET = demo SRC = $(TARGET).c SRC += aap4uart.c app4adc.c app4lcd.c app4delay.c
I will usually divide the file in three sections: Things you change for each project, things you might change (but probably won't), and things you should probably not change unless you are trying to move things over to another platform.
Examples of things you might not change very often would be the port to find the programmer on (e.g., /dev/ttyUSB2) and the type of programmer. The option for the processor type would be another good example.
You can also hide cross build platform difference with variables. For example:
REMOVE = rm –f COPY = cp
Here's a typical default rule from one of my files:
all: begin $(TARGET).elf $(TARGET).hex end
end targets let you do operations before and after the build. You might want to do something with version control, logging, or something as simple as:
begin: @echo Starting Build @$(CC) --version end: @echo Build complete
If you are like me and don't like the built in rules, you can build your own without having to copy them for each target. For example:
%.o : %.c @echo Compiling $< $(CC) –c $(ALL_CFLAGS) $< -o $@
make that .c files make .o files and gives the steps for building.
$< is, of course, the .c file name (in this case) and
$@ is the .o file name.
Of course, building an embedded program is only part of the battle. You have to get it to the target machine, too. There are several options. If you are using a microcontroller with some kind of programmer (like a JTAG programmer), you might want to define a target to do the download that depends on the image file:
jtagburn : $(TARGET).hex jtag –p /dev/ttyUSB2 –I $(TARGET).hex –p1550
Sometimes you have multiple ways you might program, so I usually define the
PROGRAMMER variable near the top (in the "sometimes changed" section) and set it to the target (like
jtagburn). Then I have single rule down at the bottom:
program : $(PROGRAMMER)
That way "make program" burns the device using the selected programmer. Don't forget to set the program target as a .PHONY target.
If the target runs Linux or another "big" operating system, you have more choices. You can use scp, for example, to copy files over to the embedded filesystem. You can also use sshfs to "mount" the remote filesystem locally and then just use normal commands like
cp (or, you can have the build tools just write to the remote filesystem, although that could turn into a performance issue).
Ultimately, you could use ssh to run remote commands (either the native compiler on the target, or run make on the target but use ssh to execute remote tools on a host computer). I haven't found a good reason to do either of those, but it is possible.
Make is not the latest build tool, but it is certainly versatile, portable, and very useful, particularly for normal embedded-system projects. Like most powerful tools, it can seem a little arcane at first, but with minimal effort you can master enough make to write a basic template makefile for your application. After that, it can be very simple to start a new project.