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.
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.
Even dos programs runs on DosEmu much faster than on native DOS on the same machine.
Emulators are often faster than their original implementations. That’s one of the most popular points/goals of making an emulator in the first place.
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
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.
I’m all for saving energy, so I like this.
Although to be honest, I thought this was common best practice.
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.
Best practise in embedded is generally to try and find some sort of sleep state until you get woken by interrupts or call backs
Whine mode On:
“But I want the server to respond to my request right NOW!”
Whine mode Off:
B^)
Make it inerrupt driven, then you get both low power and fast responce.
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.
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.
void loop()
{
while (request_pending()) {
handle_request();
}
delay(1);
}
That’s not going to save any power, cpu will be stuck in the while loop constantly checking for the request pending. Better to put an “if ” not a while loop.
Loop handling part and add timer resetting each response,after 1s? go to 1ms wait?like turbo boost
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.
You have a power supply that’s 90% efficient and one that’s 95%. Only a 5% difference in total power consumed? Or one produces 2x as much waste heat as the other? Or both? :)
Of course it’s both. The difference is whether you are more concerned about heat or power consumption.
No, it’s a 5 percentage point difference. Subtracting percentages results in percentage points–not percentages.
When you find yourself using a term like “3 times less power”, you need to rethink your math.
Colloquial language is not same as the one used for scientific or engineering communication. If a battery lasts 3x longer, that’s 3x less power consumption for the layman.
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’.
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.
In the old days, the mass-spring mechanism in a meter movement provided the low-pass filtering that give a good result. Not so with DMMs in general.
Or if you are a hobbyist and that is out of your budget, runtime on a good battery is a pretty good proxy.
Yes, a battery is a pretty good integrator under the right circumstances.
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) ;)
Even 15-20 mA is pretty high for a modern processor doing nothing.
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.
How did you get it to 42mW, or is that just normal for a Zero W doing very little?
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)
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..
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.
Not a side effects of how the hardware is under the hood when the code is written for is not the best.
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.
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.
Isn’t this just busy-waiting? I thought it was common knowledge to never have a logic loop that refused to yield CPU time. No offence intended…. Maybe I’m just old lol.
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:)
Otherwise another hack would be to wake up the wifi with another radio that does not consume as much current, say BLE or 433mhz for example. And add some delay when the esp8266 is sleeping.
Doesn’t delay() use a timer interrupt?
So it actually isn’t counting seconds.
(please correct me if I’m wrong here)
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 ?
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.