0

I'm working on a project that is running off of a Sparkfun Pro Micro 5V. I'm trying to accomplish a color rainbow cycle animation which will have a function when triggered to fade the rainbow to black and fade-in some white LED. I'm using a Adafruit RGBW Neopixel for this. I'm having trouble getting some code that does this as when I try to trigger my fade-out it's turning off faster than expected due to the way I wrote it. I'm hoping someone can point me to a piece of code or suggest an edit that will help.

Constraints for animation:

  • Must execute quickly (if at all possible avoid floats or other constructs that take a lot of time; relatively)
  • Be able to configure a maximum brightness set on initialization (0 - 255)
  • Must not have any "delay" or "while" loops that are designed to hold the code up.
  • When triggered by a button press the rainbow should fade to black (i.e. off) and previously off white LED should fade in.
  • When button pressed a second time white should fade out and rainbow starts or resumes.

I don't really care how the color animation works. It can cycle to/between specified colors or use whatever. To keep it simple what I tried was to have it just cycle RGB values with each 1/3 out of phase. My cycle includes a "dead" time where the color stays off for a part of the loop to keep the colors more distinct so they don't merge into white through color mixing. I specifically didn't use any sort of HSV conversion or sin/cos/whatever to calculate position in the cycle I am because those require a lot of calculation relative to incrementing a number.

Here is the code I wrote for your review. This has some servo control and the button checking and the LED in it. The bit at the very end is to prevent the pixels from updating during the time the servo pulses are being sent which prevents servo jitter due to the neoPixels.

#include <Servo.h>
#include <Adafruit_NeoPixel.h>

#define usToTicks(_us) (( clockCyclesPerMicrosecond()* _us) / 8)

class ColorCycle {
  private: int maxB;
  private: int offH;
  private: int stepD;
  private: int curD;
  private: int rVal;
  private: int gVal;
  private: int bVal;
  private: int wVal;
  private: int rHold;
  private: int gHold;
  private: int bHold;
  private: int wHold;
  private: bool rDir;
  private: bool gDir;
  private: bool bDir;
  private: bool wDir;
  private: bool showWhite;

  public: ColorCycle(int maxBrightness, int offHold, int stepDelay) {
      maxB = maxBrightness;
      offH = offHold;
      rVal = gVal = bVal = wVal = 0;
      rHold = 0;
      gHold = (maxB + offH) * .5;
      bHold = (maxB + offH);
      wHold = 0;
      rDir = gDir = bDir = true;
      wDir = false;
      stepD = stepDelay;
      curD = 0;
    }

    void moveStep() {
      moveSteps(1);
    }

    void moveSteps(int count) {
      if (count == 1) { //going to skip the delay if you are trying to move more than one step intentionally.
        if (curD > 0) {
          curD--;
          return;
        }
        curD = stepD;
      }

      for (int s = 0; s < count; s++) {
        if (showWhite) {
          whiteHold();
          wDir = true;
        } else {
          wDir = false;
        }

        //setting the direction and holds when appropriate
        if (rVal == 0)
          rDir = true;
        if (rVal == maxB) {
          rDir = false;
          rHold = offH;
        }

        if (gVal == 0)
          gDir = true;
        if (gVal == maxB) {
          gDir = false;
          gHold = offH;
        }

        if (bVal == 0)
          bDir = true;
        if (bVal == maxB) {
          bDir = false;
          bHold = offH;
        }

        //stepping the values
        if (rDir && rHold == 0)
          rVal++;
        if (!rDir && rVal > 0)
          rVal--;
        if (rVal == 0 && rHold > 0) {
          rHold--;
        }

        if (gDir && gHold == 0)
          gVal++;
        if (!gDir && gVal > 0)
          gVal--;
        if (gVal == 0 && gHold > 0) {
          gHold--;
        }

        if (bDir && bHold == 0)
          bVal++;
        if (!bDir && bVal > 0)
          bVal--;
        if (bVal == 0 && bHold > 0) {
          bHold--;
        }

        if (wDir && wVal < maxB)
          wVal++;
        if (!wDir && wVal > 0)
          wVal--;
      }
    }

    int getRedValue() {
      return rVal;
    }
    int getGreenValue() {
      return gVal;
    }
    int getBlueValue() {
      return bVal;
    }
    int getWhiteValue() {
      return 0;
    }

    void setShowWhite(bool sw) {
      showWhite = sw;
    }

    void whiteHold() {
      rHold = 5;
      gHold = (maxB + offH) * .5;
      bHold = (maxB + offH);
      rDir = gDir = bDir = false;
    }
};



