Binary transfer of eye diagram data

This Python example, binary_transfer_eye.py, performs a binary transfer of an eye diagram waveform. The resulting data is shown in a tkinter GUI window. If you do not wish to import matplotlib library, comment out the The draw_graph() function.

The main purpose of this program is to demonstrate how to return binary eye diagram data with the graph used as a visual confirmation that the data was correctly interpreted. The example works with FlexDCA offline and installs simulated modules. No hardware is required. This script imports tkinter and matplotlib to draw a graph.

As shown in the following code, query_binary_values() method, is used to return the data.

eyedata = FlexDCA.query_binary_values(message,
                                      datatype='L',
                                      container=list,
                                      is_big_endian=False,
                                      header_fmt='ieee')

As described in :WAVeform:EYE:INteger:DATa?, the returned data is 391,271 points (1,565,084 bytes). This array corresponds to the graticule display, where each point contains a sample hit count. The array is transferred column by column, starting with the lower left corner of the graticule.

The following picture shows an example of the displayed graph.

Example Script

Copy

binary-transfer-eye.py

# -*- coding: utf-8 -*-
""" Installs a simulated module to supply waveform.
This program transfers binary eye data for a channel to the PC.
The eye format data (color grade/gray scale database values) are
returned in binary format using ':WAVeform:EYE:INTeger:DATa?'.
The draw_graph() function shows a graph of the data in a tkinter GUI window.
If you do not wish to import matplotlib library, comment out this function.
"""
import pyvisa as visa  # import VISA library
import sys

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


def open_flexdca_connection(address):
    """ Opens visa connection to FlexFlexDCA. """
    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)
        connection.write(':SYSTem:DEFault')
        connection.query('*OPC?')
    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.
    channel
        Simulated module's channel to use. Installation slot is
        derived from channel.
    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. """
    print('Configuring FlexDCA.', flush=True)
    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?')


def waveform_acquisition(flexdca, count):
    """ Perform an acquisition limit test to capture waveform data. """
    flexdca.write(':ACQuire:STOP')  # single acquisition mode
    flexdca.write(':ACQuire:CDISplay')  # Clear display
    if '1' in flexdca.query(':TRIGger:PLOCk?'):  # pattern lock is on
        flexdca.write(':LTESt:ACQuire:CTYPe:PATTerns ' + str(count))
    else:
        flexdca.write(':LTESt:ACQuire:CTYPe:WAVeforms ' + str(count))
    flexdca.write(':LTESt:ACQuire:STATe ON')
    flexdca.query(':ACQuire:RUN;*OPC?')  # OPC required when limit testing
    flexdca.write(':LTESt:ACQuire:STATe OFF')


def eng_notation(numstring, resolution):
    """  Converts a string in scientific notation to engineering notation.
     Unit multiplier character is appended to in final return string.
    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 string 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_eye_info(flexdca, channel):
    """ Returns information about eye diagram. """
    label = {}
    datasize = {}
    print('\nInformation on waveform:', flush=True)
    flexdca.write(':WAVeform:SOURce CHANnel' + channel)
    # xmin label on graph
    xmin = flexdca.query(':WAVeform:EYE:XORigin?')
    s = eng_notation(xmin, '0.2') + 's'
    print('\tX origin: ', s)
    label['xmin'] = s
    # ymin label on graph
    ymin = flexdca.query(':WAVeform:EYE:YORigin?')
    s = eng_notation(ymin, '0.2') + 'V'
    print('\tY origin: ', s)
    label['ymin'] = s
    # time spacing is delta time between columns
    timespacing = flexdca.query(':WAVeform:EYE:XINCrement?')
    s = eng_notation(timespacing, '0.2') + 's'
    print('\tTime Spacing: ', s)
    # row spacing is delta amplitude between rows
    rowspacing = flexdca.query(':WAVeform:EYE:YINCrement?')
    s = eng_notation(rowspacing, '0.2') + 'V'
    print('\tRow Spacing: ', s)
    #  number of data rows and columns
    s = flexdca.query(':WAVeform:EYE:ROWS?')
    datasize['rows'] = int(s)
    print('\tNumber of rows: ', s)
    s = flexdca.query(':WAVeform:EYE:COLumns?')
    datasize['columns'] = int(s)
    print('\tNumber of Columns: ', s)
    # Calculate x and y max labels on graph
    s = str(float(xmin) + (float(timespacing) * float(datasize['columns'])))
    s = eng_notation(s, '0.2') + 's'
    label['xmax'] = s
    s = str(float(ymin) + (float(rowspacing) * float(datasize['rows'])))
    s = eng_notation(s, '0.2') + 'V'
    label['ymax'] = s
    return label, datasize


def get_binary_eye_data(flexdca, channel):
    """ Returns the data points for an eye diagram by transferring binary data to computer.
    """
    print('Returning eye waveform data.', flush=True)
    flexdca.read_termination = ''
    flexdca.write_termination = ''
    endiansetting = flexdca.query(':SYSTem:BORDER?')
    flexdca.write(':SYSTem:BORDER LENDian')
    message = ':WAVeform:EYE:INTeger:DATa?'
    eyedata = list()
    eyedata = flexdca.query_binary_values(message, datatype='L', container=list, is_big_endian=False, header_fmt='ieee')
    flexdca.write(':SYSTem:BORDER ' + endiansetting)
    flexdca.read_termination = '\n'
    flexdca.write_termination = '\n'
    return eyedata


def draw_graph(graph_data, label, datasize, channel):
    """ Plots binary data on tkinter window and saves graphics file to show that we received the
    data from FlexDCA. Data is not amplitude and time values but rather, for
    each database row and column, an integer count of number of hits.
    A 0 value indicates not hits for the point.
    """
    import tkinter
    from matplotlib.backends.backend_tkagg import (
        FigureCanvasTkAgg, NavigationToolbar2Tk)
    from matplotlib.figure import Figure

    root = tkinter.Tk()
    root.wm_title("Graph of Eye Width")
    print('Drawing graph.', flush=True)
    fig = Figure(figsize=(10.0, 8.0), dpi=96, edgecolor='black')  # width and height in inches
    ax = fig.add_subplot(111)
    ax.set_xlim(0, 10)
    ax.set_ylim(0, 8)
    ax.set_aspect('0.8')
    ax.grid(which='major', axis='both', linewidth=0.75,
            linestyle='-', color='0.75')
    ax.set_xticks([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    ax.set_yticks([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
    ax.set_xticklabels([label['xmin'], '', '', '', '', '', '', '',
                        '', '', label['xmax']])
    ax.set_yticklabels([label['ymin'], '', '', '', '0', '', '',
                        '', label['ymax']])
    ax.set_ylabel('Voltage', fontsize=12)
    ax.set_xlabel('Time', fontsize=12)
    fig.suptitle('Channel ' + channel + ' Eye Diagram',
                 y=0.85, fontsize=12)
    x = []  # scaled point x position
    y = []  # scaled point y position
    n = 0  # list index
    col = 0
    rows = datasize['rows']
    columns = datasize['columns']
    while col < columns:
        row = 0
        while row < rows:
            point = graph_data[n]
            if point:  # point has hits
                x.append(round((col / columns * 10), 3))  # scaled x location
                y.append(round((row / rows), 3) * 8)  # scaled y location
            row += 1
            n += 1
        col += 1
    ax.scatter(x, y, s=2, color='green', alpha=1, marker='.')
    canvas = FigureCanvasTkAgg(fig, master=root)
    canvas.draw()
    toolbar = NavigationToolbar2Tk(canvas, root)
    toolbar.update()
    button = tkinter.Button(master=root, text="Quit", command=root.quit)
    button.pack(side=tkinter.BOTTOM)
    toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X)
    canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=1)
    print('Graph of returned data is displayed on separate window.')
    sys.stdout.flush()  # Flush print message in stdout
    tkinter.mainloop()


FlexDCA = open_flexdca_connection(ADDRESS)
configure_FlexDCA(FlexDCA, CHANNEL)
waveform_acquisition(FlexDCA, 100)
label, datasize = get_eye_info(FlexDCA, CHANNEL)
graph_data = get_binary_eye_data(FlexDCA, CHANNEL)
draw_graph(graph_data, label, datasize, CHANNEL)
FlexDCA.write(':SYSTem:GTLocal')
FlexDCA.close()