OctoWS2811 Library - for large LED display at video speed refresh

OctoWS2811 was mentioned on Hack-a-Day, Adafruit and other sites Monday... but thought maybe it'd be worthwhile to mention here, in case anyone missed it.

http://www.pjrc.com/teensy/td_libs_OctoWS2811.html

Full disclosure: I'm the author and this is a 3rd party board not made by Arduino.

There are other WS2811 libraries, and for a small project they work great. For a large project, OctoWS2811 is the way to get great performance.

Hi Paul,

Pretty stellar work from you. as usual!
What you need is not a gamma correction but CIE lightness correction :

I've seen lookup tables for the above on the forum usually in 8 bit resolution and have one in 12 bit used for my LED shields http://ledshield.wordpress.com/used for my projects http://trippylighting.com/

Even better would be true CIE lab color correction:

A++

Excellent frontier moving project!

Hi, This is really cool. I have been working on a similiar fast SPI libarary that uses double buffering, but for the TLC5940 as I don't really like the library thats out there...

When I look at your matrix, I think I gotta see Plasma on it. :slight_smile: I took a look at your library and the example code and saw you avoid using floats so you might appreciate this attached program .. I adapted my fast plasma generator program I use to test my library in development to work with your library. Im curious to see the performance of your display if you or someone with a similar setup can test it. I guarantee you will think this is some pretty wicked eye candy should you try it. It has a gamma correction table, which I think I saw you were requesting somewhere.. Its very fast as it uses only integer math and some other tricks for trigonometric calculations.

//PlazINT  -  Fast Plasma Generator using Integer Math Only
//Edmund "Skorn" Horn
//March 4,2013
//Version 1.0 adapted for OctoWS2811Lib (untested)


//#include <Arduino.h> // Not sure if this is needed.
#include <avr/pgmspace.h>
#include <OctoWS2811.h>

//OctoWS2811 Defn. Stuff
#define COLS_LEDs 40  //all of the following params need to be adjusted for screen size
#define LEDS_PER_STRIP 120 //40x3(r,g,b)
#define ROWS_LEDs 8
#define NUM_LEDs 960  //40 cols x 8 rows x 3 leds(rgb)
const int ledsPerStrip = 120;
DMAMEM int displayMemory[LEDS_PER_STRIP*6];
int drawingMemory[LEDS_PER_STRIP*6];
const int config = WS2811_GRB | WS2811_800kHz;
OctoWS2811 leds(ledsPerStrip, displayMemory, drawingMemory, config);


//Byte val 2PI Cosine Wave, offset by 1 PI 
//supports fast trig calcs and smooth LED fading/pulsing.
uint8_t const cos_wave[256] PROGMEM =  
{0,0,0,0,1,1,1,2,2,3,4,5,6,6,8,9,10,11,12,14,15,17,18,20,22,23,25,27,29,31,33,35,38,40,42,
45,47,49,52,54,57,60,62,65,68,71,73,76,79,82,85,88,91,94,97,100,103,106,109,113,116,119,
122,125,128,131,135,138,141,144,147,150,153,156,159,162,165,168,171,174,177,180,183,186,
189,191,194,197,199,202,204,207,209,212,214,216,218,221,223,225,227,229,231,232,234,236,
238,239,241,242,243,245,246,247,248,249,250,251,252,252,253,253,254,254,255,255,255,255,
255,255,255,255,254,254,253,253,252,252,251,250,249,248,247,246,245,243,242,241,239,238,
236,234,232,231,229,227,225,223,221,218,216,214,212,209,207,204,202,199,197,194,191,189,
186,183,180,177,174,171,168,165,162,159,156,153,150,147,144,141,138,135,131,128,125,122,
119,116,113,109,106,103,100,97,94,91,88,85,82,79,76,73,71,68,65,62,60,57,54,52,49,47,45,
42,40,38,35,33,31,29,27,25,23,22,20,18,17,15,14,12,11,10,9,8,6,6,5,4,3,2,2,1,1,1,0,0,0,0
};


