ClockManager

ClockManager owns the live Clock instances for the active hardware loadout, maps each RfConfig::ClockType role to the physical clock object that serves it, and mediates all configure and read operations that flow between HardwareManager and the underlying oscillator hardware. It inherits from SettingsStorage and persists the available clock output table so that the RF configuration UI can present the correct options without querying live hardware.

ClockManager is created by HardwareManager and runs on the HardwareManager thread. HardwareManager holds it via std::unique_ptr<ClockManager> pu_clockManager and calls setClocksFromHardwareManager() to hand off the current set of Clock pointers after any hardware sync cycle. When the runtime hardware map changes, HardwareManager calls updateClockManager() to rebuild the internal clock list and re-emit routing signals. Direct calls from other threads must go through HardwareManager’s queued connections — ClockManager’s public slots are not safe to invoke directly across thread boundaries.

Primary collaborators: HardwareManager (owner, signal forwarder, and caller of setClocks / configureClocks); RfConfig (source of the RfConfig::ClockType enum used as the routing key and of the RfConfig::ClockFreq descriptor passed in clock maps); HardwareLoadout (clock assignments are part of the loadout’s RF section and are persisted per FTMW preset). The Clock base class is a HardwareObject; ClockManager does not own the Clock objects — it borrows raw pointers from HardwareManager.

Clock-routing model

Each RfConfig::ClockType role (UpLO, DownLO, AwgRef, DRClock, DigRef, ComRef) is satisfied by exactly one Clock object paired with an output index on that clock. A single Clock device may expose multiple independent outputs (for example the Valon 5009 has two independently tunable channels), so one physical device can simultaneously satisfy several distinct roles — each on a different output port.

Internally, d_clockRoles maps every currently active ClockType to the Clock that serves it. d_clockList holds the full set of Clock objects that HardwareManager has provided; entries in d_clockRoles are always a subset of that list. When configureClocks() is called, the existing role map is cleared, each entry in the incoming QHash<ClockType, ClockFreq> is resolved against d_clockList by hardware key, the matching clock’s output is registered via Clock::addRole(), and the multiplication factor is written to the output via Clock::setMultFactor(). Roles that were present before the call but absent from the new map receive a clockHardwareUpdate(type, "", -1) signal so consumers know the role is no longer active.

The multiplication factor in RfConfig::ClockFreq accounts for clocks whose oscillator output frequency differs from the logical RF frequency by a fixed multiply-or-divide relationship (e.g., a ×2 doubler stage). When the factor is less than 1.0 the operation is RfConfig::Divide; otherwise it is RfConfig::Multiply.

The available clock outputs are persisted in SettingsStorage under the BC::Key::ClockManager::hwClocks array so that the RF configuration dialog can populate its output selector without polling live hardware.

Configuration flow

The typical configure path during experiment preparation is:

  1. HardwareManager::initializeExperiment() calls ClockManager::prepareForExperiment(Experiment &exp).

  2. prepareForExperiment() extracts the clock map from exp.ftmwConfig()->d_rfConfig.getClocks() and passes it to configureClocks().

  3. configureClocks() resolves each ClockTypeClock* binding, sets the multiplication factor, calls Clock::setFrequency() on each affected output, and emits clockHardwareUpdate() for every newly assigned role.

  4. After configureClocks() returns, prepareForExperiment() writes the achieved frequencies back into exp via RfConfig::setCurrentClocks(getCurrentClocks()).

  5. When a mid-acquisition clock step requires a frequency change, MainWindow::clockPrompt calls HardwareManager::setClocks() via QMetaObject::invokeMethod; HardwareManager gates the FTMW digitizer during the transition and emits HardwareManager::allClocksReady() when all clocks have settled. That signal is connected directly to AcquisitionManager::clockSettingsComplete, which gates progression to the next acquisition step.

Frequency reads (readClockFrequency(), readActiveClocks()) forward directly to Clock::readFrequency(). Each Clock emits frequencyUpdate(ClockType, double); setupClocks() connects that signal to ClockManager::clockFrequencyUpdate, which HardwareManager then re-emits to the rest of the system.

For the end-user view of clock assignment see RF Configuration and FTMW Presets.

API Reference

class ClockManager : public QObject, public SettingsStorage

Routes RF clock operations to the correct Clock hardware objects by role.

ClockManager owns the role-to-hardware mapping for all clock sources in the active loadout. Each RfConfig::ClockType role (e.g., UpLO, DownLO, AwgRef) is served by exactly one Clock object on one of that clock’s numbered outputs. A single physical clock may serve multiple roles on different outputs simultaneously.

ClockManager is created and owned by HardwareManager (pu_clockManager) and runs on the HardwareManager thread. HardwareManager hands off the current Clock* pointers via setClocksFromHardwareManager() after each hardware sync cycle, and calls updateClockManager() whenever the set of available clocks changes. Direct invocation of ClockManager slots from other threads is not safe; all cross-thread callers must go through HardwareManager's queued connections.

See also

HardwareManager, RfConfig, Clock

Public Functions

explicit ClockManager(QObject *parent = nullptr)

Constructs a ClockManager with an empty clock list.

The manager reads its SettingsStorage state from the BC::Key::ClockManager::clockManager key. Clock objects must be supplied by HardwareManager via setClocksFromHardwareManager() before any configure or read operations are meaningful.

Parameters:

parent – Optional parent QObject.

Public Slots

void readActiveClocks()

Reads the current hardware frequency for every active clock role.

Iterates over d_clockRoles and calls Clock::readFrequency() for each role whose clock reports isConnected(). Each successful read causes the corresponding Clock to emit frequencyUpdate, which propagates through clockFrequencyUpdate.

