Programming an Arduino Nano R4 (Renesas RA4M1 / R7FA4M1AB) from VSCode on Arch Linux,
driven entirely by arduino-cli. No Arduino IDE, no Arduino VSCode extension.
| Board | Arduino Nano R4 |
| FQBN | arduino:renesas_uno:nanor4 |
| Core | arduino:renesas_uno |
| Port | /dev/ttyACM0 |
| IntelliSense | clangd (system /usr/bin/clangd) |
I tried the obvious route first (the "Arduino Community Edition" VSCode extension) and it
would not install. Its hard dependency ms-vscode.cpptools is a ~100MB download, and the
marketplace fetch aborted every single time on this machine. Small extensions installed fine,
only the big one failed, so it's a flaky large transfer, not a broken marketplace.
So I dropped the extension entirely and built the lab on arduino-cli, which has its own
resuming downloader and pulled the 95MB ARM toolchain without complaint. The pieces:
- Build and upload:
arduino-cli, wired into VSCode as Tasks (no extension needed). - IntelliSense: clangd (already on the system via pacman), not cpptools. clangd reads a
generated
.clangdconfig so autocomplete works in.inofiles. - Debugging: Serial prints for the normal case. Real step debugging is possible but needs an external SWD probe (see below), because the Nano R4 has no onboard debugger.
Everything below is enough to rebuild this from nothing.
sudo pacman -S arduino-cli # or: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
arduino-cli config init
arduino-cli core update-index
arduino-cli core install arduino:renesas_unoarduino-cli core install is the one big download. It grabs the Renesas/ARM toolchain
(arm-none-eabi-gcc), bossac, dfu-util, and openocd. This is also where the original extension
fell over, so doing it through arduino-cli is the whole trick.
Confirm the board is seen (plug it in first, genuine boards self-identify):
arduino-cli board list
# /dev/ttyACM0 serial Arduino Nano R4 arduino:renesas_uno:nanor4 arduino:renesas_unoThe port /dev/ttyACM0 is owned by root:uucp. Add yourself to uucp so you can read it
(serial monitor), then log out and back in for it to take effect:
sudo usermod -aG uucp $USERUploading is separate from the serial port. The Nano R4 flashes over a DFU USB bootloader,
a raw USB device that uucp does not cover, so without a rule the upload dies with
LIBUSB_ERROR_ACCESS.
The fix is a udev rule, and the file number matters. uaccess (which hands the device to
whoever is logged into the local session) is applied by systemd's 73-seat-late.rules. A rule
numbered above 73 runs too late, the tag is not set when systemd checks, and nothing happens.
So the file has to sort below 73:
sudo tee /etc/udev/rules.d/70-arduino.rules >/dev/null <<'EOF'
SUBSYSTEM=="usb", ATTRS{idVendor}=="2341", TAG+="uaccess"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2341", TAG+="uaccess"
EOF
sudo udevadm control --reload-rules
sudo udevadm trigger --action=addUnplug and replug the board, then check:
getfacl /dev/ttyACM0 # want a line: user:<you>:rw-If that line is missing (for example your login is not a local "seat"), skip uaccess and
just open the permissions, which has no session dependency:
printf 'SUBSYSTEM=="usb", ATTRS{idVendor}=="2341", MODE="0666"\nSUBSYSTEM=="tty", ATTRS{idVendor}=="2341", MODE="0666"\n' | sudo tee /etc/udev/rules.d/70-arduino.rules
sudo udevadm control --reload-rules && sudo udevadm trigger --action=addMODE="0666" lets any local user open the port, which is fine on a personal machine.
Note on multiline commands: this shell uses ble.sh, which drops into a MULTILINE buffer for heredocs. Press
Ctrl+Jto run it (plain Enter just adds a line).Ctrl+Ccancels.
code --install-extension llvm-vs-code-extensions.vscode-clangd # IntelliSense engine
code --install-extension ms-vscode.vscode-serial-monitor # serial monitor UI
code --install-extension marus25.cortex-debug # SWD debugging (needs a probe)cpptools is deliberately not used (it is the download that fails). clangd does the same job
here and is already installed system-wide via pacman.
.vscode/settings.jsonpoints clangd at/usr/bin/clangdwith a--query-driverallowlist covering every.arduino15toolchain, and maps*.inoto C++ for highlighting..vscode/tasks.jsonholds Compile / Upload / Upload & Monitor / Serial Monitor / Refresh IntelliSense / List Boards.FQBNandPORTlive at the top as env vars..vscode/keybindings(user-levelkeybindings.json) binds the shortcuts below..clangdis the generated IntelliSense config. Do not hand-edit it..vscode/gen-clangd.pyregenerates.clangd. It reads the real compiler and include paths out ofarduino-cli's compile database, so it works for any board, not just this one..vscode/launch.jsonis the cortex-debug config for hardware debugging.
Open the folder ~/Projects/ArduinoWings (not a loose file), and keep the .ino you are
working on as the focused editor tab. The tasks act on whichever sketch is open.
| Shortcut | Action |
|---|---|
Ctrl+Shift+B |
Compile the open sketch |
F6 |
Upload & Monitor: compile, flash, then stream serial output |
F7 |
Open the serial monitor only |
Prefer menus: Terminal then Run Task... lists every task. Output shows in the terminal
panel at the bottom. Ctrl+C in that panel stops the monitor.
bin/ard is a small subcommand-based CLI that wraps the compile, upload, and monitor steps so I
don't have to retype the FQBN and port every time. It is symlinked onto my PATH, so I can run
ard from any sketch folder. It works for any board: pass the FQBN, or let it auto-detect a
connected one.
Install it (from a fresh checkout):
chmod +x bin/ard
ln -sf "$PWD/bin/ard" ~/.local/bin/ard # ~/.local/bin is on PATH
mkdir -p ~/.local/share/man/man1 # man page
ln -sf "$PWD/man/ard.1" ~/.local/share/man/man1/ard.1Use it:
ard upload # auto-detect board + port, build + flash current sketch
ard flash # ... then open the serial monitor
ard compile ~/sk/Blink # compile a specific sketch, no flashing
ard up -b arduino:avr:uno # force a board FQBN
ard monitor # serial monitor only
ard list # what is plugged in
ard help # built-in help (with logo)
ard version # version
man ard # full manual| Command | Does | Aliases |
|---|---|---|
upload [sketch] |
Compile and flash | up |
compile [sketch] |
Compile only | build |
flash [sketch] |
Compile, flash, then monitor | run |
monitor |
Open the serial monitor | mon |
list |
List connected boards | boards, ls |
help / version |
Help (with logo) / version |
| Option | Meaning |
|---|---|
-b, --fqbn FQBN |
Board, e.g. arduino:renesas_uno:nanor4. Omit to auto-detect. A bare packager:arch:board arg also works. |
-p, --port PORT |
Serial port. Defaults to auto-detected, then /dev/ttyACM0. |
-r, --baud RATE |
Monitor baud rate. Default 115200. |
-h, --help / -V, --version |
Help / version. |
Auto-detect reads arduino-cli board list, so for a genuine board that self-identifies I can
run ard upload with no other arguments. Clones that do not report an FQBN need it passed
explicitly. Upload still depends on the udev rule from setup step 3, an upload fails with
LIBUSB_ERROR_ACCESS if that is not in place.
It also finds the sketch for me: run ard from anywhere in the project and if the current
folder is not a sketch (the project root, say) it looks one level down and uses the single
sketch it finds, or lists them if there is more than one.
There is only one copy of the tool: bin/ard, symlinked to ~/.local/bin/ard. Nothing lives in
.bashrc, so the installed command and the project file can never drift apart.
If you ever need to recreate it, here is the whole thing:
#!/usr/bin/env bash
#
# ard - Arduino build/upload/monitor helper built on arduino-cli.
# Subcommand-based CLI. See `ard help` or `man ard`.
#
set -euo pipefail
ARD_VERSION="1.0.0"
DEFAULT_BAUD="115200"
PORT_FALLBACK="/dev/ttyACM0"
# ---- output helpers -------------------------------------------------------
if [[ -t 1 ]]; then C_INFO=$'\033[1;36m'; C_OK=$'\033[1;32m'; C_DIM=$'\033[2m'; C_OFF=$'\033[0m'
else C_INFO=""; C_OK=""; C_DIM=""; C_OFF=""; fi
err() { printf 'ard: %s\n' "$*" >&2; }
die() { err "$*"; exit 1; }
info() { printf '%s==>%s %s\n' "$C_INFO" "$C_OFF" "$*"; }
ok() { printf '%s[ok]%s %s\n' "$C_OK" "$C_OFF" "$*"; }
banner() {
cat <<'BANNER'
__ _ _ __ __| |
/ _` | '__/ _` |
| (_| | | | (_| |
\__,_|_| \__,_|
BANNER
}
usage() {
banner
cat <<EOF
${C_DIM}ard ${ARD_VERSION} - compile, upload and monitor Arduino sketches (wraps arduino-cli).${C_OFF}
USAGE
ard <command> [sketch] [options]
COMMANDS
upload [sketch] Compile and flash the board (alias: up)
compile [sketch] Compile only, do not flash (alias: build)
flash [sketch] Compile, flash, then open monitor (alias: run)
monitor Open the serial monitor (alias: mon)
list List connected boards (alias: boards, ls)
help Show this help
version Show version
OPTIONS
-b, --fqbn FQBN Board FQBN (default: auto-detect, e.g. arduino:avr:uno)
-p, --port PORT Serial port (default: auto-detect, else ${PORT_FALLBACK})
-r, --baud RATE Monitor baud rate (default: ${DEFAULT_BAUD})
-h, --help Show this help
-V, --version Show version
ARGUMENTS
sketch Path to a sketch folder. Defaults to the current directory; if that is
not a sketch, ard looks one level down and picks the sketch it finds.
EXAMPLES
ard upload # auto-detect board, build + flash current sketch
ard flash # ... then open the serial monitor
ard compile ~/sk/Blink # just compile a specific sketch
ard up -b arduino:avr:uno # force a board
ard list # what is plugged in
See 'man ard' for the full manual.
EOF
}
command -v arduino-cli >/dev/null || die "arduino-cli not found on PATH"
# ---- subcommand -----------------------------------------------------------
[[ $# -eq 0 ]] && { usage; exit 0; }
CMD=""
case "$1" in
upload|up) CMD="upload"; shift ;;
compile|build) CMD="compile"; shift ;;
flash|run) CMD="flash"; shift ;;
monitor|mon) CMD="monitor"; shift ;;
list|boards|ls) CMD="list"; shift ;;
help|-h|--help) usage; exit 0 ;;
version|-V|--version) printf 'ard %s\n' "$ARD_VERSION"; exit 0 ;;
-*) die "unknown option '$1' (try 'ard help')" ;;
*) die "unknown command '$1' (try 'ard help')" ;;
esac
# ---- options + positional sketch -----------------------------------------
FQBN=""; PORT=""; BAUD="$DEFAULT_BAUD"; DIR=""
while [[ $# -gt 0 ]]; do
case "$1" in
-b|--fqbn) FQBN="${2:?--fqbn needs a value}"; shift 2 ;;
-p|--port) PORT="${2:?--port needs a value}"; shift 2 ;;
-r|--baud) BAUD="${2:?--baud needs a value}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
-V|--version) printf 'ard %s\n' "$ARD_VERSION"; exit 0 ;;
-*) die "unknown option '$1' (try 'ard help')" ;;
*) if [[ "$1" == *:*:* ]]; then FQBN="$1"; else DIR="$1"; fi; shift ;;
esac
done
DIR="${DIR:-.}"
# ---- list needs nothing else ---------------------------------------------
if [[ "$CMD" == "list" ]]; then arduino-cli board list; exit $?; fi
# ---- auto-detect FQBN / port from the connected board --------------------
if [[ -z "$FQBN" || -z "$PORT" ]]; then
read -r DET_PORT DET_FQBN < <(
arduino-cli board list --format json 2>/dev/null | python3 -c '
import sys, json
try:
d = json.load(sys.stdin)
except Exception:
sys.exit(0)
for p in d.get("detected_ports", []):
addr = p.get("port", {}).get("address", "")
boards = p.get("matching_boards") or []
fqbn = boards[0].get("fqbn", "") if boards else ""
if addr:
print(addr, fqbn)
break
') || true
[[ -z "$PORT" ]] && PORT="${DET_PORT:-$PORT_FALLBACK}"
[[ -z "$FQBN" ]] && FQBN="${DET_FQBN:-}"
fi
# ---- monitor needs only a port -------------------------------------------
if [[ "$CMD" == "monitor" ]]; then
info "monitoring ${PORT} @ ${BAUD} baud (Ctrl+C to stop)"
exec arduino-cli monitor -p "$PORT" -c baudrate="$BAUD"
fi
# ---- resolve the sketch folder (build/flash/compile) ---------------------
[[ -n "$FQBN" ]] || die "no FQBN given and none auto-detected. Pass one (e.g. -b arduino:avr:uno) or run 'ard list'."
[[ -d "$DIR" ]] || die "directory not found: $DIR"
DIR="$(cd "$DIR" && pwd)"
is_sketch() { [[ -f "$1/$(basename "$1").ino" ]]; }
if ! is_sketch "$DIR"; then
mapfile -t SKETCHES < <(shopt -s nullglob; for d in "$DIR"/*/; do d="${d%/}"; is_sketch "$d" && echo "$d"; done)
if [[ ${#SKETCHES[@]} -eq 1 ]]; then DIR="${SKETCHES[0]}"
elif [[ ${#SKETCHES[@]} -gt 1 ]]; then
err "multiple sketches under $(basename "$DIR"):"; printf ' %s\n' "${SKETCHES[@]##*/}" >&2
die "pick one, e.g. 'ard ${CMD} ${SKETCHES[0]##*/}'"
else
die "no sketch here. A sketch is a folder with a matching .ino (e.g. Blink/Blink.ino). cd into one, or pass it."
fi
fi
info "board=${FQBN} port=${PORT} sketch=${DIR}"
case "$CMD" in
compile)
arduino-cli compile -b "$FQBN" "$DIR"
ok "compiled (not uploaded)"
;;
upload)
arduino-cli compile -b "$FQBN" -u -p "$PORT" "$DIR"
ok "uploaded to ${PORT}"
;;
flash)
arduino-cli compile -b "$FQBN" -u -p "$PORT" "$DIR"
ok "uploaded to ${PORT}"
info "monitoring ${PORT} @ ${BAUD} baud (Ctrl+C to stop)"
exec arduino-cli monitor -p "$PORT" -c baudrate="$BAUD"
;;
esacThe ard script above is the quick path. The raw commands it runs are below, in case you want
them directly or need to tweak flags.
Sketch-folder rule: every sketch lives in its own folder, and the main
.inomust share the folder's name (for exampleBlink/Blink.ino). The tools build the folder, not the file.
Create a new sketch:
arduino-cli sketch new ~/Projects/ArduinoWings/MySketch # makes MySketch/MySketch.inoCompile (build only, no flashing):
arduino-cli compile -b arduino:renesas_uno:nanor4 ~/Projects/ArduinoWings/MySketchIn VSCode that is just Ctrl+Shift+B with the .ino focused. Success prints memory usage
(Sketch uses NNNN bytes ...). Errors show in the terminal and the Problems panel.
Upload (compile then flash):
arduino-cli compile -b arduino:renesas_uno:nanor4 \
--build-path ~/Projects/ArduinoWings/MySketch/build ~/Projects/ArduinoWings/MySketch
arduino-cli upload -b arduino:renesas_uno:nanor4 -p /dev/ttyACM0 \
--input-dir ~/Projects/ArduinoWings/MySketch/build ~/Projects/ArduinoWings/MySketchIn VSCode: F6. The board resets and runs the new program right away.
Your sketch needs Serial.begin(115200); (or another rate). Logs show in the bottom panel of
VSCode, two ways:
- Task (simplest):
F7(or the "Arduino: Serial Monitor (115200)" task) runsarduino-cli monitorin a terminal tab.F6opens it automatically after uploading.Ctrl+Cstops it. - Serial Monitor extension (richer): click the SERIAL MONITOR tab next to Terminal, set
Port
/dev/ttyACM0and Baud115200, click Start Monitoring. The input box sends data back to the board.
Only one program can hold the port. If it says busy, close the other monitor or terminal first, and likewise before uploading.
A core is the toolchain plus board definitions for a chip family. Install it once per family,
then every board in that family reuses it. The udev rule and uucp access already cover all
genuine Arduino boards, so you do not redo those.
-
Refresh the index and find the core:
arduino-cli core update-index arduino-cli core search nano
-
Install it:
arduino-cli core install <packager>:<arch>
Board Core Uno, Nano (classic), Mega (ATmega) arduino:avrNano R4, Uno R4 (Renesas, this project) arduino:renesas_unoNano 33 IoT, Zero (SAMD) arduino:samdNano 33 BLE, Nano RP2040 (mbed) arduino:mbed_nanoThird-party cores (ESP32, ESP8266) need their board-manager URL added first:
arduino-cli config add board_manager.additional_urls \ https://espressif.github.io/arduino-esp32/package_esp32_index.json arduino-cli core update-index arduino-cli core install esp32:esp32
-
Find the FQBN (plug the board in, genuine ones self-identify):
arduino-cli board list # FQBN + port for connected boards arduino-cli board listall nano # FQBNs available in an installed core
-
Point the lab at it: edit
FQBN(andPORTif it changed) at the top of.vscode/tasks.json. -
Refresh IntelliSense for the new board: run the "Arduino: Refresh IntelliSense" task, then
Ctrl+Shift+Pand "clangd: Restart language server".gen-clangd.pyauto-detects the right compiler (avr, arm, xtensa), so no edits are needed.
Non-Arduino USB vendors: the upload rule matches Arduino's vendor ID
2341. A board from another vendor (many ESP32 boards use a CH3401a86or CP210210c4chip) needs itsidVendoradded to/etc/udev/rules.d/70-arduino.rules. Plain serial boards usually just needuucpaccess, which you already have.
clangd reads ./.clangd to resolve Arduino.h, Serial, pinMode, and so on, including in
.ino files. Two non-obvious things make .ino work, both handled by gen-clangd.py:
- It force-includes
Arduino.h. Sketches never include it themselves; the Arduino build prepends it silently, and clangd does not know that. - It expands the GCC response file to recover the FSP and CMSIS include paths that the Nano R4
pulls in through
-iwithprefixbefore.
After adding a library (#include <SomeLib.h>), run "Arduino: Refresh IntelliSense" and
restart the clangd server, or the new headers will not resolve.
.clangd is generated, not written by hand. From a fresh checkout (or any time you want to
rebuild it) run the generator against a sketch:
python3 .vscode/gen-clangd.py Blink # uses the default FQBN
python3 .vscode/gen-clangd.py Blink arduino:renesas_uno:nanor4 # or pass the FQBNThe VSCode task "Arduino: Refresh IntelliSense" runs exactly this. It compiles a compilation
database, then reads the real compiler, defines, and include paths out of it (expanding any GCC
response file), and writes .clangd at the project root. Because it takes the compiler from the
database, it works for any board, not just this one.
#!/usr/bin/env python3
"""Regenerate ./.clangd so clangd gives full Arduino IntelliSense for .ino/.cpp/.h.
Usage: python3 .vscode/gen-clangd.py <sketch-dir> [fqbn]
Run via the VSCode task "Arduino: Refresh IntelliSense" (re-run after adding libraries).
"""
import json, shlex, os, sys, subprocess
sketch = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else ".")
fqbn = sys.argv[2] if len(sys.argv) > 2 else "arduino:renesas_uno:nanor4"
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # workspace root
build = os.path.join(sketch, "build")
# 1. (re)generate the compilation database
subprocess.run(["arduino-cli", "compile", "-b", fqbn,
"--only-compilation-database", "--build-path", build, sketch], check=True)
db = json.load(open(os.path.join(build, "compile_commands.json")))
entry = next(e for e in db if e["file"].endswith(".ino.cpp"))
cmd = entry.get("command") or " ".join(entry["arguments"])
def expand(tokens):
out = []
for t in tokens:
if t.startswith("@") and os.path.isfile(t[1:]):
out += expand(shlex.split(open(t[1:]).read()))
else:
out.append(t)
return out
toks = expand(shlex.split(cmd))
compiler = toks[0] # the exact compiler arduino-cli used for THIS board (avr, arm, xtensa)
includes, defines, std, iprefix = [], [], None, None
i = 0
while i < len(toks):
a = toks[i]; nxt = toks[i + 1] if i + 1 < len(toks) else None
if a.startswith("-std="): std = a
elif a == "-iprefix": iprefix = nxt; i += 1
elif a.startswith("-iprefix"): iprefix = a[8:]
elif a.startswith("-iwithprefixbefore"):
v = nxt if a == "-iwithprefixbefore" else a[18:]
if a == "-iwithprefixbefore": i += 1
if iprefix: includes.append(os.path.normpath(iprefix + "/" + v.lstrip("/")))
elif a == "-I": includes.append(nxt); i += 1
elif a.startswith("-I"): includes.append(a[2:])
elif a == "-isystem": includes.append(nxt); i += 1
elif a.startswith("-isystem"): includes.append(a[8:])
elif a == "-D": defines.append(nxt); i += 1
elif a.startswith("-D"): defines.append(a[2:])
i += 1
inc = list(dict.fromkeys(p for p in includes if os.path.isdir(p)))
defs = list(dict.fromkeys(defines))
L = ["# AUTO-GENERATED by .vscode/gen-clangd.py, do not hand-edit.",
"# Gives clangd full Arduino IntelliSense (.ino/.cpp/.h). Re-run the",
"# 'Arduino: Refresh IntelliSense' task after adding libraries.",
"CompileFlags:",
" Compiler: " + compiler,
" Add:",
" - -include", " - Arduino.h", " - -xc++"]
if std: L.append(" - " + std)
for x in defs: L.append(" - -D" + x)
for x in inc: L.append(" - -I" + x)
# Strip arch-specific codegen flags clangd's parser may not understand
# (arm: -mcpu/-mthumb/-mfloat-abi/-mfpu/-mabi ; avr: -mmcu ; xtensa/esp: -mlongcalls/-mtext-section-literals).
L += [" Remove:", " - -mcpu=*", " - -mthumb", " - -mfloat-abi=*",
" - -mfpu=*", " - -mabi=*", " - -mmcu=*", " - -mlongcalls",
" - -mtext-section-literals", " - -mno-*", " - --param=*"]
open(os.path.join(root, ".clangd"), "w").write("\n".join(L) + "\n")
print(f"Wrote {root}/.clangd ({len(inc)} include dirs, {len(defs)} defines)")- Print debugging (no extra hardware):
Serial.println()plus the serial monitor. This is the normal workflow. - Step debugging (breakpoints): the Nano R4 has no onboard debugger, so it needs an external
SWD probe (CMSIS-DAP, J-Link, or ST-Link) wired to the SWD pads. With one attached, the
Run and Debug panel has a "Debug Nano R4 (external SWD probe)" config (
.vscode/launch.json) that gives breakpoints and register views. Without a probe it will not run.
| Symptom | Fix |
|---|---|
LIBUSB_ERROR_ACCESS on upload |
udev rule not applied. Re-check step 3, confirm the file sorts below 73, replug. |
| Upload cannot find the board | arduino-cli board list for the real port, update PORT in tasks.json. |
| Task builds the wrong sketch | Make sure the correct .ino tab is the focused editor. |
Red squiggles on Serial, pinMode |
Run "Arduino: Refresh IntelliSense", then restart clangd. |
| Serial monitor blank or garbled | Baud must match Serial.begin(...), this project uses 115200. |
| Port busy | Close any other monitor or terminal holding /dev/ttyACM0. |
void setup() { /* runs once at boot */ }
void loop() { /* runs forever after setup */ }| Function | Purpose |
|---|---|
pinMode(pin, mode) |
Set a pin's mode. mode is INPUT, OUTPUT, or INPUT_PULLUP. |
digitalWrite(pin, value) |
Drive an output pin HIGH or LOW. |
digitalRead(pin) |
Read a digital pin, returns HIGH or LOW. |
| Function | Purpose |
|---|---|
analogRead(pin) |
Read an analog input (A0, A1, ...). Returns 0 to 1023 by default. |
analogWrite(pin, value) |
PWM output, value 0 to 255. |
analogReadResolution(bits) |
Change ADC resolution (the R4 supports up to 14-bit). |
analogReference(type) |
Select the analog reference voltage. |
| Function | Purpose |
|---|---|
delay(ms) |
Block for milliseconds. |
delayMicroseconds(us) |
Block for microseconds. |
millis() |
Milliseconds since boot (unsigned long, wraps after ~50 days). |
micros() |
Microseconds since boot. |
| Function | Purpose |
|---|---|
tone(pin, freq[, dur]) |
Square wave on a pin (buzzers). |
noTone(pin) |
Stop tone(). |
pulseIn(pin, value) |
Measure a pulse width in microseconds. |
shiftOut(dataPin, clockPin, order, value) |
Shift a byte out bit by bit. |
shiftIn(dataPin, clockPin, order) |
Shift a byte in bit by bit. |
| Function | Purpose |
|---|---|
min(a, b), max(a, b), abs(x) |
Basic math. |
constrain(x, lo, hi) |
Clamp x into a range. |
map(x, inLo, inHi, outLo, outHi) |
Re-scale a value from one range to another. |
pow(b, e), sqrt(x), sq(x) |
Power, square root, square. |
sin(x), cos(x), tan(x) |
Trig (radians). |
random(max), random(min, max) |
Pseudo-random number. |
randomSeed(seed) |
Seed the RNG. |
| Function | Purpose |
|---|---|
lowByte(x), highByte(x) |
Extract a byte. |
bitRead(x, n), bitWrite(x, n, b) |
Read or write a single bit. |
bitSet(x, n), bitClear(x, n) |
Set or clear a bit. |
bit(n) |
Value of bit n (1 << n). |
| Function | Purpose |
|---|---|
Serial.begin(baud) |
Start serial, for example Serial.begin(115200). |
Serial.print(x), Serial.println(x) |
Send text, with or without a newline. |
Serial.available() |
Bytes waiting to be read. |
Serial.read() |
Read one incoming byte. |
Serial.write(b) |
Send raw bytes. |
| Function | Purpose |
|---|---|
attachInterrupt(digitalPinToInterrupt(pin), isr, mode) |
Run isr on a pin edge. mode is RISING, FALLING, or CHANGE. |
detachInterrupt(digitalPinToInterrupt(pin)) |
Stop it. |
Keep ISRs tiny, use
volatilefor variables shared with an ISR, and rememberdelay()andmillis()do not advance inside one.
| Constant | Meaning |
|---|---|
HIGH / LOW |
Pin high (on) or low (off). |
INPUT / OUTPUT / INPUT_PULLUP |
Pin modes. INPUT_PULLUP enables the internal pull-up. |
true / false |
Booleans (1 and 0). |
LED_BUILTIN |
The onboard LED pin. |
A0, A1, ... |
Analog pin names. |
PI, HALF_PI, TWO_PI, DEG_TO_RAD, RAD_TO_DEG |
Math constants. |
bool, char, byte (0 to 255), int, unsigned int, long, unsigned long,
float, double, String. Use unsigned long for millis() and micros() results.
unsigned long previous = 0;
const unsigned long interval = 500; // ms
void loop() {
unsigned long now = millis();
if (now - previous >= interval) {
previous = now;
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle
}
// other work keeps running here, nothing is blocked
}