Jitter mode measurements

This simple Python script, jitter_mode.py, installs a simulated module and makes some Jitter measurements.

Script

Copy

jitter-mode.py

# -*- coding: utf-8 -*-
""" Example of making Jitter Mode measurements """

import time
import pyvisa as visa  # import VISA library

ADDRESS = 'TCPIP0::localhost::hislip0,4880::INSTR'
#ADDRESS = 'TCPIP0::K-N1000A-30053::hislip0,4880::INSTR'  # DCA-X
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 signal. 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.query(':SYSTem:DEFault;*OPC?')
    all_channels_off(flexdca)
    install_simulated_module(flexdca, channel, 'DEM')
    flexdca.write(':CHAN' + channel + ':DISPlay ON')
    flexdca.write(':ACQuire:RUN')
    flexdca.write(':SYSTem:MODE EYE')
    flexdca.query(':SYSTem:AUToscale;*OPC?')
    flexdca.query(':TRIGger:PLOCk ON;*OPC?')  # pattern lock on
    flexdca.write(':SYSTem:MODE JITTer')
    flexdca.write(':MEASure:AMPLitude:DEFine:ANALysis ON')


def return_scalar(flexdca, query):
    """ Returns a FlexDCA scalar measurement result. Uses a loop that checks
    the status of the measurement every 200 ms until the measurement
    result is ready. If the status is invalid 'INV', the loop
    continues waiting for the measurement to be ready.
    If status is questionable 'QUES', an exception is thrown.
    If a valid measurement cannot be returned within the Visa
    timeout setting an exception is thrown.
    """
    class ScalarMeasQuestionableException(Exception):
        """ A scalar measurement result is questionable. """
        pass

    class ScalarMeasTimeOutException(Exception):
        """ A scalar measurement has timed out. """
        pass

    timeout = flexdca.timeout / 1000  # convert ms to s
    query = query.strip('?')
    start_time = time.time()  # time in seconds
    while(True):
        time.sleep(0.2)
        status = flexdca.query(query + ':STATus?')
        if 'CORR' in status:  # valid measurement result
            return flexdca.query(query + '?')  # get measurement
        elif 'QUES' in status:  # questionable results
            s = query + '\nReason: ' + flexdca.query(query + ':STATus:REASon?')
            raise ScalarMeasQuestionableException(s)
        elif (int(time.time() - start_time)) > timeout:  # IO timout
            s = query + '\nReason: ' + flexdca.query(query + ':STATus:REASon?')
            raise ScalarMeasTimeOutException(s)


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 run_measurements(flexdca, channel):
    """ Returns several Jitter measurements.
    """
    print('\n' + '_' * 30)
    print('Jitter Measurement Results')
    meas = return_scalar(flexdca, ':MEASure:Jitter:EWIDth?')
    print('Eye Width: ' + eng_notation(meas, '1.0') + 's')
    meas = return_scalar(flexdca, ':MEASure:Jitter:TJ?')
    print('TJ: ' + eng_notation(meas, '1.0') + 's')
    print('_' * 30)
    print('Amplitude Measurement Results')
    flexdca.write(':MEASure:AMPLitude:EHEight:EYE EYE0')
    meas = return_scalar(flexdca, ':MEASure:AMPLitude:EHEight?')
    print('Eye Height: ' + eng_notation(meas, '1.00') + 'V')
    flexdca.write(':MEASure:AMPLitude:TI:LEVel LEVel1')
    meas = return_scalar(flexdca, ':MEASure:AMPLitude:TI?')
    print('TI (level 1): ' + eng_notation(meas, '1.00') + 'V')
    flexdca.write(':MEASure:AMPLitude:TI:LEVel LEVel0')
    meas = return_scalar(flexdca, ':MEASure:AMPLitude:TI?')
    print('TI (level 0): ' + eng_notation(meas, '1.00') + 'V')
    flexdca.write(':ACQuire:STOP')


FlexDCA = open_flexdca_connection(ADDRESS)
configure_FlexDCA(FlexDCA, CHANNEL)
run_measurements(FlexDCA, CHANNEL)
FlexDCA.write('DISPlay:TSMode FSIZe')
FlexDCA.write(':SYSTem:GTLocal')
FlexDCA.close()