AD9913Controller

class rfblocks.ad9913_controller.AD9913Controller(controller_id: str, device: ad9913, config: Dict | None = None)[source]

Higher level control for the AD9913 DDS.

Documentation for the AD9913 DDS signal source rfblocks module can be found here: An AD9913 DDS Signal Source.

Parameters:
  • controller_id (str) – The controller name.

  • device (rfblocks.ad9913) – An instance of rfblocks.ad9913.

  • config (Optional[Dict]) – Initial configuration for the AD9913 DDS board. If this is None the default configuration will be used. See AD9913Controller.DEFAULT_DDS_CONFIG for a brief description of the structure for config.

AD9913Controller maintains the state configuration for an AD9913 DDS board. The following signals are defined:

  • state_changed(int)

  • freq_changed(float)

  • phase_changed(float)

  • level_changed(float)

  • pmod_changed(bool)

  • selected_profile_changed(int)

  • profile_type_changed(ad9913.SweepType)

  • sweep_start_changed(float)

  • sweep_end_changed(float)

  • sweep_type_changed(ad9913.SweepType)

  • ramp_type_changed(ad9913.SweepRampType)

  • rising_step_changed(float)

  • falling_step_changed(float)

  • rising_rate_changed(float)

  • falling_rate_changed(float)

  • profile_freq_changed(int, float)

  • profile_phase_changed(int, float)

An example of creating a DDSChan instance:

DEFAULT_DDS_FREQ = 10.0
DEFAULT_DDS_PHASE = 0.0
DEFAULT_DDS_LEVEL = 512

DEFAULT_DDS_CONFIG: Dict = {
    'freq':      DEFAULT_DDS_FREQ,
    'ph':        DEFAULT_DDS_PHASE,
    'lvl':       DEFAULT_DDS_LEVEL,
    'lvl_units': DDSChan.LVL_UNITS.DBM,
    'pmod':      False,
    'state':     ad9913.POWER_UP,
    'sweep': {
        'type':     ad9913.SweepType.FREQUENCY,
        'start':    0.5,
        'end':      5.0,
        'ramp':     ad9913.SweepRampType.SWEEP_OFF,
        'delta':    [0.1, 0.1],   # MHz per step
        'rate':     [10.0, 10.0], # microsecs per step
        'dwell':    False,
        'trigsrc':  ad9913.SweepTriggerSource.REGISTER,
        'trigtype': ad9913.SweepTriggerType.EDGE_TRIGGER
    },
    'profiles': [[0.5, 0.0], [1.0, 0.0], [2.0, 0.0],
                 [5.0, 0.0], [10.0, 0.0], [20.0, 0.0],
                 [50.0, 0.0], [100.0, 0.0]],
    'selected_profile': 0,
    'profile_type': ad9913.SweepType.FREQUENCY
}

DDSCH1_HWCONF: Dict = {'cs': 'C6', 'io_update': 'B6',
                       'reset': 'C4',
                       'ps0': 'D4', 'ps1': 'D5', 'ps2': 'D6',
                       'board_model': '27dB-RF'}

device = ad9913(**DDSCH1_HWCONF)

ch1_ctl = AD9913Controller('dds1', device, DEFAULT_DDS_CONFIG)
configure(ser: Serial) None[source]

Set the DDS hardware ‘base’ configuration.

Parameters:

ser (serial.Serial) –

This will configure the DDS hardware for the following:

  • Output frequency

  • Phase offset

  • Output level

  • Active state - powered on or off

If the programmable modulus flag is set to True, the controller will also attempt to set the configured output frequency precisely.

configure_profile(ser: Serial) None[source]

Update the AD9913 registers to use the currently selected profile.

configure_sweep(ser: Serial) None[source]

Update the DDS hardware registers with the current sweep parameters.

property dbm: float

The DDS output level in dBm.

This emits the level_changed(float) signal if the level value is changed.

Type:

float

Note that configure() must be invoked in order to update the DDS hardware

>>> dds = ad9913('d1', 'd2', sysclk=250.0, board_model="20dB")
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.millivolts = 300
>>> f'{ctl.dbm:.2f}'
'2.55'
>>> ctl.dbm = -10.0
>>> ctl.level
118
property dbm_range: List[float]

Return the range of the dbm (power) setting.

The range is returned as a two element list: [min_dbm, max_dbm]

dbm_to_level(d) float[source]

Return the DAC code for a given output power (in dBm).

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.freq = 40.0
>>> f'{ctl.level_to_dbm(100):.2f}'
'-12.35'
>>> f'{ctl.level_to_dbm(512):.2f}'
'1.56'
>>> f'{ctl.dbm_to_level(1.56):.2f}'
'512.00'
>>> f'{ctl.dbm_to_level(-12.35):.2f}'
'100.00'
property direct_switch_enabled: bool

