Skip to content

Triggering a Recording

This tutorial describes how to trigger the start of a samples stream using the front panel trigger port on the AIR-T.

For starting a samples stream at a specific time see the Timed RX Streams tutorial.

The code below is based off of the Hello World example with minimal modifications.

The AIR-T provides an external triggering and pulse per second front panel port labeled PPS. This port detects the rising edge of 3.3V LVCMOS signals.

Relevant code changes

  1. Import the SOAPY_SDR_WAIT_TRIGGER flag from SoapySDR.
  2. Pass the SOAPY_SDR_WAIT_TRIGGER flag to activateStream. This turns the radio on, dropping samples until the trigger rising edge is detected. readStream will not return until the trigger rising edge is detected (or timeout occurs).
  3. Set the cal_delay value to align trigger position in sample window.

Python Code

#!/usr/bin/env python3

# Import Packages
import numpy as np
from matplotlib import pyplot as plt
import SoapySDR
from SoapySDR import SOAPY_SDR_RX, SOAPY_SDR_CS16, SOAPY_SDR_WAIT_TRIGGER

# Settings
rx_chan = 0             # RX1 = 0, RX2 = 1
N = 16384               # Number of complex samples per transfer
fs = 31.25e6            # Radio sample Rate
freq = 2.4e9            # LO tuning frequency in Hz
use_agc = True          # Use or don't use the AGC
timeout_us = int(5e6)   # Stream read timeout
rx_bits = 16            # The AIR-T's ADC is 16 bits
cal_delay = 0           # Calibration delay to align trigger position in window

#  Initialize the AIR-T receiver using SoapyAIRT
sdr = SoapySDR.Device(dict(driver='SoapyAIRT'))       # Create AIR-T instance
sdr.setSampleRate(SOAPY_SDR_RX, rx_chan, fs)          # Set sample rate
sdr.setGainMode(SOAPY_SDR_RX, rx_chan, use_agc)       # Set the gain mode
sdr.setFrequency(SOAPY_SDR_RX, rx_chan, freq)         # Tune the LO

# Write calibration delay register
cal_reg_addrs = [0x0005006C, 0x00050070]
sdr.writeRegister('FPGA', cal_reg_addrs[rx_chan], cal_delay)

# Create data buffer and start streaming samples to it
# Create memory buffer for data stream
rx_buff = np.empty(2 * N, np.int16)
# Setup data stream
rx_stream = sdr.setupStream(SOAPY_SDR_RX, SOAPY_SDR_CS16, [rx_chan])
# Turn on the radio. It will stream samples once trigger rising edge is seen
sdr.activateStream(rx_stream, flags=SOAPY_SDR_WAIT_TRIGGER)

# Trigger should occur now
print(f'Waiting up to {timeout_us} usec for trigger rising edge...')

# Read the samples from the data buffer
sr = sdr.readStream(rx_stream, [rx_buff], N, timeoutUs=timeout_us)
rc = sr.ret # number of samples read or the error code
# Note that a common error code here is SOAPY_SDR_TIMEOUT, meaning that the
# trigger signal has not been detected within the timeout specified by timeoutUs.
assert rc == N, f'Error Reading Samples from Device (error code = {rc})!'

# Stop streaming
sdr.deactivateStream(rx_stream)
sdr.closeStream(rx_stream)

# Plot Signal
# Convert interleaved shorts (received signal) to numpy.complex64 and normalize
s0 = rx_buff.astype(float) / np.power(2.0, rx_bits-1)
s = (s0[::2] + 1j*s0[1::2])

# Take the fourier transform of the signal and perform FFT Shift
S = np.fft.fftshift(np.fft.fft(s, N) / N)

# Time Domain Plot
plt.figure(num=1, figsize=(12.95, 7.8), dpi=150)
plt.subplot(211)
t_us = np.arange(N) / fs / 1e-6
plt.plot(t_us, s.real, 'k', label='I')
plt.plot(t_us, s.imag, 'r', label='Q')
plt.xlim(t_us[0], t_us[-1])
plt.xlabel('Time (us)')
plt.ylabel('Normalized Amplitude')

# Frequency Domain Plot
plt.subplot(212)
f_ghz = (freq + (np.arange(0, fs, fs/N) - (fs/2) + (fs/N))) / 1e9
plt.plot(f_ghz, 20*np.log10(np.abs(S)))
plt.xlim(f_ghz[0], f_ghz[-1])
plt.ylim(-100, 0)
plt.xlabel('Frequency (GHz)')
plt.ylabel('Amplitude (dBFS)')
plt.show()


Application Notes

  • The AIR-T samples the trigger signal at 62.5MHz, i.e., 16ns.
  • Delay calibration registers are available that can align the trigger position in the sample capture window. See code changes above.
  • Each time activateStream is called the radio datapath calibrates. If it is required that the radio datapath delay is constant between calibrations, see additional register details at the bottom of this page.


Maintaining Fixed Delay Between Calibrations

This setting applies to both RX channels, it is not configurable per channel.

Include and call the below Python function in your code to apply a setting which ensures fixed datapath delay.

def set_jesd_sysref_delay(val=15):
    '''
    SYSREF delay: add additional delay to SYSREF re-alignment of LMFC counter
    1111 = 15 core_clk cycles delay
    ....
    0000 = 0 core_clk cycles delay
    In order to move away from the LFMC rollover we need to set bits 11:8
    of the SYSREF handling register which is at address 0x0004_0010.
    This register needs to be set before we try to sync the JESD204B bus.
    '''
    addr = 0x00040010;
    start_bit = 8
    field_size = 4
    bit_range = range(start_bit,start_bit+field_size)
    field_mask = 0
    for bit in bit_range:
        field_mask |= 1<<bit;

    # Read curr value
    reg = sdr.readRegister('FPGA', addr)
    # Clear the bit field
    reg &= ~field_mask
    # Set values of mask, dropping extra bits
    field_val_mask = (val << start_bit) & field_mask;
    # Set the bits
    reg |= field_val_mask
    # Write reg back
    sdr.writeRegister('FPGA', addr, reg)

Last update: January 17, 2024