NTP Clock Project Revisit and ESP8266 RTC Memory

NTP Clock project revisit

When I built the NTP Clock project two years ago, I did a quick estimation that the battery will last for about 80 days for a single charge. I was wrong, it only last for 47 days. Of course, my original estimation was not precise but I was wondering why my estimation was far away from my estimation, so I recently decided to take a closer look.

Original (Wrong) Assumptions and Estimation

When I build the NTP Clock in April 2022, the objective is to make it work, sort of proof-of-concept, of course I did put some thought on how to lower the battery consumption, such as running the ESP-12S (ESP8266) at 80MHz instead of 160MHz, figuring out the way to shorten the WiFi connecting time from 6 seconds to about 2 seconds. All these contribute to reduce the battery consumption. I did a quick estimation back to then the battery life would be last for 80-day battery based on the following estimations:

WiFi On: The WiFi is turn on to sync the NTP clock every 5 minute when the ESP-12S is waking up from deepsleep, the duration for the WiFi-on stage is about 2 seconds with the assumption of drawing average of 70mA during the 2 seconds period. So total mAH for the whole 24-hour is 70mA x 2s x 12times x 24hours / 3600s = 11.2mAh.

LED Blinking: WiFi is turned off after the initial 2 seconds for NTP sync, and ESP-12S go into the moden sleep mode, but the LED blinking for showing the time took exactly 5 seconds before the ESP-12S go into deep sleep mode. The estimated current consumption for the two blinking LEDs is 3.5mA, so the total mAh is 3.5mA x 5s x 12 times x 24hours / 3600s = 1.4mAH per day.

DeepSleep: Most of the time the ESP-12S is in deepsleep, I used the assumption of deep sleep current of 10uA, so I estimated that during the deep sleep the power consumption is (24 hours - (7s x 12times x 24hours / 3600s)) x 0.01mA = 0.23mAh.

So giving the battery capacity of 1000mAh that I'm using, it will last for 1000 / (11.2 + 1.4 + 0.23) = 77.92 days.

I realized that I was wrong on several counts:

  • The actual measurement of the current consumption when the two LED on is slightly better at 3mA. What I did wrong is that I forgot to include the 12mA ESP-12S current consumption during the moden sleep which is the time when the LEDs are blinking, the current consumption during modem sleep is 15mA, so the total should be 15 + 3 = 18mA during the LED blinking period.

  • My final design has two modes, night mode between 22:00 to 7:00 where the clock only wake-up once/hour, and day mode for the rest of the hours where the clock wake-up once every 5 minutes. So the battery consumption during day mode and night mode are different. So my original estimation of day mode and night mode power consumption was supposed to be 4.8mAh/day and 0.3mAh/day respectively. It turns out that there is also a software bug in my code that the night mode doesn't wake-up once every hour, but instead just operate like day mode due to the software bug. So the battery consumption for blinking became 4.8 + 2.4 = 7.2mAh/day.

  • I somehow had the impression that the ESP8266 has a deep sleep current of 10uA, but a closer check on ESP Low Power Solutions documentation shows that the deep sleep current is 20uA instead of 10uA. The AP2112K LDO datasheet shows that the quiescent current is 55uA and should be considered when calculating the deep sleep current consumption. So the total current consumption during the deep sleep is more likely be 55+20 = 75uA instead of originally estimated 10uA.

So with those correction, we have a theoritical battery life of 49.61 days. Assuming 20% discharge safety, which is closer to the actual battery life of 47 days.

Operation Current (mA) Duration(sec) Freq/Day Duration/day(H) Total mAh/day
WiFi On 70 2 288 0.160 11.20
LED Blinking Daytime 18 5 192 0.267 4.80
LED Blinking Night 18 5 96 0.133 2.40
DeepSleep 0.075 23.44 1.76
Total mAh 20.16
Battery Capacity 1000.00
Num of Days 49.61

How to extend battery life?

Reduce WiFi on time

