News:

PROTON pic BASIC Compilers for PIC, PIC24, dsPIC33

Main Menu

SK9822 / APA102 example

Started by RGV250, Sep 03, 2026, 01:19 PM

Previous topic - Next topic

RGV250

Hi,
Les said he had created a routine? for APA102 in a post somewhere. I cannot find any examples so rather than reinventing the wheel I thought I would ask here first.

Regards,
Bob

top204

#1
It has been about 8 years since I wrote the code for the APA102 RGBW LED devices, and I remember it worked well, but I cannot find my APA102 devices since we moved house, so I cannot test the code again.

The APA102 devices use a form of SPI interface, and from what I remember, they worked extremely well, and have a separate byte in them for their brightness, hence the 'RGBW' acronym. i.e. Red, Green, Blue, White. Where the White LED gives the extra brightness.

The APA102 library code is listed below:

$ifndef _APA102_INC_
$define _APA102_INC_
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' APA102 RGB LED Interface routines.
'
' Written for the Positron8 BASIC Compiler, by Les Johnson.
'
$ifndef APA_CKI_Pin                                                 ' Has a define been placed in the main program for the CKI pin?
    $define APA_CKI_Pin PORTB.0                                     ' No. So set the default pin that connects to the APA102's Clock In pin
    $SendWarning "No $define APA_CKI_Pin found, so using the default Pin for the CKI line to the APA102"
$endif
$ifndef APA_SDI_Pin                                                 ' Has a define been placed in the main program for the SDI pin?
    $define APA_SDI_Pin PORTB.1                                     ' No. So Set the default pin that connects to the APA102's Data In pin
    $SendWarning "No $define APA_SDI_Pin found, so using the default Pin for the SDI line to the APA102"
$endif

$ifndef cAPA_LED_Amount                                             ' Has a define been placed in the main program for the amount of LEDs on the strip?
    $define cAPA_LED_Amount 60                                      ' No. So Set the default amount of LEDs
    $SendWarning "No $define cAPA_LED_Amount found, so using the default amount of LEDs on the strip"
$endif
$define cAPA_LED_LoopAmount $eval (cAPA_LED_Amount - 1)
$ifndef True
    $define True 1
$endif
$ifndef False
    $define False 0
$endif
'
' Create some variables
'
    Dim APA_dColourArray[cAPA_LED_Amount] As Dword Heap
    Dim APA_wTemp     As Word Access
    Dim APA_wLEDIndex As Word Access
    Dim APA_dBBGR     As Dword Access                               ' Holds the brightness and red, green and blue data
                                                                    ' Byte 0 = Red
                                                                    ' Byte 1 = Green
                                                                    ' Byte 2 = Blue
                                                                    ' Byte 3 = Brightness
    Dim APA_bBrightness As APA_dBBGR.Byte3                          ' \
    Dim APA_bBlue       As APA_dBBGR.Byte2                          ' | Alias the bytes of Dword APA_dBBGR
    Dim APA_bGreen      As APA_dBBGR.Byte1                          ' | Brightness is sent first, then blue, green, red
    Dim APA_bRed        As APA_dBBGR.Byte0                          ' /

    Dim APA_lBGR As APA_dBBGR.Long                                  ' Holds the red, green and blue data
                                                                    ' Byte 0 = Red
                                                                    ' Byte 1 = Green
                                                                    ' Byte 2 = Blue

'----------------------------------------------------------------------------------
' Write 32-bits of data to an APA102 device
' Input     : APA_dBBGR holds the data to write
' Output    : None
' Notes     : Clock idles low
'           : Shift data out MSB first, so brightness is sent first, then blue, green, red
'
Proc APA_Write32(pValue As APA_dBBGR)
Global Dim APA_bIndex As Byte Access Shared

    For APA_bIndex = 31 DownTo 0                                    ' Create a loop for the bits of data
        PinClear APA_SDI_Pin                                        ' \
        If APA_dBBGR.31 = 1 Then                                    ' | Put the current outgoing bit on the data Pin
            PinSet APA_SDI_Pin                                      ' |
        EndIf                                                       ' /
        PinSet APA_CKI_Pin                                          ' Set the Clock Pin high
        Rol APA_dBBGR                                               ' Shift the next bit into MSB
        PinClear APA_CKI_Pin                                        ' Pull the Clock Pin low
    Next
    PinClear APA_SDI_Pin