Must be called on the HardwareManager thread.

QHash<RfConfig::ClockType, RfConfig::ClockFreq> getCurrentClocks()

Returns the current frequency descriptor for every active clock role.

Queries each entry in d_clockRoles for its hardware key, output index, multiplication factor, and live frequency, and assembles the results into a hash that can be written back into an RfConfig via RfConfig::setCurrentClocks().

Must be called on the HardwareManager thread.

Returns:

A hash mapping each active RfConfig::ClockType to its RfConfig::ClockFreq descriptor.

double setClockFrequency(RfConfig::ClockType t, double freqMHz)

Sets the frequency of the clock assigned to t.

Forwards the call to Clock::setFrequency() on the Clock* in d_clockRoles for t. Logs a warning and returns -1.0 if no clock is currently assigned to t.

Must be called on the HardwareManager thread.

Parameters:
  • t – The clock role to tune.

  • freqMHz – Desired output frequency in MHz.

Returns:

Actual achieved frequency in MHz, or -1.0 on failure.

double readClockFrequency(RfConfig::ClockType t)

Reads the current frequency of the clock assigned to t.

Forwards the call to Clock::readFrequency() on the assigned Clock*. Logs a warning and returns -1.0 if no clock is assigned to t.

Must be called on the HardwareManager thread.

Parameters:

t – The clock role to query.

Returns:

Current frequency in MHz, or -1.0 on failure.

bool configureClocks(QHash<RfConfig::ClockType, RfConfig::ClockFreq> clocks)

Applies a complete clock configuration to hardware.

Clears all existing role assignments, then for each entry in clocks: resolves the hardware key to a Clock* in d_clockList, registers the role on the correct output via Clock::addRole(), writes the multiplication factor via Clock::setMultFactor(), calls Clock::setFrequency(), and emits clockHardwareUpdate(). Roles present before the call but absent from clocks receive a clockHardwareUpdate with an empty key and output -1 to signal that they are no longer active.

Returns false (and logs an error) if a hardware key in clocks cannot be matched to a known Clock, if the requested output index is out of range, or if a setFrequency call fails.

Must be called on the HardwareManager thread.

Parameters:

clocks – Desired role-to-frequency mapping as produced by RfConfig::getClocks().

Returns:

true if all roles were configured successfully; false on the first error.

bool prepareForExperiment(Experiment &exp)

Configures clocks for an experiment and writes achieved frequencies back into the experiment object.

Returns true immediately if the experiment does not have FTMW enabled. Otherwise calls configureClocks() with the clock map from exp.ftmwConfig()->d_rfConfig.getClocks(), and on success calls RfConfig::setCurrentClocks(getCurrentClocks()) to record the achieved frequencies in exp.

Must be called on the HardwareManager thread.

Parameters:

exp – The experiment to prepare. Modified in place with the actual clock frequencies after configuration.

Returns:

true on success or if FTMW is disabled; false if configureClocks() fails.

void setClocksFromHardwareManager(const QVector<Clock*> &clocks)

Accepts a new set of Clock pointers from HardwareManager.

Replaces d_clockList with clocks (which remain owned by HardwareManager) and calls setupClocks() to rebuild the role map and re-connect frequencyUpdate signals.

Must be called on the HardwareManager thread.

Parameters:

clocks – Ordered list of Clock* objects currently in the hardware map.

void reconfigureFromRuntimeConfig()

Rebuilds the internal clock setup from the current d_clockList.

Called by HardwareManager when the hardware map changes after setClocksFromHardwareManager() has already been called. Equivalent to re-running setupClocks() on the existing pointer list.

Must be called on the HardwareManager thread.

QVector<Clock*> getClockList() const

Returns the current list of Clock pointers held by this manager.

Returns:

A copy of d_clockList as supplied by the most recent setClocksFromHardwareManager() call.

Signals

void clockFrequencyUpdate(RfConfig::ClockType type, double freqMHz)

Emitted when the frequency of a clock role changes.

Connected by setupClocks() to each Clock::frequencyUpdate signal and re-emitted by HardwareManager to the rest of the system.

Parameters:
  • type – The logical clock role whose frequency has changed.

  • freqMHz – The new frequency in MHz.

void clockHardwareUpdate(RfConfig::ClockType type, const QString &hwKey, int output)

Emitted when the hardware binding for a clock role changes.

Fires once per role when configureClocks() assigns or clears a role. If a role is removed (e.g., the clock map no longer includes it), this signal is emitted with an empty hwKey and output equal to -1.

Parameters:
  • type – The logical clock role that was reassigned.

  • hwKey – Hardware key of the newly assigned Clock, or an empty string if the role is now unassigned.

  • output – Output port index on the Clock device, or -1 if the role is unassigned.

Private Functions

void setupClocks()

Populates the SettingsStorage output table and re-connects signals.

Iterates d_clockList, writes each clock’s outputs to the BC::Key::ClockManager::hwClocks array in SettingsStorage, emits clockHardwareUpdate for roles that are already assigned on each clock, and connects Clock::frequencyUpdate to ClockManager::clockFrequencyUpdate. Called from setClocksFromHardwareManager() and reconfigureFromRuntimeConfig().

Private Members

QVector<Clock*> d_clockList

Ordered list of all Clock objects in the active loadout.

Pointers are borrowed from HardwareManager; lifetime is managed by HardwareManager. Updated by setClocksFromHardwareManager().

QHash<RfConfig::ClockType, Clock*> d_clockRoles

Maps each active RfConfig::ClockType role to the Clock* that serves it.

Rebuilt by configureClocks(). A role is present in this hash only when a clock has been successfully assigned to it. Multiple roles may map to the same Clock* (each on a different output index).