3.2.1.1 Host Timestamp Synchronization Example [T-Series Datasheet]
Synchronizing CORE_TIMER readings to a System Timestamp
To simplify this example, let's assume that it takes zero time to read the CORE_TIMER and then get a system timestamp. After starting stream, you can get a CORE_TIMER reading and correlate the timestamp to the stream start timestamp like the following:
# The core timer is a uint32 value that will overflow/rollover
def tickDiffWithRoll(start, end):
diffTicks = 0
if end < start: # The core timer rolled over, account for it
diffTicks = 0xFFFFFFFF - start + end
else:
diffTicks = end - start
return diffTicks
synchCoreRead = ljm.eReadName(handle, "CORE_TIMER")
sysTimestamp = time.time()
# startTime is the var storing the stream start timestamp
diffTicks = tickDiffWithRoll(startTime, synchCoreRead)
# coreTicksPerSecond would be 40000000 on a T7
diffSeconds = diffTicks / coreTicksPerSecond
# system timestamp corresponding to the start of stream
streamStartTimeSystemAligned = sysTimestamp - diffSeconds
You could stop here, using the system timestamp corresponding to the start of stream to approximate each scan timestamp. The system clock and LabJack clock will drift from each other over time, so doing this would not be an accurate way to synchronize the timestamps:
scanTimeSinceStreamStart = scanNumber / scanRate
scanTimestamp = streamStartTimeSystemAligned + scanTimeSinceStreamStart
Re-synchronizing Clocks
One way to re-synchronize the CORE_TIMER to the system/host clock is to read the CORE_TIMER and system timestamp at some later time and calculate the elapsed time since your last synchronization. The difference between the core clock timed duration and system timestamp timed duration is the amount that the clocks drifted in that time. From there, you can realign the start timestamp based on the drift value. Say you still have access to the synchCoreRead
and sysTimestamp
you grabbed at the start of stream, then you can do the same synchronization process again and calculate the differences:
newCoreRead = ljm.eReadName(handle, "CORE_TIMER")
newSysTimestamp = time.time()
newDiffTicks = tickDiffWithRoll(synchCoreRead, newCoreRead)
newDiffSeconds = newDiffTicks / coreTicksPerSecond
diffSysTimestamps = newSysTimestamp - sysTimestamp
drift = diffSysTimestamps - newDiffSeconds
streamStartTimeSystemAligned += drift
The CORE_TIMER is a 32-bit unsigned integer system timer. As such, the CORE_TIMER value will roll over back to 0 every 2^32 / coreTimerFrequency
seconds. The timestamp re-synchronization interval should be faster than this time period; longer intervals cannot be tracked properly. The CORE_TIMER runs at half the device core clock speed. See Appendix A-5 for core clock specifications.
Accounting for CORE_TIMER Read Overhead
The example above assumes that it takes zero time to capture the CORE_TIMER reading, but in practice it might take a millisecond or more. This introduces an error to the calculation, however some of this overhead can be accounted for.
On average, the amount of time for the LabJack to receive the CORE_TIMER read command and grab its value is about half of the total command-response overhead time. For example, if you find that it takes 1 millisecond to complete a CORE_TIMER read, the CORE_TIMER value was likely grabbed about 0.5 ms before you received the value in software. You could then subtract 0.5 ms from your synchronizing CORE_TIMER readings to more accurately align the CORE_TIMER to your system clock.
Calculating the Average Time to Read the CORE_TIMER
If the CORE_TIMER is read using command-response mode, the time it takes to read the CORE_TIMER in a command can occasionally take much longer than usual. These readings should be tossed and the CORE_TIMER value should be re-acquired to avoid excessive error into the timestamp calculations. To define what the usual command time is, you can take the average of the CORE_TIMER several times at the start of your program. For example:
averageReadTime = 0
numIts = 5
for val in range(numIts):
start = time.time()
ljm.eReadName(handle, "CORE_TIMER")
end = time.time()
diff = end - start
averageReadTime += diff
averageReadTime = averageReadTime/numIts
To tell if a particular read took a long time, you could take a system timestamp both before and after the synchronization CORE_TIMER read; the difference between the system timestamps would be the time it took to read the CORE_TIMER.