Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

First Adafruit NeoPixel Blinks with the FRDM Board

DZone's Guide to

First Adafruit NeoPixel Blinks with the FRDM Board

· IoT Zone
Free Resource

LEDs are getting smarter these days. An amazing example are the WS2812(B) or ‘NeoPixels’ from Adafruit: RGB LEDs with a built-in constant current controller and shift register! With a single wire data wire hundreds of RGB LEDs can be controlled. Exactly what I need for a project I had in mind for a very long time. So I ordered a bunch of different LEDs from Adafruit to experiment. Exactly the right thing on dark and rainy week-end. And the result is, well: bright and colorful :-)

Adafruit NeoPixel LED Ring

Adafruit NeoPixel LED Ring

The WS2812 LEDs are really interesting: they have a constant current controller with shift register in that LED. A close up view of an LED shows the silicon die with the bonding wires:

WS2812 LED

WS2812(S) LED

The LED silicon die for red, green and blue are actually very small:

WS2812 LED with red green and blue

WS2812(S) LED with red green and blue

Adafruit offers many different shapes and boards with NeoPixel LEDs, from individual LEDs, to stripes, rings and matrix boards. I recommend to read the following articles:

Protocol

Each pixel needs 24bit of data (8bit Red, 8bit Green and 8bit Blue). The bits are shifted using a one wire protocol to the LED DIN (Data In) pin. A bit odd, but the bits are shifted as Green-Red-Blue (so not RGB). The MSB (Most Significant Bit) is shifted first. Each pixel will take the first 24bits, and shift out the remaining bits DOUT (Data Out) to the next LED, and so on. So the LED’s can be easily chained together:

WS2812 LED Chain

WS2812 LED Chain

The following shows a capture of the bit stream: the first line is the input (DIN) pin of the first LED, and the second line is the output pin (DOUT) for a total of 4 pixels. The broader pulse marks a 1 bit, and the shorter pulses are zero bits. The first pixel consumes the first 24 bits (green-red-blue) and shifts the other 3×24 bits out. At the end, having the signal low for 50 μs will latch the bits and show them in the LED.

WS2812 Bit Stream

WS2812 Bit Stream (click to enlarge)

The signal is rather fast with 1250 ns period for a single bit. The zero bit is encoded with 350 ns high, and the one bit is encoded with the signal high for 700 ns. Actual timing depends on the version of the pixels, as outlined by this article.

Starting with a Bread Board

As a starter, I ordered 4 bread-board-friendly WS2812(S) NeoPixels to make sure my timing and driver works. Following the Adafruit guidelines, I have put a 400 Ohm resistor between the digital output pin and the first LED. The LEDs need a 5V power supply. While it is possible to get below that, going higher than 5V might destroy the LED. Initially I’m using the 5V from USB, so with a few LEDs no external power supply is needed. Each LED in full brightness needs about 3x20mA, see the Adafruit guide on this topic. For more LEDs, a beefier power supply is needed.

In the picture below I’m not yet using the recommended capacitor of 1000 µF, so make sure you have one added. Because the voltage of the LEDs is 5V, and my microcontroller has 3.3V logic levels, I had to add a level shifter (more about this later). With that setup, my software driver and some wiring, I had the first NeoPixel bright and shiny:

Breadboarding a single neopixel

Breadboarding a single NeoPixel

With adding more pixels, I have added the recommended 1000 µF capacitor to avoid an onrush of current which could damage the LEDs. Using a fast logic analyzer is recommended to inspect the signals.

Test Bench Setup with 1000 uF Capacitor added

Test Bench Setup with 1000 uF Capacitor added

WS2812 Driver

As the protocol and timing is critical, I used my knowledge with DMA on the FRDM-KL25Z board. The DMA allows me to spit out the bits fast enough, within the timing requirements without loading the CPU too much. I have configured a PWM output pin with a period of 1250 ns, and I’m changing the duty with PWM to encode the zero and one bits.

For the PWM values I use a global buffer in RAM:

#define NEO_NOF_PRE         2 /* somehow need trailing values? */
#define NEO_NOF_BITS_PIXEL  24  /* 24 bits for pixel */
#define NEO_NOF_POST        40 /* latch, low for at least 50 us (40x1.25us) */
#define NEO_DMA_NOF_BYTES   sizeof(transmitBuf)
 
static uint16_t transmitBuf[NEO_NOF_PRE+(NEO_NOF_PIXEL*NEO_NOF_BITS_PIXEL)+NEO_NOF_POST];

Ideally I would only use 3 bytes of RAM for each Pixel. However, as I need a 16bit value for each PWM duty value, I need 16×3 bytes for each pixel. That’s ok for now, but I already have a different implementation in my mind which will cut down that RAM amount needed.

:idea: There are bit-banging implementations available on the internet which only need 3 byte RAM per pixel. They shift the bits with bit banging with very timing sensitive assembly code. I did not want to implement such a low-level implementation: with sacrificing RAM and using DMA I still have the microcontroller available for doing other things and not keeping the microprocessor busy 100% with the bit shifting.

The PWM is intitialized with period and with DMA enabled:

