Merge remote-tracking branch 'origin/master' into fast-truncate
* origin/master: Adding comment about checking for flag pass completion Adding comments about execution hanging to transfer complete waits Removing default parameter settings Updating DAC SPI structures to own HAL SPI structure for safety guarantees Moving constants to a new file Removing copy to DAC buffers, adding in-place borrow of output buffers Marking AXISRAM as NOLOAD Refactoring timer channels to macros, adding safety notes Adding DMA support for DAC writes Adding sampling_timer file Adding compile-time management of TIM2 channels Reverting changeset Adding documentation Reverting openocd change Adding updated docs for adc file Combining ADC + DAC ISRs Adding WIP updates Formatting Adding ADC/DAC modules Adding WIP updates to using DMA
This commit is contained in:
commit
cf086abaed
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -192,6 +192,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-dma"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c8c02e4347a0267ca60813c952017f4c5948c232474c6010a381a337f1bda4"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal"
|
||||
version = "0.2.4"
|
||||
@ -473,6 +482,7 @@ dependencies = [
|
||||
"nb 1.0.0",
|
||||
"panic-halt",
|
||||
"panic-semihosting",
|
||||
"paste",
|
||||
"serde",
|
||||
"serde-json-core",
|
||||
"smoltcp",
|
||||
@ -500,12 +510,13 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "stm32h7xx-hal"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/stm32-rs/stm32h7xx-hal#cbb31d0b6d0c8530437367032a600a4ff74657f7"
|
||||
source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#0bfeeca4ce120c1b7c6d140a7da73a4372b874d8"
|
||||
dependencies = [
|
||||
"bare-metal 1.0.0",
|
||||
"cast",
|
||||
"cortex-m",
|
||||
"cortex-m-rt",
|
||||
"embedded-dma",
|
||||
"embedded-hal",
|
||||
"nb 1.0.0",
|
||||
"paste",
|
||||
|
@ -40,6 +40,7 @@ embedded-hal = "0.2.4"
|
||||
nb = "1.0.0"
|
||||
asm-delay = "0.9.0"
|
||||
enum-iterator = "0.6.0"
|
||||
paste = "1"
|
||||
dsp = { path = "dsp" }
|
||||
|
||||
[dependencies.mcp23017]
|
||||
@ -56,6 +57,7 @@ path = "ad9959"
|
||||
[dependencies.stm32h7xx-hal]
|
||||
features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"]
|
||||
git = "https://github.com/stm32-rs/stm32h7xx-hal"
|
||||
branch = "dma"
|
||||
|
||||
[features]
|
||||
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]
|
||||
|
2
memory.x
2
memory.x
@ -17,7 +17,7 @@ SECTIONS {
|
||||
*(.itcm .itcm.*);
|
||||
. = ALIGN(8);
|
||||
} > ITCM
|
||||
.axisram : ALIGN(8) {
|
||||
.axisram (NOLOAD) : ALIGN(8) {
|
||||
*(.axisram .axisram.*);
|
||||
. = ALIGN(8);
|
||||
} > AXISRAM
|
||||
|
384
src/adc.rs
Normal file
384
src/adc.rs
Normal file
@ -0,0 +1,384 @@
|
||||
///! Stabilizer ADC management interface
|
||||
///!
|
||||
///! The Stabilizer ADCs utilize a DMA channel to trigger sampling. The SPI streams are configured
|
||||
///! for full-duplex operation, but only RX is connected to physical pins. A timer channel is
|
||||
///! configured to generate a DMA write into the SPI TXFIFO, which initiates a SPI transfer and
|
||||
///! results in an ADC sample read for both channels.
|
||||
///!
|
||||
///! In order to read multiple samples without interrupting the CPU, a separate DMA transfer is
|
||||
///! configured to read from each of the ADC SPI RX FIFOs. Due to the design of the SPI peripheral,
|
||||
///! these DMA transfers stall when no data is available in the FIFO. Thus, the DMA transfer only
|
||||
///! completes after all samples have been read. When this occurs, a CPU interrupt is generated so
|
||||
///! that software can process the acquired samples from both ADCs. Only one of the ADC DMA streams
|
||||
///! is configured to generate an interrupt to handle both transfers, so it is necessary to ensure
|
||||
///! both transfers are completed before reading the data. This is usually not significant for
|
||||
///! busy-waiting because the transfers should complete at approximately the same time.
|
||||
use super::{
|
||||
hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral,
|
||||
PeripheralToMemory, Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE,
|
||||
};
|
||||
|
||||
// The following data is written by the timer ADC sample trigger into each of the SPI TXFIFOs. Note
|
||||
// that because the SPI MOSI line is not connected, this data is dont-care. Data in AXI SRAM is not
|
||||
// initialized on boot, so the contents are random.
|
||||
#[link_section = ".axisram.buffers"]
|
||||
static mut SPI_START: [u16; 1] = [0x00];
|
||||
|
||||
// The following global buffers are used for the ADC sample DMA transfers. Two buffers are used for
|
||||
// each transfer in a ping-pong buffer configuration (one is being acquired while the other is being
|
||||
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
||||
// startup are undefined.
|
||||
#[link_section = ".axisram.buffers"]
|
||||
static mut ADC0_BUF0: [u16; SAMPLE_BUFFER_SIZE] = [0; SAMPLE_BUFFER_SIZE];
|
||||
|
||||
#[link_section = ".axisram.buffers"]
|
||||
static mut ADC0_BUF1: [u16; SAMPLE_BUFFER_SIZE] = [0; SAMPLE_BUFFER_SIZE];
|
||||
|
||||
#[link_section = ".axisram.buffers"]
|
||||
static mut ADC1_BUF0: [u16; SAMPLE_BUFFER_SIZE] = [0; SAMPLE_BUFFER_SIZE];
|
||||
|
||||
#[link_section = ".axisram.buffers"]
|
||||
static mut ADC1_BUF1: [u16; SAMPLE_BUFFER_SIZE] = [0; SAMPLE_BUFFER_SIZE];
|
||||
|
||||
/// SPI2 is used as a ZST (zero-sized type) for indicating a DMA transfer into the SPI2 TX FIFO
|
||||
/// whenever the tim2 update dma request occurs.
|
||||
struct SPI2 {
|
||||
_channel: sampling_timer::tim2::Channel1,
|
||||
}
|
||||
impl SPI2 {
|
||||
pub fn new(_channel: sampling_timer::tim2::Channel1) -> Self {
|
||||
Self { _channel }
|
||||
}
|
||||
}
|
||||
|
||||
// Note(unsafe): This structure is only safe to instantiate once. The DMA request is hard-coded and
|
||||
// may only be used if ownership of the timer2 channel 1 compare channel is assured, which is
|
||||
// ensured by maintaining ownership of the channel.
|
||||
unsafe impl TargetAddress<MemoryToPeripheral> for SPI2 {
|
||||
/// SPI2 is configured to operate using 16-bit transfer words.
|
||||
type MemSize = u16;
|
||||
|
||||
/// SPI2 DMA requests are generated whenever TIM2 CH1 comparison occurs.
|
||||
const REQUEST_LINE: Option<u8> = Some(DMAReq::TIM2_CH1 as u8);
|
||||
|
||||
/// Whenever the DMA request occurs, it should write into SPI2's TX FIFO to start a DMA
|
||||
/// transfer.
|
||||
fn address(&self) -> u32 {
|
||||
// Note(unsafe): It is assumed that SPI2 is owned by another DMA transfer and this DMA is
|
||||
// only used for the transmit-half of DMA.
|
||||
let regs = unsafe { &*hal::stm32::SPI2::ptr() };
|
||||
®s.txdr as *const _ as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// SPI3 is used as a ZST (zero-sized type) for indicating a DMA transfer into the SPI3 TX FIFO
|
||||
/// whenever the tim2 update dma request occurs.
|
||||
struct SPI3 {
|
||||
_channel: sampling_timer::tim2::Channel2,
|
||||
}
|
||||
impl SPI3 {
|
||||
pub fn new(_channel: sampling_timer::tim2::Channel2) -> Self {
|
||||
Self { _channel }
|
||||
}
|
||||
}
|
||||
|
||||
// Note(unsafe): This structure is only safe to instantiate once. The DMA request is hard-coded and
|
||||
// may only be used if ownership of the timer2 channel 2 compare channel is assured, which is
|
||||
// ensured by maintaining ownership of the channel.
|
||||
unsafe impl TargetAddress<MemoryToPeripheral> for SPI3 {
|
||||
/// SPI3 is configured to operate using 16-bit transfer words.
|
||||
type MemSize = u16;
|
||||
|
||||
/// SPI3 DMA requests are generated whenever TIM2 CH2 comparison occurs.
|
||||
const REQUEST_LINE: Option<u8> = Some(DMAReq::TIM2_CH2 as u8);
|
||||
|
||||
/// Whenever the DMA request occurs, it should write into SPI3's TX FIFO to start a DMA
|
||||
/// transfer.
|
||||
fn address(&self) -> u32 {
|
||||
// Note(unsafe): It is assumed that SPI3 is owned by another DMA transfer and this DMA is
|
||||
// only used for the transmit-half of DMA.
|
||||
let regs = unsafe { &*hal::stm32::SPI3::ptr() };
|
||||
®s.txdr as *const _ as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents both ADC input channels.
|
||||
pub struct AdcInputs {
|
||||
adc0: Adc0Input,
|
||||
adc1: Adc1Input,
|
||||
}
|
||||
|
||||
impl AdcInputs {
|
||||
/// Construct the ADC inputs.
|
||||
pub fn new(adc0: Adc0Input, adc1: Adc1Input) -> Self {
|
||||
Self { adc0, adc1 }
|
||||
}
|
||||
|
||||
/// Interrupt handler to handle when the sample collection DMA transfer completes.
|
||||
///
|
||||
/// # Returns
|
||||
/// (adc0, adc1) where adcN is a reference to the collected ADC samples. Two array references
|
||||
/// are returned - one for each ADC sample stream.
|
||||
pub fn transfer_complete_handler(
|
||||
&mut self,
|
||||
) -> (&[u16; SAMPLE_BUFFER_SIZE], &[u16; SAMPLE_BUFFER_SIZE]) {
|
||||
let adc0_buffer = self.adc0.transfer_complete_handler();
|
||||
let adc1_buffer = self.adc1.transfer_complete_handler();
|
||||
(adc0_buffer, adc1_buffer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents data associated with ADC0.
|
||||
pub struct Adc0Input {
|
||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
||||
transfer: Transfer<
|
||||
hal::dma::dma::Stream1<hal::stm32::DMA1>,
|
||||
hal::spi::Spi<hal::stm32::SPI2, hal::spi::Disabled, u16>,
|
||||
PeripheralToMemory,
|
||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
||||
>,
|
||||
_trigger_transfer: Transfer<
|
||||
hal::dma::dma::Stream0<hal::stm32::DMA1>,
|
||||
SPI2,
|
||||
MemoryToPeripheral,
|
||||
&'static mut [u16; 1],
|
||||
>,
|
||||
}
|
||||
|
||||
impl Adc0Input {
|
||||
/// Construct the ADC0 input channel.
|
||||
///
|
||||
/// # Args
|
||||
/// * `spi` - The SPI interface used to communicate with the ADC.
|
||||
/// * `trigger_stream` - The DMA stream used to trigger each ADC transfer by writing a word into
|
||||
/// the SPI TX FIFO.
|
||||
/// * `data_stream` - The DMA stream used to read samples received over SPI into a data buffer.
|
||||
/// * `_trigger_channel` - The ADC sampling timer output compare channel for read triggers.
|
||||
pub fn new(
|
||||
spi: hal::spi::Spi<hal::stm32::SPI2, hal::spi::Enabled, u16>,
|
||||
trigger_stream: hal::dma::dma::Stream0<hal::stm32::DMA1>,
|
||||
data_stream: hal::dma::dma::Stream1<hal::stm32::DMA1>,
|
||||
trigger_channel: sampling_timer::tim2::Channel1,
|
||||
) -> Self {
|
||||
// Generate DMA events when an output compare of the timer hitting zero (timer roll over)
|
||||
// occurs.
|
||||
trigger_channel.listen_dma();
|
||||
trigger_channel.to_output_compare(0);
|
||||
|
||||
// The trigger stream constantly writes to the TX FIFO using a static word (dont-care
|
||||
// contents). Thus, neither the memory or peripheral address ever change. This is run in
|
||||
// circular mode to be completed at every DMA request.
|
||||
let trigger_config = DmaConfig::default()
|
||||
.priority(Priority::High)
|
||||
.circular_buffer(true);
|
||||
|
||||
// Construct the trigger stream to write from memory to the peripheral.
|
||||
let mut trigger_transfer: Transfer<_, _, MemoryToPeripheral, _> =
|
||||
Transfer::init(
|
||||
trigger_stream,
|
||||
SPI2::new(trigger_channel),
|
||||
// Note(unsafe): Because this is a Memory->Peripheral transfer, this data is never
|
||||
// actually modified. It technically only needs to be immutably borrowed, but the
|
||||
// current HAL API only supports mutable borrows.
|
||||
unsafe { &mut SPI_START },
|
||||
None,
|
||||
trigger_config,
|
||||
);
|
||||
|
||||
// The data stream constantly reads from the SPI RX FIFO into a RAM buffer. The peripheral
|
||||
// stalls reads of the SPI RX FIFO until data is available, so the DMA transfer completes
|
||||
// after the requested number of samples have been collected. Note that only ADC1's data
|
||||
// stream is used to trigger a transfer completion interrupt.
|
||||
let data_config = DmaConfig::default()
|
||||
.memory_increment(true)
|
||||
.priority(Priority::VeryHigh);
|
||||
|
||||
// A SPI peripheral error interrupt is used to determine if the RX FIFO overflows. This
|
||||
// indicates that samples were dropped due to excessive processing time in the main
|
||||
// application (e.g. a second DMA transfer completes before the first was done with
|
||||
// processing). This is used as a flow control indicator to guarantee that no ADC samples
|
||||
// are lost.
|
||||
let mut spi = spi.disable();
|
||||
spi.listen(hal::spi::Event::Error);
|
||||
|
||||
// The data transfer is always a transfer of data from the peripheral to a RAM buffer.
|
||||
let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> =
|
||||
Transfer::init(
|
||||
data_stream,
|
||||
spi,
|
||||
// Note(unsafe): The ADC0_BUF0 is "owned" by this peripheral. It shall not be used
|
||||
// anywhere else in the module.
|
||||
unsafe { &mut ADC0_BUF0 },
|
||||
None,
|
||||
data_config,
|
||||
);
|
||||
|
||||
data_transfer.start(|spi| {
|
||||
// Allow the SPI FIFOs to operate using only DMA data channels.
|
||||
spi.enable_dma_rx();
|
||||
spi.enable_dma_tx();
|
||||
|
||||
// Enable SPI and start it in infinite transaction mode.
|
||||
spi.inner().cr1.modify(|_, w| w.spe().set_bit());
|
||||
spi.inner().cr1.modify(|_, w| w.cstart().started());
|
||||
});
|
||||
|
||||
trigger_transfer.start(|_| {});
|
||||
|
||||
Self {
|
||||
// Note(unsafe): The ADC0_BUF1 is "owned" by this peripheral. It shall not be used
|
||||
// anywhere else in the module.
|
||||
next_buffer: unsafe { Some(&mut ADC0_BUF1) },
|
||||
transfer: data_transfer,
|
||||
_trigger_transfer: trigger_transfer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a transfer completion.
|
||||
///
|
||||
/// # Returns
|
||||
/// A reference to the underlying buffer that has been filled with ADC samples.
|
||||
pub fn transfer_complete_handler(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] {
|
||||
let next_buffer = self.next_buffer.take().unwrap();
|
||||
|
||||
// Wait for the transfer to fully complete before continuing.
|
||||
// Note: If a device hangs up, check that this conditional is passing correctly, as there is
|
||||
// no time-out checks here in the interest of execution speed.
|
||||
while self.transfer.get_transfer_complete_flag() == false {}
|
||||
|
||||
// Start the next transfer.
|
||||
self.transfer.clear_interrupts();
|
||||
let (prev_buffer, _) =
|
||||
self.transfer.next_transfer(next_buffer).unwrap();
|
||||
|
||||
self.next_buffer.replace(prev_buffer);
|
||||
self.next_buffer.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the data input stream from ADC1
|
||||
pub struct Adc1Input {
|
||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
||||
transfer: Transfer<
|
||||
hal::dma::dma::Stream3<hal::stm32::DMA1>,
|
||||
hal::spi::Spi<hal::stm32::SPI3, hal::spi::Disabled, u16>,
|
||||
PeripheralToMemory,
|
||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
||||
>,
|
||||
_trigger_transfer: Transfer<
|
||||
hal::dma::dma::Stream2<hal::stm32::DMA1>,
|
||||
SPI3,
|
||||
MemoryToPeripheral,
|
||||
&'static mut [u16; 1],
|
||||
>,
|
||||
}
|
||||
|
||||
impl Adc1Input {
|
||||
/// Construct a new ADC1 input data stream.
|
||||
///
|
||||
/// # Args
|
||||
/// * `spi` - The SPI interface connected to ADC1.
|
||||
/// * `trigger_stream` - The DMA stream used to trigger ADC conversions on the SPI interface.
|
||||
/// * `data_stream` - The DMA stream used to read ADC samples from the SPI RX FIFO.
|
||||
/// * `trigger_channel` - The ADC sampling timer output compare channel for read triggers.
|
||||
pub fn new(
|
||||
spi: hal::spi::Spi<hal::stm32::SPI3, hal::spi::Enabled, u16>,
|
||||
trigger_stream: hal::dma::dma::Stream2<hal::stm32::DMA1>,
|
||||
data_stream: hal::dma::dma::Stream3<hal::stm32::DMA1>,
|
||||
trigger_channel: sampling_timer::tim2::Channel2,
|
||||
) -> Self {
|
||||
// Generate DMA events when an output compare of the timer hitting zero (timer roll over)
|
||||
// occurs.
|
||||
trigger_channel.listen_dma();
|
||||
trigger_channel.to_output_compare(0);
|
||||
|
||||
// The trigger stream constantly writes to the TX FIFO using a static word (dont-care
|
||||
// contents). Thus, neither the memory or peripheral address ever change. This is run in
|
||||
// circular mode to be completed at every DMA request.
|
||||
let trigger_config = DmaConfig::default()
|
||||
.priority(Priority::High)
|
||||
.circular_buffer(true);
|
||||
|
||||
// Construct the trigger stream to write from memory to the peripheral.
|
||||
let mut trigger_transfer: Transfer<_, _, MemoryToPeripheral, _> =
|
||||
Transfer::init(
|
||||
trigger_stream,
|
||||
SPI3::new(trigger_channel),
|
||||
// Note(unsafe). This transaction is read-only and SPI_START is a dont-care value,
|
||||
// so it is always safe to share.
|
||||
unsafe { &mut SPI_START },
|
||||
None,
|
||||
trigger_config,
|
||||
);
|
||||
|
||||
// The data stream constantly reads from the SPI RX FIFO into a RAM buffer. The peripheral
|
||||
// stalls reads of the SPI RX FIFO until data is available, so the DMA transfer completes
|
||||
// after the requested number of samples have been collected. Note that only ADC1's data
|
||||
// stream is used to trigger a transfer completion interrupt.
|
||||
let data_config = DmaConfig::default()
|
||||
.memory_increment(true)
|
||||
.transfer_complete_interrupt(true)
|
||||
.priority(Priority::VeryHigh);
|
||||
|
||||
// A SPI peripheral error interrupt is used to determine if the RX FIFO overflows. This
|
||||
// indicates that samples were dropped due to excessive processing time in the main
|
||||
// application (e.g. a second DMA transfer completes before the first was done with
|
||||
// processing). This is used as a flow control indicator to guarantee that no ADC samples
|
||||
// are lost.
|
||||
let mut spi = spi.disable();
|
||||
spi.listen(hal::spi::Event::Error);
|
||||
|
||||
// The data transfer is always a transfer of data from the peripheral to a RAM buffer.
|
||||
let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> =
|
||||
Transfer::init(
|
||||
data_stream,
|
||||
spi,
|
||||
// Note(unsafe): The ADC1_BUF0 is "owned" by this peripheral. It shall not be used
|
||||
// anywhere else in the module.
|
||||
unsafe { &mut ADC1_BUF0 },
|
||||
None,
|
||||
data_config,
|
||||
);
|
||||
|
||||
data_transfer.start(|spi| {
|
||||
// Allow the SPI FIFOs to operate using only DMA data channels.
|
||||
spi.enable_dma_rx();
|
||||
spi.enable_dma_tx();
|
||||
|
||||
// Enable SPI and start it in infinite transaction mode.
|
||||
spi.inner().cr1.modify(|_, w| w.spe().set_bit());
|
||||
spi.inner().cr1.modify(|_, w| w.cstart().started());
|
||||
});
|
||||
|
||||
trigger_transfer.start(|_| {});
|
||||
|
||||
Self {
|
||||
// Note(unsafe): The ADC1_BUF1 is "owned" by this peripheral. It shall not be used
|
||||
// anywhere else in the module.
|
||||
next_buffer: unsafe { Some(&mut ADC1_BUF1) },
|
||||
transfer: data_transfer,
|
||||
_trigger_transfer: trigger_transfer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle a transfer completion.
|
||||
///
|
||||
/// # Returns
|
||||
/// A reference to the underlying buffer that has been filled with ADC samples.
|
||||
pub fn transfer_complete_handler(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] {
|
||||
let next_buffer = self.next_buffer.take().unwrap();
|
||||
|
||||
// Wait for the transfer to fully complete before continuing.
|
||||
// Note: If a device hangs up, check that this conditional is passing correctly, as there is
|
||||
// no time-out checks here in the interest of execution speed.
|
||||
while self.transfer.get_transfer_complete_flag() == false {}
|
||||
|
||||
// Start the next transfer.
|
||||
self.transfer.clear_interrupts();
|
||||
let (prev_buffer, _) =
|
||||
self.transfer.next_transfer(next_buffer).unwrap();
|
||||
|
||||
self.next_buffer.replace(prev_buffer);
|
||||
self.next_buffer.as_ref().unwrap()
|
||||
}
|
||||
}
|
316
src/dac.rs
Normal file
316
src/dac.rs
Normal file
@ -0,0 +1,316 @@
|
||||
///! Stabilizer DAC management interface
|
||||
///!
|
||||
///! The Stabilizer DAC utilize a DMA channel to generate output updates. A timer channel is
|
||||
///! configured to generate a DMA write into the SPI TXFIFO, which initiates a SPI transfer and
|
||||
///! results in DAC update for both channels.
|
||||
use super::{
|
||||
hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress,
|
||||
Transfer, SAMPLE_BUFFER_SIZE,
|
||||
};
|
||||
|
||||
// The following global buffers are used for the DAC code DMA transfers. Two buffers are used for
|
||||
// each transfer in a ping-pong buffer configuration (one is being prepared while the other is being
|
||||
// processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on
|
||||
// startup are undefined.
|
||||
#[link_section = ".axisram.buffers"]
|
||||
static mut DAC0_BUF0: [u16; SAMPLE_BUFFER_SIZE] = [0; SAMPLE_BUFFER_SIZE];
|
||||
|
||||
#[link_section = ".axisram.buffers"]
|
||||
static mut DAC0_BUF1: [u16; SAMPLE_BUFFER_SIZE] = [0; SAMPLE_BUFFER_SIZE];
|
||||
|
||||
#[link_section = ".axisram.buffers"]
|
||||
static mut DAC1_BUF0: [u16; SAMPLE_BUFFER_SIZE] = [0; SAMPLE_BUFFER_SIZE];
|
||||
|
||||
#[link_section = ".axisram.buffers"]
|
||||
static mut DAC1_BUF1: [u16; SAMPLE_BUFFER_SIZE] = [0; SAMPLE_BUFFER_SIZE];
|
||||
|
||||
/// SPI4 is used as a type for indicating a DMA transfer into the SPI4 TX FIFO
|
||||
struct SPI4 {
|
||||
spi: hal::spi::Spi<hal::stm32::SPI4, hal::spi::Disabled, u16>,
|
||||
_channel: sampling_timer::tim2::Channel3,
|
||||
}
|
||||
|
||||
impl SPI4 {
|
||||
pub fn new(
|
||||
_channel: sampling_timer::tim2::Channel3,
|
||||
spi: hal::spi::Spi<hal::stm32::SPI4, hal::spi::Disabled, u16>,
|
||||
) -> Self {
|
||||
Self { _channel, spi }
|
||||
}
|
||||
}
|
||||
|
||||
// Note(unsafe): This is safe because the DMA request line is logically owned by this module.
|
||||
// Additionally, the SPI is owned by this structure and is known to be configured for u16 word
|
||||
// sizes.
|
||||
unsafe impl TargetAddress<MemoryToPeripheral> for SPI4 {
|
||||
/// SPI2 is configured to operate using 16-bit transfer words.
|
||||
type MemSize = u16;
|
||||
|
||||
/// SPI4 DMA requests are generated whenever TIM2 CH3 comparison occurs.
|
||||
const REQUEST_LINE: Option<u8> = Some(DMAReq::TIM2_CH3 as u8);
|
||||
|
||||
/// Whenever the DMA request occurs, it should write into SPI4's TX FIFO.
|
||||
fn address(&self) -> u32 {
|
||||
&self.spi.inner().txdr as *const _ as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// SPI5 is used as a ZST (zero-sized type) for indicating a DMA transfer into the SPI5 TX FIFO
|
||||
struct SPI5 {
|
||||
_channel: sampling_timer::tim2::Channel4,
|
||||
spi: hal::spi::Spi<hal::stm32::SPI5, hal::spi::Disabled, u16>,
|
||||
}
|
||||
|
||||
impl SPI5 {
|
||||
pub fn new(
|
||||
_channel: sampling_timer::tim2::Channel4,
|
||||
spi: hal::spi::Spi<hal::stm32::SPI5, hal::spi::Disabled, u16>,
|
||||
) -> Self {
|
||||
Self { _channel, spi }
|
||||
}
|
||||
}
|
||||
|
||||
// Note(unsafe): This is safe because the DMA request line is logically owned by this module.
|
||||
// Additionally, the SPI is owned by this structure and is known to be configured for u16 word
|
||||
// sizes.
|
||||
unsafe impl TargetAddress<MemoryToPeripheral> for SPI5 {
|
||||
/// SPI5 is configured to operate using 16-bit transfer words.
|
||||
type MemSize = u16;
|
||||
|
||||
/// SPI5 DMA requests are generated whenever TIM2 CH4 comparison occurs.
|
||||
const REQUEST_LINE: Option<u8> = Some(DMAReq::TIM2_CH4 as u8);
|
||||
|
||||
/// Whenever the DMA request occurs, it should write into SPI5's TX FIFO
|
||||
fn address(&self) -> u32 {
|
||||
&self.spi.inner().txdr as *const _ as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents both DAC output channels.
|
||||
pub struct DacOutputs {
|
||||
dac0: Dac0Output,
|
||||
dac1: Dac1Output,
|
||||
}
|
||||
|
||||
impl DacOutputs {
|
||||
/// Construct the DAC outputs.
|
||||
pub fn new(dac0: Dac0Output, dac1: Dac1Output) -> Self {
|
||||
Self { dac0, dac1 }
|
||||
}
|
||||
|
||||
/// Borrow the next DAC output buffers to populate the DAC output codes in-place.
|
||||
///
|
||||
/// # Returns
|
||||
/// (dac0, dac1) where each value is a mutable reference to the output code array for DAC0 and
|
||||
/// DAC1 respectively.
|
||||
pub fn prepare_data(
|
||||
&mut self,
|
||||
) -> (
|
||||
&mut [u16; SAMPLE_BUFFER_SIZE],
|
||||
&mut [u16; SAMPLE_BUFFER_SIZE],
|
||||
) {
|
||||
(self.dac0.prepare_buffer(), self.dac1.prepare_buffer())
|
||||
}
|
||||
|
||||
/// Enqueue the next DAC output codes for transmission.
|
||||
///
|
||||
/// # Note
|
||||
/// It is assumed that data was populated using `prepare_data()` before this function is
|
||||
/// called.
|
||||
pub fn commit_data(&mut self) {
|
||||
self.dac0.commit_buffer();
|
||||
self.dac1.commit_buffer();
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents data associated with DAC0.
|
||||
pub struct Dac0Output {
|
||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
||||
// Note: SPI TX functionality may not be used from this structure to ensure safety with DMA.
|
||||
transfer: Transfer<
|
||||
hal::dma::dma::Stream4<hal::stm32::DMA1>,
|
||||
SPI4,
|
||||
MemoryToPeripheral,
|
||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
||||
>,
|
||||
first_transfer: bool,
|
||||
}
|
||||
|
||||
impl Dac0Output {
|
||||
/// Construct the DAC0 output channel.
|
||||
///
|
||||
/// # Args
|
||||
/// * `spi` - The SPI interface used to communicate with the ADC.
|
||||
/// * `stream` - The DMA stream used to write DAC codes over SPI.
|
||||
/// * `trigger_channel` - The sampling timer output compare channel for update triggers.
|
||||
pub fn new(
|
||||
spi: hal::spi::Spi<hal::stm32::SPI4, hal::spi::Enabled, u16>,
|
||||
stream: hal::dma::dma::Stream4<hal::stm32::DMA1>,
|
||||
trigger_channel: sampling_timer::tim2::Channel3,
|
||||
) -> Self {
|
||||
// Generate DMA events when an output compare of the timer hitting zero (timer roll over)
|
||||
// occurs.
|
||||
trigger_channel.listen_dma();
|
||||
trigger_channel.to_output_compare(0);
|
||||
|
||||
// The stream constantly writes to the TX FIFO to write new update codes.
|
||||
let trigger_config = DmaConfig::default()
|
||||
.memory_increment(true)
|
||||
.peripheral_increment(false);
|
||||
|
||||
// Listen for any potential SPI error signals, which may indicate that we are not generating
|
||||
// update codes.
|
||||
let mut spi = spi.disable();
|
||||
spi.listen(hal::spi::Event::Error);
|
||||
|
||||
// Allow the SPI FIFOs to operate using only DMA data channels.
|
||||
spi.enable_dma_tx();
|
||||
|
||||
// Enable SPI and start it in infinite transaction mode.
|
||||
spi.inner().cr1.modify(|_, w| w.spe().set_bit());
|
||||
spi.inner().cr1.modify(|_, w| w.cstart().started());
|
||||
|
||||
// Construct the trigger stream to write from memory to the peripheral.
|
||||
let transfer: Transfer<_, _, MemoryToPeripheral, _> = Transfer::init(
|
||||
stream,
|
||||
SPI4::new(trigger_channel, spi),
|
||||
// Note(unsafe): This buffer is only used once and provided for the DMA transfer.
|
||||
unsafe { &mut DAC0_BUF0 },
|
||||
None,
|
||||
trigger_config,
|
||||
);
|
||||
|
||||
Self {
|
||||
transfer,
|
||||
// Note(unsafe): This buffer is only used once and provided for the next DMA transfer.
|
||||
next_buffer: unsafe { Some(&mut DAC0_BUF1) },
|
||||
first_transfer: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutably borrow the next output buffer to populate it with DAC codes.
|
||||
pub fn prepare_buffer(&mut self) -> &mut [u16; SAMPLE_BUFFER_SIZE] {
|
||||
self.next_buffer.as_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Enqueue the next buffer for transmission to the DAC.
|
||||
///
|
||||
/// # Args
|
||||
/// * `data` - The next data to write to the DAC.
|
||||
pub fn commit_buffer(&mut self) {
|
||||
let next_buffer = self.next_buffer.take().unwrap();
|
||||
|
||||
// If the last transfer was not complete, we didn't write all our previous DAC codes.
|
||||
// Wait for all the DAC codes to get written as well.
|
||||
if self.first_transfer {
|
||||
self.first_transfer = false
|
||||
} else {
|
||||
// Note: If a device hangs up, check that this conditional is passing correctly, as
|
||||
// there is no time-out checks here in the interest of execution speed.
|
||||
while self.transfer.get_transfer_complete_flag() == false {}
|
||||
}
|
||||
|
||||
// Start the next transfer.
|
||||
self.transfer.clear_interrupts();
|
||||
let (prev_buffer, _) =
|
||||
self.transfer.next_transfer(next_buffer).unwrap();
|
||||
|
||||
self.next_buffer.replace(prev_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the data output stream from DAC1.
|
||||
pub struct Dac1Output {
|
||||
next_buffer: Option<&'static mut [u16; SAMPLE_BUFFER_SIZE]>,
|
||||
transfer: Transfer<
|
||||
hal::dma::dma::Stream5<hal::stm32::DMA1>,
|
||||
SPI5,
|
||||
MemoryToPeripheral,
|
||||
&'static mut [u16; SAMPLE_BUFFER_SIZE],
|
||||
>,
|
||||
first_transfer: bool,
|
||||
}
|
||||
|
||||
impl Dac1Output {
|
||||
/// Construct a new DAC1 output data stream.
|
||||
///
|
||||
/// # Args
|
||||
/// * `spi` - The SPI interface connected to DAC1.
|
||||
/// * `stream` - The DMA stream used to write DAC codes the SPI TX FIFO.
|
||||
/// * `trigger_channel` - The timer channel used to generate DMA requests for DAC updates.
|
||||
pub fn new(
|
||||
spi: hal::spi::Spi<hal::stm32::SPI5, hal::spi::Enabled, u16>,
|
||||
stream: hal::dma::dma::Stream5<hal::stm32::DMA1>,
|
||||
trigger_channel: sampling_timer::tim2::Channel4,
|
||||
) -> Self {
|
||||
// Generate DMA events when an output compare of the timer hitting zero (timer roll over)
|
||||
// occurs.
|
||||
trigger_channel.listen_dma();
|
||||
trigger_channel.to_output_compare(0);
|
||||
|
||||
// The trigger stream constantly writes to the TX FIFO to generate DAC updates.
|
||||
let trigger_config = DmaConfig::default()
|
||||
.memory_increment(true)
|
||||
.peripheral_increment(false)
|
||||
.circular_buffer(true);
|
||||
|
||||
// Listen for any SPI errors, as this may indicate that we are not generating updates on the
|
||||
// DAC.
|
||||
let mut spi = spi.disable();
|
||||
spi.listen(hal::spi::Event::Error);
|
||||
|
||||
// Allow the SPI FIFOs to operate using only DMA data channels.
|
||||
spi.enable_dma_tx();
|
||||
|
||||
// Enable SPI and start it in infinite transaction mode.
|
||||
spi.inner().cr1.modify(|_, w| w.spe().set_bit());
|
||||
spi.inner().cr1.modify(|_, w| w.cstart().started());
|
||||
|
||||
// Construct the stream to write from memory to the peripheral.
|
||||
let transfer: Transfer<_, _, MemoryToPeripheral, _> = Transfer::init(
|
||||
stream,
|
||||
SPI5::new(trigger_channel, spi),
|
||||
// Note(unsafe): This buffer is only used once and provided to the transfer.
|
||||
unsafe { &mut DAC1_BUF0 },
|
||||
None,
|
||||
trigger_config,
|
||||
);
|
||||
|
||||
Self {
|
||||
// Note(unsafe): This buffer is only used once and provided for the next DMA transfer.
|
||||
next_buffer: unsafe { Some(&mut DAC1_BUF1) },
|
||||
transfer,
|
||||
first_transfer: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutably borrow the next output buffer to populate it with DAC codes.
|
||||
pub fn prepare_buffer(&mut self) -> &mut [u16; SAMPLE_BUFFER_SIZE] {
|
||||
self.next_buffer.as_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Enqueue the next buffer for transmission to the DAC.
|
||||
///
|
||||
/// # Args
|
||||
/// * `data` - The next data to write to the DAC.
|
||||
pub fn commit_buffer(&mut self) {
|
||||
let next_buffer = self.next_buffer.take().unwrap();
|
||||
|
||||
// If the last transfer was not complete, we didn't write all our previous DAC codes.
|
||||
// Wait for all the DAC codes to get written as well.
|
||||
if self.first_transfer {
|
||||
self.first_transfer = false
|
||||
} else {
|
||||
// Note: If a device hangs up, check that this conditional is passing correctly, as
|
||||
// there is no time-out checks here in the interest of execution speed.
|
||||
while self.transfer.get_transfer_complete_flag() == false {}
|
||||
}
|
||||
|
||||
// Start the next transfer.
|
||||
self.transfer.clear_interrupts();
|
||||
let (prev_buffer, _) =
|
||||
self.transfer.next_transfer(next_buffer).unwrap();
|
||||
|
||||
self.next_buffer.replace(prev_buffer);
|
||||
}
|
||||
}
|
6
src/design_parameters.rs
Normal file
6
src/design_parameters.rs
Normal file
@ -0,0 +1,6 @@
|
||||
/// The ADC setup time is the number of seconds after the CSn line goes low before the serial clock
|
||||
/// may begin. This is used for performing the internal ADC conversion.
|
||||
pub const ADC_SETUP_TIME: f32 = 220e-9;
|
||||
|
||||
/// The maximum DAC/ADC serial clock line frequency. This is a hardware limit.
|
||||
pub const ADC_DAC_SCK_MHZ_MAX: u32 = 50;
|
486
src/main.rs
486
src/main.rs
@ -1,6 +1,4 @@
|
||||
#![deny(warnings)]
|
||||
// Deprecation warnings are temporarily allowed as the HAL DMA goes through updates.
|
||||
#![allow(deprecated)]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
@ -38,9 +36,13 @@ use stm32h7xx_hal::prelude::*;
|
||||
use embedded_hal::digital::v2::{InputPin, OutputPin};
|
||||
|
||||
use hal::{
|
||||
dma::{DmaChannel, DmaExt, DmaInternal},
|
||||
dma::{
|
||||
config::Priority,
|
||||
dma::{DMAReq, DmaConfig},
|
||||
traits::TargetAddress,
|
||||
MemoryToPeripheral, PeripheralToMemory, Transfer,
|
||||
},
|
||||
ethernet::{self, PHY},
|
||||
rcc::rec::ResetEnable,
|
||||
};
|
||||
|
||||
use smoltcp as net;
|
||||
@ -49,14 +51,26 @@ use smoltcp::wire::Ipv4Address;
|
||||
|
||||
use heapless::{consts::*, String};
|
||||
|
||||
// The desired sampling frequency of the ADCs.
|
||||
const SAMPLE_FREQUENCY_KHZ: u32 = 500;
|
||||
|
||||
// The desired ADC sample processing buffer size.
|
||||
const SAMPLE_BUFFER_SIZE: usize = 1;
|
||||
|
||||
#[link_section = ".sram3.eth"]
|
||||
static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new();
|
||||
|
||||
mod adc;
|
||||
mod afe;
|
||||
mod dac;
|
||||
mod design_parameters;
|
||||
mod eeprom;
|
||||
mod pounder;
|
||||
mod sampling_timer;
|
||||
mod server;
|
||||
|
||||
use adc::{Adc0Input, Adc1Input, AdcInputs};
|
||||
use dac::{Dac0Output, Dac1Output, DacOutputs};
|
||||
use dsp::iir;
|
||||
|
||||
#[cfg(not(feature = "semihosting"))]
|
||||
@ -102,8 +116,6 @@ static mut NET_STORE: NetStorage = NetStorage {
|
||||
|
||||
const SCALE: f32 = ((1 << 15) - 1) as f32;
|
||||
|
||||
const SPI_START: u32 = 0x00;
|
||||
|
||||
// static ETHERNET_PENDING: AtomicBool = AtomicBool::new(true);
|
||||
|
||||
const TCP_RX_BUFFER_SIZE: usize = 8192;
|
||||
@ -173,17 +185,13 @@ macro_rules! route_request {
|
||||
#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)]
|
||||
const APP: () = {
|
||||
struct Resources {
|
||||
adc0: hal::spi::Spi<hal::stm32::SPI2, hal::spi::Enabled, u16>,
|
||||
dac0: hal::spi::Spi<hal::stm32::SPI4, hal::spi::Enabled, u16>,
|
||||
afe0: AFE0,
|
||||
|
||||
adc1: hal::spi::Spi<hal::stm32::SPI3, hal::spi::Enabled, u16>,
|
||||
dac1: hal::spi::Spi<hal::stm32::SPI5, hal::spi::Enabled, u16>,
|
||||
afe1: AFE1,
|
||||
|
||||
eeprom_i2c: hal::i2c::I2c<hal::stm32::I2C2>,
|
||||
adcs: AdcInputs,
|
||||
dacs: DacOutputs,
|
||||
|
||||
timer: hal::timer::Timer<hal::stm32::TIM2>,
|
||||
eeprom_i2c: hal::i2c::I2c<hal::stm32::I2C2>,
|
||||
|
||||
// Note: It appears that rustfmt generates a format that GDB cannot recognize, which
|
||||
// results in GDB breakpoints being set improperly.
|
||||
@ -257,207 +265,183 @@ const APP: () = {
|
||||
afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin)
|
||||
};
|
||||
|
||||
ccdr.peripheral.DMA1.reset().enable();
|
||||
let mut dma_channels = dp.DMA1.split();
|
||||
let dma_streams =
|
||||
hal::dma::dma::StreamsTuple::new(dp.DMA1, ccdr.peripheral.DMA1);
|
||||
|
||||
// Configure timer 2 to trigger conversions for the ADC
|
||||
let timer2 = dp.TIM2.timer(
|
||||
SAMPLE_FREQUENCY_KHZ.khz(),
|
||||
ccdr.peripheral.TIM2,
|
||||
&ccdr.clocks,
|
||||
);
|
||||
|
||||
let mut sampling_timer = sampling_timer::SamplingTimer::new(timer2);
|
||||
let sampling_timer_channels = sampling_timer.channels();
|
||||
|
||||
// Configure the SPI interfaces to the ADCs and DACs.
|
||||
let adc0_spi = {
|
||||
let spi_miso = gpiob
|
||||
.pb14
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let spi_sck = gpiob
|
||||
.pb10
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let _spi_nss = gpiob
|
||||
.pb9
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let adcs = {
|
||||
let adc0 = {
|
||||
let spi_miso = gpiob
|
||||
.pb14
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let spi_sck = gpiob
|
||||
.pb10
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let _spi_nss = gpiob
|
||||
.pb9
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
|
||||
let config = hal::spi::Config::new(hal::spi::Mode {
|
||||
polarity: hal::spi::Polarity::IdleHigh,
|
||||
phase: hal::spi::Phase::CaptureOnSecondTransition,
|
||||
})
|
||||
.manage_cs()
|
||||
.suspend_when_inactive()
|
||||
.cs_delay(220e-9);
|
||||
let config = hal::spi::Config::new(hal::spi::Mode {
|
||||
polarity: hal::spi::Polarity::IdleHigh,
|
||||
phase: hal::spi::Phase::CaptureOnSecondTransition,
|
||||
})
|
||||
.manage_cs()
|
||||
.suspend_when_inactive()
|
||||
.cs_delay(design_parameters::ADC_SETUP_TIME);
|
||||
|
||||
dma_channels.0.set_peripheral_address(
|
||||
&dp.SPI2.txdr as *const _ as u32,
|
||||
false,
|
||||
);
|
||||
dma_channels
|
||||
.0
|
||||
.set_memory_address(&SPI_START as *const _ as u32, false);
|
||||
dma_channels
|
||||
.0
|
||||
.set_direction(hal::dma::Direction::MemoryToPeripherial);
|
||||
dma_channels.0.set_transfer_length(1);
|
||||
dma_channels.0.cr().modify(|_, w| {
|
||||
w.circ()
|
||||
.enabled()
|
||||
.psize()
|
||||
.bits16()
|
||||
.msize()
|
||||
.bits16()
|
||||
.pfctrl()
|
||||
.dma()
|
||||
});
|
||||
dma_channels.0.dmamux().modify(|_, w| {
|
||||
w.dmareq_id()
|
||||
.variant(hal::stm32::dmamux1::ccr::DMAREQ_ID_A::TIM2_UP)
|
||||
});
|
||||
let spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi(
|
||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||
config,
|
||||
design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(),
|
||||
ccdr.peripheral.SPI2,
|
||||
&ccdr.clocks,
|
||||
);
|
||||
|
||||
let mut spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi(
|
||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||
config,
|
||||
50.mhz(),
|
||||
ccdr.peripheral.SPI2,
|
||||
&ccdr.clocks,
|
||||
);
|
||||
Adc0Input::new(
|
||||
spi,
|
||||
dma_streams.0,
|
||||
dma_streams.1,
|
||||
sampling_timer_channels.ch1,
|
||||
)
|
||||
};
|
||||
|
||||
// Kick-start the SPI transaction - we will add data to the TXFIFO to read from the ADC.
|
||||
let spi_regs = unsafe { &*hal::stm32::SPI2::ptr() };
|
||||
spi_regs.cr1.modify(|_, w| w.cstart().started());
|
||||
let adc1 = {
|
||||
let spi_miso = gpiob
|
||||
.pb4
|
||||
.into_alternate_af6()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let spi_sck = gpioc
|
||||
.pc10
|
||||
.into_alternate_af6()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let _spi_nss = gpioa
|
||||
.pa15
|
||||
.into_alternate_af6()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
|
||||
spi.listen(hal::spi::Event::Rxp);
|
||||
let config = hal::spi::Config::new(hal::spi::Mode {
|
||||
polarity: hal::spi::Polarity::IdleHigh,
|
||||
phase: hal::spi::Phase::CaptureOnSecondTransition,
|
||||
})
|
||||
.manage_cs()
|
||||
.suspend_when_inactive()
|
||||
.cs_delay(design_parameters::ADC_SETUP_TIME);
|
||||
|
||||
spi
|
||||
let spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi(
|
||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||
config,
|
||||
design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(),
|
||||
ccdr.peripheral.SPI3,
|
||||
&ccdr.clocks,
|
||||
);
|
||||
|
||||
Adc1Input::new(
|
||||
spi,
|
||||
dma_streams.2,
|
||||
dma_streams.3,
|
||||
sampling_timer_channels.ch2,
|
||||
)
|
||||
};
|
||||
|
||||
AdcInputs::new(adc0, adc1)
|
||||
};
|
||||
|
||||
let adc1_spi = {
|
||||
let spi_miso = gpiob
|
||||
.pb4
|
||||
.into_alternate_af6()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let spi_sck = gpioc
|
||||
.pc10
|
||||
.into_alternate_af6()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let _spi_nss = gpioa
|
||||
.pa15
|
||||
.into_alternate_af6()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let dacs = {
|
||||
let _dac_clr_n =
|
||||
gpioe.pe12.into_push_pull_output().set_high().unwrap();
|
||||
let _dac0_ldac_n =
|
||||
gpioe.pe11.into_push_pull_output().set_low().unwrap();
|
||||
let _dac1_ldac_n =
|
||||
gpioe.pe15.into_push_pull_output().set_low().unwrap();
|
||||
|
||||
let config = hal::spi::Config::new(hal::spi::Mode {
|
||||
polarity: hal::spi::Polarity::IdleHigh,
|
||||
phase: hal::spi::Phase::CaptureOnSecondTransition,
|
||||
})
|
||||
.manage_cs()
|
||||
.suspend_when_inactive()
|
||||
.cs_delay(220e-9);
|
||||
let dac0_spi = {
|
||||
let spi_miso = gpioe
|
||||
.pe5
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let spi_sck = gpioe
|
||||
.pe2
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let _spi_nss = gpioe
|
||||
.pe4
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
|
||||
dma_channels.1.set_peripheral_address(
|
||||
&dp.SPI3.txdr as *const _ as u32,
|
||||
false,
|
||||
let config = hal::spi::Config::new(hal::spi::Mode {
|
||||
polarity: hal::spi::Polarity::IdleHigh,
|
||||
phase: hal::spi::Phase::CaptureOnSecondTransition,
|
||||
})
|
||||
.manage_cs()
|
||||
.suspend_when_inactive()
|
||||
.communication_mode(hal::spi::CommunicationMode::Transmitter)
|
||||
.swap_mosi_miso();
|
||||
|
||||
dp.SPI4.spi(
|
||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||
config,
|
||||
design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(),
|
||||
ccdr.peripheral.SPI4,
|
||||
&ccdr.clocks,
|
||||
)
|
||||
};
|
||||
|
||||
let dac1_spi = {
|
||||
let spi_miso = gpiof
|
||||
.pf8
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let spi_sck = gpiof
|
||||
.pf7
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let _spi_nss = gpiof
|
||||
.pf6
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
|
||||
let config = hal::spi::Config::new(hal::spi::Mode {
|
||||
polarity: hal::spi::Polarity::IdleHigh,
|
||||
phase: hal::spi::Phase::CaptureOnSecondTransition,
|
||||
})
|
||||
.manage_cs()
|
||||
.communication_mode(hal::spi::CommunicationMode::Transmitter)
|
||||
.suspend_when_inactive()
|
||||
.swap_mosi_miso();
|
||||
|
||||
dp.SPI5.spi(
|
||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||
config,
|
||||
design_parameters::ADC_DAC_SCK_MHZ_MAX.mhz(),
|
||||
ccdr.peripheral.SPI5,
|
||||
&ccdr.clocks,
|
||||
)
|
||||
};
|
||||
|
||||
let dac0 = Dac0Output::new(
|
||||
dac0_spi,
|
||||
dma_streams.4,
|
||||
sampling_timer_channels.ch3,
|
||||
);
|
||||
dma_channels
|
||||
.1
|
||||
.set_memory_address(&SPI_START as *const _ as u32, false);
|
||||
dma_channels
|
||||
.1
|
||||
.set_direction(hal::dma::Direction::MemoryToPeripherial);
|
||||
dma_channels.1.dmamux().modify(|_, w| {
|
||||
w.dmareq_id()
|
||||
.variant(hal::stm32::dmamux1::ccr::DMAREQ_ID_A::TIM2_UP)
|
||||
});
|
||||
dma_channels.1.set_transfer_length(1);
|
||||
dma_channels.1.cr().modify(|_, w| {
|
||||
w.circ()
|
||||
.enabled()
|
||||
.psize()
|
||||
.bits16()
|
||||
.msize()
|
||||
.bits16()
|
||||
.pfctrl()
|
||||
.dma()
|
||||
});
|
||||
|
||||
let mut spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi(
|
||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||
config,
|
||||
50.mhz(),
|
||||
ccdr.peripheral.SPI3,
|
||||
&ccdr.clocks,
|
||||
let dac1 = Dac1Output::new(
|
||||
dac1_spi,
|
||||
dma_streams.5,
|
||||
sampling_timer_channels.ch4,
|
||||
);
|
||||
|
||||
let spi_regs = unsafe { &*hal::stm32::SPI3::ptr() };
|
||||
spi_regs.cr1.modify(|_, w| w.cstart().started());
|
||||
|
||||
spi.listen(hal::spi::Event::Rxp);
|
||||
|
||||
spi
|
||||
};
|
||||
|
||||
let _dac_clr_n = gpioe.pe12.into_push_pull_output().set_high().unwrap();
|
||||
let _dac0_ldac_n =
|
||||
gpioe.pe11.into_push_pull_output().set_low().unwrap();
|
||||
let _dac1_ldac_n =
|
||||
gpioe.pe15.into_push_pull_output().set_low().unwrap();
|
||||
|
||||
let dac0_spi = {
|
||||
let spi_miso = gpioe
|
||||
.pe5
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let spi_sck = gpioe
|
||||
.pe2
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let _spi_nss = gpioe
|
||||
.pe4
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
|
||||
let config = hal::spi::Config::new(hal::spi::Mode {
|
||||
polarity: hal::spi::Polarity::IdleHigh,
|
||||
phase: hal::spi::Phase::CaptureOnSecondTransition,
|
||||
})
|
||||
.manage_cs()
|
||||
.suspend_when_inactive()
|
||||
.communication_mode(hal::spi::CommunicationMode::Transmitter)
|
||||
.swap_mosi_miso();
|
||||
|
||||
dp.SPI4.spi(
|
||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||
config,
|
||||
50.mhz(),
|
||||
ccdr.peripheral.SPI4,
|
||||
&ccdr.clocks,
|
||||
)
|
||||
};
|
||||
|
||||
let dac1_spi = {
|
||||
let spi_miso = gpiof
|
||||
.pf8
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let spi_sck = gpiof
|
||||
.pf7
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
let _spi_nss = gpiof
|
||||
.pf6
|
||||
.into_alternate_af5()
|
||||
.set_speed(hal::gpio::Speed::VeryHigh);
|
||||
|
||||
let config = hal::spi::Config::new(hal::spi::Mode {
|
||||
polarity: hal::spi::Polarity::IdleHigh,
|
||||
phase: hal::spi::Phase::CaptureOnSecondTransition,
|
||||
})
|
||||
.manage_cs()
|
||||
.suspend_when_inactive()
|
||||
.communication_mode(hal::spi::CommunicationMode::Transmitter)
|
||||
.swap_mosi_miso();
|
||||
|
||||
dp.SPI5.spi(
|
||||
(spi_sck, spi_miso, hal::spi::NoMosi),
|
||||
config,
|
||||
50.mhz(),
|
||||
ccdr.peripheral.SPI5,
|
||||
&ccdr.clocks,
|
||||
)
|
||||
DacOutputs::new(dac0, dac1)
|
||||
};
|
||||
|
||||
let mut fp_led_0 = gpiod.pd5.into_push_pull_output();
|
||||
@ -741,28 +725,16 @@ const APP: () = {
|
||||
// Utilize the cycle counter for RTIC scheduling.
|
||||
cp.DWT.enable_cycle_counter();
|
||||
|
||||
// Configure timer 2 to trigger conversions for the ADC
|
||||
let timer2 =
|
||||
dp.TIM2.timer(500.khz(), ccdr.peripheral.TIM2, &ccdr.clocks);
|
||||
{
|
||||
let t2_regs = unsafe { &*hal::stm32::TIM2::ptr() };
|
||||
t2_regs.dier.modify(|_, w| w.ude().set_bit());
|
||||
}
|
||||
|
||||
// Start the SPI transfers.
|
||||
dma_channels.0.start();
|
||||
dma_channels.1.start();
|
||||
// Start sampling ADCs.
|
||||
sampling_timer.start();
|
||||
|
||||
init::LateResources {
|
||||
afe0: afe0,
|
||||
adc0: adc0_spi,
|
||||
dac0: dac0_spi,
|
||||
|
||||
afe1: afe1,
|
||||
adc1: adc1_spi,
|
||||
dac1: dac1_spi,
|
||||
|
||||
timer: timer2,
|
||||
adcs,
|
||||
dacs,
|
||||
|
||||
pounder: pounder_devices,
|
||||
|
||||
eeprom_i2c,
|
||||
@ -772,38 +744,40 @@ const APP: () = {
|
||||
}
|
||||
}
|
||||
|
||||
#[task(binds = SPI3, resources = [adc1, dac1, iir_state, iir_ch], priority = 2)]
|
||||
fn spi3(c: spi3::Context) {
|
||||
let output: u16 = {
|
||||
let a: u16 = c.resources.adc1.read().unwrap();
|
||||
let x0 = f32::from(a as i16);
|
||||
let y0 =
|
||||
c.resources.iir_ch[1].update(&mut c.resources.iir_state[1], x0);
|
||||
// note(unsafe): The filter limits ensure that the value is in range.
|
||||
// The truncation introduces 1/2 LSB distortion.
|
||||
let y0 = unsafe { y0.to_int_unchecked::<i16>() };
|
||||
// convert to DAC code
|
||||
y0 as u16 ^ 0x8000
|
||||
};
|
||||
#[task(binds=DMA1_STR3, resources=[adcs, dacs, iir_state, iir_ch], priority=2)]
|
||||
fn adc_update(c: adc_update::Context) {
|
||||
let (adc0_samples, adc1_samples) =
|
||||
c.resources.adcs.transfer_complete_handler();
|
||||
|
||||
c.resources.dac1.send(output).unwrap();
|
||||
}
|
||||
let (dac0, dac1) = c.resources.dacs.prepare_data();
|
||||
|
||||
#[task(binds = SPI2, resources = [adc0, dac0, iir_state, iir_ch], priority = 2)]
|
||||
fn spi2(c: spi2::Context) {
|
||||
let output: u16 = {
|
||||
let a: u16 = c.resources.adc0.read().unwrap();
|
||||
let x0 = f32::from(a as i16);
|
||||
let y0 =
|
||||
c.resources.iir_ch[0].update(&mut c.resources.iir_state[0], x0);
|
||||
// note(unsafe): The filter limits ensure that the value is in range.
|
||||
// The truncation introduces 1/2 LSB distortion.
|
||||
let y0 = unsafe { y0.to_int_unchecked::<i16>() };
|
||||
// convert to DAC code
|
||||
y0 as u16 ^ 0x8000
|
||||
};
|
||||
for (i, (adc0, adc1)) in
|
||||
adc0_samples.iter().zip(adc1_samples.iter()).enumerate()
|
||||
{
|
||||
dac0[i] = {
|
||||
let x0 = f32::from(*adc0 as i16);
|
||||
let y0 = c.resources.iir_ch[0]
|
||||
.update(&mut c.resources.iir_state[0], x0);
|
||||
// note(unsafe): The filter limits ensure that the value is in range.
|
||||
// The truncation introduces 1/2 LSB distortion.
|
||||
let y0 = unsafe { y0.to_int_unchecked::<i16>() };
|
||||
// convert to DAC code
|
||||
y0 as u16 ^ 0x8000
|
||||
};
|
||||
|
||||
c.resources.dac0.send(output).unwrap();
|
||||
dac1[i] = {
|
||||
let x1 = f32::from(*adc1 as i16);
|
||||
let y1 = c.resources.iir_ch[1]
|
||||
.update(&mut c.resources.iir_state[1], x1);
|
||||
// note(unsafe): The filter limits ensure that the value is in range.
|
||||
// The truncation introduces 1/2 LSB distortion.
|
||||
let y1 = unsafe { y1.to_int_unchecked::<i16>() };
|
||||
// convert to DAC code
|
||||
y1 as u16 ^ 0x8000
|
||||
};
|
||||
}
|
||||
|
||||
c.resources.dacs.commit_data();
|
||||
}
|
||||
|
||||
#[idle(resources=[net_interface, pounder, mac_addr, eth_mac, iir_state, iir_ch, afe0, afe1])]
|
||||
@ -996,6 +970,26 @@ const APP: () = {
|
||||
unsafe { ethernet::interrupt_handler() }
|
||||
}
|
||||
|
||||
#[task(binds = SPI2, priority = 3)]
|
||||
fn spi2(_: spi2::Context) {
|
||||
panic!("ADC0 input overrun");
|
||||
}
|
||||
|
||||
#[task(binds = SPI3, priority = 3)]
|
||||
fn spi3(_: spi3::Context) {
|
||||
panic!("ADC0 input overrun");
|
||||
}
|
||||
|
||||
#[task(binds = SPI4, priority = 3)]
|
||||
fn spi4(_: spi4::Context) {
|
||||
panic!("DAC0 output error");
|
||||
}
|
||||
|
||||
#[task(binds = SPI5, priority = 3)]
|
||||
fn spi5(_: spi5::Context) {
|
||||
panic!("DAC1 output error");
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
// hw interrupt handlers for RTIC to use for scheduling tasks
|
||||
// one per priority
|
||||
|
119
src/sampling_timer.rs
Normal file
119
src/sampling_timer.rs
Normal file
@ -0,0 +1,119 @@
|
||||
///! The sampling timer is used for managing ADC sampling and external reference timestamping.
|
||||
use super::hal;
|
||||
|
||||
/// The timer used for managing ADC sampling.
|
||||
pub struct SamplingTimer {
|
||||
timer: hal::timer::Timer<hal::stm32::TIM2>,
|
||||
channels: Option<tim2::Channels>,
|
||||
}
|
||||
|
||||
impl SamplingTimer {
|
||||
/// Construct the sampling timer.
|
||||
pub fn new(mut timer: hal::timer::Timer<hal::stm32::TIM2>) -> Self {
|
||||
timer.pause();
|
||||
|
||||
Self {
|
||||
timer,
|
||||
// Note(unsafe): Once these channels are taken, we guarantee that we do not modify any
|
||||
// of the underlying timer channel registers, as ownership of the channels is now
|
||||
// provided through the associated channel structures. We additionally guarantee this
|
||||
// can only be called once because there is only one Timer2 and this resource takes
|
||||
// ownership of it once instantiated.
|
||||
channels: unsafe { Some(tim2::Channels::new()) },
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the timer capture/compare channels.
|
||||
pub fn channels(&mut self) -> tim2::Channels {
|
||||
self.channels.take().unwrap()
|
||||
}
|
||||
|
||||
/// Start the sampling timer.
|
||||
pub fn start(&mut self) {
|
||||
self.timer.reset_counter();
|
||||
self.timer.resume();
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! timer_channel {
|
||||
($name:ident, $TY:ty, ($ccxde:expr, $ccrx:expr, $ccmrx_output:expr, $ccxs:expr)) => {
|
||||
pub struct $name {}
|
||||
|
||||
paste::paste! {
|
||||
impl $name {
|
||||
/// Construct a new timer channel.
|
||||
///
|
||||
/// Note(unsafe): This function must only be called once. Once constructed, the
|
||||
/// constructee guarantees to never modify the timer channel.
|
||||
unsafe fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
/// Allow CH4 to generate DMA requests.
|
||||
pub fn listen_dma(&self) {
|
||||
let regs = unsafe { &*<$TY>::ptr() };
|
||||
regs.dier.modify(|_, w| w.[< $ccxde >]().set_bit());
|
||||
}
|
||||
|
||||
/// Operate CH2 as an output-compare.
|
||||
///
|
||||
/// # Args
|
||||
/// * `value` - The value to compare the sampling timer's counter against.
|
||||
pub fn to_output_compare(&self, value: u32) {
|
||||
let regs = unsafe { &*<$TY>::ptr() };
|
||||
assert!(value <= regs.arr.read().bits());
|
||||
regs.[< $ccrx >].write(|w| w.ccr().bits(value));
|
||||
regs.[< $ccmrx_output >]()
|
||||
.modify(|_, w| unsafe { w.[< $ccxs >]().bits(0) });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod tim2 {
|
||||
use stm32h7xx_hal as hal;
|
||||
|
||||
/// The channels representing the timer.
|
||||
pub struct Channels {
|
||||
pub ch1: Channel1,
|
||||
pub ch2: Channel2,
|
||||
pub ch3: Channel3,
|
||||
pub ch4: Channel4,
|
||||
}
|
||||
|
||||
impl Channels {
|
||||
/// Construct a new set of channels.
|
||||
///
|
||||
/// Note(unsafe): This is only safe to call once.
|
||||
pub unsafe fn new() -> Self {
|
||||
Self {
|
||||
ch1: Channel1::new(),
|
||||
ch2: Channel2::new(),
|
||||
ch3: Channel3::new(),
|
||||
ch4: Channel4::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timer_channel!(
|
||||
Channel1,
|
||||
hal::stm32::TIM2,
|
||||
(cc1de, ccr1, ccmr1_output, cc1s)
|
||||
);
|
||||
timer_channel!(
|
||||
Channel2,
|
||||
hal::stm32::TIM2,
|
||||
(cc2de, ccr2, ccmr1_output, cc1s)
|
||||
);
|
||||
timer_channel!(
|
||||
Channel3,
|
||||
hal::stm32::TIM2,
|
||||
(cc3de, ccr3, ccmr2_output, cc3s)
|
||||
);
|
||||
timer_channel!(
|
||||
Channel4,
|
||||
hal::stm32::TIM2,
|
||||
(cc4de, ccr4, ccmr2_output, cc4s)
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user