PWM flickering with small analogWrite values [$15] #836

Open
ge0rg opened this Issue Sep 29, 2015 · 58 comments

Projects

None yet
@ge0rg
ge0rg commented Sep 29, 2015 edited

Hi, running the ESP8266 to control an RGB LED set via PWM on pins 12, 13, 15 using analogWrite().

When setting very low PWM values (<5), there is occasional flickering of the LEDs (the light turns to full brightness for a noticeable moment). Sometimes this is reinforced by network traffic, sometimes it happens without any apparent activity. The flickering is unrelated to the time when analogWrite() is called.

It helps a little to reduce the PWM frequency from 1KHz to 200Hz, but the flickering still happens from time to time.

I am aware that the PWM is based on a timer IRQ, not a hardware implementation, and I presume this issue is triggered by some other part of the code (wifi? tcp?) disabling interrupts for a short moment, leading to the timer interrupt being missed.

Unfortunately, the code in core_esp8266_wiring_pwm.c is not well documented and it's hard to understand for an outsider what exactly is happening there.

Would it be possible to mitigate the issue in the timer code? Maybe change it from edge- to level triggered or have the ISR called more often?

There is a $15 open bounty on this issue. Add to the bounty at Bountysource.

@igrr
Member
igrr commented Sep 29, 2015

Timer interrupt is edge triggered — this is part of the chip design and it is not possible to change it in software. I suppose using NMI for PWM would fix this problem because NMI is higher priority than WiFi interrupts.

@ge0rg
ge0rg commented Sep 29, 2015

Would it be possible to try that out with moderate overhead? s/timer1/???/ in the pwm source?

@igrr
Member
igrr commented Sep 29, 2015

You may try replacing ETS_FRC_TIMER1_INTR_ATTACH(timer1_isr_handler, NULL)
with ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1_isr_handler)
in core_esp8266_timer.c, line 45. Not sure if it will work though.

edit: Most likely there will be issues because ETS_FRC1_INTR_DISABLE will no longer work.

@ge0rg
ge0rg commented Sep 30, 2015

Replacing the regular interrupt with NMI leads to a watchdog reset ~10 seconds after startup:

ets Jan  8 2013,rst cause:4, boot mode:(1,6)
wdt reset

I even tried feeding the (soft) dog with system_soft_wdt_feed from the ISR, to no avail. It seems there is no function to feed the hardware watchdog, though.

Setting the timer to TIM_LOOP doesn't seem to improve the situation either - I hoped that a repeat of the ISR if it was missed will actually lead to some useful results.

I think I'll try the pwm_* functions from the official SDK next; let's hope they have some magic sauce to provide better stability.

@igrr
Member
igrr commented Sep 30, 2015

As I mentioned, current implementation will have issues with NMI because it relies on a "critical section" implemented using ETS_FRC1_INTR_DISABLE. So the right thing to do would be fix this by disabling the timer or detaching NMI interrupt for the duration of the critical section.

@ge0rg
ge0rg commented Oct 5, 2015

I'm not sure this has to do with the PWM critical section at all. Merely adding a ETS_FRC_TIMER1_NMI_INTR_ATTACH(noop_function); to the code leads to a wdt reset. It rather seems to me that the espressif code is using an Nmi handler internally for watchdog purposes, and as long as we don't know which function was added as the Nmi handler, we can't just override it (without calling into it from our own handler).

I'd like to help out with that, but my knowledge of the platform and the different SDKs is rather limited. I'd be glad to get one or two pointers in the right direction.

P.S: When using the IoT_Demo from 1.3, the PWM is much more smooth and reliable, and it's got a usable value range of ~ 0..22222.

@mangelajo
Contributor

Hmm, I tried this early this weekend, and I found the flickering.

Analyzing the code I saw that we block the interrupt of the timer while we're copying the old values to the new ones (so the timer won't get half-copied info).

Also, I found that flickering is more likely to happen while you're doing heavy operations over the flash.

I think that a good strategy, and probably no NMI needed:

  1. Move all the ISR/PWM the code to ICACHE RAM, so there's no issues with caching, and blocked flash.

  2. Instead of disabling the interrupt for mutex, I'd do a mutex via a flag.
    a) The setter code, waits for the flag to be clear, before trying to set any new values,
    b) Set's the new value in a set of structures (mailbox-like),
    c) Set's the flag
    d) ISR updates PWM with any value it already had (to avoid any jitter)
    e) ISR finds the flag, and copies the new values to a final structure it will use.
    f) ISR clears the flag.

Does it sound reasonable?

@igrr
Member
igrr commented Oct 6, 2015

