News:

Let's find out together what makes a PIC Tick!

Main Menu

Positron8 - WS2812B RGB LED library

Started by top204, Mar 14, 2022, 12:05 PM

Previous topic - Next topic

top204

After reading Trikkitt's excellent code for HSV to RGB, I searched for the Hue type mechanism I created for the WS2812B RGB LED devices last year, and eventually found it. :-) Trikkit's code can be found here: HSV to RGB

Below is the complete library for controlling WS2812B devices, and it has the rudimentary Hue procedure I created in it, named: "WS2812B_ColourHSB". Looking at it now, I think Trikkitt's version is a bit more optimised than mine, so I'll get into the code again ASAP. The WS2812B library is optimised by using the compiler's Long variables, which are 24-bits, thus no RAM wastage with 24-bit RGB in a 32-bit variable. :-) It also has procedures for "with and without" Gamma control of the LEDs.

$ifndef _WS2812B_INC_
$define _WS2812B_INC_
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' WS2812B RGB LED interface library
' Because of the very tight pulse requirements of the WS2812B device, this code is for a PIC device operating at higher oscillation frequency
' The frequencies supported with this library are 12MHz, 16MHz, 32MHz, 40MHz, 48MHz and 64MHz
'
' Written by Les Johnson for the Positron8 BASIC compiler.
'
' Create variables
'
    Dim WS2812B_wIndex    As Word Access            ' Used to access the amount of WM2812B devices on the line
    Dim WS2812B_bBitIndex As Byte Access            ' Used to access each bit in the WS2812B interface
    Dim WS2812B_lRGB      As Long Access            ' Used to hold the Green, Red, and Blue bytes
'
' The colour of each LED is encoded as three LED brightness values, which must be sent in GRB (Green-Red-Blue) order.
'
    Dim WS2812B_bGreen As WS2812B_lRGB.Byte2        ' Alias the green byte
    Dim WS2812B_bRed   As WS2812B_lRGB.Byte1        ' Alias the red byte
    Dim WS2812B_bBlue  As WS2812B_lRGB.Byte0        ' Alias the blue byte

    Dim WS2812B_wChipNumber As Word                 ' The WS2812B to access on a line of them
    Dim WS2812B_pGreen As Byte                      ' The Green value for the WS2812B to access on a line of them
    Dim WS2812B_pRed   As Byte                      ' The Red value for the WS2812B to access on a line of them
    Dim WS2812B_pBlue  As Byte                      ' The Blue value for the WS2812B to access on a line of them
'
' The amount of WS2812B chips on the strip
'
$ifndef WS2812B_Amount
    $error "$define WS2812B_Amount not issued in the main program"
$else
    Symbol cWS2812B_IndexAmount = (WS2812B_Amount - 1)
$endif

    Dim WS2812B_bRedArray[WS2812B_Amount]   As Byte Heap  ' Holds the red value for a particular WS2812B device
    Dim WS2812B_bGreenArray[WS2812B_Amount] As Byte Heap  ' Holds the green value for a particular WS2812B device
    Dim WS2812B_bBlueArray[WS2812B_Amount]  As Byte Heap  ' Holds the blue value for a particular WS2812B device
'
' WS2812B pulse timing constants for 12MHz, 16MHz, 32MHz, 40MHz, 48MHz and 64MHz oscillators
'
$if _xtal = 64                                      ' Are we using a 64MHz oscillator?
    Symbol cWS2812B_Zero = 6                        ' Amount of cycles for a zero delay (approx 350ns)
    Symbol cWS2812B_One  = 16                       ' Amount of cycles for a one delay (approx 900ns)
$elseif _xtal = 48                                  ' Are we using a 48MHz oscillator?
    Symbol cWS2812B_Zero = 4                        ' Amount of cycles for a zero delay (approx 350ns)
    Symbol cWS2812B_One  = 10                       ' Amount of cycles for a one delay (approx 900ns)
$elseif _xtal = 40                                  ' Are we using a 40MHz oscillator?
    Symbol cWS2812B_Zero = 3                        ' Amount of cycles for a zero delay (approx 350ns)
    Symbol cWS2812B_One  = 9                        ' Amount of cycles for a one delay (approx 900ns)
$elseif _xtal = 32                                  ' Are we using a 32MHz oscillator?
    Symbol cWS2812B_Zero = 2                        ' Amount of cycles for a zero delay (approx 350ns)
    Symbol cWS2812B_One  = 7                        ' Amount of cycles for a one delay (approx 900ns)
$elseif _xtal = 16                                  ' Are we using a 32MHz oscillator?
    Symbol cWS2812B_Zero = 0                        ' Amount of cycles for a zero delay (approx 350ns)
    Symbol cWS2812B_One  = 2                        ' Amount of cycles for a one delay (approx 900ns)
$elseif _xtal = 12                                  ' Are we using a 12MHz oscillator?
    Symbol cWS2812B_Zero = 0                        ' Amount of cycles for a zero delay (approx 350ns)
    Symbol cWS2812B_One  = 1                        ' Amount of cycles for a one delay (approx 900ns)
$else
    $error "12MHz, 16MHz, 32MHZ, 40MHz, 48MHz and 64MHz oscillators suitable for this code"
$endif
    Symbol cWS2812B_ResetUs = 60                    ' Amount of microseconds for a reset delay
'
' The pin that the WS2812B chips are connected too
'
$ifndef WS2812B_Pin
    $define WS2812B_Pin PORTB.0
    $SendWarning "$define WS2812B_Pin not issued in the program. Using the default pin of PORTB.0"
$endif

'---------------------------------------------------------------------------------------------
' Send a 0 (approx 0.35us) to the WS2812B device
' Input     : None
' Output    : None
' Notes     : Uses microcontroller instruction cycles for the small delay
'
$define WS2812B_SendZero() '
    PinSet WS2812B_Pin     '
    DelayCS cWS2812B_Zero  '
    PinClear WS2812B_Pin

'---------------------------------------------------------------------------------------------
' Send a 1 (approx 0.9us) to the WS2812B device
' Input     : None
' Output    : None
' Notes     : Uses microcontroller instruction cycles for the small delay
'
$define WS2812B_SendOne() '
    PinSet WS2812B_Pin    '
    DelayCS cWS2812B_One  '
    PinClear WS2812B_Pin

'---------------------------------------------------------------------------------------------
' Send a reset (50us) to the WS2812B device
' Input     : None
' Output    : None
' Notes     : None
'
$define WS2812B_Finish()    '
    PinClear WS2812B_Pin    '
    DelayUS cWS2812B_ResetUs

