Makuna / NeoPixelBus

An Arduino NeoPixel support library supporting a large variety of individually addressable LEDs. Please refer to the Wiki for more details. Please use the GitHub Discussions to ask questions as the GitHub Issues feature is used for bug tracking.
GNU Lesser General Public License v3.0
1.17k stars 255 forks source link

Support for LED Curtains, Active tee/hook for each vertical drop #790

Open deece opened 3 months ago

deece commented 3 months ago

Is your feature request related to a problem? Please describe. As per https://wled.discourse.group/t/has-anyone-reverse-engineered-how-led-curtain-hooks-work/7423/11

Some LED curtains have a chip in the top tee that connects each vertical drop.

Some implementations just count the number of pixels and direct the remainder downstream, however, the one I have come across is a bit different.

This one always directs the input to the vertical drop, and will direct the stream to the next tee once it receives a ~5uS high pulse, followed by a low of ~35 uS.

Screenshot from 2024-03-23 14-28-03

Describe the solution you'd like The ability to break the stream into N pixels, and between each chunk, send the 5uS/35uS pulse to address the next segment of the curtain.

I'm happy to cut the code for it, but some pointers about where to look & how to structure it would be appreciated.

Makuna commented 3 months ago

This is problematic for many "methods". Any method that relies on hardware (esp8266, esp32, Rp2040, Nano 33 BLE) rely on hardware features that dictate a consistent pulse wavelength with two states. The core feature of these is that they are asynchronous (fire and forget). If they can't form arbitrary pulses, they can't be used for this.

ESP8266 I2S DMA - can't be done. ESP8266 UART - can't be done. ESP32 I2S DMA - can't be done. ESP32 RMT - significant refactor but maybe possible but see caveat below. Nano 33 BLE - significant refactor but maybe possible. RP2040 PIO - can't be done. BitBang - ranges from can't be done to significant refactor. Many implementations are fine-tuned code to keep to instruction count low and fast enough to maintain current pulses. Some are assembly code. Adding a simple branch to "know" when to inject this "switch" pulse will stop some from being functional at all.

Another issue that comes up is that a key customer (WLED) relies on the data pointer to be array of RGB, but to support injecting into a DMA requires the buffer to contain the non-color data (the PWM signal for this "switch" pulse). So, any solution would not be functional with WLED or anyone relying on accessing the data pointer and assuming it's just color data.

I am sure there are some ways to hack something that would work, but they would lose the async nature of key methods to inject the switch and restart the next string.

I will be including this a future consideration if/when a rearchitecting happens.

deece commented 3 months ago

Thanks for the info.

My use case is actually for WLED/ESP32-RMT, and I know the hardware itself is capable of it (I had to do some tricks with ESPhome's RMT support to make it open drain).

I figured I'd post here first before WLED, as it feels like a generic enough feature to be done low level.

The alternative would be to patch WLED to break up the transmission into multiple calls to NeopixelBus then manually handle the column switch pulse in WLED between the calls.

Makuna commented 3 months ago

Thanks for the info.

My use case is actually for WLED/ESP32-RMT, and I know the hardware itself is capable of it (I had to do some tricks with ESPhome's RMT support to make it open drain).

The alternative would be to patch WLED to break up the transmission into multiple calls to NeopixelBus then manually handle the column switch pulse in WLED between the calls.

The issue you will run into with RMT method is that it is asynchronous. So, when the "show" returns (API or at the lowest level in my work), it is still sending the data stream. And the only way to know it finished is polling, which has an unknown timing aspect so if that switch pulse requires tight timing, it won't work. To maintain reasonable timing, you will Block and poll waiting for the async stream to finish, then send "switch pulse" again blocking (RMT can do the pulse, but again, see my comment about WLED assuming the buffer is color data only), then send the next stream, rinse and repeat. BUT you are now synchronous and wasting CPU that was in WLED case being used to receive, calc, prepare the next frame. Slowing everything down.

Another more functional solution is rewriting the low level translate callback from RMT, so it auto "interleaves" multiple or segmented streams with the injected pulse. But this is a significant change and again only works with RMT. This translate is an ISR callback, thus static, and has no access to instance data (no this pointer) so everything it can access is either constants, given to it from the RMT system as arguments to the callback, or static/global variable state which gets ugly due to RMT allows multiple instances (4 to 8 channels). It is different enough I would create a new RMT method and leave the current work alone.

Another solution is a custom send buffer format, so that the data is translated into that format in the show which includes the segments with a switch "identification", then the translate method will read it and translate it into RMT formatted data. Currently today RMT is double buffered, one sending and one editing. It just switches between them, so this would change to copy from editing to sending and do the reformatting as it does it. It will slow the system down some.

Makuna commented 3 months ago

Then there are the high-level API changes. How does it know how long each "drop string" is? Are they always the same length or does a map of how many and each length need to be provided. This concept is foreign to NeoPixelBus. It does not manage multiple bus as one or expose a single bus as multiple. These "organizations" have been left up to sketch authors. Lots of thought down these paths has always led to solutions that are very custom to the specific needs of the sketch author.

You are welcome to hack something together for your purpose, but without a well thought out solution that works across platforms (this is a cross platform Arduino library) I am not likely to merge it in.