adafruit / Adafruit_NeoPixel

Arduino library for controlling single-wire LED pixels (NeoPixel, WS2812, etc.)
GNU Lesser General Public License v3.0
3.08k stars 1.27k forks source link

ESP32: timing error every 1ms corrupts LED pixel data #139

Closed MustBeArt closed 3 years ago

MustBeArt commented 7 years ago

On the ESP32, an extra delay of about 5 microseconds is inserted into the signal output every millisecond. This is far outside the Neopixel timing tolerance of 150 ns, so it leads to incorrect values in some of the pixels.

The data for up to 33 RGB pixels can be transmitted in less than one millisecond (1.25 uS per bit X 24 bits per pixel X 33 pixels = 990 uS). In the common case where the show() method call immediately follows a delay() function call, the problem is not encountered unless the strand has at least 34 RGB pixels. However, if the code keeps time some other way (such as by calling millis() repeatedly) the extra delay may occur with random alignment to the signal. In that case, the signal may be corrupted with even a single pixel strand.

The extra delay may also be avoided by disabling interrupts during the call to the show() method. In the ESP32 Arduino environment, this cannot be done in the usual way, by calling noInterrupts() -- that function is defined to be nothing in Arduino.h. It can, however, be accomplished by invoking the macro portDISABLE_INTERRUPTS() defined by FreeRTOS in the file portmacro.h, since the Arduino code in the ESP32 is actually running on top of FreeRTOS. The macro portENABLE_INTERRUPTS() re-enables interrupts. Of course, if this method is used, any other time-critical operations will suffer interference, probably including millisecond timekeeping.

Method to Reproduce

The best way is to observe the output signal on a logic analyzer. Use any sketch that talks to pixels, and set the number of pixels to at least 34. In each burst of activity on the signal line representing a single strand update, you should see at least one pulse (high or low) of between 5 and 6 uS, and those pulses should occur every millisecond thereafter (when the millisecond coincides with signal activity).

If no logic analyzer is available, you can see the problem with just a strand of pixels. If a strand of more than 33 pixels is available, simply hook it up and try to run any of the example sketches. Flaky pixels will be visible.

If no long strand of pixels is available, the following sketch will illustrate the problem with whatever length strand you do have. Because the strand is short, the extra pulse will only occur some fraction of the time, so you may have to watch for a while to see flaky pixels. (This is how I originally discovered the problem. I use this method of timekeeping when I need to process more than one time-critical device.)

#include <Adafruit_NeoPixel.h>

unsigned long tickTime = millis();

#define PIN 17
#define FRAME_INTERVAL 50

// Parameters set for the 24-Neopixel ring of RGB (no white) pixels
#define NUM_LEDS 24
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
}

void loop() {
  if(millis() >= tickTime)
  {
    colorWipeUpdate(strip.Color(0, 255, 0));
    tickTime = millis() + FRAME_INTERVAL;
//    delay(1);                     // one partial workaround
//    portDISABLE_INTERRUPTS();     // the other workaround
    strip.show();
//    portENABLE_INTERRUPTS();
  }

}

// One frame update of the usual colorWipe demo function
void colorWipeUpdate(uint32_t c) {
  static uint16_t numLitPixels = 0;

  strip.setPixelColor(numLitPixels, c);
  numLitPixels++;
  if(numLitPixels >= strip.numPixels())
    numLitPixels = 0;
}

Workarounds

If your sketch is written so that every call to the show() method always follows immediately after a call to delay(), you can use up to 33 pixels in a strand without a problem. (Note: delay(0) doesn't work, it has to be at least delay(1).) The provided example sketches work this way.

If your sketch keeps time some other way, you can add a delay(1) call immediately before each call to the show() method, and use up to 33 pixels in a strand without a problem.

If you must have strands of 34 or more pixels, your only choice is to disable interrupts as described above and suffer the consequences.

Suggested Solution

The problem is pretty fundamental. The only way to prevent the extra delay appears to be to disable interrupts, and disabling interrupts for more than a millisecond (much more for long strands) is hardly a good answer.

See http://www.insentricity.com/a.cl/268/controlling-ws2812-rgb-leds-from-the-esp32 for an alternative driver design that should avoid this difficulty, if it can be ported to the Arduino environment.

MustBeArt commented 7 years ago

Thanks to @abraxas3d for working with me to find this issue.

antonio-fiol commented 7 years ago

If the strip is of type NEO_GRBW, the maximum length is even shorter, and matching your math perfectly: 24 LEDs work, but (with the delay(1) workaround in place) LED#25 (n=24 starting from 0) of a 30-pixel strip shows a red offset when the brightness is low. It seems like one bit is forced to 1 due to the timing issue. I confirm that disabling the interrupts also works for me, but agree that interrupts should never be disabled that long.

chepecarlos commented 7 years ago

Thanks @MustBeArt for the documentation, I had the same problem in my code but I already manage to solve it

aaronw2 commented 6 years ago

When researching NeoPixels for the ESP32 I came across a library for FreeRTOS that uses the IR engine to generate the pulses and uses interrupts to keep the pipe full. I think for the ESP32 this would be a better solution than the normal method for driving the LEDs since the pulses and timing are handled by the hardware and it also would be fine for interrupts.

See https://github.com/marcmerlin/Neopixel-IR/blob/master/esp32_ws2812.cpp

ajvpot commented 6 years ago

Using the RMT is addressed here. Waiting on driver support from upstream.

scott-linenberger commented 6 years ago

No idea if this is the same issue, but my NeoPixel code that works perfectly using AdafruitIO on a Feather Huzzah does not work properly on the FeatherESP32. I can flash the exact same code to both and the ESP32 board always sets the first pixel green and flickers no matter what color I set it to after that. It's been incredibly frustrating trying to figure out why...

Heljick commented 6 years ago

Hi @scott-linenberger I have exactly the same issue with Artnet on Feather HUZZAH ESP32, actually looking to fix It without any luck so far

scott-linenberger commented 6 years ago

@Heljick --> I couldn't fix the issue either. The NeoPixels seem to work fine so long as I don't make an AdafruitIO connection. So, it would seem that when I'm using WiFi and the NeoPixels, the first pixel flicker is there. I tried different level shifters, tried a ton of code to reset and clear things...Wound up having to return my ESP32s and just use the updated Huzzah Feather (which is working absolutely fine).

Heljick commented 6 years ago

Thanks @scott-linenberger for the feedback, can you give me the link to your Feather ? I am in France adn it's very annoying for me, not sure I can't return mine :/ I saw people had issue and solved It using an other library than Adafruit one but can't remember where and if It was exactly for that issue. Will post a review if I can found it but for what I remember this was a delay issue and just having delay(2) solved there problem (maybe was for ESP8266)

Heljick commented 6 years ago

Found the thread https://github.com/Makuna/NeoPixelBus/issues/152

scott-linenberger commented 6 years ago

@Heljick This is the other Feather I used that works fine with AdafruitIO and NeoPixels Huzzah Feather I also tried a different library: FastLED and had the exact same issue, as soon as I introduced a WiFi connection...flickering lights..

Heljick commented 6 years ago

OK thanks I assume It's better to stay on ESP8266 indeed even if it's three times slower :/ Shame was very happy so far.

ladyada commented 6 years ago

heya @me-no-dev do you have any recommendations?

me-no-dev commented 6 years ago

@ladyada issue comes from the fact that we are running on top of freertos so the scheduler switches task every tick (1ms). In the beginning Arduino was the only task running on Core1, but that was later changed in IDF so some tasks run without core affinity, thus interrupting the loop task on Core1. The only long time solution is to use either RMT or I2S in combination with DMA so the transmission is not interrupted. Bitbanging time critical protocols that transmit longer than the tick period is kinda impossible on ESP32.

me-no-dev commented 6 years ago

@ladyada I can help with a custom driver based on one of the above protocols. Let me know :)

ladyada commented 6 years ago

@me-no-dev we've got no indepth experience with the RMT/DMA framework on ESP32 so a pull req from y'all would be super appreciated by everyone it seems :)

me-no-dev commented 6 years ago

Will do :) I have a feather-wing with neopixels here. Just one question... AFAIK there are two different signal standards and colour orders right? Like RGB and BGR and two different timing schemes. Could you point me to the proper info on those? Or I can get all that info from the lib and your website?

ladyada commented 6 years ago

the color order is swapped in software, the timing scheme is identical. the library passes a uint8_t array, and the number of bytes to write to the HAL. the is800khz flag is always true (almost nobody has 400khz pixels anymore) https://github.com/adafruit/Adafruit_NeoPixel/blob/master/esp8266.c#L24

