pxi6733: add mediator

This commit is contained in:
Sebastien Bourdeauducq 2015-07-05 19:07:57 +02:00
parent 2bc8286f3f
commit f9d878119a
1 changed files with 182 additions and 0 deletions

View File

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