Build and upload tool (arduino-build.sh): - Compile, upload, and monitor via arduino-cli - Device discovery with USB ID identification (--devices) - Persistent reconnecting serial monitor (--watch) - Split compile/upload workflow (--verify, --upload-only) - First-time setup wizard (--setup) - Comprehensive --help with troubleshooting and RedBoard specs Testable application architecture: - Hardware abstraction layer (lib/hal/) decouples logic from Arduino API - Google Mock HAL for unit tests (exact call verification) - Simulated HAL for system tests (GPIO state, virtual clock, I2C devices) - Example I2C temperature sensor simulator (TMP102) - Host-side test suite via CMake + Google Test (21 tests) Example sketch: - blink/ -- LED blink with button-controlled speed, wired through HAL
999 lines
33 KiB
Bash
Executable File
999 lines
33 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# arduino-build.sh -- Build, upload, and monitor Arduino sketches
|
|
# for SparkFun RedBoard (and other Uno-compatible boards)
|
|
#
|
|
# Run with --help for full usage information.
|
|
# Run with --setup for first-time toolchain installation.
|
|
# Run with --devices to find connected boards.
|
|
|
|
set -euo pipefail
|
|
|
|
# -- Configuration (override via env vars if needed) -------------------------
|
|
FQBN="${ARDUINO_FQBN:-arduino:avr:uno}"
|
|
BAUD="${ARDUINO_BAUD:-115200}"
|
|
BUILD_DIR="${ARDUINO_BUILD_DIR:-/tmp/arduino-build}"
|
|
ARDUINO_CLI="${ARDUINO_CLI_BIN:-arduino-cli}"
|
|
VERSION="1.1.0"
|
|
|
|
# -- Color output (disabled if stdout is not a terminal) ---------------------
|
|
if [[ -t 1 ]]; then
|
|
RED=$'\033[0;31m'; GRN=$'\033[0;32m'; YEL=$'\033[0;33m'
|
|
CYN=$'\033[0;36m'; BLD=$'\033[1m'; DIM=$'\033[2m'; RST=$'\033[0m'
|
|
else
|
|
RED=''; GRN=''; YEL=''; CYN=''; BLD=''; DIM=''; RST=''
|
|
fi
|
|
|
|
info() { echo -e "${CYN}[INFO]${RST} $*"; }
|
|
ok() { echo -e "${GRN}[ OK ]${RST} $*"; }
|
|
warn() { echo -e "${YEL}[WARN]${RST} $*"; }
|
|
die() { echo -e "${RED}[ERR]${RST} $*" >&2; exit 1; }
|
|
|
|
# ============================================================================
|
|
# HELP
|
|
# ============================================================================
|
|
show_help() {
|
|
cat <<EOF
|
|
|
|
${BLD}NAME${RST}
|
|
arduino-build.sh - Build, upload, and monitor Arduino sketches
|
|
|
|
${BLD}VERSION${RST}
|
|
${VERSION}
|
|
|
|
${BLD}SYNOPSIS${RST}
|
|
./arduino-build.sh ${BLD}--setup${RST}
|
|
./arduino-build.sh ${BLD}--devices${RST}
|
|
./arduino-build.sh [OPTIONS] <sketch_dir>
|
|
|
|
${BLD}DESCRIPTION${RST}
|
|
A self-contained script that replaces the Arduino IDE for command-line
|
|
workflows. Compiles sketches with arduino-cli, uploads to the board,
|
|
and optionally opens a serial monitor -- all in one command.
|
|
|
|
Designed for the SparkFun RedBoard but works with any Uno-compatible
|
|
board. Just plug in, run --devices to find your port, and go.
|
|
|
|
$(_help_section_commands)
|
|
$(_help_section_build_options)
|
|
$(_help_section_config_options)
|
|
$(_help_section_env_vars)
|
|
$(_help_section_examples)
|
|
$(_help_section_cheatsheet)
|
|
$(_help_section_sketch_layout)
|
|
$(_help_section_redboard)
|
|
$(_help_section_port_permissions)
|
|
$(_help_section_first_time)
|
|
$(_help_section_troubleshooting)
|
|
$(_help_section_usb_ids)
|
|
EOF
|
|
}
|
|
|
|
_help_section_commands() {
|
|
cat <<EOF
|
|
${BLD}STANDALONE COMMANDS${RST}
|
|
These run independently. No sketch directory needed.
|
|
|
|
${BLD}-h, --help${RST}
|
|
Show this help and exit. You are reading it now.
|
|
|
|
${BLD}--version${RST}
|
|
Print version string and exit.
|
|
|
|
${BLD}--setup${RST}
|
|
First-time setup wizard. Installs the arduino:avr core, checks
|
|
that your user is in the dialout group (serial port access), and
|
|
verifies the toolchain. Run once after installing arduino-cli.
|
|
|
|
${BLD}--devices${RST}
|
|
Scan for connected boards. For each /dev/ttyUSB* and /dev/ttyACM*
|
|
port found, shows:
|
|
- USB vendor and product strings
|
|
- USB vendor:product ID (hex)
|
|
- Kernel driver in use (ch341-uart, cdc_acm, etc.)
|
|
- Best guess at what board it is
|
|
- Whether you have write permission to the port
|
|
Also runs 'arduino-cli board list' for cross-reference.
|
|
|
|
${BLD}--watch${RST}
|
|
Open a persistent serial monitor that automatically reconnects
|
|
when the port disappears and reappears (e.g., during upload from
|
|
another terminal). Run in one terminal, build/upload in another.
|
|
|
|
Survives: upload resets, board power cycles, USB re-plugs.
|
|
Exit with Ctrl+C.
|
|
|
|
Combine with -p and -b:
|
|
./arduino-build.sh --watch -p /dev/ttyUSB0 -b 9600
|
|
EOF
|
|
}
|
|
|
|
_help_section_build_options() {
|
|
cat <<EOF
|
|
|
|
${BLD}BUILD & UPLOAD OPTIONS${RST}
|
|
|
|
${BLD}--verify${RST}
|
|
Compile only -- do not upload. Good for syntax checking, CI
|
|
pipelines, or when the board is not plugged in. Still reports
|
|
binary size so you can track flash usage.
|
|
|
|
${BLD}--upload${RST}
|
|
Compile and upload. This is the default when you pass a sketch
|
|
directory, so you almost never need to type it explicitly.
|
|
|
|
${BLD}--upload-only${RST}
|
|
Upload previously compiled artifacts without recompiling. Requires
|
|
a prior --verify or build to have cached the .hex file. Useful for
|
|
separating build and flash steps:
|
|
|
|
Terminal 1: ./arduino-build.sh --verify ./blink (compile)
|
|
Terminal 2: ./arduino-build.sh --upload-only ./blink (flash)
|
|
|
|
Fails if no cached build exists for the sketch.
|
|
|
|
${BLD}--monitor${RST}
|
|
After a successful upload, automatically open the serial monitor
|
|
at the configured baud rate. Ctrl+C exits the monitor. Combines
|
|
the build-upload-monitor cycle into a single command.
|
|
|
|
${BLD}--clean${RST}
|
|
Delete cached build artifacts for this sketch before compiling.
|
|
Forces a full recompile from scratch. Use when you suspect stale
|
|
object files or after changing compiler flags.
|
|
|
|
${BLD}--verbose${RST}
|
|
Show full compiler and avrdude output. Helpful for debugging
|
|
linker errors, upload failures, or library conflicts.
|
|
EOF
|
|
}
|
|
|
|
_help_section_config_options() {
|
|
cat <<EOF
|
|
|
|
${BLD}CONFIGURATION OPTIONS${RST}
|
|
|
|
${BLD}-p, --port PORT${RST}
|
|
Use this serial port instead of auto-detecting.
|
|
Example: -p /dev/ttyUSB0
|
|
|
|
${BLD}-b, --baud RATE${RST}
|
|
Serial monitor baud rate (default: 115200). Only affects the
|
|
--monitor feature; upload speed is set by the bootloader.
|
|
Common values: 9600, 19200, 38400, 57600, 115200
|
|
|
|
${BLD}--fqbn FQBN${RST}
|
|
Override the Fully Qualified Board Name. Default: arduino:avr:uno
|
|
|
|
Common FQBNs:
|
|
arduino:avr:uno Uno / RedBoard
|
|
arduino:avr:mega:cpu=atmega2560 Mega 2560
|
|
arduino:avr:nano:cpu=atmega328 Nano (old bootloader)
|
|
arduino:avr:nano:cpu=atmega328old Nano (new bootloader)
|
|
esp32:esp32:esp32 ESP32 DevKit
|
|
SparkFun:avr:RedBoard SparkFun board package
|
|
|
|
To list all installed FQBNs:
|
|
arduino-cli board listall
|
|
EOF
|
|
}
|
|
|
|
_help_section_env_vars() {
|
|
cat <<EOF
|
|
|
|
${BLD}ENVIRONMENT VARIABLES${RST}
|
|
Override defaults without editing the script or passing flags.
|
|
|
|
Variable Default What it does
|
|
-------- ------- ------------
|
|
ARDUINO_FQBN arduino:avr:uno Board identifier
|
|
ARDUINO_BAUD 115200 Serial monitor baud rate
|
|
ARDUINO_BUILD_DIR /tmp/arduino-build Where .o/.elf/.hex go
|
|
ARDUINO_CLI_BIN arduino-cli Path to arduino-cli binary
|
|
EOF
|
|
}
|
|
|
|
_help_section_examples() {
|
|
cat <<EOF
|
|
|
|
${BLD}EXAMPLES${RST}
|
|
|
|
${DIM}# --- Getting started (do these once) ---${RST}
|
|
|
|
./arduino-build.sh --setup
|
|
Install the AVR toolchain and verify permissions.
|
|
|
|
./arduino-build.sh --devices
|
|
See what boards are plugged in and which port to use.
|
|
|
|
${DIM}# --- Day-to-day usage ---${RST}
|
|
|
|
./arduino-build.sh ./blink
|
|
Compile and upload blink sketch. Port auto-detected.
|
|
|
|
./arduino-build.sh --verify ./blink
|
|
Compile only. Good for checking code on a laptop with no board.
|
|
|
|
./arduino-build.sh -p /dev/ttyUSB0 ./blink
|
|
Upload to a specific port (skip auto-detection).
|
|
|
|
./arduino-build.sh --monitor ./blink
|
|
Build, upload, and open serial monitor in one shot.
|
|
|
|
./arduino-build.sh --clean --verbose ./blink
|
|
Nuke the cache and rebuild with full compiler output.
|
|
|
|
${DIM}# --- Two-terminal workflow (monitor + build separately) ---${RST}
|
|
|
|
${DIM}# Terminal 1: persistent monitor that reconnects after uploads${RST}
|
|
./arduino-build.sh --watch
|
|
|
|
${DIM}# Terminal 2: build and upload (monitor in T1 reconnects)${RST}
|
|
./arduino-build.sh ./blink
|
|
|
|
${DIM}# --- Split compile and upload (CI / unit test workflow) ---${RST}
|
|
|
|
./arduino-build.sh --verify ./blink
|
|
Compile and cache artifacts. No board needed.
|
|
|
|
./arduino-build.sh --upload-only ./blink
|
|
Flash cached artifacts. No recompile.
|
|
|
|
${DIM}# --- Different boards ---${RST}
|
|
|
|
./arduino-build.sh --fqbn arduino:avr:mega:cpu=atmega2560 ./my_sketch
|
|
Upload to an Arduino Mega instead of a RedBoard.
|
|
|
|
ARDUINO_FQBN=esp32:esp32:esp32 ./arduino-build.sh ./my_esp_sketch
|
|
Upload to an ESP32 board via environment variable.
|
|
EOF
|
|
}
|
|
|
|
_help_section_cheatsheet() {
|
|
cat <<EOF
|
|
|
|
${BLD}QUICK REFERENCE (tape this to your monitor)${RST}
|
|
|
|
Command What it does
|
|
------- ------------
|
|
--help You are here
|
|
--setup One-time toolchain install
|
|
--devices What's plugged in?
|
|
--verify ./sketch Does it compile?
|
|
./sketch Build + upload (auto port)
|
|
--upload-only ./sketch Flash cached build (no compile)
|
|
-p /dev/ttyUSB0 ./sketch Build + upload (manual port)
|
|
--monitor ./sketch Build + upload + serial console
|
|
--watch Persistent monitor (reconnects)
|
|
--clean ./sketch Full rebuild (no cache)
|
|
--verbose ./sketch Show all compiler output
|
|
|
|
${BLD}Two-terminal workflow:${RST}
|
|
Terminal 1: ./arduino-build.sh --watch
|
|
Terminal 2: ./arduino-build.sh ./blink (watch reconnects)
|
|
EOF
|
|
}
|
|
|
|
_help_section_sketch_layout() {
|
|
cat <<EOF
|
|
|
|
${BLD}SKETCH DIRECTORY LAYOUT${RST}
|
|
Arduino requires the .ino filename to match the directory name:
|
|
|
|
my_sketch/ <-- pass this directory to the script
|
|
my_sketch.ino <-- main sketch file (name must match dir)
|
|
config.h <-- optional extra files
|
|
utils.cpp <-- optional extra files
|
|
|
|
Quick way to create a new sketch:
|
|
|
|
mkdir blink
|
|
cat > blink/blink.ino << 'SKETCH'
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
}
|
|
void loop() {
|
|
digitalWrite(LED_BUILTIN, HIGH);
|
|
Serial.println("ON");
|
|
delay(500);
|
|
digitalWrite(LED_BUILTIN, LOW);
|
|
Serial.println("OFF");
|
|
delay(500);
|
|
}
|
|
SKETCH
|
|
EOF
|
|
}
|
|
|
|
_help_section_redboard() {
|
|
cat <<EOF
|
|
|
|
${BLD}SPARKFUN REDBOARD SPECS${RST}
|
|
|
|
MCU: ATmega328P at 16 MHz
|
|
Flash: 32 KB total (31.5 KB usable; 512 B bootloader)
|
|
SRAM: 2 KB (runtime variables and stack)
|
|
EEPROM: 1 KB (persistent storage)
|
|
Digital I/O: 14 pins (6 with PWM on pins 3, 5, 6, 9, 10, 11)
|
|
Analog inputs: 6 pins (A0-A5, 10-bit ADC)
|
|
USB chip: CH340G (shows up as /dev/ttyUSB*)
|
|
Voltage: 5V logic, 7-15V barrel jack input
|
|
FQBN: arduino:avr:uno (same as Arduino Uno)
|
|
|
|
The RedBoard is electrically identical to an Arduino Uno. The only
|
|
real difference is the CH340 USB-to-serial chip (Uno uses ATmega16U2).
|
|
The CH340 shows up as /dev/ttyUSB* while the 16U2 shows as /dev/ttyACM*.
|
|
EOF
|
|
}
|
|
|
|
_help_section_port_permissions() {
|
|
cat <<EOF
|
|
|
|
${BLD}SERIAL PORT PERMISSIONS${RST}
|
|
If you get "Permission denied" on /dev/ttyUSB0 or /dev/ttyACM0:
|
|
|
|
sudo usermod -aG dialout \$USER
|
|
|
|
Then ${BLD}log out and back in${RST} (or reboot). Verify with:
|
|
|
|
groups | grep dialout
|
|
|
|
Alternatively, for a quick one-off test:
|
|
|
|
sudo chmod 666 /dev/ttyUSB0 (resets on reboot/replug)
|
|
EOF
|
|
}
|
|
|
|
_help_section_first_time() {
|
|
cat <<EOF
|
|
|
|
${BLD}FIRST-TIME INSTALL (step by step)${RST}
|
|
|
|
1. Install arduino-cli:
|
|
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
|
|
sudo mv bin/arduino-cli /usr/local/bin/
|
|
|
|
2. Run first-time setup:
|
|
./arduino-build.sh --setup
|
|
|
|
3. Plug in your RedBoard and verify detection:
|
|
./arduino-build.sh --devices
|
|
|
|
4. Create a test sketch:
|
|
mkdir blink && cat > blink/blink.ino << 'SKETCH'
|
|
void setup() { pinMode(LED_BUILTIN, OUTPUT); }
|
|
void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(500);
|
|
digitalWrite(LED_BUILTIN, LOW); delay(500); }
|
|
SKETCH
|
|
|
|
5. Build and upload:
|
|
./arduino-build.sh ./blink
|
|
|
|
6. Verify the LED on the board is blinking. Done!
|
|
EOF
|
|
}
|
|
|
|
_help_section_troubleshooting() {
|
|
cat <<EOF
|
|
|
|
${BLD}TROUBLESHOOTING${RST}
|
|
|
|
${BLD}Board not detected (--devices shows nothing)${RST}
|
|
- Try a different USB cable. Many cables are charge-only with no
|
|
data lines. If your phone does not offer file transfer with the
|
|
cable, it is probably charge-only.
|
|
- Check the USB bus directly:
|
|
lsusb | grep -i -E 'ch34|arduino|1a86|2341'
|
|
- Check kernel log for attach/detach events:
|
|
dmesg | tail -20
|
|
- Try a different USB port (front panel ports can be flaky).
|
|
|
|
${BLD}Upload fails: "avrdude: stk500_getsync(): not in sync"${RST}
|
|
- Most common cause: wrong port. Run --devices to double-check.
|
|
- Close any other program using the port (serial monitors, other
|
|
IDE instances, screen sessions, etc.)
|
|
- Try pressing the board reset button just before upload starts.
|
|
- Bad or loose USB cable.
|
|
|
|
${BLD}"Sketch uses N bytes ... maximum is 32256 bytes"${RST}
|
|
- The ATmega328P has 32,256 bytes for program storage.
|
|
- Use --verify to check size without uploading.
|
|
- Tips to reduce size: use F() macro for string literals,
|
|
avoid the String class (use char arrays), remove unused libraries.
|
|
- If you genuinely need more space, use a Mega 2560 (256 KB).
|
|
|
|
${BLD}Compilation errors about missing libraries${RST}
|
|
- Search: arduino-cli lib search "Servo"
|
|
- Install: arduino-cli lib install "Servo"
|
|
- List: arduino-cli lib list
|
|
|
|
${BLD}Serial monitor shows garbage characters${RST}
|
|
- Baud rate mismatch. Make sure -b/--baud matches the
|
|
Serial.begin() value in your sketch. Default here is 115200.
|
|
|
|
${BLD}CH340 driver not loading (Linux)${RST}
|
|
- Most kernels since 4.x include ch341 out of the box.
|
|
- Verify: lsmod | grep ch341
|
|
- If missing: sudo modprobe ch341
|
|
- Still gone: sudo apt install linux-modules-extra-\$(uname -r)
|
|
EOF
|
|
}
|
|
|
|
_help_section_usb_ids() {
|
|
cat <<EOF
|
|
|
|
${BLD}KNOWN USB VENDOR:PRODUCT IDS${RST}
|
|
Used by --devices to identify boards.
|
|
|
|
ID Board / Chip
|
|
---- ------------
|
|
1a86:7523 CH340 (SparkFun RedBoard, most Arduino clones)
|
|
1a86:55d4 CH340C (RedBoard Qwiic, newer clones)
|
|
2341:0043 Arduino Uno R3 (ATmega16U2)
|
|
2341:0001 Arduino Uno (older revision)
|
|
2341:0010 Arduino Mega 2560
|
|
2341:0042 Arduino Mega 2560 R3
|
|
2341:003d Arduino Due (Programming Port)
|
|
2341:003e Arduino Due (Native USB)
|
|
2341:8036 Arduino Leonardo
|
|
2341:8037 Arduino Micro
|
|
0403:6001 FTDI FT232R (older RedBoard, FTDI clones)
|
|
0403:6015 FTDI FT231X (SparkFun FTDI breakout)
|
|
10c4:ea60 CP2102 (NodeMCU, some ESP32 boards)
|
|
|
|
EOF
|
|
}
|
|
|
|
# ============================================================================
|
|
# DEVICE SCANNER
|
|
# ============================================================================
|
|
scan_devices() {
|
|
echo ""
|
|
echo -e "${BLD}=== Connected Serial Devices ===${RST}"
|
|
echo ""
|
|
|
|
local found=0
|
|
|
|
for port in /dev/ttyUSB* /dev/ttyACM*; do
|
|
[[ -e "$port" ]] || continue
|
|
found=1
|
|
|
|
echo -e " ${BLD}${GRN}$port${RST}"
|
|
|
|
local sysfs_path="" usb_dev_path=""
|
|
|
|
# Find the kernel driver for this tty
|
|
local driver=""
|
|
local tty_device="/sys/class/tty/$(basename "$port")/device/driver"
|
|
if [[ -L "$tty_device" ]]; then
|
|
driver="$(basename "$(readlink -f "$tty_device")")"
|
|
fi
|
|
|
|
# Walk up from the tty device node to find the USB device node.
|
|
# The tty sits under a USB interface node; idVendor/idProduct
|
|
# live on the USB device node which is one or more levels above.
|
|
if [[ -e "/sys/class/tty/$(basename "$port")/device" ]]; then
|
|
sysfs_path="$(readlink -f "/sys/class/tty/$(basename "$port")/device")"
|
|
usb_dev_path="$sysfs_path"
|
|
# Walk up until we find idVendor (max 5 levels to avoid runaway)
|
|
local walk=0
|
|
while [[ $walk -lt 5 ]] && [[ "$usb_dev_path" != "/" ]]; do
|
|
if [[ -f "$usb_dev_path/idVendor" ]]; then
|
|
break
|
|
fi
|
|
usb_dev_path="$(dirname "$usb_dev_path")"
|
|
walk=$((walk + 1))
|
|
done
|
|
# If we didn't find idVendor, clear the path
|
|
if [[ ! -f "$usb_dev_path/idVendor" ]]; then
|
|
usb_dev_path=""
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$usb_dev_path" ]]; then
|
|
local vendor="" product="" vid="" pid="" serial=""
|
|
|
|
[[ -f "$usb_dev_path/manufacturer" ]] && vendor="$(cat "$usb_dev_path/manufacturer" 2>/dev/null)"
|
|
[[ -f "$usb_dev_path/product" ]] && product="$(cat "$usb_dev_path/product" 2>/dev/null)"
|
|
[[ -f "$usb_dev_path/idVendor" ]] && vid="$(cat "$usb_dev_path/idVendor" 2>/dev/null)"
|
|
[[ -f "$usb_dev_path/idProduct" ]] && pid="$(cat "$usb_dev_path/idProduct" 2>/dev/null)"
|
|
[[ -f "$usb_dev_path/serial" ]] && serial="$(cat "$usb_dev_path/serial" 2>/dev/null)"
|
|
|
|
[[ -n "$vendor" ]] && echo " Manufacturer: $vendor"
|
|
[[ -n "$product" ]] && echo " Product: $product"
|
|
[[ -n "$vid" ]] && [[ -n "$pid" ]] && echo " USB ID: ${vid}:${pid}"
|
|
[[ -n "$serial" ]] && echo " Serial: $serial"
|
|
[[ -n "$driver" ]] && echo " Driver: $driver"
|
|
|
|
echo -n " Likely board: "
|
|
case "${vid}:${pid}" in
|
|
1a86:7523) echo "SparkFun RedBoard / CH340 clone" ;;
|
|
1a86:55d4) echo "SparkFun RedBoard Qwiic / CH340C" ;;
|
|
2341:0043) echo "Arduino Uno R3 (ATmega16U2)" ;;
|
|
2341:0001) echo "Arduino Uno (older revision)" ;;
|
|
2341:0010) echo "Arduino Mega 2560" ;;
|
|
2341:0042) echo "Arduino Mega 2560 R3" ;;
|
|
2341:003d) echo "Arduino Due (Programming Port)" ;;
|
|
2341:003e) echo "Arduino Due (Native USB)" ;;
|
|
2341:8036) echo "Arduino Leonardo" ;;
|
|
2341:8037) echo "Arduino Micro" ;;
|
|
0403:6001) echo "FTDI FT232R (older RedBoard / clone)" ;;
|
|
0403:6015) echo "FTDI FT231X (SparkFun breakout)" ;;
|
|
10c4:ea60) echo "CP2102 (NodeMCU / some ESP32 boards)" ;;
|
|
1a86:*) echo "CH340 variant (likely Arduino clone)" ;;
|
|
2341:*) echo "Arduino (unknown model -- USB ID ${vid}:${pid})" ;;
|
|
*) echo "Unknown device -- USB ID ${vid}:${pid}" ;;
|
|
esac
|
|
else
|
|
# Could not find USB device node, but we may still have the driver
|
|
[[ -n "$driver" ]] && echo " Driver: $driver"
|
|
echo " USB info: Could not read (sysfs walk failed)"
|
|
fi
|
|
|
|
if [[ -w "$port" ]]; then
|
|
echo -e " Permissions: ${GRN}OK (writable)${RST}"
|
|
else
|
|
echo -e " Permissions: ${RED}NOT writable${RST} -- run: sudo usermod -aG dialout \$USER"
|
|
fi
|
|
|
|
echo ""
|
|
done
|
|
|
|
if [[ $found -eq 0 ]]; then
|
|
echo -e " ${YEL}No serial devices found.${RST}"
|
|
echo ""
|
|
echo " Checklist:"
|
|
echo " 1. Is the board plugged in via USB?"
|
|
echo " 2. Is the USB cable a data cable (not charge-only)?"
|
|
echo " 3. Check kernel log: dmesg | tail -20"
|
|
echo " 4. Check USB bus: lsusb | grep -i -E 'ch34|arduino|1a86|2341|0403'"
|
|
echo " 5. Try a different USB port or cable"
|
|
echo ""
|
|
fi
|
|
|
|
if command -v "$ARDUINO_CLI" &>/dev/null; then
|
|
echo -e "${BLD}=== arduino-cli Board Detection ===${RST}"
|
|
echo ""
|
|
$ARDUINO_CLI board list 2>/dev/null || warn "arduino-cli board list failed (is the core installed?)"
|
|
echo ""
|
|
else
|
|
warn "arduino-cli not found -- run --setup first"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# FIRST-TIME SETUP
|
|
# ============================================================================
|
|
run_setup() {
|
|
echo ""
|
|
echo -e "${BLD}=== Arduino CLI First-Time Setup ===${RST}"
|
|
echo ""
|
|
|
|
if ! command -v "$ARDUINO_CLI" &>/dev/null; then
|
|
die "arduino-cli not found in PATH.
|
|
|
|
Install it:
|
|
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh
|
|
sudo mv bin/arduino-cli /usr/local/bin/
|
|
|
|
Or via package manager:
|
|
sudo apt install arduino-cli (Debian/Ubuntu)
|
|
brew install arduino-cli (macOS)
|
|
yay -S arduino-cli (Arch)
|
|
|
|
Then re-run: ./arduino-build.sh --setup"
|
|
fi
|
|
ok "arduino-cli found: $($ARDUINO_CLI version 2>/dev/null | head -1)"
|
|
|
|
info "Updating board index..."
|
|
$ARDUINO_CLI core update-index
|
|
ok "Board index updated."
|
|
|
|
if $ARDUINO_CLI core list 2>/dev/null | grep -q "arduino:avr"; then
|
|
ok "arduino:avr core already installed."
|
|
else
|
|
info "Installing arduino:avr core (this may take a minute)..."
|
|
$ARDUINO_CLI core install arduino:avr
|
|
ok "arduino:avr core installed."
|
|
fi
|
|
|
|
if command -v avr-size &>/dev/null; then
|
|
ok "avr-size found (binary size reporting enabled)."
|
|
else
|
|
warn "avr-size not found. Install for binary size details:"
|
|
warn " sudo apt install gcc-avr"
|
|
fi
|
|
|
|
echo ""
|
|
if groups 2>/dev/null | grep -q dialout; then
|
|
ok "User '$(whoami)' is in the 'dialout' group."
|
|
else
|
|
warn "User '$(whoami)' is NOT in the 'dialout' group."
|
|
warn "Fix with: sudo usermod -aG dialout \$USER"
|
|
warn "Then log out and back in (or reboot)."
|
|
fi
|
|
|
|
echo ""
|
|
scan_devices
|
|
|
|
echo -e "${BLD}Setup complete!${RST}"
|
|
echo ""
|
|
echo " Next steps:"
|
|
echo " 1. Plug in your RedBoard"
|
|
echo " 2. ./arduino-build.sh --devices"
|
|
echo " 3. ./arduino-build.sh ./your_sketch"
|
|
echo ""
|
|
}
|
|
|
|
# ============================================================================
|
|
# AUTO-DETECT SERIAL PORT
|
|
# ============================================================================
|
|
auto_detect_port() {
|
|
local candidates=()
|
|
for p in /dev/ttyUSB* /dev/ttyACM*; do
|
|
[[ -e "$p" ]] && candidates+=("$p")
|
|
done
|
|
|
|
if [[ ${#candidates[@]} -eq 0 ]]; then
|
|
die "No serial ports found. Is the board plugged in?
|
|
Run: ./arduino-build.sh --devices"
|
|
fi
|
|
|
|
if [[ ${#candidates[@]} -eq 1 ]]; then
|
|
echo "${candidates[0]}"
|
|
return
|
|
fi
|
|
|
|
warn "Multiple serial ports detected:"
|
|
for p in "${candidates[@]}"; do
|
|
warn " $p"
|
|
done
|
|
|
|
# Prefer /dev/ttyUSB* (CH340 on RedBoard) over /dev/ttyACM*
|
|
for p in "${candidates[@]}"; do
|
|
if [[ "$p" == /dev/ttyUSB* ]]; then
|
|
warn "Auto-selected $p (CH340 -- likely RedBoard)"
|
|
warn "Use -p to override if this is wrong."
|
|
echo "$p"
|
|
return
|
|
fi
|
|
done
|
|
|
|
warn "Auto-selected ${candidates[0]}. Use -p to override."
|
|
echo "${candidates[0]}"
|
|
}
|
|
|
|
# ============================================================================
|
|
# PERSISTENT SERIAL MONITOR (--watch)
|
|
# ============================================================================
|
|
run_watch_monitor() {
|
|
local port="$1"
|
|
local baud="$2"
|
|
|
|
# If no port specified, auto-detect once to get a starting point
|
|
if [[ -z "$port" ]]; then
|
|
# Don't die if no port yet -- we'll wait for it
|
|
for p in /dev/ttyUSB* /dev/ttyACM*; do
|
|
if [[ -e "$p" ]]; then
|
|
port="$p"
|
|
break
|
|
fi
|
|
done
|
|
if [[ -z "$port" ]]; then
|
|
# Default to the most common RedBoard port
|
|
port="/dev/ttyUSB0"
|
|
info "No port detected yet. Waiting for $port to appear..."
|
|
fi
|
|
fi
|
|
|
|
info "Persistent monitor on ${BLD}$port${RST} at ${baud} baud"
|
|
info "Reconnects automatically after upload / reset / replug."
|
|
info "Press Ctrl+C to exit."
|
|
echo ""
|
|
|
|
trap 'echo ""; info "Monitor stopped."; exit 0' INT TERM
|
|
|
|
while true; do
|
|
# Wait for port to exist
|
|
if [[ ! -e "$port" ]]; then
|
|
echo -e "${DIM}--- Waiting for $port ...${RST}"
|
|
while [[ ! -e "$port" ]]; do
|
|
sleep 0.5
|
|
done
|
|
# Brief settle time after port appears (CH340 needs a moment)
|
|
sleep 1.0
|
|
echo -e "${GRN}--- $port connected ---${RST}"
|
|
fi
|
|
|
|
# Open the monitor (will exit when port disappears)
|
|
$ARDUINO_CLI monitor -p "$port" -c baudrate="$baud" 2>/dev/null || true
|
|
|
|
# If we get here, the monitor exited (port vanished during upload,
|
|
# board reset, USB replug, etc.). Brief pause then retry.
|
|
echo -e "${YEL}--- $port disconnected ---${RST}"
|
|
sleep 0.5
|
|
done
|
|
}
|
|
|
|
# ============================================================================
|
|
# ARGUMENT PARSING
|
|
# ============================================================================
|
|
SKETCH_DIR=""
|
|
PORT=""
|
|
VERIFY_ONLY=0
|
|
UPLOAD_ONLY=0
|
|
DO_MONITOR=0
|
|
DO_CLEAN=0
|
|
DO_VERBOSE=0
|
|
ACTION=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-h|--help)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
--version)
|
|
echo "arduino-build.sh v${VERSION}"
|
|
exit 0
|
|
;;
|
|
--setup)
|
|
ACTION="setup"
|
|
shift
|
|
;;
|
|
--devices)
|
|
ACTION="devices"
|
|
shift
|
|
;;
|
|
--watch)
|
|
ACTION="watch"
|
|
shift
|
|
;;
|
|
--verify)
|
|
VERIFY_ONLY=1
|
|
shift
|
|
;;
|
|
--upload)
|
|
shift # default behavior, accepted for explicitness
|
|
;;
|
|
--upload-only)
|
|
UPLOAD_ONLY=1
|
|
shift
|
|
;;
|
|
--monitor)
|
|
DO_MONITOR=1
|
|
shift
|
|
;;
|
|
--clean)
|
|
DO_CLEAN=1
|
|
shift
|
|
;;
|
|
--verbose)
|
|
DO_VERBOSE=1
|
|
shift
|
|
;;
|
|
-p|--port)
|
|
[[ -n "${2:-}" ]] || die "--port requires a value (e.g., -p /dev/ttyUSB0)"
|
|
PORT="$2"
|
|
shift 2
|
|
;;
|
|
-b|--baud)
|
|
[[ -n "${2:-}" ]] || die "--baud requires a value (e.g., -b 115200)"
|
|
BAUD="$2"
|
|
shift 2
|
|
;;
|
|
--fqbn)
|
|
[[ -n "${2:-}" ]] || die "--fqbn requires a value (e.g., --fqbn arduino:avr:uno)"
|
|
FQBN="$2"
|
|
shift 2
|
|
;;
|
|
-*)
|
|
die "Unknown option: $1
|
|
Run with --help for usage."
|
|
;;
|
|
*)
|
|
if [[ -z "$SKETCH_DIR" ]]; then
|
|
SKETCH_DIR="$1"
|
|
else
|
|
die "Unexpected argument: $1
|
|
Sketch directory already set to: $SKETCH_DIR"
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# ============================================================================
|
|
# DISPATCH STANDALONE COMMANDS
|
|
# ============================================================================
|
|
case "${ACTION:-}" in
|
|
setup) run_setup; exit 0 ;;
|
|
devices) scan_devices; exit 0 ;;
|
|
watch) run_watch_monitor "$PORT" "$BAUD" ;;
|
|
esac
|
|
|
|
# ============================================================================
|
|
# VALIDATE SKETCH
|
|
# ============================================================================
|
|
if [[ $UPLOAD_ONLY -eq 1 ]] && [[ $VERIFY_ONLY -eq 1 ]]; then
|
|
die "--upload-only and --verify are contradictory.
|
|
--verify means compile only, --upload-only means upload only."
|
|
fi
|
|
|
|
if [[ $UPLOAD_ONLY -eq 1 ]] && [[ $DO_CLEAN -eq 1 ]]; then
|
|
die "--upload-only and --clean are contradictory.
|
|
--clean would delete the cached build that --upload-only needs."
|
|
fi
|
|
|
|
if [[ -z "$SKETCH_DIR" ]]; then
|
|
echo ""
|
|
echo -e "${RED}Error: No sketch directory specified.${RST}"
|
|
echo ""
|
|
echo " Usage: ./arduino-build.sh [OPTIONS] <sketch_dir>"
|
|
echo ""
|
|
echo " Quick start:"
|
|
echo " --help Full help with examples and troubleshooting"
|
|
echo " --setup First-time toolchain installation"
|
|
echo " --devices Find connected boards"
|
|
echo " ./my_sketch Build and upload a sketch"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
SKETCH_DIR="$(realpath "$SKETCH_DIR")"
|
|
[[ -d "$SKETCH_DIR" ]] || die "Not a directory: $SKETCH_DIR"
|
|
|
|
SKETCH_NAME="$(basename "$SKETCH_DIR")"
|
|
INO_FILE="$SKETCH_DIR/$SKETCH_NAME.ino"
|
|
|
|
if [[ ! -f "$INO_FILE" ]]; then
|
|
ALT_INO="$(find "$SKETCH_DIR" -maxdepth 1 -name '*.ino' | head -1)"
|
|
if [[ -n "$ALT_INO" ]]; then
|
|
warn "Expected ${SKETCH_NAME}.ino but found $(basename "$ALT_INO")"
|
|
warn "Arduino convention: .ino filename must match directory name."
|
|
INO_FILE="$ALT_INO"
|
|
SKETCH_NAME="$(basename "$INO_FILE" .ino)"
|
|
else
|
|
die "No .ino file found in $SKETCH_DIR
|
|
Expected: $SKETCH_DIR/$SKETCH_NAME.ino
|
|
Create one: touch $SKETCH_DIR/$SKETCH_NAME.ino"
|
|
fi
|
|
fi
|
|
|
|
info "Sketch: ${BLD}$SKETCH_NAME${RST}"
|
|
info "Board: $FQBN"
|
|
|
|
# ============================================================================
|
|
# PRE-FLIGHT CHECKS
|
|
# ============================================================================
|
|
command -v "$ARDUINO_CLI" &>/dev/null \
|
|
|| die "arduino-cli not found. Run: ./arduino-build.sh --setup"
|
|
|
|
if ! $ARDUINO_CLI core list 2>/dev/null | grep -q "arduino:avr"; then
|
|
die "arduino:avr core not installed. Run: ./arduino-build.sh --setup"
|
|
fi
|
|
|
|
# ============================================================================
|
|
# CLEAN (if requested)
|
|
# ============================================================================
|
|
SKETCH_BUILD_DIR="$BUILD_DIR/$SKETCH_NAME"
|
|
|
|
if [[ $DO_CLEAN -eq 1 ]] && [[ -d "$SKETCH_BUILD_DIR" ]]; then
|
|
info "Cleaning build cache..."
|
|
rm -rf "$SKETCH_BUILD_DIR"
|
|
ok "Cache cleared."
|
|
fi
|
|
|
|
# ============================================================================
|
|
# COMPILE
|
|
# ============================================================================
|
|
# ============================================================================
|
|
# COMPILE (skipped with --upload-only)
|
|
# ============================================================================
|
|
if [[ $UPLOAD_ONLY -eq 1 ]]; then
|
|
# Verify cached build artifacts exist
|
|
if [[ ! -d "$SKETCH_BUILD_DIR" ]]; then
|
|
die "No cached build found for '$SKETCH_NAME'.
|
|
Run a compile first: ./arduino-build.sh --verify ./$SKETCH_NAME
|
|
Build cache expected at: $SKETCH_BUILD_DIR"
|
|
fi
|
|
|
|
HEX_FILE="$SKETCH_BUILD_DIR/$SKETCH_NAME.ino.hex"
|
|
if [[ ! -f "$HEX_FILE" ]]; then
|
|
die "Build cache exists but no .hex file found.
|
|
Try a clean rebuild: ./arduino-build.sh --clean ./$SKETCH_NAME"
|
|
fi
|
|
|
|
ok "Using cached build."
|
|
info "Hex: $HEX_FILE"
|
|
|
|
# Report binary size if possible
|
|
ELF_FILE="$SKETCH_BUILD_DIR/$SKETCH_NAME.ino.elf"
|
|
if command -v avr-size &>/dev/null && [[ -f "$ELF_FILE" ]]; then
|
|
echo ""
|
|
avr-size --mcu=atmega328p -C "$ELF_FILE" 2>/dev/null || avr-size "$ELF_FILE"
|
|
echo ""
|
|
fi
|
|
else
|
|
info "Compiling..."
|
|
mkdir -p "$SKETCH_BUILD_DIR"
|
|
|
|
COMPILE_ARGS=(
|
|
--fqbn "$FQBN"
|
|
--build-path "$SKETCH_BUILD_DIR"
|
|
--warnings all
|
|
)
|
|
[[ $DO_VERBOSE -eq 1 ]] && COMPILE_ARGS+=(--verbose)
|
|
|
|
if ! $ARDUINO_CLI compile "${COMPILE_ARGS[@]}" "$SKETCH_DIR"; then
|
|
die "Compilation failed."
|
|
fi
|
|
|
|
ok "Compile succeeded."
|
|
|
|
# Report binary size
|
|
ELF_FILE="$SKETCH_BUILD_DIR/$SKETCH_NAME.ino.elf"
|
|
if command -v avr-size &>/dev/null && [[ -f "$ELF_FILE" ]]; then
|
|
echo ""
|
|
avr-size --mcu=atmega328p -C "$ELF_FILE" 2>/dev/null || avr-size "$ELF_FILE"
|
|
echo ""
|
|
fi
|
|
fi
|
|
|
|
# ============================================================================
|
|
# VERIFY-ONLY EXIT
|
|
# ============================================================================
|
|
if [[ $VERIFY_ONLY -eq 1 ]]; then
|
|
ok "Verify-only mode. Done."
|
|
exit 0
|
|
fi
|
|
|
|
# ============================================================================
|
|
# UPLOAD
|
|
# ============================================================================
|
|
if [[ -z "$PORT" ]]; then
|
|
PORT="$(auto_detect_port)"
|
|
fi
|
|
|
|
info "Uploading to ${BLD}$PORT${RST} ..."
|
|
|
|
if [[ ! -e "$PORT" ]]; then
|
|
die "Port $PORT does not exist. Run --devices to check."
|
|
fi
|
|
|
|
if [[ ! -w "$PORT" ]]; then
|
|
warn "No write access to $PORT"
|
|
warn "Fix: sudo usermod -aG dialout \$USER (then log out/in)"
|
|
fi
|
|
|
|
UPLOAD_ARGS=(
|
|
--fqbn "$FQBN"
|
|
--port "$PORT"
|
|
--input-dir "$SKETCH_BUILD_DIR"
|
|
)
|
|
[[ $DO_VERBOSE -eq 1 ]] && UPLOAD_ARGS+=(--verbose)
|
|
|
|
if ! $ARDUINO_CLI upload "${UPLOAD_ARGS[@]}"; then
|
|
die "Upload failed. Run with --verbose for details.
|
|
Also try: ./arduino-build.sh --devices"
|
|
fi
|
|
|
|
ok "Upload complete!"
|
|
|
|
# ============================================================================
|
|
# SERIAL MONITOR
|
|
# ============================================================================
|
|
if [[ $DO_MONITOR -eq 1 ]]; then
|
|
echo ""
|
|
info "Opening serial monitor on $PORT at ${BAUD} baud..."
|
|
info "Press Ctrl+C to exit."
|
|
echo ""
|
|
$ARDUINO_CLI monitor -p "$PORT" -c baudrate="$BAUD"
|
|
else
|
|
echo ""
|
|
info "To open serial monitor:"
|
|
echo " ./arduino-build.sh --monitor -p $PORT ./your_sketch"
|
|
echo " arduino-cli monitor -p $PORT -c baudrate=$BAUD"
|
|
echo " screen $PORT $BAUD"
|
|
fi |