EndProc

'----------------------------------------------------------------------------------
' Write 32-bits of 0 data to an APA102 device
' Input     : None
' Output    : None
' Notes     : None
'
Proc APA_WriteZeros32()
Global Dim APA_bIndex As Byte Access Shared

    For APA_bIndex = 31 DownTo 0                                    ' Create a loop for the bits of data
        PinClear APA_SDI_Pin                                        ' Pull the data pin low
        PinSet APA_CKI_Pin                                          ' Set the Clock Pin high
        PinClear APA_CKI_Pin                                        ' Pull the Clock Pin low
    Next
EndProc

'----------------------------------------------------------------------------------
' Sends 24-bit colour and a 5-bit brightness value as a single 32-bit value.
' Input     : pRGB holds the 8-bit data for the Red LED (Byte0)
'           :            the 8-bit data for the Green LED (Byte1)
'           :            the the 8-bit data for the Blue LED (Byte2)
'           :            and the 5-bit data for brightness
' Output    : None
' Notes     : None
'
Proc APA_SendRGBB(pRGB As APA_dBBGR)
    APA_bBrightness = %11100000 | APA_bBrightness                   ' Make sure bits-5 to 7 are set
    APA_Write32(APA_dBBGR)
EndProc

'----------------------------------------------------------------------------------
' Sends 24-bit colour and full brightness value as a single 32-bit value.
' Input     : pRGB holds the 8-bit data for the Red LED (Byte0)
'           :            the 8-bit data for the Green LED (Byte1)
'           :            the the 8-bit data for the Blue LED (Byte2)
' Output    : None
' Notes     : None
'
Proc APA_Send_RGB(pRGB As APA_lBGR)
    APA_bBrightness = %11111111                                     ' Full brightness and make sure bits-5 to 7 are set
    APA_Write32(APA_dBBGR)
EndProc

'----------------------------------------------------------------------------------
' Send a "Start Frame" signal to the LED strip.
'
' This is part of the low-level interface, which
' allows you to send LED Colours as you are computing them instead of
' storing them in an array.  To use the low-level interface, first call
' APA_StartFrame(), then call APA_Send_RGB() some number of times, then call
' APA_EndFrame().
'
$define APA_StartFrame() APA_Write32($00000000)

'----------------------------------------------------------------------------------
' Send an "End Frame" signal to the LED strip.
' This is the last step in updating an LED strip
' Input     : None
' Output    : None
' Notes     : Use the end of frame procedure after the LEDs have been updated with colours
'
Proc APA_EndFrame()
    For APA_wTemp = cAPA_LED_LoopAmount DownTo 0
        APA_WriteZeros32()
    Next
EndProc

'----------------------------------------------------------------------------------
' Clear the strip of LEDs
' Input     : None
' Output    : None
' Notes     : None
'
Proc APA_Clear()
    Clear APA_dColourArray                                          ' Clear the array that holds the RGBB data
    APA_StartFrame()                                                ' Start a frame
    For APA_wTemp = cAPA_LED_LoopAmount DownTo 0                    ' \
        APA_SendRGBB(0)                                             ' | Send the colour 0 to the APA102 LEDs
    Next                                                            ' /
    APA_EndFrame()                                                  ' Update the strip of LEDs
EndProc

'----------------------------------------------------------------------------------
' Alter an LEDs Red level
' Input     : pRed holds the red LED's value
'           : pLEDIndex holds the LED in the strip to alter
' Output    : None
' Notes     : None
'
Proc APA_Red(pLEDIndex As APA_wLEDIndex, pRed As APA_bRed)
    'APA_dBBGR = APA_dColourArray[APA_wLEDIndex]                    ' Read an LED's colour from the array
    APA_dColourArray[APA_wLEDIndex] = APA_dBBGR                     ' Write it back to the array
    Light_LED()                                                     ' Alter the LEDs on the strip
