Previously I wrote a local article about using an Arduino as a peripheral device connected to the Raspberry Pi (using Firmata). This new article looks at a different way to connect these two star-crossed lovers enabling us to manage the beautiful analog sweetness of the Arduino within a much larger, more powerful, and Internet-connected Raspberry Pi. This article shows how you can have a program on the Raspberry Pi dynamically reprogram (i.e., re-flash) your Arduino in the field, using the arduino-cli. In this approach, you can think of the Raspberry Pi as an Arduino peripheral device that enables the Arduino to be re-flashed over the Internet whenever desired.

I originally started thinking about this approach when I found this small round screen for sale on Amazon. I have always liked round screens and I immediately wanted to create a project using one. Conveniently (or inconveniently, depending on one’s point of view I guess) my bedside table clock was starting to fail. It’s a digital clock with 3 individually switched colored backlights (red, green, and blue) that are actual light bulbs! First the blue one failed, so my desired blue+green combo no longer worked. So I lived with green for a while. Then green went. Now I have to see it in red, and I don’t like that (and someday soon I expect red will go too). So from misfortune comes opportunity! I ordered a screen and began designing a 3D model I could print to enclose it as my night stand clock. Even though it said clearly that it was only for Arduino, I assumed I could figure out how to use it from a Raspberry Pi, but I never figured that out. Instead I used the approach in this article.

I considered making this a pure Arduino project, but I wanted Internet connectivity (to keep the time accurate) and I wanted to be able to easily make code changes without disassembling to project to flash the tiny Arduino Pro Mini inside. Using the Firmata approach I describe in the other article was not viable since I needed the DFRobot code to run on the device as well, so I decided to add a Raspberry Pi, connected to the Arduino that could communicate with it over serial to update the time periodically, and to enable me to re-flash the Arduino anytime I wanted to change the appearance or behavior of this project.
Before settling on the solution here, I identified these options that you may wish to consider:
- running the Arduino IDE on the RPi
- using the
arduino
executable deployed with the IDE as a CLI tool - using the
arduino-cli
tool
I did not find either of the first two options desirable. At the best of times I find the Arduino IDE to be unreliable (!) and difficult to debug when it misbehaves. Also, I found the arduino
executable similarly difficult to work with. In contrast, the arduino-cli
tool was very straightforward to setup, and provided great instructions. I also found it easy to debug when things went wrong. So this article shows how to use the arduino-cli
. Also, because I prefer to do all coding inside Docker containers, this article does so too.
I used the hardware setup shown below to develop this code:

