Flashing the firmware
Install the toolchain, build the sketch, upload to the ESP32-C3 SuperMini.
The firmware is plain C++ on the Arduino core, built and uploaded entirely
from the terminal with arduino-cli.
There's no Arduino IDE involved.
If you haven't installed arduino-cli and the ESP32 core yet, do that
on the Setup page first. This page covers the day-to-day
build/flash workflow once your toolchain is in place.
Before the daily workflow, make sure the board is visible:
make portsYou should see something like /dev/cu.usbmodem14101 (macOS),
/dev/ttyACM0 (Linux), or COM5 (Windows) tagged with esp32:esp32:esp32c3.
The trailing digits vary by machine and USB port — match on the
usbmodem / ttyACM / COM prefix and the esp32c3 tag, not the
exact number. If nothing shows up, see Troubleshooting.
Daily workflow
The repo's Makefile wraps every common operation. The port is
auto-detected; you only need PORT=... if you have multiple boards
plugged in.
| Command | What it does |
|---|---|
make build | Compile the sketch |
make flash | Compile + upload |
make monitor | Open the serial monitor (115200, Ctrl-C exits) |
make flash-monitor | Flash, then immediately attach the monitor |
make db | Regenerate compile_commands.json for clangd |
make format | Auto-format all sources |
make ports | List connected ESP32 boards |
make clean | Delete build artifacts |
make erase | Wipe the entire flash (factory reset) |
The first build takes ~30 seconds. Subsequent rebuilds are 2–5 seconds (arduino-cli caches aggressively).
When you run make monitor you'll land on a line like:
Connecting to /dev/cu.usbmodem101. Press CTRL-C to exit.That's it — you're connected. The monitor stays attached and streams the
board's serial output; hit Ctrl-C when you want to detach.
Wiping the board
make clean only deletes the host-side build folder — it doesn't
touch the chip. To wipe the board itself (the app, plus saved NVS data
like wifi credentials), use make erase:
make eraseIt finds esptool on your PATH — or falls back to the copy bundled with
the ESP32 core, so there's nothing extra to install — frees the serial
port if the gochi daemon is holding it, and chip-erases the flash. The
board is blank afterwards; run make flash to put the firmware back.
If it hangs at "Connecting…", force download mode
(hold BOOT, tap RESET, release BOOT) and re-run. Pass
PORT=... if you have more than one board plugged in.
Use make flash-monitor 90% of the time. It builds, uploads, and
drops you into the serial console so you can immediately interact with
the pet over the serial protocol.
OLED I²C address
The SSD1306 modules in the kit speak to address 0x3C. A small number of
clones use 0x3D instead. If the screen wakes up black after a successful
flash, run the I²C scanner sketch (one terminal command):
make flash SKETCH=examples/i2c_scanThen make monitor — you'll see the address printed. If it's 0x3D,
edit firmware/src/config.h and change OLED_I2C_ADDR to match.
Force download mode
The ESP32-C3 SuperMini occasionally won't enter the bootloader on its own. Symptoms:
make flashhangs at "Connecting...___"make portsshows the board, but uploads time out
Fix:
BOOT button.RESET while still holding BOOT.BOOT.make flash.You only have to do this once per power-cycle; after a successful upload the board reboots into your code and accepts future flashes normally.
Flashing on Linux / WSL2
Flashing from a native Linux host is straightforward. WSL2 is a lightweight VM with its own kernel, so USB serial devices need extra plumbing that a native host doesn't. Pick your environment:
On a real Linux box it usually just works once the board is plugged in — the
Makefile's autodetect already probes the Linux device names
(/dev/ttyACM*, /dev/ttyUSB*):
make ports # confirm the board shows up, e.g. /dev/ttyACM0
make flash # autodetects PORT; pass PORT=/dev/ttyACM0 if you have several boardsIf you hit Permission denied: /dev/ttyACM0, add yourself to the dialout
group (then log out and back in so it takes effect):
sudo usermod -aG dialout $USERNeed make format? Install clang-format at user level — no root, lands in
~/.local/bin:
pip install --user clang-format # apt alternative: sudo apt install clang-formatWSL2 works, but the ESP32-C3's USB re-enumeration on reset makes it finicky.
If WSL keeps fighting you, flashing from native Windows arduino-cli is
the more reliable path — it skips the usbipd hop entirely. Everything below is
for when you specifically want to flash from inside WSL.
Forward the USB device into WSL with usbipd-win. WSL2 can't see Windows USB devices by default.
# Windows PowerShell (Admin) — one time:
winget install usbipd
# Each session: find the board, bind once, then attach.
usbipd list # note the ESP32's BUSID, e.g. 2-4
usbipd bind --busid 2-4 # one-time per device
usbipd attach --wsl --busid 2-4 --auto-attach # --auto-attach survives re-enumeration--auto-attach matters: the ESP32-C3's native USB re-enumerates often, and
without it the attach drops and won't come back on its own.
Load the CDC-ACM serial driver in WSL. It ships with the WSL kernel but
isn't loaded by default, so no /dev/ttyACM* node is ever created even after a
successful attach:
sudo modprobe cdc_acm # once per bootVerify with lsmod | grep cdc_acm. An attached board then shows up as
/dev/ttyACM0 (confirm in dmesg: cdc_acm 1-1:1.0: ttyACM0: USB ACM device).
If the board reset-loops, force download mode. If dmesg shows the device
number climbing rapidly (re-enumerating every ~0.6s, each lasting under a
second), the firmware is in a crash / watchdog boot-loop and usbipd can never
hold the attach. Force download mode — hold BOOT,
tap RESET, release BOOT — so the stable ROM bootloader runs instead. The
usbipd flapping goes quiet and /dev/ttyACM0 persists.
Fix serial-port permissions. usbipd-attached devices come up owned
root:root mode 600 — the udev rule that normally hands them to the dialout
group doesn't fire under WSL, so even a dialout user can't open the port:
sudo chmod 666 /dev/ttyACM0
make flashThe chmod resets every time the device re-attaches (it's a new node). For a
permanent fix, add a udev rule so every ttyACM* comes up world-accessible:
echo 'KERNEL=="ttyACM[0-9]*", MODE="0666"' | sudo tee /etc/udev/rules.d/99-ttyacm.rules
sudo udevadm control --reload-rules(udev rules only fire if udev/systemd is actually running in your WSL distro.)
Need make format? Install clang-format at user level — no root, lands in
~/.local/bin: pip install --user clang-format (apt alternative:
sudo apt install clang-format).
Cold-start cheat sheet
# Windows (Admin):
usbipd attach --wsl --busid 2-4 --auto-attach# WSL:
sudo modprobe cdc_acm # once per boot
# Hold BOOT, tap RESET, release BOOT (puts board in download mode)
ls /dev/ttyACM* # confirm /dev/ttyACM0 is present & stable
sudo chmod 666 /dev/ttyACM0
make flashEditor setup (optional but nice)
Any editor with clangd support gets full code intelligence — completion,
go-to-definition, inline errors — via the compile_commands.json
generated by make db.
VS Code users can install the clangd extension and it works out of
the box. Other editors with clangd support work similarly — point them
at compile_commands.json and treat .ino files as C++.
Don't install Microsoft's "C/C++" extension alongside clangd — they compete and you'll get duplicate squiggles.
Troubleshooting
| Symptom | Try this |
|---|---|
make: command not found | macOS: xcode-select --install. Linux: sudo apt install make |
arduino-cli: command not found | The binary isn't on your PATH. Run which arduino-cli |
No board in make ports | Try a different USB cable (many are power-only). Reboot the host. |
Upload hangs at Connecting... | Force download mode |
Permission denied: /dev/ttyACM0 (Linux) | sudo usermod -aG dialout $USER, then log out + back in |
Permission denied: /dev/ttyACM0 (WSL2) | dialout doesn't apply — sudo chmod 666 /dev/ttyACM0. WSL setup |
No /dev/ttyACM* in WSL2 after attaching | Load the driver: sudo modprobe cdc_acm. WSL setup |
usbipd attach keeps dropping (WSL2) | Board is reset-looping — force download mode, use --auto-attach |
| Screen flickers after upload | Loose SDA/SCL joint — reflow the OLED end |
| Pet works for 5s then dies | Brown-out: try a higher-quality USB cable or power source |