Arduino from the Command Line: Break Free from the GUI with Git and Vim!
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:
- https://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.