Pi Zero NeoPixel
I needed a status indicator for a headless Raspberry Pi Zero project and came across these PCBs with 8 RGB LEDs that looked interesting. The LEDs on the PCB are WS2812B that are chained together and driven by a 3 wire connection, ground, five volts power, and a single data line.

To cut to the chase, the initial result is a simple C program in the pi0neopixel repo that provides a demo light show on the LED strip.

Clocks and Bits

The data signal is a combination of a clock and data bits where the pulse width of each half of a clock cycle determines if the bit for that cycle has a value of 0 or 1. Note in the following square wave signals the difference in the time that the signal is high versus low for a 0 value bit and a 1 value bit. While the cycle time for each clock pulse is the same the time that the clock signal remains high is shorter for the 0 bit and longer for a 1 bit.

      <= clock cycle =>
       ______
0 bit |      |        |
      |      |________|

       _________
1 bit |         |     |
      |         |_____|

time  +-------+--------+
    
Each LED pixel takes an RGB value made up of 3 bytes to make up the color components of the pixel, and this results in a 24 bit value for each LED. And for the LED strip used in this project there are 8 pixels * 24 bits for a total of 192 bits, or clock cycles, that need to be applied to the data line to program the color for each LED in the strip.

Serial Bit Hacking

The Raspberry Pi Zero can be configured to provide a SPI interface for serial communication and the data for the LED strip is serial. However, the SPI uses a separate data clock and we need and combination clock and data signal. So we will ignore the SPI clock signal and use some bit hacking to turn the SPI data signal into the combination clock and data signal needed to drive the LEDs.

Enable SPI in /boot/config.txt

    [all]
    dtparam=spi=on
    

Raspberry Pi Zero to WS2812B-8

The Raspberry Pi Zero provides the +5V (Pin 2) and Ground (Pin 14) to power the WS2812B-8 LED PCB and the SPI MOSI (Pin 19) provides the data signal to program the LED display.

Bits to Nybles to Bytes

To combine the clock and data each bit in the 3 RGB bytes must be converted into a clock cycle. And these clock cycles must have shorter pulse widths for 0 bits and longer pules widths for the 1 bits. To accomplish this each data bit will be represented by 4 signal bits to allow shaping of the clock signal with variable widths.
      data   signal
       0  =>  1000
       1  =>  1110
    
Each byte in the RGB value will be converted into a total of 4 signal bytes. As an example, assume we have an RGB value with the R byte value is 218 which in binary is 0b11011010. Each bit in this byte must be converted into the 4 bit representation for the signal...
 (R data byte)    1    1      0    1      1    0      1    0
                  v    v      v    v      v    v      v    v
(4 Signal Bytes) 1110 1110 | 1000 1110 | 1110 1000 | 1110 1000
    
The R byte value of 218 now becomes 4 signal bytes with the values 238, 142, 232, 232. Also notice in the binary values how the signal always starts high for each data bit and toggles low before the next data bit.
The following function is from the C demo program and demonstrates how a single byte value from the RGB data is convereted into the 4 signal bytes that will be sent out on the SPI data line.

      // define the nibble values when converting color bits into the NeoPixel signal
      // values in the T0H-T0L and T1H-T1L format, may require adjustment for SPI speed
      #define T0MSN 0x80 // Most significant nibble for 0 bit
      #define T1MSN 0xE0 // Most significant nibble for 1 bit
      #define T0LSN 0x08 // Least significant nibble for 0 bit
      #define T1LSN 0x0E // Least significant nibble for 1 bit

      // fill the buffer at the provided pointer with the NeoPixel T0H-T0L/T1H-T1L signal for the given RGBW byte
      unsigned char* fillColor(unsigned char* colorPt, int RGBWvalue)
      {
      	int loop;
      	unsigned char _temp;

      	// loop through 4 sets of 2 bits for the given RGBW byte
      	for (loop = 0; loop < 4; loop++)
      	{
      		// take 2 most significant bits and spread them across one byte
      		// into ws2812B signal format for T0H-T0L and T1H-T1L
      		_temp = (RGBWvalue & 0x80) ? T1MSN : T0MSN;
      		_temp |= (RGBWvalue & 0x40) ? T1LSN : T0LSN;
      		// store signal byte in buffer
      		*(colorPt++) = _temp;
      		// shift to the next two bits
      		RGBWvalue <<= 2;
      	}

      	// return the incremented pointer for potential reuse
      	return colorPt;
      }
    

Demo Light Show

The C source code contains additional functions to facilitate the initialization, SPI communication, and the light show demo logic. The end result is what is shown in the following video...