Open RF Prototyping

A Low Phase Noise DDS Signal Generator

DDSGen front

DDS Signal Generator - Front

DDSGen back

DDS Signal Generator - Back


This design makes use of AD9913 DDS Signal Source and HMC833 Frequency Synthesizer.

Usage

The DDSGen app is started from the command line. If the app is started without specifying a serial device (using the -d option) a list of suitable connected devices will be presented in the Control Port drop down in app UI.

cd referencedesigns
python LowPNDDSGen/app/ddsgen.py

or

cd referencedesigns
python LowPNDDSGen/app/ddsgen.py -d /dev/cu.usbmodem141201

The app may also be started from the app launcher if this is configured and running.

Once running the app is used as follows:

  1. Select the signal generator hardware control port. This is only necessary when the USB control port device has not been specified either on the command line of via the app launcher.

  2. Initialize the signal generator hardware by selecting the Initialize UI button.

  3. Optionally, select the signal generator reference source. If an external reference is being used this must be a 100MHz source connected to the 100MHz In SMA connector on the rear of the signal generator.

  4. The DDS channels can now be configured using the relevant Channel controls.

app start

Figure 1: DDS signal generator app

Initially the DDS reference clock is configured to be the default 250 MHz. This may be configured to other values by entering the desired clock frequency in the DDS Clk panel and selecting Configure. The DDS reference clock will be immediately updated to the specified value. The maximum reference clock frequency is given as 250 MHz in the AD9913 data sheet.

Channel controls

The usual frequency, phase and level controls are available. The level control allows for the level units to be selected via a drop down list. On entering the desired values the DDS channel hardware is updated using the Configure button.

chan controls

Figure 2: DDS signal generator channel controls

Other available controls are:

Pogrammable Modulus

When this is checked the programmable modulus mode of the AD9913 is used to provide an exact frequency output. For further details see the Programmable Modulus Mode section on p 14 of the Rev. D AD9913 datasheet and Direct Digital Synthesis (DDS) with a Programmable Modulus.

Active

When checked the DDS channel signal output is enabled. When un-checked the signal output is muted.

Sweep...

Displays the channel sweep settings.

Profiles...

Displays the channel profile settings.

Profiles

The channel profile settings allow the configuration and use of profile selections and direct switch mode as documented in the AD9913 datasheet (Profile Selections on p13 and Direct Switch Mode on p14 of the Rev. D AD9913 datasheet).

chan profiles

Figure 3: Channel profiles

Sweeping

The channel sweep settings allows the configuration of simple frequency and phase sweeps as documented in the AD9913 datasheet (Linear Sweep Mode section on pages 14 to 16 of the Rev. D AD9913 datasheet).

sweeping

Figure 4: Sweeping

Software Control

Example rpyc client for the DDS signal generator. Before running the example code below the signal generator app should be started, see Usage.

If the app is run 'headless' (by specifying the '--nogui' command line option) then the control serial device must also be specified via the command line. By default the ddsgen service will listen on port 18866 of the localhost interface. This may be changed by using the -P and -A command line options when starting the app.

>>> import rpyc
>>> from rfblocks import ad9913

>>> ddsgen = rpyc.connect("127.0.0.1", 18866)
>>> ddsgen.root.initialize()
>>> dds1 = ddsgen.root.dds_controllers['Chan 1']
>>> dds2 = ddsgen.root.dds_controllers['Chan 2']

>>> dds2.state = ad9913.POWER_UP
>>> dds2.freq = 25.0
>>> dds2.dbm = dds2.dbm_range[1]
>>> ddsgen.root.configure('Chan 2')

