dds: allow single tone iterative setup

pull/4/head
occheung 2020-09-16 14:23:01 +08:00
parent ad34f6311f
commit 412e6c0ea9
4 changed files with 217 additions and 12 deletions

View File

@ -55,7 +55,10 @@ use firmware::{
ClockSourceCommand,
ClockDivisionCommand,
ProfileCommand,
Channel0Profile0Singletone
Channel0Profile0SingletoneCommand,
Channel0Profile0SingletoneFrequencyCommand,
Channel0Profile0SingletonePhaseCommand,
Channel0Profile0SingletoneAmplitudeCommand
},
Urukul, scpi_root, recursive_scpi_tree, scpi_tree
};

View File

@ -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.

View File

@ -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)
}

View File

@ -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