//Gamma Correction Curve
uint8_t const exp_gamma[256] PROGMEM=
{0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,
4,4,4,4,4,5,5,5,5,5,6,6,6,7,7,7,7,8,8,8,9,9,9,10,10,10,11,11,12,12,12,13,13,14,14,14,15,15,
16,16,17,17,18,18,19,19,20,20,21,21,22,23,23,24,24,25,26,26,27,28,28,29,30,30,31,32,32,33,
34,35,35,36,37,38,39,39,40,41,42,43,44,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,
61,62,63,64,65,66,67,68,70,71,72,73,74,75,77,78,79,80,82,83,84,85,87,89,91,92,93,95,96,98,
99,100,101,102,105,106,108,109,111,112,114,115,117,118,120,121,123,125,126,128,130,131,133,
135,136,138,140,142,143,145,147,149,151,152,154,156,158,160,162,164,165,167,169,171,173,175,
177,179,181,183,185,187,190,192,194,196,198,200,202,204,207,209,211,213,216,218,220,222,225,
227,229,232,234,236,239,241,244,246,249,251,253,254,255
};


void setup()
{
leds.begin();
leds.show();  
}


void loop()
{
long frameCount=25500;  // arbitrary seed to calculate the three time displacement variables t,t2,t3
  while(1)
  {
    uint16_t pixelIndex=0;
    frameCount++ ; 
    uint16_t t = fastCosineCalc((42 * frameCount)/100);  //time displacement - fiddle with these til it looks good...
    uint16_t t2 = fastCosineCalc((35 * frameCount)/100); 
    uint16_t t3 = fastCosineCalc((38 * frameCount)/100);
    for (uint8_t y = 0; y < ROW_LEDs; y++)
    {
       for (uint8_t x = 0; x < COLS_LEDs ; x++) 
       {
          uint8_t r = fastCosineCalc(((x << 3) + (t >> 1) + fastCosineCalc((t2 + (y << 3)))));  //Calculate 3 seperate plasma waves, one for each color channel
          uint8_t g = fastCosineCalc(((y << 3) + t + fastCosineCalc(((t3 >> 2) + (x << 3)))));
          uint8_t b = fastCosineCalc(((y << 3) + t2 + fastCosineCalc((t + x + (g >> 2)))));
          //uncomment the following to enable gamma correction
          //r=pgm_read_byte_near(exp_gamma+r);  
          //g=pgm_read_byte_near(exp_gamma+g);
          //b=pgm_read_byte_near(exp_gamma+b);
          leds.setPixel(pixelIndex, (r << 16) | (g << 8) | b));  // Is this the fastest method to update the draw buffer with colors? 
          pixelIndex++;
      }
    }
leds.show();  // not sure if this function is needed  to update each frame
}
}


inline uint8_t fastCosineCalc( uint16_t preWrapVal)
{
  uint8_t wrapVal = (preWrapVal % 255);
  if (wrapVal<0) wrapVal=255+wrapVal;
  return (pgm_read_byte_near(cos_wave+wrapVal)); 
}

Thanks.

@Headroom - Thanks! I'm planning to work on version 1.1 in April. Will definitely give this a try.

@Skorn - I've got the LEDs right here, still next to my computer. Will give this a try soon. If this works, would it be ok to distribute this code in version 1.1's examples?

Hi Paul,

If you like the program and want to it include it in your next library release contact me via an IM. I have no problem with this and you have my approval to do so, I would just like to ensure that the code is bugfree and properly commented etc..

Thanks,

I ran the code. A few minor edits were needed to make it run.

Here's a really quick video. Each half of the display is run from a separate Teensy 3.0, with no coordination between them. They're both running the code below. Sorry about the shaky cam... I don't have time to set up the tripod and do a proper video. But I wanted to at least get a quick capture so you can see it.

I added code to blink the LED as each update begins. I measured 99.9 Hz.

//PlazINT  -  Fast Plasma Generator using Integer Math Only
//Edmund "Skorn" Horn
//March 4,2013
//Version 1.0 adapted for OctoWS2811Lib (tested, working...)


#include <OctoWS2811.h>

//OctoWS2811 Defn. Stuff
#define COLS_LEDs 60  // all of the following params need to be adjusted for screen size
#define ROWS_LEDs 16  // LED_LAYOUT assumed 0 if ROWS_LEDs > 8
#define LEDS_PER_STRIP (COLS_LEDs * (ROWS_LEDs / 6))

DMAMEM int displayMemory[LEDS_PER_STRIP*6];
int drawingMemory[LEDS_PER_STRIP*6];
const int config = WS2811_GRB | WS2811_800kHz;
OctoWS2811 leds(LEDS_PER_STRIP, displayMemory, drawingMemory, config);