'---------------------------------------------------------------------------------------------
' Interface to a single WS2812B RGB controller chip without gamma
' Input     : pRed holds the red value (0 to 255)
'           : pGreen holds the green value (0 to 255)
'           : pBlue holds the blue value (0 to 255)
' Output    : None
' Notes     : Sends the 24-bits MSB.
'           : A zero bit is a high pulse for approx 350ns
'           : A one bit is a high pulse for approx 900ns
'
Proc WS2812B_RGB(pRed As WS2812B_bRed, pGreen As WS2812B_bGreen, pBlue As WS2812B_bBlue)
    For WS2812B_bBitIndex = 23 DownTo 0                                 ' Create a loop for the 24-bits of data to send to the WS2812B
        If WS2812B_lRGB.23 = 1 Then                                     ' Is the bit a 1?
            WS2812B_SendOne()                                           ' Yes. So send a 1 bit to the WS2812B chip
        Else                                                            ' Otherwise... We have a zero bit. So...
            WS2812B_SendZero()                                          ' Send a 0 bit to the WS2812B chip
        EndIf
        Rol pBlue                                                       ' WS2812B_dRGB.Byte0 \
        Rol pRed                                                        ' WS2812B_dRGB.Byte1  | Rotate the 24 colour bits
        Rol pGreen                                                      ' WS2812B_dRGB.Byte2 /
    Next
EndProc

'---------------------------------------------------------------------------------------------
' Interface to a single WS2812B RGB controller chip (with gamma control)
' Input     : pRed holds the red value (0 to 255)
'           : pGreen holds the green value (0 to 255)
'           : pBlue holds the blue value (0 to 255)
' Output    : None
' Notes     : Sends the 24-bits MSB.
'           : A zero bit is a high pulse for approx 350ns
'           : A one bit is a high pulse for approx 900ns
'
Proc WS2812B_RGB_Gamma(pRed As WS2812B_bRed, pGreen As WS2812B_bGreen, pBlue As WS2812B_bBlue)
'
' Create a Gamma correction table
'
    Dim Gamma8 As Flash8 = {0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
                            0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,
                            1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2,
                            2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5,
                            5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10,
                            10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
                            17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
                            25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
                            37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
                            51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
                            69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
                            90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114,
                            115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142,
                            144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
                            177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213,
                            215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255}

    pRed   = CRead8 Gamma8[pRed]                                        ' \
    pGreen = CRead8 Gamma8[pGreen]                                      ' | Add Gamma correction to the LEDs
    pBlue  = CRead8 Gamma8[pBlue]                                       ' /
    For WS2812B_bBitIndex = 23 DownTo 0                                 ' Create a loop for the 24-bits of data to send to the WS2812B
        If WS2812B_lRGB.23 = 1 Then                                     ' Is the bit a 1?
            WS2812B_SendOne()                                           ' Yes. So send a 1 bit to the WS2812B chip
        Else                                                            ' Otherwise... We have a zero bit. So...
            WS2812B_SendZero()                                          ' Send a 0 bit to the WS2812B chip
        EndIf
        Rol pBlue                                                       ' WS2812B_dRGB.Byte0 \
        Rol pRed                                                        ' WS2812B_dRGB.Byte1 | Rotate the 24 colour bits
        Rol pGreen                                                      ' WS2812B_dRGB.Byte2 /
    Next
EndProc

'---------------------------------------------------------------------------------------------
' Illuminate a WS2812B based upon its position in a string of them
' Input     : pChipNum holds the WS2812B in the line to alter (0 to 65535)
'           : pRed holds the red value (0 To 255)
'           : pGreen holds the green value (0 to 255)
'           : pBlue holds the blue value (0 to 255)
' Output    : None
' Notes     : None
'
Proc WS2812B_Colour(pChipNum As WS2812B_wChipNumber, pRed As WS2812B_pRed, pGreen As WS2812B_pGreen, pBlue As WS2812B_pBlue)
$if WS2812B_Amount = 1                                                  ' Is there only 1 WS2812B on the line?
    WS2812B_bRedArray[0]   = pRed                                       ' \
    WS2812B_bGreenArray[0] = pGreen                                     ' | Yes. So save the colour in the colour array
    WS2812B_bBlueArray[0]  = pBlue                                      ' /
    WS2812B_RGB(pRed, pGreen, pBlue)                                    ' Access a single WS2812B
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B
$else                                                                   ' Otherwise... There are multiple WS2812B devices on the line
    If pChipNum > cWS2812B_IndexAmount Then ExitProc                    ' So... Do nothing if the chip does not exist on the line
    WS2812B_wIndex = 0                                                  ' \ Create a loop for the amount of WS2812B devices on the line
    Repeat                                                              ' /
        If WS2812B_wIndex = pChipNum Then                               ' Is it the chip we want to alter?
            WS2812B_bRedArray[WS2812B_wIndex]   = pRed                  ' \
            WS2812B_bGreenArray[WS2812B_wIndex] = pGreen                ' | Yes. So save the colour in the colour array
            WS2812B_bBlueArray[WS2812B_wIndex]  = pBlue                 ' /
        EndIf
        WS2812B_RGB(WS2812B_bRedArray[WS2812B_wIndex], WS2812B_bGreenArray[WS2812B_wIndex], WS2812B_bBlueArray[WS2812B_wIndex])
        Inc WS2812B_wIndex
    Until WS2812B_wIndex >= WS2812B_Amount                              ' Loop for all the devices attached to the line
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B devices
$endif
EndProc

'---------------------------------------------------------------------------------------------
' Illuminate a WS2812B based upon its position in a string of them (with gamma control)
' Input     : pChipNum holds the WS2812B in the line to alter (0 to 65535)
'           : pRed holds the red value (0 To 255)
'           : pGreen holds the green value (0 to 255)
'           : pBlue holds the blue value (0 to 255)
' Output    : None
' Notes     : None
'
Proc WS2812B_ColourG(pChipNum As WS2812B_wChipNumber, pRed As WS2812B_pRed, pGreen As WS2812B_pGreen, pBlue As WS2812B_pBlue)
$if WS2812B_Amount = 1                                                  ' Is there only 1 WS2812B on the line?
    WS2812B_bRedArray[0]   = pRed                                       ' \
    WS2812B_bGreenArray[0] = pGreen                                     ' | Yes. So save the colour in the colour array
    WS2812B_bBlueArray[0]  = pBlue                                      ' /
    WS2812B_RGB(pRed, pGreen, pBlue)                                    ' Access a single WS2812B
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B
$else                                                                   ' Otherwise... There are multiple WS2812B devices on the line
    If pChipNum > cWS2812B_IndexAmount Then ExitProc                    ' So... Do nothing if the chip does not exist on the line
    WS2812B_wIndex = 0                                                  ' \ Create a loop for the amount of WS2812B devices on the line
    Repeat                                                              ' /
        If WS2812B_wIndex = pChipNum Then                               ' Is it the chip we want to alter?
            WS2812B_bRedArray[WS2812B_wIndex]   = pRed                  ' \
            WS2812B_bGreenArray[WS2812B_wIndex] = pGreen                ' | Yes. So save the colour in the colour array
            WS2812B_bBlueArray[WS2812B_wIndex]  = pBlue                 ' /
        EndIf
        WS2812B_RGB_Gamma(WS2812B_bRedArray[WS2812B_wIndex], WS2812B_bGreenArray[WS2812B_wIndex], WS2812B_bBlueArray[WS2812B_wIndex])
        Inc WS2812B_wIndex
    Until WS2812B_wIndex >= WS2812B_Amount                              ' Loop for all the devices attached to the line
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B devices
$endif
EndProc

'---------------------------------------------------------------------------------------------
' Illuminate a WS2812B based upon its position in a string of them
' Input     : pChipNum holds the WS2812B in the line to alter (0 to 65535)
'           : pColour holds the 24-bit RGB value
' Output    : None
' Notes     : Red is pColour.Byte0
'             Green is pColour.Byte1
'             Blue is pColour.Byte2
'
Proc WS2812B_Colour24(pChipNum As WS2812B_wChipNumber, pColour As Long)
$if WS2812B_Amount = 1                                                  ' Is there only 1 WS2812B on the line?
    WS2812B_bRedArray[0]   = pRed                                       ' \
    WS2812B_bGreenArray[0] = pGreen                                     ' | Yes. So save the colour in the colour array
    WS2812B_bBlueArray[0]  = pBlue                                      ' /
    WS2812B_RGB(pRed, pGreen, pBlue)                                    ' Access a single WS2812B
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B
$else                                                                   ' Otherwise... There are multiple WS2812B devices on the line
    If pChipNum > cWS2812B_IndexAmount Then ExitProc                    ' So... Do nothing if the chip does not exist on the line
    WS2812B_wIndex = 0                                                  ' \ Create a loop for the amount of WS2812B devices on the line
    Repeat                                                              ' /
        If WS2812B_wIndex = pChipNum Then                               ' Is it the chip we want to alter?
            WS2812B_bRedArray[WS2812B_wIndex]   = pColour.Byte0         ' \
            WS2812B_bGreenArray[WS2812B_wIndex] = pColour.Byte1         ' | Save the colour in the colour array
            WS2812B_bBlueArray[WS2812B_wIndex]  = pColour.Byte2         ' /
        EndIf
        WS2812B_RGB(WS2812B_bRedArray[WS2812B_wIndex], WS2812B_bGreenArray[WS2812B_wIndex], WS2812B_bBlueArray[WS2812B_wIndex])
        Inc WS2812B_wIndex
    Until WS2812B_wIndex >= WS2812B_Amount                              ' Loop for all the devices attached to the line
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B devices
$endif
EndProc

'---------------------------------------------------------------------------------------------
' Illuminate a WS2812B based upon its position in a string of them (with gamma control)
' Input     : pChipNum holds the WS2812B in the line to alter (0 to 65535)
'           : pColour holds the 24-bit RGB value
' Output    : None
' Notes     : Red is pColour.Byte0
'             Green is pColour.Byte1
'             Blue is pColour.Byte2
'
Proc WS2812B_Colour24G(pChipNum As WS2812B_wChipNumber, pColour As Long)
$if WS2812B_Amount = 1                                                  ' Is there only 1 WS2812B on the line?
    WS2812B_bRedArray[0]   = pRed                                       ' \
    WS2812B_bGreenArray[0] = pGreen                                     ' | Yes. So save the colour in the colour array
    WS2812B_bBlueArray[0]  = pBlue                                      ' /
    WS2812B_RGB(pRed, pGreen, pBlue)                                    ' Yes. So access a single WS2812B
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B
$else                                                                   ' Otherwise... There are multiple WS2812B devices on the line
    If pChipNum > cWS2812B_IndexAmount Then ExitProc                    ' So... Do nothing if the chip does not exist on the line
    WS2812B_wIndex = 0                                                  ' \ Create a loop for the amount of WS2812B devices on the line
    Repeat                                                              ' /
        If WS2812B_wIndex = pChipNum Then                               ' Is it the chip we want to alter?
            WS2812B_bRedArray[WS2812B_wIndex]   = pColour.Byte0         ' \
            WS2812B_bGreenArray[WS2812B_wIndex] = pColour.Byte1         ' | Save the colour in the colour array
            WS2812B_bBlueArray[WS2812B_wIndex]  = pColour.Byte2         ' /
        EndIf
        WS2812B_RGB_Gamma(WS2812B_bRedArray[WS2812B_wIndex], WS2812B_bGreenArray[WS2812B_wIndex], WS2812B_bBlueArray[WS2812B_wIndex])
        Inc WS2812B_wIndex
    Until WS2812B_wIndex >= WS2812B_Amount                              ' Loop for all the devices attached to the line
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B devices
$endif
EndProc

'---------------------------------------------------------------------------------------
' Read the colour data from a particular WS2812B chip in the line
' Input     : pChipNum holds the chip that the colour needs to be read from (0 to 65535)
' Output    : Returns a 24-bit value containing the RGB details (Red is Byte0, Green is Byte1 and Blue is Byte2)
' Notes     : None
'
Proc WS2812B_ColourGet(pChipNum As WS2812B_wChipNumber), Long
    Result.Byte0 = WS2812B_bRedArray[pChipNum]                          ' \
    Result.Byte1 = WS2812B_bGreenArray[pChipNum]                        ' | Read the colours from the colour arrays
    Result.Byte2 = WS2812B_bBlueArray[pChipNum]                         ' /
EndProc

'---------------------------------------------------------------------------------------
' Convert hue, saturation, and brighness into a 24-bit RGB colour
' Input     : pHue holds the 16-bit Hue value, representing one full loop of the colour wheel
'           : pSat holds the 8-bit Saturation value. 0 (min or pure grayscale) to 255 (max or pure pHue)
'           : pBright holds the 8-bit Brightness value. 0 (minimum brightness) to 255 (full brightness)
' Output    : Returns the RGB values as a 24-bit variable. Byte0 is Red, Byte1 is Green, and Byte2 is Blue
' Notes     : None
'
Proc WS2812B_ColourHSB(pHue As Word, pSat As Byte, pBright As Byte), Long
    Dim bRed   As Byte                                                  ' Holds the Red value
    Dim bGreen As Byte                                                  ' Holds the Green value
    Dim bBlue  As Byte                                                  ' Holds the Blue value
    Dim dHue   As Dword                                                 ' Holds the re-mapped Hue value
'
' Re-Map 0-65535 to 0-1529
'
    dHue = pHue * 1530
    dHue = dHue + 32768
    dHue.Word0 = dHue / 65536
'
' Convert dHue to seperate Red, Green, and Blue values
'
    If dHue.Word0 < 510 Then                                            ' Red to Green - 1
        bBlue = 0
        If dHue.Word0 < 255 Then                                        ' Red to Yellow - 1
            bRed = 255
            bGreen = dHue.Word0                                         ' Green = 0 to 254
        Else                                                            ' Yellow to Green - 1
            bRed = 510 - dHue                                           ' bRed = 255 to 1
            bGreen = 255
        EndIf
    ElseIf dHue.Word0 < 1020 Then                                       ' Green to Blue - 1
        bRed = 0
        If dHue.Word0 < 765 Then                                        ' Green to Cyan - 1
            bGreen = 255
            bBlue = dHue.Word0 - 510                                    ' Blue = 0 to 254
        Else                                                            ' Cyan to Blue - 1
            bGreen = 1020 - dHue.Word0                                  ' bGreen = 255 to 1
            bBlue = 255
        EndIf
    ElseIf dHue.Word0 < 1530 Then                                       ' Blue to Red - 1
        bGreen = 0                                                      ' Blue to Magenta - 1
        If dHue.Word0 < 1275 Then
            bRed = dHue.Word0 - 1020                                    ' Red = 0 to 254
            bBlue = 255
        Else                                                            ' Magenta to Red - 1
            bRed = 255
            bBlue = 1530 - dHue.Word0                                   ' Blue = 255 to 1
        EndIf
    Else                                                                ' Last 0.5 Red
        bRed = 255
        bGreen = 0
        bBlue = 0
    EndIf
