Adding updates for QSPI streaming

This commit is contained in:
Ryan Summers 2020-11-17 14:23:56 +01:00
parent 055c92c684
commit 6c2bc22b7a
4 changed files with 90 additions and 232 deletions

View File

@ -501,52 +501,8 @@ impl<I: Interface> Ad9959<I> {
/ (1u64 << 32) as f32) / (1u64 << 32) as f32)
} }
pub fn serialize_profile( pub fn free(self) -> I {
&self, self.interface
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)
} }
} }
@ -578,7 +534,12 @@ impl ProfileSerializer {
} }
} }
pub fn serialize_profile(channel: Channel, ftw: u32, pow: u16, acr: Option<u16>) -> [u32; 4] { pub fn serialize_profile(
channel: Channel,
ftw: u32,
pow: u16,
acr: Option<u16>,
) -> [u32; 4] {
let mut serializer = ProfileSerializer::new(); let mut serializer = ProfileSerializer::new();
let csr: u8 = *0x00_u8 let csr: u8 = *0x00_u8

View File

@ -55,7 +55,7 @@ use heapless::{consts::*, String};
const SAMPLE_FREQUENCY_KHZ: u32 = 500; const SAMPLE_FREQUENCY_KHZ: u32 = 500;
// The desired ADC sample processing buffer size. // The desired ADC sample processing buffer size.
const SAMPLE_BUFFER_SIZE: usize = 8; const SAMPLE_BUFFER_SIZE: usize = 1;
#[link_section = ".sram3.eth"] #[link_section = ".sram3.eth"]
static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new();
@ -194,7 +194,7 @@ const APP: () = {
eeprom_i2c: hal::i2c::I2c<hal::stm32::I2C2>, eeprom_i2c: hal::i2c::I2c<hal::stm32::I2C2>,
dds_output: DdsOutput, dds_output: Option<DdsOutput>,
// Note: It appears that rustfmt generates a format that GDB cannot recognize, which // Note: It appears that rustfmt generates a format that GDB cannot recognize, which
// results in GDB breakpoints being set improperly. // 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. // Measure the Pounder PGOOD output to detect if pounder is present on Stabilizer.
let pounder_pgood = gpiob.pb13.into_pull_down_input(); let pounder_pgood = gpiob.pb13.into_pull_down_input();
delay.delay_ms(2u8); delay.delay_ms(2u8);
let pounder_devices = if pounder_pgood.is_high().unwrap() { let (pounder_devices, dds_output) = if pounder_pgood.is_high().unwrap()
let ad9959 = { {
let mut ad9959 = {
let qspi_interface = { let qspi_interface = {
// Instantiate the QUADSPI pins and peripheral interface. // Instantiate the QUADSPI pins and peripheral interface.
let qspi_pins = { let qspi_pins = {
@ -592,20 +593,60 @@ const APP: () = {
let adc1_in_p = gpiof.pf11.into_analog(); let adc1_in_p = gpiof.pf11.into_analog();
let adc2_in_p = gpiof.pf14.into_analog(); let adc2_in_p = gpiof.pf14.into_analog();
Some( let pounder_devices = pounder::PounderDevices::new(
pounder::PounderDevices::new( io_expander,
io_expander, &mut ad9959,
ad9959, spi,
spi, adc1,
adc1, adc2,
adc2, adc1_in_p,
adc1_in_p, adc2_in_p,
adc2_in_p,
)
.unwrap(),
) )
.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 { } else {
None (None, None)
}; };
let mut eeprom_i2c = { let mut eeprom_i2c = {
@ -728,49 +769,6 @@ const APP: () = {
// Utilize the cycle counter for RTIC scheduling. // Utilize the cycle counter for RTIC scheduling.
cp.DWT.enable_cycle_counter(); 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. // Start sampling ADCs.
sampling_timer.start(); sampling_timer.start();
@ -781,7 +779,6 @@ const APP: () = {
adcs, adcs,
dacs, dacs,
dds_output, dds_output,
pounder: pounder_devices, pounder: pounder_devices,
eeprom_i2c, eeprom_i2c,
@ -791,13 +788,8 @@ const APP: () = {
} }
} }
#[task(binds = TIM3, resources=[dds_output], priority = 3)] #[task(binds=DMA1_STR3, resources=[adcs, dacs, dds_output, iir_state, iir_ch], priority=2)]
fn dds_update(c: dds_update::Context) { fn adc_update(c: adc_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) {
let (adc0_samples, adc1_samples) = let (adc0_samples, adc1_samples) =
c.resources.adcs.transfer_complete_handler(); c.resources.adcs.transfer_complete_handler();
@ -820,25 +812,23 @@ const APP: () = {
.update(&mut c.resources.iir_state[1], x1); .update(&mut c.resources.iir_state[1], x1);
y1 as i16 as u16 ^ 0x8000 y1 as i16 as u16 ^ 0x8000
}; };
}
if c.resources.pounder.is_some() { if let Some(dds_output) = c.resources.dds_output {
let profile = ad9959::serialize_profile( let profile = ad9959::serialize_profile(
pounder::Channel::Out0.into(), pounder::Channel::Out0.into(),
u32::MAX / 4, u32::MAX / 4,
0, 0,
None, None,
); );
c.resources.dds_output.lock(|dds_output| { dds_output.write_profile(profile);
dds_output.push(profile);
});
}
} }
c.resources.dacs.next_data(&dac0, &dac1); 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) -> ! { fn idle(mut c: idle::Context) -> ! {
let mut socket_set_entries: [_; 8] = Default::default(); let mut socket_set_entries: [_; 8] = Default::default();
let mut sockets = let mut sockets =

View File

@ -1,43 +1,21 @@
use super::QspiInterface;
use crate::hrtimer::HighResTimerE; use crate::hrtimer::HighResTimerE;
use stm32h7xx_hal as hal; use stm32h7xx_hal as hal;
pub struct DdsOutput { pub struct DdsOutput {
profiles: heapless::spsc::Queue<[u32; 4], heapless::consts::U32>, _qspi: QspiInterface,
update_timer: hal::timer::Timer<hal::stm32::TIM3>,
io_update_trigger: HighResTimerE, io_update_trigger: HighResTimerE,
} }
impl DdsOutput { impl DdsOutput {
pub fn new( pub fn new(_qspi: QspiInterface, io_update_trigger: HighResTimerE) -> Self {
mut timer: hal::timer::Timer<hal::stm32::TIM3>,
io_update_trigger: HighResTimerE,
) -> Self {
timer.pause();
timer.reset_counter();
timer.clear_uif_bit();
timer.listen(hal::timer::Event::TimeOut);
Self { Self {
update_timer: timer, _qspi,
io_update_trigger, io_update_trigger,
profiles: heapless::spsc::Queue::new(),
} }
} }
pub fn update_handler(&mut self) { pub fn write_profile(&mut self, profile: [u32; 4]) {
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]) {
let regs = unsafe { &*hal::stm32::QUADSPI::ptr() }; let regs = unsafe { &*hal::stm32::QUADSPI::ptr() };
unsafe { unsafe {
core::ptr::write_volatile( core::ptr::write_volatile(

View File

@ -262,7 +262,6 @@ impl ad9959::Interface for QspiInterface {
/// A structure containing implementation for Pounder hardware. /// A structure containing implementation for Pounder hardware.
pub struct PounderDevices { pub struct PounderDevices {
pub ad9959: ad9959::Ad9959<QspiInterface>,
mcp23017: mcp23017::MCP23017<hal::i2c::I2c<hal::stm32::I2C1>>, mcp23017: mcp23017::MCP23017<hal::i2c::I2c<hal::stm32::I2C1>>,
attenuator_spi: hal::spi::Spi<hal::stm32::SPI1, hal::spi::Enabled, u8>, attenuator_spi: hal::spi::Spi<hal::stm32::SPI1, hal::spi::Enabled, u8>,
adc1: hal::adc::Adc<hal::stm32::ADC1, hal::adc::Enabled>, adc1: hal::adc::Adc<hal::stm32::ADC1, hal::adc::Enabled>,
@ -283,7 +282,7 @@ impl PounderDevices {
/// * `adc2_in_p` - The input channel for the RF power measurement on IN1. /// * `adc2_in_p` - The input channel for the RF power measurement on IN1.
pub fn new( pub fn new(
mcp23017: mcp23017::MCP23017<hal::i2c::I2c<hal::stm32::I2C1>>, mcp23017: mcp23017::MCP23017<hal::i2c::I2c<hal::stm32::I2C1>>,
ad9959: ad9959::Ad9959<QspiInterface>, ad9959: &mut ad9959::Ad9959<QspiInterface>,
attenuator_spi: hal::spi::Spi<hal::stm32::SPI1, hal::spi::Enabled, u8>, attenuator_spi: hal::spi::Spi<hal::stm32::SPI1, hal::spi::Enabled, u8>,
adc1: hal::adc::Adc<hal::stm32::ADC1, hal::adc::Enabled>, adc1: hal::adc::Adc<hal::stm32::ADC1, hal::adc::Enabled>,
adc2: hal::adc::Adc<hal::stm32::ADC2, hal::adc::Enabled>, adc2: hal::adc::Adc<hal::stm32::ADC2, hal::adc::Enabled>,
@ -292,7 +291,6 @@ impl PounderDevices {
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let mut devices = Self { let mut devices = Self {
mcp23017, mcp23017,
ad9959,
attenuator_spi, attenuator_spi,
adc1, adc1,
adc2, adc2,
@ -317,87 +315,18 @@ impl PounderDevices {
.map_err(|_| Error::I2c)?; .map_err(|_| Error::I2c)?;
// Select the on-board clock with a 4x prescaler (400MHz). // Select the on-board clock with a 4x prescaler (400MHz).
devices.select_onboard_clock(4u8)?; devices
.mcp23017
// 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
.digital_write(EXT_CLK_SEL_PIN, false) .digital_write(EXT_CLK_SEL_PIN, false)
.map_err(|_| Error::I2c)?; .map_err(|_| Error::I2c)?;
self.ad9959 ad9959
.configure_system_clock(100_000_000f32, multiplier) .configure_system_clock(100_000_000f32, 4)
.map_err(|_| Error::Dds)?; .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. Ok(devices)
///
/// 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<DdsClockConfig, Error> {
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,
})
} }
} }