Enable or disable direct switch mode.

Setting direct switch mode to True enables profile switching. Profile switching may then be done using the internal mode, for example:

dds_ctl.direct_switch_enabled = True
dds_ctl.profile_type = ad9913.SweepType.FREQUENCY
dds_ctl.selected_profile = 4
dds_ctl.configure_profile(ser)

Alternatively, profile switching can be achieved using the profile select pins, for example:

dds_ctl.direct_switch_enabled = True
dds_ctl.profile_type = ad9913.SweepType.FREQUENCY
dds_ctl._ad9913.set_profile_control(
    ad9913.ProfileControl.PINS)
cmd = 'M{},{:02X},{:02X}:'.format(dds.ps_port, dds.ps_mask, 4)
write_cmd(ser, cmd)
dump_config() Dict[source]

Return the current configuration for this channel.

Returns:

A dictionary containing the current DDS channel

configuration:

freq:

channel frequency

ph:

phase offset

lvl:

channel output level (in DAC units)

pmod:

programmable modulus mode. True for active.

state:

channel state (active or powered down)

sweep:

sweep information

profiles:

channel profile configuration

selected_profile:

currently selected profile

profile_type:

frequency or phase

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> from pprint import pprint as pp
>>> pp(ctl.dump_config())
{'freq': 10.0,
 'lvl': 512,
 'ph': 0.0,
 'pmod': False,
 'profile_type': <SweepType.FREQUENCY: 0>,
 'profiles': [[0.5, 0.0],
              [1.0, 0.0],
              [2.0, 0.0],
              [5.0, 0.0],
              [10.0, 0.0],
              [20.0, 0.0],
              [50.0, 0.0],
              [100.0, 0.0]],
 'selected_profile': 0,
 'state': 1,
 'sweep': {'delta': [0.1, 0.1],
           'dwell': False,
           'end': 5.0,
           'ramp': <SweepRampType.SWEEP_OFF: 0>,
           'rate': [10.0, 10.0],
           'start': 0.5,
           'trigsrc': <SweepTriggerSource.REGISTER: 134217728>,
           'trigtype': <SweepTriggerType.EDGE_TRIGGER: 0>,
           'type': <SweepType.FREQUENCY: 0>}}
property freq: float

The DDS output frequency in MHz.

The freq_changed(float) signal is emitted if the freq value changes.

Type:

float

Note that configure() must be invoked in order to update the DDS hardware

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.freq
10.0
>>> ctl.freq = 25.0
>>> ctl.freq
25.0
initialize(ser: Serial) None[source]

Initialize the DDS board.

property intercept: float

Return the intercept of the DAC code/output mV relation at the currently set output frequency.

Type:

float

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> dds.freq = 20.0
>>> f'{ctl.intercept:.2f}'
'1.78'
property label

Returns a label string.

This is generally used when constructing a user interface for the controller. See, for example, qtrfblocks.DDSChan. If no label has been specified when the controller instance was created the controller_id will be used.

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.label
'dds_ctl'
>>> ctl2 = AD9913Controller(
...     'dds_ctl', dds,
...     { 'label': 'Chan X', **AD9913Controller.DEFAULT_DDS_CONFIG })
>>> ctl2.label
'Chan X'
property level: float

The DDS output level in DDS DAC units (DAC code).

The AD9913 uses a 10-bit DAC so the level range is 0 to 1023. This emits the level_changed(float) signal if the level value changes.

Type:

float

Note that configure() must be invoked in order to update the DDS hardware

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.level
512
>>> ctl.level = 250
>>> ctl.level
250
property level_range: List[float]

Return the range of the level setting.

The range is returned as a two element list: [min_level, max_level]

level_to_dbm(lvl) float[source]

Return the output power (in dBm) for a given DAC code.

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.freq = 40.0
>>> f'{ctl.level_to_dbm(100):.2f}'
'-12.35'
>>> f'{ctl.level_to_dbm(512):.2f}'
'1.56'
level_to_millivolts(lvl)[source]

Return RMS output voltage for a given DAC code.

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.freq = 20.0
>>> f'{ctl.level_to_millivolts(100.0):.2f}'
'56.91'
>>> ctl.freq = 40.0
>>> f'{ctl.level_to_millivolts(100.0):.2f}'
'53.97'
load_config(config: Dict) None[source]

Set the current configuration for this channel.

Parameters:

config (Dict) – A dictionary containing the channel configuration to be set.

