From 585613f48f464c9df2d9637d5687cda16b73ee8a Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 17 Nov 2020 10:45:37 +0100 Subject: [PATCH 01/12] 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 02/12] 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 03/12] 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 04/12] 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 05/12] 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 06/12] 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 07/12] 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 08/12] 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 09/12] 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 10/12] 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 11/12] 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 12/12] 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.