>>> dds1.state = ad9913.POWER_UP
>>> dds1.freq = 25.0
>>> dds1.dbm = dds1.dbm_range[1]
>>> ddsgen.root.configure('Chan 1')
>>> dds2._ad9913.cfr1 = ad9913.DEFAULT_CFR1
>>> dds2.sweep_start = 20.0
>>> dds2.sweep_end = 30.0
>>> dds2.sweep_type = ad9913.SweepType.FREQUENCY
>>> dds2.sweep_ramp_type = ad9913.SweepRampType.RAMP_BIDIR
>>> dds2.sweep_rising_step = 0.001
>>> dds2.sweep_falling_step = 0.001
>>> dds2.sweep_rising_rate = 256
>>> dds2.sweep_falling_rate = 256
>>> ddsgen.root.configure_sweep('Chan 2')
>>> ddsgen.root.start_sweep('Chan 2')
>>> dds2.direct_switch_enabled = True
>>> dds2.profile_type = ad9913.SweepType.FREQUENCY
>>> dds2.set_profile_freq(0, 25.0)
>>> dds2.set_profile_freq(1, 35.0)
>>> ddsgen.root.update_profile('Chan 2', 0)
>>> ddsgen.root.update_profile('Chan 2', 1)
>>> dds2.selected_profile = 0
>>> ddsgen.root.configure_profile('Chan 2')
>>> dds2.selected_profile = 1
>>> ddsgen.root.configure_profile('Chan 2')
>>> dds2.selected_profile = 0
>>> ddsgen.root.configure_profile('Chan 2')
>>> dds2.direct_switch_enabled = False
>>> dds2.state = ad9913.POWER_UP
>>> dds2.freq = 20.0
>>> dds2.level = 512
>>> ddsgen.root.set_pmod('Chan 2', True)
>>> ddsgen.root.configure('Chan 2')

Low phase noise DDSGen service API

def initialize(self) -> None:
    """Initialize the signal generator hardware and software.

    >>> import rpyc
    >>> ddsgen = rpyc.connect("127.0.0.1", 18866)
    >>> ddsgen.root.initialize()

    """

@property
def dds_controllers(self) -> Dict[str, AD9913Controller]:
    """A dictionary containing the signal generator DDS controllers.

    The controllers are instances of AD9913_Controller keyed using
    the channel identifiers 'Chan 1' and 'Chan 2' respectively.

    >>> import rpyc
    >>> ddsgen = rpyc.connect("127.0.0.1", 18866)
    >>> ddsgen.root.initialize()
    >>> dds1 = ddsgen.root.dds_controllers['Chan 1']
    >>> dds1
    <rfblocks.ad9913_controller.AD9913Controller object at 0x126f8b4c0>
    >>> dds2 = ddsgen.root.dds_controllers['Chan 2']

    """

@property
def clk_controller(self) -> HMC833Controller:
    """The controller for the DDS reference clock.

    An instance of HMC833Controller.

    >>> import rpyc
    >>> ddsgen = rpyc.connect("127.0.0.1", 18866)
    >>> ddsgen.root.initialize()
    >>> refclk = ddsgen.root.clk_controller

    """

def configure(self, ctl_id: str) -> None:
    """Configure AD9913 DDS hardware registers for a specified channel.

    :param ctl_id: A channel id.  This will be one of "Chan 1" or "Chan 2".
    :type ctl_id: str

    >>> import rpyc
    >>> ddsgen = rpyc.connect("127.0.0.1", 18866)
    >>> ddsgen.root.initialize()
    >>> dds1 = ddsgen.root.dds_controllers['Chan 1']
    >>> dds1.freq = 25.0
    >>> dds1.dbm = -10.0
    >>> ddsgen.root.configure('Chan 1')
    """

def configure_sweep(self, ctl_id: str) -> None:
    """Configure AD9913 DDS sweep registers for a specified channel.

    :param ctl_id: A channel id.  This will be one of "Chan 1" or "Chan 2".
    :type ctl_id: str

    >>> import rpyc
    >>> ddsgen = rpyc.connect("127.0.0.1", 18862)
    >>> ddsgen.root.initialize()
    >>> dds1 = ddsgen.root.dds_controllers['Chan 1']

    >>> dds1._ad9913.cfr1 = ad9913.DEFAULT_CFR1
    >>> dds1.sweep_start = 20.0
    >>> dds1.sweep_end = 30.0
    >>> dds1.sweep_type = ad9913.SweepType.FREQUENCY
    >>> dds1.sweep_ramp_type = ad9913.SweepRampType.RAMP_BIDIR
    >>> dds1.sweep_rising_step = 0.001
    >>> dds1.sweep_falling_step = 0.001
    >>> dds1.sweep_rising_rate = 256
    >>> dds1.sweep_falling_rate = 256
    >>> ddsgen.root.configure_sweep('Chan 1')
    >>> ddsgen.root.start_sweep('Chan 1')
    """

def update_profile(self, ctl_id: str, profnum: int) -> None:
    """Update AD9913 DDS profile registers for a specified channel and profile.

    :param ctl_id: A channel id.  This will be one of "Chan 1" or "Chan 2".
    :type ctl_id: str
    :param profnum: The AD9913 profile register to update.
    :type profnum: An integer in the range 0 to 7.

    >>> import rpyc
    >>> ddsgen = rpyc.connect("127.0.0.1", 18862)
    >>> ddsgen.root.initialize()
    >>> dds1 = ddsgen.root.dds_controllers['Chan 1']

    >>> dds1.direct_switch_enabled = True
    >>> dds1.profile_type = ad9913.SweepType.FREQUENCY
    >>> dds1.set_profile_freq(0, 25.0)
    >>> dds1.set_profile_freq(1, 35.0)
    >>> ddsgen.root.update_profile('Chan 1', 0)
    >>> ddsgen.root.update_profile('Chan 1', 1)
    """

def configure_profile(self, ctl_id: str) -> None:
    """Configure the currently set profile parameters in DDS hardware.

    This updates the DDS output to reflect the selected profile.

    :param ctl_id: A channel id.  This will be one of "Chan 1" or "Chan 2".
    :type ctl_id: str

    >>> import rpyc
    >>> ddsgen = rpyc.connect("127.0.0.1", 18862)
    >>> ddsgen.root.initialize()
    >>> dds1 = ddsgen.root.dds_controllers['Chan 1']

    >>> dds1.direct_switch_enabled = True
    >>> dds1.profile_type = ad9913.SweepType.FREQUENCY
    >>> dds1.set_profile_freq(0, 25.0)
    >>> dds1.set_profile_freq(1, 35.0)
    >>> ddsgen.root.update_profile('Chan 1', 0)
    >>> ddsgen.root.update_profile('Chan 1', 1)
    >>> dds1.selected_profile = 0
    >>> ddsgen.root.configure_profile('Chan 1')
    >>> dds1.selected_profile = 1
    >>> ddsgen.root.configure_profile('Chan 1')
    >>> dds1.selected_profile = 0
    >>> ddsgen.root.configure_profile('Chan 1')
    >>> dds1.direct_switch_enabled = False
    """

def start_sweep(self, ctl_id: str) -> None:
    """Start an AD9913 sweep operation for a specified channel.

    :param ctl_id: A channel id.  This will be one of "Chan 1" or "Chan 2".
    :type ctl_id: str
    """

def stop_sweep(self, ctl_id: str) -> None:
    """Stop an AD9913 sweep operation.

    :param ctl_id: A channel id.  This will be one of "Chan 1" or "Chan 2".
    :type ctl_id: str
    """

def set_pmod(self, ctl_id: str, state: bool) -> None:
    """Set the DDS programmable modulus state.

    :param ctl_id: A channel id.  This will be one of "Chan 1" or "Chan 2".
    :type ctl_id: str
    :param state: Set to True in order to enable progammable modulus.
        False to disable it.
    :type state: bool

    Note that :py:meth:`configure` must be invoked in order to
    update the DDS hardware
    """

Design Notes

Schematic design

Figure 5: DDS generator schematic design.

Assembly

DDSGen top

Figure 6a: Top side of assembly.

DDSGen bottom

Figure 6b: Reverse side of assembly.