
Arduino Code Generator
- 292 installs
- 19 repo stars
- Updated May 26, 2026
- wedsamuel1230/arduino-skills
Helps with ai & agent building tasks.
About
arduino-code-generator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- arduino-code-generator
- AI & Agent Building
- AI-coding skill
Arduino Code Generator by the numbers
- 292 all-time installs (skills.sh)
- +14 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,346 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wedsamuel1230/arduino-skills --skill arduino-code-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 292 |
|---|---|
| repo stars | ★ 19 |
| Last updated | May 26, 2026 |
| Repository | wedsamuel1230/arduino-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Arduino Code Generator
Generate production-quality Arduino code snippets for sensors, actuators, communication, and embedded patterns.
Quick Start
Browse example sketches:
# See 9 production-ready examples in examples/ folder
ls examples/
# config-example.ino, filtering-example.ino, buttons-example.ino,
# i2c-example.ino, csv-example.ino, scheduler-example.ino,
# state-machine-example.ino, hardware-detection-example.ino,
# data-logging-example.inoList available patterns:
uv run --no-project scripts/generate_snippet.py --listGenerate code for specific pattern and board:
uv run --no-project scripts/generate_snippet.py --pattern i2c --board esp32
uv run --no-project scripts/generate_snippet.py --pattern buttons --board uno --output button.inoInteractive mode:
uv run --no-project scripts/generate_snippet.py --interactiveResources
- examples/ - 9 production-ready example sketches (one per pattern category)
- examples/README.md - Detailed documentation for each example with wiring diagrams
- scripts/generate_snippet.py - CLI tool for code generation with 9 pattern templates
- scripts/verify_patterns.ps1 - Compile examples for UNO/ESP32/RP2040 (PowerShell)
- scripts/verify_patterns.sh - Compile examples for UNO/ESP32/RP2040 (bash)
- assets/workflow.mmd - Mermaid diagram of code generation workflow
Supported Patterns
Hardware Abstraction
- Multi-board config.h with conditional compilation
- Pin definitions for UNO/ESP32/RP2040
- Memory budget tracking
See patterns-config.md | Example: config-example.ino
Sensor Reading & Filtering
- ADC noise reduction (moving average, median, Kalman)
- DHT22, BME280, analog sensors
- Data validation and calibration
See patterns-filtering.md | Example: filtering-example.ino
Input Handling
- Software button debouncing
- Edge detection (PRESSED/RELEASED/LONG_PRESS)
- Multi-button management
See patterns-buttons.md | Example: buttons-example.ino
Communication
- I2C device scanning and diagnostics
- SPI configuration
- UART/Serial protocols
- CSV data output
See patterns-i2c.md and patterns-csv.md | Examples: i2c-example.ino, csv-example.ino
Timing & Concurrency
- Non-blocking millis() patterns
- Task scheduling without delay()
- Priority-based schedulers
- State machines
See patterns-scheduler.md and patterns-state-machine.md | Examples: scheduler-example.ino, state-machine-example.ino
Hardware Detection
- Auto-detect boards (UNO/ESP32/RP2040)
- SRAM usage monitoring
- Sensor fallback strategies
- Adaptive configuration
See patterns-hardware-detection.md | Example: hardware-detection-example.ino
Data Persistence
- EEPROM with CRC validation
- SD card FAT32 logging
- Wear leveling for EEPROM
- Buffered writes
See patterns-data-logging.md | Example: data-logging-example.ino
Code Generation Workflow
- [ ] [Identify Pattern Type](workflow/step1-identify-pattern.md) - Analyze user request to determine core pattern category
- [ ] [Read Reference Documentation](workflow/step2-read-reference.md) - Consult pattern-specific reference files for implementation details
- [ ] [Generate Code](workflow/step3-generate-code.md) - Create production-ready code following quality standards
- [ ] [Provide Instructions](workflow/step4-provide-instructions.md) - Include wiring diagrams and usage guidance
- [ ] [Mention Integration](workflow/step5-mention-integration.md) - Suggest combinations with other patterns when relevant
Quality Standards & Rules
- [ ] [Quality Standards](rules/quality-standards.md) - Compilation, timing, memory safety, and error handling requirements
- [ ] [Board Optimization](rules/board-optimization.md) - UNO, ESP32, and RP2040 specific optimizations and features
- [ ] [Common Pitfalls](rules/common-pitfalls.md) - Critical mistakes to avoid in Arduino development
Code Output Template
- [ ] [Code Template](templates/code-output-template.md) - Standardized structure for generated Arduino sketches
Resources
- examples/ - 9 production-ready example sketches (one per pattern category)
- examples/README.md - Detailed documentation for each example with wiring diagrams
- scripts/generate_snippet.py - CLI tool for code generation with 9 pattern templates
- assets/workflow.mmd - Mermaid diagram of code generation workflow
- workflow/ - Step-by-step code generation process
- rules/ - Quality standards and board-specific optimizations
- templates/ - Code output templates and structure guidelines
- references/ - Detailed pattern documentation and API references
- references/README.md - Reference structure and formatting guide
{
"name": "arduino-code-generator",
"metadata": {
"description": "Generate Arduino/embedded C++ code snippets and patterns on demand for UNO/ESP32/RP2040. Use when users request Arduino code for sensors, actuators, communication protocols, state machines, non-blocking timers, data logging, or hardware abstraction. Generates production-ready code with proper memory management, timing patterns, and board-specific optimization.",
"version": "1.3.0",
"license": "MIT",
"author": "arduino-skills contributors",
"tags": ["arduino", "embedded-systems", "code-generation", "iot", "maker"],
"category": "embedded-systems"
},
"plugins": [
{
"name": "arduino-code-generator",
"description": "Generate production-quality Arduino code snippets for sensors, actuators, communication, and embedded patterns",
"enabled": true
}
]
}
```mermaid
flowchart TD
A["User Request:\nGenerate Arduino Code"] --> B{"Pattern Type?"}
B -->|config| C1["patterns-config.md"]
B -->|buttons| C2["patterns-buttons.md"]
B -->|i2c| C3["patterns-i2c.md"]
B -->|scheduler| C4["patterns-scheduler.md"]
B -->|filtering| C5["patterns-filtering.md"]
B -->|state-machine| C6["patterns-state-machine.md"]
B -->|csv| C7["patterns-csv.md"]
B -->|data-logging| C8["patterns-data-logging.md"]
B -->|hardware| C9["patterns-hardware.md"]
C1 & C2 & C3 & C4 & C5 & C6 & C7 & C8 & C9 --> D["Template Engine\ngenerate_snippet.py"]
D --> E{"Board Type?"}
E -->|Arduino UNO| F1["2KB SRAM\n9600 baud\nF macro"]
E -->|ESP32| F2["520KB SRAM\nWiFi/BLE\n115200 baud"]
E -->|RP2040| F3["264KB SRAM\nDual-core\n115200 baud"]
F1 & F2 & F3 --> G["Generated .ino Code"]
style A fill:#e1f5fe
style G fill:#c8e6c9
style D fill:#fff3e0
```
/*
* Debounced Button Pattern Example
*
* Demonstrates robust button handling with:
* - Debouncing (noise rejection)
* - Press/release event detection
* - Long-press detection (1 second threshold)
* - Non-blocking state machine implementation
*
* Generated by: arduino-code-generator
* Pattern: Button Input with Debouncing
* License: MIT
*/
// === Configuration ===
#if defined(ESP32)
const uint8_t BUTTON_PIN = 4;
const uint32_t SERIAL_BAUD = 115200;
#elif defined(ARDUINO_ARCH_RP2040)
const uint8_t BUTTON_PIN = 10;
const uint32_t SERIAL_BAUD = 115200;
#else
const uint8_t BUTTON_PIN = 2;
const uint32_t SERIAL_BAUD = 9600;
#endif
// === Debounced Button Class ===
class DebouncedButton {
public:
enum Event {
NONE,
PRESSED,
RELEASED,
LONG_PRESS
};
private:
const uint8_t pin;
const uint16_t DEBOUNCE_MS = 50;
const uint16_t LONG_PRESS_MS = 1000;
bool lastReading = HIGH;
bool lastStableState = HIGH;
unsigned long lastDebounceTime = 0;
unsigned long pressStartTime = 0;
bool longPressTriggered = false;
public:
DebouncedButton(uint8_t buttonPin) : pin(buttonPin) {}
void begin() {
pinMode(pin, INPUT_PULLUP);
}
Event update() {
bool reading = digitalRead(pin);
unsigned long now = millis();
// Reset debounce timer on state change
if (reading != lastReading) {
lastDebounceTime = now;
}
lastReading = reading;
// Wait for stable state
if ((now - lastDebounceTime) < DEBOUNCE_MS) {
return NONE;
}
// State changed
if (reading != lastStableState) {
lastStableState = reading;
if (reading == LOW) {
// Button pressed
pressStartTime = now;
longPressTriggered = false;
return PRESSED;
} else {
// Button released
return longPressTriggered ? NONE : RELEASED;
}
}
// Check for long press (while held)
if (lastStableState == LOW && !longPressTriggered) {
if ((now - pressStartTime) >= LONG_PRESS_MS) {
longPressTriggered = true;
return LONG_PRESS;
}
}
return NONE;
}
bool isPressed() const { return lastStableState == LOW; }
};
// === Application ===
DebouncedButton button(BUTTON_PIN);
uint16_t pressCount = 0;
uint16_t longPressCount = 0;
void setup() {
Serial.begin(SERIAL_BAUD);
button.begin();
Serial.println(F("\n=== Debounced Button Example ==="));
Serial.println(F("Events detected:"));
Serial.println(F("- PRESS: Short button press"));
Serial.println(F("- RELEASE: Button released"));
Serial.println(F("- LONG_PRESS: Hold for 1 second"));
Serial.println();
}
void loop() {
DebouncedButton::Event event = button.update();
switch (event) {
case DebouncedButton::PRESSED:
Serial.println(F("-> PRESSED"));
pressCount++;
break;
case DebouncedButton::RELEASED:
Serial.print(F("-> RELEASED (count: "));
Serial.print(pressCount);
Serial.println(F(")"));
break;
case DebouncedButton::LONG_PRESS:
Serial.println(F("-> LONG_PRESS"));
longPressCount++;
Serial.print(F(" Long press count: "));
Serial.println(longPressCount);
break;
case DebouncedButton::NONE:
// No event
break;
}
}
/*
* Hardware Configuration Pattern Example
*
* Demonstrates board-agnostic hardware abstraction using compile-time
* configuration. This pattern allows a single codebase to run on multiple
* Arduino boards (UNO, ESP32, RP2040) with automatic pin mapping.
*
* Generated by: arduino-code-generator
* Pattern: Hardware Configuration
* License: MIT
*/
// === Board Detection & Configuration ===
#if defined(ARDUINO_AVR_UNO)
#define BOARD_NAME "Arduino UNO"
#define LED_PIN 13
#define BUTTON_PIN 2
#define SERIAL_BAUD 9600
#define HAS_WIFI false
#elif defined(ESP32)
#define BOARD_NAME "ESP32"
#define LED_PIN 2
#define BUTTON_PIN 4
#define SERIAL_BAUD 115200
#define HAS_WIFI true
#elif defined(ARDUINO_ARCH_RP2040)
#define BOARD_NAME "RP2040"
#define LED_PIN LED_BUILTIN
#define BUTTON_PIN 10
#define SERIAL_BAUD 115200
#define HAS_WIFI false
#else
#error "Unsupported board - add configuration above"
#endif
// === Application Logic ===
void setup() {
Serial.begin(SERIAL_BAUD);
while (!Serial && millis() < 3000); // Wait for USB serial
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
Serial.println(F("\n=== Hardware Configuration Example ==="));
Serial.print(F("Board: "));
Serial.println(F(BOARD_NAME));
Serial.print(F("LED Pin: "));
Serial.println(LED_PIN);
Serial.print(F("Button Pin: "));
Serial.println(BUTTON_PIN);
Serial.print(F("Baud Rate: "));
Serial.println(SERIAL_BAUD);
Serial.print(F("WiFi Available: "));
Serial.println(HAS_WIFI ? F("Yes") : F("No"));
Serial.println(F("\nPress button to toggle LED"));
}
void loop() {
static bool lastButtonState = HIGH;
static unsigned long lastDebounce = 0;
const unsigned long DEBOUNCE_MS = 50;
bool reading = digitalRead(BUTTON_PIN);
// Simple debounce
if (reading != lastButtonState) {
lastDebounce = millis();
}
if ((millis() - lastDebounce) > DEBOUNCE_MS) {
if (reading == LOW) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
Serial.println(digitalRead(LED_PIN) ? F("LED ON") : F("LED OFF"));
// Wait for release
while (digitalRead(BUTTON_PIN) == LOW) {
// Non-blocking wait
}
}
}
lastButtonState = reading;
}
/*
* CSV Data Output Pattern Example
*
* Demonstrates structured data logging in CSV format with:
* - Timestamp (milliseconds)
* - Multiple sensor channels (temperature, humidity, pressure)
* - F() macro for PROGMEM string storage
* - Proper CSV formatting with headers
*
* Generated by: arduino-code-generator
* Pattern: CSV Data Formatting
* License: MIT
*/
// === Configuration ===
const uint16_t SAMPLE_INTERVAL_MS = 1000; // 1 second
const uint8_t TEMP_PIN = A0;
const uint8_t HUMIDITY_PIN = A1;
const uint8_t PRESSURE_PIN = A2;
// === Simulated Sensor Class ===
class SensorSimulator {
private:
float baseValue;
float noiseLevel;
public:
SensorSimulator(float base, float noise)
: baseValue(base), noiseLevel(noise) {}
float read() {
// Simulate sensor with noise and slow drift
float drift = sin(millis() / 10000.0) * (noiseLevel * 2);
float noise = (random(-100, 100) / 100.0) * noiseLevel;
return baseValue + drift + noise;
}
};
// === Sensor Instances ===
SensorSimulator tempSensor(22.5, 0.5); // 22.5°C ± 0.5°C
SensorSimulator humiditySensor(55.0, 2.0); // 55% ± 2%
SensorSimulator pressureSensor(1013.25, 1.0); // 1013.25 hPa ± 1 hPa
// === CSV Output Functions ===
void printCSVHeader() {
Serial.println(F("timestamp_ms,temperature_c,humidity_percent,pressure_hpa"));
}
void printCSVRow(unsigned long timestamp, float temp, float humidity, float pressure) {
Serial.print(timestamp);
Serial.print(F(","));
Serial.print(temp, 2);
Serial.print(F(","));
Serial.print(humidity, 1);
Serial.print(F(","));
Serial.println(pressure, 2);
}
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000);
randomSeed(analogRead(A3)); // Seed for simulation
Serial.println(F("\n=== CSV Data Logger Example ==="));
Serial.println(F("Simulated environmental monitoring"));
Serial.println(F("Copy output to .csv file for analysis\n"));
delay(500);
printCSVHeader();
}
void loop() {
static unsigned long lastSample = 0;
static uint16_t sampleCount = 0;
unsigned long now = millis();
if (now - lastSample >= SAMPLE_INTERVAL_MS) {
lastSample = now;
// Read sensors
float temperature = tempSensor.read();
float humidity = humiditySensor.read();
float pressure = pressureSensor.read();
// Output CSV row
printCSVRow(now, temperature, humidity, pressure);
sampleCount++;
// Optional: Stop after 60 samples (1 minute)
if (sampleCount >= 60) {
Serial.println(F("\n# Data logging complete (60 samples)"));
while (1); // Halt
}
}
}
/*
* Data Logging Pattern Example
*
* Demonstrates persistent data storage using EEPROM:
* - Sensor data logging with timestamps
* - Circular buffer implementation
* - EEPROM wear leveling (write minimization)
* - Data retrieval and playback
*
* Generated by: arduino-code-generator
* Pattern: Data Logging & Persistence
* License: MIT
*/
#include <EEPROM.h>
// === Configuration ===
const uint16_t LOG_INTERVAL_MS = 5000; // 5 seconds
const uint8_t MAX_LOG_ENTRIES = 20; // Circular buffer size
// === Log Entry Structure ===
struct LogEntry {
uint32_t timestamp; // Milliseconds since boot
int16_t temperature; // °C * 10 (e.g., 235 = 23.5°C)
uint16_t humidity; // % * 10 (e.g., 550 = 55.0%)
uint16_t lightLevel; // Raw ADC value
};
// === EEPROM Data Logger ===
class EEPROMLogger {
private:
const uint16_t MAGIC_NUMBER = 0xABCD;
const uint16_t ADDR_MAGIC = 0;
const uint16_t ADDR_HEAD = 2;
const uint16_t ADDR_COUNT = 4;
const uint16_t ADDR_DATA_START = 6;
uint8_t headIndex;
uint8_t entryCount;
void writeUint16(uint16_t addr, uint16_t value) {
EEPROM.write(addr, value & 0xFF);
EEPROM.write(addr + 1, (value >> 8) & 0xFF);
}
uint16_t readUint16(uint16_t addr) {
uint8_t low = EEPROM.read(addr);
uint8_t high = EEPROM.read(addr + 1);
return (high << 8) | low;
}
void writeLogEntry(uint8_t index, const LogEntry& entry) {
uint16_t addr = ADDR_DATA_START + (index * sizeof(LogEntry));
const uint8_t* data = (const uint8_t*)&entry;
for (size_t i = 0; i < sizeof(LogEntry); i++) {
EEPROM.write(addr + i, data[i]);
}
}
LogEntry readLogEntry(uint8_t index) {
uint16_t addr = ADDR_DATA_START + (index * sizeof(LogEntry));
LogEntry entry;
uint8_t* data = (uint8_t*)&entry;
for (size_t i = 0; i < sizeof(LogEntry); i++) {
data[i] = EEPROM.read(addr + i);
}
return entry;
}
public:
void begin() {
// Check if EEPROM is initialized
uint16_t magic = readUint16(ADDR_MAGIC);
if (magic != MAGIC_NUMBER) {
// First-time initialization
Serial.println(F("Initializing EEPROM logger..."));
writeUint16(ADDR_MAGIC, MAGIC_NUMBER);
writeUint16(ADDR_HEAD, 0);
writeUint16(ADDR_COUNT, 0);
headIndex = 0;
entryCount = 0;
} else {
// Load existing state
headIndex = readUint16(ADDR_HEAD);
entryCount = readUint16(ADDR_COUNT);
Serial.print(F("Loaded "));
Serial.print(entryCount);
Serial.println(F(" existing log entries"));
}
}
void logData(const LogEntry& entry) {
writeLogEntry(headIndex, entry);
headIndex = (headIndex + 1) % MAX_LOG_ENTRIES;
if (entryCount < MAX_LOG_ENTRIES) entryCount++;
writeUint16(ADDR_HEAD, headIndex);
writeUint16(ADDR_COUNT, entryCount);
}
void printAllLogs() {
Serial.println(F("\n=== Stored Logs ==="));
Serial.println(F("Index Timestamp Temp(°C) Humidity(%) Light"));
Serial.println(F("----- --------- -------- ----------- -----"));
for (uint8_t i = 0; i < entryCount; i++) {
// Read in circular order (oldest to newest)
uint8_t index = (headIndex - entryCount + i + MAX_LOG_ENTRIES) % MAX_LOG_ENTRIES;
LogEntry entry = readLogEntry(index);
Serial.print(i);
Serial.print(F(" "));
Serial.print(entry.timestamp / 1000);
Serial.print(F("s "));
Serial.print(entry.temperature / 10.0, 1);
Serial.print(F(" "));
Serial.print(entry.humidity / 10.0, 1);
Serial.print(F(" "));
Serial.println(entry.lightLevel);
}
Serial.println();
}
void clearAll() {
headIndex = 0;
entryCount = 0;
writeUint16(ADDR_HEAD, 0);
writeUint16(ADDR_COUNT, 0);
Serial.println(F("All logs cleared"));
}
uint8_t getCount() const { return entryCount; }
};
// === Application ===
EEPROMLogger logger;
LogEntry readSensors() {
LogEntry entry;
entry.timestamp = millis();
// Simulate temperature sensor (22-25°C)
entry.temperature = random(220, 250);
// Simulate humidity sensor (40-60%)
entry.humidity = random(400, 600);
// Read real light sensor
entry.lightLevel = analogRead(A0);
return entry;
}
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000);
Serial.println(F("\n=== EEPROM Data Logger Example ==="));
Serial.print(F("EEPROM Size: "));
Serial.print(EEPROM.length());
Serial.println(F(" bytes"));
Serial.print(F("Max Log Entries: "));
Serial.println(MAX_LOG_ENTRIES);
Serial.print(F("Entry Size: "));
Serial.print(sizeof(LogEntry));
Serial.println(F(" bytes"));
Serial.println();
logger.begin();
Serial.println(F("Commands:"));
Serial.println(F(" 'p' - Print all logs"));
Serial.println(F(" 'c' - Clear all logs"));
Serial.println();
}
void loop() {
static unsigned long lastLog = 0;
// Log data periodically
if (millis() - lastLog >= LOG_INTERVAL_MS) {
lastLog = millis();
LogEntry entry = readSensors();
logger.logData(entry);
Serial.print(F("Logged: "));
Serial.print(entry.temperature / 10.0, 1);
Serial.print(F("°C, "));
Serial.print(entry.humidity / 10.0, 1);
Serial.print(F("%, Light="));
Serial.print(entry.lightLevel);
Serial.print(F(" ("));
Serial.print(logger.getCount());
Serial.println(F(" total)"));
}
// Handle serial commands
if (Serial.available()) {
char cmd = Serial.read();
if (cmd == 'p') {
logger.printAllLogs();
} else if (cmd == 'c') {
logger.clearAll();
}
}
}
/*
* ADC Filtering Pattern Example
*
* Demonstrates multiple filtering techniques for noisy sensor readings:
* - Moving average filter (smoothing)
* - Exponential moving average (EMA)
* - Median filter (spike rejection)
*
* Generated by: arduino-code-generator
* Pattern: Filtering & Signal Processing
* License: MIT
*/
// === Configuration ===
const uint8_t SENSOR_PIN = A0;
const uint16_t SAMPLE_INTERVAL_MS = 100;
// === Moving Average Filter ===
template<size_t N>
class MovingAverage {
float buffer[N] = {0};
size_t index = 0;
size_t count = 0;
public:
float update(float value) {
buffer[index] = value;
index = (index + 1) % N;
if (count < N) count++;
float sum = 0;
for (size_t i = 0; i < count; i++) {
sum += buffer[i];
}
return sum / count;
}
void reset() {
count = 0;
index = 0;
}
};
// === Exponential Moving Average ===
class EMA {
float alpha;
float value;
bool initialized = false;
public:
EMA(float smoothing = 0.1) : alpha(smoothing) {}
float update(float newValue) {
if (!initialized) {
value = newValue;
initialized = true;
return value;
}
value = alpha * newValue + (1.0 - alpha) * value;
return value;
}
void reset() {
initialized = false;
}
};
// === Median Filter (3-sample) ===
class MedianFilter {
float buffer[3] = {0};
size_t index = 0;
public:
float update(float value) {
buffer[index] = value;
index = (index + 1) % 3;
// Simple 3-value sort
float sorted[3];
memcpy(sorted, buffer, sizeof(buffer));
if (sorted[0] > sorted[1]) { float t = sorted[0]; sorted[0] = sorted[1]; sorted[1] = t; }
if (sorted[1] > sorted[2]) { float t = sorted[1]; sorted[1] = sorted[2]; sorted[2] = t; }
if (sorted[0] > sorted[1]) { float t = sorted[0]; sorted[0] = sorted[1]; sorted[1] = t; }
return sorted[1]; // Return median
}
};
// === Filter Instances ===
MovingAverage<10> movingAvg;
EMA ema(0.15);
MedianFilter medianFilter;
void setup() {
Serial.begin(115200);
pinMode(SENSOR_PIN, INPUT);
Serial.println(F("\n=== ADC Filtering Example ==="));
Serial.println(F("Comparing 4 filtering techniques:"));
Serial.println(F("RAW, MovingAvg(10), EMA(0.15), Median(3)"));
Serial.println();
}
void loop() {
static unsigned long lastSample = 0;
if (millis() - lastSample >= SAMPLE_INTERVAL_MS) {
lastSample = millis();
// Read raw sensor value
int rawADC = analogRead(SENSOR_PIN);
float rawVoltage = rawADC * (5.0 / 1023.0);
// Apply filters
float avgFiltered = movingAvg.update(rawVoltage);
float emaFiltered = ema.update(rawVoltage);
float medianFiltered = medianFilter.update(rawVoltage);
// Output CSV format
Serial.print(rawVoltage, 3);
Serial.print(F(","));
Serial.print(avgFiltered, 3);
Serial.print(F(","));
Serial.print(emaFiltered, 3);
Serial.print(F(","));
Serial.println(medianFiltered, 3);
}
}
/*
* Hardware Detection Pattern Example
*
* Demonstrates runtime board detection and capability reporting:
* - Compile-time board identification
* - Memory capacity detection
* - Clock speed determination
* - Feature flags (WiFi, Bluetooth, etc.)
*
* Generated by: arduino-code-generator
* Pattern: Hardware Detection
* License: MIT
*/
// === Board Detection ===
struct BoardInfo {
const char* name;
const char* mcu;
uint32_t flashSize;
uint32_t sramSize;
uint32_t clockSpeed;
uint8_t adcBits;
bool hasWiFi;
bool hasBluetooth;
uint32_t serialBaud;
};
BoardInfo detectBoard() {
BoardInfo info;
#if defined(ARDUINO_AVR_UNO)
info.name = "Arduino UNO";
info.mcu = "ATmega328P";
info.flashSize = 32768;
info.sramSize = 2048;
info.clockSpeed = 16000000;
info.adcBits = 10;
info.hasWiFi = false;
info.hasBluetooth = false;
info.serialBaud = 9600;
#elif defined(ARDUINO_AVR_MEGA2560)
info.name = "Arduino Mega 2560";
info.mcu = "ATmega2560";
info.flashSize = 262144;
info.sramSize = 8192;
info.clockSpeed = 16000000;
info.adcBits = 10;
info.hasWiFi = false;
info.hasBluetooth = false;
info.serialBaud = 9600;
#elif defined(ESP32)
info.name = "ESP32";
info.mcu = "ESP32 Dual-Core";
info.flashSize = 4194304; // Typical 4MB
info.sramSize = 520000;
info.clockSpeed = 240000000;
info.adcBits = 12;
info.hasWiFi = true;
info.hasBluetooth = true;
info.serialBaud = 115200;
#elif defined(ARDUINO_ARCH_RP2040)
info.name = "Raspberry Pi Pico (RP2040)";
info.mcu = "RP2040 Dual-Core ARM";
info.flashSize = 2097152; // 2MB
info.sramSize = 264000;
info.clockSpeed = 133000000;
info.adcBits = 12;
info.hasWiFi = false;
info.hasBluetooth = false;
info.serialBaud = 115200;
#elif defined(ARDUINO_SAMD_ZERO)
info.name = "Arduino Zero";
info.mcu = "SAMD21";
info.flashSize = 262144;
info.sramSize = 32768;
info.clockSpeed = 48000000;
info.adcBits = 12;
info.hasWiFi = false;
info.hasBluetooth = false;
info.serialBaud = 115200;
#else
info.name = "Unknown Board";
info.mcu = "Unknown";
info.flashSize = 0;
info.sramSize = 0;
info.clockSpeed = 0;
info.adcBits = 10;
info.hasWiFi = false;
info.hasBluetooth = false;
info.serialBaud = 9600;
#endif
return info;
}
// === Helper Functions ===
void printBoardInfo(const BoardInfo& info) {
Serial.println(F("\n=== Hardware Detection Report ===\n"));
Serial.print(F("Board: "));
Serial.println(info.name);
Serial.print(F("MCU: "));
Serial.println(info.mcu);
Serial.print(F("Flash Memory: "));
Serial.print(info.flashSize / 1024);
Serial.println(F(" KB"));
Serial.print(F("SRAM: "));
Serial.print(info.sramSize);
Serial.println(F(" bytes"));
Serial.print(F("Clock Speed: "));
Serial.print(info.clockSpeed / 1000000);
Serial.println(F(" MHz"));
Serial.print(F("ADC Resolution: "));
Serial.print(info.adcBits);
Serial.println(F(" bits"));
Serial.println(F("\nCapabilities:"));
Serial.print(F(" WiFi: "));
Serial.println(info.hasWiFi ? F("Yes") : F("No"));
Serial.print(F(" Bluetooth: "));
Serial.println(info.hasBluetooth ? F("Yes") : F("No"));
Serial.println(F("\nCompile-Time Defines:"));
#ifdef ARDUINO_AVR_UNO
Serial.println(F(" - ARDUINO_AVR_UNO"));
#endif
#ifdef ESP32
Serial.println(F(" - ESP32"));
#endif
#ifdef ARDUINO_ARCH_RP2040
Serial.println(F(" - ARDUINO_ARCH_RP2040"));
#endif
Serial.print(F("\nArduino IDE Version: "));
Serial.println(ARDUINO);
}
void printMemoryUsage() {
Serial.println(F("\n=== Memory Usage ==="));
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_MEGA2560)
// AVR can report free RAM
extern int __heap_start, *__brkval;
int freeRam = (int)&freeRam - (__brkval == 0 ? (int)&__heap_start : (int)__brkval);
Serial.print(F("Free SRAM: "));
Serial.print(freeRam);
Serial.println(F(" bytes"));
#else
Serial.println(F("(Memory reporting not available on this platform)"));
#endif
}
void setup() {
BoardInfo board = detectBoard();
Serial.begin(board.serialBaud);
while (!Serial && millis() < 3000);
printBoardInfo(board);
printMemoryUsage();
Serial.println(F("\n=== Board detection complete ==="));
}
void loop() {
// Periodically print memory usage
static unsigned long lastReport = 0;
if (millis() - lastReport >= 10000) {
lastReport = millis();
printMemoryUsage();
}
}
/*
* I2C Communication Pattern Example
*
* Demonstrates I2C bus scanning and device detection with:
* - 7-bit address scanning (0x08-0x77)
* - Board-specific SDA/SCL pin configuration
* - Error detection and reporting
* - Common device identification
*
* Generated by: arduino-code-generator
* Pattern: I2C Communication
* License: MIT
*/
#include <Wire.h>
// === Board Configuration ===
#if defined(ARDUINO_AVR_UNO)
#define BOARD_NAME "Arduino UNO"
#define SDA_PIN A4
#define SCL_PIN A5
#define SERIAL_BAUD 9600
#elif defined(ESP32)
#define BOARD_NAME "ESP32"
#define SDA_PIN 21
#define SCL_PIN 22
#define SERIAL_BAUD 115200
#elif defined(ARDUINO_ARCH_RP2040)
#define BOARD_NAME "RP2040"
#define SDA_PIN 4
#define SCL_PIN 5
#define SERIAL_BAUD 115200
#else
#define BOARD_NAME "Generic"
#define SERIAL_BAUD 9600
#endif
// === Common I2C Device Addresses ===
struct KnownDevice {
uint8_t address;
const char* name;
};
const KnownDevice knownDevices[] = {
{0x20, "MCP23017 GPIO Expander"},
{0x27, "PCF8574 LCD Backpack"},
{0x3C, "SSD1306 OLED Display"},
{0x3D, "SSD1306 OLED Display (Alt)"},
{0x48, "ADS1115 ADC"},
{0x50, "AT24C EEPROM"},
{0x57, "AT24C32 EEPROM"},
{0x68, "DS1307 RTC / MPU6050 IMU"},
{0x76, "BMP280 Sensor"},
{0x77, "BMP280 Sensor (Alt)"}
};
const size_t knownDeviceCount = sizeof(knownDevices) / sizeof(knownDevices[0]);
// === Helper Functions ===
const char* identifyDevice(uint8_t address) {
for (size_t i = 0; i < knownDeviceCount; i++) {
if (knownDevices[i].address == address) {
return knownDevices[i].name;
}
}
return "Unknown Device";
}
void scanI2CBus() {
Serial.println(F("\nScanning I2C bus..."));
Serial.println(F("Addr Status Device Name"));
Serial.println(F("---- -------- ---------------------"));
uint8_t devicesFound = 0;
for (uint8_t address = 0x08; address < 0x78; address++) {
Wire.beginTransmission(address);
uint8_t error = Wire.endTransmission();
if (error == 0) {
// Device found
Serial.print(F("0x"));
if (address < 16) Serial.print(F("0"));
Serial.print(address, HEX);
Serial.print(F(" Found "));
Serial.println(identifyDevice(address));
devicesFound++;
}
else if (error == 4) {
// Unknown error
Serial.print(F("0x"));
if (address < 16) Serial.print(F("0"));
Serial.print(address, HEX);
Serial.println(F(" Error Unknown I2C error"));
}
}
Serial.println(F("---- -------- ---------------------"));
Serial.print(F("Total devices found: "));
Serial.println(devicesFound);
if (devicesFound == 0) {
Serial.println(F("\nNo I2C devices detected!"));
Serial.println(F("Check wiring:"));
Serial.print(F(" SDA -> Pin "));
Serial.println(SDA_PIN);
Serial.print(F(" SCL -> Pin "));
Serial.println(SCL_PIN);
Serial.println(F(" VCC -> 3.3V or 5V"));
Serial.println(F(" GND -> GND"));
}
}
void setup() {
Serial.begin(SERIAL_BAUD);
while (!Serial && millis() < 3000);
Serial.println(F("\n=== I2C Scanner Example ==="));
Serial.print(F("Board: "));
Serial.println(F(BOARD_NAME));
#if defined(ARDUINO_AVR_UNO)
Wire.begin();
#else
Wire.begin(SDA_PIN, SCL_PIN);
#endif
Wire.setClock(100000); // 100kHz standard mode
Serial.print(F("I2C Clock: 100 kHz\n"));
Serial.print(F("SDA Pin: "));
Serial.println(SDA_PIN);
Serial.print(F("SCL Pin: "));
Serial.println(SCL_PIN);
scanI2CBus();
}
void loop() {
static unsigned long lastScan = 0;
const unsigned long SCAN_INTERVAL_MS = 10000; // Scan every 10 seconds
if (millis() - lastScan >= SCAN_INTERVAL_MS) {
lastScan = millis();
scanI2CBus();
}
}
Arduino Code Generator - Example Sketches
This directory contains 9 production-ready example sketches demonstrating each pattern category in the arduino-code-generator skill. All examples follow best practices from arduino-skills.md.
📁 Available Examples
1. config-example.ino - Hardware Configuration Pattern
Purpose: Board-agnostic hardware abstraction using compile-time configuration
Demonstrates:
- Multi-board support (UNO/ESP32/RP2040) with automatic pin mapping
- Compile-time board detection using preprocessor directives
- Single codebase for multiple platforms
Boards: Arduino UNO, ESP32, RP2040
---
2. filtering-example.ino - ADC Filtering & Signal Processing
Purpose: Clean noisy sensor readings using multiple filtering techniques
Demonstrates:
- Moving average filter (10-sample window)
- Exponential moving average (EMA) with alpha = 0.15
- Median filter (3-sample spike rejection)
- CSV output format for data analysis
Boards: All (uses analog input A0)
---
3. buttons-example.ino - Debounced Button Input
Purpose: Robust button handling with debouncing and event detection
Demonstrates:
- Hardware debouncing (50ms threshold)
- Press/release event detection
- Long-press detection (1 second)
- Non-blocking state machine implementation
Boards: Arduino UNO (pin 2), ESP32 (pin 4), RP2040 (pin 10)
---
4. i2c-example.ino - I2C Communication & Device Scanner
Purpose: I2C bus scanning with device identification
Demonstrates:
- 7-bit address scanning (0x08-0x77)
- Board-specific SDA/SCL pin configuration
- Error detection and reporting
- Common device identification (OLED, RTC, IMU, sensors)
Boards: Arduino UNO (A4/A5), ESP32 (21/22), RP2040 (4/5)
---
5. csv-example.ino - CSV Data Output Pattern
Purpose: Structured data logging in CSV format for analysis
Demonstrates:
- Timestamp + multi-channel sensor data
- F() macro for PROGMEM string storage
- Proper CSV formatting with headers
- Data suitable for Excel/Python analysis
Boards: All (simulated sensors)
---
6. scheduler-example.ino - Non-Blocking Task Scheduler
Purpose: Cooperative multitasking without delay()
Demonstrates:
- Lightweight task scheduler (millis() based)
- 5 independent tasks with different intervals
- Task enable/disable control
- Execution tracking and status reporting
Boards: All
---
7. state-machine-example.ino - Finite State Machine (FSM)
Purpose: Traffic light controller using explicit state machine
Demonstrates:
- State enumeration and transition logic
- Timing-based state changes (non-blocking)
- State-specific behavior and LED outputs
- 4-state cycle: RED → RED+YELLOW → GREEN → YELLOW
Boards: Arduino UNO (pins 9/10/11), ESP32 (pins 25/26/27)
---
8. hardware-detection-example.ino - Runtime Board Detection
Purpose: Detect board capabilities and report system information
Demonstrates:
- Compile-time board identification
- Memory capacity detection (Flash/SRAM)
- Clock speed and ADC resolution reporting
- Feature flags (WiFi, Bluetooth)
Boards: UNO, Mega 2560, ESP32, RP2040, SAMD21
---
9. data-logging-example.ino - EEPROM Persistent Data Storage
Purpose: Long-term sensor data logging with EEPROM
Demonstrates:
- Circular buffer implementation (20 entries)
- EEPROM wear leveling (write minimization)
- Data retrieval and playback
- Sensor data persistence across reboots
Boards: All (uses EEPROM library)
---
🚀 Quick Start
Upload an Example
1. Open Arduino IDE 2. Select your board:
- Tools → Board → Arduino UNO / ESP32 / RP2040
3. Open example:
- File → Open →
arduino-code-generator/examples/<example>.ino
4. Upload:
- Click Upload (or Ctrl+U)
5. Open Serial Monitor:
- Tools → Serial Monitor (or Ctrl+Shift+M)
- Set baud rate (9600 for UNO, 115200 for ESP32/RP2040)
Generate Custom Code
Use the generate_snippet.py script to create custom variations:
# Generate config pattern for ESP32
uv run --no-project scripts/generate_snippet.py --pattern config --board esp32
# Generate button handler for UNO on pin 3
uv run --no-project scripts/generate_snippet.py --pattern buttons --board uno --pin 3
# Interactive mode
uv run --no-project scripts/generate_snippet.py --interactive---
📚 Pattern Reference Documentation
Each example corresponds to a detailed reference file in references/:
| Example | Reference File |
|---|---|
| config-example.ino | patterns-config.md |
| filtering-example.ino | patterns-filtering.md |
| buttons-example.ino | patterns-buttons.md |
| i2c-example.ino | patterns-i2c.md |
| csv-example.ino | patterns-csv.md |
| scheduler-example.ino | patterns-scheduler.md |
| state-machine-example.ino | patterns-state-machine.md |
| hardware-detection-example.ino | patterns-hardware-detection.md |
| data-logging-example.ino | patterns-data-logging.md |
---
🛠️ Design Principles
All examples follow arduino-skills best practices:
✅ No `delay()` - All timing uses millis() for non-blocking execution ✅ F() macros - String literals stored in PROGMEM to save RAM ✅ Board-agnostic - Compile for UNO/ESP32/RP2040 without code changes ✅ Modular classes - Reusable components with clear interfaces ✅ Serial output - Verifiable behavior for testing/debugging ✅ Production-ready - Proper error handling and edge cases
---
📊 Testing & Verification
Each example includes:
- Serial output for runtime verification
- Compile-time board detection for hardware compatibility
- Non-blocking loops for responsive systems
- Documentation explaining pattern rationale
To verify an example compiles for all boards:
# Verify UNO compilation
arduino-cli compile --fqbn arduino:avr:uno config-example.ino
# Verify ESP32 compilation
arduino-cli compile --fqbn esp32:esp32:esp32 config-example.ino
# Verify RP2040 compilation
arduino-cli compile --fqbn rp2040:rp2040:rpipico config-example.inoAutomated verification scripts are also available:
# Windows PowerShell
../scripts/verify_patterns.ps1
# Linux/macOS
../scripts/verify_patterns.shPrerequisites (once per environment):
# Install cores before compiling
arduino-cli core install arduino:avr
arduino-cli core install esp32:esp32
arduino-cli core install rp2040:rp2040---
🤝 Contributing
To add a new example:
1. Follow the template structure (header comment, configuration, classes, setup, loop) 2. Ensure board compatibility (UNO/ESP32/RP2040 minimum) 3. Add documentation to this README 4. Create corresponding reference file in references/ 5. Test compilation on all target boards
---
📄 License
MIT License - See LICENSE for details
Generated by: arduino-code-generator v1.3.0 Last Updated: February 2026
/*
* Non-Blocking Task Scheduler Pattern Example
*
* Demonstrates cooperative multitasking without delay() using:
* - Lightweight task scheduler (millis() based)
* - Multiple independent tasks with different intervals
* - Task priority and execution tracking
* - No blocking delays - maintains responsiveness
*
* Generated by: arduino-code-generator
* Pattern: Non-Blocking Task Scheduler
* License: MIT
*/
// === Task Scheduler ===
class Task {
public:
using TaskFunction = void (*)();
private:
TaskFunction function;
unsigned long interval;
unsigned long lastRun;
bool enabled;
const char* name;
uint32_t executionCount;
public:
Task(TaskFunction func, unsigned long intervalMs, const char* taskName)
: function(func), interval(intervalMs), lastRun(0),
enabled(true), name(taskName), executionCount(0) {}
void update() {
if (!enabled) return;
unsigned long now = millis();
if (now - lastRun >= interval) {
lastRun = now;
function();
executionCount++;
}
}
void setEnabled(bool state) { enabled = state; }
void setInterval(unsigned long ms) { interval = ms; }
bool isEnabled() const { return enabled; }
const char* getName() const { return name; }
uint32_t getExecutionCount() const { return executionCount; }
unsigned long getInterval() const { return interval; }
};
// === Task Scheduler Manager ===
template<size_t MAX_TASKS>
class Scheduler {
private:
Task* tasks[MAX_TASKS];
size_t taskCount = 0;
public:
bool addTask(Task* task) {
if (taskCount >= MAX_TASKS) return false;
tasks[taskCount++] = task;
return true;
}
void run() {
for (size_t i = 0; i < taskCount; i++) {
tasks[i]->update();
}
}
void printStatus() {
Serial.println(F("\n=== Task Status ==="));
for (size_t i = 0; i < taskCount; i++) {
Serial.print(F("Task: "));
Serial.print(tasks[i]->getName());
Serial.print(F(" | Interval: "));
Serial.print(tasks[i]->getInterval());
Serial.print(F("ms | Executions: "));
Serial.print(tasks[i]->getExecutionCount());
Serial.print(F(" | Status: "));
Serial.println(tasks[i]->isEnabled() ? F("ENABLED") : F("DISABLED"));
}
Serial.println();
}
};
// === Task Functions ===
void taskFast() {
static uint16_t counter = 0;
Serial.print(F("[FAST] Execution #"));
Serial.println(++counter);
}
void taskMedium() {
Serial.print(F("[MEDIUM] Sensor reading: "));
Serial.println(analogRead(A0));
}
void taskSlow() {
Serial.print(F("[SLOW] Uptime: "));
Serial.print(millis() / 1000);
Serial.println(F(" seconds"));
}
void taskHeartbeat() {
static bool ledState = false;
ledState = !ledState;
digitalWrite(LED_BUILTIN, ledState);
Serial.println(ledState ? F("[LED] ON") : F("[LED] OFF"));
}
void taskReport() {
scheduler.printStatus();
}
// === Task Definitions ===
Task fastTask(taskFast, 500, "Fast Task");
Task mediumTask(taskMedium, 2000, "Medium Task");
Task slowTask(taskSlow, 5000, "Slow Task");
Task heartbeatTask(taskHeartbeat, 1000, "Heartbeat LED");
Task reportTask(taskReport, 15000, "Status Report");
// === Scheduler Instance ===
Scheduler<5> scheduler;
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
Serial.println(F("\n=== Non-Blocking Scheduler Example ==="));
Serial.println(F("Running 5 independent tasks:"));
Serial.println(F("- Fast: 500ms (counter)"));
Serial.println(F("- Medium: 2000ms (sensor read)"));
Serial.println(F("- Slow: 5000ms (uptime)"));
Serial.println(F("- Heartbeat: 1000ms (LED toggle)"));
Serial.println(F("- Report: 15000ms (status)\n"));
// Register tasks
scheduler.addTask(&fastTask);
scheduler.addTask(&mediumTask);
scheduler.addTask(&slowTask);
scheduler.addTask(&heartbeatTask);
scheduler.addTask(&reportTask);
Serial.println(F("Scheduler started...\n"));
}
void loop() {
scheduler.run();
// Main loop remains responsive for other operations
// (e.g., button checks, serial input, etc.)
}
/*
* Finite State Machine Pattern Example
*
* Demonstrates FSM implementation for traffic light control:
* - Explicit state enumeration
* - State transition logic
* - Timing-based state changes (non-blocking)
* - State-specific behavior and outputs
*
* Generated by: arduino-code-generator
* Pattern: State Machine
* License: MIT
*/
// === Configuration ===
#if defined(ESP32)
const uint8_t RED_LED = 25;
const uint8_t YELLOW_LED = 26;
const uint8_t GREEN_LED = 27;
#else
const uint8_t RED_LED = 11;
const uint8_t YELLOW_LED = 10;
const uint8_t GREEN_LED = 9;
#endif
// === State Machine Definition ===
enum TrafficLightState {
STATE_RED,
STATE_RED_YELLOW,
STATE_GREEN,
STATE_YELLOW
};
class TrafficLight {
private:
TrafficLightState currentState;
unsigned long stateStartTime;
// State durations (milliseconds)
static const unsigned long RED_DURATION = 5000;
static const unsigned long RED_YELLOW_DURATION = 2000;
static const unsigned long GREEN_DURATION = 5000;
static const unsigned long YELLOW_DURATION = 2000;
void setLEDs(bool red, bool yellow, bool green) {
digitalWrite(RED_LED, red ? HIGH : LOW);
digitalWrite(YELLOW_LED, yellow ? HIGH : LOW);
digitalWrite(GREEN_LED, green ? HIGH : LOW);
}
const char* getStateName(TrafficLightState state) {
switch (state) {
case STATE_RED: return "RED";
case STATE_RED_YELLOW: return "RED+YELLOW";
case STATE_GREEN: return "GREEN";
case STATE_YELLOW: return "YELLOW";
default: return "UNKNOWN";
}
}
void enterState(TrafficLightState newState) {
currentState = newState;
stateStartTime = millis();
Serial.print(F("State: "));
Serial.print(getStateName(newState));
Serial.print(F(" (duration: "));
// Set LEDs and print duration
switch (newState) {
case STATE_RED:
setLEDs(true, false, false);
Serial.print(RED_DURATION / 1000);
break;
case STATE_RED_YELLOW:
setLEDs(true, true, false);
Serial.print(RED_YELLOW_DURATION / 1000);
break;
case STATE_GREEN:
setLEDs(false, false, true);
Serial.print(GREEN_DURATION / 1000);
break;
case STATE_YELLOW:
setLEDs(false, true, false);
Serial.print(YELLOW_DURATION / 1000);
break;
}
Serial.println(F("s)"));
}
public:
TrafficLight() : currentState(STATE_RED), stateStartTime(0) {}
void begin() {
pinMode(RED_LED, OUTPUT);
pinMode(YELLOW_LED, OUTPUT);
pinMode(GREEN_LED, OUTPUT);
enterState(STATE_RED);
}
void update() {
unsigned long elapsed = millis() - stateStartTime;
// State transition logic
switch (currentState) {
case STATE_RED:
if (elapsed >= RED_DURATION) {
enterState(STATE_RED_YELLOW);
}
break;
case STATE_RED_YELLOW:
if (elapsed >= RED_YELLOW_DURATION) {
enterState(STATE_GREEN);
}
break;
case STATE_GREEN:
if (elapsed >= GREEN_DURATION) {
enterState(STATE_YELLOW);
}
break;
case STATE_YELLOW:
if (elapsed >= YELLOW_DURATION) {
enterState(STATE_RED);
}
break;
}
}
TrafficLightState getState() const {
return currentState;
}
};
// === Application ===
TrafficLight trafficLight;
void setup() {
Serial.begin(115200);
Serial.println(F("\n=== Traffic Light FSM Example ==="));
Serial.println(F("Cycle: RED -> RED+YELLOW -> GREEN -> YELLOW"));
Serial.println(F("Pin Configuration:"));
Serial.print(F(" RED LED: Pin "));
Serial.println(RED_LED);
Serial.print(F(" YELLOW LED: Pin "));
Serial.println(YELLOW_LED);
Serial.print(F(" GREEN LED: Pin "));
Serial.println(GREEN_LED);
Serial.println();
trafficLight.begin();
}
void loop() {
trafficLight.update();
// Main loop remains responsive for other operations
}
Button Debouncing & Input Handling
Purpose
- Provide robust button handling without blocking or false triggers.
- Support press, release, and long-press events.
When to Use
- Any sketch that reads mechanical buttons or switches.
- UI flows that need reliable event detection.
Basic Software Debouncing
class DebouncedButton {
private:
uint8_t pin;
uint8_t lastState;
unsigned long lastDebounceTime;
uint16_t debounceDelay;
public:
DebouncedButton(uint8_t buttonPin, uint16_t delay = 50)
: pin(buttonPin), lastState(HIGH), lastDebounceTime(0), debounceDelay(delay) {
pinMode(pin, INPUT_PULLUP);
}
bool isPressed() {
uint8_t reading = digitalRead(pin);
if (reading != lastState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading == LOW) {
lastState = reading;
return true;
}
}
lastState = reading;
return false;
}
};
// Usage
DebouncedButton button(2);
void loop() {
if (button.isPressed()) {
Serial.println(F("Button pressed!"));
}
}Edge Detection (Press/Release Events)
class ButtonWithEvents {
private:
uint8_t pin;
uint8_t currentState;
uint8_t previousState;
unsigned long lastDebounceTime;
uint16_t debounceDelay;
public:
enum Event { NONE, PRESSED, RELEASED };
ButtonWithEvents(uint8_t buttonPin, uint16_t delay = 50)
: pin(buttonPin), currentState(HIGH), previousState(HIGH),
lastDebounceTime(0), debounceDelay(delay) {
pinMode(pin, INPUT_PULLUP);
}
Event update() {
uint8_t reading = digitalRead(pin);
if (reading != previousState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != currentState) {
currentState = reading;
previousState = reading;
return (currentState == LOW) ? PRESSED : RELEASED;
}
}
previousState = reading;
return NONE;
}
};
// Usage
ButtonWithEvents button(2);
void loop() {
ButtonWithEvents::Event event = button.update();
if (event == ButtonWithEvents::PRESSED) {
Serial.println(F("↓ Button pressed"));
} else if (event == ButtonWithEvents::RELEASED) {
Serial.println(F("↑ Button released"));
}
}Long Press Detection
class ButtonWithLongPress {
private:
uint8_t pin;
uint8_t state;
unsigned long pressStartTime;
unsigned long longPressThreshold;
bool longPressTriggered;
public:
enum Event { NONE, SHORT_PRESS, LONG_PRESS, RELEASED };
ButtonWithLongPress(uint8_t buttonPin, unsigned long threshold = 1000)
: pin(buttonPin), state(HIGH), pressStartTime(0),
longPressThreshold(threshold), longPressTriggered(false) {
pinMode(pin, INPUT_PULLUP);
}
Event update() {
uint8_t reading = digitalRead(pin);
if (reading == LOW && state == HIGH) {
// Button just pressed
pressStartTime = millis();
longPressTriggered = false;
state = LOW;
}
else if (reading == LOW && state == LOW) {
// Button held down
if (!longPressTriggered && (millis() - pressStartTime) >= longPressThreshold) {
longPressTriggered = true;
return LONG_PRESS;
}
}
else if (reading == HIGH && state == LOW) {
// Button released
state = HIGH;
if (!longPressTriggered) {
return SHORT_PRESS;
}
return RELEASED;
}
return NONE;
}
};
// Usage
ButtonWithLongPress button(2, 2000); // 2 second long press
void loop() {
ButtonWithLongPress::Event event = button.update();
if (event == ButtonWithLongPress::SHORT_PRESS) {
Serial.println(F("Short press - Toggle LED"));
} else if (event == ButtonWithLongPress::LONG_PRESS) {
Serial.println(F("Long press - Reset system"));
}
}Multi-Button Manager
class MultiButtonManager {
private:
static const uint8_t MAX_BUTTONS = 8;
DebouncedButton* buttons[MAX_BUTTONS];
uint8_t buttonCount;
public:
MultiButtonManager() : buttonCount(0) {}
void addButton(DebouncedButton* btn) {
if (buttonCount < MAX_BUTTONS) {
buttons[buttonCount++] = btn;
}
}
int checkButtons() {
for (uint8_t i = 0; i < buttonCount; i++) {
if (buttons[i]->isPressed()) {
return i;
}
}
return -1;
}
};
// Usage
DebouncedButton btn1(2);
DebouncedButton btn2(3);
DebouncedButton btn3(4);
MultiButtonManager manager;
void setup() {
Serial.begin(115200);
manager.addButton(&btn1);
manager.addButton(&btn2);
manager.addButton(&btn3);
}
void loop() {
int pressed = manager.checkButtons();
if (pressed >= 0) {
Serial.print(F("Button "));
Serial.print(pressed);
Serial.println(F(" pressed"));
}
}Verification
- Open Serial Monitor and press/release the button; verify expected events.
- Hold the button past the long-press threshold to confirm long-press events.
Common Pitfalls & Tips
- Always debounce buttons (hardware bounce lasts 5–50 ms).
- Use INPUT_PULLUP to avoid external resistors.
- Never use delay() in button checking (blocks other code).
- Edge detection should report PRESS and RELEASE separately.
- Long press requires tracking press duration before release.
- For multi-button setups, scan all buttons each loop.
Config.h Hardware Abstraction Pattern
Purpose
- Centralize board-specific pins, features, and limits in one header.
- Enable a single codebase to compile across UNO/ESP32/RP2040.
When to Use
- Multi-board projects or reusable libraries.
- Any project that needs consistent pin and feature definitions.
Basic Template
// config.h
#ifndef CONFIG_H
#define CONFIG_H
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
#define BOARD_NAME "Arduino UNO"
#define LED_PIN 13
#define BUTTON_PIN 2
#define I2C_SDA A4
#define I2C_SCL A5
#define SRAM_SIZE 2048
#elif defined(ESP32)
#define BOARD_NAME "ESP32"
#define LED_PIN 2
#define BUTTON_PIN 4
#define I2C_SDA 21
#define I2C_SCL 22
#define SRAM_SIZE 520000
#define HAS_WIFI 1
#elif defined(ARDUINO_ARCH_RP2040)
#define BOARD_NAME "RP2040"
#define LED_PIN 25
#define BUTTON_PIN 14
#define I2C_SDA 4
#define I2C_SCL 5
#define SRAM_SIZE 264000
#else
#error "Unsupported board!"
#endif
// Common constants
#define SERIAL_BAUD 115200
#define DEBOUNCE_DELAY 50
#define SENSOR_READ_INTERVAL 1000
#endifUsage
#include "config.h"
void setup() {
Serial.begin(SERIAL_BAUD);
pinMode(LED_PIN, OUTPUT);
Wire.begin(I2C_SDA, I2C_SCL);
}Verification
- Compile for UNO, ESP32, and RP2040 to confirm board detection paths.
- Confirm Serial output and I2C initialization succeed on each target.
Common Pitfalls & Tips
- Use
#if defined()for board detection. - Define all pins in config.h, never hardcode in sketches.
- Include memory limits for adaptive code paths.
- Add feature flags (HAS_WIFI, HAS_BLE) for capabilities.
CSV Data Output Pattern
Purpose
- Emit structured, analysis-ready telemetry over Serial.
- Standardize headers and column formatting for tooling.
When to Use
- Logging sensor data for Excel/Python analysis.
- Any workflow that needs consistent, parseable output.
Basic CSV Logger
class CSVLogger {
private:
bool headerPrinted;
public:
CSVLogger() : headerPrinted(false) {}
void printHeader(const char* header) {
if (!headerPrinted) {
Serial.println(header);
headerPrinted = true;
}
}
void logData(float val1, float val2, int val3) {
Serial.print(val1, 2);
Serial.print(F(","));
Serial.print(val2, 2);
Serial.print(F(","));
Serial.println(val3);
}
};
// Usage
CSVLogger logger;
void setup() {
Serial.begin(115200);
logger.printHeader("Time_ms,Temp_C,Humidity_%,Light");
}
void loop() {
static unsigned long lastLog = 0;
if (millis() - lastLog >= 1000) {
logger.logData(25.5, 60.2, 512);
lastLog = millis();
}
}With Timestamp
void logWithTimestamp(float temp, float humid) {
Serial.print(millis());
Serial.print(F(","));
Serial.print(temp, 2);
Serial.print(F(","));
Serial.println(humid, 1);
}Verification
- Confirm the header prints once on boot.
- Capture a few lines and import into Excel or Python to validate parsing.
Common Pitfalls & Tips
- Use F() macro for strings on UNO (saves SRAM).
- Print the header once at startup.
- Use consistent decimal places (e.g., 2 for 25.50).
- Excel/Python can import CSV directly.
Data Logging & Persistence Patterns
Purpose
- Persist sensor data across reboots with EEPROM/SD/flash.
- Use buffering and validation to protect data integrity.
When to Use
- Long-running data collection or audit trails.
- Systems that must survive power loss or resets.
EEPROM Logging with CRC Validation
#include <EEPROM.h>
struct LogEntry {
uint32_t timestamp;
float temperature;
float humidity;
uint16_t crc;
};
class EEPROMLogger {
private:
uint16_t currentAddress;
const uint16_t maxAddress;
uint16_t calculateCRC(const LogEntry& entry) {
uint16_t crc = 0xFFFF;
const uint8_t* data = (const uint8_t*)&entry;
for (size_t i = 0; i < sizeof(entry) - sizeof(entry.crc); i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc = crc >> 1;
}
}
}
return crc;
}
public:
EEPROMLogger(uint16_t maxAddr = 1024)
: currentAddress(0), maxAddress(maxAddr) {}
void writeEntry(uint32_t timestamp, float temp, float humid) {
if (currentAddress + sizeof(LogEntry) > maxAddress) {
Serial.println(F("EEPROM full"));
return;
}
LogEntry entry;
entry.timestamp = timestamp;
entry.temperature = temp;
entry.humidity = humid;
entry.crc = calculateCRC(entry);
EEPROM.put(currentAddress, entry);
currentAddress += sizeof(LogEntry);
Serial.print(F("Logged to EEPROM at "));
Serial.println(currentAddress - sizeof(LogEntry));
}
bool readEntry(uint16_t index, LogEntry& entry) {
uint16_t addr = index * sizeof(LogEntry);
if (addr >= currentAddress) return false;
EEPROM.get(addr, entry);
uint16_t calculatedCRC = calculateCRC(entry);
if (calculatedCRC != entry.crc) {
Serial.println(F("CRC mismatch - corrupted data"));
return false;
}
return true;
}
void dumpAll() {
Serial.println(F("=== EEPROM Dump ==="));
uint16_t entryCount = currentAddress / sizeof(LogEntry);
for (uint16_t i = 0; i < entryCount; i++) {
LogEntry entry;
if (readEntry(i, entry)) {
Serial.print(entry.timestamp);
Serial.print(F(","));
Serial.print(entry.temperature, 2);
Serial.print(F(","));
Serial.println(entry.humidity, 2);
}
}
}
void clear() {
currentAddress = 0;
Serial.println(F("EEPROM cleared"));
}
};
EEPROMLogger logger;
void setup() {
Serial.begin(115200);
}
void loop() {
static EveryMs logTimer(60000); // Log every 60 seconds
if (logTimer.check()) {
float temp = readTemperature();
float humid = readHumidity();
logger.writeEntry(millis(), temp, humid);
}
if (Serial.available() && Serial.read() == 'd') {
logger.dumpAll();
}
}SD Card Buffered Logger
#include <SD.h>
class SDBufferedLogger {
private:
static const uint8_t BUFFER_SIZE = 10;
String buffer[BUFFER_SIZE];
uint8_t bufferIndex;
const char* filename;
bool sdReady;
public:
SDBufferedLogger(const char* file)
: bufferIndex(0), filename(file), sdReady(false) {}
bool begin(uint8_t csPin = 10) {
sdReady = SD.begin(csPin);
if (!sdReady) {
Serial.println(F("SD card init failed"));
return false;
}
// Write CSV header if file doesn't exist
if (!SD.exists(filename)) {
File dataFile = SD.open(filename, FILE_WRITE);
if (dataFile) {
dataFile.println(F("Timestamp,Temperature,Humidity,Light"));
dataFile.close();
}
}
Serial.println(F("SD card ready"));
return true;
}
void logData(uint32_t timestamp, float temp, float humid, int light) {
// Build CSV line
String line = String(timestamp) + "," +
String(temp, 2) + "," +
String(humid, 2) + "," +
String(light);
buffer[bufferIndex++] = line;
// Flush buffer when full
if (bufferIndex >= BUFFER_SIZE) {
flush();
}
}
void flush() {
if (!sdReady || bufferIndex == 0) return;
File dataFile = SD.open(filename, FILE_WRITE);
if (dataFile) {
for (uint8_t i = 0; i < bufferIndex; i++) {
dataFile.println(buffer[i]);
}
dataFile.close();
Serial.print(F("Flushed "));
Serial.print(bufferIndex);
Serial.println(F(" entries to SD"));
bufferIndex = 0;
} else {
Serial.println(F("Failed to open SD file"));
}
}
void dumpFile() {
if (!sdReady) return;
File dataFile = SD.open(filename, FILE_READ);
if (dataFile) {
Serial.println(F("=== SD Card Contents ==="));
while (dataFile.available()) {
Serial.write(dataFile.read());
}
dataFile.close();
}
}
};
SDBufferedLogger sdLogger("datalog.csv");
void setup() {
Serial.begin(115200);
sdLogger.begin(10); // CS pin 10
}
void loop() {
static EveryMs logTimer(5000); // Log every 5 seconds
if (logTimer.check()) {
float temp = readTemperature();
float humid = readHumidity();
int light = analogRead(A0);
sdLogger.logData(millis(), temp, humid, light);
}
// Manual flush command
if (Serial.available() && Serial.read() == 'f') {
sdLogger.flush();
}
}Wear Leveling for Flash Storage
#ifdef ESP32
#include <Preferences.h>
class WearLeveledStorage {
private:
Preferences prefs;
const char* namespaceName;
uint8_t currentSlot;
static const uint8_t MAX_SLOTS = 10;
public:
WearLeveledStorage(const char* ns) : namespaceName(ns), currentSlot(0) {}
bool begin() {
if (!prefs.begin(namespaceName, false)) {
Serial.println(F("Failed to init Preferences"));
return false;
}
// Load last used slot
currentSlot = prefs.getUChar("slot", 0);
return true;
}
void writeValue(const char* key, float value) {
// Rotate through slots to distribute writes
String slotKey = String(key) + String(currentSlot);
prefs.putFloat(slotKey.c_str(), value);
currentSlot = (currentSlot + 1) % MAX_SLOTS;
prefs.putUChar("slot", currentSlot);
}
float readValue(const char* key) {
// Read from current slot
uint8_t readSlot = (currentSlot == 0) ? MAX_SLOTS - 1 : currentSlot - 1;
String slotKey = String(key) + String(readSlot);
return prefs.getFloat(slotKey.c_str(), 0.0);
}
void clear() {
prefs.clear();
currentSlot = 0;
Serial.println(F("Storage cleared"));
}
};
WearLeveledStorage storage("myapp");
void setup() {
Serial.begin(115200);
storage.begin();
}
void loop() {
static EveryMs saveTimer(10000);
if (saveTimer.check()) {
float temp = readTemperature();
storage.writeValue("temp", temp);
Serial.print(F("Saved: "));
Serial.println(temp);
}
}
#endifCircular Buffer for In-Memory Logging
template <typename T, uint16_t SIZE>
class CircularBuffer {
private:
T buffer[SIZE];
uint16_t writeIndex;
uint16_t count;
public:
CircularBuffer() : writeIndex(0), count(0) {}
void push(const T& value) {
buffer[writeIndex] = value;
writeIndex = (writeIndex + 1) % SIZE;
if (count < SIZE) count++;
}
T get(uint16_t index) const {
if (index >= count) return T();
uint16_t actualIndex = (writeIndex - count + index + SIZE) % SIZE;
return buffer[actualIndex];
}
uint16_t size() const { return count; }
bool isFull() const { return count == SIZE; }
void clear() {
writeIndex = 0;
count = 0;
}
void dump() const {
for (uint16_t i = 0; i < count; i++) {
Serial.println(get(i));
}
}
};
CircularBuffer<float, 100> tempHistory;
void loop() {
static EveryMs sampleTimer(1000);
if (sampleTimer.check()) {
float temp = readTemperature();
tempHistory.push(temp);
if (tempHistory.isFull()) {
Serial.println(F("Buffer full - oldest data overwritten"));
}
}
if (Serial.available() && Serial.read() == 'h') {
tempHistory.dump();
}
}Verification
- Write a few entries, power cycle, and dump to confirm persistence.
- Trigger CRC mismatch by editing data and confirm it is detected.
Common Pitfalls & Tips
- EEPROM: use CRC validation to detect corrupted data.
- SD Card: buffer writes to reduce open/close operations (reduces wear).
- Wear leveling: rotate write locations to extend flash lifetime.
- Circular buffer: in-memory logging with automatic overflow handling.
- Always flush buffers before power loss or reset.
- EEPROM has limited write cycles (~100,000 writes per byte).
- F() macro stores strings in flash, not RAM.
Sensor Filtering & ADC Patterns
Purpose
- Reduce noise and spikes in analog sensor readings.
- Normalize readings for downstream control logic.
When to Use
- Noisy ADC signals or slow-changing sensors.
- Any pipeline that depends on stable sensor values.
Moving Average Filter
class MovingAverageFilter {
private:
float* buffer;
uint8_t size;
uint8_t index;
float sum;
public:
MovingAverageFilter(uint8_t windowSize) : size(windowSize), index(0), sum(0) {
buffer = new float[size];
for (uint8_t i = 0; i < size; i++) buffer[i] = 0;
}
~MovingAverageFilter() { delete[] buffer; }
float filter(float newValue) {
sum -= buffer[index];
buffer[index] = newValue;
sum += newValue;
index = (index + 1) % size;
return sum / size;
}
};
// Usage
MovingAverageFilter tempFilter(10);
void loop() {
float raw = analogRead(A0);
float filtered = tempFilter.filter(raw);
Serial.println(filtered);
delay(100);
}Median Filter (Noise Spike Removal)
class MedianFilter {
private:
float* buffer;
uint8_t size;
uint8_t index;
public:
MedianFilter(uint8_t windowSize) : size(windowSize), index(0) {
buffer = new float[size];
}
float filter(float newValue) {
buffer[index++] = newValue;
if (index >= size) index = 0;
float sorted[size];
memcpy(sorted, buffer, size * sizeof(float));
// Bubble sort
for (uint8_t i = 0; i < size - 1; i++) {
for (uint8_t j = 0; j < size - i - 1; j++) {
if (sorted[j] > sorted[j + 1]) {
float temp = sorted[j];
sorted[j] = sorted[j + 1];
sorted[j + 1] = temp;
}
}
}
return sorted[size / 2];
}
};DHT22 Non-Blocking Reader
#include <DHT.h>
#include "config.h"
#define DHT_PIN 2
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);
MovingAverageFilter tempFilter(5);
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
static unsigned long lastRead = 0;
if (millis() - lastRead >= 2000) { // DHT22 needs 2s between reads
float temp = dht.readTemperature();
if (!isnan(temp)) {
float filtered = tempFilter.filter(temp);
Serial.print(F("Temp: "));
Serial.print(filtered, 1);
Serial.println(F(" °C"));
}
lastRead = millis();
}
}Calibration Pattern
struct SensorCalibration {
float offset;
float gain;
};
float applyCalibration(float rawValue, SensorCalibration cal) {
return (rawValue * cal.gain) + cal.offset;
}
// Example usage
SensorCalibration tempCal = {-0.5, 1.02}; // -0.5°C offset, 2% gain correction
float calibrated = applyCalibration(rawTemp, tempCal);Validation Pattern
bool validateSensorReading(float value, float min, float max) {
if (isnan(value) || isinf(value)) {
Serial.println(F("❌ Invalid reading (NaN/Inf)"));
return false;
}
if (value < min || value > max) {
Serial.print(F("⚠️ Out of range: "));
Serial.println(value);
return false;
}
return true;
}
// Usage
float temp = dht.readTemperature();
if (validateSensorReading(temp, -40, 80)) {
// Use validated temperature
}Verification
- Log raw vs filtered values and confirm noise reduction.
- Inject a spike and verify median filter rejects it.
Common Pitfalls & Tips
- Always use filters for ADC readings (analog sensors are noisy).
- Moving average smooths readings, best for slow-changing sensors.
- Median filter removes spikes, best for occasional glitches.
- DHT22 requires 2000 ms between reads (hardware limitation).
- Always validate readings (NaN/range checks).
- Prefer fixed-size buffers over heap allocations on small MCUs.
- Calibration: measure known values, calculate offset and gain.
Hardware Detection & Adaptive Configuration
Purpose
- Detect board capabilities and adapt configuration safely.
- Prevent memory and feature mismatches across targets.
When to Use
- Multi-board deployments or libraries.
- Systems that scale features based on memory or peripherals.
Board Detection Pattern
// config.h - Auto-detect board and set defaults
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
#define BOARD_TYPE "Arduino UNO/Nano"
#define MAX_SRAM 2048
#define HAS_WIFI false
#define SERIAL_BAUD 9600
#elif defined(ARDUINO_ESP32_DEV) || defined(ESP32)
#define BOARD_TYPE "ESP32"
#define MAX_SRAM 327680
#define HAS_WIFI true
#define SERIAL_BAUD 115200
#elif defined(ARDUINO_ARCH_RP2040)
#define BOARD_TYPE "Raspberry Pi Pico"
#define MAX_SRAM 262144
#define HAS_WIFI false
#define SERIAL_BAUD 115200
#else
#define BOARD_TYPE "Unknown Board"
#define MAX_SRAM 2048
#define HAS_WIFI false
#define SERIAL_BAUD 9600
#endif
// Runtime board info
struct BoardInfo {
static const char* getBoardType() { return BOARD_TYPE; }
static uint32_t getMaxSRAM() { return MAX_SRAM; }
static bool hasWiFi() { return HAS_WIFI; }
static uint32_t getSerialBaud() { return SERIAL_BAUD; }
};
void printBoardInfo() {
Serial.println(F("=== Board Information ==="));
Serial.print(F("Board: "));
Serial.println(BoardInfo::getBoardType());
Serial.print(F("Max SRAM: "));
Serial.print(BoardInfo::getMaxSRAM());
Serial.println(F(" bytes"));
Serial.print(F("WiFi: "));
Serial.println(BoardInfo::hasWiFi() ? F("Yes") : F("No"));
}Memory Monitoring
#ifdef __AVR__
#include <AvailableMemory.h>
int getFreeMemory() {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
#endif
class MemoryMonitor {
private:
int minFreeMemory;
unsigned long checkInterval;
unsigned long lastCheck;
public:
MemoryMonitor(unsigned long intervalMs = 1000)
: minFreeMemory(999999), checkInterval(intervalMs), lastCheck(0) {}
void update() {
unsigned long now = millis();
if (now - lastCheck >= checkInterval) {
#ifdef __AVR__
int freeMem = getFreeMemory();
if (freeMem < minFreeMemory) {
minFreeMemory = freeMem;
}
if (freeMem < 200) {
Serial.println(F("WARNING: Low memory!"));
}
#endif
lastCheck = now;
}
}
void printStats() {
#ifdef __AVR__
Serial.print(F("Free SRAM: "));
Serial.print(getFreeMemory());
Serial.print(F(" bytes (min: "));
Serial.print(minFreeMemory);
Serial.println(F(")"));
#else
Serial.println(F("Memory monitoring not available"));
#endif
}
};
MemoryMonitor memMonitor;
void setup() {
Serial.begin(SERIAL_BAUD);
printBoardInfo();
}
void loop() {
memMonitor.update();
// Your code here
if (Serial.available() && Serial.read() == 'm') {
memMonitor.printStats();
}
}Adaptive Buffer Sizing
// Adjust buffer size based on available SRAM
#if defined(ARDUINO_AVR_UNO)
#define BUFFER_SIZE 64 // UNO has limited RAM
#define MAX_SAMPLES 50
#elif defined(ESP32)
#define BUFFER_SIZE 1024 // ESP32 has plenty of RAM
#define MAX_SAMPLES 1000
#elif defined(ARDUINO_ARCH_RP2040)
#define BUFFER_SIZE 512
#define MAX_SAMPLES 500
#else
#define BUFFER_SIZE 64 // Safe default for unknown boards
#define MAX_SAMPLES 50
#endif
class AdaptiveLogger {
private:
float samples[MAX_SAMPLES];
uint16_t sampleCount;
public:
AdaptiveLogger() : sampleCount(0) {}
void addSample(float value) {
if (sampleCount < MAX_SAMPLES) {
samples[sampleCount++] = value;
} else {
Serial.println(F("Buffer full"));
}
}
float getAverage() const {
if (sampleCount == 0) return 0.0;
float sum = 0.0;
for (uint16_t i = 0; i < sampleCount; i++) {
sum += samples[i];
}
return sum / sampleCount;
}
uint16_t getMaxCapacity() const { return MAX_SAMPLES; }
uint16_t getCurrentCount() const { return sampleCount; }
};Feature Detection
class FeatureDetection {
public:
static bool hasSPIFFS() {
#ifdef ESP32
return true;
#else
return false;
#endif
}
static bool hasEEPROM() {
#if defined(ARDUINO_AVR_UNO) || defined(ESP32)
return true;
#else
return false;
#endif
}
static bool hasAnalogRead() {
// All Arduino boards have ADC
return true;
}
static bool hasDAC() {
#ifdef ESP32
return true; // ESP32 has 2 DAC channels
#else
return false;
#endif
}
static uint8_t getAnalogResolution() {
#ifdef ESP32
return 12; // 12-bit ADC
#elif defined(ARDUINO_ARCH_RP2040)
return 12; // 12-bit ADC
#else
return 10; // 10-bit ADC (UNO, Nano)
#endif
}
};
void setup() {
Serial.begin(SERIAL_BAUD);
Serial.println(F("=== Feature Detection ==="));
Serial.print(F("SPIFFS: "));
Serial.println(FeatureDetection::hasSPIFFS() ? F("Yes") : F("No"));
Serial.print(F("EEPROM: "));
Serial.println(FeatureDetection::hasEEPROM() ? F("Yes") : F("No"));
Serial.print(F("DAC: "));
Serial.println(FeatureDetection::hasDAC() ? F("Yes") : F("No"));
Serial.print(F("ADC Resolution: "));
Serial.print(FeatureDetection::getAnalogResolution());
Serial.println(F(" bits"));
}Verification
- Compile for UNO, ESP32, and RP2040 to confirm board paths.
- Trigger memory monitor output on AVR to confirm warnings.
Common Pitfalls & Tips
- Use preprocessor directives (#if defined) for compile-time detection.
- Create a BoardInfo static class for runtime queries.
- Monitor SRAM on AVR boards (UNO has only 2KB).
- Adaptive buffer sizing prevents out-of-memory crashes.
- Feature detection allows graceful degradation.
- Print board info in setup() for debugging.
- Use F() macro to store strings in flash (saves SRAM).
I2C Communication Patterns
Purpose
- Discover and communicate with I2C peripherals reliably.
- Standardize register read/write helpers.
When to Use
- Any project using I2C sensors, displays, or RTC modules.
- Diagnostics for wiring or address conflicts.
I2C Scanner
#include <Wire.h>
void scanI2C() {
Serial.println(F("\n=== I2C Scanner ==="));
uint8_t found = 0;
for (uint8_t addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
uint8_t error = Wire.endTransmission();
if (error == 0) {
Serial.print(F("Device found at 0x"));
if (addr < 16) Serial.print(F("0"));
Serial.println(addr, HEX);
found++;
}
}
Serial.print(F("Total devices: "));
Serial.println(found);
}
void setup() {
Serial.begin(115200);
Wire.begin();
scanI2C();
}Read Register Pattern
uint8_t readRegister(uint8_t addr, uint8_t reg) {
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(addr, (uint8_t)1);
return Wire.read();
}
// Usage
uint8_t chipID = readRegister(0x76, 0xD0); // BME280 chip IDWrite Register Pattern
void writeRegister(uint8_t addr, uint8_t reg, uint8_t value) {
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.write(value);
Wire.endTransmission();
}Device Detection
bool checkI2CDevice(uint8_t addr) {
Wire.beginTransmission(addr);
return (Wire.endTransmission() == 0);
}
// Usage
if (checkI2CDevice(0x76)) {
Serial.println(F("BME280 detected!"));
}Verification
- Run the scanner and confirm expected device addresses appear.
- Read a known register (e.g., BME280 ID) to validate wiring.
Common Pitfalls & Tips
- Ensure correct SDA/SCL pins for the target board.
- Use pull-up resistors if the module does not include them.
- I2C addresses are 7-bit (0x08–0x77 in scans).
Non-Blocking Scheduler & Timing Patterns
Purpose
- Replace blocking delays with cooperative scheduling.
- Coordinate multiple time-based tasks in a single loop.
When to Use
- Any sketch with multiple timed actions or sensor polls.
- Systems that must remain responsive while doing periodic work.
EveryMs Pattern (Core Building Block)
class EveryMs {
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {}
bool check() {
unsigned long now = millis();
if (now - lastTrigger >= interval) {
lastTrigger = now;
return true;
}
return false;
}
void reset() {
lastTrigger = millis();
}
};
// Usage: Blink LED every 1000ms, read sensor every 500ms
EveryMs blinkTimer(1000);
EveryMs sensorTimer(500);
void loop() {
if (blinkTimer.check()) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
if (sensorTimer.check()) {
int value = analogRead(A0);
Serial.println(value);
}
}Priority Task Scheduler
class Task {
public:
typedef void (*TaskFunction)();
enum Priority { LOW, NORMAL, HIGH, CRITICAL };
private:
TaskFunction callback;
unsigned long interval;
unsigned long lastRun;
Priority priority;
bool enabled;
public:
Task(TaskFunction func, unsigned long ms, Priority pri = NORMAL)
: callback(func), interval(ms), lastRun(0), priority(pri), enabled(true) {}
bool shouldRun() const {
if (!enabled) return false;
return (millis() - lastRun >= interval);
}
void execute() {
if (callback) callback();
lastRun = millis();
}
Priority getPriority() const { return priority; }
void enable() { enabled = true; }
void disable() { enabled = false; }
};
class Scheduler {
private:
static const uint8_t MAX_TASKS = 10;
Task* tasks[MAX_TASKS];
uint8_t taskCount;
public:
Scheduler() : taskCount(0) {}
bool addTask(Task* task) {
if (taskCount >= MAX_TASKS) return false;
tasks[taskCount++] = task;
return true;
}
void run() {
Task* readyTask = nullptr;
Task::Priority highestPriority = Task::LOW;
// Find highest priority task that's ready
for (uint8_t i = 0; i < taskCount; i++) {
if (tasks[i]->shouldRun()) {
if (tasks[i]->getPriority() > highestPriority || readyTask == nullptr) {
readyTask = tasks[i];
highestPriority = tasks[i]->getPriority();
}
}
}
if (readyTask) {
readyTask->execute();
}
}
};
// Task functions
void taskBlinkLED() {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
void taskReadSensor() {
int value = analogRead(A0);
Serial.println(value);
}
// Global scheduler
Scheduler scheduler;
Task ledTask(taskBlinkLED, 1000, Task::NORMAL);
Task sensorTask(taskReadSensor, 500, Task::HIGH);
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
scheduler.addTask(&ledTask);
scheduler.addTask(&sensorTask);
}
void loop() {
scheduler.run();
}Multi-Task Environmental Monitor
#include <DHT.h>
DHT dht(2, DHT22);
EveryMs readDHTTimer(2000); // DHT22 needs 2s between reads
EveryMs readLightTimer(1000);
EveryMs displayTimer(5000);
EveryMs csvLogTimer(10000);
struct SensorData {
float temperature;
float humidity;
int lightLevel;
} data;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
pinMode(A0, INPUT);
dht.begin();
Serial.println(F("Time_ms,Temp_C,Humidity_%,Light"));
}
void loop() {
// Task 1: Read DHT22
if (readDHTTimer.check()) {
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
}
// Task 2: Read light sensor
if (readLightTimer.check()) {
data.lightLevel = analogRead(A0);
}
// Task 3: Display summary
if (displayTimer.check()) {
Serial.print(F("Temp: "));
Serial.print(data.temperature, 1);
Serial.print(F("°C | Humidity: "));
Serial.print(data.humidity, 1);
Serial.println(F("%"));
}
// Task 4: Log CSV
if (csvLogTimer.check()) {
Serial.print(millis());
Serial.print(F(","));
Serial.print(data.temperature, 1);
Serial.print(F(","));
Serial.print(data.humidity, 1);
Serial.print(F(","));
Serial.println(data.lightLevel);
}
// LED heartbeat
digitalWrite(LED_PIN, (millis() % 2000) < 100);
}Verification
- Confirm tasks fire at the expected intervals without blocking.
- Leave running past 49 days in simulation to verify rollover behavior.
Common Pitfalls & Tips
- NEVER use delay() for timing (blocks everything).
- Use unsigned long for millis() (handles overflow correctly).
- EveryMs pattern is the simplest non-blocking timer.
- Scheduler can run multiple tasks with priorities.
- Each task tracks its own last execution time.
- Unsigned arithmetic handles millis() overflow (every 49 days).
State Machine Patterns
Purpose
- Model complex behavior with explicit, testable states.
- Avoid nested conditionals and timing bugs.
When to Use
- Multi-step processes (traffic lights, robots, mode controllers).
- Systems with clear operational modes and transitions.
Enum-Based FSM (Recommended)
enum TrafficLightState {
RED,
RED_YELLOW,
GREEN,
YELLOW
};
TrafficLightState currentState = RED;
unsigned long stateStartTime = 0;
void setup() {
pinMode(RED_PIN, OUTPUT);
pinMode(YELLOW_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
stateStartTime = millis();
}
void loop() {
unsigned long elapsed = millis() - stateStartTime;
switch (currentState) {
case RED:
digitalWrite(RED_PIN, HIGH);
digitalWrite(YELLOW_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
if (elapsed >= 5000) {
currentState = RED_YELLOW;
stateStartTime = millis();
}
break;
case RED_YELLOW:
digitalWrite(RED_PIN, HIGH);
digitalWrite(YELLOW_PIN, HIGH);
digitalWrite(GREEN_PIN, LOW);
if (elapsed >= 2000) {
currentState = GREEN;
stateStartTime = millis();
}
break;
case GREEN:
digitalWrite(RED_PIN, LOW);
digitalWrite(YELLOW_PIN, LOW);
digitalWrite(GREEN_PIN, HIGH);
if (elapsed >= 5000) {
currentState = YELLOW;
stateStartTime = millis();
}
break;
case YELLOW:
digitalWrite(RED_PIN, LOW);
digitalWrite(YELLOW_PIN, HIGH);
digitalWrite(GREEN_PIN, LOW);
if (elapsed >= 2000) {
currentState = RED;
stateStartTime = millis();
}
break;
}
}Robot Controller FSM
enum RobotState {
IDLE,
MOVING_FORWARD,
TURNING_LEFT,
TURNING_RIGHT,
OBSTACLE_DETECTED,
EMERGENCY_STOP
};
RobotState state = IDLE;
unsigned long stateStartTime = 0;
struct RobotContext {
int distanceSensor;
bool buttonPressed;
int batteryLevel;
} context;
void updateState(RobotState newState) {
state = newState;
stateStartTime = millis();
Serial.print(F("State changed to: "));
Serial.println(newState);
}
void loop() {
// Update sensor context
context.distanceSensor = analogRead(A0);
context.buttonPressed = digitalRead(BUTTON_PIN) == LOW;
context.batteryLevel = analogRead(BATTERY_PIN);
unsigned long elapsed = millis() - stateStartTime;
// Emergency stop has highest priority
if (context.batteryLevel < 500) {
if (state != EMERGENCY_STOP) {
updateState(EMERGENCY_STOP);
}
}
switch (state) {
case IDLE:
stopMotors();
if (context.buttonPressed) {
updateState(MOVING_FORWARD);
}
break;
case MOVING_FORWARD:
setMotors(255, 255);
if (context.distanceSensor < 200) {
updateState(OBSTACLE_DETECTED);
} else if (context.buttonPressed) {
updateState(IDLE);
}
break;
case OBSTACLE_DETECTED:
stopMotors();
if (elapsed >= 500) {
// Decide turn direction based on obstacle position
updateState(TURNING_RIGHT);
}
break;
case TURNING_RIGHT:
setMotors(255, -255);
if (elapsed >= 1000) {
updateState(MOVING_FORWARD);
}
break;
case EMERGENCY_STOP:
stopMotors();
digitalWrite(LED_PIN, (millis() % 500) < 250); // Blink LED
if (context.batteryLevel > 550) {
updateState(IDLE);
}
break;
}
}
void setMotors(int left, int right) {
// Motor driver code here
}
void stopMotors() {
setMotors(0, 0);
}Button-Triggered FSM
enum SystemMode {
OFF,
HEATING,
COOLING,
AUTO
};
SystemMode mode = OFF;
DebouncedButton modeButton(2);
void setup() {
Serial.begin(115200);
pinMode(HEATER_PIN, OUTPUT);
pinMode(COOLER_PIN, OUTPUT);
}
void loop() {
modeButton.update();
// Button cycles through modes
if (modeButton.pressed()) {
switch (mode) {
case OFF: mode = HEATING; break;
case HEATING: mode = COOLING; break;
case COOLING: mode = AUTO; break;
case AUTO: mode = OFF; break;
}
Serial.print(F("Mode: "));
Serial.println(mode);
}
// Execute mode behavior
float temperature = readTemperature();
switch (mode) {
case OFF:
digitalWrite(HEATER_PIN, LOW);
digitalWrite(COOLER_PIN, LOW);
break;
case HEATING:
digitalWrite(HEATER_PIN, HIGH);
digitalWrite(COOLER_PIN, LOW);
break;
case COOLING:
digitalWrite(HEATER_PIN, LOW);
digitalWrite(COOLER_PIN, HIGH);
break;
case AUTO:
if (temperature < 20.0) {
digitalWrite(HEATER_PIN, HIGH);
digitalWrite(COOLER_PIN, LOW);
} else if (temperature > 25.0) {
digitalWrite(HEATER_PIN, LOW);
digitalWrite(COOLER_PIN, HIGH);
} else {
digitalWrite(HEATER_PIN, LOW);
digitalWrite(COOLER_PIN, LOW);
}
break;
}
}Verification
- Print current state changes and verify transitions follow the diagram.
- Simulate inputs and confirm no invalid transitions occur.
Common Pitfalls & Tips
- Use enum for state definitions (readable, type-safe).
- Track stateStartTime for elapsed time checks.
- Each state handles its own transitions.
- Emergency states should be checked BEFORE normal state logic.
- Use updateState() helper to centralize state changes.
- Combine with EveryMs timers for periodic state updates.
- States should be mutually exclusive (only one active at a time).
Arduino Code Generator — Reference Structure
Each reference file follows the same structured layout to make patterns easy to scan and verify:
1. Purpose — What the pattern achieves. 2. When to Use — Scenarios where the pattern fits best. 3. Implementation — Code templates and usage (often labeled “Basic Template” and “Usage”). 4. Verification — Steps to validate behavior. 5. Common Pitfalls & Tips — Frequent mistakes and best practices.
Keep pattern names consistent with generate_snippet.py and link to examples in examples/.
Board-Specific Optimization
Optimize Arduino code for specific board capabilities and constraints to maximize performance and reliability.
Arduino UNO (ATmega328P)
Memory: 2KB SRAM, 32KB Flash, 1KB EEPROM Architecture: 8-bit AVR, 16MHz clock Features: Basic I/O, limited peripherals
Memory Optimization
- Use
F()macro for all string literals to store in flash memory - Minimize global variables and buffers
- Prefer
char[]arrays overStringobjects - Use
PROGMEMfor read-only data tables
// UNO-optimized string handling
Serial.println(F("Initializing sensor..."));
const char message[] PROGMEM = "Error: Sensor not found";
// Avoid String class
char buffer[32];
strncpy(buffer, "Sensor data", sizeof(buffer));Timing Considerations
millis()resolution: ~16ms (due to 16MHz/1024 prescaler)- Avoid microsecond-precision timing
- Use
delayMicroseconds()sparingly for short delays
Peripheral Limitations
- Limited interrupt pins (2 external interrupts)
- No hardware I2C/SPI buffering
- Basic ADC (10-bit, ~100μs conversion time)
- No native USB (uses serial-to-USB converter)
ESP32 (ESP32-WROOM-32)
Memory: 520KB SRAM, 4MB+ Flash, 4KB EEPROM emulation Architecture: 32-bit Xtensa LX6, dual-core, 240MHz Features: WiFi, Bluetooth, extensive peripherals
Advanced Features
- FreeRTOS multitasking - use tasks for concurrent operations
- WiFi connectivity - implement IoT patterns
- Bluetooth/BLE - wireless communication
- Dual-core processing - distribute workloads
// ESP32-specific patterns
#include <WiFi.h>
#include <esp_task_wdt.h>
// Use FreeRTOS tasks
TaskHandle_t sensorTask;
void sensorTaskFunction(void *parameter) {
for (;;) {
readSensors();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}Memory Management
- Larger buffers acceptable (up to KB range)
- Use PSRAM if available for large data structures
- Implement proper task stack sizing
- Monitor heap usage with
ESP.getFreeHeap()
Power Management
- Deep sleep modes for battery-powered applications
- Dynamic frequency scaling
- Peripheral power gating
RP2040 (Raspberry Pi Pico)
Memory: 264KB SRAM, 2MB Flash Architecture: Dual-core ARM Cortex-M0+, 133MHz Features: PIO, USB host, extensive I/O
PIO (Programmable I/O)
- Custom protocols for timing-critical applications
- Precise timing without CPU intervention
- Parallel interfaces for displays and sensors
// RP2040 PIO example (conceptual)
#include <hardware/pio.h>
// PIO for precise timing
PIO pio = pio0;
uint offset = pio_add_program(pio, &timing_program);
pio_sm_config config = timing_program_get_default_config(offset);Multicore Features
- Dual-core processing with
Core1for dedicated tasks - Inter-core communication via FIFO or shared memory
- Load balancing for compute-intensive operations
// RP2040 multicore
#include <pico/multicore.h>
void core1_entry() {
while (true) {
// Dedicated processing on core 1
processData();
}
}
void setup() {
multicore_launch_core1(core1_entry);
}USB Capabilities
- USB host mode for connecting peripherals
- High-speed data transfer
- Device emulation for custom interfaces
Performance Optimization
- 133MHz clock speed for faster processing
- Large SRAM allows more complex algorithms
- Hardware floating point for mathematical operations
Cross-Board Compatibility
Conditional Compilation
Use preprocessor directives for board-specific code:
#if defined(ARDUINO_AVR_UNO)
// UNO-specific code
Serial.println(F("UNO detected"));
#elif defined(ESP32)
// ESP32-specific code
WiFi.begin(ssid, password);
#elif defined(ARDUINO_ARCH_RP2040)
// RP2040-specific code
multicore_launch_core1(taskFunction);
#endifRuntime Detection
Implement runtime board detection for adaptive behavior:
// Board detection patterns
bool isESP32() {
#ifdef ESP32
return true;
#else
return false;
#endif
}
void adaptiveConfiguration() {
if (isESP32()) {
// ESP32 configuration
enableWiFi();
} else {
// UNO/RP2040 configuration
useSerialOnly();
}
}Performance Benchmarks
Memory Usage Guidelines
- UNO: Keep RAM usage under 1.5KB for stability
- ESP32: Up to 300KB RAM acceptable for most applications
- RP2040: Up to 200KB RAM available for user applications
Timing Accuracy
- UNO: ±16ms for millis() timing
- ESP32: ±1ms with hardware timers
- RP2040: ±1μs with PIO for precise timing
Power Consumption
- UNO: ~50mA active, ~20mA sleep
- ESP32: 80-240mA active, <1mA deep sleep
- RP2040: ~25mA active, ~1mA sleep
Common Pitfalls to Avoid
Critical mistakes that can cause Arduino projects to fail, crash, or behave unexpectedly.
❌ Timing Pitfalls
Never use delay() for timing
Problem: delay() blocks all execution, preventing other tasks from running.
// WRONG - blocks everything
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(1000); // Nothing else can happen here
digitalWrite(LED_PIN, LOW);
delay(1000);
}// CORRECT - non-blocking timing
unsigned long lastBlink = 0;
const unsigned long BLINK_INTERVAL = 1000;
void loop() {
unsigned long currentTime = millis();
if (currentTime - lastBlink >= BLINK_INTERVAL) {
digitalToggle(LED_PIN);
lastBlink = currentTime;
}
// Other code can run here
}Never use signed types for millis() comparisons
Problem: int overflows at 32,767ms (~32 seconds), causing timing failures.
// WRONG - will fail after 32 seconds
int startTime = millis();
if (millis() - startTime > 5000) { ... }
// CORRECT - use unsigned long
unsigned long startTime = millis();
if (millis() - startTime > 5000) { ... }Never ignore millis() overflow
Problem: millis() wraps to 0 every ~49 days, breaking timing calculations.
// WRONG - fails when millis() overflows
if (millis() > previousTime + INTERVAL) { ... }
// CORRECT - handles overflow properly
if (millis() - previousTime >= INTERVAL) { ... }❌ Hardware Initialization Pitfalls
Never forget to call begin() on peripherals
Problem: Sensors and communication modules won't work without initialization.
// WRONG - sensor won't respond
DHT dht(DHT_PIN, DHT_TYPE);
// Missing: dht.begin();
// CORRECT
DHT dht(DHT_PIN, DHT_TYPE);
dht.begin(); // Required initializationNever assume hardware is present without checking
Problem: Code crashes or hangs when hardware is missing.
// WRONG - assumes sensor is connected
float temp = dht.readTemperature();
if (isnan(temp)) {
// Handle error
}
// BETTER - check hardware availability
if (dht.begin()) {
float temp = dht.readTemperature();
// Use temperature
} else {
Serial.println(F("DHT sensor not found"));
}❌ Memory Management Pitfalls
Never use String class on memory-constrained boards
Problem: String concatenation causes heap fragmentation on UNO.
// WRONG on UNO - causes memory issues
String message = "Temperature: ";
message += String(temp);
Serial.println(message);
// CORRECT - use char arrays
char buffer[32];
snprintf(buffer, sizeof(buffer), "Temperature: %.1f", temp);
Serial.println(buffer);Never forget F() macro for strings on UNO
Problem: String literals consume precious RAM instead of flash.
// WRONG - uses 20 bytes of RAM
Serial.println("Initializing sensor...");
// CORRECT - stores in flash memory
Serial.println(F("Initializing sensor..."));❌ Input Handling Pitfalls
Never mix polling and interrupt-based input
Problem: Inconsistent behavior and missed events.
// WRONG - mixing approaches
volatile bool buttonPressed = false;
void buttonISR() {
buttonPressed = true;
}
void loop() {
if (digitalRead(BUTTON_PIN) == LOW) { // Polling
// Handle press
}
if (buttonPressed) { // Interrupt flag
// Handle press again?
}
}
// CORRECT - choose one approach
// Either pure polling with debouncing, or pure interrupt-drivenNever debounce buttons incorrectly
Problem: False triggers from contact bounce.
// WRONG - no debouncing
if (digitalRead(BUTTON_PIN) == LOW) {
// Button press detected (but might be bounce)
}
// CORRECT - software debouncing
unsigned long lastDebounceTime = 0;
const unsigned long DEBOUNCE_DELAY = 50;
bool lastButtonState = HIGH;
bool buttonState;
void loop() {
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) {
// Valid button press
}
}
}
lastButtonState = reading;
}❌ Communication Pitfalls
Never ignore I2C/SPI initialization failures
Problem: Silent failures lead to debugging nightmares.
// WRONG - no error checking
Wire.begin();
Wire.beginTransmission(0x68);
Wire.write(0x00);
Wire.endTransmission();
// CORRECT - check for errors
Wire.begin();
Wire.beginTransmission(0x68);
byte error = Wire.endTransmission();
if (error == 0) {
// Success - device found
} else {
Serial.print(F("I2C error: "));
Serial.println(error);
}Never use blocking Serial operations in time-critical code
Problem: Serial.print() can block for milliseconds.
// WRONG in timing-critical sections
void loop() {
unsigned long start = micros();
// Time-critical operation
Serial.println(micros() - start); // Blocks execution
}
// CORRECT - buffer or use non-blocking approaches
char buffer[32];
snprintf(buffer, sizeof(buffer), "Time: %lu", micros() - start);
Serial.println(buffer);❌ Data Handling Pitfalls
Never access arrays without bounds checking
Problem: Buffer overflows corrupt memory.
// WRONG - potential buffer overflow
char buffer[10];
for (int i = 0; i < 20; i++) {
buffer[i] = data[i]; // Overflows buffer
}
// CORRECT - bounds checking
char buffer[10];
int copyLength = min(sizeof(buffer) - 1, dataLength);
memcpy(buffer, data, copyLength);
buffer[copyLength] = '\0';Never use floating point in interrupt service routines
Problem: Floating point operations are not reentrant and slow.
// WRONG in ISR
volatile float temperature;
void sensorISR() {
temperature = readSensor(); // Floating point in ISR
}
// CORRECT - use integer math in ISRs
volatile int32_t temperatureRaw;
void sensorISR() {
temperatureRaw = analogRead(SENSOR_PIN);
}
// Convert to float in main code✅ Best Practices Summary
- Always use millis() for non-blocking timing
- Always check hardware initialization return values
- Always use F() macro for strings on UNO
- Always debounce buttons properly
- Always check array bounds before access
- Always handle millis() overflow in timing calculations
- Never use delay() in production code
- Never mix input handling strategies
- Never ignore error conditions
- Never use String class on memory-constrained boards
Quality Standards
All generated Arduino code must adhere to these quality standards for production readiness.
Compilation Requirements
- ✅ Compile without warnings on Arduino IDE 1.8.x and 2.x
- ✅ No deprecated function usage (avoid old Arduino APIs)
- ✅ Proper include guards for custom headers
- ✅ Correct preprocessor directives (#ifdef, #define, etc.)
Timing and Concurrency
- ✅ Use unsigned long for millis() timing - never signed types
- ✅ Handle overflow conditions - millis() wraps every ~49 days
- ✅ Never use delay() for timing-critical operations
- ✅ Implement proper timing comparisons with overflow protection
// Correct timing implementation
unsigned long previousTime = 0;
const unsigned long INTERVAL = 1000;
void loop() {
unsigned long currentTime = millis();
if (currentTime - previousTime >= INTERVAL) {
// Execute timed action
previousTime = currentTime;
}
}Memory Safety
- ✅ Include bounds checking for all array operations
- ✅ Use const for read-only data to prevent accidental modification
- ✅ Define magic numbers as named constants
- ✅ Avoid buffer overflows through proper sizing
- ✅ Use F() macro for strings on memory-constrained boards
// Memory-safe implementations
#define BUFFER_SIZE 64
char buffer[BUFFER_SIZE];
const char* SENSOR_NAME = "DHT22";
const uint8_t PIN_LED = 13;
// Use F() macro on UNO
Serial.println(F("Sensor initialized"));Error Handling
- ✅ Check return values from peripheral initialization functions
- ✅ Provide meaningful error messages via Serial output
- ✅ Implement fallback strategies for missing hardware
- ✅ Handle edge cases gracefully (null pointers, invalid data)
// Proper error handling
bool initializeSensor() {
if (!sensor.begin()) {
Serial.println(F("ERROR: Sensor initialization failed"));
return false;
}
Serial.println(F("Sensor initialized successfully"));
return true;
}Code Structure
- ✅ Clear, descriptive variable names following Arduino conventions
- ✅ Consistent indentation (2 or 4 spaces, or Arduino IDE default)
- ✅ Logical function organization with single responsibilities
- ✅ Proper comment placement explaining "why" not "what"
Board Compatibility
- ✅ Test compilation on target boards (UNO, ESP32, RP2040)
- ✅ Account for board-specific limitations (memory, pins, features)
- ✅ Use conditional compilation for board-specific code
- ✅ Document board requirements and compatibility
Performance Standards
- ✅ Minimize blocking operations in loop()
- ✅ Optimize for power consumption when applicable
- ✅ Use appropriate data types for the task
- ✅ Avoid unnecessary computations in tight loops
Testing Requirements
- ✅ Provide expected output for verification
- ✅ Include debugging Serial output for troubleshooting
- ✅ Document test procedures and expected behavior
- ✅ Handle common failure modes gracefully
Documentation Standards
- ✅ Include setup instructions with wiring diagrams
- ✅ Document configuration options and parameters
- ✅ Explain integration points with other patterns
- ✅ Provide troubleshooting guidance for common issues
param(
[string[]]$FqbnList = @(
"arduino:avr:uno",
"esp32:esp32:esp32",
"rp2040:rp2040:rpipico"
)
)
$cli = Get-Command arduino-cli -ErrorAction SilentlyContinue
if (-not $cli) {
Write-Error "arduino-cli not found in PATH. Install it before running this script."
exit 1
}
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$examplesDir = Resolve-Path (Join-Path $scriptRoot "..\examples")
$examples = @(
"config-example.ino",
"filtering-example.ino",
"buttons-example.ino",
"i2c-example.ino",
"csv-example.ino",
"scheduler-example.ino",
"state-machine-example.ino",
"hardware-detection-example.ino",
"data-logging-example.ino"
)
$failed = @()
foreach ($fqbn in $FqbnList) {
Write-Host "\n=== Compiling examples for $fqbn ===" -ForegroundColor Cyan
foreach ($example in $examples) {
$examplePath = Join-Path $examplesDir $example
Write-Host "Compiling $example" -ForegroundColor Gray
& arduino-cli compile --fqbn $fqbn $examplePath
if ($LASTEXITCODE -ne 0) {
$failed += "$fqbn :: $example"
}
}
}
if ($failed.Count -gt 0) {
Write-Host "\nCompilation failures:" -ForegroundColor Red
$failed | ForEach-Object { Write-Host " - $_" }
exit 1
}
Write-Host "\nAll examples compiled successfully." -ForegroundColor Green
#!/usr/bin/env bash
set -euo pipefail
FQBNS=(
"arduino:avr:uno"
"esp32:esp32:esp32"
"rp2040:rp2040:rpipico"
)
if ! command -v arduino-cli >/dev/null 2>&1; then
echo "arduino-cli not found in PATH. Install it before running this script." >&2
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXAMPLES_DIR="${SCRIPT_DIR}/../examples"
EXAMPLES=(
"config-example.ino"
"filtering-example.ino"
"buttons-example.ino"
"i2c-example.ino"
"csv-example.ino"
"scheduler-example.ino"
"state-machine-example.ino"
"hardware-detection-example.ino"
"data-logging-example.ino"
)
FAILED=()
for fqbn in "${FQBNS[@]}"; do
echo ""
echo "=== Compiling examples for ${fqbn} ==="
for example in "${EXAMPLES[@]}"; do
echo "Compiling ${example}"
if ! arduino-cli compile --fqbn "${fqbn}" "${EXAMPLES_DIR}/${example}"; then
FAILED+=("${fqbn} :: ${example}")
fi
done
done
if [ "${#FAILED[@]}" -gt 0 ]; then
echo ""
echo "Compilation failures:"
for item in "${FAILED[@]}"; do
echo " - ${item}"
done
exit 1
fi
echo ""
echo "All examples compiled successfully."
Code Output Template
Standardized template for generating Arduino code snippets with consistent structure and documentation.
Template Structure
/*
* [Pattern Name] - [Brief Description]
* Generated for [Board Type] using Arduino Code Generator
*
* Features:
* - [List key features]
* - [List capabilities]
*
* Hardware Requirements:
* - [Board type]
* - [List required components]
*
* Connections:
* [ASCII wiring diagram]
*
* Usage:
* 1. Upload this sketch to your Arduino
* 2. Open Serial Monitor at 9600 baud
* 3. [Expected behavior description]
*
* Expected Output:
* [Sample Serial output]
*/
#include <[Required Libraries]>
// Configuration
#define [PIN_NAME] [PIN_NUMBER]
#define [CONSTANT_NAME] [VALUE]
// Timing constants
const unsigned long [INTERVAL_NAME] = [VALUE_MS];
// Global variables
unsigned long [timing_variable] = 0;
[Other global variables]
// Function declarations
void [function_name]();
[Other function declarations]
void setup() {
// Initialize serial communication
Serial.begin(9600);
#if defined(ARDUINO_ARCH_AVR)
while (!Serial); // Wait for serial on Leonardo/UNO
#endif
Serial.println(F("[Initialization message]"));
// Hardware initialization
[hardware_init_code]
// Verify initialization
if ([init_check_condition]) {
Serial.println(F("[Success message]"));
} else {
Serial.println(F("[Error message]"));
while (1); // Halt on critical error
}
}
void loop() {
// Non-blocking timing
unsigned long currentTime = millis();
// Handle millis() overflow
if (currentTime - [timing_variable] >= [INTERVAL_NAME]) {
[timed_action_code]
[timing_variable] = currentTime;
}
// Main logic
[main_logic_code]
// Error handling
[error_handling_code]
}
// Implementation functions
void [function_name]() {
[function_implementation]
}
[Additional helper functions]Template Customization Guide
Header Comments
- Pattern Name: Descriptive name (e.g., "DHT22 Temperature Sensor")
- Board Type: Target board (UNO, ESP32, RP2040)
- Features List: 3-5 bullet points of capabilities
- Hardware Requirements: Specific components needed
- Connections: ASCII art wiring diagram
- Usage Steps: Numbered steps for user
- Expected Output: Sample Serial monitor output
Include Section
- Group standard libraries first (
<Wire.h>,<SPI.h>) - Then third-party libraries (
<DHT.h>) - Finally custom headers (
"config.h")
Configuration Section
- Use
#definefor pin numbers and constants - Group related constants together
- Use descriptive names in ALL_CAPS
Timing Section
- Define timing intervals as
const unsigned long - Use milliseconds for all time values
- Name intervals descriptively (e.g.,
READ_INTERVAL_MS)
Global Variables
- Initialize timing variables to 0
- Use descriptive names
- Minimize global scope
Setup Function
- Always initialize Serial first
- Use F() macro for strings on UNO
- Include hardware initialization checks
- Provide clear success/error messages
Loop Function
- Implement proper millis() timing with overflow protection
- Keep loop() responsive (no blocking operations)
- Include error checking and recovery
Function Organization
- Declare all functions at top (Arduino requirement)
- Implement functions after loop()
- Use clear, descriptive names
- Single responsibility per function
Board-Specific Templates
Arduino UNO Template
// Memory-optimized for 2KB SRAM
#define BUFFER_SIZE 64
char buffer[BUFFER_SIZE];
// Use F() macro for all strings
Serial.println(F("UNO-optimized output"));ESP32 Template
// WiFi-capable with FreeRTOS
#include <WiFi.h>
// Task for concurrent operations
TaskHandle_t sensorTask;RP2040 Template
// Dual-core capable
#include <pico/multicore.h>
// PIO for precise timing if neededError Handling Template
// Standardized error handling
bool initializeHardware() {
if (![init_success_condition]) {
Serial.println(F("ERROR: Hardware initialization failed"));
return false;
}
return true;
}
void handleErrors() {
static unsigned long lastErrorCheck = 0;
const unsigned long ERROR_CHECK_INTERVAL = 5000;
if (millis() - lastErrorCheck >= ERROR_CHECK_INTERVAL) {
if ([error_condition]) {
Serial.println(F("WARNING: [specific error]"));
[recovery_action]
}
lastErrorCheck = millis();
}
}Integration Template
When combining patterns, use this structure:
// Primary pattern includes
#include <[primary_pattern_libs]>
// Secondary pattern includes
#include <[secondary_pattern_libs]>
// Shared configuration
#define SHARED_PIN [pin_number]
// Primary pattern variables
[primary_variables]
// Secondary pattern variables
[secondary_variables]
// Initialize both patterns
void setup() {
initPrimaryPattern();
initSecondaryPattern();
}
void loop() {
updatePrimaryPattern();
updateSecondaryPattern();
}Testing Template
Include this testing section for validation:
// Testing and debugging
#define DEBUG_MODE true
void debugOutput() {
#if DEBUG_MODE
Serial.print(F("Debug: "));
Serial.println([debug_value]);
#endif
}
// Call debugOutput() at key pointsDocumentation Standards
- Comments: Explain "why" decisions, not "what" code does
- Variable Names: descriptive and consistent
- Function Names: verb-based (readSensor, updateDisplay)
- Constants: ALL_CAPS_WITH_UNDERSCORES
- Indentation: 2 spaces (Arduino IDE default)
Step 1: Identify Pattern Type
When a user requests Arduino code, first identify the core pattern category from their request:
Pattern Categories
Hardware Abstraction
- Multi-board configuration
- Pin definitions
- Memory management
- Board detection
Keywords: config, pins, setup, board, memory
Sensor Reading & Filtering
- ADC noise reduction
- Sensor data processing
- Calibration and validation
- Environmental sensors (DHT22, BME280)
Keywords: sensor, read, temperature, humidity, filter, noise, adc
Input Handling
- Button debouncing
- Edge detection
- Multi-button management
- User input processing
Keywords: button, press, input, debounce, switch
Communication
- I2C device management
- SPI configuration
- UART/Serial protocols
- Data output (CSV)
Keywords: i2c, spi, serial, uart, communication, csv, data
Timing & Concurrency
- Non-blocking timing
- Task scheduling
- State machines
- Event-driven programming
Keywords: timer, schedule, state machine, non-blocking, millis
Hardware Detection
- Auto-detection
- Fallback strategies
- Adaptive configuration
- Resource monitoring
Keywords: detect, auto, fallback, adaptive
Data Persistence
- EEPROM storage
- SD card logging
- Data validation
- Wear leveling
Keywords: eeprom, sd card, log, save, store, data
Identification Process
1. Scan request for keywords from the categories above 2. Determine primary pattern (most relevant category) 3. Note secondary patterns that might be needed for integration 4. Consider board constraints (UNO vs ESP32 vs RP2040)
Examples
"Generate code to read a DHT22 sensor without blocking" → Primary: Sensor Reading & Filtering + Timing & Concurrency
"Create a button handler with long press detection" → Primary: Input Handling
"Make an I2C scanner for my Arduino" → Primary: Communication (I2C)
"Log data to SD card every 10 seconds" → Primary: Data Persistence + Timing & Concurrency
Step 2: Read Relevant Reference File
After identifying the pattern type, read the corresponding reference documentation to understand implementation details.
Reference File Mapping
| Pattern Category | Reference File | Location |
|---|---|---|
| Hardware Abstraction | patterns-config.md | references/ |
| Sensor Reading & Filtering | patterns-filtering.md | references/ |
| Input Handling | patterns-buttons.md | references/ |
| Communication (I2C) | patterns-i2c.md | references/ |
| Communication (SPI) | patterns-spi.md | references/ |
| Communication (CSV) | patterns-csv.md | references/ |
| Timing & Concurrency (Scheduler) | patterns-scheduler.md | references/ |
| Timing & Concurrency (State Machine) | patterns-state-machine.md | references/ |
| Hardware Detection | patterns-hardware-detection.md | references/ |
| Data Persistence | patterns-data-logging.md | references/ |
Reading Process
1. Locate the reference file based on pattern mapping 2. Read the complete file to understand:
- Required libraries and includes
- Pin configurations
- Function signatures
- Error handling patterns
- Board-specific considerations
- Example implementations
3. Extract key implementation details:
- Data structures used
- Timing constraints
- Memory requirements
- Initialization sequences
- Common pitfalls
4. Note integration points with other patterns
Reference File Structure
Each reference file contains:
- Overview: Pattern description and use cases
- API Reference: Function signatures and parameters
- Implementation Guide: Step-by-step coding instructions
- Board-Specific Notes: UNO/ESP32/RP2040 differences
- Examples: Code snippets and usage patterns
- Integration: How to combine with other patterns
Fallback Strategy
If a specific reference file doesn't exist: 1. Check for similar patterns in existing files 2. Use general Arduino best practices 3. Consult the quality standards and common pitfalls 4. Generate code following established patterns
Step 3: Generate Code
Generate production-ready Arduino code following these comprehensive rules and best practices.
Code Generation Rules
1. Include Statements
- Include all necessary
#includestatements at the top - Use angle brackets for standard libraries:
#include <Wire.h> - Use quotes for custom headers:
#include "config.h" - Group includes logically (standard, then third-party, then custom)
2. Configuration Management
- Define pins in config.h style with conditional compilation
- Use
#ifdeffor board-specific configurations - Define constants for magic numbers
- Group related constants together
3. Timing Patterns
- Never use `delay()` - always use
millis()for non-blocking timing - Use
unsigned longfor all time variables - Handle millis() overflow (wraps every ~49 days)
- Implement proper timing comparisons
4. Memory Management
- Use
F()macro for string literals on memory-constrained boards - Avoid
Stringclass on UNO - use char arrays - Implement bounds checking for all arrays
- Use
constfor read-only data - Minimize global variables
5. Error Handling
- Check return values from peripheral initialization
- Provide meaningful error messages via Serial
- Implement fallback strategies for missing hardware
- Use appropriate error codes or states
6. Debugging Support
- Add Serial output for debugging at key points
- Include status messages during initialization
- Log errors and unusual conditions
- Comment "why" decisions were made, not "what" the code does
7. Code Structure
- Use clear, descriptive variable names
- Implement proper indentation and spacing
- Add comments for complex logic
- Separate concerns into functions
- Follow Arduino naming conventions
Board-Specific Considerations
Arduino UNO (ATmega328P, 2KB SRAM)
// Use F() macro for all strings
Serial.println(F("Initializing sensor..."));
// Prefer char arrays over String
char buffer[32];
// Minimize buffer sizes
#define BUFFER_SIZE 64ESP32 (520KB SRAM, WiFi/BLE capable)
// Can use larger buffers
#define BUFFER_SIZE 1024
// Leverage FreeRTOS
TaskHandle_t sensorTask;
// Enable WiFi patterns
#include <WiFi.h>RP2040 (264KB SRAM, dual-core)
// Use PIO for timing-critical tasks
// Support USB host mode
// Multicore patterns availableCode Template Structure
// 1. Includes
#include <Wire.h>
#include <SPI.h>
#include "config.h"
// 2. Constants and Configuration
#define PIN_LED 13
#define INTERVAL_MS 1000
const char* DEVICE_NAME = "SensorNode";
// 3. Global Variables
unsigned long lastUpdate = 0;
bool sensorActive = false;
// 4. Function Declarations
void initializeHardware();
void updateSensor();
void handleErrors();
// 5. Setup Function
void setup() {
Serial.begin(9600);
while (!Serial); // Wait for serial on Leonardo/UNO
Serial.println(F("Initializing..."));
initializeHardware();
Serial.println(F("Ready"));
}
// 6. Main Loop
void loop() {
unsigned long currentTime = millis();
// Handle millis() overflow
if (currentTime - lastUpdate >= INTERVAL_MS) {
updateSensor();
lastUpdate = currentTime;
}
handleErrors();
}
// 7. Implementation Functions
void initializeHardware() {
// Hardware initialization with error checking
}
void updateSensor() {
// Sensor reading and processing
}
void handleErrors() {
// Error detection and recovery
}Quality Assurance
Before finalizing code:
- ✅ Verify compilation on target board
- ✅ Check memory usage estimates
- ✅ Validate timing calculations
- ✅ Test error conditions
- ✅ Review against common pitfalls
Step 4: Provide Usage Instructions
After generating code, provide comprehensive usage instructions including wiring diagrams, configuration steps, and expected behavior.
Instruction Components
1. Hardware Requirements
- Components List: Specific sensors, actuators, or modules needed
- Board Compatibility: Which Arduino boards are supported
- Power Requirements: Voltage and current specifications
- Additional Hardware: Cables, resistors, breadboards, etc.
2. Wiring Instructions
- Pin Connections: Clear mapping between Arduino pins and component pins
- Circuit Diagrams: ASCII art or reference to visual diagrams
- Pull-up/Pull-down Resistors: When and where to use them
- Power Connections: VCC, GND, and signal pins
3. Software Setup
- Library Installation: Which Arduino libraries to install via Library Manager
- Board Selection: How to select the correct board in Arduino IDE
- Configuration: Any settings or parameters to modify
- Dependencies: Other code or configurations required
4. Usage Instructions
- Initialization: What happens during setup()
- Operation: How the code behaves during normal operation
- User Interaction: Buttons to press, sensors to trigger, etc.
- Output: What to expect on Serial monitor, LEDs, displays
5. Testing and Validation
- Expected Output: Sample Serial output or LED patterns
- Error Conditions: What to look for if something goes wrong
- Debugging Tips: How to troubleshoot common issues
- Performance Metrics: Expected timing or resource usage
Wiring Diagram Format
Use clear ASCII art for wiring diagrams:
Arduino UNO DHT22 Sensor
----------- ------------
5V ---------> VCC
GND ---------> GND
D2 ---------> DATA
(with 10K pull-up resistor)Or reference existing diagrams:
- See examples/README.md for detailed wiring diagrams
- Check Fritzing diagrams in assets/ folder
Example Instructions Template
Hardware Required
- Arduino UNO, ESP32, or RP2040 board
- DHT22 temperature/humidity sensor
- 10KΩ pull-up resistor
- Jumper wires
Wiring
Arduino Pin | DHT22 Pin | Notes
-------------|-----------|--------
5V | VCC | Power supply
GND | GND | Ground
D2 | DATA | Data signal (with 10K pull-up to 5V)Setup Steps
1. Install DHT sensor library: Sketch > Include Library > Manage Libraries > "DHT sensor library" 2. Select your board: Tools > Board > Arduino UNO 3. Upload the sketch 4. Open Serial Monitor: Tools > Serial Monitor
Expected Output
Initializing DHT22 sensor...
DHT22 initialized successfully
Temperature: 23.50°C, Humidity: 45.20%
Temperature: 23.52°C, Humidity: 45.15%
...Troubleshooting
- No readings: Check wiring and power connections
- Invalid readings: Ensure proper pull-up resistor on data pin
- Slow response: DHT22 sensors can take up to 2 seconds between readings
Integration Notes
When combining with other patterns:
- Mention how wiring integrates with existing circuits
- Note any pin conflicts or shared resources
- Explain how multiple components work together
- Provide combined wiring diagrams for complex projects
Step 5: Mention Integration
When relevant, suggest how the generated code can integrate with other patterns for more complex projects.
Integration Patterns
Environmental Monitor
Combines: Filtering + Scheduler + CSV + Data Logging
Use Case: Collect sensor data over time and log to SD card Components: DHT22/BME280 sensors + SD card module + real-time clock
Integration Points:
- Use Scheduler for periodic readings
- Apply Filtering for noise reduction
- Format data as CSV for logging
- Use Data Logging for persistent storage
Button-Controlled Robot
Combines: Buttons + State Machine + Scheduler
Use Case: Robot with button controls and different operational modes Components: Multiple buttons + motor drivers + state indicators
Integration Points:
- Buttons trigger state transitions
- State Machine manages robot behavior
- Scheduler handles timing-critical motor control
IoT Data Logger
Combines: Hardware Detection + WiFi + Data Logging + CSV
Use Case: Remote environmental monitoring with cloud connectivity Components: ESP32 board + sensors + WiFi module + SD card
Integration Points:
- Hardware Detection for sensor availability
- WiFi for data transmission
- Data Logging as backup when offline
- CSV for structured data format
Sensor Hub
Combines: I2C + Filtering + Scheduler + Hardware Detection
Use Case: Multiple I2C sensors with coordinated readings Components: Multiple I2C sensors + microcontroller with I2C bus
Integration Points:
- I2C for sensor communication
- Filtering for each sensor type
- Scheduler for coordinated sampling
- Hardware Detection for plug-and-play sensors
Integration Guidelines
When to Suggest Integration
- User requests complex functionality requiring multiple patterns
- Hardware setup naturally combines different components
- Project scope involves data flow between subsystems
- Performance requirements need coordinated timing
How to Present Integration
1. Identify the primary pattern from the user's request 2. Suggest complementary patterns that enhance functionality 3. Explain the benefits of combining patterns 4. Provide integration examples from the examples/ folder 5. Note any constraints or considerations
Integration Examples
"I want to log temperature data" → Primary: Data Logging → Integration: Add Filtering for noise reduction, Scheduler for timing
"Build a smart button interface" → Primary: Buttons → Integration: Add State Machine for complex interactions, Scheduler for timeouts
"Create a wireless sensor network" → Primary: Communication (WiFi/I2C) → Integration: Add Hardware Detection for robustness, Data Logging for offline operation
Cross-Pattern Considerations
Shared Resources
- Pins: Ensure no conflicts between integrated patterns
- Memory: Account for combined memory usage
- Timing: Coordinate timing requirements between patterns
- Power: Consider power consumption of combined components
Code Organization
- Modular Functions: Keep patterns in separate functions
- Shared Constants: Define common pins/constants once
- Error Handling: Integrate error handling across patterns
- Initialization Order: Ensure proper startup sequence
Testing Strategy
- Individual Testing: Test each pattern separately first
- Integration Testing: Verify combined functionality
- Edge Cases: Test interactions between patterns
- Resource Limits: Verify memory and timing constraints
Documentation Links
For detailed integration examples, see:
- examples/README.md - Complete project examples
- Individual pattern references in references/ folder
- assets/workflow.mmd - Integration workflow diagram