Building Arduino projects with makefiles

How to build Arduino programs using Makefiles


The Arduino IDE is a great bit of software to get you up and running quickly, but as projects get larger, or if you already have preferred code editor etc., it might not be the best way to go. In this post, we will discuss a simple approach to building projects for Arduino with-out using the IDE. The Arduino IDE still needs to be installed, we need some of the libraries it provides, but we won’t actually need to run it to develop code for the Arduino platform.


  • You have a working installation of the GNU AVR toolchain. See this previous blog entry on how to build this from source.

  • You have the Arduino IDE installed. This can be downloaded from here.

  • You have an Arduino platform of some sort connected to you Linux box, here I will be using the uno. Other platforms should be very similar.

Let’s go

Right with the prerequisites out of the way, we are ready to start.

First of all, we need to define an Environment Variable called ARDUINO_IDE that contains the base directory of where you installed your Arduino IDE. In my case, I installed version 1.6.7 in my home directory. Therefore ARDUINO_IDE is defined as $HOME\arduino-1.6.7, adjust this based on whichever version you’ve got. For future use, I added the following to the end of my .profile:

export ARDUINO_IDE=$HOME/arduino-1.6.7
For convenience, I’ve created a basic makefile project of the ‘classic’ blink project on github.

To get it, just run the following:

git clone
The initial contents of Arduino-Make, should be as follows:
tfh@hex64:/tmp$ ls -l Arduino-Make/
total 80
-rw-rw-r-- 1 tfh tfh 16959 Mar  5 15:20
-rw-rw-r-- 1 tfh tfh   494 Mar  5 15:20 blink.cpp
-rw-rw-r-- 1 tfh tfh 35141 Mar  5 15:20 LICENSE
-rw-rw-r-- 1 tfh tfh   242 Mar  5 15:20 Makefile
-rwxrwxr-x 1 tfh tfh   589 Mar  5 15:20 newProject
-rwxrwxr-x 1 tfh tfh  4210 Mar  5 15:20 parseBoardsFile
-rw-rw-r-- 1 tfh tfh   233 Mar  5 15:20
tfh@hex64:/tmp$ Arduino-Make


As a quick test, you can build the this project by simply running make.

If everything is working OK, you should see a lot of compiler output, with the last line being something similar to this:

/home/tfh/opt/avr/bin/avr-objcopy -O ihex -R .eeprom build-cli/Arduino-Make.elf build-cli/Arduino-Make.hex
If you don’t see this, here are a few things to check:

  • ARDUINO_IDE is not defined. Check this by running echo $ARDUINO_IDE.

  • The GNU AVR toolchain is not installed correctly. Check this by running avr-gcc --version. If you got an error, go back through these instructions.

The output of your build will be in the build-cli sub directory.

As shown above, the default make target is to build the project, other make targets are:

  • show_boards: this will produce a list of the supported boards, and their tag.
  • clean: this will remove all of the generate files left over from a build.
  • upload: this will upload the program to a connected Arduino (more on this later).

As far as the contents of the project is concerned, the main files of interest here are Makefile & blink.cpp. We will now discuss each one in turn.


This defines a few things about your project, the initial contents are as follows:

AVR_TOOLS_PATH = $(HOME)/opt/avr/bin
ARDUINO_ETC_PATH = $(ARDUINO_DIR)/hardware/tools/avr/etc/
You can change the fields as required to suit your project. Entries you are likely to change are:

  • BOARD_TAG: this specifies your target platform, see the output of make show_boards for a complete list.

  • ARDUINO_PORT: this is the Linux device file that’s connected to your Arduino platform (again, more on this later).

  • ARDUINO_LIBS: For anything more that the most basic project, you will usual use some external library, SPI & Wire being two such examples. Simple add them to this to ensure that they get included in your project.

  • ARDUINO_OPT_LIB_PATH: Sometimes, you will need use a library that doesn’t come with the IDE, this define allows you to specify a directory on where to look for these.

