From db182b923d82c3e8c6f5a5a02fa27be15585b7ec Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 21 Oct 2020 10:17:22 +0200 Subject: [PATCH 01/19] Adding WIP QSPI streaming --- Cargo.toml | 5 ++-- ad9959/src/lib.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++-- pounder_test.py | 4 +-- src/main.rs | 34 +++++++++++++++++++++-- src/pounder/mod.rs | 11 ++------ 5 files changed, 102 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0595c33..db15743 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,9 +59,8 @@ features = ["stm32h743v"] [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven"] - -[patch.crates-io] -stm32h7xx-hal = { git = "https://github.com/quartiq/stm32h7xx-hal.git", branch = "feature/pounder-support" } +git = "https://github.com/quartiq/stm32h7xx-hal.git" +branch = "feature/pounder-support" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index d12041c..a537397 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -376,9 +376,6 @@ where // Latch the configuration and restore the previous CSR. Note that the re-enable of the // channel happens immediately, so the CSR update does not need to be latched. self.latch_configuration()?; - self.interface - .write(Register::CSR as u8, &csr) - .map_err(|_| Error::Interface)?; Ok(()) } @@ -571,4 +568,69 @@ where Ok(tuning_word as f64 * self.system_clock_frequency() / (1u64 << 32) as f64) } + + pub fn write_profile(&mut self, channel: Channel, freq: f64, turns: f32, amplitude: f32) -> Result<(), Error> { + // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the + // frequency tuning word and f_s is the system clock rate. + let tuning_word: u32 = + ((freq as f64 / self.system_clock_frequency()) + * 1u64.wrapping_shl(32) as f64) as u32; + + let phase_offset: u16 = (turns * (1 << 14) as f32) as u16 & 0x3FFFu16; + + let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16; + let mut acr: [u8; 3] = [0; 3]; + + // Enable the amplitude multiplier for the channel if required. The amplitude control has + // full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever + // full-scale is used. + if amplitude_control < (1 << 10) { + let masked_control = amplitude_control & 0x3FF; + acr[1] = masked_control.to_be_bytes()[0]; + acr[2] = masked_control.to_be_bytes()[1]; + + // Enable the amplitude multiplier + acr[1].set_bit(4, true); + } + + self.modify_channel_closure(channel, |interface| { + let mut data: [u8; 11] = [0; 11]; + data[0..2].copy_from_slice(&phase_offset.to_be_bytes()); + data[2] = Register::CFTW0 as u8; + data[3..7].copy_from_slice(&tuning_word.to_be_bytes()); + data[7] = Register::ACR as u8; + data[8..11].copy_from_slice(&acr); + interface.write(Register::CPOW0 as u8, &data).map_err(|_| Error::Interface) + })?; + + Ok(()) + } + + fn modify_channel_closure(&mut self, channel: Channel, f: F) -> Result<(), Error> + where + F: FnOnce(&mut INTERFACE) -> Result<(), Error>, + { + // Disable all other outputs so that we can update the configuration register of only the + // specified channel. + let mut csr: [u8; 1] = [0]; + self.interface + .read(Register::CSR as u8, &mut csr) + .map_err(|_| Error::Interface)?; + + let mut new_csr = csr; + new_csr[0].set_bits(4..8, 0); + new_csr[0].set_bit(4 + channel as usize, true); + + self.interface + .write(Register::CSR as u8, &new_csr) + .map_err(|_| Error::Interface)?; + + let result = f(&mut self.interface); + + // Latch the configuration and restore the previous CSR. Note that the re-enable of the + // channel happens immediately, so the CSR update does not need to be latched. + self.latch_configuration()?; + + result + } } diff --git a/pounder_test.py b/pounder_test.py index a50856a..5d43dd3 100644 --- a/pounder_test.py +++ b/pounder_test.py @@ -91,11 +91,11 @@ def main(): # A sample configuration for an output channel. channel_config = { - 'attenuation': 31.5, + 'attenuation': 0.0, 'parameters': { 'phase_offset': 0.5, 'frequency': 100.0e6, - 'amplitude': 0.2, + 'amplitude': 1.0, 'enabled': True, } } diff --git a/src/main.rs b/src/main.rs index 9e0c0ff..a766914 100644 --- a/src/main.rs +++ b/src/main.rs @@ -187,8 +187,7 @@ const APP: () = { 'static, 'static, 'static, - ethernet::EthernetDMA<'static>, - >, + ethernet::EthernetDMA<'static>>, eth_mac: ethernet::EthernetMAC, mac_addr: net::wire::EthernetAddress, @@ -654,7 +653,7 @@ const APP: () = { ); // Configure timer 2 to trigger conversions for the ADC - let mut timer2 = dp.TIM2.timer(500.khz(), &mut clocks); + let mut timer2 = dp.TIM2.timer(50.khz(), &mut clocks); timer2.configure_channel(hal::timer::Channel::One, 0.25); timer2.configure_channel(hal::timer::Channel::Two, 0.75); @@ -746,6 +745,35 @@ const APP: () = { // TODO: Replace with reference to CPU clock from CCDR. next_ms += 400_000.cycles(); + match c.resources.pounder { + Some(pounder) => { + + let state = pounder::ChannelState { + parameters: pounder::DdsChannelState { + phase_offset: 0.0, + frequency: 100_000_000.0, + amplitude: 1.0, + enabled: true, + }, + attenuation: 10.0, + }; + + let state1 = pounder::ChannelState { + parameters: pounder::DdsChannelState { + phase_offset: 0.5, + frequency: 50_000_000.0, + amplitude: 1.0, + enabled: true, + }, + attenuation: 10.0, + }; + + pounder.set_channel_state(pounder::Channel::Out0, state).unwrap(); + pounder.set_channel_state(pounder::Channel::Out1, state1).unwrap(); + }, + _ => panic!("Failed"), + } + loop { let tick = Instant::now() > next_ms; diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index 1288765..97c9fd5 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -474,15 +474,8 @@ where channel: Channel, state: ChannelState, ) -> Result<(), Error> { - self.ad9959 - .set_frequency(channel.into(), state.parameters.frequency) - .map_err(|_| Error::Dds)?; - self.ad9959 - .set_phase(channel.into(), state.parameters.phase_offset) - .map_err(|_| Error::Dds)?; - self.ad9959 - .set_amplitude(channel.into(), state.parameters.amplitude) - .map_err(|_| Error::Dds)?; + self.ad9959.write_profile(channel.into(), state.parameters.frequency, + state.parameters.phase_offset, state.parameters.amplitude).map_err(|_| Error::Dds)?; if state.parameters.enabled { self.ad9959 From 071ccd17dcd383f764fca12ed8d7bb4ca9954b7a Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 22 Oct 2020 16:16:38 +0200 Subject: [PATCH 02/19] Adding WIP experimental code --- Cargo.lock | 3 +- Cargo.toml | 5 +- ad9959/src/lib.rs | 161 ++++++++++++++------------------------------- src/main.rs | 10 ++- src/pounder/mod.rs | 45 ++++++------- 5 files changed, 82 insertions(+), 142 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 082a02c..56f8eed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,7 +462,7 @@ dependencies = [ "serde-json-core", "smoltcp", "stm32h7-ethernet", - "stm32h7xx-hal 0.5.0 (git+https://github.com/quartiq/stm32h7xx-hal.git?branch=feature/pounder-support)", + "stm32h7xx-hal 0.5.0", ] [[package]] @@ -497,7 +497,6 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.5.0" -source = "git+https://github.com/quartiq/stm32h7xx-hal.git?branch=feature/pounder-support#ff00e938f2b226211c178f26c092f36462c44404" dependencies = [ "bare-metal", "cast", diff --git a/Cargo.toml b/Cargo.toml index db15743..578420e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,8 +59,9 @@ features = ["stm32h743v"] [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven"] -git = "https://github.com/quartiq/stm32h7xx-hal.git" -branch = "feature/pounder-support" +# git = "https://github.com/quartiq/stm32h7xx-hal.git" +# branch = "feature/pounder-support" +path = "../stm32h7xx-hal" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index a537397..1a5de18 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -14,11 +14,12 @@ use embedded_hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; /// The chip supports a number of serial interfaces to improve data throughput, including normal, /// dual, and quad SPI configurations. pub struct Ad9959 { - interface: INTERFACE, + pub interface: INTERFACE, delay: DELAY, reference_clock_frequency: f32, system_clock_multiplier: u8, io_update: UPDATE, + communication_mode: Mode, } /// A trait that allows a HAL to provide a means of communicating with the AD9959. @@ -30,6 +31,8 @@ pub trait Interface { fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>; fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>; + + fn write_profile(&mut self, data: [u32; 4]) -> Result<(), Self::Error>; } /// Indicates various communication modes of the DDS. The value of this enumeration is equivalent to @@ -125,6 +128,7 @@ where delay, reference_clock_frequency: clock_frequency, system_clock_multiplier: 1, + communication_mode: desired_mode, }; ad9959.io_update.set_low().or_else(|_| Err(Error::Pin))?; @@ -176,7 +180,8 @@ where } /// Latch the DDS configuration to ensure it is active on the output channels. - fn latch_configuration(&mut self) -> Result<(), Error> { + pub fn latch_configuration(&mut self) -> Result<(), Error> { + self.delay.delay_ms(2); self.io_update.set_high().or_else(|_| Err(Error::Pin))?; // The SYNC_CLK is 1/4 the system clock frequency. The IO_UPDATE pin must be latched for one // full SYNC_CLK pulse to register. For safety, we latch for 5 here. @@ -199,7 +204,7 @@ where &mut self, reference_clock_frequency: f32, multiplier: u8, - ) -> Result { + ) -> Result { self.reference_clock_frequency = reference_clock_frequency; if multiplier != 1 && (multiplier > 20 || multiplier < 4) { @@ -207,8 +212,8 @@ where } let frequency = - multiplier as f64 * self.reference_clock_frequency as f64; - if frequency > 500_000_000.0f64 { + multiplier as f32 * self.reference_clock_frequency as f32; + if frequency > 500_000_000.0f32 { return Err(Error::Frequency); } @@ -227,6 +232,8 @@ where .map_err(|_| Error::Interface)?; self.system_clock_multiplier = multiplier; + self.latch_configuration()?; + Ok(self.system_clock_frequency()) } @@ -299,47 +306,9 @@ where } /// Get the current system clock frequency in Hz. - fn system_clock_frequency(&self) -> f64 { - self.system_clock_multiplier as f64 - * self.reference_clock_frequency as f64 - } - - /// Enable an output channel. - pub fn enable_channel(&mut self, channel: Channel) -> Result<(), Error> { - let mut csr: [u8; 1] = [0]; - self.interface - .read(Register::CSR as u8, &mut csr) - .map_err(|_| Error::Interface)?; - csr[0].set_bit(channel as usize + 4, true); - self.interface - .write(Register::CSR as u8, &csr) - .map_err(|_| Error::Interface)?; - - Ok(()) - } - - /// Disable an output channel. - pub fn disable_channel(&mut self, channel: Channel) -> Result<(), Error> { - let mut csr: [u8; 1] = [0]; - self.interface - .read(Register::CSR as u8, &mut csr) - .map_err(|_| Error::Interface)?; - csr[0].set_bit(channel as usize + 4, false); - self.interface - .write(Register::CSR as u8, &csr) - .map_err(|_| Error::Interface)?; - - Ok(()) - } - - /// Determine if an output channel is enabled. - pub fn is_enabled(&mut self, channel: Channel) -> Result { - let mut csr: [u8; 1] = [0; 1]; - self.interface - .read(Register::CSR as u8, &mut csr) - .map_err(|_| Error::Interface)?; - - Ok(csr[0].get_bit(channel as usize + 4)) + fn system_clock_frequency(&self) -> f32 { + self.system_clock_multiplier as f32 + * self.reference_clock_frequency as f32 } /// Update an output channel configuration register. @@ -356,17 +325,12 @@ where ) -> Result<(), Error> { // Disable all other outputs so that we can update the configuration register of only the // specified channel. - let mut csr: [u8; 1] = [0]; - self.interface - .read(Register::CSR as u8, &mut csr) - .map_err(|_| Error::Interface)?; - - let mut new_csr = csr; - new_csr[0].set_bits(4..8, 0); - new_csr[0].set_bit(4 + channel as usize, true); + let csr: u8 = *0x00_u8 + .set_bits(1..=2, self.communication_mode as u8) + .set_bit(4 + channel as usize, true); self.interface - .write(Register::CSR as u8, &new_csr) + .write(Register::CSR as u8, &[csr]) .map_err(|_| Error::Interface)?; self.interface @@ -530,8 +494,8 @@ where pub fn set_frequency( &mut self, channel: Channel, - frequency: f64, - ) -> Result { + frequency: f32, + ) -> Result { if frequency < 0.0 || frequency > self.system_clock_frequency() { return Err(Error::Bounds); } @@ -539,15 +503,15 @@ where // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the // frequency tuning word and f_s is the system clock rate. let tuning_word: u32 = - ((frequency as f64 / self.system_clock_frequency()) - * 1u64.wrapping_shl(32) as f64) as u32; + ((frequency as f32 / self.system_clock_frequency()) + * 1u64.wrapping_shl(32) as f32) as u32; self.modify_channel( channel, Register::CFTW0, &tuning_word.to_be_bytes(), )?; - Ok((tuning_word as f64 / 1u64.wrapping_shl(32) as f64) + Ok((tuning_word as f32 / 1u64.wrapping_shl(32) as f32) * self.system_clock_frequency()) } @@ -558,79 +522,50 @@ where /// /// Returns: /// The frequency of the channel in Hz. - pub fn get_frequency(&mut self, channel: Channel) -> Result { + pub fn get_frequency(&mut self, channel: Channel) -> Result { // Read the frequency tuning word for the channel. let mut tuning_word: [u8; 4] = [0; 4]; self.read_channel(channel, Register::CFTW0, &mut tuning_word)?; let tuning_word = u32::from_be_bytes(tuning_word); // Convert the tuning word into a frequency. - Ok(tuning_word as f64 * self.system_clock_frequency() - / (1u64 << 32) as f64) + Ok((tuning_word as f32 * self.system_clock_frequency()) / (1u64 << 32) as f32) } - pub fn write_profile(&mut self, channel: Channel, freq: f64, turns: f32, amplitude: f32) -> Result<(), Error> { + pub fn write_profile(&mut self, channel: Channel, freq: f32, turns: f32, amplitude: f32) -> Result<(), Error> { + + let csr: u8 = *0x00_u8 + .set_bits(1..=2, self.communication_mode as u8) + .set_bit(4 + channel as usize, true); + // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the // frequency tuning word and f_s is the system clock rate. - let tuning_word: u32 = - ((freq as f64 / self.system_clock_frequency()) - * 1u64.wrapping_shl(32) as f64) as u32; + let tuning_word: u32 = ((freq * (1u64 << 32) as f32) / self.system_clock_frequency()) as u32; let phase_offset: u16 = (turns * (1 << 14) as f32) as u16 & 0x3FFFu16; + let pow: u32 = *0u32.set_bits(24..32, Register::CPOW0 as u32) + .set_bits(8..24, phase_offset as u32) + .set_bits(0..8, Register::CFTW0 as u32); - let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16; - let mut acr: [u8; 3] = [0; 3]; // Enable the amplitude multiplier for the channel if required. The amplitude control has // full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever // full-scale is used. - if amplitude_control < (1 << 10) { - let masked_control = amplitude_control & 0x3FF; - acr[1] = masked_control.to_be_bytes()[0]; - acr[2] = masked_control.to_be_bytes()[1]; + let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16; - // Enable the amplitude multiplier - acr[1].set_bit(4, true); - } + let acr: u32 = *0u32.set_bits(24..32, Register::ACR as u32) + .set_bits(0..10, amplitude_control as u32 & 0x3FF) + .set_bit(12, amplitude_control < (1 << 10)); - self.modify_channel_closure(channel, |interface| { - let mut data: [u8; 11] = [0; 11]; - data[0..2].copy_from_slice(&phase_offset.to_be_bytes()); - data[2] = Register::CFTW0 as u8; - data[3..7].copy_from_slice(&tuning_word.to_be_bytes()); - data[7] = Register::ACR as u8; - data[8..11].copy_from_slice(&acr); - interface.write(Register::CPOW0 as u8, &data).map_err(|_| Error::Interface) - })?; + let serialized: [u32; 4] = [ + u32::from_le_bytes([Register::CSR as u8, csr, Register::CSR as u8, csr]), + acr.to_be(), + pow.to_be(), + tuning_word.to_be(), + ]; + + self.interface.write_profile(serialized).map_err(|_| Error::Interface)?; Ok(()) } - - fn modify_channel_closure(&mut self, channel: Channel, f: F) -> Result<(), Error> - where - F: FnOnce(&mut INTERFACE) -> Result<(), Error>, - { - // Disable all other outputs so that we can update the configuration register of only the - // specified channel. - let mut csr: [u8; 1] = [0]; - self.interface - .read(Register::CSR as u8, &mut csr) - .map_err(|_| Error::Interface)?; - - let mut new_csr = csr; - new_csr[0].set_bits(4..8, 0); - new_csr[0].set_bit(4 + channel as usize, true); - - self.interface - .write(Register::CSR as u8, &new_csr) - .map_err(|_| Error::Interface)?; - - let result = f(&mut self.interface); - - // Latch the configuration and restore the previous CSR. Note that the re-enable of the - // channel happens immediately, so the CSR update does not need to be latched. - self.latch_configuration()?; - - result - } } diff --git a/src/main.rs b/src/main.rs index a766914..fb55d23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -629,6 +629,7 @@ const APP: () = { }; cp.SCB.enable_icache(); + //cp.SCB.enable_dcache(&mut cp.CPUID); // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); // info!("Built on {}", build_info::BUILT_TIME_UTC); @@ -747,6 +748,7 @@ const APP: () = { match c.resources.pounder { Some(pounder) => { + pounder.ad9959.interface.start_stream(); let state = pounder::ChannelState { parameters: pounder::DdsChannelState { @@ -755,21 +757,23 @@ const APP: () = { amplitude: 1.0, enabled: true, }, - attenuation: 10.0, + attenuation: 0.0, }; let state1 = pounder::ChannelState { parameters: pounder::DdsChannelState { phase_offset: 0.5, - frequency: 50_000_000.0, + frequency: 100_000_000.0, amplitude: 1.0, enabled: true, }, - attenuation: 10.0, + attenuation: 0.0, }; pounder.set_channel_state(pounder::Channel::Out0, state).unwrap(); pounder.set_channel_state(pounder::Channel::Out1, state1).unwrap(); + + pounder.ad9959.latch_configuration().unwrap(); }, _ => panic!("Failed"), } diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index 97c9fd5..78062e5 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -43,7 +43,7 @@ pub enum Channel { #[derive(Serialize, Deserialize, Copy, Clone, Debug)] pub struct DdsChannelState { pub phase_offset: f32, - pub frequency: f64, + pub frequency: f32, pub amplitude: f32, pub enabled: bool, } @@ -90,6 +90,7 @@ impl Into for Channel { pub struct QspiInterface { pub qspi: hal::qspi::Qspi, mode: ad9959::Mode, + streaming: bool, } impl QspiInterface { @@ -106,8 +107,14 @@ impl QspiInterface { Ok(Self { qspi, mode: ad9959::Mode::SingleBitTwoWire, + streaming: false, }) } + + pub fn start_stream(&mut self) { + self.streaming = true; + self.qspi.enter_write_stream_mode().unwrap(); + } } impl ad9959::Interface for QspiInterface { @@ -205,13 +212,23 @@ impl ad9959::Interface for QspiInterface { .map_err(|_| Error::Qspi) } ad9959::Mode::FourBitSerial => { - self.qspi.write(addr, &data).map_err(|_| Error::Qspi) + if self.streaming { + Err(Error::Qspi) + } else { + self.qspi.write(addr, data).map_err(|_| Error::Qspi)?; + Ok(()) + } } _ => Err(Error::Qspi), } } - fn read(&mut self, addr: u8, mut dest: &mut [u8]) -> Result<(), Error> { + fn write_profile(&mut self, data: [u32; 4]) -> Result<(), Error> { + self.qspi.write_profile(data); + Ok(()) + } + + fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Error> { if (addr & 0x80) != 0 { return Err(Error::InvalidAddress); } @@ -221,9 +238,7 @@ impl ad9959::Interface for QspiInterface { return Err(Error::Qspi); } - self.qspi - .read(0x80_u8 | addr, &mut dest) - .map_err(|_| Error::Qspi) + self.qspi.read(0x80_u8 | addr, dest).map_err(|_| Error::Qspi) } } @@ -426,16 +441,12 @@ where .ad9959 .get_amplitude(channel.into()) .map_err(|_| Error::Dds)?; - let enabled = self - .ad9959 - .is_enabled(channel.into()) - .map_err(|_| Error::Dds)?; Ok(DdsChannelState { phase_offset, frequency, amplitude, - enabled, + enabled: true, }) } @@ -477,17 +488,7 @@ where self.ad9959.write_profile(channel.into(), state.parameters.frequency, state.parameters.phase_offset, state.parameters.amplitude).map_err(|_| Error::Dds)?; - if state.parameters.enabled { - self.ad9959 - .enable_channel(channel.into()) - .map_err(|_| Error::Dds)?; - } else { - self.ad9959 - .disable_channel(channel.into()) - .map_err(|_| Error::Dds)?; - } - - self.set_attenuation(channel, state.attenuation)?; + //self.set_attenuation(channel, state.attenuation)?; Ok(()) } From 28cb3906acc4f545f70e81001669b9639b116bca Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Sat, 7 Nov 2020 11:01:48 +0100 Subject: [PATCH 03/19] Adding WIP HRTimer --- src/hrtimer.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 13 ++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/hrtimer.rs diff --git a/src/hrtimer.rs b/src/hrtimer.rs new file mode 100644 index 0000000..ff074d1 --- /dev/null +++ b/src/hrtimer.rs @@ -0,0 +1,81 @@ +use crate::hal; +use hal::rcc::{CoreClocks, ResetEnable, rec}; + +pub enum Channel { + One, + Two, +} + +struct HighResTimerE { + master: hal::stm32::HRTIM_MASTER, + timer: hal::stm32::HRTIM_TIME, + common: hal::stm32::HRTIM_COMMON, + + clocks: CoreClocks, +} + +impl HighResTimerE { + pub fn new(timer_regs: hal::stm32::HRTIM_TIME, clocks: CoreClocks, prec: rec::Hrtim) -> Self { + let master = unsafe { &*hal::stm32::HRTIM_MASTER::ptr() }; + let common = unsafe { &*hal::stm32::HRTIM_COMMON::ptr() }; + prec.reset().enable(); + + Self { master, timer: timer_regs, common, clocks } + } + + pub fn configure_single_shot(&mut self, channel: Channel, set_duration: f32, set_offset: f32) { + // Disable the timer before configuration. + self.master.mcr.modify(|_, w| w.tecen().clear_bit()); + + // Configure the desired timer for single shot mode with set and reset of the specified + // channel at the desired durations. The HRTIM is on APB2 (D2 domain), and the kernel clock + // is the APB bus clock. + let minimum_duration = set_duration + set_offset; + + let source_frequency = self.clocks.timy_ker_ck; + let source_cycles = minimum_duration * source_frequency; + + // Determine the clock divider, which may be 1, 2, or 4. We will choose a clock divider that + // allows us the highest resolution per tick, so lower dividers are favored. + let divider = if source_cycles < 0xFFDF { + 1 + } else if (source_cycles / 2) < 0xFFDF { + 2 + } else if (source_cycles / 4) < 0xFFDF { + 4 + } else { + panic!("Unattainable timing parameters!"); + }; + + // The period register must be greater than or equal to 3 cycles. + assert!((source_cycles / divider) > 2); + + // We now have the prescaler and the period registers. Configure the timer. + self.timer.timecr.modify(|_, w| unsafe{w.ck_pscx().bits(divider)}) + self.timer.perer.write(|w| unsafe{w.per().bits(source_cycles / divider)}); + + // Configure the comparator 1 level. + self.timer.cmpe1r.write(|w| unsafe{w.cmp1().bits(set_offset * source_frequency)}); + + // Configure the set/reset signals. + // Set on compare with CMP1, reset upon reaching PER + match channel { + Channel::One => { + self.timer.sete1r().write(|w| w.cmp1().set_bit()); + self.timer.resete1r().write(|w| w.per().set_bit()); + }, + Channel::Two => { + self.timer.sete2r().write(|w| w.cmp1().set_bit()); + self.timer.resete2r().write(|w| w.per().set_bit()); + }, + } + + // Enable the timer now that it is configured. + self.master.mcr.modify(|_, w| w.tecen().set_bit()); + } + + pub fn trigger(&mut self) { + // Generate a reset event to force the timer to start counting. + self.common.cr2.write(|w| w.terst().set_bit()); + } +} diff --git a/src/main.rs b/src/main.rs index fb55d23..2485133 100644 --- a/src/main.rs +++ b/src/main.rs @@ -445,6 +445,19 @@ const APP: () = { }; let mut reset_pin = gpioa.pa0.into_push_pull_output(); + + // Configure the IO_Update signal for the DDS. + let mut hrtimer = HighResTimerE::new(dp.HRTIM_TIME, ccdr.clocks, ccdr.peripheral.HRTIM); + + // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes + // are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32 + // QSPI clock cycles. The QSPI is configured for 10MHz, so this comes out to an + // offset of 3.2uS. + // TODO: This currently does not meet the 2uS timing that we have for profile + // updates, since we want to send a profile update for every DAC update. We should + // increase the QSPI clock frequency. + hrtimer.configure_single_shot(hrtimer::Channel::Two, 50e-9, 3.2e-6); + let io_update = gpiog.pg7.into_push_pull_output(); let asm_delay = { From 8d807fa09bee71c124578334c1d4012bb5a7dec4 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 9 Nov 2020 12:30:02 +0100 Subject: [PATCH 04/19] Adding WIP --- ad9959/src/lib.rs | 95 ++++++++--------------- src/hrtimer.rs | 60 +++++++++----- src/main.rs | 123 +++++++++-------------------- src/pounder/mod.rs | 189 ++++++++++++++++++--------------------------- 4 files changed, 185 insertions(+), 282 deletions(-) diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index 1a5de18..5a2d132 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -13,12 +13,10 @@ use embedded_hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; /// /// The chip supports a number of serial interfaces to improve data throughput, including normal, /// dual, and quad SPI configurations. -pub struct Ad9959 { +pub struct Ad9959 { pub interface: INTERFACE, - delay: DELAY, reference_clock_frequency: f32, system_clock_multiplier: u8, - io_update: UPDATE, communication_mode: Mode, } @@ -31,8 +29,6 @@ pub trait Interface { fn write(&mut self, addr: u8, data: &[u8]) -> Result<(), Self::Error>; fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Self::Error>; - - fn write_profile(&mut self, data: [u32; 4]) -> Result<(), Self::Error>; } /// Indicates various communication modes of the DDS. The value of this enumeration is equivalent to @@ -93,12 +89,7 @@ pub enum Error { Frequency, } -impl Ad9959 -where - INTERFACE: Interface, - DELAY: DelayMs, - UPDATE: OutputPin, -{ +impl Ad9959 { /// Construct and initialize the DDS. /// /// Args: @@ -110,36 +101,26 @@ where /// * `clock_frequency` - The clock frequency of the reference clock input. /// * `multiplier` - The desired clock multiplier for the system clock. This multiplies /// `clock_frequency` to generate the system clock. - pub fn new( - interface: INTERFACE, - reset_pin: &mut RST, - io_update: UPDATE, - delay: DELAY, + pub fn new( + interface: I, + reset_pin: &mut impl OutputPin, + delay: &mut impl DelayMs, desired_mode: Mode, clock_frequency: f32, multiplier: u8, - ) -> Result - where - RST: OutputPin, - { + ) -> Result { let mut ad9959 = Ad9959 { interface, - io_update, - delay, reference_clock_frequency: clock_frequency, system_clock_multiplier: 1, communication_mode: desired_mode, }; - ad9959.io_update.set_low().or_else(|_| Err(Error::Pin))?; - // Reset the AD9959 reset_pin.set_high().or_else(|_| Err(Error::Pin))?; // Delay for a clock cycle to allow the device to reset. - ad9959 - .delay - .delay_ms((1000.0 / clock_frequency as f32) as u8); + delay.delay_ms((1000.0 / clock_frequency as f32) as u8); reset_pin.set_low().or_else(|_| Err(Error::Pin))?; @@ -156,9 +137,6 @@ where .write(Register::CSR as u8, &csr) .map_err(|_| Error::Interface)?; - // Latch the configuration registers to make them active. - ad9959.latch_configuration()?; - ad9959 .interface .configure_mode(desired_mode) @@ -179,19 +157,6 @@ where Ok(ad9959) } - /// Latch the DDS configuration to ensure it is active on the output channels. - pub fn latch_configuration(&mut self) -> Result<(), Error> { - self.delay.delay_ms(2); - self.io_update.set_high().or_else(|_| Err(Error::Pin))?; - // The SYNC_CLK is 1/4 the system clock frequency. The IO_UPDATE pin must be latched for one - // full SYNC_CLK pulse to register. For safety, we latch for 5 here. - self.delay - .delay_ms((5000.0 / self.system_clock_frequency()) as u8); - self.io_update.set_low().or_else(|_| Err(Error::Pin))?; - - Ok(()) - } - /// Configure the internal system clock of the chip. /// /// Arguments: @@ -232,8 +197,6 @@ where .map_err(|_| Error::Interface)?; self.system_clock_multiplier = multiplier; - self.latch_configuration()?; - Ok(self.system_clock_frequency()) } @@ -337,10 +300,6 @@ where .write(register as u8, &data) .map_err(|_| Error::Interface)?; - // Latch the configuration and restore the previous CSR. Note that the re-enable of the - // channel happens immediately, so the CSR update does not need to be latched. - self.latch_configuration()?; - Ok(()) } @@ -529,43 +488,55 @@ where let tuning_word = u32::from_be_bytes(tuning_word); // Convert the tuning word into a frequency. - Ok((tuning_word as f32 * self.system_clock_frequency()) / (1u64 << 32) as f32) + Ok((tuning_word as f32 * self.system_clock_frequency()) + / (1u64 << 32) as f32) } - pub fn write_profile(&mut self, channel: Channel, freq: f32, turns: f32, amplitude: f32) -> Result<(), Error> { - + pub fn serialize_profile( + &self, + channel: Channel, + freq: f32, + turns: f32, + amplitude: f32, + ) -> Result<[u32; 4], Error> { let csr: u8 = *0x00_u8 .set_bits(1..=2, self.communication_mode as u8) .set_bit(4 + channel as usize, true); // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the // frequency tuning word and f_s is the system clock rate. - let tuning_word: u32 = ((freq * (1u64 << 32) as f32) / self.system_clock_frequency()) as u32; + let tuning_word: u32 = ((freq * (1u64 << 32) as f32) + / self.system_clock_frequency()) + as u32; let phase_offset: u16 = (turns * (1 << 14) as f32) as u16 & 0x3FFFu16; - let pow: u32 = *0u32.set_bits(24..32, Register::CPOW0 as u32) - .set_bits(8..24, phase_offset as u32) - .set_bits(0..8, Register::CFTW0 as u32); - + let pow: u32 = *0u32 + .set_bits(24..32, Register::CPOW0 as u32) + .set_bits(8..24, phase_offset as u32) + .set_bits(0..8, Register::CFTW0 as u32); // Enable the amplitude multiplier for the channel if required. The amplitude control has // full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever // full-scale is used. let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16; - let acr: u32 = *0u32.set_bits(24..32, Register::ACR as u32) + let acr: u32 = *0u32 + .set_bits(24..32, Register::ACR as u32) .set_bits(0..10, amplitude_control as u32 & 0x3FF) .set_bit(12, amplitude_control < (1 << 10)); let serialized: [u32; 4] = [ - u32::from_le_bytes([Register::CSR as u8, csr, Register::CSR as u8, csr]), + u32::from_le_bytes([ + Register::CSR as u8, + csr, + Register::CSR as u8, + csr, + ]), acr.to_be(), pow.to_be(), tuning_word.to_be(), ]; - self.interface.write_profile(serialized).map_err(|_| Error::Interface)?; - - Ok(()) + Ok(serialized) } } diff --git a/src/hrtimer.rs b/src/hrtimer.rs index ff074d1..4af2144 100644 --- a/src/hrtimer.rs +++ b/src/hrtimer.rs @@ -1,12 +1,12 @@ use crate::hal; -use hal::rcc::{CoreClocks, ResetEnable, rec}; +use hal::rcc::{rec, CoreClocks, ResetEnable}; pub enum Channel { One, Two, } -struct HighResTimerE { +pub struct HighResTimerE { master: hal::stm32::HRTIM_MASTER, timer: hal::stm32::HRTIM_TIME, common: hal::stm32::HRTIM_COMMON, @@ -15,15 +15,29 @@ struct HighResTimerE { } impl HighResTimerE { - pub fn new(timer_regs: hal::stm32::HRTIM_TIME, clocks: CoreClocks, prec: rec::Hrtim) -> Self { - let master = unsafe { &*hal::stm32::HRTIM_MASTER::ptr() }; - let common = unsafe { &*hal::stm32::HRTIM_COMMON::ptr() }; + pub fn new( + timer_regs: hal::stm32::HRTIM_TIME, + master_regs: hal::stm32::HRTIM_MASTER, + common_regs: hal::stm32::HRTIM_COMMON, + clocks: CoreClocks, + prec: rec::Hrtim, + ) -> Self { prec.reset().enable(); - Self { master, timer: timer_regs, common, clocks } + Self { + master: master_regs, + timer: timer_regs, + common: common_regs, + clocks, + } } - pub fn configure_single_shot(&mut self, channel: Channel, set_duration: f32, set_offset: f32) { + pub fn configure_single_shot( + &mut self, + channel: Channel, + set_duration: f32, + set_offset: f32, + ) { // Disable the timer before configuration. self.master.mcr.modify(|_, w| w.tecen().clear_bit()); @@ -32,12 +46,12 @@ impl HighResTimerE { // is the APB bus clock. let minimum_duration = set_duration + set_offset; - let source_frequency = self.clocks.timy_ker_ck; - let source_cycles = minimum_duration * source_frequency; + let source_frequency: u32 = self.clocks.timy_ker_ck().0; + let source_cycles = (minimum_duration * source_frequency as f32) as u32; // Determine the clock divider, which may be 1, 2, or 4. We will choose a clock divider that // allows us the highest resolution per tick, so lower dividers are favored. - let divider = if source_cycles < 0xFFDF { + let divider: u8 = if source_cycles < 0xFFDF { 1 } else if (source_cycles / 2) < 0xFFDF { 2 @@ -48,26 +62,32 @@ impl HighResTimerE { }; // The period register must be greater than or equal to 3 cycles. - assert!((source_cycles / divider) > 2); + let period = (source_cycles / divider as u32) as u16; + assert!(period > 2); // We now have the prescaler and the period registers. Configure the timer. - self.timer.timecr.modify(|_, w| unsafe{w.ck_pscx().bits(divider)}) - self.timer.perer.write(|w| unsafe{w.per().bits(source_cycles / divider)}); + self.timer + .timecr + .modify(|_, w| unsafe { w.ck_pscx().bits(divider) }); + self.timer.perer.write(|w| unsafe { w.perx().bits(period) }); // Configure the comparator 1 level. - self.timer.cmpe1r.write(|w| unsafe{w.cmp1().bits(set_offset * source_frequency)}); + let offset = (set_offset * source_frequency as f32) as u16; + self.timer + .cmp1er + .write(|w| unsafe { w.cmp1x().bits(offset) }); // Configure the set/reset signals. // Set on compare with CMP1, reset upon reaching PER match channel { Channel::One => { - self.timer.sete1r().write(|w| w.cmp1().set_bit()); - self.timer.resete1r().write(|w| w.per().set_bit()); - }, + self.timer.sete1r.write(|w| w.cmp1().set_bit()); + self.timer.rste1r.write(|w| w.per().set_bit()); + } Channel::Two => { - self.timer.sete2r().write(|w| w.cmp1().set_bit()); - self.timer.resete2r().write(|w| w.per().set_bit()); - }, + self.timer.sete2r.write(|w| w.cmp1().set_bit()); + self.timer.rste2r.write(|w| w.per().set_bit()); + } } // Enable the timer now that it is configured. diff --git a/src/main.rs b/src/main.rs index 2485133..db0558a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,6 +45,7 @@ static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); mod afe; mod eeprom; +mod hrtimer; mod iir; mod pounder; mod server; @@ -187,11 +188,12 @@ const APP: () = { 'static, 'static, 'static, - ethernet::EthernetDMA<'static>>, + ethernet::EthernetDMA<'static>, + >, eth_mac: ethernet::EthernetMAC, mac_addr: net::wire::EthernetAddress, - pounder: Option>, + pounder: Option, #[init([[0.; 5]; 2])] iir_state: [iir::IIRState; 2], @@ -439,41 +441,19 @@ const APP: () = { .set_speed(hal::gpio::Speed::VeryHigh); let qspi = - hal::qspi::Qspi::new(dp.QUADSPI, &mut clocks, 10.mhz()) + hal::qspi::Qspi::new(dp.QUADSPI, &mut clocks, 50.mhz()) .unwrap(); pounder::QspiInterface::new(qspi).unwrap() }; let mut reset_pin = gpioa.pa0.into_push_pull_output(); - // Configure the IO_Update signal for the DDS. - let mut hrtimer = HighResTimerE::new(dp.HRTIM_TIME, ccdr.clocks, ccdr.peripheral.HRTIM); - - // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes - // are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32 - // QSPI clock cycles. The QSPI is configured for 10MHz, so this comes out to an - // offset of 3.2uS. - // TODO: This currently does not meet the 2uS timing that we have for profile - // updates, since we want to send a profile update for every DAC update. We should - // increase the QSPI clock frequency. - hrtimer.configure_single_shot(hrtimer::Channel::Two, 50e-9, 3.2e-6); - - let io_update = gpiog.pg7.into_push_pull_output(); - - let asm_delay = { - let frequency_hz = clocks.clocks.c_ck().0; - asm_delay::AsmDelay::new(asm_delay::bitrate::Hertz( - frequency_hz, - )) - }; - ad9959::Ad9959::new( qspi_interface, &mut reset_pin, - io_update, - asm_delay, + &mut delay, ad9959::Mode::FourBitSerial, - 100_000_000f32, + 100_000_000_f32, 5, ) .unwrap() @@ -533,10 +513,39 @@ const APP: () = { let adc1_in_p = gpiof.pf11.into_analog(); let adc2_in_p = gpiof.pf14.into_analog(); + let io_update_trigger = { + let _io_update = gpiog + .pg7 + .into_alternate_af2() + .set_speed(hal::gpio::Speed::VeryHigh); + + // Configure the IO_Update signal for the DDS. + let mut hrtimer = hrtimer::HighResTimerE::new( + dp.HRTIM_TIME, + dp.HRTIM_MASTER, + dp.HRTIM_COMMON, + clocks.clocks, + clocks.peripheral.HRTIM, + ); + + // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes + // are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32 + // QSPI clock cycles. The QSPI is configured for 50MHz, so this comes out to an + // offset of 640nS. We use 900ns to be safe. + hrtimer.configure_single_shot( + hrtimer::Channel::Two, + 50_e-9, + 900_e-9, + ); + + hrtimer + }; + Some( pounder::PounderDevices::new( io_expander, ad9959, + io_update_trigger, spi, adc1, adc2, @@ -759,38 +768,6 @@ const APP: () = { // TODO: Replace with reference to CPU clock from CCDR. next_ms += 400_000.cycles(); - match c.resources.pounder { - Some(pounder) => { - pounder.ad9959.interface.start_stream(); - - let state = pounder::ChannelState { - parameters: pounder::DdsChannelState { - phase_offset: 0.0, - frequency: 100_000_000.0, - amplitude: 1.0, - enabled: true, - }, - attenuation: 0.0, - }; - - let state1 = pounder::ChannelState { - parameters: pounder::DdsChannelState { - phase_offset: 0.5, - frequency: 100_000_000.0, - amplitude: 1.0, - enabled: true, - }, - attenuation: 0.0, - }; - - pounder.set_channel_state(pounder::Channel::Out0, state).unwrap(); - pounder.set_channel_state(pounder::Channel::Out1, state1).unwrap(); - - pounder.ad9959.latch_configuration().unwrap(); - }, - _ => panic!("Failed"), - } - loop { let tick = Instant::now() > next_ms; @@ -827,34 +804,6 @@ const APP: () = { }), "stabilizer/afe0/gain": (|| c.resources.afe0.get_gain()), "stabilizer/afe1/gain": (|| c.resources.afe1.get_gain()), - "pounder/in0": (|| { - match c.resources.pounder { - Some(pounder) => - pounder.get_input_channel_state(pounder::Channel::In0), - _ => Err(pounder::Error::Access), - } - }), - "pounder/in1": (|| { - match c.resources.pounder { - Some(pounder) => - pounder.get_input_channel_state(pounder::Channel::In1), - _ => Err(pounder::Error::Access), - } - }), - "pounder/out0": (|| { - match c.resources.pounder { - Some(pounder) => - pounder.get_output_channel_state(pounder::Channel::Out0), - _ => Err(pounder::Error::Access), - } - }), - "pounder/out1": (|| { - match c.resources.pounder { - Some(pounder) => - pounder.get_output_channel_state(pounder::Channel::Out1), - _ => Err(pounder::Error::Access), - } - }), "pounder/dds/clock": (|| { match c.resources.pounder { Some(pounder) => pounder.get_dds_clock_config(), diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index 78062e5..1e3c8c9 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -4,6 +4,7 @@ mod attenuators; mod rf_power; use super::hal; +use super::hrtimer::HighResTimerE; use attenuators::AttenuatorInterface; use rf_power::PowerMeasurementInterface; @@ -111,9 +112,54 @@ impl QspiInterface { }) } - pub fn start_stream(&mut self) { + pub fn start_stream(&mut self) -> Result<(), Error> { + if self.qspi.is_busy() { + return Err(Error::Qspi); + } + + // Configure QSPI for infinite transaction mode using only a data phase (no instruction or + // address). + let qspi_regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; + qspi_regs.fcr.modify(|_, w| w.ctcf().set_bit()); + + unsafe { + qspi_regs.dlr.write(|w| w.dl().bits(0xFFFF_FFFF)); + qspi_regs + .ccr + .modify(|_, w| w.imode().bits(0).fmode().bits(1)); + } + self.streaming = true; - self.qspi.enter_write_stream_mode().unwrap(); + + Ok(()) + } + + pub fn write_profile(&mut self, data: [u32; 4]) -> Result<(), Error> { + if self.streaming == false { + return Err(Error::Qspi); + } + + let qspi_regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; + unsafe { + core::ptr::write_volatile( + &qspi_regs.dr as *const _ as *mut u32, + data[0], + ); + core::ptr::write_volatile( + &qspi_regs.dr as *const _ as *mut u32, + data[1], + ); + core::ptr::write_volatile( + &qspi_regs.dr as *const _ as *mut u32, + data[2], + ); + core::ptr::write_volatile( + &qspi_regs.dr as *const _ as *mut u32, + data[3], + ); + } + + Ok(()) } } @@ -223,11 +269,6 @@ impl ad9959::Interface for QspiInterface { } } - fn write_profile(&mut self, data: [u32; 4]) -> Result<(), Error> { - self.qspi.write_profile(data); - Ok(()) - } - fn read(&mut self, addr: u8, dest: &mut [u8]) -> Result<(), Error> { if (addr & 0x80) != 0 { return Err(Error::InvalidAddress); @@ -238,17 +279,16 @@ impl ad9959::Interface for QspiInterface { return Err(Error::Qspi); } - self.qspi.read(0x80_u8 | addr, dest).map_err(|_| Error::Qspi) + self.qspi + .read(0x80_u8 | addr, dest) + .map_err(|_| Error::Qspi) } } /// A structure containing implementation for Pounder hardware. -pub struct PounderDevices { - pub ad9959: ad9959::Ad9959< - QspiInterface, - DELAY, - hal::gpio::gpiog::PG7>, - >, +pub struct PounderDevices { + pub ad9959: ad9959::Ad9959, + pub io_update_trigger: HighResTimerE, mcp23017: mcp23017::MCP23017>, attenuator_spi: hal::spi::Spi, adc1: hal::adc::Adc, @@ -257,26 +297,21 @@ pub struct PounderDevices { adc2_in_p: hal::gpio::gpiof::PF14, } -impl PounderDevices -where - DELAY: embedded_hal::blocking::delay::DelayMs, -{ +impl PounderDevices { /// Construct and initialize pounder-specific hardware. /// /// Args: /// * `ad9959` - The DDS driver for the pounder hardware. /// * `attenuator_spi` - A SPI interface to control digital attenuators. + /// * `io_update_timer` - The HRTimer with the IO_update signal connected to the output. /// * `adc1` - The ADC1 peripheral for measuring power. /// * `adc2` - The ADC2 peripheral for measuring power. /// * `adc1_in_p` - The input channel for the RF power measurement on IN0. /// * `adc2_in_p` - The input channel for the RF power measurement on IN1. pub fn new( mcp23017: mcp23017::MCP23017>, - ad9959: ad9959::Ad9959< - QspiInterface, - DELAY, - hal::gpio::gpiog::PG7>, - >, + ad9959: ad9959::Ad9959, + io_update_trigger: HighResTimerE, attenuator_spi: hal::spi::Spi, adc1: hal::adc::Adc, adc2: hal::adc::Adc, @@ -285,6 +320,7 @@ where ) -> Result { let mut devices = Self { mcp23017, + io_update_trigger, ad9959, attenuator_spi, adc1, @@ -312,6 +348,9 @@ where // Select the on-board clock with a 4x prescaler (400MHz). devices.select_onboard_clock(4u8)?; + // Run the DDS in stream-only mode (no read support). + devices.ad9959.interface.start_stream(); + Ok(devices) } @@ -390,91 +429,6 @@ where }) } - /// Get the state of a Pounder input channel. - /// - /// Args: - /// * `channel` - The pounder channel to get the state of. Must be an input channel - /// - /// Returns: - /// The read-back channel input state. - pub fn get_input_channel_state( - &mut self, - channel: Channel, - ) -> Result { - match channel { - Channel::In0 | Channel::In1 => { - let channel_state = self.get_dds_channel_state(channel)?; - - let attenuation = self.get_attenuation(channel)?; - let power = self.measure_power(channel)?; - - Ok(InputChannelState { - attenuation, - power, - mixer: channel_state, - }) - } - _ => Err(Error::InvalidChannel), - } - } - - /// Get the state of a DDS channel. - /// - /// Args: - /// * `channel` - The pounder channel to get the state of. - /// - /// Returns: - /// The read-back channel state. - fn get_dds_channel_state( - &mut self, - channel: Channel, - ) -> Result { - let frequency = self - .ad9959 - .get_frequency(channel.into()) - .map_err(|_| Error::Dds)?; - let phase_offset = self - .ad9959 - .get_phase(channel.into()) - .map_err(|_| Error::Dds)?; - let amplitude = self - .ad9959 - .get_amplitude(channel.into()) - .map_err(|_| Error::Dds)?; - - Ok(DdsChannelState { - phase_offset, - frequency, - amplitude, - enabled: true, - }) - } - - /// Get the state of a DDS output channel. - /// - /// Args: - /// * `channel` - The pounder channel to get the output state of. Must be an output channel. - /// - /// Returns: - /// The read-back output channel state. - pub fn get_output_channel_state( - &mut self, - channel: Channel, - ) -> Result { - match channel { - Channel::Out0 | Channel::Out1 => { - let channel_state = self.get_dds_channel_state(channel)?; - let attenuation = self.get_attenuation(channel)?; - - Ok(OutputChannelState { - attenuation, - channel: channel_state, - }) - } - _ => Err(Error::InvalidChannel), - } - } - /// Configure a DDS channel. /// /// Args: @@ -485,16 +439,25 @@ where channel: Channel, state: ChannelState, ) -> Result<(), Error> { - self.ad9959.write_profile(channel.into(), state.parameters.frequency, - state.parameters.phase_offset, state.parameters.amplitude).map_err(|_| Error::Dds)?; + let profile = self + .ad9959 + .serialize_profile( + channel.into(), + state.parameters.frequency, + state.parameters.phase_offset, + state.parameters.amplitude, + ) + .map_err(|_| Error::Dds)?; + self.ad9959.interface.write_profile(profile).unwrap(); + self.io_update_trigger.trigger(); - //self.set_attenuation(channel, state.attenuation)?; + self.set_attenuation(channel, state.attenuation)?; Ok(()) } } -impl AttenuatorInterface for PounderDevices { +impl AttenuatorInterface for PounderDevices { /// Reset all of the attenuators to a power-on default state. fn reset_attenuators(&mut self) -> Result<(), Error> { self.mcp23017 @@ -566,7 +529,7 @@ impl AttenuatorInterface for PounderDevices { } } -impl PowerMeasurementInterface for PounderDevices { +impl PowerMeasurementInterface for PounderDevices { /// Sample an ADC channel. /// /// Args: From c97e4d9d20c866f10ba9935244ed819668629845 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 9 Nov 2020 12:33:18 +0100 Subject: [PATCH 05/19] Fixing merge issues --- src/main.rs | 4 ++-- src/pounder/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 565c8b3..4295170 100644 --- a/src/main.rs +++ b/src/main.rs @@ -559,8 +559,8 @@ const APP: () = { dp.HRTIM_TIME, dp.HRTIM_MASTER, dp.HRTIM_COMMON, - clocks.clocks, - clocks.peripheral.HRTIM, + ccdr.clocks, + ccdr.peripheral.HRTIM, ); // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index 6b637f4..e299e2b 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -349,7 +349,7 @@ impl PounderDevices { devices.select_onboard_clock(4u8)?; // Run the DDS in stream-only mode (no read support). - devices.ad9959.interface.start_stream(); + devices.ad9959.interface.start_stream().unwrap(); Ok(devices) } From fca38e5d6330f94fece3671289ef3d7b8e27a7ef Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 9 Nov 2020 15:16:44 +0100 Subject: [PATCH 06/19] Adding support for hardware IO_update --- Cargo.lock | 2 +- ad9959/src/lib.rs | 9 ++++ src/hrtimer.rs | 7 ++- src/main.rs | 115 ++++++++++++++++++++++++++++++++------------- src/pounder/mod.rs | 2 +- 5 files changed, 98 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff768f0..5395319 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -567,7 +567,7 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.8.0" -source = "git+https://github.com/quartiq/stm32h7xx-hal?branch=feature/stabilizer-dma#5fbbfa9352f720994c210e5c21601f3acf9dc40c" +source = "git+https://github.com/quartiq/stm32h7xx-hal?branch=feature/stabilizer-dma#8516690d4f35bc4bb184eba2ee8b48d4490ec85b" dependencies = [ "bare-metal 1.0.0", "cast", diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index 5a2d132..99f1072 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -104,6 +104,7 @@ impl Ad9959 { pub fn new( interface: I, reset_pin: &mut impl OutputPin, + io_update: &mut impl OutputPin, delay: &mut impl DelayMs, desired_mode: Mode, clock_frequency: f32, @@ -119,6 +120,8 @@ impl Ad9959 { // Reset the AD9959 reset_pin.set_high().or_else(|_| Err(Error::Pin))?; + io_update.set_low().or_else(|_| Err(Error::Pin))?; + // Delay for a clock cycle to allow the device to reset. delay.delay_ms((1000.0 / clock_frequency as f32) as u8); @@ -137,6 +140,12 @@ impl Ad9959 { .write(Register::CSR as u8, &csr) .map_err(|_| Error::Interface)?; + // Latch the new interface configuration. + io_update.set_high().or_else(|_| Err(Error::Pin))?; + // Delay for a clock cycle to allow the device to reset. + delay.delay_ms(2 * (1000.0 / clock_frequency as f32) as u8); + io_update.set_low().or_else(|_| Err(Error::Pin))?; + ad9959 .interface .configure_mode(desired_mode) diff --git a/src/hrtimer.rs b/src/hrtimer.rs index 4af2144..d344396 100644 --- a/src/hrtimer.rs +++ b/src/hrtimer.rs @@ -47,7 +47,7 @@ impl HighResTimerE { let minimum_duration = set_duration + set_offset; let source_frequency: u32 = self.clocks.timy_ker_ck().0; - let source_cycles = (minimum_duration * source_frequency as f32) as u32; + let source_cycles = (minimum_duration * source_frequency as f32) as u32 + 1; // Determine the clock divider, which may be 1, 2, or 4. We will choose a clock divider that // allows us the highest resolution per tick, so lower dividers are favored. @@ -68,7 +68,7 @@ impl HighResTimerE { // We now have the prescaler and the period registers. Configure the timer. self.timer .timecr - .modify(|_, w| unsafe { w.ck_pscx().bits(divider) }); + .modify(|_, w| unsafe { w.ck_pscx().bits(divider + 4) }); self.timer.perer.write(|w| unsafe { w.perx().bits(period) }); // Configure the comparator 1 level. @@ -83,13 +83,16 @@ impl HighResTimerE { Channel::One => { self.timer.sete1r.write(|w| w.cmp1().set_bit()); self.timer.rste1r.write(|w| w.per().set_bit()); + self.common.oenr.write(|w| w.te1oen().set_bit()); } Channel::Two => { self.timer.sete2r.write(|w| w.cmp1().set_bit()); self.timer.rste2r.write(|w| w.per().set_bit()); + self.common.oenr.write(|w| w.te2oen().set_bit()); } } + // Enable the timer now that it is configured. self.master.mcr.modify(|_, w| w.tecen().set_bit()); } diff --git a/src/main.rs b/src/main.rs index 4295170..9c0d474 100644 --- a/src/main.rs +++ b/src/main.rs @@ -183,6 +183,8 @@ const APP: () = { timer: hal::timer::Timer, + profiles: heapless::spsc::Queue<[u32; 4], heapless::consts::U32>, + // Note: It appears that rustfmt generates a format that GDB cannot recognize, which // results in GDB breakpoints being set improperly. #[rustfmt::skip] @@ -241,7 +243,7 @@ const APP: () = { let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD); let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE); let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF); - let gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG); + let mut gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG); let afe0 = { let a0_pin = gpiof.pf2.into_push_pull_output(); @@ -469,16 +471,25 @@ const APP: () = { }; let mut reset_pin = gpioa.pa0.into_push_pull_output(); + let mut io_update = gpiog + .pg7 + .into_push_pull_output(); - ad9959::Ad9959::new( + let ad9959 = ad9959::Ad9959::new( qspi_interface, &mut reset_pin, + &mut io_update, &mut delay, ad9959::Mode::FourBitSerial, 100_000_000_f32, 5, ) - .unwrap() + .unwrap(); + + // Return IO_Update + gpiog.pg7 = io_update.into_analog(); + + ad9959 }; let io_expander = { @@ -573,6 +584,9 @@ const APP: () = { 900_e-9, ); + // Ensure that we have enough time for an IO-update every sample. + assert!(1.0 / (1000 * SAMPLE_FREQUENCY_KHZ) as f32 > 900_e-9); + hrtimer }; @@ -733,15 +747,24 @@ const APP: () = { net_interface: network_interface, eth_mac, mac_addr, + + profiles: heapless::spsc::Queue::new(), } } - #[task(binds = TIM3, resources=[dacs], priority = 3)] + #[task(binds = TIM3, resources=[dacs, profiles, pounder], priority = 3)] fn dac_update(c: dac_update::Context) { c.resources.dacs.update(); + + if let Some(pounder) = c.resources.pounder { + if let Some(profile) = c.resources.profiles.dequeue() { + pounder.ad9959.interface.write_profile(profile).unwrap(); + pounder.io_update_trigger.trigger(); + } + } } - #[task(binds=DMA1_STR3, resources=[adcs, dacs, iir_state, iir_ch], priority=2)] + #[task(binds=DMA1_STR3, resources=[adcs, dacs, pounder, profiles, iir_state, iir_ch], priority=2)] fn adc_update(mut c: adc_update::Context) { let (adc0_samples, adc1_samples) = c.resources.adcs.transfer_complete_handler(); @@ -756,6 +779,20 @@ const APP: () = { c.resources .dacs .lock(|dacs| dacs.push(result_adc0, result_adc1)); + + let profiles = &mut c.resources.profiles; + c.resources.pounder.lock(|pounder| { + if let Some(pounder) = pounder { + profiles.lock(|profiles| { + let profile = pounder.ad9959.serialize_profile(pounder::Channel::Out0.into(), + 100_000_000_f32, + 0.0_f32, + *adc0 as f32 / 0xFFFF as f32).unwrap(); + + profiles.enqueue(profile).unwrap(); + }); + } + }); } } @@ -822,10 +859,12 @@ const APP: () = { "stabilizer/afe0/gain": (|| c.resources.afe0.get_gain()), "stabilizer/afe1/gain": (|| c.resources.afe1.get_gain()), "pounder/dds/clock": (|| { - match c.resources.pounder { - Some(pounder) => pounder.get_dds_clock_config(), - _ => Err(pounder::Error::Access), - } + c.resources.pounder.lock(|pounder| { + match pounder { + Some(pounder) => pounder.get_dds_clock_config(), + _ => Err(pounder::Error::Access), + } + }) }) ], @@ -853,38 +892,48 @@ const APP: () = { }) }), "pounder/in0": pounder::ChannelState, (|state| { - match c.resources.pounder { - Some(pounder) => - pounder.set_channel_state(pounder::Channel::In0, state), - _ => Err(pounder::Error::Access), - } + c.resources.pounder.lock(|pounder| { + match pounder { + Some(pounder) => + pounder.set_channel_state(pounder::Channel::In0, state), + _ => Err(pounder::Error::Access), + } + }) }), "pounder/in1": pounder::ChannelState, (|state| { - match c.resources.pounder { - Some(pounder) => - pounder.set_channel_state(pounder::Channel::In1, state), - _ => Err(pounder::Error::Access), - } + c.resources.pounder.lock(|pounder| { + match pounder { + Some(pounder) => + pounder.set_channel_state(pounder::Channel::In1, state), + _ => Err(pounder::Error::Access), + } + }) }), "pounder/out0": pounder::ChannelState, (|state| { - match c.resources.pounder { - Some(pounder) => - pounder.set_channel_state(pounder::Channel::Out0, state), - _ => Err(pounder::Error::Access), - } + c.resources.pounder.lock(|pounder| { + match pounder { + Some(pounder) => + pounder.set_channel_state(pounder::Channel::Out0, state), + _ => Err(pounder::Error::Access), + } + }) }), "pounder/out1": pounder::ChannelState, (|state| { - match c.resources.pounder { - Some(pounder) => - pounder.set_channel_state(pounder::Channel::Out1, state), - _ => Err(pounder::Error::Access), - } + c.resources.pounder.lock(|pounder| { + match pounder { + Some(pounder) => + pounder.set_channel_state(pounder::Channel::Out1, state), + _ => Err(pounder::Error::Access), + } + }) }), "pounder/dds/clock": pounder::DdsClockConfig, (|config| { - match c.resources.pounder { - Some(pounder) => pounder.configure_dds_clock(config), - _ => Err(pounder::Error::Access), - } + c.resources.pounder.lock(|pounder| { + match pounder { + Some(pounder) => pounder.configure_dds_clock(config), + _ => Err(pounder::Error::Access), + } + }) }), "stabilizer/afe0/gain": afe::Gain, (|gain| { Ok::<(), ()>(c.resources.afe0.set_gain(gain)) diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index e299e2b..0e32a22 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -126,7 +126,7 @@ impl QspiInterface { qspi_regs.dlr.write(|w| w.dl().bits(0xFFFF_FFFF)); qspi_regs .ccr - .modify(|_, w| w.imode().bits(0).fmode().bits(1)); + .modify(|_, w| w.imode().bits(0).fmode().bits(0).admode().bits(0)); } self.streaming = true; From 014137acf012876051bb06feab314df35b2c5e7e Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 9 Nov 2020 15:57:58 +0100 Subject: [PATCH 07/19] Updating QSPI frequency --- src/main.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9c0d474..82b5f37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -462,7 +462,7 @@ const APP: () = { let qspi = hal::qspi::Qspi::bank2( dp.QUADSPI, qspi_pins, - 50.mhz(), + 40.mhz(), &ccdr.clocks, ccdr.peripheral.QSPI, ); @@ -576,8 +576,10 @@ const APP: () = { // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes // are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32 - // QSPI clock cycles. The QSPI is configured for 50MHz, so this comes out to an - // offset of 640nS. We use 900ns to be safe. + // QSPI clock cycles. The QSPI is configured for 40MHz, so this comes out to an + // offset of 800nS. We use 900ns to be safe - note that the timer is triggered after + // the QSPI write, which can take approximately 120nS, so there is additional + // margin. hrtimer.configure_single_shot( hrtimer::Channel::Two, 50_e-9, From 585613f48f464c9df2d9637d5687cda16b73ee8a Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 17 Nov 2020 10:45:37 +0100 Subject: [PATCH 08/19] Refactoring DDS output control --- src/main.rs | 144 ++++++++++++++++----------------------------- src/pounder/mod.rs | 33 +---------- 2 files changed, 51 insertions(+), 126 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3f5588e..9033895 100644 --- a/src/main.rs +++ b/src/main.rs @@ -592,44 +592,10 @@ const APP: () = { let adc1_in_p = gpiof.pf11.into_analog(); let adc2_in_p = gpiof.pf14.into_analog(); - let io_update_trigger = { - let _io_update = gpiog - .pg7 - .into_alternate_af2() - .set_speed(hal::gpio::Speed::VeryHigh); - - // Configure the IO_Update signal for the DDS. - let mut hrtimer = hrtimer::HighResTimerE::new( - dp.HRTIM_TIME, - dp.HRTIM_MASTER, - dp.HRTIM_COMMON, - ccdr.clocks, - ccdr.peripheral.HRTIM, - ); - - // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes - // are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32 - // QSPI clock cycles. The QSPI is configured for 40MHz, so this comes out to an - // offset of 800nS. We use 900ns to be safe - note that the timer is triggered after - // the QSPI write, which can take approximately 120nS, so there is additional - // margin. - hrtimer.configure_single_shot( - hrtimer::Channel::Two, - 50_e-9, - 900_e-9, - ); - - // Ensure that we have enough time for an IO-update every sample. - assert!(1.0 / (1000 * SAMPLE_FREQUENCY_KHZ) as f32 > 900_e-9); - - hrtimer - }; - Some( pounder::PounderDevices::new( io_expander, ad9959, - io_update_trigger, spi, adc1, adc2, @@ -763,13 +729,46 @@ const APP: () = { cp.DWT.enable_cycle_counter(); let dds_output = { + let io_update_trigger = { + let _io_update = gpiog + .pg7 + .into_alternate_af2() + .set_speed(hal::gpio::Speed::VeryHigh); + + // Configure the IO_Update signal for the DDS. + let mut hrtimer = hrtimer::HighResTimerE::new( + dp.HRTIM_TIME, + dp.HRTIM_MASTER, + dp.HRTIM_COMMON, + ccdr.clocks, + ccdr.peripheral.HRTIM, + ); + + // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes + // are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32 + // QSPI clock cycles. The QSPI is configured for 40MHz, so this comes out to an + // offset of 800nS. We use 900ns to be safe - note that the timer is triggered after + // the QSPI write, which can take approximately 120nS, so there is additional + // margin. + hrtimer.configure_single_shot( + hrtimer::Channel::Two, + 50_e-9, + 900_e-9, + ); + + // Ensure that we have enough time for an IO-update every sample. + assert!(1.0 / (1000 * SAMPLE_FREQUENCY_KHZ) as f32 > 900_e-9); + + hrtimer + }; + let timer3 = dp.TIM3.timer( SAMPLE_FREQUENCY_KHZ.khz(), ccdr.peripheral.TIM3, &ccdr.clocks, ); - DdsOutput::new(timer3) + DdsOutput::new(timer3, io_update_trigger) }; // Start sampling ADCs. @@ -792,14 +791,9 @@ const APP: () = { } } - #[task(binds = TIM3, resources=[dds_output, pounder], priority = 3)] + #[task(binds = TIM3, resources=[dds_output], priority = 3)] fn dds_update(c: dds_update::Context) { - if let Some(pounder) = c.resources.pounder { - if let Some(profile) = c.resources.dds_output.update_handler() { - pounder.ad9959.interface.write_profile(profile).unwrap(); - pounder.io_update_trigger.trigger(); - } - } + c.resources.dds_output.update_handler(); } #[task(binds=DMA1_STR3, resources=[adcs, dacs, pounder, dds_output, iir_state, iir_ch], priority=2)] @@ -828,22 +822,20 @@ const APP: () = { }; let dds_output = &mut c.resources.dds_output; - c.resources.pounder.lock(|pounder| { - if let Some(pounder) = pounder { - dds_output.lock(|dds_output| { - let profile = pounder - .ad9959 - .serialize_profile( - pounder::Channel::Out0.into(), - 100_000_000_f32, - 0.0_f32, - *adc0 as f32 / 0xFFFF as f32, - ) - .unwrap(); - dds_output.push(profile); - }); - } - }); + if let Some(pounder) = c.resources.pounder { + dds_output.lock(|dds_output| { + let profile = pounder + .ad9959 + .serialize_profile( + pounder::Channel::Out0.into(), + 100_000_000_f32, + 0.0_f32, + *adc0 as f32 / 0xFFFF as f32, + ) + .unwrap(); + dds_output.push(profile); + }); + } } c.resources.dacs.next_data(&dac0, &dac1); @@ -944,42 +936,6 @@ const APP: () = { Ok::(req) }) }), - "pounder/in0": pounder::ChannelState, (|state| { - c.resources.pounder.lock(|pounder| { - match pounder { - Some(pounder) => - pounder.set_channel_state(pounder::Channel::In0, state), - _ => Err(pounder::Error::Access), - } - }) - }), - "pounder/in1": pounder::ChannelState, (|state| { - c.resources.pounder.lock(|pounder| { - match pounder { - Some(pounder) => - pounder.set_channel_state(pounder::Channel::In1, state), - _ => Err(pounder::Error::Access), - } - }) - }), - "pounder/out0": pounder::ChannelState, (|state| { - c.resources.pounder.lock(|pounder| { - match pounder { - Some(pounder) => - pounder.set_channel_state(pounder::Channel::Out0, state), - _ => Err(pounder::Error::Access), - } - }) - }), - "pounder/out1": pounder::ChannelState, (|state| { - c.resources.pounder.lock(|pounder| { - match pounder { - Some(pounder) => - pounder.set_channel_state(pounder::Channel::Out1, state), - _ => Err(pounder::Error::Access), - } - }) - }), "pounder/dds/clock": pounder::DdsClockConfig, (|config| { c.resources.pounder.lock(|pounder| { match pounder { diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index b673887..ac37491 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -7,7 +7,6 @@ mod rf_power; pub use dds_output::DdsOutput; use super::hal; -use super::hrtimer::HighResTimerE; use attenuators::AttenuatorInterface; use rf_power::PowerMeasurementInterface; @@ -37,6 +36,7 @@ pub enum Error { } #[derive(Debug, Copy, Clone)] +#[allow(dead_code)] pub enum Channel { In0, In1, @@ -291,7 +291,6 @@ impl ad9959::Interface for QspiInterface { /// A structure containing implementation for Pounder hardware. pub struct PounderDevices { pub ad9959: ad9959::Ad9959, - pub io_update_trigger: HighResTimerE, mcp23017: mcp23017::MCP23017>, attenuator_spi: hal::spi::Spi, adc1: hal::adc::Adc, @@ -306,7 +305,6 @@ impl PounderDevices { /// Args: /// * `ad9959` - The DDS driver for the pounder hardware. /// * `attenuator_spi` - A SPI interface to control digital attenuators. - /// * `io_update_timer` - The HRTimer with the IO_update signal connected to the output. /// * `adc1` - The ADC1 peripheral for measuring power. /// * `adc2` - The ADC2 peripheral for measuring power. /// * `adc1_in_p` - The input channel for the RF power measurement on IN0. @@ -314,7 +312,6 @@ impl PounderDevices { pub fn new( mcp23017: mcp23017::MCP23017>, ad9959: ad9959::Ad9959, - io_update_trigger: HighResTimerE, attenuator_spi: hal::spi::Spi, adc1: hal::adc::Adc, adc2: hal::adc::Adc, @@ -323,7 +320,6 @@ impl PounderDevices { ) -> Result { let mut devices = Self { mcp23017, - io_update_trigger, ad9959, attenuator_spi, adc1, @@ -431,33 +427,6 @@ impl PounderDevices { external_clock, }) } - - /// Configure a DDS channel. - /// - /// Args: - /// * `channel` - The pounder channel to configure. - /// * `state` - The state to configure the channel for. - pub fn set_channel_state( - &mut self, - channel: Channel, - state: ChannelState, - ) -> Result<(), Error> { - let profile = self - .ad9959 - .serialize_profile( - channel.into(), - state.parameters.frequency, - state.parameters.phase_offset, - state.parameters.amplitude, - ) - .map_err(|_| Error::Dds)?; - self.ad9959.interface.write_profile(profile).unwrap(); - self.io_update_trigger.trigger(); - - self.set_attenuation(channel, state.attenuation)?; - - Ok(()) - } } impl AttenuatorInterface for PounderDevices { From c518797d08587509275404c4b41dfbcbe1b280ec Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 17 Nov 2020 10:48:31 +0100 Subject: [PATCH 09/19] Removing unused code --- src/main.rs | 18 +++++++++--------- src/pounder/mod.rs | 30 +----------------------------- 2 files changed, 10 insertions(+), 38 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9033895..e73238c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -823,16 +823,16 @@ const APP: () = { let dds_output = &mut c.resources.dds_output; if let Some(pounder) = c.resources.pounder { + let profile = pounder + .ad9959 + .serialize_profile( + pounder::Channel::Out0.into(), + 100_000_000_f32, + 0.0_f32, + *adc0 as f32 / 0xFFFF as f32, + ) + .unwrap(); dds_output.lock(|dds_output| { - let profile = pounder - .ad9959 - .serialize_profile( - pounder::Channel::Out0.into(), - 100_000_000_f32, - 0.0_f32, - *adc0 as f32 / 0xFFFF as f32, - ) - .unwrap(); dds_output.push(profile); }); } diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index ac37491..d8db728 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -115,7 +115,7 @@ impl QspiInterface { }) } - pub fn start_stream(&mut self) -> Result<(), Error> { + fn start_stream(&mut self) -> Result<(), Error> { if self.qspi.is_busy() { return Err(Error::Qspi); } @@ -136,34 +136,6 @@ impl QspiInterface { Ok(()) } - - pub fn write_profile(&mut self, data: [u32; 4]) -> Result<(), Error> { - if self.streaming == false { - return Err(Error::Qspi); - } - - let qspi_regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; - unsafe { - core::ptr::write_volatile( - &qspi_regs.dr as *const _ as *mut u32, - data[0], - ); - core::ptr::write_volatile( - &qspi_regs.dr as *const _ as *mut u32, - data[1], - ); - core::ptr::write_volatile( - &qspi_regs.dr as *const _ as *mut u32, - data[2], - ); - core::ptr::write_volatile( - &qspi_regs.dr as *const _ as *mut u32, - data[3], - ); - } - - Ok(()) - } } impl ad9959::Interface for QspiInterface { From 72db53ccd00ceb3f77cb82875888e3a7d385cd12 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 17 Nov 2020 10:51:31 +0100 Subject: [PATCH 10/19] Removing pounder test, adding file --- pounder_test.py | 111 -------------------------------------- src/pounder/dds_output.rs | 62 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 111 deletions(-) delete mode 100644 pounder_test.py create mode 100644 src/pounder/dds_output.rs diff --git a/pounder_test.py b/pounder_test.py deleted file mode 100644 index 5d43dd3..0000000 --- a/pounder_test.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/python3 -""" -Description: Test Stabilizer communication and DDS configuration. - -Author: Ryan Summers -""" - -import socket -import json - -HOST = '10.0.16.99' -PORT = 1235 - -def do_request(s, request): - """ Perform a request with the Stabilizer. - - Args: - s: The socket to the stabilizer. - request: The request to transmit. - - Returns: - The received response object. - """ - # Transform the value field. - request['value'] = json.dumps(request['value'], separators=[',', ':']).replace('"', "'") - data = (json.dumps(request, separators=[',', ':']) + '\n').encode('ascii') - s.send(data) - - response = b'' - while not response.endswith(b'\n'): - response += s.recv(1024) - - # Decode the value array - response = json.loads(response.decode('ascii')) - response['value'] = response['value'].replace("'", '"') - response['value'] = json.loads(response['value']) - - return response - - -def read_attribute(s, attribute_name): - """ Read an attribute on the Stabilizer device. - - Args: - s: The socket to the stabilizer. - attribute_name: The name of the endpoint to write to (the attribute name). - - Returns: - The value of the attribute. May be a string or a dictionary. - """ - request = { - "req": "Read", - "attribute": attribute_name, - "value": "", - } - - response = do_request(s, request) - - if 'code' not in response or response['code'] != 200: - raise Exception(f'Failed to read {attribute_name}: {response}') - - return response['value'] - - -def write_attribute(s, attribute_name, attribute_value): - """ Write an attribute on the Stabilizer device. - - Args: - s: The socket to the stabilizer. - attribute_name: The name of the endpoint to write to (the attribute name). - attribute_value: The value to write to the attribute. May be a string or a dictionary. - """ - request = { - "req": "Write", - "attribute": attribute_name, - "value": attribute_value, - } - - response = do_request(s, request) - - if 'code' not in response or response['code'] != 200: - raise Exception(f'Failed to write {attribute_name}: {response}') - - -def main(): - """ Main program entry point. """ - with socket.socket() as s: - - # Connect to the stabilizer. - s.connect((HOST, PORT)) - - # A sample configuration for an output channel. - channel_config = { - 'attenuation': 0.0, - 'parameters': { - 'phase_offset': 0.5, - 'frequency': 100.0e6, - 'amplitude': 1.0, - 'enabled': True, - } - } - - # Configure OUT0 and read it back. - write_attribute(s, "pounder/out0", channel_config) - print('Pounder OUT0: ', read_attribute(s, "pounder/out0")) - - print('Pounder IN1: ', read_attribute(s, "pounder/in1")) - print('Pounder OUT1: ', read_attribute(s, "pounder/out1")) - -if __name__ == '__main__': - main() diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs new file mode 100644 index 0000000..2c793e7 --- /dev/null +++ b/src/pounder/dds_output.rs @@ -0,0 +1,62 @@ +use crate::hrtimer::HighResTimerE; +use stm32h7xx_hal as hal; + +pub struct DdsOutput { + profiles: heapless::spsc::Queue<[u32; 4], heapless::consts::U32>, + update_timer: hal::timer::Timer, + io_update_trigger: HighResTimerE, +} + +impl DdsOutput { + pub fn new( + mut timer: hal::timer::Timer, + io_update_trigger: HighResTimerE, + ) -> Self { + timer.pause(); + timer.reset_counter(); + timer.listen(hal::timer::Event::TimeOut); + + Self { + update_timer: timer, + io_update_trigger, + profiles: heapless::spsc::Queue::new(), + } + } + + pub fn update_handler(&mut self) { + match self.profiles.dequeue() { + Some(profile) => self.write_profile(profile), + None => self.update_timer.pause(), + } + } + + pub fn push(&mut self, profile: [u32; 4]) { + self.profiles.enqueue(profile).unwrap(); + self.update_timer.resume(); + } + + fn write_profile(&mut self, profile: [u32; 4]) { + let regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; + unsafe { + core::ptr::write_volatile( + ®s.dr as *const _ as *mut u32, + profile[0], + ); + core::ptr::write_volatile( + ®s.dr as *const _ as *mut u32, + profile[1], + ); + core::ptr::write_volatile( + ®s.dr as *const _ as *mut u32, + profile[2], + ); + core::ptr::write_volatile( + ®s.dr as *const _ as *mut u32, + profile[3], + ); + } + + // Trigger the IO_update signal generating timer to asynchronous create the IO_Update pulse. + self.io_update_trigger.trigger(); + } +} From 84167c7f6f2e7d907ac5df6c709112f65b579195 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 17 Nov 2020 11:28:45 +0100 Subject: [PATCH 11/19] Updating DDS control --- src/main.rs | 22 +++------------------- src/pounder/dds_output.rs | 1 + 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index e73238c..704a03e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ use heapless::{consts::*, String}; const SAMPLE_FREQUENCY_KHZ: u32 = 500; // The desired ADC sample processing buffer size. -const SAMPLE_BUFFER_SIZE: usize = 1; +const SAMPLE_BUFFER_SIZE: usize = 8; #[link_section = ".sram3.eth"] static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); @@ -829,7 +829,7 @@ const APP: () = { pounder::Channel::Out0.into(), 100_000_000_f32, 0.0_f32, - *adc0 as f32 / 0xFFFF as f32, + 1.0_f32, ) .unwrap(); dds_output.lock(|dds_output| { @@ -902,15 +902,7 @@ const APP: () = { Ok::(state) }), "stabilizer/afe0/gain": (|| c.resources.afe0.get_gain()), - "stabilizer/afe1/gain": (|| c.resources.afe1.get_gain()), - "pounder/dds/clock": (|| { - c.resources.pounder.lock(|pounder| { - match pounder { - Some(pounder) => pounder.get_dds_clock_config(), - _ => Err(pounder::Error::Access), - } - }) - }) + "stabilizer/afe1/gain": (|| c.resources.afe1.get_gain()) ], modifiable_attributes: [ @@ -936,14 +928,6 @@ const APP: () = { Ok::(req) }) }), - "pounder/dds/clock": pounder::DdsClockConfig, (|config| { - c.resources.pounder.lock(|pounder| { - match pounder { - Some(pounder) => pounder.configure_dds_clock(config), - _ => Err(pounder::Error::Access), - } - }) - }), "stabilizer/afe0/gain": afe::Gain, (|gain| { Ok::<(), ()>(c.resources.afe0.set_gain(gain)) }), diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index 2c793e7..61bd590 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -24,6 +24,7 @@ impl DdsOutput { } pub fn update_handler(&mut self) { + self.update_timer.clear_uif_bit(); match self.profiles.dequeue() { Some(profile) => self.write_profile(profile), None => self.update_timer.pause(), From 055c92c6845472dd851eaf1979f78d73a2e7f18f Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 17 Nov 2020 13:09:45 +0100 Subject: [PATCH 12/19] Adding WIP refactor --- ad9959/src/lib.rs | 61 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 21 ++++++-------- src/pounder/dds_output.rs | 1 + 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index 99f1072..b1a4001 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -549,3 +549,64 @@ impl Ad9959 { Ok(serialized) } } + +struct ProfileSerializer { + data: [u8; 16], + index: usize, +} + +impl ProfileSerializer { + fn new() -> Self { + Self { + data: [0; 16], + index: 0, + } + } + + fn add_write(&mut self, register: Register, value: &[u8]) { + let data = &mut self.data[self.index..]; + assert!(value.len() + 1 <= data.len()); + + data[0] = register as u8; + data[1..][..value.len()].copy_from_slice(value); + self.index += value.len() + 1; + } + + fn finalize(self) -> [u32; 4] { + assert!(self.index == self.data.len()); + unsafe { core::mem::transmute(self.data) } + } +} + +pub fn serialize_profile(channel: Channel, ftw: u32, pow: u16, acr: Option) -> [u32; 4] { + let mut serializer = ProfileSerializer::new(); + + let csr: u8 = *0x00_u8 + .set_bits(1..=2, Mode::FourBitSerial as u8) + .set_bit(4 + channel as usize, true); + + let acr: [u8; 3] = { + let mut data = [0u8; 3]; + if acr.is_some() { + data[2].set_bit(0, acr.is_some()); + data[0..2].copy_from_slice(&acr.unwrap().to_be_bytes()); + } + + data + }; + + // 4 bytes + serializer.add_write(Register::CSR, &[csr]); + serializer.add_write(Register::CSR, &[csr]); + + // 5 bytes + serializer.add_write(Register::CFTW0, &ftw.to_be_bytes()); + + // 3 bytes + serializer.add_write(Register::CPOW0, &pow.to_be_bytes()); + + // 4 bytes + serializer.add_write(Register::ACR, &acr); + + serializer.finalize() +} diff --git a/src/main.rs b/src/main.rs index 704a03e..b0666fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -821,18 +821,15 @@ const APP: () = { y1 as i16 as u16 ^ 0x8000 }; - let dds_output = &mut c.resources.dds_output; - if let Some(pounder) = c.resources.pounder { - let profile = pounder - .ad9959 - .serialize_profile( - pounder::Channel::Out0.into(), - 100_000_000_f32, - 0.0_f32, - 1.0_f32, - ) - .unwrap(); - dds_output.lock(|dds_output| { + if c.resources.pounder.is_some() { + let profile = ad9959::serialize_profile( + pounder::Channel::Out0.into(), + u32::MAX / 4, + 0, + None, + ); + + c.resources.dds_output.lock(|dds_output| { dds_output.push(profile); }); } diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index 61bd590..b847d28 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -14,6 +14,7 @@ impl DdsOutput { ) -> Self { timer.pause(); timer.reset_counter(); + timer.clear_uif_bit(); timer.listen(hal::timer::Event::TimeOut); Self { From 6c2bc22b7add42f5372081589b7a83d5b65c1f40 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 17 Nov 2020 14:23:56 +0100 Subject: [PATCH 13/19] Adding updates for QSPI streaming --- ad9959/src/lib.rs | 55 +++----------- src/main.rs | 148 ++++++++++++++++++-------------------- src/pounder/dds_output.rs | 32 ++------- src/pounder/mod.rs | 87 +++------------------- 4 files changed, 90 insertions(+), 232 deletions(-) diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index b1a4001..c9935d8 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -501,52 +501,8 @@ impl Ad9959 { / (1u64 << 32) as f32) } - pub fn serialize_profile( - &self, - channel: Channel, - freq: f32, - turns: f32, - amplitude: f32, - ) -> Result<[u32; 4], Error> { - let csr: u8 = *0x00_u8 - .set_bits(1..=2, self.communication_mode as u8) - .set_bit(4 + channel as usize, true); - - // The function for channel frequency is `f_out = FTW * f_s / 2^32`, where FTW is the - // frequency tuning word and f_s is the system clock rate. - let tuning_word: u32 = ((freq * (1u64 << 32) as f32) - / self.system_clock_frequency()) - as u32; - - let phase_offset: u16 = (turns * (1 << 14) as f32) as u16 & 0x3FFFu16; - let pow: u32 = *0u32 - .set_bits(24..32, Register::CPOW0 as u32) - .set_bits(8..24, phase_offset as u32) - .set_bits(0..8, Register::CFTW0 as u32); - - // Enable the amplitude multiplier for the channel if required. The amplitude control has - // full-scale at 0x3FF (amplitude of 1), so the multiplier should be disabled whenever - // full-scale is used. - let amplitude_control: u16 = (amplitude * (1 << 10) as f32) as u16; - - let acr: u32 = *0u32 - .set_bits(24..32, Register::ACR as u32) - .set_bits(0..10, amplitude_control as u32 & 0x3FF) - .set_bit(12, amplitude_control < (1 << 10)); - - let serialized: [u32; 4] = [ - u32::from_le_bytes([ - Register::CSR as u8, - csr, - Register::CSR as u8, - csr, - ]), - acr.to_be(), - pow.to_be(), - tuning_word.to_be(), - ]; - - Ok(serialized) + pub fn free(self) -> I { + self.interface } } @@ -578,7 +534,12 @@ impl ProfileSerializer { } } -pub fn serialize_profile(channel: Channel, ftw: u32, pow: u16, acr: Option) -> [u32; 4] { +pub fn serialize_profile( + channel: Channel, + ftw: u32, + pow: u16, + acr: Option, +) -> [u32; 4] { let mut serializer = ProfileSerializer::new(); let csr: u8 = *0x00_u8 diff --git a/src/main.rs b/src/main.rs index b0666fd..bdede85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ use heapless::{consts::*, String}; const SAMPLE_FREQUENCY_KHZ: u32 = 500; // The desired ADC sample processing buffer size. -const SAMPLE_BUFFER_SIZE: usize = 8; +const SAMPLE_BUFFER_SIZE: usize = 1; #[link_section = ".sram3.eth"] static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); @@ -194,7 +194,7 @@ const APP: () = { eeprom_i2c: hal::i2c::I2c, - dds_output: DdsOutput, + dds_output: Option, // Note: It appears that rustfmt generates a format that GDB cannot recognize, which // results in GDB breakpoints being set improperly. @@ -460,8 +460,9 @@ const APP: () = { // Measure the Pounder PGOOD output to detect if pounder is present on Stabilizer. let pounder_pgood = gpiob.pb13.into_pull_down_input(); delay.delay_ms(2u8); - let pounder_devices = if pounder_pgood.is_high().unwrap() { - let ad9959 = { + let (pounder_devices, dds_output) = if pounder_pgood.is_high().unwrap() + { + let mut ad9959 = { let qspi_interface = { // Instantiate the QUADSPI pins and peripheral interface. let qspi_pins = { @@ -592,20 +593,60 @@ const APP: () = { let adc1_in_p = gpiof.pf11.into_analog(); let adc2_in_p = gpiof.pf14.into_analog(); - Some( - pounder::PounderDevices::new( - io_expander, - ad9959, - spi, - adc1, - adc2, - adc1_in_p, - adc2_in_p, - ) - .unwrap(), + let pounder_devices = pounder::PounderDevices::new( + io_expander, + &mut ad9959, + spi, + adc1, + adc2, + adc1_in_p, + adc2_in_p, ) + .unwrap(); + + let dds_output = { + let io_update_trigger = { + let _io_update = gpiog + .pg7 + .into_alternate_af2() + .set_speed(hal::gpio::Speed::VeryHigh); + + // Configure the IO_Update signal for the DDS. + let mut hrtimer = hrtimer::HighResTimerE::new( + dp.HRTIM_TIME, + dp.HRTIM_MASTER, + dp.HRTIM_COMMON, + ccdr.clocks, + ccdr.peripheral.HRTIM, + ); + + // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes + // are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32 + // QSPI clock cycles. The QSPI is configured for 40MHz, so this comes out to an + // offset of 800nS. We use 900ns to be safe - note that the timer is triggered after + // the QSPI write, which can take approximately 120nS, so there is additional + // margin. + hrtimer.configure_single_shot( + hrtimer::Channel::Two, + 50_e-9, + 900_e-9, + ); + + // Ensure that we have enough time for an IO-update every sample. + assert!( + 1.0 / (1000 * SAMPLE_FREQUENCY_KHZ) as f32 > 900_e-9 + ); + + hrtimer + }; + + let qspi = ad9959.free(); + DdsOutput::new(qspi, io_update_trigger) + }; + + (Some(pounder_devices), Some(dds_output)) } else { - None + (None, None) }; let mut eeprom_i2c = { @@ -728,49 +769,6 @@ const APP: () = { // Utilize the cycle counter for RTIC scheduling. cp.DWT.enable_cycle_counter(); - let dds_output = { - let io_update_trigger = { - let _io_update = gpiog - .pg7 - .into_alternate_af2() - .set_speed(hal::gpio::Speed::VeryHigh); - - // Configure the IO_Update signal for the DDS. - let mut hrtimer = hrtimer::HighResTimerE::new( - dp.HRTIM_TIME, - dp.HRTIM_MASTER, - dp.HRTIM_COMMON, - ccdr.clocks, - ccdr.peripheral.HRTIM, - ); - - // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes - // are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32 - // QSPI clock cycles. The QSPI is configured for 40MHz, so this comes out to an - // offset of 800nS. We use 900ns to be safe - note that the timer is triggered after - // the QSPI write, which can take approximately 120nS, so there is additional - // margin. - hrtimer.configure_single_shot( - hrtimer::Channel::Two, - 50_e-9, - 900_e-9, - ); - - // Ensure that we have enough time for an IO-update every sample. - assert!(1.0 / (1000 * SAMPLE_FREQUENCY_KHZ) as f32 > 900_e-9); - - hrtimer - }; - - let timer3 = dp.TIM3.timer( - SAMPLE_FREQUENCY_KHZ.khz(), - ccdr.peripheral.TIM3, - &ccdr.clocks, - ); - - DdsOutput::new(timer3, io_update_trigger) - }; - // Start sampling ADCs. sampling_timer.start(); @@ -781,7 +779,6 @@ const APP: () = { adcs, dacs, dds_output, - pounder: pounder_devices, eeprom_i2c, @@ -791,13 +788,8 @@ const APP: () = { } } - #[task(binds = TIM3, resources=[dds_output], priority = 3)] - fn dds_update(c: dds_update::Context) { - c.resources.dds_output.update_handler(); - } - - #[task(binds=DMA1_STR3, resources=[adcs, dacs, pounder, dds_output, iir_state, iir_ch], priority=2)] - fn adc_update(mut c: adc_update::Context) { + #[task(binds=DMA1_STR3, resources=[adcs, dacs, dds_output, iir_state, iir_ch], priority=2)] + fn adc_update(c: adc_update::Context) { let (adc0_samples, adc1_samples) = c.resources.adcs.transfer_complete_handler(); @@ -820,25 +812,23 @@ const APP: () = { .update(&mut c.resources.iir_state[1], x1); y1 as i16 as u16 ^ 0x8000 }; + } - if c.resources.pounder.is_some() { - let profile = ad9959::serialize_profile( - pounder::Channel::Out0.into(), - u32::MAX / 4, - 0, - None, - ); + if let Some(dds_output) = c.resources.dds_output { + let profile = ad9959::serialize_profile( + pounder::Channel::Out0.into(), + u32::MAX / 4, + 0, + None, + ); - c.resources.dds_output.lock(|dds_output| { - dds_output.push(profile); - }); - } + dds_output.write_profile(profile); } c.resources.dacs.next_data(&dac0, &dac1); } - #[idle(resources=[net_interface, pounder, mac_addr, eth_mac, iir_state, iir_ch, afe0, afe1])] + #[idle(resources=[net_interface, mac_addr, eth_mac, iir_state, iir_ch, afe0, afe1])] fn idle(mut c: idle::Context) -> ! { let mut socket_set_entries: [_; 8] = Default::default(); let mut sockets = diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index b847d28..f242033 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -1,43 +1,21 @@ +use super::QspiInterface; use crate::hrtimer::HighResTimerE; use stm32h7xx_hal as hal; pub struct DdsOutput { - profiles: heapless::spsc::Queue<[u32; 4], heapless::consts::U32>, - update_timer: hal::timer::Timer, + _qspi: QspiInterface, io_update_trigger: HighResTimerE, } impl DdsOutput { - pub fn new( - mut timer: hal::timer::Timer, - io_update_trigger: HighResTimerE, - ) -> Self { - timer.pause(); - timer.reset_counter(); - timer.clear_uif_bit(); - timer.listen(hal::timer::Event::TimeOut); - + pub fn new(_qspi: QspiInterface, io_update_trigger: HighResTimerE) -> Self { Self { - update_timer: timer, + _qspi, io_update_trigger, - profiles: heapless::spsc::Queue::new(), } } - pub fn update_handler(&mut self) { - self.update_timer.clear_uif_bit(); - match self.profiles.dequeue() { - Some(profile) => self.write_profile(profile), - None => self.update_timer.pause(), - } - } - - pub fn push(&mut self, profile: [u32; 4]) { - self.profiles.enqueue(profile).unwrap(); - self.update_timer.resume(); - } - - fn write_profile(&mut self, profile: [u32; 4]) { + pub fn write_profile(&mut self, profile: [u32; 4]) { let regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; unsafe { core::ptr::write_volatile( diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index d8db728..d20f200 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -262,7 +262,6 @@ impl ad9959::Interface for QspiInterface { /// A structure containing implementation for Pounder hardware. pub struct PounderDevices { - pub ad9959: ad9959::Ad9959, mcp23017: mcp23017::MCP23017>, attenuator_spi: hal::spi::Spi, adc1: hal::adc::Adc, @@ -283,7 +282,7 @@ impl PounderDevices { /// * `adc2_in_p` - The input channel for the RF power measurement on IN1. pub fn new( mcp23017: mcp23017::MCP23017>, - ad9959: ad9959::Ad9959, + ad9959: &mut ad9959::Ad9959, attenuator_spi: hal::spi::Spi, adc1: hal::adc::Adc, adc2: hal::adc::Adc, @@ -292,7 +291,6 @@ impl PounderDevices { ) -> Result { let mut devices = Self { mcp23017, - ad9959, attenuator_spi, adc1, adc2, @@ -317,87 +315,18 @@ impl PounderDevices { .map_err(|_| Error::I2c)?; // Select the on-board clock with a 4x prescaler (400MHz). - devices.select_onboard_clock(4u8)?; - - // Run the DDS in stream-only mode (no read support). - devices.ad9959.interface.start_stream().unwrap(); - - Ok(devices) - } - - /// Select the an external for the DDS reference clock source. - /// - /// Args: - /// * `frequency` - The frequency of the external clock source. - /// * `multiplier` - The multiplier of the reference clock to use in the DDS. - fn select_external_clock( - &mut self, - frequency: f32, - prescaler: u8, - ) -> Result<(), Error> { - self.mcp23017 - .digital_write(EXT_CLK_SEL_PIN, true) - .map_err(|_| Error::I2c)?; - self.ad9959 - .configure_system_clock(frequency, prescaler) - .map_err(|_| Error::Dds)?; - - Ok(()) - } - - /// Select the onboard oscillator for the DDS reference clock source. - /// - /// Args: - /// * `multiplier` - The multiplier of the reference clock to use in the DDS. - fn select_onboard_clock(&mut self, multiplier: u8) -> Result<(), Error> { - self.mcp23017 + devices + .mcp23017 .digital_write(EXT_CLK_SEL_PIN, false) .map_err(|_| Error::I2c)?; - self.ad9959 - .configure_system_clock(100_000_000f32, multiplier) + ad9959 + .configure_system_clock(100_000_000f32, 4) .map_err(|_| Error::Dds)?; - Ok(()) - } + // Run the DDS in stream-only mode (no read support). + ad9959.interface.start_stream().unwrap(); - /// Configure the Pounder DDS clock. - /// - /// Args: - /// * `config` - The configuration of the DDS clock desired. - pub fn configure_dds_clock( - &mut self, - config: DdsClockConfig, - ) -> Result<(), Error> { - if config.external_clock { - self.select_external_clock( - config.reference_clock, - config.multiplier, - ) - } else { - self.select_onboard_clock(config.multiplier) - } - } - - /// Get the pounder DDS clock configuration - /// - /// Returns: - /// The current pounder DDS clock configuration. - pub fn get_dds_clock_config(&mut self) -> Result { - let external_clock = self - .mcp23017 - .digital_read(EXT_CLK_SEL_PIN) - .map_err(|_| Error::I2c)?; - let multiplier = self - .ad9959 - .get_reference_clock_multiplier() - .map_err(|_| Error::Dds)?; - let reference_clock = self.ad9959.get_reference_clock_frequency(); - - Ok(DdsClockConfig { - multiplier, - reference_clock, - external_clock, - }) + Ok(devices) } } From d2e8b30622351205e8679a19241d909eaf980417 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 17 Nov 2020 14:27:17 +0100 Subject: [PATCH 14/19] Increasing batch size --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index bdede85..4fcdb0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ use heapless::{consts::*, String}; const SAMPLE_FREQUENCY_KHZ: u32 = 500; // The desired ADC sample processing buffer size. -const SAMPLE_BUFFER_SIZE: usize = 1; +const SAMPLE_BUFFER_SIZE: usize = 2; #[link_section = ".sram3.eth"] static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); From d93d0c712510e4b27b79c4f8eb37e9dbe0a4cbb5 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 2 Dec 2020 17:01:40 +0100 Subject: [PATCH 15/19] Adding updated QSPI stream writer --- ad9959/src/lib.rs | 151 ++++++++++++++++++++++++-------------- src/main.rs | 22 +++--- src/pounder/dds_output.rs | 73 +++++++++++++----- src/pounder/mod.rs | 5 +- 4 files changed, 162 insertions(+), 89 deletions(-) diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index fd3c25b..b591c37 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] use bit_field::BitField; -use embedded_hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; +use embedded_hal::{blocking::delay::DelayUs, digital::v2::OutputPin}; /// A device driver for the AD9959 direct digital synthesis (DDS) chip. /// @@ -14,7 +14,7 @@ use embedded_hal::{blocking::delay::DelayMs, digital::v2::OutputPin}; /// The chip supports a number of serial interfaces to improve data throughput, including normal, /// dual, and quad SPI configurations. pub struct Ad9959 { - pub interface: INTERFACE, + interface: INTERFACE, reference_clock_frequency: f32, system_clock_multiplier: u8, communication_mode: Mode, @@ -72,6 +72,7 @@ pub enum Register { } /// Specifies an output channel of the AD9959 DDS chip. +#[derive(Copy, Clone, PartialEq)] pub enum Channel { One = 0, Two = 1, @@ -103,9 +104,9 @@ impl Ad9959 { /// `clock_frequency` to generate the system clock. pub fn new( interface: I, - reset_pin: &mut impl OutputPin, + mut reset_pin: impl OutputPin, io_update: &mut impl OutputPin, - delay: &mut impl DelayMs, + delay: &mut impl DelayUs, desired_mode: Mode, clock_frequency: f32, multiplier: u8, @@ -124,8 +125,9 @@ impl Ad9959 { io_update.set_low().or(Err(Error::Pin))?; - // Delay for a clock cycle to allow the device to reset. - delay.delay_ms((1000.0 / clock_frequency as f32) as u8); + // Delay for at least 1 SYNC_CLK period for the reset to occur. The SYNC_CLK is guaranteed + // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). + delay.delay_us(5); reset_pin.set_low().or(Err(Error::Pin))?; @@ -141,8 +143,11 @@ impl Ad9959 { // Latch the new interface configuration. io_update.set_high().or(Err(Error::Pin))?; - // Delay for a clock cycle to allow the device to reset. - delay.delay_ms(2 * (1000.0 / clock_frequency as f32) as u8); + + // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed + // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). + delay.delay_us(5); + io_update.set_low().or(Err(Error::Pin))?; ad9959 @@ -150,6 +155,13 @@ impl Ad9959 { .configure_mode(desired_mode) .or(Err(Error::Interface))?; + // Empirical evidence indicates a delay is necessary here for the IO update to become + // active. This is likely due to needing to wait at least 1 clock cycle of the DDS for the + // interface update to occur. + // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed + // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). + delay.delay_us(5); + // Read back the CSR to ensure it specifies the mode correctly. let mut updated_csr: [u8; 1] = [0]; ad9959.read(Register::CSR, &mut updated_csr)?; @@ -480,24 +492,96 @@ impl Ad9959 { / (1u64 << 32) as f32) } - pub fn free(self) -> I { - self.interface + pub fn freeze(self) -> (I, DdsConfig) { + let config = DdsConfig { + mode: self.communication_mode, + }; + (self.interface, config) } } -struct ProfileSerializer { +pub struct DdsConfig { + mode: Mode, +} + +impl DdsConfig { + pub fn builder(&self) -> ProfileSerializer { + ProfileSerializer::new(self.mode) + } +} + +pub struct ProfileSerializer { data: [u8; 16], index: usize, + mode: Mode, } impl ProfileSerializer { - fn new() -> Self { + fn new(mode: Mode) -> Self { Self { + mode, data: [0; 16], index: 0, } } + pub fn update_channels( + &mut self, + channels: &[Channel], + ftw: Option, + pow: Option, + acr: Option, + ) { + // If there are no updates requested, skip this update cycle. + if (ftw.is_none() && acr.is_none() && pow.is_none()) + || channels.len() == 0 + { + panic!("Invalid config"); + } + let mut csr: u8 = *0u8.set_bits(1..3, self.mode as u8); + for channel in channels.iter() { + csr.set_bit(4 + *channel as usize, true); + } + + self.add_write(Register::CSR, &[csr]); + + if let Some(ftw) = ftw { + self.add_write(Register::CFTW0, &ftw.to_be_bytes()); + } + + if let Some(pow) = pow { + self.add_write(Register::CPOW0, &pow.to_be_bytes()); + } + + if let Some(acr) = acr { + self.add_write(Register::ACR, &acr.to_be_bytes()); + } + } + + pub fn finalize<'a>(&'a mut self) -> &[u32] { + //&self.data[..self.index] + // Pad the buffer to 32-bit alignment by adding dummy writes to CSR and FR2. + let padding = 4 - (self.index % 4); + match padding { + 0 => {} + 1 => { + // For a pad size of 1, we have to pad with 5 bytes to align things. + self.add_write(Register::CSR, &[(self.mode as u8) << 1]); + self.add_write(Register::FR2, &[0, 0, 0]); + } + 2 => self.add_write(Register::CSR, &[(self.mode as u8) << 1]), + 3 => self.add_write(Register::FR2, &[0, 0, 0]), + + _ => panic!("Invalid"), + } + unsafe { + core::slice::from_raw_parts::<'a, u32>( + &self.data as *const _ as *const u32, + self.index / 4, + ) + } + } + fn add_write(&mut self, register: Register, value: &[u8]) { let data = &mut self.data[self.index..]; assert!(value.len() + 1 <= data.len()); @@ -506,47 +590,4 @@ impl ProfileSerializer { data[1..][..value.len()].copy_from_slice(value); self.index += value.len() + 1; } - - fn finalize(self) -> [u32; 4] { - assert!(self.index == self.data.len()); - unsafe { core::mem::transmute(self.data) } - } -} - -pub fn serialize_profile( - channel: Channel, - ftw: u32, - pow: u16, - acr: Option, -) -> [u32; 4] { - let mut serializer = ProfileSerializer::new(); - - let csr: u8 = *0x00_u8 - .set_bits(1..=2, Mode::FourBitSerial as u8) - .set_bit(4 + channel as usize, true); - - let acr: [u8; 3] = { - let mut data = [0u8; 3]; - if acr.is_some() { - data[2].set_bit(0, acr.is_some()); - data[0..2].copy_from_slice(&acr.unwrap().to_be_bytes()); - } - - data - }; - - // 4 bytes - serializer.add_write(Register::CSR, &[csr]); - serializer.add_write(Register::CSR, &[csr]); - - // 5 bytes - serializer.add_write(Register::CFTW0, &ftw.to_be_bytes()); - - // 3 bytes - serializer.add_write(Register::CPOW0, &pow.to_be_bytes()); - - // 4 bytes - serializer.add_write(Register::ACR, &acr); - - serializer.finalize() } diff --git a/src/main.rs b/src/main.rs index eb70cd7..f6a6d91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,8 +72,8 @@ mod server; use adc::{Adc0Input, Adc1Input, AdcInputs}; use dac::{Dac0Output, Dac1Output, DacOutputs}; -use pounder::DdsOutput; use dsp::iir; +use pounder::DdsOutput; #[cfg(not(feature = "semihosting"))] fn init_log() {} @@ -509,12 +509,12 @@ const APP: () = { pounder::QspiInterface::new(qspi).unwrap() }; - let mut reset_pin = gpioa.pa0.into_push_pull_output(); + let reset_pin = gpioa.pa0.into_push_pull_output(); let mut io_update = gpiog.pg7.into_push_pull_output(); let ad9959 = ad9959::Ad9959::new( qspi_interface, - &mut reset_pin, + reset_pin, &mut io_update, &mut delay, ad9959::Mode::FourBitSerial, @@ -643,8 +643,9 @@ const APP: () = { hrtimer }; - let qspi = ad9959.free(); - DdsOutput::new(qspi, io_update_trigger) + let (mut qspi, config) = ad9959.freeze(); + qspi.start_stream().unwrap(); + DdsOutput::new(qspi, io_update_trigger, config) }; (Some(pounder_devices), Some(dds_output)) @@ -817,14 +818,13 @@ const APP: () = { } if let Some(dds_output) = c.resources.dds_output { - let profile = ad9959::serialize_profile( - pounder::Channel::Out0.into(), - u32::MAX / 4, - 0, + let builder = dds_output.builder().update_channels( + &[pounder::Channel::Out0.into()], + Some(u32::MAX / 4), + None, None, ); - - dds_output.write_profile(profile); + builder.write_profile(); } c.resources.dacs.commit_data(); diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index f242033..3a3ce6c 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -1,42 +1,77 @@ use super::QspiInterface; use crate::hrtimer::HighResTimerE; +use ad9959::{Channel, DdsConfig, ProfileSerializer}; use stm32h7xx_hal as hal; pub struct DdsOutput { _qspi: QspiInterface, io_update_trigger: HighResTimerE, + config: DdsConfig, } impl DdsOutput { - pub fn new(_qspi: QspiInterface, io_update_trigger: HighResTimerE) -> Self { + pub fn new( + _qspi: QspiInterface, + io_update_trigger: HighResTimerE, + dds_config: DdsConfig, + ) -> Self { Self { + config: dds_config, _qspi, io_update_trigger, } } - pub fn write_profile(&mut self, profile: [u32; 4]) { + pub fn builder(&mut self) -> ProfileBuilder { + let builder = self.config.builder(); + ProfileBuilder { + dds_stream: self, + serializer: builder, + } + } + + fn write_profile(&mut self, profile: &[u32]) { + assert!(profile.len() <= 16); + + // Note(unsafe): We own the QSPI interface, so it is safe to access the registers in a raw + // fashion. let regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; - unsafe { - core::ptr::write_volatile( - ®s.dr as *const _ as *mut u32, - profile[0], - ); - core::ptr::write_volatile( - ®s.dr as *const _ as *mut u32, - profile[1], - ); - core::ptr::write_volatile( - ®s.dr as *const _ as *mut u32, - profile[2], - ); - core::ptr::write_volatile( - ®s.dr as *const _ as *mut u32, - profile[3], - ); + + for word in profile.iter() { + // Note(unsafe): We are writing to the SPI TX FIFO in a raw manner for performance. This + // is safe because we know the data register is a valid address to write to. + unsafe { + core::ptr::write_volatile( + ®s.dr as *const _ as *mut u32, + *word, + ); + } } // Trigger the IO_update signal generating timer to asynchronous create the IO_Update pulse. self.io_update_trigger.trigger(); } } + +pub struct ProfileBuilder<'a> { + dds_stream: &'a mut DdsOutput, + serializer: ProfileSerializer, +} + +impl<'a> ProfileBuilder<'a> { + pub fn update_channels( + mut self, + channels: &[Channel], + ftw: Option, + pow: Option, + acr: Option, + ) -> Self { + self.serializer.update_channels(channels, ftw, pow, acr); + self + } + + pub fn write_profile(mut self) { + let profile = self.serializer.finalize(); + self.dds_stream.write_profile(profile); + } +} diff --git a/src/pounder/mod.rs b/src/pounder/mod.rs index a1e9dbc..c20d251 100644 --- a/src/pounder/mod.rs +++ b/src/pounder/mod.rs @@ -115,7 +115,7 @@ impl QspiInterface { }) } - fn start_stream(&mut self) -> Result<(), Error> { + pub fn start_stream(&mut self) -> Result<(), Error> { if self.qspi.is_busy() { return Err(Error::Qspi); } @@ -323,9 +323,6 @@ impl PounderDevices { .configure_system_clock(100_000_000f32, 4) .map_err(|_| Error::Dds)?; - // Run the DDS in stream-only mode (no read support). - ad9959.interface.start_stream().unwrap(); - Ok(devices) } } From 91f722f4500a74c98375c36dcf8491794b3dc91b Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 2 Dec 2020 17:11:06 +0100 Subject: [PATCH 16/19] Fixing buffer size --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 2cf5645..6e120c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,7 +58,7 @@ use heapless::{consts::*, String}; const SAMPLE_FREQUENCY_KHZ: u32 = 500; // The desired ADC sample processing buffer size. -const SAMPLE_BUFFER_SIZE: usize = 2; +const SAMPLE_BUFFER_SIZE: usize = 1; #[link_section = ".sram3.eth"] static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); From 677d017c3cd08f86f44d00c592b1bf72af142adb Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 2 Dec 2020 17:40:24 +0100 Subject: [PATCH 17/19] Adding documentation --- ad9959/src/lib.rs | 64 ++++++++++++++++++++++++++++----------- src/hrtimer.rs | 17 +++++++++++ src/main.rs | 1 - src/pounder/dds_output.rs | 37 ++++++++++++++++++++-- 4 files changed, 97 insertions(+), 22 deletions(-) diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index b591c37..8501e3a 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -123,8 +123,6 @@ impl Ad9959 { // Reset the AD9959 reset_pin.set_high().or(Err(Error::Pin))?; - io_update.set_low().or(Err(Error::Pin))?; - // Delay for at least 1 SYNC_CLK period for the reset to occur. The SYNC_CLK is guaranteed // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). delay.delay_us(5); @@ -492,6 +490,13 @@ impl Ad9959 { / (1u64 << 32) as f32) } + /// Finalize DDS configuration + /// + /// # Note + /// This is intended for when the DDS profiles will be written as a stream of data to the DDS. + /// + /// # Returns + /// (I, config) where `I` is the interface to the DDS and `config` is the frozen `DdsConfig`. pub fn freeze(self) -> (I, DdsConfig) { let config = DdsConfig { mode: self.communication_mode, @@ -500,16 +505,20 @@ impl Ad9959 { } } +/// The frozen DDS configuration. pub struct DdsConfig { mode: Mode, } impl DdsConfig { + /// Create a serializer that can be used for generating a serialized DDS profile for writing to + /// a QSPI stream. pub fn builder(&self) -> ProfileSerializer { ProfileSerializer::new(self.mode) } } +/// Represents a means of serializing a DDS profile for writing to a stream. pub struct ProfileSerializer { data: [u8; 16], index: usize, @@ -517,6 +526,10 @@ pub struct ProfileSerializer { } impl ProfileSerializer { + /// Construct a new serializer. + /// + /// # Args + /// * `mode` - The communication mode of the DDS. fn new(mode: Mode) -> Self { Self { mode, @@ -525,6 +538,13 @@ impl ProfileSerializer { } } + /// Update a number of channels with the requested profile. + /// + /// # Args + /// * `channels` - A list of channels to apply the configuration to. + /// * `ftw` - If provided, indicates a frequency tuning word for the channels. + /// * `pow` - If provided, indicates a phase offset word for the channels. + /// * `acr` - If provided, indicates the amplitude control register for the channels. pub fn update_channels( &mut self, channels: &[Channel], @@ -532,12 +552,12 @@ impl ProfileSerializer { pow: Option, acr: Option, ) { - // If there are no updates requested, skip this update cycle. - if (ftw.is_none() && acr.is_none() && pow.is_none()) - || channels.len() == 0 - { - panic!("Invalid config"); - } + // The user should have provided something to update. + assert!( + (ftw.is_some() || acr.is_some() || pow.is_some()) + && channels.len() > 0 + ); + let mut csr: u8 = *0u8.set_bits(1..3, self.mode as u8); for channel in channels.iter() { csr.set_bit(4 + *channel as usize, true); @@ -558,8 +578,25 @@ impl ProfileSerializer { } } + /// Add a register write to the serialization data. + fn add_write(&mut self, register: Register, value: &[u8]) { + let data = &mut self.data[self.index..]; + assert!(value.len() + 1 <= data.len()); + + data[0] = register as u8; + data[1..][..value.len()].copy_from_slice(value); + self.index += value.len() + 1; + } + + /// Get the serialized profile as a slice of 32-bit words. + /// + /// # Note + /// The serialized profile will be padded to the next 32-bit word boundary by adding dummy + /// writes to the CSR or FR2 registers. + /// + /// # Returns + /// A slice of `u32` words representing the serialized profile. pub fn finalize<'a>(&'a mut self) -> &[u32] { - //&self.data[..self.index] // Pad the buffer to 32-bit alignment by adding dummy writes to CSR and FR2. let padding = 4 - (self.index % 4); match padding { @@ -581,13 +618,4 @@ impl ProfileSerializer { ) } } - - fn add_write(&mut self, register: Register, value: &[u8]) { - let data = &mut self.data[self.index..]; - assert!(value.len() + 1 <= data.len()); - - data[0] = register as u8; - data[1..][..value.len()].copy_from_slice(value); - self.index += value.len() + 1; - } } diff --git a/src/hrtimer.rs b/src/hrtimer.rs index 47ea5c2..5ccb2d7 100644 --- a/src/hrtimer.rs +++ b/src/hrtimer.rs @@ -1,11 +1,14 @@ +///! The HRTimer (High Resolution Timer) is used to generate IO_Update pulses to the Pounder DDS. use crate::hal; use hal::rcc::{rec, CoreClocks, ResetEnable}; +/// A HRTimer output channel. pub enum Channel { One, Two, } +/// The high resolution timer. Currently, only Timer E is supported. pub struct HighResTimerE { master: hal::stm32::HRTIM_MASTER, timer: hal::stm32::HRTIM_TIME, @@ -15,6 +18,7 @@ pub struct HighResTimerE { } impl HighResTimerE { + /// Construct a new high resolution timer for generating IO_update signals. pub fn new( timer_regs: hal::stm32::HRTIM_TIME, master_regs: hal::stm32::HRTIM_MASTER, @@ -32,6 +36,18 @@ impl HighResTimerE { } } + /// Configure the timer to operate in single-shot mode. + /// + /// # Note + /// This will configure the timer to generate a single pulse on an output channel. The timer + /// will only count up once and must be `trigger()`'d after / configured. + /// + /// The output will be asserted from `set_offset` to `set_offset` + `set_duration` in the count. + /// + /// # Args + /// * `channel` - The timer output channel to configure. + /// * `set_duration` - The duration that the output should be asserted for. + /// * `set_offset` - The first time at which the output should be asserted. pub fn configure_single_shot( &mut self, channel: Channel, @@ -97,6 +113,7 @@ impl HighResTimerE { self.master.mcr.modify(|_, w| w.tecen().set_bit()); } + /// Generate a single trigger of the timer to start the output pulse generation. pub fn trigger(&mut self) { // Generate a reset event to force the timer to start counting. self.common.cr2.write(|w| w.terst().set_bit()); diff --git a/src/main.rs b/src/main.rs index 6e120c9..80cb90a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -766,7 +766,6 @@ const APP: () = { }; cp.SCB.enable_icache(); - //cp.SCB.enable_dcache(&mut cp.CPUID); // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); // info!("Built on {}", build_info::BUILT_TIME_UTC); diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index b519c83..e2179f1 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -1,8 +1,10 @@ +///! The DdsOutput is used as an output stream to the pounder DDS. use super::QspiInterface; use crate::hrtimer::HighResTimerE; use ad9959::{Channel, DdsConfig, ProfileSerializer}; use stm32h7xx_hal as hal; +/// The DDS profile update stream. pub struct DdsOutput { _qspi: QspiInterface, io_update_trigger: HighResTimerE, @@ -10,11 +12,23 @@ pub struct DdsOutput { } impl DdsOutput { + /// Construct a new DDS output stream. + /// + /// # Note + /// It is assumed that the QSPI stream and the IO_Update trigger timer have been configured in a + /// way such that the profile has sufficient time to be written before the IO_Update signal is + /// generated. + /// + /// # Args + /// * `qspi` - The QSPI interface to the run the stream on. + /// * `io_update_trigger` - The HighResTimerE used to generate IO_Update pulses. + /// * `dds_config` - The frozen DDS configuration. pub fn new( - _qspi: QspiInterface, + mut qspi: QspiInterface, io_update_trigger: HighResTimerE, dds_config: DdsConfig, ) -> Self { + qspi.start_stream(); Self { config: dds_config, _qspi, @@ -22,6 +36,7 @@ impl DdsOutput { } } + /// Get a builder for serializing a Pounder DDS profile. pub fn builder(&mut self) -> ProfileBuilder { let builder = self.config.builder(); ProfileBuilder { @@ -30,9 +45,15 @@ impl DdsOutput { } } + /// Write a profile to the stream. + /// + /// # Note: + /// If a profile of more than 4 words is provided, it is possible that the QSPI interface will + /// stall execution. + /// + /// # Args + /// * `profile` - The serialized DDS profile to write. fn write_profile(&mut self, profile: &[u32]) { - assert!(profile.len() <= 16); - // Note(unsafe): We own the QSPI interface, so it is safe to access the registers in a raw // fashion. let regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; @@ -47,17 +68,26 @@ impl DdsOutput { ); } } + // Trigger the IO_update signal generating timer to asynchronous create the IO_Update pulse. self.io_update_trigger.trigger(); } } +/// A temporary builder for serializing and writing profiles. pub struct ProfileBuilder<'a> { dds_stream: &'a mut DdsOutput, serializer: ProfileSerializer, } impl<'a> ProfileBuilder<'a> { + /// Update a number of channels with the provided configuration + /// + /// # Args + /// * `channels` - A list of channels to apply the configuration to. + /// * `ftw` - If provided, indicates a frequency tuning word for the channels. + /// * `pow` - If provided, indicates a phase offset word for the channels. + /// * `acr` - If provided, indicates the amplitude control register for the channels. pub fn update_channels( mut self, channels: &[Channel], @@ -69,6 +99,7 @@ impl<'a> ProfileBuilder<'a> { self } + /// Write the profile to the DDS asynchronously. pub fn write_profile(mut self) { let profile = self.serializer.finalize(); self.dds_stream.write_profile(profile); From f4a435739649faff04e2d655ac1b3f66ca07d5d9 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 2 Dec 2020 18:08:49 +0100 Subject: [PATCH 18/19] Fixing semantics --- src/main.rs | 3 +-- src/pounder/dds_output.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 80cb90a..28c2ed9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -645,8 +645,7 @@ const APP: () = { hrtimer }; - let (mut qspi, config) = ad9959.freeze(); - qspi.start_stream().unwrap(); + let (qspi, config) = ad9959.freeze(); DdsOutput::new(qspi, io_update_trigger, config) }; diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index e2179f1..a8fc432 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -28,10 +28,10 @@ impl DdsOutput { io_update_trigger: HighResTimerE, dds_config: DdsConfig, ) -> Self { - qspi.start_stream(); + qspi.start_stream().unwrap(); Self { config: dds_config, - _qspi, + _qspi: qspi, io_update_trigger, } } From 14a647867a123097e9a2f7da1b656aa3e8c76ed8 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 7 Dec 2020 10:55:09 +0100 Subject: [PATCH 19/19] Updating after review --- ad9959/src/lib.rs | 27 +++++++++++---------------- src/hrtimer.rs | 17 ++++++++++++++--- src/main.rs | 16 ++++++++++------ src/pounder/dds_output.rs | 4 ++++ 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/ad9959/src/lib.rs b/ad9959/src/lib.rs index 8501e3a..d5de1ec 100644 --- a/ad9959/src/lib.rs +++ b/ad9959/src/lib.rs @@ -124,7 +124,8 @@ impl Ad9959 { reset_pin.set_high().or(Err(Error::Pin))?; // Delay for at least 1 SYNC_CLK period for the reset to occur. The SYNC_CLK is guaranteed - // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). + // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to + // guarantee conformance with datasheet requirements. delay.delay_us(5); reset_pin.set_low().or(Err(Error::Pin))?; @@ -143,7 +144,8 @@ impl Ad9959 { io_update.set_high().or(Err(Error::Pin))?; // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed - // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). + // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to + // guarantee conformance with datasheet requirements. delay.delay_us(5); io_update.set_low().or(Err(Error::Pin))?; @@ -157,7 +159,8 @@ impl Ad9959 { // active. This is likely due to needing to wait at least 1 clock cycle of the DDS for the // interface update to occur. // Delay for at least 1 SYNC_CLK period for the update to occur. The SYNC_CLK is guaranteed - // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). + // to be at least 250KHz (1/4 of 1MHz minimum REF_CLK). We use 5uS instead of 4uS to + // guarantee conformance with datasheet requirements. delay.delay_us(5); // Read back the CSR to ensure it specifies the mode correctly. @@ -552,12 +555,6 @@ impl ProfileSerializer { pow: Option, acr: Option, ) { - // The user should have provided something to update. - assert!( - (ftw.is_some() || acr.is_some() || pow.is_some()) - && channels.len() > 0 - ); - let mut csr: u8 = *0u8.set_bits(1..3, self.mode as u8); for channel in channels.iter() { csr.set_bit(4 + *channel as usize, true); @@ -581,8 +578,6 @@ impl ProfileSerializer { /// Add a register write to the serialization data. fn add_write(&mut self, register: Register, value: &[u8]) { let data = &mut self.data[self.index..]; - assert!(value.len() + 1 <= data.len()); - data[0] = register as u8; data[1..][..value.len()].copy_from_slice(value); self.index += value.len() + 1; @@ -592,24 +587,24 @@ impl ProfileSerializer { /// /// # Note /// The serialized profile will be padded to the next 32-bit word boundary by adding dummy - /// writes to the CSR or FR2 registers. + /// writes to the CSR or LSRR registers. /// /// # Returns /// A slice of `u32` words representing the serialized profile. pub fn finalize<'a>(&'a mut self) -> &[u32] { - // Pad the buffer to 32-bit alignment by adding dummy writes to CSR and FR2. + // Pad the buffer to 32-bit alignment by adding dummy writes to CSR and LSRR. let padding = 4 - (self.index % 4); match padding { 0 => {} 1 => { // For a pad size of 1, we have to pad with 5 bytes to align things. self.add_write(Register::CSR, &[(self.mode as u8) << 1]); - self.add_write(Register::FR2, &[0, 0, 0]); + self.add_write(Register::LSRR, &[0, 0, 0]); } 2 => self.add_write(Register::CSR, &[(self.mode as u8) << 1]), - 3 => self.add_write(Register::FR2, &[0, 0, 0]), + 3 => self.add_write(Register::LSRR, &[0, 0, 0]), - _ => panic!("Invalid"), + _ => unreachable!(), } unsafe { core::slice::from_raw_parts::<'a, u32>( diff --git a/src/hrtimer.rs b/src/hrtimer.rs index 5ccb2d7..2831bbe 100644 --- a/src/hrtimer.rs +++ b/src/hrtimer.rs @@ -68,28 +68,39 @@ impl HighResTimerE { // Determine the clock divider, which may be 1, 2, or 4. We will choose a clock divider that // allows us the highest resolution per tick, so lower dividers are favored. - let divider: u8 = if source_cycles < 0xFFDF { + let setting: u8 = if source_cycles < 0xFFDF { 1 } else if (source_cycles / 2) < 0xFFDF { 2 } else if (source_cycles / 4) < 0xFFDF { - 4 + 3 } else { panic!("Unattainable timing parameters!"); }; + let divider = 1 << (setting - 1); + // The period register must be greater than or equal to 3 cycles. let period = (source_cycles / divider as u32) as u16; assert!(period > 2); // We now have the prescaler and the period registers. Configure the timer. + // Note(unsafe): The prescaler is guaranteed to be greater than or equal to 4 (minimum + // allowed value) due to the addition. The setting is always 1, 2, or 3, which represents + // all valid values. self.timer .timecr - .modify(|_, w| unsafe { w.ck_pscx().bits(divider + 4) }); + .modify(|_, w| unsafe { w.ck_pscx().bits(setting + 4) }); + + // Note(unsafe): The period register is guaranteed to be a 16-bit value, which will fit in + // this register. self.timer.perer.write(|w| unsafe { w.perx().bits(period) }); // Configure the comparator 1 level. let offset = (set_offset * source_frequency as f32) as u16; + // Note(unsafe): The offset is always a 16-bit value, so is always valid for values >= 3, as + // specified by the datasheet. + assert!(offset >= 3); self.timer .cmp1er .write(|w| unsafe { w.cmp1x().bits(offset) }); diff --git a/src/main.rs b/src/main.rs index 28c2ed9..f7b07e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -625,12 +625,16 @@ const APP: () = { ccdr.peripheral.HRTIM, ); - // IO_Update should be latched for 50ns after the QSPI profile write. Profile writes - // are always 16 bytes, with 2 cycles required per byte, coming out to a total of 32 - // QSPI clock cycles. The QSPI is configured for 40MHz, so this comes out to an - // offset of 800nS. We use 900ns to be safe - note that the timer is triggered after - // the QSPI write, which can take approximately 120nS, so there is additional - // margin. + // IO_Update should be latched for 4 SYNC_CLK cycles after the QSPI profile + // write. With pounder SYNC_CLK running at 100MHz (1/4 of the pounder reference + // clock of 400MHz), this corresponds to 40ns. To accomodate rounding errors, we + // use 50ns instead. + // + // Profile writes are always 16 bytes, with 2 cycles required per byte, coming + // out to a total of 32 QSPI clock cycles. The QSPI is configured for 40MHz, so + // this comes out to an offset of 800nS. We use 900ns to be safe - note that the + // timer is triggered after the QSPI write, which can take approximately 120nS, + // so there is additional margin. hrtimer.configure_single_shot( hrtimer::Channel::Two, 50_e-9, diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index a8fc432..418eb65 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -58,6 +58,10 @@ impl DdsOutput { // fashion. let regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; + if regs.sr.read().flevel() != 0 { + warn!("QSPI stalling") + } + for word in profile.iter() { // Note(unsafe): We are writing to the SPI TX FIFO in a raw manner for performance. This // is safe because we know the data register is a valid address to write to.