Teensy 4.0, OctoWS2811 adapter and FastLED problems

chris.nz

Well-known member
I have a project that is using (among other things) a Teensy 3.2, OctoWS2811 adapter and FastLED, with 8 channels of WS2812B strips. Each strip is between 72 and 86 LEDs long, 630 LEDs in total. All this is working fine.

I want to swap out the Teensy 3.2 for a Teensy 4.0, keeping the rest of the hardware the same. When I do this however, three of the LED channels (2, 5 and 6) no longer get updated, and they change to random colours or bright white when the Teensy is rebooted or power cycled. The other five channels work as expected. Interestingly, if I swap the two RJ45 connectors around, it is STILL channels 2, 5 and 6 that don't work correctly. If I swap the Teensy 3.2 back in (painful, since soldering/desoldering is required on some of the bottom pads!), everything works fine again.

I've seen the recent FastLED fixes for OctoWS2811 and WS2812B LEDs, but they don't seem to make any difference. Regardless of whether I use FastLED 3.4.0, FastLED 3.5.0 or the latest from https://github.com/PaulStoffregen/FastLED, the behaviour is the same. Additionally, I am using the latest code from https://github.com/PaulStoffregen/OctoWS2811, and Teensyduino 1.56. For what it's worth I use PlatformIO for this project, with the following in platformio.ini:

Code:
[env:myproject]
platform = https://github.com/platformio/platform-teensy.git
board = teensy40
framework = arduino
lib_deps =
  ;fastled/FastLED @ ^3.5.0
  https://github.com/PaulStoffregen/FastLED
  https://github.com/PaulStoffregen/OctoWS2811