//Byte val 2PI Cosine Wave, offset by 1 PI 
//supports fast trig calcs and smooth LED fading/pulsing.
uint8_t const cos_wave[256] PROGMEM =
{0,0,0,0,1,1,1,2,2,3,4,5,6,6,8,9,10,11,12,14,15,17,18,20,22,23,25,27,29,31,33,35,38,40,42,
45,47,49,52,54,57,60,62,65,68,71,73,76,79,82,85,88,91,94,97,100,103,106,109,113,116,119,
122,125,128,131,135,138,141,144,147,150,153,156,159,162,165,168,171,174,177,180,183,186,
189,191,194,197,199,202,204,207,209,212,214,216,218,221,223,225,227,229,231,232,234,236,
238,239,241,242,243,245,246,247,248,249,250,251,252,252,253,253,254,254,255,255,255,255,
255,255,255,255,254,254,253,253,252,252,251,250,249,248,247,246,245,243,242,241,239,238,
236,234,232,231,229,227,225,223,221,218,216,214,212,209,207,204,202,199,197,194,191,189,
186,183,180,177,174,171,168,165,162,159,156,153,150,147,144,141,138,135,131,128,125,122,
119,116,113,109,106,103,100,97,94,91,88,85,82,79,76,73,71,68,65,62,60,57,54,52,49,47,45,
42,40,38,35,33,31,29,27,25,23,22,20,18,17,15,14,12,11,10,9,8,6,6,5,4,3,2,2,1,1,1,0,0,0,0
};


//Gamma Correction Curve
uint8_t const exp_gamma[256] PROGMEM =
{0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,
4,4,4,4,4,5,5,5,5,5,6,6,6,7,7,7,7,8,8,8,9,9,9,10,10,10,11,11,12,12,12,13,13,14,14,14,15,15,
16,16,17,17,18,18,19,19,20,20,21,21,22,23,23,24,24,25,26,26,27,28,28,29,30,30,31,32,32,33,
34,35,35,36,37,38,39,39,40,41,42,43,44,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,
61,62,63,64,65,66,67,68,70,71,72,73,74,75,77,78,79,80,82,83,84,85,87,89,91,92,93,95,96,98,
99,100,101,102,105,106,108,109,111,112,114,115,117,118,120,121,123,125,126,128,130,131,133,
135,136,138,140,142,143,145,147,149,151,152,154,156,158,160,162,164,165,167,169,171,173,175,
177,179,181,183,185,187,190,192,194,196,198,200,202,204,207,209,211,213,216,218,220,222,225,
227,229,232,234,236,239,241,244,246,249,251,253,254,255
};


void setup()
{
  pinMode(13, OUTPUT);
  leds.begin();
  leds.show();
}

void loop()
{
  unsigned long frameCount=25500;  // arbitrary seed to calculate the three time displacement variables t,t2,t3
  while(1) {
    frameCount++ ;
    uint16_t t = fastCosineCalc((42 * frameCount)/100);  //time displacement - fiddle with these til it looks good...
    uint16_t t2 = fastCosineCalc((35 * frameCount)/100);
    uint16_t t3 = fastCosineCalc((38 * frameCount)/100);

    for (uint8_t y = 0; y < ROWS_LEDs; y++) {
      int left2Right, pixelIndex;
      if (((y % (ROWS_LEDs/8)) & 1) == 0) {
        left2Right = 1;
        pixelIndex = y * COLS_LEDs;
      } else {
        left2Right = -1;
        pixelIndex = (y + 1) * COLS_LEDs - 1;
      }
      for (uint8_t x = 0; x < COLS_LEDs ; x++) {
        //Calculate 3 seperate plasma waves, one for each color channel
        uint8_t r = fastCosineCalc(((x << 3) + (t >> 1) + fastCosineCalc((t2 + (y << 3)))));
        uint8_t g = fastCosineCalc(((y << 3) + t + fastCosineCalc(((t3 >> 2) + (x << 3)))));
        uint8_t b = fastCosineCalc(((y << 3) + t2 + fastCosineCalc((t + x + (g >> 2)))));
        //uncomment the following to enable gamma correction
        //r=pgm_read_byte_near(exp_gamma+r);  
        //g=pgm_read_byte_near(exp_gamma+g);
        //b=pgm_read_byte_near(exp_gamma+b);
        leds.setPixel(pixelIndex, ((r << 16) | (g << 8) | b));
        pixelIndex += left2Right;
      }
    }
    digitalWrite(13, HIGH);
    leds.show();  // not sure if this function is needed  to update each frame
    digitalWrite(13, LOW);
  }
}


