ESP8266 Web Server Saves 60% Power With A 1 Ms Delay

Arduino has a library for quickly and easily setting up a simple web server on an ESP8622-based board, and [Tomaž] found that power consumption on an ESP-01 can be reduced a considerable amount by simply inserting a 1 ms delay in the right place. The reason this works isn’t because of some strange bug or oddball feature — it’s really just a side effect of how the hardware operates under the hood.

[Tomaž] uses the “hello world” example from ESP8266WebServer to explain. In it, the main loop essentially consists of calling server.handleClient() forever. That process checks for incoming HTTP connections, handles them, sends responses, exits — and then does it all over again. A simple web server like this one spends most of its time waiting.

A far more efficient way to handle things would be to launch server.handleClient() only when an incoming network connection calls for it, and put the hardware to sleep whenever that is not happening. However, that level of control just isn’t possible in the context of the Arduino’s ESP8266WebServer library.

So what’s to be done? The next best thing turns out to be a simple delay(1) statement right after each server.handleClient() call in the main loop.

Why does this work? Adding delay(1) actually causes the CPU to spend the vast majority of its time in that one millisecond loop. And counting microseconds turns out to be a far less demanding task, power-wise, than checking for incoming network requests about a hundred thousand times per second. In [Tomaž]’s tests, that one millisecond delay reduced idle power consumption at 3.3 V from roughly 230 mW to around 70 mW — about 60% — while only delaying the web server’s response times by 6-8 milliseconds.

For simple web server applications, this is is for sure a good trick to keep in mind. There are also much more advanced techniques for saving power on ESP8266-based boards; from boards that barely sip a single microamp while sleeping, to coin-cell powered boards that go so far as to modify the TCP/IP stack to help squeeze every bit of power savings possible.

