Arduino ships with two robust command-line tools that make it easy to pair your favorite text editor with their incredible out-of-the-box tooling: arduino and arduino-builder. We've written about arduino in a previous blog post. Here we'll use arduino-builder to compile a simple sketch and then use avrdude to upload our compiled sketch to an Arduino.

Let's get started!

Set-up

arduino-builder is a command-line tool that herds various tools and libraries to compile Arduino sketches in a snap ✨.

If you already have the Arduino IDE installed, good news: it ships with arduino-builder. Depending on how you installed it, you may need to configure your shell. If you installed the IDE using a package manager like brew, like so:

$ brew cask install arduino

your shell should be ready-to-go. Test this by running the following:

$ arduino-builder --version

If you see the version information, skip ahead to the next section, Command-line Options. If you see something like command not found read on.

Tip: On a macOS the arduino-builder executable is located at Arduino.app/Contents/Java/arduino-builder

We'll begin by creating a symlink to arduino-builder so we don't have to type the absolute path to the executable each time we want to compile a file.

$ ln -s /Applications/Arduino.app/Contents/Java/arduino-builder /usr/local/bin/arduino-builder

Now, running the above version command should produce:

$ arduino-builder --version
Arduino Builder 1.4.4
Copyright (C) 2015 Arduino LLC and contributors
See https://www.arduino.cc/ and https://github.com/arduino/arduino-builder/graphs/contributors
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Command-line options

Continuing to mise-en-place, let's make sure we have a sketch to compile.

If you already have something you're working on, awesome. You can skip this part and replace all references to blinky.ino with the name of your own sketch. If you don't, we'll turn to Blinky.

For those unfamiliar, blinky.ino initializes the built-in LED connected to pin 13 and toggles it. Use your favorite text editor to create this file and save it to a folder called src .

#blinky.ino
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT); 
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
  delay(1000);  // wait for a second
  digitalWrite(LED_BUILTIN, LOW);  // turn the LED off by making the voltage LOW
  delay(1000);  // wait for a second
}

On to the Good Stuff: It's Time to Compile

Let's get to work!

If you're in the same directory as your sketch, a full command for an Uno takes the form:

$ arduino-builder \
  -hardware /Applications/Arduino.app/Contents/Java/hardware \
  -tools /Applications/Arduino.app/Contents/Java/hardware/tools/avr \
  -tools /Applications/Arduino.app/Contents/Java/tools-builder \
  -libraries /Applications/Arduino.app/Contents/Java/libraries \
  -fqbn arduino:avr:uno \
  -build-path build blinky.ino

Note: Make sure your build-path is not the same as your source.

Here's what these options mean:

  • -hardware: Required. Path to the directory containing Arduino platforms
  • -tools: Required. Path to the directory containing tools like gcc and avrdude that arduino-builder relies on for compilation. Takes more than one argument. We'll point to two locations hardware/tools and tools-builder
  • -fqbn: Required. Stands for "fully qualified board name". For Arduino Uno, this would be arduino:avr:uno. If you're unsusre what your board name is, consult the Arduino boards.txt file to confirm.
  • -build-path: Specifies where to save compiled files. If you don't include this, compiled files get saved in a temporary folder specified by your OS.
  • the final required parameter is the path to the sketch itself

On successful compilation, arduino-builder creates a build folder populated with various files and directories. The ones we care the most about are the .hex files.

$ ls ./src/build
./src
├── blinky.ino
└── build
    ├── blinky.ino.eep
    ├── blinky.ino.elf
    ├── blinky.ino.hex
    ├── blinky.ino.with_bootloader.hex
    ├── build.options.json
    ├── core
    ├── includes.cache
    ├── libraries
    ├── preproc
    └── sketch

But wait...there's more: Uploading compiled files

Once we've compiled our sketch, we'll use avrdude to upload the hex (compiled) code to our board.

avrdude is a very popular command-line tool used to program AVR chips. The Arduino IDE uses avrdude under the hood to upload compiled programs to your board. We'll use the same tool, just in the terminal.

cd into the newly created build folder and then:

$ /Applications/Arduino.app/Contents/Java/hardware/tools/avr/bin/avrdude \
-C/Applications/Arduino.app/Contents/Java/hardware/tools/avr/etc/avrdude.conf \
-v -patmega328p \
-carduino \
-P/dev/cu.usbmodem1451301 \
-b115200 \
-Uflash:w:blinky.ino.hex:i

To give you a sense of what's what here, this is what the various command-line options mean:

  • -U :r|w|v:[:format] This is the most important part of the command. It tells avrdude how to put data on the chip.

    • The is either flash or eeprom (or hfuse, lfuse or efuse but ignore those for now).
    • The r|w|v means you can r (read) w (write) or v (verify).
    • The is the file that you want to write to or read from.
    • And finally, [:format] means theres an optional format flag. We will always be using "Intel Hex" format, so use i.

    Since we want to write the file blinky**.hex** to flash, we use -U flash:w:blinky.hex:i.

  • -c Specifies programmer type. Here, we're using arduino as a programmer.

  • -C Specifies the location of the configuration file. This file tells avrdude about the different ways it can talk to the programmer. Since we're using arduino as a programmer, we'll use the Arduino provided config file.

  • -p Required. This tells avrdude what AVR microcontroller we're programming. For Arduino Unos which have an ATmega328 microcontroller, this is atmega328p.

  • -b Overrides the serial baud rate

  • -P Specifies the connection port to use to talk to the programmer. If you ls/dev/ with your Arduino plugged in, you'll see a portname with "usb" in it, and that's the one you want.

  • -v This gives you verbose output which is helpful for debugging in case something goes wrong. -v -v for more.

avrdude goes through the following steps to flash a device:

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: reading input file "blinky.ino.hex"
avrdude: writing flash (876 bytes):

Writing | ################################################## | 100% 0.15s

avrdude: 876 bytes of flash written
avrdude: verifying flash memory against blinky.ino.hex:
avrdude: load data flash data from input file blinky.ino.hex:
avrdude: input file blinky.ino.hex contains 876 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.11s

avrdude: verifying ...
avrdude: 876 bytes of flash verified

avrdude done.  Thank you.

Once these steps are complete, the built-in LED on our Arduino will blink every second.

Nice work! 🥳


Thanks to Phil Lange for reading and editing drafts of this!