On a hunch, I tried removing some audio/FFT code from my codebase (which sadly doesn't work well for me on the Teensy 4.0 anyway - see my question at https://forum.pjrc.com/threads/60618-FFT-on-Teesy-4?p=297070&viewfull=1#post297070). Once I removed this line:
Code:
AudioOutputI2S i2s1;
all 8 LED channels started working again... for the most part at least. I did still have a different channel combination (4 and 6) fail in one instance even with my simplified code.

I don't really know where to go from here. My guess is that there's some sort of timing or interrupt problem with the OctoWS2811/FastLED (and possibly audio) combination. Do you have any suggestions for what I can try?
 
Sorry to bump this but I don't yet have a solution and am running out of ideas. I've tried simplifying my code right down and the problem still persists:

Code:
#define USE_OCTOWS2811
#include<OctoWS2811.h>
#include "Arduino.h"
#define FASTLED_INTERNAL
#include <FastLED.h>
#include <Audio.h>

// Uncommenting this line causes several channels of LEDs to stop working
// AudioOutputI2S i2s1;

constexpr uint16_t LEDS_PER_STRIP{86};
CRGB leds[8 * LEDS_PER_STRIP]{};

void setup() {
  AudioMemory(30);
  CFastLED::addLeds<OCTOWS2811>(leds, LEDS_PER_STRIP);
}

void loop() {
  static int i{0};
  leds[i] = CRGB::Red;
  FastLED.show();
  leds[i] = CRGB::Black;
  i = (i + 1) % (8 * LEDS_PER_STRIP);
  delay(20);
}

With the AudioOutputI2S line commented out, all eight LED strips work as expected. Uncommenting that line however results in several of the LED strips not working (and sometimes displaying a handful of random or white LEDs).

Is it simply that the Audio library isn't ready for primetime on the Teensy 4.0 and I should look for a different library for audio FFT, or might I be doing something wrong?
 
Had hoped that someone who fully knows what they're talking abut would weigh in, but ... apparently not... I'm pretty familiar with the Audio library, not so much (at all) with OctoWS2811. However.

As far as I can see, the Teensy 3.x variant of OctoWS2811 is pure DMA, so very hard to break. But the Teensy 4.x reverts to a mixed DMA/interrupt implementation. On the face of it this should still work, so long as the OctoWS2811 interrupt priority can pre-empt a running Audio library interrupt, which is set at a very low level (208, high numbers are low priority) because a heavily-loaded audio system can spend a large proportion of CPU time in the service routine.

Looking at OctoWS2811_imxrt.cpp, I can't see any attempt to set a specific interrupt priority, and DMAChannel.h doesn't give any clues as to the default used when not specified.

What I can see (line 38 in the version I have, Teensyduino 1.56) is the definition #define BYTES_PER_DMA 40. So far as I can tell this is not what it says, the number of bytes transferred between DMA interrupts - it's actually the bytes per channel. At 10us per byte, the default interrupt rate is thus going to be every 0.4ms, and your strips of 86 RGB LEDs will need 258 bytes, 2.58ms to transfer, and 7 OctoWS2811 interrupts. Audio interrupts occur every 2.9ms, but unfortunately the standard library doesn't provide a hook to synchronise with this. If you're using FFT, though, available() will tell you it has run (though not every time, I think) - when it ran is still a matter for conjecture...

So, you might want to try changing BYTES_PER_DMA to 129, which will allocate a whopping 33024 bytes to the OctoWS2811 bitdata[] buffer, but also mean that the interrupt never really does anything. (The first transfer set up when you call show() is a double-length one, so you don't need to set BYTES_PER_DMA to 258). You could also try seeing what happens if you synchronise OctoWS2811::show() with the audio interrupt - it might at least make the display corruption appear at a predictable point along the LED strings.

(Parenthetically, a choice of 40 bytes per DMA is a bit weird, especially as it's not configurable at run time. At least a multiple of 12 would mean that RGB or RGBW LEDs would get a complete transfer per interrupt...)
 
OctoWS2811 defaults to use of pins 2, 14, 7, 8, 6, 20, 21, 5 on all Teensy models.

AudioOutputI2S uses pins 21, 23, 7, 20 on Teensy 4.0.

screenshot1.png

screenshot2.png

Hopefully a deep dive into DMA & interrupts isn't needed to see the conflict here?
 
On Teensy 4.x, OctoWS2811 supports use of a custom pin list. See the Teensy4_PinList example for details. Unfortunately, (so far) FastLED's API doesn't provide any way to specify the pin list, or use any number other than 8 channels.

Probably the simplest way to work around this problem immediately would be the edit the default pin list. You can find it in OctoWS2811_imxrt.cpp on line 40.

As a very first step to getting I2S1 and OctoWS2811+FastLED to work together, edit that line so OctoWS2811 isn't using any of the I2S digital audio pins. For compatibility with FastLED, you probably need to keep it at 8 pins.

Ideally, the FastLED code to use OctoWS2811 should someday be updated to allow any number of pins and specify the pin list. But it may be quite a while until I or anyone else gets around to doing that. Maybe someone who enjoys complicated C++ template syntax (eg, not me) will take this on?
 
Hopefully a deep dive into DMA & interrupts isn't needed to see the conflict here?
Quite so :D. Funny how one always assumes the OP will have properly checked the Really Obvious Possibilities before posting.... But then again, I've been know to fall down a similar hole, e.g. "did you make sure you're using the latest library?" :rolleyes:.
 
Thank you h4yn0nnym0u5e for taking the time to look at this in depth, and thank you PaulStoffregen for providing a clear explanation for what I'm seeing. I apologise for not having spotted the "really obvious". I'm just a hobbyist trying to figure this all out as best I can, and clearly I made a couple of bad assumptions here. For what it's worth I arrived at this point as follows:


1) Before buying the Teensy 4.0 I had asked here whether I'd hit any compatibility problems, and the response I received implied it would be fine as long as I used the latest FastLED library. I had listed all the pins and sensor modules I was using, but unfortunately hadn't mentioned the Audio/FFT dependency so issues around that weren't picked up on initially.

2) When I then swapped my Teensy 3.2 for a Teensy 4.0, I noticed a problem with the audio FFT code not working. I found something of a solution in this post, however it never occurred to me that code would introduce extra (and conflicting!) pin dependencies. Up until today I had been assuming the audio code only cared about my microphone pin (as was the case on the Teensy 3.2). In hindsight it's something I should have picked up on, but I'd also assumed the OctoWS2811 adapter and Audio library would play together nicely, given they're both from the same company.

3) Because I was making other changes at the time (including physical/wiring ones), it was a while before I noticed the problem with some LED strips not working, and it wasn't clear to me what triggered it other than knowing it was related to the introduction of the Teensy 4.0. It was only very recently I've had time to investigate further and narrowed it down to AudioOutputI2S. Maybe with a bit more digging I'd have realised that extra pins had come in to play via AudioOutputI2S, but it definitely wasn't jumping out at me.


Anyway, thank you again for identifying the problem! As for the solution, I appreciate the Teensy 4 with the OctoWS2811 library can drive LEDs on arbitrary pins, but isn't the OctoWS2811 adaptor hard-wired to use pins 2, 14, 7, 8, 6, 20, 21, 5, so the changes you suggest won't help? I realise the physical adaptor is somewhat redundant on the Teensy 4 but I really want to avoid removing it because it's an integral part of my project's assembly/structure, plus I'd have to replace/rework the RJ45 cables and so on.

As I understand it, another solution would be to get rid of the AudioOutputI2S dependency completely, or maybe change the pins it depends on from 21,7,20 to e.g. 3,16,4? I guess I should also see if I can find a different FFT library that works on the Teensy 4 without needing AudioOutputI2S since that might be the easiest solution for now.
 
but isn't the OctoWS2811 adaptor hard-wired to use pins 2, 14, 7, 8, 6, 20, 21, 5, so the changes you suggest won't help?

Yes, the Octo28 adaptor uses those original 8 pins. If you pair it together with the audio shield (rev D) or any other use of I2S1, you'll get 3 pins conflicting.

Ideally we would make another LED adaptor board specifically for Teensy 4, much like how we have 2 different versions of the audio shield. But so far that hasn't happened. Launching any new boards during the pandemic just hasn't been practical. Teensy 4.0 was released in late 2019 and Teensy 4.1 released in May 2020, just a couple months into the pandemic, with the Rev D audio shield somewhere between those 2. PJRC hasn't been able to release anything new since (other than Lockable Teensy), and just keeping Teensy in stock with the chip shortages has taken all our resources.

So yeah, it's a less than ideal situation that we have 2 add-on boards which had no pin overlap when used with Teensy 3.x, but now do conflict on 3 pins when used together, even though either works well when used on Teensy 4.x without the other.

For now, while less than ideal, the only solutions are to use only 5 of the 8 outputs, or awkwardly solder 3 wires to connect to other pins if you really want all 8. Or you could make a different adaptor PCB of your own, which probably isn't worthwhile for a one-off projects, but maybe a good plan if you're building a large number of identical products.
 
Understood, and my sympathies go out to you for the challenges the pandemic has thrown your way.

Given I'm not using the audio shield (I just need FFT on a MAX9814 microphone) I think my best option is to find another FFT library that I can use instead of this one. It'll likely mean having to rework some of my code, but I'd much prefer that over rewiring the LED strips. Most of them are enclosed and the ends are hotglued so it won't be easy to rewire them appropriately.

Thanks again for pointing out the problem, I now feel like there's a good bit of light at the end of the tunnel :)
 
If anyone else is in a similar situation, hopefully it's helpful to know that I've managed to get things working by doing the following:

  • Replacing the Audio library with the ADC library (pedvide/Teensy_ADC)
  • Setting up two DMA buffers that are populated from the microphone (via ADC) at 44.1kHz
  • Then periodically/as needed:
    • copying the appropriate buffer and converting it to real+imaginary values (where imaginary values = 0)
    • converting the real values to Q15 fixed point (including scaling and compensating for the DC offset of the MAX9812)
    • applying a Hanning window function
    • calling arm_cfft_q15()
That gives me FFT output that is nearly a drop-in replacement for the Audio FFT output. For my purposes I don't care if I skip some data since I only want the frequencies for a fairly low resolution spectrum analyzer and other audio-driven LED effects anyway. Each buffer copy/convert/FFT takes around 120us, which is plenty fast enough for what I need.
 
Last edited:
Thanks bamos, I hadn't seen that thread and there's some interesting info in there. I was aware I could try to change the physical pins as a workaround but I've been trying to avoid any hardware changes. I'm happy now that I have a software solution, and in some ways it's more flexible than what I had previously when using the Audio library.
 
Back
Top