Pixel manipulation

Hello...
I am most certainly no expert at Arduino programming but have successfully used the Arduino in a number of projects.
A while ago, I started using pixel LED's and strips using a range of controllers both Arduino and others. For clarification, 3 pin LED strip 2811/2812 etc.

Cut to the chase.
I want to capture the pixel data as it comes out of a controller then be able to send the data to different strips without using the output from the first strip (Last LED) into the second strip (First LED) and so on - i.e. not needing the out in wire from one strip to the next.
So far, I have completely failed at the first hurdle.

Lets assume the Arduino is NOT creating the LED data itself and this is coming from else where.
How do I capture the data signal?
How do I strip away the unwanted data which would have gone to the previous strip and send it to the next strip?

I am going to be honest and don't even know if this can be done but any help, guidance or advice is very much appreciated.

Thank you in advance for reading this and offering advice or guidence.

The WS2811/WS2812 protocol is well-documented and deterministic ➜ it makes a man-in-the-middle attack theoretically "straightforward".

Each LED receives a stream of 24-bit RGB data (8 bits per channel), passes it along to the next LED, and the frame resets after a ~50µs low signal and since the data flows in one direction and the structure is fixed, you can intercept it, modify the values in real time, and re-inject the altered stream downstream.

The protocol is clear but timing is the challenge : Each bit is encoded as a precise high/low pulse: a 1 bit is roughly 0.8µs high and 0.45µs low, a 0 bit is roughly 0.4µs high and 0.85µs low, for a total bit period of ~1.25µs.

So you have 1.25µs per bit to receive it, decode it, apply your transformation, and re-transmit it, all before the next bit arrives and the tolerance is tight (±150ns or so depending on the chip revision), which means you essentially cannot do this on a small arduino.

A bare-metal STM32 with carefully tuned DMA can get close, but the realistic options would probably be an RP2040 using its PIO state machines which run independently of the CPU, an ESP32 with RMT peripheral or programmable logic device / FPGA so that you get hardware-level timing control without CPU jitter.

A good question would be why you want this to see if there is another approach.

Welcome to the forum

You don't say why you need to do this as the obvious thing to do would be to daisychain the LEDs into one strip

How exactly is the data going to be received by the Arduino ?

Thank you for your reply. It is very much appreciated.

I was aware that the timings were going to be tight but I was completely at a blank to if this could be done.

Just for clarification, the rationale behind this project /activity is so that LED strips can be hung from a ceiling with only three wires.
Getting a wire from the bottom of the strip to the top is tight if the LED strips are in an aluminium and Perspex profile tube which is how I was intending this to be.

Its not altogether laziness, its logistics and i was hoping for gaps between the strips which may cause more timing issues. Oh the joys of having good ideas.

Thank you again.

Thank you for your reply.

My thoughts were to have strips hanging in aluminium profile from the ceiling without having to add a return wire.

You ask how exactly how is the data going to be received by the Arduino....
Other than connecting the data pin of the LED's to an Arduino input and monitoring that input - I have no idea.

I cant see if anyone has done anything like this before.

I can see that many people have controlled 2811 / 2812 LED's etc. from an Arduino but I can't see if anyone has captured the LED data and undertaken manipulation.

Thanks again for your reply.

Hi @pixelmad !

There is a library on gitHub

https://github.com/PaulStoffregen/WS2812Capture

created by Paul Stoffregen to capture and analyze timing of WS2812 LED Data:

WS2812Capture is a Teensy 4.x library which can capture and analyze WS2812 LED data. Its primary purpose is to verify the correctness of timing used by other libraries which transmit data to WS2812 addressable LEDs.

It could be a start, but I do not know if capturing and controlling further strips can be done without interference.

Good luck!
ec2021

BTW: Are you really depending on a specific led controller? The technically easiest way would be to replace the controller by an Arduino or ESP32 board ...

Do you want the same thing to happen to all the strips ? it could be just having the data wire split to each strip and no analysis needed...

if you want to see it as a 30 led strip, you could have one data wire per strip (easier I suppose from the ceiling than having the return wire) and use code (library) that will group those into a virtual strip.

Adding a return wire from the bottom of the hanging strip to the top sounds like a much easier solution than trying to decode the incoming data

Are you planning on using LED strips or individual LEDs ?

More likely to be strips 2811 / 2812

Then it would be easy to run the wire ip the back of the strip. It will not be carrying much current so could be thin enameled wire

I hadn't thought of laquored wire to be honest.

Use the same wire for the power and data in and twist the 4 wires together for a decorative effect. Mind you, the lED strips them selves are going to look pretty ugly and not visible from all directions. have you considered using individual addressable LEDs ?

