diff --git a/src/config.rs b/src/config.rs index e6954cd..ed483d5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -35,11 +35,32 @@ pub struct ChannelConfig { pub sw: bool, pub att: f32, pub sys_clk: f64, + pub freq: f64, + pub asf: f64, + pub profile: ProfileSetup, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SingleTone { pub freq: f64, pub phase: f64, pub asf: f64, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RAM { + pub start: u16, + pub end: u16, + pub stride: u16, + pub op_mode: u8, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ProfileSetup { + Singletone(SingleTone), + RAM(RAM), +} + pub fn get_net_config(store: &mut FlashStore) -> NetConfig { let net_config = NetConfig { ip_cidr: { diff --git a/src/dds.rs b/src/dds.rs index 1b49472..972f55d 100644 --- a/src/dds.rs +++ b/src/dds.rs @@ -4,7 +4,7 @@ use core::mem::size_of; use core::convert::TryInto; use heapless::Vec; use heapless::consts::*; -use log::{ trace, debug, warn }; +use log::debug; /* * Bitmask for all configurations (Order: CFR3, CFR2, CFR1) @@ -66,9 +66,10 @@ construct_bitmask!(DDSCFRMask; u32; const WRITE_MASK :u8 = 0x00; const READ_MASK :u8 = 0x80; +#[link_section = ".sram2.ram"] static mut RAM_VEC: Vec = Vec(heapless::i::Vec::new()); -#[derive(Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, Debug)] pub enum RAMDestination { Frequency = 0, Phase = 1, @@ -76,7 +77,7 @@ pub enum RAMDestination { Polar = 3, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum RAMOperationMode { DirectSwitch = 0, RampUp = 1, @@ -89,6 +90,7 @@ pub struct DDS { spi: SPI, f_ref_clk: f64, f_sys_clk: f64, + ram_dest: RAMDestination } impl DDS @@ -100,6 +102,7 @@ where spi, f_ref_clk, f_sys_clk: f_ref_clk, + ram_dest: RAMDestination::Frequency, } } } @@ -508,6 +511,41 @@ where ]) } + // Helper function to configure the default frequency in the FTW register (0x07) + pub fn set_default_ftw(&mut self, frequency: f64) -> Result<(), Error> { + let mut ftw: [u8; 4] = self.frequency_to_ftw(frequency).to_be_bytes(); + self.write_register(0x07, &mut ftw) + } + + // Helper function to configure the default amplitude in the ASF register (0x09) + pub fn set_default_asf(&mut self, amplitude: f64) -> Result<(), Error> { + let shifted_asf: [u8; 2] = (self.amplitude_to_asf(amplitude) << 2).to_be_bytes(); + let mut asf_register: [u8; 4] = [0; 4]; + self.read_register(0x09, &mut asf_register)?; + // Override original ASF + asf_register[2] = shifted_asf[0]; + asf_register[3] = (shifted_asf[1] & 0xFC) | (asf_register[3] & 0x03); + self.write_register(0x09, &mut asf_register) + } + + pub fn get_default_ftw(&mut self) -> Result> { + let mut ftw_bytes: [u8; 4] = [0; 4]; + self.read_register(0x07, &mut ftw_bytes)?; + let ftw: u64 = (ftw_bytes[0] as u64) << 24 | + (ftw_bytes[1] as u64) << 16 | + (ftw_bytes[2] as u64) << 8 | + (ftw_bytes[3] as u64); + Ok(((ftw as f64)/(((1_u64) << 32) as f64))*self.f_sys_clk) + } + + pub fn get_default_asf(&mut self) -> Result> { + let mut asf_register: [u8; 4] = [0; 4]; + self.read_register(0x09, &mut asf_register)?; + let asf: u64 = ((asf_register[2] as u64) << 6) | + ((asf_register[3] as u64) >> 2); + Ok((asf as f64)/(((1_u64) << 14) as f64)) + } + // Helper function to switch into RAM mode // Need to setup configuration registers before writing into RAM profile register fn enable_ram_configuration(&mut self, ram_dst: RAMDestination) -> Result<(), Error> { @@ -525,275 +563,51 @@ where ]) } - /* - * Configure a RAM mode profile, wrt supplied frequency data - * This will setup the static RAM_VEC by converting frequency to ftw - */ - pub unsafe fn set_frequency_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16, - no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64, - frequency_data: &[f64] - ) -> Result<(), Error> { - - // Check the legality of the profile setup + // Setup a RAM profile + pub fn set_up_ram_profile(&mut self, profile: u8, start_addr: u16, + end_addr: u16, no_dwell_high: bool, zero_crossing: bool, + op_mode: RAMOperationMode, ramp_rate: u16 + )-> Result<(), Error> + { assert!(profile <= 7); assert!(end_addr >= start_addr); assert!(end_addr < 1024); - assert_eq!(frequency_data.len() as u16, end_addr - start_addr + 1); - // Clear RAM vector, and add address byte - RAM_VEC.clear(); - RAM_VEC.push(0x16) - .map_err(|_| Error::DDSRAMError)?; + self.enable_ram_configuration(self.ram_dest)?; - // Convert frequency data into bytes recognized by DDS - for freq in frequency_data.iter() { - let ftw = self.frequency_to_ftw(*freq); - RAM_VEC.push(((ftw >> 24) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((ftw >> 16) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((ftw >> 8) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((ftw >> 0) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - } - - self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Frequency, - no_dwell_high, zero_crossing, op_mode, playback_rate) - } - - /* - * Configure a RAM mode profile, wrt supplied amplitude data - * This will setup the static RAM_VEC by converting amplitude to asf - */ - pub unsafe fn set_amplitude_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16, - no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64, - amplitude_data: &[f64] - ) -> Result<(), Error> { - - // Check the legality of the profile setup - assert!(profile <= 7); - assert!(end_addr >= start_addr); - assert!(end_addr < 1024); - assert_eq!(amplitude_data.len() as u16, end_addr - start_addr + 1); - - // Clear RAM vector, and add address byte - RAM_VEC.clear(); - RAM_VEC.push(0x16) - .map_err(|_| Error::DDSRAMError)?; - - // Convert amplitude data into bytes recognized by DDS - for amp in amplitude_data.iter() { - let asf = self.amplitude_to_asf(*amp); - RAM_VEC.push(((asf >> 8) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((asf << 2) & 0xFC) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(0) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(0) - .map_err(|_| Error::DDSRAMError)?; - } - - self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Amplitude, - no_dwell_high, zero_crossing, op_mode, playback_rate) - } - - /* - * Configure a RAM mode profile, wrt supplied phase data - * This will setup the static RAM_VEC by converting phase to ftw - */ - pub unsafe fn set_phase_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16, - no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64, - phase_data: &[f64] - ) -> Result<(), Error> { - - // Check the legality of the profile setup - assert!(profile <= 7); - assert!(end_addr >= start_addr); - assert!(end_addr < 1024); - assert_eq!(phase_data.len() as u16, end_addr - start_addr + 1); - - // Clear RAM vector, and add address byte - RAM_VEC.clear(); - RAM_VEC.push(0x16) - .map_err(|_| Error::DDSRAMError)?; - - // Convert phase data into bytes recognized by DDS - for deg in phase_data.iter() { - let pow = self.degree_to_pow(*deg); - RAM_VEC.push(((pow >> 8) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((pow >> 0) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(0) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(0) - .map_err(|_| Error::DDSRAMError)?; - } - - self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Phase, - no_dwell_high, zero_crossing, op_mode, playback_rate) - } - - /* - * Configure a RAM mode profile, wrt supplied phase data - * This will setup the static RAM_VEC by converting phase to ftw - */ - pub unsafe fn set_polar_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16, - no_dwell_high: bool, zero_crossing: bool, op_mode: RAMOperationMode, playback_rate: f64, - polar_data: &[(f64, f64)] - ) -> Result<(), Error> { - - // Check the legality of the profile setup - assert!(profile <= 7); - assert!(end_addr >= start_addr); - assert!(end_addr < 1024); - assert_eq!(polar_data.len() as u16, end_addr - start_addr + 1); - - // Clear RAM vector, and add address byte - RAM_VEC.clear(); - RAM_VEC.push(0x16) - .map_err(|_| Error::DDSRAMError)?; - - // Convert amplitude data into bytes recognized by DDS - for (deg, amp) in polar_data.iter() { - let pow = self.degree_to_pow(*deg); - let asf = self.amplitude_to_asf(*amp); - RAM_VEC.push(((pow >> 8) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((pow >> 0) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((asf >> 8) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((asf << 2) & 0xFC) as u8) - .map_err(|_| Error::DDSRAMError)?; - } - - self.set_ram_profile(profile, start_addr, end_addr, RAMDestination::Phase, - no_dwell_high, zero_crossing, op_mode, playback_rate) - } - - /* - * Configure a frequency sweep RAM mode profile - */ - pub unsafe fn set_frequency_sweep_profile(&mut self, profile: u8, start_addr: u16, - lower_boundary: f64, upper_boundary: f64, f_resolution: f64, - no_dwell_high: bool, op_mode: RAMOperationMode, playback_rate: f64 - ) -> Result<(), Error> { - - // Check the legality of the profile setup - assert!(profile <= 7); - assert!(start_addr < 1024); - - // Find out the required RAM size - // Frequencies may have to be repeated if the playback rate is too low - // Reject impossible setups - // E.g. Higher playback rate than f_dds_clk, insufficient RAM allocation - let nominal_step_rate = self.f_sys_clk/(4.0 * playback_rate); - if nominal_step_rate < 1.0 { - return Err(Error::DDSRAMError); - } - - // TODO: Handle unfortunate scenario: step_rate / 0xFFFF gives a round number - // Current implmentation unnecessarily allocates 1 extra RAM space for each data - let duplication = (nominal_step_rate / (0xFFFF as f64)) as u64 + 1; - - // Acquire the RAM size needed by multiplying duplication. - // All data needs to be duplicated such that a desired step_rate can be reached - // Return DDS RAM Error if it does not fix into the RAM - let span = upper_boundary - lower_boundary; - let data_size = if core::intrinsics::roundf64(span/f_resolution) == (span/f_resolution) { - (span/f_resolution) as u64 + 1 - } else { - (span/f_resolution) as u64 - }; - let ram_size = data_size * duplication; - - let end_addr = (start_addr as u64) + ram_size - 1; - trace!("Required RAM size: {}", ram_size); - if end_addr >= 1024 { - warn!("RAM address out of bound"); - return Err(Error::DDSRAMError); - } - - // Clear RAM vector, and add address byte - RAM_VEC.clear(); - RAM_VEC.push(0x16) - .map_err(|_| Error::DDSRAMError)?; - - // Drop in the data into RAM_VEC - for data_index in 0..data_size { - let freq = lower_boundary + f_resolution * (data_index as f64); - for _ in 0..duplication { - let ftw = self.frequency_to_ftw(freq); - RAM_VEC.push(((ftw >> 24) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((ftw >> 16) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((ftw >> 8) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - RAM_VEC.push(((ftw >> 0) & 0xFF) as u8) - .map_err(|_| Error::DDSRAMError)?; - } - } - - debug!("start_addr: {}\nend_addr: {}\n, duplication: {}\n, data_size: {}\n", - start_addr, end_addr, duplication, data_size); - - self.set_ram_profile(profile, start_addr, end_addr.try_into().unwrap(), RAMDestination::Frequency, - no_dwell_high, true, op_mode, playback_rate * (duplication as f64)) - } - - /* - * Configure a RAM mode profile, w.r.t static vector (RAM_VEC) - */ - fn set_ram_profile(&mut self, profile: u8, start_addr: u16, end_addr: u16, - ram_dst: RAMDestination, no_dwell_high: bool, zero_crossing: bool, - op_mode: RAMOperationMode, playback_rate: f64 - ) -> Result<(), Error> { - - // Check the legality of the profile setup - assert!(profile <= 7); - assert!(end_addr >= start_addr); - assert!(end_addr < 1024); - // assert_eq! RAM_VEC.len() as u16, ((end_addr - start_addr + 1) * 4) + 1); - - // Calculate address step rate, and check legality - let step_rate = (self.f_sys_clk/(4.0 * playback_rate)) as u64; - trace!("Setting up RAM profile, step_rate: {}", step_rate); - if step_rate == 0 || step_rate > 0xFFFF { - return Err(Error::DDSRAMError); - } - - // Before setting up RAM, disable RAM_ENABLE - self.enable_ram_configuration(ram_dst.clone())?; - - // Write a RAM profile, but include all data in RAM self.write_register(0x0E + profile, &mut [ 0x00, - ((step_rate >> 8) & 0xFF).try_into().unwrap(), - ((step_rate >> 0) & 0xFF).try_into().unwrap(), + ((ramp_rate >> 8) & 0xFF).try_into().unwrap(), + ((ramp_rate >> 0) & 0xFF).try_into().unwrap(), ((end_addr >> 2) & 0xFF).try_into().unwrap(), ((end_addr & 0x3) << 6).try_into().unwrap(), ((start_addr >> 2) & 0xFF).try_into().unwrap(), ((start_addr & 0x3) << 6).try_into().unwrap(), ((no_dwell_high as u8) << 5) | ((zero_crossing as u8) << 3) | (op_mode as u8) - ])?; + ]) + } + + pub fn get_ram_profile(&mut self, profile: u8) -> Result<(u16, u16, u16, u8), Error> { + assert!(profile <= 7); - // Temporarily disable RAM mode while accessing into RAM - self.disable_ram_configuration()?; - unsafe { - self.write_ram()?; - } + let mut buffer: [u8; 8] = [0; 8]; + self.read_register(0x0E + profile, &mut buffer)?; + let start = u16::from_be_bytes([buffer[5], buffer[6]]) >> 6; + let end = u16::from_be_bytes([buffer[3], buffer[4]]) >> 6; + let stride = u16::from_be_bytes([buffer[1], buffer[2]]); + let op_mode = buffer[7] & 0x07; - // Properly configure start_addr and end_addr - self.enable_ram_configuration(ram_dst) + Ok((start, end, stride, op_mode)) + } + pub fn ram_mode_enabled(&mut self) -> Result> { + let mut ram_query = [(DDSCFRMask::RAM_ENABLE, 0)]; + self.get_configurations(&mut ram_query)?; + Ok(ram_query[0].1 != 0) } // Calculate ftw (frequency tuning word) - fn frequency_to_ftw(&mut self, f_out: f64) -> u32 { + pub fn frequency_to_ftw(&mut self, f_out: f64) -> u32 { let f_res: u64 = 1 << 32; ((f_res as f64) * f_out / self.f_sys_clk) as u32 } @@ -811,11 +625,36 @@ where ((amp_res as f64) * amplitude) as u16 } - // Write data in RAM - unsafe fn write_ram(&mut self) -> Result<(), Error> { + // Write RAM bytes into DDS channel + // Assume profile 7 is selected by the CPLD in prior + pub unsafe fn commit_ram_buffer(&mut self, start_addr: u16, ram_dest: RAMDestination) -> Result<(), Error> { + let ram_size = ((RAM_VEC.len() - 1) as u16)/4; + if RAM_VEC.len() == 0 || RAM_VEC[0] != 0x16 || + (start_addr + ram_size) > 1024 || start_addr >= 1024 { + return Err(Error::DDSRAMError) + } + + let end_addr: [u8; 2] = ((ram_size + start_addr - 1) << 6).to_be_bytes(); + + // Use profile 7 to setup a temperory RAM profile + self.enable_ram_configuration(ram_dest.clone())?; + self.write_register(0x15, &mut [ + 0x00, + 0x00, 0x01, + end_addr[0], end_addr[1], + ((start_addr >> 2) & 0xFF).try_into().unwrap(), + ((start_addr & 0x3) << 6).try_into().unwrap(), + 0x00 + ])?; + self.disable_ram_configuration()?; + + log::info!("RAM buffer: {:?}", RAM_VEC); self.spi.transfer(&mut RAM_VEC) - .map(|_| ()) - .map_err(Error::SPI) + .map(|_| ()) + .map_err(Error::SPI)?; + RAM_VEC.clear(); + self.ram_dest = ram_dest; + self.enable_ram_configuration(ram_dest) } /* @@ -954,3 +793,19 @@ impl_register_io!( 0x14, 8, 0x15, 8 ); + +// Append bytes to the RAM buffer +pub unsafe fn append_ram_byte(data: &[u8]) { + assert!(data.len() <= 4096); + + // Add RAM address if needed + if RAM_VEC.len() == 0 { + RAM_VEC.push(0x16).unwrap(); + } else if RAM_VEC[0] != 0x16 || (data.len() + RAM_VEC.len()) >= RAM_VEC.capacity() { + RAM_VEC.clear(); + RAM_VEC.push(0x16).unwrap(); + } + + RAM_VEC.extend_from_slice(data).unwrap(); + log::info!("RAM buffer: {:?}", RAM_VEC); +} diff --git a/src/mqtt_mux.rs b/src/mqtt_mux.rs index 918f40e..7bd0871 100644 --- a/src/mqtt_mux.rs +++ b/src/mqtt_mux.rs @@ -15,9 +15,10 @@ use crate::urukul::Urukul; use crate::urukul::Error; use crate::flash_store::{ FlashStore, update_flash }; use crate::flash::Flash; -use crate::config::{ UrukulConfig, ChannelConfig }; +use crate::config::{ UrukulConfig, ChannelConfig, ProfileSetup, SingleTone, RAM }; +use crate::dds::{ RAMDestination, RAMOperationMode }; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum MqttTopic { Reset, Switch(u8), @@ -34,6 +35,11 @@ pub enum MqttTopic { Profile, Save, Load, + DefaultFTW(u8), + DefaultASF(u8), + AppendBytes, + CommitBuffer, + RAM(u8, u8), } // Prossible change: Make this enum public to all comm protocol (if any) @@ -56,6 +62,11 @@ pub enum MqttCommand { Profile(u8), Save, Load, + DefaultFTW(u8, f64), + DefaultASF(u8, f64), + AppendBytes(usize), + CommitBuffer(RAMDestination, u16, u8), + RAM(u8, u8, u16, u16, RAMOperationMode, u16), } pub struct MqttMux<'s, SPI> { @@ -118,13 +129,39 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer { channel as u8, channel_config.sys_clk )?; - self.urukul.set_channel_single_tone_profile( + self.urukul.set_channel_default_ftw( channel as u8, - profile, - channel_config.freq, - channel_config.phase, - channel_config.asf, + channel_config.freq )?; + self.urukul.set_channel_default_asf( + channel as u8, + channel_config.asf + )?; + if let ProfileSetup::Singletone(singletone) = channel_config.profile { + self.urukul.set_channel_single_tone_profile( + channel as u8, + profile, + singletone.freq, + singletone.phase, + singletone.asf + )?; + } else if let ProfileSetup::RAM(ram) = channel_config.profile { + let op_mode = match ram.op_mode { + 0 => RAMOperationMode::DirectSwitch, + 1 => RAMOperationMode::RampUp, + 2 => RAMOperationMode::BidirectionalRamp, + 3 => RAMOperationMode::ContinuousBidirectionalRamp, + _ => RAMOperationMode::ContinuousRecirculate, + }; + self.urukul.set_channel_ram_profile( + channel as u8, + profile, + ram.start, + ram.end, + op_mode, + ram.stride + )?; + } }, None => () }; @@ -151,10 +188,26 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer { unsafe { self.flash_store.write_value("urukul", &urukul_config, &mut SERDE_BUFFER).unwrap(); } for channel in 0..4 { - let (freq, phase, asf) = self.urukul.get_channel_single_tone_profile( - channel as u8, - urukul_config.profile - )?; + let ram = self.urukul.get_channel_ram_mode_enabled(channel as u8)?; + let profile_setup = if ram { + let (start, end, stride, op_mode) = self.urukul.get_channel_ram_profile( + channel as u8, + urukul_config.profile + )?; + let ram_profile = RAM { + start, end, stride, op_mode + }; + ProfileSetup::RAM(ram_profile) + } else { + let (freq, phase, asf) = self.urukul.get_channel_single_tone_profile( + channel as u8, + urukul_config.profile + )?; + let singletone_profile = SingleTone { + freq, phase, asf + }; + ProfileSetup::Singletone(singletone_profile) + }; let channel_config = ChannelConfig { sw: { self.urukul.get_channel_switch_status(channel as u32)? @@ -165,9 +218,13 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer { sys_clk: { self.urukul.get_channel_sys_clk(channel as u8)? }, - freq, - phase, - asf + freq: { + self.urukul.get_channel_default_ftw(channel as u8)? + }, + asf: { + self.urukul.get_channel_default_asf(channel as u8)? + }, + profile: profile_setup }; unsafe { self.flash_store.write_value(CHANNELS[channel], &channel_config, &mut SERDE_BUFFER).unwrap(); @@ -188,13 +245,26 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer { return; } }; - let command = match self.parse_message(topic, message) { - Ok((_, cmd)) => cmd, - Err(_) => { - self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot parse MQTT message")); + + // All MQTT messages should be parsed except appending bytes to buffer + // Directly pass the buffer to urukul in this case + let command = if topic == MqttTopic::AppendBytes { + let length = message.len(); + if self.urukul.append_dds_ram_buffer(message).is_err() { + self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot push bytes to buffer")); return; } + MqttCommand::AppendBytes(length) + } else { + match self.parse_message(topic, message) { + Ok((_, cmd)) => cmd, + Err(_) => { + self.yet_to_respond = Some(MqttCommand::ProcessError("Cannot parse MQTT message")); + return; + } + } }; + self.yet_to_respond = match self.execute(command.clone()) { Err(_) => Some(MqttCommand::ProcessError("Cannot execute MQTT command")), Ok(()) => Some(command) @@ -409,6 +479,110 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer { )).map_err(|_| Error::VectorOutOfSpace)?; Ok(vec) } + + MqttCommand::DefaultFTW(ch, _freq) => { + vec.push(( + { + let mut topic_string = String::from(self.name); + topic_string.push_str("/Feedback/Channel") + .map_err(|_| Error::StringOutOfSpace)?; + topic_string.push(char::from_digit(ch.into(), 10).unwrap()) + .map_err(|_| Error::StringOutOfSpace)?; + topic_string.push_str("/Background/Frequency") + .map_err(|_| Error::StringOutOfSpace)?; + topic_string + }, + { + let freq = self.urukul.get_channel_default_ftw(ch)?; + let mut message_str = String::from( + self.float_buffer.format_finite(freq) + ); + message_str.push_str(" Hz") + .map_err(|_| Error::StringOutOfSpace)?; + message_str + } + )).map_err(|_| Error::VectorOutOfSpace)?; + Ok(vec) + } + + MqttCommand::DefaultASF(ch, _ampl) => { + vec.push(( + { + let mut topic_string = String::from(self.name); + topic_string.push_str("/Feedback/Channel") + .map_err(|_| Error::StringOutOfSpace)?; + topic_string.push(char::from_digit(ch.into(), 10).unwrap()) + .map_err(|_| Error::StringOutOfSpace)?; + topic_string.push_str("/Background/Amplitude") + .map_err(|_| Error::StringOutOfSpace)?; + topic_string + }, + { + let ampl = self.urukul.get_channel_default_asf(ch)?; + let message_str = String::from( + self.float_buffer.format_finite(ampl) + ); + message_str + } + )).map_err(|_| Error::VectorOutOfSpace)?; + Ok(vec) + } + + MqttCommand::AppendBytes(len) => { + vec.push(( + { + let mut topic_string = String::from(self.name); + topic_string.push_str("/Feedback/Buffer/Append") + .map_err(|_| Error::StringOutOfSpace)?; + topic_string + }, + { + let mut message_str = String::from("Pushed "); + message_str.push_str(self.float_buffer.format_finite(len as f64)) + .map_err(|_| Error::StringOutOfSpace)?; + message_str.push_str(" bytes to buffer.") + .map_err(|_| Error::StringOutOfSpace)?; + message_str + } + )).map_err(|_| Error::VectorOutOfSpace)?; + Ok(vec) + } + + MqttCommand::CommitBuffer(_dest, _start_addr, ch) => { + vec.push(( + { + let mut topic_string = String::from(self.name); + topic_string.push_str("/Feedback/Buffer/Commit") + .map_err(|_| Error::StringOutOfSpace)?; + topic_string + }, + { + let mut message_str = String::from("Pushed bytes to channel "); + message_str.push(char::from_digit(ch.into(), 10).unwrap()) + .map_err(|_| Error::StringOutOfSpace)?; + message_str + } + )).map_err(|_| Error::VectorOutOfSpace)?; + Ok(vec) + } + + MqttCommand::RAM(ch, _pr, _start_addr, _end_addr, _op_mode, _) => { + vec.push(( + { + let mut topic_string = String::from(self.name); + topic_string.push_str("/Feedback/RAM") + .map_err(|_| Error::StringOutOfSpace)?; + topic_string + }, + { + let mut message_str = String::from("Selected RAM profile for channel "); + message_str.push(char::from_digit(ch.into(), 10).unwrap()) + .map_err(|_| Error::StringOutOfSpace)?; + message_str + } + )).map_err(|_| Error::VectorOutOfSpace)?; + Ok(vec) + } }, None => Ok(vec), } @@ -605,6 +779,41 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer { return Ok(MqttTopic::Load); } + "Background/Frequency" => { + if !assigned_channel || assigned_profile { + return Err(Error::MqttCommandError) + } + return Ok(MqttTopic::DefaultFTW(channel)); + } + + "Background/Amplitude" => { + if !assigned_channel || assigned_profile { + return Err(Error::MqttCommandError) + } + return Ok(MqttTopic::DefaultASF(channel)); + } + + "Buffer/Append" => { + if assigned_channel || assigned_profile { + return Err(Error::MqttCommandError) + } + return Ok(MqttTopic::AppendBytes); + } + + "Buffer/Commit" => { + if assigned_channel || assigned_profile { + return Err(Error::MqttCommandError) + } + return Ok(MqttTopic::CommitBuffer); + } + + "RAM" => { + if !assigned_channel || !assigned_profile { + return Err(Error::MqttCommandError) + } + return Ok(MqttTopic::RAM(channel, profile)); + } + _ => return Err(Error::MqttCommandError), }; } @@ -627,6 +836,11 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer { MqttTopic::Profile => profile_message(message), MqttTopic::Save => Ok((message, MqttCommand::Save)), MqttTopic::Load => Ok((message, MqttCommand::Load)), + MqttTopic::DefaultFTW(ch) => default_frequency_message(ch, message), + MqttTopic::DefaultASF(ch) => default_amplitude_message(ch, message), + MqttTopic::AppendBytes => unreachable!(), // This topic should not be parsed + MqttTopic::CommitBuffer => commit_buffer_message(message), + MqttTopic::RAM(ch, pr) => ram_message(ch, pr, message), } } @@ -648,6 +862,13 @@ impl<'s, SPI, E> MqttMux<'s, SPI> where SPI: Transfer { MqttCommand::Profile(prof) => self.urukul.set_profile(prof), MqttCommand::Save => self.save_device(), MqttCommand::Load => self.load_device(), + MqttCommand::DefaultFTW(ch, freq) => self.urukul.set_channel_default_ftw(ch, freq), + MqttCommand::DefaultASF(ch, ampl) => self.urukul.set_channel_default_asf(ch, ampl), + MqttCommand::AppendBytes(_) => Ok(()), // The bytes were not parsed and pushed + MqttCommand::CommitBuffer(dest, start_addr, ch) => self.urukul.commit_ram_buffer_to_channel(ch, start_addr, dest), + MqttCommand::RAM(ch, pr, start_addr, end_addr, op_mode, ramp_rate) => { + self.urukul.set_channel_ram_profile(ch, pr, start_addr, end_addr, op_mode, ramp_rate) + } } } @@ -951,7 +1172,17 @@ fn singletone_frequency_message(channel: u8, profile: u8, message: &[u8]) -> IRe )(message) } -// Parser for Singletone AMplitude Command Message +// Parser for default frequency Command Message +fn default_frequency_message(channel: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> { + all_consuming( + map( + read_frequency, + |freq: f64| MqttCommand::DefaultFTW(channel, freq) + ) + )(message) +} + +// Parser for Singletone Amplitude Command Message fn singletone_amplitude_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> { all_consuming( map( @@ -961,6 +1192,16 @@ fn singletone_amplitude_message(channel: u8, profile: u8, message: &[u8]) -> IRe )(message) } +// Parser for Default Amplitude Command Message +fn default_amplitude_message(channel: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> { + all_consuming( + map( + double, + |ampl: f64| MqttCommand::DefaultASF(channel, ampl) + ) + )(message) +} + // Parser for Phase Command Message fn singletone_phase_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> { all_consuming( @@ -994,24 +1235,6 @@ fn singletone_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8] ) ) ), - preceded( - tag_no_case("phase:"), - preceded( - whitespace, - terminated( - double, - preceded( - opt( - preceded( - whitespace, - tag_no_case("deg") - ) - ), - message_separator - ) - ) - ) - ), preceded( tag_no_case("amplitude:"), preceded( @@ -1021,9 +1244,29 @@ fn singletone_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8] message_separator ) ) + ), + opt( + preceded( + tag_no_case("phase:"), + preceded( + whitespace, + terminated( + double, + preceded( + opt( + preceded( + whitespace, + tag_no_case("deg") + ) + ), + message_separator + ) + ) + ) + ) ) )), - |(freq, phase, ampl): (f64, f64, f64)| MqttCommand::Singletone(channel, profile, freq, phase, ampl) + |(freq, ampl, phase): (f64, f64, Option)| MqttCommand::Singletone(channel, profile, freq, phase.unwrap_or(0.0), ampl) ) )(message) } @@ -1040,3 +1283,123 @@ fn profile_message(message: &[u8]) -> IResult<&[u8], MqttCommand> { ) )(message) } + +fn commit_buffer_message(message: &[u8]) -> IResult<&[u8], MqttCommand> { + all_consuming( + map( + permutation(( + preceded( + tag_no_case("dest:"), + preceded( + whitespace, + terminated( + alt(( + value(RAMDestination::Frequency, tag_no_case("frequency")), + value(RAMDestination::Amplitude, tag_no_case("amplitude")), + value(RAMDestination::Phase, tag_no_case("phase")), + value(RAMDestination::Polar, tag_no_case("polar")), + )), + message_separator + ) + ) + ), + preceded( + tag_no_case("start:"), + preceded( + whitespace, + terminated( + map_res( + digit1, + |start_addr: &[u8]| u16::from_str_radix(core::str::from_utf8(start_addr).unwrap(), 10) + ), + message_separator + ) + ) + ), + preceded( + tag_no_case("ch:"), + preceded( + whitespace, + terminated( + map_res( + digit1, + |ch: &[u8]| u8::from_str_radix(core::str::from_utf8(ch).unwrap(), 10) + ), + message_separator + ) + ) + ) + )), + |(dest, start_addr, ch): (RAMDestination, u16, u8)| { + MqttCommand::CommitBuffer(dest, start_addr, ch) + } + ) + )(message) +} + +fn ram_message(channel: u8, profile: u8, message: &[u8]) -> IResult<&[u8], MqttCommand> { + all_consuming( + map( + permutation(( + preceded( + tag_no_case("start:"), + preceded( + whitespace, + terminated( + map_res( + digit1, + |ch: &[u8]| u16::from_str_radix(core::str::from_utf8(ch).unwrap(), 10) + ), + message_separator + ) + ) + ), + preceded( + tag_no_case("end:"), + preceded( + whitespace, + terminated( + map_res( + digit1, + |ch: &[u8]| u16::from_str_radix(core::str::from_utf8(ch).unwrap(), 10) + ), + message_separator + ) + ) + ), + preceded( + tag_no_case("op_mode:"), + preceded( + whitespace, + terminated( + alt(( + value(RAMOperationMode::DirectSwitch, tag_no_case("DS")), + value(RAMOperationMode::RampUp, tag_no_case("RU")), + value(RAMOperationMode::BidirectionalRamp, tag_no_case("BDR")), + value(RAMOperationMode::ContinuousBidirectionalRamp, tag_no_case("CBDR")), + value(RAMOperationMode::ContinuousRecirculate, tag_no_case("CR")), + )), + message_separator + ) + ) + ), + preceded( + tag_no_case("ramp:"), + preceded( + whitespace, + terminated( + map_res( + digit1, + |ch: &[u8]| u16::from_str_radix(core::str::from_utf8(ch).unwrap(), 10) + ), + message_separator + ) + ) + ) + )), + |(start_addr, end_addr, op_mode, ramp_rate): (u16, u16, RAMOperationMode, u16)| { + MqttCommand::RAM(channel, profile, start_addr, end_addr, op_mode, ramp_rate) + } + ) + )(message) +} diff --git a/src/urukul.rs b/src/urukul.rs index 9ca3cdf..1878fee 100644 --- a/src/urukul.rs +++ b/src/urukul.rs @@ -9,7 +9,7 @@ use crate::config_register::ConfigRegister; use crate::config_register::CFGMask; use crate::config_register::StatusMask; use crate::attenuator::Attenuator; -use crate::dds::{ DDS, RAMOperationMode }; +use crate::dds::{ DDS, RAMOperationMode, RAMDestination }; /* * Enum for structuring error @@ -56,7 +56,7 @@ pub struct Urukul { config_register: ConfigRegister, attenuator: Attenuator, multi_dds: DDS, - dds: [DDS; 4], + pub dds: [DDS; 4], f_master_clk: f64, } @@ -329,14 +329,52 @@ where self.dds[usize::from(channel)].set_single_tone_profile_amplitude(profile, amplitude) } - pub fn set_channel_frequency_sweep_profile(&mut self, channel: u8, profile: u8, start_addr: u16, lower_boundary: f64, - upper_boundary: f64, f_resolution: f64, playback_rate: f64) -> Result<(), Error> - { + pub fn append_dds_ram_buffer(&mut self, data: &[u8]) -> Result<(), Error> { + unsafe { + Ok(crate::dds::append_ram_byte(data)) + } + } + + // Use profile 7 to write into the RAM + pub fn commit_ram_buffer_to_channel(&mut self, channel: u8, start_addr: u16, ram_dest: RAMDestination) -> Result<(), Error> { + let profile = self.get_profile()?; + self.set_profile(7)?; unsafe { self.dds[usize::from(channel)] - .set_frequency_sweep_profile(profile, start_addr, lower_boundary, upper_boundary, - f_resolution, true, RAMOperationMode::ContinuousRecirculate, playback_rate) + .commit_ram_buffer(start_addr, ram_dest)?; } + self.set_profile(profile) + } + + pub fn set_channel_default_ftw(&mut self, channel: u8, frequency: f64) -> Result<(), Error> { + self.dds[usize::from(channel)].set_default_ftw(frequency) + } + + pub fn set_channel_default_asf(&mut self, channel: u8, amplitude_scale: f64) -> Result<(), Error> { + self.dds[usize::from(channel)].set_default_asf(amplitude_scale) + } + + pub fn get_channel_default_ftw(&mut self, channel: u8) -> Result> { + self.dds[usize::from(channel)].get_default_ftw() + } + + pub fn get_channel_default_asf(&mut self, channel: u8) -> Result> { + self.dds[usize::from(channel)].get_default_asf() + } + + pub fn set_channel_ram_profile(&mut self, channel: u8, profile: u8, start_addr: u16, + end_addr: u16, op_mode: RAMOperationMode, ramp_rate: u16 + ) -> Result<(), Error> { + self.dds[usize::from(channel)] + .set_up_ram_profile(profile, start_addr, end_addr, true, false, op_mode, ramp_rate) + } + + pub fn get_channel_ram_profile(&mut self, channel: u8, profile: u8) -> Result<(u16, u16, u16, u8), Error> { + self.dds[usize::from(channel)].get_ram_profile(profile) + } + + pub fn get_channel_ram_mode_enabled(&mut self, channel: u8) -> Result> { + self.dds[usize::from(channel)].ram_mode_enabled() } pub fn set_channel_sys_clk(&mut self, channel: u8, f_sys_clk: f64) -> Result<(), Error> {