Tracking markers

This Python example, tracking_markers.py, demonstrates using tracking markers in Oscilloscope mode. A simulated module and waveform is used instead of an DCA-X. Python 3 supports unicode, so a Greek symbol is shown in output from a print statement.

Example Script

Copy

tracking-markers.py

# -*- coding: utf-8 -*-
""" Installs simulated module and demonstrates using tracking markers on a simulated waveform.
Python 3 is unicode, so a Greek symbol is shown in output from a print statement.
"""

import pyvisa as visa  # import VISA library

ADDRESS = 'TCPIP0::localhost::hislip0,4880::INSTR'
CHANNEL = '5A'


def open_flexdca_connection(address):
    """ Opens visa connection to FlexFlexDCA. """
    print('Connecting to Flexdca ...')
    try:
        rm = visa.ResourceManager()
        connection = rm.open_resource(address)
        connection.timeout = 20000  # Set connection timeout to 20s
        connection.read_termination = '\n'
        connection.write_termination = '\n'
        inst_id = connection.query('*IDN?')
        print('\nFlexDCA connection established to:\n' + inst_id, flush=True)
    except (visa.VisaIOError, visa.InvalidSession):
        print('\nVISA ERROR: Cannot open instrument address.\n', flush=True)
        return None
    except Exception as other:
        print('\nVISA ERROR: Cannot connect to instrument:', other, flush=True)
        print('\n')
        return None
    return connection


def all_channels_off(flexdca):
    """ Turns all available channels off. """
    for slot in '12345678':
        for letter in 'ABCD':
            channel = slot + letter
            flexdca.write(':CHANnel' + channel + ':DISPlay OFF')


def install_simulated_module(flexdca, channel, model, signal='NRZ'):
    """ Simplified installation of a simulated FlexDCA module.
    model
        Type of simulated module. "DEM" is a dual-electrical module.
        "QEM" is a quad-electrical module. "DOM" is a dual-optical
        module. "QEM" is a electrical-optical module.
    signal
        Format of waveform. NRZ or PAM4.
    """
    slot = channel[0]
    flexdca.write(':EMODules:SLOT' + slot + ':SELection ' + model)
    if signal in 'NRZ':
        flexdca.write(':SOURce' + channel + ':FORMat NRZ')
    else:
        flexdca.write(':SOURce' + channel + ':FORMat PAM4')
    flexdca.write(':SOURce' + channel + ':DRATe 9.95328E+9')
    flexdca.write(':SOURce' + channel + ':WTYPe DATA')
    flexdca.write(':SOURce' + channel + ':PLENgth 127')
    flexdca.write(':SOURce' + channel + ':AMPLitude 90E-3')
    flexdca.write(':SOURce' + channel + ':NOISe:RN 3.0E-6')
    flexdca.write(':SOURce' + channel + ':JITTer:RJ 4.0E-12')
    flexdca.write(':CHANnel' + channel + ':DISPlay ON')


def configure_FlexDCA(flexdca, channel):
    """ Installs a simulated module and prepares FlexDCA for
    measurements.
    """
    flexdca.write('*CLS')
    flexdca.query(':SYSTem:DEFault;*OPC?')
    all_channels_off(flexdca)
    install_simulated_module(flexdca, channel, 'DEM')
    flexdca.write(':CHAN'+channel+':COLor TCOLor2')
    flexdca.write(':CHANnel' + channel + ':DISPlay ON')
    flexdca.query(':SYSTem:MODE OSCilloscope;*OPC?')
    flexdca.query(':TRIGger:PLOCk ON;*OPC?')
    flexdca.query(':SYSTem:AUToscale;*OPC?')
    flexdca.write(':ACQuire:EPATtern ON')  # Acquire entire pattern
    flexdca.write(':ACQuire:SPBit:MODe MANual')  # Select samples/bit
    flexdca.write(':ACQuire:SPBit 31.99212598')  # 32 samples/bit


def create_math_operator(flexdca, channel):  # Function Apply S2P
    """ Creates a "Bessel" waveform signal processing function.
    The required s2p file is selected from the demo folder as it is
    always available for this example. You can substitute your own file.
    """
    flexdca.write(':FUNCtion1:FOPerator BESSel')
    flexdca.write(':SPRocess1:BESSel:BANDwidth 4.000E+9')
    flexdca.write(':FUNCtion1:OPERand1 CHAN' + channel)
    flexdca.write(':FUNCtion1:COLor TCOLor4')  # Color for Function 1
    flexdca.write(':FUNCtion1:DISPlay ON')  # Turn on Function 1
    flexdca.query(':SYSTem:AUToscale;*OPC?')


