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 - finished #725

Closed
wants to merge 77 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
747372d
dds profile: expose dds clock reconfig
occheung Aug 30, 2022
4839914
ad9959: make channel enum start from ZERO
occheung Aug 30, 2022
bd304fe
update pounder::Channel to ad9959::Channel conversion
occheung Aug 30, 2022
7994998
pounder mqtt: add topics
occheung Aug 30, 2022
6d05f64
dual-iir: handle mqtt pounder topics
occheung Aug 30, 2022
651331d
ad9959: enlarge serialzier buffer
occheung Aug 30, 2022
0d4f724
telemetry: add pounder temperature & input powers
occheung Aug 30, 2022
f6b2ca5
cargo fmt
occheung Aug 30, 2022
6a9d992
add pounder telemetry to lockin
occheung Sep 1, 2022
be9dd69
fix struct path in doc
occheung Sep 1, 2022
1094adb
wrap pdh config in option
occheung Sep 1, 2022
ad15977
add stricter frequency checks
occheung Sep 1, 2022
ee0d7ec
fix comment regarding assumption of FR1 value
occheung Sep 1, 2022
a85bf18
move vco_range threshold
occheung Sep 1, 2022
ea3bb76
only reconfig dds clock when requested
occheung Sep 1, 2022
c49691f
use ad9959 idea of amp-to-acr conversion for serializer
occheung Sep 1, 2022
6d00ca4
replace hex with shifts in phase-to-pow
occheung Sep 1, 2022
afaa0af
remove unnecessary wrapping shift
occheung Sep 1, 2022
e06f192
limit dds frequency within the first Nyquist zone
occheung Sep 1, 2022
6a9043a
drop f_foo in favor of foo_frequency
occheung Sep 1, 2022
5cd6e15
update telemetry structure
occheung Sep 1, 2022
e9139f0
cargo fmt
occheung Sep 1, 2022
486b1e6
lockin: update telemetry handling
occheung Sep 1, 2022
88a2b6a
dual-iir: reshuffle telemetry write order
occheung Sep 1, 2022
1e2c84b
Omit type annotation for FR1 bytes
occheung Sep 1, 2022
ad1d9ce
lockin: pounder devices are local resources
occheung Sep 2, 2022
173ad18
dual-iir: refrain from destructuring Pounder from setup
occheung Sep 2, 2022
bd2f29b
fix setter function naming
occheung Sep 2, 2022
de3b2f0
fix system clock update docs in dds builder
occheung Sep 2, 2022
148fdcd
apply threshold patch in ad9959 frequency config
occheung Sep 2, 2022
9c58bd7
reuse ad9959 conversion & validation code
occheung Sep 2, 2022
c2809d2
de-pdh the settings
occheung Sep 2, 2022
2257d7a
fix get attenuation method
occheung Sep 2, 2022
4044c30
revert buffer size enlargement
occheung Sep 2, 2022
b7c8ebb
attenuators: fix space
occheung Sep 2, 2022
7d6e191
ad9959: fix doc space
occheung Sep 2, 2022
87dfe56
revert "ad9959: make channel enum start from ZERO"
occheung Dec 14, 2022
994826f
ad9959: make helpers global
occheung Dec 15, 2022
11016ba
profile: rename & impl fram trait
occheung Dec 15, 2022
9df9137
clocking: unify wrapper name
occheung Dec 15, 2022
0ad435e
clocking: fix docs
occheung Dec 15, 2022
28b0d4c
ad9959: remove profile specific wrapper
occheung Dec 15, 2022
f354f5a
ad9959: fix set_amplitude
occheung Dec 15, 2022
e9f48e3
move fine-grained control to setup
occheung Dec 15, 2022
ef2fb4d
net settings: restore default
occheung Dec 15, 2022
add5059
telemetry: restore cpu temp handling
occheung Dec 15, 2022
34cdc15
clocking: remove dds prefixes
occheung Dec 15, 2022
8e96d43
dds: set default frequency as 0
occheung Dec 15, 2022
586b438
fix imports
occheung Dec 15, 2022
578407a
lockin: remove pounder
occheung Jan 10, 2023
d2db0db
use updated miniconf interface
occheung Jan 10, 2023
481a89b
cargo fmt
occheung Jan 10, 2023
5346a09
mqtt: use miniconf primitive for defer
occheung Jan 10, 2023
1f62fa4
settings: replace field instead
occheung Jan 20, 2023
bb91707
ad9959: clean up frequency checking
occheung Jan 20, 2023
74dcdcd
ad9959: revert set_amplitude
occheung Jan 20, 2023
edf520c
ad9959: revert bit operations
occheung Jan 20, 2023
16111ec
ad9959: add limitation to set_system_clock
occheung Jan 20, 2023
67812cf
ad9959: lighter amplitude check code
occheung Jan 20, 2023
0d19129
ad9959: add docs for converters
occheung Jan 20, 2023
db4b612
migrate ad9959 Profile to pounder
occheung Jan 20, 2023
61ad90f
pounder: in/out_ch -> in/out_channel
occheung Jan 20, 2023
7ac0279
dds_output: update profile fields reference
occheung Jan 20, 2023
47819fa
pounder: handle errors & log
occheung Jan 20, 2023
0d90440
mqtt: remove pounder telem from buffer
occheung Jan 20, 2023
0913327
settings: remove pounder telem setter
occheung Jan 20, 2023
803c2f4
ad9959: specify the amplitude is a scaling
occheung Jan 20, 2023
4607833
remove profile wrapper
occheung Jan 20, 2023
452f144
ad9959: make doc for constants
occheung Jan 20, 2023
692ea7c
clocking: detach from designed params
occheung Jan 20, 2023
934af89
Profile: comment -> docstring
occheung Jan 20, 2023
f9e0daa
set_system_clock: fix docstring brackets
occheung Jan 20, 2023
fa8c3f2
apply previous suggestions
Spaqin Apr 11, 2023
0f50c28
fix bitflags API usage
Spaqin Apr 13, 2023
fa18441
ad9959: fix top frequency not being included
Spaqin Apr 13, 2023
13392a9
apply cargo fmt
Spaqin Apr 13, 2023
a351beb
fix panic on amplitude set to 1.0
Spaqin Apr 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
169 changes: 156 additions & 13 deletions ad9959/src/lib.rs
Expand Up @@ -2,8 +2,24 @@

