Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Addressable LEDs WS2813/WS2812

Not open for further replies.


So I been looking at these addressable LEDs and it seems that it's somewhat difficult to get started with them because of the non-standard protocol. I was thinking of running them with a PIC18F56K42 which can be clocked up to 64MHz. The required period of the WS2813 according to the datasheet is 1.25uS.

I'm first trying to understand why some suggest programming the pic must be done in assembly or using the CLC. Running at 64MHz I would get an instruction cycle of 64MHz/4 = 16MHz or 64.5ns per instruction. Wouldn't this suffice to generate the required timing required if programmed in C?

Has anyone here have tried these out?

I've not used these but have thought about how I would. If it was me I would setup an interrupt every 200uS and write 24 bits during that interrupt. For something this time critical I would write the transfer code in embedded asm as the time of the low pulse is only 200nS = 3 cycles!! Actually, I'd go as far as saying you would have to use assembly.

Good luck, keep us informed of your results.

As an afterthought, maybe a gap between bytes is acceptable and you can just call a write byte routine that's in asm from your C code.

Note from that diagram above. T0H = 375nS and T0L = 200nS - total 575nS which is not 1.25μS ± 300nS.
They work on the arduino in C and it's nowhere that fast.
Well it's fast I see how there doing it there using the spi

And one is in asm the Adafruit_NeoPixel

// WS2812 (rev A) timing is 0.35 and 0.7us
//#define MAGIC_T0H 5UL | (0x8000) // 0.3125us
//#define MAGIC_T1H 12UL | (0x8000) // 0.75us
// WS2812B (rev B) timing is 0.4 and 0.8 us
Last edited:
As be80 says the bitrate is high, however if you use a chip with an Spi peripheral the processor doesnt need to be fast.
The app note I posted above uses the Configurable logic cell with SPI and PWM to produce the protocol in hardware.

CLC version would be pretty cool

It looked cool I got some of these leds use them with a arduino I was kind of stuck on how it was being done because one library had about 50 include files.

The Adafruit_NeoPixel has just 2 there using timer and pwm and something that lets you cheat clock cycle on 8 nhz clock said it's not that hard on 16 tho
And 64 noth a problem
Ok thanks guys, seems like the way to go is with CLC on a microchip. There is an example on microchip's website on how to get started with it on a MPLAB XPRESS EVAL BOARD. I just bought one and will take a stab at it.

Ok another question, If had a string of 25 LEDs, but I just want to turn on LED #16 down the line, how could this be accomplished? I assume since the devices are cascaded down LEDs 1-15 will turn on and off and then stop at #16. Is this correct?

Here is the link to the CLC method using microchip.
Last edited:
The protocol is not like normal serial. The first 24 bits sent go to the first chip and it then passes on the pulses to the next chip. The data is only used when a latch state is detected - low for over 300μS.

So the LEDs will not turn on and off but will wait until all data has been sent (for 16 or 25 LEDs) and 300μS has elapsed without any pulses.

You basically send what each should show and latch it from What I been reading I started playing with the pic and this i put it on back burner
The frist code I found was overwhelming.

The arduino call's it show

/Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow(uint8_t wait) {
  for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
    for (int q=0; q < 3; q++) {
      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, Wheel( (i+j) % 255));    //turn every third pixel on
      for (uint16_t i=0; i < strip.numPixels(); i=i+3) {
        strip.setPixelColor(i+q, 0);        //turn every third pixel off
Unless you're running the processor at a high clock rate, I don't think you can bit bang the WS281x in C. I have code that runs on an 8 MHz ATTiny - assembler. Been looking at a 20 MHz C version and don't have a lot of hope.

I've looked at using SPI and think it's doable if you can get a SPI clock period of 125 nS. The problem is that there is no CS (or even clock) so the WS281x will respond to other SPI activity. You could gate the DI line. Also, you will have to keep the SPI fed pretty fast.

By the way, the Arduino drivers use in-line assembly code.

And to the question about turning on/off specific LEDs. Zeros are the same as off so the sending the 0xFF, 0xFF, 0xFF to the first LED, it will be off. heh heh 0xFF == off. [edit] yes, the correct statement is send 0x00 (not 0xFF).[/edit]
Last edited:
255 Is on you still got write the color

// Data latch = 300+ microsecond pause in the output stream. Rather than
// put a delay at the end of the function, the ending time is noted and
// the function will simply hold off (if needed) on issuing the
// subsequent round of data until the latch time has elapsed. This
// allows the mainline code to start generating the next frame of data
// rather than stalling for the latch.
// endTime is a private member (rather than global var) so that mutliple
// instances on different pins can be quickly issued in succession (each
// instance doesn't delay the next).
// In order to make this code runtime-configurable to work with any pin,
// SBI/CBI instructions are eschewed in favor of full PORT writes via the
// OUT or ST instructions. It relies on two facts: that peripheral
// functions (such as PWM) take precedence on output pins, so our PORT-
// wide writes won't interfere, and that interrupts are globally disabled
// while data is being issued to the LEDs, so no other code will be
// accessing the PORT. The code takes an initial 'snapshot' of the PORT
// state, computes 'pin high' and 'pin low' values, and writes these back
// to the PORT register as needed.
// NRF52 may use PWM + DMA (if available), may not need to disable interrupt

Hand-tuned assembly code issues data to the LED drivers at a specific
// rate. There's separate code for different CPU speeds (8, 12, 16 MHz)
// for both the WS2811 (400 KHz) and WS2812 (800 KHz) drivers. The
// datastream timing for the LED drivers allows a little wiggle room each
// way (listed in the datasheets), so the conditions for compiling each
// case are set up for a range of frequencies rather than just the exact
// 8, 12 or 16 MHz values, permitting use with some close-but-not-spot-on
// devices (e.g. 16.5 MHz DigiSpark). The ranges were arrived at based
// on the datasheet figures and have not been extensively tested outside
// the canonical 8/12/16 MHz speeds; there's no guarantee these will work
// close to the extremes (or possibly they could be pushed further).
// Keep in mind only one CPU speed case actually gets compiled; the
// resulting program isn't as massive as it might look from source here.
// 8 MHz(ish) AVR ---
Further thoughts on SPI for driving WS281x LEDs.

Summary: Using SPI to drive these LED is possible but there are issues that need to be addressed. I'm not convinced it's worth the effort.

The WS281x family of LEDs uses an odd timing based serial protocol.
ws2812b protocol.png

The SPI clock rate has to match the WS device in a way that aligns with the WS 0 and 1 timings. An SPI clock period of 400 nS (2.5 MHz) would work if using 3 SPI bits to represent one WS bit. A WS 0 bit would looks like 400 nS high, 800 nS low (SPI bits would be 100). For a WS 1, it would be 800 nS high and 400 nS low (SPI bits are 110).

The SPI bits need to be packed into the 8 bit SPI buffer which then has to be loaded every 8 SPI clocks (3.2 uS). FIFO buffer SPI HW would be a big help. However, this discussion presumes the SPI HW just has a single byte buffer.
ws timings.png

So that shows a stream of 9 bytes that must be fed to the SPI port at the correct time (approx every 8 SPI clocks, depending on the specific SPI HW). The WS serial engine does have some leeway as documented in the datasheet - about 300 nS max. However any longer delays in getting the byte to the SPI HW will cause the transfer to fail. This has implications for interrupts for simple SPI hardware. Either highest priority to the SPI int handler or ints off during the entire transfer sequence (and busy loop feeding the SPI port).

SPI clock: While I've shown a 2.5 MHz clock, there is some flexibility. The minimum high time for a 0 WS bit is 200 nS, maximum time is 500 nS. So a 2 Mhz clock might work though total time of a bit sequence would be slightly out of spec. The minimum low time for a 0 WS bit is 750 nS. Using the 3 bit encoding scheme from above, that makes the minimum SPI clock period 375 nS for a max clock rate of about 2.66 MHz. The timings for a 1 WS bit are reversed so we derive the same numbers. In summary, SPI clock range is 2 MHz to 2.66 MHz.[edit] Staying in spec, the clock range is 2.143 MHz to 2.667 MHz).[/edit] Note that the times mentioned above would have to be adjusted for a different clock rate.

The WS281x serial scheme makes it problematic to share SPI hardware. SPI devices all have an enable pin (CE, CS, Enable, ...) that prevent them from acting on random SPI signals. There is no such enable on the WS HW so it will respond to any signals on it's DI pin. This is undesirable as the WS will exhibit random behavior. So, either the SPI controller is dedicated to the WS LED string or a simple gate device is used. A single channel buffer with a enable pin would work though there are plenty of other devices. If driving the WS (a 5V device) from 3.3V or lower logic, the level shifter could incorporate an enable pin. I'm using a 74HCT buffer with output enable, for example. Note that any buffer device will need to be able to handle the data rate so take care in selecting a part for this.

There are alternate encoding schemes, 4 SPI bits per WS bit for example though they will require higher SPI clocks and faster response to filling the SPI buffer.
Last edited:
Pommie I found this code what do you think

#include <NeoCol.h>
#include <stdlib.h>
unsigned int8 NeoGreen [NeoNum];
unsigned int8 NeoBlue [NeoNum];
unsigned int8 NeoRed [NeoNum];
void NeoBit (int1 Bit)
   if (Bit == 1)
   { output_high (NeoPin); delay_cycles (6); output_low (NeoPin); } // delay_cycles (3); // Bit '1' 
   { output_high (NeoPin); delay_cycles (3); output_low (NeoPin); } // delay_cycles (6); // Bit '0' 
void NeoInit (void)
   unsigned int8 NeoPixel;
   for (NeoPixel = 0; NeoPixel < NeoNum; NeoPixel++) 
      if (NeoPixel < 10)
         { NeoGreen[NeoPixel] = 0; NeoBlue[NeoPixel] = 0; NeoRed[NeoPixel] = 64; }
      else if ((NeoPixel >= 10) & (NeoPixel < 20))
         { NeoGreen[NeoPixel] = 0; NeoBlue[NeoPixel] = 64; NeoRed[NeoPixel] = 0; }
      else if ((NeoPixel >= 20) & (NeoPixel < 30))
         { NeoGreen[NeoPixel] = 0; NeoBlue[NeoPixel] = 64; NeoRed[NeoPixel] = 64; }
      else if ((NeoPixel >= 30) & (NeoPixel < 40))
         { NeoGreen[NeoPixel] = 64; NeoBlue[NeoPixel] = 0; NeoRed[NeoPixel] = 0; }
      else if ((NeoPixel >= 40) & (NeoPixel < 50))
         { NeoGreen[NeoPixel] = 64; NeoBlue[NeoPixel] = 0; NeoRed[NeoPixel] = 64; }
      else if ((NeoPixel >= 50) & (NeoPixel < NeoNum))
         { NeoGreen[NeoPixel] = 64; NeoBlue[NeoPixel] = 64; NeoRed[NeoPixel] = 0; }   
void NeoDraw (void)
   unsigned int8 NeoPixel;
   signed int8 BitCount;
   for (NeoPixel = 0; NeoPixel < NeoNum; NeoPixel++)
      for (BitCount = 7; BitCount >= 0; BitCount--)   
         NeoBit(bit_test(NeoGreen[NeoPixel], BitCount));   
      for (BitCount = 7; BitCount >= 0; BitCount--)         
         NeoBit(bit_test(NeoRed[NeoPixel], BitCount));         
      for (BitCount = 7; BitCount >= 0; BitCount--)   
         NeoBit(bit_test(NeoBlue[NeoPixel], BitCount));   
   output_low (NeoPin);
void NeoRotate (void)
   unsigned int8 NeoPixel; 
   for (NeoPixel = 0; NeoPixel < NeoNum - 1; NeoPixel++) 
      NeoGreen[NeoPixel] = NeoGreen[NeoPixel + 1];
      NeoBlue[NeoPixel] = NeoBlue[NeoPixel + 1];
      NeoRed[NeoPixel] = NeoRed[NeoPixel + 1];
   NeoGreen[NeoNum - 1] = NeoGreen[0];
   NeoBlue[NeoNum - 1] = NeoBlue[0];
   NeoRed[NeoNum - 1] = NeoRed[0];
void main()
   NeoInit (); 
      NeoDraw ();
      NeoRotate ();
      delay_ms (25);

Looks like it would work

Here's the header file
#include <18F2550.h>
#device ADC = 16
#FUSES NOWDT                    //No Watch Dog Timer
#FUSES WDT128                   //Watch Dog Timer uses 1:128 Postscale
#FUSES NOBROWNOUT               //No brownout reset
#FUSES NOLVP                    //No low voltage prgming, B3(PIC16) or B5(PIC18) used for I/O
#FUSES NOXINST                  //Extended set extension and Indexed Addressing mode disabled (Legacy mode)
#use delay(clock=48000000,crystal=20000000)
#define NeoPin PIN_E0
#define NeoNum 60
#define RAND_MAX 64
#define ALL_OUT 0x00
#define ALL_IN  0xFF
#byte PORTA = 0xF80

Hope will look at above code I can't get the bug's out of it with xc8 1.44
Last edited:
Happy to have a look at that code Burt but there are some files missing. Where are int8, output_high, bit_test etc. defined?

Not open for further replies.

Latest threads

New Articles From Microcontroller Tips