Arduino Uno Edition

C++ for Arduino
Complete Reference Guide

// A fast-track guide for developers coming from other languages

// Table of Contents
1. Why C++ on Arduino? 2. Program Structure 3. Data Types & Variables 4. Operators 5. Control Flow 6. Functions 7. Arrays & Strings 8. Pointers & References 9. Classes & OOP 10. Arduino I/O API 11. Hello World 12. Comprehensive Beginner Project 13. Memory & Best Practices
01

Why C++ on Arduino?

The Arduino Uno (and its R3/R4 variants) uses a microcontroller — the ATmega328P (Uno R3) or Renesas RA4M1 (Uno R4). Unlike Python or JavaScript that run in a managed runtime, C++ compiles directly to native machine code, giving you total control of hardware with zero overhead.

ATmega328P (R3)

16 MHz · 32KB Flash · 2KB SRAM · 1KB EEPROM · 14 digital pins · 6 analog pins

Renesas RA4M1 (R4)

48 MHz · 256KB Flash · 32KB SRAM · 14 digital · 6 analog · USB · CAN

Toolchain

avr-g++ compiler · AVR-Libc · Arduino framework wraps hardware registers in readable APIs

Key Constraint

No OS, no heap GC, no STL (Uno R3), limited SRAM — code must be lean and deterministic

⚠ Arduino C++ vs Desktop C++

The Arduino IDE uses a subset of C++14. On the Uno R3, the STL (vector, map, etc.) is unavailable. Dynamic memory (new/delete) works but causes heap fragmentation on 2KB SRAM — avoid it. Exceptions and RTTI are disabled.


02

Program Structure

Every Arduino sketch is a .ino file (or a .cpp file in a library). The IDE auto-generates a wrapper that calls your two mandatory functions.

C++structure.ino
// 1. Preprocessor directives — processed before compilation
#include <Arduino.h>    // usually auto-included by IDE
#define LED_PIN  13       // compile-time constant (no memory used)
#define BAUD     9600

// 2. Global variables — live for the entire program lifetime
int counter = 0;

// 3. Function declarations (prototypes) — optional in .ino; required in .cpp
void blinkLED(int times);

// ──────────────────────────────────────────────
// setup() — runs ONCE when the board powers on
// ──────────────────────────────────────────────
void setup() {
  Serial.begin(BAUD);          // start serial monitor at 9600 baud
  pinMode(LED_PIN, OUTPUT);     // configure pin 13 as output
  Serial.println("Arduino ready!");
}

// ──────────────────────────────────────────────
// loop() — runs FOREVER after setup() completes
// ──────────────────────────────────────────────
void loop() {
  counter++;
  blinkLED(3);
  delay(1000);               // pause 1000 ms
}

// 4. User-defined function
void blinkLED(int times) {
  for (int i = 0; i < times; i++) {
    digitalWrite(LED_PIN, HIGH);
    delay(200);
    digitalWrite(LED_PIN, LOW);
    delay(200);
  }
}
💡 Key Difference from Python/JS

C++ is compiled, not interpreted. Every variable needs a declared type. Curly braces {} define scope. Statements end with ;. There is no garbage collector — you manage memory.


03

Data Types & Variables

Primitive Types

TypeArduino SizeRangeUse Case
bool1 bytetrue / falseFlags, on/off state
byte1 byte0 – 255Raw pin values, buffers
char1 byte-128 – 127ASCII characters
unsigned char1 byte0 – 255Same as byte
int2 bytes (AVR)-32,768 – 32,767General integers ⚠ small!
unsigned int2 bytes0 – 65,535Counts, indices
long4 bytes±2.1 billionmillis(), large counts
unsigned long4 bytes0 – 4.29 billionTimestamps, millis()
float4 bytes±3.4×10³⁸Sensor math, 6-7 sig digits
double4 bytes (AVR!)same as floatOn Uno R3, identical to float
Stringobjectheap-basedConvenient but use cautiously
char[]static arrayfixed lengthPreferred for strings (no heap)
C++variables.ino
// Declaration and initialization
int           count   = 0;
unsigned long t       = 0UL;      // UL suffix = unsigned long literal
float         voltage = 3.14f;    // f suffix = float literal
bool          isOn    = false;
char          letter  = 'A';      // single quotes for char
const int     PIN     = 7;        // const: value cannot change

