From e1abf8735113287c7a51ceb9023c656d27c7434e Mon Sep 17 00:00:00 2001 From: occheung Date: Wed, 16 Sep 2020 12:05:45 +0800 Subject: [PATCH] dds: more clk ctrl; scpi: sys_clk ctrl --- Cargo.toml | 4 +- examples/ethernet.rs | 7 +-- examples/mqtt_client.rs | 4 +- src/dds.rs | 20 +++++++- src/lib.rs | 50 ++++++++++++++----- src/main.rs | 106 ++++++++++++++++++++-------------------- src/scpi.rs | 87 ++++++++++++++++++++++++++++++++- src/translation.rs | 36 +++++++------- 8 files changed, 221 insertions(+), 93 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f024764..5a858d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ libm = "0.2.0" embedded-nal = "0.1.0" minimq = { git = "https://github.com/quartiq/minimq.git", branch = "master" } heapless = "0.5.5" -arrayvec = { version = "0.5.1", default-features = false, features = ["array-sizes-33-128", "array-sizes-129-255"] } +arrayvec = { version = "0.5.1", default-features = false, features = [ "array-sizes-33-128", "array-sizes-129-255" ] } # Logging and Panicking panic-itm = "0.4.1" @@ -31,7 +31,7 @@ lazy_static = { version = "1.4.0", features = ["spin_no_std"] } git = "https://github.com/occheung/scpi-rs" branch = "issue-4" default-features = false -features = [ "build-info", "unit-frequency" ] +features = [ "build-info", "unit-frequency", "unit-angle" ] # Use below SCPI dependency when need to modify SCPI fork offline # [dependencies.scpi] diff --git a/examples/ethernet.rs b/examples/ethernet.rs index 2b77806..41f4d18 100644 --- a/examples/ethernet.rs +++ b/examples/ethernet.rs @@ -47,13 +47,15 @@ use firmware::{ Channel1SwitchCommand, Channel2SwitchCommand, Channel3SwitchCommand, + Channel0SystemClockCommand, Channel0AttenuationCommand, Channel1AttenuationCommand, Channel2AttenuationCommand, Channel3AttenuationCommand, ClockSourceCommand, ClockDivisionCommand, - ProfileCommand + ProfileCommand, + Channel0Profile0Singletone }, Urukul, scpi_root, recursive_scpi_tree, scpi_tree }; @@ -208,8 +210,7 @@ fn main() -> ! { let switch = CPLD::new(spi, (cs0, cs1, cs2), io_update); let parts = switch.split(); let mut urukul = Urukul::new( - parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7, - [25_000_000.0, 25_000_000.0, 25_000_000.0, 25_000_000.0] + parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7 ); // Setup ethernet pins diff --git a/examples/mqtt_client.rs b/examples/mqtt_client.rs index b126864..527300e 100644 --- a/examples/mqtt_client.rs +++ b/examples/mqtt_client.rs @@ -44,6 +44,7 @@ use firmware::{ ClockSourceCommand, ClockDivisionCommand, ProfileCommand, + Channel0Profile0Singletone }, Urukul, scpi_root, recursive_scpi_tree, scpi_tree }; @@ -209,8 +210,7 @@ fn main() -> ! { let parts = cpld.split(); let mut urukul = Urukul::new( - parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7, - [25_000_000.0, 25_000_000.0, 25_000_000.0, 25_000_000.0] + parts.spi1, parts.spi2, parts.spi3, parts.spi4, parts.spi5, parts.spi6, parts.spi7 ); cp.SCB.invalidate_icache(); diff --git a/src/dds.rs b/src/dds.rs index 7f9238c..0d2bf00 100644 --- a/src/dds.rs +++ b/src/dds.rs @@ -216,6 +216,22 @@ where } } + // Change sys_clk frequency, method to be determined + pub fn set_sys_clk_frequency(&mut self, f_sys_clk: f64) -> Result<(), Error> { + // If f_sys_clk is exactly the same as f_ref_clk, then invoke enable_normal_ref_clk + if f_sys_clk == self.f_ref_clk { + self.enable_normal_ref_clk() + } + // Otherwise, if the requested sys_clk is half of ref_clk, invoke enable_divided_ref_clk + else if f_sys_clk == (self.f_ref_clk / 2.0) { + self.enable_divided_ref_clk() + } + // Finally, try enabling PLL + else { + self.enable_pll(f_sys_clk) + } + } + #[allow(non_snake_case)] fn get_VCO_no(&mut self, f_sys_clk: f64, divider: u8) -> Result> { // Select a VCO @@ -310,8 +326,8 @@ where /* * Set a single tone profile - * Phase: Expressed in positive degree - * Frequency: Must be integer + * 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 */ pub fn set_single_tone_profile(&mut self, profile: u8, f_out: f64, phase_offset: f64, amp_scale_factor: f64) -> Result<(), Error> { diff --git a/src/lib.rs b/src/lib.rs index 8bb8e31..28c868e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,7 @@ pub struct Urukul { config_register: ConfigRegister, attenuator: Attenuator, dds: [DDS; 4], + f_master_clk: f64, } impl Urukul @@ -82,17 +83,21 @@ where * Master constructor for the entire Urukul device * Supply 7 SPI channels to Urukul and 4 reference clock frequencies */ - pub fn new(spi1: SPI, spi2: SPI, spi3: SPI, spi4: SPI, spi5: SPI, spi6: SPI, spi7: SPI, f_ref_clks: [f64; 4]) -> Self { + pub fn new(spi1: SPI, spi2: SPI, spi3: SPI, spi4: SPI, spi5: SPI, spi6: SPI, spi7: SPI) -> Self { // Construct Urukul Urukul { config_register: ConfigRegister::new(spi1), attenuator: Attenuator::new(spi2), + // Create 4 DDS instances with fixed 25MHz clock + // Counter-intuitive to assign urukul clock before having a urukul dds: [ - DDS::new(spi4, f_ref_clks[1]), - DDS::new(spi5, f_ref_clks[1]), - DDS::new(spi6, f_ref_clks[2]), - DDS::new(spi7, f_ref_clks[3]), + DDS::new(spi4, 25_000_000.0), + DDS::new(spi5, 25_000_000.0), + DDS::new(spi6, 25_000_000.0), + DDS::new(spi7, 25_000_000.0), ], + // Default clock selection: OSC, fixed 100MHz speed + f_master_clk: 100_000_000.0, } } @@ -128,10 +133,11 @@ where for chip_no in 0..4 { self.dds[chip_no].init()?; } - - // Clock tree reset. CPLD divides clock frequency by 4 by default. + // Clock tree reset. OSC clock source by default + self.f_master_clk = 100_000_000.0; + // CPLD divides clock frequency by 4 by default. for chip_no in 0..4 { - self.dds[chip_no].set_ref_clk_frequency(25_000_000.0)?; + self.dds[chip_no].set_ref_clk_frequency(self.f_master_clk / 4.0)?; } Ok(()) } @@ -158,6 +164,8 @@ pub trait UrukulTraits { fn set_clock_division(&mut self, division: u8) -> 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_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Self::Error>; + fn set_channel_sys_clk(&mut self, channel: u8, sys_clk: f64) -> Result<(), Self::Error>; } impl UrukulTraits for Urukul @@ -207,13 +215,16 @@ where (CFGMask::CLK_SEL0, 1), ]), }?; + + // Save the new master clock frequency + self.f_master_clk = frequency; // Calculate reference clock frequency after clock division from configuration register - let frequency = frequency / (self.config_register.get_configuration(CFGMask::DIV) as f64); + let f_ref_clk = self.f_master_clk / (self.config_register.get_configuration(CFGMask::DIV) as f64); // Update all DDS chips on reference clock frequency for dds_channel in 0..4 { - self.dds[dds_channel].set_ref_clk_frequency(frequency)?; + self.dds[dds_channel].set_ref_clk_frequency(f_ref_clk)?; } Ok(()) } @@ -230,7 +241,16 @@ where (CFGMask::DIV, 3), ]), _ => Err(Error::ParameterError), - }.map(|_| ()) + }?; + + // Calculate reference clock frequency after clock division from configuration register + let f_ref_clk = self.f_master_clk / (division as f64); + + // Update all DDS chips on reference clock frequency + for dds_channel in 0..4 { + self.dds[dds_channel].set_ref_clk_frequency(f_ref_clk)?; + } + Ok(()) } fn set_channel_attenuation(&mut self, channel: u8, attenuation: f32) -> Result<(), Self::Error> { @@ -242,4 +262,12 @@ where (CFGMask::PROFILE, profile.into()) ]).map(|_| ()) } + + fn set_channel_single_tone_profile(&mut self, channel: u8, profile: u8, frequency: f64, phase: f64, amplitude: f64) -> Result<(), Self::Error> { + self.dds[usize::from(channel)].set_single_tone_profile(profile, frequency, phase, 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/main.rs b/src/main.rs index 69f62e3..76be76f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -112,7 +112,7 @@ fn main() -> ! { let mut config = ConfigRegister::new(parts.spi1); let mut att = Attenuator::new(parts.spi2); - let mut dds0 = DDS::new(parts.spi4, 25_000_000); + let mut dds0 = DDS::new(parts.spi4, 25_000_000.0); // Reset all DDS, set CLK_SEL to 0 config.set_configurations(&mut [ @@ -135,75 +135,75 @@ fn main() -> ! { (DDSCFRMask::READ_EFFECTIVE_FTW, 1), ]).unwrap(); - dds0.enable_pll(1_000_000_000).unwrap(); + dds0.set_sys_clk_frequency(1_000_000_000.0).unwrap(); // Attenuator att.set_attenuation([ - 0.0, 31.5, 24.0, 0.0 + 5.0, 31.5, 24.0, 0.0 ]).unwrap(); - dds0.set_single_tone_profile(1, 10_000_000, 0.0, 0.5).unwrap(); + dds0.set_single_tone_profile(1, 10_000_000.0, 0.0, 0.5).unwrap(); config.set_configurations(&mut [ (CFGMask::PROFILE, 1), ]).unwrap(); - // Setup RAM configuration - dds0.set_configurations(&mut [ - (DDSCFRMask::RAM_ENABLE, 1), - (DDSCFRMask::RAM_PLAYBACK_DST, 2), - ]).unwrap(); + // // Setup RAM configuration + // dds0.set_configurations(&mut [ + // (DDSCFRMask::RAM_ENABLE, 1), + // (DDSCFRMask::RAM_PLAYBACK_DST, 2), + // ]).unwrap(); - // Configure RAM profile 0 - dds0.write_register(0x0E, &mut [ - 0x00, // Open - 0x09, 0xC4, // Address step rate (2500) - 0xFF, 0xC0, // End at address 1023 - 0x00, 0x00, // Start at address 0 - 0x04, // Recirculate mode - ]).unwrap(); + // // Configure RAM profile 0 + // dds0.write_register(0x0E, &mut [ + // 0x00, // Open + // 0x09, 0xC4, // Address step rate (2500) + // 0xFF, 0xC0, // End at address 1023 + // 0x00, 0x00, // Start at address 0 + // 0x04, // Recirculate mode + // ]).unwrap(); - debug!("{:#X?}", dds0.read_register(0x0E, &mut[ - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - ]).unwrap()); + // debug!("{:#X?}", dds0.read_register(0x0E, &mut[ + // 0x00, 0x00, 0x00, 0x00, + // 0x00, 0x00, 0x00, 0x00, + // ]).unwrap()); - // Choose profile 0 - config.set_configurations(&mut [ - (CFGMask::PROFILE, 0), - ]).unwrap(); + // // Choose profile 0 + // config.set_configurations(&mut [ + // (CFGMask::PROFILE, 0), + // ]).unwrap(); - // Set RAM to be amplitudes, disable RAM momentarily - dds0.set_configurations(&mut [ - (DDSCFRMask::RAM_PLAYBACK_DST, 0), - (DDSCFRMask::RAM_ENABLE, 0), - ]).unwrap(); + // // Set RAM to be amplitudes, disable RAM momentarily + // dds0.set_configurations(&mut [ + // (DDSCFRMask::RAM_PLAYBACK_DST, 0), + // (DDSCFRMask::RAM_ENABLE, 0), + // ]).unwrap(); - let mut ram_data: [u8; ((1024 * 4) + 1)] = [0; (1024 * 4) + 1]; - ram_data[0] = 0x16; - for index in 0..1024 { - if index % 2 == 1 { - ram_data[(index * 4) + 1] = 0x3F; - ram_data[(index * 4) + 2] = 0xFF; - } else { - ram_data[(index * 4) + 1] = 0x00; - ram_data[(index * 4) + 2] = 0x00; - } - // ram_data[(index * 4) + 1] = ((index >> 2) & 0xFF) as u8; - // ram_data[(index * 4) + 2] = ((index & 0x03) << 6) as u8; - } - dds0.transfer(&mut ram_data).unwrap(); + // let mut ram_data: [u8; ((1024 * 4) + 1)] = [0; (1024 * 4) + 1]; + // ram_data[0] = 0x16; + // for index in 0..1024 { + // if index % 2 == 1 { + // ram_data[(index * 4) + 1] = 0x3F; + // ram_data[(index * 4) + 2] = 0xFF; + // } else { + // ram_data[(index * 4) + 1] = 0x00; + // ram_data[(index * 4) + 2] = 0x00; + // } + // // ram_data[(index * 4) + 1] = ((index >> 2) & 0xFF) as u8; + // // ram_data[(index * 4) + 2] = ((index & 0x03) << 6) as u8; + // } + // dds0.transfer(&mut ram_data).unwrap(); - config.set_configurations(&mut [ - (CFGMask::PROFILE, 1), - ]).unwrap(); + // config.set_configurations(&mut [ + // (CFGMask::PROFILE, 1), + // ]).unwrap(); - config.set_configurations(&mut [ - (CFGMask::PROFILE, 0), - ]).unwrap(); + // config.set_configurations(&mut [ + // (CFGMask::PROFILE, 0), + // ]).unwrap(); - dds0.set_configurations(&mut [ - (DDSCFRMask::RAM_ENABLE, 1), - ]).unwrap(); + // dds0.set_configurations(&mut [ + // (DDSCFRMask::RAM_ENABLE, 1), + // ]).unwrap(); loop {} } diff --git a/src/scpi.rs b/src/scpi.rs index c3bfc68..fc85db7 100644 --- a/src/scpi.rs +++ b/src/scpi.rs @@ -13,6 +13,7 @@ use scpi::{ }; use scpi::suffix::{Amplitude, Db}; use uom::si::frequency::{gigahertz, hertz, kilohertz, megahertz, Frequency}; +use uom::si::angle::{degree, gon, minute as aminute, radian, revolution, Angle}; use uom::si::{f32, f64}; use embedded_hal::blocking::spi::Transfer; @@ -116,7 +117,11 @@ macro_rules! scpi_tree { scpi_root!( "CHANNEL0" => { "SWitch" => Channel0SwitchCommand, - "Attenuation" => Channel0AttenuationCommand + "Attenuation" => Channel0AttenuationCommand, + "SYSCLOCK" => Channel0SystemClockCommand, + "PROFILE0" => { + "SINGLEtone" => Channel0Profile0Singletone + } }, "CHANNEL1" => { "SWitch" => Channel1SwitchCommand, @@ -164,11 +169,16 @@ pub struct Channel2SwitchCommand {} pub struct Channel3SwitchCommand {} pub struct ClockSourceCommand {} pub struct ClockDivisionCommand {} +pub struct Channel0SystemClockCommand {} pub struct Channel0AttenuationCommand {} pub struct Channel1AttenuationCommand {} pub struct Channel2AttenuationCommand {} pub struct Channel3AttenuationCommand {} pub struct ProfileCommand {} +pub struct Channel0Profile0Singletone {} +pub struct Channel0Profile0SingletoneFrequency {} +pub struct Channel0Profile0SingletonePhase {} +pub struct Channel0Profile0SingletoneAmplitude {} impl Command for Channel0SwitchCommand { nquery!(); @@ -253,7 +263,7 @@ impl Command for ClockSourceCommand { _ => Err(ErrorCode::IllegalParameterValue.into()), }) })?; - trace!("Received frequency: {:?}", frequency); + trace!("Received master clock frequency: {:?}", frequency); let clock_source = match s_str { source if source.eq_ignore_ascii_case("OSC") => { @@ -302,6 +312,28 @@ impl Command for ClockDivisionCommand { } } +impl Command for Channel0SystemClockCommand { + nquery!(); + + // Param: + // The exact method of generating this frequency is auto-decided + // The process is delegated to individual DDS chip + fn event(&self, context: &mut Context, args: &mut Tokenizer) -> Result<()> { + + let frequency: f64::Frequency = args.next_data(true)? + .map_or(Ok(f64::Frequency::new::(0.0)), |t| { + t.numeric(|s| match s { + NumericValues::Default => Ok(f64::Frequency::new::(0.0)), + _ => Err(ErrorCode::IllegalParameterValue.into()), + }) + })?; + trace!("Received channel 0 system clock frequency: {:?}", frequency); + + // Setup sys_clk through urukul interface + context.device.set_channel_sys_clk(0, frequency.get::()).map_err(|_| Error::new(ErrorCode::IllegalParameterValue)) + } +} + impl Command for Channel0AttenuationCommand { nquery!(); @@ -376,6 +408,57 @@ impl Command for ProfileCommand { } } +impl Command for Channel0Profile0Singletone { + nquery!(); + + // Params: frequency, phase, amplitude (all mandatory) + 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()); + } + + // 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()); + } + + // 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()); + } + + // TODO: Setup single tone on DDS + context.device.set_channel_single_tone_profile(0, 0, frequency.get::(), phase.get::(), amplitude) + .map_err(|_| Error::new(ErrorCode::HardwareError)) + } +} + /* * Implement "Device" trait from SCPI * TODO: Implement mandatory commands diff --git a/src/translation.rs b/src/translation.rs index ee13727..42d50e3 100644 --- a/src/translation.rs +++ b/src/translation.rs @@ -5,8 +5,8 @@ use log::{trace, info}; use arrayvec::{ArrayVec}; pub trait MqttScpiTranslator { - // Convert an MQTT publish message into SCPI compatible command - // The argument part/ MQTT message must follow SCPI standard for parameter formatting + // Unwrap an MQTT publish message into SCPI compatible command + // The command part/ MQTT message must follow SCPI standard for parameter formatting fn run_with_mqtt(&mut self, topic: &str, args: &str, response: &mut FMT) -> Result<()>; } @@ -20,27 +20,27 @@ impl<'a, T: Device> MqttScpiTranslator for Context<'a, T> { return Ok(()); } - let command_topic = topic.strip_prefix("Urukul/Control/") - .unwrap_or(""); + // let command_topic = topic.strip_prefix("Urukul/Control") + // .unwrap_or(""); // Create a fixed-size buffer to handle slice operation let mut buffer = ArrayVec::<[u8; 1024]>::new(); - // Copy MQTT topic, convert it into SCPI header format - for i in command_topic.chars() { - if i == '/' { - // The topic separator is colon(':') in SCPI, and slash('/') in MQTT - buffer.try_push(b':') - .map_err(|_| ErrorCode::OutOfMemory)?; - } else { - buffer.try_push(i as u8) - .map_err(|_| ErrorCode::OutOfMemory)?; - } - } + // // Copy MQTT topic, convert it into SCPI header format + // for i in command_topic.chars() { + // if i == '/' { + // // The topic separator is colon(':') in SCPI, and slash('/') in MQTT + // buffer.try_push(b':') + // .map_err(|_| ErrorCode::OutOfMemory)?; + // } else { + // buffer.try_push(i as u8) + // .map_err(|_| ErrorCode::OutOfMemory)?; + // } + // } - // Place a space bar between header and parameter - buffer.try_push(b' ') - .map_err(|_| ErrorCode::OutOfMemory)?; + // // Place a space bar between header and parameter + // buffer.try_push(b' ') + // .map_err(|_| ErrorCode::OutOfMemory)?; // Copy the arguments into the buffer for i in args.chars() {