EndProc

'----------------------------------------------------------------------------------
' Alter an LEDs Green level
' Input     : pGreen holds the green LED's value
'           : pLEDIndex holds the LED in the strip to alter
' Output    : None
' Notes     : None
'
Proc APA_Green(pLEDIndex As APA_wLEDIndex, pGreen As APA_bGreen)
    'APA_dBBGR = APA_dColourArray[APA_wLEDIndex]                    ' Read the LED's colour from the array
    APA_dColourArray[APA_wLEDIndex] = APA_dBBGR                     ' Write it back to the array
    Light_LED()                                                     ' Alter the colours
EndProc

'----------------------------------------------------------------------------------
' Alter an LEDs Blue level
' Input     : pBlue holds the Blue LED's value
'           : pLEDIndex holds the LED in the strip to alter
' Output    : None
' Notes     : None
'
Proc APA_Blue(pLEDIndex As APA_wLEDIndex, pBlue As APA_bBlue)
    'APA_dBBGR = APA_dColourArray[APA_wLEDIndex]                    ' Read the LED's colour from the array
    APA_dColourArray[APA_wLEDIndex] = APA_dBBGR                     ' Write it back to the array
    Light_LED()                                                     ' Alter the colours
EndProc

'----------------------------------------------------------------------------------
' Sends 24-bit colour value as seperate bytes.
' Input     : APA_bRed holds the 8-bit data for the Red LED
'           : APA_bGreen holds the 8-bit data for the Green LED
'           : APA_bBlue holds the 8-bit data for the Blue LED
'           : pLEDIndex holds the LED in the strip to alter
' Output    : None
' Notes     : None
'
$define APA_SendColours(pLEDIndex, pRed, pGreen, pBlue) '
    APA_bRed   = pRed                                   '
    APA_bGreen = pGreen                                 '
    APA_bBlue  = pBlue                                  '
    APA_wLEDIndex = pLEDIndex                           '
    Light_LED(APA_wLEDIndex, APA_dBBGR)

'----------------------------------------------------------------------------------
' Sends 24-bit colour value as a single 32-bit value.
' Input     : pColours holds the Red, Green, Blue, and Brightness values
'               Byte-0 = Red
'               Byte-1 = Green
'               Byte-2 = Blue
'               Byte-3 = Not Used
'           : pLEDIndex holds the LED in the strip to alter
' Output    : None
' Notes     : None
'
Proc Light_LED(pLEDIndex As APA_wLEDIndex, pColours As APA_dBBGR)

    APA_dColourArray[APA_wLEDIndex] = APA_dBBGR         ' Load the correct element in the array with the colour required
'
' Scan the whole strip of LEDs and colour them individually from the contents of the APA_dColourArray array
'
    APA_StartFrame()                                    ' Start a frame to begin at the first LED on the strip
    For APA_wTemp = 0 To cAPA_LED_LoopAmount            ' Create a loop for the amount of LEDs on the strip
        APA_dBBGR = APA_dColourArray[APA_wTemp]         ' Get a colour from the array
        APA_Send_RGB(APA_dBBGR)                         ' Send it to the LED strip
    Next
    APA_EndFrame()                                      ' Close the frame
EndProc

'----------------------------------------------------------------------------------
' Load the correct element in the array with the colour required
' Input     : pColours holds the Red, Green, Blue, and Brightness values
'               Byte-0 = Red
'               Byte-1 = Green
'               Byte-2 = Blue
'               Byte-3 = Not Used
'           : pLEDIndex holds the LED in the strip to alter
' Output    : None
' Notes     : None
'
$define APA_ShadowColour(pLEDIndex, pColours) APA_dColourArray[pLEDIndex] = pColours

