Building Arduino Sketches from the Command Line

Posted on Jan 14, 2018

Just over two years ago, I wrote a little version of Snake for a 4x4x4 LED cube. Since then I've picked up several little Arduino devices, include a 6x6x6 LED cube, an Arduino Esplora, and the one I'm most excited about, an Arduboy. For every Arduino I bought, I had some grand plan. I never followed through on any of them though. It wasn't because of the technical challenge of writing an executable less than 32k in size. Nor was it the minuscule 2.5k of RAM. I find the limited capability of the system to be an intriguing challenge. It wasn't even something simple like a fear to try or a simple lack of time. The reason was much worse than any of that: simply, I don't enjoy the Arduino IDE. It's not that it is a bad IDE, it's just that it isn't a good IDE. It isn't fun.

So, what should I do? I could pinch my nose, keep a stiff upper lip, and run full force ahead, I suppose. I don't see the point though. If I bought the systems for the joy of programming, I don't see why I would shortchange myself by using an IDE that inhibits my fun. As another option, I could shelve the little computational wonder, grieving all the little widgets and gizmos that would now never be. Apart from being a bit melodramatic, it also doesn't get me any closer to my goal of enjoying my Arduinos. Still yet is another option though: If there were a way to compile and upload sketches without using the Arduino IDE, I could use whatever IDE I wanted without having to interact with the standard Arduino one. So, how do we build sketches without using the Arduino IDE? By putting together a Makefile.

Avoiding Pain… With a Makefile?

Makefiles are files that describe the specifics of how a program, typically C or C++, is to be compiled and linked. This information is used by make to not only to compile an application from source, but also to potentially perform other tasks, such as uploading a sketch to an Arduino. If you have familiarity with Gradle or Ant, this might sound familiar. Despite the power, makefiles aren't exactly renowned for being friendly and fun; Quite the opposite even. Thankfully, it isn't necessary for me to create a Makefile for this: some of the wonderful people in the Arduino community have already seen fit to create one.

Weighing in at roughly 2,000 lines, their script works with virtually every Arduino on the market with only minimal configuration. To use it, we need to download the repo that contains the Makefile. We'll need to store the repo in a location where we can reference it later on.

git clone https://github.com/sudar/Arduino-Makefile

Next, we need to install the Arduino IDE. While I don't want to use it as my IDE, the fact that it comes with a compiler, libraries, and an entire toolchain capable of building Arduino sketches brings us much closer to our goal of building sketches from the command line or another tool.

Creating our Makefile

The idea of this Makefile is that, rather than configuring everything ourselves, we can fill in a few environmental variables as needed then reference the Makefile supplied by the community. This allows each project to customize the script as necessary without having to reimplement the whole. You can think of it as being similar to subclassing.

To start, create a file named “Makefile” in your project directory and paste the following into it.

BOARD_TAG     = THE_BOARD_TAG
MONITOR_PORT  = /dev/ttyACM0

ARDMK_DIR     = /MAKEFILE_REPO_PATH
ARDUINO_DIR   = /ARDUINO_IDE_PATH
AVR_TOOLS_DIR = /ARDUINO_IDE_PATH/hardware/tools/avr
AVRDUDE_CONF  = /ARDUINO_IDE_PATH/hardware/tools/avr/etc/avrdude.conf

CXXFLAGS_STD  = -std=gnu++11 -fno-threadsafe-statics -ffunction-sections -fdata-sections 

include $(ARDMK_DIR)/Arduino.mk

Looking at the text, there are two settings that we can set right away: ARDUINO_IDE_PATH, and MAKEFILE_REPO_PATH. Substitute the full path to the extracted Arduino IDE for ARDUINO_IDE_PATH (probably not a surprise). Next, put the path to the local clone of the Arduino-Makefile repository in for MAKEFILE_REPO_PATH.

BOARD_TAG     = THE_BOARD_TAG
MONITOR_PORT  = /dev/ttyACM0

ARDMK_DIR     = /home/jrogers/Source/arduino/Arduino-Makefile
ARDUINO_DIR   = /home/jrogers/Applications/Arduino
AVR_TOOLS_DIR = /home/jrogers/Applications/Arduino/hardware/tools/avr
AVRDUDE_CONF  = /home/jrogers/Applications/Arduino/hardware/tools/avr/etc/avrdude.conf

CXXFLAGS_STD  = -std=gnu++11 -fno-threadsafe-statics -ffunction-sections -fdata-sections 

include $(ARDMK_DIR)/Arduino.mk

In order to get the value for THE_BOARD_TAG, we need to run make show_boards. This will cause the build system to list out all of the tags and their corresponding Arduino model.

make show_boards
...
gemma               Arduino Gemma
leonardoeth         Arduino Leonardo ETH
leonardo            Arduino Leonardo
lilypad             LilyPad Arduino
LilyPadUSB          LilyPad Arduino USB
megaADK             Arduino Mega ADK
mega                Arduino/Genuino Mega or Mega 2560
...

The Arduboy uses an Arduino Leonardo, so I'll substitute ‘leonardo’ for THE_BOARD_TAG. At this point, the Makefile has everything it needs to build.

It works. In theory.

In all but the most simple of builds, this won't be enough to fully compile a sketch as most sketches will rely on at least one external library. Assuming that libraries are installed via the Arudino IDE, we need only add them into the Makefile.

ARDUINO_LIBS  += Arduboy2 \
                 EEPROM

And with that, our Makefile is done, as seen here:

BOARD_TAG     = leonardo
MONITOR_PORT  = /dev/ttyACM0

ARDMK_DIR     = /home/jrogers/Source/arduino/Arduino-Makefile
ARDUINO_DIR   = /home/jrogers/Applications/Arduino
AVR_TOOLS_DIR = /home/jrogers/Applications/Arduino/hardware/tools/avr
AVRDUDE_CONF  = /home/jrogers/Applications/Arduino/hardware/tools/avr/etc/avrdude.conf

ARDUINO_LIBS  += Arduboy2 \
                 EEPROM

CXXFLAGS_STD  = -std=gnu++11 -fno-threadsafe-statics -ffunction-sections -fdata-sections 

include $(ARDMK_DIR)/Arduino.mk

With this in place, we should now be able to to build and deploy our sketches from the command line. To build a sketch run make. You can clean up a sketch by running make clean. To upload a sketch to your Arduino, run make upload.

Bonus

As soon as the first compile finished something jumped out at me. Just the act of including the Arduboy2 library ate over 1k of my 2.5k of RAM.

Program:    8824 bytes (26.9% Full)
(.text + .data + .bootloader)

Data:       1243 bytes (48.6% Full)
(.data + .bss + .noinit)

How am I supposed to make a game on here when 40% of my space is consumed just from including a library?! After reading up for a bit, I found that Arduboy is a bit different than libraries I've used in the past in that it manages the framebuffer for me. For a 128x64 monochrome screen, that comes out at 1024 bytes. This is a responsibility I am more than glad to let it keep.