A Low Phase Noise DDS Signal Generator
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.
or
The app may also be started from the app launcher if this is configured and running.
Once running the app is used as follows:
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.
Initialize the signal generator hardware by selecting the
Initialize
UI button.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.The DDS channels can now be configured using the relevant Channel controls.
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.
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).
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).
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 """