inline uint8_t fastCosineCalc( uint16_t preWrapVal)
{
  uint8_t wrapVal = (preWrapVal % 255);
  if (wrapVal<0) wrapVal=255+wrapVal;
  return (pgm_read_byte_near(cos_wave+wrapVal));
}

Hi Paul,

Thanks for the video and trying out the code. One thing I didn't do in the code is to account for size of displays. Personally I think the waveforms would look better if they were tweaked to suit your larger screen. It looks good and fast though. Almost 100fps realtime for that screen size is really good. My Atmega 328p +TLC5940 based 16x8 matrix calculates pages at just over 30FPS, however, the screen refreshed by the TLCs at 150Hz.

Anyway, thanks for the feedback.

Hi Skorn,

thanks for the pretty fast and nice eye candy!

Just by the way, are you aware, that there is a beta of FastSPI_LED2 out there? [url=http://http://waitingforbigo.com/2013/02/19/fastspi_led2_preview_release/]http://waitingforbigo.com/2013/02/19/fastspi_led2_preview_release/ and Google Code Archive - Long-term storage for Google Code Project Hosting. but that will not deal with 12 bit PWM...

Skorn,

I was playing with the Teensy 3.0 library and got your plasma program up and running on a small 16x16 matrix (see attached pic). I am waiting to make a larger display but am testing out code in the meantime. I was wondering how you generated the gamma correction table? Did you use this link? http://neuroelec.com/2011/04/led-brightness-to-your-eye-gamma-correction-no/ I plotted the values you used for reference and compared them to the equation that was used as a reference http://www.poynton.com/PDFs/SMPTE93_Gamma.pdf. Based on the Matlab Plot (see attachment) using the equation:

res=0:255;
GammaE=255*(res/255).^(1/.45);

They are a bit different. Nothing to be worried about, however I was just trying to track down where the gamma you used came from?

//Gamma Correction Curve
uint8_t const exp_gamma[256] PROGMEM =
{0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,
4,4,4,4,4,5,5,5,5,5,6,6,6,7,7,7,7,8,8,8,9,9,9,10,10,10,11,11,12,12,12,13,13,14,14,14,15,15,
16,16,17,17,18,18,19,19,20,20,21,21,22,23,23,24,24,25,26,26,27,28,28,29,30,30,31,32,32,33,
34,35,35,36,37,38,39,39,40,41,42,43,44,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,
61,62,63,64,65,66,67,68,70,71,72,73,74,75,77,78,79,80,82,83,84,85,87,89,91,92,93,95,96,98,
99,100,101,102,105,106,108,109,111,112,114,115,117,118,120,121,123,125,126,128,130,131,133,
135,136,138,140,142,143,145,147,149,151,152,154,156,158,160,162,164,165,167,169,171,173,175,
177,179,181,183,185,187,190,192,194,196,198,200,202,204,207,209,211,213,216,218,220,222,225,
227,229,232,234,236,239,241,244,246,249,251,253,254,255

Edit: Running the Plasma Example with the Gamma Correction provided a much more dramatic effect. Great Job!

Gamma.jpg

What you're looking for is not Gamma correction, but CIE lab brightness correction, as the article at neuroelec.com explains. However, the difference is relatively small and I am not sure if its visible.
I have a 12 bit version of it that I coupled post, and ther is also an 8 bit versioning the forum.
However, you can expect the 8 bit a bit ... Choppy.. At lower intensities due to limitation of resolution.

Thanks for the clarification. Of course after re-reading the neuelec article again this morning it becomes clear that cie was implemented.

Hi,

I originally made the correction table many months back, I wasn't even aware of the CIE 1931 report and CIE lab brightness specification at that time. I remember finding online a simple exponential algortithm to calculate gamma, then calculated a 256 value 12 bit table in excel. Later, for another project I created an 8 bit table by dividing each 12 bit value by 16... Then I tested the table in my project and adjusted the values slightly as I saw fit at the time. I would suggest that you go with one of the more "formal" implimentations already mentioned...

Thanks for the feedback on the Plazma program. I hope to impliment some other changes to it down the road such as adjust/scale the waveforms to display size, and change waveform shape over time. I think its already pretty dynamic as it is though but its fun to tinker with stuff...