Clocking and Timing¶
Clock Reference¶
Many AIR-T models support receiving an external 10 MHz reference clock to act as the device clock for applications that require synchronization between multiple AIR-Ts. This can be accomplished via piping in a signal to the REF input on the AIR-T. For details on the specifications of the external signal, please see the Product Guide for your hardware.
To enable this feature in software, you will need to either set the clk_src
device argument or call setClockSource()
via the SoapySDR API (e.g., sdr.setClockSource(“external”)
). The clk_src
parameter can be set via the device constructor (e.g., sdr = SoapySDR.Device(dict(driver="SoapyAIRT", clk_src="external"))
) or if you are using GNU Radio, simply add clk_src=”external"
to the Device arguments
field in the sink or source block.
The AIR-T model 8201 comes with a GPS module that can be used as a reference clock if desired. To enable this functionality, follow the same software steps as for the external reference, except replace external
with GPS
. No hardware modifications are required on the 8201 to use the GPS reference, simply enable this feature via software. Note that the GPS clock may not function if there is not a clear GPS signal to the device. As a result, it is recommended to revert back to the internal clock in the event of a transient GPS signal, as shown in the Python code example below.
sdr = SoapySDR.Device(dict(driver="SoapyAIRT"))
try:
sdr.setClockSource("GPS")
except RuntimeError as e:
print("GPS Clock Reference Failed!")
print(e)
sdr.setClockSource("internal")
Understanding Clock Rates¶
AirStack Core Version 2+¶
AirStack Core v2+ is used to control the 2nd generation AIR-Ts feature. These products have a much more flexible clock architecture. Namely, the master clock rate on the device can be set to a wide range of values to support different applications.
In addition to the variable master clock rate, AirStack Core 2+ features fixed integer dividers in firmware to derive the final sample rate. The notable difference is the inclusion of a final stage ( i.e., interpolate/decimate by 32) to support smaller sample rates.
The variable master clock rate feature was enabled in software in AirStack Core versions 2.1 and above.
Master Clock Rate¶
Available master clock rates are shown by calling getMasterClockRates()
. In addition, the available master clock rates for each AIR-T model may be found in its Product Guide.
Setting the Master Clock Rate¶
The master clock rate can simply be set by calling setMasterClockRate()
or by passing in a device argument. After setting the master clock rate, sample rates for all channels are reset and now use a divider of 1. That is, after the master clock rate is set, the sample rate for any given channel is always equal to the master clock rate.
Note that the master clock rate can only be set if the device is in an idle state, with no active data streams in the application.
Master Clock Rate and the Time API¶
Changing the master clock rate also affects the internal clock in firmware. That is, the internal firmware clock rate will always be half of the master clock rate. The internal firmware clock is used for the Time API for functions like starting a stream at a specific time. As a result, users who want the most precision/granularity when using the Time API should set the master clock rate to the highest value possible for their application.
It is also highly recommended that users of the Time API set the hardware time after the master clock rate has been changed. This is because the counters inside the firmware might have been between ticks when the clock rate was changed. Details on the Time API and how to use this functionality can be seen here.
Sample Rates¶
Sample rates are all derived from the master clock rate by fixed integer dividers inside the firmware. As a result, there are some dependencies between the master clock rate and sample rate settings. Supported sample rate ranges can be displayed by calling getSampleRateRange()
. Note that this function simply takes the available ranges of master clock rate and divides them by the available fixed integer dividers.
Setting and Verifying the Sample Rate¶
Like with AirStack Core 1.0, sample rate can be set via calling setSampleRate()
in AirStack Core 2+ . However, note that only certain sample rates can be supported by the current master clock rate (i.e., the master clock rate divided by the integer decimation value). As a result, users must first set the master clock rate appropriately prior to setting the sample rate. Attempting to set the sample rate to a value that requires first changing the master clock rate will result in an exception being thrown detailing the value of the master clock rate that first must be set.
Also, it should be noted, that the software will attempt to find a sample rate "close enough" to the desired sample rate. For AirStack Core 2+, this threshold is computed by taking the supported step size of the master clock rate (10 KHz) and dividing by the available fixed integer dividers. These thresholds can be viewed by calling getSampleRateRange()
. Users should call getSampleRate()
after setting the sample rate to verify that their desired sample rate has been applied by the hardware.
Example Code¶
The following code snippet shows the interdependencies of the master clock rate and sample rate settings in AirStack Core 2+.
# Initialize device with 125 MHz master clock. Note that this also changes
# the sample rate to 125 MSPS for all channels.
sdr = SoapySDR.Device(dict(driver="SoapyAIRT", master_clock_rate="125e6"))
# Change sample rate for RX channel 0.
sdr.setSampleRate(SOAPY_SDR_RX, 0, 31.25e6)
# ERROR: Sample rate cannot be supported with a master clock rate of 125 MHz.
sdr.setSampleRate(SOAPY_SDR_RX, 0, 30.0e6)
# Change the master clock rate, which will in turn change the sample rate to
# 100 MSPS for all channels.
sdr.setMasterClockRate(100e6)
# Start streaming data on RX channel 0
rx_stream = sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CS16, [0])
# OK to change sample rate with an active stream.
sdr.setSampleRate(SOAPY_SDR_RX, 0, 25.0e6)
# ERROR: Cannot change master clock rate with an active data stream.
sdr.setMasterClockRate(122.88e6)
AirStack Core Version 1¶
The first generation AIR-Ts are supported by AirStack Core Version 1.0 and feature a fixed 125 MHz clock. From there, sample rates can be derived further via interpolation or decimation in the FPGA. The 7101 features fixed integer dividers, while the 7201 and 8201 have continuously variable filters. To see what sample rates are available, simply call getSampleRateRange()
.
Setting and Verifying the Sample Rate¶
In AirStack Core 1.0, sample rate can be set via simply calling setSampleRate()
. The function will attempt to find a sample rate within a small tolerance (1%) of the requested sample rate. To find the sample rate that the hardware was actually able to apply, please call getSampleRate()
immediately after setting the sample rate. This step is especially critical if your application requires a very specific/exact sample rate.
Time Reference¶
Similar to the clock reference, an external PPS input port is present on all AIR-T models. In AirStack 1.0 and later, the time reference (i.e., PPS) can be enabled similarly to the clock (i.e., 10 MHz) reference. This can be accomplished via the SoapySDR Time API ( e.g., sdr.setTimeSource("external")
) or via the time_src
device argument ( e.g., sdr = SoapySDR.Device(dict(driver="SoapyAIRT", time_src="external"))
). Note that users of GNU Radio can also set the time_src
device argument in their source or sink block. Also, as with the clock reference, the AIR-T model 8201 has a GPS
option to use the PPS from the GPS module as the time reference.
One important thing to test when setting the time reference is the presence or absence of the PPS when using an external
or GPS
time source. The following class provides a simplified interface to ensuring the PPS is present.
class TimeSourceValidator:
def __init__(self, sdr):
if not sdr.hasHardwareTime("pps"):
raise ValueError("SDR hardware does not support PPS!")
self._sdr = sdr
self._init_pps_count = self._read_pps_count()
def pps_detected(self):
return self._init_pps_count != self._read_pps_count()
def _read_pps_count(self):
return int(self._sdr.getHardwareTime("pps") / 1e9)
The following example shows how this class can be leveraged to detect the presence or absence of a time reference.
sdr = SoapySDR.Device(dict(driver="SoapyAIRT"))
sdr.setTimeSource("external")
time_src_check = TimeSourceValidator(sdr)
# At this point, perform some operation that takes at least a whole second. That
# is, call the pps_detected() method once you are sure a whole second has passed
# and that the PPS time should have incremented. setupStream() is a good choice
# here as it generally takes a good amount of time to initialize the hardware to
# begin streaming.
rx_stream = sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CS16, [0])
# setupStream() has returned, so a good amount of time has passed, PPS should
# have incremented.
if not time_src_check.pps_detected():
raise RuntimeError("PPS not detected!")
External Local Oscillators¶
Some AIR-T models have the ability to directly accept an external local oscillator (LO) for tuning the transceiver. Please check the Product Guide for your model AIR-T to determine if the interface exists.
If your model does support usage of and external LO, the LO source will be set using a Device Argument. See the documentation here for instructions on calling device arguments.