forked from M-Labs/artiq
shuttler: add documentation
This commit is contained in:
parent
ab8247b3d7
commit
73ab71f443
|
@ -9,10 +9,27 @@ from artiq.language.units import us
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def shuttler_volt_to_mu(volt):
|
def shuttler_volt_to_mu(volt):
|
||||||
|
"""Return the equivalent DAC code. Valid input range is from -10 to
|
||||||
|
10 - LSB.
|
||||||
|
"""
|
||||||
return round((1 << 14) * (volt / 20.0)) & 0x3fff
|
return round((1 << 14) * (volt / 20.0)) & 0x3fff
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
"""Shuttler configuration registers interface.
|
||||||
|
|
||||||
|
The configuration registers control waveform phase auto-clear, and pre-DAC
|
||||||
|
gain & offset values for calibration with ADC on the Shuttler AFE card.
|
||||||
|
|
||||||
|
To find the calibrated DAC code, the Shuttler core first multiplies the
|
||||||
|
output data with pre-DAC gain, then adds the offset.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The DAC code is capped at 0x1fff and 0x2000.
|
||||||
|
|
||||||
|
:param channel: RTIO channel number of this interface.
|
||||||
|
:param core_device: Core device name.
|
||||||
|
"""
|
||||||
kernel_invariants = {
|
kernel_invariants = {
|
||||||
"core", "channel", "target_base", "target_read",
|
"core", "channel", "target_base", "target_read",
|
||||||
"target_gain", "target_offset", "target_clr"
|
"target_gain", "target_offset", "target_clr"
|
||||||
|
@ -29,30 +46,87 @@ class Config:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_clr(self, clr):
|
def set_clr(self, clr):
|
||||||
|
"""Set/Unset waveform phase clear bits.
|
||||||
|
|
||||||
|
Each bit corresponds to a Shuttler waveform generator core. Setting a
|
||||||
|
clear bit forces the Shuttler core to clear the phase accumulator on
|
||||||
|
waveform trigger (See :class:`Trigger` for the trigger method).
|
||||||
|
Otherwise, the phase accumulator increments from its original value.
|
||||||
|
|
||||||
|
:param clr: Waveform phase clear bits. The MSB corresponds to Channel
|
||||||
|
15, LSB corresponds to Channel 0.
|
||||||
|
"""
|
||||||
rtio_output(self.target_base | self.target_clr, clr)
|
rtio_output(self.target_base | self.target_clr, clr)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_gain(self, channel, gain):
|
def set_gain(self, channel, gain):
|
||||||
|
"""Set the 16-bits pre-DAC gain register of a Shuttler Core channel.
|
||||||
|
|
||||||
|
The `gain` parameter represents the decimal portion of the gain
|
||||||
|
factor. The MSB represents 0.5 and the sign bit. Hence, the valid
|
||||||
|
total gain value (1 +/- 0.gain) ranges from 0.5 to 1.5 - LSB.
|
||||||
|
|
||||||
|
:param channel: Shuttler Core channel to be configured.
|
||||||
|
:param gain: Shuttler Core channel gain.
|
||||||
|
"""
|
||||||
rtio_output(self.target_base | self.target_gain | channel, gain)
|
rtio_output(self.target_base | self.target_gain | channel, gain)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def get_gain(self, channel):
|
def get_gain(self, channel):
|
||||||
|
"""Return the pre-DAC gain value of a Shuttler Core channel.
|
||||||
|
|
||||||
|
:param channel: The Shuttler Core channel.
|
||||||
|
:return: Pre-DAC gain value. See :meth:`set_gain`.
|
||||||
|
"""
|
||||||
rtio_output(self.target_base | self.target_gain |
|
rtio_output(self.target_base | self.target_gain |
|
||||||
self.target_read | channel, 0)
|
self.target_read | channel, 0)
|
||||||
return rtio_input_data(self.channel)
|
return rtio_input_data(self.channel)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_offset(self, channel, offset):
|
def set_offset(self, channel, offset):
|
||||||
|
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
:meth:`shuttler_volt_to_mu`
|
||||||
|
|
||||||
|
:param channel: Shuttler Core channel to be configured.
|
||||||
|
:param offset: Shuttler Core channel offset.
|
||||||
|
"""
|
||||||
rtio_output(self.target_base | self.target_offset | channel, offset)
|
rtio_output(self.target_base | self.target_offset | channel, offset)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def get_offset(self, channel):
|
def get_offset(self, channel):
|
||||||
|
"""Return the pre-DAC offset value of a Shuttler Core channel.
|
||||||
|
|
||||||
|
:param channel: The Shuttler Core channel.
|
||||||
|
:return: Pre-DAC offset value. See :meth:`set_offset`.
|
||||||
|
"""
|
||||||
rtio_output(self.target_base | self.target_offset |
|
rtio_output(self.target_base | self.target_offset |
|
||||||
self.target_read | channel, 0)
|
self.target_read | channel, 0)
|
||||||
return rtio_input_data(self.channel)
|
return rtio_input_data(self.channel)
|
||||||
|
|
||||||
|
|
||||||
class Volt:
|
class Volt:
|
||||||
|
"""Shuttler Core cubic DC-bias spline.
|
||||||
|
|
||||||
|
A Shuttler channel can generate a waveform `w(t)` that is the sum of a
|
||||||
|
cubic spline `a(t)` and a sinusoid modulated in amplitude by a cubic
|
||||||
|
spline `b(t)` and in phase/frequency by a quadratic spline `c(t)`, where
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
w(t) = a(t) + b(t) * cos(c(t))
|
||||||
|
|
||||||
|
And `t` corresponds to time in seconds.
|
||||||
|
This class controls the cubic spline `a(t)`, in which
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
a(t) = p_0 + p_1t + \\frac{p_2t^2}{2} + \\frac{p_3t^3}{6}
|
||||||
|
|
||||||
|
And `a(t)` is in Volt.
|
||||||
|
|
||||||
|
:param channel: RTIO channel number of this DC-bias spline interface.
|
||||||
|
:param core_device: Core device name.
|
||||||
|
"""
|
||||||
kernel_invariants = {"core", "channel", "target_o"}
|
kernel_invariants = {"core", "channel", "target_o"}
|
||||||
|
|
||||||
def __init__(self, dmgr, channel, core_device="core"):
|
def __init__(self, dmgr, channel, core_device="core"):
|
||||||
|
@ -62,6 +136,34 @@ class Volt:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64):
|
def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64):
|
||||||
|
"""Set the DC-bias spline waveform.
|
||||||
|
|
||||||
|
Given `a(t)` as defined in :class:`Volt`, the coefficients should be
|
||||||
|
configured by the following formulae.
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
T &= 8*10^{-9}
|
||||||
|
|
||||||
|
a_0 &= p_0
|
||||||
|
|
||||||
|
a_1 &= p_1T + \\frac{p_2T^2}{2} + \\frac{p_3T^3}{6}
|
||||||
|
|
||||||
|
a_2 &= p_2T^2 + p_3T^3
|
||||||
|
|
||||||
|
a_3 &= p_3T^3
|
||||||
|
|
||||||
|
:math:`a_0`, :math:`a_1`, :math:`a_2` and :math:`a_3` are 14, 30, 46
|
||||||
|
and 46 bits in width respectively. See :meth:`shuttler_volt_to_mu` for
|
||||||
|
machine unit conversion.
|
||||||
|
|
||||||
|
Note: The waveform is not updated to the Shuttler core until
|
||||||
|
triggered. See :class:`Trigger` for the update triggering mechanism.
|
||||||
|
|
||||||
|
:param a0: The :math:`a_0` coefficient in machine unit.
|
||||||
|
:param a1: The :math:`a_1` coefficient in machine unit.
|
||||||
|
:param a2: The :math:`a_2` coefficient in machine unit.
|
||||||
|
:param a3: The :math:`a_3` coefficient in machine unit.
|
||||||
|
"""
|
||||||
pdq_words = [
|
pdq_words = [
|
||||||
a0,
|
a0,
|
||||||
a1,
|
a1,
|
||||||
|
@ -80,6 +182,30 @@ class Volt:
|
||||||
|
|
||||||
|
|
||||||
class Dds:
|
class Dds:
|
||||||
|
"""Shuttler Core DDS spline.
|
||||||
|
|
||||||
|
A Shuttler channel can generate a waveform `w(t)` that is the sum of a
|
||||||
|
cubic spline `a(t)` and a sinusoid modulated in amplitude by a cubic
|
||||||
|
spline `b(t)` and in phase/frequency by a quadratic spline `c(t)`, where
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
w(t) = a(t) + b(t) * cos(c(t))
|
||||||
|
|
||||||
|
And `t` corresponds to time in seconds.
|
||||||
|
This class controls the cubic spline `b(t)` and quadratic spline `c(t)`,
|
||||||
|
in which
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
b(t) &= g * (q_0 + q_1t + \\frac{q_2t^2}{2} + \\frac{q_3t^3}{6})
|
||||||
|
|
||||||
|
c(t) &= r_0 + r_1t + \\frac{r_2t^2}{2}
|
||||||
|
|
||||||
|
And `b(t)` is in Volt, `c(t)` is in number of turns. Note that `b(t)`
|
||||||
|
contributes to a constant gain of :math:`g=1.64676`.
|
||||||
|
|
||||||
|
:param channel: RTIO channel number of this DC-bias spline interface.
|
||||||
|
:param core_device: Core device name.
|
||||||
|
"""
|
||||||
kernel_invariants = {"core", "channel", "target_o"}
|
kernel_invariants = {"core", "channel", "target_o"}
|
||||||
|
|
||||||
def __init__(self, dmgr, channel, core_device="core"):
|
def __init__(self, dmgr, channel, core_device="core"):
|
||||||
|
@ -90,6 +216,44 @@ class Dds:
|
||||||
@kernel
|
@kernel
|
||||||
def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64,
|
def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64,
|
||||||
c0: TInt32, c1: TInt32, c2: TInt32):
|
c0: TInt32, c1: TInt32, c2: TInt32):
|
||||||
|
"""Set the DDS spline waveform.
|
||||||
|
|
||||||
|
Given `b(t)` and `c(t)` as defined in :class:`Dds`, the coefficients
|
||||||
|
should be configured by the following formulae.
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
T &= 8*10^{-9}
|
||||||
|
|
||||||
|
b_0 &= q_0
|
||||||
|
|
||||||
|
b_1 &= q_1T + \\frac{q_2T^2}{2} + \\frac{q_3T^3}{6}
|
||||||
|
|
||||||
|
b_2 &= q_2T^2 + q_3T^3
|
||||||
|
|
||||||
|
b_3 &= q_3T^3
|
||||||
|
|
||||||
|
c_0 &= r_0
|
||||||
|
|
||||||
|
c_1 &= r_1T + \\frac{r_2T^2}{2}
|
||||||
|
|
||||||
|
c_2 &= r_2T^2
|
||||||
|
|
||||||
|
:math:`b_0`, :math:`b_1`, :math:`b_2` and :math:`b_3` are 14, 30, 46
|
||||||
|
and 46 bits in width respectively. See :meth:`shuttler_volt_to_mu` for
|
||||||
|
machine unit conversion. :math:`c_0`, :math:`c_1` and :math:`c_2` are
|
||||||
|
16, 32 and 32 bits in width respectively.
|
||||||
|
|
||||||
|
Note: The waveform is not updated to the Shuttler core until
|
||||||
|
triggered. See :class:`Trigger` for the update triggering mechanism.
|
||||||
|
|
||||||
|
:param b0: The :math:`b_0` coefficient in machine unit.
|
||||||
|
:param b1: The :math:`b_1` coefficient in machine unit.
|
||||||
|
:param b2: The :math:`b_2` coefficient in machine unit.
|
||||||
|
:param b3: The :math:`b_3` coefficient in machine unit.
|
||||||
|
:param c0: The :math:`c_0` coefficient in machine unit.
|
||||||
|
:param c1: The :math:`c_1` coefficient in machine unit.
|
||||||
|
:param c2: The :math:`c_2` coefficient in machine unit.
|
||||||
|
"""
|
||||||
pdq_words = [
|
pdq_words = [
|
||||||
b0,
|
b0,
|
||||||
b1,
|
b1,
|
||||||
|
@ -113,6 +277,11 @@ class Dds:
|
||||||
|
|
||||||
|
|
||||||
class Trigger:
|
class Trigger:
|
||||||
|
"""Shuttler Core spline coefficients update trigger.
|
||||||
|
|
||||||
|
:param channel: RTIO channel number of the trigger interface.
|
||||||
|
:param core_device: Core device name.
|
||||||
|
"""
|
||||||
kernel_invariants = {"core", "channel", "target_o"}
|
kernel_invariants = {"core", "channel", "target_o"}
|
||||||
|
|
||||||
def __init__(self, dmgr, channel, core_device="core"):
|
def __init__(self, dmgr, channel, core_device="core"):
|
||||||
|
@ -122,6 +291,16 @@ class Trigger:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def trigger(self, trig_out):
|
def trigger(self, trig_out):
|
||||||
|
"""Triggers coefficient update of (a) Shuttler core channel(s).
|
||||||
|
|
||||||
|
Each bit corresponds to a Shuttler waveform generator core. Setting
|
||||||
|
`trig_out` bits commits the pending coefficient update (from
|
||||||
|
`set_waveform` in :class:`Volt` and :class:`Dds`) to the Shuttler core
|
||||||
|
synchronously.
|
||||||
|
|
||||||
|
:param trig_out: Coefficient update trigger bits. The MSB corresponds
|
||||||
|
to Channel 15, LSB corresponds to Channel 0.
|
||||||
|
"""
|
||||||
rtio_output(self.target_o, trig_out)
|
rtio_output(self.target_o, trig_out)
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,6 +336,19 @@ _AD4115_REG_SETUPCON0 = 0x20
|
||||||
|
|
||||||
|
|
||||||
class Relay:
|
class Relay:
|
||||||
|
"""Shuttler AFE relay switches.
|
||||||
|
|
||||||
|
It controls the AFE relay switches and the LEDs. Switch on the relay to
|
||||||
|
enable AFE output; And off to disable the output. The LEDs indicates the
|
||||||
|
relay status.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The relay does not disable ADC measurements. Voltage of any channels
|
||||||
|
can still be read by the ADC even after switching off the relays.
|
||||||
|
|
||||||
|
:param spi_device: SPI bus device name.
|
||||||
|
:param core_device: Core device name.
|
||||||
|
"""
|
||||||
kernel_invariant = {"core", "bus"}
|
kernel_invariant = {"core", "bus"}
|
||||||
|
|
||||||
def __init__(self, dmgr, spi_device, core_device="core"):
|
def __init__(self, dmgr, spi_device, core_device="core"):
|
||||||
|
@ -165,15 +357,34 @@ class Relay:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def init(self):
|
def init(self):
|
||||||
|
"""Initialize SPI device.
|
||||||
|
|
||||||
|
Configures the SPI bus to 16-bits, write-only, simultaneous relay
|
||||||
|
switches and LED control.
|
||||||
|
"""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED)
|
RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def enable(self, en: TInt32):
|
def enable(self, en: TInt32):
|
||||||
|
"""Enable/Disable relay switches of corresponding channels.
|
||||||
|
|
||||||
|
Each bit corresponds to the relay switch of a channel. Asserting a bit
|
||||||
|
turns on the corresponding relay switch; Deasserting the same bit
|
||||||
|
turns off the switch instead.
|
||||||
|
|
||||||
|
:param en: Switch enable bits. The MSB corresponds to Channel 15, LSB
|
||||||
|
corresponds to Channel 0.
|
||||||
|
"""
|
||||||
self.bus.write(en << 16)
|
self.bus.write(en << 16)
|
||||||
|
|
||||||
|
|
||||||
class ADC:
|
class ADC:
|
||||||
|
"""Shuttler AFE ADC (AD4115) driver.
|
||||||
|
|
||||||
|
:param spi_device: SPI bus device name.
|
||||||
|
:param core_device: Core device name.
|
||||||
|
"""
|
||||||
kernel_invariant = {"core", "bus"}
|
kernel_invariant = {"core", "bus"}
|
||||||
|
|
||||||
def __init__(self, dmgr, spi_device, core_device="core"):
|
def __init__(self, dmgr, spi_device, core_device="core"):
|
||||||
|
@ -182,13 +393,26 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read_id(self) -> TInt32:
|
def read_id(self) -> TInt32:
|
||||||
|
"""Read the product ID of the ADC.
|
||||||
|
|
||||||
|
The expected return value is 0x38DX, the 4 LSbs are don't cares.
|
||||||
|
|
||||||
|
:return: The read-back product ID.
|
||||||
|
"""
|
||||||
return self.read16(_AD4115_REG_ID)
|
return self.read16(_AD4115_REG_ID)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def reset(self):
|
def reset(self):
|
||||||
# Hold DIN high for 64 cycles
|
"""AD4115 reset procedure.
|
||||||
# However, asserting CS right after the 64 cycles seems to interrupt
|
|
||||||
# the start-up sequence.
|
This performs a write operation of 96 serial clock cycles with DIN
|
||||||
|
held at high. It resets the entire device, including the register
|
||||||
|
contents.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The datasheet only requires 64 cycles, but reaserting `CS_n` right
|
||||||
|
after the transfer appears to interrupt the start-up sequence.
|
||||||
|
"""
|
||||||
self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC)
|
self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC)
|
||||||
self.bus.write(0xffffffff)
|
self.bus.write(0xffffffff)
|
||||||
self.bus.write(0xffffffff)
|
self.bus.write(0xffffffff)
|
||||||
|
@ -198,6 +422,11 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read8(self, addr: TInt32) -> TInt32:
|
def read8(self, addr: TInt32) -> TInt32:
|
||||||
|
"""Read from 8 bit register.
|
||||||
|
|
||||||
|
:param addr: Register address.
|
||||||
|
:return: Read-back register content.
|
||||||
|
"""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||||
16, SPIT_ADC_RD, CS_ADC)
|
16, SPIT_ADC_RD, CS_ADC)
|
||||||
|
@ -206,6 +435,11 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read16(self, addr: TInt32) -> TInt32:
|
def read16(self, addr: TInt32) -> TInt32:
|
||||||
|
"""Read from 16 bit register.
|
||||||
|
|
||||||
|
:param addr: Register address.
|
||||||
|
:return: Read-back register content.
|
||||||
|
"""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||||
24, SPIT_ADC_RD, CS_ADC)
|
24, SPIT_ADC_RD, CS_ADC)
|
||||||
|
@ -214,6 +448,11 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read24(self, addr: TInt32) -> TInt32:
|
def read24(self, addr: TInt32) -> TInt32:
|
||||||
|
"""Read from 24 bit register.
|
||||||
|
|
||||||
|
:param addr: Register address.
|
||||||
|
:return: Read-back register content.
|
||||||
|
"""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||||
32, SPIT_ADC_RD, CS_ADC)
|
32, SPIT_ADC_RD, CS_ADC)
|
||||||
|
@ -222,44 +461,102 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write8(self, addr: TInt32, data: TInt32):
|
def write8(self, addr: TInt32, data: TInt32):
|
||||||
|
"""Write to 8 bit register.
|
||||||
|
|
||||||
|
:param addr: Register address.
|
||||||
|
:param data: Data to be written.
|
||||||
|
"""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
ADC_SPI_CONFIG | spi.SPI_END, 16, SPIT_ADC_WR, CS_ADC)
|
ADC_SPI_CONFIG | spi.SPI_END, 16, SPIT_ADC_WR, CS_ADC)
|
||||||
self.bus.write(addr << 24 | (data & 0xff) << 16)
|
self.bus.write(addr << 24 | (data & 0xff) << 16)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write16(self, addr: TInt32, data: TInt32):
|
def write16(self, addr: TInt32, data: TInt32):
|
||||||
|
"""Write to 16 bit register.
|
||||||
|
|
||||||
|
:param addr: Register address.
|
||||||
|
:param data: Data to be written.
|
||||||
|
"""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
ADC_SPI_CONFIG | spi.SPI_END, 24, SPIT_ADC_WR, CS_ADC)
|
ADC_SPI_CONFIG | spi.SPI_END, 24, SPIT_ADC_WR, CS_ADC)
|
||||||
self.bus.write(addr << 24 | (data & 0xffff) << 8)
|
self.bus.write(addr << 24 | (data & 0xffff) << 8)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write24(self, addr: TInt32, data: TInt32):
|
def write24(self, addr: TInt32, data: TInt32):
|
||||||
|
"""Write to 24 bit register.
|
||||||
|
|
||||||
|
:param addr: Register address.
|
||||||
|
:param data: Data to be written.
|
||||||
|
"""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC)
|
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC)
|
||||||
self.bus.write(addr << 24 | (data & 0xffffff))
|
self.bus.write(addr << 24 | (data & 0xffffff))
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read_ch(self, channel: TInt32) -> TFloat:
|
def read_ch(self, channel: TInt32) -> TFloat:
|
||||||
|
"""Sample a Shuttler channel on the AFE.
|
||||||
|
|
||||||
|
It performs a single conversion using profile 0 and setup 0, on the
|
||||||
|
selected channel. The sample is then recovered and converted to volt.
|
||||||
|
|
||||||
|
:param channel: Shuttler channel to be sampled.
|
||||||
|
:return: Voltage sample in volt.
|
||||||
|
"""
|
||||||
# Always configure Profile 0 for single conversion
|
# Always configure Profile 0 for single conversion
|
||||||
self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4))
|
self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4))
|
||||||
self.write16(_AD4115_REG_SETUPCON0, 0x1300)
|
self.write16(_AD4115_REG_SETUPCON0, 0x1300)
|
||||||
self.write16(_AD4115_REG_ADCMODE, 0x8010)
|
self.single_conversion()
|
||||||
|
|
||||||
delay(100*us)
|
delay(100*us)
|
||||||
adc_code = self.read24(_AD4115_REG_DATA)
|
adc_code = self.read24(_AD4115_REG_DATA)
|
||||||
return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1
|
return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def single_conversion(self):
|
||||||
|
"""Place the ADC in single conversion mode.
|
||||||
|
|
||||||
|
The ADC returns to standby mode after the conversion is complete.
|
||||||
|
"""
|
||||||
|
self.write16(_AD4115_REG_ADCMODE, 0x8010)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def standby(self):
|
def standby(self):
|
||||||
|
"""Place the ADC in standby mode and disables power down the clock.
|
||||||
|
|
||||||
|
The ADC can be returned to single conversion mode by calling
|
||||||
|
:meth:`single_conversion`.
|
||||||
|
"""
|
||||||
# Selecting internal XO (0b00) also disables clock during standby
|
# Selecting internal XO (0b00) also disables clock during standby
|
||||||
self.write16(_AD4115_REG_ADCMODE, 0x8020)
|
self.write16(_AD4115_REG_ADCMODE, 0x8020)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def power_down(self):
|
def power_down(self):
|
||||||
|
"""Place the ADC in power-down mode.
|
||||||
|
|
||||||
|
The ADC must be reset before returning to other modes.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The AD4115 datasheet suggests placing the ADC in standby mode
|
||||||
|
before power-down. This is to prevent accidental entry into the
|
||||||
|
power-down mode.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
:meth:`standby`
|
||||||
|
|
||||||
|
:meth:`power_up`
|
||||||
|
|
||||||
|
"""
|
||||||
self.write16(_AD4115_REG_ADCMODE, 0x8030)
|
self.write16(_AD4115_REG_ADCMODE, 0x8030)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def exit_power_down(self):
|
def power_up(self):
|
||||||
|
"""Exit the ADC power-down mode.
|
||||||
|
|
||||||
|
The ADC should be in power-down mode before calling this method.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
:meth:`power_down`
|
||||||
|
"""
|
||||||
self.reset()
|
self.reset()
|
||||||
# Although the datasheet claims 500 us reset wait time, only waiting
|
# Although the datasheet claims 500 us reset wait time, only waiting
|
||||||
# for ~500 us can result in DOUT pin stuck in high
|
# for ~500 us can result in DOUT pin stuck in high
|
||||||
|
@ -267,6 +564,30 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]):
|
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]):
|
||||||
|
"""Calibrate the Shuttler waveform generator using the ADC on the AFE.
|
||||||
|
|
||||||
|
It finds the average slope rate and average offset by samples, and
|
||||||
|
compensate by writing the pre-DAC gain and offset registers in the
|
||||||
|
configuration registers.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
If the pre-calibration slope rate < 1, the calibration procedure
|
||||||
|
will introduce a pre-DAC gain compensation. However, this may
|
||||||
|
saturate the pre-DAC voltage code. (See :class:`Config` notes).
|
||||||
|
Shuttler cannot cover the entire +/- 10 V range in this case.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
:meth:`Config.set_gain`
|
||||||
|
|
||||||
|
:meth:`Config.set_offset`
|
||||||
|
|
||||||
|
:param volts: A list of all 16 cubic DC-bias spline.
|
||||||
|
(See :class:`Volt`)
|
||||||
|
:param trigger: The Shuttler spline coefficient update trigger.
|
||||||
|
:param config: The Shuttler core configuration registers.
|
||||||
|
:param samples: A list of sample voltages for calibration. There must
|
||||||
|
be at least 2 samples to perform slope rate calculation.
|
||||||
|
"""
|
||||||
assert len(volts) == 16
|
assert len(volts) == 16
|
||||||
assert len(samples) > 1
|
assert len(samples) > 1
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,12 @@ DAC/ADC drivers
|
||||||
.. automodule:: artiq.coredevice.fastino
|
.. automodule:: artiq.coredevice.fastino
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
:mod:`artiq.coredevice.shuttler` module
|
||||||
|
++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
.. automodule:: artiq.coredevice.shuttler
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Miscellaneous
|
Miscellaneous
|
||||||
-------------
|
-------------
|
||||||
|
|
Loading…
Reference in New Issue