Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ Electronics ➜ Microprocessors ➜ Simple method for using NeoPixels (WS2812 LED strip)

Simple method for using NeoPixels (WS2812 LED strip)

Postings by administrators only.

Refresh page


Posted by Nick Gammon   Australia  (23,100 posts)  Bio   Forum Administrator
Date Sat 12 Mar 2016 05:21 AM (UTC)

Amended on Thu 19 Apr 2018 06:21 AM (UTC) by Nick Gammon

Message
NeoPixels are the new rage for addressable RGB LED strings. They are available from Adafruit, Sparkfun, eBay and other places.

From Wikipedia - Adafruit: "NeoPixel is Adafruit's brand of individually-addressable red-green-blue (RGB) LED. They are based on the WS2812 LED and WS2811 driver, where the WS2811 is integrated into the LED, for reduced footprint."

NeoPixels can be obtained individually, however are often sold in matrices, strings, or circles.

Each pixel has a controller chip soldered to it:


  • It can display 24-bit colour by the controller doing PWM (Pulse Width Modulation) of the attached Red, Green and Blue LEDs.

  • Each pixel can display a different colour from the others.

  • Built in current-limiting means you don't need current-limiting resistors.

  • Only three wires are needed: Ground, +5V and data.


What they look like


Below is a photo of an Adafruit "Flora RGB Smart NeoPixel version 2".



Visible in the image is the board they have mounted the NeoPixel onto, it has data in at the bottom, and data out at the top. You are intended to connect them together in sequence. All pixels share the +5V and Gnd connections.

Inside the WS2812 device which is the heart of the NeoPixel are three LEDs which can output blue, red and green, as shown by the small dots. There is a chip inside which connects to the pins on the WS2812, and also to the LEDs themselves. This chip handles the incoming data, reshaping of the outgoing data, and doing PWM for the LEDs.

A close-up photo of the Neopixel:



A close-up photo of the WS2812 chip:




Judging by the photo, the three LEDs are arranged in a common-anode configuration (they are all connected to VDD - marked "Power supply LED" on the datasheet). Thus the chip would sink current when it needs to enable an LED. The red LED is connected directly to VDD, and there are two wires for the other two LEDs.




I present below some simple code to drive NeoPixels. First, however, some theory.

One-wire NRZ protocol


The controllers use a NRZ protocol - where both zero bits and one bits are represented by a high on the data line. However, a zero bit is shorter than a one bit as illustrated below.



Both 0-bit and 1-bit start with a transition from 0V to 5V. The length of the pulse after that determines which sort of bit it is. There is a leeway of ±150 ns as illustrated by the shaded boxes. After that there is a defined time for the data line to be at zero volts again (with another ±150 ns leeway - not shown), after which the next bit can be sent.

The datasheet doesn't totally agree with the claimed performance of the chip. They are marketed as 800 kHz (800 bits per second) transfer rate. This would therefore be 1/800000 (1250 ns) per bit, however neither of the times on the datasheet add up to 1250 ns.

In practice, as bigjosh2 points out in his article (link below), the zero volt times are not critical, they can be any length, providing that they are not so long that they reset the chip. That is, they can be any length up to about 5 µs long. Also, the length of the 1-bit isn't particularly critical.

The timing above is for one bit. For each pixel 24 bits are required (8 bits each for red, green, and blue). The bits are sent in this order:



That is: Green/Red/Blue with the high-order bit first.

