From 30b94794379fefcadecf8aaffbd4e6225eb431d8 Mon Sep 17 00:00:00 2001 From: newell Date: Wed, 16 Oct 2024 23:14:45 -0700 Subject: [PATCH] add AD9834 core device driver (#2596) --- RELEASE_NOTES.rst | 1 + artiq/coredevice/ad9834.py | 397 ++++++++++++++++++++++++++ artiq/test/coredevice/test_ad9834.py | 321 +++++++++++++++++++++ doc/manual/core_device.rst | 30 +- doc/manual/core_drivers_reference.rst | 6 + 5 files changed, 752 insertions(+), 3 deletions(-) create mode 100644 artiq/coredevice/ad9834.py create mode 100644 artiq/test/coredevice/test_ad9834.py diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 1b374d1f5..eb97387e8 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -20,6 +20,7 @@ ARTIQ-9 (Unreleased) * Compiler can now give automatic suggestions for ``kernel_invariants``. * Idle kernels now restart when written with ``artiq_coremgmt`` and stop when erased/removed from config. * New support for the EBAZ4205 Zynq-SoC control card. +* New core device driver for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module. ARTIQ-8 ------- diff --git a/artiq/coredevice/ad9834.py b/artiq/coredevice/ad9834.py new file mode 100644 index 000000000..306aa712d --- /dev/null +++ b/artiq/coredevice/ad9834.py @@ -0,0 +1,397 @@ +""" +RTIO Driver for the Analog Devices AD9834 DDS via 3-wire SPI interface. +""" + +# https://www.analog.com/media/en/technical-documentation/data-sheets/AD9834.pdf +# https://www.analog.com/media/en/technical-documentation/app-notes/an-1070.pdf + +from artiq.coredevice import spi2 as spi +from artiq.experiment import * +from artiq.language.core import * +from artiq.language.types import * +from artiq.language.units import * + +AD9834_B28 = 1 << 13 +AD9834_HLB = 1 << 12 +AD9834_FSEL = 1 << 11 +AD9834_PSEL = 1 << 10 +AD9834_PIN_SW = 1 << 9 +AD9834_RESET = 1 << 8 +AD9834_SLEEP1 = 1 << 7 +AD9834_SLEEP12 = 1 << 6 +AD9834_OPBITEN = 1 << 5 +AD9834_SIGN_PIB = 1 << 4 +AD9834_DIV2 = 1 << 3 +AD9834_MODE = 1 << 1 + +AD9834_FREQ_REG_0 = 0b01 << 14 +AD9834_FREQ_REG_1 = 0b10 << 14 +FREQ_REGS = [AD9834_FREQ_REG_0, AD9834_FREQ_REG_1] + +AD9834_PHASE_REG = 0b11 << 14 +AD9834_PHASE_REG_0 = AD9834_PHASE_REG | (0 << 13) +AD9834_PHASE_REG_1 = AD9834_PHASE_REG | (1 << 13) +PHASE_REGS = [AD9834_PHASE_REG_0, AD9834_PHASE_REG_1] + + +class AD9834: + """ + AD9834 DDS driver. + + This class provides control for the DDS AD9834. + + The driver utilizes bit-controlled :const:`AD9834_FSEL`, :const:`AD9834_PSEL`, and + :const:`AD9834_RESET`. To pin control ``FSELECT``, ``PSELECT``, and ``RESET`` set + :const:`AD9834_PIN_SW`. The ``ctrl_reg`` attribute is used to maintain the state of + the control register, enabling persistent management of various configurations. + + :param spi_device: SPI bus device name. + :param spi_freq: SPI bus clock frequency (default: 10 MHz, max: 40 MHz). + :param clk_freq: DDS clock frequency (default: 75 MHz). + :param core_device: Core device name (default: "core"). + """ + + kernel_invariants = {"core", "bus", "spi_freq", "clk_freq"} + + def __init__( + self, dmgr, spi_device, spi_freq=10 * MHz, clk_freq=75 * MHz, core_device="core" + ): + self.core = dmgr.get(core_device) + self.bus = dmgr.get(spi_device) + assert spi_freq <= 40 * MHz, "SPI frequency exceeds maximum value of 40 MHz" + self.spi_freq = spi_freq + self.clk_freq = clk_freq + self.ctrl_reg = 0x0000 # Reset control register + + @kernel + def init(self): + """ + Initialize the AD9834: configure the SPI bus and reset the DDS. + + This method performs the necessary setup for the AD9834 device, including: + - Configuring the SPI bus parameters (clock polarity, data width, and frequency). + - Putting the AD9834 into a reset state to ensure proper initialization. + + The SPI bus is configured to use 16 bits of data width with the clock frequency + provided as a parameter when creating the AD9834 instance. After configuring + the SPI bus, the method invokes :meth:`enable_reset()` to reset the AD9834. + This is an essential step to prepare the device for subsequent configuration + of frequency and phase. + + This method should be called before any other operations are performed + on the AD9834 to ensure that the device is in a known state. + """ + self.bus.set_config(spi.SPI_CLK_POLARITY | spi.SPI_END, 16, self.spi_freq, 1) + self.enable_reset() + + @kernel + def set_frequency_reg(self, freq_reg, frequency: TFloat): + """ + Set the frequency for the specified frequency register. + + This method calculates the frequency word based on the provided frequency in Hz + and writes it to the specified frequency register. + + :param freq_reg: The frequency register to write to, must be one of + :const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`. + :param frequency: The desired frequency in Hz, which will be converted to a + frequency word suitable for the AD9834. + + The frequency word is calculated using the formula: + + ``freq_word = (frequency * (1 << 28)) / clk_freq`` + + The result is limited to the lower 28 bits for compatibility with the AD9834. + + The method first sets the control register to enable the appropriate settings, + then sends the fourteen least significant bits LSBs and fourteen most significant + bits MSBs in two consecutive writes to the specified frequency register. + """ + if freq_reg not in FREQ_REGS: + raise ValueError("Invalid frequency register") + assert frequency <= 37.5 * MHz, "Frequency exceeds maximum value of 37.5 MHz" + freq_word = int((frequency * (1 << 28)) / self.clk_freq) & 0x0FFFFFFF + self.ctrl_reg |= AD9834_B28 + self.write(self.ctrl_reg) + lsb = freq_word & 0x3FFF + msb = (freq_word >> 14) & 0x3FFF + self.write(freq_reg | lsb) + self.write(freq_reg | msb) + + @kernel + def set_frequency_reg_msb(self, freq_reg, word: TInt32): + """ + Set the fourteen most significant bits MSBs of the specified frequency register. + + This method updates the specified frequency register with the provided MSB value. + It configures the control register to indicate that the MSB is being set. + + :param freq_reg: The frequency register to update, must be one of + :const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`. + :param word: The value to be written to the fourteen MSBs of the frequency register. + + The method first clears the appropriate control bits, sets :const:`AD9834_HLB` to + indicate that the MSB is being sent, and then writes the updated control register + followed by the MSB value to the specified frequency register. + """ + if freq_reg not in FREQ_REGS: + raise ValueError("Invalid frequency register") + self.ctrl_reg &= ~AD9834_B28 + self.ctrl_reg |= AD9834_HLB + self.write(self.ctrl_reg) + self.write(freq_reg | (word & 0x3FFF)) + + @kernel + def set_frequency_reg_lsb(self, freq_reg, word: TInt32): + """ + Set the fourteen least significant bits LSBs of the specified frequency register. + + This method updates the specified frequency register with the provided LSB value. + It configures the control register to indicate that the LSB is being set. + + :param freq_reg: The frequency register to update, must be one of + :const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`. + :param word: The value to be written to the fourteen LSBs of the frequency register. + + The method first clears the appropriate control bits and writes the updated control + register followed by the LSB value to the specified frequency register. + """ + if freq_reg not in FREQ_REGS: + raise ValueError("Invalid frequency register") + self.ctrl_reg &= ~AD9834_B28 + self.ctrl_reg &= ~AD9834_HLB + self.write(self.ctrl_reg) + self.write(freq_reg | (word & 0x3FFF)) + + @kernel + def select_frequency_reg(self, freq_reg): + """ + Select the active frequency register for the phase accumulator. + + This method chooses between the two available frequency registers in the AD9834 to + control the frequency of the output waveform. The control register is updated + to reflect the selected frequency register. + + :param freq_reg: The frequency register to select. Must be one of + :const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`. + """ + if freq_reg not in FREQ_REGS: + raise ValueError("Invalid frequency register") + if freq_reg == FREQ_REGS[0]: + self.ctrl_reg &= ~AD9834_FSEL + else: + self.ctrl_reg |= AD9834_FSEL + + self.ctrl_reg &= ~AD9834_PIN_SW + self.write(self.ctrl_reg) + + @kernel + def set_phase_reg(self, phase_reg, phase: TInt32): + """ + Set the phase for the specified phase register. + + This method updates the specified phase register with the provided phase value. + + :param phase_reg: The phase register to update, must be one of + :const:`AD9834_PHASE_REG_0` or :const:`AD9834_PHASE_REG_1`. + :param phase: The value to be written to the phase register. + + The method masks the phase value to ensure it fits within the 12-bit limit + and writes it to the specified phase register. + """ + if phase_reg not in PHASE_REGS: + raise ValueError("Invalid phase register") + phase_word = phase & 0x0FFF + self.write(phase_reg | phase_word) + + @kernel + def select_phase_reg(self, phase_reg): + """ + Select the active phase register for the phase accumulator. + + This method chooses between the two available phase registers in the AD9834 to + control the phase of the output waveform. The control register is updated + to reflect the selected phase register. + + :param phase_reg: The phase register to select. Must be one of + :const:`AD9834_PHASE_REG_0` or :const:`AD9834_PHASE_REG_1`. + """ + if phase_reg not in PHASE_REGS: + raise ValueError("Invalid phase register") + if phase_reg == PHASE_REGS[0]: + self.ctrl_reg &= ~AD9834_PSEL + else: + self.ctrl_reg |= AD9834_PSEL + + self.ctrl_reg &= ~AD9834_PIN_SW + self.write(self.ctrl_reg) + + @kernel + def enable_reset(self): + """ + Enable the DDS reset. + + This method sets :const:`AD9834_RESET`, putting the AD9834 into a reset state. + While in this state, the digital-to-analog converter (DAC) is not operational. + + This method should be called during initialization or when a reset is required + to reinitialize the device and ensure proper operation. + """ + self.ctrl_reg |= AD9834_RESET + self.write(self.ctrl_reg) + + @kernel + def output_enable(self): + """ + Disable the DDS reset and start signal generation. + + This method clears :const:`AD9834_RESET`, allowing the AD9834 to begin generating + signals. Once this method is called, the device will resume normal operation and + output the generated waveform. + + This method should be called after configuration of the frequency and phase + settings to activate the output. + """ + self.ctrl_reg &= ~AD9834_RESET + self.write(self.ctrl_reg) + + @kernel + def sleep(self, dac_pd: bool = False, clk_dis: bool = False): + """ + Put the AD9834 into sleep mode by selectively powering down the DAC and/or disabling + the internal clock. + + This method controls the sleep mode behavior of the AD9834 by setting or clearing the + corresponding bits in the control register. Two independent options can be specified: + + :param dac_pd: Set to ``True`` to power down the DAC (:const:`AD9834_SLEEP12` is set). + ``False`` will leave the DAC active. + :param clk_dis: Set to ``True`` to disable the internal clock (:const:`AD9834_SLEEP1` is set). + ``False`` will keep the clock running. + + Both options can be enabled independently, allowing the DAC and/or clock to be powered down as needed. + + The method updates the control register and writes the changes to the AD9834 device. + """ + if dac_pd: + self.ctrl_reg |= AD9834_SLEEP12 + else: + self.ctrl_reg &= ~AD9834_SLEEP12 + + if clk_dis: + self.ctrl_reg |= AD9834_SLEEP1 + else: + self.ctrl_reg &= ~AD9834_SLEEP1 + + self.write(self.ctrl_reg) + + @kernel + def awake(self): + """ + Exit sleep mode and restore normal operation. + + This method brings the AD9834 out of sleep mode by clearing any DAC power-down or + internal clock disable settings. It calls :meth:`sleep()` with no arguments, + effectively setting both ``dac_powerdown`` and ``internal_clk_disable`` to ``False``. + + The device will resume generating output based on the current frequency and phase + settings. + """ + self.sleep() + + @kernel + def config_sign_bit_out( + self, + high_z: bool = False, + msb_2: bool = False, + msb: bool = False, + comp_out: bool = False, + ): + """ + Configure the ``SIGN BIT OUT`` pin for various output modes. + + This method sets the output mode for the ``SIGN BIT OUT`` pin of the AD9834 based on the provided flags. + The user can enable one of several modes, including high impedance, MSB/2 output, MSB output, + or comparator output. These modes are mutually exclusive, and passing ``True`` to one flag will + configure the corresponding mode, while other flags should be left as ``False``. + + :param high_z: Set to ``True`` to place the ``SIGN BIT OUT`` pin in high impedance (disabled) mode. + :param msb_2: Set to ``True`` to output DAC Data MSB divided by 2 on the ``SIGN BIT OUT`` pin. + :param msb: Set to ``True`` to output DAC Data MSB on the ``SIGN BIT OUT`` pin. + :param comp_out: Set to ``True`` to output the comparator signal on the ``SIGN BIT OUT`` pin. + + Only one flag should be set to ``True`` at a time. If no valid mode is selected, the ``SIGN BIT OUT`` + pin will default to high impedance mode. + + The method updates the control register with the appropriate configuration and writes it to the AD9834. + """ + if high_z: + self.ctrl_reg &= ~AD9834_OPBITEN + elif msb_2: + self.ctrl_reg |= AD9834_OPBITEN + self.ctrl_reg &= ~AD9834_MODE + self.ctrl_reg &= ~AD9834_SIGN_PIB + self.ctrl_reg &= ~AD9834_DIV2 + elif msb: + self.ctrl_reg |= AD9834_OPBITEN + self.ctrl_reg &= ~AD9834_MODE + self.ctrl_reg &= ~AD9834_SIGN_PIB + self.ctrl_reg |= AD9834_DIV2 + elif comp_out: + self.ctrl_reg |= AD9834_OPBITEN + self.ctrl_reg &= ~AD9834_MODE + self.ctrl_reg |= AD9834_SIGN_PIB + self.ctrl_reg |= AD9834_DIV2 + else: + self.ctrl_reg &= ~AD9834_OPBITEN + + self.write(self.ctrl_reg) + + @kernel + def enable_triangular_waveform(self): + """ + Enable triangular waveform generation. + + This method configures the AD9834 to output a triangular waveform. It does so + by clearing :const:`AD9834_OPBITEN` in the control register and setting :const:`AD9834_MODE`. + Once this method is called, the AD9834 will begin generating a triangular waveform + at the frequency set for the selected frequency register. + + This method should be called when a triangular waveform is desired for signal + generation. Ensure that the frequency is set appropriately before invoking this method. + """ + self.ctrl_reg &= ~AD9834_OPBITEN + self.ctrl_reg |= AD9834_MODE + self.write(self.ctrl_reg) + + @kernel + def disable_triangular_waveform(self): + """ + Disable triangular waveform generation. + + This method disables the triangular waveform output by clearing :const:`AD9834_MODE`. + After invoking this method, the AD9834 will cease generating a triangular waveform. + The device can then be configured to output other waveform types if needed. + + This method should be called when switching to a different waveform type or + when the triangular waveform is no longer required. + """ + self.ctrl_reg &= ~AD9834_MODE + self.write(self.ctrl_reg) + + @kernel + def write(self, data: TInt32): + """ + Write a 16-bit word to the AD9834. + + This method sends a 16-bit data word to the AD9834 via the SPI bus. The input + data is left-shifted by 16 bits to ensure proper alignment for the SPI controller, + allowing for accurate processing of the command by the AD9834. + + This method is used internally by other methods to update the control registers + and frequency settings of the AD9834. It should not be called directly unless + low-level register manipulation is required. + + :param data: The 16-bit word to be sent to the AD9834. + """ + self.bus.write(data << 16) diff --git a/artiq/test/coredevice/test_ad9834.py b/artiq/test/coredevice/test_ad9834.py new file mode 100644 index 000000000..0a1eadb36 --- /dev/null +++ b/artiq/test/coredevice/test_ad9834.py @@ -0,0 +1,321 @@ +from artiq.coredevice.ad9834 import ( + AD9834_B28, + AD9834_DIV2, + AD9834_FSEL, + AD9834_HLB, + AD9834_MODE, + AD9834_OPBITEN, + AD9834_PIN_SW, + AD9834_PSEL, + AD9834_RESET, + AD9834_SIGN_PIB, + AD9834_SLEEP1, + AD9834_SLEEP12, + FREQ_REGS, + PHASE_REGS, +) +from artiq.experiment import * +from artiq.language.units import MHz +from artiq.test.hardware_testbench import ExperimentCase + + +class AD9834Exp(EnvExperiment): + def build(self, runner): + self.setattr_device("core") + self.dev = self.get_device("dds0") + self.runner = runner + + def run(self): + getattr(self, self.runner)() + + @kernel + def instantiate(self): + pass + + @kernel + def init(self): + self.core.break_realtime() + self.dev.init() + self.set_dataset("spi_freq", self.dev.spi_freq) + self.set_dataset("clk_freq", self.dev.clk_freq) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def set_frequency_reg_fail1(self): + self.core.break_realtime() + frequency = 10 * MHz + self.dev.set_frequency_reg(19, frequency) + + @kernel + def set_frequency_reg_fail2(self): + self.core.break_realtime() + self.dev.set_frequency_reg(FREQ_REGS[0], 37.6 * MHz) + + @kernel + def set_frequency_reg(self): + self.core.break_realtime() + self.dev.init() + self.dev.set_frequency_reg(FREQ_REGS[1], 19 * MHz) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def set_frequency_reg_msb(self): + self.core.break_realtime() + self.dev.init() + self.dev.ctrl_reg |= AD9834_B28 + self.dev.set_frequency_reg_msb(FREQ_REGS[0], 0x1111) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def set_frequency_reg_lsb(self): + self.core.break_realtime() + self.dev.init() + self.dev.ctrl_reg |= AD9834_B28 | AD9834_HLB + self.dev.set_frequency_reg_lsb(FREQ_REGS[1], 0xFFFF) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def select_frequency_reg_0(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_FSEL | AD9834_PIN_SW + self.dev.select_frequency_reg(FREQ_REGS[0]) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def select_frequency_reg_1(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_PIN_SW + self.dev.select_frequency_reg(FREQ_REGS[1]) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def set_phase_reg_fail(self): + self.core.break_realtime() + self.dev.set_phase_reg(19, 0x123) + + @kernel + def set_phase_reg(self): + self.core.break_realtime() + self.dev.init() + self.dev.set_phase_reg(PHASE_REGS[0], 0x123) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def select_phase_reg_0(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_PSEL | AD9834_PIN_SW + self.dev.select_phase_reg(PHASE_REGS[0]) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def select_phase_reg_1(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_PIN_SW + self.dev.select_phase_reg(PHASE_REGS[1]) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def enable_reset(self): + self.core.break_realtime() + self.dev.enable_reset() + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def output_enable(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_RESET + self.dev.output_enable() + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def sleep_dac_powerdown(self): + self.core.break_realtime() + self.dev.sleep(dac_pd=True) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def sleep_internal_clk_disable(self): + self.core.break_realtime() + self.dev.sleep(clk_dis=True) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def sleep(self): + self.core.break_realtime() + self.dev.sleep(dac_pd=True, clk_dis=True) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def awake(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_SLEEP1 | AD9834_SLEEP12 + self.dev.sleep() + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def sign_bit_high_z(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_OPBITEN + self.dev.config_sign_bit_out() + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def sign_bit_msb_2(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_MODE | AD9834_SIGN_PIB | AD9834_DIV2 + self.dev.config_sign_bit_out(msb_2=True) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def sign_bit_msb(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_MODE | AD9834_SIGN_PIB + self.dev.config_sign_bit_out(msb=True) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def sign_bit_comp_out(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_MODE + self.dev.config_sign_bit_out(comp_out=True) + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def enable_triangular_waveform(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_OPBITEN + self.dev.enable_triangular_waveform() + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + @kernel + def disable_triangular_waveform(self): + self.core.break_realtime() + self.dev.ctrl_reg |= AD9834_MODE + self.dev.disable_triangular_waveform() + self.set_dataset("ctrl_reg", self.dev.ctrl_reg) + + +class AD9834Test(ExperimentCase): + def test_instantiate(self): + self.execute(AD9834Exp, "instantiate") + + def test_init(self): + self.execute(AD9834Exp, "init") + spi_freq = self.dataset_mgr.get("spi_freq") + clk_freq = self.dataset_mgr.get("clk_freq") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(spi_freq, 10 * MHz) + self.assertEqual(clk_freq, 75 * MHz) + self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET) + + def test_set_frequency_reg_fail(self): + with self.assertRaises(ValueError): + self.execute(AD9834Exp, "set_frequency_reg_fail1") + with self.assertRaises(AssertionError): + self.execute(AD9834Exp, "set_frequency_reg_fail2") + + def test_set_frequency_reg(self): + self.execute(AD9834Exp, "set_frequency_reg") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET | AD9834_B28) + + def test_set_frequency_reg_msb(self): + self.execute(AD9834Exp, "set_frequency_reg_msb") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET | AD9834_HLB) + + def test_set_frequency_reg_lsb(self): + self.execute(AD9834Exp, "set_frequency_reg_lsb") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET) + + def test_select_frequency_reg_0(self): + self.execute(AD9834Exp, "select_frequency_reg_0") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000) + + def test_select_frequency_reg_1(self): + self.execute(AD9834Exp, "select_frequency_reg_1") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_FSEL) + + def test_set_phase_reg_fail(self): + with self.assertRaises(ValueError): + self.execute(AD9834Exp, "set_phase_reg_fail") + + def test_set_phase_reg(self): + self.execute(AD9834Exp, "set_phase_reg") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET) + + def test_select_phase_reg_0(self): + self.execute(AD9834Exp, "select_phase_reg_0") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000) + + def test_select_phase_reg_1(self): + self.execute(AD9834Exp, "select_phase_reg_1") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_PSEL) + + def test_enable_reset(self): + self.execute(AD9834Exp, "enable_reset") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET) + + def test_output_enable(self): + self.execute(AD9834Exp, "output_enable") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000) + + def test_sleep_dac_powerdown(self): + self.execute(AD9834Exp, "sleep_dac_powerdown") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP12) + + def test_sleep_internal_clk_disable(self): + self.execute(AD9834Exp, "sleep_internal_clk_disable") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP1) + + def test_sleep(self): + self.execute(AD9834Exp, "sleep") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP1 | AD9834_SLEEP12) + + def test_awake(self): + self.execute(AD9834Exp, "awake") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000) + + def test_sign_bit_high_z(self): + self.execute(AD9834Exp, "sign_bit_high_z") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000) + + def test_sign_bit_msb_2(self): + self.execute(AD9834Exp, "sign_bit_msb_2") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_OPBITEN) + + def test_sign_bit_msb(self): + self.execute(AD9834Exp, "sign_bit_msb") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_OPBITEN | AD9834_DIV2) + + def test_sign_bit_comp_out(self): + self.execute(AD9834Exp, "sign_bit_comp_out") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual( + ctrl_reg, 0x0000 | AD9834_OPBITEN | AD9834_SIGN_PIB | AD9834_DIV2 + ) + + def test_enble_triangular_waveform(self): + self.execute(AD9834Exp, "enable_triangular_waveform") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000 | AD9834_MODE) + + def test_disble_triangular_waveform(self): + self.execute(AD9834Exp, "disable_triangular_waveform") + ctrl_reg = self.dataset_mgr.get("ctrl_reg") + self.assertEqual(ctrl_reg, 0x0000) diff --git a/doc/manual/core_device.rst b/doc/manual/core_device.rst index b55c638be..4177e6414 100644 --- a/doc/manual/core_device.rst +++ b/doc/manual/core_device.rst @@ -156,15 +156,39 @@ Common KC705 problems * When connected, the CLOCK adapter breaks the JTAG chain due to TDI not being connected to TDO on the FMC mezzanine. * On some boards, the JTAG USB connector is not correctly soldered. +VADJ +"""" + +With the NIST CLOCK and QC2 adapters, for safe operation of the DDS buses (to prevent damage to the IO banks of the FPGA), the FMC VADJ rail of the KC705 should be changed to 3.3V. Plug the Texas Instruments USB-TO-GPIO PMBus adapter into the PMBus connector in the corner of the KC705 and use the Fusion Digital Power Designer software to configure (requires Windows). Write to chip number U55 (address 52), channel 4, which is the VADJ rail, to make it 3.3V instead of 2.5V. Power cycle the KC705 board to check that the startup voltage on the VADJ rail is now 3.3V. + EBAZ4205 ^^^^^^^^ The `EBAZ4205 `_ Zynq-SoC control card, originally used in the Ebit E9+ BTC miner, is a low-cost development board (around $20-$30 USD), making it an ideal option for experimenting with ARTIQ. To use the EBAZ4205, it's important to carefully follow the board documentation to configure it to boot from the SD card, as network booting via ``artiq_netboot`` is currently unsupported. This is because the Ethernet PHY is routed through the EMIO, requiring the FPGA to be programmed before the board can establish a network connection. -VADJ -"""" +One useful application of the EBAZ4205 is controlling external devices like the AD9834 DDS Module from ZonRi Technology Co., Ltd. To establish communication between the EBAZ4205 and the AD9834 module, proper configuration of the SPI interface pins is essential. The board's flexibility allows for straightforward control of the DDS once the correct pinout is known. The table below details the necessary connections between the EBAZ4205 and the AD9834 module, including power, ground, and SPI signals. -With the NIST CLOCK and QC2 adapters, for safe operation of the DDS buses (to prevent damage to the IO banks of the FPGA), the FMC VADJ rail of the KC705 should be changed to 3.3V. Plug the Texas Instruments USB-TO-GPIO PMBus adapter into the PMBus connector in the corner of the KC705 and use the Fusion Digital Power Designer software to configure (requires Windows). Write to chip number U55 (address 52), channel 4, which is the VADJ rail, to make it 3.3V instead of 2.5V. Power cycle the KC705 board to check that the startup voltage on the VADJ rail is now 3.3V. ++--------------------------+---------------------+----------------------------+ +| Pin on AD9834 Module | Chip Function | Connection on EBAZ4205 | ++==========================+=====================+============================+ +| SCLK | SCLK | CLK: DATA3-19 (Pin V20) | ++--------------------------+---------------------+----------------------------+ +| DATA | SDATA | MOSI: DATA3-17 (Pin U20) | ++--------------------------+---------------------+----------------------------+ +| SYNC | FSYNC | CS_N: DATA3-15 (Pin P19) | ++--------------------------+---------------------+----------------------------+ +| FSE (Tied to GND) | FSELECT | N/A: Bit Controlled | ++--------------------------+---------------------+----------------------------+ +| PSE (Tied to GND) | PSELECT | N/A: Bit Controlled | ++--------------------------+---------------------+----------------------------+ +| GND | Ground | GND: J8-1, J8-3 | ++--------------------------+---------------------+----------------------------+ +| VIN | AVDD/DVDD | 3.3V: J8-2 | ++--------------------------+---------------------+----------------------------+ +| RESET (Unused) | RESET | N/A: Bit Controlled | ++--------------------------+---------------------+----------------------------+ + +For a step-by-step guide, see the `EBAZ4205 and AD9834 setup guide `_. Variant details --------------- diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index c7f8de3d6..56d6cd46d 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -85,6 +85,12 @@ RF generation drivers .. automodule:: artiq.coredevice.ad9914 :members: +:mod:`artiq.coredevice.ad9834` module +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. automodule:: artiq.coredevice.ad9834 + :members: + :mod:`artiq.coredevice.mirny` module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^