Skip to content

Conversation

@rcloran
Copy link
Contributor

@rcloran rcloran commented Nov 7, 2025

This change allows the use of a SPI bus to send correctly timed data to a string of neopixels.

The bitbang approach cannot be used directly from a Raspberry Pi secondary MCU as the bitbang timing is not precise enough. Using the SPI bus to send data to neopixels is a commonly used technique among other open source projects, and seems to work well in Kalico, too.

On the devices I have tested (Raspberry Pi 5 and STM32F427) the available SPI speeds work with the data patterns that I have hardcoded, but it is conceivable that it may be necessary to make the data configurable or automatically generated based on requested bus speed in order to support some devices.

Checklist

  • pr title makes sense
  • added a test case if possible
  • if new feature, added to the readme
  • ci is happy and green

@rcloran
Copy link
Contributor Author

rcloran commented Nov 7, 2025

One thing I'm unable to test, and is probably quite important for how this would be used out in the wild, is whether it works properly on Pi 4 and older hardware. On the Pi 5 the SPI clock rate is controlled by the RP1's clock, which is fixed at 200MHz. On older Pi hardware the SPI clock is determined by the core clock, so the actual SPI clock rate might be much slower than requested, depending on how the core clock is scaled at the time of sending data. Any suggestions on how to get some willing testers?

I'm also unsure of whether sending this direct to Kalico is appropriate, instead of to Klipper?

I'm leaving this as draft for a few days as I want to do some final rounds of testing as soon as I get back to the same room as my hardware. I /think/ it's pretty much ready for review, though.

@rcloran rcloran force-pushed the neopixel-spi branch 4 times, most recently from de86afd to f4b6e68 Compare November 7, 2025 14:58
@ali1234
Copy link

ali1234 commented Nov 8, 2025

For maximum compatibility with WS2812 clones and revisions it is best if 1 bits are high for at least 800ns. You shouldn't need to make the patterns longer. Just switch some 0 to 1. See Klipper3d/klipper#7113 for my reasoning. (Even Klipper's current T1H = 650 is not long enough for my LEDs.)

@rcloran
Copy link
Contributor Author

rcloran commented Nov 8, 2025

You shouldn't need to make the patterns longer. Just switch some 0 to 1. See Klipper3d/klipper#7113 for my reasoning.

Good timing (😉). Actually even easier, I believe running at 4MHz may solve the problem (1us T1H, 1us T1L, 500ns T0H, 1.5us T0L), and that's user-configurable here -- but switching 1 of the 1-bit bits would probably give wider ranges of compatibility. I need to work through the timings again, anyways. I veered away from what the Adafruit libraries were doing because I couldn't find documentation about the reasoning, but I'll look again with an eye towards this, thanks.

I'll also use your test macros, looks more thorough than what I'd been doing.

(Even Klipper's current T1H = 650 is not long enough for my LEDs.)

Would you be willing to help test out this patch?

@ali1234
Copy link

ali1234 commented Nov 8, 2025

500ns may be too long for T0H. My research seems to indicate 200 to 300 is best, and 400+ will generate bug reports.

The general idea of using SPI and DMA to avoid pre-emption trouble on the Raspberry Pi goes back pretty much all the way to the original launch, although I am not sure how well it ever worked.

At the moment I can only test on RP2040 as I don't have a stand-alone voltage shifter to use with other boards, other than the one on my SB0000 toolhead breakout. I have the parts to build one on order though.

@rcloran
Copy link
Contributor Author

rcloran commented Nov 8, 2025

I put together a spreadsheet to show the different timings from various bit patterns and how they would work out for different SPI data rates: https://docs.google.com/spreadsheets/d/15FCtdJybMRscKh2xRNOzyStjmfH_7MbzbncTJFRtUMw

It seems like a default SPI data rate around 5-6MHz and a bit pattern of 2 high bits for a 0 and 5 for a 1 would be a good range, and hopefully the fact that the SPI data rate is configurable by the user would allow even more flexibility for compatibility. WDYT?

The main thing I still wanted to check is what exactly happens on the boards I have when you request a SPI rate that the board doesn't support -- I can't remember if both the Pi and STM32 gave the closest possible rate, or some other behaviour.

@rcloran
Copy link
Contributor Author

rcloran commented Nov 8, 2025

At the moment I can only test on RP2040

I don't know much about the RP2040, but if it supports SPI on the pin you're using for your neopixels that'd be a useful datapoint on a piece of hardware that I don't have (and so haven't tested on).

@ali1234
Copy link

ali1234 commented Nov 8, 2025

I have a SB2209/RP2040. The LED port is GPIO16. That's a SPI Rx, so it won't work for me.

When I get the parts I can test a generic Pico.

I have never touched SPI on RP2040 as PIO is the go to for signal synthesis. Afaik Klipper does not use it because it is arch specific, but it is really powerful.

This change allows the use of a SPI bus to send correctly timed data to
a string of neopixels.

The current bitbang approach cannot be used directly from a Raspberry Pi
secondary MCU as the bitbang timing is not precise enough. Using the SPI
bus to send data to neopixels is a commonly used technique among other
open source projects, and seems to work well in Kalico, too.

On the devices I have tested (Raspberry Pi 5 and STM32F427) the
available SPI speeds work with the data patterns that I have hardcoded,
but it is conceivable that it may be necessary to make the data
configurable or automatically generated based on requested bus speed in
order to support some devices.

Signed-off-by: Russell Cloran <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants