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

Closed
MustBeArt opened this issue May 16, 2017 · 50 comments
Closed

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

MustBeArt opened this issue May 16, 2017 · 50 comments

Comments

@MustBeArt
Copy link

@MustBeArt MustBeArt 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
Copy link
Author

@MustBeArt MustBeArt commented May 16, 2017

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

Loading

@antonio-fiol
Copy link

@antonio-fiol antonio-fiol 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.

Loading

@chepecarlos
Copy link

@chepecarlos chepecarlos commented Sep 2, 2017

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

Loading

@aaronw2
Copy link

@aaronw2 aaronw2 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

Loading

@ajvpot
Copy link

@ajvpot ajvpot commented Feb 4, 2018

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

Loading

@scott-linenberger
Copy link

@scott-linenberger scott-linenberger 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...

Loading

@Heljick
Copy link

@Heljick Heljick 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

Loading

@scott-linenberger
Copy link

@scott-linenberger scott-linenberger 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).

Loading

@Heljick
Copy link

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

Loading

@Heljick
Copy link

@Heljick Heljick commented Apr 6, 2018

Found the thread Makuna/NeoPixelBus#152

Loading

@scott-linenberger
Copy link

@scott-linenberger scott-linenberger 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..

Loading

@Heljick
Copy link

@Heljick Heljick 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.

Loading

@ladyada
Copy link
Member

@ladyada ladyada commented Apr 6, 2018

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

Loading

@me-no-dev
Copy link

@me-no-dev me-no-dev 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.

Loading

@me-no-dev
Copy link

@me-no-dev me-no-dev commented Apr 7, 2018

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

Loading

@ladyada
Copy link
Member

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

Loading

@me-no-dev
Copy link

@me-no-dev me-no-dev 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?

Loading

@ladyada
Copy link
Member

@ladyada ladyada 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

Loading

@mouridis
Copy link

@mouridis mouridis 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.

Loading

@me-no-dev
Copy link

@me-no-dev me-no-dev 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?

Loading

@ladyada
Copy link
Member

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

Loading

@cscott
Copy link

@cscott cscott 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.

Loading

@ladyada
Copy link
Member

@ladyada ladyada commented Jul 8, 2018

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

Loading

@me-no-dev
Copy link

@me-no-dev me-no-dev 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.

Loading

@me-no-dev
Copy link

@me-no-dev me-no-dev 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 :)

Loading

@ladyada
Copy link
Member

@ladyada ladyada commented Jan 9, 2019

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

Loading

@leonyuhanov
Copy link

@leonyuhanov leonyuhanov 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

Loading

@Yurik72
Copy link

@Yurik72 Yurik72 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

Loading

@redengin
Copy link

@redengin redengin 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.

Loading

@zostay
Copy link

@zostay zostay 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.

Loading

@mattncsu
Copy link

@mattncsu mattncsu commented Dec 21, 2019

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

Loading

@ramarro123
Copy link

@ramarro123 ramarro123 commented Mar 8, 2020

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

Loading

@Yurik72
Copy link

@Yurik72 Yurik72 commented Mar 8, 2020

Loading

@ramarro123
Copy link

@ramarro123 ramarro123 commented Mar 9, 2020

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

Loading

@IAmOrion
Copy link

@IAmOrion IAmOrion commented Aug 29, 2020

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

Loading

@tablatronix
Copy link
Contributor

@tablatronix tablatronix commented Sep 17, 2020

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?

Loading

@martinberlin
Copy link

@martinberlin martinberlin commented Sep 18, 2020

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();

Loading

@tablatronix
Copy link
Contributor

@tablatronix tablatronix commented Oct 1, 2020

Does this issue sound like the same problem ?

#159

Loading

@BatsIhor
Copy link

@BatsIhor BatsIhor commented Oct 2, 2020

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.

Loading

@martinberlin
Copy link

@martinberlin martinberlin commented Oct 3, 2020

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

Loading

@dorianim
Copy link

@dorianim dorianim commented Oct 7, 2020

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);
}

Loading

@ladyada
Copy link
Member

@ladyada ladyada commented Oct 30, 2020

plesae try #253

Loading

@ladyada
Copy link
Member

@ladyada ladyada commented Nov 5, 2020

released as 1.7.0

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet