Introduction
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.
Resources
-
The Raspberry Pi Pico is a $4 microcontroller board that looks sensible.
-
The C/C++ SDK for Pi Pico. Looks sensible.
-
A helpful article about using arduino-cli.
-
ORA has some good stuff for literate people.
-
Not quite getting what all these devices do? Sparkfun’s Arduino Comparison Guide can help.
-
Details on the Qwiic Pro Micro USB-C DEV-15795 board. This includes a good test program and general tutorial.
-
Pro Micro Pin Out Diagram. Hard to say how universal this is but it illustrates many important themes.
-
Spec sheet for ATmega16/32U4 - very, very detailed.
-
A neat tiny Arduino (is it really) board: Adafruit QT Py - SAMD21
-
Need a much faster clock speed? Maybe for fine motion control. Check out the newer ESP32 chips. I don’t know how a 240MHz chip (presumably with an onboard oscillator) works on with 32kHz board oscillator, but if it does, that’s a lot faster than the 8MHz board oscillator of a normal ATmega32u4 board.
-
A nice general purpose CNC controller based on ESP32.
-
This STM32F072 microprocessor is known to work fine in custom keyboard applications.
Install
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 https://arduino.github.io/arduino-cli
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.
wget https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Linux_64bit.tar.gz
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 https://arduino.esp8266.com/stable/package_esp8266com_index.json
$ 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
Microcontroller |
SRAM |
Max I/O Pins |
USB Speed |
USB Interface |
ATmega32 |
2kB |
32 |
No |
No |
ATmega32A |
2kB |
32 |
No |
No |
ATmega32U4 |
2.5kB |
26 |
Full Speed |
Device |
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
HID And Keyboards
Interested in having the Arduino pretend to be a keyboard or some such Human Interface Device thingy? JWZ has a nice thread asking this very thing. An interesting answer…
…the ATmega328P does not have any USB hardware built in and typically use a FTDI (or knockoff) USB serial converter that doesn’t have any flexibility. There is the bit-banging V-usb stack that fakes it on GPIO pins, and can emulate USB HID keyboards, although you might be better off with a slightly fancier Arduino if you want stability.
Most of the Arduino USB HID keyboards use ATmega32U4 variants like the Leonardo, which do have a USB peripheral. I’ve been very happy with the smaller form-factor Teensy 2 for 5v peripherals or the Teensy 3.2 for 3.3v with a much faster CPU.
Pins
Programming
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.
wget https://raw.githubusercontent.com/arduino/Arduino/ide-1.5.x/hardware/arduino/avr/cores/arduino/WString.h
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 number++; }
String
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()
.
Serial
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.read(); Serial.print("I received: "); Serial.println(testbyte, DEC); } }
Note that Serial.read()
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. ACLI=./arduino-cli FQBN=arduino:avr:leonardo PORT=/dev/ttyACM0 SKETCH=MyLilProject SRC=$(SKETCH)/$(SKETCH).ino BIN=$(SKETCH)/build/arduino.avr.leonardo/$(SKETCH).ino.elf $(BIN): $(SRC) Makefile $(ACLI) compile --fqbn $(FQBN) $< PHONY += install up up: install install: $(BIN) $(ACLI) upload --fqbn $(FQBN) $(SKETCH) --port $(PORT) clean: rm -rv $(SKETCH)/build .PHONY: $(PHONY)
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.
Testing
If you used the sample program you might be able to do this.
$ cat /dev/ttyACM0
Value: 995
Value: 996
Value: 997
Troubleshooting
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.begin(9600);
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
c++;
}
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