Issues w/ LEDs Controlled by Rotary Encoder

Overview
The project is to have a user set the behavior of LEDs from five different "modes" using a rotary encoder.

Issue(s)
There are two issues: (1) the LEDs flicker at three "transition points" when the user changes the color of the LEDs (Mode 1) and (2) a single LED at the beginning of the LED strands remains lit up white while the user "moves" the red LED along the strand (Mode 3 & 4).

Topic
How can the project be modified so the LEDs no longer flicker so the colors change smoothly when the user uses the encoder and so the single LED at the beginning of the LED strands is not lit up while the user "moves" the red LED along the strand?

Hardware
2x WS2801 LED strands of 35, 12mm
Arduino Mega 2560
EN11 Rotary Encoder
5V = 10A 50W Adapter
Connector Terminal
Jumper Wires

Code

#include <Encoder.h>
#include <Adafruit_WS2801.h>

const int NUM_LEDS = 50;            // number of leds in strand
const int DATA_PIN = 11;            // data pin for WS2801 strand
const int CLOCK_PIN = 13;           // clock pin for WS2801 strand
const int BRIGHTNESS = 255;         // brightness of all leds
const int WHEEL_SIZE = 256;         // how many entries in the color wheel
const boolean MOVE_LIGHT = false;   // move one light around or keep all lights on
const int ENCODER_PIN_1 = 2;
const int ENCODER_PIN_2 = 3;
const int ENCODER_BUTTON = 4;        // Button pin for the encoder
const int MIN_LED_INDEX = 1;       // Define the range of LEDs
const int MAX_LED_INDEX = 50;
bool rainbowDisplayed = false; // Global variable to track if rainbow pattern has been displayed

Adafruit_WS2801 strip = Adafruit_WS2801(NUM_LEDS, DATA_PIN, CLOCK_PIN);
Encoder encoder(ENCODER_PIN_1, ENCODER_PIN_2);
int mode = 0;
long lastPush = 0;
int autoPosition = 0;
int position = 0;                  // Current position of the LED
int encoderPos = encoder.read();   // Read encoder position
int ledIndex = map(encoderPos, 0, 1023, MIN_LED_INDEX, MAX_LED_INDEX);   // Map encoder position to LED index

// Define a threshold for encoder position change
const int ENCODER_THRESHOLD = 5; // Adjust as needed

// Variable to store the previous encoder position
int prevEncoderPos = 0;

// Function prototype for interpolateColor
uint32_t interpolateColor(uint32_t startColor, uint32_t endColor, int step, int totalSteps);

void initializeToBlack() {
  for (int i =0; i < NUM_LEDS; i++) {
   strip.setPixelColor(i, 0);
  }  
}

void setup() {
    
    Serial.begin(9600);
  // Print a test message to the serial monitor
  //Serial.println("Test message");
    
    
    pinMode(ENCODER_BUTTON, INPUT);
    digitalWrite(ENCODER_BUTTON, HIGH); //turn pullup resistor on
    
    strip.begin();
    initializeToBlack();
    strip.show();    
}

long normalize(long value, long radix) {
  long rval = value % radix;
  if (rval < 0) return radix + rval;
  else return rval; 
}

