suservo: documentation, small API changes

pull/972/merge
Robert Jördens 2018-04-27 14:53:11 +00:00 committed by Robert Jordens
parent 4e2d9abaf7
commit 73fa572275
3 changed files with 198 additions and 31 deletions

View File

@ -1,4 +1,4 @@
from artiq.language.core import kernel, delay, portable, now_mu
from artiq.language.core import kernel, delay, portable, now_mu, delay_mu
from artiq.language.units import us, ms
from artiq.coredevice.rtio import rtio_output, rtio_input_data
@ -39,8 +39,18 @@ class SUServo:
@kernel
def init(self):
"""Initialize the Servo, Sampler and both Urukuls.
Leaves the Servo disabled (see :meth:`set_config`), resets all DDS.
Urukul initialization is performed blindly as there is no readback from
the DDS or the CPLDs.
This method does not alter the profile configuration memory
or the channel controls.
"""
self.set_config(0)
delay(2*us) # pipeline flush
delay(3*us) # pipeline flush
self.pgia.set_config_mu(
sampler.SPI_CONFIG | spi.SPI_END,
@ -60,29 +70,62 @@ class SUServo:
@kernel
def write(self, addr, value):
"""Write to Servo memory.
This method advances the timeline by one coarse RTIO cycle.
:param addr: Memory location address.
:param value: Data to be written.
"""
rtio_output(now_mu(), self.channel, addr | WE, value)
delay_mu(self.ref_period_mu)
@kernel
def read(self, addr):
"""Read from Servo memory.
This method does not advance the timeline but consumes all slack.
:param addr: Memory location address.
"""
rtio_output(now_mu(), self.channel, addr, 0)
return rtio_input_data(self.channel)
@kernel
def set_config(self, start):
self.write(CONFIG_ADDR, start)
def set_config(self, enable):
"""Set SU Servo configuration.
This method advances the timeline by one Servo memory access.
:param enable: Enable Servo operation. Disabling takes up to 2 Servo
cycles (~2.2 µs).
"""
self.write(CONFIG_ADDR, enable)
@kernel
def get_status(self):
"""Get current SU Servo status.
This method does not advance the timeline but consumes all slack.
:return: Status. Bit 0: enabled, bit 1: done
"""
return self.read(CONFIG_ADDR)
@kernel
def get_adc_mu(self, adc):
"""Get an ADC reading (IIR filter input X0).
This method does not advance the timeline but consumes all slack.
:param adc: ADC channel number (0-7)
:return: 16 bit signed Y0
"""
return self.read(STATE_SEL | (adc << 1) | (1 << 8))
@kernel
def set_gain_mu(self, channel, gain):
"""Set instrumentation amplifier gain of a channel.
def set_pgia_mu(self, channel, gain):
"""Set instrumentation amplifier gain of a ADC channel.
The four gain settings (0, 1, 2, 3) corresponds to gains of
(1, 10, 100, 1000) respectively.
@ -96,6 +139,10 @@ class SUServo:
self.pgia.write(gains << 16)
self.gains = gains
@kernel
def get_adc(self, adc):
raise NotImplementedError # FIXME
class Channel:
kernel_invariants = {"channel", "core", "servo", "servo_channel"}
@ -105,28 +152,123 @@ class Channel:
self.core = dmgr.get(core_device)
self.servo = dmgr.get(servo_device)
self.channel = channel
self.servo_channel = self.channel + 8 - self.servo.channel # FIXME
# FIXME: this assumes the mem channel is right after the control
# channels
self.servo_channel = self.channel + 8 - self.servo.channel
@kernel
def set(self, en_out, en_iir=0, profile=0):
"""Operate channel.
This method does not advance the timeline.
:param en_out: RF switch enable
:param en_iir: IIR updates enable
:param profile: Active profile (0-31)
"""
rtio_output(now_mu(), self.channel, 0,
en_out | (en_iir << 1) | (profile << 2))
@kernel
def set_profile_mu(self, profile, ftw, adc, offset,
a1, b0, b1, delay, pow=0):
def set_dds_mu(self, profile, ftw, offset, pow=0):
"""Set profile DDS coefficients.
This method advances the timeline by four Servo memory accesses.
:param profile: Profile number (0-31)
:param ftw: Frequency tuning word (32 bit unsigned)
:param offset: IIR offset (setpoint)
:param pow: Phase offset word (16 bit unsigned)
"""
base = (self.servo_channel << 8) | (profile << 3)
data = [ftw >> 16, b1, pow, adc | (delay << 8), offset, a1, ftw, b0]
for i in range(8):
self.servo.write(base + i, data[i])
self.servo.write(base + 0, ftw >> 16)
self.servo.write(base + 6, ftw)
self.servo.write(base + 4, offset)
self.servo.write(base + 2, pow)
@kernel
def set_dds(self, profile, frequency, offset, phase=0.):
raise NotImplementedError # FIXME
@kernel
def set_iir_mu(self, profile, adc, a1, b0, b1, delay=0):
"""Set profile IIR coefficients.
This method advances the timeline by four Servo memory accesses.
:param profile: Profile number (0-31)
:param adc: ADC channel to use (0-7)
:param a1: 18 bit signed A1 coefficient (Y1 coefficient,
feedback, integrator gain)
:param b0: 18 bit signed B0 coefficient (recent,
X0 coefficient, feed forward, proportional gain)
:param b1: 18 bit signed B1 coefficient (old,
X1 coefficient, feed forward, proportional gain)
:param delay: Number of Servo cycles (~1.1 µs each) to suppress
IIR updates for after either (1) enabling or disabling RF output,
(2) enabling or disabling IIR updates, or (3) setting the active
profile number: i.e. after invoking :meth:`set`.
"""
base = (self.servo_channel << 8) | (profile << 3)
self.servo.write(base + 1, b1)
self.servo.write(base + 3, adc | (delay << 8))
self.servo.write(base + 5, a1)
self.servo.write(base + 7, b0)
@kernel
def set_iir(self, profile, adc, i_gain, p_gain, delay=0.):
raise NotImplementedError # FIXME
@kernel
def get_profile_mu(self, profile, data):
"""Retrieve profile data.
The data is returned in the `data` argument as:
`[ftw >> 16, b1, pow, adc | (delay << 8), offset, a1, ftw, b0]`.
This method advances the timeline by 32 µs and consumes all slack.
:param profile: Profile number (0-31)
:param data: List of 8 integers to write the profile data into
"""
base = (self.servo_channel << 8) | (profile << 3)
for i in range(8):
for i in range(len(data)):
data[i] = self.servo.read(base + i)
delay(2*us)
delay(4*us)
@kernel
def get_asf_mu(self, profile):
def get_y_mu(self, profile):
"""Get a profile's IIR state (filter output, Y0).
The IIR state is also know as the "integrator", or the DDS amplitude
scale factor. It is 18 bits wide and unsigned.
This method does not advance the timeline but consumes all slack.
:param profile: Profile number (0-31)
:return: 18 bit unsigned Y0
"""
return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile)
@kernel
def get_y(self, profile):
raise NotImplementedError # FIXME
@kernel
def set_y_mu(self, profile, y):
"""Set a profile's IIR state (filter output, Y0).
The IIR state is also know as the "integrator", or the DDS amplitude
scale factor. It is 18 bits wide and unsigned.
This method advances the timeline by one Servo memory access.
:param profile: Profile number (0-31)
:param y: 18 bit unsigned Y0
"""
return self.servo.write(
STATE_SEL | (self.servo_channel << 5) | profile, y)
@kernel
def set_y(self, profile, y):
raise NotImplementedError # FIXME

View File

@ -24,33 +24,48 @@ class SUServo(EnvExperiment):
self.suservo0.init()
delay(1*us)
self.suservo0.cpld0.set_att_mu(0, 255)
# ADC PGIA gain
self.suservo0.set_pgia_mu(0, 0)
# DDS attenuator
self.suservo0.cpld0.set_att_mu(0, 64)
delay(1*us)
assert self.suservo0.get_status() == 2
print(self.suservo0.get_status())
delay(3*ms)
self.suservo0_ch0.set_profile_mu(
profile=0, ftw=0x12345667, adc=0, offset=0x10,
a1=-0x2000, b0=0x1ffff, b1=0, delay=0, pow=0xaa55)
# set up profile 0 on channel 0
self.suservo0_ch0.set_y_mu(0, 0)
self.suservo0_ch0.set_iir_mu(
profile=0, adc=0, a1=-0x800, b0=0x1000, b1=0, delay=0)
self.suservo0_ch0.set_dds_mu(
profile=0, ftw=0x12345667, offset=0x1, pow=0xaa55)
# enable channel
self.suservo0_ch0.set(en_out=1, en_iir=1, profile=0)
delay(10*ms)
# enable servo iterations
self.suservo0.set_config(1)
delay(10*ms)
# read back profile data
data = [0] * 8
self.suservo0_ch0.get_profile_mu(0, data)
self.p(data)
delay(10*ms)
# check servo status
assert self.suservo0.get_status() == 1
# reach back ADC data
print(self.suservo0.get_adc_mu(0))
delay(10*ms)
print(self.suservo0.get_adc_mu(1))
# read out IIR data
print(self.suservo0_ch0.get_y_mu(0))
delay(10*ms)
print(self.suservo0_ch0.get_asf_mu(0))
delay(10*ms)
print(self.suservo0_ch0.get_asf_mu(0))
delay(10*ms)
print(self.suservo0.get_status())
# repeatedly clear the IIR state/integrator
# with the ADC yielding 0's and given the profile configuration,
# this will lead to a slow ram up of the amplitude over about 200ms
# followed by saturation and repetition.
while True:
self.suservo0_ch0.set_y_mu(0, 0)
delay(.2*s)
@kernel
def led(self):

View File

@ -132,3 +132,13 @@ DAC/ADC drivers
.. automodule:: artiq.coredevice.novogorny
:members:
Compound drivers
----------------
:mod:`artiq.coredevice.suservo` module
++++++++++++++++++++++++++++++++++++++
.. automodule:: artiq.coredevice.suservo
:members: