forked from M-Labs/artiq
coredevice/zotino: port to NAC3
This commit is contained in:
parent
64877c0588
commit
bd95d9cf3d
@ -8,17 +8,19 @@ time is an error.
|
||||
# Designed from the data sheets and somewhat after the linux kernel
|
||||
# iio driver.
|
||||
|
||||
from numpy import int32
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import (kernel, portable, delay_mu, delay, now_mu,
|
||||
at_mu)
|
||||
from artiq.language.core import nac3, kernel, portable, KernelInvariant
|
||||
from artiq.language.units import ns, us
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
from artiq.coredevice.spi2 import *
|
||||
|
||||
SPI_AD53XX_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
0*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
|
||||
SPI_AD53XX_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
|
||||
0*SPI_INPUT | 0*SPI_CS_POLARITY |
|
||||
0*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE |
|
||||
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
|
||||
|
||||
AD53XX_CMD_DATA = 3 << 22
|
||||
AD53XX_CMD_OFFSET = 2 << 22
|
||||
@ -52,7 +54,7 @@ AD53XX_READ_AB3 = 0x109 << 7
|
||||
|
||||
|
||||
@portable
|
||||
def ad53xx_cmd_write_ch(channel, value, op):
|
||||
def ad53xx_cmd_write_ch(channel: int32, value: int32, op: int32) -> int32:
|
||||
"""Returns the word that must be written to the DAC to set a DAC
|
||||
channel register to a given value.
|
||||
|
||||
@ -67,7 +69,7 @@ def ad53xx_cmd_write_ch(channel, value, op):
|
||||
|
||||
|
||||
@portable
|
||||
def ad53xx_cmd_read_ch(channel, op):
|
||||
def ad53xx_cmd_read_ch(channel: int32, op: int32) -> int32:
|
||||
"""Returns the word that must be written to the DAC to read a given
|
||||
DAC channel register.
|
||||
|
||||
@ -82,7 +84,7 @@ def ad53xx_cmd_read_ch(channel, op):
|
||||
|
||||
# maintain function definition for backward compatibility
|
||||
@portable
|
||||
def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.):
|
||||
def voltage_to_mu(voltage: float, offset_dacs: int32 = 0x2000, vref: float = 5.) -> int32:
|
||||
"""Returns the 16-bit DAC register value required to produce a given output
|
||||
voltage, assuming offset and gain errors have been trimmed out.
|
||||
|
||||
@ -100,22 +102,25 @@ def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.):
|
||||
:param vref: DAC reference voltage (default: 5.)
|
||||
:return: The 16-bit DAC register value
|
||||
"""
|
||||
code = int(round((1 << 16) * (voltage / (4. * vref)) + offset_dacs * 0x4))
|
||||
code = round(float(1 << 16) * (voltage / (4. * vref))) + offset_dacs * 0x4
|
||||
if code < 0x0 or code > 0xffff:
|
||||
raise ValueError("Invalid DAC voltage!")
|
||||
# NAC3TODO raise ValueError("Invalid DAC voltage!")
|
||||
pass
|
||||
return code
|
||||
|
||||
|
||||
@nac3
|
||||
class _DummyTTL:
|
||||
@portable
|
||||
@kernel
|
||||
def on(self):
|
||||
pass
|
||||
|
||||
@portable
|
||||
@kernel
|
||||
def off(self):
|
||||
pass
|
||||
|
||||
|
||||
@nac3
|
||||
class AD53xx:
|
||||
"""Analog devices AD53[67][0123] family of multi-channel Digital to Analog
|
||||
Converters.
|
||||
@ -137,8 +142,15 @@ class AD53xx:
|
||||
experiments. (default: 8192)
|
||||
:param core_device: Core device name (default: "core")
|
||||
"""
|
||||
kernel_invariants = {"bus", "ldac", "clr", "chip_select", "div_write",
|
||||
"div_read", "vref", "core"}
|
||||
core: KernelInvariant[Core]
|
||||
bus: KernelInvariant[SPIMaster]
|
||||
ldac: KernelInvariant[TTLOut]
|
||||
clr: KernelInvariant[TTLOut]
|
||||
chip_select: KernelInvariant[int32]
|
||||
div_write: KernelInvariant[int32]
|
||||
div_read: KernelInvariant[int32]
|
||||
vref: KernelInvariant[float]
|
||||
offset_dacs: int32
|
||||
|
||||
def __init__(self, dmgr, spi_device, ldac_device=None, clr_device=None,
|
||||
chip_select=1, div_write=4, div_read=16, vref=5.,
|
||||
@ -161,7 +173,7 @@ class AD53xx:
|
||||
self.core = dmgr.get(core)
|
||||
|
||||
@kernel
|
||||
def init(self, blind=False):
|
||||
def init(self, blind: bool = False):
|
||||
"""Configures the SPI bus, drives LDAC and CLR high, programmes
|
||||
the offset DACs, and enables overtemperature shutdown.
|
||||
|
||||
@ -177,22 +189,25 @@ class AD53xx:
|
||||
self.chip_select)
|
||||
self.write_offset_dacs_mu(self.offset_dacs)
|
||||
if not blind:
|
||||
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL)
|
||||
ctrl = self.read_reg(0, AD53XX_READ_CONTROL)
|
||||
if ctrl == 0xffff:
|
||||
raise ValueError("DAC not found")
|
||||
if ctrl & 0b10000:
|
||||
raise ValueError("DAC over temperature")
|
||||
delay(25*us)
|
||||
# NAC3TODO raise ValueError("DAC not found")
|
||||
pass
|
||||
if (ctrl & 0b10000) != 0:
|
||||
# NAC3TODO raise ValueError("DAC over temperature")
|
||||
pass
|
||||
self.core.delay(125.*us) # NAC3TODO try to restore original 25us after kernel invariants
|
||||
self.bus.write( # enable power and overtemperature shutdown
|
||||
(AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8)
|
||||
if not blind:
|
||||
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL)
|
||||
ctrl = self.read_reg(0, AD53XX_READ_CONTROL)
|
||||
if (ctrl & 0b10111) != 0b00010:
|
||||
raise ValueError("DAC CONTROL readback mismatch")
|
||||
delay(15*us)
|
||||
# NAC3TODO raise ValueError("DAC CONTROL readback mismatch")
|
||||
pass
|
||||
self.core.delay(115.*us) # NAC3TODO try to restore original 15us after kernel invariants
|
||||
|
||||
@kernel
|
||||
def read_reg(self, channel=0, op=AD53XX_READ_X1A):
|
||||
def read_reg(self, channel: int32 = 0, op: int32 = AD53XX_READ_X1A) -> int32:
|
||||
"""Read a DAC register.
|
||||
|
||||
This method advances the timeline by the duration of two SPI transfers
|
||||
@ -205,17 +220,16 @@ class AD53xx:
|
||||
:return: The 16 bit register value
|
||||
"""
|
||||
self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8)
|
||||
self.bus.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24,
|
||||
self.bus.set_config_mu(SPI_AD53XX_CONFIG | SPI_INPUT, 24,
|
||||
self.div_read, self.chip_select)
|
||||
delay(270*ns) # t_21 min sync high in readback
|
||||
self.core.delay(270.*ns) # t_21 min sync high in readback
|
||||
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_NOP) << 8)
|
||||
self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write,
|
||||
self.chip_select)
|
||||
# FIXME: the int32 should not be needed to resolve unification
|
||||
return self.bus.read() & int32(0xffff)
|
||||
return self.bus.read() & 0xffff
|
||||
|
||||
@kernel
|
||||
def write_offset_dacs_mu(self, value):
|
||||
def write_offset_dacs_mu(self, value: int32):
|
||||
"""Program the OFS0 and OFS1 offset DAC registers.
|
||||
|
||||
Writes to the offset DACs take effect immediately without requiring
|
||||
@ -230,7 +244,7 @@ class AD53xx:
|
||||
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_OFS1 | value) << 8)
|
||||
|
||||
@kernel
|
||||
def write_gain_mu(self, channel, gain=0xffff):
|
||||
def write_gain_mu(self, channel: int32, gain: int32 = 0xffff):
|
||||
"""Program the gain register for a DAC channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
@ -242,7 +256,7 @@ class AD53xx:
|
||||
ad53xx_cmd_write_ch(channel, gain, AD53XX_CMD_GAIN) << 8)
|
||||
|
||||
@kernel
|
||||
def write_offset_mu(self, channel, offset=0x8000):
|
||||
def write_offset_mu(self, channel: int32, offset: int32 = 0x8000):
|
||||
"""Program the offset register for a DAC channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
@ -254,7 +268,7 @@ class AD53xx:
|
||||
ad53xx_cmd_write_ch(channel, offset, AD53XX_CMD_OFFSET) << 8)
|
||||
|
||||
@kernel
|
||||
def write_offset(self, channel, voltage):
|
||||
def write_offset(self, channel: int32, voltage: float):
|
||||
"""Program the DAC offset voltage for a channel.
|
||||
|
||||
An offset of +V can be used to trim out a DAC offset error of -V.
|
||||
@ -267,7 +281,7 @@ class AD53xx:
|
||||
self.vref))
|
||||
|
||||
@kernel
|
||||
def write_dac_mu(self, channel, value):
|
||||
def write_dac_mu(self, channel: int32, value: int32):
|
||||
"""Program the DAC input register for a channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
@ -277,7 +291,7 @@ class AD53xx:
|
||||
ad53xx_cmd_write_ch(channel, value, AD53XX_CMD_DATA) << 8)
|
||||
|
||||
@kernel
|
||||
def write_dac(self, channel, voltage):
|
||||
def write_dac(self, channel: int32, voltage: float):
|
||||
"""Program the DAC output voltage for a channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
@ -299,11 +313,11 @@ class AD53xx:
|
||||
This method advances the timeline by two RTIO clock periods.
|
||||
"""
|
||||
self.ldac.off()
|
||||
delay_mu(2*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low
|
||||
delay_mu(int64(2)*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low
|
||||
self.ldac.on()
|
||||
|
||||
@kernel
|
||||
def set_dac_mu(self, values, channels=list(range(40))):
|
||||
def set_dac_mu(self, values: list[int32], channels: list[int32] = list(range(40))):
|
||||
"""Program multiple DAC channels and pulse LDAC to update the DAC
|
||||
outputs.
|
||||
|
||||
@ -322,17 +336,19 @@ class AD53xx:
|
||||
t0 = now_mu()
|
||||
|
||||
# t10: max busy period after writing to DAC registers
|
||||
t_10 = self.core.seconds_to_mu(1500*ns)
|
||||
t_10 = self.core.seconds_to_mu(1500.*ns)
|
||||
# NAC3TODO len(values) https://git.m-labs.hk/M-Labs/nac3/issues/103
|
||||
len_values = 4
|
||||
# compensate all delays that will be applied
|
||||
delay_mu(-t_10-len(values)*self.bus.xfer_duration_mu)
|
||||
for i in range(len(values)):
|
||||
delay_mu(-t_10-int64(len_values)*self.bus.xfer_duration_mu)
|
||||
for i in range(len_values):
|
||||
self.write_dac_mu(channels[i], values[i])
|
||||
delay_mu(t_10)
|
||||
self.load()
|
||||
at_mu(t0)
|
||||
|
||||
@kernel
|
||||
def set_dac(self, voltages, channels=list(range(40))):
|
||||
def set_dac(self, voltages: list[float], channels: list[int32] = list(range(40))):
|
||||
"""Program multiple DAC channels and pulse LDAC to update the DAC
|
||||
outputs.
|
||||
|
||||
@ -351,8 +367,8 @@ class AD53xx:
|
||||
self.set_dac_mu(values, channels)
|
||||
|
||||
@kernel
|
||||
def calibrate(self, channel, vzs, vfs):
|
||||
""" Two-point calibration of a DAC channel.
|
||||
def calibrate(self, channel: int32, vzs: float, vfs: float):
|
||||
"""Two-point calibration of a DAC channel.
|
||||
|
||||
Programs the offset and gain register to trim out DAC errors. Does not
|
||||
take effect until LDAC is pulsed (see :meth load:).
|
||||
@ -371,15 +387,15 @@ class AD53xx:
|
||||
gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - (
|
||||
offset_err + 0xffff)
|
||||
|
||||
assert offset_err <= 0
|
||||
assert gain_err >= 0
|
||||
# NAC3TODO assert offset_err <= 0
|
||||
# NAC3TODO assert gain_err >= 0
|
||||
|
||||
self.core.break_realtime()
|
||||
self.write_offset_mu(channel, 0x8000-offset_err)
|
||||
self.write_gain_mu(channel, 0xffff-gain_err)
|
||||
|
||||
@portable
|
||||
def voltage_to_mu(self, voltage):
|
||||
def voltage_to_mu(self, voltage: float) -> int32:
|
||||
"""Returns the 16-bit DAC register value required to produce a given
|
||||
output voltage, assuming offset and gain errors have been trimmed out.
|
||||
|
||||
|
@ -4,19 +4,23 @@ Output event replacement is not supported and issuing commands at the same
|
||||
time is an error.
|
||||
"""
|
||||
|
||||
from artiq.language.core import kernel
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from numpy import int32
|
||||
|
||||
from artiq.language.core import nac3, kernel
|
||||
from artiq.coredevice.spi2 import *
|
||||
from artiq.coredevice.ad53xx import SPI_AD53XX_CONFIG, AD53xx
|
||||
|
||||
_SPI_SR_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
|
||||
_SPI_SR_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
|
||||
0*SPI_INPUT | 0*SPI_CS_POLARITY |
|
||||
0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE |
|
||||
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
|
||||
|
||||
_SPI_CS_DAC = 1
|
||||
_SPI_CS_SR = 2
|
||||
|
||||
|
||||
@nac3
|
||||
class Zotino(AD53xx):
|
||||
""" Zotino 32-channel, 16-bit 1MSPS DAC.
|
||||
|
||||
@ -42,8 +46,8 @@ class Zotino(AD53xx):
|
||||
div_read=div_read, core=core)
|
||||
|
||||
@kernel
|
||||
def set_leds(self, leds):
|
||||
""" Sets the states of the 8 user LEDs.
|
||||
def set_leds(self, leds: int32):
|
||||
"""Sets the states of the 8 user LEDs.
|
||||
|
||||
:param leds: 8-bit word with LED state
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user