Note that in order to update the DDS channel hardware configure() should be called.

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> import copy
>>> config = copy.deepcopy(AD9913Controller.DEFAULT_DDS_CONFIG)
>>> config['freq'] = 45.0
>>> config['lvl'] = 100
>>> config['sweep']['type'] = ad9913.SweepType.PHASE
>>> config['profiles'][0][0] = 10.0
>>> ctl.load_config(config)
>>> from pprint import pprint as pp
>>> pp(ctl.dump_config())
{'freq': 45.0,
 'lvl': 100,
 'ph': 0.0,
 'pmod': False,
 'profile_type': <SweepType.FREQUENCY: 0>,
 'profiles': [[10.0, 0.0],
              [1.0, 0.0],
              [2.0, 0.0],
              [5.0, 0.0],
              [10.0, 0.0],
              [20.0, 0.0],
              [50.0, 0.0],
              [100.0, 0.0]],
 'selected_profile': 0,
 'state': 1,
 'sweep': {'delta': [0.1, 0.1],
           'dwell': False,
           'end': 5.0,
           'ramp': <SweepRampType.SWEEP_OFF: 0>,
           'rate': [10.0, 10.0],
           'start': 0.5,
           'trigsrc': <SweepTriggerSource.REGISTER: 134217728>,
           'trigtype': <SweepTriggerType.EDGE_TRIGGER: 0>,
           'type': <SweepType.PHASE: 4096>}}
property millivolts: float

The DDS output level in millivolts.

This emits the level_changed(float) signal if the level value is changed.

Type:

float

Note that configure() must be invoked in order to update the DDS hardware

>>> dds = ad9913('d1', 'd2', board_model="20dB")
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.level = 256
>>> f'{ctl.millivolts:.2f}'
'151.05'
>>> ctl.millivolts = 300
>>> ctl.level
511
property millivolts_range: List[float]

Return the range of the millivolts setting.

The range is returned as a two element list: [min_millivolts, max_millivolts]

millivolts_to_level(mv) int[source]

Return the DAC code which produces a given RMS output voltage.

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.freq = 40.0
>>> f'{ctl.level_to_millivolts(100.0):.2f}'
'53.97'
>>> f'{ctl.millivolts_to_level(53.97):.2f}'
'100.00'
property phase: float

The DDS phase offset in degrees.

The phase_changed(float) signal is emitted if the phase value changes.

Type:

float

Note that configure() must be invoked in order to update the DDS hardware

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> ctl.phase
0.0
>>> ctl.phase = 90.0
>>> ctl.phase
90.0
property pmod: bool

The current DDS programmable modulus state.

This emits the pmod_changed(bool) signal if the pmod value changes.

Type:

bool

This will be True if the programmable modulus facility is active and False otherwise.

profile_freq(profnum: int) float[source]

The profile frequency (in MHz) for the specified profile.

Parameters:

profnum (An integer in the range 0 to 7.) – The target profile number.

Raise:

IndexError if an invalid profile is specified.

profile_phase(profnum: int) float[source]

The profile phase (in degrees) for the specified profile.

Parameters:

profnum (An integer in the range 0 to 7.) – The target profile number.

Raise:

IndexError if an invalid profile is specified.

property profile_type: SweepType

The current profile type.

This emits a profile_type_changed(ad9913.SweepType) signal if the profile_type value changes.

Type:

rfblocks.ad9913.SweepType

property profiles

Returns a list of the currently configured profiles.

The list contains a sublist for each of the eight DDS profiles. Each sublist contains the profile frequency and phase.

Type:

List

>>> from pprint import pprint as pp
>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> pp(ctl.profiles)
[[0.5, 0.0],
 [1.0, 0.0],
 [2.0, 0.0],
 [5.0, 0.0],
 [10.0, 0.0],
 [20.0, 0.0],
 [50.0, 0.0],
 [100.0, 0.0]]
property selected_profile: int

The currently selected DDS profile (0 to 7).

This emits the selected_profile_changed(int) signal if the selected_profile value changes.

Type:

int

set_cal_data(cal_data: Dict[float, List[float]]) None[source]

Set the calibration data for the board associated with the controller.

Parameters:

cal_data (Dict[float, List[float]]) – The new calibration data to be used.

The format for the data is:

{
    freq-0: [slope-0, intercept-0],
    freq-1: [slope-1, intercept-1],
    ...
    freq-n [slope-n, intercept-n]
}

where the frequencies run from 0 to 100MHz.

The slope and intercept data are derived from calibration measurements using the procedure documented in Calibration Procedure.

Note that calibration measurements are made down to 5MHz so the values measured at 5MHz are also used for 0MHz. This allows slope and intercept numbers to be interpolated for f < 5MHz.

This will override any default calibration data as loaded during the initialization of the controller instance.

set_profile_freq(profnum: int, f: float) None[source]

Set the frequency of the specified profile.

Parameters:
  • profnum (An integer in the range 0 to 7.) – The target profile number.

  • f (float) – The frequency in MHz.

Raise:

IndexError if an invalid profile is specified.

set_profile_phase(profnum: int, p: float) None[source]

Set the phase of the specified profile.

Parameters:
  • profnum (An integer in the range 0 to 7.) – The target profile number.

  • p (float) – The phase in degrees.

Raise:

IndexError if an invalid profile is specified.

property slope: float

Return the slope of the DAC code/output mV relation at the currently set output frequency.

Type:

float

>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> dds.freq = 20.0
>>> f'{ctl.slope:.2f}'
'0.58'
start_sweep(ser: Serial) None[source]

Start the DDS sweep operation.

property state: int

The state of the DDS channel.

This should be either ad9913.POWER_UP or ad9913.POWER_DOWN. The state_changed(int) signal is emitted if the state changes.

Type:

int

Note that configure() must be invoked in order to update the DDS hardware

stop_sweep(ser: Serial) None[source]

Stop the DDS sweep operation.

property sweep_config

Returns a dict containing the current DDS sweep parameters.

>>> from pprint import pprint as pp
>>> dds = ad9913('d1', 'd2')
>>> ctl = AD9913Controller('dds_ctl', dds)
>>> pp(ctl.sweep_config)
{'delta': [0.1, 0.1],
 'dwell': False,
 'end': 5.0,
 'ramp': <SweepRampType.SWEEP_OFF: 0>,
 'rate': [10.0, 10.0],
 'start': 0.5,
 'trigsrc': <SweepTriggerSource.REGISTER: 134217728>,
 'trigtype': <SweepTriggerType.EDGE_TRIGGER: 0>,
 'type': <SweepType.FREQUENCY: 0>}
property sweep_end: float

The sweep end value.

This will be the frequency if sweep_type is ad9913.SweepType.FREQUENCY or the phase if sweep_type is ad9913.SweepType.PHASE.

This will emit a sweep_end_changed(float) signal if the sweep_end value changes.

property sweep_falling_rate: float

The current falling rate value in seconds.

Type:

float

A falling_rate_changed(float) signal is emitted if the sweep_falling_rate value changes.

Note

Note that there is granularity with the sweep ramp rates. This will depend on the system clock. With the default system clock of 250 MHz the smallest possible step is 4 nS. This will also be the smallest increment between step sizes.

Since the ramp rate registers are 16 bits, the largest possible step size (with a 250 MHz system clock) will be 262 uS.

property sweep_falling_step: float

The current sweep falling step value.

This will be a frequency value if sweep_type is ad9913.SweepType.FREQUENCY or a phase value if sweep_type is ad9913.SweepType.PHASE.

Type:

float

A falling_step_changed(float) signal is emitted if the falling_step value changes.

property sweep_ramp_type: SweepRampType

The current sweep ramp type.

This is one of values defined in rfblocks.ad9913.SweepRampType.

A ramp_type_changed(ad9913.SweepRampType) signal is emitted if the sweep_ramp_type changes.

property sweep_rising_rate: float

The current rising rate value in seconds.

Type:

float

A rising_rate_changed(float) signal is emitted if the sweep_rising_rate value changes.

Note

Note that there is granularity with the sweep ramp rates. This will depend on the system clock. With the default system clock of 250 MHz the smallest possible step is 4 nS. This will also be the smallest increment between step sizes.

Since the ramp rate registers are 16 bits, the largest possible step size (with a 250 MHz system clock) will be 262 uS.

property sweep_rising_step: float

The current sweep rising step value.

This will be a frequency value if sweep_type is ad9913.SweepType.FREQUENCY or a phase value if sweep_type is ad9913.SweepType.PHASE.

Type:

float

A rising_step_changed(float) signal is emitted if the rising_step value changes.

property sweep_start: float

The sweep start value.

This will be the frequency if sweep_type is ad9913.SweepType.FREQUENCY or the phase if sweep_type is ad9913.SweepType.PHASE.

This will emit a sweep_start_changed(float) signal if the sweep_start value changes.

property sweep_type: SweepType

The current sweep type.

This is one of values defined in rfblocks.ad9913.SweepType.

A sweep_type_changed(ad9913.SweepType) signal is emitted if the sweep_type changes.

update_profile(ser: Serial, profnum: int) None[source]

Update the AD9913 profile registers for the specified profile.

Parameters:

profnum (int) – The profile registers to update. This should be in the range 0 to 7.

Raise:

IndexError if an invalid profile number is specified.