static void InitTimer(void) {
  TPM_PDD_WriteModuloReg(TPM0_BASE_PTR, TICKS_PERIOD); /* set period */
  TPM_PDD_WriteChannelValueReg(TPM0_DEVICE, 1, 0); /* PWM low, zero duty */
  TPM_PDD_EnableChannelDma(TPM0_DEVICE, 1); /* enable DMA for channel */
}

For the DMA, I configure the source and destination address, along with the DMA transfer size:

static void InitDMA(void) {
  DMA_PDD_SetSourceAddress(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, (uint32_t)&transmitBuf[0]); /* set source address */
  DMA_PDD_SetSourceAddressModulo(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, DMA_PDD_CIRCULAR_BUFFER_DISABLED); /* no circular buffer */
  DMA_PDD_EnableSourceAddressIncrement(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, PDD_ENABLE); /* source address will be incremented by transfer size */
  DMA_PDD_SetSourceDataTransferSize(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, DMA_PDD_16_BIT); /* Transfer size from source is 16bit */
 
  DMA_PDD_SetDestinationAddress(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, (uint32_t)&TPM0_C1V); /* set destination address */
  DMA_PDD_SetDestinationAddressModulo(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, DMA_PDD_CIRCULAR_BUFFER_DISABLED); /* no circular buffer */
  DMA_PDD_EnableDestinationAddressIncrement(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, PDD_DISABLE); /* no auto-increment for destination address */
  DMA_PDD_SetDestinationDataTransferSize(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, DMA_PDD_16_BIT); /* Transfer to destination size is 16bit */
 
  DMA_PDD_SetByteCount(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, NEO_DMA_NOF_BYTES); /* set number of bytes to transfer */
  DMA_PDD_EnableTransferCompleteInterrupt(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, PDD_ENABLE); /* request interrupt at the end of the DMA transfer to set new byte count */
  (void)DMA_PDD_GetRequestAutoDisableEnabled(DMA_BASE_PTR, DMA_PDD_CHANNEL_0); /* disable DMA request at the end of the sequence */
  DMA_PDD_EnablePeripheralRequest(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, PDD_ENABLE); /* enable request from peripheral */
}

I have configured it I get a flag set at the end of the DMA cycle. This allows me to check a flag if the DMA transfer is still in progress or not. The flag is reset from the ‘on DMA end interrupt’:

voidMyDMAComplete(void) {
 transferComplete = TRUE;
}
[/sourcecode
Sending the bits over the wire with DMA is then very simple:</pre>
<pre>1
static uint8_t Transfer(uint32_t src) {
  while(!transferComplete) {
    /* wait until previous transfer is complete */
  }
  transferComplete = FALSE;
  DMA_PDD_SetSourceAddress(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, src); /* set source address */
  DMA_PDD_SetByteCount(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, NEO_DMA_NOF_BYTES); /* set number of bytes to transfer */
  DMA_PDD_EnablePeripheralRequest(DMA_BASE_PTR, DMA_PDD_CHANNEL_0, PDD_ENABLE); /* enable request from peripheral */
  return ERR_OK;
}
 
uint8_t NEO_TransferPixels(void) {
  return Transfer((uint32_t)&transmitBuf[0]);
}

After using the 4 bread board pixels worked fine, I added more pixels with the Adafruit 60 pixel ring, right after the bread board pixels:

Adafruit Neopixel Ring

Adafruit Neopixel Ring

Video of the pixel ring in action (the bright LEDs created funny pixel artifacts on my camera!):


Level Shifter is Critical!

I was soooooooo happy to see that things are working well. Until I wanted to use the ring without the bread board pixels: the pixels in the ring did not work any more :-(

Looking at my bits shifted out of the microprocessor looked fine: the microcontroller was sending 750ns/500ns Bit-1 signal to the first WS2812B pixel. But this 1-Bit signal gets out as 625ns/625ns out of the WS2812B :-(:

Input Ouput Bit 1

Input Ouput Bit 1

Similar picture: I’m sending a 375ns/875 ns 0-bit from the microcontroller, but the first WS2812B makes a 312ns/937ns signal out of it:

Analyzing 2812 ouput

Analyzing 2812 ouput

After some heads-scratching and more scoping it was clear: my level shifter was not fast and crisp enough for the WS2812B LEDs :-(. It worked fine for the older WS21812(S) version. And as long as I had a WS2812B as first pixel, that pixel was able to generate the correct signals for the new WS2812B LEDs.

Well, if I only would have found and read http://happyinmotion.com/?p=1247 before…. I had an Adafruit BSS138 available, and while it worked with the WS2812(S), I was wrongly thinking that it would work with the WS2812B too. Well, after the fact it is clear that the pull-ups ruined my signals and my day….

So I have ordered a handful of 74HCT245 to test with and to make it right. Until then, another video with even more pixels in action:



Summary

I think I have been falling in love with these LEDs. They are amazing, and amazing effects can be created with them. It is just that for now my wife does not share that excitment (yet?). Guess I need to work on a more impressive application then… :-). Right now my driver is using to much RAM, so there is for sure room for improvements. While the older WS2812(S) LEDs work with a sluggish level shifter, the new WS2812B need clean signals e.g. from a 74HCT245 (to be verified). Until then a WS2812 as first pixel in the chain helps :-).

The project and sources for Eclipse Kepler are available on GitHub here.

Topics:

Published at DZone with permission of Erich Styger, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}