'----------------------------------------------------------------------------------
' Update the LEDs with the contents of the buffer array
' Input     : None
' Output    : None
' Notes     : None
'
Proc APA_Update()
    APA_StartFrame()                                        ' Start a frame to begin at the first LED on the strip
    For APA_wLEDIndex = 0 To cAPA_LED_LoopAmount            ' Create a loop for the amount of LEDs on the strip
        APA_dBBGR = APA_dColourArray[APA_wLEDIndex]         ' Get a colour from the array
        APA_Send_RGB(APA_dBBGR)                             ' Send it to the LED strip
    Next
    APA_EndFrame()                                          ' Close the frame
EndProc

'----------------------------------------------------------------------------------
' Setup the interface to the APA102 chips
' Input     : None
' Output    : None
' Notes     : None
'
Proc APA_Setup()
    PinLow APA_CKI_Pin                                      ' Make the CKI pin a low output
    PinLow APA_SDI_Pin                                      ' Make the SDI pin a low output
    APA_Clear()                                             ' Extinguish all the LEDs in the strip
EndProc

'----------------------------------------------------------------------------------
_APA102_Main_:

$endif

Save the file as "APA102.inc"

A test program I wrote all of those years ago is listed below. Some of it may work, and some of it may not, because it was a test program, and I cannot re-test it now. I commented out the blocks of code that I had tested, and uncommented out blocks I was testing:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' APA102 RGBW LED Interface routines
' Written for the Positron8 BASIC Compiler, by Les Johnson
'
    Device = 18F26K40                                               ' Tell the compiler what device to compile for
    Declare Xtal = 64                                               ' Tell the compiler what frequency the device is operating at (in MHz)
    Declare Auto_Heap_Arrays = On                                   ' Make all arrays "Heap" types, so they always get placed after standard variables
    Declare Auto_Heap_Strings = On                                  ' Make all Strings "Heap" types, so they always get placed after standard variables
    Declare Auto_Variable_Bank_Cross = On                           ' Make sure all multi-byte variables remain within a single RAM bank
'
' Setup USART1
'
    Declare HRSOut_Pin = PORTC.6
    Declare HRSIn_Pin = PORTC.7
    Declare Hserial_Baud = 9600

$define APA_CKI_Pin PORTB.0                                         ' Connects to the APA102's Clock In pin
$define APA_SDI_Pin PORTB.1                                         ' Connects to the APA102's Data In pin
$define cAPA_LED_Amount 144                                         ' The amount of APA102 LEDs

    Include "APA102.inc"                                            ' Load the APA102 routines into the program
'
' Create global variable for the demo here
'
    Dim LED_bNumber     As Byte
    Dim LED_bBrightness As Byte
    Dim dColours        As Dword = 0
    Dim Red             As dColours.Byte0
    Dim Green           As dColours.Byte1
    Dim Blue            As dColours.Byte2

'----------------------------------------------------------------------------------
' The main program starts here
'
Main:
    Setup()                                                         ' Setup the program and any peripherals

(*
    APA_StartFrame()                                                ' Start a frame to begin at the first LED on the strip
    APA_Send_RGB($0000FF)                                           ' Send it to the LED strip
    APA_Send_RGB($FF0000)                                           ' Send it to the LED strip
    APA_Send_RGB($00FF00)                                           ' Send it to the LED strip
    APA_EndFrame()
    Stop
*)

    'APA_ShadowColour(0, $0000FF)
    'APA_ShadowColour(2, $FF0000)
    'APA_ShadowColour(3, $00FF00)
    'APA_Update()
    'Stop

    Do
        For LED_bNumber = 0 To 59
        Red = Random
        Green = Random
        Blue = Random
            APA_ShadowColour(LED_bNumber, dColours)
        Next
        APA_Update()
        'Inc Blue
        'Inc Green
        'Inc Red
        DelayMS 100
    Loop

(*
    For LED_bNumber = 0 To 20
        APA_Brightness(LED_bNumber, 30)
    Next

    For LED_bNumber = 0 To 20
        APA_Red(LED_bNumber, $80)
        DelayMs 100
    Next

    For LED_bNumber = 0 To 20
        APA_Green(LED_bNumber, $80)
        DelayMS 100
    Next

    For LED_bNumber = 0 To 20
        APA_Blue(LED_bNumber, $80)
        DelayMS 100
    Next
*)
(*
    Do
        LED_bBrightness = 0
        For LED_bNumber = 0 To 20
            APA_Brightness(LED_bNumber, LED_bBrightness)
            LED_bBrightness = LED_bBrightness + 2
            DelayMS 50
        Next

        LED_bBrightness = 0
        For LED_bNumber = 20 To 0 Step -1
            APA_Brightness(LED_bNumber, LED_bBrightness)
            LED_bBrightness = LED_bBrightness + 2
            DelayMS 50
        Next
*)
        (*
        For LED_bNumber = 0 To 20
            APA_SendColours(LED_bNumber, $80, 00, 00, 20 - LED_bNumber)
            DelayMS 100
            APA_SendColour(0, LED_bNumber)
        Next
        For LED_bNumber = 20 To 0 Step -1
            APA_SendColours(LED_bNumber, $80, 00, 00, LED_bNumber)
            DelayMS 100
            APA_SendColour(0, LED_bNumber)
        Next
        *)
'    Loop

    'APA_SendColour(1, $10001010)
    'DelayMS 500
    'APA_SendColour(2, $10100010)
    'DelayMS 500
    'APA_SendColour(3, $10110000)
    'DelayMS 500


(*
    APA_SendColours($80, 00, 00, 10, 0)
    DelayMs 100
    APA_SendColours(00, $80, 00, 10, 1)
    DelayMS 100
    APA_SendColours(00, 00, $80, 10, 2)
    DelayMS 100
    APA_SendColours($80, $80, 00, 10, 3)
    DelayMS 100
    APA_SendColours(00, $80, $80, 10, 4)
    DelayMS 100
    APA_SendColours($80, $80, 00, 10, 5)
    DelayMS 100
    APA_SendColours($80, 00, $80, 10, 6)
    DelayMS 100

    APA_SendColours($80, 00, 00, 10, 5)
*)


'---------------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    APA_Setup()
    Seed $0345
EndProc

'---------------------------------------------------------------------------------------------
' Setup the config fuses to use the internal oscillator at 64MHz on PIC18FxxK40 devices.
' With pins RA6 and RA7 as general purpose I/O.
'
Config_Start
    RSTOSC = HFINTOSC_64MHZ                                         ' HFINTOSC with HFFRQ = 64MHz and CDIV = 1:1
    FEXTOSC = Off                                                   ' External Oscillator not enabled
    WDTE = Off                                                      ' Watchdog Timer 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 = Off                                                     ' Brown-out disabled
    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 (After 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 = Off                                                       ' Low Voltage programming disabled
    WDTCPS = WDTCPS_2                                               ' Watchdog Divider Ratio 1:128 (4 milliseconds)
    WDTCWS = WDTCWS_7                                               ' Window always open (100%). Software control. Keyed access not required
    WDTCCS = LFINTOSC                                               ' WDT reference clock is the 31.2kHz HFINTOSC output
    WRT0 = Off                                                      ' Block 0 (000800-001FFF) not write-protected
    WRT1 = Off                                                      ' Block 1 (002000-003FFF) not write-protected
    WRTC = Off                                                      ' Configuration registers (300000-30000B) not write-protected
    WRTB = Off                                                      ' Boot Block (000000-0007FF) write-protected
    WRTD = Off                                                      ' Data EEPROM not write-protected
    Cp = Off                                                        ' User NVM code protection disabled
    CPD = Off                                                       ' Data NVM code protection disabled
    EBTR0 = Off                                                     ' Block 0 (000800-001FFF) not protected from table reads executed in other blocks
    EBTR1 = Off                                                     ' Block 1 (002000-003FFF) not protected from table reads executed in other blocks
    EBTRB = Off                                                     ' Boot Block (000000-0007FF) not protected from table reads executed in other blocks
Config_End


RGV250

Hi Les,
I changed the device to 18F2680 (only 5v one I could find) and with John's Amicus8 board and "random sequence" code worked first time.

Thanks,
Bob