Most examples for setting the RTC on an ESP32 use WiFi or Ethernet connection and the builtin SNTP library or the NTPClient library. However, if a device operates in an environment without WiFi over 3G (as in our case), setting the RTC is not so straightforward.

This is just a quick note to understand the workings of timeservers and synchronizing the RTC using SNTP over TCP and UDP connections.

Getting the time isn't as easy as you'd think. Most examples for setting the RTC on an ESP32 use WiFi or Ethernet connection and the builtin SNTP library or the NTPClient library. However, if a device operates in an environment without WiFi over 3G (as in our case), setting the RTC is not so straightforward.

This is just a quick note to understand the workings of timeservers and synchronizing the RTC using SNTP.

The ESP32

An RTC is a real time clock which is capable of keeping accurate time. You can get access to time functions by including both <time.h> and <sys/time.h> as dependencies int your header files. The following code snippets are useful:

Getting the time

If you want to get time accurate the microsecond, you need to use the following functions.

    timeval tim; //Contains secs and usecs both as signed longs

    timezone tzone;     //Can set the timezone if you want it auto translated
    tzone.tz_dsttime = 0;
    tzone.tz_minuteswest = 0;

    gettimeofday(&tim, &tzone);

    uint64_print_hex((uint64_t) tim.tv_sec);    //I wrote these print functions. Will print out HEX rep of numbers
    uint64_print_hex((uint64_t) tim.tv_usec);

Setting the Time

This sets the clock to 100 seconds, 500 microseconds. Represents the Unix Epoch.

    struct timeval tv = { .tv_sec = 100, .tv_usec = 500 };
    settimeofday(&tv, NULL);

Time in General

Basically there is a standard way of representing time using an epoch. It works in the following way

  • Choose a start date (The unix epoch start data is midnight Jan 1st 1970)
  • Choose the precision you'll use to measure time (The unix epoch is in seconds)
  • Choose the size that an epoch value has ( The unix epoch is represented as a signed 32-bit integer )

Once you've done this you can measure time. However, there are a bunch of different standards that need to be translated so it's super important that if you're working with time you know exactly how the epoch and precision is calculated. The two I have come across are:

  • Unix Epoch, Seconds since midnight Jan 1st 1970 as 32-bit signed int
  • 1900 Epoch, microseconds since Jan 1st 1900 as 64-bit unsigned int

Inside the ESP32 as far as I can tell, time is represented by 64 bits, the first 32 bits are the unix epoch and the last 32 bits are the fractional micro second part.

Synchronizing Time

So now we have a standardized(ish) time measure. We need to tell our devices what time it is. This is where the SNTP (Simple Network Time Protocol) comes in to play.

We're using SNTP instead of NTP because we're working on an embedded system that requires speed. A really good site to understand is

I'll give a brief explanation though.

Time Servers

There are time servers set up that only keep time. You send them a request for time and they return the time. A good open time service is for world side NTP servers.

UDP

The request is sent over UDP. That means no socket is created. You simply throw your packet into the void towards an address and hopefully it'll get there. There is no guarantee so no response might mean the packet didn't make it. Normally though, if you sent the right packet to the right place it'll work.

All SNTP request packets are sent the port 123. (SUPER IMPORTANT)

SNTP Request Packet

This is the nitty gritty. But I'll explain quick. Use this link if you want more info

The request packet is 48 bytes, many of which are 0s. The byte contents are listed below with chosen values for the packet sent from the device.

  1. Leap Indicator (2 bits) - Used to indicate leap seconds - set to 3 (unsynced)
  2. Version (3 bits) - We're at version 4 - set to 4
  3. Mode (3 bits) - says who packet sender is - set to 4 (client)
  4. Stratum (8 bits) - quality of time source - set to 0 (not set yet)
  5. Poll (8 bits) - log2 of seconds between send and rec of packet - set to 6 (64 seconds)
  6. Precision (8 bits) - log2 of precision of system clock - set to -18 (micro second precision)
  7. Root Delay(32 bits) - This comes from server - set to 0
  8. Root Dispersion (32 bits) - This comes from server - set to 0
  9. Reference Id (32 bits) - Gives some meta info, arbitrary - set to 0
  10. Reference Timestamp(64 bits) - This comes from server - set to 0
  11. Originate Timestamp(64 bits) - This comes from server - set to 0
  12. Receive Timestamp(64 bits) - This comes from server - set to 0
  13. Transmit Timestamp(64 bits) - This comes from server - set to 0

If sent the server successfully and the server isn't over loaded, a packet of the same size is returned with the server values filled in. From here you can do calculations. The most imporant values are the following:

  • Reference Timestamp: Last time the system clock was set
  • Originate Timestamp: Time request departs client
  • Receive Timestamp: Time request arrives at server
  • Transmit Timestamp: Time server reply left server

You should also record the time the server replay was received inside the device. From these numbers you calculate the time.

The 64 bit timestamp values

The 64 bit value is 32 bits in seconds (unsigned value) followed by a 32 bit fraction.

It represents the unit in time in seconds from the 1 January 1900 epoch, meaning that it will cycle in 2036 (two years before the unix epoch in 2038)

The smallest time fraction that can represented in this format is 232 picoseconds

Blog Comments powered by Disqus.

Next Post Previous Post