narduino

Write and flash Arduino firmware with Nim using your favorite IDE - easily!

Pure Nim score 15/100 · tests present · no docs generated

Summary

Latest Version 0.5.0
License MIT
CI Status Failing
Downloads 0
Last Indexed 2026-09-05 07:27

Authors

  • Leon Lysak (Niminem)

Installation

nimble install narduino
choosenim install narduino
git clone https://github.com/Niminem/narduino

OS Compatibility

Platform Linux macOS Windows FreeBSD OpenBSD NetBSD Android iOS WASM Embedded
narduino - - - - - - -

Dependencies

Package Version Optional
nim >= 2.2.10 No

Source

Repository https://github.com/Niminem/narduino
Homepage https://github.com/Niminem/narduino
Registry Source nimble_official

README

narduino

Write and flash Arduino firmware (and ESP32) with Nim using your favorite IDE — easily!

Arduino CLI powers the Arduino IDE and other official tooling. narduino provides abstractions on top of it and the Nim compiler so you can build firmware in Nim from any editor: your Nim code is translated to C++, placed into a standard Arduino sketch, and arduino-cli then compiles that sketch for your board and flashes it — all from one command.

nim source ──(nim cpp)──> generated .cpp/.h ──> sketch dir ──(arduino-cli)──> board

narduino is both a CLI tool and a library:

  • CLI: detect boards, compile Nim into sketches, and flash — zero-config.
  • Core API (import narduino): the Arduino API in Nim — digital/analog I/O, time, interrupts, Serial, and setup: / loop: templates so firmware code needs no FFI boilerplate.
  • Library bindings (import narduino/libs/<name>): Nim wrappers for popular Arduino libraries (see src/narduino/libs/ for the full list). Install the Arduino library first with narduino libinstall, then import the binding.
  • Toolchain (import narduino/toolchain): everything the CLI does, as procs you can call from your own tools.

Prerequisites

On macOS, this installs arduino-cli to a directory already on PATH (tested):

curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh

No other dependencies. Stdlib only.

Installation

Install via nimble:

nimble install narduino

Or clone and install from a local copy via git:

git clone https://github.com/Niminem/narduino
cd narduino
nimble install

Quick start

Plug in your board, then clone this repo (or copy the blink example from Writing firmware in Nim below) and run from its root:

narduino flash --src:examples/blink.nim

note: this example was tested on an Arduino UNO

That's it. narduino detects the connected board, installs its core if needed, compiles the Nim code, and uploads it. The built-in LED should start blinking.

Zero-config is the default everywhere: the board's fqbn, serial port, and target cpu are auto-detected from whatever is plugged in.

Every flag exists only to override that detection (multiple boards connected, boards like Teensy that can't report their cpu, uploading to a board that isn't currently attached, etc.).

Examples

The examples/ directory is a collection of working firmware written with narduino — flash any of them with narduino flash --src:examples/<name>.nim. Some are commented walkthroughs, others are bare-bones references, but all are real programs tested on real hardware:

  • Basics — blink, serial echo, button input, internal pull-up, RGB LED with buttons
  • Sensors — photoresistor, PIR motion, DHT11 temperature/humidity
  • Motors — servo sweep, servo driven by a potentiometer or photoresistor, DC motor, stepper
  • Sound — active buzzer via transistor, passive buzzer playing a melody
  • UNO R4 WiFi — built-in LED matrix, temperature readout on the matrix, LED toggling from a browser over WiFi, and a UDP listener with a host-side client

CLI reference

narduino <command> [--flag:value]
Command What it does
new Creates a new, blank Nim firmware file
boards Lists connected boards (detected serial ports and their matches)
active Shows the active board (fqbn and port)
install Installs the core (platform) for the active board
sketch Creates a sketch directory and compiles Nim code into it
upload Uploads a compiled sketch to the board
flash One-shot sketch + upload
monitor Opens an interactive serial monitor (Ctrl+C to exit)
libsearch Searches the Arduino library index
libinstall Installs an Arduino library
docs Opens the API documentation in your default browser
help Shows help for all commands (also shown when run with no arguments)

narduino new

Creates a new Nim firmware file from a minimal template (setup: with Serial ready, empty loop:) — a quick starting point for a new project. Won't overwrite an existing file.

Flag Description
--dest:<path> Destination directory, relative or absolute (default: current directory)
--name:<name> Filename without extension, e.g. --name:motor creates motor.nim (default: blank)

narduino sketch

Compiles a Nim source file to C++ and places the generated files in an Arduino sketch directory (created as sketch_<name>/ in the current directory if not specified).

Flag Description
--src:<path> Path to the Nim source file [required]
--dir:<path> Path to the sketch directory
--cpu:<cpu> CPU for Nim compilation (ex: avr, arm)

narduino upload

Hands a sketch directory to arduino-cli, which compiles the C++ for the board and uploads it (the Nim-to-C++ step happens in sketch, not here). If no directory is given, uses the first sketch_* directory found in the current directory.

Flag Description
--dir:<path> Path to the sketch directory
--fqbn:<fqbn> Fully qualified board name (ex: arduino:avr:uno)
--port:<port> Serial port of the board
--autoinstall:<bool> Auto-install the board's core (default: true)
--verbose:<bool> Verbose arduino-cli output (default: false)

narduino flash

Does sketch + upload in one shot, resolving the board once and reusing it for both steps. Accepts all flags from both commands (--src required).

Note: flags take the --flag:value form. Boolean flags need an explicit value, e.g. --verbose:true or --autoinstall:false.

narduino monitor

Opens an interactive serial monitor on the board's port. Data is streamed in real time; press Ctrl+C to exit.

Flag Description
--port:<port> Serial port of the board
--baud:<rate> Baud rate (default: 9600)

narduino libsearch

Searches the Arduino library index (updates the index first to ensure fresh results). The query is passed directly to arduino-cli lib search, so the full qualifier-value syntax is supported.

Flag Description
--query:<text> Search query [required]

Examples:

narduino libsearch --query:servo
narduino libsearch --query:'servo category:"Device Control"'
narduino libsearch --query:'name:Servo'

narduino libinstall

Installs an Arduino library by name. Supports versioned syntax to pin a specific version.

Flag Description
--lib:<name> Library name (supports Name@version syntax) [required]

Examples:

narduino libinstall --lib:Servo
narduino libinstall --lib:"Servo@1.2.1"

Writing firmware in Nim

Importing narduino gives you the Arduino API in Nim, so a blink is just:

import narduino

setup:
  pinMode(LED_BUILTIN, OUTPUT)

loop:
  digitalWrite(LED_BUILTIN, HIGH)
  delay(1000)
  digitalWrite(LED_BUILTIN, LOW)
  delay(1000)

The setup: and loop: templates take care of the entry points the Arduino core expects (exported with C linkage, no name mangling) and of initializing the Nim runtime — no boilerplate in your firmware code.

The core API covers everything from the official reference: digital and analog I/O, time, tone/pulse/shift, interrupts, random numbers, character tests, the String class, and Serial (see src/narduino/api/ for details):

import narduino

setup:
  Serial.begin(9600)

loop:
  if Serial.available() > 0:
    Serial.print("got: ")
    Serial.println(Serial.read())

Anything not (yet) wrapped can be bound by hand.

proc bitRead(value: culong, bit: uint8): cint {.importc, header: "Arduino.h".}

Arduino library bindings

Some popular Arduino libraries have Nim wrappers under narduino/libs/. Install the Arduino library with narduino libinstall, then import the binding alongside narduino:

import narduino
import narduino/libs/servo

var myServo = initServo()

setup:
  myServo.attach(9)

loop:
  myServo.write(90)
  delay(1000)
Library Import Arduino install
Servo import narduino/libs/servo narduino libinstall --lib:Servo
ArduinoGraphics import narduino/libs/arduino_graphics narduino libinstall --lib:ArduinoGraphics
Arduino LED Matrix import narduino/libs/arduino_led_matrix Bundled with UNO R4 board package

See src/narduino/libs/ for the full updated list of available library bindings.

If you write the entry points manually instead of using the templates, export setup()/loop() with {.exportc.} and call NimMain() first thing in setup() to initialize the Nim runtime.

Under the hood, narduino compiles with flags suited for embedded targets (--os:any --mm:arc -d:useMalloc --noMain -d:danger ...) and copies the generated C++ files plus nimbase.h into the sketch directory, where arduino-cli treats them as ordinary sketch sources.

Using the toolchain as a library

Everything the CLI does is available programmatically via the narduino/toolchain module. It runs on your computer (not on the board), so keep it out of firmware code — import narduino alone stays firmware-safe:

import narduino/toolchain

# board discovery
let boards = listBoards()            # all detected ports + matching boards
let active = getActiveBoard()        # the single connected board (fqbn + port)

# core management
ensureCoreInstalled(active.fqbn)     # install the board's core if missing

# library management
searchLib("servo")                   # search + print results from the library index
installLib("Servo")                  # install a library (or "Servo@1.2.1" for a specific version)

# build & flash
let sketchDir = createSketch("blink.nim")  # nim -> c++ -> sketch dir
upload(sketchDir)                          # compile & upload via arduino-cli

All procs raise errors with user-ready messages on failure, and auto-detect the board when fqbn/port/cpu arguments are omitted.

Supported boards

Board detection works for anything arduino-cli recognizes. The Nim --cpu is derived automatically from the board's architecture — AVR (UNO, Mega, Nano, ...), ARM (SAMD, RP2040, Renesas UNO R4, STM32, nRF52, ...), ESP8266/ESP32, and CH32V RISC-V boards. Teensy boards need an explicit --cpu since they don't report which CPU they carry (2.x is AVR, 3.x/4.x are ARM).

Third-party cores that aren't in the official package index (ESP8266, ATTinyCore, ...) work too: register the core's package index URL with arduino-cli once, and narduino's core installation picks it up automatically from your arduino-cli configuration.

arduino-cli config add board_manager.additional_urls https://arduino.esp8266.com/stable/package_esp8266com_index.json

note: third-party cores are untested with narduino; see the arduino-cli docs for details on registering additional package indexes

Troubleshooting

Garbled serial monitor output (Arduino UNO R4 WiFi): the R4 WiFi routes serial through an ESP32-S3 bridge chip, which can produce garbled serial monitor output after plugging in or flashing. This is a known hardware issue — press the board's reset button before opening the monitor to resolve it.

Board unresponsive after flashing: some boards (including the R4 WiFi) can occasionally end up in a bad state after flashing — the board may stop responding or behave unexpectedly. If a reset doesn't fix it, disconnecting and reconnecting the USB cable reliably resolves the issue.

Serial monitor line endings: the Arduino IDE lets you choose "No line ending" when sending data, but arduino-cli monitor (which narduino uses under the hood) has no such option — your terminal's newline is sent as-is when you press Enter. If your firmware reads line-based input, one option is to use readStringUntil('\n') followed by trim() to strip the trailing \r that terminals send (see examples/serial.nim for a working pattern). A built-in serial monitor with line-ending control is planned; PRs welcome.

License

MIT — see LICENSE.