Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
MustBeArt opened this issue May 16, 2017 · 37 comments

Comments

@MustBeArt
Copy link

commented May 16, 2017

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

This comment has been minimized.

Copy link
Author

commented May 16, 2017

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

@antonio-fiol

This comment has been minimized.

Copy link

commented Jul 9, 2017

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

This comment has been minimized.

Copy link

commented Sep 2, 2017

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

@aaronw2

This comment has been minimized.

Copy link

commented Feb 2, 2018

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

This comment has been minimized.

Copy link

commented Feb 4, 2018

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

@scott-linenberger

This comment has been minimized.

Copy link

commented Mar 18, 2018

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

This comment has been minimized.

Copy link

commented Apr 6, 2018

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

This comment has been minimized.

Copy link

commented Apr 6, 2018

@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

This comment has been minimized.

Copy link

commented Apr 6, 2018

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

This comment has been minimized.

Copy link

commented Apr 6, 2018

Found the thread Makuna/NeoPixelBus#152

@scott-linenberger

This comment has been minimized.

Copy link

commented Apr 6, 2018

@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

This comment has been minimized.

Copy link

commented Apr 6, 2018

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

This comment has been minimized.

Copy link
Member

commented Apr 6, 2018

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

@me-no-dev

This comment has been minimized.

Copy link

commented Apr 7, 2018

@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

This comment has been minimized.

Copy link

commented Apr 7, 2018

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

@ladyada

This comment has been minimized.

Copy link
Member

commented Apr 7, 2018

@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

This comment has been minimized.

Copy link

commented Apr 7, 2018

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

This comment has been minimized.

Copy link
Member

commented Apr 7, 2018

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

This comment has been minimized.

Copy link

commented Apr 10, 2018

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

This comment has been minimized.

Copy link

commented Apr 22, 2018

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

This comment has been minimized.

Copy link
Member

commented Apr 22, 2018

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

This comment has been minimized.

Copy link

commented Jul 8, 2018

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 Makuna/NeoPixelBus#212 on Linux. I can confirm that's a good workaround until the Adafruit library gets hardware support on ESP32.

@ladyada

This comment has been minimized.

Copy link
Member

commented Jul 8, 2018

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

@me-no-dev

This comment has been minimized.

Copy link

commented Jul 9, 2018

@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

This comment has been minimized.

Copy link

commented Jul 9, 2018

@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 :)

phylor added a commit to phylor/kuehl that referenced this issue Nov 14, 2018

Changed neopixel library.
The Adafruit library does not yet support the ESP32, as the chip has timing problems in this scenario.

Cf. adafruit/Adafruit_NeoPixel#139
@haering

This comment has been minimized.

Copy link

commented Nov 18, 2018

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

@warner83

This comment has been minimized.

Copy link

commented Nov 18, 2018

Is a workaround available?

@jordan9001

This comment has been minimized.

Copy link

commented Nov 18, 2018

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

This comment has been minimized.

Copy link

commented Nov 19, 2018

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

This comment has been minimized.

Copy link
Member

commented Nov 19, 2018

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

This comment has been minimized.

Copy link

commented Jan 9, 2019

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

@cscott

This comment has been minimized.

Copy link

commented Jan 9, 2019

@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

This comment has been minimized.

Copy link
Member

commented Jan 9, 2019

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

@leonyuhanov

This comment has been minimized.

Copy link

commented Feb 11, 2019

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

This comment has been minimized.

Copy link

commented Feb 25, 2019

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

This comment has been minimized.

Copy link

commented Apr 4, 2019

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 3*TRS 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

This comment has been minimized.

Copy link

commented Jun 15, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.