// Storage class qualifiers
static int    runCount = 0;  // persists between function calls
volatile int  isr_flag = 0;  // used in interrupt service routines

// Type casting
int   raw = analogRead(A0);        // 0 – 1023
float v   = (float)raw * 5.0 / 1023.0; // C-style cast

// PROGMEM — store constant data in Flash instead of SRAM
#include <avr/pgmspace.h>
const char msg[] PROGMEM = "Hello from Flash!"; // saves precious SRAM
⚠ int is only 2 bytes on Uno R3!

Unlike desktop C++ where int is 4 bytes, on the ATmega328P it's only 2 bytes. Always use long when working with millis() (which overflows after ~49 days). Use uint8_t, int16_t etc. for explicit sizes.


04

Operators

CategoryOperatorExampleNote
Arithmetic+ - * / %x = a + b;Integer division truncates
Increment++ --i++; ++i;Post vs pre increment matters in expressions
Compound assign+= -= *= /= %=x += 5;Shorthand for x = x + 5
Comparison== != < > <= >=if (x == 10)Returns bool
Logical&& || !if (a && !b)Short-circuit evaluation
Bitwise& | ^ ~ << >>x &= 0x0F;Essential for register manipulation
Ternary? :y = x > 0 ? 1 : 0;Inline if/else
C++bitwise.ino
// Bitwise operations are critical in embedded programming
byte reg = 0b10110000;   // binary literal

reg |=  (1 << 2);  // SET bit 2   → 10110100
reg &= ~(1 << 7);  // CLEAR bit 7 → 00110100
reg ^=  (1 << 4);  // TOGGLE bit 4→ 00100100

bool bit4set = (reg >> 4) & 0x01; // READ bit 4

05

Control Flow

C++control_flow.ino
// ── IF / ELSE IF / ELSE ──────────────────────
int temp = 25;
if (temp < 0) {
  Serial.println("Freezing");
} else if (temp < 20) {
  Serial.println("Cold");
} else {
  Serial.println("Warm");
}

// ── SWITCH ───────────────────────────────────
byte mode = 2;
switch (mode) {
  case 1:  Serial.println("Mode: Blink");   break;
  case 2:  Serial.println("Mode: Fade");    break;
  case 3:  Serial.println("Mode: Pulse");   break;
  default: Serial.println("Mode: Off");
}

// ── FOR LOOP ─────────────────────────────────
for (int i = 0; i < 10; i++) {
  Serial.println(i);
}

// ── WHILE LOOP ───────────────────────────────
int count = 0;
while (count < 5) {
  count++;
}

// ── DO-WHILE — executes at least once ────────
do {
  Serial.println("Always runs once");
} while (false);

// ── BREAK & CONTINUE ─────────────────────────
for (int i = 0; i < 10; i++) {
  if (i == 5) break;     // exit loop
  if (i % 2 == 0) continue; // skip even numbers
  Serial.println(i);
}

// ── NON-BLOCKING TIMING (preferred over delay)
unsigned long previousMillis = 0;
const unsigned long INTERVAL = 1000;

void loop() {
  unsigned long now = millis();
  if (now - previousMillis >= INTERVAL) {
    previousMillis = now;
    // do periodic work here without blocking
  }
}

06

Functions

C++functions.ino
// ── Basic function ───────────────────────────
// return_type name(param_type param) { body }
int add(int a, int b) {
  return a + b;
}

// ── void function (no return value) ──────────
void printSeparator() {
  Serial.println("──────────────");
}

// ── Default parameters ────────────────────────
void blink(int pin, int ms = 500) {
  digitalWrite(pin, HIGH);
  delay(ms);
  digitalWrite(pin, LOW);
  delay(ms);
}
// Usage: blink(13);       — uses default 500ms
// Usage: blink(13, 100);  — uses 100ms

// ── Pass by value vs by reference ────────────
void byValue(int x) { x = 99; }        // original unchanged
void byRef  (int &x) { x = 99; }       // modifies original (& = reference)
void byPtr  (int *x) { *x = 99; }      // pointer dereference

// ── Overloaded functions (same name, diff params)
float mapVal(int   v, int   lo, int   hi) { return (float)v / (hi - lo); }
float mapVal(float v, float lo, float hi) { return (v - lo) / (hi - lo); }

