forked from M-Labs/artiq
suservo: documentation, small API changes
This commit is contained in:
parent
4e2d9abaf7
commit
73fa572275
|
@ -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.language.units import us, ms
|
||||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||||
|
|
||||||
|
@ -39,8 +39,18 @@ class SUServo:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def init(self):
|
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)
|
self.set_config(0)
|
||||||
delay(2*us) # pipeline flush
|
delay(3*us) # pipeline flush
|
||||||
|
|
||||||
self.pgia.set_config_mu(
|
self.pgia.set_config_mu(
|
||||||
sampler.SPI_CONFIG | spi.SPI_END,
|
sampler.SPI_CONFIG | spi.SPI_END,
|
||||||
|
@ -60,29 +70,62 @@ class SUServo:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write(self, addr, value):
|
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)
|
rtio_output(now_mu(), self.channel, addr | WE, value)
|
||||||
delay_mu(self.ref_period_mu)
|
delay_mu(self.ref_period_mu)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read(self, addr):
|
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)
|
rtio_output(now_mu(), self.channel, addr, 0)
|
||||||
return rtio_input_data(self.channel)
|
return rtio_input_data(self.channel)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_config(self, start):
|
def set_config(self, enable):
|
||||||
self.write(CONFIG_ADDR, start)
|
"""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
|
@kernel
|
||||||
def get_status(self):
|
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)
|
return self.read(CONFIG_ADDR)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def get_adc_mu(self, adc):
|
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))
|
return self.read(STATE_SEL | (adc << 1) | (1 << 8))
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_gain_mu(self, channel, gain):
|
def set_pgia_mu(self, channel, gain):
|
||||||
"""Set instrumentation amplifier gain of a channel.
|
"""Set instrumentation amplifier gain of a ADC channel.
|
||||||
|
|
||||||
The four gain settings (0, 1, 2, 3) corresponds to gains of
|
The four gain settings (0, 1, 2, 3) corresponds to gains of
|
||||||
(1, 10, 100, 1000) respectively.
|
(1, 10, 100, 1000) respectively.
|
||||||
|
@ -96,6 +139,10 @@ class SUServo:
|
||||||
self.pgia.write(gains << 16)
|
self.pgia.write(gains << 16)
|
||||||
self.gains = gains
|
self.gains = gains
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def get_adc(self, adc):
|
||||||
|
raise NotImplementedError # FIXME
|
||||||
|
|
||||||
|
|
||||||
class Channel:
|
class Channel:
|
||||||
kernel_invariants = {"channel", "core", "servo", "servo_channel"}
|
kernel_invariants = {"channel", "core", "servo", "servo_channel"}
|
||||||
|
@ -105,28 +152,123 @@ class Channel:
|
||||||
self.core = dmgr.get(core_device)
|
self.core = dmgr.get(core_device)
|
||||||
self.servo = dmgr.get(servo_device)
|
self.servo = dmgr.get(servo_device)
|
||||||
self.channel = channel
|
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
|
@kernel
|
||||||
def set(self, en_out, en_iir=0, profile=0):
|
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,
|
rtio_output(now_mu(), self.channel, 0,
|
||||||
en_out | (en_iir << 1) | (profile << 2))
|
en_out | (en_iir << 1) | (profile << 2))
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_profile_mu(self, profile, ftw, adc, offset,
|
def set_dds_mu(self, profile, ftw, offset, pow=0):
|
||||||
a1, b0, b1, delay, 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)
|
base = (self.servo_channel << 8) | (profile << 3)
|
||||||
data = [ftw >> 16, b1, pow, adc | (delay << 8), offset, a1, ftw, b0]
|
self.servo.write(base + 0, ftw >> 16)
|
||||||
for i in range(8):
|
self.servo.write(base + 6, ftw)
|
||||||
self.servo.write(base + i, data[i])
|
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
|
@kernel
|
||||||
def get_profile_mu(self, profile, data):
|
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)
|
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)
|
data[i] = self.servo.read(base + i)
|
||||||
delay(2*us)
|
delay(4*us)
|
||||||
|
|
||||||
@kernel
|
@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)
|
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
|
||||||
|
|
|
@ -24,33 +24,48 @@ class SUServo(EnvExperiment):
|
||||||
|
|
||||||
self.suservo0.init()
|
self.suservo0.init()
|
||||||
delay(1*us)
|
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)
|
delay(1*us)
|
||||||
|
assert self.suservo0.get_status() == 2
|
||||||
|
|
||||||
print(self.suservo0.get_status())
|
# set up profile 0 on channel 0
|
||||||
delay(3*ms)
|
self.suservo0_ch0.set_y_mu(0, 0)
|
||||||
|
self.suservo0_ch0.set_iir_mu(
|
||||||
self.suservo0_ch0.set_profile_mu(
|
profile=0, adc=0, a1=-0x800, b0=0x1000, b1=0, delay=0)
|
||||||
profile=0, ftw=0x12345667, adc=0, offset=0x10,
|
self.suservo0_ch0.set_dds_mu(
|
||||||
a1=-0x2000, b0=0x1ffff, b1=0, delay=0, pow=0xaa55)
|
profile=0, ftw=0x12345667, offset=0x1, pow=0xaa55)
|
||||||
|
# enable channel
|
||||||
self.suservo0_ch0.set(en_out=1, en_iir=1, profile=0)
|
self.suservo0_ch0.set(en_out=1, en_iir=1, profile=0)
|
||||||
|
# enable servo iterations
|
||||||
delay(10*ms)
|
|
||||||
self.suservo0.set_config(1)
|
self.suservo0.set_config(1)
|
||||||
delay(10*ms)
|
|
||||||
|
# read back profile data
|
||||||
data = [0] * 8
|
data = [0] * 8
|
||||||
self.suservo0_ch0.get_profile_mu(0, data)
|
self.suservo0_ch0.get_profile_mu(0, data)
|
||||||
self.p(data)
|
self.p(data)
|
||||||
delay(10*ms)
|
delay(10*ms)
|
||||||
|
|
||||||
|
# check servo status
|
||||||
|
assert self.suservo0.get_status() == 1
|
||||||
|
|
||||||
|
# reach back ADC data
|
||||||
print(self.suservo0.get_adc_mu(0))
|
print(self.suservo0.get_adc_mu(0))
|
||||||
delay(10*ms)
|
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)
|
delay(10*ms)
|
||||||
print(self.suservo0_ch0.get_asf_mu(0))
|
|
||||||
delay(10*ms)
|
# repeatedly clear the IIR state/integrator
|
||||||
print(self.suservo0_ch0.get_asf_mu(0))
|
# with the ADC yielding 0's and given the profile configuration,
|
||||||
delay(10*ms)
|
# this will lead to a slow ram up of the amplitude over about 200ms
|
||||||
print(self.suservo0.get_status())
|
# followed by saturation and repetition.
|
||||||
|
while True:
|
||||||
|
self.suservo0_ch0.set_y_mu(0, 0)
|
||||||
|
delay(.2*s)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def led(self):
|
def led(self):
|
||||||
|
|
|
@ -132,3 +132,13 @@ DAC/ADC drivers
|
||||||
|
|
||||||
.. automodule:: artiq.coredevice.novogorny
|
.. automodule:: artiq.coredevice.novogorny
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
Compound drivers
|
||||||
|
----------------
|
||||||
|
|
||||||
|
:mod:`artiq.coredevice.suservo` module
|
||||||
|
++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
.. automodule:: artiq.coredevice.suservo
|
||||||
|
:members:
|
||||||
|
|
Loading…
Reference in New Issue