'
' Apply saturation and brightness to RGB, and pack it into the 24-bit result
'
    bRed   = (bRed * (pSat + 1)) >> 8
    bGreen = (bGreen * (pSat + 1)) >> 8
    bBlue  = (bBlue * (pSat + 1)) >> 8

    Result.Byte0 = (((bRed + (255 - pSat)) * (pBright + 1))) >> 8
    Result.Byte1 = (((bGreen + (255 - pSat)) * (pBright + 1))) >> 8
    Result.Byte2 = (((bBlue + (255 - pSat)) * (pBright + 1))) >> 8
EndProc

'---------------------------------------------------------------------------------------------
' Fill all or part of a WS2812B strip with a colour
' Input     : pFirst holds the first LED chip to illuminate (0 to 65535)
'           : pAmount holds the number of LED chips to illuminate (0 To 65535)
'           : pColour holds the 24-bit colour value
' Output    : None
' Notes     : Red is pColour.Byte0
'             Green is pColour.Byte1
'             Blue is pColour.Byte2
'
Proc WS2812B_Fill(pFirst As Word, pAmount As Word, pColour As Long)
    If pFirst >= WS2812B_Amount Then ExitProc                           ' Do nothing if the first LED is past the end of the strip

    pAmount = pFirst + pAmount                                          ' Calculate the last chip
    If pAmount > cWS2812B_IndexAmount Then                              ' Make sure that the loop will not go past the last chip on teh line
        pAmount = cWS2812B_IndexAmount
    EndIf

    WS2812B_wIndex = 0                                                  ' \ Create a loop for the amount of WS2812B devices to alter
    Repeat                                                              ' /
        WS2812B_RGB(pColour.Byte0, pColour.Byte1, pColour.Byte2)
        Inc WS2812B_wIndex
    Until WS2812B_wIndex >= pAmount                                     ' Loop for the amount of devices specified in pAmount
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B devices
EndProc

'---------------------------------------------------------------------------------------------
' Fill all or part of a WS2812B strip with a colour (with gamma control)
' Input     : pFirst holds the first LED chip to illuminate (0 to 65535)
'           : pAmount holds the number of LED chips to illuminate (0 To 65535)
'           : pColour holds the 24-Bit colour value
' Output    : None
' Notes     : None
'
Proc WS2812B_FillG(pFirst As Word, pAmount As Word, pColour As Long)
    If pFirst >= WS2812B_Amount Then                                    ' \
        ExitProc                                                        ' | If first LED is past the end of the strip, then exit the procedure
    EndIf                                                               ' /

    pAmount = pFirst + pAmount                                          ' Calculate the last chip
    If pAmount > cWS2812B_IndexAmount Then                              ' Make sure that the loop will not go past the last pixel
        pAmount = cWS2812B_IndexAmount
    EndIf

    WS2812B_wIndex = 0                                                  ' \ Create a loop for the amount of WS2812B devices to alter
    Repeat                                                              ' /
        WS2812B_RGB_Gamma(pColour.Byte0, pColour.Byte1, pColour.Byte2)
        Inc WS2812B_wIndex
    Until WS2812B_wIndex >= pAmount                                     ' Loop for the amount of devices specified in pAmount
    WS2812B_Finish()                                                    ' Bring the pin low long enough to reset the WS2812B devices
EndProc

'---------------------------------------------------------------------------------------------
' Setup the interface with WS2812B RGB controller chips
' Input     : None
' Output    : None
' Notes     : None
'
Proc WS2812B_Setup()
    PinLow WS2812B_Pin                                                  ' Make the pin that connects to the WS2812B chip a low output
    DelayMS 50                                                          ' Reset the WS2812B
    Clear WS2812B_bRedArray                                             ' \
    Clear WS2812B_bGreenArray                                           ' | Clear the arrays holding the WS2812B colours
    Clear WS2812B_bBlueArray                                            ' /
EndProc

'---------------------------------------------------------------------------------------------
_WS2812B_Main:

$endif  ' _WS2812B_INC_

Below is a crude candle simulation I created using four WS2812B devices, and the Hue procedure. So users can see how it works, to a certain extent:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Candle simulation using WS2812B RGB LED chips on a StoryPainter PCB
' Written by Les Johnson for the Positron8 BASIC compiler.
'
    Device = 18F26K40
    Declare Xtal = 64
'
' Set the defines for the WS2812B library routines
'
$define WS2812B_Pin PORTA.0                                     ' The pin used for the WS2812B chips
$define WS2812B_Amount 4                                        ' The amount of WS2812B chips to control

    Include "WS2812B.inc"                                       ' Load the RGB WS2812B library into the program

$define WS2812B_Power_Pin PORTA.1                               ' Turns on or off the power to the WS2812B chips(active low because it uses a P-Channel MOSFET)
'
' Create some constants
'
    Symbol cMin_UnCalm = (5 * 256)
    Symbol cMax_UnCalm = (60 * 256)
    Symbol cMin_UnCalm_Div2 = (cMin_UnCalm / 2)
    Symbol cUnCalm_Inc = 10
    Symbol cMax_Deviation = 100
'
' Create some variables
'
    Dim wCenterX    As SWord                                 
    Dim wCenterY    As SWord                                   
    Dim wX_Velocity As SWord
    Dim wY_Velocity As SWord
    Dim wUnCalm     As SWord
    Dim wUnCalmDir  As SWord
    Dim bCounter    As Byte
    Dim wTemp       As Word
    Dim wMovX       As SWord
    Dim wMovY       As SWord

'---------------------------------------------------------------------------------------------------
' Control the MOSFET that supplies power to the WS2812B chips on the StoryPainter PCB
'
$define WS2812B_PowerOn() PinLow WS2812B_Power_Pin              ' Turn on the power to the WS2812B chips(active low because it uses a P-Channel MOSFET)
$define WS2812B_PowerOff() PinHigh WS2812B_Power_Pin            ' Turn on the power to the WS2812B chips(active low because it uses a P-Channel MOSFET)

