OctoWS2811 Adaptor issues (Teensy 4.1 & WS2812B)

Hello,

For starters, I'm quite the newb. I have gotten an Arduino Uno R3 to run TouchDesigner on my 6 strips (after 6feet of 3-pin cable run) up to 40 pixels across all strips. But when I tried to bump it across the whole strip (140 pixels), I naturally ran into issues.

That's when I heard about the Teensy/Octo combo.

Atm, I'm trying to get some test code working to just get a Teensy 4.1 running my 5V LED strip/s (WS2812B, GRB) through an OctoWS2811 adaptor.
Here is the test code. I've provided a picture of my connections. I've gotten FastLED library-based test to work with the Teensy 4.1 pin 2 direct to the strip's DIN.

My current issue, is that when I try to use the OctoWS2811 adaptor as a logic level shifter, I do not receive red. The lights are all white.

I've tried every color order and even every color order at 400kHz.

When I'd move the OCto's power or ground connection, there'd be a shift in the all white lights (like a glitch), sometimes resulting ni more lights lighting up.

I tired soldering the connections to see if I get a better feed, but still the same issue: all white.

I believe I'll need the OctoWS2811 adaptor to run 140 pixels per strip at 6 strips with anywhere from 4-6ft worth of cable between the start of each strip and the power.

I am running a 5V 60amp separate PSU with the GND of the LEDs, Octo, and Teensy connected to its GND rail. And then I have the %V power from Octo and the LED strip connected to the 5V rail of the PSU.

The Teensy 4.1 derives its power via USB.

Any help would be greatly appreciated. 'm at my whit's end with this having switched from the uno r3. I've tried two different Ocot adaptors and really just have no clue how to make this work (especially after soldering).

Thanks for any help or consideration and if you need any other materials to help understand, let me know in the comments and I can upload.

Cheers.


Code:
#include <OctoWS2811.h>

#define NUM_LEDS_PER_STRIP 10
#define NUM_STRIPS 1

DMAMEM int displayMemory[NUM_LEDS_PER_STRIP * NUM_STRIPS * 3 / 4];
int drawingMemory[NUM_LEDS_PER_STRIP * NUM_STRIPS * 3 / 4];

const int config = WS2811_GRB | WS2811_800kHz;

OctoWS2811 leds(NUM_LEDS_PER_STRIP, displayMemory, drawingMemory, config);

void setup() {
  leds.begin();
  leds.show();

  for (int i = 0; i < NUM_LEDS_PER_STRIP; i++) {
    leds.setPixel(i, 255, 0, 0);  // RED only
  }

  leds.show();
}

void loop() {
  // Nothing to update
}


Here is the FastLED code when I tried connecting the Teensy 4.1 pin 2 directly to the LED DIN, bypassing the Octo (successfully turning red):

Code:
#define FASTLED_ALLOW_INTERRUPTS 0
#include <FastLED.h>

#define NUM_LEDS 10
#define DATA_PIN 2  // Direct pin from Teensy to strip DIN

CRGB leds[NUM_LEDS];

void setup() {
  delay(500);  // Let power settle
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(100);

  // Fill red
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CRGB(255, 0, 0);  // Red
  }

  FastLED.show();
}

void loop() {
  // Static display
}


*Using Teensyduino.
*Had to mount Octo and Teensy 4.1 to breadboard separately - all double insulator pins for Teensy 4.1-to-Octo soldout.
 

Attachments

  • Untitled - May 6, 2025 22.25.jpg
    Untitled - May 6, 2025 22.25.jpg
    262.1 KB · Views: 179
  • IMG_0127.jpg
    IMG_0127.jpg
    276.4 KB · Views: 148
If you're interested in where I'm headed and what's worked so far, Here is the the main/ultimate project's code that I used successfully with the Arduino Uno R3, which receives via Serial from TouchDesigner. I switched to the Teensy 4.1/Octo to expand to light up 6 8ft (140 pixel) LED "tubes":

Code:
#include <OctoWS2811.h>