use bit_field::BitField;
use bitflags::bitflags;
use core::ops::Range;
use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin};

/// The minimum reference clock input frequency with REFCLK multiplier disabled.
const MIN_REFCLK_FREQUENCY: f32 = 1e6;
/// The minimum reference clock input frequency with REFCLK multiplier enabled.
const MIN_MULTIPLIED_REFCLK_FREQUENCY: f32 = 10e6;
/// The system clock frequency range with high gain configured for the internal VCO.
const HIGH_GAIN_VCO_RANGE: Range<f32> = Range {
start: 255e6,
end: 500e6,
};
/// The system clock frequency range with low gain configured for the internal VCO.
const LOW_GAIN_VCO_RANGE: Range<f32> = Range {
start: 100e6,
end: 160e6,
};

/// A device driver for the AD9959 direct digital synthesis (DDS) chip.
///
/// This chip provides four independently controllable digital-to-analog output sinusoids with
Expand Down Expand Up @@ -216,23 +232,17 @@
reference_clock_frequency: f32,
multiplier: u8,
) -> Result<f32, Error> {
let frequency =
validate_clocking(reference_clock_frequency, multiplier)?;
self.reference_clock_frequency = reference_clock_frequency;

if multiplier != 1 && !(4..=20).contains(&multiplier) {
return Err(Error::Bounds);
}

let frequency = multiplier as f32 * self.reference_clock_frequency;
if frequency > 500_000_000.0f32 {
return Err(Error::Frequency);
}

// 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 = HIGH_GAIN_VCO_RANGE.contains(&frequency)
|| frequency == HIGH_GAIN_VCO_RANGE.end;
fr1[0].set_bit(7, vco_range);

self.write(Register::FR1, &fr1)?;
Expand Down Expand Up @@ -363,9 +373,7 @@
channel: Channel,
phase_turns: f32,
) -> Result<f32, Error> {
let phase_offset: u16 =
(phase_turns * (1 << 14) as f32) as u16 & 0x3FFFu16;

let phase_offset = phase_to_pow(phase_turns)?;
self.modify_channel(
channel,
Register::CPOW0,
Expand Down Expand Up @@ -511,6 +519,108 @@
}
}

