LeCroy scope trace acquisition over IP with VISA

Average rating
(1 vote)

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

Nice work!

Nice work!

Thanks, Howard. This routine

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

Back to top