Categories
algorithm Arduino software

Neopixel Fades and Color Transitions

Whilst updating an old application that implemented PWM color control of hard-wired RBG leds to NeoPixel type devices, I had to work out a new way to transition between colors. PWM is not an applicable technique when using serially controlled LEDs.

Here’s what resulted.

I wrote about LED fading transitions some time ago (see this previous post) applying in relation to the special case of transitioning between the colors at the corners of a RGB color cube.

In summary, the RGB color space can be imagined as a 3 dimensional cube whose points are defined by the coordinates R, G and B, which also happen to represent colors. All the RGB colors are therefore contained inside a cube with sides of length 255 units.

With respect to fading a WS2812 LED between 2 colors, I found that the FastLED library (which is my go-to for these types of serial LEDs) does not seem to have mechanisms to dim or fade individual LEDs, and no functions to fade to anything but black.

Thinking about the problem in terms of the color cube, it quickly becomes apparent that fading smoothly between one two arbitrary colors (R1G1B1) and (R2G2B2) happens when you follow a linear path, inside the RGB cube, between the two endpoints. In other words, the intermediate colors are the RGB coordinates of the points that lie on the straight line joining the start and end coordinates.

The problem of calculating the integer coordinates and drawing a rasterized line forming a close approximation to a straight line in n-dimensional space was solved long ago. A common algorithm to implement this is Bresenham’s algorithm (see here for an explanation and here for a good selection of ready-made code).

Implementing color fading

As a solution I implemented a class NeoFade that iterates through all the RGB coordinates on the line. These are returned to the caller so the fading algorithm is not limited to FastLED but can be used with any NeoPixel LED library.

A small complication is that the colors need to transition over time or they will not be seen to change. This delay could be managed in the application or the fading class. I elected to put it in the class to simplify application coding.

The class is presented below. It uses the algorithm code from the source referenced above, with all the variables in the original function moved to class member variables and the code split into ‘setup’ and ‘iteration’ functions (setFade() and getNext() respectively) to work in this situation.

class NeoFade
{
public:
  void begin(void) 
  { 
    _period = 1000;
    reset(); 
    _ended = true;
  }

  inline void reset(void) 
  {
    setFade(_rStart, _gStart, _bStart, _rEnd, _gEnd, _bEnd);
  }

  inline bool isEnded(void) { return(_ended); }
  
  inline void setPeriod(uint16_t p) { _period = p; calcTimeInterval(); }

  void setFade(uint16_t r0, uint16_t g0, uint16_t b0, uint16_t r1, uint16_t g1, uint16_t b1)
    {
      _rStart = _r0 = r0;  _rEnd = _r1 = r1;
      _gStart = _g0 = g0;  _gEnd = _g1 = g1;
      _bStart = _b0 = b0;  _bEnd = _b1 = b1;

      _dR = uiabs(_r1,_r0);  _sR = _r0 < _r1 ? 1 : -1;
      _dG = uiabs(_g1,_g0);  _sG = _g0 < _g1 ? 1 : -1;
      _dB = uiabs(_b1,_b0);  _sB = _b0 < _b1 ? 1 : -1;
      _dMax = max3(_dR, _dG, _dB);
      _steps = _dMax;
      calcTimeInterval();
      _timeStart = 0;

      _r1 = _g1 = _b1 = _dMax/2;            /* error offset */

      _ended = false;
    }

  bool getNext(uint16_t &r, uint16_t &g, uint16_t&b)
  {
    if (millis() - _timeStart < _timeInterval)
      return(false);

    _timeStart = millis();

    r = _r0;
    g = _g0;
    b = _b0;

    _ended = (_steps == 0);
    if (!_ended)
    {
      _steps--;
      _r1 -= _dR; if (_r1 < 0) { _r1 += _dMax; _r0 += _sR; }
      _g1 -= _dG; if (_g1 < 0) { _g1 += _dMax; _g0 += _sG; }
      _b1 -= _dB; if (_b1 < 0) { _b1 += _dMax; _b0 += _sB; }
    }

    return(true);
  }

private:
  uint16_t _rStart, _gStart, _bStart, _rEnd, _gEnd, _bEnd;
  int32_t _r0, _g0, _b0, _r1, _g1, _b1, _steps;
  int16_t _dR, _dG, _dB, _dMax;
  int8_t _sR, _sG, _sB;
  bool _ended;

  // timing parameters
  uint16_t _period;    // fade duration in ms
  uint32_t _timeStart; // animation time start
  uint32_t _timeInterval; // interval between color changes
  
  void calcTimeInterval(void)
  {
    _timeInterval = _period / _steps;
    if (_timeInterval == 0) _timeInterval = 1;
  }

  uint16_t max3(uint16_t x, uint16_t y, uint16_t z)
  {
    uint16_t m = z;    // assume z to start with
    
    if (x > y) { if (x > z) m = x; }
    else { if (y > z) m = y; }
    
    return(m);
  }

  inline uint16_t uiabs(uint16_t a, uint16_t b) { return((a > b) ? (a - b) : (b - a)); }

};

Using this class in an application to fade between 2 RGB colors (for example, [0,0,0] and [255,128,64]) over a 5 second period is straightforward. The Arduino sketch below illustrates the basic code flow needed.

#include <FastLED.h>
#include "NeoFade.h"


#define NUM_LEDS 6 // Number of LEDs in the strip
#define DATA_PIN 2 // WS2812 DATA_PIN.

CRGB leds[NUM_LEDS]; // the array of leds

NeoFade F;

void setup(void)
{
  F.begin();
  F.setPeriod(5000);
  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(255);
}

void loop(void)
{
  uint16_t r, g, b;

  if (F.isEnded())
    F.setFade(0, 0, 0, 255, 128, 64);

  if (F.getNext(r, g, b))
  {
    leds[0] = CRGB(r, g, b);
    FastLED.show();
  }

}

As a bonus, this general fading algorithm also includes a fade to ‘black’ or ‘white’ as these are just another RGB color.

3 replies on “Neopixel Fades and Color Transitions”

Great work!
But I am having some issues regarding the time/period.
If I change “.setPeriod(5000)”, nothing seems to change?
If I change “_period = 1000;” in the class code it doesn’t seem to have any effect either.

Could you tell me how to adjust the speed of fading?
Also can I call the .setPeriod in the loop to change it several times in the loop?
(Tried it but no effect..)

thank you for your time and effort
Regards,

Like

Not sure why you are having problems, as it works for me. You have all the code in the article, so perhaps some debugging print() statements when you change the period will shed some light on why you can’t see a change when you change the time.

Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s