I'm fixing the first part (moving handlers to RAM), commit coming in a few minutes.
Your approach with a flag does sound reasonable, actually it's the right way to go even if we are going to use NMI. I think NMI is necessary because otherwise we have flickering when there is heavy WiFi traffic.

@igrr
Member
igrr commented Oct 6, 2015

First part pushed, fixing #819.

@ge0rg
ge0rg commented Oct 6, 2015

Wow, this really is a significant improvement! I'm test running the new code now and it looks much more stable.

I'm still experiencing a slight flickering when the PWM values get changed (i.e. when running a color fader), and a more noticeable flickering when one of the PWM values is switching between 0 and non-0, which seems to trigger a longer code path.

I wonder if it would be acceptable to mark all of the routines behind analogWrite as ICACHE_RAM_ATTR to solve the first problem.

I hope the second problem will be addressed with the flag-based ISR rewrite.

@Diaoul
Contributor
Diaoul commented Oct 11, 2015

I'm experiencing the same issue with GPIO0 and GPIO2. ESP8266 is listening for POST requests and changes the value accordingly so that means that when the change happens, there is some network load. My knowledge is very limited on the ESP8266 so I don't know if I can be of any help troubleshooting this, however I can test the patches.

@Diaoul
Contributor
Diaoul commented Oct 30, 2015

I'd be glad to help though my knowledge is limited, can you point me where to start and what should be done?

@demlak
demlak commented Nov 1, 2015

same here.. sketch to reproduce:

const int pin = 13;
void setup() {
  pinMode(pin,OUTPUT);
  Serial.begin(115200);
}
void loop() {
  analogWrite(pin, 1);
  delay(10); //to not overload esp8266
}

btw.. same flickering on analog Values >= 1019 as on <=4

edit:
value 5 and 1018 also has flickering.. but very rarely, compared with the other values..

edit2:
value 6 and 1017 also flickering.. but very very rarely... like 2-3 times an hour or so..

@Diaoul
Contributor
Diaoul commented Nov 1, 2015

Using master branch is a lot better than the releases however some flickering still occur on network trafic, it's blinking once then back to the correct PWM setting.

@igrr igrr modified the milestone: 2.0.0 Nov 5, 2015
@ArnieO
ArnieO commented Nov 26, 2015

I am experiencing the same flickering on ESP-03s.
@igrr : I have several ESP-8266 LED faders that have been running for several months with LUA code. I have never seen any glitching there, so I am not so certain if this is really only a HW issue?
My workaround until further will be to make sure the code avoids the sensitive value intervals.

@igrr
Member
igrr commented Nov 26, 2015

This isn't a hardware issue. @mangelajo described software changes that need to be done pretty accurately.

@Diaoul
Contributor
Diaoul commented Nov 26, 2015
@ArnieO
ArnieO commented Nov 26, 2015

I found a workaround that seems to work well for me:
The default PWM frequency is 1000 Hz. By reducing to 200 Hz I got rid of the flickering on my module. (I tested step by step until the flickering disappeared).
PWM frequency is set to 200 Hz by this code:
analogWriteFreq(200);

@igrr igrr modified the milestone: 2.0.0, 2.1.0 Nov 30, 2015
@demlak
demlak commented Dec 6, 2015

thx a lot for sharing this workaround...
seems to fix the given problem of random flickering..

BUT opens another problem: 200hz seems to be too low to give a smooth output.. no random flickering of different brightnes anymore.. but continue flickering because of too low frequency..

@ArnieO
ArnieO commented Dec 6, 2015

I know 200 Hz is a bit low but on the ESP-03 I use I find the output to be smooth except for the very lowest setting, when the light is anyway almost invisible. So for me, the workaround is fully acceptable and without any noticeable drawback.

@demlak
demlak commented Dec 6, 2015

i´ll take a look at a low-pass filter for me.. but this is not ideal..

EDIT:
nevermind.. i just tested.. analogwrite value of 1.. with 200hz..
there are still brightnes flicker sometimes...

so.. problem still exists.. also at 200hz

@9H1LO
9H1LO commented Feb 2, 2016

issue still exists, flicker to full o/p when there is network traffic, i notice it when using more than 3 pwm channels, seems ok at 200hz up to 3ch i need 4 channels (rgbw with mqtt) and pca9685 will be over kill although i found it to work great

@krissfr
krissfr commented Feb 4, 2016

i have the problem too, i need to control 5 pwm output (3 leds, 2 fans) from mqtt messages, but each wifi transmission flicker all leds (and make the fan noisy during the transmission). i need to use a high pwm frequency for the fan, so workaround are not good for my usage.

I hope somone with a high esp8266/C++ developpement skill will find a solution

@9H1LO
9H1LO commented Feb 4, 2016

i have found cheap pca9685 and i'll be going that route, advantage is that led's remain running on last state even if esp reboots

@ArnieO
ArnieO commented Feb 5, 2016

Good idea to use a PWM driver chip. An alternative is TLC5940, which I used in a previous project. I do not have hands-on experience with the PCA9685.
@krissfr : I think you need a hardware workaround here, so you could take a look at those two chips.

@demlak
demlak commented Feb 5, 2016

wtf? a workaround with additional hardware in an issue about a softwarebug?
guys.. this is not acceptable

@tuxedo0801
Contributor

@demlak
+1 and full ack ...

@ArnieO
ArnieO commented Feb 8, 2016

@demlak @tuxedo0801 i agree, it is far from ideal. My SW workaround is to reduce the frequency to 200 Hz and avoid the smallest values. In cases where the reduced frequency do not work, I think a HW workaround is better than very annoying flickering - until some wizard fixes the software bug.

@demlak
demlak commented Feb 8, 2016

maybe this is acceptable for you.. but not for the issue

@larsenglund
Contributor

Any news on getting access to a NMI timer on arduino? @igrr what/where is this critical section, I could have a stab at coding the workaround you deacribed.

@larsenglund
Contributor

I've been experimenting with getting access to the bare timer but keep getting watchdog resets, code here: https://github.com/larsenglund/esp8266_arduino_nmi_timer

@misan
misan commented Feb 14, 2016