mouridis commented 6 years ago

Unfortunately I based my WS2812B hardware application on ESP32 before doing thorough checking (in my prototype I used a strip of 30 LEDs while the final project, for which I already printed PCBs, uses 120 LEDs). As you can imagine, there are glitches all over the place after LED #32.

As expected, using delay() doesn't help in my case of 120 LEDs.

What I find odd though, is that even if I use the portDISABLE_INTERRUPTS() "workaround" the problem does not disappear completely. In theory, even though disabling interrupts introduces so many other problems, it should totally fix the glitches in the LEDs. In practice, in my case, the glitches are reduced 90% but not completely disappear.

Anyway, that's my 2 cents on the issue. Since I have a custom PCBA with ESP32 and 120 WS2812B sitting there, I can help with any debugging you guys may need.

me-no-dev commented 6 years ago

I have success with using I2S, but the IDF I2S driver will not do. The way that DMA is implemented causes issues. So... I use my own I2S driver and all is fine. At least for 340 pixels (DMA accepts up to 4096 bytes at once). Now I need to write the I2S driver so it can go into Arduino's HAL and will PR the changes here. Maybe 8266 can go the same direction and use I2S as well?

ladyada commented 6 years ago

sure, weve used SPI to DMA befroe, on the samd21. as long as you have pin flexibility i don’t think people care too much how it happens. :)

cscott commented 6 years ago

Same issue, ESP32 feather + NeoPixel FeatherWing == glitches even on strandtest unless you use delay(1);strip.show(), and the delay(1) trick doesn't seem to work at all once you turn WiFi on. Looking forward to @me-no-dev's PR! But in the meantime you might want to include a note on the NeoPixel FeatherWing product page re: ESP32 (in)compatibility? I bought my ESP32 feather + featherwing just a few weeks ago, I would have gotten the Huzzah if I had a had any clue there was a problem.

EDIT: I just tried https://github.com/Makuna/NeoPixelBus with total success, even with WiFi and Serial. Had to install from git, though, due to https://github.com/Makuna/NeoPixelBus/pull/212 on Linux. I can confirm that's a good workaround until the Adafruit library gets hardware support on ESP32.

ladyada commented 6 years ago

@cscott added a note to the product page. @me-no-dev any updates or code we can test?

me-no-dev commented 6 years ago

@ladyada sorry I have been quite busy with a whole lot of things. I have a form of the driver running through I2S and one of my colleagues wrote a driver and example with RMT (in PR already), so you will have all the options quite soon. I have tested with up to 48 leds (that is all I have) and everything seems good.

me-no-dev commented 6 years ago

@cscott Makuna uses an early version of my I2S driver, but will also switch to the main one once finished. Afterall using a half-ass driver is not an option for Adafruit :)

haering commented 5 years ago

Is there any update on this? What is the current problem?

warner83 commented 5 years ago

Is a workaround available?

jordan9001 commented 5 years ago

Hey everyone. I made my own workaround a while ago, and have been meaning to make a pull request. I don't think I will get around to it soon, so I am just posting my implementation here that gets around it. I am using RMT, like @me-no-dev suggested. Feel free to use/abuse my code how you like. https://github.com/jordan9001/ColorWaves/blob/master/Hardware/EspControl/WS2812_ESP_RMT.cpp

cscott commented 5 years ago

Looks like https://github.com/Makuna/NeoPixelBus has made a new release since my post above, so you no longer have to build it from git. That should be a reasonable workaround for most folks, I think.

ladyada commented 5 years ago

neat - if someone is feeling especially kind, a PR would be greatfully appreciated! if not, we'll try to get to it next time we do a sweep thru this library

@cscott its' good to cyber-see you - ahh i remember that killer mystery hunt puzzle with the pic micro and the mirror-ball. would be neat to rebuild it! (it was assigned to me but i never solved it :)

majodi commented 5 years ago

I can confirm that the Adafruit library is not working right on (my) ESP32 but NeoPixelBus is. Thank you!

cscott commented 5 years ago

@ladyada Are you hunting this year? Gnireenigne Lab was arguably too hard a puzzle given that it required decompilation of PIC flash contents (I was a novice writer!) but I was quite pleased/proud of http://web.mit.edu/puzzle/www/2012/puzzles/ben_bitdiddle/investigators_report/ ...

Sorry for drifting off topic...

ladyada commented 5 years ago

