Arduino from the Command Line: Break Free from the GUI with Git and Vim!

assorted micro controllers

Love Arduino but hate the GUI? Try arduino-cli.

In this article, I explore a new tool released by the Arduino team that can free you from the existing Java-based Arduino graphical user interface. This allows developers to use their preferred tools and workflow. And perhaps more important, it'll enable easier and deeper innovation into the Arduino toolchain itself.

The Good-Old Days

When I started building hobby electronics projects with microprocessors in the 1990s, the process entailed a discrete processor, RAM, ROM and masses of glue logic chips connected together using a point-to-point or "wire wrapping" technique. (Look it up kids!) Programs were stored on glass-windowed EPROM chips that needed to be erased under UV light. All the tools were expensive and difficult to use, and development cycles were very slow. Figures 1–3 show some examples of my mid-1990s microprocessor projects with discrete CPU, RAM and ROM. Note: no Flash, no I/O, no DACs, no ADCs, no timers—all that means more chips!

""

Figure 1. Example Mid-1990s Microprocessor

""

Figure 2. Example Mid-1990s Microprocessor

""

Figure 3. Example Mid-1990s Microprocessor

It all changed in 2003 with Arduino.

The word "Arduino" often invokes a wide range of opinions and sometimes emotion. For many, it represents a very low bar to entry into the world of microcontrollers. This world before 2003 often required costly, obscure and closed-source development tools. Arduino has been a great equalizer, blowing the doors off the walled garden. Arduino now represents a huge ecosystem of hardware that speaks a (mostly) common language and eases transition from one hardware platform to another. Today, if you are a company that sells microcontrollers, it's in your best interest to get your dev boards working with Arduino. It offers a low-friction path to getting your products into lots of hands quickly.

It's also important to note that Arduino's simplicity does not inhibit digging deep into the microcontroller. Nothing stops you from directly twiddling registers and using advanced features. It does, however, decrease your portability between boards.

For people new to embedded development, the Arduino software is exceptionally easy to get running. That has been a major reason for its success—it dumps out of the box ready to rock. But for more seasoned developers, the included graphical user interface can be frustrating. It can be a barrier to using modern development tools and version control like Git. I know the compiler and toolchain is buried deep in that GUI somewhere. I just want to use my favorite editor, compile my code and upload my project to a dev board using my favored workflow. For many developers, this is a command-line or scripted process.

Enter arduino-cli

There have been a couple attempts to break out Arduino to the command line, but most failed to get wide support. However, now the Arduino team has alpha-released arduino-cli. This new framework not only provides a comprehensive set of command-line functions, but the team also says it will be used as the core underneath the next generation of the Arduino graphical interface. This is exciting news and shows commitment to this new concept.

For me, my preferred development workflow is using Git for version control and the Vim editor, so that's what I demonstrate in the remainder of this article.

Installing arduino-cli

