forked from M-Labs/humpback-dds
dds: allow single tone iterative setup
This commit is contained in:
parent
ad34f6311f
commit
412e6c0ea9
|
@ -55,7 +55,10 @@ use firmware::{
|
||||||
ClockSourceCommand,
|
ClockSourceCommand,
|
||||||
ClockDivisionCommand,
|
ClockDivisionCommand,
|
||||||
ProfileCommand,
|
ProfileCommand,
|
||||||
Channel0Profile0Singletone
|
Channel0Profile0SingletoneCommand,
|
||||||
|
Channel0Profile0SingletoneFrequencyCommand,
|
||||||
|
Channel0Profile0SingletonePhaseCommand,
|
||||||
|
Channel0Profile0SingletoneAmplitudeCommand
|
||||||
},
|
},
|
||||||
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
|
||||||
};
|
};
|
||||||
|
|
111
src/dds.rs
111
src/dds.rs
|
@ -325,7 +325,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set a single tone profile
|
* Setup a complete single tone profile
|
||||||
* Phase: Expressed in positive degree, i.e. [0.0, 360.0)
|
* Phase: Expressed in positive degree, i.e. [0.0, 360.0)
|
||||||
* Frequency: Must be non-negative
|
* Frequency: Must be non-negative
|
||||||
* Amplitude: In a scale from 0 to 1, taking float
|
* Amplitude: In a scale from 0 to 1, taking float
|
||||||
|
@ -333,6 +333,7 @@ where
|
||||||
pub fn set_single_tone_profile(&mut self, profile: u8, f_out: f64, phase_offset: f64, amp_scale_factor: f64) -> Result<(), Error<E>> {
|
pub fn set_single_tone_profile(&mut self, profile: u8, f_out: f64, phase_offset: f64, amp_scale_factor: f64) -> Result<(), Error<E>> {
|
||||||
|
|
||||||
assert!(profile < 8);
|
assert!(profile < 8);
|
||||||
|
assert!(f_out >= 0.0);
|
||||||
assert!(phase_offset >= 0.0 && phase_offset < 360.0);
|
assert!(phase_offset >= 0.0 && phase_offset < 360.0);
|
||||||
assert!(amp_scale_factor >=0.0 && amp_scale_factor <= 1.0);
|
assert!(amp_scale_factor >=0.0 && amp_scale_factor <= 1.0);
|
||||||
|
|
||||||
|
@ -367,6 +368,114 @@ where
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set frequency of a single tone profile
|
||||||
|
* Frequency: Must be non-negative
|
||||||
|
* Keep other field unchanged in the register
|
||||||
|
*/
|
||||||
|
pub fn set_single_tone_profile_frequency(&mut self, profile: u8, f_out: f64) -> Result<(), Error<E>> {
|
||||||
|
|
||||||
|
// Setup configuration registers before writing single tone register
|
||||||
|
self.set_configurations(&mut [
|
||||||
|
(DDSCFRMask::RAM_ENABLE, 0),
|
||||||
|
(DDSCFRMask::DIGITAL_RAMP_ENABLE, 0),
|
||||||
|
(DDSCFRMask::OSK_ENABLE, 0),
|
||||||
|
(DDSCFRMask::PARALLEL_DATA_PORT_ENABLE, 0),
|
||||||
|
])?;
|
||||||
|
self.set_configurations(&mut [
|
||||||
|
(DDSCFRMask::EN_AMP_SCALE_SINGLE_TONE_PRO, 1),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
// Calculate frequency tuning work (FTW)
|
||||||
|
let f_res: u64 = 1 << 32;
|
||||||
|
let ftw = ((f_res as f64) * f_out / self.f_sys_clk) as u32;
|
||||||
|
|
||||||
|
// Read existing amplitude/phase data
|
||||||
|
let mut register: [u8; 8] = [0; 8];
|
||||||
|
self.read_register(0x0E + profile, &mut register)?;
|
||||||
|
|
||||||
|
// Overwrite FTW
|
||||||
|
register[4] = ((ftw >> 24) & 0xFF) as u8;
|
||||||
|
register[5] = ((ftw >> 16) & 0xFF) as u8;
|
||||||
|
register[6] = ((ftw >> 8) & 0xFF) as u8;
|
||||||
|
register[7] = ((ftw >> 0) & 0xFF) as u8;
|
||||||
|
|
||||||
|
// Update FTW by writing back the register
|
||||||
|
self.write_register(0x0E + profile, &mut register)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set phase offset of a single tone profile
|
||||||
|
* Phase: Expressed in positive degree, i.e. [0.0, 360.0)
|
||||||
|
* Keep other field unchanged in the register
|
||||||
|
*/
|
||||||
|
pub fn set_single_tone_profile_phase(&mut self, profile: u8, phase_offset: f64) -> Result<(), Error<E>> {
|
||||||
|
|
||||||
|
// Setup configuration registers before writing single tone register
|
||||||
|
self.set_configurations(&mut [
|
||||||
|
(DDSCFRMask::RAM_ENABLE, 0),
|
||||||
|
(DDSCFRMask::DIGITAL_RAMP_ENABLE, 0),
|
||||||
|
(DDSCFRMask::OSK_ENABLE, 0),
|
||||||
|
(DDSCFRMask::PARALLEL_DATA_PORT_ENABLE, 0),
|
||||||
|
])?;
|
||||||
|
self.set_configurations(&mut [
|
||||||
|
(DDSCFRMask::EN_AMP_SCALE_SINGLE_TONE_PRO, 1),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
// Calculate phase offset work (POW)
|
||||||
|
let phase_res: u64 = 1 << 16;
|
||||||
|
let pow = ((phase_res as f64) * phase_offset / 360.0) as u16;
|
||||||
|
|
||||||
|
// Read existing amplitude/frequency data
|
||||||
|
let mut register: [u8; 8] = [0; 8];
|
||||||
|
self.read_register(0x0E + profile, &mut register)?;
|
||||||
|
|
||||||
|
// Overwrite POW
|
||||||
|
register[2] = ((pow >> 8) & 0xFF) as u8;
|
||||||
|
register[3] = ((pow >> 0) & 0xFF) as u8;
|
||||||
|
|
||||||
|
// Update POW by writing back the register
|
||||||
|
self.write_register(0x0E + profile, &mut register)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set amplitude offset of a single tone profile
|
||||||
|
* Amplitude: In a scale from 0 to 1, taking float
|
||||||
|
* Keep other field unchanged in the register
|
||||||
|
*/
|
||||||
|
pub fn set_single_tone_profile_amplitude(&mut self, profile: u8, amp_scale_factor: f64) -> Result<(), Error<E>> {
|
||||||
|
|
||||||
|
// Setup configuration registers before writing single tone register
|
||||||
|
self.set_configurations(&mut [
|
||||||
|
(DDSCFRMask::RAM_ENABLE, 0),
|
||||||
|
(DDSCFRMask::DIGITAL_RAMP_ENABLE, 0),
|
||||||
|
(DDSCFRMask::OSK_ENABLE, 0),
|
||||||
|
(DDSCFRMask::PARALLEL_DATA_PORT_ENABLE, 0),
|
||||||
|
])?;
|
||||||
|
self.set_configurations(&mut [
|
||||||
|
(DDSCFRMask::EN_AMP_SCALE_SINGLE_TONE_PRO, 1),
|
||||||
|
])?;
|
||||||
|
|
||||||
|
// Calculate amplitude_scale_factor (ASF)
|
||||||
|
let amp_res: u64 = 1 << 14;
|
||||||
|
let asf :u16 = if amp_scale_factor == 1.0 {
|
||||||
|
0x3FFF
|
||||||
|
} else {
|
||||||
|
((amp_res as f64) * amp_scale_factor) as u16
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read existing frequency/phase data
|
||||||
|
let mut register: [u8; 8] = [0; 8];
|
||||||
|
self.read_register(0x0E + profile, &mut register)?;
|
||||||
|
|
||||||
|
// Overwrite POW
|
||||||
|
register[0] = ((asf >> 8) & 0xFF) as u8;
|
||||||
|
register[1] = ((asf >> 0) & 0xFF) as u8;
|
||||||
|
|
||||||
|
// Update POW by writing back the register
|
||||||
|
self.write_register(0x0E + profile, &mut register)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Test method for DDS.
|
* Test method for DDS.
|
||||||
|
|
15
src/lib.rs
15
src/lib.rs
|
@ -165,6 +165,9 @@ pub trait UrukulTraits {
|
||||||
fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Self::Error>;
|
fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Self::Error>;
|
||||||
fn set_profile(&mut self, profile: u8) -> Result<(), Self::Error>;
|
fn set_profile(&mut self, profile: u8) -> Result<(), Self::Error>;
|
||||||
fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Self::Error>;
|
fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Self::Error>;
|
||||||
|
fn set_channel_single_tone_profile_frequency(&mut self, channel: u8, profile: u8, frequency: f64)-> Result<(), Self::Error>;
|
||||||
|
fn set_channel_single_tone_profile_phase(&mut self, channel: u8, profile: u8, phase: f64)-> Result<(), Self::Error>;
|
||||||
|
fn set_channel_single_tone_profile_amplitude(&mut self, channel: u8, profile: u8, amplitude: f64)-> Result<(), Self::Error>;
|
||||||
fn set_channel_sys_clk(&mut self, channel: u8, sys_clk: f64) -> Result<(), Self::Error>;
|
fn set_channel_sys_clk(&mut self, channel: u8, sys_clk: f64) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,6 +270,18 @@ where
|
||||||
self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, amplitude)
|
self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, amplitude)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_channel_single_tone_profile_frequency(&mut self, channel: u8, profile: u8, frequency: f64)-> Result<(), Self::Error> {
|
||||||
|
self.dds[usize::from(channel)].set_single_tone_profile_frequency(profile, frequency)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_channel_single_tone_profile_phase(&mut self, channel: u8, profile: u8, phase: f64)-> Result<(), Self::Error> {
|
||||||
|
self.dds[usize::from(channel)].set_single_tone_profile_phase(profile, phase)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_channel_single_tone_profile_amplitude(&mut self, channel: u8, profile: u8, amplitude: f64)-> Result<(), Self::Error> {
|
||||||
|
self.dds[usize::from(channel)].set_single_tone_profile_amplitude(profile, amplitude)
|
||||||
|
}
|
||||||
|
|
||||||
fn set_channel_sys_clk(&mut self, channel: u8, f_sys_clk: f64) -> Result<(), Self::Error> {
|
fn set_channel_sys_clk(&mut self, channel: u8, f_sys_clk: f64) -> Result<(), Self::Error> {
|
||||||
self.dds[usize::from(channel)].set_sys_clk_frequency(f_sys_clk)
|
self.dds[usize::from(channel)].set_sys_clk_frequency(f_sys_clk)
|
||||||
}
|
}
|
||||||
|
|
98
src/scpi.rs
98
src/scpi.rs
|
@ -117,23 +117,28 @@ macro_rules! scpi_tree {
|
||||||
scpi_root!(
|
scpi_root!(
|
||||||
"CHANNEL0" => {
|
"CHANNEL0" => {
|
||||||
"SWitch" => Channel0SwitchCommand,
|
"SWitch" => Channel0SwitchCommand,
|
||||||
"Attenuation" => Channel0AttenuationCommand,
|
"ATTenuation" => Channel0AttenuationCommand,
|
||||||
"SYSCLOCK" => Channel0SystemClockCommand,
|
"SYSCLOCK" => Channel0SystemClockCommand,
|
||||||
"PROFILE0" => {
|
"PROFILE0" => {
|
||||||
"SINGLEtone" => Channel0Profile0Singletone
|
"SINGLEtone" => {
|
||||||
|
"FREQuency" => Channel0Profile0SingletoneFrequencyCommand,
|
||||||
|
"PHASE" => Channel0Profile0SingletonePhaseCommand,
|
||||||
|
"AMPlitude" => Channel0Profile0SingletoneAmplitudeCommand,
|
||||||
|
["Setup"] => Channel0Profile0SingletoneCommand
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CHANNEL1" => {
|
"CHANNEL1" => {
|
||||||
"SWitch" => Channel1SwitchCommand,
|
"SWitch" => Channel1SwitchCommand,
|
||||||
"Attenuation" => Channel1AttenuationCommand
|
"ATTenuation" => Channel1AttenuationCommand
|
||||||
},
|
},
|
||||||
"CHANNEL2" => {
|
"CHANNEL2" => {
|
||||||
"SWitch" => Channel2SwitchCommand,
|
"SWitch" => Channel2SwitchCommand,
|
||||||
"Attenuation" => Channel2AttenuationCommand
|
"ATTenuation" => Channel2AttenuationCommand
|
||||||
},
|
},
|
||||||
"CHANNEL3" => {
|
"CHANNEL3" => {
|
||||||
"SWitch" => Channel3SwitchCommand,
|
"SWitch" => Channel3SwitchCommand,
|
||||||
"Attenuation" => Channel3AttenuationCommand
|
"ATTenuation" => Channel3AttenuationCommand
|
||||||
},
|
},
|
||||||
"CLOCK" => {
|
"CLOCK" => {
|
||||||
"SOURCE" => ClockSourceCommand,
|
"SOURCE" => ClockSourceCommand,
|
||||||
|
@ -200,10 +205,10 @@ pub struct ClockSourceCommand {}
|
||||||
pub struct ClockDivisionCommand {}
|
pub struct ClockDivisionCommand {}
|
||||||
pub struct Channel0SystemClockCommand {}
|
pub struct Channel0SystemClockCommand {}
|
||||||
pub struct ProfileCommand {}
|
pub struct ProfileCommand {}
|
||||||
pub struct Channel0Profile0Singletone {}
|
pub struct Channel0Profile0SingletoneCommand {}
|
||||||
pub struct Channel0Profile0SingletoneFrequency {}
|
pub struct Channel0Profile0SingletoneFrequencyCommand {}
|
||||||
pub struct Channel0Profile0SingletonePhase {}
|
pub struct Channel0Profile0SingletonePhaseCommand {}
|
||||||
pub struct Channel0Profile0SingletoneAmplitude {}
|
pub struct Channel0Profile0SingletoneAmplitudeCommand {}
|
||||||
|
|
||||||
// Handle CLOCK:SOURCE command, setup the proper source for the system clock
|
// Handle CLOCK:SOURCE command, setup the proper source for the system clock
|
||||||
// Leave clock division to CLOCK:DIVision command
|
// Leave clock division to CLOCK:DIVision command
|
||||||
|
@ -352,7 +357,7 @@ impl<T:Device + UrukulTraits> Command<T> for ProfileCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0Singletone {
|
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0SingletoneCommand {
|
||||||
nquery!();
|
nquery!();
|
||||||
|
|
||||||
// Params: frequency, phase, amplitude (all mandatory)
|
// Params: frequency, phase, amplitude (all mandatory)
|
||||||
|
@ -403,6 +408,79 @@ impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0Singletone {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0SingletoneFrequencyCommand {
|
||||||
|
// TODO: Implement query for publishing
|
||||||
|
nquery!();
|
||||||
|
|
||||||
|
// Param: frequency
|
||||||
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||||
|
|
||||||
|
// Read output frequency
|
||||||
|
let frequency: f64::Frequency = args.next_data(false)?
|
||||||
|
.map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| {
|
||||||
|
t.numeric(|s| match s {
|
||||||
|
NumericValues::Default => Ok(f64::Frequency::new::<hertz>(0.0)),
|
||||||
|
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
trace!("Received channel 0 profile 0 output single tone frequency: {:?}", frequency);
|
||||||
|
// Handle negative frequency
|
||||||
|
if frequency.get::<hertz>() < 0.0 {
|
||||||
|
return Err(ErrorCode::DataOutOfRange.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
context.device.set_channel_single_tone_profile_frequency(0, 0, frequency.get::<hertz>())
|
||||||
|
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0SingletonePhaseCommand {
|
||||||
|
// TODO: Implement query for publishing
|
||||||
|
nquery!();
|
||||||
|
|
||||||
|
// Param: frequency
|
||||||
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||||
|
|
||||||
|
// Read phase offset
|
||||||
|
let phase: f64::Angle = args.next_data(false)?
|
||||||
|
.map_or(Err(Error::new(ErrorCode::MissingParameter)), |t| {
|
||||||
|
t.numeric(
|
||||||
|
|s| match s {
|
||||||
|
NumericValues::Default => Ok(f64::Angle::new::<degree>(0.0)),
|
||||||
|
_ => Err(ErrorCode::IllegalParameterValue.into()),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
trace!("Received channel 0 profile 0 output single tone phase offset: {:?}", phase);
|
||||||
|
// Handle out-of-bound phase offset
|
||||||
|
if phase.get::<degree>() < 0.0 || phase.get::<degree>() >= 360.0 {
|
||||||
|
return Err(ErrorCode::DataOutOfRange.into());
|
||||||
|
}
|
||||||
|
context.device.set_channel_single_tone_profile_phase(0, 0, phase.get::<degree>())
|
||||||
|
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T:Device + UrukulTraits> Command<T> for Channel0Profile0SingletoneAmplitudeCommand {
|
||||||
|
// TODO: Implement query for publishing
|
||||||
|
nquery!();
|
||||||
|
|
||||||
|
// Param: frequency
|
||||||
|
fn event(&self, context: &mut Context<T>, args: &mut Tokenizer) -> Result<()> {
|
||||||
|
|
||||||
|
// Read amplitude offset
|
||||||
|
let amplitude: f64 = args.next_data(false)?
|
||||||
|
.map_or(Err(Error::new(ErrorCode::MissingParameter)),
|
||||||
|
|token| token.try_into())?;
|
||||||
|
trace!("Received channel 0 profile 0 output single tone amplitude offset: {:?}", amplitude);
|
||||||
|
// Handle out-of-bound phase offset
|
||||||
|
if amplitude < 0.0 || amplitude > 1.0 {
|
||||||
|
return Err(ErrorCode::DataOutOfRange.into());
|
||||||
|
}
|
||||||
|
context.device.set_channel_single_tone_profile_amplitude(0, 0, amplitude)
|
||||||
|
.map_err(|_| Error::new(ErrorCode::HardwareError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Implement "Device" trait from SCPI
|
* Implement "Device" trait from SCPI
|
||||||
* TODO: Implement mandatory commands
|
* TODO: Implement mandatory commands
|
||||||
|
|
Loading…
Reference in New Issue