42 thoughts on “ESP8266 Web Server Saves 60% Power With A 1 Ms Delay

  1. This reminds me of reports years ago about how Linux was much more power efficient/longer battery life and ran cooler than the same hardware under windows.

    The difference was (as I recall) was that windows just grinds away trying to get user input, and Linux would go into wait states until input came in from the user.

    1. This comparison between Linux and Windows too place in Windows 95 times, right?🤣

      Seriously, though. Windows supports 486 era HLT instruction (HALT), at least. Newer releases ACPI/APM, too.

      Even Windows 3.1 had an APM option in Setup, too, I vaguely remember.

      For Windows 3.1 Enhanced-Mode, there’s WQGHLT, a VXD, which triggers HLT during idle times. Ideal for VMs.

      For pure DOS, DOSIDLE can be used (POWER is part if DOS 6.2x, but I have little experience with it.

      https://web.archive.org/web/20220721064808/https://www.scampers.org/steve/vmware/#31pack

      For Windows 98, there are Raine, AMNHLT, etc.

      https://web.archive.org/web/20060212132151/http://www.user.cityline.ru/~maxamn/amnhltm.zip

      1. I was gonna say something similar lol.

        Win32 API has, as far as I know, always been event based with a timer resolution that’s quite high by default, 10ms I think. Meaning that input is polled no faster than that. 10ms was fine for a long time, until > 75hz displays became common….

        ….these days in WinAPI (7 and newer) computers, it’s easy for any software to request a faster timing in the windows kernel. I’m pretty sure Chrome even will request 2ms. But this is modern age and we have so many advanced power features. For a start we don’t use 12v PS/2 input devices anymore haha.

    1. It is, but not everybody knows about it. I see lots of sample code for GUI toolkits (e.g., fltk, GTK+) that go into a loop continually polling for keyboard or mouse events, where they could just as easily just a blocking call that just returns when there’s an event, or include a delay. You know when someone has made this mistake on a desktop application because you run a performance monitor and you see one core at 100% while the rest are idling. Less obvious on microcontrollers.

  2. The MAIN problem is due to Arduino awful TCPIP stack that doesn’t support select (because Arduino was designed as OS-free system with a single main loop). Now, with delay(1) you can only answer, at best, 1000 requests per second. With select, the power consumption would be even lower (since the CPU could be put to sleep until the WIFI interrupt/wake it), and you wouldn’t drop any server performance.

    1. It’s especially silly when most ESP8266/ESP32 frameworks actually have some kind of RTOS included, so they would be perfectly capable of proper wakeup events, either with portable select() style API or something custom. And I wouldn’t be surprised if the hardware has capability to also power down significant parts of the chip while waiting if just instructed to do so.

  3. Man, I looked at 60% and 230mw and 70mw and my brain said “nah”. 230mw is > 300% of 70mw, but 70mw is just 30% of 230mw. If I’m thinking in terms of “power savings”, I would think “this uses 3 times less power than it did before”. Numbers are hard sometimes, sometimes they just don’t intuit.

    1. It’s one of my pet peeves when a ratio is shown as a 3 times reduction or a five fold improvement. 3 times means 3 times, which is a greater number, not a reduction. A lot of people get stuck in the vortex of why a percentage of a greater number is different from the percentage of the reduced number, but salesmen have been understanding this for hundreds of years with markup and margin which are two very different ratios. So. IMHO, saying that a second number is a percentage of a greater number (y=50% of x) is totally fine. Saying a number is greater than a smaller number by a percentage of the smaller number (x=50% greater than y [but this is not the same result as the previous example’s x]), OK, but saying a Nissan car has 3 times better gas mileage than a Ford pickup is totally nutzoid nonsense. Saying that the Covid shot has a 90 times (or 90 fold) better improvement over a placebo also whacky.
      I know it sounds a little pedantic, but just sayin’.

  4. If the current consumption is not constant DC and it’s not an AC current, you can’t really trust a multimeter. I would use professional equipment that records current samples thousands of times per second and averages them out over a few seconds to get an accurate average current consumption.

  5. That’s an quite impressive result! From my experience the esp8266 draws around 70 mA with ‘WiFi on’ and ‘doing nothing’. With ‘WiFi off’ I got 15-20 mA. So this might improve also the power consumption in other use cases than a web server?
    Maybe it activated the “light sleep” mode, which I and others never really got working…(few years ago) ;)

      1. I measured my Raspberry Pico W at 42mA with the example webserver code running. Haven’t tried to squeeze that down yet but I bet 20mA is well within reach even in Micropython.

  6. I was thinking the same, but what interrupt? A timer interrupt with a lock? Wake up every 5ms from the timer,run the webserver, go to deep-ish/er sleep? Wonder how much could be saved them. Followup anyone? :) (I don’t have an esp)

  7. running any type of high speed loop is not the way to go with many many things, including a web server. The program needs to change to interrupts/events/waits etc which is very easy to do with the 8266 and esp32. Then your processor is doing not much at all for most of the time, and is responsive when it needs to be..

    1. Agreed. Those “wait for input” loops are perhaps okay for just beginning and learning to program — probably why I see it so much in Arduino examples — but once one has moved beyond that, and needs a device to be more efficient, performant, etc., one needs to step up to using tools designed for the job. As you said, interrupts are a great solution, and they’re often so fast, they seem like magic, as if the uC can truly multitask.

  8. So… not turning on a power-hungry radio uses less power then turning on a power-hungry radio? Interesting. Maybe there could be some low power protocol developed to take account of this. We could call it BLE.

  9. The real reason that we see a reduction in current draw is that the delay() allows the WiFi modem to enter modem-sleep mode after a few seconds of network inactivity. The CPU is still running at full whack, which consumes around 15mA. You can test this by disabling sleep mode with:

    WiFi.setSleepMode(WIFI_NONE_SLEEP, 3);

    In modem-sleep mode the modem will wake up every few DTIM intervals (adjustable with the second parameter of setSleepMode()). And listen for the DTIM packet from the WiFi router (or something like that, it’s been around a year since I last looked into this). This does however introduce a latency for initial incoming packets, but once a packet is received it will then stay out of sleep mode for a while so further packets will arrive quickly. That’s why the latency benchmark shows good results with the delay(), because there’s not enough time between requests to allow the modem to enter sleep mode again.

  10. Honestly as much as I think this is a neat idea to spread power peaks, I don’t think it actually consumes less power.
    It’s hard to say with a multimeter, you’d need something like an oscilloscope or power consumption meter at very least, and actually count the time and peaks of the power usage.

    Milliamps and milliwatts aren’t a measure of how much total power was consumed, only (peak) usage within a certain timeframe. You need to find the amount of mAh, joules or mWh.

    That all said, neat project, interesting results nonetheless:)

  11. Using the AsyncWebServer – all events are triggered and thus there isn’t a ‘handleClient()’ in the loop().

    So is there any where a delay(1) would be appropriate ?

    1. Yes: in the application’s main event loop. In most operating systems, if the original thread is terminated, the application is closed and all of its resources are released. The main loop has to be maintained, if for no reason but to know when to shut down. Of course, a delay(1000) could be even more better, especially if the only way to shut it down is to cut power. I’ve seen many cases of example code that don’t include a delay in their main loops, and they all jump to 100% CPU usage, polling continuously for nothing to happen.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.