========================================= Clock Synchronization and Time References ========================================= Timing and synchronization requirements in LoRaWAN gateways depend on their mode of operation: Indoor gateways which operate purely on LoRaWAN Class A traffic do not require any synchronization mechanism because the only relevant time domain is given by the concentrator's clock. Nevertheless, LoRa Basics™ Station tracks relative clock drifts and a UTC time reference for convenience, even under this scenario. For outdoor gateways which are equipped with GPS receivers, Station additionally tracks high-precision clock drifts and a time reference to the global GPS time. The following sections, we look at the synchronization and time reference tracking requirements for each of the clocks involved in a LoRaWAN gateway as well as the time-related strategies implemented in LoRa Basics™ Station. Clocks and Their Purposes ------------------------- The gateway operates on various clocks with different purposes: **PPS (pulse per second)**: The PPS is a 1Hz clock synchronized to global GPS time with a precision in the order of nanoseconds. The main purpose of the PPS in LoRaWAN gateways is to provide a global time reference which allows time-synchronous packet emission of gateways with an accuracy of up to 1 microsecond. This is required for LoRaWAN Class B beacons. The PPS can also be used to calculate the various clock drifts with respect to the global time reference. **SX1301 (xticks)**: The sx1301 maintains a 32-bit microsecond counter - the ``xticks`` counter, which is driven by a 32 MHz temperature-compensated crystal oscillator (TCXO). Receiving packets get timestamped with, and packet transmission times are expressed by, the ``xticks``. If the gateway design allows for access to a PPS, the `xticks` can be latched to the PPS rising edge. Station abstracts the sx1301's hardware `xticks` counter as a monotonic 64-bit microsecond counter: the ``xtime``. All actions-related packet routing (like the allocation of a packet into the TX queue) are expressed in the ``xtime`` time domain. LoRaWAN Class B requires a conversion between GPS time and ``xtime``. **Host MCU**: Station uses the host MCU's clock_monotonic to schedule jobs. The most time-critical job is the TX job, which puts a packet into the sx1301's TX buffer just before the transmission is due. This can be done with an accuracy of a few milliseconds with respect to the sx1301 clock. Clock Synchronization --------------------- .. note:: The width of pulses generated by crystal oscillators is impacted by various physical conditions, such as ambient temperature, and is subject to product variation. Consequently, any two oscillators will generate pulses of slightly different widths and counters counting those pulses will increment with different speeds. The effect of this could be, for example, that in a given second a hypothetical ``counter A`` counted 1000000 (1e6) pulses while hypothetical ``counter B``, driven by a different oscillator, counted 999998 pulses. In this particular example, within one second ``counter B`` drifted two microseconds with respect to ``counter A``, which corresponds to 0.0002%, i.e. 2ppm. Synchronizing a pair of clocks in this context means tracking their relative drift. This allows us to express a given time interval in terms of both clocks. The synchronization precision is a measure of the error we make during conversion of a fixed-time interval between the clocks. In regular intervals Station executes the ``ral_getTimesync`` function, which collects the following `timesync measurements` based on the RAL implementation: .. c:type:: timesync_t .. c:member:: sL_t pps_xtime Last latched sx1301 `xticks` counter value, accounted for rollovers. 0 if PPS disabled. .. c:member:: sL_t xtime Instantaneous sx1301 ``xticks`` counter value, accounted for rollovers. .. c:member:: ustime_t ustime MCU system time (clock_monotonic) in microseconds at the moment at which the ``xticks`` counter was read (the expected error is in the order of a few milliseconds). Each measurement round is assigned a `timesync quality` value which is defined by the time in microseconds it took to fetch the instantaneous ``xticks`` counter value. Station keeps statistics on the timesync quality and discards measurements whose quality is considered an outlier. Timesync quality outliers can occur if the Station process is preempted during the SPI transaction where the ``xticks`` counter is fetched. This could impact the MCU/SX1301 drift estimation and is therefore discarded. Based on the timesync measurements the following clock drift statistics are tracked: PPS <-> SX1301 (if PPS enabled) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The PPS/SX1301 drift characterizes the drift of the SX1301 clock with respect to the global time reference. Compensating for this drift is necessary whenever packets whose transmission time is expressed in GPS time need to be emitted with microsecond-accuracy, i.e. Class B beacons. .. .. note:: .. SX1301 drift compensation is not needed when the transmission just requires milliseconds-accuracy, i.e. Class B downlinks. This is for the following reason: At any given time, a Station has access to the last record of a latched ``xticks`` counter. The age of this record is ``TIMESYNC_RADIO_INTV`` in the worst case. This is an example of PPS/SX1301 drift statistics printed into the log: :: [SYN:INFO] PPS/SX1301 drift stats: min: -1.3ppm q50: -1.5ppm q80: -1.5ppm max: -1.7ppm - threshold q80: -1.5ppm MCU <-> SX1301 ^^^^^^^^^^^^^^ Station tracks the MCU/SX1301 drift under the assumption that the SX1301 clock is more accurate (i.e. more aligned with the global reference time) than the MCU clock. This is needed to correct time intervals which are expressed in system time for the MCU clock drift. An example where the MCU clock drift correct comes into play is the ``RefTime`` field in the ``updf`` message which is used by the server to track the IP link latency between the server and the gateway. For downlink packet scheduling, the MCU drift is irrelevant because the scheduling is done purely in the SX1301 clock time domain. Internal job scheduling related to packet transmission does not require MCU drift compensation either. This is because the errors incurred to job scheduling time intervals due to MCU clock drift are in the order of microseconds which is insignificant. This is an example of MCU/SX1301 drift statistics printed into the log: :: [SYN:INFO] MCU/SX1301 drift stats: min: +1.1ppm q50: +1.6ppm q80: +1.8ppm max: +2.8ppm - threshold q90: +2.1ppm Time Domains and Conversions ---------------------------- References between time domains are tracked as relative offsets between their epochs. In particular, Station keeps track of the UTC time reference with respect to the MCU clock and (if PPS is present) the GPS time reference with respect to the sx1301 clock. System Time ^^^^^^^^^^^ The system time is the free-running, monotonically-increasing, 64-bit microsecond counter driven by the MCU clock. .. c:function:: ustime_t rt_getTime () Runtime function to retrieve current system time. Maps to a platform-specific ``sL_t sys_time()`` function. On Linux, this calls ``clock_gettime(CLOCK_MONOTONIC, &t)``. UTC ^^^ Every message from the server down to the gateway can contain the ``MuxTime`` field with the UTC timestamp at the moment the message was sent. Therefore, for every server message Station receives, there is an opportunity to adjust the offset between the system time and UTC if the MCU drift is significant. Under this scheme, the Station's UTC time reference is impacted by various latencies incurred in the message exchange, out of which the network latency is the most severe. This can be tolerated because the UTC time reference is of purely informative value. It is used for timestamping log messages and providing a rough arrival time estimate of uplink packets (``rxtime`` in the ``upinfo`` structure). .. c:var:: ustime_t rt_utcOffset The offset between the system time epoch and UTC epoch in microseconds with a precision of a few hundred milliseconds (network roundtrip latency). Add to ``rt_getTime()`` to obtain UTC microsecond timestamp (number of microseconds since UTC epoch). Updated on every server message which contains ``MuxTime``. .. c:function:: ustime_t rt_getUTC () Convenience function to retrieve current UTC time. Expects ``rt_utcOffset`` to be set. GPS (if PPS enabled) ^^^^^^^^^^^^^^^^^^^^ With PPS enabled, a Station has access to a microsecond counter latched to the last rising edge of the PPS (PPS-latched ``xticks`` of the SX1301). In order to convert between ``xtime`` (i.e., roll-over compensated ``xticks``) and GPS time, we need to establish how many seconds have passed between an observed PPS pulse and the GPS epoch (00:00h 6-Jan-1980). This is done via a message exchange with the LNS (see :ref:`timesyncproto`). The result of this exchange is a tuple (``txtime``, ``gpstime``, ``rxtime``) and a value for ``gpsOffset``, which is the difference between the local epoch and the GPS time epoch, i.e., ``gpsOffset = us_0 - gps_0``: :: gps_s gpstime gps_0 | v.... gps_us ....v v | ____________________________________ PPS x-----|____________________| GPS Time | ______________________________________________________ USS x--|__|... ppsOffset ... Local Time ^ | ^ ^ ^ ^ us_0 | us_s pps_ustime txtime rxtime This example illustrates how the GPS time reference is obtained: :: [] Last PPS: pps_xtime = 0x520000003906F0 pps_ustime = 0xA03F1BEC1D [] Obtained initial ppsOffset = 561885 [] Timesync message: {'msgtype': 'timesync', 'gpstime': 1238942913655858, 'txtime': 688254167114} [] Timesync LNS: tx/rx:0xA03F1C956D..0xA03F1E2975 (103ms432us) us/gps:0xA03F1BEC1D/0x466CFE043F432 (ppsOffset=561885) - 1 solutions [] Timesync with LNS: gpsOffset=0x466CFE039F240 us_0 - gps_0 (gps_s + gps_0) = (pps_ustime + us_0) gps_s - pps_ustime = us_0 - gps_0 1. Fetch last PPS-latched ``pps_xtime`` counter and convert it to ``pps_ustime``: ``pps_ustime = ts_xtime2ustime(pps_xtime)`` 2. Calculate ``ppsOffset = pps_ustime % 1e6`` 3. Do a timesync server exchange, which yields ``txtime``/``rxtime`` (local time domain) and ``gpstime`` (gps time domain). 4. Verify that ``txtime%1e6-ppsOffset < gpstime%1e6 < rxtime%1e6-ppsOffset`` .. c:var:: ustime_t ppsOffset The fractional part of the system time second, where the PPS rising edge occurs (in microseconds). This value is refreshed after it drifts more than Q90 of the MCU drift. The value is between ``0`` and ``1e6-1``. ``-1`` if no PPS. .. c:var:: sL_t gpsOffset The offset between the ``xtime`` epoch and the GPS time epoch, in microseconds. This value is calculated after a ``timesync`` message exchange with the server. Time Conversion APIs -------------------- .. c:function:: ustime_t rt_ustime2utc (ustime_t ustime) Convenience function to convert from system time to UTC by adding ``rt_utcOffset``. .. c:function:: sL_t ts_xticks2xtime (u4_t xticks, sL_t last_xtime) .. c:function:: sL_t ts_xtime2xtime (sL_t xtime, u1_t dst_txunit) .. c:function:: ustime_t ts_xtime2ustime (sL_t xtime) .. c:function:: sL_t ts_ustime2xtime (u1_t txunit, ustime_t ustime) .. c:function:: sL_t ts_xtime2gpstime (sL_t xtime) .. c:function:: sL_t ts_gpstime2xtime (u1_t txunit, sL_t gpstime) Time Transfer ------------- FAQ --- **Why is there no PPS <-> MCU synchronization?**