LeCroy scope trace acquisition over IP with VISA

This procedure grabs a trace from the scope and inserts into a wave. Multiple scopes may be accommodated, each with a fixed IPv4 address. Only tested on WinXP and LeCroy WaveRunner 104Xi, but other LeCroy scopes will probably work.

Synopsis:
LeCroyGetTrace(99, "C1", awave) // Scope has IP address of 192.168.1.99

Wave is redimensioned to number of points in the trace, so original size doesn't matter. Wave scaling is correct for both horizontal and vertical axes. Averaged traces are handled properly, using 2-byte transfer to maintain higher precision.
#pragma rtGlobals=1     // Use modern global access method.

// File LeCroyGetTrace.ipf
// Version 2009-02-19
//
// Read a trace from LeCroy digital oscilloscope over IP
// Assumes 'scope is on local LAN with fixed IP address of the form 192.168.1.x
// This allows using multiple scopes (with different IP addresses).
//
// Only tested on WinXP and LeCroy 104Xi
//
// Requires NI-VISA - ftp.ni.com/support/visa/drivers/win32/4.4/visa441full.exe
// Requires LeCroy VICP Passport - lecroy.com/tm/Library/Software/LabView/VICP.asp?menuid=8
// Requires VISA.xop - comes with Igor, put into C:\Program Files\Igor\Igor Extensions
//
// Synopsis:
//
// Make awave                        // Number of points doesn't matter, it will be redimensioned
// LeCroyGetTrace(99, "C1", awave)   // Scope has IP address of 192.168.1.99
// Display awave
// print SelectString(WaveIsClipped(a), "Wave is not clipped", "Wave is clipped")
//
// Wave is redimensioned to number of points in the trace, so original size doesn't matter
// Wave scaling is correct for both horizontal and vertical axes.
//
// Averaged traces are handled properly, using 2-byte transfer to maintain higher precision
//
// Author: Robert Hiller   bob AT roberthiller DOT com
//


// This function is copied from VISA.ipf (and renamed) to allow operation
// even if the VISA.ipf procedure file is not present.
Function LocalReportVISAError(name, session, status)
    String name             // VISA function name, e.g., viRead or other identifier
    Variable session            // Session ID obtained from viOpen
    Variable status         // Status code from VISA library
   
    String desc
   
    viStatusDesc(session, status, desc)
    Printf "%s error (%x): %s\r", name, status, desc
End

Structure LeCroy_time       // Time Stamp structure for trigger time from LeCroy scope
    double      seconds             // seconds and fractional seconds
    char        minutes             // (0-59)
    char        hours                   // (0-23)
    char        days                    // (0-31)
    char        months                  // (1-12)
    int16       year                    // (0-16000)
    int16       unused                  //
EndStructure       

Structure WAVEDESC          // Wave descriptor block for LeCroy scope
    char        DEF9header[16]      // 'DESC,#9000000346'  (Not part of template)
    char        DESCRIPTOR_NAME[16] // 'WAVEDESC        '
    char        TEMPLATE_NAME[16]   // 'LECROY_2_3      ' Name of the template
    int16       COMM_TYPE               // 0=byte, 1=word
    int16       COMM_ORDER          // 0=hifirst, 1=lofirst
    int32       WAVE_DESCRIPTOR     // length of block WAVEDESC
    int32       USER_TEXT               // length of block USERTEXT
    int32       RES_DESC1               // unknown
    int32       TRIGTIME_ARRAY      // length of TRIGTIME array
    int32       RIS_TIME_ARRAY      // length of RIS_TIME array
    int32       RES_ARRAY1          //    (expansion entry)
    int32       WAVE_ARRAY_1            // lengh of first data array
    int32       WAVE_ARRAY_2            // length of second data array
    int32       RES_ARRAY2          //    (expansion entry)
    int32       RES_ARRAY3          //    (expansion entry)
    char        INSTRUMENT_NAME[16] // string name of instrument
    int32       INSTRUMENT_NUMBER   // serial number
    char        TRACE_LABEL[16]     // identifies the waveform
    int16       RESERVED1               //    (expansion entry)
    int16       RESERVED2               //    (expansion entry)
    int32       WAVE_ARRAY_COUNT        // number of data points in data array
    int32       PNTS_PER_SCREEN     // nominal number of points on screen
    int32       FIRST_VALID_PNT     // number points to skip before first valid
    int32       LAST_VALID_PNT      // index of last good point
    int32       FIRST_POINT         // offset from beginning of trace buffer
    int32       SPARSING_FACTOR     // sparsing into the data block
    int32       SEGMENT_INDEX       // index of the transmitted segment
    int32       SUBARRAY_COUNT      // acquired segment count
    int32       SWEEPS_PER_ACQ      // for average or extrama
    int16       POINTS_PER_PAIR     // for peak detect
    int16       PAIR_OFFSET         // for peak detect, offset array2 to array1
    float       VERTICAL_GAIN       // to get floating values from raw data:
    float       VERTICAL_OFFSET     //     value = VERTICAL_GAIN * data - VERTICAL_OFFSET
    float       MAX_VALUE               // maximum allowed value, upper edge of the grid.
    float       MIN_VALUE               // minimum allowed value, lower edge of the grid.
    int16       NOMINAL_BITS            // intrinsic precision: ADC data is 8 bit, averate 10-12
    int16       NOM_SUBARRAY_COUNT  // for Sequence, nominal segment count, else 1
    float       HORIZ_INTERVAL      // sampling interval for time domain waveforms
    double      HORIZ_OFFSET            // seconds between trigger and the first data point
    double      PIXEL_OFFSET            // "needed to know how to display the waveform"
    char        VERTUNIT[48]            // units of the vertical axis
    char        HORUNIT[48]         // units of the horizontal axis
    float       HORIZ_UNCERTAINTY   // uncertainty of the horizontal offset in seconds
    STRUCT      LeCroy_time TRIGGER_TIME    // time of the trigger
    float       ACQ_DURATION            // duration of the acquisition (in sec)
    int16       RECORD_TYPE         // 0=single_sweep, etc
    int16       PROCESSING_DONE     // 0=no_processing, etc
    int16       RESERVED5               // (expansion entry)
    int16       RIS_SWEEPS          // for RIS, number of sweeps, else 1
    int16       TIMEBASE                // 0=1_ps/div, 1=2_ps/div, etc
    int16       VERT_COUPLING       // 0=DC_50_Ohms, 1=ground, 2=DC_1MOhm, 3=ground, 4=AC_1MOhm
    float       PROBE_ATT               // probe attenuation
    int16       FIXED_VERT_GAIN     // 0=1_uV/div, 1=2_uv/div, etc
    int16       BANDWIDTH_LIMIT     // 0=off, 1=on
    float       VERTICAL_VERNIER        // vertical gain fine adjust
    float       ACQ_VERT_OFFSET     // unknown
    int16       WAVE_SOURCE         // 0=CHANNEL_1, 1=CHANNEL_2, 2=CHANNEL_3, 3=CHANNEL_4, 9=UNKNOWN
EndStructure

// Get waveform data from LeCroy scope into a wave using IP (ethernet)
// Scope is expected to be on local network with ip address 192.168.1.X
//
// The wave is properly scaled for time.  Vertical scaling shows scope range.
// The wave is re-dimensioned to hold the number of points in the waveform.
// Averaged waves are handled properly, using 2-byte transfer to maintain higher precision
//
// This function requires: NIVisa, LeCroy VICP passport, and the Igor VISA xop.
//
Function LeCroyGetTrace(ip, trace, datawave)
    Variable ip                 // Last number of IPv4 address: 192.168.1.ip
    String trace                    // Trace to fetch, e.g. "C1" or "Z2" or "F3" or "M4"
    Wave datawave               // Wave to receive data.  This wave is redimensioned to fit the data.
   
    Variable defaultRM          // Resource manager session ID
    Variable instr              // Instrument ID, used for subsequent read and write calls
    Variable status             // Status return from vi calls.  Non-zero value indicates failure
    String cmd
    STRUCT WAVEDESC wd
    String wdstring
    Variable NominalBits, DataBits, DataOffset, Minimum, Maximum

    // Create a Resource Manager for VISA calls.  It will be closed at the end of the function.    
    status = viOpenDefaultRM(defaultRM)
    if (status != 0)
        LocalReportVISAError("viOpenDefaultRM", instr, status)
        return status
    endif

    // Open a session with the scope.  The variable instr is used for subsequent communication.
    String resourceName
    sprintf resourceName, "VICP::192.168.1.%d", ip   // VICP requires LeCroy VICP passport plugin
    status = viOpen(defaultRM, resourceName, 0, 0, instr)  
    if (status != 0)
        viClose(defaultRM)
        LocalReportVISAError("viOpen", instr, status)
        return status
    endif

    // Turn off command headers for predictable response from scope
    VISAwrite instr, "CHDR OFF"     // Turn off command headers (echo) in return
    if (V_flag == 0)                        // Problem with communication
        LocalReportVISAError("VISAWrite", instr, V_status)
        viClose(instr) 
        viClose(defaultRM)
        return V_status
    endif

    // Non-averaged data has 8 bits of resolution.  Averaged or calculated data can have more bits.
    // The number of bits is interrogated first.  If data has more than 8 bits of resolution
    // then the data is transferred as words.  This has to be resolved before the header is read.
    // Bad return for NOMINAL_BITS means bad communication or bad trace identifier 
    sprintf cmd, "%s:INSPECT? 'NOMINAL_BITS'", trace   // Get bit depth
    VISAwrite instr, cmd
    VISAread/T="\r\n" instr, cmd
    Sscanf cmd, "\"NOMINAL_BITS       : %d", NominalBits    // This scan only works with CHDR OFF
    if (NominalBits == 8)
        VISAwrite instr, "CFMT DEF9, BYTE,BIN"          // Use single byte transfer if bit depth is 8
    elseif (NominalBits > 8)
        VISAwrite instr, "CFMT DEF9, WORD,BIN"        // Use 2 byte transfer if bit depth is greater
    else
        Print "Failure: bad return for NOMINAL_BITS:", NominalBits, " Trace requested:", trace
        viClose(instr) 
        viClose(defaultRM)
        Return NominalBits
    endif

    // Get the Wave Descriptor block, and parse into WAVEDESC structure
    sprintf cmd, "%s:WF? DESC", trace   // Request waveform descriptor
    VISAwrite instr, cmd
    VISAReadBinary/S=362 instr, wdstring    // Header is 346 bytes, plus 16 byte IEEE488.2 DEF9 leadin
    StructGet/S/b=3 wd, wdstring        // String is parsed into header template. Little-endian byte order.
   
    // Redimension wave to fit the data.  Wave is made single precision (to save space).
    Redimension/S/N=(wd.WAVE_ARRAY_COUNT) datawave
   
    // Get the data array block.  Bytes per datum (1 or 2) is set above, depending on nominal bits.
    // Data is scaled on the fly with /Y option to VISAReadBinaryWave to true vertical scaling (volts)
    sprintf cmd, "%s:WF? DAT1", trace   // Request data array (bytes or words depending on CommType)
    VISAwrite instr, cmd
    DataOffset = -wd.VERTICAL_OFFSET / wd.VERTICAL_GAIN   // This is needed for on-the-fly scaling in VISAReadBinary
    VISAReadBinary/S=16 instr, cmd      // Get past IEEE488 DEF9 leadin "DAT1,#9xxxxxxxx"
    DataBits = (wd.COMM_TYPE + 1) * 8       // TYPE flag is 8 for byte, 16 for word
    VISAReadBinaryWave/B/TYPE=(DataBits)/Y={DataOffset,wd.VERTICAL_GAIN} instr, datawave   // Vertical scale data on the fly

    // Set horizontal scaling for correct timing on each point
    SetScale/P x, wd.HORIZ_OFFSET, wd.HORIZ_INTERVAL, "s", datawave   // Change wave scaling for correct horizontal
   
    // Set vertical scaling to represent absolute maximum and minimum.
    // If data values exceed these it may be assumed to be clipped.
    // Use functions FullScaleMax(w) and FullScaleMin(w) (in this .ipf file) to retrieve these values
    Minimum = wd.MIN_VALUE*wd.VERTICAL_GAIN-wd.VERTICAL_OFFSET
    Maximum = wd.MAX_VALUE*wd.VERTICAL_GAIN-wd.VERTICAL_OFFSET
    SetScale d Minimum,Maximum, "V", datawave    // Set vertical units to volts
       
    viClose(instr) 
    viClose(defaultRM)
End


// Get scope channel trace by channel number.  See LeCroyGetTrace for details.
Function LeCroyGetChannel(ip, channel, datawave)
    Variable ip                 // Last number of IPv4 address: 192.168.1.ip
    Variable channel                // Channel to fetch, 1-4
    Wave datawave                   // Wave to receive data.  This wave is redimensioned to fit the data.
   
    String trace
    sprintf trace, "C%d", channel
    LeCroyGetTrace(ip, trace, datawave)
End

// Generic command send and receive from the scope
// Example:
//     print LeCroyQuery(99, "*IDN?")
Function/S LeCroyQuery(ip, cmd)
    Variable ip
    String cmd
   
    Variable defaultRM, instr  
    Variable status

    status = viOpenDefaultRM(defaultRM)
    String resourceName
    sprintf resourceName, "VICP::192.168.1.%d", ip
    status = viOpen(defaultRM, resourceName, 0, 0, instr)
   
    VISAwrite instr, cmd
   
    VISAread/T="\r\n" instr, cmd

    viClose(instr) 
    viClose(defaultRM)
   
    Return cmd