A return wire is by far the simplest solution and the return wire can and should be as thin as possible. The thinner the wire the less capacitance.

Capturing Ws281x signal is tricky and a fast processor is required, but i have seen (here on the forum) someone who wanted to simply 'ignore' or 'mute' the signal of the first bunch of LEDs so it could be the new starting point for the next strip. Unlike you there was no longer an option to add a return wire. He had a sketch which used a 328p and got the timer to switch line drivers.

Another option to look at is to create a controller yourself using an ESP32 and use a 16 channel parallel transmission either from the I2S peripheral or the LCD peripheral (S2 or S3 i think) with makuna Neopixelbus.

But a return wire is the option i would go for. It is for the data only so it's a single wire. I have used some AWG28 (or even AWG22) , but coated wire is also an option and even thinner. Usually the soldered surface of that is quiet smaal unless you burn and sand the coating off for a few mm, and multi-strand wire is less likely to break.

Consider using a frost filter, paper or fabric.

Yo, I get exactly what you’re trying to do, and honestly, you stumbled right into a massive hardware timing bottleneck. Don't sweat failing at the first hurdle—this shit is way harder than it looks on paper.

​Here is the brutal truth about why you're hitting a wall, and how you can actually make this work.

​The Problem: WS2812 Timing is Tight as Hell

​The 3-pin LED strips (WS2811/WS2812B) use a single-wire protocol running at 800kHz. The time windows for a high or low pulse to signify a 0 or a 1 are insanely fast, measured in nanoseconds.

​A standard 16MHz Arduino Uno or Nano just does not have the horsepower to sit there, accurately sample that incoming raw data stream, parse the bytes, strip out what it doesn't want, and then re-transmit it to other pins in real-time. It'll choke instantly because it spends all its clock cycles just trying to read the incoming pulses.

​How the Data Actually Works

​The way these strips work normally is pretty clever: the very first LED grabs the first 24 bits of data (8 bits each for Red, Green, and Blue) to color itself, and then it literally chops that data off and passes the remaining data stream down the line to the next LED.

​To do what you want—capturing the stream and routing specific chunks to different strips without daisy-chaining them—your intermediate Arduino has to act like a giant, super-fast digital siphon.

​How to Actually Fix This

​If you want to pull this off, you've got a couple of realistic paths depending on your setup:

  • Option 1: Upgrade your microcontroller (Use an ESP32 or RP2040) If you absolutely must sniff the raw WS2812 data wire from an external controller, throw the standard Arduino in the parts bin and grab something like a Raspberry Pi Pico (RP2040) or an ESP32. The RP2040 has a feature called PIO (Programmable I/O). This is a separate, dedicated hardware state machine that can handle the high-speed timing of decoding the WS2812 data stream in the background without burning up your main CPU cycles. Once the PIO dumps the data into an array, your main code can easily slice up that array and spit it out to different pins using standard libraries like FastLED.
  • Option 2: Change how the data is sent from the source If you have any control over the original controller, stop sending WS2812 data out of it. Instead, have that master controller send the pixel data via standard Serial (UART), SPI, or over Wi-Fi/Ethernet using a protocol like Art-Net / E1.31. Reading a raw serial packet or an SPI stream is a million times easier for an Arduino to handle. It can grab the whole packet, say "Okay, bytes 1-300 go to Pin 2, and bytes 301-600 go to Pin 3," and blast them out.
  • Option 3: Parallel output (If you are just mirroring) If you just want the exact same animation to play on multiple strips at the exact same time without running data wires from the end of one to the start of the next, you don't need code at all. Just split the single data output wire from your master controller and run it to the data-in of all your strips in parallel. Just make sure to use a level shifter (like a 74HCT125) if the wire runs are long, so your signal doesn't degrade.

​Bottom line: Trying to sniff a raw 1-wire addressable LED signal with a basic Arduino is a massive pain in the ass. Swap to a faster board with hardware peripherals that can handle the timing, or change the data protocol coming out of the master box.

​Good luck with the build, man. It's a tricky puzzle, but once you get the right hardware piece in place, it's totally doable. Maybe Even Uno Q . Mmmhhhmmm

Thank you for your help and guidance.

Yes... I think you are absolutely correct that this is going to be a pain to do and a wire is possibly the way forward.

