VISA XOP ReadBinary DataFormat

Dear Igor Community,
currently I'm working on a data acquisition program, which reads from a Keyside 2985 electromter using the VISA XOP. To accomplish a high data transfer rate, I would like to read the data in binary format with the VISAReadBinaryWave operation. But unfortunately, in my case, the operation does not work as expected. Instead of the measured values, the VISAReadBinaryWave operation fills the wave with random numbers. Additionally, I get a the following VISA library error:

"(3fff0006) The number of bytes transferred is equal to the requested input count. More data might be available."

My code works fine if I set the Instrument to ASCII output and recieve the dataset via the VISAReadWave operation. I have also disabled terminator character detection as suggested in the VISA XOP manual as you can see in the reduced example below.

// ExampleCode
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Function KeysightReadBinaryData()
   
    // Initialization request
    String K2980Init = "*CLS;:SYSTem:PRESet;"
    K2980Init += ":FUNCtion:ON 'CURRent';"    
    K2980Init += ":FORMat:ELEMents:SENSe CURRent,TIME;"     // VOLTage|CURRent|CHARge|RESistance|TIME|STATus|SOURce|TEMPerature|HUMidity
    K2980Init += ":FORMat REAL,32;"                                // Set Format to 32 bit float
    K2980Init += ":CURRent:NPLCycles:AUTO OFF;"
    K2980Init += ":CURRent:NPLCycles 1;"    
    K2980Init += ":CURRent:RANGe 2e-12;"    
    K2980Init += ":CURRent:AVERage:MOVing OFF;"
    K2980Init += ":INPut ON;"                                        // Enables Current or Charge measurements
    K2980Init += ":SYSTem:TIME:TIMer:COUNt:RESet;"
    K2980Init += ":TRIGger:ACQuire:COUNt INF;"    
    K2980Init += ":TRIGger:ACQuire:SOURce TIMer;"
    K2980Init += ":TRIGger:ACQuire:TIMer 25E-3\n"
   
    // Connect
    Variable defaultRM, InstrumentID
    viOpenDefaultRM(defaultRM)
    viOpen(defaultRM, "GPIB0::23::INSTR", 0, 0, InstrumentID)
   
    viClear(InstrumentID)
    viSetAttribute(InstrumentID, VI_ATTR_TERMCHAR_EN, 0)    // Disable terminator character detection
   
   
    // Send Initialization
    VISAWrite InstrumentID, K2980Init
   
    // Get Errors
    String Error = ""
    int i = 0
    do
        VISAWrite InstrumentID, ":SYSTem:ERRor?\n"
        VISARead/T="\n" InstrumentID, Error
        if (!StringMatch(Error, "*No error*"))
            Print Error
        endif
        i++
    while (!StringMatch(Error, "*No error*") && i<20)

    VISAWrite InstrumentID, ":INITiate:ACQuire\n"
    Sleep/S 10
   
    // Create Waves
    Make/D/O/N=40 Currentwave, TimeWave
   
    // Request 40 Datapoints
    VISAWrite InstrumentID, ":DATA? CURR,40\n"
   
    // Read binary data
    VisaReadBinaryWave/B/TYPE=2 InstrumentID, Currentwave, TimeWave // Type: 2 -> 32-bit float, 4 -> 64-bit float
   
    // Print Error
    Print ReportVISAError("VISAReadBinary", InstrumentID, V_status)
   
    viClose(InstrumentID)
   
    return 0
End

Is it possible, that the dataformat of the Instrument is not compatible?

The instruments manual gives the following information:

"IEEE-754 single precision format, set by :FORMat[:DATA] REAL,32
4-byte definite length block data, #<number of digits for byte length><byte length><byte>...<byte><terminator>. For example, two data elements are sent by a data block which consists of a header (3 bytes, #18), two 4-byte data, and a terminator (1 byte). A 4-byte data is used for each data element. Each element consists of a fraction (bits 0 (LSB) to 22), exponent (bits 23 to 30), and sign (bit 31).
Order of bytes set by :FORMat:BORDer NORMal (default): byte 1 to 4
Order of bytes set by :FORMat:BORDer SWAPped: byte 4 to 1
NaN indicates “not a number.”
+infinity indicates positive infinity.
-infinity indicates negative infinity.

IEEE-754 double precision format, set by :FORMat[:DATA] REAL,64
8-byte definite length block data, #<number of digits for byte length><byte length><byte>...<byte><terminator>. For example, one data element is sent by a data block which consists of a header (3 bytes, #18), one 8-byte data, and a terminator (1 byte). An 8-byte data is used for each data element. Each element consists of a fraction (bits 0 (LSB) to 51), exponent (bits 52 to 62), and sign (bit 63).
Order of bytes set by :FORMat:BORDer NORMal (default): byte 1 to 8
Order of bytes set by :FORMat:BORDer SWAPped: byte 8 to 1
NaN indicates “not a number.”
+infinity indicates positive infinity.
-infinity indicates negative infinity."

I tested both, single and double precision, I also tried to change the byte-order. But no approach was succesful. I would be very happy, if anybody has an idea what is going wrong, or what else I could try. Thanks in advance.

4-byte definite length block data, #<number of digits for byte length><byte length><byte>...<byte><terminator>. For example, two data elements are sent by a data block which consists of a header (3 bytes, #18), two 4-byte data, and a terminator (1 byte). A 4-byte data is used for each data element.

I don't completely understand this description of the format but...

