mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
feat(usrp): Sync to CLOCK_REALTIME without GPS
Add a method to sync to CLOCK_REALTIME for USRPs without GPS sync. The main idea centers around the round-trip time of USRP timing API. As long as the API RTT is low enough (witihn a few symbols) it is possible to achieve partial synchronization and maintain it until the clocks drift. Signed-off-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
This commit is contained in:
@@ -151,3 +151,53 @@ There are two workarounds:
|
||||
|
||||
You can also find more information on this in the [5G/NR gNB with COTS UE
|
||||
tutorial](./NR_SA_Tutorial_COTS_UE.md).
|
||||
|
||||
### Map UTC timestamp to sample index
|
||||
|
||||
#### Without GPS sync
|
||||
|
||||
The current software attempts to generate a valid sample_index from UTC timestamp
|
||||
in `get_timestamp`. This is done by first continuously polling `get_time_last_pps`
|
||||
setting up the clock reference using `set_time_next_pps`.
|
||||
|
||||
This works only for devices with short and stable round-trip time of `get_time_last_pps`
|
||||
|
||||
The following graph describes the delay:
|
||||
|
||||
```mermaid
|
||||
|
||||
sequenceDiagram
|
||||
participant host
|
||||
participant usrp
|
||||
participant last_pps
|
||||
participant usrp_clock
|
||||
|
||||
loop get_last_pps
|
||||
host ->>+ usrp: get_last_pps query
|
||||
usrp ->> last_pps: read
|
||||
alt delay start
|
||||
usrp_clock -->> last_pps: write ?
|
||||
end
|
||||
usrp ->>- host: get_last_pps response
|
||||
end
|
||||
host ->>+ usrp: get_last_pps query
|
||||
alt delay start
|
||||
usrp_clock -->> last_pps: write ?
|
||||
end
|
||||
usrp ->> last_pps: read
|
||||
usrp ->>- host: get_last_pps response
|
||||
note over host: read CLOCK_REALTIME
|
||||
|
||||
host ->> usrp: set_time_next_pps
|
||||
```
|
||||
|
||||
The total delay between the PPS pulse on the USRP and the host reading CLOCK_REALTIME can
|
||||
be estimated to between 1.5 RTT (if last_pps was written after USRP read it in the
|
||||
`get_last_pps` loop above) to 0.5 RTT (if last_pps was written right before the final
|
||||
`get_last_pps` call).
|
||||
|
||||
This mechanism makes it so that USRP clock is running ahead of the host CLOCK_REALTIME.
|
||||
to circumvent that, an extra offset is added when calculating sample index in `get_timestamp`.
|
||||
|
||||
For 5G, if the RTT value is small (~few symbols) and stable (low stdev) it would allow for
|
||||
temporary synchronization to CLOCK_REALTIME at least until the clocks drift.
|
||||
|
||||
@@ -84,6 +84,8 @@ typedef struct {
|
||||
//int first_rx;
|
||||
//! timestamp of RX packet
|
||||
openair0_timestamp_t rx_timestamp;
|
||||
bool utc_sync;
|
||||
double wall_to_hw_offset;
|
||||
} usrp_state_t;
|
||||
|
||||
//void print_notes(void)
|
||||
@@ -1039,6 +1041,23 @@ static void usrp_sync_pps(usrp_state_t *s)
|
||||
LOG_I(HW, "USRP clock set to %f sec\n", tai_sec);
|
||||
}
|
||||
|
||||
openair0_timestamp_t get_timestamp(openair0_device *device, struct timespec *utc_ts) {
|
||||
usrp_state_t *s = (usrp_state_t *)device->priv;
|
||||
if (!s->utc_sync) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 1. CPU-domain UTC (The "Wall Clock")
|
||||
double wall_secs = (double)utc_ts->tv_sec + (double)utc_ts->tv_nsec / 1e9;
|
||||
|
||||
// 2. Map to Hardware-domain UTC
|
||||
double hw_secs = wall_secs + s->wall_to_hw_offset;
|
||||
auto ts = uhd::time_spec_t(hw_secs);
|
||||
|
||||
// 3. Convert to Absolute Ticks
|
||||
return (openair0_timestamp_t)(ts.to_ticks(s->sample_rate));
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
int device_init(openair0_device_t *device, openair0_config_t *openair0_cfg)
|
||||
{
|
||||
@@ -1440,7 +1459,46 @@ extern "C" {
|
||||
if (s->usrp->get_time_source(0) == "external") {
|
||||
usrp_sync_pps(s);
|
||||
} else {
|
||||
s->usrp->set_time_next_pps(uhd::time_spec_t(0.0));
|
||||
// No GPS sync and no external clock - best effort to sync to host clock
|
||||
// estimate rtt of get_time_last_pps()
|
||||
const int n_tries = 100;
|
||||
const uint64_t ns_to_s = 1000000000;
|
||||
std::vector<uint64_t> rtt_samples;
|
||||
for (int i = 0; i < n_tries; i++) {
|
||||
struct timespec start;
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
s->usrp->get_time_last_pps();
|
||||
struct timespec end;
|
||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||
uint64_t rtt_sample = (end.tv_sec - start.tv_sec) * ns_to_s + (end.tv_nsec - start.tv_nsec);
|
||||
rtt_samples.push_back(rtt_sample);
|
||||
}
|
||||
uint64_t rtt_sum = 0;
|
||||
for (int i = 0; i < n_tries; i++) {
|
||||
rtt_sum += rtt_samples[i];
|
||||
}
|
||||
double rtt_mean = rtt_sum / (double)n_tries;
|
||||
double rtt_var = 0;
|
||||
for (int i = 0; i < n_tries; i++) {
|
||||
rtt_var += (rtt_mean - rtt_samples[i]) * (rtt_mean - rtt_samples[i]);
|
||||
}
|
||||
rtt_var /= n_tries;
|
||||
|
||||
LOG_A(HW, "RTT of get_time_last_pps() mean %f stdev %f uS\n", rtt_mean / 1e3, sqrt(rtt_var) / 1e3);
|
||||
uhd::time_spec_t time_last_pps = s->usrp->get_time_last_pps();
|
||||
|
||||
while (time_last_pps == s->usrp->get_time_last_pps()) {
|
||||
}
|
||||
struct timespec now;
|
||||
int ret = clock_gettime(CLOCK_REALTIME, &now);
|
||||
if (ret != 0) {
|
||||
LOG_E(HW, "Failed to get time from clock");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
double utc_timestamp = now.tv_sec + 1.0 + now.tv_nsec / 1e9;
|
||||
s->usrp->set_time_next_pps(uhd::time_spec_t(utc_timestamp));
|
||||
s->wall_to_hw_offset = rtt_mean * 2;
|
||||
s->utc_sync = true;
|
||||
}
|
||||
|
||||
if (s->usrp->get_clock_source(0) == "external") {
|
||||
|
||||
Reference in New Issue
Block a user