Source code for artiq.coredevice.ad5360

from artiq.language.core import (kernel, portable, delay_mu, delay,
                                 seconds_to_mu)
from artiq.language.units import ns, us
from artiq.coredevice import spi

# Designed from the data sheets and somewhat after the linux kernel
# iio driver.

_AD5360_SPI_CONFIG = (0*spi.SPI_OFFLINE | 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)

_AD5360_CMD_DATA = 3 << 22
_AD5360_CMD_OFFSET = 2 << 22
_AD5360_CMD_GAIN = 1 << 22
_AD5360_CMD_SPECIAL = 0 << 22


@portable
def _AD5360_WRITE_CHANNEL(c):
    return (c + 8) << 16

_AD5360_SPECIAL_NOP = 0 << 16
_AD5360_SPECIAL_CONTROL = 1 << 16
_AD5360_SPECIAL_OFS0 = 2 << 16
_AD5360_SPECIAL_OFS1 = 3 << 16
_AD5360_SPECIAL_READ = 5 << 16


@portable
def _AD5360_READ_CHANNEL(ch):
    return (ch + 8) << 7

_AD5360_READ_X1A = 0x000 << 7
_AD5360_READ_X1B = 0x040 << 7
_AD5360_READ_OFFSET = 0x080 << 7
_AD5360_READ_GAIN = 0x0c0 << 7
_AD5360_READ_CONTROL = 0x101 << 7
_AD5360_READ_OFS0 = 0x102 << 7
_AD5360_READ_OFS1 = 0x103 << 7


[docs]class AD5360: """ Support for the Analog devices AD53[67][0123] multi-channel Digital to Analog Converters :param spi_device: Name of the SPI bus this device is on. :param ldac_device: Name of the TTL device that LDAC is connected to (optional). Needs to be explicitly initialized to high. :param chip_select: Value to drive on the chip select lines during transactions. """ def __init__(self, dmgr, spi_device, ldac_device=None, chip_select=1): self.core = dmgr.get("core") self.bus = dmgr.get(spi_device) if ldac_device is not None: self.ldac = dmgr.get(ldac_device) self.chip_select = chip_select @kernel
[docs] def setup_bus(self, write_div=4, read_div=7): """Configure the SPI bus and the SPI transaction parameters for this device. This method has to be called before any other method if the bus has been used to access a different device in the meantime. This method advances the timeline by the duration of two RTIO-to-Wishbone bus transactions. :param write_div: Write clock divider. :param read_div: Read clock divider. """ # write: 2*8ns >= 10ns = t_6 (clk falling to cs_n rising) # read: 4*8*ns >= 25ns = t_22 (clk falling to miso valid) self.bus.set_config_mu(_AD5360_SPI_CONFIG, write_div, read_div) self.bus.set_xfer(self.chip_select, 24, 0)
@kernel
[docs] def write(self, data): """Write 24 bits of data. This method advances the timeline by the duration of the SPI transfer and the required CS high time. """ self.bus.write(data << 8) delay_mu(self.bus.ref_period_mu) # get to 20ns min cs high
@kernel
[docs] def write_offsets(self, value=0x1fff): """Write the OFS0 and OFS1 offset DACs. This method advances the timeline by twice the duration of :meth:`write`. :param value: Value to set both offset registers to. """ value &= 0x3fff self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_OFS0 | value) self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_OFS1 | value)
@kernel
[docs] def write_channel(self, channel=0, value=0, op=_AD5360_CMD_DATA): """Write to a channel register. This method advances the timeline by the duration of :meth:`write`. :param channel: Channel number to write to. :param value: 16 bit value to write to the register. :param op: Operation to perform, one of :const:`_AD5360_CMD_DATA`, :const:`_AD5360_CMD_OFFSET`, :const:`_AD5360_CMD_GAIN` (default: :const:`_AD5360_CMD_DATA`). """ channel &= 0x3f value &= 0xffff self.write(op | _AD5360_WRITE_CHANNEL(channel) | value)
@kernel
[docs] def read_channel_sync(self, channel=0, op=_AD5360_READ_X1A): """Read a channel register. This method advances the timeline by the duration of :meth:`write` plus three RTIO-to-Wishbone transactions. :param channel: Channel number to read from. :param op: Operation to perform, one of :const:`_AD5360_READ_X1A`, :const:`_AD5360_READ_X1B`, :const:`_AD5360_READ_OFFSET`, :const:`_AD5360_READ_GAIN` (default: :const:`_AD5360_READ_X1A`). :return: The 16 bit register value. """ channel &= 0x3f self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_READ | op | _AD5360_READ_CHANNEL(channel)) self.bus.set_xfer(self.chip_select, 0, 24) self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_NOP) self.bus.read_async() self.bus.set_xfer(self.chip_select, 24, 0) return self.bus.input_async() & 0xffff
@kernel
[docs] def load(self): """Pulse the LDAC line. This method advances the timeline by two RTIO clock periods (16 ns). """ self.ldac.off() # t13 = 10ns ldac pulse width low delay_mu(2*self.bus.ref_period_mu) self.ldac.on()
@kernel
[docs] def set(self, values, op=_AD5360_CMD_DATA): """Write to several channels and pulse LDAC to update the channels. This method does not advance the timeline. Write events are scheduled in the past. The DACs will synchronously start changing their output levels `now`. :param values: List of 16 bit values to write to the channels. :param op: Operation to perform, one of :const:`_AD5360_CMD_DATA`, :const:`_AD5360_CMD_OFFSET`, :const:`_AD5360_CMD_GAIN` (default: :const:`_AD5360_CMD_DATA`). """ # compensate all delays that will be applied delay_mu(-len(values)*(self.bus.xfer_period_mu + self.bus.write_period_mu + self.bus.ref_period_mu) - 3*self.bus.ref_period_mu - seconds_to_mu(1.5*us)) for i in range(len(values)): self.write_channel(i, values[i], op) delay_mu(3*self.bus.ref_period_mu + # latency alignment ttl to spi seconds_to_mu(1.5*us)) # t10 max busy low for one channel self.load() delay_mu(-2*self.bus.ref_period_mu) # load(), t13