End

// Generic command send to the scope
// Example:
//     LeCroyWrite(99, "MSG 'Your Ad Here'")
Function LeCroyWrite(ip, cmd)
    Variable ip
    String cmd
   
    Variable defaultRM, instr  
    Variable status, ret

    status = viOpenDefaultRM(defaultRM)
    if (status)
        LocalReportVISAError("viOpenDefaultRM", instr, status)
    endif

    String resourceName
    sprintf resourceName, "VICP::192.168.1.%d", ip
    status = viOpen(defaultRM, resourceName, 0, 0, instr)
    if (status)
        LocalReportVISAError("viOpen", instr, status)
    endif
   
    status=viWrite(instr, cmd, strlen(cmd), ret)
    if (status)
        LocalReportVISAError("viWrite", instr, status)
    endif

    // Intermittent bug
    // The write command is not completed, and is lost, unless some
    // other command is done before the instrument is closed.  If
    // the following line is removed, the scope does not see the
    // data sent by the viWrite command above.
//  status = viGetAttribute(instr, VI_ATTR_TMO_VALUE, ret)
//  if (status)
//      LocalReportVISAError("viGetAttribute", instr, status)
//  endif

    viClose(instr) 
End


// Returns Full Scale Minimum for a wave.  Settable/viewable in Change Wave Scaling dialog.
Function FullScaleMin(w)
    Wave w
    String wi, fsinfo
   
    wi= WaveInfo(w,0)       // WaveInfo string is key-value pairs.  Zero is required.
    fsinfo = StringByKey("FULLSCALE", wi)       // Fullscale info is HasFSinfo, FSMin, FSMax
    if (str2num(StringFromList(0, fsinfo, ",")))        // True means Full scale info is present
        return str2num(StringFromList(1, fsinfo, ","))
    else
        return NaN      // If no full scale info, then return Nan
    endif
End

// Returns Full Scale Minimum for a wave.  Settable/viewable in Change Wave Scaling dialog.
Function FullScaleMax(w)
    Wave w
    String wi, fsinfo
   
    wi= WaveInfo(w,0)       // WaveInfo string is key-value pairs.  Zero is required.
    fsinfo = StringByKey("FULLSCALE", wi)       // Fullscale info is HasFSinfo, FSMin, FSMax
    if (str2num(StringFromList(0, fsinfo, ",")))        // True means Full scale info is present
        return str2num(StringFromList(2, fsinfo, ","))
    else
        return NaN      // If no full scale info, then return Nan
    endif
End

// Tests if wave is clipped (range extends to or beyond Full Scale maximum or minimum)
// If full scale information is not present, returns FALSE (depends on funky NaN math).
Function WaveIsClipped(w)
    Wave w
   
    WaveStats/Q w
    return (V_max>=FullScaleMax(w)) || (V_min<=FullScaleMin(w))
End
Thanks, Howard.

This routine is fairly bulletproof; it's been running in a number of applications, and has come to be a workhorse around our lab. The combination of Igor and digitizing scopes makes a powerful tool. The data transfer runs faster than 10 MSa/s, even through multiple IP switches.

If more folks are interested in modifying this, it might do better as an Igor Exchange project. I don't know how to set that up, but would be supportive if somebody else did.

A few notes on this routine:

The wavedescriptor structure is taken from the template from the scope. The scope has a text description of these fields built-in, which can be accessed with the TMPL? query. All of the fields are automatically filled in, but only a few are used in this script. An improved version would put many of these into global variables, such as BANDWIDTH_LIMIT, VERT_COUPLING, the trigger time, and so on.

Note that the scope query function LeCroyQuery(ip, cmd) as is currently written will terminate on the first endline, so it cannot read the template without modification. Change VISAread/T="\r\n" instr, cmd to VISAread/N=65535 instr, cmd

The maximum and minimum possible values are stored in the wave's 'd' scaling. They may be accessed with the addition functions FullScaleMin(w) and FullScaleMax(w) in this procedure file. The scope *does* return values outside of these limits (by a few bits) but I think it's safe to assume the wave is clipped if any values are outside.

Robert Hiller

Nice work!!!

I have used Igor Pro 8 to communicate to the LeCroy HDO6104, HDO4014, and Wave Surfer 4054HD.

I have confirmed that this routine works fine on these systems (although I have modified the code somewhat).

Forum

Support

Gallery

Igor Pro 9

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More