Arduino Leonardo, VirtualBox, and Windows

I realize this post may be useful to about three other people in the world, but sharing is caring.

What I wanted to do

On my Windows 11 machine I wanted to be able to write and upload firmware to an Arduino Pro Micro (Leonardo) using PlatformIO.

The standard way to accomplish this is to install PlatformIO in VSCode for Windows and you’re done. However, I’m a bit of long time *nix user and really like my PlatformIO + Git workflow in that environment.

WSL sounds like a great option for this case, but unfortunately from what I’ve read, serial communication doesn’t play too nicely without a bunch of hoops to jump through.

To keep things (somewhat) simple I installed a Linux VM through VirtualBox and added a USB Device Filter for the attached Arduino Leonardo so it would be forwarded and accessible by PlatformIO. Using VSCode as my editor would still be possible thanks to its awesome remote features.

The Problem

At this point I thought I’d be done, but after running a quick upload test I kept facing the follow error:

Looking for upload port…
Use manually specified: /dev/ttyACM0
Forcing reset using 1200bps open/close on port /dev/ttyACM0
Waiting for the new upload port…
Error: Couldn't find a board on the selected port. Check that you have the correct port selected. If it is correct, try pressing the board's reset button after initiating the upload.
*** [upload] Explicit exit, status 1
============================================== [FAILED] Took 7.34 seconds ==============================================

PlatformIO would successfully send the reset command to the board, but it would never come back in time to get the new firmware.

Reading up on how the Leonardo works tells us what’s really happening under the hood:

Uploading Code to the Leonardo, Leonardo ETH and Micro

In general, you upload code to the Leonardo or Micro as you would with the Uno or other Arduino boards. Click the upload button in the Arduino IDE and your sketch will be automatically uploaded onto the board and then started. This works more or less the same way as with the Uno: the Arduino software initiates a reset of the board, launching the bootloader – which is responsible for receiving, storing, and starting the new sketch.

However, because the serial port is virtual, it disappears when the board resets, the Arduino software uses a different strategy for timing the upload than with the Uno and other boards. In particular, after initiating the auto-reset of the Leonardo, Leonardo ETH or Micro (using the serial port selected in the Tools > Serial Port menu), the Arduino software waits for a new virtual (CDC) serial / COM port to appear – one that it assumes represents the bootloader. It then performs the upload on this newly-appeared port.

Source – Arduino docs

The Solution

After initiating a reset from PlatformIO and monitoring Device Manager in Windows, I spotted the new “bootloader” device that appeared on a different port. This device was not being forwarded to VirtualBox, so PlatformIO couldn’t see it and gave up looking for it!

Digging down into the device’s properties in Device Manager showed me the two most important pieces I needed to know – the Vendor ID and Product ID, which are 2341 and 8036 respectively.

Ultimately I ended up with two filters in VirtualBox:

After adding the bootloader filter everything works great!

A couple optional but potentially helpful steps I took were installing the Arduino IDE to get drivers, as well as the VirtualBox Extension Pack to get USB 3.0 support. Though we won’t need USB 3.0’s bandwidth, it seems like the controller is better overall.


Looking for upload port...
Use manually specified: /dev/ttyACM0
Forcing reset using 1200bps open/close on port /dev/ttyACM0
Waiting for the new upload port...
Uploading .pio/build/tx/firmware.hex

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

avrdude: AVR device initialized and ready to accept instructions

The same can apply for other boards

Though this example is for the Leonardo, the same can apply for other boards that reset into a special bootloader mode to get new firmware. For the Adafruit Feather M0 I had to do something similar:

  1. Filter for the board in the regular mode with Vendor ID of 239a and (in my case) Product ID of 800b.
  2. Filter for the bootloader mode with a Vendor ID of 239a and Product ID of 0015.

In both of these cases you can make things simpler by just filtering on the Vendor ID. This should work fine unless you have devices that you don’t want to be used in VirtualBox.

Happy New Year!

Watch your tabs!

Yay computers!

Because I missed a tab in a Python script, my home Docker box has been running with a higher-than-necessary CPU utilization and spiky temperatures for five months. 😬

Tab has now been tabbed. 🐍

Categorized as Thoughts

Wyze Cam in iOS

Joining Automattic

I’m thrilled to be joining the fine folks at Automattic this month as a mobile software engineer. Though I’ll miss all of my wonderful Clemson coworkers immensely, I’m excited for the road ahead. 🚀

Categorized as News

Flip Dots!

I saw flip dots (also called flip discs) last year for the first time and instantly knew I needed some in my life. If you’re not familiar with them, check out how they work!

The particular model I have is the ALFAZETA XY5, which may be the easiest way to get up and running, but certainly not the least expensive.

After getting the board, all you need is:

  1. 24V power supply
  2. Something that talks over RS485 (in my case I used an ESP8266 connected to a MAX3485 board)
  3. Their documentation that defines the controller data protocol

I plan to write in more detail how it all works, but for this demo the stack is:

  1. SwiftUI app that runs SwiftGFXWrapper (which is mainly Adafruit’s GFX Library under the hood)
  2. The app sends the entire pixel buffer over UDP to the ESP8266
  3. The ESP8266 sends data to the XY5 over RS485 using their controller’s protocol