Arduino is an interesting ecosystem of generally wholesome microcontroller technologies using wholesome licenses. The designs and software are all GPL and the hardware reference standards, which any manufacturer can make and sell, are also pretty open.

This overview is from the spec sheet for ATmega16/32U4.

The ATmega16U4/ATmega32U4 is a low-power CMOS 8-bit microcontroller based on the AVR enhanced RISC architecture. By executing powerful instructions in a single clock cycle, theATmega16U4/ATmega32U4 achieves throughputs approaching 1 MIPS per MHz allowing thesystem designer to optimize power consumption versus processing speed.

An Arduino board (like this one) contains a little microprocessor which can be programmed — using a C like language — to do custom tasks. Although this technology is beloved by electronics hobbyists and electrical engineers, it was strangely envisioned to be used by people who had far less technical abilities. This is why the development platform was strangely mired in a gloopy GUI IDE. These days, however, there are sensible tools for people competent enough to even consider using one of these devices. This specifically is arduino-cli.

Why would such an approach be especially helpful? If you’re like me, you simply can not stand the cruft of IDEs and want to use a proper editor. But imagine that your microcontroller is controlling something based on commands from a Raspberry Pi — on a ship somewhere with expensive bandwidth. How extremely not fun would it be to figure out how to get the bloated GUI stuff worked out? Instead, you can just log in, edit your code, type make and be done.

These notes cover how to get that working efficiently.



There are a few Arduino support packages in native Debian to note. I didn’t explore these but they might be interesting.

arduino-core avr-libc avra binutils-avr

Note that there is a command line utility called avrdude. On Debian systems this can be installed the obvious wholesome way.

sudo apt install avrdude

Oh look. And here’s another one.

apt install dfu-programmer

Both of these seem sufficient. One of them or something equivalent seems necessary.

But it seems helpful to have an additional utility called arduino-cli.

This seems to be the Golang source code (maybe) that the CLI utility is based on. I don’t think this stuff is needed at all. Probably safe to ignore but good to know about if becomes important.

git clone

This archive is the pre-compiled executable directly. Obviously for Raspberry Pi or other architectures, find the right executable. This one has been known to just work for me on AMD64.

tar -xvzf arduino-cli_latest_Linux_64bit.tar.gz

It is a single executable and a license, which along with the archive can be ditched. Here are details about the executable.

$ file arduino-cli
./arduino-cli: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=4G...., stripped

Does it work? Is the architecture of the executable happy? Give it a try.

./arduino-cli version
./arduino-cli help

Some esoteric chores seem to be required.

./arduino-cli lib list

This returned "No libraries installed." So some things need to be done. Start with this.

$ ./arduino-cli core update-index

This might also be needed. I don’t know exactly what this is but it seems to help.

$ wget
$ md5sum package_esp8266com_index.json
db3711727a69c3fdec99b2ae1046539d  package_esp8266com_index.json
$ ./arduino-cli lib update-index

This turned out to be needed to avoid a "platform not installed" error.

arduino-cli core install arduino:avr

Detect Hardware



Max I/O Pins

USB Speed

USB Interface














Full Speed


The chip I’ll use as an example says the following in microscopic print.

|   ATMEL        |
|   MEGA32U4     |
|   MU           |
|   1823E   PH   |
|   1823E6T      |

Now you can plug in your board. I am seeing this.

$ lsusb | grep Arduino
Bus 001 Device 004: ID 2341:8036 Arduino SA Leonardo (CDC ACM, HID)

Now you should be able to see the board detected by the arduino-cli program.

arduino-cli board list
arduino-cli core list

This one may not actually do anything.

arduino-cli board listall mkr


pro micro pins

eliteC pins

chip pins


Ok, now you need a "sketch". This sketchy concept seems to just be the .ino file, which is the C program itself, in a directory of the same name. Something like this.

mkdir MyLilProject
cp /the_repo_path/MyLilProject.ino ./MyLilProject/