// ── Recursion — use sparingly (limited stack!) 
long factorial(int n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

// ── Interrupt Service Routine (ISR) ──────────
volatile bool buttonPressed = false;
void IRAM_ATTR onButtonPress() {   // runs on hardware interrupt
  buttonPressed = true;
}
// In setup(): attachInterrupt(digitalPinToInterrupt(2), onButtonPress, FALLING);

07

Arrays & Strings

C++arrays_strings.ino
// ── Arrays ────────────────────────────────────
int  pins[4] = {9, 10, 11, 12};    // fixed size at compile time
int  len   = sizeof(pins) / sizeof(pins[0]); // = 4

for (int i = 0; i < len; i++) {
  pinMode(pins[i], OUTPUT);
}

// ── 2D Arrays ─────────────────────────────────
byte matrix[3][3] = {
  {1, 0, 1},
  {0, 1, 0},
  {1, 0, 1}
};

// ── C-style strings (char arrays) ─────────────
char greeting[] = "Hello";     // auto-sized: 6 bytes (incl. \0)
char buf[32];                  // buffer for building strings
sprintf(buf, "Temp: %d C", 25); // printf-style formatting
Serial.println(buf);

// String functions from <string.h>
int len2 = strlen(greeting);   // length without null
strcpy(buf, greeting);          // copy string
strcat(buf, " World");          // concatenate
int cmp = strcmp("abc", "abc"); // 0 if equal

// ── Arduino String object ─────────────────────
String s1 = "Sensor: ";
String s2 = s1 + String(42);    // "Sensor: 42" — heap allocation!
Serial.println(s2);
// ⚠ Avoid String in loops — causes heap fragmentation on R3

08

Pointers & References

Pointers are addresses of memory locations. They unlock direct hardware register access — essential in embedded programming.

C++pointers.ino
int  value = 42;
int *ptr   = &value;   // ptr holds the ADDRESS of value

Serial.println(*ptr);  // dereference: prints 42
*ptr = 100;            // modifies value through pointer
Serial.println(value); // prints 100

// ── Pointers and arrays ───────────────────────
int  arr[] = {10, 20, 30};
int *p = arr;           // array name IS a pointer to first element
Serial.println(*(p + 1)); // prints 20 (pointer arithmetic)

// ── Passing array to function ─────────────────
void printArray(int *data, int length) {
  for (int i = 0; i < length; i++) {
    Serial.println(data[i]);
  }
}

// ── References (C++ only, not in C) ──────────
void swap(int &a, int &b) {  // & makes it a reference param
  int tmp = a;
  a = b;
  b = tmp;
}
// swap(x, y); — no & needed at call site

// ── Function pointers — useful for callbacks ──
void doWork(void (*callback)()) {
  // ... do something ...
  callback();   // call whatever function was passed in
}
void myAction() { Serial.println("Action!"); }
// doWork(myAction);

09

Classes & Object-Oriented Programming

C++ adds OOP on top of C. Classes bundle data (member variables) with behavior (member functions/methods). On Arduino this is used extensively — every library is a class (e.g., Serial, Wire, Servo).

C++LED.h + LED.cpp
// ── Class Definition (usually in a .h header file) ──
class LED {
  private:                     // accessible only inside the class
    int  _pin;
    bool _state;

  public:                      // accessible from anywhere
    // Constructor — called when object is created
    LED(int pin) : _pin(pin), _state(false) {
      pinMode(_pin, OUTPUT);
    }

    // Destructor — called when object goes out of scope
    ~LED() { digitalWrite(_pin, LOW); }

    void on()     { _state = true;  digitalWrite(_pin, HIGH); }
    void off()    { _state = false; digitalWrite(_pin, LOW); }
    void toggle() { _state ? off() : on(); }

    // Getter — const means it doesn't modify the object
    bool isOn() const { return _state; }

    void blink(int times, int ms = 200) {
      for (int i = 0; i < times; i++) {
        on(); delay(ms);
        off(); delay(ms);
      }
    }
};

// ── Inheritance ───────────────────────────────
class FadingLED : public LED {  // inherits everything from LED
  private:
    int _brightness;
  public:
    FadingLED(int pin) : LED(pin), _brightness(0) {}
    void fadeTo(int brightness) {
      _brightness = brightness;
      analogWrite(_pin, brightness);  // 0-255 PWM
    }
};

// ── Usage in sketch ───────────────────────────
LED statusLED(13);          // creates object, calls constructor
FadingLED rgbLED(9);

void setup() {
  statusLED.blink(3);       // object.method()
  rgbLED.fadeTo(128);
}
📌 Virtual Functions & Polymorphism

C++ supports virtual methods for runtime polymorphism. On Arduino this works but adds a vtable overhead (~2–6 bytes/object). Use it in libraries, but avoid in tight loops on the Uno R3.


10

Arduino I/O API Quick Reference

FunctionPurposeExample
pinMode(pin, mode)Set pin as INPUT, OUTPUT, or INPUT_PULLUPpinMode(7, INPUT_PULLUP);
digitalWrite(pin, val)Write HIGH or LOW to digital pindigitalWrite(13, HIGH);
digitalRead(pin)Read digital pin → HIGH or LOWint v = digitalRead(2);
analogRead(pin)Read analog pin → 0–1023 (10-bit ADC)int v = analogRead(A0);
analogWrite(pin, val)PWM output 0–255 on PWM pins (3,5,6,9,10,11)analogWrite(9, 128);
delay(ms)Block for N millisecondsdelay(1000);
delayMicroseconds(us)Block for N microsecondsdelayMicroseconds(50);
millis()Milliseconds since boot (unsigned long)unsigned long t = millis();
micros()Microseconds since bootunsigned long t = micros();
map(v, f1, t1, f2, t2)Re-map a value from one range to anothermap(val, 0, 1023, 0, 255)
constrain(v, lo, hi)Clamp value to rangeconstrain(x, 0, 100)
random(min, max)Pseudo-random integerrandom(0, 256)
Serial.begin(baud)Init serial @ baud rateSerial.begin(9600);
Serial.print/println()Send data to serial monitorSerial.println(3.14);
Serial.available()Bytes waiting to be readif (Serial.available())
Serial.read()Read one byte from serialchar c = Serial.read();
attachInterrupt(pin, ISR, mode)Hardware interrupt on pinattachInterrupt(0, isr, FALLING);
tone(pin, freq, dur)Generate square wave (buzzer)tone(8, 440, 500);
noTone(pin)Stop tonenoTone(8);

11

Hello World

On a microcontroller there's no terminal, so "Hello World" means blinking an LED and printing to the Serial Monitor.

C++helloworld.ino
/*
 * Hello World — Arduino Uno
 * Blinks the built-in LED on pin 13
 * and prints a message to the Serial Monitor.
 *
 * Hardware: No extra components needed.
 * Open: Tools → Serial Monitor, set baud to 9600
 */

const int LED = 13;     // built-in LED on all Uno boards
int       blinkCount = 0;

void setup() {
  Serial.begin(9600);
  pinMode(LED, OUTPUT);
  Serial.println("Hello, Arduino World!");
}

void loop() {
  digitalWrite(LED, HIGH);   // LED on
  delay(500);
  digitalWrite(LED, LOW);    // LED off
  delay(500);

  blinkCount++;
  Serial.print("Blink #");
  Serial.println(blinkCount);
}

12

Comprehensive Beginner Project: Smart LED Station

This single sketch demonstrates virtually every core C++ and Arduino concept in one cohesive program. It simulates a "smart LED control station" with serial commands, multiple modes, a class, timers, arrays, and more.

Hardware Needed

Arduino Uno · 3× LEDs (pins 9, 10, 11) · 3× 220Ω resistors · 1× pushbutton (pin 2) · USB cable

Concepts Covered

Classes · Arrays · Enums · Pointers · ISR · Serial I/O · Timers · Functions · Bitwise ops · Control flow

How to Use

Upload sketch → Open Serial Monitor at 9600 baud → Type commands: 1 (blink), 2 (chase), 3 (pulse), 0 (off), ? (help)

C++ — Full ProjectSmartLEDStation.ino
/*
 * ╔══════════════════════════════════════════════════════╗
 * ║        SMART LED STATION — C++ Arduino Project       ║
 * ║  Demonstrates core C++ and Arduino concepts in one   ║
 * ║  cohesive beginner program.                          ║
 * ╚══════════════════════════════════════════════════════╝
 *
 * CIRCUIT:
 *   Pins 9, 10, 11 → LED anode → 220Ω resistor → GND
 *   Pin 2          → one leg of pushbutton → GND
 *   (use INPUT_PULLUP so no external pull-up resistor needed)
 *
 * CONCEPTS DEMONSTRATED:
 *  #defines and const         Enumerations (enum)
 *  Data types and casting     Arrays and sizeof
 *  Classes and objects        Inheritance
 *  Pointers and references    Function overloading
 *  Default parameters         ISR / hardware interrupts
 *  Non-blocking timing        Serial communication
 *  switch/case, for, while    Bitwise operations
 *  PROGMEM (Flash strings)    Static variables
 */

#include <avr/pgmspace.h>   // PROGMEM macro

// ════════════════════════════════════════════════
// 1. CONSTANTS & DEFINES
// ════════════════════════════════════════════════
#define BAUD_RATE   9600
#define BTN_PIN     2           // interrupt-capable pin
#define NUM_LEDS    3

const int LED_PINS[NUM_LEDS] = {9, 10, 11};  // PWM-capable
const unsigned long TICK_MS  = 50;            // main timer tick

// ════════════════════════════════════════════════
// 2. ENUMERATION — named integer constants
//    Cleaner than bare #defines for related states
// ════════════════════════════════════════════════
enum Mode {
  MODE_OFF   = 0,
  MODE_BLINK = 1,
  MODE_CHASE = 2,
  MODE_PULSE = 3
};

// ════════════════════════════════════════════════
// 3. CLASS — encapsulates a single LED with state
// ════════════════════════════════════════════════
class SmartLED {
  private:
    uint8_t  _pin;           // explicit 8-bit unsigned type
    uint8_t  _brightness;   // 0–255
    bool     _isOn;

  public:
    // Constructor using member initializer list
    SmartLED(uint8_t pin)
      : _pin(pin), _brightness(0), _isOn(false)
    {
      pinMode(_pin, OUTPUT);
      off();
    }

    void on(uint8_t brightness = 255) {  // default param
      _brightness = brightness;
      _isOn = true;
      analogWrite(_pin, _brightness);
    }

    void off() {
      _isOn = false;
      _brightness = 0;
      analogWrite(_pin, 0);
    }

    void toggle() { _isOn ? off() : on(); }

    // Fade using pointer-to-brightness for demo
    void setFromPtr(uint8_t *brightnessPtr) {
      on(*brightnessPtr);              // dereference pointer
    }

    // Getters (const methods — promise not to modify object)
    bool    isOn()         const { return _isOn; }
    uint8_t brightness()  const { return _brightness; }
    uint8_t pin()          const { return _pin; }
};

// ════════════════════════════════════════════════
// 4. GLOBAL VARIABLES & OBJECTS
// ════════════════════════════════════════════════
SmartLED leds[NUM_LEDS] = {
  SmartLED(LED_PINS[0]),   // pin 9
  SmartLED(LED_PINS[1]),   // pin 10
  SmartLED(LED_PINS[2])    // pin 11
};

Mode           currentMode    = MODE_OFF;
unsigned long lastTick       = 0;
int            tickCount      = 0;
volatile bool btnEvent       = false; // volatile: changed in ISR

// PROGMEM string — stored in Flash, not SRAM
const char HELP_STR[] PROGMEM =
  "\r\n=== Smart LED Station ===\r\n"
  "  0  = OFF\r\n"
  "  1  = Blink all LEDs\r\n"
  "  2  = Chase pattern\r\n"
  "  3  = Pulse / fade\r\n"
  "  ?  = Show this help\r\n"
  "  s  = Print status\r\n"
  "========================\r\n";

// ════════════════════════════════════════════════
// 5. INTERRUPT SERVICE ROUTINE
//    Called automatically by hardware when button
//    is pressed (pin 2 goes LOW = FALLING edge)
// ════════════════════════════════════════════════
void onButtonPress() {          // ISR must be fast, no delay()
  btnEvent = true;              // set flag; handle in loop()
}

// ════════════════════════════════════════════════
// 6. HELPER FUNCTIONS
// ════════════════════════════════════════════════

// Turn off all LEDs — passing array by pointer
void allOff(SmartLED *ledArray, int count) {
  for (int i = 0; i < count; i++) {
    ledArray[i].off();
  }
}

// Function overloading — same name, different params
void printStatus() {
  Serial.print("[STATUS] Mode=");
  Serial.print((int)currentMode);
  Serial.print("  Tick=");
  Serial.print(tickCount);
  Serial.print("  LEDs: ");
  for (int i = 0; i < NUM_LEDS; i++) {
    Serial.print(leds[i].isOn() ? "ON " : "off ");
  }
  Serial.println();
}

void printStatus(const char *label) {  // overload with label
  Serial.print(label);
  Serial.print(": ");
  printStatus();
}

// Map mode enum to name — demonstrates switch
const char* modeName(Mode m) {
  switch (m) {
    case MODE_OFF:   return "OFF";
    case MODE_BLINK: return "BLINK";
    case MODE_CHASE: return "CHASE";
    case MODE_PULSE: return "PULSE";
    default:         return "UNKNOWN";
  }
}

// Cycle to next mode (demonstrates static variable)
void cycleMode() {
  currentMode = (Mode)(((int)currentMode + 1) % 4); // 0→1→2→3→0
  allOff(leds, NUM_LEDS);
  Serial.print("Button → Mode: ");
  Serial.println(modeName(currentMode));
}

// ════════════════════════════════════════════════
// 7. MODE UPDATE FUNCTIONS (called each tick)
// ════════════════════════════════════════════════

// MODE 1: Blink all LEDs together
void updateBlink() {
  bool ledState = (tickCount / 10) % 2 == 0;  // toggle every 10 ticks
  for (int i = 0; i < NUM_LEDS; i++) {
    ledState ? leds[i].on() : leds[i].off();
  }
}

// MODE 2: LED chase / Knight Rider pattern
void updateChase() {
  int active = (tickCount / 8) % NUM_LEDS;  // cycles 0→1→2→0
  for (int i = 0; i < NUM_LEDS; i++) {
    (i == active) ? leds[i].on() : leds[i].off();
  }
}

// MODE 3: PWM pulse / breathing effect
//   Uses a sine-like triangle wave for smooth fade
//   Demonstrates bitwise ops and uint8_t arithmetic
void updatePulse() {
  uint8_t phase = (uint8_t)(tickCount * 4); // 0–255 cycling

  // Triangle wave: up 0→255 then down 255→0
  uint8_t brightness;
  if (phase < 128) {
    brightness = phase * 2;           // ramp up
  } else {
    brightness = 255 - (phase - 128) * 2; // ramp down
  }

  // Use a pointer to brightness to demo setFromPtr()
  uint8_t *bPtr = &brightness;
  for (int i = 0; i < NUM_LEDS; i++) {
    // Each LED has a different phase offset using bitwise shift
    uint8_t offset = brightness + (uint8_t)(i * 85); // 85 = 255/3
    leds[i].setFromPtr(&offset);
  }
}

// ════════════════════════════════════════════════
// 8. SERIAL COMMAND PARSER
//    Demonstrates while loop, char handling, Serial
// ════════════════════════════════════════════════
void handleSerial() {
  while (Serial.available() > 0) {
    char cmd = (char)Serial.read();  // cast int→char
    allOff(leds, NUM_LEDS);
    tickCount = 0;

    switch (cmd) {
      case '0': currentMode = MODE_OFF;   Serial.println("Mode: OFF");   break;
      case '1': currentMode = MODE_BLINK; Serial.println("Mode: BLINK"); break;
      case '2': currentMode = MODE_CHASE; Serial.println("Mode: CHASE"); break;
      case '3': currentMode = MODE_PULSE; Serial.println("Mode: PULSE"); break;
      case '?': Serial.println((__FlashStringHelper*)HELP_STR); break;
      case 's': printStatus("Manual"); break;  // overloaded version
      default:  Serial.println("Unknown cmd. Type ? for help");
    }
  }
}

// ════════════════════════════════════════════════
// 9. SETUP — runs once
// ════════════════════════════════════════════════
void setup() {
  Serial.begin(BAUD_RATE);

  // Configure button with internal pull-up resistor
  // Pin reads HIGH normally, LOW when pressed
  pinMode(BTN_PIN, INPUT_PULLUP);

  // Attach hardware interrupt on pin 2
  //   digitalPinToInterrupt() converts pin# to interrupt#
  //   FALLING = trigger when signal goes HIGH→LOW
  attachInterrupt(digitalPinToInterrupt(BTN_PIN),
                   onButtonPress, FALLING);

  // Startup sequence — demonstrates for loop & array access
  Serial.println((__FlashStringHelper*)HELP_STR);
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i].on(255);
    delay(150);
    leds[i].off();
  }

  unsigned long uptime = millis();   // record startup time
  Serial.print("Ready in ");
  Serial.print(uptime);
  Serial.println("ms");
}

