Arduino Sketches skill for Codex
Sketches that run for weeks, not minutes. Codex builds Arduino sketches from the command line with pinned cores and libraries, restructures blocking delay() code into rollover-safe millis() tasks, guards ISR-shared variables, and keeps strings out of SRAM on AVR boards. Hardware bugs get a symptom table and an I2C scan before anyone rewrites a driver.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- arduino-sketches Installs to ~/.agents/skills/arduino-sketches/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install arduino-sketches The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project arduino-sketches Installs to .agents/skills/arduino-sketches/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/arduino-sketches.zipunzip -o arduino-sketches.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) arduino-sketches PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $arduino-sketches, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Writes, builds and debugs Arduino sketches with arduino-cli and sketch.yaml profiles - non-blocking millis() loops, interrupt-safe state, I2C/SPI sensor libraries, F() strings and RAM budgeting on AVR, SAMD, RP2040 and ESP boards. Use when a sketch hangs, resets, prints garbage on Serial, runs out of SRAM, a library will not compile, a sensor is not detected on I2C, or when turning a delay()-based prototype into a reliable loop.
Use it when
- An Arduino sketch hangs, resets or prints garbage on Serial
- A delay()-based prototype needs to handle several things at once
- A sensor library compiles but the device is not detected
- You want reproducible command-line builds of an .ino project
Use something else when
- The project needs multiple environments, unit tests or CI: use platformio
- You are writing native ESP-IDF code rather than the Arduino core: use esp32-firmware
How to check the result
Compile with --warnings all under the sketch.yaml profile, compare flash and RAM figures, and confirm a serial log from the real board covers longer than the slowest timer.
The mistake it prevents
Comparing millis() against a precomputed future timestamp breaks at rollover; always subtract the start time and compare the difference.
Done well means: the sketch builds reproducibly from the command line with pinned core and
library versions, loop() never blocks longer than the fastest thing it must react to, and
every hardware claim (pin, address, baud) was checked on the board, not assumed.
Before touching code
- Read
AGENTS.mdand anyREADMEfor the board, wiring and how the user flashes. - Identify the exact board and core:
arduino-cli board listshows the port and FQBN (arduino:avr:uno,arduino:samd:mkrwifi1010,rp2040:rp2040:rpipico,esp32:esp32:esp32s3). Memory limits differ by two orders of magnitude between them. - Look for
sketch.yaml; if present, it pins platform and library versions. Build with its profile instead of whatever is installed globally. - Compile once, unchanged, with all warnings, and save the memory line from the output.
arduino-cli core update-index # network
arduino-cli core install arduino:avr # network; may need approval in the sandbox
arduino-cli compile --fqbn arduino:avr:uno --warnings all ./Blink
arduino-cli upload --fqbn arduino:avr:uno -p /dev/ttyACM0 ./Blink
arduino-cli monitor -p /dev/ttyACM0 -c baudrate=115200
Codex usually cannot see the USB port from its sandbox. Compile there; ask the user to run
upload and monitor and paste the serial output when a step needs the real board.
Pin the build with sketch.yaml
# sketch.yaml, next to the .ino
profiles:
uno:
fqbn: arduino:avr:uno
platforms:
- platform: arduino:avr (1.8.6)
libraries:
- Adafruit BME280 Library (2.2.4)
- Adafruit Unified Sensor (1.1.14)
default_profile: uno
arduino-cli compile --profile uno then installs exactly those versions. Use
arduino-cli lib search bme280 to find exact library names; names with spaces must match.
Loop structure: never block
Replace delay() with elapsed-time checks. Unsigned subtraction is rollover-safe; adding to
millis() and comparing is not.
const uint32_t SAMPLE_MS = 1000;
uint32_t lastSample = 0;
void loop() {
const uint32_t now = millis();
if (now - lastSample >= SAMPLE_MS) { // correct across the 49.7-day wrap
lastSample += SAMPLE_MS; // fixed cadence, no drift
readSensor();
}
pollButton();
serviceSerial();
}
| Need | Use |
|---|---|
| Periodic task | millis() comparison as above, one timestamp per task |
| Sub-millisecond timing | micros() (wraps every ~71 min, same subtraction rule) |
| Multi-step behaviour (connect, wait, retry) | enum state machine with a switch in loop() |
| Debounced button | Sample every 5-10 ms, accept after N stable reads; or the Bounce2 library |
| Exact pulse capture | Hardware interrupt or input capture, not polling |
Interrupts
volatile uint32_t pulseCount = 0;
void onPulse() { pulseCount++; } // short: no Serial, no delay, no allocation
void setup() {
pinMode(2, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2), onPulse, FALLING);
}
uint32_t readPulses() {
noInterrupts(); // a 32-bit read is not atomic on 8-bit AVR
uint32_t c = pulseCount;
pulseCount = 0;
interrupts();
return c;
}
- Only certain pins support
attachInterrupt(Uno: 2 and 3). Always go throughdigitalPinToInterrupt. - On ESP32 mark the handler
IRAM_ATTR, or it can crash when flash cache is disabled. millis()does not advance inside an ISR on AVR;delay()there hangs forever.
Memory on small boards
An Uno has 2 KB of SRAM. The compile output’s “Global variables use X bytes” is static use only; the stack and heap need the rest. Stay below roughly 75 % static on AVR.
- Wrap literal strings:
Serial.println(F("Sensor not found"));keeps them in flash. - Avoid
Stringconcatenation inloop(); it fragments the heap and fails after hours. Use fixedchar buf[32]withsnprintf. On AVR,%fis not supported bysnprintf; usedtostrf(). - Large constant tables:
const uint8_t table[] PROGMEM = {...};andpgm_read_byte(). - Quick free-RAM probe on AVR (gap between heap and stack):
extern int __heap_start, *__brkval;
int freeRam() { int v; return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval); }
Sensors and buses
- Run an I2C scan first; never trust the address in a tutorial (BME280 is 0x76 or 0x77).
#include <Wire.h>
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000) {} // native-USB boards; times out on Uno-style
Wire.begin();
for (uint8_t a = 1; a < 127; a++) {
Wire.beginTransmission(a);
if (Wire.endTransmission() == 0) { Serial.print(F("found 0x")); Serial.println(a, HEX); }
}
}
void loop() {}
- Nothing found: check pull-ups (4.7 kOhm to the logic voltage), SDA/SCL swapped, 3.3 V
sensor on a 5 V board without a level shifter, and that
Wire.begin(sda, scl)uses the right pins on ESP32/RP2040. - Check every library
begin()return value and print a clear error; many libraries returnfalsesilently.
Debugging symptoms
| Symptom | Likely cause | Check |
|---|---|---|
| Garbage on Serial | Baud mismatch | Monitor rate equals Serial.begin rate |
| Board resets repeatedly | SRAM exhausted, brownout from motor or servo on USB power | Free-RAM print; separate supply with common ground |
| Works on USB, fails on battery | Code waits on while (!Serial) forever | Add a timeout, as above |
| Upload fails “programmer not responding” | Wrong port or board, serial monitor holding port | Close monitor, re-run board list |
| ESP32 boot loop with “Guru Meditation” | Stack overflow, null pointer, ISR not in IRAM | Decode the backtrace (esp32-firmware) |
| Reading floats at random | Floating input pin | INPUT_PULLUP or external resistor |
multiple definition of at link | Two libraries with the same header, wrong one picked | Compile with -v, read “Multiple libraries were found” |
Add debug output behind a macro so release builds stay small:
#define DEBUG 1
#if DEBUG
#define DBG(x) Serial.println(x)
#else
#define DBG(x)
#endif
Code organisation
- Past ~300 lines, move drivers into
.h/.cpptabs in the sketch folder or intosrc/; the IDE’s auto-prototype generation only applies to.inofiles. - Keep pin numbers and tunables as
constexprat the top, one place to change wiring. - For multiple environments, unit tests or CI, move to
platformiorather than scripting around the IDE.
Verification
-
arduino-cli compile --warnings allwith the profile: zero new warnings - Flash and RAM use recorded before and after the change
- Serial log from the real board shows the expected sequence (ask the user to paste it)
- Ran for longer than the longest timer (for
String/heap issues: hours, not seconds) - Unplug-and-replug cold boot works without the serial monitor open
Report
**Board:** Arduino Uno R3 (arduino:avr 1.8.6), BME280 on I2C 0x76
**Change:** replaced delay()-based loop with 3 millis() tasks; ISR counter read atomically
**Memory:** flash 14,212 -> 12,980 B; globals 1,610 -> 1,094 B (53 %)
**Verified:** compile clean; user-provided serial log shows 1 Hz samples for 30 min
**Open:** sensor at 0x77 on the second board revision needs a config flag What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Arduino Sketches"
short_description: "Sketches that run for weeks, not minutes"
default_prompt: "$arduino-sketches My Arduino resets every few hours, find out why" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.