At the time of this writing, the arduino-cli is in alpha release. It's being distributed both as a Go source package and pre-built binaries. The Go language produces very portable binaries, so you just need to download the correct file and put the binary somewhere in your $PATH. Most users reading this likely will want the Linux 64-bit for Intel/AMD systems. In the examples here, my system happens to be running Fedora 29, but any recent Linux should work. Check the project's GitHub page for updated versions; at the time of this writing, 0.3.2 is the latest alpha release. Finally, make sure your user can access serial- and USB-connected Arduino devboards by adding them to the "dialout" group (note: you'll need to re-log in to pick up the new group membership, and substitute "me" for your user name in the last command):


me@mybox:~ $ wget https://downloads.arduino.cc/arduino-cli/
↪arduino-cli-0.3.2-alpha.preview-linux64.tar.bz2
***downloading***
me@mybox:~ $ tar -xjf arduino-cli-0.3.2-alpha.preview-
↪linux64.tar.bz2
me@mybox:~ $ sudo mv arduino-cli-0.3.2-alpha.preview-linux64
 ↪/usr/local/bin/arduino-cli
me@mybox:~ $ sudo usermod -a -G dialout me

Assuming /usr/local/bin is in your $PATH, you should be able to run arduino-cli as any user on your Linux system.

Alternatively, if you want to build the Go package from source, you can use the get function to download, build and install the source package from the Arduino GitHub repository:


me@mybox:~ $ sudo dnf -y install golang
me@mybox:~ $ cd ; export GOPATH=`pwd`/go
me@mybox:~ $ go get -u github.com/arduino/arduino-cli
me@mybox:~ $ sudo mv $GOPATH/bin/arduino-cli /usr/local/bin/

Arduino from the Command Line

First, let's do some housekeeping. You need to pull over the current index of Arduino "cores" and search for the core that supports your dev board. In this first example, let's install support for the classic UNO board powered by an ATMega AVR processor:


me@mybox:~ $ arduino-cli core update-index
Updating index: package_index.json downloaded
me@mybox:~ $ arduino-cli core search avr
Searching for platforms matching 'avr'

ID                    Version   Name
arduino:avr           1.6.23    Arduino AVR Boards
arduino:megaavr       1.6.24    Arduino megaAVR Boards
atmel-avr-xminis:avr  0.6.0     Atmel AVR Xplained-minis
emoro:avr             3.2.2     EMORO 2560
littleBits:avr        1.0.0     littleBits Arduino AVR Modules

me@mybox:~ $ arduino-cli core install arduino:avr
*** lots of downloading omitted ***
me@mybox:~ $ arduino-cli  core list
ID                   Installed   Latest  Name
arduino:avr@1.6.23   1.6.23      1.6.23  Arduino AVR Boards

That's it. You have everything you need to create a new Arduino project for an Arduino UNO board. Now, let's create a project called myBlinky. You'll also initialize and set up a Git repository to manage version control, then make your first commit:


me@mybox:~ $ arduino-cli sketch new myBlinky
Sketch created in: /home/me/Arduino/myBlinky
me@mybox:~ $ cd /home/me/Arduino/myBlinky
me@mybox:~/Arduino/myBlinky $ git config --global
 ↪user.email "me@mybox.com"
me@mybox:~/Arduino/myBlinky $ git config --global
 ↪user.name "My Name"
me@mybox:~/Arduino/myBlinky $ git init
Initialized empty Git repository in /home/me/Arduino/
↪myBlinky/.git/
me@mybox:~/Arduino/myBlinky $ ls -la
total 16
drwxr-xr-x 3 me me 4096 Nov 22 10:45 .
drwxr-xr-x 3 me me 4096 Nov 22 10:45 ..
drwxr-xr-x 7 me me 4096 Nov 22 10:45 .git
-rw-r--r-- 1 me me   35 Nov 22 10:45 myBlinky.ino

me@mybox:~/Arduino/myBlinky $ cat myBlinky.ino

void setup() {
}

void loop() {
}

me@mybox:~/Arduino/myBlinky $ git add -A
me@mybox:~/Arduino/myBlinky $ git commit -m "Initial Commit"
[master (root-commit) ee95972] Initial Commit
1 file changed, 6 insertions(+)
create mode 100644 myBlinky.ino

me@mybox:~/Arduino/myBlinky $ git log
commit ee9597269c5da49d573d6662fe8f8137083b7768
 ↪(HEAD -> master)
Author: My Name <me@mybox.com>
Date:   Thu Nov 22 10:48:33 2018 -0500

   Initial Commit

Nice! The tool creates the project under the same Arduino directory structure where the graphical tools would expect to find them allowing you to flip between tools if you wish. It also creates a template .ino file with the familiar setup() and loop() functions.

The empty Git repository was initialized, and you can see it created the .git subdirectory where all the version data will be kept. Time to code!

Now, simply open up myBlinky.ino in your preferred editor—which in the interest of maximum street cred is Vim of course. Never EMACS (joking!)...seriously, use any editor you like (I hear Eclipse is nice)—then type in and save a classic "hello world" blinky program. Something like this:


// Simple Demo Blinker -MEH
#define PIN_LED 13

void setup() {
       pinMode(PIN_LED,OUTPUT);
}

void loop() {
       digitalWrite(PIN_LED,HIGH);
       delay(500);
       digitalWrite(PIN_LED,LOW);
       delay(500);
}

Now, let's compile and upload it to the Arduino UNO. Use the board set of commands to search for the upload:


me@mybox:~/Arduino/myBlinky $ arduino-cli   board list
FQBN            Port           ID          Board Name
arduino:avr:uno /dev/ttyACM0   2341:0001   Arduino/Genuino Uno

It found the board. Now compile:


me@mybox:~/Arduino/myBlinky $ arduino-cli  compile -b
 ↪arduino:avr:uno
Build options changed, rebuilding all
Sketch uses 930 bytes (2%) of program storage space. Maximum
 ↪is 32256 bytes.
Global variables use 9 bytes (0%) of dynamic memory, leaving
 ↪2039 bytes for local variables. Maximum is 2048 bytes.

Compile was clean. Next, upload to the board using the upload command, board name and port you discovered earlier:


me@mybox:~/Arduino/myBlinky $ arduino-cli  upload
 ↪-b arduino:avr:uno -p /dev/ttyACM0

As is common with command-line tools, silence is golden. The upload command completes, and the UNO is happily blinking away. You better lock in this good fortune with a git commit:


me@mybox:~/Arduino/myBlinky $ git commit -a -m "It works!
 ↪First blink."
[master 35309a0] It works! First blink.
1 file changed, 7 insertions(+)

The -m option takes a commit message; it should be a note about what's included in this commit. If you omit the message, git will open a template message in a text editor (the default is Vim, but you can change it by setting $EDITOR).

Support for Third-Party Development Boards

Now for something a little more ambitious, let's try to use a non-Arduino development board and walk through the steps of adding a third-party core. This can be tricky even with the graphical user interface, but it's pretty simple with arduino-cli. Let's target the very popular ESP8266.

For starters, you need to add the third-party repository to a file so arduino-cli knows how to locate the core. This is done with a file named .cli-config.yml. You might think this should go in your home directory or in the project directory, and you would be right to think that. But, one quirk of this early release of arduino-cli is that this file lands in the same directory where the arduino-cli program lives. For now, this is /usr/local/bin, but keep an eye on the website, as this is likely to change in future releases! Below, you'll add a new board config definition. This file uses YAML format, so be careful to use only spaces in the indenting. Edit (with sudo), and place the following text in /usr/local/bin/.cli-config.yml:


board_manager:
 additional_urls:
   - http://arduino.esp8266.com/stable/
↪package_esp8266com_index.json

Now, like before, update the index and install the core:


me@mybox:~/Arduino/myBlinky $ arduino-cli core update-index
Updating index: package_index.json downloaded                                                                                                                            
Updating index: package_esp8266com_index.json downloaded

You can see that it found and downloaded the index for esp8266 cores. Good. Now, let's download and install the core itself:


me@mybox:~/Arduino/myBlinky $ arduino-cli core search esp
Searching for platforms matching 'esp'

ID              Version Name
esp8266:esp8266 2.4.2   esp8266

me@mybox:~/Arduino/myBlinky $ arduino-cli core install
 ↪esp8266:esp8266
Downloading esp8266:esptool@0.4.13...
esp8266:esptool@0.4.13 downloaded  *** much omitted ***

Now, you can rebuild your myBlinky project for the esp8266 and upload it. You first need to edit your myBlinky.ino and change the #define PIN_LED to whichever pin has the LED. On my dev board, it's pin 2. Make that modification and save it:


#define PIN_LED 2

After plugging in the esp8266 dev board, you again run the board list command to try to find it:


me@mybox:~/Arduino/myBlinky $ arduino-cli board list
FQBN    Port            ID              Board Name
       /dev/ttyUSB0    1a86:7523       unknown

It detects it, but it can't determine what it is. That is quite common for boards like the esp8266 that require a button push or special programming mode.

Next, compile and upload after resetting your esp8266 board while pressing the program button. Like last time, use the board name and port discovered during the list operation:


me@mybox:~/Arduino/myBlinky $ arduino-cli board listall
 ↪|grep esp8266
Generic ESP8266 Module                  esp8266:esp8266:generic
(omitted long list)
me@mybox:~/Arduino/myBlinky $ arduino-cli upload  -b
 ↪esp8266:esp8266:generic -p /dev/ttyUSB0
Uploading 252000 bytes from /home/me/Arduino/myBlinky/
↪myBlinky.esp8266.esp8266.generic.bin to flash at 0x00000000
..................................................... [ 32% ]
..................................................... [ 64% ]
..................................................... [ 97% ]
.......                                               [ 100% ]

And, it blinks! Make another git commit and save your progress:


me@mybox:~/Arduino/myBlinky $ git add -A
me@mybox:~/Arduino/myBlinky $ git commit  -m
 ↪"Blinking on esp8266 board"
[master 2ccff1d] Blinking on esp8266 board
5 files changed, 61 insertions(+), 1 deletion(-)
create mode 100755 myBlinky.arduino.avr.uno.elf
create mode 100644 myBlinky.arduino.avr.uno.hex
create mode 100644 myBlinky.esp8266.esp8266.generic.bin
create mode 100755 myBlinky.esp8266.esp8266.generic.elf

As you can see, it saves the compiled binary versions of the compiled code in the project directory. You can add *.bin, *.hex and *.elf to a .gitignore file if you wish to omit these from your commits. If you do save them, you can use the -i option and the .bin file to upload a specific binary.

Adding Libraries

Building on your success, you should download and install a library. Let's up the blinky game and install support for some Adafruit NeoPixels (aka WS2812B, PL9823 and so on). First, search for it using the lib command, then download and install:


me@mybox:~/Arduino/myBlinky $ arduino-cli  lib  search neopixel
(omitting large list)
me@mybox:~/Arduino/myBlinky $ arduino-cli  lib  install
 ↪"Adafruit NeoPixel"
Adafruit NeoPixel@1.1.7 downloaded                                                                                                                                       
Installed Adafruit NeoPixel@1.1.7

Now you need to modify the program; edit the .ino file with these modifications:


// Fancy NeoPixel Blinky Blinker

#include <Adafruit_NeoPixel.h>
#define PIN_LED 14

Adafruit_NeoPixel strip = Adafruit_NeoPixel(1, PIN_LED,
 ↪NEO_GRB + NEO_KHZ800);

void setup() {
       strip.begin();
}

void loop() {

       strip.setPixelColor(0,strip.Color(255,0,0))'
       delay(200);
       strip.setPixelColor(0,strip.Color(0,255,0));
       delay(200);
       strip.setPixelColor(0,strip.Color(0,0,255));
       delay(200);
}

Next, do the same compile and upload dance, after, of course, attaching a NeoPixel or compatible LED to pin 14:


me@mybox:~/Arduino/myBlinky $ arduino-cli compile -b
 ↪esp8266:esp8266:generic
Build options changed, rebuilding all
Sketch uses 248592 bytes (49%) of program storage space.
 ↪Maximum is 499696 bytes.
Global variables use 28008 bytes (34%) of dynamic memory,
 ↪leaving 53912 bytes for local variables. Maximum
 ↪is 81920 bytes.
me@mybox:~/Arduino/myBlinky $ arduino-cli upload  -b
 ↪esp8266:esp8266:generic -p /dev/ttyUSB0
Uploading 252960 bytes from /home/me/Arduino/myBlinky/
↪myBlinky.esp8266.esp8266.generic.bin to flash at 0x00000000
.................................................... [ 32% ]
.................................................... [ 64% ]
.................................................... [ 96% ]
........                                             [ 100% ]

And, you should have a colorful blinky—pretty slick. It's another good time to commit your changes to capture your progress:


me@mybox:~/Arduino/myBlinky $ git add -A
me@mybox:~/Arduino/myBlinky $ git commit -m
 ↪"Blinky with NeoPixels"
[master 122911f] Blinky with NeoPixels
3 files changed, 20 insertions(+), 13 deletions(-)
rewrite myBlinky.ino (81%)

Using GitHub

Up to now, the git repository has been completely local in your project directory. There's nothing wrong with that, but let's say you want to publish your work to GitHub. It's pretty quick and easy to get started. First, log in to github.com and create an account if you don't already have one. Then, click the button to create a "new repository".

""

Figure 4. New Repository

Fill in the details for your project, and be sure to leave unchecked the option to initialize the repository with a README. This is because you already have a repository created, and you'll be "pushing" your local repository to GitHub, so you want it empty.

""

Figure 5. Create Repo

After creating it, you will be presented with some helpful options on how to push code into GitHub. You want the commands for "push existing repository".

""

Figure 6. Push Existing Repository

Now let's do it! Follow the instructions to create a git "remote" entry named "origin", which will represent GitHub. Then you will push, and it will prompt you for your GitHub user name and password (substitute your own GitHub URL, user name and password):


me@mybox:~/Arduino/myBlinky $ git remote add origin
 ↪https://github.com/sysmatt/myBlinky.git
me@mybox:~/Arduino/myBlinky $ git push -u origin master
Username for 'https://github.com': sysmatt
Password for 'https://sysmatt@github.com':  ******************
Counting objects: 18, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (18/18), 825.31 KiB | 5.00 MiB/s, done.
Total 18 (delta 4), reused 0 (delta 0)
remote: Resolving deltas: 100% (4/4), done.
* [new branch]      master -> master
Branch master set up to track remote branch master from origin.

Now, if you browse to the repository on GitHub.com, you'll see all your files and every one of your commits. You can travel back in time and look at every revision. In practice, you can use SSH keys to eliminate having to enter your password every time.

""

Figure 7. Commit History

Time Travel (aka Breaking the Laws of Physics with Git)

Time travel, you say? Yes indeed. Let's say you want to jump back in time and review your version of myBlinky from when you had it working with the Arduino UNO. It seems like ages ago. It's easy! You just need to identify the commit id and "checkout" that version.

Use the log command to list all your commits:


me@mybox:~/Arduino/myBlinky $ git log
commit 122911f99dddce2dabbb251c3b640c8c7f9f98d9 (HEAD ->
 ↪master, origin/master)
Author: My Name <me@mybox.com>
Date:   Thu Nov 22 21:22:59 2018 -0500

   Blinky with NeoPixels

commit 2ccff1d7326b1523649510f24644c96df6dc6e12
Author: My Name <me@mybox.com>
Date:   Thu Nov 22 11:42:02 2018 -0500

   Blinking on esp8266 board
commit 35309a0c9e90668052abc9644f77f906ab57949c
Author: My Name <me@mybox.com>
Date:   Thu Nov 22 11:09:44 2018 -0500

   It works! First blink.

commit ee9597269c5da49d573d6662fe8f8137083b7768
Author: My Name <me@mybox.com>
Date:   Thu Nov 22 10:48:33 2018 -0500

   Initial Commit

It looks like the commit starting with 35309a0c is the one you're after. Note: you can shorten the commit hash string to as few as four characters, as long as it uniquely identifies only one commit. Let's explore that version:


me@mybox:~/Arduino/myBlinky $ git checkout 35309a0c
HEAD is now at 35309a0... It works! First blink.
me@mybox:~/Arduino/myBlinky $ ls -l
-rw-r--r-- 1 me me  191 Nov 22 22:01 myBlinky.ino
me@mybox:~/Arduino/myBlinky $ cat myBlinky.ino
// Simple Demo Blinker -MEH
#define PIN_LED 13

void setup() {
        pinMode(PIN_LED,OUTPUT);
}

void loop() {
        digitalWrite(PIN_LED,HIGH);
        delay(500);
        digitalWrite(PIN_LED,LOW);
        delay(500);
}

Now let's say you're done poking around, so let's time travel forward to the current day and get things back to before you broke the laws of physics. In the simple git repository, this means jumping back to the current commit in the "master" branch:


me@mybox:~/Arduino/myBlinky $ git checkout master
Previous HEAD position was 35309a0... It works! First blink.
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
me@mybox:~/Arduino/myBlinky $ ls -l
total 1332
-rwxr-xr-x 1 me me   13956 Nov 22 22:04
 ↪myBlinky.arduino.avr.uno.elf
-rw-r--r-- 1 me me    2640 Nov 22 22:04
 ↪myBlinky.arduino.avr.uno.hex
-rw-r--r-- 1 me me  252960 Nov 22 22:04
 ↪myBlinky.esp8266.esp8266.generic.bin
-rwxr-xr-x 1 me me 1082905 Nov 22 22:04
 ↪myBlinky.esp8266.esp8266.generic.elf
-rw-r--r-- 1 me me     436 Nov 22 22:04 myBlinky.ino

Nice! You're back to the NeoPixel blinky. You see, git stores all the versions of your committed code under the .git subdirectory. This is actually the real location of the repository. The files you edit are just the "work area". When you jump around between commits, git is doing all the magic of modifying the files in the work area. If you wanted to jump back and start modifying the old version of code, you could create a new "branch" to contain that work and move forward with an AVR and esp8266 fork of your code. It's very powerful.

I've barely scratched the surface here. Git, GitHub and arduino-cli are all quite comprehensive tools. I hope this article has given you a taste of what's possible when you harness good programming workflows for your Arduino projects.

Resources

Matthew Hoskins is the Senior Enterprise Architect at New Jersey Institute of Technology where he leads the team of talented professionals that keep our dizzying array of services working together. Systems, storage, on-premises and in the cloud, we do it all. When it's done right, no one notices. Matt is @SYSMATT on Twitter.

Load Disqus comments