NeoPixel

NeoPixel

This is a small module developped to control WS2812B (4 pins) NeoPixel LED modules. The module uses simple pin bitbanging in order to shift data into the LED strip.

Used datasheet was this one http://www.pocketmt.com/downloads/WS2812B.pdf

The datasheet claims that the High pulse needs to be two times larger than low pulse (0.80µs vs 0.40µs) for one (1), and the reverse for zero (0). From my personal testing, the values need not to be as exact as on the datasheet. Worse, on different strips coming from different manufacturer (chineese strips on ebay), sometimes code didn't even worked. So I implemented the bitbanging code on a powerful ARM chip with fast IO capabilities (bitbanging up to 72MHz). I tried to check the limits that makes the LEDs work or not.

From my personal experience, those conditions need to be true:

- The pulse need to be 2 vs 1 and 1 vs 2 for One and Zero bit to be correct. Be careful, even if it works for single LED, you need to check that your code works for multiple LEDs also (data propagation).

- The High pulse for One need absolutely to be wider than 0.38µs in order to work for all LED strips I tested.

- The Low pulse for One need to be at least as large as the high pulse (two times is not 100% mandatory), if it doesn't, you'll have problem making "long" LED strips to work.

- After powering up your circuit, you need to give about 2 seconds to the LED strips before starting communication with them. If you communicate with them "before" this timing, you can write ANY value for RGB, but not the (0,0,0) which is equivalent to shutdown (the LEDs keeps a bit lighting).

Be careful, the following program and module have been tuned for a PIC18F25K22 running at 64MHz with internal Oscillator. If you want to change PIC frequency (in order to use another PIC chip for example), you need to re-tune the neopixel module procedure Inline Sub PulseWidth() This procedure must provide approximatively 0.40µs timing (it's better to have something a bit beyond 0.40 than below it).

I have done my tests using this module, but a lot of friends reported that it works for them on rings and other LED strips modules

PS. The DataPin PIC pin need to be pulled up with a 4K7 resistor.

Main Features of Swordfish NeoPixel module :

1- Handle single RGB NeoPixel LED or multiple NeoPixel RGB LED Strips

For any comment, or feedback (or bug report), please post to Swordfish Forum.

IMPORTANT Options

  1. option LED_COUNT = nnValue // where nnValue is the number of the NeoPixel strip RGB LEDs, default to 8
  2. option DATA_PIN = PORTB.0 // Data Pin used to communicate with the LED strip

Example Code NeoPixel module handling a strop of 8 NeoPixel LEDs

{
*************************************************************************
*  Name    : neopixeltest.BAS                                           *
*  Author  : Ahmed Lazreg  ([email protected])                  *
*            http://www.pocketmt.com                                    *
*            http://www.microcodeproject.com                            *
*  Notice  : Copyright (c) 20017 Pocket MicroTechnics                   *
*          : All Rights Reserved                                        *
*  Date    : 18/01/2017                                                 *
*  Version : 1.0                                                        *
*  Desc.   : Test program for NeoPixel module                           *
*          : PIC18F25K22 running at 64MHz with internal OSC             *
*************************************************************************
}

Device = 18F25K22      'Automatically brings in device file 18F25K22.bas
Clock = 64             '64MHz
Config FOSC = INTIO7   'tells the chip to use its internal oscillator

//dim dataPin as LATB.0

Include "SetDigitalIO.bas"
include "spi.bas"

#option LED_COUNT = 8
#option DATA_PIN = PORTB.0  

include "neopixel.bas"

dim i as byte
Dim gPixels(8) as TColor

Const gConstPixels(LED_COUNT * 3 ) as Byte = (
                                           // G  , R  , B
                                              0  , 0  , 255, 
                                              0  , 255, 0  , 
                                              255, 0  , 0  , 
                                              0  , 0  , 255, 
                                              0  , 255, 0  , 
                                              255, 0  , 0  , 
                                              0  , 0  , 255, 
                                              255, 255, 255
                                             )

OSCCON = %01111100     'Tells the chip how fast to run (amongst other things), see below
OSCTUNE.6  = 1

SetAllDigital()
TRISB = $00
LATB = 0

delayMs(2000)

//if you use only a single RGB LED, you can directly write to it
// WriteRGBPixel(255, 255, 255)

while true
    NeoPixel.ClearStrip()
    delayMs(1000)

    for i = 0 to 7
      NeoPixel.Pixels(i) = NeoPixel.MakeColor(255, 0, 0)
    next
    NeoPixel.UpdateStrip()
    delayMs(1000)

    for i = 0 to 7
      NeoPixel.Pixels(i) = NeoPixel.MakeColor(0, 255, 0)
    next
    NeoPixel.UpdateStrip()
    delayMs(1000)

    for i = 0 to 7
      NeoPixel.Pixels(i) = NeoPixel.MakeColor(0, 0, 255)
    next
    NeoPixel.UpdateStrip()
    delayMs(1000)

    for i = 0 to 7
      gPixels(i).R = 255
      gPixels(i).G = 255
      gPixels(i).B = 255
    next
    NeoPixel.WriteRGBPixels(gPixels) // copy new pixels 
    NeoPixel.UpdateStrip()
    delayMs(1000)

    NeoPixel.ClearStrip()
    delayMs(1000)
    WriteRGBPixels(gConstPixels)
    NeoPixel.UpdateStrip()
    delayMs(1000)

wend

Module Code

{
*************************************************************************
*  Name    : NeoPixel.BAS                                               *
*  Author  : Ahmed Lazreg  ([email protected])                  *
*            http://www.pocketmt.com                                    *
*            http://www.microcodeproject.com                            *
*  Notice  : Copyright (c) 20017 Pocket MicroTechnics                   *
*          : All Rights Reserved                                        *
*  Date    : 15/01/2017                                                 *
*  Version : 0.1                                                        *
*  Modified:                                                            *
*  Notes   : This module handles NeoPixels LED strips                   *
*************************************************************************
}

Module NeoPixel

#option LED_COUNT = 8
#if LED_COUNT <= 0
    #error LED_COUNT, "Invalid option. LED_COUNT must be greater than zero"
#endif

#option DATA_PIN = PORTB.0     
#if IsOption(DATA_PIN) And Not IsValidPortPin(DATA_PIN) 
   #error DATA_PIN, "Invalid option. DATA_PIN must be a valid port pin."
#endif

Dim
    DataPin As DATA_PIN.DATA_PIN@      // PixelStrip  data-pin

Public Const PIXELS_COUNT = LED_COUNT

Public Structure TColor
   G as Byte
   R as Byte
   B as Byte
End Structure

Public Dim Pixels(PIXELS_COUNT) as TColor

Public Function MakeColor(R As Byte, G As Byte, B As Byte) as TColor
    result.R = R
    result.G = G
    result.B = B
End Function

Inline sub NOP()
   asm
    NOP
   end asm
end sub

// this is tuned for 64MHz internal osc of PIC18F25K22 internal oscillator
Inline Sub PulseWidth()
    NOP()
    NOP()
    NOP()
    NOP()
End Sub

Inline Sub One()
    DataPin = 1
    PulseWidth()
    PulseWidth()
    DataPin = 0
    PulseWidth()
End Sub

Inline Sub Zero()
    DataPin = 1
    PulseWidth()
    DataPin = 0
    PulseWidth()
    PulseWidth()
End Sub

Inline Sub ResetCom()
    DelayUs(50)
End Sub

Inline Sub WriteNeoByte(pB as Byte)
    ' do NOT replace me with a loop !!!
    If (pB.7) = 1 Then One() Else Zero() EndIf
    If (pB.6) = 1 Then One() Else Zero() EndIf
    If (pB.5) = 1 Then One() Else Zero() EndIf
    If (pB.4) = 1 Then One() Else Zero() EndIf
    If (pB.3) = 1 Then One() Else Zero() EndIf
    If (pB.2) = 1 Then One() Else Zero() EndIf
    If (pB.1) = 1 Then One() Else Zero() EndIf
    If (pB.0) = 1 Then One() Else Zero() EndIf
End Sub

Public Sub WriteRGBPixel(Green As Byte, Red As Byte, Blue As Byte )
    WriteNeoByte(Green)
    WriteNeoByte(Red)
    WriteNeoByte(Blue)
End Sub

Public Sub WriteRGBPixel(pColor as TColor )
    WriteNeoByte(pColor.G)
    WriteNeoByte(pColor.R)
    WriteNeoByte(pColor.B)
End Sub

Public Sub WriteRGBPixels(ByRef pPixels() As TColor)
    Dim i As Byte
    For i = 0 To Bound(pPixels)
        Pixels(i) = pPixels(i)
    Next
End Sub

// Const Arrays of Structures cannot be defined in Swordfish BASIC
// User can define array of bytes arranged in G,R,B order
Public Sub WriteRGBPixels(ByRefConst pPixelsBytesGRB() As Byte)
    Dim i As Byte
    For i = 0 To Bound(pPixelsBytesGRB) Step 3
        Pixels(i).G = pPixelsBytesGRB(i)
        Pixels(i).R = pPixelsBytesGRB(i+1)
        Pixels(i).B = pPixelsBytesGRB(i+2)
    Next
End Sub

// puts pixels content on hardware strip 
Public Sub UpdateStrip()
    Dim i As Byte
    For i = 0 To PIXELS_COUNT-1
        WriteRGBPixel(Pixels(i))
    Next
    ResetCom()
End Sub

// Writes Zero everywhere on hardware strip. Dos not alter the module pixels array
Public Sub ClearStrip() 
    Dim i As Byte
    For i = 0 To PIXELS_COUNT-1
        WriteRGBPixel(0, 0, 0)
    Next
    ResetCom()
End Sub

Sub Initialize()
    Output(DataPin)
    DataPin = 0
End Sub

Initialize()

End Module