Board presets: - anvil new --board mega (uno, mega, nano, nano-old, leonardo, micro) - anvil new --list-boards shows presets with compatible clones - FQBN and baud rate flow into .anvil.toml via template variables - Defaults to uno when --board is omitted Devices --clear: - anvil devices --clear deletes .anvil.local, reverts to auto-detect
Anvil
Arduino project generator and build tool -- forges clean embedded projects.
A single binary that scaffolds self-contained Arduino projects with hardware abstraction, Google Mock infrastructure, and a streamlined build/upload/monitor workflow. Works on Linux and Windows.
Generated projects are fully standalone -- they only need arduino-cli in
PATH. The Anvil binary is a scaffolding and diagnostic tool, not a runtime
dependency.
Anvil is a Nexus Workshops project.
Install
Download the latest release binary for your platform:
# Linux
chmod +x anvil
sudo mv anvil /usr/local/bin/
# Windows -- add anvil.exe to a directory in your PATH
Then run first-time setup:
anvil setup
This checks for arduino-cli, installs the arduino:avr core, and verifies
your system is ready. If something is missing, Anvil tells you exactly what
to do.
Your First Project
Create a project, plug in your board, and upload:
anvil new blink
cd blink
On Linux/macOS:
./build.sh # compile only (verify)
./upload.sh # compile + upload to board
./upload.sh --monitor # compile, upload, open serial monitor
./monitor.sh # serial monitor (no compile)
./test/run_tests.sh # host-side unit tests (no board needed)
On Windows:
build REM compile only
upload REM compile + upload
upload --monitor REM compile, upload, open serial monitor
monitor REM serial monitor
test\run_tests REM host-side unit tests
Every script reads its settings from .anvil.toml in the project root.
You edit that file to change the board, baud rate, include paths, or
compiler flags. No Anvil binary required for any of this -- students clone
the repo, plug in a board, and run upload.
Telling Anvil Which Board to Use
When you run upload without specifying a port, Anvil's scripts auto-detect
the board. They prefer USB serial ports over legacy motherboard COM ports,
which is usually the right thing. But if you have multiple boards plugged in,
or you want a consistent setup, you can pin a specific device.
See what's connected
anvil devices
This lists every serial port with its board name, protocol, and -- for USB
devices -- its VID:PID identifier. The VID:PID is a pair of hex codes that
uniquely identifies the USB chip on your board (e.g. 0403:6001 for an FTDI,
1a86:7523 for a CH340).
Save a default device
anvil devices --set COM3 # save a specific port
anvil devices --set # auto-detect the best port and save it
Both forms write a .anvil.local file in your project directory. This file
stores the port name and the VID:PID of the device on that port. It looks
like this:
# Machine-specific Anvil config (not tracked by git)
port = "COM3"
vid_pid = "0403:6001"
The .anvil.local file is gitignored by default. Each machine that works on
the project keeps its own copy with its own port assignment.
Why VID:PID matters
On Windows, if you unplug a USB device and plug it back in to a different
port, it often gets a new COM number (COM3 becomes COM5). The VID:PID
doesn't change -- it's baked into the USB chip. When the scripts see a
VID:PID in .anvil.local, they search for that device on whatever port
it's currently on. If it moved, you'll see:
info Device 0403:6001 found on COM5 (moved from COM3)
No manual intervention needed.
Check what's saved
anvil devices --get
This shows the saved port and VID:PID for the current project, whether the device is currently connected, and whether it has moved to a different port.
Port resolution priority
When the scripts need a port, they check in this order:
-pflag --upload.bat -p COM3always wins- VID:PID from
.anvil.local-- finds the device regardless of port number - Saved port from
.anvil.local-- fallback if VID:PID lookup fails - Auto-detect -- prefers USB serial over legacy COM ports
Specifying a project directory
Both --set and --get default to the current directory, but you can point
them at another project:
anvil devices --set COM3 -d ../other-project
anvil devices --get -d ../other-project
Updating Project Scripts
When you upgrade Anvil, your existing projects still have the old build
scripts. The refresh command updates them to the latest versions without
touching your source code, configuration, or test files:
anvil refresh # check current project (dry run)
anvil refresh --force # overwrite differing scripts
anvil refresh ../other-project # check a different project
Refresh only replaces build infrastructure: build.sh, build.bat,
upload.sh, upload.bat, monitor.sh, monitor.bat, _detect_port.ps1,
and test/run_tests.sh, test/run_tests.bat. It never touches .anvil.toml,
.anvil.local, your .ino files, HAL headers, app code, mocks, or test
sources.
What Anvil Does vs. What the Project Does
| Need Anvil for | Don't need Anvil for |
|---|---|
anvil new (create project) |
./build.sh (compile) |
anvil setup (install core) |
./upload.sh (flash) |
anvil doctor (diagnose) |
./monitor.sh (serial) |
anvil devices (port scan) |
./test/run_tests.sh (test) |
anvil refresh (update scripts) |
Once a project is created, Anvil is optional for daily work.
Commands
| Command | Description |
|---|---|
anvil new NAME |
Create a new project with HAL and test scaffold |
anvil setup |
Install arduino-cli and AVR core |
anvil doctor |
Check system prerequisites |
anvil devices |
List connected boards and serial ports |
anvil devices --set [PORT] |
Save default port + VID:PID to .anvil.local |
anvil devices --get |
Show saved port for this project |
anvil refresh [DIR] [--force] |
Update project scripts to latest templates |
Project Structure
Every generated project follows the same layout:
your-project/
your-project/your-project.ino Entry point (setup + loop)
lib/
hal/
hal.h Hardware abstraction interface
hal_arduino.h Real hardware implementation
app/
your-project_app.h Application logic (testable)
test/
mocks/
mock_hal.h Google Mock HAL
sim_hal.h Stateful simulator HAL
test_unit.cpp Unit tests
CMakeLists.txt Test build system
run_tests.sh / .bat Test runner
build.sh / build.bat Compile sketch
upload.sh / upload.bat Compile + upload to board
monitor.sh / monitor.bat Serial monitor
_detect_port.ps1 Port detection helper (Windows)
.anvil.toml Project config (tracked by git)
.anvil.local Machine-specific config (gitignored)
All hardware access goes through the Hal interface in lib/hal/hal.h.
Application code in lib/app/ depends only on Hal, never on Arduino.h
directly. This means the app logic compiles and runs on the host for testing
with Google Mock -- no board required.
Configuration
.anvil.toml (tracked by git)
Shared project settings. Edit this to change the board, baud rate, or compiler flags:
[project]
name = "blink"
anvil_version = "1.0.0"
[build]
fqbn = "arduino:avr:uno"
warnings = "more"
include_dirs = ["lib/hal", "lib/app"]
extra_flags = ["-Werror"]
[monitor]
baud = 115200
.anvil.local (not tracked by git)
Machine-specific settings, created by anvil devices --set:
port = "COM3"
vid_pid = "0403:6001"
Building from Source
cargo build --release
The release binary is at target/release/anvil (Linux) or
target\release\anvil.exe (Windows).
License
MIT -- see LICENSE.