Only the data after the byte length and before the terminator is "IEEE-754 single precision format" (FP32 for short). So use VISAReadBinary to read the bytes before the FP32 data, then use VISAReadBinaryWave to read only the FP32 data, then use VISAReadBinary to read the terminator.

Before calling VISAReadBinaryWave, set the number of points in the wave to the number of FP32 data elements, such as (if you know that there are 40 FP32 data elements):

Make/O/N=40 Currentwave, TimeWave

VISAReadBinaryWave reads one element (FP32 in this case) for each element of the wave. (Remove the /D in your current Make statement as shown above.)

I don't know what byte order the instrument uses and it is not clear from the description above. You may need the /B flag or you may need to remove it.

 

Thanks a lot for your comment, which helped me to solve the problem.

Here comes my solution in case anybody has a similar problem:

With the format setting REAL,32 the Keysight 2985 transmits the binary data as follows:

  •    A header consisting of at least 3 bytes:

        -> The first byte is 35 in decimal (which corresponds to the #-singn in ASCII code)
        -> The second byte represents the number of the following header-bytes in ASCII code (1-9 in ASCII, this is represented by the decimal numbers 49-57)
        -> The following header bytes describe the number of databytes in ASCII code

  •     The 4-byte data in blocks pair-wise if more than one data element is selected, e.g. Currrent and Time: Current0 (4bytes), Time0 (4bytes), Current1 (4bytes), Time1 (4bytes)
  •     A 1-byte Terminator (10 in decimal, which corresponds to LF in ASCII)

As suggested, the header bytes are read first by VisaReadBinary. As VisaReadBinaryWave fills two waves subsequently, in my example the data are read in only one wave, which is subsequently split up.
 

// ExampleCode
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Function KeysightReadBinaryData()
   
    // Initialization request
    String K2980Init = "*CLS;:SYSTem:PRESet;"
    K2980Init += ":FUNCtion:ON 'CURRent';"  
    K2980Init += ":FORMat:ELEMents:SENSe CURRent, TIME;"    // VOLTage|CURRent|CHARge|RESistance|TIME|STATus|SOURce|TEMPerature|HUMidity
    K2980Init += ":FORMat REAL,32;"                             // Set Format to 32 bit float
    K2980Init += ":CURRent:NPLCycles:AUTO OFF;"
    K2980Init += ":CURRent:NPLCycles 1;"   
    K2980Init += ":CURRent:RANGe 2e-12;"   
    K2980Init += ":CURRent:AVERage:MOVing OFF;"
    K2980Init += ":INPut ON;"                                       // Enables Current or Charge measurements
    K2980Init += ":SYSTem:TIME:TIMer:COUNt:RESet;"
    K2980Init += ":TRIGger:ACQuire:COUNt INF;" 
    K2980Init += ":TRIGger:ACQuire:SOURce TIMer;"
    K2980Init += ":TRIGger:ACQuire:TIMer 25E-3\n"
   
    // Connect
    Variable defaultRM, InstrumentID
    viOpenDefaultRM(defaultRM)
    viOpen(defaultRM, "GPIB0::23::INSTR", 0, 0, InstrumentID)
   
    viClear(InstrumentID)
    viSetAttribute(InstrumentID, VI_ATTR_TERMCHAR_EN, 0)    // Disable terminator character detection
   
   
    // Send Initialization
    VISAWrite InstrumentID, K2980Init
   
    // Get Errors
    String Error = ""
    int i = 0
    do
        VISAWrite InstrumentID, ":SYSTem:ERRor?\n"
        VISARead/T="\n" InstrumentID, Error
        if (!StringMatch(Error, "*No error*"))
            Print Error
        endif
        i++
    while (!StringMatch(Error, "*No error*") && i<20)

    VISAWrite InstrumentID, ":INITiate:ACQuire\n"
    Sleep/S 5
   
    // Request 40 Datapoints
    VISAWrite InstrumentID, ":DATA? CURR,40\n"
   
    // Read Header
    Variable Prefix, HeaderLineNum
    VisaReadBinary/TYPE=8 InstrumentID, Prefix, HeaderLineNum
   
    String ByteNumStr = ""
    VisaReadBinary/S=(ASCIIToDecimal(HeaderLineNum)) InstrumentID, ByteNumStr
    Variable ByteNum = str2num(ByteNumStr)
   
    // Create Waves
    Make/O/N=(ByteNum/8) Currentwave, TimeWave
    Make/O/N=(ByteNum/4) TransferWave
   
    // Read binary data
    VisaReadBinaryWave/TYPE=2 InstrumentID, TransferWave // Type: 2 -> 32-bit float, 4 -> 64-bit float
    CurrentWave = TransferWave[2*p]
    TimeWave = TransferWave[2*p+1]
   
    // Read Terminator
    Variable Terminator
    VisaReadBinary/TYPE=8 InstrumentID, Terminator
   
    // Print Error
    Print ReportVISAError("VISAReadBinary", InstrumentID, V_status)
   
    viClose(InstrumentID)
   
    return 0
End


// ASCII conversion
Function ASCIIToDecimal(Input)
    Variable Input
   
    Switch (Input)
        case 48:
            return 0
        case 49:
            return 1
            break
        case 50:
            return 2
            break
        case 51:
            return 3
            break
        case 52:
            return 4
            break
        case 53:
            return 5
            break
        case 54:
            return 6
            break
        case 55:
            return 7
            break
        case 56:
            return 8
            break
        case 57:
            return 9
            break
        default:
            return NaN
            break
    Endswitch
End