My initial setup using the Arduino IDE required a lot of RAM and a Desktop Linux Image, so I went with a big Raspberry Pi. Also, to get going I used a classic Arduino Uno which is “standard” and supported by default without me having to add other board support in the IDE. So this was intended to be a nice easy way to get things working before I tried anything more difficult. The final version using arduino-cli
is much smaller, and easy to configure for other boards, so this setup was overkill by the time I got to that solution.
So let’s jump right in to the solution. This is the Dockerfile I used:
FROM ubuntu:bionic # Install curl RUN apt update && apt install -y curl && rm -rf /var/lib/apt/lists/* # Install the Arduino CLI RUN mkdir /arduino WORKDIR /arduino RUN curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh RUN mv /arduino/bin/arduino-cli /bin WORKDIR / RUN rmdir /arduino/bin RUN rmdir /arduino # Validate the install RUN arduino-cli version # Copy the Blink sketch WORKDIR /code RUN mkdir -p /code/Blink COPY sketches/Blink.ino /code/Blink RUN mkdir -p /code/Faster COPY sketches/Faster.ino /code/Faster # Copy the example reprogrammer code and run it as the default CMD # It repeatedly programs the Uno with the Blink.ino code, then reprograms it # with the Faster.ino code, and repeats (pausing between each of these steps). WORKDIR /code COPY example.sh /code CMD ./example.sh
The arduino-cli
provides a simple installer, so the Dockerfile begins by installing the curl
utility, then it downloads the installer with curl
and pipes it directly into sh
. That’s almost all there is to it.
After it is installed I verify it by running the arduino-cli version
command, then I just copy over two example Arduino “sketches” (Arduino porgrams are called “sketches”) and then my example code. And finally I set the example code as the default command. We’ll take a look at that example program in a moment.
Since this code is run in a container, you will need to give it access to the Arduino attached to USB. There may be a less permissive way to do this, but I simply granted root level privilege to the container (by passing --privileged
to docker run
). E.g., I build and run the example container like this:
docker -t ibmosquito/aardvark:1.0.0 . docker run -it --privileged ibmosquito/aardvark:1.0.0
I provide a Makefile that saves these technical command details. Given that, I usually just use make
to do the build step, and make run
to run it. Check out the Makefile
if you are interested in what happens when these make
targets are invoked.
Now let’s look at the example.sh
program to see how to use the arduino-cli
. Begin by attaching your Arduino with the USB cable, then start the container.
This is the example.sh
program I use:
#!/bin/bash # What board will be used? MY_BOARD="Arduino Uno" echo "***** Board=${MY_BOARD}" # Get the details for this board (i.e., for an "Arduino Uno") # - Port (serial) where your board is mounted is needed for uploading to it # - Fully Qualified Board name is needed to identify your board specifically # - Core is required so the appropriate support libraries can be included # The code below extracts these from the "arduino-cli board list" output: MY_PORT=`arduino-cli board list | grep "${MY_BOARD}" | head -1 | awk '{print $1;}' ` MY_FQBN=`arduino-cli board list | grep "${MY_BOARD}" | head -1 | awk '{print $(NF-1);}' ` MY_CORE=`arduino-cli board list | grep "${MY_BOARD}" | head -1 | awk '{print $(NF);}' ` echo "***** FQBN=${MY_FQBN}, Port=${MY_PORT}, Core=${MY_CORE}" echo "***** Installing the \"core\" support code." arduino-cli core install "${MY_CORE}" # Loop forever, installing Blink, then Faster, then repeating cd /code while :; do echo "***** Compiling Blink.." arduino-cli compile --fqbn "${MY_FQBN}" Blink echo "***** Uploading Blink.." arduino-cli upload -p "${MY_PORT}" --fqbn "${MY_FQBN}" Blink echo "***** Sleeping..." sleep 30; echo "***** Compiling Faster.." arduino-cli compile --fqbn "${MY_FQBN}" Faster echo "***** Uploading Faster.." arduino-cli upload -p "${MY_PORT}" --fqbn "${MY_FQBN}" Faster echo "***** Sleeping..." sleep 30; done
Note that the first step is to run arduino-cli board list
to list all the currently connected Arduino boards. Your board must show up in this list, and you need to extract 3 things from that output. The code above extracts those things (Port, FQBN, and Core) and stores them in variables for you (by searching the output for your board name, and chopping away at that line to get these things). The output from that command looks like this:
Port Type Board Name FQBN Core /dev/ttyACM0 Serial Port (USB) Arduino Uno arduino:avr:uno arduino:avr ...
Note that column 1 (“Port”) contains the device path used to communicate with the Arduino (in this case a serial port). This port path will be needed when you upload your code to the Arduino. Column 4 (“FQDN”) shows the Fully Qualified Board Name for your board. You will need to use this name to identify your board for both compilation and for upload. Column 5 (“Core”) identifies the appropriate core package to install for board support. This package must be installed before you can compile and upload your code to the board. So installing that board support is the next step.
The arduino-cli core install ...
command is used to install bard support libraries. In the case of my Uno, the required core is arduino:avr
, so I install that. Once that is done, you are ready to compile and upload code to the Arduino board.
Now the example code drops into an infinite loop. In that loop it repeatedly re-programs (re-flashes) the Arduino board, first with the sketches/Blink.ino
program, then 30 seconds later with the sketches/Faster.ino
program, then 30 seconds later it repeats. Since the Uno has a built-in LED that these programs act upon, you should easily be able to visually watch these code changes.
The Blink.ino
program turns the LED on for 1 second, then off for 1 second, then repeats. The Faster.ino
program turns the LED on for 0.4 seconds, then off for 0.1 seconds, so it flashes much more quickly when the latter is running.
In the loop you will see the compile
and upload
commands being used over and over. E.g.:
arduino-cli compile --fqbn MyFQBN MyProgram arduino-cli upload -p MyPort --fqbn MyFQBN MyProgram
The MyProgram
in both commands above is a directory containing the .ino
“sketch” code you wish to compile and upload. After running the arduino-cli compile ...
command you should see three new files (.elf
, .hex
, and with_boodloader.hex
) in that directory, e.g.:
Blink.arduino.avr.uno.elf Blink.arduino.avr.uno.hex Blink.arduino.avr.uno.with_bootloader.hex Blink.ino
The MyFQBN
in both the compile
and upload
commands above needs to be replaced with the Fully Qualified Board Name you got from the arduino-cli board list
command. Similarly, the MyPort
in the upload
command above needs to be replaced with the port path from the first column in the arduino-cli board list
output.
For illustration purposes, the two programs are being recompiled each time through the loop here, but of course the compilation could be done once for each before entering the loop, then the loop could simply alternate only uploading the already compiled files.
All of this example code (the Dockerfile
, Makefile
, example.sh
and sketches, etc.) with some additional documentation is available in my GitHub repo.
P.S. This is the final hardware setup for my nightstand clock. I use an Arduino Pro Mini directly connected to the round display, then a Sparkfun FTDI USB-to-Serial board is attached to the Pro Mini on one side and to a very old Raspberry Pi 2B on the other side, using a tiny home-made cable to save space:

