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

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/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/pioarduino/src/Interfaces/ILedService.h) Abstracts FastLED operations into a testable interface
FastLED 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.

// 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.

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

// 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

// 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

// 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

// 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 handles lazy initialization:

// 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/pioarduino/src/Controllers/LedController.h) Class declaration with command handlers
[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/pioarduino/src/Interfaces/ILedService.h) Pure virtual interface for LED operations
src/Services/LedService.cpp FastLED integration (concrete implementation)
[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/pioarduino/src/Views/WebTerminalView.h) Serial and web CLI output

Complete Example Session

#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 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 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, 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().

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →