Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control Pounder through MQTT #610

Closed
wants to merge 73 commits into from
Closed

Conversation

occheung
Copy link

Description

This PR implements control of devices on Pounder through MQTT.

These parameters are configurable through MQTT (topic prefix: <prefix>/settings/pdh/ch/<n>).

pub struct PDHChannel {
    pub osc_frequency: f32,     // Frequency of the IN/OUT DDS pair
    pub out_phase: f32,         // Phase offset of the OUT DDS channel
    pub in_phase_offset: f32,   // Phase offset of the IN DDS channel relative to the OUT DDS channel
    pub amplitude: f32,         // Amplitude of the OUT DDS channel
    pub in_attenuation: f32,    // Attenuation of the signal injected to INx connector
    pub out_attenuation: f32,   // Attenuation of the OUT DDS channel
    pub enabled: bool,          // Amplitude of the IN DDS channel (0.0 or 1.0)
}

Temperature reading from Pounder (LM75) & input powers (after attenuators) are reported in telemetry.

I also tried reporting power level through the LED, but I got issues like AttRstN being stuck at low voltage. I have another branch in my fork that has working LED using the mcp23017 library.

@occheung
Copy link
Author

occheung commented Sep 1, 2022

Added the same Pounder telemetry to lockin variant.

@jordens What are the plans of Pounder for lockin?

@occheung occheung changed the title [WIP]: Control Pounder through MQTT Control Pounder through MQTT Sep 1, 2022
@occheung occheung marked this pull request as ready for review September 1, 2022 02:20
src/bin/dual-iir.rs Outdated Show resolved Hide resolved
ad9959/src/lib.rs Outdated Show resolved Hide resolved
ad9959/src/lib.rs Outdated Show resolved Hide resolved
ad9959/src/lib.rs Outdated Show resolved Hide resolved
ad9959/src/lib.rs Outdated Show resolved Hide resolved
src/bin/dual-iir.rs Outdated Show resolved Hide resolved
ad9959/src/lib.rs Outdated Show resolved Hide resolved
src/bin/dual-iir.rs Outdated Show resolved Hide resolved
src/net/telemetry.rs Outdated Show resolved Hide resolved
src/bin/dual-iir.rs Outdated Show resolved Hide resolved
pub ch: [PDHChannel; 2],
}

impl Default for PDHLockGeneratorConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Derive it.

@jordens
Copy link
Member

jordens commented Sep 1, 2022

There are no plans for Pounder and lockin.

Copy link
Member

@ryan-summers ryan-summers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking much better! Most of my comments are related to documentation and clarifying magic numbers in various files.

There's quite a few things Robert pointed out that still need clean up as well

// TODO: Update / disable any enabled channels?
let mut fr1: [u8; 3] = [0, 0, 0];
self.read(Register::FR1, &mut fr1)?;
fr1[0].set_bits(2..=6, multiplier);

let vco_range = frequency > 255e6;
let vco_range = frequency > 200e6;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the 200e6 to a const variable with a name at the top of the file. Also, can you please document where this limit is coming from? It's not clear to me (nor can I remember why the original 255e6 was selected)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documented. Its selection is somewhat arbitrary.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I talk about this below, but we should just use the min of the high VCO gain config or max of the low VCO gain config constants instead of this arbitrary value.