'---------------------------------------------------------------------------------------
' The main program starts here
'
Main:
    Setup()                                                     ' Setup the program
   
    Do
        '
        ' Random trigger of brightness oscillation, if at least half wUnCalm
        '
        If wUnCalm > cMin_UnCalm_Div2 Then                      ' Is it time to look to see if the flame should be uncalm?
            If Random16(2000) < 5 Then                          ' Yes. So is a random number within range?
                wUnCalm = cMax_UnCalm * 2                       ' Yes. So alter the flame for a wind
            EndIf
        EndIf
        '
        ' Random intensity determined by wUnCalm value (0 is calm)
        '
        wTemp = Abs(wUnCalm)
        wMovX = Random16(wTemp.Byte1 - (wTemp.Byte1 >> 1))
        wMovY = Random16(wTemp.Byte1 - (wTemp.Byte1 >> 1))
       
        If wUnCalm < cMin_UnCalm Then                           ' If reached min calm value, start moving towards uncalm
            wUnCalmDir = cUnCalm_Inc
        ElseIf wUnCalm > cMax_UnCalm Then                       ' If reached max uncalm value, start going towards calm
            wUnCalmDir = -cUnCalm_Inc
        EndIf
        wUnCalm = wUnCalm + wUnCalmDir
        '
        ' Move the center of the flame by the current velocity
        '
        wCenterX = wCenterX + (wMovX + (wX_Velocity / 4))
        wCenterY = wCenterY + (wMovY + (wY_Velocity / 4))
        '
        ' Range limits
        '
        If wCenterX < -cMax_Deviation Then
            wCenterX = -cMax_Deviation
        EndIf
        If wCenterX > cMax_Deviation Then
            wCenterX = cMax_Deviation
        EndIf

        If wCenterY < -cMax_Deviation Then
            wCenterY = -cMax_Deviation
        EndIf
        If wCenterY > cMax_Deviation Then
            wCenterY = cMax_Deviation
        EndIf
       
        Inc bCounter
        If (bCounter & %00000011) = 0 Then
            '
            ' Attenuate velocity by approx a quarter
            '
            wX_Velocity = (wX_Velocity * 999) / 1000
            wY_Velocity = (wY_Velocity * 999) / 1000
        EndIf
        '
        ' Apply acceleration towards center
        '
        wX_Velocity = wX_Velocity - wCenterX
        wY_Velocity = wY_Velocity - wCenterY
       
        WS2812B_Colour24(0, FlameBrightnes((128 + wCenterX) - wCenterY))
        WS2812B_Colour24(1, FlameBrightnes((128 - wCenterX) - wCenterY))
        WS2812B_Colour24(2, FlameBrightnes((128 + wCenterX) + wCenterY))
        WS2812B_Colour24(3, FlameBrightnes((128 - wCenterX) + wCenterY))
       
        DelayMS RandomMinMax16(30, 70)                          ' Delay for a random amount of milliseconds
    Loop

'---------------------------------------------------------------------------------------
' Alter the flame brightness
' Input     : pValue holds the 8-bit values to alter the flame's brightness
' Output    : Returns a 24-bit RGB value
' Notes     : None
'
Proc FlameBrightnes(pValue As SWord), Long
    If pValue < 0 Then
        pValue = 0
    ElseIf pValue > 255 Then
        pValue = 255
    EndIf
    Result = WS2812B_ColourHSB(pValue.Byte0 / 4, pValue.Byte0 / 2, pValue.Byte0 / 4)
EndProc

'---------------------------------------------------------------------------------------
' Return a 16-bit pseudo random number
' Input     : pMax is the limit of the random value
' Output    : Returns a 16-bit pseudo random value
' Notes     : None
'
Proc Random16(pMax As Word), Word
    If pMax = 0 Then pMax = 1
    Result = (Random) // pMax
EndProc

'--------------------------------------------------------------------------
' Create a pseudo random integer value between minimum and maximum values
' Input     : pMin holds the minimum random value to return
'           : pMax holds the maximum random value to return
' Output    : Returns a 16-bit pseudo random value
' Notes     : Maximum 16-bit return (1 to 65535)
'           : Uses the equation (Random // (pMax - pMin)) + pMin + 1
'
Proc RandomMinMax16(pMin As Word, pMax As Word), Word
    If pMax = 0 Then pMax = 1
    pMax = pMax - pMin
    Result = (Random) // pMax
    Result = Result + pMin
    Result = Result + 1
EndProc

'---------------------------------------------------------------------------------------------------
' Setup the program and peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    Oscillator_64MHz()                                          ' Set the device to operate at 64MHz using the internal oscillator
    Seed $0345                                                  ' Seed the pseudo random number generator
    WS2812B_PowerOn()                                           ' Power on the WS2812B LEDs on the storypainter PCB
    WS2812B_Setup()                                             ' Setup the interface to the WS2812B RGB controller chips

    wCenterX = cMax_Deviation
    wCenterY = cMax_Deviation / 2
    wX_Velocity = 0
    wY_Velocity = 0
    wUnCalm = cMin_UnCalm
    wUnCalmDir = cUnCalm_Inc
    bCounter = 0
    wMovX = 0
    wMovY = 0
EndProc

'---------------------------------------------------------------------------------------------------
' Set the internal oscillator to 64MHz on a PIC18F26K40 device
' Input     : None
' Output    : None
' Notes     : Waits for the oscillator to become stable
'
Proc Oscillator_64MHz()
    OSCCON1 = %01100000
    OSCCON3 = %00000000
    OSCEN   = %00000000
    OSCFRQ  = %00001000
    OSCTUNE = %00000000
    Repeat: Until OSCSTATbits_HFOR = 1
    DelayMS 50
EndProc

'---------------------------------------------------------------------------------------------
' Setup for internal oscillator on a PIC18F26K40 device
'
Config_Start
    RSTOSC = HFINTOSC_1MHZ          ' With HFFRQ = 4MHz and CDIV = 4:1
    FEXTOSC = Off                   ' External Oscillator not enabled
    WDTE = Off                      ' WDT disabled
    CLKOUTEN = Off                  ' CLKOUT function is disabled
    CSWEN = On                      ' Writing to NOSC and NDIV is allowed
    FCMEN = Off                     ' Fail-Safe Clock Monitor disabled
    MCLRE = EXTMCLR                 ' If LVP = 0, MCLR pin is MCLR. If LVP = 1, RE3 pin function is MCLR
    PWRTE = On                      ' Power up timer enabled
    LPBOREN = off                   ' ULPBOR disabled
    BOREN = On                      ' Brown-out turned on
    BORV = VBOR_245                 ' Brown-out Reset Voltage (VBOR) set to 2.45V
    ZCD = Off                       ' ZCD disabled. ZCD can be enabled by setting the ZCDSEN bit of ZCDCON
    PPS1WAY = Off                   ' PPSLOCK bit can be set and cleared repeatedly (subject to the unlock sequence)
    STVREN = Off                    ' Stack full/underflow will not cause Reset
    Debug = Off                     ' Background debugger disabled
    XINST = Off                     ' Extended Instruction Set and Indexed Addressing Mode disabled
    SCANE = Off                     ' Scanner module is Not available for use. SCANMD bit is ignored
    LVP = On                        ' Low Voltage programming enabled
    WDTCPS = WDTCPS_15              ' Watchdog Divider ratio 1:1048576 (32 seconds)
    WDTCWS = WDTCWS_7               ' Window always open (100%). Software control. Keyed access not required
    WDTCCS = LFINTOSC               ' WDT input clock selector->WDT reference clock is the 31.2kHz HFINTOSC output
    WRT0 = Off                      ' Block 0 (000800-001FFFh) not write-protected
    WRT1 = Off                      ' Block 1 (002000-003FFFh) not write-protected
    WRTC = Off                      ' Configuration registers (300000-30000Bh) not write-protected
    WRTB = Off                      ' Boot Block (000000-0007FFh) write-protected
    WRTD = Off                      ' Data EEPROM not write-protected
    Cp = Off                        ' UserNVM code protection disabled
    CPD = Off                       ' DataNVM code protection disabled
    EBTR0 = Off                     ' Block 0 (000800-001FFFh) not protected from table reads executed in other blocks
    EBTR1 = Off                     ' Block 1 (002000-003FFFh) not protected from table reads executed in other blocks
    EBTRB = Off                     ' Boot Block (000000-0007FFh) not protected from table reads executed in other blocks
