diff --git a/examples/ethernet.rs b/examples/ethernet.rs index 41f4d18..2d36136 100644 --- a/examples/ethernet.rs +++ b/examples/ethernet.rs @@ -55,7 +55,10 @@ use firmware::{ ClockSourceCommand, ClockDivisionCommand, ProfileCommand, - Channel0Profile0Singletone + Channel0Profile0SingletoneCommand, + Channel0Profile0SingletoneFrequencyCommand, + Channel0Profile0SingletonePhaseCommand, + Channel0Profile0SingletoneAmplitudeCommand }, Urukul, scpi_root, recursive_scpi_tree, scpi_tree }; diff --git a/src/dds.rs b/src/dds.rs index 0d2bf00..4cb51d5 100644 --- a/src/dds.rs +++ b/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> { 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> { + + // 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> { + + // 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> { + + // 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. diff --git a/src/lib.rs b/src/lib.rs index 28c868e..7c9c2a0 100644 --- a/src/lib.rs +++ b/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) } diff --git a/src/scpi.rs b/src/scpi.rs index 5b07a40..9fa2a8a 100644 --- a/src/scpi.rs +++ b/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 Command for ProfileCommand { } } -impl Command for Channel0Profile0Singletone { +impl Command for Channel0Profile0SingletoneCommand { nquery!(); // Params: frequency, phase, amplitude (all mandatory) @@ -403,6 +408,79 @@ impl Command for Channel0Profile0Singletone { } } +impl Command for Channel0Profile0SingletoneFrequencyCommand { + // TODO: Implement query for publishing + nquery!(); + + // Param: frequency + fn event(&self, context: &mut Context, 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::(0.0)), + _ => Err(ErrorCode::IllegalParameterValue.into()), + }) + })?; + trace!("Received channel 0 profile 0 output single tone frequency: {:?}", frequency); + // Handle negative frequency + if frequency.get::() < 0.0 { + return Err(ErrorCode::DataOutOfRange.into()); + } + + context.device.set_channel_single_tone_profile_frequency(0, 0, frequency.get::()) + .map_err(|_| Error::new(ErrorCode::HardwareError)) + } +} + +impl Command for Channel0Profile0SingletonePhaseCommand { + // TODO: Implement query for publishing + nquery!(); + + // Param: frequency + fn event(&self, context: &mut Context, 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::(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::() < 0.0 || phase.get::() >= 360.0 { + return Err(ErrorCode::DataOutOfRange.into()); + } + context.device.set_channel_single_tone_profile_phase(0, 0, phase.get::()) + .map_err(|_| Error::new(ErrorCode::HardwareError)) + } +} + +impl Command for Channel0Profile0SingletoneAmplitudeCommand { + // TODO: Implement query for publishing + nquery!(); + + // Param: frequency + fn event(&self, context: &mut Context, 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