Now if your program is using any kind of library, that will have to be sorted out. This one uses "String". Which I could not find. But WString.h was available! Great! We’ll use it by downloading it directly as a file.


Program Organization

Although the BareMinimum.ino code listed here is bare, I suspect the real bare minimum of code required is this.

void setup(){}
void loop(){}

Note the two important required functions are setup and loop. I’m under the impression these are like the BEGIN and main sections of an awk program and are obvious by their names.

This Oreilly guide has a nice tiny example that just spits out numbers and I think it is ideal to just make sure you have a device that you can listen to properly which is doing something you have programmed.

Save this as MyLilProject/MyLilProject.ino

void setup() {
  Serial.begin(9600); // send and receive at 9600 baud

int number= 0; // Simple counter.

void loop() {
  Serial.print("Value: ");
  Serial.println(number);    // adds a cr and/or lf?
  delay(500);                // in milliseconds


I think the string libraries are pretty functional but a bit sloppy. The first problem is that the command line tools seemed to be missing the libraries completely. I had to go find some random internet version.

Then there’s the documentation. I love how this page says "The String function substring() is closely related to charAt(), startsWith() and endsWith()…" but fails to make any of those items links. Web page much? And then on that very page, we find the index to the S.substring(index) function is a "position"; great, I know how this works, the first one is zero, right? Wrong! The first character of the string is 1 — so much for pointer math working in your favor!

Note that to get the size of a string, don’t use sizeof. Instead use S.length().


A sensible way to communicate from a normal computer to the board during its run time is the serial interface. This will be found on something like /dev/ttyACM0.

Note that the Linux command land produces \n (dec 10, 0x0A, [C]-j, linefeed) like C likes. Some other systems like to see \r (dec 13, 0x0D, [C]-m, carriage return) or even (I’m looking at you DOS) \r\n. Comically, Linux will sometimes make a CR into a LF; for example if you just cat >/dev/ttyACM0 and type [C]-m, it will send a [C]-j. So watch out for that.

How do you avoid these gruesome pitfalls? Check exactly what’s really going on with the data source you’re really going to use. For example, if I’m using Bash or Python on a PiOS, I’ll probably want to be looking for \n. How do we check what the Arduino sees? Try this small test code.

void setup() {
  Serial.begin(9600); // send and receive at 9600 baud

void loop() {
  int testbyte= 0;
  if (Serial.available() > 0) {
    testbyte =;
    Serial.print("I received: ");
    Serial.println(testbyte, DEC);

Note that only reads one byte on the 64 byte input stream buffer. It will return a -1 if there is nothing there to input. The Serial.available() returns how many bytes are piling up. I don’t consider this return value too useful because while you’re acting on it, it could clutter up with more. Of course you can handle it, but there’s no special help.

Compiling And Loading To Board

Now it should be possible to compile the ino sketch into something usable.

arduino-cli compile --fqbn arduino:avr:leonardo MyLilProject/MyLilProject.ino

If that worked, you might be ready to upload. This is the upload command which is very likely to not work!

arduino-cli upload --fqbn arduino:avr:leonardo MyLilProject --port /dev/ttyACM0

It may give you some errors like this.

avrdude: Expected signature for ATmega32U4 is 1E 95 87
         Double check chip, or use -F to override this check.
avrdude: error: programmer did not respond to command: leave prog mode
avrdude: error: programmer did not respond to command: exit bootloader
Error during Upload: uploading error: uploading error: exit status 1

To cure this, the astonishing solution is to disable the terrible ModemManager which seems to only cause trouble.

sudo systemctl status ModemManager # Check that it's on (which is a problem).
sudo systemctl stop ModemManager   # Turn it off.
sudo systemctl status ModemManager # Make sure you really did turn it off.

Now the upload command should work. You should also be able to talk directly to the device with some kind of interaction like this.

screen /dev/ttyACM0
echo -en "gpio clear 2\n" > /dev/ttyACM0

Here’s a decent workflow using make — the following Makefile seems to work.

# Makefile to compile and upload Arduino code to boards.


$(BIN): $(SRC) Makefile
        $(ACLI) compile --fqbn $(FQBN) $<

PHONY += install up
up: install
install: $(BIN)
        $(ACLI) upload --fqbn $(FQBN) $(SKETCH) --port $(PORT)

        rm -rv $(SKETCH)/build


Here it is in action.

$ make clean
rm -rv MyLilProject/build
removed 'MyLilProject/build/arduino.avr.leonardo/MyLilProject.ino.hex'
removed 'MyLilProject/build/arduino.avr.leonardo/MyLilProject.ino.eep'
removed 'MyLilProject/build/arduino.avr.leonardo/MyLilProject.ino.with_bootloader.bin'
removed 'MyLilProject/build/arduino.avr.leonardo/MyLilProject.ino.with_bootloader.hex'
removed 'MyLilProject/build/arduino.avr.leonardo/MyLilProject.ino.elf'
removed directory 'MyLilProject/build/arduino.avr.leonardo'
removed directory 'MyLilProject/build'

:->[x][~/ba/arduino]$ make
./arduino-cli compile --fqbn arduino:avr:leonardo  MyLilProject/MyLilProject.ino
Sketch uses 7260 bytes (25%) of program storage space. Maximum is 28672 bytes.
Global variables use 690 bytes (26%) of dynamic memory, leaving 1870 bytes for local variables. Maximum is 2560 bytes.

:->[x][~/ba/arduino]$ make # Second compile is superfluous so skipped.
make: 'MyLilProject/build/arduino.avr.leonardo/MyLilProject.ino.elf' is up to date.

:->[x][~/ba/arduino]$ make up
./arduino-cli upload --fqbn arduino:avr:leonardo  MyLilProject --port /dev/ttyACM0
Connecting to programmer: .
Found programmer: Id = "CATERIN"; type = S
    Software Version = 1.0; No Hardware Version given.
Programmer supports auto addr increment.
Programmer supports buffered memory access with buffersize=128 bytes.

Programmer supports the following devices:
    Device code: 0x44

Note that no compilation was necessary a second time because Make could see that nothing of consequence had changed. Also make up will also compile before uploading if that is necessary allowing you to skip a step if you’re sure of your edit.


If you used the sample program you might be able to do this.

$ cat /dev/ttyACM0
Value:  995

Value:  996

Value:  997


Really, ModemManager is very bad. It causes all kinds of badness. Before doing any troubleshooting, just double check that you really really turned it off.

sudo systemctl status ModemManager
sudo systemctl stop ModemManager
sudo apt-get purge --auto-remove modemmanager

It seems that it’s easy to brick these little things. Or maybe you’re getting garbled transmissions. Both problems are probably related. Could something like this help?

stty -F /dev/ttyACM0 speed 57600 # Or 9600?

What if the unit only presents a serial port for a couple of seconds and then spontaneously disconnects, i.e. no /dev/ttyACM0? This is where it can be helpful to use the reset button. I double clicked that and then was able to sort things out and install a test program with this command.

avrdude -v -patmega32u4 -cavr109 -P/dev/ttyACM0 -b57600 -D -Uflash:w:TestBlink.ino.hex:i

Here is the test program I used to blink the lights.

void setup() {
  Serial.println("Initialize serial output...");

unsigned int c= 0;
void loop() {
  Serial.print("Serial output working...");
  Serial.println(c);  //Send count to serial.
  TXLED0; delay(100); //TX GRN LED off
  RXLED1; delay(100); //RX YEL LED on
  RXLED0; delay(100); //RX YEL LED off
  TXLED1; delay(600); //TX GRN LED on

One problem I kept having was an endless loop when I would watch the serial port on one terminal and feed it commands on another. What seems to be happening is that what the port receives gets echoed back for some arcane reason, but that echo goes in as more input and a feedback loop results. To cure it I did this.

stty -F /dev/ttyACM0 echo