From 97918447a3426c39f23d3766049c7fb644c0a33e Mon Sep 17 00:00:00 2001 From: Robert Jordens Date: Wed, 21 Mar 2018 10:06:31 +0000 Subject: [PATCH] sampler: add coredevice driver --- artiq/coredevice/sampler.py | 138 ++++++++++++++++++++++++++ doc/manual/core_drivers_reference.rst | 6 ++ 2 files changed, 144 insertions(+) create mode 100644 artiq/coredevice/sampler.py diff --git a/artiq/coredevice/sampler.py b/artiq/coredevice/sampler.py new file mode 100644 index 000000000..3bb94f555 --- /dev/null +++ b/artiq/coredevice/sampler.py @@ -0,0 +1,138 @@ + +from artiq.language.core import kernel, delay, portable +from artiq.language.units import ns + +from artiq.coredevice import spi2 as spi + + +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 = 0 # no CS, SPI_END does not matter, framing is done with CNV +SPI_CS_PGIA = 1 # separate SPI bus, CS used as RCLK + + +@portable +def adc_mu_to_volt(data, gain=0, v_ref=5.): + """Convert ADC data in machine units to Volts. + + :param data: 16 bit signed ADC word + :param gain: PGIA gain setting (0: 1, ..., 3: 1000) + :param v_ref: Reference voltage in Volts + :return: Voltage in Volts + """ + for i in range(gain): + v_ref /= 10. + volt_per_lsb = v_ref/(1 << 15) + return data*volt_per_lsb + + +class Sampler: + """Sampler ADC. + + Controls the LTC2320-16 8 channel 16 bit ADC with SPI interface and + the switchable gain instrumentation amplifiers. + + :param spi_adc_device: ADC SPI bus device name + :param spi_pgia_device: PGIA SPI bus device name + :param cnv_device: CNV RTIO TTLOut channel name + :param div: SPI clock divider (default: 8) + :param core_device: Core device name + """ + kernel_invariants = {"bus_adc", "bus_pgia", "core", "cnv", "div", "v_ref"} + + def __init__(self, dmgr, spi_adc_device, spi_pgia_device, cnv_device, + div=8, core_device="core"): + self.bus_adc = dmgr.get(spi_adc_device) + self.bus_pgia = dmgr.get(spi_pgia_device) + self.core = dmgr.get(core_device) + self.cnv = dmgr.get(cnv_device) + self.div = div + self.gains = 0x0000 + self.v_ref = 10. # 5 Volt reference, 0.5 AFE diff gain + + @kernel + def init(self): + """Initialize the device. + + Sets up SPI channels. + """ + self.bus_adc.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, + 32, self.div, SPI_CS_ADC) + self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END, + 16, self.div, SPI_CS_PGIA) + + @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 + """ + gains = self.gains + gains &= ~(0b11 << (channel*2)) + gains |= gain << (channel*2) + self.bus_pgia.write(gains << 16) + self.gains = gains + + @kernel + def get_gains_mu(self): + """Read the PGIA gain settings of all channels. + + :return: The PGIA gain settings in machine units. + """ + self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + 16, self.div, SPI_CS_PGIA) + self.bus_pgia.write(self.gains << 16) + self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END, + 16, self.div, SPI_CS_PGIA) + self.gains = self.bus_pgia.read() & 0xffff + return self.gains + + @kernel + def sample_mu(self, data): + """Acquire a set of samples. + + Perform a conversion and transfer the samples. + + This assumes that the input FIFO of the ADC SPI RTIO channel is deep + enough to buffer the samples (half the length of `data` deep). + If it is not, there will be RTIO input overflows. + + :param data: List of data samples to fill. Must have even length. + Samples are always read from the last channel (channel 7) down. + The `data` list will always be filled with the last item + holding to the sample from channel 7. + """ + self.cnv.pulse(30*ns) # t_CNVH + delay(450*ns) # t_CONV + mask = 1 << 15 + for i in range(len(data)//2): + self.bus_adc.write(0) + for i in range(len(data) - 1, -1, -2): + val = self.bus_adc.read() + data[i] = val >> 16 + val &= 0xffff + data[i - 1] = -(val & mask) + (val & ~mask) + + @kernel + def sample(self, data): + """Acquire a set of samples. + + .. seealso:: :meth:`sample_mu` + + :param data: List of floating point data samples to fill. + """ + n = len(data) + adc_data = [0]*n + self.sample_mu(adc_data) + for i in range(n): + channel = 8 - len(data) + gain = (self.gains >> (channel*2)) & 0b11 + data[i] = adc_mu_to_volt(adc_data[i], gain, self.v_ref) diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 4185ddc47..8655fbcea 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -81,6 +81,12 @@ These drivers are for the core device and the peripherals closely integrated int .. automodule:: artiq.coredevice.novogorny :members: +:mod:`artiq.coredevice.sampler` module +-------------------------------------- + +.. automodule:: artiq.coredevice.sampler + :members: + :mod:`artiq.coredevice.urukul` module -------------------------------------