// ════════════════════════════════════════════════
// 10. LOOP — runs forever
//     Uses NON-BLOCKING timing (millis) — not delay
// ════════════════════════════════════════════════
void loop() {

  // ── Handle hardware interrupt flag ───────────
  if (btnEvent) {
    btnEvent = false;        // clear flag FIRST (re-entrant safety)
    static unsigned long lastPress = 0;  // static persists between calls!
    unsigned long now = millis();
    if (now - lastPress > 200) {    // debounce: ignore if <200ms ago
      lastPress = now;
      cycleMode();
    }
  }

  // ── Non-blocking timer — tick every TICK_MS ──
  unsigned long now = millis();
  if (now - lastTick >= TICK_MS) {
    lastTick = now;
    tickCount++;             // increment global tick counter

    // ── Dispatch to mode update function ─────────
    switch (currentMode) {
      case MODE_OFF:   allOff(leds, NUM_LEDS); break;
      case MODE_BLINK: updateBlink();           break;
      case MODE_CHASE: updateChase();           break;
      case MODE_PULSE: updatePulse();           break;
    }

    // ── Print status every 100 ticks (5 sec) ─────
    if (tickCount % 100 == 0) {
      printStatus();          // no-arg overload
    }
  }

  // ── Check serial commands (runs every loop) ───
  handleSerial();

  // ── Demonstrate do-while: read & discard overflow
  // (no busy-wait; this just drains any extra bytes)
}

Explanation of Key Concepts Used

Line / BlockConceptWhy It Matters
enum Mode {...}EnumerationNamed constants improve readability vs magic numbers
class SmartLEDOOP / EncapsulationPin state stays private; public interface is clean
: _pin(pin), _brightness(0)Member initializer listEfficient constructor — initializes before body runs
void on(uint8_t br = 255)Default parametersFlexible API with sensible defaults
volatile bool btnEventvolatile keywordTells compiler variable can change outside normal flow (ISR)
PROGMEMFlash storageSaves precious 2KB SRAM by putting strings in 32KB Flash
attachInterrupt(...)Hardware ISRButton handled instantly regardless of what loop() is doing
static unsigned long lastPressStatic local variablePersists between calls without being global
now - lastTick >= TICK_MSNon-blocking timingLoop stays responsive while timing events
void setFromPtr(uint8_t *bPtr)PointersPasses address; called with &offset to demo dereferencing
void printStatus() + overloadFunction overloadingSame function name, different signatures — compiler picks right one
uint8_t, int16_tExplicit-size typesPortable, avoids platform size surprises

13

Memory & Best Practices for Arduino

✅ Do

Use F("string") or PROGMEM for string literals. Use uint8_t, int16_t etc. for explicit sizes. Use non-blocking timing with millis(). Declare arrays with const size. Keep ISRs short.