@cscott nah, im terrible at that stuff - and not local :) anyways, just wanted to 👋

leonyuhanov commented 5 years ago

Hi all my lib works cross platform using the SPI MOSI pin https://github.com/leonyuhanov/SK6812viaSPI/tree/master/ESP32 feel free to integrate

Yurik72 commented 5 years ago

Hi All, As well, I found a workaround to keep interface and WS2812FX class functionality Fell free to see https://github.com/Yurik72/ESPHomeController/wiki/WS2812-driver-to-remove-flickering

redengin commented 5 years ago

I have written a WS2812B driver loosely based upon https://github.com/MartyMacGyver/ESP32-Digital-RGB-LED-Drivers

Instead of ending transmission, I keep the RMT in constant transmit (i.e. I never fill RMT entries with 0). I have tested my code on these https://www.aliexpress.com/item/DC5V-WS2812B-1m-4m-5m-30-60-74-96-144-pixels-leds-m-Smart-led-pixel/32832420003.html?spm=a2g0s.9042311.0.0.319e4c4dX1ZIpO (upto 150 pixels in a strand).

To get it to work though, I had to increase the stretch of duration1 on the last pixel to 3 TRS (i.e. 150us vs the datasheet's 50us). Using TRS values lower than 3TRS resulted in the strand not being reset and therefore cascading forever.

I don't have a scope to measure what is actually happening on the data line, but as I still get high refresh rates, my hypothesis is that the actual time of duration1 is lower than described by the datasheet when duration0 is much smaller than duration1.

zostay commented 5 years ago

The ESP32 library has what looks to be some official RMT tools built-in. They even provide an example. They work just fine for driving WS2812B pixels. I have it working with a flexible 8x32 display with no problems (which is amazing after fighting all sorts of weird flashing and flickering when driving the pin, even with interrupts disabled). With the RMT code, all artifacts have been completely silenced (though, I found I could somewhat reduce them by strapping a couple 220pf capacitors across data and ground).

Anyway, to get this working, I monkey-patched Adafruit_NeoPixel.cpp to use the RMT peripheral. You can find my code here:

That code is horrible, but only because I'm rushing by monkey-patching rather than making a careful fix. It ignores standard defines and hardcodes things in a way I would never do for a library I'm actually distributing. I hate to share my code in it's current state, but by sharing it I'm hoping to remind myself to come back next week and fix it.

I based my code off of the example that is on the official espressif repo. That code can be found here:

I hope to make a PR next week AFTER I finish giving a talk at The Perl Conference which features the device I need this for. I will have time after that, but definitely not before because I don't even have slides yet.

mattncsu commented 4 years ago

I think I'm experiencing this same bug on an ESP32 board I made. I'm using the Neo7Segment library which relies on the Adafruit_NeoPixel library. Here is what I am seeing:

Video of Neo7Segment demo -> https://imgur.com/5zKBg4n Same board running FastLED demo -> https://imgur.com/jAo15dh

ramarro123 commented 4 years ago

Hi, any news about this issue? experiencing the same problem with esp32

Yurik72 commented 4 years ago

Fixed year ago https://github.com/Yurik72/ESPHomeController/wiki/WS2812-driver-to-remove-flickering

On Sun, Mar 8, 2020 at 2:49 PM ramarro123 notifications@github.com wrote:

Hi, any news about this issue? experiencing the same problem with esp32

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/adafruit/Adafruit_NeoPixel/issues/139?email_source=notifications&email_token=AKDREXOG5FBJPDDTYYCAMI3RGOH55A5CNFSM4DLP5P7KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEOEVFUI#issuecomment-596202193, or unsubscribe https://github.com/notifications/unsubscribe-auth/AKDREXIS4YNUXSYFBFBJBBDRGOH55ANCNFSM4DLP5P7A .

ramarro123 commented 4 years ago

did you submit a patch for this project? are you promoting your project saying that you managed to fix in a different library? was your fix backported here?

i don't see how linking a different library can help :) i am aware that fastled, neopixelbus, and now this other library probably, support rmt on esp32 ... but i am asking about adafruit_neoload not about other_library

IAmOrion commented 4 years ago

Anyway, to get this working, I monkey-patched Adafruit_NeoPixel.cpp to use the RMT peripheral. You can find my code here:

That code is horrible, but only because I'm rushing by monkey-patching rather than making a careful fix. It ignores standard defines and hardcodes things in a way I would never do for a library I'm actually distributing. I hate to share my code in it's current state, but by sharing it I'm hoping to remind myself to come back next week and fix it.

@zostay

Apologies for digging up an old post, but I had a similar issue with NeoPixels and ESP32. I made my own NeoPattern library but am not skilled enough to convert it to the likes of NeoPixelBus so when I came across your post and RMT version I thought I'd give it a try, however, it doesn't seem to work. When using your .h and .cpp the neopixels do absolutely nothing. Is there something special I need to add or change? I was hoping it'd just work by dropping it in (I've included your .h and .cpp locally in the project folder)

