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,
|
||||
ClockDivisionCommand,
|
||||
ProfileCommand,
|
||||
Channel0Profile0Singletone
|
||||
Channel0Profile0SingletoneCommand,
|
||||
Channel0Profile0SingletoneFrequencyCommand,
|
||||
Channel0Profile0SingletonePhaseCommand,
|
||||
Channel0Profile0SingletoneAmplitudeCommand
|
||||
},
|
||||
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)
|
||||
* Frequency: Must be non-negative
|
||||
* 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>> {
|
||||
|
||||
assert!(profile < 8);
|
||||
assert!(f_out >= 0.0);
|
||||
assert!(phase_offset >= 0.0 && phase_offset < 360.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.
|
||||
|
|
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_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_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>;
|
||||
}
|
||||
|
||||
|
@ -267,6 +270,18 @@ where
|
|||
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> {
|
||||
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!(
|
||||
"CHANNEL0" => {
|
||||
"SWitch" => Channel0SwitchCommand,
|
||||
"Attenuation" => Channel0AttenuationCommand,
|
||||
"ATTenuation" => Channel0AttenuationCommand,
|
||||
"SYSCLOCK" => Channel0SystemClockCommand,
|
||||
"PROFILE0" => {
|
||||
"SINGLEtone" => Channel0Profile0Singletone
|
||||
"SINGLEtone" => {
|
||||
"FREQuency" => Channel0Profile0SingletoneFrequencyCommand,
|
||||
"PHASE" => Channel0Profile0SingletonePhaseCommand,
|
||||
"AMPlitude" => Channel0Profile0SingletoneAmplitudeCommand,
|
||||
["Setup"] => Channel0Profile0SingletoneCommand
|
||||
}
|
||||
}
|
||||
},
|
||||
"CHANNEL1" => {
|
||||
"SWitch" => Channel1SwitchCommand,
|
||||
"Attenuation" => Channel1AttenuationCommand
|
||||
"ATTenuation" => Channel1AttenuationCommand
|
||||
},
|
||||
"CHANNEL2" => {
|
||||
"SWitch" => Channel2SwitchCommand,
|
||||
"Attenuation" => Channel2AttenuationCommand
|
||||
"ATTenuation" => Channel2AttenuationCommand
|
||||
},
|
||||
"CHANNEL3" => {
|
||||
"SWitch" => Channel3SwitchCommand,
|
||||
"Attenuation" => Channel3AttenuationCommand
|
||||
"ATTenuation" => Channel3AttenuationCommand
|
||||
},
|
||||
"CLOCK" => {
|
||||
"SOURCE" => ClockSourceCommand,
|
||||
|
@ -200,10 +205,10 @@ pub struct ClockSourceCommand {}
|
|||
pub struct ClockDivisionCommand {}
|
||||
pub struct Channel0SystemClockCommand {}
|
||||
pub struct ProfileCommand {}
|
||||
pub struct Channel0Profile0Singletone {}
|
||||
pub struct Channel0Profile0SingletoneFrequency {}
|
||||
pub struct Channel0Profile0SingletonePhase {}
|
||||
pub struct Channel0Profile0SingletoneAmplitude {}
|
||||
pub struct Channel0Profile0SingletoneCommand {}
|
||||
pub struct Channel0Profile0SingletoneFrequencyCommand {}
|
||||
pub struct Channel0Profile0SingletonePhaseCommand {}
|
||||
pub struct Channel0Profile0SingletoneAmplitudeCommand {}
|
||||
|
||||
// Handle CLOCK:SOURCE command, setup the proper source for the system clock
|
||||
// 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!();
|
||||
|
||||
// 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
|
||||
* TODO: Implement mandatory commands
|
||||
|
|
Loading…
Reference in New Issue