Question (trying to control the neopixels using SPI)

Hi

I have been trying to control a small 8-led board with WS2812B’s using some SPI examples. Since i have SPI1 used already for other communication, and SPI6 does not have regular DMA, i chose SPI2 (Daisy pin D20), it was not working for a long time because i supposed that baud prescaler is applied to a 400Mhz but then I kinda brute-forced and it worked with a prescaler 4 so i suspect that SPI1,2,3 is driven from some other PLL. So i hope someone could sched a light to some questions i got:

  1. what is the actual SPI frequency before prescaler (and which PLL is used - i tried reading the code but somehow it goes deep into HAL and i have little knowledge about low level clock registers)

  2. I have the first LED being off color (seems like green is always at max level), and it is not related to that data signal is 3.3V - i have added LVC1G125 (powered from 5v) and it is still the same, i even tried connecting two led boards in parallel, one with and other without level shifting and they look exactly the same. My theory is that i would need more precise control over SPI frequency so maybe changing the PLL clock or so, because i suspect that LED controller part which is collecting data to shift to next pixel is more tolerant and thus '‘fixes’ the timing problems while the pther part which drives PWM on the LED is not that good with vague timings. Is there any side effects if i change PLL for SPIs 1,2,3? And what is proper approach for this? Direct HAL calls or…? Btw, the SPI1 is in use but as slave (other MCU is talking to it)

  3. I would like also to try the other, timer PWM + DMA approach, if i can’t fix SPI, but all the examples i have found are for STM32F series and mainly also involving STM32CubeIDE while i would like to either have an example for STM23H MCU or at least to know the timer frequencies etc in words, not in IDE screenshots, so i could try to port example to Daisy. Did anyone try it and could share anything about it?

Thanks in advance, and have a great day!

FYI i have connected it to ther MCU where i know clocks, and setting it to even a perfect bitrate (6.4Mbaud) was not fixing the issue. I have a feeling that maybe newer LEDs are more timing sensitive or so, because whatever worked for other a couple of years ago is not working well now. So i give up SPI approach, leaving the timer PWM + DMA to try. If anyone by any chance have a STM32H7xx code to peek into or so, let me know, because STM32 is a new platform for me and i have no idea what changes would i need to change code using STM32F series to work on Daisy

I’m also trying to drive WS2812 through SPI pin11 (SPI1 MOSI) because driving the LEDs in serial causes interruption of the audio stream.

Using the attached test program doesn’t work properly (LEDs are just on)

#include "daisy_seed.h"
#include <array>
using namespace daisy;

// ============================
// Hardware & SPI objects
// ============================
DaisySeed   hw;
SpiHandle   spi_handle;
SpiHandle::Config spi_cfg;

// ============================
// User config
// ============================
constexpr int NUM_LEDS = 8;                 // change as needed
constexpr int BYTES_PER_LED = 24;           // 24 WS bits -> 24 SPI bytes
constexpr int RESET_BYTES   = 80;           // >50us latch (~1.28us/byte -> >=40)

constexpr int SPI_BUF_SIZE  = NUM_LEDS * BYTES_PER_LED + RESET_BYTES;

uint8_t led_data[NUM_LEDS * 3];                              // GRB
uint8_t DMA_BUFFER_MEM_SECTION spi_buffer[SPI_BUF_SIZE];     // DMA-safe

inline uint8_t encode_ws_bit(uint8_t bit)
{
    // SPI ~6.25 MHz (PS_16): 1 byte ~1.28us
    // 0 -> 11000000  (short high, long low)
    // 1 -> 11110000  (longer high, shorter low)
    return bit ? 0xF0 : 0xC0;
}

void ws2812_encode(const uint8_t *data, int led_count)
{
    int spi_index = 0;
    for(int i = 0; i < led_count; i++)
    {
        uint8_t g = data[i * 3 + 0];
        uint8_t r = data[i * 3 + 1];
        uint8_t b = data[i * 3 + 2];

        for(int byte : {g, r, b})
        {
            for(int bit = 7; bit >= 0; bit--)
            {
                spi_buffer[spi_index++] = encode_ws_bit((byte >> bit) & 0x01);
            }
        }
    }
    while(spi_index < SPI_BUF_SIZE)
    {
        spi_buffer[spi_index++] = 0x00; // reset/latch
    }
}

void ws2812_send(const uint8_t *data, int led_count)
{
    ws2812_encode(data, led_count);
    spi_handle.DmaTransmit(spi_buffer, SPI_BUF_SIZE, nullptr, nullptr, nullptr);
}

int main(void)
{
    hw.Init();

    // SPI1 (SCK=D8, MOSI=D10)
    spi_cfg.mode        = SpiHandle::Config::Mode::MASTER;
    spi_cfg.periph      = SpiHandle::Config::Peripheral::SPI_1;
    spi_cfg.direction   = SpiHandle::Config::Direction::TWO_LINES_TX_ONLY;

    spi_cfg.nss                 = SpiHandle::Config::NSS::SOFT;
    spi_cfg.pin_config.sclk     = hw.GetPin(8);   // D8  PB3
    spi_cfg.pin_config.mosi     = hw.GetPin(10);  // D10 PB5
    spi_cfg.pin_config.miso     = Pin();          // unused
    spi_cfg.pin_config.nss      = Pin();          // unused

    spi_cfg.clock_polarity      = SpiHandle::Config::ClockPolarity::LOW;
    spi_cfg.clock_phase         = SpiHandle::Config::ClockPhase::ONE_EDGE;
    spi_cfg.datasize            = 8;
    spi_cfg.baud_prescaler      = SpiHandle::Config::BaudPrescaler::PS_16; // ~6.25 MHz

    spi_handle.Init(spi_cfg);

    int current = 0;
    while(1)
    {
        for(int i = 0; i < NUM_LEDS; i++)
        {
            led_data[i*3 + 0] = 0;  // G
            led_data[i*3 + 1] = 0;  // R
            led_data[i*3 + 2] = 0;  // B
        }

        // one pixel green
        led_data[current*3 + 0] = 255;

        ws2812_send(led_data, NUM_LEDS);
        System::Delay(500);
        current = (current + 1) % NUM_LEDS;
    }
}

Is that code your own creation?

Suggestion:

I’m really sorry for the delay in response.
So for that particular LED, you would need to use hardware PWM and not SPI.
We unfortunately, at the moment, don’t have a support and example available for using the WS2812 with the Daisy.
I believe the Fast LED library that’s made for Arduino IDE should work?

You could consider using the SK9822 LED which infrasonicaudio made a PR for that was merged to libdaisy. It uses SPI.

Hey, no worries. This project has two MCUs one for audio/mmcsd (guess which) and other for low level hardware stuff, exhanging data with SPI. Initially wanted to offload this task from other MCU which must bitbang 3.5Mhz bus keeping the right timings, but … ended up using UART-based solution on that NXP MCU for RGB LEDs. Required some nasty coding using state machines though to fit the timings. As of now - schematics finished, PCB routed, prototypes soldered, so not a time to revert this decision :smiley:
Well, still will try your suggestions when i have more time just to close this question for myself but only after other things are ready

Hi, We (@Nico ) managed to create an SPI+DMA method that drives the WS2812B. The main problem with the test code that we posted is that the baud prescaler is incorrect. The issue of the first LED being the wrong colour (green) can also be mitigated by adding a small number of leading 0 bits. I’m not sure if this counts as a hack or an acceptable solution. It works quite smoothly now for eight leds. I can make a pull request if it helps.

Glad to hear that it’s working!
Please feel free to open up a PR any time. We’re happy to review :slight_smile:

1 Like