`

include "Adafruit_NeoPixel_RMT.h"

include "NeoPatterns.h"

`

There's no error, the neopixel strip just does nothing. If I upload basic NeoPixel test, everything works albeit glitchy with the odd artifact, so it's definitely something related to swapping the libs and NOT a hardware/wiring/power issue

MTIA

tablatronix commented 4 years ago

Yeah what ever happened with this? I do not think a PR was ever made for RMT on esp32, are people just using other libraries now?

martinberlin commented 4 years ago

I‘m also interested in getting this to work. Question: Did someone tried to use Neomatrix with Neopixels library instead of this one? The demos work but I see this issue everywhere in a 50x20 matrix 51B8F3A3-F6E8-49B2-821B-327E86B2A18C By the way is funny this 1 ms tick is displayed as a "diamond" I've the feeling there is more than this. Using the port* disable interrupts still shows some so I guess not a real solution.

      portDISABLE_INTERRUPTS();
      matrix->show();
      portENABLE_INTERRUPTS();
tablatronix commented 4 years ago

Does this issue sound like the same problem ?

159

BatsIhor commented 4 years ago

Some people say that if you move your pixel rendering to second core it helps. So far I tried NeoPixel library and other Library called NeoPixelBus and FastLED, all of them suffer from flickering on ESP32 when you use Wifi. The only think that left is running on separate core which I'm going to do next.

martinberlin commented 4 years ago

NeoPixelBus supports Matrix, here the reply of Michael in the gitter channel: Matrix is supported, check out the wiki, including not only a single panel layout but also mosaics of those panels. There are samples for it. But it is just a coordinate translation object.

So the killer solution would be to fork Neomatrix from Adafruit and make it work with Neopixel Bus. This library is simply not for ESP32. You cannot avoid this 1ms ticks and is really annoying to use like it is right now. Need to find the time to see if this idea can be done, keep tuned

dorianim commented 4 years ago

Thanks @BatsIhor for the hint that moving to the second core helps! Doing that removed all the flickering for me, I still had to disable interrupts though. But at least it works now.

This is my code:

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>

#define PIN 2

TaskHandle_t Task1;

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(8, 8, 2, 1, PIN,
                            NEO_TILE_TOP   + NEO_TILE_LEFT   + NEO_TILE_ROWS   + NEO_TILE_PROGRESSIVE +
                            NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS + NEO_TILE_PROGRESSIVE,
                            NEO_GRB + NEO_KHZ800);

const uint16_t colors[] = {
  matrix.Color(255, 0, 0), matrix.Color(0, 255, 0), matrix.Color(0, 0, 255)
};

void Task1code();

int x    = matrix.width();
int pass = 0;
String scroll_text = "This is a test !!!";

void setup() {
  Serial.begin(115200);

  matrix.begin();
  matrix.setTextWrap(false);
  matrix.setBrightness(40);
  matrix.setTextColor(colors[0]);

  xTaskCreatePinnedToCore(Task1code, "Task1", 10000, NULL, 1, &Task1, 0);
  delay(500);
}

void Task1code( void * pvParameters ) {
  for (;;) {
    matrix.fillScreen(0);
    matrix.setCursor(x, 0);
    matrix.print(scroll_text.c_str());
    if (--x < (int)(-6 * scroll_text.length())) {
      x = matrix.width();
      if (++pass >= 3) pass = 0;
      matrix.setTextColor(colors[pass]);
    }

    portDISABLE_INTERRUPTS();
    matrix.show();
    portENABLE_INTERRUPTS();
    delay(100);
  }
}

void loop() {
  delay(10);
}
ladyada commented 3 years ago

plesae try https://github.com/adafruit/Adafruit_NeoPixel/pull/253

ladyada commented 3 years ago

released as 1.7.0