diff --git a/CMakeLists.txt b/CMakeLists.txt index d58f3d0..b43354e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,15 +3,6 @@ cmake_minimum_required(VERSION 3.25) -# Initialize Mbed OS build system -set(MBED_TARGET "DISCO_F769NI") -set(MBED_APP_JSON_PATH mbed_app.json) -include(mbed-os/tools/cmake/app.cmake) -add_subdirectory(mbed-os) - -# Globally enable some compiler sanity -add_compile_options(-fwrapv -fno-strict-aliasing -fsigned-char) - # Enable tracing if wanted, on by default option(ENABLE_TRACING "Enable trace logging for debugging" ON) if(ENABLE_TRACING) @@ -19,6 +10,15 @@ add_compile_options(-DMBED_TRACE_MAX_LEVEL=TRACE_LEVEL_DEBUG) endif() +# Globally enable some compiler sanity +add_compile_options(-fwrapv -fno-strict-aliasing -fsigned-char) + +# Initialize Mbed OS build system +set(MBED_TARGET "DISCO_F769NI") +set(MBED_APP_JSON_PATH mbed_app.json) +include(mbed-os/tools/cmake/app.cmake) +add_subdirectory(mbed-os) + # Create SNTP project project(sntp) file(GLOB SNTP_FILES "${PROJECT_SOURCE_DIR}/third_party/sntp/*.cpp") diff --git a/include/Clock.h b/include/Clock.h index 2ed163b..5ea27e4 100644 --- a/include/Clock.h +++ b/include/Clock.h @@ -7,16 +7,21 @@ #define CLOCK_H #include "NetworkInterface.h" +#include "mbed.h" #include "sntp.h" -#include +#include + +typedef std::chrono::time_point kernel_timepoint; +std::chrono::microseconds kernel_to_real_time(kernel_timepoint time); +kernel_timepoint real_to_kernel_time(std::chrono::microseconds time); void clock_setup(void); - -struct timeval clock_time(void); - SNTPError clock_sync(NetworkInterface &net); -const char *clock_timestring(const char *format); -const char *clock_iso8601string(void); +std::chrono::microseconds clock_now(void); + +const char *clock_timestring(std::chrono::microseconds, const char *format); +const char *clock_iso8601string(std::chrono::microseconds); +const char *clock_iso8601string_now(void); #endif diff --git a/src/Clock.cpp b/src/Clock.cpp index 93a1f8c..f14a2ba 100644 --- a/src/Clock.cpp +++ b/src/Clock.cpp @@ -6,31 +6,96 @@ #include "Clock.h" #include "mbed.h" +using namespace std::chrono; + +// The kernel's internal clock duration +// As of writing this is a millisecond +typedef Kernel::Clock::duration kernel_duration; + +// The time the system started in microseconds since 1970 +microseconds boot_start; +// Always use this lock to access boot_start +Mutex boot_start_access; + +// Sets the boot_start time based on kernel uptime and real time +// in microseconds since 1970 +// This is done by removing the uptime from the real time to get the +// time the system booted +// Both kernel_time and real_time should be sampled at the same time +void set_boot_start(kernel_timepoint kernel_time, microseconds real_time) { + microseconds uptime = kernel_time.time_since_epoch(); + boot_start_access.lock(); + boot_start = real_time - uptime; + boot_start_access.unlock(); +} + +// Converts a kernel timepoint to microseconds since 1970 +// This is done by adding boot_start to the kernel time passed +microseconds kernel_to_real_time(kernel_timepoint kernel_time) { + microseconds boot_time = kernel_time.time_since_epoch(); + boot_start_access.lock(); + microseconds real_time = boot_start + boot_time; + boot_start_access.unlock(); + return real_time; +} + +// Converts microseconds since 1970 to a kernel timepoint +// This is done by subtracting boot_start from the real time passed +// The microseconds will be rounded to the kernel clock duration +kernel_timepoint real_to_kernel_time(microseconds real_time) { + boot_start_access.lock(); + microseconds uptime = real_time - boot_start; + boot_start_access.unlock(); + kernel_duration uptime_rounded = round(uptime); + kernel_timepoint kernel_time(uptime_rounded); + return kernel_time; +} + +// Initializes the clock, run once at boot void clock_setup(void) { - // mbed reads the RTC at boot, do nothing for now + // Get RTC time + time_t rtc_time = time(NULL); + // Update boot start time + microseconds rtc_time_microsecs = seconds(rtc_time); + kernel_timepoint kernel_now = Kernel::Clock::now(); + set_boot_start(kernel_now, rtc_time_microsecs); } -struct timeval clock_time() { - struct timeval timeval; - timeval.tv_sec = time(NULL); - timeval.tv_usec = 0; - return timeval; -} - +// Syncs the clock using SNTP, run any time SNTPError clock_sync(NetworkInterface &net) { + // Get time from SNTP struct timeval time; SNTPError err; err = sntp(net, "time.google.com", 123, &time); if (err != SNTPSuccess) { return err; } + // Set RTC time set_time(time.tv_sec); + // Convert SNTP timeval to microseconds + microseconds time_microsecs(0); + time_microsecs += seconds(time.tv_sec); + time_microsecs += microseconds(time.tv_usec); + // Update boot start time + kernel_timepoint kernel_now = Kernel::Clock::now(); + set_boot_start(kernel_now, time_microsecs); return SNTPSuccess; } -const char *clock_timestring(const char *format) { - struct timeval time = clock_time(); - struct tm *local_time = localtime(&time.tv_sec); +// Gets the current clock time in microseconds since 1970 +microseconds clock_now(void) { + kernel_timepoint kernel_now = Kernel::Clock::now(); + return kernel_to_real_time(kernel_now); +} + +// Formats a time in microseconds using strftime +// Returned value is a buffer containing the formatted string +// or "(time overflow)" +// Calling this function overwrites the previous buffer contents +// so if you want to preserve it please copy the string +const char *clock_timestring(microseconds time, const char *format) { + time_t time_seconds = ceil(time).count(); + struct tm *local_time = localtime(&time_seconds); static char buffer[64]; size_t buffer_size = sizeof(buffer); @@ -45,6 +110,32 @@ } } -const char *clock_iso8601string(void) { - return clock_timestring("%Y-%m-%dT%H:%M:%S"); +// Formats a time in microseconds according to ISO 8601 +// Returned value is a buffer containing the formatted string +// or "(iso8601 overflow)" +// Calling this function overwrites the previous buffer contents +// so if you want to preserve it please copy the string +const char *clock_iso8601string(microseconds time) { + const char *timestring = clock_timestring(time, "%Y-%m-%dT%H:%M:%S"); + long only_microseconds = time.count() % 1000000; + + static char buffer[128]; + int buffer_size = sizeof(buffer); + int written; + written = snprintf( + buffer, buffer_size, "%s.%06li", timestring, only_microseconds); + + if (written >= buffer_size) { + // It overflowed, so return something useful + return "(iso8601 overflow)"; + } else { + return buffer; + } +} + +// Formats the current time in microseconds according to ISO 8601 +// This is a quick wrapper for clock_iso8601string using the current +// clock time. Read its documentation for full usage details +const char *clock_iso8601string_now(void) { + return clock_iso8601string(clock_now()); } diff --git a/src/main.cpp b/src/main.cpp index a008a4b..985c200 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,7 +49,7 @@ tr_info("Project Tardis"); clock_setup(); - tr_info("The time is %s", clock_iso8601string()); + tr_info("The time is %s", clock_iso8601string_now()); mountFilesystem(mainBD, mainFS); @@ -82,14 +82,22 @@ NetworkInterface *net = network_interface(); tr_info("Syncing clock..."); tr_info("The current time is %s", - clock_iso8601string()); + clock_iso8601string_now()); SNTPError err = clock_sync(*net); if (err != SNTPSuccess) { tr_err("Failed to sync clock, error %i", err); } else { tr_info("The new time is %s", - clock_iso8601string()); + clock_iso8601string_now()); } + // Wait until the nearest second + std::chrono::microseconds future = clock_now(); + future += std::chrono::seconds(1); + future = std::chrono::floor( + future); + kernel_timepoint target = real_to_kernel_time(future); + ThisThread::sleep_until(target); + tr_info("Waited to %s", clock_iso8601string_now()); } }