Using it at 20Khz or 15Kkhz or 12Khz and 8-bit PWM I get an ok result for all values except for 254 that is like 0 value (not a good thing (tm) if you are driving a motor's PID). Lowering the freq to 10Khz seems to get back to the right behaviour.

@patrickjahns

I`ve been experimenting with PWM for fading LEDs. I am using 5 channels and I only experience the flickering when (cross)fading the channels.
It for some reason seems that triggering several analogWrite changes inflicts timer/value issues. For me the flickering appears to be that either the Duty cycle is 0 or 100 - and thus a visible flicker occurs

Do you guys know how the pwm from the ESP SDK is working? Is it even possible to use it as an alternative ?

@ge0rg
ge0rg commented Feb 19, 2016

I'm also experiencing the flickering when doing fading on multiple channels in parallel, and it seems not to be related to small/large values (I did some capping experiments to no avail).

The ESP SDK pwm is rock-solid. It can be used with the IoT_Demo example, which allows very smooth multi-channel fading. Unfortunately, I have no idea how it is realized internally, besides of vague NMI hints.

@ArnieO
ArnieO commented Feb 19, 2016

I have seen severe flickering (bright flashes while fader set to almost zero) with only one channel. After reducing frequency, the light is pulsating when fader set low. Visible but acceptable. I have never seen such issues on my RGBW (4 channels) fader modules running LUA firmware, But programming with Arduino IDE is such a big advantage that I prefer to go in that direction.

@patrickjahns

Wouldn`t it be possible to create a wrapper around the SDK PWM ? Or will this brake things?

I am researching currently how to include the sdk pwm and use it instead of analogWrite - if anyone has any hints on this?

@igrr igrr modified the milestone: 2.2.0, 2.1.0 Feb 27, 2016
@kaeferfreund

running the pwm at low values is still an issue for me, since I don't know how to fix this.
https://www.bountysource.com/issues/27026077-pwm-flickering-with-small-analogwrite-values
Somebody said, that the sdk pwm would be very solid, adding that into the core might solve this...

I haven't tried this code, but maybe this works better...
https://github.com/FastLED/FastLED/blob/8412262567e162eebcb9baa07c6ab1312185a92f/platforms/esp/8266/clockless_esp8266.h

@igrr igrr added the bounty label Mar 14, 2016
@patrickjahns

@kaeferfreund

The linked part of the FastLED library is for leds driven via spi and not pwm.

I can confirm that utilizing the SDK PWM is solid and I did not experience any of these reported issues here. The downside is - right now I am not able to use SDK PWM with Arduino IDE (see #1654).
I am not too familiar with the Arduino changes/structure/specifics - after including the library files (lpwm) it will compile - yet I can not initialize it - always results in a WDT reset. I gave up trying to solve this with Arduino IDE and switched to SMING - framework compatible to Arduino Functions and most Arduino libraries are also compatible.

@igrr igrr modified the milestone: 2.2.0, 2.3.0 Apr 18, 2016
@kaeferfreund
kaeferfreund commented May 22, 2016 edited

yap, I have:

for (uint16_t i = 1; i < brt; i++) {
        analogWrite(PIN1, i);
        analogWrite(PIN2, i);
        yield();
      }

But I also have some web sockets-stuff going on, which is a bit slower but still to fast...

Just switched to SDK-pwm, seems like this also is fading a bit slower than arduino-pwm

@kaeferfreund
kaeferfreund commented May 23, 2016 edited

One more thing:
If revert the changes back to the non-NMI timer settings, the PWM completely stops working...
Even if I redownload the whole esp core and reflash it.
Seems that there is an issue with detaching the timer...

That goes away, if I properly reset the ESP

Change I made, but later reverted:
from ETS_FRC_TIMER1_INTR_ATTACH(timer1_isr_handler, NULL) to
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1_isr_handler)

About watchdog reset thing: If I drag my web sockets brightens slider very slowly, I don`t get a reset.

ESP12-e is running on 160 Mhz CPU, 80 Mhz Flash, QIO which worked like a charm with the "old" PWM. Had over 30 days of stable daily usage

@igrr igrr modified the milestone: 2.3.0, 2.4.0 Jun 3, 2016
@igrr igrr changed the title from PWM flickering with small analogWrite values to PWM flickering with small analogWrite values [$15] Jun 8, 2016
@kaeferfreund
kaeferfreund commented Jun 12, 2016 edited

today I've been working a bit on this issue and I found that the second TEIE |= TEIE1; in "core_esp8266_wiring_pwm.c" seems to cause the issue. But since this line enables the edge trigger, without that line, pwm won't work. So that needs to be part of the Interrupt Service Routine...

Does anybody have an idea?

@me-no-dev
Collaborator

here is an attempt at this: #2299
with default values you get the maximum that the software can do, or:

  • 10bit@1KHz with CPU at 160MHz (~1MHz)
  • 9bit@1KHz with CPU at 80MHz (~500KHz)
    The routine will automatically downscale the range and values when 80MHz CPU is detected.
    Since this is a software driver, timing isn't 100% accurate, but pulse length stays constant. Error is higher with lower values given.
@kaeferfreund
kaeferfreund commented Jul 19, 2016 edited

Thanks A LOT! I will test it when I come home tonight.

Edit:
Although I can't see why this implementation could be slower than the other one, I had the old (non nmi) version running at a range of 19560 and 1khz frequency. Which is close to what espressif claims in their sdk documentation...
just wondering why there is such a huge difference...?

@Rom3oDelta7

A friend and I are building an LED panel for photographic use and the flickering issue was a significant problem. We've been monitoring this thread and we were excited to see this fix. I went ahead and tried this (via the current git dev master branch) without changing the values we had been using (66Hz and 12 bit resolution, 160MHz CPU) and it works great! We select the LED brightness using the following table:

const uint16_t  PWM_duty_cycle [] = {0, 1, 2, 3, 4, 6, 9, 13, 19, 28, 40, 58, 83, 118, 168, 238, 339, 482, 686, 978, 1382, 1996, 2874, 4095};

I'll look at the code now to see what happens with this frequency and resolution value as currently the the light output of index values 1 & 2 are the same as well as 3 & 4. Starting with index value 5 (pwm = 6) each successive increase in value in the table results in the expected 2 stop increase in light output. With virtually no flicker at any index value. So we have some adjustments to do but this fix looks solid and was a huge help.

BIG THANK YOU to @me-no-dev for this fix.

@dml1333
dml1333 commented Jul 22, 2016

Follow up to Rom3oDelta7's note. Our LED panel dimmer for night photography, while set to 66 Hz and 12-bits (4095), actually measures at a period of 74.6 Hz, and the length of the duty cycle shortens very well all the way down to a duty cycle of 1, with all of the low values working properly. Where the prior code showed frequent jumping up from 4 uS to ~6uS, at a duty cycle of 1, the new code show NO jumping up, and only an occasional very quick dropout, maybe missing one period now and then. This fixes the issue for me! And a BIG Thanks!

@me-no-dev
Collaborator

@kaeferfreund I can not get the ISR to take less than 100-130 cycles and you have to add time between interrupt and jumps as well, so all in all there is some 2us wasted in code and switching (at 80MHz). With those numbers you can see how the ISR can not be called often enough to actually give you that high of resolution.

@Rom3oDelta7 @dml1333 Thank you for confirming this "fix" :) Glad it's working for you

@kaeferfreund

Okay didnt know that. Thx for the explaination.

How can espressif then claim their sdk pwm has 22.222 range and 1khz frequency?
Am I misunderstanding something here?

@kaeferfreund

Sorry it took me so long to test everything. For now me-no-dev's claims are very accurate. I can't run the pwm any faster than the 1khz at 10 bit resolution.
It's quite surprising, that the non NMI pwm is so much faster. I can run it at 19560 resolution and 1khz frequency with no problems.
I also noticed, that my LEDs are flickering slightly on wifi traffic (I use the async web server lib), which seems similar, maybe even a bit worse than on the non NMI Version. But as far as stability is concerned, things look rather good. No trouble so far.

I'll try to speed up pwm stuff a bit in the upcoming days...

@me-no-dev
Collaborator

@kaeferfreund how did you test that 1kHz 19560 resolution? Was there any difference between values that are close?
I calculated and constrained the functions to the point where there is actual difference between samples and that they are not too close to cause WDT and other issues.
Also I ran a test here with my scope to see if there is any fluctuation in the pulses and I saw sometimes some nanoseconds being added which will not be really visible or noticeable.
Other than that the pulses were being really steady regardless of the given input value.

@boneskull boneskull referenced this issue in idolpx/mobile-rr Jul 29, 2016
Closed

console - "beep rr" command doesn't work #1

@boneskull

Here's a non-LED-related issue.

A silly thing is that in idolpx/mobile-rr#1, the calls to analogWriteFreq() and analogWrite() all work fine before websockets happen. Specifically, when attempting to play the song from a WS console (which is triggered by an event handler), the buzzer does nothing.

I haven't taken a closer look at that port with my bus pirate or xprotolab, though. worth doing?

I tried dumping @me-no-dev 's patch into ~/.platformio/packages/framework-arduinoespressif/cores/esp8266/, cleaned my build, and uploaded the result, but there was no difference--I'm not entirely convinced core_esp8266_wiring_pwm.c actually got recompiled, though...

@baoxianzhang

Are there any documents about the reigsters and the codes? It's hard to understand for an outsider what exactly is happening there.

@kvv213
kvv213 commented Sep 6, 2016

Hello Guys,

I think that another issue can be connected with this one. I'm speaking about #2477

I faced almost random WDT resets when I used fade of onboard led + WiFi communications. Fade means PWM.

@nepeee
nepeee commented Oct 14, 2016 edited

Hi!

I found a code that works extremely well, i tested it for 4 channels ~20KHz and custom wifi pockets for rc remote control. The pwm is very stable with the nmi interrupt mode while the module receiving 1700 pockets/second! Be sure that the wifi is initialized before the pwm or the esp crashes.

Link for the code: https://github.com/StefanBruens/ESP8266_new_pwm

Have fun!

@micw
micw commented Nov 20, 2016 edited

Hello,

I had serious problems with flickering when I fade my LEDs. PWM seems not to flicker when the LEDs are dimmes to a certain value, only when analogWrite is called with changing values.

I completely solved it by disabling interrupts before calling analogWrite and re-enable afterwards. There's my minimal sketch:

void setup() {
  analogWriteFreq(1000);
  pinMode(D1, OUTPUT);
  pinMode(D2, OUTPUT);
  pinMode(D3, OUTPUT);
  Serial.begin(115200);
  Serial.println(PWMRANGE);
}

int value=0;
int delta=1;

void loop() {
  value+=delta;
  if (value>1023) {
    value=1023;
    delta=-delta;
  } else if (value<0) {
    value=0;
    delta=-delta;
  }
  noInterrupts();
  analogWrite(D1,value);
  analogWrite(D2,value);
  analogWrite(D3,value);
  interrupts();
  delay(1);
}

Without noInterrupts/interrupts it flickers like hell. My guess is that it is a race condition when writing new pwm value while PWM interrupt is executed.

Regards,
Michael.

@micw
micw commented Dec 7, 2016

Update: Flicker occurs again for some reason on my sketch when there's network traffic ;-(

@micw
micw commented Dec 7, 2016

I have switched my sketch to use https://github.com/StefanBruens/ESP8266_new_pwm. I use a period of 5000 which gives me perfect 1kHz pwm. No flicker at all, works like charm.
Only drawback is that the API is not as clean as analogWrite().

IMO you really should replace the current implementation by this one.

@simonliu009
simonliu009 commented Dec 28, 2016 edited

@micw Does https://github.com/StefanBruens/ESP8266_new_pwm mean you switch to the ESP8266 SDK?

@micw
micw commented Jan 1, 2017

@simonliu009 no, you can still use arduino ide with esp8266 extensions.

@dpeddi dpeddi referenced this issue in mcbittech/souliss-smart-thermostat-WiFi Feb 6, 2017
Open

backlight non abbastanza progressiva e con flickering #45

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment