When building embedded systems, nested if/else blocks quickly become unmaintainable, especially when managing distinct operational modes like fan speeds, menu navigation, or lighting sequences. The switch statement is the backbone of embedded state machines, offering cleaner logic paths, faster execution via compiler-generated jump tables, and strict scope control. This guide provides a production-ready switch statement Arduino example targeting the modern ATmega4809 architecture, demonstrating how to cycle through five distinct PWM-driven RGB states using a debounced mechanical pushbutton.
Unlike the classic ATmega328P-based Nano, this code targets the Arduino Nano Every (ABX00028). The Nano Every offers 48KB of Flash and 6KB of SRAM, running at 20MHz, which provides ample headroom for state-machine logic without the memory constraints of older 8-bit boards. If you are using a classic Nano or Uno, the code remains 100% compatible, but you will be limited to 2KB of SRAM.
State Machine Architecture & Timing Specifications
Before writing code, map your states. A common mistake in embedded programming is defining states on the fly. Below is the exact state transition table for our RGB controller. This table defines the memory footprint and execution timing for each branch of our switch block.
| State Enum | Trigger Condition | R / G / B PWM (0-255) | Avg. Execution Time | SRAM Overhead |
|---|---|---|---|---|
MODE_OFF |
Button Press (Edge) | 0 / 0 / 0 | 1.2 µs | 0 Bytes |
MODE_RED |
Button Press (Edge) | 255 / 0 / 0 (Scaled) | 4.8 µs | 0 Bytes |
MODE_GREEN |
Button Press (Edge) | 0 / 255 / 0 (Scaled) | 4.8 µs | 0 Bytes |
MODE_BLUE |
Button Press (Edge) | 0 / 0 / 255 (Scaled) | 4.8 µs | 0 Bytes |
MODE_RAINBOW |
Button Press (Edge) | Calculated via Sine | 145.0 µs | 12 Bytes (Vars) |
MODE_RAINBOW state requires floating-point or lookup-table math to generate smooth color transitions. On the 20MHz ATmega4809, this takes roughly 145µs per loop iteration. If you were running this on a 16MHz ATmega328P without hardware multiplication, that same math could block the main loop for over 300µs, causing button-read latency.
Hardware BOM & Pin Mapping
Precision in hardware selection prevents ghosting and PWM flicker. Do not use a common-anode RGB LED for this specific code block without inverting the PWM logic in the switch cases.
Bill of Materials (BOM)
- Microcontroller: Arduino Nano Every (ABX00028) - ~$11.50
- RGB LED: Lite-On LTST-SP1WTJ (Common Cathode, 5mm Diffused) - ~$0.15
- Potentiometer: Bourns PTV09A-4020F-B103 (10kΩ Linear, Knurled Shaft) - ~$0.80
- Pushbutton: Omron B3F-1000 (Momentary, 12x12mm, 160gf) - ~$0.20
- Resistors: 3x 220Ω 1/4W Carbon Film (Yageo CFR-25JR-52-220R) for LED current limiting.
- Pull-up Resistor: 1x 10kΩ for the button input (or use internal
INPUT_PULLUP).
Pin Mapping Table
| Component | Arduino Nano Every Pin | ATmega4809 Port/Alt | Notes |
|---|---|---|---|
| Red LED Anode | D9 | PORTB / TCA0 WO0 | Hardware PWM capable |
| Green LED Anode | D10 | PORTB / TCA0 WO1 | Hardware PWM capable |
| Blue LED Anode | D11 | PORTB / TCA0 WO2 | Hardware PWM capable |
| Potentiometer Wiper | A0 | PORTD / ADC0 AIN0 | 10-bit ADC resolution |
| Pushbutton Output | D2 | PORTA / EXTINT2 | Configured with internal pull-up |
The Switch Statement Arduino Example: Complete Code
The following code implements a non-blocking state machine. It uses millis() for button debouncing rather than delay(), ensuring the rainbow calculation loop is never stalled by mechanical switch bounce. According to the official Arduino switch...case reference, using an enum paired with a switch block allows the compiler to optimize the branching logic far better than chained if statements.
#include <math.h>
// --- PIN DEFINITIONS ---
#define PIN_LED_R 9
#define PIN_LED_G 10
#define PIN_LED_B 11
#define PIN_POT A0
#define PIN_BUTTON 2
// --- STATE MACHINE ENUM ---
enum RGBState {
MODE_OFF,
MODE_RED,
MODE_GREEN,
MODE_BLUE,
MODE_RAINBOW
};
RGBState currentState = MODE_OFF;
// --- DEBOUNCE VARIABLES ---
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // 50ms debounce
// --- RAINBOW MATH VARIABLES ---
float hue = 0.0;
void setup() {
Serial.begin(115200);
pinMode(PIN_LED_R, OUTPUT);
pinMode(PIN_LED_G, OUTPUT);
pinMode(PIN_LED_B, OUTPUT);
pinMode(PIN_POT, INPUT);
pinMode(PIN_BUTTON, INPUT_PULLUP); // Uses internal 20k-50k pull-up
// Ensure all LEDs are off on boot
analogWrite(PIN_LED_R, 0);
analogWrite(PIN_LED_G, 0);
analogWrite(PIN_LED_B, 0);
Serial.println(F("RGB State Machine Initialized."));
}
void loop() {
// 1. Read and Debounce Button
bool currentButtonState = digitalRead(PIN_BUTTON);
if (currentButtonState != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
// If the button state has changed and it is currently LOW (pressed)
if (currentButtonState == LOW && lastButtonState == HIGH) {
advanceState();
}
}
lastButtonState = currentButtonState;
// 2. Read Brightness Potentiometer (0-1023 mapped to 0-255)
int rawPot = analogRead(PIN_POT);
float brightnessMultiplier = map(rawPot, 0, 1023, 0, 255) / 255.0;
// 3. Execute State Machine
switch (currentState) {
case MODE_OFF:
analogWrite(PIN_LED_R, 0);
analogWrite(PIN_LED_G, 0);
analogWrite(PIN_LED_B, 0);
break;
case MODE_RED:
analogWrite(PIN_LED_R, 255 * brightnessMultiplier);
analogWrite(PIN_LED_G, 0);
analogWrite(PIN_LED_B, 0);
break;
case MODE_GREEN:
analogWrite(PIN_LED_R, 0);
analogWrite(PIN_LED_G, 255 * brightnessMultiplier);
analogWrite(PIN_LED_B, 0);
break;
case MODE_BLUE:
analogWrite(PIN_LED_R, 0);
analogWrite(PIN_LED_G, 0);
analogWrite(PIN_LED_B, 255 * brightnessMultiplier);
break;
case MODE_RAINBOW:
// Generate smooth RGB values using sine waves offset by 120 degrees
hue += 0.02;
if (hue > 6.28318) hue = 0.0; // 2 * PI
int r = (sin(hue) * 127.5) + 127.5;
int g = (sin(hue + 2.094) * 127.5) + 127.5;
int b = (sin(hue + 4.188) * 127.5) + 127.5;
analogWrite(PIN_LED_R, r * brightnessMultiplier);
analogWrite(PIN_LED_G, g * brightnessMultiplier);
analogWrite(PIN_LED_B, b * brightnessMultiplier);
break;
default:
// Failsafe: Reset to OFF if memory corruption alters the enum state
currentState = MODE_OFF;
Serial.println(F("Error: Unknown state. Resetting to OFF."));
break;
}
}
void advanceState() {
// Cycle through the enum values
int nextState = currentState + 1;
if (nextState > MODE_RAINBOW) {
nextState = MODE_OFF;
}
currentState = (RGBState)nextState;
Serial.print(F("State changed to: "));
Serial.println(currentState);
}
Debugging: Handling Switch Fall-Through and Scope Errors
When scaling state machines, the C++ compiler is your best debugging tool. If you compile with strict warnings enabled (standard in modern Arduino IDE 2.x and PlatformIO), you will likely encounter this exact error string when modifying the code:
error: enumeration value 'MODE_RAINBOW' not handled in switch [-Werror=switch]
According to the GCC Warning Options documentation, the -Wswitch flag warns whenever a switch statement has an index of an enumerated type and lacks a case for one or more of the named codes of that enumeration. This is a critical safety feature in embedded systems.
The First 3 Things to Check When It Fails
- Missing
break;Statements (Fall-Through): If your RGB LED cycles to Red, but immediately flashes Green and Blue before settling, you forgot thebreak;at the end of theMODE_REDcase. Withoutbreak;, C++ executes the next sequential case block automatically. Always terminate cases withbreak;orreturn;unless intentional fall-through is explicitly documented. - Enum-to-Switch Mismatch: If you add
MODE_WHITEto yourenumblock but forget to add a correspondingcase MODE_WHITE:inside theswitchblock, the compiler will throw the-Wswitcherror. Always define yourswitchcases immediately after updating theenum. - Uninitialized State Variables: If
currentStateis not initialized at the top of the sketch (e.g.,RGBState currentState = MODE_OFF;), it may default to0. If yourenumstarts at1, theswitchwill bypass all cases and hit thedefault:block, causing erratic boot behavior. Always explicitly initialize state variables.
switch statement to control high-voltage AC relays without hardware interlocks and software watchdog timers. A software crash in the default case could leave a relay permanently energized. For mains voltage control, use zero-crossing SSRs and hardware-enforced timeouts.
Extending and Simplifying the Build
As your project grows, hardcoding PWM values inside the switch block becomes tedious. Here are two professional strategies to modify this architecture.
How to Extend: Adding Non-Volatile Memory
If you want the device to remember its last state after a power cycle, extend the build by integrating the Nano Every's EEPROM. Add #include <EEPROM.h> to the top of the sketch. Inside the advanceState() function, write the new state to address 0: EEPROM.update(0, currentState);. In the setup() function, read it back: currentState = (RGBState)EEPROM.read(0);. This adds roughly 400 bytes to your flash footprint but drastically improves user experience.
How to Simplify: Array-Based State Mapping
If your states only change static PWM values (no complex math like the rainbow mode), you can eliminate the switch statement entirely for those branches. Define a 2D array mapping states to PWM values:
const uint8_t pwmMap[4][3] = {
{0, 0, 0}, // MODE_OFF
{255, 0, 0}, // MODE_RED
{0, 255, 0}, // MODE_GREEN
{0, 0, 255} // MODE_BLUE
};
Then, replace the first four cases in your switch block with a single lookup: analogWrite(PIN_LED_R, pwmMap[currentState][0] * brightnessMultiplier);. This reduces code size and execution time, reserving the switch statement strictly for states that require unique procedural logic, like the sine-wave calculations in MODE_RAINBOW.






