Most basic 'push button LED Arduino' tutorials stop at blinking a 5mm through-hole LED via a 220Ω resistor. But when you scale that GPIO logic to drive real architectural lighting—like 24V COB LED strips or 120V AC recessed downlights—the physics of the circuit change entirely. You are no longer just switching logic levels; you are managing inrush currents, power factor, and high-frequency PWM dimming.

This guide bridges the gap between basic microcontroller logic and real-world electrical lighting circuits, giving you the exact math, component choices, and code needed to build a reliable, flicker-free push-button lighting controller.

Sizing the Driver and Dimmer for Arduino-Switched Loads

Before writing a single line of code, you must match your LED fixtures to the correct driver and dimming topology. Driving LEDs without understanding efficacy and dimmer compatibility leads to strobing, overheating, and destroyed microcontrollers.

Table 1: LED Fixture Efficacy and Driver Sizing Reference
LED Fixture Type Nominal Wattage Total Lumens Efficacy (lm/W) Recommended Dimming Topology
24V COB LED Strip 14 W/m 1,250 lm/m 89 lm/W DC PWM (Logic-level MOSFET)
24V 2835 SMD Strip 9.6 W/m 1,100 lm/m 114 lm/W DC PWM or 0-10V Analog
Mains LED Downlight 12 W 900 lm 75 lm/W AC Trailing-Edge (ELV) SSR
High-Bay UFO LED 150 W 21,000 lm 140 lm/W 0-10V or DALI Driver

Dimmer Compatibility: Trailing Edge and Minimum Load

If your Arduino is switching AC mains lighting via a solid-state relay (SSR) or an AC dimmer module (like a BTA16 Triac), you must use trailing-edge (ELV) dimming. Leading-edge (TRIAC) dimmers were designed for resistive incandescent loads and will cause electronic LED drivers to buzz and fail prematurely.

The Minimum Load Trap: Most AC dimmer modules require a minimum load of 10W to 20W to keep the internal Triac latching correctly. If your Arduino switches a single 7W LED bulb, the dimmer will drop out every AC half-cycle, causing violent strobing. The Fix: Wire a 10W, 50-ohm wirewound bleeder resistor in parallel with the LED fixture to satisfy the minimum load requirement.

Which driver for your fixture count? If you are driving 1 to 4 low-voltage DC strips, a simple IRLZ44N logic-level MOSFET driven directly by the Arduino's PWM pin is sufficient. If you are driving 5 or more high-power fixtures, or running long distances, step up to a dedicated 0-10V dimmable LED driver (such as the Mean Well HLG series). You can generate the 0-10V signal by passing the Arduino's PWM output through an RC low-pass filter (10kΩ resistor + 10µF capacitor) and buffering it with an op-amp.

Circuit Impact Math: Inrush, Power Factor, and Thermal Constraints

Microcontroller GPIO pins output 3.3V or 5V at a few milliamps. To switch real lighting, we use intermediary semiconductors. But those semiconductors must survive the electrical abuse that LED drivers inflict on the circuit.

Inrush Current and Power Factor (PF)

Let's run the math for an Arduino-controlled array of ten 15W mains LED downlights.

  • Real Power: 10 × 15W = 150W.
  • Apparent Power (VA): LED drivers are highly capacitive. Assuming a Power Factor (PF) of 0.9, the apparent power is 150W / 0.9 = 166.6 VA. Your SSR must be rated for the VA, not just the Watts.
  • Inrush Current: When an LED driver powers on, it must charge its internal bulk capacitors. A typical 15W driver can pull 30A for 150µs at 230VAC. If your Arduino triggers a contactor that powers all ten drivers simultaneously, you face a potential 300A inrush spike. This will easily weld the contacts of a standard 10A mechanical relay.

The Fix: Never switch multiple large LED drivers simultaneously on a single mechanical relay. Use a zero-crossing SSR, or stagger the turn-on sequence in your Arduino code by 50ms per fixture to allow the bulk caps to charge sequentially.

Heat and Enclosure Constraints

When driving 24V DC LED strips, the MOSFET acts as a variable resistor during PWM dimming. Heat generation is calculated using P = I²R.

If you are switching a 10A COB LED strip using an IRLZ44N MOSFET with an Rds(on) of 0.022Ω at 5V Vgs, the power dissipated as heat is: 10² × 0.022 = 2.2W.

While 2.2W sounds small, a bare TO-220 package without a heatsink has a thermal resistance of roughly 62°C/W to ambient air. That 2.2W will raise the junction temperature by 136°C above ambient, likely triggering thermal shutdown or melting your breadboard. Always attach a small finned heatsink to any MOSFET passing more than 3A.

