Fmilburn

Using SPI and Energia with WS2812 LEDs

2 posts in this topic

Nick Gammon published an interesting post on using SPI on 16 MHz Arduinos to run WS2812 LEDs (aka neopixels) at: http://gammon.com.au/forum/?id=13357. He also provides a link with a lot of information about the NRZ protocol used by the WS2812 and tolerances:  https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/.  The tolerances are actually quite a bit looser than what I previously believed.  So, I set out to do something similar with Energia and LaunchPads running at different speeds.  Spoiler alert:  It works.

 

The previously linked articles provide all the background so minimal information is repeated here.  NRZ is a one wire protocol that transfers information to the LEDs by varying the length of the signal when high.  A longer pulse is used for a 1, and a shorter one for a 0.  The timing, with tolerances, is shown in the figure below.

 

post-45284-0-01321000-1478378261_thumb.jpg

The length between pulses cannot exceed about 5 us and most everything else is pretty loose.

 

The protocol is implemented using SPI which I find pretty clever.  A byte is sent out with the SPI module with the proper length to represent the desired bit for the protocol.  The following must be determined and set to do this:

  • Set proper SPI clock speed using SPI.setClockDivider() in Energia
  • Determine the proper byte to send by SPI.transfer() in Energia to represent a 0 or 1 bit

For example, using the MSP430F5529:

  1. Clock speed is 25.6 MHz
  2. Setting the SPI clock divider to 4 gives a SPI clock of 6.4 MHz and since the SPI block executes in one cycle (Arduino executes in 2), each bit in the byte is equivalent to 156.25 ns.
  3. Therefore, to send a pulse indicating a "1", a byte equal to 0b1111000 could be used which gives 4x156.25 = 625 ns.  This is in the acceptable range of 550 to 850 ns.
  4. Similarly, for a 0 an acceptable byte would be 0b11000000 or 312.5 ns.

A similar process can be used to determine acceptable values for the MSP430G2553.

 

The sketch below is a simplification of the library presented by Nick which and includes the modifications described above to run on both the G2553 and F5529.  The preprocessor is used to set appropriate values for the clock divider and long and short bytes.   The functions are very nearly the same as posted by Nick.  Note that interrupts must be disabled before sending data and then reenabled manually after.

/*
 * WS2812 display using SPI on various TI LaunchPads with Energia
 *
 * Connections:
 *   LaunchPad      LED Strip
 *   ---------      ---------
 *   3V3            5VDC
 *   Pin 15 (MOSI)  DIN
 *   GND            GND
 *
 * How to use:
 *  ledsetup ();         - Get ready to send.
 *                         Call once at the beginning of the program.
 *  sendPixel (r, g, ; - Send a single pixel to the string.
 *                         Call this once for each pixel in a frame.
 *                         Each colour is in the range 0 to 255. Turn off 
 *                         interrupts before use and turn on after all pixels
 *                         have been programmed.
 *  show ();             - Latch the recently sent pixels onto the LEDs .
 *                         Call once per frame.
 *  showColor (count, r, g, ; - Set the entire string of count Neopixels
 *                         to this one colour. Turn off interrupts before use
 *                         and remember to turn on afterwards.
 *
 * Derived from NeoPixel display library by Nick Gammon
 * https://github.com/nickgammon/NeoPixels_SPI
 * With ideas from:
 * http://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
 * Released for public use under the Creative Commons Attribution 3.0 Australia License
 * http://creativecommons.org/licenses/by/3.0/au/
 *
 * F Milburn  November 2016
 * Tested with Energia V17 and WS2812 8 pixel strip on launchpads shown below.
 */
#include <SPI.h>

#if defined(__MSP430G2553)
  #define SPIDIV     SPI_CLOCK_DIV2       // 16 MHz/2 gives 125 ns for each on bit in byte
  #define SPILONG    0b11111100           // 750 ns (acceptable "on" range 550 to 850 ns)
  #define SPISHORT   0b11100000           // 375 ns (acceptable "on" range 200 to 500 ns)
#elif defined(__MSP430F5529)
  #define SPIDIV     SPI_CLOCK_DIV4       // 25.6 MHz/4 gives 156.25 ns for each on bit in byte
  #define SPILONG    0b11110000           // 625 ns (acceptable "on" range 550 to 850 ns)
  #define SPISHORT   0b11000000           // 312.5 ns (acceptable "on" range 200 to 500 ns)
#else
  #error This microcontroller is not supported
#endif

const unsigned int PIXELS = 8;           // Pixels in the strip

void setup (){
  ledsetup();
} 

void loop (){
  // Show a solid color across the strip
  noInterrupts();                        // no interrupts while sending data
  showColor (PIXELS, 0xBB, 0x22, 0x22);  // single color on entire strip
  interrupts();                          // interrupts are OK now
  delay(1000);                           // hold it for a second
  
  // Show a different color on every pixel
  noInterrupts();                        // no interrupts while sending data
  sendPixel(0xBB, 0x00, 0x00);           // red
  sendPixel(0x00, 0xBB, 0x00);           // green
  sendPixel(0x00, 0x00, 0xBB);           // blue
  sendPixel(0xBB, 0xBB, 0xBB);           // white
  sendPixel(0xBB, 0x22, 0x22);           // pinkish
  sendPixel(0x22, 0xBB, 0x22);           // light green
  sendPixel(0x22, 0x22, 0xBB);           // purplish blue
  sendPixel(0x00, 0x00, 0x00);           // pixel off
  interrupts();                          // interrupts are OK now
  delay(1000);                           // hold it for a second                      
} 
 
// Sends one byte to the LED strip by SPI.
void sendByte (unsigned char {
    for (unsigned char bit = 0; bit < 8; bit++){
      if (b & 0x80) // is high-order bit set?
        SPI.transfer (SPILONG);     // long on bit (~700 ns) defined for each clock speed
      else
        SPI.transfer (SPISHORT);    // short on bit (~350 ns) defined for each clock speed
      b <<= 1;                      // shift next bit into high-order position
    } // end of for each bit
} // end of sendByte

// Set up SPI
void ledsetup(){
  SPI.begin ();
  SPI.setClockDivider (SPIDIV);  // defined for each clock speed
  SPI.setBitOrder (MSBFIRST);
  SPI.setDataMode (SPI_MODE1);   // MOSI normally low.
  show ();                       // in case MOSI went high, latch in whatever-we-sent
  sendPixel (0, 0, 0);           // now change back to black
  show ();                       // and latch that
}  // end of ledsetup

// Send a single pixel worth of information.  Turn interrupts off while using.
void sendPixel (unsigned char r, unsigned char g, unsigned char {
  sendByte (g);        // NeoPixel wants colors in green-then-red-then-blue order
  sendByte (r);
  sendByte (;
} // end of sendPixel

// Wait long enough without sending any bits to allow the pixels to latch and
// display the last sent frame
void show(){
  delayMicroseconds (9);
} // end of show

// Display a single color on the whole string.  Turn interrupts off while using.
void showColor (unsigned int count, unsigned char r , unsigned char g , unsigned char {
  noInterrupts ();
  for (unsigned int pixel = 0; pixel < count; pixel++)
    sendPixel (r, g, ;
  interrupts ();
  show ();  // latch the colours
} // end of showColor

The timing, when checked on a logic analyzer, checks out with the calculations above (hooray for math).  The "gaps" between pulses are within tolerance and largely set by code overhead as well as the byte being sent.

 

post-45284-0-01209200-1478378511_thumb.jpg

post-45284-0-69054100-1478378581_thumb.jpg

And here it is showing the strip lit up in one color.

 

post-45284-0-05387800-1478378613_thumb.jpg

I tried this on several other LaunchPads I had handy and here is a summary:

  • FR6989 - I had never noticed, but Energia defaults to 8 MHz.  Doing the math, there isn't a good match to the WS2812 requirements without changing processor speed (which I did not try).
  • MSP432 - there was behavior I couldn't explain, probably due to RTOS and I didn't pursue this for long.

In summary, the method works although I did limited experimentation.  It would be even easier to implement outside of Energia with full access to clocks.  It was an interesting exercise but alternative methods have been posted here on 43oh with tuned assembler and having used those successfully in the past, I will probably continue to preferentially use them in the future.

chicken and dubnet like this

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now