In my naiveite, I thought that I could have 'simply' sent 6 LED's worth of data, thinking that's ((8 bits x 3 colour) x 6 LED's) worth of data to LED strip 1, then send the next 8x3x6 to the next LED strip and so on...
I guess almost demultiplexing the signal to a number of strips then repeating...

Ohh how I love addressable LED's

You did not answer about the use case.

here is a small code you can test in wokwi where I have 5 separate strips of 6 LEDs and using FastLed I aggregate them into a 30 leds virtual strip and then in the loop I just go through each pixel turning it red for 100ms.

click to see the code
#include <FastLED.h>

constexpr uint8_t NUM_STRIPS     = 5;
constexpr uint8_t LEDS_PER_STRIP = 6;
constexpr uint8_t TOTAL_LEDS     = NUM_STRIPS * LEDS_PER_STRIP;

CRGB leds[TOTAL_LEDS];

void setup() {
  FastLED.addLeds<NEOPIXEL, 8>(leds, 0,  LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 9>(leds, 6,  LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 10>(leds, 12, LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 11>(leds, 18, LEDS_PER_STRIP);
  FastLED.addLeds<NEOPIXEL, 12>(leds, 24, LEDS_PER_STRIP);

  FastLED.setBrightness(255);
}

void loop() {
  for (int i = 0; i < TOTAL_LEDS; i++) {
    leds[i] = CHSV(0, 255, 255);
    FastLED.show();
    delay(100);
    leds[i] = CRGB::Black;
  }
}

I mentioned before there was a topic creating 3 way splitters using a 328p. This was it.
I am not a big fan of the OP in this thread because basically he had posted previously with the same query several months before but then left out the schematic. The idea is obviously to just simply count the pulses using a hardware Counter and switching a bus driver on. (or off)

In theory one could expand on it, but seriously a small wire returning the Dout back to where Din is, is the way to go.

Electric rain.

This is an example of the timing you will be trapping (links to source)... not perfect, because this code sometimes skips an LED due to inexact timing...

sketch.s

;------------------------
; Assembly Code
;------------------------
#define __SFR_OFFSET 0x00
#include "avr/io.h"
;------------------------
.global WS2812B
;===============================================================
WS2812B:
;-------
    SBI   DDRD, 4     ;PD4 o/p (connected to DI of WS2812B)
;---------------------------------------------------------------
again:
    ;-----------------------------------------------------------
    RCALL clr_display ;turn OFF all pixels & reset to pixel 1
    ;-----------------------------------------------------------
    LDI   R18, 16       ; 16 x WS2812B
    MOV   R19, R18
a1: RCALL black       ; black
    DEC   R18
    BRNE  a1
    RCALL red         ; red
    RCALL delay_ms
    DEC   R19
    MOV   R18, R19
    CPI   R18, 0
    BRNE  a1
    RCALL red         ; red
    RCALL delay_ms
    ;-----------------------------------------------------------
    RCALL clr_display
    ;-----------------------------------------------------------
    LDI   R18, 0
    MOV   R19, R18
a2: RCALL black       ; black
    DEC   R18
    BRNE  a2
    RCALL blue        ; blue
    RCALL delay_ms
    INC   R19
    MOV   R18, R19
    CPI   R18, 15
    BRNE  a2
    ;-----------------------------------------------------------
    RJMP  again
;===============================================================
green:
;-----
    LDI   R17, 8      ;counter for 1st 8 bits
    ;----------------------------------------------------------
    ;logic 1 code
    ;----------------------------------------------------------
l0: SBI   PORTD, 4    ;0.80us high pulse
    NOP               ;NOP = 1 CLK cycle = 0.0625us
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    CBI   PORTD, 4    ;0.45us low pulse
    NOP
    NOP
    NOP
    NOP
    DEC   R17
    BRNE  l0
    ;----------------------------------------------------------
    LDI   R17, 16     ;counter for remaining 16 bits
    ;----------------------------------------------------------
    ;logic 0 code
    ;----------------------------------------------------------
l1: SBI   PORTD, 4    ;0.40us high pulse
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    CBI   PORTD, 4    ;0.85us low pulse
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    DEC   R17
    BRNE  l1
    RET
;===============================================================
red:
;---
    LDI   R17, 8      ;counter for 8 bits
    ;----------------------------------------------------------
    ;logic 0 code
    ;----------------------------------------------------------
l2: SBI   PORTD, 4    ;0.40us high pulse
    NOP               ;NOP = 1 CLK cycle = 0.0625us
    NOP
    NOP
    NOP
    NOP
    NOP
    CBI   PORTD, 4    ;0.85us low pulse
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    DEC   R17
    BRNE  l2
    ;----------------------------------------------------------
    LDI   R17, 8      ;counter for 8 bits
    ;----------------------------------------------------------
    ;logic 1 code
    ;----------------------------------------------------------
l3: SBI   PORTD, 4    ;0.80us high pulse
    NOP               ;NOP = 1 CLK cycle = 0.0625us
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    CBI   PORTD, 4    ;0.45us low pulse
    NOP
    NOP
    NOP
    NOP
    DEC   R17
    BRNE  l3
    ;----------------------------------------------------------
    LDI   R17, 8      ;counter for 8 bits
    ;----------------------------------------------------------
    ;logic 0 code
    ;----------------------------------------------------------
l4: SBI   PORTD, 4    ;0.40us high pulse
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    CBI   PORTD, 4    ;0.85us low pulse
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    DEC   R17
    BRNE  l4
    RET
;===============================================================
blue:
;----
    LDI   R17, 16     ;counter for 16 bits
    ;----------------------------------------------------------
    ;logic 0 code
    ;------------
l5: SBI   PORTD, 4    ;0.40us high pulse
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    CBI   PORTD, 4    ;0.85us low pulse
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    DEC   R17
    BRNE  l5
    ;----------------------------------------------------------
    LDI   R17, 8      ;counter for 8 bits
    ;----------------------------------------------------------
    ;logic 1 code
    ;----------------------------------------------------------
l6: SBI   PORTD, 4    ;0.80us high pulse
    NOP               ;NOP = 1 CLK cycle = 0.0625us
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    CBI   PORTD, 4    ;0.45us low pulse
    NOP
    NOP
    NOP
    NOP
    DEC   R17
    BRNE  l6
    RET
;===============================================================
black:
;-----
    LDI   R17, 24     ;counter for 24 bits
    ;----------------------------------------------------------
    ;logic 0 code
    ;------------
l7: SBI   PORTD, 4    ;0.40us high pulse
    NOP               ;NOP = 1 CLK cycle = 0.0625us
    NOP
    NOP
    NOP
    NOP
    NOP
    CBI   PORTD, 4    ;0.85us low pulse
    NOP               ;we need 14 CLK cycle
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP               ;12 NOP = 12 CLK cycles
    DEC   R17         ;1 CLK cycle
    BRNE  l7          ;1 CLK cycle
    RET
;===============================================================
white:
;-----
    LDI   R17, 24     ;counter for 24 bits
    ;----------------------------------------------------------
    ;logic 1 code
    ;------------
l8: SBI   PORTD, 4    ;0.80us high pulse
    NOP               ;NOP = 1 CLK cycle = 0.0625us
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    NOP
    CBI   PORTD, 4    ;0.45us low pulse
    NOP
    NOP
    NOP
    NOP
    DEC   R17
    BRNE  l8
    RET
;===============================================================
clr_display:
;-----------
    LDI   R18, 16 ; number of pixels
agn:RCALL black
    DEC   R18
    BRNE  agn
    RCALL delay_ms
    RET
;===============================================================
delay_ms:
     LDI   R21, 255
ll0: LDI   R22, 255
ll1: LDI   R23, 40
ll2: DEC   R23
     BRNE  ll2
     DEC   R22
     BRNE  ll1
     DEC   R21
     BRNE  ll0
     RET

sketch.ino

// Anas Kuzechie

// Assembly via Arduino - Programming WS2812B @ 8m01s
// https://www.youtube.com/watch?v=GA3VnWGf4S4
// https://akuzechie.blogspot.com/2022/01/assembly-via-arduino-programming-ws2812b.html

// Creating Arduino Library for WS2812 - Code 2
// https://www.youtube.com/watch?v=QXlNZXMwl30


//--------------------------------------------
// Assembly via Arduino - Programming WS2812B
//--------------------------------------------
extern "C" {
  void WS2812B();
}

void setup() {
  WS2812B();
}

void loop(){}

It looks like 3 bytes x 6 LEDS = 18 bytes per strip

For 6 strips (from the demux picture) it is 108 bytes, something which even simple Arduino can temporary store .

Also as simple Arduinos are able generate the signal using bit banging and NOPs for timeing, I think, that it should be possible to just wait for the start of the signal, capture the whole 108 bytes (or how much), and then simply process that and send it out as 6x 18bytes on 6 different pins, for each of 6 different LED strings.

This would requre, that new collor setting would not come sooner, that the length of previous collor setting. But it is probabelly fullfiled by the logic of color changes anyway.


Alternatively you may use some DECODERS/DEMULTIPLEXERS like 74HC137 / 74HC138 / 74HC154 and similar to demultiplex the incoming signal to 8 / 16 strips, where Arduino will just counts packets and separate the strips by setting the right strip address to the 74HC chip

(it can be simply improved for more strips by using more such chips)