So how could we improve the battery life? The table above show that the biggest power drain is the 2s WiFi connection during the wake-up. We need to connect to the WiFi for getting the NTP time when the Clock is power-up for the first time, we needs to sync the NTP clock from time to time so that it won't out of sync, but if we could reduce the usage of WiFi from every 5 minutes to once in every hour, it would significantly reduce the power consumption required for connecting to WiFi.

So the battery consumption during WiFi is in-used is still the same of 70mA, the duration also remains the same as 2 seconds, but the frequency/day drops from 12x24=288/day to just 1x24/day, and the total mAh for WiFi-On will be 0.93mAH/day instead of 11.2mAh/day. Assuming the rest are the same, we now have a theoritical battery life of 0.93 + 4.8 + 0.2 + 1.78 = 7.71mAh, that means 1000/7.71 = 129.68 days!

Reduce LED current without affecting brightness

There is not much we could do about the 5 seconds during the LED blinking, but I decided to take an actual measurement of current consumption versus brightness, and I noticed that the two LEDs took about 3mA when it is on, and changing the 1k resistors to 2k resistors does not make a lot of differences in brigthness difference visually on the LED but the current is drop by half to 1.5mA. This further increase the battery life from 129.66 days to 137.08 days!!

Quiesent Current

The reason that I used AP2112K as the choice of LDO regulator is that I have many of them in my inventory, it is also one of a few that is capable of delivering maximum of 600mA current to support the sudden surge of current when WiFi is in operation, it has Low Dropout (LDO) voltage of 125mA at 300mA and 250mA 600mA operating current at 3.3V regulated output. But it quiescent current of 55uA is not really low especially for long deep sleep application.

Another LDO that I have with me is RT9080 which is pin-to-pin compatible with AP2112k (for SOT-23-5 package), but it has a very low and almost unbelievable quiescent current of 2uA, with slightly higher dropout voltage of 310mA. I decided to replace the AP2112K with the RT9080, this will reduce the total current consumption from 75uA to 22uA, I decided to use slightly higher value of 25uA for calculation, and this add another 31 days of battery life to the equation, bring the estimate battery life from 137 days to 163.68 days!

We can't further reduce the ESP-12S clock frequency below 80MHz as well, as in order to use the WiFi capability the minimum frequency is 80MHz, even though ESP-12S could run at much lower frequency of 10MHz, provided not using WiFi.

Of course the excercise gives the theoritical battery life, in reality, as the actual measurement shows that the battery protection circuit cut-off when battery drop to 2.9v instead of 2.5v(the lowest ESP8266 operating voltage), if we assumed a 10% 10% of battery safety margin, so it will be more realistically at 163.68 x 90% = 147 days. I will do any update on this web page on the actual battery life, but this time I think it should within 147 days to 163 days.

Operation Current (mA) Duration(sec) Freq/Day Duration/day(H) Total mAh/day
WiFi On 70 2 24 0.013 .93
LED Blinking Daytime 16.5 5 192 0.267 4.40
LED Blinking Night 16.5 5 8 0.011 0.18
DeepSleep 0.025 23.709 0.59
Total mAh 6.1094
Battery Capacity 1000.00
Num of Days 163.68

I have running the actual battery test for 100 days now, I should know the actual battery life in the next 45 - 60 days from now if the calculation is correct.

ESP8266 RTC Memory and Software Modification

In order to keep tracking the next NTP update time, we need a variable to keep track when will be the next update. The variable need to be able to survive the deep sleep. The ESP8266 RTC has 512 bytes of RAM that persist across restarts but not during power outages. While this RAM isn't persistent across power outages but it doesn't have the write limitations that flash memory has, making it suitable for frequent write as in our use case.

ESP8266 Arduino Framework has two methods ESP.rtcUserMemoryRead(offset, data, size) for reading from RTC memory and ESP.rtcUserMemoryWrite(offset, data, size) for writing to RTC memory. These methods behind the scene called system_rtc_mem_read(64 + offset, data, size) and system_rtc_mem_read(64 + offset, data, size), so we can simply direct call these functions instead of going through the Arduino ESP Class methods.