def run_acquisition_limit_test(flexdca):
    """ Configures and runs acquisition limit test.
    Sixteen patterns are acquired. For pattern smoothing, patterns acquired
    equal patterns smoothed.
    """
    PatternCount = '16'
    flexdca.write(':ACQuire:SMOothing AVERage')
    flexdca.write(':LTESt:ACQuire:CTYPe:PATTerns ' + PatternCount)
    flexdca.write(':ACQuire:ECOunt ' + PatternCount)
    flexdca.write('ACQuire:SINGle')
    flexdca.write(':ACQuire:CDISplay')  # Clear acquired data
    flexdca.query(':LTESt:ACQuire:STATe ON;*OPC?')
    flexdca.timeout = 60000  # 60 seconds
    print('Acquiring ' + PatternCount + ' patterns.', flush=True)
    flexdca.query(':ACQuire:RUN;*OPC?')
    flexdca.write(':LTESt:ACQuire:STATe OFF')


def find_symbol_sequence(flexdca, channel):
    flexdca.write(':ACQuire:STOP')
    flexdca.query(':ACQuire:SINGle;*OPC?')
    flexdca.write(':TIMebase:UNITs UINTerval')
    flexdca.write(':TIMebase:FIND:SIGNal CHAN' + channel)
    flexdca.write(':TIMebase:FIND:SEQuence "0101"')
    i = float(flexdca.query(':TIMebase:UIPosition?'))
    pattern_start = int(i)
    flexdca.write(':TIMebase:FIND:NEXT')  # find first bit sequence
    i = float(flexdca.query(':TIMebase:UIPosition?'))
    ui_offset = int(i)
    ui = ui_offset - pattern_start
    if ui == 0:
        print('The symbol sequence was not found.')
    else:
        print('The symbol sequence was found.')


def create_measurement_regions(flexdca):
    """ Turns on two measurement regions."""
    flexdca.write(':MEASure:REGions:STATe ON')
    flexdca.write(':MEASure:REGions:COUNt 2')
    flexdca.write(':MEASure:REGions:ALIGn')


def create_markers(flexdca, channel):
    """ Turns on 4 tracking markers and positions them. """
    # Marker 1 on channel maximum amplitude region 1
    flexdca.write(':MEASure:OSCilloscope:TMAXimum:SOURce1 CHAN' + channel)
    flexdca.write(':MEASure:OSCilloscope:TMAXimum:REGion REGion1')
    s = flexdca.query(':MEASure:OSCilloscope:TMAXimum?')
    flexdca.write(':MARKer:Y1A:STATe TRACk')
    flexdca.write(':MARKer:X1:POSition ' + s)
    # Marker 2 on function maximum amplitude region 1
    flexdca.write(':MEASure:OSCilloscope:TMAXimum:SOURce1 FUNCtion1')
    flexdca.write(':MEASure:OSCilloscope:TMAXimum:REGion REGion1')
    s = flexdca.query(':MEASure:OSCilloscope:TMAXimum?')
    flexdca.write(':MARKer:Y2A:STATe TRACk')
    flexdca.write(':MARKer:X2:SOURce FUNCtion1')
    flexdca.write(':MARKer:X2:POSition ' + s)
    # Marker 3 on channel maximum amplitude region 2
    flexdca.write(':MEASure:OSCilloscope:TMAXimum:SOURce1 CHAN' + channel)
    flexdca.write(':MEASure:OSCilloscope:TMAXimum:REGion REGion2')
    s = flexdca.query(':MEASure:OSCilloscope:TMAXimum?')
    flexdca.write(':MARKer:Y3A:STATe TRACk')
    flexdca.write(':MARKer:X3:POSition ' + s)
    # Marker 4 on function maximum amplitude region 2
    flexdca.write(':MEASure:OSCilloscope:TMAXimum:SOURce1 FUNCtion1')
    flexdca.write(':MEASure:OSCilloscope:TMAXimum:REGion REGion2')
    s = flexdca.query(':MEASure:OSCilloscope:TMAXimum?')
    flexdca.write(':MARKer:Y4A:STATe TRACk')
    flexdca.write(':MARKer:X4:SOURce FUNCtion1')
    flexdca.write(':MARKer:X4:POSition ' + s)
    flexdca.write(':DISPlay:MARKER:DELTas ON')


def clean_display(flexdca):
    """ Turns off display annotations and measurement regions. """
    flexdca.write(':MEASure:ANNotations:STATe OFF')
    flexdca.write(':MEASure:REGions:STATe OFF')


def show_results(flexdca, channel):
    """ Displays marker values for high symbol in symbol sequence. """
    DELTAU = '\u0394'  # Δ
    print('-' * 40)
    print('\nResults For "0101" symbol Sequence')
    print('-' * 40)
    print('For first high symbol:')
    print(' '*24, 'Time'.ljust(11, ' '), 'Amplitude')
    s = build_table_rows('input waveform (' +
                         channel + '):', get_marker_values(flexdca, 1))
    print(s)
    s = build_table_rows('output waveform (F1):',
                         get_marker_values(flexdca, 2))
    print(s)
    s = build_table_rows(DELTAU + ':', get_marker_deltas(flexdca, 2))
    print(s)
    print('\nFor second high symbol:')
    s = build_table_rows('input waveform (' +
                         channel + '):', get_marker_values(flexdca, 3))
    print(s)
    s = build_table_rows('output waveform (F1):',
                         get_marker_values(flexdca, 4))
    print(s)
    s = build_table_rows(DELTAU + ':', get_marker_deltas(flexdca, 4))
    print(s)
    print()


def eng_notation(numstring, resolution):
    """  Converts a string in scientific notation to engineering notation.
     Unit multiplier character is appended to in final return string.
    Args
    ====
    numstring
        Number string for conversion. For example, '12.3e-4'.
    resolution
        An real that indicates number of decimal places in
        the result. For example, '0.01' would give '1.23 m'
    Returns
    =======
    Str representing number with multiplier character.
    """
    import decimal as dc
    MU = '\u03BC'  # μ
    if numstring in '9.91E+37':
        return ''
    dc.getcontext().prec = 10  # prevent limiting of decimal places
    multiplier = {12: 'T', 9: 'G', 6: 'M', 3: 'k', 0: '', -3: 'm',
                  -6: MU, -9: 'n', -12: 'p', -15: 'f'}
    numberReal = dc.Decimal(numstring)
    # absolute value is not between 0 and 1 (fraction)
    if abs(numberReal) >= 1:
        exponentMult = 0
        while int(numberReal) != 0:
            numberReal = numberReal / 1000
            exponentMult += 1
        numberReal *= 1000
        exponentMult -= 1
    elif (abs(numberReal) > 0) and (abs(numberReal) < 1):  # fraction
        exponentMult = 0
        while int(numberReal) == 0:
            numberReal = numberReal * 1000
            exponentMult += 1
        exponentMult *= -1
    elif numberReal == 0:  # number must be zero
        exponentMult = 0
    exponentMult *= 3
    if exponentMult == -15:
        n = numberReal.quantize(dc.Decimal('1'))
    else:
        n = numberReal.quantize(dc.Decimal(str(resolution)))
    return str(n) + ' ' + multiplier[exponentMult]


def get_marker_values(flexdca, marker):
    s = str(marker)
    x = flexdca.query(':MARKer:X' + s + ':POSition?')
    y = flexdca.query(':MEASure:MARKer:Y' + s + 'A?')
    return (eng_notation(x, '1.000'), eng_notation(y, '1.000'))


def get_marker_deltas(flexdca, marker):
    s = str(marker - 1)
    flexdca.write(':MARKer:REFerence X' + s)
    x = flexdca.query(':MEASure:MARKer:DX' + str(marker) + '?')
    y = flexdca.query(':MEASure:MARKer:DY' + str(marker) + 'A?')
    return (eng_notation(x, '1.000'), eng_notation(y, '1.000'))


def build_table_rows(name, values):
    x = values[0] + 's'
    x = x.ljust(12, ' ')
    y = values[1] + 'V'
    return name.rjust(23, ' ') + '  ' + x + y


FlexDCA = open_flexdca_connection(ADDRESS)
configure_FlexDCA(FlexDCA, CHANNEL)
create_math_operator(FlexDCA, CHANNEL)
run_acquisition_limit_test(FlexDCA)
find_symbol_sequence(FlexDCA, CHANNEL)
create_measurement_regions(FlexDCA)
create_markers(FlexDCA, CHANNEL)
clean_display(FlexDCA)
show_results(FlexDCA, CHANNEL)
FlexDCA.write(':SYSTem:GTLocal')
FlexDCA.close()