// --- Configuration ---
#define NUM_LEDS_PER_STRIP 40
#define NUM_STRIPS 1  // Currently using only 1 strip on Pin 2
const int totalLEDs = NUM_LEDS_PER_STRIP * NUM_STRIPS;
const int numOfBytes = totalLEDs * 3;  // 3 bytes per LED (R, G, B)
  
// --- Memory for OctoWS2811 (must be exact) ---
DMAMEM int displayMemory[totalLEDs / 2];
int drawingMemory[totalLEDs / 2];

// --- OctoWS2811 LED Controller ---
OctoWS2811 leds(NUM_LEDS_PER_STRIP, displayMemory, drawingMemory, WS2811_GRB | WS2811_800kHz);

// --- Serial Data Buffer ---
char inputBuffer[numOfBytes];

void setup() {
  Serial.begin(2000000);          // Match this in TouchDesigner
  Serial.setTimeout(50);          // Prevents waiting forever for incomplete frames
  leds.begin();
  leds.show();                    // Clear all LEDs
  Serial.println("Teensy Ready for RGB Data");
}

void loop() {
  // Wait until a full frame (420 bytes) is available
  if (Serial.available() >= numOfBytes) {
    Serial.readBytes(inputBuffer, numOfBytes);  // Read full RGB frame

    for (int i = 0; i < totalLEDs; i++) {
      int r = inputBuffer[i * 3 + 0];
      int g = inputBuffer[i * 3 + 1];
      int b = inputBuffer[i * 3 + 2];
      leds.setPixel(i, leds.color(r, g, b));    // Assign RGB color to LED
    }

    leds.show();  // Push new frame to LEDs
  }
}
 
It is possible that there is something wrong with the configuration of the Octo library - although I don't see anything.
10 Leds? Your strip looks longer
btw the color order just changes the order ... even with the wrong order you would get the single colors - just a different one.

Or: there is something with the connections.
Note that the OctoWS2811 Adaptor is just a level shifter. You don't necessarily need the OctoWS2811 LED Library to use it (it has several advantages). You could check the working FastLED code with the adaptor to make sure the cable connections are all working. Then you could look into the other code with the Octo library.
 
#define NUM_STRIPS 1 // Currently using only 1 strip on Pin 2

By default OctoWS2811 will output 8 parallel data streams. On Teensy 4.x you can change this with the pinlist feature. See the pinlist example by clicking File > Examples > OctoWS2811. On older Teensy 3.x the 8 pins are fixed. Pinlist only works on Teensy 4.x.

Defining "NUM_STRIPS" in your code has no effect on the behavior of OctoWS2811. It is simply a define that only applys to use of that specific word, only in your code. OctoWS2811 will still use 8 pins.

There are 2 important things you must know.

1: The buffer memory must be large enough for all the pins OctoWS2811 uses, even if you do not connect LEDs to those pins.

2: OctoWS2811 will take control of all 8 pins. If you try to use any of the other 7 pins, for something like HardwareSerial, expect conflicts because OctoWS2811 is also trying to use those pins.
 
It is possible that there is something wrong with the configuration of the Octo library - although I don't see anything.
10 Leds? Your strip looks longer
btw the color order just changes the order ... even with the wrong order you would get the single colors - just a different one.

Or: there is something with the connections.
Note that the OctoWS2811 Adaptor is just a level shifter. You don't necessarily need the OctoWS2811 LED Library to use it (it has several advantages). You could check the working FastLED code with the adaptor to make sure the cable connections are all working. Then you could look into the other code with the Octo library.
Thanks for the clarification on the color order. And as far as the 10 LEDs, it was just to do a low-power test and then amp up once I could see even 10 were working correctly. (it's a 140 pixel strip in an 8' polycarbonate tube, 6 tubes/strips total)

I appreciate the details on Octo and the FastLED heads up! I've had some great experiences with FastLED in other approaches. Though, I was in fact able to get this working with the Octo library beautifully! I'll post the setup and code below. There was a combination of not-so-great hard-connections, some python that was trying to do too much in a TouchDesigner execute and a few other things. But here is where I landed with the Teensy code:

Code:
#include <OctoWS2811.h>

// --- Configuration ---
const int ledsPerStrip = 140;
const int numOfStrips = 6;  // unused by OctoWS2811 library, merely for own reference
const int totalLEDs = ledsPerStrip * numOfStrips;
const int numOfBytes = totalLEDs * 3;  // 3 bytes per LED (R, G, B)

// --- Memory for OctoWS2811 (must be exact) ---
DMAMEM int displayMemory[totalLEDs];
int drawingMemory[totalLEDs];

const int config = WS2811_GRB | WS2811_800kHz;

// --- OctoWS2811 LED Controller ---
OctoWS2811 leds(ledsPerStrip, displayMemory, drawingMemory, config);

// --- Serial Data Buffer ---
char inputBuffer[numOfBytes];

void setup() {
  Serial.begin(2000000);          // Match this in TouchDesigner
  Serial.setTimeout(50);          // Prevents waiting forever for incomplete frames
  leds.begin();
  leds.show();                    // Clear all LEDs
  Serial.println("Teensy Ready for RGB Data");
}

void loop() {
  // Wait until a full RGB frame is available
  if (Serial.available() >= numOfBytes) {
    Serial.println("📥 Frame arriving...");
    Serial.readBytes(inputBuffer, numOfBytes);  // Read full RGB frame

    for (int i = 0; i < totalLEDs; i++) {
      int r = inputBuffer[i * 3 + 0];
      int g = inputBuffer[i * 3 + 1];
      int b = inputBuffer[i * 3 + 2];
      leds.setPixel(i, leds.color(r, g, b));    // Assign RGB color to LED
    }

    leds.show();  // Push new frame to LEDs
  }
}
 
By default OctoWS2811 will output 8 parallel data streams. On Teensy 4.x you can change this with the pinlist feature. See the pinlist example by clicking File > Examples > OctoWS2811. On older Teensy 3.x the 8 pins are fixed. Pinlist only works on Teensy 4.x.

Defining "NUM_STRIPS" in your code has no effect on the behavior of OctoWS2811. It is simply a define that only applys to use of that specific word, only in your code. OctoWS2811 will still use 8 pins.

There are 2 important things you must know.

1: The buffer memory must be large enough for all the pins OctoWS2811 uses, even if you do not connect LEDs to those pins.

2: OctoWS2811 will take control of all 8 pins. If you try to use any of the other 7 pins, for something like HardwareSerial, expect conflicts because OctoWS2811 is also trying to use those pins.
Hey Paul!

SUPER helpful! I didn't realize the Octo would process those last two regardless (good to know as I already have the 7th and 8th strips accounted for, just not in my core setup). That it is already assuming them with its current speed/baud rate is a nice green light.

I see/hear what you mean about the number of strips as well! I get why I was seeing so many calculate the strips as one whole string. I've left it in for now as just a note to myself of how many there are and a way of "defining" (for myself/other eyes) where the total LEDs are coming from.

That all being said, I have officially got it working! So many of your posts/forum engagement helped so much with me narrowing in on this. I appreciate all the guidance! Here's where I landed with the code.

Code:
#include <OctoWS2811.h>

// --- Configuration ---
const int ledsPerStrip = 140;
const int numOfStrips = 6;
const int totalLEDs = ledsPerStrip * numOfStrips;
const int numOfBytes = totalLEDs * 3;  // 3 bytes per LED (R, G, B)

// --- Memory for OctoWS2811 (must be exact) ---
DMAMEM int displayMemory[totalLEDs];
int drawingMemory[totalLEDs];

const int config = WS2811_GRB | WS2811_800kHz;

// --- OctoWS2811 LED Controller ---
OctoWS2811 leds(ledsPerStrip, displayMemory, drawingMemory, config);

// --- Serial Data Buffer ---
char inputBuffer[numOfBytes];

void setup() {
  Serial.begin(2000000);          // Match this in TouchDesigner
  Serial.setTimeout(50);          // Prevents waiting forever for incomplete frames
  leds.begin();
  leds.show();                    // Clear all LEDs
  Serial.println("Teensy Ready for RGB Data");
}

