mirror of
https://github.com/jonathanhogg/scopething
synced 2025-07-14 11:12:09 +01:00
Fix capture timeout; fix buffer offset bug; calibrate in both 8bit and 16bit sample sizes; compute calibration limits
This commit is contained in:
77
scope.py
77
scope.py
@ -51,18 +51,18 @@ class Scope(vm.VirtualMachine):
|
|||||||
self.awg_wavetable_size = 1024
|
self.awg_wavetable_size = 1024
|
||||||
self.awg_sample_buffer_size = 1024
|
self.awg_sample_buffer_size = 1024
|
||||||
self.awg_minimum_clock = 33
|
self.awg_minimum_clock = 33
|
||||||
self.awg_maximum_voltage = 3.3
|
self.awg_maximum_voltage = 3.33
|
||||||
self.analog_params = (18.52, -3.33, 296.41, 18.337, 0.40811)
|
self.analog_params = (20.17, -5.247, 299.0, 18.47, 0.4082)
|
||||||
self.analog_offsets = {'A': 0.0008, 'B': -0.0008}
|
self.analog_offsets = {'A': -0.0117, 'B': 0.0117}
|
||||||
self.analog_min = -5.7
|
self.analog_min = -5.7
|
||||||
self.analog_max = 8
|
self.analog_max = 8
|
||||||
self.capture_clock_period = 25e-9
|
self.capture_clock_period = 25e-9
|
||||||
self.capture_buffer_size = 12*1024
|
self.capture_buffer_size = 12<<10
|
||||||
self.timeout_clock_period = 6.4e-6
|
self.timeout_clock_period = 6.4e-6
|
||||||
self.trigger_low = -7.517
|
self.trigger_low = -7.517
|
||||||
self.trigger_high = 10.816
|
self.trigger_high = 10.816
|
||||||
# await self.load_params() XXX switch this off until I understand EEPROM better
|
# await self.load_params() XXX switch this off until I understand EEPROM better
|
||||||
self._generator_running = False
|
self._awg_running = False
|
||||||
Log.info(f"Initialised scope, revision: {revision}")
|
Log.info(f"Initialised scope, revision: {revision}")
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
@ -103,26 +103,25 @@ class Scope(vm.VirtualMachine):
|
|||||||
|
|
||||||
async def capture(self, channels=['A'], trigger=None, trigger_level=None, trigger_type='rising', hair_trigger=False,
|
async def capture(self, channels=['A'], trigger=None, trigger_level=None, trigger_type='rising', hair_trigger=False,
|
||||||
period=1e-3, nsamples=1000, timeout=None, low=None, high=None, raw=False, trigger_position=0.25):
|
period=1e-3, nsamples=1000, timeout=None, low=None, high=None, raw=False, trigger_position=0.25):
|
||||||
analog_channels = []
|
analog_channels = set()
|
||||||
logic_channels = []
|
logic_channels = set()
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
if channel in {'A', 'B'}:
|
if channel in {'A', 'B'}:
|
||||||
analog_channels.append(channel)
|
analog_channels.add(channel)
|
||||||
if trigger is None:
|
if trigger is None:
|
||||||
trigger = channel
|
trigger = channel
|
||||||
elif channel == 'L':
|
elif channel == 'L':
|
||||||
logic_channels.extend(range(8))
|
logic_channels.update(range(8))
|
||||||
if trigger is None:
|
if trigger is None:
|
||||||
trigger = {0: 1}
|
trigger = {0: 1}
|
||||||
elif channel.startswith('L'):
|
elif channel.startswith('L'):
|
||||||
i = int(channel[1:])
|
i = int(channel[1:])
|
||||||
if i not in logic_channels:
|
logic_channels.add(i)
|
||||||
logic_channels.append(i)
|
|
||||||
if trigger is None:
|
if trigger is None:
|
||||||
trigger = {i: 1}
|
trigger = {i: 1}
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unrecognised channel: {channel}")
|
raise ValueError(f"Unrecognised channel: {channel}")
|
||||||
if self._generator_running and 4 in logic_channels:
|
if self._awg_running and 4 in logic_channels:
|
||||||
logic_channels.remove(4)
|
logic_channels.remove(4)
|
||||||
if 'A' in analog_channels and 7 in logic_channels:
|
if 'A' in analog_channels and 7 in logic_channels:
|
||||||
logic_channels.remove(7)
|
logic_channels.remove(7)
|
||||||
@ -154,7 +153,10 @@ class Scope(vm.VirtualMachine):
|
|||||||
raise RuntimeError("Unable to find appropriate capture mode")
|
raise RuntimeError("Unable to find appropriate capture mode")
|
||||||
if capture_mode.clock_max is not None and ticks > capture_mode.clock_max:
|
if capture_mode.clock_max is not None and ticks > capture_mode.clock_max:
|
||||||
ticks = capture_mode.clock_max
|
ticks = capture_mode.clock_max
|
||||||
nsamples = int(round(period / ticks / self.capture_clock_period / clock_scale))
|
if analog_channels:
|
||||||
|
nsamples = int(round(period / ticks / self.capture_clock_period / clock_scale / len(analog_channels))) * len(analog_channels)
|
||||||
|
else:
|
||||||
|
nsamples = int(round(period / ticks / self.capture_clock_period / clock_scale))
|
||||||
total_samples = nsamples*2 if logic_channels and analog_channels else nsamples
|
total_samples = nsamples*2 if logic_channels and analog_channels else nsamples
|
||||||
buffer_width = self.capture_buffer_size // capture_mode.sample_width
|
buffer_width = self.capture_buffer_size // capture_mode.sample_width
|
||||||
if total_samples > buffer_width:
|
if total_samples > buffer_width:
|
||||||
@ -171,7 +173,7 @@ class Scope(vm.VirtualMachine):
|
|||||||
|
|
||||||
spock_option = vm.SpockOption.TriggerTypeHardwareComparator
|
spock_option = vm.SpockOption.TriggerTypeHardwareComparator
|
||||||
kitchen_sink_a = kitchen_sink_b = 0
|
kitchen_sink_a = kitchen_sink_b = 0
|
||||||
if self._generator_running:
|
if self._awg_running:
|
||||||
kitchen_sink_b |= vm.KitchenSinkB.WaveformGeneratorEnable
|
kitchen_sink_b |= vm.KitchenSinkB.WaveformGeneratorEnable
|
||||||
if trigger == 'A' or 7 in logic_channels:
|
if trigger == 'A' or 7 in logic_channels:
|
||||||
kitchen_sink_a |= vm.KitchenSinkA.ChannelAComparatorEnable
|
kitchen_sink_a |= vm.KitchenSinkA.ChannelAComparatorEnable
|
||||||
@ -200,14 +202,16 @@ class Scope(vm.VirtualMachine):
|
|||||||
trigger_logic |= mask
|
trigger_logic |= mask
|
||||||
if trigger_type.lower() in {'falling', 'below'}:
|
if trigger_type.lower() in {'falling', 'below'}:
|
||||||
spock_option |= vm.SpockOption.TriggerInvert
|
spock_option |= vm.SpockOption.TriggerInvert
|
||||||
trigger_intro = 0 if trigger_type.lower() in {'above', 'below'} else (1 if hair_trigger else 4)
|
|
||||||
trigger_outro = 2 if hair_trigger else 4
|
trigger_outro = 2 if hair_trigger else 4
|
||||||
trace_intro = min(max(0, int(nsamples*trigger_position)), nsamples)
|
trigger_intro = 0 if trigger_type.lower() in {'above', 'below'} else trigger_outro
|
||||||
trace_outro = max(0, nsamples-trace_intro-trigger_outro)
|
trigger_samples = min(max(0, int(nsamples*trigger_position)), nsamples)
|
||||||
|
trace_outro = max(0, nsamples-trigger_samples-trigger_outro)
|
||||||
|
trace_intro = max(0, trigger_samples-trigger_intro)
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
trigger_timeout = int(period*5/self.timeout_clock_period)
|
trigger_timeout = int(period*5/self.timeout_clock_period)
|
||||||
else:
|
else:
|
||||||
trigger_timeout = max(1, int((trace_intro*ticks*clock_scale*self.capture_clock_period + timeout)/self.timeout_clock_period))
|
trigger_timeout = max(1, int(math.ceil(((trigger_outro+trace_outro)*ticks*clock_scale*self.capture_clock_period
|
||||||
|
+ timeout)/self.timeout_clock_period)))
|
||||||
|
|
||||||
async with self.transaction():
|
async with self.transaction():
|
||||||
await self.set_registers(TraceMode=capture_mode.TraceMode, BufferMode=capture_mode.BufferMode,
|
await self.set_registers(TraceMode=capture_mode.TraceMode, BufferMode=capture_mode.BufferMode,
|
||||||
@ -230,6 +234,10 @@ class Scope(vm.VirtualMachine):
|
|||||||
start_timestamp += 1<<32
|
start_timestamp += 1<<32
|
||||||
timestamp += 1<<32
|
timestamp += 1<<32
|
||||||
address = int((await self.read_replies(1))[0], 16)
|
address = int((await self.read_replies(1))[0], 16)
|
||||||
|
if capture_mode.BufferMode in {vm.BufferMode.Chop, vm.BufferMode.Dual, vm.BufferMode.MacroChop}:
|
||||||
|
address -= address % 2
|
||||||
|
elif capture_mode.BufferMode == vm.BufferMode.ChopDual:
|
||||||
|
address -= address % 4
|
||||||
traces = DotDict()
|
traces = DotDict()
|
||||||
for dump_channel, channel in enumerate(sorted(analog_channels)):
|
for dump_channel, channel in enumerate(sorted(analog_channels)):
|
||||||
asamples = nsamples // len(analog_channels)
|
asamples = nsamples // len(analog_channels)
|
||||||
@ -292,9 +300,9 @@ class Scope(vm.VirtualMachine):
|
|||||||
raise ValueError(f"Wavetable data must be {self.awg_wavetable_size} samples")
|
raise ValueError(f"Wavetable data must be {self.awg_wavetable_size} samples")
|
||||||
await self.set_registers(Cmd=0, Mode=1, Address=0, Size=1)
|
await self.set_registers(Cmd=0, Mode=1, Address=0, Size=1)
|
||||||
await self.wavetable_write_bytes(wavetable)
|
await self.wavetable_write_bytes(wavetable)
|
||||||
await self.set_registers(Cmd=0, Mode=0, Level=vpp / self.awg_maximum_voltage,
|
await self.set_registers(Cmd=0, Mode=0, Level=vpp/self.awg_maximum_voltage,
|
||||||
Offset=offset / self.awg_maximum_voltage,
|
Offset=offset/self.awg_maximum_voltage,
|
||||||
Ratio=nwaves * self.awg_wavetable_size / size,
|
Ratio=nwaves*self.awg_wavetable_size/size,
|
||||||
Index=0, Address=0, Size=size)
|
Index=0, Address=0, Size=size)
|
||||||
await self.issue_translate_wavetable()
|
await self.issue_translate_wavetable()
|
||||||
await self.set_registers(Cmd=2, Mode=0, Clock=clock, Modulo=size,
|
await self.set_registers(Cmd=2, Mode=0, Clock=clock, Modulo=size,
|
||||||
@ -303,7 +311,7 @@ class Scope(vm.VirtualMachine):
|
|||||||
await self.set_registers(KitchenSinkB=vm.KitchenSinkB.WaveformGeneratorEnable)
|
await self.set_registers(KitchenSinkB=vm.KitchenSinkB.WaveformGeneratorEnable)
|
||||||
await self.issue_configure_device_hardware()
|
await self.issue_configure_device_hardware()
|
||||||
await self.issue('.')
|
await self.issue('.')
|
||||||
self._generator_running = True
|
self._awg_running = True
|
||||||
return actualf
|
return actualf
|
||||||
|
|
||||||
async def stop_generator(self):
|
async def stop_generator(self):
|
||||||
@ -312,7 +320,7 @@ class Scope(vm.VirtualMachine):
|
|||||||
await self.issue_control_waveform_generator()
|
await self.issue_control_waveform_generator()
|
||||||
await self.set_registers(KitchenSinkB=0)
|
await self.set_registers(KitchenSinkB=0)
|
||||||
await self.issue_configure_device_hardware()
|
await self.issue_configure_device_hardware()
|
||||||
self._generator_running = False
|
self._awg_running = False
|
||||||
|
|
||||||
async def read_wavetable(self):
|
async def read_wavetable(self):
|
||||||
with self.transaction():
|
with self.transaction():
|
||||||
@ -333,26 +341,30 @@ class Scope(vm.VirtualMachine):
|
|||||||
if int((await self.read_replies(2))[1], 16) != byte:
|
if int((await self.read_replies(2))[1], 16) != byte:
|
||||||
raise RuntimeError("Error writing EEPROM byte")
|
raise RuntimeError("Error writing EEPROM byte")
|
||||||
|
|
||||||
async def calibrate(self, n=33):
|
async def calibrate(self, n=32):
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy.optimize import least_squares
|
from scipy.optimize import least_squares
|
||||||
items = []
|
items = []
|
||||||
await self.start_generator(1000, waveform='square')
|
await self.start_generator(frequency=1000, waveform='square')
|
||||||
for low in np.linspace(0.063, 0.4, n):
|
i = 0
|
||||||
for high in np.linspace(0.877, 0.6, n):
|
low_min, high_max = self.calculate_lo_hi(self.analog_min, self.analog_max)
|
||||||
data = await self.capture(['A','B'], period=2e-3, nsamples=2000, low=low, high=high, trigger_level=1.65, raw=True)
|
low_max, high_min = self.calculate_lo_hi(0, self.awg_maximum_voltage)
|
||||||
|
for low in np.linspace(low_min, low_max*0.9, n):
|
||||||
|
for high in np.linspace(high_min*1.1, high_max, n):
|
||||||
|
data = await self.capture(channels=['A','B'], period=2e-3 if i%2 == 0 else 1e-3, nsamples=2000, low=low, high=high, timeout=0, raw=True)
|
||||||
A = np.fromiter(data['A'].values(), dtype='float')
|
A = np.fromiter(data['A'].values(), dtype='float')
|
||||||
A.sort()
|
A.sort()
|
||||||
B = np.fromiter(data['B'].values(), dtype='float')
|
B = np.fromiter(data['B'].values(), dtype='float')
|
||||||
B.sort()
|
B.sort()
|
||||||
Azero, A3v3 = A[10:490].mean(), A[510:990].mean()
|
Azero, Amax = A[10:490].mean(), A[510:990].mean()
|
||||||
Bzero, B3v3 = B[10:490].mean(), B[510:990].mean()
|
Bzero, Bmax = B[10:490].mean(), B[510:990].mean()
|
||||||
zero = (Azero + Bzero) / 2
|
zero = (Azero + Bzero) / 2
|
||||||
analog_range = 3.3 / ((A3v3 + B3v3)/2 - zero)
|
analog_range = self.awg_maximum_voltage / ((Amax + Bmax)/2 - zero)
|
||||||
analog_low = -zero * analog_range
|
analog_low = -zero * analog_range
|
||||||
analog_high = analog_low + analog_range
|
analog_high = analog_low + analog_range
|
||||||
offset = (Azero - Bzero) / 2 * analog_range
|
offset = (Azero - Bzero) / 2 * analog_range
|
||||||
items.append((analog_low, analog_high, low, high, offset))
|
items.append((analog_low, analog_high, low, high, offset))
|
||||||
|
i += 1
|
||||||
await self.stop_generator()
|
await self.stop_generator()
|
||||||
items = np.array(items)
|
items = np.array(items)
|
||||||
def f(params, analog_low, analog_high, low, high):
|
def f(params, analog_low, analog_high, low, high):
|
||||||
@ -361,11 +373,10 @@ class Scope(vm.VirtualMachine):
|
|||||||
result = least_squares(f, self.analog_params, args=items.T[:4], bounds=([0, -np.inf, 250, 0, 0], [np.inf, np.inf, 350, np.inf, np.inf]))
|
result = least_squares(f, self.analog_params, args=items.T[:4], bounds=([0, -np.inf, 250, 0, 0], [np.inf, np.inf, 350, np.inf, np.inf]))
|
||||||
if result.success in range(1, 5):
|
if result.success in range(1, 5):
|
||||||
self.analog_params = tuple(result.x)
|
self.analog_params = tuple(result.x)
|
||||||
offset = items[:, 4].mean()
|
offset = items[:,4].mean()
|
||||||
self.analog_offsets = {'A': -offset, 'B': +offset}
|
self.analog_offsets = {'A': -offset, 'B': +offset}
|
||||||
else:
|
else:
|
||||||
Log.warning(f"Calibration failed: {result.message}")
|
Log.warning(f"Calibration failed: {result.message}")
|
||||||
print(result.message)
|
|
||||||
return result.success
|
return result.success
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user