Config_End

Even though the code was created by me and for me, over the years I have gotten into the "good" habit of adding comments and header texts as I write the code itself, so I can look at code I created years ago and still see what I was thinking when I created it, and how the code works, because creating code is a form of artistry, and the mind's flow at the time of creating it, dictates what it will look like and do. :-)

Trikkitt

My process for sending to the WS281x LEDs has been to bit bang it.  My next task is to switch it to using hardware.  Not as easily ported but my current project is very interrupt driven, so that means turning off interrupts while sending LED data, which in turn causes other issues. Currently wrapping my head around https://blog.kubovy.eu/2019/02/17/ws281x-using-pic/ in the hope I can port that to Proton.

Trikkitt

Well I got my code to work!  In this instance I'm using the 16F15355 running at 32Mhz.  It'll happily send the data via SPI and hardware interrupts.  This will consume Timer 2, PWM6, SSP2 and CLC2.  The code will drive whatever length of string you want provided the buffer is long enough.  LEDByteCount is the number of bytes in the buffer and not the number of LEDs.

Again - any optimisations you can suggest are welcome.

  Symbol NeoPixelOut1=PORTA.2
 
  On_Hardware_Interrupt GoTo ISR_Handler

Dim LEDData[58] As Byte
Dim LEDByteCount As Word
Dim ClockCount As Word
Dim SPINextByte As Byte


    ' This section configures the hardware to send WS2812 protocol
    ' Load the CLC2 to take input from PWM6 and SPI2
CLC2GLS0  = 0x05
CLC2GLS1  = 0x10
CLC2GLS2  = 0x08
CLC2GLS3  = 0x20
CLC2SEL0  = 0x14
CLC2SEL1  = 0x25
CLC2SEL2  = 0x24
CLC2SEL3  = 0x00
CLC2POL   = 0x01
CLC2CON   = 0x80
   
    ' Timer 2 feeds the SPI2 and PWM6 timings
    T2CLKCON=%00000001 ' Fosc/4
    T2CON=%10000000 ' Timer 2 on with no post scaler
    T2HLT=0
    T2PR=0x4
    T2RST=0
    T2TMR=0
   
    ' This feeds in to the CLC2
    PWM6CON=0x80
    PWM6DCH=2
    PWM6DCL=0x40
   
    ' This feeds the data in to the CLC2
    SSP2ADD=1
    SSP2CON1=0x23
    SSP2CON2=0
    SSP2CON3=0
    SSP2MSK=0xFF
    SSP2STAT=0x40
   
    ' Map the CLC2 output to the port with the WS2812 string on it
    RA2PPS=2   ' CLC2 out

    ' Turn on global and peripheral interrupts
    Set INTCON.7
    Set INTCON.6



  DelayMS 2
  GoTo Main

  ISR_Handler: ' Interrupt handler will check if a byte has been received by SPI and then send the next byte.  Interrupts are turned off once the message sending has been completed.
               ' Code can check if the interrupts are enabled to establish if the current LED message has finished sending or not.
  Context Save
  If PIR3.2 =1 Then  ' SPI2 interrupt
    atemp=SSP2BUF ' Clear the receive buffer
    Clear PIR3.2
    SSP2BUF=SPINextByte
    If ClockCount>0 Then
      Dec ClockCount
      SPINextByte=LEDData[ClockCount]
    Else
      Clear PIE3.2
    EndIf
   
  EndIf
  Context Restore
  Return

' This starts the SPI interface sending the LED data
SendLEDData:
  Output NeoPixelOut1
  ClockCount=LEDByteCount
  Dec ClockCount
  SSP2BUF=LEDData[ClockCount]
  Dec ClockCount
  SPINextByte=LEDData[ClockCount]
  Clear PIR3.2
  Set PIE3.2 ' enable MSSP2 (SPI) interrupts
  Return
 
Main:


Here is the full code for the LED demo which includes the HSV to RGB and some gamma correction.  It isn't perfect on the bottom end of the scale but does a decent enough job.

Device = 16F15355

Config1 FEXTOSC_OFF, RSTOSC_HFINT32, CLKOUTEN_OFF, CSWEN_ON, FCMEN_ON
Config2 MCLRE_OFF, PWRTE_OFF, LPBOREN_OFF, BOREN_ON, BORV_LO, ZCD_OFF, PPS1WAY_ON, STVREN_ON
Config3 WDTCPS_WDTCPS_31, WDTE_SWDTEN, WDTCWS_WDTCWS_7, WDTCCS_LFINTOSC
Config4 BBSIZE_BB512, BBEN_OFF, SAFEN_OFF, WRTAPP_OFF, WRTB_OFF, WRTC_OFF, WRTSAF_OFF, LVP_ON
Config5 CP_OFF

  Xtal = 32
  All_Digital = TRUE    ' Set PORTA and PORTC to all digital
 
  'Declare Optimiser_Level = 0
  Declare Watchdog = Off

  Symbol NeoPixelPower1=PORTA.1
  Symbol NeoPixelOut1=PORTA.2
 
  On_Hardware_Interrupt GoTo ISR_Handler
 
Dim LEDData[58] As Byte
Dim ReadData As Byte
Dim LEDByteCount As Word
Dim ClockCount As Word
Dim OwnerHue As Word

Dim SPINextByte As Byte

Dim C As Byte
Dim ColourValue As Byte
Dim Frame As Word
Dim FrameTemp As Word
Dim Ftemp As Byte
Dim atemp As Byte

    ' This section configures the hardware to send WS2812 protocol

    ' Load the CLC2 to take input from PWM6 and SPI2
CLC2GLS0  = 0x05
CLC2GLS1  = 0x10
CLC2GLS2  = 0x08
CLC2GLS3  = 0x20
CLC2SEL0  = 0x14
CLC2SEL1  = 0x25
CLC2SEL2  = 0x24
CLC2SEL3  = 0x00
CLC2POL   = 0x01
CLC2CON   = 0x80
   
    ' Timer 2 feeds the SPI2 and PWM6 timings
    T2CLKCON=%00000001 ' Fosc/4
    T2CON=%10000000 ' Timer 2 on with no post scaler
    T2HLT=0
    T2PR=0x4
    T2RST=0
    T2TMR=0
   
    ' This feeds in to the CLC2
    PWM6CON=0x80
    PWM6DCH=2
    PWM6DCL=0x40
   
    ' This feeds the data in to the CLC2
    SSP2ADD=1
    SSP2CON1=0x23
    SSP2CON2=0
    SSP2CON3=0
    SSP2MSK=0xFF
    SSP2STAT=0x40
   
    ' Map the CLC2 output to the port with the WS2812 string on it
    RA2PPS=2   ' CLC2 out

    ' Turn on global and peripheral interrupts
    Set INTCON.7
    Set INTCON.6   


  DelayMS 2
  GoTo Main

  ISR_Handler: ' Interrupt handler will check if a byte has been received by SPI and then send the next byte.  Interrupts are turned off once the message sending has been completed.
               ' Code can check if the interrupts are enabled to establish if the current LED message has finished sending or not.
  Context Save
  If PIR3.2 =1 Then  ' SPI2 interrupt
    atemp=SSP2BUF ' Clear the receive buffer
    Clear PIR3.2
    SSP2BUF=SPINextByte
    If ClockCount>0 Then
      Dec ClockCount
      SPINextByte=LEDData[ClockCount]
    Else
      Clear PIE3.2
    EndIf
   
  EndIf
  Context Restore
  Return

' This starts the SPI interface sending the LED data
SendLEDData:
  ClockCount=LEDByteCount
  Dec ClockCount
  SSP2BUF=LEDData[ClockCount]
  Dec ClockCount
  SPINextByte=LEDData[ClockCount]
  Clear PIR3.2
  Set PIE3.2 ' enable MSSP2 (SPI) interrupts
  Return

 
Main:

' this is for my test circuit and keeps the power regulator turned on.
Symbol PowerControl=PORTB.1
 
  ' Make sure we are turning on our power supply that'll keep us powered up
  Set PowerControl
  Output PowerControl
  Set PowerControl
 
' The number of bytes of data.  This is to run a 17 LED string
LEDByteCount=51
Output NeoPixelPower1
Set NeoPixelPower1
' Cautious delay after turning them on before sending data.  Could be much less.
DelayMS 1000
' only make the data pin an output once we know they're powered on.
Output NeoPixelOut1

Symbol FrameDelay = 10 ' Delay between frames of the LED animation

' Note that DelayMs has been used.  Code can go and do anything during this time as interrupts will keep the data flowing.

DemoLoop:
  GoSub RGBCycle
  GoSub FullColourCycle
  GoSub PulseDemo

  Clear Frame
  OwnerHue.HighByte=3
  OwnerHue.LowByte=0
  dl1:
  GoSub OwnerFlash1
  DelayMS FrameDelay
  If Frame>0 Then GoTo dl1
  GoSub BlankLEDs
  DelayMS 1000
 
  OwnerHue.HighByte= 5
  OwnerHue.LowByte=0
  dl2:
  GoSub OwnerFlash1
  DelayMS FrameDelay
  If Frame>0 Then GoTo dl2
  GoSub BlankLEDs
  DelayMS 1000

  OwnerHue.HighByte=0
  OwnerHue.LowByte=170
  dl3:
  GoSub OwnerFlash1
  DelayMS FrameDelay
  If Frame>0 Then GoTo dl3
  GoSub BlankLEDs
  DelayMS 1000

  OwnerHue.HighByte=3
  OwnerHue.LowByte=0
  dl4:
  GoSub OwnerLoop1
  DelayMS FrameDelay
  If Frame>0 Then GoTo dl4
  GoSub BlankLEDs
  DelayMS 1000

  OwnerHue.HighByte=5
  OwnerHue.LowByte=0
  dl5:
  GoSub OwnerLoop1
  DelayMS FrameDelay
  If Frame>0 Then GoTo dl5
  GoSub BlankLEDs
  DelayMS 1000

  OwnerHue.HighByte=0
  OwnerHue.LowByte=170
  dl6:
  GoSub OwnerLoop1
  DelayMS FrameDelay
  If Frame>0 Then GoTo dl6
  GoSub BlankLEDs
  DelayMS 1000

  OwnerHue.HighByte=3
  OwnerHue.LowByte=0
  dl7:
  GoSub OwnerFlash2
  DelayMS FrameDelay
  If Frame>0 Then GoTo dl7
  GoSub BlankLEDs
  DelayMS 1000
 
  OwnerHue.HighByte= 5
  OwnerHue.LowByte=0
  dl8:
  GoSub OwnerFlash2
  DelayMS FrameDelay
  If Frame>0 Then GoTo dl8
  GoSub BlankLEDs
  DelayMS 1000

  OwnerHue.HighByte=0
  OwnerHue.LowByte=170
  dl9:
  GoSub OwnerFlash2
  DelayMS FrameDelay
  If Frame>0 Then GoTo dl9
  GoSub BlankLEDs
  DelayMS 1000


GoTo DemoLoop

CapCycle:
 
Return

BlankLEDs: ' Sets all LEDs to zero
  Clear OutB
  Clear OutR
  Clear OutG
  GoSub LoadAllLEDs
  GoSub SendLEDData
Return

OwnerFlash1: ' Flash a particular colour
  If Frame=0 Then Frame=20
  Dec Frame
  HSVh=OwnerHue
  HSVs=255
  HSVv=255
  GoSub HSV2RGB
  GoSub LoadAllLEDs
  GoSub SendLEDData
Return

OwnerFlash2: ' flash alternate LEDs back and forth
  If Frame=0 Then Frame=40
  Dec Frame
  HSVh=OwnerHue
  HSVs=255
  HSVv=255
  GoSub HSV2RGB
  Clear LEDData
  If Frame.2=1 Then
    For C=0 To 51 Step 6
      LEDData[C]=OutB
      LEDData[C+1]=OutR
      LEDData[C+2]=OutG
    Next
  Else
    For C=3 To 51 Step 6
      LEDData[C]=OutB
      LEDData[C+1]=OutR
      LEDData[C+2]=OutG
    Next
  EndIf
  GoSub SendLEDData
Return


OwnerLoop1: ' Spin the colour around the string
  If Frame=0 Then Frame=50
  Dec Frame
  Clear LEDData
  HSVh=OwnerHue
  HSVs=255
  HSVv=200
  GoSub HSV2RGB
  Ftemp=Frame // 3
  FrameTemp = Frame - Ftemp
  LEDData[FrameTemp]=OutB
  LEDData[FrameTemp+1]=OutR
  LEDData[FrameTemp+2]=OutG
  HSVv=80
  GoSub HSV2RGB
  If Frame>2 Then
    LEDData[FrameTemp-3]=OutB
    LEDData[FrameTemp-2]=OutR
    LEDData[FrameTemp-1]=OutG
  EndIf
  If Frame<49 Then
    LEDData[FrameTemp+3]=OutB
    LEDData[FrameTemp+4]=OutR
    LEDData[FrameTemp+5]=OutG
  EndIf
  HSVv=20
  GoSub HSV2RGB
  If Frame>5 Then
    LEDData[FrameTemp-6]=OutB
    LEDData[FrameTemp-5]=OutR
    LEDData[FrameTemp-4]=OutG
  EndIf
  If Frame<46 Then
    LEDData[FrameTemp+6]=OutB
    LEDData[FrameTemp+7]=OutR
    LEDData[FrameTemp+8]=OutG
  EndIf
  GoSub SendLEDData