#define BUTTON_LED_PIN 16
#define BUTTON_SWITCH_PIN 14

Servo lidServo;
int lidServoPosition;
Servo leftServo;
int leftServoPosition;
Servo rightServo;
int rightServoPosition;

#define LID_LED_CONTROL_PIN 3
#define LID_LED_COUNT 8
Adafruit_NeoPixel lidPixel(LID_LED_COUNT, LID_LED_CONTROL_PIN, NEO_RGBW + NEO_KHZ800);
ColorCycle ccLid(50, 50, 10);


#define BASE_LED_CONTROL_PIN 4
#define BASE_LED_COUNT 8
Adafruit_NeoPixel basePixel(BASE_LED_COUNT, BASE_LED_CONTROL_PIN, NEO_RGBW + NEO_KHZ800);
ColorCycle ccBase(50, 50, 10);


void setup() {
  pinMode(BUTTON_LED_PIN, OUTPUT);
  digitalWrite(BUTTON_LED_PIN, LOW);
  pinMode(BUTTON_SWITCH_PIN, INPUT);
  lidServo.attach(5);
  leftServo.attach(6);
  rightServo.attach(9);
  lidPixel.begin();
  lidPixel.show();
  basePixel.begin();
  basePixel.show();

  Serial.begin(9600); //This pipes to the serial monitor
  Serial.println("Starting");
}

void loop() {
  if (digitalRead(BUTTON_SWITCH_PIN) == true)  {
    digitalWrite(BUTTON_LED_PIN, HIGH);
    lidServoPosition = 0;
    leftServoPosition = 0;
    rightServoPosition = 0;
    ccLid.setShowWhite(true);
    ccBase.setShowWhite(true);
  } else {
    digitalWrite(BUTTON_LED_PIN, LOW);
    lidServoPosition = 180;
    leftServoPosition = 180;
    rightServoPosition = 180;
    ccLid.setShowWhite(false);
    ccBase.setShowWhite(false);
  }
  lidServo.write(lidServoPosition);
  leftServo.write(leftServoPosition);
  rightServo.write(rightServoPosition);

  ccLid.moveStep();
  ColorCycle tempCc = ccLid;
  for (int i = 0; i < lidPixel.numPixels(); i++) {
    lidPixel.setPixelColor(i, lidPixel.Color(tempCc.getGreenValue(), tempCc.getRedValue(), tempCc.getBlueValue(), tempCc.getWhiteValue()));
    tempCc.moveSteps(10);
  }

  ccBase.moveStep();
  tempCc = ccBase;
  for (int i = 0; i < lidPixel.numPixels(); i++) {
    basePixel.setPixelColor(i, basePixel.Color(tempCc.getGreenValue(), tempCc.getRedValue(), tempCc.getBlueValue(), tempCc.getWhiteValue()));
    tempCc.moveSteps(10);
  }

  if (TCNT1 > usToTicks(8192)) {
    lidPixel.show();
    basePixel.show();
  };
}
2
  • You are updating the LEDs in a timed way through TCNT1, but the setting of the pixels is not part of that, so that will be done very fast. I think, that code should also be timed. Then you only change the values, when you are really updating the colors.
    – chrisl
    May 20, 2019 at 5:44
  • The TCNT1 timer is only used to make sure that the "show" command doesn't execute during a servo update as this would cause servo jitter.
    – William
    May 20, 2019 at 18:26

1 Answer 1

1

You are writing your code non-blocking, but you don't introduce any way of timing code. This way the calculation of the new color values happens as fast, as the code can run. You can write timed action in a non-blocking way by using the coding style from the BlinkWithoutDelay example, that comes with the Arduino IDE. Then the LED code will only be executed, if the correct time has come. You save a timestamp of the last calculation execution in a global variable and then check, if enough time since then has passed. Only then you again execute the calculation and update your timestamp.

unsigned long timestamp = 0;
unsigned int interval = 10;

void loop(){
    //--------
    // Omitted the servo code
    //--------
    if(millis() - timestamp > interval){
        timestamp += interval; // update the timestamp to the next value
        //--------
        // Omitted the calcuation and the LED update (show() method)
        //--------
    }
}

Note, that here I used millis(), which will return the number of milliseconds, that passed since the Arduinos startup. I used an interval of 10 milliseconds, so that the LEDs will be updated every 10 milliseconds. You can change this value as you need it (also during the code execution). If you need faster execution, than a few milliseconds, you can use micros() instead of millis(), so that you work with microseconds. Of course, you cannot get faster, than the rest of the code runs.

For further explanation of this principle, you can google it. It has been described numerous times on the web and on this site.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.