Files
scanengine-3/pymso/test_fastframe_acquisition.py
T
Thomas Ales [M S E] fc43fbe4b0 Initial commit: merge nuescan, pymso, pybbd202, and pypewpewhops into scanengine-3
- Merged four separate hardware control projects into unified platform
- Created unified requirements.txt with all dependencies
- Added comprehensive .gitignore
- Added project overview README

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-16 20:11:31 -06:00

293 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Test script for FastFrame waveform acquisition.
Tests acquiring 1000 FastFrame records from a 1MHz square wave on CH1.
Signal: 1MHz square wave, +250mV to -250mV
Records: 1000 frames, 2500 points each
"""
import time
from tektronix_base import TektronixOscilloscopeBase
def main():
"""Test FastFrame acquisition with 1MHz square wave on CH1"""
scope_ip = "192.168.10.105"
print(f"Connecting to oscilloscope at {scope_ip}...")
scope = TektronixOscilloscopeBase(resource_name=scope_ip, timeout=20.0)
try:
# Connect to scope
scope.connect()
print(f"Connected to {scope.identify()}\n")
print("=== Configuring Oscilloscope ===\n")
# Configure CH1 for the signal
print("Configuring CH1...")
print(" Setting vertical scale to 100mV/div...")
scope.set_channel_scale(1, 0.1) # 100mV/div for ±250mV signal
print(" Setting vertical offset to 0V...")
scope.set_channel_offset(1, 0.0)
print(" Setting coupling to DC...")
scope.set_channel_coupling(1, 'DC')
print(" Setting termination to 50 ohms...")
scope.set_channel_termination(1, 50)
# Verify settings
actual_scale = scope.get_channel_scale(1)
actual_offset = scope.get_channel_offset(1)
actual_coupling = scope.get_channel_coupling(1)
actual_term = scope.get_channel_termination(1)
print(f" Verified: {actual_scale}V/div, {actual_offset}V offset, {actual_coupling} coupling, {actual_term}Ω\n")
# Configure horizontal timebase
# For 1MHz square wave (1µs period), let's capture ~4 cycles (4µs)
# 4µs over 10 divisions = 400ns/div
print("Configuring horizontal timebase...")
print(" Setting time scale to 400ns/div (4µs total for ~4 cycles of 1MHz)...")
scope.set_time_scale(400e-9) # 400ns/div
print(" Setting record length to 2500 points...")
scope.set_record_length(2500)
# Verify settings
actual_time_scale = scope.get_time_scale()
actual_record_length = scope.get_record_length()
actual_sample_rate = scope.get_sample_rate()
print(f" Verified: {actual_time_scale*1e9:.0f}ns/div, {actual_record_length} points")
print(f" Sample rate: {actual_sample_rate/1e6:.1f} MS/s\n")
# Configure trigger
print("Configuring trigger...")
print(" Setting trigger source to CH1...")
scope.set_trigger_source('CH1')
print(" Setting trigger level to +100mV...")
scope.set_trigger_level(1, 0.1) # +100mV
print(" Setting trigger slope to rising...")
scope.set_trigger_slope('RISE')
print(" Setting trigger mode to NORMAL...")
scope.set_trigger_mode('NORMAL')
print(" Setting trigger coupling to DC...")
scope.set_trigger_coupling('DC')
# Verify trigger settings
actual_trigger_source = scope.get_trigger_source()
actual_trigger_level = scope.get_trigger_level(1)
actual_trigger_slope = scope.get_trigger_slope()
actual_trigger_mode = scope.get_trigger_mode()
print(f" Verified: {actual_trigger_source}, {actual_trigger_level}V, {actual_trigger_slope}, {actual_trigger_mode}\n")
# Configure FastFrame
print("Configuring FastFrame...")
print(" Enabling FastFrame...")
scope.set_fastframe_state(True)
print(" Setting frame count to 1000...")
scope.set_fastframe_count(1000)
# Verify FastFrame settings
ff_state = scope.get_fastframe_state()
ff_count = scope.get_fastframe_count()
print(f" Verified: FastFrame {'enabled' if ff_state else 'disabled'}, {ff_count} frames\n")
# Configure waveform transfer
print("Configuring waveform transfer...")
scope.set_data_encoding('RIBinary')
scope.set_wfmoutpre_encoding('BINary')
scope.set_wfmoutpre_byte_count(1)
scope.set_wfmoutpre_byte_order('MSB')
scope.set_data_source('CH1')
# Verify configuration
actual_encoding = scope.get_data_encoding()
actual_source = scope.get_data_source()
print(f" Verified: {actual_encoding} encoding, source {actual_source}")
print(" Data transfer configured for 8-bit signed binary\n")
print("=== Acquiring FastFrame Records ===\n")
# Wait for FastFrame settings to take effect
print("Waiting for FastFrame configuration to settle...")
time.sleep(0.5)
# Start acquisition - this arms the scope and begins acquiring triggered frames
print("Starting acquisition (arming scope)...")
scope.write("ACQuire:STATE RUN")
print(" Scope is now armed and acquiring triggered events")
# Wait and poll for acquisition to complete
print(f" Waiting for {ff_count} triggers to be acquired...")
print(" (Polling acquisition state...)")
# Poll for up to 10 seconds
for i in range(100):
time.sleep(0.1)
state = scope.query("ACQuire:STATE?")
if i % 10 == 0: # Print every second
print(f" Polling... state: {state.strip()}")
# Check if we've acquired enough frames
if i > 20: # After 2 seconds minimum
break
# Stop acquisition
print(" Stopping acquisition...")
scope.write("ACQuire:STATE STOP")
# Verify we have frames
time.sleep(0.2)
print("✓ Acquisition stopped, scope should now have all 1000 frames\n")
# Clear any leftover data in the receive buffer
print("Clearing receive buffer...")
scope.socket.setblocking(False)
try:
while True:
junk = scope.socket.recv(4096)
if not junk:
break
print(f" Cleared {len(junk)} bytes of junk data")
except:
pass
scope.socket.setblocking(True)
print(" Buffer cleared\n")
# Test transferring a single frame first
print("Testing single frame transfer first...")
try:
scope.set_fastframe_selected(1)
print(" Selected frame 1")
test_curve = scope.transfer_curve()
print(f" ✓ Successfully transferred {len(test_curve)} bytes")
test_waveform = scope.parse_curve_data(test_curve, byte_count=1, signed=True, byte_order='MSB')
print(f" ✓ Parsed {len(test_waveform)} samples")
print(f" First 10 values: {test_waveform[:10]}\n")
except Exception as e:
print(f" ✗ Single frame test failed: {e}")
print(" Cannot proceed with bulk transfer\n")
raise
# Now transfer all 1000 frames
print(f"Transferring {ff_count} frames from scope...")
print("This may take a while...\n")
start_time = time.time()
all_waveforms = []
for frame_num in range(1, ff_count + 1):
# Show progress every 100 frames
if frame_num % 100 == 0 or frame_num == 1:
elapsed = time.time() - start_time
if frame_num > 1:
rate = frame_num / elapsed
eta = (ff_count - frame_num) / rate
print(f" Frame {frame_num}/{ff_count} - Elapsed: {elapsed:.1f}s - Rate: {rate:.1f} frames/s - ETA: {eta:.1f}s")
else:
print(f" Frame {frame_num}/{ff_count}...")
# Select this frame
scope.set_fastframe_selected(frame_num)
# Verify frame selection for first frame
if frame_num == 1:
actual_frame = scope.get_fastframe_selected()
if actual_frame != frame_num:
print(f" Warning: Frame mismatch - requested {frame_num}, got {actual_frame}")
# Transfer curve data directly with longer timeout
old_timeout = scope.socket.gettimeout()
scope.socket.settimeout(30.0)
try:
curve_bytes = scope.transfer_curve()
waveform = scope.parse_curve_data(curve_bytes, byte_count=1, signed=True, byte_order='MSB')
all_waveforms.append(waveform)
finally:
scope.socket.settimeout(old_timeout)
end_time = time.time()
total_time = end_time - start_time
print(f"\n✓ Acquired all {ff_count} frames in {total_time:.2f} seconds")
print(f" Average rate: {ff_count / total_time:.2f} frames/second")
print(f" Average time per frame: {total_time / ff_count * 1000:.2f} ms\n")
# Analyze the acquired data
print("=== Waveform Statistics ===\n")
# Check that all frames have the expected length
frame_lengths = [len(wf) for wf in all_waveforms]
print(f"Frame lengths: {min(frame_lengths)} to {max(frame_lengths)} points")
# Calculate statistics for first frame
first_frame = all_waveforms[0]
print(f"\nFirst frame (frame 1):")
print(f" Samples: {len(first_frame)}")
print(f" Min ADC value: {min(first_frame)}")
print(f" Max ADC value: {max(first_frame)}")
print(f" Average ADC value: {sum(first_frame) / len(first_frame):.2f}")
print(f" First 20 values: {first_frame[:20]}")
# Calculate statistics for middle frame
mid_frame_idx = ff_count // 2
mid_frame = all_waveforms[mid_frame_idx]
print(f"\nMiddle frame (frame {mid_frame_idx + 1}):")
print(f" Samples: {len(mid_frame)}")
print(f" Min ADC value: {min(mid_frame)}")
print(f" Max ADC value: {max(mid_frame)}")
print(f" Average ADC value: {sum(mid_frame) / len(mid_frame):.2f}")
# Calculate statistics for last frame
last_frame = all_waveforms[-1]
print(f"\nLast frame (frame {ff_count}):")
print(f" Samples: {len(last_frame)}")
print(f" Min ADC value: {min(last_frame)}")
print(f" Max ADC value: {max(last_frame)}")
print(f" Average ADC value: {sum(last_frame) / len(last_frame):.2f}")
# Calculate overall statistics
all_values = [val for wf in all_waveforms for val in wf]
print(f"\nOverall statistics (all {ff_count} frames, {len(all_values)} total samples):")
print(f" Min ADC value: {min(all_values)}")
print(f" Max ADC value: {max(all_values)}")
print(f" Average ADC value: {sum(all_values) / len(all_values):.2f}")
print(f" Total data transferred: {len(all_values)} bytes")
print(f" Transfer rate: {len(all_values) / total_time / 1024 / 1024:.2f} MB/s")
# Check for square wave characteristics
print(f"\nSquare wave detection:")
# A square wave should have values clustered around two levels
positive_samples = sum(1 for v in first_frame if v > 0)
negative_samples = sum(1 for v in first_frame if v < 0)
zero_samples = sum(1 for v in first_frame if v == 0)
print(f" Frame 1: {positive_samples} positive, {negative_samples} negative, {zero_samples} zero samples")
# Estimate duty cycle from first frame
if len(first_frame) > 0:
duty_cycle = (positive_samples / len(first_frame)) * 100
print(f" Estimated duty cycle: {duty_cycle:.1f}%")
print("\n=== FastFrame Acquisition Test Completed Successfully! ===")
except Exception as e:
print(f"\nError: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
finally:
if scope.is_connected:
# Try to disable FastFrame before disconnecting
try:
print("\nDisabling FastFrame...")
scope.set_fastframe_state(False)
except Exception:
pass
scope.disconnect()
print("Disconnected from oscilloscope")
if __name__ == "__main__":
main()