Questions about the Time Synchronization in Specification

Hello Pavel, I try to understand how to implement the time synchronization in my application and I think that detailed explanation in uavcan.time.Synchronization data type description in Specification is useful, but I little bit confused about this :roll_eyes::

  1. In a pseudocode of function update(message) one can find this comment:
 // A message is assumed to have two timestamps:
 // Real - sampled from the clock that is being synchronized
 // Monotonic - clock that never leaps and never changes rate

But the message of type uavcan.time.Synchronization has only one field:
truncated uint56 previous_transmission_timestamp_microsecond
Or in this case this is a message of another type?

  1. In a pseudocode of function adjust(message) there is this line:
local_time_phase_error := previous_rx_real_timestamp - msg.previous_transmission_timestamp_microsecond;

Will the local_time_phase_error always be less than zero? In handleReceivedTimeSyncMessage(message) the state of the slave is changing from STATE_ADJUST to STATE_UPDATE and vice versa. The previous_rx_real_timestamp gets the previous message timestamp which will be always one time step behind new arrived message. Is it right?

I also have read the original paper which you referenced, but there is no description of synchronization slave logic.

The text you referenced speaks about message metadata, not about its fields. The UAVCAN stack is expected to provide the application with two timestamps, plus the one contained in the message, overall three values:

  1. Real timestamp, aka wall time, supplied by the local UAVCAN stack. On a POSIX system this may be obtained using clock_gettime() with CLOCK_REALTIME.

  2. Monotonic timestamp supplied by the local UAVCAN stack. On a POSIX system, this may be obtained using clock_gettime() with CLOCK_MONOTONIC. On a typical embedded system, this would be the time since power-on.

  3. The time that you receive from the network in the time synchronization message — previous_transmission_timestamp_microsecond.

No. The data you receive in the message – previous_transmission_timestamp_microsecond – is already one time step behind. Therefore, when you do the subtraction, you obtain the time error observed when the previous message was received. You might see now that the observations provided by this algorithm are always delayed.

This is somewhat similar to IEEE 1588 synchronization with the critical difference being that there is only one type of exchange defined.

Thank you very much, Pavel :slightly_smiling_face:. Do you think that it will be more clear if the text in Specification would explicitly mention the message metadata?
Ok, in case of Libcanard how exactly one should get these metadata timestamps (in case of stm32)?

  1. I can get the monotonic timestamp using a general hardware timer with microsecond resolution. In case of bxCAN I think that optimal solution is to timestamp the appropriate CAN frame directly in CAN IRQ handler.
  2. Is the real timestamp a CanardMicrosecond timestamp_usec in struct CanardRxTransfer? But comment to this field says that it is

The timestamp of the first received CAN frame of this transfer

So is this timestamp equal to timestamp that I get using hardware timer in CAN IRQ handler (when synchronization frame is received)? If it’s true what’s the difference between monotonic and real timestamps in case of microcontroller? I understand that the real clock we must adjust and it will leap back and forth but the main question is how to yield it from Canard message?

Certainly. Looking at the text now, I see that it could be improved in more than one way, so by all means, feel free to send a pull request, it will be reviewed promptly.

Generally, the clock that you use with libcanard’s timestamp_usec et al should be monotonic because it is used for measuring time intervals. Any clock that is synchronized with an external source (e.g., your wall clock) is not monotonic by definition because in order to keep synchronization it has to occasionally change rate or make leaps; hence it cannot be used for precise measurement of small time intervals.

If you adhere to the above, then libcanard is not really aware of any non-monotonic clock, since it is designed to deal with lower levels of the protocol.

Normally, yes, this is how it is intended to be. Observe that libcanard does not timestamp anything on its own, it simply doesn’t have access to platform clocks/timers. It merely ferries the timestamps that you provide around. Say, when you receive a new frame, you timestamp it using whatever means are available (e.g., sampling some hardware timer as you wrote), then you pass that timestamp to canardRxAccept(), then it becomes the transfer timestamp or just discarded:

There are several solutions.

Perhaps the cleanest is to define your wall time as a linear function of monotonic time and two coefficients:

t_\text{wall} = (t_\text{monotonic} + D) R

Where R is the rate multiplier (R \approx 1) and D is the offset. If the wall clock needs to leap, you adjust D; to adjust its speed, you change R. In this case you will be able to obtain the wall clock as it was observed back when the message was received if you have its monotonic timestamp.

Alternatively, if you don’t want to or can’t define the wall clock as a function of monotonic, you could simply store the wall timestamp in some state that is external to libcanard. If you want to go full caveman you could just use a global variable that is updated whenever a synchronization frame is received (you will have to detect such frames in your CAN driver below libcanard), or you could store it somewhere in the user state attached to the synchronization subject subscription:

Another approach is to add a new timestamp field to libcanard but you see how that might be a little more involved.

1 Like

Thanks again, Pavel, this is a very detailed answer (it’s even more comprehensive than I expected!). I think that I will make a pull request (but after little experiments with my application) :thinking: