-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clock correction tests #328
Changes from 8 commits
4d409a6
792d5c9
0597dd6
755a628
bd0acb7
7bd77e4
10fa8a9
756d133
8b3fd34
2de29c2
0761306
51dc39f
e53a36a
9c8f436
54b1b7a
2bd22e4
67d7af4
2731055
143e0e0
c9a2d00
779cca9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
Exposes the public API used for clock correction in an Object Oriented way (and hides the functions that are only part of the internal implementation). | ||
*/ | ||
|
||
#ifndef clockCorrectionEngine_h | ||
#define clockCorrectionEngine_h | ||
|
||
#include <stdbool.h> | ||
#include "clockCorrectionStorage.h" | ||
|
||
typedef struct { | ||
double (*getClockCorrection)(clockCorrectionStorage_t* storage); | ||
double (*calculateClockCorrection)(const uint64_t new_t_in_cl_reference, const uint64_t old_t_in_cl_reference, const uint64_t new_t_in_cl_x, const uint64_t old_t_in_cl_x); | ||
bool (*updateClockCorrection)(clockCorrectionStorage_t* storage, const double clockCorrectionCandidate); | ||
|
||
} clockCorrectionEngine_t; | ||
|
||
extern clockCorrectionEngine_t clockCorrectionEngine; | ||
|
||
#endif /* clockCorrectionEngine_h */ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/** | ||
Exposes all the functions used for clock correction, for testing purposes. | ||
*/ | ||
|
||
#ifndef clockCorrectionFunctions_h | ||
#define clockCorrectionFunctions_h | ||
|
||
#include <stdbool.h> | ||
#include "clockCorrectionStorage.h" | ||
|
||
double getClockCorrection(clockCorrectionStorage_t* storage); | ||
uint64_t truncateTimeStampFromDW1000(uint64_t timeStamp); | ||
void fillClockCorrectionBucket(clockCorrectionStorage_t* storage); | ||
bool emptyClockCorrectionBucket(clockCorrectionStorage_t* storage); | ||
double calculateClockCorrection(const uint64_t new_t_in_cl_reference, const uint64_t old_t_in_cl_reference, const uint64_t new_t_in_cl_x, const uint64_t old_t_in_cl_x); | ||
bool updateClockCorrection(clockCorrectionStorage_t* storage, const double clockCorrectionCandidate); | ||
|
||
#endif /* clockCorrectionFunctions_h */ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/** | ||
Exposes the storage required by the clock correction engine. | ||
*/ | ||
|
||
#ifndef clockCorrectionStorage_h | ||
#define clockCorrectionStorage_h | ||
|
||
#include <stdint.h> | ||
|
||
typedef struct { | ||
double clockCorrection; | ||
unsigned int clockCorrectionBucket; | ||
} clockCorrectionStorage_t; | ||
|
||
|
||
#endif /* clockCorrectionStorage_h */ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#include "clockCorrectionEngine.h" | ||
#include "clockCorrectionFunctions.h" | ||
|
||
clockCorrectionEngine_t clockCorrectionEngine = { | ||
.getClockCorrection = getClockCorrection, | ||
.calculateClockCorrection = calculateClockCorrection, | ||
.updateClockCorrection = updateClockCorrection | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
#include "clockCorrectionFunctions.h" | ||
|
||
#define MAX_CLOCK_DEVIATION_SPEC 10e-6 | ||
#define CLOCK_CORRECTION_SPEC_MIN (1.0 - MAX_CLOCK_DEVIATION_SPEC * 2) | ||
#define CLOCK_CORRECTION_SPEC_MAX (1.0 + MAX_CLOCK_DEVIATION_SPEC * 2) | ||
|
||
#define CLOCK_CORRECTION_ACCEPTED_NOISE 0.03e-6 | ||
#define CLOCK_CORRECTION_FILTER 0.1 | ||
#define CLOCK_CORRECTION_BUCKET_MAX 4 | ||
|
||
/** | ||
Obtains the clock correction from a clockCorrectionStorage_t object. This is the recommended public API to obtan the clock correction, instead of getting it directly from the storage object. | ||
*/ | ||
double getClockCorrection(clockCorrectionStorage_t* storage) { | ||
return storage->clockCorrection; | ||
} | ||
|
||
/** | ||
Truncates a timestamp to 40 bits, which is the number of bits used to timestamp events in the DW1000 chip. This truncation ensures that the value returned is a valid time event, even if the DW1000 wrapped around at some point. | ||
*/ | ||
uint64_t truncateTimeStampFromDW1000(uint64_t timeStamp) { | ||
uint64_t mask = 0xFFFFFFFFFF; // 40 bits | ||
return timeStamp & mask; | ||
} | ||
|
||
/** | ||
Implementation of the leaky bucket algorithm. See: https://en.wikipedia.org/wiki/Leaky_bucket | ||
*/ | ||
void fillClockCorrectionBucket(clockCorrectionStorage_t* storage) { | ||
if (storage->clockCorrectionBucket < CLOCK_CORRECTION_BUCKET_MAX) { | ||
storage->clockCorrectionBucket++; | ||
} | ||
} | ||
|
||
/** | ||
Implementation of the leaky bucket algorithm. See: https://en.wikipedia.org/wiki/Leaky_bucket | ||
*/ | ||
bool emptyClockCorrectionBucket(clockCorrectionStorage_t* storage) { | ||
if (storage->clockCorrectionBucket > 0) { | ||
storage->clockCorrectionBucket--; | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
Calculates the clock correction between a reference clock and another clock (x). | ||
|
||
@param new_t_in_cl_reference The newest time of occurrence for an event (t), meassured by the clock of reference | ||
@param old_t_in_cl_reference An older time of occurrence for an event (t), meassured by the clock of reference | ||
@param new_t_in_cl_x The newest time of occurrence for an event (t), meassured by clock x | ||
@param old_t_in_cl_x The previous time of occurrence for an event (t), meassured by clock x | ||
@return The necessary clock correction to apply to timestamps meassured by clock x, in order to obtain their value like if the meassurement was done by the reference clock. Or -1 if it was not possible to perform the computation. Example: timestamp_in_cl_reference = clockCorrection * timestamp_in_cl_x | ||
*/ | ||
double calculateClockCorrection(const uint64_t new_t_in_cl_reference, const uint64_t old_t_in_cl_reference, const uint64_t new_t_in_cl_x, const uint64_t old_t_in_cl_x) { | ||
const uint64_t tickCount_in_cl_reference = truncateTimeStampFromDW1000(new_t_in_cl_reference - old_t_in_cl_reference); | ||
const uint64_t tickCount_in_cl_x = truncateTimeStampFromDW1000(new_t_in_cl_x - old_t_in_cl_x); | ||
|
||
if (tickCount_in_cl_x == 0) { | ||
return -1; | ||
} | ||
|
||
return (double)tickCount_in_cl_reference / (double)tickCount_in_cl_x; | ||
} | ||
|
||
/** | ||
Updates the clock correction only if the provided value follows certain conditions. This is used to discard wrong clock correction meassurements. | ||
@return True if the provided clock correction was valid and accepted, false otherwise. | ||
*/ | ||
bool updateClockCorrection(clockCorrectionStorage_t* storage, const double clockCorrectionCandidate) { | ||
bool sampleIsAccepted = false; | ||
|
||
if (CLOCK_CORRECTION_SPEC_MIN < clockCorrectionCandidate && clockCorrectionCandidate < CLOCK_CORRECTION_SPEC_MAX) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved the spec check to the start of the function, so there is no processing of the sample if it is not inside the specs range |
||
const double currentClockCorrection = storage->clockCorrection; | ||
const double difference = clockCorrectionCandidate - currentClockCorrection; | ||
|
||
if (-CLOCK_CORRECTION_ACCEPTED_NOISE < difference && difference < CLOCK_CORRECTION_ACCEPTED_NOISE) { | ||
// Simple low pass filter | ||
const double newClockCorrection = currentClockCorrection * CLOCK_CORRECTION_FILTER + clockCorrectionCandidate * (1.0 - CLOCK_CORRECTION_FILTER); | ||
|
||
fillClockCorrectionBucket(storage); | ||
|
||
storage->clockCorrection = newClockCorrection; | ||
sampleIsAccepted = true; | ||
} else { | ||
const bool shouldAcceptANewClockReference = emptyClockCorrectionBucket(storage); | ||
if (shouldAcceptANewClockReference) { | ||
fillClockCorrectionBucket(storage); | ||
storage->clockCorrection = clockCorrectionCandidate; | ||
sampleIsAccepted = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In previous implementation, when the sample was accepted after the bucket was emptied, |
||
} | ||
} | ||
} | ||
|
||
return sampleIsAccepted; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was previously masked to 32 bits (0xFFFFFFFF) instead of 40 (0xFFFFFFFFFF). Any reason for it? I understand that, in this case, with 32 bits is enough because the timestamp represents a very short amount of time that can be represented in 32bits, but in general the truncation should be done in 40 bits, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, both TDoA2 and TDoA3 only has 32 bits in the protocol for DWM timestamps (see https://wiki.bitcraze.io/doc:lps:tdoa3:protocol). This works as long as we transmit packets more often than the wrap time (about 67 ms) and use sequence numbers.
The current implementation is a bit dirty in that protocol properties leaks into the algorithm. I suggest that you add a parameter with a bit mask to the functions that require truncation of timestamps in your generalized implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took me some time to see why using 40 bits would throw a wrong result, but I finally got it.
An unrelated question: why in calcClockCorrection in the tdoa3 engine you use uint64_t instead of uint32_t?