(I believe some variants may use different colour orders, so if the colours don't look right try changing the order).

As the bits are received they are buffered in an internal buffer (and not shown yet). After 24 bits are received the controller simply forwards on any further pixels to the next one in the chain (via the DOUT pin). As part of this process the chip "reshapes" the individual bits, so that they conform to the timing requirements. That way, any capacitance in the line does not cause the signal to degrade as it moves further down the line of pixels.

When the controlling processor (ie. the Arduino) is ready, it "latches" all of the pixel data by sending at least a 6 µs length of 0V. The datasheet says 50µs but empirically, a lower delay is all that is required.

Once latched, the process starts again. The first pixel in the chain is now ready to receive data for the next colour required to be shown.


Note: The 1-wire protocol here is not the same as the protocol used in various components made by Dallas Semiconductor Corp. such as the DS18B20 temperature sensor. That is totally a different sort of 1-wire protocol.

Wikipedia - 1-Wire


http://www.gammon.com.au/forum/?id=10902


Code to output one byte


We can make use of the SPI hardware on the Arduino to keep our pulses an accurate width. Once the hardware starts delivering one SPI byte, it keeps going regardless of whether it gets interrupted.

The fastest the SPI hardware can clock out bits is one bit per two processor clock cycles. At 16 MHz, that means one SPI bit per 125 ns.

So, for the zero-bit to have as 350 ns pulse width (for a zero bit) we can clock out 0b11100000 - that is, 3 x SPI 1-bits. That will take 3 x 125 ns, which is 375 ns. This is well within spec for a 0-bit.



To clock out a one-bit to the NeoPixel we will double that to 0b11111100 - that is 6 x SPI 1-bits. That will take 750 ns which is within spec for a one-bit.



Applying those timings to the required timing for the chip, we see this result:



The pulse widths generated are slightly longer than the ideal from the spec, but well within tolerance.

So, after working that out, the code for clocking out one byte is simply this:


void sendByte (byte b)
  {
    // send one byte to the Neopixels - note that the "off" gap is partly handled by the loop overhead
    //   gaps measured empirically to be 1.7 µs to 2 µs, so we don't need to add any more of our own
    for (byte bit = 0; bit < 8; bit++) 
      {
      if (b & 0x80) // is high-order bit set?
        SPI.transfer (0b11111100);  // 1 bit - 750 ns on + 250 ns off (acceptable "on" range 550 ns to 850 ns)
      else
        SPI.transfer (0b11100000);  // 0 bit - 375 ns on + 625 ns off (acceptable "on" range 200 ns to 500 ns)
      b <<= 1; // shift next bit into high-order position
    } // end of for each bit
} // end of sendByte


The time taken to execute the loop fill up the required gap between bits, so we don't need any extra delays.

To send an entire pixel (all 24 bits) we just need to send out the green byte, then the red byte, then the blue byte:


void sendPixel (const byte r, const byte g, const byte b) 
  {
  sendByte (g);        // NeoPixel wants colors in green-then-red-then-blue order
  sendByte (r);
  sendByte (b);
  } // end of sendPixel


To latch the data into the pixels so it is displayed, we just need a brief delay:


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


The datasheet says 50 µs, however various tests have shown that 6 to 9 µs is plenty.

To set all the pixels to a single colour, we just iterate through them all:


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


We have to turn interrupts off in this loop, because a timer or other interrupt in the middle might cause a longer than 6 µs pause between pixels, and then the controller resets.

All that is left is to set up the SPI hardware:


void ledsetup() 
{
  SPI.begin ();
  SPI.setClockDivider (SPI_CLOCK_DIV2);
  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


The last three lines of code in the function are to deal with a brief pulse that can appear on MOSI as the SPI hardware is activated. Without it, an unwanted colour can be latched into the first pixel.

Now we hook up the NeoPixels data line to MOSI (SPI master-out, slave-in) and we are ready to go. On the Arduino Uno MOSI is digital pin 11. On the Arduino Mega2560 MOSI is digital pin 51. On the Arduino Micro MOSI is marked "MOSI" next to the Reset button. On the Arduino Leonardo MOSI is only available on the ICSP header.



Putting it all together, we just have to add setup and loop:


void setup() 
  {
  ledsetup();
  showColor (PIXELS, 0xB2, 0x22, 0x22);  // firebrick
  } // end of setup

void loop() 
  {
  } // end of loop


We assume here that PIXELS is a constant representing the number of pixels in our string.

Handling precautions


From the Adafruit NeoPixels best practices guide:


  • Before connecting NeoPixels to any large power source (DC “wall wart” or even a large battery), add a capacitor (1000 µF, 6.3V or higher) across the + and – terminals.

  • Place a 300 to 500 Ohm resistor in series between the Arduino data output pin and the input to the first NeoPixel. This resistor must be at the NeoPixel end of the wire to be effective!

  • Try to minimize the distance between the Arduino and first pixel.

  • Avoid connecting NeoPixels to a live circuit. If you simply must, always connect ground first, then +5V, then data. Disconnect in the reverse order.

  • If powering the pixels with a separate supply, apply power to the pixels before applying power to the microcontroller.


Library


To simplify using this code, I have uploaded a library to GitHub:

https://github.com/nickgammon/NeoPixels_SPI

A minimal example of using it:


#include <SPI.h>
#include <NeoPixels_SPI.h>

const unsigned int PIXELS = 8; // Number of pixels in the string

void setup ()
  {
  ledsetup();
  showColor (PIXELS, 0xB2, 0x22, 0x22);  // firebrick
  } // end of setup

void loop ()
  {
  } // end of loop


That just shows a single colour. Obviously you change PIXELS to be the number of pixels in your strand. There are other examples which do a bit more.

Determining the length of the string


It is possible to work out how many NeoPixels are in a string of them by sending increasing numbers of pixels down the string, connecting a wire to the "output" end (Data Out) and waiting until one appears at the far end. When that happens, you have as many pixels as you sent (minus one) in the string. For example:


/*
 * 
 * Find length of NeoPixel string.
 * Author: Nick Gammon
 * Date:   11 March 2016
 */

#include <SPI.h>
#include <NeoPixels_SPI.h>

const unsigned long LIMIT = 10000;  // maximum pixels to test

// Note: Connect NeoPixels to MOSI (pin D11 on a Uno, pin D51 on a Mega)
//       Connect the other end of the NeoPixels to D2 (External Interrupt 0)

volatile bool triggered;

void myISR ()
  {
  triggered = true;
  } // end of myISR

void setup() 
  {
  ledsetup();
  Serial.begin (115200);
  Serial.println (F("Starting ..."));
  attachInterrupt (0, myISR, RISING);
  } // end of setup

unsigned int increment = 1000;
unsigned int counter = increment;

void loop() 
  {
  noInterrupts ();
  triggered = false;
  EIFR = bit (INTF0);  // clear flag for interrupt 0
  
  for (unsigned int i = 0; i < counter; i++)
    sendPixel (5, 0, 0);
  TIMSK0 = bit (TOIE0);  // throw away timer overflow interrupt
  interrupts ();
  show ();  // latch the colours - handle the interrupt meantime

  if (triggered)
    {
    if (increment <= 1)
      {
      Serial.print (F("There are "));
      Serial.print (counter - 1);
      Serial.println (F(" NeoPixels in the string."));
      Serial.flush ();
      exit (0);
      }
    else
      {  // binary search
      counter -= increment;
      increment /= 2;
      } // end of if 
    } // end of if triggered
    
  counter += increment;

  if (counter > LIMIT)
    {
    Serial.print (F("Could not find any NeoPixels after trying "));
    Serial.print (LIMIT);
    Serial.println (F(" pixels."));
    Serial.flush ();
    exit (0);
    }

  } // end of loop


Resources





Demo




Example of rainbow effect


Inspired by the Adafruit NeoPixels "strandtest" sketch, here it is adapted for this library:


/*

   NeoPixels demo - rainbow colours

*/

#include <SPI.h>
#include <NeoPixels_SPI.h>

const unsigned int PIXELS = 300; // Number of pixels in the string

void setup()
{
  ledsetup();
  showColor (PIXELS, 0, 0, 0);  // all to black
} // end of setup

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
void Wheel (byte WheelPos, byte & r, byte & g, byte & b)
{
  if (WheelPos < 85)
  {
    r = WheelPos * 3;
    g = 255 - WheelPos * 3;
    b = 0;
  }
  else if (WheelPos < 170)
  {
    WheelPos -= 85;
    r = 255 - WheelPos * 3;
    g = 0;
    b = WheelPos * 3;
  }
  else
  {
    WheelPos -= 170;
    r = 0;
    g = WheelPos * 3;
    b = 255 - WheelPos * 3;
  }
} // end of Wheel

typedef struct colour
{
  byte r;
  byte g;
  byte b;
};  // end of struct colour

// store the rainbox in memory
// WARNING! 3 bytes per pixel - take care you don't exceed available RAM
colour pixelArray [PIXELS];

void rainbow (const byte wait)
{
  unsigned int i, j;

  // cycle the starting point
  for (j = 0; j < 256; j++)
  {
    // build into in-memory array, as these calculations take too long to do on the fly
    for (i = 0; i < PIXELS; i++)
    {
      byte r, g, b;
      Wheel ((i + j) & 255, r, g, b);
      pixelArray [i].r = r;
      pixelArray [i].g = g;
      pixelArray [i].b = b;
    }
    // now show results
    noInterrupts ();
    for (i = 0; i < PIXELS; i++)
      sendPixel (pixelArray [i].r, pixelArray [i].g, pixelArray [i].b);
    interrupts ();
    show();
    delay(wait);
  }   // end of for each cycle
} // end of rainbox

void loop()
{
  rainbow(10);
} // end of loop


Experimentation showed that the calculations for the colours took too long if done on-the-fly, and the pixels reset. Thus, in this case, we have to precalculate the pixel colours into an RGB array. Then, we just "dump" the array to the pixels in a tight loop.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


16,997 views.

Postings by administrators only.

Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.