Return

LoadAllLEDs:  ' Make all the LEDs the same colour
  For C=0 To 51 Step 3
    LEDData[C]=OutB
    LEDData[C+1]=OutR
    LEDData[C+2]=OutG
  Next
Return

RGBCycle: ' Simple set LEDs to a single colour

  For C=0 To 51 Step 3
    LEDData[C]=0
    LEDData[C+1]=0
    LEDData[C+2]=255  ' Green
  Next
  GoSub SendLEDData
  DelayMS 2000

  For C=0 To 51 Step 3
    LEDData[C]=0
    LEDData[C+1]=255  ' Red
    LEDData[C+2]=0
  Next
  GoSub SendLEDData
  DelayMS 2000

  For C=0 To 51 Step 3
    LEDData[C]=255  ' Blue
    LEDData[C+1]=0
    LEDData[C+2]=0
  Next
  GoSub SendLEDData
  DelayMS 2000
Return


FullColourCycle: ' Perform a full hue cycle
Clear HSVh
HSVs=255
HSVv=255

For HSVh=0 To   1535
  GoSub HSV2RGB
  For C=0 To 51 Step 3
    LEDData[C]=OutB
    LEDData[C+1]=OutR
    LEDData[C+2]=OutG
  Next
  GoSub SendLEDData
  DelayMS 10
Next

Return


PulseDemo: ' Fade in and out of each colour
HSVh.LowByte=0
HSVh.HighByte=3 ' Cyan
HSVs=255
GoSub PulseRun
HSVh.LowByte=0
HSVh.HighByte=5 ' Purple
HSVs=255
GoSub PulseRun
HSVh.LowByte=0
HSVh.HighByte=1 ' Yellow
HSVs=255
GoSub PulseRun
Return


PulseRun:
For ColourValue=0 To 255
  HSVv=CRead gamma8 + ColourValue
  GoSub HSV2RGB
  For C=0 To 51 Step 3
    LEDData[C]=OutB
    LEDData[C+1]=OutR
    LEDData[C+2]=OutG
  Next
  GoSub SendLEDData
  DelayMS 10
Next

For HSVv=255 To 0 Step -1
  GoSub HSV2RGB
  For C=0 To 51 Step 3
    LEDData[C]=OutB
    LEDData[C+1]=OutR
    LEDData[C+2]=OutG
  Next
  GoSub SendLEDData
  DelayMS 10
Next
Return

   

HSV2RGB:
 
Dim HSVh As Word
Dim HSVs As Byte ' Only has bottom byte populated
Dim HSVv As Byte

Dim Cs As Byte ' Slope
Dim Cv As Byte ' top level
Dim Cc As Byte ' bottom level

Dim OutR As Byte
Dim OutG As Byte
Dim OutB As Byte

Dim bb As Byte
Dim ww As Word
Dim mm As Byte

If HSVs=0 Then  ' No saturation so monochrome
  OutR=HSVv
  OutG=HSVv
  OutB=HSVv
  Return
EndIf

If HSVh.HighByte >5 Then HSVh.HighByte=5 ' Ensure maximum sextant is limited
Cv=HSVv
bb=~HSVs
ww=HSVv * bb
Inc ww
ww=ww+ww.HighByte
Cc=ww.HighByte
If HSVh.8=0 Then ' sextant bit 1 (up or down slope)
  If HSVh.LowByte=0 Then
    ww.HighByte=HSVs
    Clear ww.LowByte
  Else
    mm=~HSVh.LowByte
    Inc mm
    ww=HSVs * mm
  EndIf
Else
  ww=HSVs * HSVh.LowByte
EndIf
ww=ww + ww.HighByte
bb=~ww.HighByte
ww=HSVv * bb
mm=HSVv>>1
ww=ww + mm
Cs=ww.HighByte

Select HSVh.HighByte
  Case 0
    OutR=Cv
    OutB=Cc
    OutG=Cs
  Case 1
    OutR=Cs
    OutB=Cc
    OutG=Cv
  Case 2
    OutR=Cc
    OutB=Cs
    OutG=Cv
  Case 3
    OutR=Cc
    OutB=Cv
    OutG=Cs
  Case 4
    OutR=Cs
    OutB=Cv
    OutG=Cc
  Case 5
    OutR=Cv
    OutB=Cs
    OutG=Cc
EndSelect

Return

 
gamma8:
CData  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  _
    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1, _
    1,  1,  1,  1,  1,  1,  1,  1,  1,  2,  2,  2,  2,  2,  2,  2, _
    2,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  5,  5,  5, _
    5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9, 10, _
   10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, _
   17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, _
   25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, _
   37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, _
   51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, _
   69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, _
   90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, _
  115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, _
  144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, _
  177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, _
  215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255

tumbleweed

Here's something you might try. It doesn't really speed things up any, it just simplifies the SendLEDData routine to get rid of the duplicate code.

It also lets you get rid of the 'dim atemp as byte' declaration since that's no longer required.

ISR_Handler: ' Interrupt handler will check if a byte has been received by SPI and then send the next byte.  Interrupts are turned off once the message sending has been completed.
               ' Code can check if the interrupts are enabled to establish if the current LED message has finished sending or not.
  Context Save
  If PIR3.2 = 1 Then  ' SPI2 interrupt
    WREG = SSP2BUF      ' Clear the receive buffer (not used... clears BF)
    Clear PIR3.2
    SSP2BUF = SPINextByte
    If ClockCount > 0 Then
      Dec ClockCount
      SPINextByte = LEDData[ClockCount]
    Else
      While SSP2STAT.0=0 : Wend   ' wait for BF (last byte to finish transmit)
      Clear PIE3.2                ' done... disable SPI2 intr
    EndIf
  EndIf
  Context Restore

' This starts the SPI interface sending the LED data
SendLEDData:
  ClockCount = LEDByteCount - 1
  SPINextByte = LEDData[ClockCount]
  Set PIR3.2  ' set SSP2IF to start transmission
  Set PIE3.2  ' enable MSSP2 (SPI) interrupts
  Return

Trikkitt

Thanks - some good suggestions.  On playing around with it the SSP interrupt seems possible to clear by manually clearing the bit without the need to read the buffer as well.  I'm also not comfortable with the while loop in an interrupt handler, I get the intent but it isn't ideal given what else my code could be in the middle of.  Most likely what I'll be doing in my own code is only sending LED changes 50 times a second so I know it can transmit it all before the next update to the buffer happens.  What is more fun is - I'll be driving two strings separately so I will need to be careful of timing for that!

tumbleweed

QuoteI'm also not comfortable with the while loop in an interrupt handler
I don't normally recommend doing that either, but in this case it was the simplest change to ensure that the last byte has been transmitted before the next call to SendLEDData occurs. It does add an extra 1us or so to the ISR for the last byte.