Photon2 addressable LEDs, clean alternative to neopixel & fastled

Apologies for the repost but I think posted the original in the wrong Category.

If you're interested in a clean Particle native/specific neopixel replacement take a look at pixeleds-library for controlling addressable LEDs.

I created this several years ago (8?!) but just recently updated it to Photon 2 (using SPI).

It is published as a Particle library (pixeleds-library 2.0.1) and the fully documented source code with example usage is available on GitHub: GitHub - krsmes/pixeleds-library: Learning how to control addressable LEDs

The animation support is quite extensive. There are 15 built in animation function to choose from and you can write your own -- which can be as simple or complex as imaginable (see pixeleds-library/examples/candycane-animation.cpp at master · krsmes/pixeleds-library · GitHub for a complex example).

The logic for updating the LEDs is completely separated both from the animation "Pixeleds" support and from each other between Photon1 (custom timing) and Photon2 (SPI).

This currently support WS2812B and SK6812W on both platforms but could easily be extended for other types. It has built in support for any RGB / RGBW ordering on the output (e.g. GRB / GRBW).

Its a fun toy project but I think the code is cleaner than any other addressable-LED support libraries I've seen (which is why I wrote it).

Enjoy.

3 Likes

The code looks pretty awesome! Thanks for sharing.
I’m very excited about the custom animations!

Interestingly, for the complex candycane animation example, I gave Claude.ai pixeleds-library.h and then just started describing the animation I wanted. It produced working code on the first try and after a few more rounds of change-this, redo-that I was able to produce that animation 10-100x faster than if I tried on my own.

I also used Claude.ai to try and make sense of the SPI library and bit encoding, kept poking it for a cleaner more efficient implementation. The only code I had to copy from elsewhere was the SPI setup from technobly's Particle neopixel -- not sure how anyone would be able to figure that out on their own.

/**
* Initializes SPI configuration for addressable LED control with MOSI-only operation.
* 
* This setup function configures SPI for specialized LED control by:
* 1. Preserving existing SCK and MISO pin configurations
* 2. Initializing SPI in MOSI-only mode to avoid conflicts
* 3. Setting appropriate clock speed for timing
* 4. Restoring SCK and MISO pins to their original states
* 
* The initialization process:
* - Identifies correct SPI pins based on interface (SPI or SPI1)
* - Saves current pin modes and states of SCK/MISO
* - Configures SPI with MOSI-only flag for LED data output
* - Restores SCK/MISO pins to original modes and states
* 
* This allows other pins on the SPI interface to be used for different purposes
* while dedicating MOSI for LED control.
* 
* @note SCK and MISO pins are freed for GPIO use after setup
* @note Clock speed must be set after begin() due to Device OS 5.7.0 requirement
* @note Uses PIN_INVALID to prevent automatic SS pin configuration
*/
void ParticlePixels::setup() {
    pin_t sckPin = SCK;
    pin_t misoPin = MISO;
    if (spi->interface() == HAL_SPI_INTERFACE2) {
        sckPin = SCK1;
        misoPin = MISO1;
    }
    PinMode sckPinMode = getPinMode(sckPin);
    PinMode misoPinMode = getPinMode(misoPin);
    int sckValue = (sckPinMode == OUTPUT) ? digitalRead(sckPin) : 0;
    int misoValue = (misoPinMode == OUTPUT) ? digitalRead(misoPin) : 0;
    
    // implement begin() with MOSI_ONLY flag (no Wiring API yet for this)
    hal_spi_config_t spi_config = {};
    spi_config.size = sizeof(spi_config);
    spi_config.version = HAL_SPI_CONFIG_VERSION;
    spi_config.flags = (uint32_t)HAL_SPI_CONFIG_FLAG_MOSI_ONLY;
    hal_spi_begin_ext(spi->interface(), SPI_MODE_MASTER, PIN_INVALID, &spi_config);
    spi->setClockSpeed(SPI_CLOCK_SPEED); // OS 5.7.0 requires setClockSpeed() to be set after begin()
    
    // allow SCLK and MISO pin to be used as GPIO
    pinMode(sckPin, sckPinMode);
    pinMode(misoPin, misoPinMode);
    if (sckPinMode == OUTPUT) {
        digitalWrite(sckPin, sckValue);
    }
    if (misoPinMode == OUTPUT) {
        digitalWrite(misoPin, misoValue);
    }
}

(Claude.ai generates great comment docs too)