forked from M-Labs/artiq
pxi6733: add mediator
This commit is contained in:
parent
2bc8286f3f
commit
f9d878119a
|
@ -0,0 +1,182 @@
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from artiq.language.core import *
|
||||||
|
from artiq.language.db import *
|
||||||
|
from artiq.language.units import *
|
||||||
|
from artiq.wavesynth.compute_samples import Synthesizer
|
||||||
|
|
||||||
|
|
||||||
|
class SegmentSequenceError(Exception):
|
||||||
|
"""Raised when attempting to play back a named segment which is not the
|
||||||
|
next in the sequence."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidatedError(Exception):
|
||||||
|
"""Raised when attemting to use a frame or segment that has been
|
||||||
|
invalidated (due to disarming the DAQmx)."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ArmError(Exception):
|
||||||
|
"""Raised when attempting to arm an already armed DAQmx, to modify the
|
||||||
|
program of an armed DAQmx, or to play a segment on a disarmed DAQmx."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _ceil_div(a, b):
|
||||||
|
return (a + b - 1)//b
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_duration_mu(nsamples, ftw, acc_width):
|
||||||
|
# This returns the precise duration so that the clock can be stopped
|
||||||
|
# exactly at the next rising edge (RTLink commands take precedence over
|
||||||
|
# toggling from the accumulator).
|
||||||
|
# If segments are played continuously, replacement of the stop command
|
||||||
|
# will keep the clock running. If the FTW is not a power of two, note that
|
||||||
|
# the accumulator is reset at that time, which causes jitter and frequency
|
||||||
|
# inaccuracy.
|
||||||
|
# Formally:
|
||||||
|
# duration *ftw >= nsamples*2**acc_width
|
||||||
|
# (duration - 1)*ftw < nsamples*2**acc_width
|
||||||
|
return _ceil_div(nsamples*2**acc_width, ftw)
|
||||||
|
|
||||||
|
|
||||||
|
class _Segment:
|
||||||
|
def __init__(self, frame, segment_number):
|
||||||
|
self.frame = frame
|
||||||
|
self.segment_number = segment_number
|
||||||
|
|
||||||
|
self.lines = []
|
||||||
|
|
||||||
|
# for @kernel
|
||||||
|
self.core = frame.daqmx.core
|
||||||
|
|
||||||
|
def add_line(self, duration, channel_data):
|
||||||
|
if self.frame.invalidated:
|
||||||
|
raise InvalidatedError
|
||||||
|
if self.frame.daqmx.armed:
|
||||||
|
raise ArmError
|
||||||
|
self.lines.append((duration, channel_data))
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def advance(self):
|
||||||
|
if self.frame.invalidated:
|
||||||
|
raise InvalidatedError
|
||||||
|
if not self.frame.daqmx.armed:
|
||||||
|
raise ArmError
|
||||||
|
# If the frame is currently being played, check that we are next.
|
||||||
|
if (self.frame.daqmx.next_segment >= 0
|
||||||
|
and self.frame.daqmx.next_segment != self.segment_number):
|
||||||
|
raise SegmentSequenceError
|
||||||
|
self.frame.advance()
|
||||||
|
|
||||||
|
|
||||||
|
class _Frame:
|
||||||
|
def __init__(self, daqmx):
|
||||||
|
self.daqmx = daqmx
|
||||||
|
self.segments = []
|
||||||
|
self.segment_count = 0 # == len(self.segments), used in kernel
|
||||||
|
|
||||||
|
self.invalidated = False
|
||||||
|
|
||||||
|
# for @kernel
|
||||||
|
self.core = self.daqmx.core
|
||||||
|
|
||||||
|
def create_segment(self, name=None):
|
||||||
|
if self.invalidated:
|
||||||
|
raise InvalidatedError
|
||||||
|
if self.daqmx.armed:
|
||||||
|
raise ArmError
|
||||||
|
segment = _Segment(self, self.segment_count)
|
||||||
|
if name is not None:
|
||||||
|
if hasattr(self, name):
|
||||||
|
raise NameError("Segment name already exists")
|
||||||
|
setattr(self, name, segment)
|
||||||
|
self.segments.append(segment)
|
||||||
|
self.segment_count += 1
|
||||||
|
return segment
|
||||||
|
|
||||||
|
def _arm(self):
|
||||||
|
self.segment_delays = [
|
||||||
|
_compute_duration_mu(s.get_sample_count(),
|
||||||
|
self.daqmx.sample_rate,
|
||||||
|
self.daqmx.clock.acc_width)
|
||||||
|
for s in self.segments]
|
||||||
|
|
||||||
|
def _invalidate(self):
|
||||||
|
self.invalidated = True
|
||||||
|
|
||||||
|
def _get_samples(self):
|
||||||
|
program = [
|
||||||
|
{
|
||||||
|
"dac_divider": 1,
|
||||||
|
"duration": duration,
|
||||||
|
"channel_data": channel_data,
|
||||||
|
} for duration, channel_data in segment.lines
|
||||||
|
for segment in self.segments]
|
||||||
|
synth = Synthesizer(self.daqmx.channel_count, program)
|
||||||
|
synth.select(0)
|
||||||
|
# not setting any trigger flag in the program causes the whole
|
||||||
|
# waveform to be computed here for all segments.
|
||||||
|
# slicing the segments is done by stopping the clock.
|
||||||
|
return synth.trigger()
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def advance(self):
|
||||||
|
if self.invalidated:
|
||||||
|
raise InvalidatedError
|
||||||
|
if not self.daqmx.armed:
|
||||||
|
raise ArmError
|
||||||
|
|
||||||
|
self.daqmx.clock.set(self.daqmx.sample_rate)
|
||||||
|
delay_mu(self.segment_delays[self.daqmx.next_segment])
|
||||||
|
self.daqmx.next_segment += 1
|
||||||
|
self.daqmx.clock.stop()
|
||||||
|
|
||||||
|
# test for end of frame
|
||||||
|
if self.daqmx.next_segment == self.segment_count:
|
||||||
|
self.daqmx.next_segment = -1
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundDAQmx(AutoDB):
|
||||||
|
class DBKeys:
|
||||||
|
core = Device()
|
||||||
|
daqmx_device = Argument()
|
||||||
|
clock_device = Argument()
|
||||||
|
channel_count = Argument()
|
||||||
|
sample_rate = Argument()
|
||||||
|
sample_rate_in_mu = Argument(False)
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
self.daqmx = self.dbh.get_device(self.daqmx_device)
|
||||||
|
self.clock = self.dbh.get_device(self.clock_device)
|
||||||
|
|
||||||
|
if not self.sample_rate_in_mu:
|
||||||
|
self.sample_rate = self.clock.frequency_to_ftw(sample_rate)
|
||||||
|
|
||||||
|
self.frame = None
|
||||||
|
self.next_segment = -1
|
||||||
|
self.armed = False
|
||||||
|
|
||||||
|
def disarm(self):
|
||||||
|
if self.frame is not None:
|
||||||
|
self.frame._invalidate()
|
||||||
|
self.frame = None
|
||||||
|
self.armed = False
|
||||||
|
|
||||||
|
def arm(self):
|
||||||
|
if self.armed:
|
||||||
|
raise ArmError
|
||||||
|
if self.frame is not None:
|
||||||
|
self.frame._arm()
|
||||||
|
self.daqmx.load_sample_values(
|
||||||
|
self.clock.ftw_to_frequency(self.sample_rate),
|
||||||
|
np.array(self.frame._get_samples()))
|
||||||
|
self.armed = True
|
||||||
|
|
||||||
|
def create_frame(self):
|
||||||
|
if self.armed:
|
||||||
|
raise ArmError
|
||||||
|
self.frame = _Frame(self)
|
||||||
|
return self.frame
|
Loading…
Reference in New Issue