This is your source code file, in this example, it’s contents are as follows:

#include <Arduino.h>
// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);

// the loop function runs over and over again forever
void loop() {
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(100);              // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(100);              // wait for a second
As you can see this is basically the same as the classic blink.ino file that comes with the Arduino IDE. A few things to note are:

  • The file has a .cpp extension, this is telling the compiler, and anyone looking at it, that this is a C++ file.

  • There’s an additional include at the top of the file, #include <Arduino.h>.

In general you can import an Arduino .ino program by renaming it to *.cpp, and adding #include <Arduino.h> to the top.

You are not limited to a single source file, for larger projects you can, and should, split you code over multiple *.cpp & .h files.


Once you’ve got a built project, it’s time to upload to your target platform. As previously stated, this post is assumes you’ve got an Arduino Uno. When connected to your PC, the Uno presents itself as a USB serial port. Linux will assigned a device file to this when it’s connected. An easy way to find this file, is to connect your Uno and run dmesg, you should see something similar to this:

tfh@hex64:~$ dmesg
[25453.093972] usb 3-1.4: USB disconnect, device number 3
[25455.457977] usb 3-1.4: new full-speed USB device number 4 using ehci-pci
[25455.553957] usb 3-1.4: New USB device found, idVendor=2341, idProduct=0043
[25455.553962] usb 3-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=220
[25455.553965] usb 3-1.4: Manufacturer: Arduino (
[25455.553967] usb 3-1.4: SerialNumber: 55330343731351A02282
[25455.554404] cdc_acm 3-1.4:1.0: ttyACM0: USB ACM device

The line of interest here, is

[25455.554404] cdc_acm 3-1.4:1.0: ttyACM0: USB ACM device

This means that the Uno has been allocated a device named ttyACM0, or to give it its absolute path /dev/ttyACM0. To check this, run ls -l /dev/ttyACM0, you should see something like this:

tfh@hex64:~/code/projects/fishy/inside$ ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 Mar  5 15:53 /dev/ttyACM0

This shows you that the file exists, and only root or members of the dialout group can write/read to/from it. Because of this, you need to ensure that your user is a member of dialout. If this is not the case, just perform the following:

sudo adduser $USER dialout

Note, you may need to log-out and back in again for this change to take affect

Now, you need to ensure that ARDUINO_PORT is defined as /dev/ttyACM0 in your Makefile.

To upload, simple run make upload, you should see something similar to this:

tfh@hex64:/tmp/nj6$ make
cat build-cli/blink.d > build-cli/
make: Nothing to be done for `all'.
tfh@hex64:/tmp/nj6$ make upload
for STTYF in 'stty -F' 'stty --file' 'stty -f' 'stty <' ; \
          do $STTYF /dev/tty >/dev/null 2>&1 && break ; \
        done ; \
        $STTYF /dev/ttyACM0  hupcl ; \
        (sleep 0.1 2>/dev/null || sleep 1) ; \
        $STTYF /dev/ttyACM0 -hupcl
avrdude -q -V -p atmega328p -C /home/tfh/arduino-1.6.7/hardware/tools/avr/etc//avrdude.conf -c arduino -b 115200 -P /dev/ttyACM0  \
            -U flash:w:build-cli/nj6.hex:i

avrdude: AVR device initialized and ready to accept instructions
avrdude: Device signature = 0x1e950f
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
         To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "build-cli/nj6.hex"
avrdude: writing flash (1050 bytes):
avrdude: 1050 bytes of flash written

avrdude: safemode: Fuses OK (H:00, E:00, L:00)

avrdude done.  Thank you.


If you get a failure, some things to check are:

  • Avr-dude is installed. Run avrdude --version, if this fails, install with sudo apt-get install avrdude.

  • You are not a member of dialout. Run id, if you don’t see dialout listed, add yourself as shown above.


You should now have a working build environment.

comments powered by Disqus