Getting precise NTP synchronized microsecond timestamps using ESP-IDF and ESP32

 Date: August 5, 2023

Sensor values are pretty much worthless without absolute timestamps.

The great thing: it’s easy to get them using ESP-IDF and their NTP implementation.

#include <stdio.h>
#include <time.h>
#include "esp_log.h"
#include "esp_sntp.h"

#define TIMER_INTERVAL_MS (10 * 60 * 1000)   /* 10 Minutes */

static const char *TAG = "NTP";

void time_init()
{
    esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
    esp_sntp_setservername(0, "pool.ntp.org");
    esp_sntp_init();
}

/* Routine to force a blocking sync during the init phase */
void time_sync()
{
    ESP_LOGI(TAG, "Waiting for SNTP synchronization...");
    time_t now = 0;
    struct tm timeinfo = {0};

    int retry = 0;
    const int retry_count = 10;  
    while (timeinfo.tm_year < (2020 - 1900) && ++retry < retry_count) {
        ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
        vTaskDelay(2000 / portTICK_PERIOD_MS);
        time(&now);
        localtime_r(&now, &timeinfo);
    }

    if (timeinfo.tm_year < (2020 - 1900)) {
        ESP_LOGW(TAG, "SNTP synchronization failed");
    } else {
        ESP_LOGI(TAG, "SNTP synchronized. Timestamp: %04d-%02d-%02d %02d:%02d:%02d",
                 timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
                 timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
    }
}
void time_task(void *pvParameters) {
    while (1) {
        // Wait for sync with ntp server
        while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET) {
            vTaskDelay(100 / portTICK_PERIOD_MS);
        }

        // Give it some time to settle
        vTaskDelay(100 / portTICK_PERIOD_MS);

        // Pull time out of RTC
        struct timeval now;
        gettimeofday(&now, NULL);

        // Show refreshed time
        ESP_LOGI(TAG, "Refreshed time: %s", ctime(&now.tv_sec));

        // Wait for next NTP sync
        vTaskDelay(TIMER_INTERVAL_MS / portTICK_PERIOD_MS);
    }
}

The update_time_task is refreshing the time every 10 minutes to fix any clock shifts.

From the application site you then can convert the time into microseconds.

(...)
    struct timeval now;
    gettimeofday(&now, NULL);
    uint64_t timestamp = (uint64_t)now.tv_sec * 1000000ULL + (uint64_t)now.tv_usec;
(...)

To verify this implementation I configured a Bosch BMP390L pressure sensor to a sampling rate of 12.5Hz and added a timestamp immediately after pulling the value out of the registers.

Pa                  Timestamp
98838.4393593062    2023-08-02T07:39:55.874285Z
98838.7389599497    2023-08-02T07:39:55.954506Z

Calculating at the time off a full sampling cycle (0.954506s - 0.87428s) = 0.080226s this results into a frequeny of 1/0.080226s = 12.46 Hz.

Looks Good! :-)


Previous
⏪ Infraschallmessungen mit einem Sensirion SDP600-25Pa

Next
Großes Update für das Infraschallmikrofon mit Sensirion SDP600-25Pa ⏩