/// Validate the internal system clock configuration of the chip.
///
/// Arguments:
/// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core.
/// * `multiplier` - The frequency multiplier of the system clock. Must be 1 or 4-20.
///
/// Returns:
/// The system clock frequency to be configured.
pub fn validate_clocking(
reference_clock_frequency: f32,
multiplier: u8,
) -> Result<f32, Error> {
// The REFCLK frequency must be at least 1 MHz with REFCLK multiplier disabled.
if reference_clock_frequency < MIN_REFCLK_FREQUENCY {
return Err(Error::Bounds);
}
// If the REFCLK multiplier is enabled, the multiplier (FR1[22:18]) must be between 4 to 20.
// Alternatively, the clock multiplier can be disabled. The multiplication factor is 1.
if multiplier != 1 && !(4..=20).contains(&multiplier) {
return Err(Error::Bounds);
}
// If the REFCLK multiplier is enabled, the REFCLK frequency must be at least 10 MHz.
if multiplier != 1
&& reference_clock_frequency < MIN_MULTIPLIED_REFCLK_FREQUENCY
{
return Err(Error::Bounds);
}
let frequency = multiplier as f32 * reference_clock_frequency;
// SYSCLK frequency between 255 MHz and 500 MHz (inclusive) is valid with high range VCO
if HIGH_GAIN_VCO_RANGE.contains(&frequency)
|| frequency == HIGH_GAIN_VCO_RANGE.end
{
return Ok(frequency);
}

// SYSCLK frequency between 100 MHz and 160 MHz (inclusive) is valid with low range VCO
if LOW_GAIN_VCO_RANGE.contains(&frequency)
|| frequency == LOW_GAIN_VCO_RANGE.end
{
return Ok(frequency);
}

// When the REFCLK multiplier is disabled, SYSCLK frequency can go below 100 MHz
if multiplier == 1 && (0.0..=LOW_GAIN_VCO_RANGE.start).contains(&frequency)
{
return Ok(frequency);
}

Err(Error::Frequency)
}

/// Convert and validate frequency into frequency tuning word.
///
/// Arguments:
/// * `dds_frequency` - The DDS frequency to be converted and validated.
/// * `system_clock_frequency` - The system clock frequency of the AD9959 core.
///
/// Returns:
/// The corresponding frequency tuning word.
pub fn frequency_to_ftw(
dds_frequency: f32,
system_clock_frequency: f32,
) -> Result<u32, Error> {
// Output frequency should not exceed the Nyquist's frequency.
if !(0.0..=(system_clock_frequency / 2.0)).contains(&dds_frequency) {
return Err(Error::Bounds);
}
// The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the
// frequency tuning word and f_s is the system clock rate.
Ok(((dds_frequency / system_clock_frequency) * (1u64 << 32) as f32) as u32)
}

/// Convert phase into phase offset word.
///
/// Arguments:
/// * `phase_turns` - The normalized number of phase turns of a DDS channel.
///
/// Returns:
/// The corresponding phase offset word.
pub fn phase_to_pow(phase_turns: f32) -> Result<u16, Error> {
Ok((phase_turns * (1 << 14) as f32) as u16 & 0x3FFFu16)
}

/// Convert amplitude into amplitude control register values.
///
/// Arguments:
/// * `amplitude` - The normalized amplitude of a DDS channel.
///
/// Returns:
/// The corresponding value in the amplitude control register.
pub fn amplitude_to_acr(amplitude: f32) -> Result<u32, Error> {
if !(0.0..=1.0).contains(&amplitude) {
return Err(Error::Bounds);
}

let acr: u32 = *0u32
.set_bits(0..=9, ((amplitude * (1 << 10) as f32) as u32) & 0x3FF)
.set_bit(12, amplitude != 1.0);

Ok(acr as u32)

Check warning on line 621 in ad9959/src/lib.rs

View workflow job for this annotation

GitHub Actions / style

casting to the same type is unnecessary (`u32` -> `u32`)
}

/// Represents a means of serializing a DDS profile for writing to a stream.
pub struct ProfileSerializer {
// heapless::Vec<u8, 32>, especially its extend_from_slice() is slow
Expand Down Expand Up @@ -566,6 +676,39 @@
}
}

/// Update the system clock configuration.
///
/// # Args
/// * `reference_clock_frequency` - The reference clock frequency provided to the AD9959 core.
/// * `multiplier` - The frequency multiplier of the system clock. Must be 1 or 4-20.
///
/// # Limitations
/// The correctness of the FR1 register setting code rely on FR1\[0:17\] staying 0.
pub fn set_system_clock(
&mut self,
reference_clock_frequency: f32,
multiplier: u8,
) -> Result<f32, Error> {
let frequency = reference_clock_frequency * multiplier as f32;

// The enabled channel will be updated after clock reconfig
let mut fr1 = [0u8; 3];

// The ad9959 crate does not modify FR1[0:17]. These bits keep their default value.
// These bits by default are 0.
// Reading the register then update is not possible to implement in a serializer, where
// many QSPI writes are performed in burst. Switching between read and write requires
// breaking the QSPI indirect write mode and switch into the QSPI indirect read mode.
fr1[0].set_bits(2..=6, multiplier);

// Frequencies within the VCO forbidden range (160e6, 255e6) are already rejected.
let vco_range = HIGH_GAIN_VCO_RANGE.contains(&frequency);
fr1[0].set_bit(7, vco_range);

self.add_write(Register::FR1, &fr1);
Ok(frequency)
}

