diff --git a/artiq/coredevice/novogorny.py b/artiq/coredevice/novogorny.py new file mode 100644 index 000000000..94e119973 --- /dev/null +++ b/artiq/coredevice/novogorny.py @@ -0,0 +1,174 @@ +from numpy import int32, int64 + +from artiq.language.core import kernel, delay, portable +from artiq.language.units import us, ns +from artiq.coredevice.ad9912_reg import * + +from artiq.coredevice import spi2 as spi +from artiq.coredevice import urukul + + +SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*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_CS_ADC = 1 +SPI_CS_SR = 2 + + +@portable +def adc_ctrl(channel=1, softspan=0b111, valid=1): + """Build a LTC2335-16 control word""" + return (valid << 7) | (channel << 3) | softspan + + +@portable +def adc_softspan(data): + """Return the softspan configuration index from a result packet""" + return data & 0x7 + + +@portable +def adc_channel(data): + """Return the channel index from a result packet""" + return (data >> 3) & 0x7 + + +@portable +def adc_data(data): + """Return the ADC value from a result packet""" + return (data >> 8) & 0xffff + + +@portable +def adc_value(data, v_ref=5.): + """Convert a ADC result packet to SI units (Volt)""" + softspan = adc_softspan(data) + data = adc_data(data) + if softspan & 4: + g3 = 2 + else: + g3 = 1 + if softspan & 2: + g2 = 1 << 15 + else: + g2 = 1 << 16 + if softspan & 1: + g1 = 1000 + else: + g1 = 1023 + data = -(data & g2) + (data & ~g2) + v_per_lsb = v_ref*(1250*g3)/(g1*g2) + return data*v_per_lsb + + +class Novogorny: + """Novogorny ADC. + + Controls the LTC2335-16 8 channel ADC with SPI interface and + the switchable gain instrumentation amplifiers using a shift + register. + + :param spi_device: SPI bus device name + :param conv_device: CONV RTIO TTLOut channel name + :param div: SPI clock divider (default: 8) + :param core_device: Core device name + """ + kernel_invariants = {"bus", "core", "conv", "div", "v_ref"} + + def __init__(self, dmgr, spi_device, conv_device, div=8, + core_device="core"): + self.bus = dmgr.get(spi_device) + self.core = dmgr.get(core_device) + self.conv = dmgr.get(conv_device) + self.div = div + self.gains = 0x0000 + self.v_ref = 5. # 5 Volt reference + + @kernel + def set_gain_mu(self, channel, gain): + """Set instrumentation amplifier gain of a channel. + + The four gain settings (0, 1, 2, 3) corresponds to gains of + (1, 10, 100, 1000) respectively. + + :param channel: Channel index + :param gain: Gain setting + """ + self.gains &= ~(0b11 << (channel*2)) + self.gains |= gain << (channel*2) + self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, + 2, self.div, SPI_CS_SR) + self.bus.write(self.gains << 16) + + @kernel + def configure(self, data): + """Set up the ADC sequencer. + + :param data: List of 8 bit control words to write into the sequencer + table. + """ + if len(data) > 1: + self.bus.set_config_mu(SPI_CONFIG, + 8, self.div, SPI_CS_ADC) + for i in range(len(data) - 1): + self.bus.write(data[i] << 24) + self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, + 8, self.div, SPI_CS_ADC) + self.bus.write(data[len(data) - 1] << 24) + + @kernel + def sample_mu(self, next_ctrl=0): + """Acquire a sample: + + Perform a conversion and transfer the sample. + + :param next_ctrl: ADC control word for the next sample + :return: The ADC result packet (machine units) + """ + self.conv.pulse(40*ns) # t_CNVH + delay(560*ns) # t_CONV max + self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, + 24, self.div, SPI_CS_ADC) + self.bus.write(next_ctrl << 24) + return self.bus.read() + + @kernel + def sample(self, next_ctrl=0): + """Acquire a sample + + .. seealso:: :meth:`sample_mu` + + :param next_ctrl: ADC control word for the next sample + :return: The ADC result packet (Volt) + """ + return adc_value(self.sample_mu(), self.v_ref) + + @kernel + def burst_mu(self, data, dt_mu, ctrl=0): + """Acquire a burst of samples. + + If the burst is too long and the sample rate too high, there will be + RTIO input overflows. + + High sample rates lead to gain errors since the impedance between the + instrumentation amplifier and the ADC is high. + + :param data: List of data values to write result packets into. + In machine units. + :param dt: Sample interval in machine units. + :param ctrl: ADC control word to write during each result packet + transfer. + """ + self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, + 24, self.div, SPI_CS_ADC) + for i in range(len(data)): + t0 = now_mu() + self.conv.pulse(40*ns) # t_CNVH + delay(560*ns) # t_CONV max + self.bus.write(ctrl << 24) + at_mu(t0 + dt_mu) + for i in range(len(data)): + data[i] = self.bus.read() diff --git a/artiq/coredevice/spi2.py b/artiq/coredevice/spi2.py index 3bfdd5a20..ddf81d477 100644 --- a/artiq/coredevice/spi2.py +++ b/artiq/coredevice/spi2.py @@ -16,7 +16,7 @@ __all__ = [ "SPI_OFFLINE", "SPI_END", "SPI_INPUT", "SPI_CS_POLARITY", "SPI_CLK_POLARITY", "SPI_CLK_PHASE", "SPI_LSB_FIRST", "SPI_HALF_DUPLEX", - "SPIMaster", "NRTSPIMaster" + "SPIMaster" ] SPI_DATA_ADDR = 0 diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 0af06ab38..2ae020e03 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -269,7 +269,7 @@ class Opticlock(_StandaloneBase): phy = spi2.SPIMaster(self.platform.request("eem3_spi_p"), self.platform.request("eem3_spi_n")) self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) + rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=16)) for signal in "conv".split(): pads = platform.request("eem3_{}".format(signal)) diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 003b8322d..a77244da2 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -75,6 +75,12 @@ These drivers are for the core device and the peripherals closely integrated int .. automodule:: artiq.coredevice.sawg :members: +:mod:`artiq.coredevice.novogorny` module +---------------------------------------- + +.. automodule:: artiq.coredevice.novogorny + :members: + :mod:`artiq.coredevice.urukul` module -------------------------------------