Driving APA102 & SK9822 RGB LED strings using hardware SPI on the M2 chips

Flenser

Senior Member
When I searched the forum I could not find any posts about driving APA102 RGB LED strings on M2 chips using the hardware SPI module but I did discover this post M2 series hardware SPI by GrahamGo in 2012 using PEEKSFR and POKESFR to write a byte to, and read a byte from, an SPI chip using the hardware SPI module.

The purpose of this post is to provide some example programs using PEEKSFR and POKESFR to drive APA102 & SK9822 RGB LED strings using the hardware SPI module on all the M2 chip's.

These programs define the macro m2spi_init() to initialized the hardware SPI module and the macros m2spi_head(), m2spi_send() and m2spi_tail() to send sets of RGB colours to the LED string as well as the symbol constant definitions that are used by these macros.

My example programs:
- Copy GrahamGo's code to initialize the SPI hardware module, the only differences being that I've hardcoded the spi mode to use to drive APA102 LEDs in the m2spi_init() macro and I've formatted the code in my preferred way.
- Do not include GrahamGo's code to read the SDI register or to use the /CS [Chip Select] function as the APA102 & SK9822 RGB LED strings do not use these.
- Only require you to uncomment the correct dirsX commands for the chip you want to use. You can optionally update the value of the BRIGHTNESS constant or the PAUSE in the main loop to alter the speed of the display.
- Are tested on the 08M2, 14M2 & 18M2 chips. I have also provided the dirsX commands for the 20M2 chip but this is not tested as I do not own a 20M2 chip.

These example program can be used on any M2 chip as the Special Function Registers values and variable usage are the same:
M2SPI_Rainbow_fade.bas: Fade the whole string through the rainbow one colour at a time.
This example will work with strings of any length by setting the symbol HOW_MANY_LEDS.
M2SPI_Rainbow_Scroll.bas: Displays the rainbow along the length of the string, scrolling the colours to the left.
The storage array that these examples all use has a rainbow of 40 colours and that means this rainbow scrolling example will only work with strings up to 40 LEDs long. It works fine with shorter strings.
M2SPI_Breathing: Makes all the LEDs in the string do a "breathing" effect.
This example will work with strings of any length by setting the symbol HOW_MANY_LEDS.

To write your own programs all you have to do is:
1) Include these macros and the symbol constants that they use.
2) Execute the correct dirsX commands for the chip you want to use.
3) Update the symbol HOW_MANY_LEDS value to match how many LEDs your string has.
4) Update the symbol BRIGHTNESS that you want to use
5) Call the m2spi_init() macro to initialize the SPI module. Using m2spi_init(SpiSlow) is recommended before the main loop.
6) Make calls to the macros head, send and tail in the main loop to send sets of BGR values to the LED string.

NOTES:
Spimode01 is the correct mode to use to with the APA102 & SK9822 RGB LEDs so I have hardcoded in in the m2spi_init() macro.

Using spislow the SPI module transmits each byte in a fraction of the time that the PICAXE POKESFR command takes to complete. There is nothing to be gained by using a faster speed so I recommend that spislow is always used.

These LEDs are commonly referred to as RGB serial LEDs but with the APA102 & SK9822 RGB LEDs the bytes need to be sent in BGR order.

The Microchip documentation states "The pins must have their data direction bits appropriately programmed" so the dirsX commands in the example programs set the SDO & SCK pins as outputs on all M2 chips so:
  • The dirsX commands in the example programs set the SDI pin as an output on all M2 chips. I have tested that I can set the overridden SDI pin HIGH and LOW with no impact on the sending of RGB values to the LED string.
The Microchip documentation also states "Any serial port function that is not desired may be overridden by programming the data direction" so:
  • The dirsX commands in the example programs set the SS pin as an input on the 18M2 & 20M2 chips. I did not test reading from overridden the SS pin.
The 18M2 & 20M2 chips each have two SPI modules but I only use SPI module 1 for them in order to keep my example programs simple and so my examples can be used on any M2 chip by just uncommenting the one set of dirX commands for the chip you want to use.

The PIC microcontrollers also have a feature that allows the SDO function to be assigned to to a different pin from the default.
  • I have attached the program M2SPI_Rainbow_Fade_APF.bas to post #2 of this thread showing how this is done.
  • M2SPI_Rainbow_Fade_APF.bas is the example M2SPI_Rainbow_Fade.bas from this post modified to use the alternative pin for the SDO function for each chip where this is supported.

If you would like assistance to use the second SPI hardware module on the 18M2 & 20M2 chips simply post a question in the forum and I, or one of the other forum members, will be able to help you.

This link Arduino – LEDStrip effects for NeoPixel and FastLED has gifs showing different LED string displays. The code is Arduino but the post provides some inspiration for implementing similar displays using PICAXE chips.
 

Attachments

Last edited:
This post provide an example program using PEEKSFR and POKESFR to drive APA102 & SK9822 RGB LED strings using the hardware SPI module on the 08M2, 14M2 & 18M2 chips with the SDO function assigned to it's alternative pin. It does not apply to the 20M2 as the SDO function does not have an alternative pin defined on that chip.

M2SPI_Rainbow_fade_APF.bas: Fade the whole string through the rainbow one colour at a time.
This example uses the alterative pin for the SDO function and will work with strings of any length by setting the symbol HOW_MANY_LEDS.

There are only three differences from the PEEKSFR/POKESFR code in the example programs in post #1:
  • The dirX commands for each chip have been altered for the different SDO function pin.
  • There is an additional constant definition "symbol APFCON".
  • The m2spi_init() macro has extra code included in it to assign the SDO function to it's alternative pin.
 

Attachments

Hi,

An interesting and useful thread, which I'm filing away for future use, in case I ever need to use an SPI interface. The inclusion of the Alternative Pin Function Register is particularly welcome. However, whenever the prospect of using a chip with an SPI interface arises, I ALWAYS look to see if an alternative with an I2C Bus interface exists ! IMHO the I2C Bus is far superior to the SPI, and with an M2 PICaxe can be significantly faster. But beware that many devices such as OLED Displays, often described as "I2C or SPI", have different breakout versions for the two interfaces (typically described/shown as either 4-pins or 7+ pins), so ensure that you are purchasing the correct one (and with a "known" driver chip).

Not particularly relevant for the specific (08M2 compatible) program above, but its POKE instruction consumes around 400 bytes of Program memory to load only 120 bytes of Data. If expanding it to a "larger" Application Program, beware that the POKE (and POKESFR) command, particularly with a "list" of parameters, can be remarkably slow and inefficient in code-space, especially if the Start Address is a numeric variable (e.g. b0).

The 08M2 has relatively little DATA/TABLE memory, compared with its amount of Program space, so it can be appropriate to store/load Data values from the Program space (e.g. using POKE{SFR}, LOOKUP or HI2COUT, etc.), rather than from the DATA/EEPROM area. However, the 14/20M2 in particular, have a generous amount of TABLE memory (512 bytes in each of the two "Slots"), which is often overlooked. The X2 chips have a smaller TABLE memory that (unlike the M2s) if it is used, is "borrowed" from the Program Memory area, in the same way that using the 08M2's EEPROM correspondingly reduces the available Program Memory space.

As expected, any byte value (from 0 to 255) stored in "DATA/EEPROM" Memory consumes exactly one byte of space, but if stored within a Program Instruction (POKE, LOOKUP, etc.) it generally consumes more than a single byte of Program space. Furthermore, the amount of memory occupied depends on (among other things) the value stored ; "0" and "1" are relatively compact, "2" to "15" consume somewhat more space, but less than the remaining values, i.e. "16" up to "255". This can be advantageous for datasets which have a high proportion of low values, such as Character Fonts (with a Black/zero background level).

Here are some example program instructions which can make (five) "Data" values available to the program, with a comment noting their size in a Program (from a PE6 Syntax Check). Fractional values (i.e. ending in 0.5) are the average when embedded within slightly different program structures (as can happen with almost all the instructions). Often, the number of bytes can be an indicator of the speed (or strictly execution time) of the instruction, because the token-decoding can be quite time-consuming.

Code:
#picaxe 14m2                       ; And most others
poke b0,99,99,99,99,99             ; = 29 bytes
poke 12,99,99,99,99,99             ; = 16 bytes
poke 12,0,0,0,0,0                  ; = 11 bytes
@bptrinc = 99 : @bptrinc = 99 : @bptrinc = 99 : @bptrinc = 99 : @bptr = 99   ; = 15 bytes
@bptrinc = 1 : @bptrinc = 0 : @bptrinc = 1 : @bptrinc = 0 : @bptr = 1        ; = 10 bytes
lookup b0,(99,99,99,99,99),b0      ; = 10.5 bytes
hi2cout b0,(99,99,99,99,99)        ; = 9 bytes
lookup b0,(0,0,0,0,0),b0           ; = 6.5 bytes
hi2cout b0,(0,0,0,0,0)             ; = 5.5 bytes
tablecopy 12,500                   ; = 5 bytes to transfer 500 bytes from Table to RAM !   (Not 08M2)
tablecopy 12,5                     ; = 3.5 bytes

The DATA/TABLE can be created by simply removing the POKE from the instruction line and substituting DATA or TABLE and adding braces ( ) around the numeric list (excluding the FIRST_VALUE). Note the incredible efficiency of the rarely-used TABLECOPY instruction, which is also very fast. Sadly, it implements only one address pointer (into both RAM and the TABLE) ; it would have been really useful if it could copy any consecutive set of ten ASCII characters (for example, for menus) from the 512 bytes Table into registers b18 to b27. :(

Cheers, Alan.
 
Back
Top