❌ Don't

Avoid String objects in loops (heap fragmentation). Avoid delay() in serious programs. Avoid new/delete on R3. Never call delay(), Serial.print() inside an ISR.

📊 Check Memory

In Arduino IDE: Sketch → Export Compiled Binary, then check the output for flash/SRAM usage. Or use freeMemory() from MemoryFree library at runtime.

🔧 Serial Debug

Use Serial.print(F("msg")) — the F() macro stores the string in Flash just like PROGMEM but with simpler syntax. Saves SRAM on every debug line.

C++best_practices.ino
// ✅ Use F() macro to keep string literals in Flash
Serial.println(F("This string stays in Flash, not SRAM"));

// ✅ Non-blocking blink pattern
unsigned long prev = 0;
bool state = false;
void loop() {
  if (millis() - prev >= 500) {
    prev = millis();
    state = !state;
    digitalWrite(13, state);
  }
  // other code runs here — not blocked!
}

// ✅ Debounce button manually
bool readButton(int pin) {
  if (digitalRead(pin) == LOW) {
    delay(20);                     // short delay OK in debounce
    return digitalRead(pin) == LOW;
  }
  return false;
}

// ✅ Map analog reading to voltage
float toVolts(int raw) {
  return raw * (5.0f / 1023.0f);
}

// ✅ Constrain sensor readings before use
int raw = analogRead(A0);
int safe = constrain(raw, 0, 1023);
int mapped = map(safe, 0, 1023, 0, 255);
💡 Next Steps

After mastering this guide, explore: the Wire library (I²C sensors), SPI library, Servo library, writing your own library (.h + .cpp), and the Arduino Uno R4 which adds USB HID, CAN bus, and a real-time clock out of the box.