/// Add a register write to the serialization data.
fn add_write(&mut self, register: Register, value: &[u8]) {
let data = &mut self.data[self.index..];
Expand Down
48 changes: 43 additions & 5 deletions src/bin/dual-iir.rs
Expand Up @@ -44,6 +44,8 @@ use stabilizer::{
afe::Gain,
dac::{Dac0Output, Dac1Output, DacCode},
hal,
pounder::{ClockConfig, PounderConfig},
setup::PounderDevices as Pounder,
signal_generator::{self, SignalGenerator},
timers::SamplingTimer,
DigitalInput0, DigitalInput1, SystemTimer, Systick, AFE0, AFE1,
Expand Down Expand Up @@ -145,6 +147,16 @@ pub struct Settings {
/// See [signal_generator::BasicConfig#miniconf]
#[miniconf(defer)]
signal_generator: miniconf::Array<signal_generator::BasicConfig, 2>,

/// Specifies the config for pounder DDS clock configuration, DDS channels & attenuations
///
/// # Path
/// `pounder`
///
/// # Value
/// See [PounderConfig#miniconf]
#[miniconf(defer)]
pounder: miniconf::Option<PounderConfig>,
}

impl Default for Settings {
Expand All @@ -171,6 +183,8 @@ impl Default for Settings {
.into(),

stream_target: StreamTarget::default(),

pounder: None.into(),
}
}
}
Expand All @@ -189,6 +203,7 @@ mod app {
settings: Settings,
telemetry: TelemetryBuffer,
signal_generator: [SignalGenerator; 2],
pounder: Option<Pounder>,
}

#[local]
Expand All @@ -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>,
generator: FrameGenerator,
cpu_temp_sensor: stabilizer::hardware::cpu_temp_sensor::CpuTempSensor,
}
Expand All @@ -208,14 +224,21 @@ mod app {
let clock = SystemTimer::new(|| monotonics::now().ticks() as u32);

// Configure the microcontroller
let (stabilizer, _pounder) = hardware::setup::setup(
let (stabilizer, pounder) = hardware::setup::setup(
c.core,
c.device,
clock,
BATCH_SIZE,
SAMPLE_TICKS,
);

let dds_clock_state = pounder.as_ref().map(|_| ClockConfig::default());

let mut settings = Settings::default();
if pounder.is_some() {
settings.pounder.replace(PounderConfig::default());
}

let mut network = NetworkUsers::new(
stabilizer.net.stack,
stabilizer.net.phy,
Expand All @@ -226,13 +249,12 @@ mod app {
.unwrap_or("10.34.16.1")
.parse()
.unwrap(),
settings,
);

let generator = network
.configure_streaming(StreamFormat::AdcDacData, BATCH_SIZE as _);

let settings = Settings::default();

let shared = Shared {
network,
settings,
Expand All @@ -249,6 +271,7 @@ mod app {
.unwrap(),
),
],
pounder,
};

let mut local = Local {
Expand All @@ -258,6 +281,7 @@ mod app {
adcs: stabilizer.adcs,
dacs: stabilizer.dacs,
iir_state: [[[0.; 5]; IIR_CASCADE_LENGTH]; 2],
dds_clock_state,
generator,
cpu_temp_sensor: stabilizer.temperature_sensor,
};
Expand Down Expand Up @@ -407,7 +431,7 @@ mod app {
}
}

#[task(priority = 1, local=[afes], shared=[network, settings, signal_generator])]
#[task(priority = 1, local=[afes, dds_clock_state], shared=[network, settings, signal_generator, pounder])]
fn settings_update(mut c: settings_update::Context) {
let settings = c.shared.network.lock(|net| *net.miniconf.settings());
c.shared.settings.lock(|current| *current = settings);
Expand All @@ -431,15 +455,28 @@ mod app {
}
}

// Update Pounder configurations
c.shared.pounder.lock(|pounder| {
if let Some(pounder) = pounder {
let pounder_settings = settings.pounder.as_ref().unwrap();
let mut clocking = c.local.dds_clock_state.unwrap();
pounder.update_dds(*pounder_settings, &mut clocking);
}
});

let target = settings.stream_target.into();
c.shared.network.lock(|net| net.direct_stream(target));
}

#[task(priority = 1, shared=[network, settings, telemetry], local=[cpu_temp_sensor])]
#[task(priority = 1, shared=[network, settings, telemetry, pounder], local=[cpu_temp_sensor])]
fn telemetry(mut c: telemetry::Context) {
let telemetry: TelemetryBuffer =
c.shared.telemetry.lock(|telemetry| *telemetry);

let pounder_telemetry = c.shared.pounder.lock(|pounder| {
pounder.as_mut().map(|pounder| pounder.get_telemetry())
});

let (gains, telemetry_period) = c
.shared
.settings
Expand All @@ -450,6 +487,7 @@ mod app {
gains[0],
gains[1],
c.local.cpu_temp_sensor.get_temperature().unwrap(),
pounder_telemetry,
))
});

Expand Down
6 changes: 5 additions & 1 deletion src/bin/lockin.rs
Expand Up @@ -254,6 +254,8 @@ mod app {
SAMPLE_TICKS,
);

let settings = Settings::default();

let mut network = NetworkUsers::new(
stabilizer.net.stack,
stabilizer.net.phy,
Expand All @@ -264,6 +266,7 @@ mod app {
.unwrap_or("10.34.16.1")
.parse()
.unwrap(),
settings,
);

let generator = network
Expand All @@ -272,7 +275,7 @@ mod app {
let shared = Shared {
network,
telemetry: TelemetryBuffer::default(),
settings: Settings::default(),
settings,
};

let signal_config = signal_generator::Config {
Expand Down Expand Up @@ -499,6 +502,7 @@ mod app {
gains[0],
gains[1],
c.local.cpu_temp_sensor.get_temperature().unwrap(),
None,
))
});

Expand Down