PiDuino? (ArduinoPi2)

,

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.

PiDuino
Raspberry Pi 4B and Arduino Uno

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.

Nightstand Clock
Round screens are so cool!

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:

PiDuino Dev Hardware
Development Hardware (Pi4B/4GB and Arduino Uno)

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:

Clock Hardware
Final Hardware For Nightstand Clock
Nightstand Clock Finished
This is the whole package. Hardware is finished, but software is only just started!

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.