Arduino Projects skill for Claude Code
Sketches that never block and never cook a pin. Claude writes Arduino sketches as small non-blocking tasks driven by millis(), debounces inputs, keeps ISRs to a flag, and avoids heap fragmentation on 2 KB AVRs. Before any wiring advice it checks logic voltage and per-pin current, insists on drivers and flyback diodes for inductive loads, and builds with arduino-cli using pinned core and library versions.
- name
arduino-projects - size225 lines · ≈2,400 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Builds and debugs Arduino sketches that stay responsive — setup/loop structure, millis()-based non-blocking timing, state machines, debounced inputs, library selection, arduino-cli compile/upload, and wiring checks for voltage levels and current limits. Use when writing or fixing a .ino sketch, when a sketch freezes or resets, when delay() blocks buttons or sensors, when choosing pins or libraries for Uno, Nano, Mega, Nano ESP32, or RP2040 boards, or when wiring sensors, LEDs, relays, and motors. Try asking
- My sketch ignores the button while the LED blink delay runs — rewrite it without delay()
- The Uno resets every time the servo moves. What is wrong with my wiring?
- Set up arduino-cli builds for this sketch on a Nano ESP32 with pinned library versions
Use it when
- Writing or fixing a .ino sketch for AVR, Renesas, RP2040, or ESP32 Arduino cores
- A sketch freezes, resets randomly, or misses button presses
- Wiring LEDs, relays, servos, or sensors and checking voltage and current
- Moving builds from the IDE to arduino-cli or CI
Skip it when
- Writing ESP-IDF firmware with Wi-Fi, NVS, and partitions — use esp32-firmware
- Diagnosing I2C, SPI, or UART signal-level problems — use serial-protocols
How to check the result
arduino-cli compile with --warnings all is clean, measured loop time stays under about 5 ms, and the supply holds voltage under worst-case load during a multi-hour soak.
The common mistake it prevents
Powering servos or relays from the board's 5 V pin over USB, which browns out the microcontroller and looks like a software reset bug.
Done well means: loop() never blocks for more than a few milliseconds, every input is
debounced, every output is driven within the pin’s current rating, the sketch compiles
warning-free with arduino-cli, and RAM usage leaves headroom for the stack.
Ground rules
- Check the board’s logic level before wiring anything: Uno R3/Nano/Mega are 5 V; Uno R4, Nano 33, Nano ESP32, RP2040, ESP32 and most modern boards are 3.3 V and are not 5 V tolerant. A 5 V sensor output into a 3.3 V pin needs a level shifter or divider.
- No
delay()in sketches that read inputs or run more than one task. Usemillis(). - Never drive motors, relays, solenoids or long LED strips straight from a GPIO pin. Use a transistor/MOSFET or driver board, a separate supply, a common ground, and a flyback diode across inductive loads.
- Read the datasheet of every module, not the product listing: supply range, logic level, I2C address, current draw.
- Ask before running anything that erases or overwrites a bootloader
(
arduino-cli burn-bootloader, ISP programming, fuse changes on AVR).
Electrical limits to respect
| Board family | Logic | Per-pin abs. max | Practical per-pin | Notes |
|---|---|---|---|---|
| ATmega328P (Uno R3, Nano) | 5 V | 40 mA | 20 mA | 200 mA total for the chip |
| ATmega2560 (Mega) | 5 V | 40 mA | 20 mA | Per-port group limits apply |
| RA4M1 (Uno R4) | 5 V I/O | ~8 mA | 8 mA | Much weaker than the R3; check per-pin table |
| ESP32 / Nano ESP32 | 3.3 V | ~40 mA | 10–20 mA | Not 5 V tolerant |
| RP2040 (Nano RP2040, Pico) | 3.3 V | configurable 2–12 mA | 4–8 mA | Not 5 V tolerant |
- LED resistor:
R = (Vsupply - Vf) / I. Red LED on 5 V at 10 mA: (5 - 2.0) / 0.010 = 300 Ω, use 330 Ω. - USB ports supply 500 mA (USB 2.0); servos and motors brown out the board through USB. A reset when a servo moves means the supply sags — power it separately.
- The onboard 5 V regulator on a barrel-jack Uno overheats above ~7–9 V input at a few hundred mA. Feed loads from their own regulator.
Workflow
- [ ] 1. Identify board, core, voltage, and every attached part (datasheets)
- [ ] 2. Draw the pin map: pin, direction, pull-up/down, voltage, current
- [ ] 3. Create/verify the toolchain with arduino-cli and a pinned core version
- [ ] 4. Write the sketch as non-blocking tasks + a state machine
- [ ] 5. Compile with warnings on; check flash and RAM report
- [ ] 6. Bring up one peripheral at a time with serial logging
- [ ] 7. Run the soak test (hours) and the worst-case input test
3. Toolchain with arduino-cli
arduino-cli config init
arduino-cli core update-index
arduino-cli board list # find port + FQBN
arduino-cli core install arduino:avr # or arduino:renesas_uno, esp32:esp32, rp2040:rp2040
arduino-cli lib install "Adafruit BME280 Library"
arduino-cli compile --fqbn arduino:avr:uno --warnings all ./MySketch
arduino-cli upload -p /dev/ttyACM0 --fqbn arduino:avr:uno ./MySketch
arduino-cli monitor -p /dev/ttyACM0 -c baudrate=115200
- Third-party cores need their index URL in
arduino-cli.yamlunderboard_manager.additional_urls(ESP32: Espressif’spackage_esp32_index.json). - Pin core and library versions for reproducible builds (
core install esp32:esp32@<ver>,lib install Name@<ver>) and record them in the README or asketch.yamlprofile. - The sketch folder name must match the
.inofile name.
4. Non-blocking structure
const uint8_t BUTTON_PIN = 2;
const uint8_t LED_PIN = 13;
unsigned long lastBlink = 0;
const unsigned long BLINK_MS = 500;
bool stableState = HIGH, lastReading = HIGH;
unsigned long lastChange = 0;
const unsigned long DEBOUNCE_MS = 30;
enum class Mode : uint8_t { Idle, Running, Fault };
Mode mode = Mode::Idle;
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP); // button to GND, no external resistor
pinMode(LED_PIN, OUTPUT);
}
void pollButton(unsigned long now) {
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastReading) { lastChange = now; lastReading = reading; }
if (now - lastChange >= DEBOUNCE_MS && reading != stableState) {
stableState = reading;
if (stableState == LOW) { // pressed
mode = (mode == Mode::Idle) ? Mode::Running : Mode::Idle;
}
}
}
void loop() {
unsigned long now = millis();
pollButton(now);
if (mode == Mode::Running && now - lastBlink >= BLINK_MS) {
lastBlink += BLINK_MS; // no drift; use `= now` if catch-up is unwanted
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
}
- Always compare
now - last >= intervalwithunsigned long. That form survives the 49.7-daymillis()rollover;now >= last + intervaldoes not. - One
pollX(now)function per concern;loop()just calls them. micros()wraps every ~71 minutes; same subtraction rule.
Interrupts
- Use
attachInterrupt(digitalPinToInterrupt(pin), isr, FALLING)only for events shorter than one loop pass (encoders, pulse counting). Buttons do not need interrupts. - ISRs: set a flag or increment a counter, nothing else. No
Serial, nodelay, nomillis()advancement inside, no allocation. - Shared variables are
volatile. Read multi-byte ones with interrupts paused:
volatile uint32_t pulses = 0;
void onPulse() { pulses++; }
uint32_t readPulses() {
noInterrupts();
uint32_t copy = pulses;
interrupts();
return copy;
}
Memory on small AVRs
The Uno has 2 KB of SRAM. Stack, heap, and globals share it.
- Wrap constant strings:
Serial.println(F("Sensor ready"));keeps them in flash. - Avoid
Stringconcatenation inloop()on AVR; it fragments the heap and causes resets after hours. Use fixedchar buf[32]withsnprintf. snprintfon AVR has no%f; usedtostrf(value, width, prec, buf).- The compile report “Global variables use X bytes” — keep it under ~75% on AVR so the stack has room.
- 32-bit boards (ESP32, RP2040, SAMD, Renesas) have far more RAM;
Stringis fine there in moderation.
Library choice
| Need | Default |
|---|---|
| Debounced buttons | Hand-rolled pattern above, or Bounce2 |
| Many timed tasks | millis() schedulers; avoid “thread” libraries on AVR |
| Servo | Servo (AVR/Renesas), ESP32Servo on ESP32 |
| Addressable LEDs | FastLED or Adafruit_NeoPixel; 5 V strips need a level shifter from 3.3 V boards |
| Sensors | The vendor’s library (Adafruit, SparkFun) unless it blocks for long |
| Stepper | AccelStepper with a driver (A4988, TMC2209), never GPIO coils |
Before adopting a library: check it supports your architecture (architectures= in
library.properties), is maintained, and does not call delay() internally for long.
Debugging
| Symptom | Likely cause | Check |
|---|---|---|
| Board resets when a load turns on | Supply sag, missing flyback diode | Scope or multimeter on 5 V during switching; separate supply |
| Random resets after hours | Heap fragmentation, stack overflow, watchdog | Remove String, print free RAM periodically |
| Button reads randomly | Floating input | INPUT_PULLUP or external pull resistor |
| Upload fails “not in sync” | Wrong port/board, Serial pins 0/1 in use | Disconnect modules on D0/D1, check FQBN |
| Sensor reads 0 or NaN | Wrong I2C address, missing pull-ups, wrong voltage | Run an I2C scanner sketch; check address in datasheet |
| Garbage on serial monitor | Baud mismatch | Match Serial.begin and monitor baud |
analogRead noisy | Floating reference, long wires | Average N samples, add 100 nF at sensor, check AREF |
Add a periodic heartbeat log so hangs are visible:
if (now - lastLog >= 1000) { lastLog = now; Serial.print(F("up ")); Serial.println(now / 1000); }
For bus-level problems (I2C NACKs, SPI garbage, UART framing), switch to the serial-protocols skill.
Gotchas
- Pins 0 and 1 on Uno/Nano are the USB serial lines; using them breaks upload and logs.
analogWriteis PWM, not a voltage; frequency and resolution vary by board.- A4/A5 are SDA/SCL on Uno R3; on Mega I2C is on 20/21. Do not hardcode across boards.
- Using
Wireat 5 V with a 3.3 V-only sensor breakout that lacks a regulator and level shifter damages the sensor slowly. EEPROM.writewears out after ~100,000 cycles per cell; useEEPROM.updateand write only on change.- Large local arrays inside functions overflow the 2 KB AVR stack silently.
Verification
arduino-cli compile --warnings allis warning-free; RAM report under ~75% on AVR.- Measure the loop: toggle a spare pin each pass or log max loop time; it should stay under ~5 ms with all features active.
- Measure supply voltage under worst-case load with a multimeter; it stays within the board’s range.
- Soak test for several hours (or across a
millis()rollover simulation by seeding the timing variables near0xFFFFFFFFin a test build). - Confirm every pin’s current from the pin map against the table above.
Output contract
When delivering a sketch, include:
**Board / core:** Arduino Uno R3, arduino:avr 1.8.x
**Pin map:** D2 button (INPUT_PULLUP, to GND) | D9 servo signal (servo powered from 5 V 2 A supply, common GND) | A4/A5 BME280 (I2C 0x76, 3.3 V breakout with onboard shifter)
**Libraries:** Servo (bundled), Adafruit BME280 Library 2.x
**Power:** USB for logic, separate 5 V 2 A for servo
**Build:** arduino-cli compile --fqbn arduino:avr:uno --warnings all
**Tested:** compile clean, 6 h soak, button debounced under rapid presses About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.