# Controlling LEDs with ESP32-Bit-Pirate GPIO: Complete Guide to GPIO LED Control

> Master ESP32-Bit-Pirate GPIO to control addressable LEDs like Neopixels and WS2812. This guide offers a complete walkthrough for your ESP32-S3 projects. Learn fastLED control via CLI.

- Repository: [Geo/ESP32-Bit-Pirate](https://github.com/geo-tp/ESP32-Bit-Pirate)
- Tags: how-to-guide
- Published: 2026-08-02

---

**The ESP32‑Bit‑Pirate firmware drives addressable LEDs (Neopixels, WS2812, APA102) through ESP32‑S3 GPIO pins via a command‑line interface that wraps the FastLED library.**

This article explains how the **geo‑tp/ESP32‑Bit‑Pirate** repository implements LED control. Whether you're working with WS2812 strips or APA102 dot‑stars, the firmware's layered architecture separates command parsing from hardware abstraction, making it straightforward to configure, set colors, and run animations through simple terminal commands.

## Architecture Overview

The LED subsystem is split into three distinct layers:

| Layer | File | Purpose |
|-------|------|---------|
| **`LedController`** | [[`src/Controllers/LedController.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Controllers/LedController.cpp)](https://github.com/geo‑tp/ESP32‑Bit‑Pirate/blob/pioarduino/src/Controllers/LedController.cpp) | Parses CLI commands and manages user interaction |
| **`ILedService`** | [[`src/Interfaces/ILedService.h`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Interfaces/ILedService.h)](https://github.com/geo‑tp/ESP32‑Bit‑Pirate/blob/pioarduino/src/Interfaces/ILedService.h) | Abstracts FastLED operations into a testable interface |
| **FastLED** | [`Vendors/FastLED.h`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/Vendors/FastLED.h) | Generates precise PWM timing for LED protocols |

This separation lets you add new LED protocols or animations without modifying command‑parsing logic in `LedController`.

## Configuring GPIO Pins for LED Output

### Fixed Pin Assignment

The **GPIO pins are hardcoded at compile time**. FastLED requires `LED_DATA_PIN` and `LED_CLOCK_PIN` on construction, so you must set these in your build configuration before flashing.

```cpp
// These defines are typically set in platformio.ini or a config header
#define LED_DATA_PIN  18   // Main data signal (all protocols)
#define LED_CLOCK_PIN 19   // Clock signal (SPI-based protocols only)

```

When you run `config` or `setprotocol`, the controller prints a warning that pins cannot be changed at runtime. Call `LedController::handleConfig()` to proceed with protocol selection:

```

> config
LED data pin and clock pin cannot be changed at runtime.
Select protocol:
1. WS2812 / Neopixel (single-wire)
2. APA102 / DotStar (SPI)
...

```

### Protocol Selection

The controller queries `ILedService::getSingleWireProtocols()` and `ILedService::getSpiChipsets()` to build the menu. Once selected, `ILedService::configure()` invokes FastLED's `addLeds<...>` template—see the protocol switch in [`src/Services/LedService.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Services/LedService.cpp).

## LED Control Commands

All commands route through `LedController::handleCommand()`. The controller ensures configuration via `ensureConfigured()` before executing any LED operation.

### Fill: Set All LEDs to One Color

```cpp
// Hex/HTML notation
terminalInput.receive("fill #FF00FF");

// Decimal RGB triple
terminalInput.receive("fill 255 0 255");

```

`parseFlexibleColor()` handles the conversion, then `ILedService::fill()` writes to the entire strip.

### Set: Control Individual LEDs

```cpp
// Set LED #5 to green (0-indexed)
terminalInput.receive("set 5 0 255 0");

// Hex color for single LED
terminalInput.receive("set 0x0A #00FF00");

```

The controller parses the index (decimal or hex prefix `0x`) and delegates to `ILedService::set()`.

### Reset: Clear LEDs

```cpp
// Turn off all LEDs
terminalInput.receive("reset");

// Clear specific LED (set to black)
terminalInput.receive("reset 5");

```

Reset without arguments calls `resetLeds()`; with an index, it invokes `set(index, CRGB::Black)`.

## Color Parsing in ESP32-Bit-Pirate

`LedController::parseFlexibleColor()` implements three parsing strategies:

| Format | Example | Processing |
|--------|---------|------------|
| Three decimals | `255 128 0` | Direct `CRGB(r, g, b)` construction |
| Hex/HTML | `#FF00FF` or `0xFF00FF` | `ILedService::parseHtmlColor()` |
| Named colors | `red`, `blue`, `green`, `white`, `black` | `ILedService::parseStringColor()` |

This flexibility lets you use whatever notation fits your workflow without modifying source code.

## Animations and Effects

### Running Built-in Animations

```cpp
// Chase animation runs until Enter key
terminalInput.receive("chase");

// Rainbow cycle
terminalInput.receive("rainbow");

// Blink pattern
terminalInput.receive("blink");

```

The controller validates the animation name against `ILedService::getSupportedAnimations()`, then loops `ILedService::runAnimation()` until user input terminates the effect.

### Animation Loop Safety

The `LedController` blocks on `runAnimation()` calls, checking for serial input between frames. This cooperative multitasking prevents watchdog resets while maintaining responsive LED output.

## Runtime Configuration State

The controller tracks configuration with a `bool configured` flag. `LedController::ensureConfigured()` at [`LedController.cpp#L18-33`](https://github.com/geo‑tp/ESP32‑Bit‑Pirate/blob/pioarduino/src/Controllers/LedController.cpp#L18) handles lazy initialization:

```cpp
// Pseudocode of the guard pattern
if (!configured) {
    handleConfig();  // Interactive setup on first use
}
// Subsequent calls skip to stored settings

```

This means you can type `fill red` immediately after boot—the firmware prompts for configuration once, then remembers your choice.

## Key Implementation Files

| File | Role in LED Control |
|------|---------------------|
| [[`src/Controllers/LedController.h`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Controllers/LedController.h)](https://github.com/geo‑tp/ESP32‑Bit‑Pirate/blob/pioarduino/src/Controllers/LedController.h) | Class declaration with command handlers |
| [[`src/Controllers/LedController.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Controllers/LedController.cpp)](https://github.com/geo‑tp/ESP32‑Bit‑Pirate/blob/pioarduino/src/Controllers/LedController.cpp#L89) | `handleConfig()` implementation and command routing |
| [[`src/Interfaces/ILedService.h`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Interfaces/ILedService.h)](https://github.com/geo‑tp/ESP32‑Bit‑Pirate/blob/pioarduino/src/Interfaces/ILedService.h) | Pure virtual interface for LED operations |
| [`src/Services/LedService.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Services/LedService.cpp) | FastLED integration (concrete implementation) |
| [[`src/Shells/HelpShell.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Shells/HelpShell.cpp)](https://github.com/geo‑tp/ESP32‑Bit‑Pirate/blob/pioarduino/src/Shells/HelpShell.cpp) | Help text for LED commands |
| [[`src/Views/WebTerminalView.h`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Views/WebTerminalView.h)](https://github.com/geo‑tp/ESP32‑Bit‑Pirate/blob/pioarduino/src/Views/WebTerminalView.h) | Serial and web CLI output |

## Complete Example Session

```cpp
#include "Controllers/LedController.h"

// Setup (typically in main.cpp)
LedController ledController;
TerminalInput terminalInput;

void setup() {
    // Initialize serial or web terminal
    terminalInput.begin();
}

void loop() {
    // Process incoming commands
    if (terminalInput.available()) {
        ledController.handleCommand(terminalInput.readLine());
    }
}

// Example interaction flow:
// > config          <-- First use triggers interactive setup
// > fill #FF00FF    <-- All LEDs magenta
// > set 0 255 0 0   <-- First LED red
// > chase           <-- Running chase effect
// [press Enter]     <-- Animation stops
// > reset           <-- All off

```

## Summary

- **Controlling LEDs with ESP32‑Bit‑Pirate GPIO** requires compile‑time pin configuration via `LED_DATA_PIN` and `LED_CLOCK_PIN` defines
- The **`LedController`** class in [`src/Controllers/LedController.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Controllers/LedController.cpp) handles all CLI interaction with lazy initialization through `ensureConfigured()`
- **`ILedService`** abstracts FastLED, supporting WS2812, APA102, and other protocols via `configure()` and protocol‑specific templates
- Colors parse flexibly as hex (`#RRGGBB`), decimals (`r g b`), or names (`red`, `blue`, `white`)
- Animations run cooperatively until user input, with validation against `getSupportedAnimations()`

## Frequently Asked Questions

### What GPIO pins does ESP32-Bit-Pirate use for LED control?

The **data pin and clock pin are fixed at compile time** through `LED_DATA_PIN` and `LED_CLOCK_PIN` defines. FastLED requires these constants for template instantiation, so you must rebuild and reflash to change pins. The firmware warns users that runtime pin changes are impossible when running `config` or `setprotocol` commands.

### Which LED protocols are supported?

The firmware supports both **single‑wire protocols** (WS2812, Neopixel, SK6812) and **SPI protocols** (APA102, DotStar, WS2801) through FastLED. The exact list appears interactively when you run `setprotocol`, populated from `ILedService::getSingleWireProtocols()` and `ILedService::getSpiChipsets()`. APA102 requires both data and clock pins; WS2812 uses data only.

### Why was the protocol scan feature disabled?

The **`scan` command** that cycled through protocols to identify working ones was disabled due to **ESP32‑S3 RMT channel constraints**. The `handleScan()` method remains in [`LedController.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/LedController.cpp) but is commented out in `handleCommand()`. Manual protocol selection via `setprotocol` is the recommended approach for identifying compatible LED hardware.

### How do I add a custom animation?

Animations implement the `ILedService` interface. Define your animation logic in [`src/Services/LedService.cpp`](https://github.com/geo-tp/ESP32-Bit-Pirate/blob/main/src/Services/LedService.cpp), add the name to `getSupportedAnimations()`, and implement the frame‑generation in `runAnimation()`. The `LedController` automatically exposes new animations through the CLI without modification—validate with `LedController::getSupportedAnimations()` and invoke via `LedController::runAnimation()`.