ESP8266 RTC is not a Real Time Clock, but a Real Time Counter driving by a not-so-accurate internal oscilation circuit, furthermore, when ESP8266 wake-up from deep sleep, it actually more or less like a reset as all the variables other than those stored in the RTC memory are lost, and this including the NTP time we obtained through the NTC server. So we need to save the time before going into deep sleep into RTC memory and recall the time value from the RTC memory after wake-up as the time until the next NTP sync which is once in every hour. The time in between the NTP sync is an estimation and will drift a little bit but it can be maintained to be within less than 1 minute drift between the NTP sync so it works for our application.

Here is the partial modified code, variables and functions that are same as the previous implmentation are omitted, please refer to the github for the complete code.

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <time.h>
#include <sntp.h>

// Reset Reason
#define POWER_UP_RESET 0
#define HARDWARE_RESET 6

// code omitted

int32_t nextNtpUpdate{0};

time_t now;
struct tm* t;

// other functions omitted

void updateNTP() {

    configTime(DAY_LIGHT_SAVING, TIME_ZONE, ntpServer);
    while (time(nullptr) < UTC_TEST_TIME)
        yield();

    now = time(nullptr);
    t = localtime(&now);
    nextNtpUpdate = (t->tm_hour + NEXT_HOUR) % 24;
    system_rtc_mem_write(64, &nextUpdate, sizeof(nextUpdate));  
    // Serial.printf("Sync with NTP, next Sync %d now=%02d:%02d\n", nextNtpSync, t->tm_hour, t->tm_min);
}

void setup() {

    // Serial.begin(115200);
    // delay(3000);

    struct rst_info *rstInfo = system_get_rst_info();
    system_rtc_mem_read(64, &nextUpdate, sizeof(nextUpdate));  // get nextUpdate value from rtc memory
    system_rtc_mem_read(68, &now, sizeof(now));                // read back the savedTimestamp
    sntp_set_timezone(TIME_ZONE/3600);
    t = localtime(&now);
    // if reset is caused by first power up (reason=0) or current hour is equal to the update hour
    if ((rstInfo->reason == POWER_UP_RESET) | (rstInfo->reason == HARDWARE_RESET) | (nextUpdate == t->tm_hour)) {
        turnOnWiFi();
        updateNTP();
        turnOffWiFi();
    }

    displayStart = millis();

}

void loop() {

    // testLED();

    uint8_t hour = t->tm_hour % 12;
    uint8_t fiveMinuteInterval = t->tm_min / 5;
    uint8_t flashes = t->tm_min % 5;

    turnOnLED(hour);
    delay(1);
    flashLED(fiveMinuteInterval, flashes);
    delay(1);

    if (millis() - displayStart > DISPLAY_TIME) {
        now = now + (time_t) (NIGHT_SLEEP_TIME/1e6 + DISPLAY_TIME/1000);
        system_rtc_mem_write(68, &now, sizeof(now));
        ESP.deepSleep(NIGHT_SLEEP_TIME + DISPLAY_TIME * 1000);
     }

}

When the ESP8266 is first time power up, it has a reset code of 0 (wake-up from deep sleep has a wake-up code of 5), it will then turn on the WiFi and connect to NTP server to get an update of NTP time, and then turn off the WiFi. Once the NTP time is obtained, it will used the current hour value plus 1 as the nextNtpUpdate value and store it into the RTC memory address 64.

If the reset code is not 0, for example, deep sleep has a reset code of 5, the value stored in the RTC memory address 68 will be retrieved as the current time value, and the nextNtpUpdate value read from the RTC memory will be used to determine whether it is hour for the NTP sync.

The LED blinking code in the loop() function is the same as previous, except that before going to deep sleep, the next weak-up time value is saved to the RTC memory address 68 so that the time value can be retrieved on the next wake-up cycle.

Conclusion

I learnt my lesson that when designing a battery powered device, the devil is in the details. By evaluating the system design, both on hardware and software, with careful selection of the right components for deep sleep application and the software approach we chosen, we could improve the battery life to great extend.

Source Code

The source code is available from github.

Leave a Reply

Your email address will not be published. Required fields are marked *

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