Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inefficient SPI Data Transmission for APA102 / SK9822 LEDs on ESP32 #1609

Open
coryking opened this issue Mar 10, 2024 · 3 comments
Open

Inefficient SPI Data Transmission for APA102 / SK9822 LEDs on ESP32 #1609

coryking opened this issue Mar 10, 2024 · 3 comments

Comments

@coryking
Copy link
Contributor

Summary

When using FastLED with ESP32 to drive APA102 and SK9822 LED strips, significant inefficiencies have been observed in SPI data transmission. The implementation appears to conduct separate DMA transactions for each byte of data, introducing noticeable gaps between 8-bit bursts. This behavior considerably diminishes the effective data transmission rate, closely mirroring the performance limitations characteristic of older one-wire LED models like the WS2812b.

Environment

  • Microcontroller: ESP32
  • LED Types: APA102 and SK9822
  • LED Count: Tested with 30 LEDs, but the issue is applicable to any strip length
  • Data Rate: Configured for 5MHz and 20MHz
  • Library Version: FastLED (latest as of writing)

Observations

  • Oscilloscope Measurements:
    • With a data rate set to 5MHz, updating a 30-LED strip takes about 422 microseconds.
    • Setting the data rate to 20MHz reduces the update sequence to approximately 244 microseconds, which, despite showing improvement, falls short of expected performance levels.
    • Gaps between each 8-bit data burst are measured to be roughly 1.7 microseconds, aligning with the setup overhead for DMA transactions described in ESP32 documentation.

Expected vs. Actual Behavior

Expected: For high-speed SPI communication, especially at higher data rates, FastLED should utilize continuous DMA transfers for the entire data packet of LED strip data, minimizing CPU overhead and maximizing throughput for quick and efficient updates.

SDS00004
This is what I'm calling the "inner byte gap". It's roughly 1.7 or so microseconds. It transmits 8 bits, takes a break, transmits 8 more bits... etc.

Actual: The implementation seems to trigger individual DMA transactions for every byte sent to the APA102 and SK9822 LEDs, leading to unnecessary overhead, reduced transmission rate, and increased update times, particularly for longer LED strips.

Investigation and Documentation

This issue aims to document and investigate the SPI data transmission inefficiency for APA102 and SK9822 LEDs on the ESP32 using FastLED. The goal is to explore this issue further, propose optimizations, and prevent duplication of efforts across multiple issues or pull requests.

SDS00002
The above image shows how long it took to transmit a 30 LED strip. Roughly 230uS at a "20MHz" setting in fastLED.

Additionally, this concern echoes a comment from @SCarter858 on Dec 27, 2023, which detailed similar inefficiencies and questioned the actual utilization of DMA for SPI communication within FastLED's ESP32 HW SPI support.

Additional Context

Note that this being caused by separate DMA transactions is only hypothetical. It might not be using DMA at all!

This inefficiency not only affects APA102 LEDs but also their SK9822 counterparts, which are often used interchangeably in projects. The potential lack of full DMA utilization for SPI transmission significantly throttles the performance capabilities of these chips, making high-density LED installations as slow as older one-wire LED protocols like the WS2801. An optimized implementation would greatly enhance performance, leveraging the ESP32's capabilities for LED projects using FastLED.

@coryking
Copy link
Contributor Author

Here is some results when I used a completely different APA102 driver I found online: DMA Findings.md

As you can see from the oscilloscope readings in the linked document, when you use DMA it looks nothing like the ones in this ticket. Plus the speeds are significantly faster (though they appear to be "rounded" for reasons I don't fully understand.

@coryking
Copy link
Contributor Author

coryking commented Mar 17, 2024

So I need everybody's opinion on how to provide the "SPIOutput" class the number of LEDs / bytes required for a DMA buffer.

In order to get DMA working we need to allocate a buffer on the heap but to do so we need the size of the buffer. I think the best place for this is in the init() function. The problem is these "SPIOutput" classes do not know how many LEDs they are supposed to be driving, thus I have no clue how big of a buffer to allocate. So I need to get the number of LED's plumbed in and probably the number of "extra" bytes needed for start and end frames.

The problem is these SPI handlers are all supposed to look like "SPIOutput" even though they are all completely different classes. So I suspect if I make a change to init() to pass in the number of leds (or perhaps just bytes) I'll have to change all the other SPIOutput classes as well (which may not be a bad idea because any of the other SPI classes that do DMA need the same info).

So, in short:

  1. Is SPIOutput the correct place to manage the DMA buffer?
  2. What is the best way to provide SPIOutput with the size of said DMA buffer?
  3. If it is the SPIOutput::init() function like I plan, are we cool with updating all the other SPIOutputs to have the same signature?
  4. Should it get the number of LEDs or the number of bytes? I'm leaning toward the number of bytes, as I suspect that SPIOutput is a "bit pusher" and isn't supposed to care about the various LED protocols.

Pinging @samguyer and @kriegsman for their thoughts as well as @Jueff & @sheaivey who looks to have done some DMA stuff for a different platform.

To avoid getting blocked, if I don't hear anything in the next few days I'm gonna proceed with doing this in the ESP32SPIOutput class and passing the number of bytes / leds through the init() function.

@coryking
Copy link
Contributor Author

Also I should add I'm going to borrow quite liberally from the led_strip_spi component from the UncleRus esp-if-lib component library. I'll make sure to adhere to its MIT License when pushing my changes.

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

No branches or pull requests

1 participant