Created
June 5, 2023 21:19
-
-
Save sigmdel/bea3b4065c6fdf2cc2d3c9c7fb1ddca0 to your computer and use it in GitHub Desktop.
Convert microseconds to 32 bit fraction of a second (for conversion of timeval struct to 64 bit NTP timestamp)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
How to convert microseconds to 32 bit fraction of a second. This | |
calculation is needed when converting a struct timeval to an | |
NTP 64 bit timestamp. | |
Four methods are tested with four ESP32 processors. Here | |
uint32_t y_us is the microseconds input | |
uint32_t x is the 32 bit fraction of a second ouput | |
A) x = (uint32_t) ( ((uint64_t) y_us * 4294967296) / 1000000); | |
B) x = (uint32_t) ( ((uint64_t) y_us << 32) / 1000000 ); | |
C) x = (uint32_t) ((float) y_us * floatfactor); | |
D) x = (uint32_t) ((double) y_us * doublefactor); | |
where | |
const float floatfactor = (float) 4294967296.0/1000000.0; | |
const double doublefactor = (double) 4294967296.0/1000000.0; | |
Here is a table of the calculted conversions for 9 microsecond values. | |
Test A | |
y_us -> x ; y_us as % x as % | |
0 -> 0 ; 0.00000000 0.00000000 | |
1 -> 4294 ; 0.00010000 0.00009998 | |
10 -> 42949 ; 0.00100000 0.00099998 | |
100 -> 429496 ; 0.01000000 0.00999998 | |
1000 -> 4294967 ; 0.10000000 0.09999999 | |
10000 -> 42949672 ; 1.00000000 0.99999998 | |
100000 -> 429496729 ; 10.00000000 9.99999999 | |
999999 -> 4294963001 ; 99.99990000 99.99990000 | |
1000000 -> 0 ; 100.00000000 0.00000000 | |
Test A took 43 milliseconds, 43122 microseconds | |
Test B | |
y_us -> x ; y_us as % x as % | |
0 -> 0 ; 0.00000000 0.00000000 | |
1 -> 4294 ; 0.00010000 0.00009998 | |
10 -> 42949 ; 0.00100000 0.00099998 | |
100 -> 429496 ; 0.01000000 0.00999998 | |
1000 -> 4294967 ; 0.10000000 0.09999999 | |
10000 -> 42949672 ; 1.00000000 0.99999998 | |
100000 -> 429496729 ; 10.00000000 9.99999999 | |
999999 -> 4294963001 ; 99.99990000 99.99990000 | |
1000000 -> 0 ; 100.00000000 0.00000000 | |
Test B took 43 milliseconds, 42301 microseconds | |
Test C | |
y_us -> x ; y_us as % x as % | |
0 -> 0 ; 0.00000000 0.00000000 | |
1 -> 4294 ; 0.00010000 0.00009998 | |
10 -> 42949 ; 0.00100000 0.00099998 | |
100 -> 429496 ; 0.01000000 0.00999998 | |
1000 -> 4294967 ; 0.10000000 0.09999999 | |
10000 -> 42949672 ; 1.00000000 0.99999998 | |
100000 -> 429496736 ; 10.00000000 10.00000015 | |
999999 -> 4294962944 ; 99.99990000 99.99989867 | |
1000000 -> 4294967295 ; 100.00000000 99.99999998 | |
Test C took 32 milliseconds, 32182 microseconds | |
Test D | |
y_us -> x ; y_us as % x as % | |
0 -> 0 ; 0.00000000 0.00000000 | |
1 -> 4294 ; 0.00010000 0.00009998 | |
10 -> 42949 ; 0.00100000 0.00099998 | |
100 -> 429496 ; 0.01000000 0.00999998 | |
1000 -> 4294967 ; 0.10000000 0.09999999 | |
10000 -> 42949672 ; 1.00000000 0.99999998 | |
100000 -> 429496729 ; 10.00000000 9.99999999 | |
999999 -> 4294963001 ; 99.99990000 99.99990000 | |
1000000 -> 4294967295 ; 100.00000000 99.99999998 | |
Test D took 42 milliseconds, 42410 microseconds | |
The last value, 1000000 µs, is out of range since it correspond to | |
a full second which should be in the other field of the | |
timeval struct. It is interesting that the floating point | |
implementations give "better" results in that case, but there | |
shoud nevertheless be some check to ensure that the | |
microsecond value is in the range 0..999999 before performing a | |
conversion. | |
The rounding errors with the single precision float calculations give | |
different 32 bit stamps compared to the other three methods. However | |
the actual error is not very significant when looking at the | |
32 bit stamp value as a percentage of a second. | |
The timing results are perhaps surprising. The integer math | |
implementations are faster than the floating math implementations with | |
the single core ESP32s. However, for the two core ESP32, the float | |
implementation is the fastest, followed by the integer implementation | |
and the double math implementation is the slowest. For the single | |
core ESP32, the double precision float implementation is faster than | |
the single precision implementation which seems odd unless all floating | |
point operations are done in double precision which require extra | |
casting operation from single to double and then back from double to | |
single precision if using the float type. | |
Timing Results | |
ESP32-C3: | |
Test A (int mult) time: 2500.000000 ms, 2523008.000000 µs | |
Test B (int shift) time: 2500.000000 ms, 2407500.000000 µs | |
Test C (float mult) time: 6000.000000 ms, 6283005.000000 µs | |
Test D (double mult) time: 4500.000000 ms, 4505004.000000 µs | |
Test A (int mult) average time: 0.001111 ms, 1.121337 µs | |
Test B (int shift) average time: 0.001111 ms, 1.070000 µs | |
Test C (float mult) average time: 0.002667 ms, 2.792447 µs | |
Test D (double mult) average time: 0.002000 ms, 2.002224 µs | |
ESP32-S2: | |
Test A (int mult) time: 1481.000000 ms, 1226958.000000 µs | |
Test B (int shift) time: 1000.000000 ms, 1082506.000000 µs | |
Test C (float mult) time: 2000.000000 ms, 2690013.000000 µs | |
Test D (double mult) time: 1501.000000 ms, 1738998.000000 µs | |
Test A (int mult) average time: 0.000658 ms, 0.545315 µs | |
Test B (int shift) average time: 0.000444 ms, 0.481114 µs | |
Test C (float mult) average time: 0.000889 ms, 1.195561 µs | |
Test D (double mult) average time: 0.000667 ms, 0.772888 µs | |
ESP32: | |
Test A (int mult) time: 1001.000000 ms, 1179501.000000 µs | |
Test B (int shift) time: 1000.000000 ms, 1067500.000000 µs | |
Test C (float mult) time: 1000.000000 ms, 632000.000000 µs | |
Test D (double mult) time: 2000.000000 ms, 1744000.000000 µs | |
Test A (int mult) average time: 0.000445 ms, 0.524223 µs | |
Test B (int shift) average time: 0.000444 ms, 0.474444 µs | |
Test C (float mult) average time: 0.000444 ms, 0.280889 µs | |
Test D (double mult) average time: 0.000889 ms, 0.775111 µs | |
ESP32-S3: | |
Test A (int mult) time: 1500.000000 ms, 1121999.000000 µs | |
Test B (int shift) time: 1000.000000 ms, 1036000.000000 µs | |
Test C (float mult) time: 1000.000000 ms, 536000.000000 µs | |
Test D (double mult) time: 1500.000000 ms, 1680000.000000 µs | |
Test A (int mult) average time: 0.000667 ms, 0.498666 µs | |
Test B (int shift) average time: 0.000444 ms, 0.460444 µs | |
Test C (float mult) average time: 0.000444 ms, 0.238222 µs | |
Test D (double mult) average time: 0.000667 ms, 0.746667 µs | |
*/ | |
#include <Arduino.h> | |
// Conversion factors for floating point calculations | |
const float floatfactor = (float) 4294967296.0/1000000.0; | |
const double doublefactor = (double) 4294967296.0/1000000.0; | |
bool CheckTest = true; | |
uint32_t y_us = 1; // microseconds | |
uint32_t x = 0; // NTP timestamp 32-bit fraction of second | |
#define SIGNIFICANT_DIGITS 8 // 6, 7, 8 | |
void printHeader(char * testname) { | |
Serial.printf("Test %s\n", (const char*) testname); | |
#if SIGNIFICANT_DIGITS >= 8 | |
Serial.println(" y_us -> x ; y_us as % x as %"); // 8 | |
#elif SIGNIFICANT_DIGITS == 7 | |
Serial.println(" y_us -> x ; y_us as % x as %"); // 7 | |
#else | |
Serial.println(" y_us -> x ; y_us as % x as %"); // 6 | |
#endif | |
} | |
// Show microsecond and calculated fractions, then show both as a % of a second | |
void printConversion(void) { | |
Serial.printf("%7u -> %10u", y_us, x); | |
double uspercent = (100.0*y_us) / 1000000.0; | |
double fpercent = (100.0*x) / 4294967296.0; | |
#if SIGNIFICANT_DIGITS >= 8 | |
Serial.printf(" ; %12.8f %12.8f\n", uspercent, fpercent); | |
#elif SIGNIFICANT_DIGITS == 7 | |
Serial.printf(" ; %11.7f %11.7f\n", uspercent, fpercent); | |
#else | |
Serial.printf(" ; %10.6f %10.6f\n", uspercent, fpercent); | |
#endif | |
} | |
// Accumulated time for each runTest_X(count) run | |
uint32_t time_A_ms = 0; | |
uint32_t time_B_ms = 0; | |
uint32_t time_C_ms = 0; | |
uint32_t time_D_ms = 0; | |
uint32_t time_A_us = 0; | |
uint32_t time_B_us = 0; | |
uint32_t time_C_us = 0; | |
uint32_t time_D_us = 0; | |
uint32_t tcount = 0; // count # of conversions in each test | |
void clearTimes(void) { | |
time_A_ms = 0; | |
time_B_ms = 0; | |
time_C_ms = 0; | |
time_D_ms = 0; | |
time_A_us = 0; | |
time_B_us = 0; | |
time_C_us = 0; | |
time_D_us = 0; | |
tcount = 0; | |
} | |
#define TEST_VALUES_COUNT 9 | |
uint32_t values[9] { 0, 1, | |
10, 100, | |
1000, 10000, | |
100000, 999999, | |
1000000 }; // last value is overflow! | |
void runTest_A(int count=1) { | |
if (CheckTest) | |
printHeader("A"); | |
unsigned long start_ms = millis(); | |
unsigned long start_us = micros(); | |
while (count) { | |
for (int i=0; i < TEST_VALUES_COUNT; i++) { | |
y_us = values[i]; | |
//x = (uint32_t) ( ((unsigned long long) y_us * 4294967296) / 1000000); | |
x = (uint32_t) ( ((uint64_t) y_us * 4294967296) / 1000000); | |
if (CheckTest) | |
printConversion(); | |
else | |
tcount++; | |
} | |
count--; | |
} | |
unsigned long runtime_us = micros() - start_us; | |
unsigned long runtime_ms = millis() - start_ms; | |
time_A_ms += runtime_ms; | |
time_A_us += runtime_us; | |
if (CheckTest) | |
Serial.printf("Test A took %u milliseconds, %u microseconds\n\n", runtime_ms, runtime_us); | |
} | |
void runTest_B(int count=1) { | |
if (CheckTest) | |
printHeader("B"); | |
unsigned long start_ms = millis(); | |
unsigned long start_us = micros(); | |
while (count) { | |
for (int i=0; i< TEST_VALUES_COUNT; i++) { | |
y_us = values[i]; | |
x = (uint32_t) ( ((unsigned long long) y_us << 32) / 1000000 ); | |
//x = (uint32_t) ( ((uint64_t) y_us << 32) / 1000000 ); | |
if (CheckTest) | |
printConversion(); | |
} | |
count--; | |
} | |
unsigned long runtime_us = micros() - start_us; | |
unsigned long runtime_ms = millis() - start_ms; | |
time_B_ms += runtime_ms; | |
time_B_us += runtime_us; | |
if (CheckTest) | |
Serial.printf("Test B took %u milliseconds, %u microseconds\n\n", runtime_ms, runtime_us); | |
} | |
void runTest_C(int count=1) { | |
if (CheckTest) | |
printHeader("C"); | |
unsigned long start_ms = millis(); | |
unsigned long start_us = micros(); | |
while (count) { | |
for (int i=0; i < TEST_VALUES_COUNT; i++) { | |
y_us = values[i]; | |
x = (uint32_t) (((float) y_us) * floatfactor); | |
if (CheckTest) | |
printConversion(); | |
} | |
count--; | |
} | |
unsigned long runtime_us = micros() - start_us; | |
unsigned long runtime_ms = millis() - start_ms; | |
time_C_ms += runtime_ms; | |
time_C_us += runtime_us; | |
if (CheckTest) | |
Serial.printf("Test C took %u milliseconds, %u microseconds\n\n", runtime_ms, runtime_us); | |
} | |
void runTest_D(int count=1) { | |
if (CheckTest) | |
printHeader("D"); | |
unsigned long start_ms = millis(); | |
unsigned long start_us = micros(); | |
while (count) { | |
for (int i=0; i < TEST_VALUES_COUNT; i++) { | |
y_us = values[i]; | |
x = (uint32_t) (((double) y_us) * doublefactor); | |
if (CheckTest) | |
printConversion(); | |
} | |
count--; | |
} | |
unsigned long runtime_us = micros() - start_us; | |
unsigned long runtime_ms = millis() - start_ms; | |
time_D_ms += runtime_ms; | |
time_D_us += runtime_us; | |
if (CheckTest) | |
Serial.printf("Test D took %u milliseconds, %u microseconds\n\n", runtime_ms, runtime_us); | |
} | |
void setup() { | |
#ifdef SERIAL_BAUD | |
Serial.begin(SERIAL_BAUD); | |
#else | |
Serial.begin(); // ESP32C3, | |
#endif | |
delay(5000); | |
Serial.println("\nConvert timeval.tv_usec to NTP timestamp 32 bit fraction of second\n"); | |
runTest_A(); | |
runTest_B(); | |
runTest_C(); | |
runTest_D(); | |
CheckTest = false; | |
clearTimes(); | |
Serial.println("Completed setup(), starting time test"); | |
} | |
#define COUNTS 500 | |
#define LOOPS 500 | |
int loops = LOOPS; | |
void loop(void) { | |
if (loops>0) { | |
Serial.print("."); | |
runTest_A(COUNTS); | |
delay(10); | |
runTest_B(COUNTS); | |
delay(10); | |
runTest_C(COUNTS); | |
delay(10); | |
runTest_C(COUNTS); | |
delay(10); | |
runTest_D(COUNTS); | |
} else if (loops == 0) { | |
Serial.println("\n Timing Results\n"); | |
Serial.printf("Test A (int mult) time: %f ms, %f µs\n", ((double) time_A_ms), ((double) time_A_us)); | |
Serial.printf("Test B (int shift) time: %f ms, %f µs\n", ((double) time_B_ms), ((double) time_B_us)); | |
Serial.printf("Test C (float mult) time: %f ms, %f µs\n", ((double) time_C_ms), ((double) time_C_us)); | |
Serial.printf("Test D (double mult) time: %f ms, %f µs\n\n", ((double) time_D_ms), ((double) time_D_us)); | |
double calcs = (double) LOOPS*COUNTS*TEST_VALUES_COUNT; | |
Serial.printf("Test A (int mult) average time: %f ms, %f µs\n", ((double) time_A_ms) / calcs, ((double) time_A_us) / calcs); | |
Serial.printf("Test B (int shift) average time: %f ms, %f µs\n", ((double) time_B_ms) / calcs, ((double) time_B_us) / calcs); | |
Serial.printf("Test C (float mult) average time: %f ms, %f µs\n", ((double) time_C_ms) / calcs, ((double) time_C_us) / calcs); | |
Serial.printf("Test D (double mult) average time: %f ms, %f µs\n", ((double) time_D_ms) / calcs, ((double) time_D_us) / calcs); | |
Serial.printf("\nCalcs: %f, tcount: %u\n", calcs, tcount); | |
} | |
loops--; | |
} | |
/* | |
; file: platformio.ini | |
; | |
; PlatformIO Project Configuration File | |
; | |
; Please visit documentation for the other options and examples | |
; https://docs.platformio.org/page/projectconf.html | |
[platformio] | |
default_envs = seeed_xiao_esp32c3 | |
;default_envs = lolin_s2_mini | |
;default_envs = lolin32_lite | |
;default_envs = seeed_xiao_esp32s3 | |
[extra] | |
monitor_baud = 115200 ; for non USB-CDC boards such as Lolin32 Lite | |
[env] | |
framework = arduino | |
platform = espressif32 | |
[env:seeed_xiao_esp32c3] | |
board = seeed_xiao_esp32c3 | |
monitor_speed = 460800 | |
build_flags = | |
-DCORE_DEBUG_LEVEL=0 | |
[env:seeed_xiao_esp32s3] | |
board = seeed_xiao_esp32s3 | |
monitor_speed = 460800 | |
build_flags = | |
-DCORE_DEBUG_LEVEL=0 | |
[env:lolin32_lite] | |
board = lolin32_lite | |
monitor_speed = ${extra.monitor_baud} | |
monitor_port = /dev/ttyUSB0 | |
upload_port = /dev/ttyUSB0 | |
build_flags = | |
-DCORE_DEBUG_LEVEL=0 | |
-DSERIAL_BAUD=${extra.monitor_baud} | |
[env:lolin_s2_mini] | |
board = lolin_s2_mini | |
build_flags = | |
-DCORE_DEBUG_LEVEL=0 | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment