Source code for openlabctrl.io.scope

from __future__ import annotations
from .base import BaseIo
import numpy as np
from enum import Enum

class ScopeCmd(Enum):
    SRC = 0x0
    ACQ = 0x1
    DEC = 0x2

[docs] class ScopeSource(Enum): RF_IN_0 = 0x0 RF_IN_1 = 0x1 RF_OUT_0 = 0x2 RF_OUT_1 = 0x3 DIGITAL_IO_0 = 0x4 DIGITAL_IO_1 = 0x5 DIGITAL_IO_2 = 0x6 DIGITAL_IO_3 = 0x7 ANALOG_OUT_0 = 0x8 ANALOG_OUT_1 = 0x9 ANALOG_OUT_2 = 0xA ANALOG_OUT_3 = 0xB ANALOG_IN_0 = 0xE ANALOG_IN_1 = 0xC ANALOG_IN_2 = 0xD ANALOG_IN_3 = 0xF
[docs] class Scope(BaseIo): """ Driver class for signal acquisition (digital oscilloscope). Before calling :meth:`acquire`, both :meth:`source` and :meth:`decimation` must be set within the same frame. Each :meth:`acquire` call is registered under a label and can be retrieved after the frame is executed. """ def __init__(self, addr, clk_freq): super().__init__(addr, clk_freq) self._acq_dict = {} self._acq_samples = 0 self._src = None self._dec = None
[docs] def reset(self): """ See :meth:`BaseIo.reset`. """ super().reset() self._acq_dict = {} self._acq_samples = 0 self._src = None self._dec = None
[docs] def source(self, src: ScopeSource): """ Set the acquisition source. Must be called before :meth:`acquire` within the same frame. :param src: Signal source to capture. Must be a :class:`ScopeSource` member. """ if src not in ScopeSource: raise Exception(f"Scope source {src} is not valid. Should be of type ScopeSource") self._add_instruction(cmd=ScopeCmd.SRC.value, data=src.value) self._src = src
[docs] def decimation(self, dec: int = 1): """ Set the decimation factor. The effective sample rate is ``clk_freq / dec``. A value of ``1`` captures every clock cycle (no decimation). :param dec: Decimation factor in the range ``[1, 2**32 - 1]``. """ DEC_MIN = 1 DEC_MAX = (1 << 32) - 1 if (dec < DEC_MIN) or (dec > DEC_MAX): raise Exception(f"Decimation factor {dec} is out of range [{DEC_MIN}, {DEC_MAX}].") self._add_instruction(cmd=ScopeCmd.DEC.value, data=dec) self._dec = dec
[docs] def acquire(self, samples: int, label: str | None = None, run_async: bool = False): """ Schedule an acquisition from the current source. :meth:`source` and :meth:`decimation` must have been called earlier in the same frame. Acquired data is stored under ``label`` and can be retrieved after the frame executes. :param samples: Number of samples to acquire, in the range ``[1, 2**32 - 1]``. :param label: Key used to retrieve the acquisition data. Auto-generated as ``acq_0``, ``acq_1``, … if not provided. :param run_async: If ``False`` (default), the frame waits for all samples before continuing. If ``True``, the frame proceeds immediately after issuing the acquisition command (non-blocking). """ SAMPLE_MIN = 1 SAMPLE_MAX = (1 << 32) - 1 if (samples < SAMPLE_MIN) or (samples > SAMPLE_MAX): raise Exception(f"Number of samples {samples} is out of range [{SAMPLE_MIN}, {SAMPLE_MAX}].") if self._src is None: raise Exception("Scope source not set within current IoSyncFrame. Please set scope source before acquiring.") if self._dec is None: raise Exception("Scope decimation not set within current IoSyncFrame. Please set scope decimation before acquiring.") cmd = ScopeCmd.ACQ.value if label is None: label = f"acq_{len(self._acq_dict)}" if label in self._acq_dict: raise Exception(f"Acquisition label '{label}' already exists.") self._acq_dict[label] = {'t': self._tnext, 'samples': samples, 'src': self._src.name, 'dec': self._dec, 'addr': None} self._acq_samples += samples if run_async: duration = 1 else: duration = samples * self._dec + 1 self._add_instruction(cmd=cmd, data=samples, duration=duration)
def _get_acquisition_dict(self): return self._acq_dict