=================================
Time synchronization over the air
=================================

In a network of multiple devices, it is often required that all participants
operate on a common time base. This document describes how to synchronize
multiple |PROPRFDEVICE| devices using the proprietary PHY.


.. aafig::
   :align: center

   +-----------+                      +---------+
   | 'Central' |                      | 'Node'  |
   |           +--- 'synchronize' --->|         |
   | 'm'       |                      | 's=m+T' |
   +-----------+                      +---------+

In our example we have a central and a node and we want to synchronize the
node clock to the central's clock. Both clocks run at the same tick rate, but
the node clock has an initial offset :math:`T` relative to the central.
Assuming that :math:`m_n` and :math:`s_n` specify absolute timestamps with
index :math:`n` on the central and the node, respectively, we get the
relationship:

.. math::
   :label: eq_rat_offset

    s_n = m_n + T

Initially, the node doesn't know :math:`T` and hence, :math:`s_n` is always
different from the central's time :math:`m_n`.

As the timer domain, we choose the radio timer :term:`RAT` on the RF core
which provides 32 bit timestamps at 4 MHz (4 ticks per µs). RAT timestamps can
be used as absolute :ref:`start and end triggers <sec_rf_command_triggers>`
for :ref:`radio operation commands <sec_rf_radio_operation_commands>`.


One-way synchronization example
-------------------------------

In order to get :math:`T`, it would be enough to send one message at a
timestamp :math:`m_0` from the central to the node as shown in the sequence
chart below:

.. _fig_rf_time_synchronization_oneway:

.. figure:: time-synchronization-oneway.png
   :align: center

   Sequence chart of the one-way time synchronization process.


The node would receive the message after a certain delay
:math:`d_{\text{TX}}` at :math:`s_1` and could deduce :math:`T` by the
following formula:

.. math::
   :label: eq_tx_offset1

    T = m_0 - \left( s_1 - d_{\text{TX}} \right)

:math:`s_1` is the time of the receiver signal `Syncword found`. The
transmission offset :math:`d_{\text{TX}}` contains the time to bring up the RF
front-end, to calibrate the synthesizer and to send the packet preamble and
the syncword. The time of flight (TOF) is very small compared to that value
and can be ignored. :math:`d_{\text{TX}}` is a fixed value and depends on the
chosen RF settings. It must be measured/only once during development and is
then compiled into the application.


A two-way synchronization algorithm
-----------------------------------

This section describes an indirect approach that doesn't require to measure
:math:`d_{\text{TX}}`. But it can also be used to deduce :math:`d_{\text{TX}}`
indirectly. We use an algorithm similar to the :wikipedia:`Network Time
Protocol <Network_Time_Protocol#Clock_synchronization_algorithm>`.

The following sequence diagram shows the synchronization process.

.. _fig_rf_time_synchronization_twoway:

.. figure:: time-synchronization-twoway.png
   :align: center

   Sequence chart of the two-way time synchronization process.


The node sends a synchronization request to the central at :math:`s_0` and
sets :math:`s_0` as an absolute start trigger for the TX command.

The central receives the message after :math:`d_{\text{TX}}` at :math:`m_1`.
This is the timestamp that is appended as meta data to the packet and
specifies the time when the sync word was found:

.. math::
   :label: eq_tx_offset2

    m_1 = s_0 + d_{\text{TX}}

After a short time :math:`d_{\text{Processing}}`, the central responds to the
request with a reply message at :math:`m_2`. It sets the TX command start
trigger to :math:`m_2` and includes :math:`m_1` into the packet payload as
well:

.. math::
   :label: eq_process_offset

    m_2 = m_1 + d_{\text{Processing}}


When the client receives the reply at :math:`s_3`, it has the following timing
information and can calculate the initial clock offset :math:`T`:

- :math:`s_0`: Request sent by the client
- :math:`m_1`: Request received by the central
- :math:`s_3`: Reply received by the client
- :math:`d_{\text{Processing}}`: A fixed delay for message processing on the central

From :eq:`eq_rat_offset`, we know that:

.. math::
   :label: eq_T0_init

    T = s_0 - m_0

We do not know :math:`m_0`, but we know its offset to :math:`m_1` which is :math:`d_{\text{TX}}`:

.. math::
   :label: eq_m0_m1_offset

    m_0 = m_1 - d_{\text{TX}}

With :eq:`eq_T0_init` and :eq:`eq_m0_m1_offset` we get:

.. math::
   :label: eq_T0_2

    T = s_0 - (m_1 - d_{\text{TX}})

We still do not know :math:`d_{\text{TX}}`, but we know that it is included
into the round-trip time :math:`s_3 - s_0` as follows:

.. math::
   :label: eq_round_trip

    s_3 - s_0 = 2 \cdot d_{\text{TX}} + d_{\text{Processing}}


With the help of :eq:`eq_round_trip` we can rewrite :eq:`eq_T0_2` to:

.. math::
   :label: eq_T0_final

    T = s_0 - \left( m_1 - \frac{s_3 - s_0 - d_{\text{Processing}}}{2} \right)

and finally obtain :math:`T`. This value must now be added to any further RF
operation on the client.




Sending and receiving timestamp messages
----------------------------------------

This section provides code snippets for implementing the above algorithms.
When sending a timestamp message at a certain time, we use ``txTimestamp`` as
an absolute start trigger, but include it also into the packet payload::

    // Convenience macro
    #define RF_convertMsToRatTicks(milliseconds) \
        (((uint32_t)(milliseconds)) * 1000 * 4)

    // Exported from SmartRF Studio
    rfc_CMD_PROP_TX_t txCommand;

    // Set a time in the near future (5ms)
    uint32_t txTimestamp = RF_getCurrentTime() + RF_convertMsToRatTicks(5);

    // Set txTimestamp as an absolute start trigger
    txCommand.startTrigger.triggerType = TRIG_ABSTIME;
    txCommand.startTime = txTimestamp;

    // Include it also into the payload
    uint32_t payload[1] = { txTimestamp };
    txCommand.pPkt = (uint8_t*)&payload[0];
    txCommand.pktLen = sizeof(payload);

When receiving this packet, the receiver must read the timestamp from the
packet payload, but must also configure the RF core to append a timestamp to
each received packet. This is the time when the RF core raises the signal
"Synchronization found" and we choose the name ``rxTimestamp``::

    // Exported from SmartRF Studio
    rfc_CMD_PROP_RX_t rxCommand;

    // Append RX timestamp to the payload
    RF_cmdPropRx.rxConf.bAppendTimestamp = 1;

    // The code to execute the RX command and to setup
    // the RX data queue is not shown here.

    // When reading the packet content from the
    // RX data queue, rxTimestamp is behind the payload:
    rfc_dataEntryGeneral_t* currentDataEntry = RFQueue_getDataEntry();
    // Assuming variable length
    uint8_t packetLength = *(uint8_t*)(&currentDataEntry->data);
    uint8_t* packetDataPointer = (uint8_t*)(&currentDataEntry->data + 1);
    uint32_t rxTimestamp;
    memcpy(&rxTimestamp, &packetDataPointer + packetLength, 4);

    // The TX timestamp is found in the payload
    uint32_t txTimestamp;
    memcpy(&rxTimestamp, packetDataPointer + packetLength, 4);