1
0
mirror of https://github.com/jonathanhogg/scopething synced 2025-07-14 03:02: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:
Jonathan Hogg
2017-03-28 16:15:54 +01:00
parent 94320a27f8
commit 0d3e73439b

View File

@ -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