void loop() {
    // Read button state
    int buttonState = digitalRead(ENCODER_BUTTON);

    // Change mode only when the button is pressed
    if (buttonState == LOW && millis() - lastPush > 500) {
        // Increment mode cyclically
        mode = (mode + 1) % 5;
        lastPush = millis(); // Record the time of the last button press

        // Reset any mode-specific flags or variables here
        rainbowDisplayed = false; // Reset the flag for rainbow pattern

        // If the mode is 2 (display rainbow pattern), set the flag to true
        if (mode == 2) {
            displayRainbowPattern(); // Display the rainbow pattern
            rainbowDisplayed = true; // Set the flag to true after the rainbow pattern is displayed
        }
    }

    // Read encoder position
    int encoderPos = encoder.read();

// Check if the absolute difference between current and previous positions exceeds the threshold
    if (abs(encoderPos - prevEncoderPos) >= ENCODER_THRESHOLD) {
        // Update LED color only if the threshold is exceeded
        // Your LED color update code here
        // For example:
        // setLedColorBasedOnEncoder();

        // Update prevEncoderPos to current position
        prevEncoderPos = encoderPos;
    }
  
    // Turn off all LEDs
    for (int i = 0; i < NUM_LEDS; i++) {
        strip.setPixelColor(i, 0, 0, 0); // Set LED color to black
    }

    // Set the color of the LED corresponding to the encoder position
    strip.setPixelColor(ledIndex, 255, 255, 255); // Set LED color to white

    /* // Print current mode
    Serial.print("Mode: ");
    Serial.println(mode);

    // Add debug statement
    Serial.println("Calling displayRainbowPattern()"); */
    
    // Handle different modes
    switch (mode) {
        case 0:
            // Mode 0: Turn off all LEDs
            initializeToBlack();
            break;
        case 1:
            // Mode 1: Control LED color with rotary encoder
            // Implement code to set LED color based on encoder position
            // For example:
             setLedColorBasedOnEncoder();
            break;
        case 2:
            // Mode 2: Display rainbow pattern on all LEDs
            displayRainbowPattern();
            break;
        case 3:
            // Mode 3: Move one LED along the strip
            // Implement code to move LED along the strip
            // For example:
             moveSingleLED();
            break;
        case 4:
            // Mode 4: Move multiple LEDs along the strip
            // Implement code to move multiple LEDs along the strip
            // For example:
            // moveMultipleLEDs();
            break;
        default:
            // Handle unknown mode
            break;
    }
    
    // Show the updated LED colors
    strip.show();
    
    /* // Print out the encoder reading
    Serial.print("Encoder: ");
    Serial.println(encoder.read());

    // Print out the state of the encoder button
    Serial.print("Button state: ");
    Serial.println(digitalRead(ENCODER_BUTTON));

    // Delay to avoid flooding the serial monitor
    delay(500); // Adjust the delay as needed */
    
    // Remaining loop code remains unchanged
}

// Function declaration for colorWheel()
void colorWheel(byte wheelPos, uint16_t pixelIndex) {
    if (wheelPos < 85) {
        strip.setPixelColor(pixelIndex, wheelPos * 3, 255 - wheelPos * 3, 0);
    } else if (wheelPos < 170) {
        wheelPos -= 85;
        strip.setPixelColor(pixelIndex, 255 - wheelPos * 3, 0, wheelPos * 3);
    } else {
        wheelPos -= 170;
        strip.setPixelColor(pixelIndex, 0, wheelPos * 3, 255 - wheelPos * 3);
    }
}

// fill the dots one after the other with said color
// good for testing purposes
void colorWipe(uint32_t c, uint8_t wait) {
  int i;
  
  for (i=0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, c); // Set color for each pixel
  }
  strip.show(); // Show the updated LED colors
  delay(wait); // Delay for the specified time
}

uint32_t hsvToRgb(uint8_t hue, uint8_t saturation, uint8_t value) {
    byte r, g, b;
    unsigned char region, remainder, p, q, t;

    if (saturation == 0) {
        r = g = b = value;
        return ((uint32_t)r << 16) | ((uint32_t)g <<  8) | b;
    }

    region = hue / 43;
    remainder = (hue - (region * 43)) * 6;

    p = (value * (255 - saturation)) >> 8;
    q = (value * (255 - ((saturation * remainder) >> 8))) >> 8;
    t = (value * (255 - ((saturation * (255 - remainder)) >> 8))) >> 8;

    switch (region) {
        case 0:
            r = value; g = t; b = p;
            break;
        case 1:
            r = q; g = value; b = p;
            break;
        case 2:
            r = p; g = value; b = t;
            break;
        case 3:
            r = p; g = q; b = value;
            break;
        case 4:
            r = t; g = p; b = value;
            break;
        default:
            r = value; g = p; b = q;
            break;
    }

    return ((uint32_t)r << 16) | ((uint32_t)g <<  8) | b;
}

