cookbook/advanced/optimization.md
Difficulty Level: ⭐⭐⭐ Advanced Time to Complete: 50-60 minutes Prerequisites:
You'll Learn:
Tips and techniques for creating smooth, efficient LED animations. Optimizing your code ensures high frame rates and responsive effects.
Avoid recalculating constants and expensive operations every frame.
void slowEffect() {
for (int i = 0; i < NUM_LEDS; i++) {
float angle = (i * 360.0) / NUM_LEDS; // Expensive floating point math!
float radians = angle * PI / 180.0;
// ... more calculations
}
}
uint8_t ledAngles[NUM_LEDS];
void setup() {
// Calculate once at startup
for (int i = 0; i < NUM_LEDS; i++) {
ledAngles[i] = (i * 255) / NUM_LEDS;
}
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
}
void fastEffect() {
for (int i = 0; i < NUM_LEDS; i++) {
// Use precalculated value
uint8_t brightness = sin8(ledAngles[i] + millis() / 10);
leds[i] = CHSV(160, 255, brightness);
}
}
Floating-point operations are slow on microcontrollers. Use integer math and bit shifts when possible.
float result = (value * 3.14159) / 2.0;
// Bit shift for division by powers of 2
uint8_t result = (value * 3) >> 1; // Divide by 2 using bit shift
// Use FastLED's scale8 for scaling
uint8_t scaled = scale8(value, 128); // Multiply by 128/255
// Integer approximations
uint8_t approxPi = (value * 157) >> 5; // Approximate × π (157/50 ≈ π)
Don't update LEDs faster than necessary. Most effects look smooth at 30-60 FPS.
void loop() {
EVERY_N_MILLISECONDS(20) { // Max 50 FPS
updateLEDs();
FastLED.show();
}
// Other non-blocking tasks can run here
}
const uint32_t FRAME_TIME = 20; // 50 FPS
uint32_t lastFrame = 0;
void loop() {
uint32_t now = millis();
if (now - lastFrame >= FRAME_TIME) {
lastFrame = now;
updateLEDs();
FastLED.show();
}
// Other tasks
}
void loop() {
updateLEDs();
FastLED.show();
// Monitor and adjust
EVERY_N_SECONDS(5) {
uint16_t fps = FastLED.getFPS();
if (fps < 30) {
Serial.println("WARNING: Low frame rate detected!");
}
}
}
Limit power consumption to stay within your power supply's capacity.
void setup() {
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
// Limit total power draw
FastLED.setMaxPowerInVoltsAndMilliamps(5, 2000); // 5V, 2A max
}
This automatically scales brightness to prevent exceeding your power budget.
void setup() {
FastLED.setBrightness(50); // Global brightness limit (0-255)
}
// Or per-effect:
void conservativeBrightness() {
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CHSV(hue, 255, 128); // Max 50% brightness
}
}
Use FastLED's optimized functions instead of manual loops.
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::Black;
}
fill_solid(leds, NUM_LEDS, CRGB::Black);
// Or even faster:
FastLED.clear();
// Fill with rainbow
fill_rainbow(leds, NUM_LEDS, startHue, deltaHue);
// Fade all LEDs
fadeToBlackBy(leds, NUM_LEDS, fadeAmount);
// Blur for smoothing
blur1d(leds, NUM_LEDS, blurAmount);
Dithering reduces visible banding in color gradients but costs performance.
void setup() {
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
// Balance quality vs speed
FastLED.setDither(BINARY_DITHER); // Better quality (default)
// FastLED.setDither(DISABLE_DITHER); // Faster, slight quality loss
}
For most effects, BINARY_DITHER is fine. Disable only if you need maximum frame rate.
Each LED uses 3 bytes of RAM:
On memory-constrained devices (Arduino Uno: 2KB RAM), consider:
// Bad: Dynamic allocation in loop
void inefficient() {
CRGB* tempBuffer = new CRGB[NUM_LEDS]; // Slow, fragments memory
// ...
delete[] tempBuffer;
}
// Good: Static allocation
CRGB tempBuffer[NUM_LEDS];
void efficient() {
// Use static buffer
}
// If you only need 0-255, use uint8_t
uint8_t brightness = 128;
// Not:
int brightness = 128; // Uses 2 bytes instead of 1
// Slower: Recalculate noise for every LED every frame
void slowNoise() {
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t noise = inoise8(i * 100, i * 100, millis());
leds[i] = CHSV(noise, 255, 255);
}
}
// Faster: Use simpler parameters
void fastNoise() {
uint16_t time = millis() / 10;
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t noise = inoise8(i * 50, time); // Fewer calculations
leds[i] = CHSV(noise, 255, 255);
}
}
// Precalculate palette index offsets
uint8_t paletteOffsets[NUM_LEDS];
void setup() {
for (int i = 0; i < NUM_LEDS; i++) {
paletteOffsets[i] = (i * 256) / NUM_LEDS;
}
}
void paletteEffect() {
static uint8_t baseIndex = 0;
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t index = baseIndex + paletteOffsets[i];
leds[i] = ColorFromPalette(palette, index, 255, LINEARBLEND);
}
baseIndex++;
}
void loop() {
updatePattern();
FastLED.show();
EVERY_N_SECONDS(1) {
uint16_t fps = FastLED.getFPS();
Serial.print("FPS: ");
Serial.println(fps);
if (fps < 30) {
Serial.println("WARNING: Low frame rate!");
}
}
}
void profileFunction() {
uint32_t startTime = micros();
// Function to profile
updateComplexEffect();
uint32_t duration = micros() - startTime;
Serial.print("Effect took: ");
Serial.print(duration);
Serial.println(" microseconds");
}
void findBottleneck() {
uint32_t t1 = micros();
part1();
uint32_t t2 = micros();
part2();
uint32_t t3 = micros();
part3();
uint32_t t4 = micros();
Serial.print("Part 1: "); Serial.println(t2 - t1);
Serial.print("Part 2: "); Serial.println(t3 - t2);
Serial.print("Part 3: "); Serial.println(t4 - t3);
}
// BAD: Serial in tight loop
for (int i = 0; i < NUM_LEDS; i++) {
Serial.println(i); // Very slow!
leds[i] = color;
}
// GOOD: Print occasionally
EVERY_N_SECONDS(1) {
Serial.println("Status: OK");
}
// BAD: Blocking delay
void loop() {
updateLEDs();
FastLED.show();
delay(1000); // Nothing can happen during delay
}
// GOOD: Non-blocking timing
EVERY_N_MILLISECONDS(1000) {
updateLEDs();
}
// If only a few LEDs change, don't recalculate all
// Instead, fade existing and add new:
fadeToBlackBy(leds, NUM_LEDS, 10);
leds[newPos] = CRGB::White; // Only set changed LED
Next Steps: