PyVISA
PyVISADocs

Large Data Transfers

How to transfer multi-megabyte waveforms and spectra from instruments with PyVISA. Binary protocols, chunked reads, and memory-efficient patterns.

Modern oscilloscopes produce 10M+ sample waveforms (40+ MB). Standard PyVISA settings can be 10-100x slower than needed. Binary format and proper chunk sizes fix most of it.

Binary vs ASCII Comparison

import pyvisa
import time
import numpy as np

rm = pyvisa.ResourceManager()
scope = rm.open_resource("USB0::0x0699::0x0363::C102912::INSTR")
scope.timeout = 30000

# ASCII: human-readable, slow
start = time.time()
scope.write("FORM:DATA ASC")
ascii_data = scope.query("CURV?")
ascii_values = [float(x) for x in ascii_data.split(",")]
ascii_time = time.time() - start

# Binary: raw bytes, fast
start = time.time()
scope.write("FORM:DATA REAL,32")
binary_values = scope.query_binary_values("CURV?", datatype="f")
binary_time = time.time() - start

print(f"ASCII:  {ascii_time:.2f}s ({len(ascii_values)} points)")
print(f"Binary: {binary_time:.2f}s ({len(binary_values)} points)")
print(f"Speedup: {ascii_time / binary_time:.1f}x")

# Typical result for 1M points:
# ASCII:  12.3s
# Binary: 0.8s
# Speedup: 15.4x

scope.close()
rm.close()

Log measurements automatically

TofuPilot records test results from your PyVISA scripts, tracks pass/fail rates, and generates compliance reports. Free to start.

Fast Waveform Capture

Set binary encoding, increase chunk_size, and use *OPC? to wait for acquisition.

import pyvisa
import numpy as np
import time

rm = pyvisa.ResourceManager()
scope = rm.open_resource("USB0::0x0699::0x0363::C102912::INSTR")
scope.timeout = 30000
scope.chunk_size = 1024 * 1024  # 1 MB chunks

scope.write("*RST")
scope.write("*CLS")

# Binary format, 16-bit samples
scope.write("DATA:ENC RIBINARY")
scope.write("DATA:WIDTH 2")
scope.write("DATA:START 1")
scope.write("DATA:STOP 10000000")  # Up to 10M points

# Fastest acquisition mode
scope.write("ACQ:MODE SAMPLE")
scope.write("HOR:RECORDLENGTH 10000000")
scope.write("ACQ:STOPAFTER RUNSTOP")
scope.write("ACQ:STATE ON")

scope.query("*OPC?")

# Read scaling from preamble
scope.write("DATA:SOURCE CH1")
preamble = scope.query("WFMPRE?").split(",")
y_scale = float(preamble[13])
y_offset = float(preamble[14])
x_scale = float(preamble[9])

# Transfer
start = time.time()
raw = scope.query_binary_values("CURVE?", datatype="h")
elapsed = time.time() - start

voltages = np.array(raw) * y_scale + y_offset
time_axis = np.arange(len(voltages)) * x_scale

print(f"{len(voltages)} points in {elapsed:.2f}s")
print(f"Transfer rate: {len(voltages) / elapsed / 1e6:.1f} MSa/s")

scope.close()
rm.close()

Chunked Transfer for Very Large Waveforms

When a waveform exceeds available memory (or you want progress feedback), transfer it in chunks.

import pyvisa
import numpy as np

def chunked_waveform(instrument, total_points, chunk_size=1000000):
    """Transfer a large waveform in chunks."""
    all_data = []

    for start in range(1, total_points + 1, chunk_size):
        end = min(start + chunk_size - 1, total_points)

        instrument.write(f"DATA:START {start}")
        instrument.write(f"DATA:STOP {end}")

        chunk = instrument.query_binary_values("CURVE?", datatype="h")
        all_data.extend(chunk)

        done = len(all_data)
        print(f"{done}/{total_points} points ({100 * done / total_points:.0f}%)")

    return np.array(all_data)