// Function definition for displayRainbowPattern()
void displayRainbowPattern() {
    for (int i = 0; i < NUM_LEDS; i++) {
        // Calculate color based on position in the rainbow
        long colorValue = normalize(i * 5, WHEEL_SIZE);
        // Set LED color
        colorWheel(colorValue, i); // Corrected line
        // Debugging output
        /* Serial.print("Setting LED ");
         Serial.print(i);
         Serial.print(" to color (R, G, B): ");
         Serial.print(strip.getPixelColor(i) >> 16 & 0xFF); // Red
         Serial.print(", ");
         Serial.print(strip.getPixelColor(i) >> 8 & 0xFF);  // Green
         Serial.print(", ");
         Serial.println(strip.getPixelColor(i) & 0xFF);      // Blue */
    }
}

// Function definition for setLedColorBasedOnEncoder()
void setLedColorBasedOnEncoder() {
    // Read encoder position
    int encoderPos = encoder.read();

    // Map encoder position to hue (0-255)
    byte hue = map(encoderPos, 0, 1023, 0, 255);

    // Convert HSV to RGB for starting and ending colors
    uint32_t startColor = hsvToRgb(hue, 255, 255);
    uint32_t endColor = hsvToRgb(hue + 1, 255, 255); // Increment hue slightly for smooth transition
    uint32_t color = hsvToRgb(hue, 255, 255);

    // Smoothly transition from startColor to endColor
    for (int i = 0; i < NUM_LEDS; i++) {
        // Calculate intermediate color
        uint32_t intermediateColor = interpolateColor(startColor, endColor, i, NUM_LEDS);

        // Set LED color based on intermediate color
        strip.setPixelColor(i, intermediateColor);
    }
}

void moveSingleLED() {
    // Read encoder position
    int encoderDelta = encoder.read(); // Read the encoder position change

    // Calculate the new position for the LED based on the encoder movement
    position += encoderDelta;

    // Constrain the position within the LED strip boundaries
    position = constrain(position, 0, NUM_LEDS - 1);

    // Illuminate the LED at the new position
    strip.setPixelColor(position, 255, 0, 0);

    // Update the color of the second LED
    if (position > 0 && position < NUM_LEDS) {
        // Read the color of the first LED
        uint32_t color = strip.getPixelColor(position - 1);
        // Set the color of the second LED to match the color of the first LED
        strip.setPixelColor(position - 1, color);
    } else if (position == 0) {
        // If the second LED is at the start of the strip, turn it off
        strip.setPixelColor(position, 0, 0, 0);
    }

    // Show the updated LED colors
    strip.show();

    // Reset encoder position to zero after each movement
    encoder.write(0);
}

// Function definition for interpolateColor()
uint32_t interpolateColor(uint32_t startColor, uint32_t endColor, int step, int totalSteps) {
    // Extract RGB components of startColor
    byte startRed = (startColor >> 16) & 0xFF;
    byte startGreen = (startColor >> 8) & 0xFF;
    byte startBlue = startColor & 0xFF;

    // Extract RGB components of endColor
    byte endRed = (endColor >> 16) & 0xFF;
    byte endGreen = (endColor >> 8) & 0xFF;
    byte endBlue = endColor & 0xFF;

    // Interpolate RGB components
    byte interpolatedRed = startRed + ((endRed - startRed) * step / totalSteps);
    byte interpolatedGreen = startGreen + ((endGreen - startGreen) * step / totalSteps);
    byte interpolatedBlue = startBlue + ((endBlue - startBlue) * step / totalSteps);

    // Combine interpolated RGB components into a single color
    return ((uint32_t)interpolatedRed << 16) | ((uint32_t)interpolatedGreen << 8) | interpolatedBlue;
}

You are making the typical mistake most beginners use and trying to do too much at once.

Just start off with a sketch that uses the rotary encoder only and get that working first.
Then when it is try and add the functionality you want with the LED strip.

Note when driving an LED strip, during the data transfer between your processor and your strip (typically using the "show" call, interrupts are disabled. This will cause in effect a period of time where nothing else, including your rotary encoders will not work correctly. This also applies to things like serial reads and servo control, where it is possible to miss data.

One way round this is to get a chip like this:-

Also consider using the dot star type of strip as they can be used without causing this delay. They are more expensive and take two pins per strip to control, but will get round this delay problem.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.