void loop() {
  // Wait until a full RGB frame is available
  if (Serial.available() >= numOfBytes) {
    Serial.println("📥 Frame arriving...");
    Serial.readBytes(inputBuffer, numOfBytes);  // Read full RGB frame

    for (int i = 0; i < totalLEDs; i++) {
      int r = inputBuffer[i * 3 + 0];
      int g = inputBuffer[i * 3 + 1];
      int b = inputBuffer[i * 3 + 2];
      leds.setPixel(i, leds.color(r, g, b));    // Assign RGB color to LED
    }

    leds.show();  // Push new frame to LEDs
  }
}

Some of the Teensy/Octo-related issues I was having were:
- the octo is separate on the breadboard and at times there just wasn't a great connection on my part
- sending r0,g0,b0,r1, etc. instead of r,g,b, etc. on the TouchDesigner end.
- (I'll leave this here for any Touch users) updates to DAT execute, python below:
Code:
def frameEnd(frame):
# --- Initialize counters safely using DAT storage ---
    me.store('frame_counter', me.fetch('frame_counter', 0))
    me.store('debug_counter', me.fetch('debug_counter', 0))

    # --- Throttle rate from CHOP ---
    rate = 400
    rate_op = op('/project1/throttleCTRL/throttleNull')
    rate = int(rate_op['rate']) if rate_op and 'rate' in rate_op.channels else 4  # fallback to 4

    frame_counter = me.fetch('frame_counter')
    if frame_counter != 0:
        me.store('frame_counter', (frame_counter + 1) % rate)
        return
    me.store('frame_counter', 1)

    if frame_counter != 0:
        me.store('frame_counter', (frame_counter + 1) % rate)
        return
    me.store('frame_counter', 1)

    # --- Get RGB source CHOP ---
    c = op('/project1/merge1')
    if not c or c.numSamples != 840:
        return
        
    print("✅ merge1 length:", c.numSamples)

    available_channels = [ch.name for ch in c.chans()]
    rgb_bytes = bytearray()

    for i in range(c.numSamples):
        try:
            r = int(max(0, min(c['r'][i] * 255, 255))) if 'r' in available_channels else 0
            g = int(max(0, min(c['g'][i] * 255, 255))) if 'g' in available_channels else 0
            b = int(max(0, min(c['b'][i] * 255, 255))) if 'b' in available_channels else 0
        except Exception as e:
            print(f"frameEnd index error at i={i}: {e}")
            r, g, b = 0, 0, 0

        rgb_bytes.extend([r, g, b])

    # --- Send to Teensy via Serial DAT ---
    s = op('/project1/serialConnector')  # or whatever its full path is

    if s:
        print(f"Sample[0] r:{r} g:{g} b:{b}")  #testing testing

        s.sendBytes(rgb_bytes)

        # --- Print only every 10th send ---
        debug_counter = me.fetch('debug_counter')
        me.store('debug_counter', debug_counter + 1)
        if debug_counter % 10 == 0:
            print("✅ Sent throttled RGB frame")

    return


If anyone would like more details on the TouchDesigner project itself and the Ableton implementation, let me know! Also, open to any other Teensy code tips or ideas. Cheers!

And thanks again for the direct and indirect help, Paul!
 

Attachments

  • Screenshot 2025-05-12 at 11.12.34 PM.png
    Screenshot 2025-05-12 at 11.12.34 PM.png
    128.2 KB · Views: 37
  • Screenshot 2025-05-13 at 5.05.38 PM.png
    Screenshot 2025-05-13 at 5.05.38 PM.png
    942 KB · Views: 39
  • Screenshot 2025-05-13 at 5.07.26 PM.png
    Screenshot 2025-05-13 at 5.07.26 PM.png
    1.2 MB · Views: 44
  • Screenshot 2025-05-13 at 5.17.46 PM.png
    Screenshot 2025-05-13 at 5.17.46 PM.png
    1.3 MB · Views: 35
Back
Top