ad9959/src/lib.rs Outdated Show resolved Hide resolved
ad9959/src/lib.rs Outdated Show resolved Hide resolved
ad9959/src/lib.rs Outdated Show resolved Hide resolved
ad9959/src/lib.rs Outdated Show resolved Hide resolved
src/hardware/pounder/mod.rs Outdated Show resolved Hide resolved
src/hardware/setup.rs Outdated Show resolved Hide resolved
src/hardware/setup.rs Outdated Show resolved Hide resolved
Comment on lines 196 to 205
if self.pounder.get_attenuation(pounder_channel).unwrap()
!= channel_config.attenuation
{
self.pounder
.set_attenuation(
pounder_channel,
channel_config.attenuation,
)
.unwrap();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any downside to unconditionally writing the attenuation (even if it's redundant). Is there any transient that occurs when we latch the attenuation? If not, I'd simplify this and just always write attenuation updates.

Copy link
Author

@occheung occheung Jan 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then should we just have a function that sets attenuation of all channels in 1 go? We are currently repeating the SPI write (read + update) 4 times when only 1 write is needed.
Then we don't need to think about preserving other channel's attenuation with a read sequence, since we are writing unconditionally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It really just comes down to how often we're updating attenuation and when we do it. If we need the efficiency, then by all means write all of them in one go, but if it doesn't matter, lets leave what we have in place and open an issue for the future

src/net/telemetry.rs Outdated Show resolved Hide resolved
Copy link
Member

@ryan-summers ryan-summers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking better, but there's still a few things to clean up. Thanks for working on this. I think there's also some outstanding conversations form @jordens and I earlier as well that still need to be addressed.

Comment on lines +7 to +9
/// An arbitrary frequency that sits between the 160 MHz to 255 MHz zone, where system clock
/// frequency is not supported by both high frequency and low frequency VCO settings.
const VCO_MIN_FREQUENCY: f32 = 200e6;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't allow configuration in the 160-255MHz range based on the clocking checks below. We should remove this constant and just use either the min (high gain) or max (low gain) as a switch for the VCO gain bit.

Comment on lines +18 to +21
/// The maximum system clock frequency with low frequency range VCO.
const MAX_OUTPUT_FREQUENCY: f32 = 160e6;
/// The minimum system clock frequency with low frequency range VCO.
const MIN_OUTPUT_FREQUENCY: f32 = 100e6;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The maximum system clock frequency with low frequency range VCO.
const MAX_OUTPUT_FREQUENCY: f32 = 160e6;
/// The minimum system clock frequency with low frequency range VCO.
const MIN_OUTPUT_FREQUENCY: f32 = 100e6;
/// The system clock frequency range with low gain configured for the internal VCO.
const LOW_GAIN_VCO_RANGE: Range<f32> = core::ops::Range { start: 100e6, end: 160e6 };

Comment on lines +14 to +17
/// The maximum system clock frequency with high frequency range VCO.
const MAX_VCO_OUTPUT_FREQUENCY: f32 = 500e6;
/// The minimum system clock frequency with high frequency range VCO.
const MIN_VCO_OUTPUT_FREQUENCY: f32 = 255e6;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The maximum system clock frequency with high frequency range VCO.
const MAX_VCO_OUTPUT_FREQUENCY: f32 = 500e6;
/// The minimum system clock frequency with high frequency range VCO.
const MIN_VCO_OUTPUT_FREQUENCY: f32 = 255e6;
/// The system clock frequency range with high gain configured for the internal VCO.
const HIGH_GAIN_VCO_RANGE: Range<f32> = core::ops::Range { start: 255e6, end: 500e6 };

Comment on lines +544 to +547
// The REFCLK frequency must be at least 1 MHz with REFCLK multiplier disabled.
if multiplier == 1 && reference_clock_frequency < MIN_REFCLK_FREQUENCY {
return Err(Error::Bounds);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to the top of the function and remove the check for multiplier == 1. In reality, 1MHz is the absolute lowest allowed REFCLK, but the limit might be higher. We never should allow a lower refclk.

/// Convert amplitude into amplitude control register values.
///
/// Arguments:
/// * `amplitude` - The amplitude scaling factor of a DDS channel.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// * `amplitude` - The amplitude scaling factor of a DDS channel.
/// * `amplitude` - The normalized amplitude of a DDS channel.

Comment on lines +614 to +624
let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16;

// Enable the amplitude multiplier for the channel if required. The amplitude control has
// full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever
// full-scale is used.
let acr = if amplitude != 1.0 {
// Enable the amplitude multiplier
(amplitude_control & 0x3FF) | (1 << 12)
} else {
0
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16;
// Enable the amplitude multiplier for the channel if required. The amplitude control has
// full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever
// full-scale is used.
let acr = if amplitude != 1.0 {
// Enable the amplitude multiplier
(amplitude_control & 0x3FF) | (1 << 12)
} else {
0
};
let acr: u32 = 0u32.set_bits(0..=9, (amplitude * (1 << 10) as f32) as u16).set_bit(12, amplitude != 1.0);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -199,6 +214,7 @@ mod app {
adcs: (Adc0Input, Adc1Input),
dacs: (Dac0Output, Dac1Output),
iir_state: [[iir::Vec5<f32>; IIR_CASCADE_LENGTH]; 2],
dds_clock_state: Option<ClockConfig>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This never actually changes in the code. Why do we store it as a Resource? It seems like this should just exist within Pounder instead.

pub temperature: f32,

/// The detected RF power into IN channels
pub input_powers: [f32; 2],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub input_powers: [f32; 2],
pub input_power: [f32; 2],

It's already plural because it's an array, naming it input_powers implies each channel has multiple power measurements

/// overhead.
#[derive(Copy, Clone, Serialize)]
pub struct PounderTelemetry {
/// The Pounder temperature in degree Celsius
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The Pounder temperature in degree Celsius
/// The Pounder temperature in degrees Celsius

Comment on lines +146 to +224
impl PounderDevices {
pub fn update_dds(
&mut self,
settings: PounderConfig,
clocking: &mut ClockConfig,
) {
if *clocking != settings.clock {
match validate_clocking(
settings.clock.reference_clock,
settings.clock.multiplier,
) {
Ok(_frequency) => {
self.pounder
.set_ext_clk(settings.clock.external_clock)
.unwrap();

self.dds_output
.builder()
.set_system_clock(
settings.clock.reference_clock,
settings.clock.multiplier,
)
.unwrap()
.write();

*clocking = settings.clock;
}
Err(err) => {
log::error!("Invalid AD9959 clocking parameters: {:?}", err)
}
}
}

for (channel_config, pounder_channel) in settings
.in_channel
.iter()
.chain(settings.out_channel.iter())
.zip([
PounderChannel::In0,
PounderChannel::In1,
PounderChannel::Out0,
PounderChannel::Out1,
])
{
match Profile::try_from((*clocking, *channel_config)) {
Ok(dds_profile) => {
self.dds_output
.builder()
.update_channels_with_profile(
pounder_channel.into(),
dds_profile,
)
.write();

if let Err(err) = self.pounder.set_attenuation(
pounder_channel,
channel_config.attenuation,
) {
log::error!("Invalid attenuation settings: {:?}", err)
}
}
Err(err) => {
log::error!("Invalid AD9959 profile settings: {:?}", err)
}
}
}
}

pub fn get_telemetry(&mut self) -> PounderTelemetry {
PounderTelemetry {
temperature: self.pounder.lm75.read_temperature().unwrap(),
input_powers: [
self.pounder.measure_power(PounderChannel::In0).unwrap(),
self.pounder.measure_power(PounderChannel::In1).unwrap(),
],
}
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This belongs in hardware/pounder/ somewhere, not the setup utilities.

@sbourdeauducq
Copy link
Member

Closed in favor of #725

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants