Adding updated QSPI stream writer
This commit is contained in:
parent
01a169ca69
commit
d93d0c7125
@ -1,7 +1,7 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
use bit_field::BitField;
|
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.
|
/// 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,
|
/// The chip supports a number of serial interfaces to improve data throughput, including normal,
|
||||||
/// dual, and quad SPI configurations.
|
/// dual, and quad SPI configurations.
|
||||||
pub struct Ad9959<INTERFACE> {
|
pub struct Ad9959<INTERFACE> {
|
||||||
pub interface: INTERFACE,
|
interface: INTERFACE,
|
||||||
reference_clock_frequency: f32,
|
reference_clock_frequency: f32,
|
||||||
system_clock_multiplier: u8,
|
system_clock_multiplier: u8,
|
||||||
communication_mode: Mode,
|
communication_mode: Mode,
|
||||||
@ -72,6 +72,7 @@ pub enum Register {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Specifies an output channel of the AD9959 DDS chip.
|
/// Specifies an output channel of the AD9959 DDS chip.
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
pub enum Channel {
|
pub enum Channel {
|
||||||
One = 0,
|
One = 0,
|
||||||
Two = 1,
|
Two = 1,
|
||||||
@ -103,9 +104,9 @@ impl<I: Interface> Ad9959<I> {
|
|||||||
/// `clock_frequency` to generate the system clock.
|
/// `clock_frequency` to generate the system clock.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
interface: I,
|
interface: I,
|
||||||
reset_pin: &mut impl OutputPin,
|
mut reset_pin: impl OutputPin,
|
||||||
io_update: &mut impl OutputPin,
|
io_update: &mut impl OutputPin,
|
||||||
delay: &mut impl DelayMs<u8>,
|
delay: &mut impl DelayUs<u8>,
|
||||||
desired_mode: Mode,
|
desired_mode: Mode,
|
||||||
clock_frequency: f32,
|
clock_frequency: f32,
|
||||||
multiplier: u8,
|
multiplier: u8,
|
||||||
@ -124,8 +125,9 @@ impl<I: Interface> Ad9959<I> {
|
|||||||
|
|
||||||
io_update.set_low().or(Err(Error::Pin))?;
|
io_update.set_low().or(Err(Error::Pin))?;
|
||||||
|
|
||||||
// Delay for a clock cycle to allow the device to reset.
|
// Delay for at least 1 SYNC_CLK period for the reset to occur. The SYNC_CLK is guaranteed
|
||||||
delay.delay_ms((1000.0 / clock_frequency as f32) as u8);
|
// to be at least 250KHz (1/4 of 1MHz minimum REF_CLK).
|
||||||
|
delay.delay_us(5);
|
||||||
|
|
||||||
reset_pin.set_low().or(Err(Error::Pin))?;
|
reset_pin.set_low().or(Err(Error::Pin))?;
|
||||||
|
|
||||||
@ -141,8 +143,11 @@ impl<I: Interface> Ad9959<I> {
|
|||||||
|
|
||||||
// Latch the new interface configuration.
|
// Latch the new interface configuration.
|
||||||
io_update.set_high().or(Err(Error::Pin))?;
|
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))?;
|
io_update.set_low().or(Err(Error::Pin))?;
|
||||||
|
|
||||||
ad9959
|
ad9959
|
||||||
@ -150,6 +155,13 @@ impl<I: Interface> Ad9959<I> {
|
|||||||
.configure_mode(desired_mode)
|
.configure_mode(desired_mode)
|
||||||
.or(Err(Error::Interface))?;
|
.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.
|
// Read back the CSR to ensure it specifies the mode correctly.
|
||||||
let mut updated_csr: [u8; 1] = [0];
|
let mut updated_csr: [u8; 1] = [0];
|
||||||
ad9959.read(Register::CSR, &mut updated_csr)?;
|
ad9959.read(Register::CSR, &mut updated_csr)?;
|
||||||
@ -480,24 +492,96 @@ impl<I: Interface> Ad9959<I> {
|
|||||||
/ (1u64 << 32) as f32)
|
/ (1u64 << 32) as f32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn free(self) -> I {
|
pub fn freeze(self) -> (I, DdsConfig) {
|
||||||
self.interface
|
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],
|
data: [u8; 16],
|
||||||
index: usize,
|
index: usize,
|
||||||
|
mode: Mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProfileSerializer {
|
impl ProfileSerializer {
|
||||||
fn new() -> Self {
|
fn new(mode: Mode) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
mode,
|
||||||
data: [0; 16],
|
data: [0; 16],
|
||||||
index: 0,
|
index: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_channels(
|
||||||
|
&mut self,
|
||||||
|
channels: &[Channel],
|
||||||
|
ftw: Option<u32>,
|
||||||
|
pow: Option<u16>,
|
||||||
|
acr: Option<u16>,
|
||||||
|
) {
|
||||||
|
// 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]) {
|
fn add_write(&mut self, register: Register, value: &[u8]) {
|
||||||
let data = &mut self.data[self.index..];
|
let data = &mut self.data[self.index..];
|
||||||
assert!(value.len() + 1 <= data.len());
|
assert!(value.len() + 1 <= data.len());
|
||||||
@ -506,47 +590,4 @@ impl ProfileSerializer {
|
|||||||
data[1..][..value.len()].copy_from_slice(value);
|
data[1..][..value.len()].copy_from_slice(value);
|
||||||
self.index += value.len() + 1;
|
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<u16>,
|
|
||||||
) -> [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()
|
|
||||||
}
|
}
|
||||||
|
22
src/main.rs
22
src/main.rs
@ -72,8 +72,8 @@ mod server;
|
|||||||
|
|
||||||
use adc::{Adc0Input, Adc1Input, AdcInputs};
|
use adc::{Adc0Input, Adc1Input, AdcInputs};
|
||||||
use dac::{Dac0Output, Dac1Output, DacOutputs};
|
use dac::{Dac0Output, Dac1Output, DacOutputs};
|
||||||
use pounder::DdsOutput;
|
|
||||||
use dsp::iir;
|
use dsp::iir;
|
||||||
|
use pounder::DdsOutput;
|
||||||
|
|
||||||
#[cfg(not(feature = "semihosting"))]
|
#[cfg(not(feature = "semihosting"))]
|
||||||
fn init_log() {}
|
fn init_log() {}
|
||||||
@ -509,12 +509,12 @@ const APP: () = {
|
|||||||
pounder::QspiInterface::new(qspi).unwrap()
|
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 mut io_update = gpiog.pg7.into_push_pull_output();
|
||||||
|
|
||||||
let ad9959 = ad9959::Ad9959::new(
|
let ad9959 = ad9959::Ad9959::new(
|
||||||
qspi_interface,
|
qspi_interface,
|
||||||
&mut reset_pin,
|
reset_pin,
|
||||||
&mut io_update,
|
&mut io_update,
|
||||||
&mut delay,
|
&mut delay,
|
||||||
ad9959::Mode::FourBitSerial,
|
ad9959::Mode::FourBitSerial,
|
||||||
@ -643,8 +643,9 @@ const APP: () = {
|
|||||||
hrtimer
|
hrtimer
|
||||||
};
|
};
|
||||||
|
|
||||||
let qspi = ad9959.free();
|
let (mut qspi, config) = ad9959.freeze();
|
||||||
DdsOutput::new(qspi, io_update_trigger)
|
qspi.start_stream().unwrap();
|
||||||
|
DdsOutput::new(qspi, io_update_trigger, config)
|
||||||
};
|
};
|
||||||
|
|
||||||
(Some(pounder_devices), Some(dds_output))
|
(Some(pounder_devices), Some(dds_output))
|
||||||
@ -817,14 +818,13 @@ const APP: () = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(dds_output) = c.resources.dds_output {
|
if let Some(dds_output) = c.resources.dds_output {
|
||||||
let profile = ad9959::serialize_profile(
|
let builder = dds_output.builder().update_channels(
|
||||||
pounder::Channel::Out0.into(),
|
&[pounder::Channel::Out0.into()],
|
||||||
u32::MAX / 4,
|
Some(u32::MAX / 4),
|
||||||
0,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
builder.write_profile();
|
||||||
dds_output.write_profile(profile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.resources.dacs.commit_data();
|
c.resources.dacs.commit_data();
|
||||||
|
@ -1,42 +1,77 @@
|
|||||||
use super::QspiInterface;
|
use super::QspiInterface;
|
||||||
use crate::hrtimer::HighResTimerE;
|
use crate::hrtimer::HighResTimerE;
|
||||||
|
use ad9959::{Channel, DdsConfig, ProfileSerializer};
|
||||||
use stm32h7xx_hal as hal;
|
use stm32h7xx_hal as hal;
|
||||||
|
|
||||||
pub struct DdsOutput {
|
pub struct DdsOutput {
|
||||||
_qspi: QspiInterface,
|
_qspi: QspiInterface,
|
||||||
io_update_trigger: HighResTimerE,
|
io_update_trigger: HighResTimerE,
|
||||||
|
config: DdsConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DdsOutput {
|
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 {
|
Self {
|
||||||
|
config: dds_config,
|
||||||
_qspi,
|
_qspi,
|
||||||
io_update_trigger,
|
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() };
|
let regs = unsafe { &*hal::stm32::QUADSPI::ptr() };
|
||||||
unsafe {
|
|
||||||
core::ptr::write_volatile(
|
for word in profile.iter() {
|
||||||
®s.dr as *const _ as *mut u32,
|
// Note(unsafe): We are writing to the SPI TX FIFO in a raw manner for performance. This
|
||||||
profile[0],
|
// is safe because we know the data register is a valid address to write to.
|
||||||
);
|
unsafe {
|
||||||
core::ptr::write_volatile(
|
core::ptr::write_volatile(
|
||||||
®s.dr as *const _ as *mut u32,
|
®s.dr as *const _ as *mut u32,
|
||||||
profile[1],
|
*word,
|
||||||
);
|
);
|
||||||
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.
|
// Trigger the IO_update signal generating timer to asynchronous create the IO_Update pulse.
|
||||||
self.io_update_trigger.trigger();
|
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<u32>,
|
||||||
|
pow: Option<u16>,
|
||||||
|
acr: Option<u16>,
|
||||||
|
) -> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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() {
|
if self.qspi.is_busy() {
|
||||||
return Err(Error::Qspi);
|
return Err(Error::Qspi);
|
||||||
}
|
}
|
||||||
@ -323,9 +323,6 @@ impl PounderDevices {
|
|||||||
.configure_system_clock(100_000_000f32, 4)
|
.configure_system_clock(100_000_000f32, 4)
|
||||||
.map_err(|_| Error::Dds)?;
|
.map_err(|_| Error::Dds)?;
|
||||||
|
|
||||||
// Run the DDS in stream-only mode (no read support).
|
|
||||||
ad9959.interface.start_stream().unwrap();
|
|
||||||
|
|
||||||
Ok(devices)
|
Ok(devices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user