# Usage
rm = pyvisa.ResourceManager()
scope = rm.open_resource("USB0::0x0699::0x0363::C102912::INSTR")
scope.timeout = 60000
scope.chunk_size = 1024 * 1024

scope.write("DATA:ENC RIBINARY")
scope.write("DATA:WIDTH 2")

waveform = chunked_waveform(scope, total_points=50000000)
print(f"Total: {len(waveform)} points")

scope.close()
rm.close()

Spectrum Analyzer Trace

Binary format works the same way for spectrum analyzers.

import pyvisa
import numpy as np
import time

rm = pyvisa.ResourceManager()
sa = rm.open_resource("TCPIP::192.168.1.101::INSTR")
sa.timeout = 60000

# Binary format, single sweep
sa.write("FORM:DATA REAL,32")
sa.write("INIT:CONT OFF")
sa.write("SWE:POIN 50001")
sa.write("FREQ:START 1e6")
sa.write("FREQ:STOP 6e9")

sa.write("INIT:IMM")
sa.query("*OPC?")

start = time.time()
trace = sa.query_binary_values("TRAC? TRACE1", datatype="f")
elapsed = time.time() - start

freqs = np.linspace(1e6, 6e9, len(trace))
print(f"{len(trace)} points in {elapsed:.2f}s")

sa.close()
rm.close()

Streaming FFT (Chunked Processing)

For waveforms too large to FFT in one shot, process power spectra per chunk and average.

import numpy as np
import gc

def streaming_fft(instrument, chunk_size=1000000):
    """Compute averaged power spectrum from a large waveform in chunks."""
    total = int(instrument.query("HOR:RECORDLENGTH?"))
    sample_rate = float(instrument.query("HOR:SAMPLERATE?"))

    accumulator = None
    n_chunks = 0

    for start in range(1, total + 1, chunk_size):
        end = min(start + chunk_size - 1, total)
        instrument.write(f"DATA:START {start}")
        instrument.write(f"DATA:STOP {end}")

        chunk = instrument.query_binary_values("CURVE?", datatype="h")
        arr = np.array(chunk, dtype=np.float32)

        power = np.abs(np.fft.fft(arr)) ** 2
        if accumulator is None:
            accumulator = power
        else:
            accumulator += power

        n_chunks += 1
        del chunk, arr, power
        gc.collect()
        print(f"Chunk {n_chunks} processed")

    avg_power = accumulator / n_chunks
    freqs = np.fft.fftfreq(len(avg_power), 1 / sample_rate)

    # Return positive half
    half = len(freqs) // 2
    return freqs[:half], avg_power[:half]

TCP/IP Tuning

For Ethernet instruments, connection parameters affect throughput.

import pyvisa

rm = pyvisa.ResourceManager()

# VXI-11 (standard)
inst = rm.open_resource(
    "TCPIP::192.168.1.100::inst0::INSTR",
    timeout=60000,
    chunk_size=2 * 1024 * 1024,  # 2 MB
)

# Raw socket (lower latency, if instrument supports port 5025)
# inst = rm.open_resource(
#     "TCPIP::192.168.1.100::5025::SOCKET",
#     read_termination="\n",
#     write_termination="\n",
#     timeout=60000,
#     chunk_size=2 * 1024 * 1024,
# )

print(inst.query("*IDN?").strip())

Avoid WiFi for large transfers

Wired Ethernet gives 10-100x more consistent throughput. WiFi latency spikes cause timeout errors on multi-megabyte transfers.

Configuration Checklist

SettingDefaultRecommendedWhy
Data formatASCIIRIBINARY or REAL,3210-50x faster
chunk_size20 KB1-2 MBFewer read calls
timeout2000 ms30000-60000 msLarge transfers take time
Data widthVaries2 (16-bit)Good resolution, half the size of 32-bit
HeaderONHEADER OFFSimplifies binary parsing
ConnectionOpen/close per testKeep openSaves 1-5 s per connection

On this page

Deploy your PyVISA test scripts with TofuPilot

Trusted by Lyft, Cobot, Pacific Defense, and more.

TofuPilot provides dashboards and analytics to track serial device testing at scale.

TofuPilot
Get Started