Furthermore, if you mount this circuit inside an IP65 polycarbonate enclosure for outdoor use, thermal derating applies. The ambient temperature inside a sealed box in direct sunlight can easily reach 60°C, which drops the maximum continuous current rating of your MOSFET and LED strips by 20% to 30%.

Why PWM Flicker Happens (and How to Fix It)

The default analogWrite() function on a standard Arduino Uno runs at approximately 490Hz (or 980Hz on pins 5 and 6). While this is fine for motor control, it is disastrous for architectural lighting. At 490Hz, human eyes perceive the light as steady, but smartphone cameras and security sensors will capture severe banding and strobing. Furthermore, prolonged exposure to low-frequency PWM flicker is linked to eye strain and headaches, as noted in Department of Energy SSL research.

The Fix: You must push the PWM frequency above 20kHz, which is both inaudible to human ears and fast enough to eliminate camera banding.

  • On AVR Arduinos (Uno/Nano): You must manually manipulate the Timer1 registers (TCCR1B) to change the prescaler and achieve 20kHz. Be warned: doing this will break the standard Servo.h and tone() libraries, which rely on Timer1.
  • On ESP32 (Recommended for Lighting): The ESP32 features a dedicated LED Control (LEDC) peripheral specifically designed for high-resolution, high-frequency lighting dimming. You can easily configure it for 20kHz without breaking other system timers. See the Espressif LEDC API documentation for deep-dive register details.

Complete Push Button LED Arduino Wiring and Code

Below is a production-ready implementation using an ESP32. It includes hardware debouncing, a 20kHz flicker-free PWM ramp, and staggered inrush management.

Pin Mapping and Hardware Setup

ComponentESP32 PinNotes
Momentary Push ButtonGPIO 34 (Input)Wire 100nF cap to GND for hardware debounce
MOSFET Gate (Strip 1)GPIO 16 (LEDC Ch 0)Use 100Ω gate resistor, 10kΩ pull-down
MOSFET Gate (Strip 2)GPIO 17 (LEDC Ch 1)Staggered turn-on for inrush mitigation
Logic-Level MOSFETN/AIRLZ44N or IRLB8721 (Vgs(th) < 3V)

ESP32 C++ Implementation

#include <Arduino.h>

// Pin Definitions
const int BTN_PIN = 34;
const int LED_CH1 = 16;
const int LED_CH2 = 17;

// PWM Configuration (20kHz for flicker-free lighting, 10-bit resolution)
const int PWM_FREQ = 20000;
const int PWM_RES = 10; 
const int MAX_DUTY = 1023;

// State variables
bool lightState = false;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;
int currentDuty = 0;

void setup() {
  pinMode(BTN_PIN, INPUT_PULLUP);
  
  // Configure LEDC channels for high-frequency dimming
  ledcSetup(0, PWM_FREQ, PWM_RES);
  ledcSetup(1, PWM_FREQ, PWM_RES);
  
  ledcAttachPin(LED_CH1, 0);
  ledcAttachPin(LED_CH2, 1);
  
  // Ensure lights are off at boot
  ledcWrite(0, 0);
  ledcWrite(1, 0);
}

void loop() {
  int reading = digitalRead(BTN_PIN);
  
  // Software debounce combined with hardware 100nF cap
  if (reading == LOW && (millis() - lastDebounceTime) > debounceDelay) {
    lastDebounceTime = millis();
    lightState = !lightState;
    
    if (lightState) {
      // Staggered ramp-up to mitigate inrush current on the power supply
      rampPWM(0, currentDuty, MAX_DUTY, 500);
      delay(50); // 50ms stagger for second fixture
      rampPWM(1, currentDuty, MAX_DUTY, 500);
      currentDuty = MAX_DUTY;
    } else {
      rampPWM(0, currentDuty, 0, 500);
      rampPWM(1, currentDuty, 0, 500);
      currentDuty = 0;
    }
  }
}

// Smooth PWM ramping function to prevent sudden optical spikes
void rampPWM(int channel, int startDuty, int endDuty, int durationMs) {
  int steps = 50;
  int stepDelay = durationMs / steps;
  int dutyStep = (endDuty - startDuty) / steps;
  
  for (int i = 0; i <= steps; i++) {
    ledcWrite(channel, startDuty + (dutyStep * i));
    delay(stepDelay);
  }
  ledcWrite(channel, endDuty); // Ensure exact final value
}
Bench Tip: Never rely solely on the ESP32's internal pull-up resistors for a push button located more than 2 feet away from the board. Long wires act as antennas for EMI, which will cause phantom button presses. Always add an external 4.7kΩ pull-up resistor and a 100nF ceramic capacitor directly across the button terminals at the switch location.