forked from M-Labs/artiq
156 lines
5.5 KiB
Python
156 lines
5.5 KiB
Python
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, corrected_fs=True):
|
|
"""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 corrected_fs: use corrected ADC FS reference.
|
|
Should be True for Samplers' revisions after v2.1. False for v2.1 and earlier.
|
|
:return: Voltage in Volts
|
|
"""
|
|
if gain == 0:
|
|
volt_per_lsb = 20.48 / (1 << 16) if corrected_fs else 20. / (1 << 16)
|
|
elif gain == 1:
|
|
volt_per_lsb = 2.048 / (1 << 16) if corrected_fs else 2. / (1 << 16)
|
|
elif gain == 2:
|
|
volt_per_lsb = .2048 / (1 << 16) if corrected_fs else .2 / (1 << 16)
|
|
elif gain == 3:
|
|
volt_per_lsb = 0.02048 / (1 << 16) if corrected_fs else .02 / (1 << 16)
|
|
else:
|
|
raise ValueError("invalid gain")
|
|
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 gains: Initial value for PGIA gains shift register
|
|
(default: 0x0000). Knowledge of this state is not transferred
|
|
between experiments.
|
|
:param hw_rev: Sampler's hardware revision string (default 'v2.2')
|
|
:param core_device: Core device name
|
|
"""
|
|
kernel_invariants = {"bus_adc", "bus_pgia", "core", "cnv", "div", "corrected_fs"}
|
|
|
|
def __init__(self, dmgr, spi_adc_device, spi_pgia_device, cnv_device,
|
|
div=8, gains=0x0000, hw_rev="v2.2", core_device="core"):
|
|
self.bus_adc = dmgr.get(spi_adc_device)
|
|
self.bus_adc.update_xfer_duration_mu(div, 32)
|
|
self.bus_pgia = dmgr.get(spi_pgia_device)
|
|
self.bus_pgia.update_xfer_duration_mu(div, 16)
|
|
self.core = dmgr.get(core_device)
|
|
self.cnv = dmgr.get(cnv_device)
|
|
self.div = div
|
|
self.gains = gains
|
|
self.corrected_fs = self.use_corrected_fs(hw_rev)
|
|
|
|
@staticmethod
|
|
def use_corrected_fs(hw_rev):
|
|
return hw_rev != "v2.1"
|
|
|
|
@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 = i + 8 - len(data)
|
|
gain = (self.gains >> (channel*2)) & 0b11
|
|
data[i] = adc_mu_to_volt(adc_data[i], gain, self.corrected_fs)
|