From 8e4a7c8fa9993f41e9386116af9daad5f7a9d222 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 15 Dec 2020 16:46:12 +0100 Subject: [PATCH 01/34] Adding documentation for ADCs and DACs --- src/adc.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++-------- src/dac.rs | 75 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 126 insertions(+), 30 deletions(-) diff --git a/src/adc.rs b/src/adc.rs index e9120aa..9b0aa33 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -1,18 +1,75 @@ ///! 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. +///! # Design ///! -///! 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. +///! Stabilizer ADCs are connected to the MCU via a simplex, SPI-compatible interface. The ADCs +///! require a setup conversion time after asserting the CSn (convert) signal to generate the ADC +///! code from the sampled level. Once the setup time has elapsed, the ADC data is clocked out of +///! MISO. The internal setup time is managed by the SPI peripheral via a CSn setup time parameter +///! during SPI configuration, which allows offloading the management of the setup time to hardware. +///! +///! Because of the SPI-compatibility of the ADCs, a single SPI peripheral + DMA is used to automate +///! the collection of multiple ADC samples without requiring processing by the CPU, which reduces +///! overhead and provides the CPU with more time for processing-intensive tasks, like DSP. +///! +///! The automation of sample collection utilizes two DMA streams, the SPI peripheral, and a timer +///! compare channel for each ADC. The timer comparison channel is configured to generate a +///! comparison event every time the timer is equal to a specific value. Each comparison then +///! generates a DMA transfer event to write into the SPI TX buffer. Although the SPI is a simplex, +///! RX-only interface, it is configured in full-duplex mode and the TX pin is left disconnected. +///! This allows the SPI interface to periodically read a single word whenever a word is written to +///! the TX side. Thus, by running a continuous DMA transfer to periodically write a value into the +///! TX FIFO, we can schedule the regular collection of ADC samples in the SPI RX buffer. +///! +///! In order to collect the acquired ADC samples into a RAM buffer, a second DMA transfer is +///! configured to read from the SPI RX FIFO into RAM. The request for this transfer is connected to +///! the SPI RX data signal, so the SPI peripheral will request to move data into RAM whenever it is +///! available. When enough samples have been collected, a transfer-complete interrupt is generated +///! and the ADC samples are available for processing. +///! +///! The SPI peripheral internally has an 8- or 16-byte TX and RX FIFO, which corresponds to a 4- or +///! 8-sample buffer for incoming ADC samples. During the handling of the DMA transfer completion, +///! there is a small window where buffers are swapped over where it's possible that a sample could +///! be lost. In order to avoid this, the SPI RX FIFO is effectively used as a "sample overflow" +///! region and can buffer a number of samples until the next DMA transfer is configured. If a DMA +///! transfer is still not set in time, the SPI peripheral will generate an input-overrun interrupt. +///! This interrupt then serves as a means of detecting if samples have been lost, which will occur +///! whenever data processing takes longer than the collection period. +///! +///! +///! ## Starting Data Collection +///! +///! Because the DMA data collection is automated via timer count comparisons and DMA transfers, the +///! ADCs can be initialized and configured, but will not begin sampling the external ADCs until the +///! sampling timer is enabled. As such, the sampling timer should be enabled after all +///! initialization has completed and immediately before the embedded processing loop begins. +///! +///! +///! ## Batch Sizing +///! +///! The ADCs collect a group of N samples, which is referred to as a batch. The size of the batch +///! is configured by the user at compile-time to allow for a custom-tailored implementation. Larger +///! batch sizes generally provide for more processing time per sample, but come at the expense of +///! increased input -> output latency. +///! +///! +///! # Note +///! +///! While there are two ADCs, only a single ADC is configured to generate transfer-complete +///! interrupts. This is done because it is assumed that the ADCs will always be sampled +///! simultaneously. If only a single ADC is used, it must always be ADC0, as ADC1 will not generate +///! transfer-complete interrupts. +///! +///! There is a very small amount of latency between sampling of ADCs due to bus matrix priority. As +///! such, one of the ADCs will be sampled marginally earlier before the other because the DMA +///! requests are generated simultaneously. This can be avoided by providing a known offset to the +///! sample DMA requests, which can be completed by setting e.g. ADC0's comparison to a counter +///! value of 0 and ADC1's comparison to a counter value of 1. +///! +///! In this implementation, single buffer mode DMA transfers are used because the SPI RX FIFO can +///! be used as a means to both detect and buffer ADC samples during the buffer swap-over. Because +///! of this, double-buffered mode does not offer any advantages over single-buffered mode (unless +///! double-buffered mode offers less overhead when accessing data). use super::{ hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, PeripheralToMemory, Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, diff --git a/src/dac.rs b/src/dac.rs index d96109c..ef0561a 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -1,8 +1,45 @@ ///! 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. +///! # Design +///! +///! Stabilizer DACs are connected to the MCU via a simplex, SPI-compatible interface. Each DAC +///! accepts a 16-bit output code. +///! +///! In order to maximize CPU processing time, the DAC code updates are offloaded to hardware using +///! a timer compare channel, DMA stream, and the DAC SPI interface. +///! +///! The timer comparison channel is configured to generate a DMA request whenever the comparison +///! occurs. Thus, whenever a comparison happens, a single DAC code can be written to the output. By +///! configuring a DMA stream for a number of successive DAC codes, hardware can regularly update +///! the DAC without requiring the CPU. +///! +///! ## Multiple Samples to Single DAC Codes +///! +///! For some applications, it may be desirable to generate a single DAC code from multiple ADC +///! samples. In order to maintain timing characteristics between ADC samples and DAC code outputs, +///! applications are required to generate one DAC code for each ADC sample. To accomodate mapping +///! multiple inputs to a single output, the output code can be repeated a number of times in the +///! output buffer corresponding with the number of input samples that were used to generate it. +///! +///! +///! # Note +///! +///! There is a very small amount of latency between updating the two DACs due to bus matrix +///! priority. As such, one of the DACs will be updated marginally earlier before the other because +///! the DMA requests are generated simultaneously. This can be avoided by providing a known offset +///! to other DMA requests, which can be completed by setting e.g. DAC0's comparison to a +///! counter value of 2 and DAC1's comparison to a counter value of 3. This will have the effect of +///! generating the DAC updates with a known latency of 1 timer tick to each other and prevent the +///! DMAs from racing for the bus. As implemented, the DMA channels utilize natural priority of the +///! DMA channels to arbitrate which transfer occurs first. +///! +///! +///! # Future Improvements +///! +///! In this implementation, single buffer mode DMA transfers are used. As a result of this, it's +///! possible that a timer comparison could be missed during the swap-over, which will result in a +///! delay of a single output code. In the future, this can be remedied by utilize double-buffer +///! mode for the DMA transfers. use super::{ hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, @@ -90,12 +127,11 @@ macro_rules! dac_output { 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()); + // AXISRAM is uninitialized. As such, we manually zero-initialize it here before + // starting the transfer. + for byte in DAC_BUF[$index].iter_mut() { + *byte = 0; + } // Construct the trigger stream to write from memory to the peripheral. let transfer: Transfer<_, _, MemoryToPeripheral, _> = @@ -108,6 +144,15 @@ macro_rules! dac_output { trigger_config, ); + transfer.start(|spi| { + // 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()); + }); + Self { transfer, // Note(unsafe): This buffer is only used once and provided for the next DMA transfer. @@ -131,15 +176,9 @@ macro_rules! dac_output { &mut self, next_buffer: &'static mut [u16; SAMPLE_BUFFER_SIZE], ) { - // 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() {} - } + // 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() {} // Start the next transfer. self.transfer.clear_interrupts(); From 438b2919741e7c2bd7b455212dd4b7db002d9286 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:09:18 +0100 Subject: [PATCH 02/34] Updating DAC output format, adding DDS stream docs --- src/dac.rs | 47 +++++++++++++++------------------- src/main.rs | 5 +--- src/pounder/dds_output.rs | 53 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/dac.rs b/src/dac.rs index ef0561a..ef4601a 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -69,6 +69,15 @@ macro_rules! dac_output { ) -> Self { Self { _channel, spi } } + + pub fn start_dma(&mut self) { + // Allow the SPI FIFOs to operate using only DMA data channels. + self.spi.enable_dma_tx(); + + // Enable SPI and start it in infinite transaction mode. + self.spi.inner().cr1.modify(|_, w| w.spe().set_bit()); + self.spi.inner().cr1.modify(|_, w| w.cstart().started()); + } } // Note(unsafe): This is safe because the DMA request line is logically owned by this module. @@ -97,7 +106,6 @@ macro_rules! dac_output { MemoryToPeripheral, &'static mut [u16; SAMPLE_BUFFER_SIZE], >, - first_transfer: bool, } impl $name { @@ -129,12 +137,14 @@ macro_rules! dac_output { // AXISRAM is uninitialized. As such, we manually zero-initialize it here before // starting the transfer. - for byte in DAC_BUF[$index].iter_mut() { - *byte = 0; + for buf in unsafe { DAC_BUF[$index].iter_mut() } { + for byte in buf.iter_mut() { + *byte = 0; + } } // Construct the trigger stream to write from memory to the peripheral. - let transfer: Transfer<_, _, MemoryToPeripheral, _> = + let mut transfer: Transfer<_, _, MemoryToPeripheral, _> = Transfer::init( stream, $spi::new(trigger_channel, spi), @@ -144,42 +154,23 @@ macro_rules! dac_output { trigger_config, ); - transfer.start(|spi| { - // 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()); - }); + transfer.start(|spi| spi.start_dma()); Self { transfer, // Note(unsafe): This buffer is only used once and provided for the next DMA transfer. next_buffer: unsafe { Some(&mut DAC_BUF[$index][1]) }, - first_transfer: true, } } /// Acquire the next output buffer to populate it with DAC codes. - pub fn acquire_buffer( - &mut self, - ) -> &'static mut [u16; SAMPLE_BUFFER_SIZE] { - self.next_buffer.take().unwrap() - } - - /// Enqueue the next buffer for transmission to the DAC. - /// - /// # Args - /// * `data` - The next data to write to the DAC. - pub fn release_buffer( - &mut self, - next_buffer: &'static mut [u16; SAMPLE_BUFFER_SIZE], - ) { + pub fn acquire_buffer(&mut self) -> &mut [u16; SAMPLE_BUFFER_SIZE] { // 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() {} + let next_buffer = self.next_buffer.take().unwrap(); + // Start the next transfer. self.transfer.clear_interrupts(); let (prev_buffer, _) = @@ -187,6 +178,8 @@ macro_rules! dac_output { // .unwrap_none() https://github.com/rust-lang/rust/issues/62633 self.next_buffer.replace(prev_buffer); + + self.next_buffer.as_mut().unwrap() } } }; diff --git a/src/main.rs b/src/main.rs index b37f740..0ac8857 100644 --- a/src/main.rs +++ b/src/main.rs @@ -805,6 +805,7 @@ const APP: () = { c.resources.adcs.0.acquire_buffer(), c.resources.adcs.1.acquire_buffer(), ]; + let dac_samples = [ c.resources.dacs.0.acquire_buffer(), c.resources.dacs.1.acquire_buffer(), @@ -836,10 +837,6 @@ const APP: () = { builder.write_profile(); } - - let [dac0, dac1] = dac_samples; - c.resources.dacs.0.release_buffer(dac0); - c.resources.dacs.1.release_buffer(dac1); } #[idle(resources=[net_interface, pounder, mac_addr, eth_mac, iir_state, iir_ch, afes])] diff --git a/src/pounder/dds_output.rs b/src/pounder/dds_output.rs index 418eb65..36399f2 100644 --- a/src/pounder/dds_output.rs +++ b/src/pounder/dds_output.rs @@ -1,4 +1,57 @@ ///! The DdsOutput is used as an output stream to the pounder DDS. +///! +///! # Design +///! +///! The DDS stream interface is a means of quickly updating pounder DDS (direct digital synthesis) +///! outputs of the AD9959 DDS chip. The DDS communicates via a quad-SPI interface and a single +///! IO-update output pin. +///! +///! In order to update the DDS interface, the frequency tuning word, amplitude control word, and +///! the phase offset word for a channel can be modified to change the frequency, amplitude, or +///! phase on any of the 4 available output channels. Changes do not propagate to DDS outputs until +///! the IO-update pin is toggled high to activate the new configurations. This allows multiple +///! channels or parameters to be updated and then effects can take place simultaneously. +///! +///! In this implementation, the phase, frequency, or amplitude can be updated for any single +///! collection of outputs simultaneously. This is done by serializing the register writes to the +///! DDS into a single buffer of data and then writing the data over QSPI to the DDS. +///! +///! In order to minimize software overhead, data is written directly into the QSPI output FIFO. In +///! order to accomplish this most efficiently, serialized data is written as 32-bit words to +///! minimize the number of bus cycles necessary to write to the peripheral FIFO. A consequence of +///! this is that additional unneeded register writes may be appended to align a transfer to 32-bit +///! word sizes. +///! +///! In order to pulse the IO-update signal, the high-resolution timer output is used. The timer is +///! configured to assert the IO-update signal after a predefined delay and then de-assert the +///! signal after a predefined assertion duration. This allows for the actual QSPI transfer and +///! IO-update toggle to be completed asynchronously to the rest of software processing - that is, +///! software can schedule the DDS updates and then continue data processing. DDS updates then take +///! place in the future when the IO-update is toggled by hardware. +///! +///! +///! # Limitations +///! +///! The QSPI output FIFO is used as an intermediate buffer for holding pending QSPI writes. Because +///! of this, the implementation only supports up to 16 serialized bytes (the QSPI FIFO is 4 32-bit +///! words wide) in a single update. +///! +///! There is currently no synchronization between completion of the QSPI data write and the +///! IO-update signal. It is currently assumed that the QSPI transfer will always complete within a +///! predefined delay (the pre-programmed IO-update timer delay). +///! +///! +///! # Future Improvement +///! +///! In the future, it would be possible to utilize a DMA transfer to complete the QSPI transfer. +///! Once the QSPI transfer completed, this could trigger the IO-update timer to start to +///! asynchronously complete IO-update automatically. This would allow for arbitrary profile sizes +///! and ensure that IO-update was in-sync with the QSPI transfer. +///! +///! Currently, serialization is performed on each processing cycle. If there is a +///! compile-time-known register update sequence needed for the application, the serialization +///! process can be done once and then register values can be written into a pre-computed serialized +///! buffer to avoid the software overhead of much of the serialization process. use super::QspiInterface; use crate::hrtimer::HighResTimerE; use ad9959::{Channel, DdsConfig, ProfileSerializer}; From 8fb37c2db9e7386abbb5036752b5e562ce74886e Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:10:36 +0100 Subject: [PATCH 03/34] Adding docs --- src/dac.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dac.rs b/src/dac.rs index ef4601a..ef65d19 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -70,6 +70,7 @@ macro_rules! dac_output { Self { _channel, spi } } + /// Start the SPI and begin operating in a DMA-driven transfer mode. pub fn start_dma(&mut self) { // Allow the SPI FIFOs to operate using only DMA data channels. self.spi.enable_dma_tx(); From ec2aaecb480cd288a15ba7efb122f74c3ebd46fb Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:11:28 +0100 Subject: [PATCH 04/34] Adding safety documentation --- src/dac.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dac.rs b/src/dac.rs index ef65d19..f21ccfd 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -138,6 +138,8 @@ macro_rules! dac_output { // AXISRAM is uninitialized. As such, we manually zero-initialize it here before // starting the transfer. + // Note(unsafe): We currently own all DAC_BUF[index] buffers and are not using them + // elsewhere, so it is safe to access them here. for buf in unsafe { DAC_BUF[$index].iter_mut() } { for byte in buf.iter_mut() { *byte = 0; From fb1ea765ce9d8a9737b95a0c6e73cf6bf0a05b43 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:27:47 +0100 Subject: [PATCH 05/34] Updating DACs to utilize DBM --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- src/adc.rs | 4 ++-- src/dac.rs | 23 ++++++++++++----------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e35ab18..866c958 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,7 +874,7 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.8.0" -source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#0bfeeca4ce120c1b7c6d140a7da73a4372b874d8" +source = "git+https://github.com/quartiq/stm32h7xx-hal?branch=feature/dma-buffer-swap-logic#5f97920b639f8cb29c9f30c89a33960d5b2082f8" dependencies = [ "bare-metal 1.0.0", "cast", diff --git a/Cargo.toml b/Cargo.toml index 9dfa28a..73b2f69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,8 +56,8 @@ default-features = false [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] -git = "https://github.com/stm32-rs/stm32h7xx-hal" -branch = "dma" +git = "https://github.com/quartiq/stm32h7xx-hal" +branch = "feature/dma-buffer-swap-logic" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/src/adc.rs b/src/adc.rs index 9b0aa33..a652d3d 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -117,11 +117,11 @@ macro_rules! adc_input { /// Whenever the DMA request occurs, it should write into SPI's TX FIFO to start a DMA /// transfer. - fn address(&self) -> u32 { + fn address(&self) -> usize { // Note(unsafe): It is assumed that SPI is owned by another DMA transfer and this DMA is // only used for the transmit-half of DMA. let regs = unsafe { &*hal::stm32::$spi::ptr() }; - ®s.txdr as *const _ as u32 + ®s.txdr as *const _ as usize } } diff --git a/src/dac.rs b/src/dac.rs index f21ccfd..3e44379 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -34,12 +34,11 @@ ///! DMA channels to arbitrate which transfer occurs first. ///! ///! -///! # Future Improvements +///! # Limitations ///! -///! In this implementation, single buffer mode DMA transfers are used. As a result of this, it's -///! possible that a timer comparison could be missed during the swap-over, which will result in a -///! delay of a single output code. In the future, this can be remedied by utilize double-buffer -///! mode for the DMA transfers. +///! While double-buffered mode is used for DMA to avoid lost DAC-update events, there is no check +///! for re-use of a previously provided DAC output buffer. It is assumed that the DMA request is +///! served promptly after the transfer completes. use super::{ hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, @@ -50,8 +49,8 @@ use super::{ // processed). Note that the contents of AXI SRAM is uninitialized, so the buffer contents on // startup are undefined. The dimensions are `ADC_BUF[adc_index][ping_pong_index][sample_index]`. #[link_section = ".axisram.buffers"] -static mut DAC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 2]; 2] = - [[[0; SAMPLE_BUFFER_SIZE]; 2]; 2]; +static mut DAC_BUF: [[[u16; SAMPLE_BUFFER_SIZE]; 3]; 2] = + [[[0; SAMPLE_BUFFER_SIZE]; 3]; 2]; macro_rules! dac_output { ($name:ident, $index:literal, $data_stream:ident, @@ -92,8 +91,8 @@ macro_rules! dac_output { const REQUEST_LINE: Option = Some(DMAReq::$dma_req as u8); /// Whenever the DMA request occurs, it should write into SPI's TX FIFO. - fn address(&self) -> u32 { - &self.spi.inner().txdr as *const _ as u32 + fn address(&self) -> usize { + &self.spi.inner().txdr as *const _ as usize } } @@ -129,6 +128,7 @@ macro_rules! dac_output { // The stream constantly writes to the TX FIFO to write new update codes. let trigger_config = DmaConfig::default() .memory_increment(true) + .double_buffer(true) .peripheral_increment(false); // Listen for any potential SPI error signals, which may indicate that we are not generating @@ -153,7 +153,8 @@ macro_rules! dac_output { $spi::new(trigger_channel, spi), // Note(unsafe): This buffer is only used once and provided for the DMA transfer. unsafe { &mut DAC_BUF[$index][0] }, - None, + // Note(unsafe): This buffer is only used once and provided for the DMA transfer. + unsafe { Some(&mut DAC_BUF[$index][1]) }, trigger_config, ); @@ -162,7 +163,7 @@ macro_rules! dac_output { Self { transfer, // Note(unsafe): This buffer is only used once and provided for the next DMA transfer. - next_buffer: unsafe { Some(&mut DAC_BUF[$index][1]) }, + next_buffer: unsafe { Some(&mut DAC_BUF[$index][2]) }, } } From ee8f4d849f51274de5f45a7c2b7f10e540032186 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Thu, 17 Dec 2020 14:32:53 +0100 Subject: [PATCH 06/34] Adding documentation about double-buffered mode to DACs --- src/dac.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/dac.rs b/src/dac.rs index 3e44379..53076b0 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -13,6 +13,16 @@ ///! configuring a DMA stream for a number of successive DAC codes, hardware can regularly update ///! the DAC without requiring the CPU. ///! +///! In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC +///! output batch is always exactly 1 batch after the ADC batch that generated it. +///! +///! The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any +///! transfer events generated by the timer (for example, when 2 update cycles occur before the DMA +///! transfer completion is handled). In this mode, there should always be a valid buffer in the +///! "next-transfer" double-buffer location for the DMA transfer. Once a transfer completes, +///! software then has exactly one batch duration to properly configure the next buffer before it +///! begins transfer. +///! ///! ## Multiple Samples to Single DAC Codes ///! ///! For some applications, it may be desirable to generate a single DAC code from multiple ADC From f825f527850712aa65185303bbb8e548feffe6d5 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 06:08:07 -0800 Subject: [PATCH 07/34] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Jördens --- src/adc.rs | 4 ++-- src/dac.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/adc.rs b/src/adc.rs index a652d3d..09f1b05 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -49,7 +49,7 @@ ///! ///! The ADCs collect a group of N samples, which is referred to as a batch. The size of the batch ///! is configured by the user at compile-time to allow for a custom-tailored implementation. Larger -///! batch sizes generally provide for more processing time per sample, but come at the expense of +///! batch sizes generally provide for lower overhead and more processing time per sample, but come at the expense of ///! increased input -> output latency. ///! ///! @@ -69,7 +69,7 @@ ///! In this implementation, single buffer mode DMA transfers are used because the SPI RX FIFO can ///! be used as a means to both detect and buffer ADC samples during the buffer swap-over. Because ///! of this, double-buffered mode does not offer any advantages over single-buffered mode (unless -///! double-buffered mode offers less overhead when accessing data). +///! double-buffered mode offers less overhead due to the DMA disable/enable procedure). use super::{ hal, sampling_timer, DMAReq, DmaConfig, MemoryToPeripheral, PeripheralToMemory, Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, diff --git a/src/dac.rs b/src/dac.rs index 53076b0..22f33fa 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -14,14 +14,14 @@ ///! the DAC without requiring the CPU. ///! ///! In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC -///! output batch is always exactly 1 batch after the ADC batch that generated it. +///! output batch is always exactly 2 batches after the ADC batch that generated it. ///! ///! The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any ///! transfer events generated by the timer (for example, when 2 update cycles occur before the DMA -///! transfer completion is handled). In this mode, there should always be a valid buffer in the +///! transfer completion is handled). In this mode, by the time DMA swaps buffers, there is always a valid buffer in the ///! "next-transfer" double-buffer location for the DMA transfer. Once a transfer completes, -///! software then has exactly one batch duration to properly configure the next buffer before it -///! begins transfer. +///! software then has exactly one batch duration to fill the next buffer before its +///! transfer begins. ///! ///! ## Multiple Samples to Single DAC Codes ///! From eefb2acfda7bbf516f1a71b135e4d29f2d8470db Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 15:12:03 +0100 Subject: [PATCH 08/34] Updating dependencies --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- src/timers.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 030ff99..76fb832 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -874,7 +874,7 @@ dependencies = [ [[package]] name = "stm32h7xx-hal" version = "0.8.0" -source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#25ee0f3a9ae27d1fd6bb390d6045aa312f29f096" +source = "git+https://github.com/stm32-rs/stm32h7xx-hal?branch=dma#3da22d4935c8f6e412b99e6662ec11da5265fb88" dependencies = [ "bare-metal 1.0.0", "cast", diff --git a/Cargo.toml b/Cargo.toml index 73b2f69..9dfa28a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,8 +56,8 @@ default-features = false [dependencies.stm32h7xx-hal] features = ["stm32h743v", "rt", "unproven", "ethernet", "quadspi"] -git = "https://github.com/quartiq/stm32h7xx-hal" -branch = "feature/dma-buffer-swap-logic" +git = "https://github.com/stm32-rs/stm32h7xx-hal" +branch = "dma" [features] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] diff --git a/src/timers.rs b/src/timers.rs index 8d7d010..0f4cdb8 100644 --- a/src/timers.rs +++ b/src/timers.rs @@ -208,9 +208,9 @@ macro_rules! timer_channels { const REQUEST_LINE: Option = Some(DMAReq::[< $TY _CH $index >]as u8); - fn address(&self) -> u32 { + fn address(&self) -> usize { let regs = unsafe { &*<$TY>::ptr() }; - ®s.[] as *const _ as u32 + ®s.[] as *const _ as usize } } } From 56366a013fe647735e957ffcb80f9ad109679483 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 15:34:12 +0100 Subject: [PATCH 09/34] Specifying consequences of failing to meet timing --- src/dac.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dac.rs b/src/dac.rs index efa9588..109dc78 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -21,7 +21,8 @@ ///! transfer completion is handled). In this mode, by the time DMA swaps buffers, there is always a valid buffer in the ///! "next-transfer" double-buffer location for the DMA transfer. Once a transfer completes, ///! software then has exactly one batch duration to fill the next buffer before its -///! transfer begins. +///! transfer begins. If software does not meet this deadline, old data will be repeatedly generated +///! on the output and output will be shifted by one batch. ///! ///! ## Multiple Samples to Single DAC Codes ///! From 5eab732d9336ca95f0801a3ea02beaee9c1426f8 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 6 Jan 2021 15:38:04 +0100 Subject: [PATCH 10/34] Adding information about DSP timing specifications --- src/main.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main.rs b/src/main.rs index c38baeb..320679a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -849,6 +849,22 @@ const APP: () = { } } + /// Main DSP processing routine for Stabilizer. + /// + /// # Note + /// Processing time for the DSP application code is bounded by the following constraints: + /// + /// DSP application code starts after the ADC has generated a batch of samples and must be + /// completed by the time the next batch of ADC samples has been acquired (plus the FIFO buffer + /// time). If this constraint is not met, firmware will panic due to an ADC input overrun. + /// + /// The DSP application code must also fill out the next DAC output buffer in time such that the + /// DAC can switch to it when it has completed the current buffer. If this constraint is not met + /// it's possible that old DAC codes will be generated on the output and the output samples will + /// be delayed by 1 batch. + /// + /// Because the ADC and DAC operate at the same rate, these two constraints actually implement + /// the same time bounds, meeting one also means the other is also met. #[task(binds=DMA1_STR3, resources=[adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] fn process(c: process::Context) { let adc_samples = [ From bcf7a5999374b646037c6b70d747b49387575dbe Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 12 Jan 2021 14:15:45 +0100 Subject: [PATCH 11/34] Removing dac isr clear --- src/dac.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/dac.rs b/src/dac.rs index 4a2f789..971a7de 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -187,7 +187,6 @@ macro_rules! dac_output { let next_buffer = self.next_buffer.take().unwrap(); // Start the next transfer. - self.transfer.clear_interrupts(); let (prev_buffer, _, _) = self.transfer.next_transfer(next_buffer).unwrap(); From 028f4a1bb2cc97f586084f35bfcfb36116e80062 Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Thu, 7 Jan 2021 09:49:23 -0800 Subject: [PATCH 12/34] fix small typos --- src/digital_input_stamper.rs | 4 ++-- src/main.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/digital_input_stamper.rs b/src/digital_input_stamper.rs index d63849c..648105f 100644 --- a/src/digital_input_stamper.rs +++ b/src/digital_input_stamper.rs @@ -26,13 +26,13 @@ ///! timestamping is desired in DI1, a separate timer + capture channel will be necessary. use super::{hal, timers, ADC_SAMPLE_TICKS, SAMPLE_BUFFER_SIZE}; -/// Calculate the period of the digital input timestampe timer. +/// Calculate the period of the digital input timestamp timer. /// /// # Note /// The period returned will be 1 less than the required period in timer ticks. The value returned /// can be immediately programmed into a hardware timer period register. /// -/// The period is calcualted to be some power-of-two multiple of the batch size, such that N batches +/// The period is calculated to be some power-of-two multiple of the batch size, such that N batches /// will occur between each timestamp timer overflow. /// /// # Returns diff --git a/src/main.rs b/src/main.rs index e2de7d5..c5a9698 100644 --- a/src/main.rs +++ b/src/main.rs @@ -353,7 +353,7 @@ const APP: () = { timer5.pause(); timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY); - // The time stamp timer must run at exactly a multiple of the sample timer based on the + // The timestamp timer must run at exactly a multiple of the sample timer based on the // batch size. To accomodate this, we manually set the prescaler identical to the sample // timer, but use a period that is longer. let mut timer = timers::TimestampTimer::new(timer5); From bae295140ddd6fe2a97ee703128f8b6d1de91b48 Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Fri, 8 Jan 2021 11:53:08 -0800 Subject: [PATCH 13/34] update lock-in for integer math and PLL --- dsp/src/lib.rs | 21 +- dsp/src/lockin.rs | 563 +++++++---------- dsp/tests/lockin_low_pass.rs | 1137 ---------------------------------- 3 files changed, 238 insertions(+), 1483 deletions(-) delete mode 100644 dsp/tests/lockin_low_pass.rs diff --git a/dsp/src/lib.rs b/dsp/src/lib.rs index fb189fa..aeec742 100644 --- a/dsp/src/lib.rs +++ b/dsp/src/lib.rs @@ -5,7 +5,7 @@ use core::ops::{Add, Mul, Neg}; pub type Complex = (T, T); -/// Round up half. +/// Bit shift, round up half. /// /// # Arguments /// @@ -20,6 +20,25 @@ pub fn shift_round(x: i32, shift: usize) -> i32 { (x + (1 << (shift - 1))) >> shift } +/// Integer division, round up half. +/// +/// # Arguments +/// +/// `dividend` - Value to divide. +/// `divisor` - Value that divides the dividend. +/// +/// # Returns +/// +/// Divided and rounded value. +#[inline(always)] +pub fn divide_round(dividend: i64, divisor: i64) -> i64 { + debug_assert!( + dividend as i128 + (divisor as i128 - 1) < i64::MAX as i128 + && dividend as i128 + (divisor as i128 - 1) > i64::MIN as i128 + ); + (dividend + (divisor - 1)) / divisor +} + fn abs(x: T) -> T where T: PartialOrd + Default + Neg, diff --git a/dsp/src/lockin.rs b/dsp/src/lockin.rs index bb649ad..5b82313 100644 --- a/dsp/src/lockin.rs +++ b/dsp/src/lockin.rs @@ -52,47 +52,36 @@ //! the demodulation frequency. This does not require any state //! information and is therefore a normal function. -use super::iir::{IIRState, IIR}; -use super::Complex; -use core::f32::consts::PI; +use super::iir_int::{IIRState, IIR}; +use super::pll::PLL; +use super::trig::{atan2, cossin}; +use super::{divide_round, Complex}; + +/// TODO these constants are copied from main.rs and should be +/// shared. Additionally, we should probably store the log2 values and +/// compute the actual values from these in main, as is done here. +pub const SAMPLE_BUFFER_SIZE_LOG2: usize = 0; +pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; + +pub const ADC_SAMPLE_TICKS_LOG2: usize = 8; +pub const ADC_SAMPLE_TICKS: usize = 1 << ADC_SAMPLE_TICKS_LOG2; + +pub const ADC_BATCHES_LOG2: usize = + 32 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_SAMPLE_TICKS_LOG2; +pub const ADC_BATCHES: usize = 1 << ADC_BATCHES_LOG2; -/// The number of ADC samples in one batch. -pub const ADC_SAMPLE_BUFFER_SIZE: usize = 16; -/// The number of outputs sent to the DAC for each ADC batch. pub const DECIMATED_BUFFER_SIZE: usize = 1; -/// Treat the 2-element array as a FIFO. This allows new elements to -/// be pushed into the array, existing elements to shift back in the -/// array, and the last element to fall off the array. -trait Fifo2 { - fn push(&mut self, new_element: Option); -} - -impl Fifo2 for [Option; 2] { - /// Push a new element into the array. The existing elements move - /// backward in the array by one location, and the current last - /// element is discarded. - /// - /// # Arguments - /// - /// * `new_element` - New element pushed into the front of the - /// array. - fn push(&mut self, new_element: Option) { - // For array sizes greater than 2 it would be preferable to - // use a rotating index to avoid unnecessary data - // copying. However, this would somewhat complicate the use of - // iterators and for 2 elements, shifting is inexpensive. - self[1] = self[0]; - self[0] = new_element; - } -} - /// Performs lock-in amplifier processing of a signal. pub struct Lockin { - phase_offset: f32, - sample_period: u32, harmonic: u32, - timestamps: [Option; 2], + phase_offset: u32, + batch_index: u32, + last_phase: Option, + last_frequency: Option, + pll: PLL, + pll_shift_frequency: u8, + pll_shift_phase: u8, iir: IIR, iirstate: [IIRState; 2], } @@ -102,31 +91,36 @@ impl Lockin { /// /// # Arguments /// - /// * `phase_offset` - Phase offset (in radians) applied to the - /// demodulation signal. - /// * `sample_period` - ADC sampling period in terms of the - /// internal clock period. /// * `harmonic` - Integer scaling factor used to adjust the /// demodulation frequency. E.g., 2 would demodulate with the /// first harmonic. + /// * `phase_offset` - Phase offset applied to the demodulation + /// signal. /// * `iir` - IIR biquad filter. + /// * `pll_shift_frequency` - See PLL::update(). + /// * `pll_shift_phase` - See PLL::update(). /// /// # Returns /// /// New `Lockin` instance. pub fn new( - phase_offset: f32, - sample_period: u32, harmonic: u32, + phase_offset: u32, iir: IIR, + pll_shift_frequency: u8, + pll_shift_phase: u8, ) -> Self { Lockin { - phase_offset: phase_offset, - sample_period: sample_period, - harmonic: harmonic, - timestamps: [None, None], - iir: iir, - iirstate: [[0.; 5]; 2], + harmonic, + phase_offset, + batch_index: 0, + last_phase: None, + last_frequency: None, + pll: PLL::default(), + pll_shift_frequency, + pll_shift_phase, + iir, + iirstate: [[0; 5]; 2], } } @@ -135,120 +129,88 @@ impl Lockin { /// # Arguments /// /// * `adc_samples` - One batch of ADC samples. - /// * `timestamps` - Counter values corresponding to the edges of - /// an external reference signal. The counter is incremented by a - /// fast internal clock. + /// * `timestamp` - Counter value corresponding to the edges of an + /// external reference signal. The counter is incremented by a + /// fast internal clock. Each ADC sample batch can contain 0 or 1 + /// timestamps. /// /// # Returns /// /// The demodulated complex signal as a `Result`. When there are /// an insufficient number of timestamps to perform processing, /// `Err` is returned. - /// - /// # Assumptions - /// - /// `demodulate` expects that the timestamp counter value is equal - /// to 0 when the ADC samples its first input in a batch. This can - /// be achieved by configuring the timestamp counter to overflow - /// at the end of the ADC batch sampling period. pub fn demodulate( &mut self, adc_samples: &[i16], - timestamps: &[u16], - ) -> Result<[Complex; ADC_SAMPLE_BUFFER_SIZE], &str> { - let sample_period = self.sample_period as i32; - // update old timestamps for new ADC batch - self.timestamps.iter_mut().for_each(|t| match *t { - Some(timestamp) => { - // Existing timestamps have aged by one ADC batch - // period since the last ADC batch. - *t = Some( - timestamp - ADC_SAMPLE_BUFFER_SIZE as i32 * sample_period, - ); - } - None => (), - }); + timestamp: Option, + ) -> Result<[Complex; SAMPLE_BUFFER_SIZE], &str> { + let frequency: i64; + let phase: i64; - // return prematurely if there aren't enough timestamps for - // processing - let old_timestamp_count = - self.timestamps.iter().filter(|t| t.is_some()).count(); - if old_timestamp_count + timestamps.len() < 2 { - return Err("insufficient timestamps"); + match timestamp { + Some(t) => { + let res = self.pll.update( + t as i32, + self.pll_shift_frequency, + self.pll_shift_phase, + ); + phase = res.0 as u32 as i64; + frequency = res.1 as u32 as i64; + self.last_phase = Some(phase); + self.last_frequency = Some(frequency); + } + None => match self.last_phase { + Some(t) => { + phase = t; + frequency = self.last_frequency.unwrap(); + } + None => { + self.batch_index += 1; + if self.batch_index == ADC_BATCHES as u32 { + self.batch_index = 0; + } + return Err("insufficient timestamps"); + } + }, } - let mut signal = [(0., 0.); ADC_SAMPLE_BUFFER_SIZE]; - // if we have not yet recorded any timestamps, the first - // reference period must be computed from the first and - // second timestamps in the array - let mut timestamp_index: usize = - if old_timestamp_count == 0 { 1 } else { 0 }; + let demodulation_frequency = divide_round( + 1 << (64 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_BATCHES_LOG2), + frequency, + ) as u32; + let demodulation_initial_phase = divide_round( + (((self.batch_index as i64) << (32 - ADC_BATCHES_LOG2)) - phase) + << 32, + frequency, + ) as u32; - // compute ADC sample phases, sines/cosines and demodulate - signal + let mut demodulation_signal = [(0_i32, 0_i32); SAMPLE_BUFFER_SIZE]; + + demodulation_signal .iter_mut() .zip(adc_samples.iter()) .enumerate() .for_each(|(i, (s, sample))| { - let adc_sample_count = i as i32 * sample_period; - // index of the closest timestamp that occurred after - // the current ADC sample - let closest_timestamp_after_index: i32 = if timestamps.len() > 0 - { - // Linear search is fast because both the timestamps - // and ADC sample counts are sorted. Because of this, - // we only need to check timestamps that were also - // greater than the last ADC sample count. - while timestamp_index < timestamps.len() - 1 - && (timestamps[timestamp_index] as i32) - < adc_sample_count - { - timestamp_index += 1; - } - timestamp_index as i32 - } else { - -1 - }; - - // closest timestamp that occurred before the current - // ADC sample - let closest_timestamp_before: i32; - let reference_period = if closest_timestamp_after_index < 0 { - closest_timestamp_before = self.timestamps[0].unwrap(); - closest_timestamp_before - self.timestamps[1].unwrap() - } else if closest_timestamp_after_index == 0 { - closest_timestamp_before = self.timestamps[0].unwrap(); - timestamps[0] as i32 - closest_timestamp_before - } else { - closest_timestamp_before = timestamps - [(closest_timestamp_after_index - 1) as usize] - as i32; - timestamps[closest_timestamp_after_index as usize] as i32 - - closest_timestamp_before - }; - - let integer_phase: i32 = (adc_sample_count - - closest_timestamp_before) - * self.harmonic as i32; - let phase = self.phase_offset - + 2. * PI * integer_phase as f32 / reference_period as f32; - let (sine, cosine) = libm::sincosf(phase); - let sample = *sample as f32; - s.0 = sine * sample; - s.1 = cosine * sample; + let sample_phase = (self.harmonic.wrapping_mul( + (demodulation_frequency.wrapping_mul(i as u32)) + .wrapping_add(demodulation_initial_phase), + )) + .wrapping_add(self.phase_offset); + let (cos, sin) = cossin(sample_phase as i32); + // cos/sin take up 32 bits and sample takes up 16 + // bits. Make this fit into a 32 bit result. + s.0 = ((*sample as i64 * cos as i64) >> 16) as i32; + s.1 = ((*sample as i64 * sin as i64) >> 16) as i32; }); - // record new timestamps - let start_index: usize = if timestamps.len() < 2 { - 0 + if self.batch_index < ADC_BATCHES as u32 - 1 { + self.batch_index += 1; } else { - timestamps.len() - 2 - }; - timestamps[start_index..] - .iter() - .for_each(|t| self.timestamps.push(Some(*t as i32))); + self.batch_index = 0; + self.last_phase = Some(self.last_phase.unwrap() - (1 << 32)); + } - Ok(signal) + Ok(demodulation_signal) } /// Filter the complex signal using the supplied biquad IIR. The @@ -257,7 +219,7 @@ impl Lockin { /// # Arguments /// /// * `signal` - Complex signal to filter. - pub fn filter(&mut self, signal: &mut [Complex]) { + pub fn filter(&mut self, signal: &mut [Complex]) { signal.iter_mut().for_each(|s| { s.0 = self.iir.update(&mut self.iirstate[0], s.0); s.1 = self.iir.update(&mut self.iirstate[1], s.1); @@ -266,8 +228,8 @@ impl Lockin { } /// Decimate the complex signal to `DECIMATED_BUFFER_SIZE`. The ratio -/// of `ADC_SAMPLE_BUFFER_SIZE` to `DECIMATED_BUFFER_SIZE` must be a -/// power of 2. +/// of `SAMPLE_BUFFER_SIZE` to `DECIMATED_BUFFER_SIZE` must be a power +/// of 2. /// /// # Arguments /// @@ -277,14 +239,12 @@ impl Lockin { /// /// The decimated signal. pub fn decimate( - signal: [Complex; ADC_SAMPLE_BUFFER_SIZE], -) -> [Complex; DECIMATED_BUFFER_SIZE] { - let n_k = ADC_SAMPLE_BUFFER_SIZE / DECIMATED_BUFFER_SIZE; - debug_assert!( - ADC_SAMPLE_BUFFER_SIZE == DECIMATED_BUFFER_SIZE || n_k % 2 == 0 - ); + signal: [Complex; SAMPLE_BUFFER_SIZE], +) -> [Complex; DECIMATED_BUFFER_SIZE] { + let n_k = SAMPLE_BUFFER_SIZE / DECIMATED_BUFFER_SIZE; + debug_assert!(SAMPLE_BUFFER_SIZE == DECIMATED_BUFFER_SIZE || n_k % 2 == 0); - let mut signal_decimated = [(0_f32, 0_f32); DECIMATED_BUFFER_SIZE]; + let mut signal_decimated = [(0_i32, 0_i32); DECIMATED_BUFFER_SIZE]; signal_decimated .iter_mut() @@ -302,11 +262,13 @@ pub fn decimate( /// /// # Arguments /// -/// * `signal` - Complex signal to decimate. -pub fn magnitude_phase(signal: &mut [Complex]) { +/// * `signal` - Complex signal for which the magnitude and phase +/// should be computed. TODO currently, we compute the square of the +/// magnitude. This should be changed to be the actual magnitude. +pub fn magnitude_phase(signal: &mut [Complex]) { signal.iter_mut().for_each(|s| { - let new_i = libm::sqrtf([s.0, s.1].iter().map(|i| i * i).sum()); - let new_q = libm::atan2f(s.1, s.0); + let new_i = [s.0, s.1].iter().map(|i| i * i).sum(); + let new_q = atan2(s.1, s.0); s.0 = new_i; s.1 = new_q; }); @@ -315,204 +277,115 @@ pub fn magnitude_phase(signal: &mut [Complex]) { #[cfg(test)] mod tests { use super::*; - use crate::testing::complex_allclose; #[test] - fn array_push() { - let mut arr: [Option; 2] = [None, None]; - arr.push(Some(1)); - assert_eq!(arr, [Some(1), None]); - arr.push(Some(2)); - assert_eq!(arr, [Some(2), Some(1)]); - arr.push(Some(10)); - assert_eq!(arr, [Some(10), Some(2)]); - } - - #[test] - fn magnitude_phase_length_1_quadrant_1() { - let mut signal: [Complex; 1] = [(1., 1.)]; - magnitude_phase(&mut signal); - assert!(complex_allclose( - &signal, - &[(2_f32.sqrt(), PI / 4.)], - f32::EPSILON, - 0. - )); - - signal = [(3_f32.sqrt() / 2., 1. / 2.)]; - magnitude_phase(&mut signal); - assert!(complex_allclose( - &signal, - &[(1., PI / 6.)], - f32::EPSILON, - 0. - )); - } - - #[test] - fn magnitude_phase_length_1_quadrant_2() { - let mut signal = [(-1., 1.)]; - magnitude_phase(&mut signal); - assert!(complex_allclose( - &signal, - &[(2_f32.sqrt(), 3. * PI / 4.)], - f32::EPSILON, - 0. - )); - - signal = [(-1. / 2., 3_f32.sqrt() / 2.)]; - magnitude_phase(&mut signal); - assert!(complex_allclose( - &signal, - &[(1_f32, 2. * PI / 3.)], - f32::EPSILON, - 0. - )); - } - - #[test] - fn magnitude_phase_length_1_quadrant_3() { - let mut signal = [(-1. / 2_f32.sqrt(), -1. / 2_f32.sqrt())]; - magnitude_phase(&mut signal); - assert!(complex_allclose( - &signal, - &[(1_f32.sqrt(), -3. * PI / 4.)], - f32::EPSILON, - 0. - )); - - signal = [(-1. / 2., -2_f32.sqrt())]; - magnitude_phase(&mut signal); - assert!(complex_allclose( - &signal, - &[((3. / 2.) as f32, -1.91063323625 as f32)], - f32::EPSILON, - 0. - )); - } - - #[test] - fn magnitude_phase_length_1_quadrant_4() { - let mut signal = [(1. / 2_f32.sqrt(), -1. / 2_f32.sqrt())]; - magnitude_phase(&mut signal); - assert!(complex_allclose( - &signal, - &[(1_f32.sqrt(), -1. * PI / 4.)], - f32::EPSILON, - 0. - )); - - signal = [(3_f32.sqrt() / 2., -1. / 2.)]; - magnitude_phase(&mut signal); - assert!(complex_allclose( - &signal, - &[(1_f32, -PI / 6.)], - f32::EPSILON, - 0. - )); - } - - #[test] - fn decimate_sample_16_decimated_1() { - let signal: [Complex; ADC_SAMPLE_BUFFER_SIZE] = [ - (0.0, 1.6), - (0.1, 1.7), - (0.2, 1.8), - (0.3, 1.9), - (0.4, 2.0), - (0.5, 2.1), - (0.6, 2.2), - (0.7, 2.3), - (0.8, 2.4), - (0.9, 2.5), - (1.0, 2.6), - (1.1, 2.7), - (1.2, 2.8), - (1.3, 2.9), - (1.4, 3.0), - (1.5, 3.1), - ]; - assert_eq!(decimate(signal), [(0.0, 1.6)]); - } - - #[test] - fn lockin_demodulate_valid_0() { + /// Ensure that the demodulation signals are within some tolerance + /// band of the target value given the phase and frequency values + /// provided by the PLL. + fn demodulate() { + const PLL_SHIFT_FREQUENCY: u8 = 4; + const PLL_SHIFT_PHASE: u8 = 3; + const HARMONIC: u32 = 1; + const PHASE_OFFSET: u32 = 0; let mut lockin = Lockin::new( - 0., - 200, - 1, - IIR { - ba: [0_f32; 5], - y_offset: 0., - y_min: -(1 << 15) as f32, - y_max: (1 << 15) as f32 - 1., - }, + HARMONIC, + PHASE_OFFSET, + IIR { ba: [0; 5] }, + PLL_SHIFT_FREQUENCY, + PLL_SHIFT_PHASE, ); - assert_eq!( - lockin.demodulate(&[0; ADC_SAMPLE_BUFFER_SIZE], &[]), - Err("insufficient timestamps") - ); - } - #[test] - fn lockin_demodulate_valid_1() { - let mut lockin = Lockin::new( - 0., - 200, - 1, - IIR { - ba: [0_f32; 5], - y_offset: 0., - y_min: -(1 << 15) as f32, - y_max: (1 << 15) as f32 - 1., - }, - ); - assert_eq!( - lockin.demodulate(&[0; ADC_SAMPLE_BUFFER_SIZE], &[0],), - Err("insufficient timestamps") - ); - } + // Duplicate the PLL outside demodulate so that we don't test + // its behavior. + let mut tracking_pll = PLL::default(); + let mut tracking_phase: i32 = 0; + let mut tracking_frequency: i32 = 0; - #[test] - fn lockin_demodulate_valid_2() { - let adc_period: u32 = 200; - let mut lockin = Lockin::new( - 0., - adc_period, - 1, - IIR { - ba: [0_f32; 5], - y_offset: 0., - y_min: -(1 << 15) as f32, - y_max: (1 << 15) as f32 - 1., - }, + const REFERENCE_FREQUENCY: usize = 10_000; + let mut reference_edge: usize = REFERENCE_FREQUENCY; + + // Ensure that we receive at most 1 timestamp per batch. + debug_assert!( + REFERENCE_FREQUENCY >= SAMPLE_BUFFER_SIZE * ADC_SAMPLE_TICKS ); - let adc_samples: [i16; ADC_SAMPLE_BUFFER_SIZE] = - [-8, 7, -7, 6, -6, 5, -5, 4, -4, 3, -3, 2, -2, -1, 1, 0]; - let reference_period: u16 = 2800; - let initial_phase_integer: u16 = 200; - let timestamps: &[u16] = &[ - initial_phase_integer, - initial_phase_integer + reference_period, - ]; - let initial_phase: f32 = - -(initial_phase_integer as f32) / reference_period as f32 * 2. * PI; - let phase_increment: f32 = - adc_period as f32 / reference_period as f32 * 2. * PI; - let mut signal = [(0., 0.); ADC_SAMPLE_BUFFER_SIZE]; - for (n, s) in signal.iter_mut().enumerate() { - let adc_phase = initial_phase + n as f32 * phase_increment; - let sine = adc_phase.sin(); - let cosine = adc_phase.cos(); - s.0 = sine * adc_samples[n] as f32; - s.1 = cosine * adc_samples[n] as f32; + + for batch in 0..100_000 { + let tick: usize = batch * ADC_SAMPLE_TICKS * SAMPLE_BUFFER_SIZE; + let timestamp: Option; + + // When the reference edge occurred during the current + // batch acquisition, register the timestamp and update + // the tracking PLL. + if reference_edge >= tick + && reference_edge < tick + ADC_SAMPLE_TICKS * SAMPLE_BUFFER_SIZE + { + timestamp = Some(reference_edge as u32); + + let tracking_update = tracking_pll.update( + reference_edge as i32, + PLL_SHIFT_FREQUENCY, + PLL_SHIFT_PHASE, + ); + tracking_phase = tracking_update.0; + tracking_frequency = tracking_update.1; + + reference_edge += REFERENCE_FREQUENCY; + } else { + timestamp = None; + } + + let timestamp_before_batch = if tracking_phase > tick as i32 { + // There can be at most 1 reference edge per batch, so + // this will necessarily place the timestamp prior to + // the current batch. + tracking_phase - tracking_frequency + } else { + tracking_phase + }; + + let initial_phase = (((tick as f64 + - timestamp_before_batch as f64) + / tracking_frequency as f64 + * (1_i64 << 32) as f64) + .round() + % u32::MAX as f64) as u32; + let frequency = ((ADC_SAMPLE_TICKS as f64 + / tracking_frequency as f64 + * (1_i64 << 32) as f64) + .round() + % u32::MAX as f64) as u32; + + match lockin.demodulate(&[i16::MAX; SAMPLE_BUFFER_SIZE], timestamp) + { + Ok(v) => { + println!("batch : {}", batch); + for sample in 0..SAMPLE_BUFFER_SIZE { + const TOL: i32 = 50_000; + let cos = v[sample].0; + let sin = v[sample].1; + + let (mut target_cos, mut target_sin) = cossin( + HARMONIC + .wrapping_mul( + (frequency.wrapping_mul(sample as u32)) + .wrapping_add(initial_phase), + ) + .wrapping_add(PHASE_OFFSET) + as i32, + ); + target_cos /= 2; + target_sin /= 2; + + println!("sample : {}", sample); + println!("tol : {}", TOL); + println!("cos, target: {}, {}", cos, target_cos); + println!("sin, target: {}, {}", sin, target_sin); + assert!((cos - target_cos).abs() < TOL); + assert!((sin - target_sin).abs() < TOL); + } + } + Err(_) => {} + } } - let result = lockin.demodulate(&adc_samples, timestamps).unwrap(); - assert!( - complex_allclose(&result, &signal, 0., 1e-5), - "\nsignal computed: {:?},\nsignal expected: {:?}", - result, - signal - ); } } diff --git a/dsp/tests/lockin_low_pass.rs b/dsp/tests/lockin_low_pass.rs deleted file mode 100644 index 37ab2b0..0000000 --- a/dsp/tests/lockin_low_pass.rs +++ /dev/null @@ -1,1137 +0,0 @@ -use dsp::iir::IIR; -use dsp::lockin::{ - decimate, magnitude_phase, Lockin, ADC_SAMPLE_BUFFER_SIZE, - DECIMATED_BUFFER_SIZE, -}; -use dsp::Complex; - -use std::f64::consts::PI; -use std::vec::Vec; - -const ADC_MAX: f64 = 1.; -const ADC_MAX_COUNT: f64 = (1 << 15) as f64; - -/// Single-frequency sinusoid. -#[derive(Copy, Clone)] -struct PureSine { - // Frequency (in Hz). - frequency: f64, - // Amplitude in dBFS (decibels relative to full-scale). A 16-bit - // ADC has a minimum dBFS for each sample of -90. - amplitude_dbfs: f64, - // Phase offset (in radians). - phase_offset: f64, -} - -/// Convert a dBFS voltage ratio to a linear ratio. -/// -/// # Arguments -/// -/// * `dbfs` - dB ratio relative to full scale. -/// -/// # Returns -/// -/// Linear value. -fn linear(dbfs: f64) -> f64 { - let base = 10_f64; - ADC_MAX * base.powf(dbfs / 20.) -} - -/// Convert a linear voltage ratio to a dBFS ratio. -/// -/// # Arguments -/// -/// * `linear` - Linear voltage ratio. -/// -/// # Returns -/// -/// dBFS value. -fn dbfs(linear: f64) -> f64 { - 20. * (linear / ADC_MAX).log10() -} - -/// Convert a real ADC input value in the range `-ADC_MAX` to -/// `+ADC_MAX` to an equivalent 16-bit ADC sampled value. This models -/// the ideal ADC transfer function. -/// -/// # Arguments -/// -/// * `x` - Real ADC input value. -/// -/// # Returns -/// -/// Sampled ADC value. -fn real_to_adc_sample(x: f64) -> i16 { - let max: i32 = i16::MAX as i32; - let min: i32 = i16::MIN as i32; - - let xi: i32 = (x / ADC_MAX * ADC_MAX_COUNT) as i32; - - // It's difficult to characterize the correct output result when - // the inputs are clipped, so panic instead. - if xi > max { - panic!("Input clipped to maximum, result is unlikely to be correct."); - } else if xi < min { - panic!("Input clipped to minimum, result is unlikely to be correct."); - } - - xi as i16 -} - -/// Generate `ADC_SAMPLE_BUFFER_SIZE` values of an ADC-sampled signal -/// starting at `timestamp_start`. -/// -/// # Arguments -/// -/// * `pure_signals` - Pure sinusoidal components of the ADC-sampled -/// signal. -/// * `timestamp_start` - Starting time of ADC-sampled signal in terms -/// of the internal clock count. -/// * `internal_frequency` - Internal clock frequency (in Hz). -/// * `adc_frequency` - ADC sampling frequency (in Hz). -/// -/// # Returns -/// -/// The sampled signal at the ADC input. -fn adc_sampled_signal( - pure_signals: &Vec, - timestamp_start: u64, - internal_frequency: f64, - adc_frequency: f64, -) -> [i16; ADC_SAMPLE_BUFFER_SIZE] { - // amplitude of each pure signal - let mut amplitude: Vec = Vec::::new(); - // initial phase value for each pure signal - let mut initial_phase: Vec = Vec::::new(); - // phase increment at each ADC sample for each pure signal - let mut phase_increment: Vec = Vec::::new(); - let adc_period = internal_frequency / adc_frequency; - - // For each pure sinusoid, compute the amplitude, phase - // corresponding to the first ADC sample, and phase increment for - // each subsequent ADC sample. - for pure_signal in pure_signals.iter() { - let signal_period = internal_frequency / pure_signal.frequency; - let phase_offset_count = - pure_signal.phase_offset / (2. * PI) * signal_period; - let initial_phase_count = - (phase_offset_count + timestamp_start as f64) % signal_period; - - amplitude.push(linear(pure_signal.amplitude_dbfs)); - initial_phase.push(2. * PI * initial_phase_count / signal_period); - phase_increment.push(2. * PI * adc_period / signal_period); - } - - // Compute the input signal corresponding to each ADC sample by - // summing the contributions from each pure sinusoid. - let mut signal: [i16; ADC_SAMPLE_BUFFER_SIZE] = [0; ADC_SAMPLE_BUFFER_SIZE]; - signal.iter_mut().enumerate().for_each(|(n, s)| { - *s = real_to_adc_sample( - amplitude - .iter() - .zip(initial_phase.iter()) - .zip(phase_increment.iter()) - .fold(0., |acc, ((a, phi), theta)| { - acc + a * (phi + theta * n as f64).sin() - }), - ); - }); - - signal -} - -/// Reference clock timestamp values in one ADC batch period starting -/// at `timestamp_start`. Also returns the number of valid timestamps. -/// -/// # Arguments -/// -/// * `reference_frequency` - External reference signal frequency (in -/// Hz). -/// * `timestamp_start` - Start time in terms of the internal clock -/// count. This is the start time of the current processing sequence -/// (i.e., for the current `ADC_SAMPLE_BUFFER_SIZE` ADC samples). -/// * `timestamp_stop` - Stop time in terms of the internal clock -/// count. -/// * `internal_frequency` - Internal clock frequency (in Hz). -/// -/// # Returns -/// -/// Tuple consisting of the number of valid timestamps in the ADC -/// batch period, followed by an array of the timestamp values. -fn adc_batch_timestamps( - reference_frequency: f64, - timestamps: &mut [u16], - timestamp_start: u64, - timestamp_stop: u64, - internal_frequency: f64, -) -> &[u16] { - let reference_period = internal_frequency / reference_frequency; - let start_count = timestamp_start as f64 % reference_period; - let mut valid_timestamps: usize = 0; - - let mut timestamp = (reference_period - start_count) % reference_period; - while timestamp < (timestamp_stop - timestamp_start) as f64 { - timestamps[valid_timestamps] = timestamp as u16; - timestamp += reference_period; - valid_timestamps += 1; - } - - ×tamps[..valid_timestamps] -} - -/// Lowpass biquad filter using cutoff and sampling frequencies. -/// Taken from: -/// https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html -/// -/// # Arguments -/// -/// * `corner_frequency` - Corner frequency, or 3dB cutoff frequency -/// (in Hz). -/// * `sampling_frequency` - Sampling frequency (in Hz). -/// -/// # Returns -/// -/// 2nd-order IIR filter coefficients in the form [b0,b1,b2,a1,a2]. a0 -/// is set to -1. -fn lowpass_iir_coefficients( - corner_frequency: f64, - sampling_frequency: f64, -) -> [f32; 5] { - let normalized_angular_frequency: f64 = - 2. * PI * corner_frequency / sampling_frequency; - let quality_factor: f64 = 1. / 2f64.sqrt(); - let alpha: f64 = normalized_angular_frequency.sin() / (2. * quality_factor); - // All b coefficients have been multiplied by a factor of 2 in - // comparison with the link above in order to set the passband - // gain to 2. - let mut b0: f64 = 1. - normalized_angular_frequency.cos(); - let mut b1: f64 = 2. * (1. - normalized_angular_frequency.cos()); - let mut b2: f64 = b0; - let a0: f64 = 1. + alpha; - let mut a1: f64 = -2. * normalized_angular_frequency.cos(); - let mut a2: f64 = 1. - alpha; - b0 /= a0; - b1 /= a0; - b2 /= a0; - a1 /= -a0; - a2 /= -a0; - - [b0 as f32, b1 as f32, b2 as f32, a1 as f32, a2 as f32] -} - -/// Check that a measured value is within some tolerance of the actual -/// value. This allows setting both fixed and relative tolerances. -/// -/// # Arguments -/// -/// * `actual` - Actual value with respect to which the magnitude of -/// the relative tolerance is computed. -/// * `computed` - Computed value. This is compared with the actual -/// value, `actual`. -/// * `fixed_tolerance` - Fixed tolerance. -/// * `relative_tolerance` - Relative tolerance. -/// `relative_tolerance`*`actual` gives the total contribution of the -/// relative tolerance. -/// -/// # Returns -/// -/// `true` if the `actual` and `computed` values are within the -/// specified tolerance of one another, and `false` otherwise. -fn tolerance_check( - actual: f32, - computed: f32, - fixed_tolerance: f32, - relative_tolerance: f32, -) -> bool { - (actual - computed).abs() - < max_error(actual, fixed_tolerance, relative_tolerance) -} - -/// Maximum acceptable error from an actual value given fixed and -/// relative tolerances. -/// -/// # Arguments -/// -/// * `actual` - Actual value with respect to which the magnitude of the -/// relative tolerance is computed. -/// * `fixed_tolerance` - Fixed tolerance. -/// * `relative_tolerance` - Relative tolerance. -/// `relative_tolerance`*`actual` gives the total contribution of the -/// relative tolerance. -/// -/// # Returns -/// -/// Maximum acceptable error. -fn max_error( - actual: f32, - fixed_tolerance: f32, - relative_tolerance: f32, -) -> f32 { - relative_tolerance * actual.abs() + fixed_tolerance -} - -/// Total noise amplitude of the input signal after sampling by the -/// ADC. This computes an upper bound of the total noise amplitude, -/// rather than its actual value. -/// -/// # Arguments -/// -/// * `noise_inputs` - Noise sources at the ADC input. -/// * `demodulation_frequency` - Frequency of the demodulation signal -/// (in Hz). -/// * `corner_frequency` - Low-pass filter 3dB corner (cutoff) -/// frequency. -/// -/// # Returns -/// -/// Upper bound of the total amplitude of all noise sources. -fn sampled_noise_amplitude( - noise_inputs: &Vec, - demodulation_frequency: f64, - corner_frequency: f64, -) -> f64 { - // There is not a simple way to compute the amplitude of a - // superpostition of sinusoids with different frequencies and - // phases. Although we can compute the amplitude in special cases - // (e.g., two signals whose periods have a common multiple), these - // do not help us in the general case. However, we can say that - // the total amplitude will not be greater than the sum of the - // amplitudes of the individual noise sources. We treat this as an - // upper bound, and use it as an approximation of the actual - // amplitude. - - let mut noise: f64 = noise_inputs - .iter() - .map(|n| { - // Noise inputs create an oscillation at the output, where the - // oscillation magnitude is determined by the strength of the - // noise and its attenuation (attenuation is determined by its - // proximity to the demodulation frequency and filter - // rolloff). - let octaves = ((n.frequency - demodulation_frequency).abs() - / corner_frequency) - .log2(); - // 2nd-order filter. Approximately 12dB/octave rolloff. - let attenuation = -2. * 20. * 2_f64.log10() * octaves; - linear(n.amplitude_dbfs + attenuation) - }) - .sum(); - - // Add in 1/2 LSB for the maximum amplitude deviation resulting - // from quantization. - noise += 1. / ADC_MAX_COUNT / 2.; - - noise -} - -/// Compute the maximum effect of input noise on the lock-in magnitude -/// computation. -/// -/// The maximum effect of noise on the magnitude computation is given -/// by: -/// -/// | sqrt((I+n*sin(x))**2 + (Q+n*cos(x))**2) - sqrt(I**2 + Q**2) | -/// -/// * I is the in-phase component of the portion of the input signal -/// with the same frequency as the demodulation signal. -/// * Q is the quadrature component. -/// * n is the total noise amplitude (from all contributions, after -/// attenuation from filtering). -/// * x is the phase of the demodulation signal. -/// -/// We need to find the demodulation phase (x) that maximizes this -/// expression. We can ignore the absolute value operation by also -/// considering the expression minimum. The locations of the minimum -/// and maximum can be computed analytically by finding the value of x -/// when the derivative of this expression with respect to x is -/// 0. When we solve this equation, we find: -/// -/// x = atan(I/Q) -/// -/// It's worth noting that this solution is technically only valid -/// when cos(x)!=0 (i.e., x!=pi/2,-pi/2). However, this is not a -/// problem because we only get these values when Q=0. Rust correctly -/// computes atan(inf)=pi/2, which is precisely what we want because -/// x=pi/2 maximizes sin(x) and therefore also the noise effect. -/// -/// The other maximum or minimum is pi radians away from this -/// value. -/// -/// # Arguments -/// -/// * `total_noise_amplitude` - Combined amplitude of all noise -/// sources sampled by the ADC. -/// * `in_phase_actual` - Value of the in-phase component if no noise -/// were present at the ADC input. -/// * `quadrature_actual` - Value of the quadrature component if no -/// noise were present at the ADC input. -/// * `desired_input_amplitude` - Amplitude of the desired input -/// signal. That is, the input signal component with the same -/// frequency as the demodulation signal. -/// -/// # Returns -/// -/// Approximation of the maximum effect on the magnitude computation -/// due to noise sources at the ADC input. -fn magnitude_noise( - total_noise_amplitude: f64, - in_phase_actual: f64, - quadrature_actual: f64, - desired_input_amplitude: f64, -) -> f64 { - // See function documentation for explanation. - let noise = |in_phase_delta: f64, quadrature_delta: f64| -> f64 { - (((in_phase_actual + in_phase_delta).powf(2.) - + (quadrature_actual + quadrature_delta).powf(2.)) - .sqrt() - - desired_input_amplitude) - .abs() - }; - - let phase = (in_phase_actual / quadrature_actual).atan(); - let max_noise_1 = noise( - total_noise_amplitude * phase.sin(), - total_noise_amplitude * phase.cos(), - ); - let max_noise_2 = noise( - total_noise_amplitude * (phase + PI).sin(), - total_noise_amplitude * (phase + PI).cos(), - ); - - max_noise_1.max(max_noise_2) -} - -/// Compute the maximum phase deviation from the correct value due to -/// the input noise sources. -/// -/// The maximum effect of noise on the phase computation is given by: -/// -/// | atan2(Q+n*cos(x), I+n*sin(x)) - atan2(Q, I) | -/// -/// See `magnitude_noise` for an explanation of the terms in this -/// mathematical expression. -/// -/// This expression is harder to compute analytically than the -/// expression in `magnitude_noise`. We could compute it numerically, -/// but that's expensive. However, we can use heuristics to try to -/// guess the values of x that will maximize the noise -/// effect. Intuitively, the difference will be largest when the -/// Y-argument of the atan2 function (Q+n*cos(x)) is pushed in the -/// opposite direction of the noise effect on the X-argument (i.e., -/// cos(x) and sin(x) have different signs). We can use: -/// -/// * sin(x)=+-1 (+- denotes plus or minus), cos(x)=0, -/// * sin(x)=0, cos(x)=+-1, and -/// * the value of x that maximizes |sin(x)-cos(x)| (when -/// sin(x)=1/sqrt(2) and cos(x)=-1/sqrt(2), or when the signs are -/// flipped) -/// -/// The first choice addresses cases in which |I|>>|Q|, the second -/// choice addresses cases in which |Q|>>|I|, and the third choice -/// addresses cases in which |I|~|Q|. We can test all of these cases -/// as an approximation for the real maximum. -/// -/// # Arguments -/// -/// * `total_noise_amplitude` - Total amplitude of all input noise -/// sources. -/// * `in_phase_actual` - Value of the in-phase component if no noise -/// were present at the input. -/// * `quadrature_actual` - Value of the quadrature component if no -/// noise were present at the input. -/// -/// # Returns -/// -/// Approximation of the maximum effect on the phase computation due -/// to noise sources at the ADC input. -fn phase_noise( - total_noise_amplitude: f64, - in_phase_actual: f64, - quadrature_actual: f64, -) -> f64 { - // See function documentation for explanation. - let noise = |in_phase_delta: f64, quadrature_delta: f64| -> f64 { - ((quadrature_actual + quadrature_delta) - .atan2(in_phase_actual + in_phase_delta) - - quadrature_actual.atan2(in_phase_actual)) - .abs() - }; - - let mut max_noise: f64 = 0.; - for (in_phase_delta, quadrature_delta) in [ - ( - total_noise_amplitude / 2_f64.sqrt(), - total_noise_amplitude / -2_f64.sqrt(), - ), - ( - total_noise_amplitude / -2_f64.sqrt(), - total_noise_amplitude / 2_f64.sqrt(), - ), - (total_noise_amplitude, 0.), - (-total_noise_amplitude, 0.), - (0., total_noise_amplitude), - (0., -total_noise_amplitude), - ] - .iter() - { - max_noise = max_noise.max(noise(*in_phase_delta, *quadrature_delta)); - } - - max_noise -} - -/// Lowpass filter test for in-phase/quadrature and magnitude/phase -/// computations. -/// -/// This attempts to "intelligently" model acceptable tolerance ranges -/// for the measured in-phase, quadrature, magnitude and phase results -/// of lock-in processing for a typical low-pass filter -/// application. So, instead of testing whether the lock-in processing -/// extracts the true magnitude and phase (or in-phase and quadrature -/// components) of the input signal, it attempts to calculate what the -/// lock-in processing should compute given any set of input noise -/// sources. For example, if a noise source of sufficient strength -/// differs in frequency by 1kHz from the reference frequency and the -/// filter cutoff frequency is also 1kHz, testing if the lock-in -/// amplifier extracts the amplitude and phase of the input signal -/// whose frequency is equal to the demodulation frequency is doomed -/// to failure. Instead, this function tests whether the lock-in -/// correctly adheres to its actual transfer function, whether or not -/// it was given reasonable inputs. The logic for computing acceptable -/// tolerance ranges is performed in `sampled_noise_amplitude`, -/// `magnitude_noise`, and `phase_noise`. -/// -/// # Arguments -/// -/// * `internal_frequency` - Internal clock frequency (Hz). The -/// internal clock increments timestamp counter values used to -/// record the edges of the external reference. -/// * `adc_frequency` - ADC sampling frequency (in Hz). -/// * `reference_frequency` - External reference frequency (in Hz). -/// * `demodulation_phase_offset` - Phase offset applied to the -/// in-phase and quadrature demodulation signals. -/// * `harmonic` - Scaling factor for the demodulation -/// frequency. E.g., 2 would demodulate with the first harmonic of the -/// reference frequency. -/// * `corner_frequency` - Lowpass filter 3dB cutoff frequency. -/// * `desired_input` - `PureSine` giving the frequency, amplitude and -/// phase of the desired result. -/// * `noise_inputs` - Vector of `PureSine` for any noise inputs on top -/// of `desired_input`. -/// * `time_constant_factor` - Number of time constants after which -/// the output is considered valid. -/// * `tolerance` - Acceptable relative tolerance for the magnitude -/// and angle outputs. The outputs must remain within this tolerance -/// between `time_constant_factor` and `time_constant_factor+1` time -/// constants. -fn lowpass_test( - internal_frequency: f64, - adc_frequency: f64, - reference_frequency: f64, - demodulation_phase_offset: f64, - harmonic: u32, - corner_frequency: f64, - desired_input: PureSine, - noise_inputs: &mut Vec, - time_constant_factor: f64, - tolerance: f32, -) { - let mut lockin = Lockin::new( - demodulation_phase_offset as f32, - (internal_frequency / adc_frequency) as u32, - harmonic, - IIR { - ba: lowpass_iir_coefficients(corner_frequency, adc_frequency), - y_offset: 0., - y_min: -ADC_MAX_COUNT as f32, - y_max: (ADC_MAX_COUNT - 1.) as f32, - }, - ); - - let mut timestamp_start: u64 = 0; - let time_constant: f64 = 1. / (2. * PI * corner_frequency); - let samples = - (time_constant_factor * time_constant * adc_frequency) as usize; - // Ensure the result remains within tolerance for 1 time constant - // after `time_constant_factor` time constants. - let extra_samples = (time_constant * adc_frequency) as usize; - let sample_count: u64 = (internal_frequency / adc_frequency) as u64 - * ADC_SAMPLE_BUFFER_SIZE as u64; - - let effective_phase_offset = - desired_input.phase_offset - demodulation_phase_offset; - let in_phase_actual = - linear(desired_input.amplitude_dbfs) * effective_phase_offset.cos(); - let quadrature_actual = - linear(desired_input.amplitude_dbfs) * effective_phase_offset.sin(); - - let total_noise_amplitude = sampled_noise_amplitude( - noise_inputs, - reference_frequency * harmonic as f64, - corner_frequency, - ); - let total_magnitude_noise = magnitude_noise( - total_noise_amplitude, - in_phase_actual, - quadrature_actual, - linear(desired_input.amplitude_dbfs), - ); - let total_phase_noise = - phase_noise(total_noise_amplitude, in_phase_actual, quadrature_actual); - - let pure_signals = noise_inputs; - pure_signals.push(desired_input); - - for n in 0..(samples + extra_samples) { - let adc_signal: [i16; ADC_SAMPLE_BUFFER_SIZE] = adc_sampled_signal( - &pure_signals, - timestamp_start, - internal_frequency, - adc_frequency, - ); - let mut timestamps_array = [0_u16; ADC_SAMPLE_BUFFER_SIZE / 2]; - let timestamps = adc_batch_timestamps( - reference_frequency, - &mut timestamps_array, - timestamp_start, - timestamp_start + sample_count - 1, - internal_frequency, - ); - - let mut signal: [Complex; ADC_SAMPLE_BUFFER_SIZE]; - match lockin.demodulate(&adc_signal, timestamps) { - Ok(s) => { - signal = s; - } - Err(_) => { - continue; - } - } - - lockin.filter(&mut signal); - let signal_decimated = decimate(signal); - - let mut magnitude_phase_decimated = signal.clone(); - // let mut magnitude_decimated = in_phase_decimated.clone(); - // let mut phase_decimated = quadrature_decimated.clone(); - - magnitude_phase(&mut magnitude_phase_decimated); - - // Ensure stable within tolerance for 1 time constant after - // `time_constant_factor`. - if n >= samples { - for k in 0..DECIMATED_BUFFER_SIZE { - let amplitude_normalized: f32 = - magnitude_phase_decimated[k].0 / ADC_MAX_COUNT as f32; - assert!( - tolerance_check(linear(desired_input.amplitude_dbfs) as f32, amplitude_normalized, total_magnitude_noise as f32, tolerance), - "magnitude actual: {:.4} ({:.2} dBFS), magnitude computed: {:.4} ({:.2} dBFS), tolerance: {:.4}", - linear(desired_input.amplitude_dbfs), - desired_input.amplitude_dbfs, - amplitude_normalized, - dbfs(amplitude_normalized as f64), - max_error(linear(desired_input.amplitude_dbfs) as f32, total_magnitude_noise as f32, tolerance) - ); - assert!( - tolerance_check( - effective_phase_offset as f32, - magnitude_phase_decimated[k].1, - total_phase_noise as f32, - tolerance - ), - "phase actual: {:.4}, phase computed: {:.4}, tolerance: {:.4}", - effective_phase_offset as f32, - magnitude_phase_decimated[k].1, - max_error( - effective_phase_offset as f32, - total_phase_noise as f32, - tolerance - ) - ); - - let in_phase_normalized: f32 = - signal_decimated[k].0 / ADC_MAX_COUNT as f32; - let quadrature_normalized: f32 = - signal_decimated[k].1 / ADC_MAX_COUNT as f32; - assert!( - tolerance_check( - in_phase_actual as f32, - in_phase_normalized, - total_noise_amplitude as f32, - tolerance - ), - "in-phase actual: {:.4}, in-phase computed: {:.3}, tolerance: {:.4}", - in_phase_actual, - in_phase_normalized, - max_error( - in_phase_actual as f32, - total_noise_amplitude as f32, - tolerance - ) - ); - assert!( - tolerance_check( - quadrature_actual as f32, - quadrature_normalized, - total_noise_amplitude as f32, - tolerance - ), - "quadrature actual: {:.4}, quadrature computed: {:.4}, tolerance: {:.4}", - quadrature_actual, - quadrature_normalized, - max_error( - quadrature_actual as f32, - total_noise_amplitude as f32, - tolerance - ) - ); - } - } - - timestamp_start += sample_count; - } -} - -#[test] -fn lowpass() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 100e3; - let harmonic: u32 = 1; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: 0., - }, - &mut vec![ - PureSine { - frequency: 1.1 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.9 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_demodulation_phase_offset_pi_2() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 100e3; - let harmonic: u32 = 1; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = PI / 2.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: 0., - }, - &mut vec![ - PureSine { - frequency: 1.1 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.9 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_phase_offset_pi_2() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 100e3; - let harmonic: u32 = 1; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: PI / 2., - }, - &mut vec![ - PureSine { - frequency: 1.1 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.9 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_fundamental_111e3_phase_offset_pi_4() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 111e3; - let harmonic: u32 = 1; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: PI / 4., - }, - &mut vec![ - PureSine { - frequency: 1.1 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.9 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_first_harmonic() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 50e3; - let harmonic: u32 = 2; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: 0., - }, - &mut vec![ - PureSine { - frequency: 1.2 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.8 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_second_harmonic() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 50e3; - let harmonic: u32 = 3; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: 0., - }, - &mut vec![ - PureSine { - frequency: 1.2 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.8 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_third_harmonic() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 50e3; - let harmonic: u32 = 4; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: 0., - }, - &mut vec![ - PureSine { - frequency: 1.2 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.8 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_first_harmonic_phase_shift() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 50e3; - let harmonic: u32 = 2; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: PI / 4., - }, - &mut vec![ - PureSine { - frequency: 1.2 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.8 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_adc_frequency_1e6() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 1e6; - let signal_frequency: f64 = 100e3; - let harmonic: u32 = 1; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: 0., - }, - &mut vec![ - PureSine { - frequency: 1.2 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.8 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_internal_frequency_125e6() { - let internal_frequency: f64 = 125e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 100e3; - let harmonic: u32 = 1; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: 0., - }, - &mut vec![ - PureSine { - frequency: 1.2 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - PureSine { - frequency: 0.8 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }, - ], - time_constant_factor, - tolerance, - ); -} - -#[test] -fn lowpass_low_signal_frequency() { - let internal_frequency: f64 = 100e6; - let adc_frequency: f64 = 500e3; - let signal_frequency: f64 = 10e3; - let harmonic: u32 = 1; - let corner_frequency: f64 = 1e3; - let demodulation_frequency: f64 = harmonic as f64 * signal_frequency; - let demodulation_phase_offset: f64 = 0.; - let time_constant_factor: f64 = 5.; - let tolerance: f32 = 1e-2; - - lowpass_test( - internal_frequency, - adc_frequency, - signal_frequency, - demodulation_phase_offset, - harmonic, - corner_frequency, - PureSine { - frequency: demodulation_frequency, - amplitude_dbfs: -30., - phase_offset: 0., - }, - &mut vec![PureSine { - frequency: 1.1 * demodulation_frequency, - amplitude_dbfs: -20., - phase_offset: 0., - }], - time_constant_factor, - tolerance, - ); -} From 31d23a3e0c784452343841d13d29e8d90d34f3ad Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Sat, 9 Jan 2021 12:57:13 -0800 Subject: [PATCH 14/34] lock-in: use same method for batch_index branching in both instances --- dsp/src/lockin.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dsp/src/lockin.rs b/dsp/src/lockin.rs index 5b82313..4ab6f02 100644 --- a/dsp/src/lockin.rs +++ b/dsp/src/lockin.rs @@ -165,8 +165,9 @@ impl Lockin { frequency = self.last_frequency.unwrap(); } None => { - self.batch_index += 1; - if self.batch_index == ADC_BATCHES as u32 { + if self.batch_index < ADC_BATCHES as u32 - 1 { + self.batch_index += 1; + } else { self.batch_index = 0; } return Err("insufficient timestamps"); From 891aad3f1751ab70fa2ad50c8ca16abe63f5227f Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Tue, 12 Jan 2021 07:43:28 -0800 Subject: [PATCH 15/34] remove debug_assert in divide_round --- dsp/src/lib.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/dsp/src/lib.rs b/dsp/src/lib.rs index aeec742..1161943 100644 --- a/dsp/src/lib.rs +++ b/dsp/src/lib.rs @@ -25,17 +25,15 @@ pub fn shift_round(x: i32, shift: usize) -> i32 { /// # Arguments /// /// `dividend` - Value to divide. -/// `divisor` - Value that divides the dividend. +/// `divisor` - Value that divides the +/// dividend. `dividend`+`divisor`-1 must be inside [i64::MIN, +/// i64::MAX]. /// /// # Returns /// /// Divided and rounded value. #[inline(always)] pub fn divide_round(dividend: i64, divisor: i64) -> i64 { - debug_assert!( - dividend as i128 + (divisor as i128 - 1) < i64::MAX as i128 - && dividend as i128 + (divisor as i128 - 1) > i64::MIN as i128 - ); (dividend + (divisor - 1)) / divisor } From e14aa8b613f4cc6dd3eeebc394235dabd6e8c354 Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Tue, 12 Jan 2021 10:45:34 -0800 Subject: [PATCH 16/34] move lock-in code to main.rs --- dsp/src/lib.rs | 1 - dsp/src/lockin.rs | 392 ---------------------------------------------- src/main.rs | 130 ++++++++++++--- 3 files changed, 110 insertions(+), 413 deletions(-) delete mode 100644 dsp/src/lockin.rs diff --git a/dsp/src/lib.rs b/dsp/src/lib.rs index 1161943..3f090b5 100644 --- a/dsp/src/lib.rs +++ b/dsp/src/lib.rs @@ -116,7 +116,6 @@ where pub mod iir; pub mod iir_int; -pub mod lockin; pub mod pll; pub mod trig; pub mod unwrap; diff --git a/dsp/src/lockin.rs b/dsp/src/lockin.rs deleted file mode 100644 index 4ab6f02..0000000 --- a/dsp/src/lockin.rs +++ /dev/null @@ -1,392 +0,0 @@ -//! Lock-in amplifier. -//! -//! Lock-in processing is performed through a combination of the -//! following modular processing blocks: demodulation, filtering, -//! decimation and computing the magnitude and phase from a complex -//! signal. These processing blocks are mutually independent. -//! -//! # Terminology -//! -//! * _demodulation signal_ - A copy of the reference signal that is -//! optionally frequency scaled and phase shifted. This is a complex -//! signal. The demodulation signals are used to demodulate the ADC -//! sampled signal. -//! * _internal clock_ - A fast internal clock used to increment a -//! counter for determining the 0-phase points of a reference signal. -//! * _reference signal_ - A constant-frequency signal used to derive -//! the demodulation signal. -//! * _timestamp_ - Timestamps record the timing of the reference -//! signal's 0-phase points. For instance, if a reference signal is -//! provided externally, a fast internal clock increments a -//! counter. When the external reference reaches the 0-phase point -//! (e.g., a positive edge), the value of the counter is recorded as a -//! timestamp. These timestamps are used to determine the frequency -//! and phase of the reference signal. -//! -//! # Usage -//! -//! The first step is to initialize a `Lockin` instance with -//! `Lockin::new()`. This provides the lock-in algorithms with -//! necessary information about the demodulation and filtering steps, -//! such as whether to demodulate with a harmonic of the reference -//! signal and the IIR biquad filter to use. There are then 4 -//! different processing steps that can be used: -//! -//! * `demodulate` - Computes the phase of the demodulation signal -//! corresponding to each ADC sample, uses this phase to compute the -//! demodulation signal, and multiplies this demodulation signal by -//! the ADC-sampled signal. This is a method of `Lockin` since it -//! requires information about how to modify the reference signal for -//! demodulation. -//! * `filter` - Performs IIR biquad filtering of a complex -//! signals. This is commonly performed on the signal provided by the -//! demodulation step, but can be performed at any other point in the -//! processing chain or omitted entirely. `filter` is a method of -//! `Lockin` since it must hold onto the filter configuration and -//! state. -//! * `decimate` - This decimates a signal to reduce the load on the -//! DAC output. It does not require any state information and is -//! therefore a normal function. -//! * `magnitude_phase` - Computes the magnitude and phase of the -//! component of the ADC-sampled signal whose frequency is equal to -//! the demodulation frequency. This does not require any state -//! information and is therefore a normal function. - -use super::iir_int::{IIRState, IIR}; -use super::pll::PLL; -use super::trig::{atan2, cossin}; -use super::{divide_round, Complex}; - -/// TODO these constants are copied from main.rs and should be -/// shared. Additionally, we should probably store the log2 values and -/// compute the actual values from these in main, as is done here. -pub const SAMPLE_BUFFER_SIZE_LOG2: usize = 0; -pub const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; - -pub const ADC_SAMPLE_TICKS_LOG2: usize = 8; -pub const ADC_SAMPLE_TICKS: usize = 1 << ADC_SAMPLE_TICKS_LOG2; - -pub const ADC_BATCHES_LOG2: usize = - 32 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_SAMPLE_TICKS_LOG2; -pub const ADC_BATCHES: usize = 1 << ADC_BATCHES_LOG2; - -pub const DECIMATED_BUFFER_SIZE: usize = 1; - -/// Performs lock-in amplifier processing of a signal. -pub struct Lockin { - harmonic: u32, - phase_offset: u32, - batch_index: u32, - last_phase: Option, - last_frequency: Option, - pll: PLL, - pll_shift_frequency: u8, - pll_shift_phase: u8, - iir: IIR, - iirstate: [IIRState; 2], -} - -impl Lockin { - /// Initialize a new `Lockin` instance. - /// - /// # Arguments - /// - /// * `harmonic` - Integer scaling factor used to adjust the - /// demodulation frequency. E.g., 2 would demodulate with the - /// first harmonic. - /// * `phase_offset` - Phase offset applied to the demodulation - /// signal. - /// * `iir` - IIR biquad filter. - /// * `pll_shift_frequency` - See PLL::update(). - /// * `pll_shift_phase` - See PLL::update(). - /// - /// # Returns - /// - /// New `Lockin` instance. - pub fn new( - harmonic: u32, - phase_offset: u32, - iir: IIR, - pll_shift_frequency: u8, - pll_shift_phase: u8, - ) -> Self { - Lockin { - harmonic, - phase_offset, - batch_index: 0, - last_phase: None, - last_frequency: None, - pll: PLL::default(), - pll_shift_frequency, - pll_shift_phase, - iir, - iirstate: [[0; 5]; 2], - } - } - - /// Demodulate an input signal with the complex reference signal. - /// - /// # Arguments - /// - /// * `adc_samples` - One batch of ADC samples. - /// * `timestamp` - Counter value corresponding to the edges of an - /// external reference signal. The counter is incremented by a - /// fast internal clock. Each ADC sample batch can contain 0 or 1 - /// timestamps. - /// - /// # Returns - /// - /// The demodulated complex signal as a `Result`. When there are - /// an insufficient number of timestamps to perform processing, - /// `Err` is returned. - pub fn demodulate( - &mut self, - adc_samples: &[i16], - timestamp: Option, - ) -> Result<[Complex; SAMPLE_BUFFER_SIZE], &str> { - let frequency: i64; - let phase: i64; - - match timestamp { - Some(t) => { - let res = self.pll.update( - t as i32, - self.pll_shift_frequency, - self.pll_shift_phase, - ); - phase = res.0 as u32 as i64; - frequency = res.1 as u32 as i64; - self.last_phase = Some(phase); - self.last_frequency = Some(frequency); - } - None => match self.last_phase { - Some(t) => { - phase = t; - frequency = self.last_frequency.unwrap(); - } - None => { - if self.batch_index < ADC_BATCHES as u32 - 1 { - self.batch_index += 1; - } else { - self.batch_index = 0; - } - return Err("insufficient timestamps"); - } - }, - } - - let demodulation_frequency = divide_round( - 1 << (64 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_BATCHES_LOG2), - frequency, - ) as u32; - let demodulation_initial_phase = divide_round( - (((self.batch_index as i64) << (32 - ADC_BATCHES_LOG2)) - phase) - << 32, - frequency, - ) as u32; - - let mut demodulation_signal = [(0_i32, 0_i32); SAMPLE_BUFFER_SIZE]; - - demodulation_signal - .iter_mut() - .zip(adc_samples.iter()) - .enumerate() - .for_each(|(i, (s, sample))| { - let sample_phase = (self.harmonic.wrapping_mul( - (demodulation_frequency.wrapping_mul(i as u32)) - .wrapping_add(demodulation_initial_phase), - )) - .wrapping_add(self.phase_offset); - let (cos, sin) = cossin(sample_phase as i32); - // cos/sin take up 32 bits and sample takes up 16 - // bits. Make this fit into a 32 bit result. - s.0 = ((*sample as i64 * cos as i64) >> 16) as i32; - s.1 = ((*sample as i64 * sin as i64) >> 16) as i32; - }); - - if self.batch_index < ADC_BATCHES as u32 - 1 { - self.batch_index += 1; - } else { - self.batch_index = 0; - self.last_phase = Some(self.last_phase.unwrap() - (1 << 32)); - } - - Ok(demodulation_signal) - } - - /// Filter the complex signal using the supplied biquad IIR. The - /// signal array is modified in place. - /// - /// # Arguments - /// - /// * `signal` - Complex signal to filter. - pub fn filter(&mut self, signal: &mut [Complex]) { - signal.iter_mut().for_each(|s| { - s.0 = self.iir.update(&mut self.iirstate[0], s.0); - s.1 = self.iir.update(&mut self.iirstate[1], s.1); - }); - } -} - -/// Decimate the complex signal to `DECIMATED_BUFFER_SIZE`. The ratio -/// of `SAMPLE_BUFFER_SIZE` to `DECIMATED_BUFFER_SIZE` must be a power -/// of 2. -/// -/// # Arguments -/// -/// * `signal` - Complex signal to decimate. -/// -/// # Returns -/// -/// The decimated signal. -pub fn decimate( - signal: [Complex; SAMPLE_BUFFER_SIZE], -) -> [Complex; DECIMATED_BUFFER_SIZE] { - let n_k = SAMPLE_BUFFER_SIZE / DECIMATED_BUFFER_SIZE; - debug_assert!(SAMPLE_BUFFER_SIZE == DECIMATED_BUFFER_SIZE || n_k % 2 == 0); - - let mut signal_decimated = [(0_i32, 0_i32); DECIMATED_BUFFER_SIZE]; - - signal_decimated - .iter_mut() - .zip(signal.iter().step_by(n_k)) - .for_each(|(s_d, s)| { - s_d.0 = s.0; - s_d.1 = s.1; - }); - - signal_decimated -} - -/// Compute the magnitude and phase from the complex signal. The -/// signal array is modified in place. -/// -/// # Arguments -/// -/// * `signal` - Complex signal for which the magnitude and phase -/// should be computed. TODO currently, we compute the square of the -/// magnitude. This should be changed to be the actual magnitude. -pub fn magnitude_phase(signal: &mut [Complex]) { - signal.iter_mut().for_each(|s| { - let new_i = [s.0, s.1].iter().map(|i| i * i).sum(); - let new_q = atan2(s.1, s.0); - s.0 = new_i; - s.1 = new_q; - }); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - /// Ensure that the demodulation signals are within some tolerance - /// band of the target value given the phase and frequency values - /// provided by the PLL. - fn demodulate() { - const PLL_SHIFT_FREQUENCY: u8 = 4; - const PLL_SHIFT_PHASE: u8 = 3; - const HARMONIC: u32 = 1; - const PHASE_OFFSET: u32 = 0; - let mut lockin = Lockin::new( - HARMONIC, - PHASE_OFFSET, - IIR { ba: [0; 5] }, - PLL_SHIFT_FREQUENCY, - PLL_SHIFT_PHASE, - ); - - // Duplicate the PLL outside demodulate so that we don't test - // its behavior. - let mut tracking_pll = PLL::default(); - let mut tracking_phase: i32 = 0; - let mut tracking_frequency: i32 = 0; - - const REFERENCE_FREQUENCY: usize = 10_000; - let mut reference_edge: usize = REFERENCE_FREQUENCY; - - // Ensure that we receive at most 1 timestamp per batch. - debug_assert!( - REFERENCE_FREQUENCY >= SAMPLE_BUFFER_SIZE * ADC_SAMPLE_TICKS - ); - - for batch in 0..100_000 { - let tick: usize = batch * ADC_SAMPLE_TICKS * SAMPLE_BUFFER_SIZE; - let timestamp: Option; - - // When the reference edge occurred during the current - // batch acquisition, register the timestamp and update - // the tracking PLL. - if reference_edge >= tick - && reference_edge < tick + ADC_SAMPLE_TICKS * SAMPLE_BUFFER_SIZE - { - timestamp = Some(reference_edge as u32); - - let tracking_update = tracking_pll.update( - reference_edge as i32, - PLL_SHIFT_FREQUENCY, - PLL_SHIFT_PHASE, - ); - tracking_phase = tracking_update.0; - tracking_frequency = tracking_update.1; - - reference_edge += REFERENCE_FREQUENCY; - } else { - timestamp = None; - } - - let timestamp_before_batch = if tracking_phase > tick as i32 { - // There can be at most 1 reference edge per batch, so - // this will necessarily place the timestamp prior to - // the current batch. - tracking_phase - tracking_frequency - } else { - tracking_phase - }; - - let initial_phase = (((tick as f64 - - timestamp_before_batch as f64) - / tracking_frequency as f64 - * (1_i64 << 32) as f64) - .round() - % u32::MAX as f64) as u32; - let frequency = ((ADC_SAMPLE_TICKS as f64 - / tracking_frequency as f64 - * (1_i64 << 32) as f64) - .round() - % u32::MAX as f64) as u32; - - match lockin.demodulate(&[i16::MAX; SAMPLE_BUFFER_SIZE], timestamp) - { - Ok(v) => { - println!("batch : {}", batch); - for sample in 0..SAMPLE_BUFFER_SIZE { - const TOL: i32 = 50_000; - let cos = v[sample].0; - let sin = v[sample].1; - - let (mut target_cos, mut target_sin) = cossin( - HARMONIC - .wrapping_mul( - (frequency.wrapping_mul(sample as u32)) - .wrapping_add(initial_phase), - ) - .wrapping_add(PHASE_OFFSET) - as i32, - ); - target_cos /= 2; - target_sin /= 2; - - println!("sample : {}", sample); - println!("tol : {}", TOL); - println!("cos, target: {}, {}", cos, target_cos); - println!("sin, target: {}, {}", sin, target_sin); - assert!((cos - target_cos).abs() < TOL); - assert!((sin - target_sin).abs() < TOL); - } - } - Err(_) => {} - } - } - } -} diff --git a/src/main.rs b/src/main.rs index c5a9698..538fb2a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,14 +60,34 @@ use heapless::{consts::*, String}; // The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is // equal to 10ns per tick. // Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz -const ADC_SAMPLE_TICKS: u16 = 256; +const ADC_SAMPLE_TICKS_LOG2: u16 = 8; +const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2; // The desired ADC sample processing buffer size. -const SAMPLE_BUFFER_SIZE: usize = 8; +const SAMPLE_BUFFER_SIZE_LOG2: usize = 3; +const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; + +// The number of ADC batches in one timer overflow period. +pub const ADC_BATCHES_LOG2: usize = + 32 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_SAMPLE_TICKS_LOG2 as usize; +pub const ADC_BATCHES: usize = 1 << ADC_BATCHES_LOG2; // The number of cascaded IIR biquads per channel. Select 1 or 2! const IIR_CASCADE_LENGTH: usize = 1; +// TODO should these be global consts? + +// Frequency scaling factor for lock-in harmonic demodulation. +const HARMONIC: u32 = 1; + +// Phase offset applied to the lock-in demodulation signal. +const PHASE_OFFSET: u32 = 0; + +// The PLL locks to an external reference signal. See `PLL::update()` +// for a description of shift_frequency and shift_phase. +const PLL_SHIFT_FREQUENCY: u8 = 4; +const PLL_SHIFT_PHASE: u8 = 3; + #[link_section = ".sram3.eth"] static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); @@ -84,7 +104,11 @@ mod timers; use adc::{Adc0Input, Adc1Input}; use dac::{Dac0Output, Dac1Output}; -use dsp::iir; +use dsp::{ + divide_round, iir, iir_int, + pll::PLL, + trig::{atan2, cossin}, +}; use pounder::DdsOutput; #[cfg(not(feature = "semihosting"))] @@ -205,6 +229,12 @@ const APP: () = { adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), input_stamper: digital_input_stamper::InputStamper, + pll: PLL, + batch_index: u32, + reference_phase: i64, + reference_frequency: i64, + iir_lockin: iir_int::IIR, + iir_state_lockin: [iir_int::IIRState; 2], eeprom_i2c: hal::i2c::I2c, @@ -927,6 +957,13 @@ const APP: () = { #[cfg(not(feature = "pounder_v1_1"))] let pounder_stamper = None; + let pll = PLL::default(); + let batch_index = 0; + let reference_phase = 0; + let reference_frequency = 0; + let iir_lockin = iir_int::IIR { ba: [0; 5] }; + let iir_state_lockin = [[0; 5]; 2]; + // Start sampling ADCs. sampling_timer.start(); timestamp_timer.start(); @@ -942,6 +979,13 @@ const APP: () = { pounder: pounder_devices, pounder_stamper, + pll, + batch_index, + reference_phase, + reference_frequency, + iir_lockin, + iir_state_lockin, + eeprom_i2c, net_interface: network_interface, eth_mac, @@ -949,7 +993,7 @@ const APP: () = { } } - #[task(binds=DMA1_STR4, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] + #[task(binds=DMA1_STR4, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper, pll, iir_lockin, iir_state_lockin, batch_index, reference_phase, reference_frequency], priority=2)] fn process(c: process::Context) { if let Some(stamper) = c.resources.pounder_stamper { let pounder_timestamps = stamper.acquire_buffer(); @@ -965,24 +1009,71 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; - let _timestamp = c.resources.input_stamper.latest_timestamp(); + let reference_phase = c.resources.reference_phase; + let reference_frequency = c.resources.reference_frequency; - for channel in 0..adc_samples.len() { - for sample in 0..adc_samples[0].len() { - let x = f32::from(adc_samples[channel][sample] as i16); - let mut y = x; - for i in 0..c.resources.iir_state[channel].len() { - y = c.resources.iir_ch[channel][i] - .update(&mut c.resources.iir_state[channel][i], y); - } - // Note(unsafe): The filter limits ensure that the value is in range. - // The truncation introduces 1/2 LSB distortion. - let y = unsafe { y.to_int_unchecked::() }; - // Convert to DAC code - dac_samples[channel][sample] = y as u16 ^ 0x8000; - } + if let Some(t) = c.resources.input_stamper.latest_timestamp() { + let res = c.resources.pll.update( + t as i32, + PLL_SHIFT_FREQUENCY, + PLL_SHIFT_PHASE, + ); + *reference_phase = res.0 as u32 as i64; + *reference_frequency = res.1 as u32 as i64; } + let demodulation_frequency = divide_round( + 1 << (64 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_BATCHES_LOG2), + *reference_frequency, + ) as u32; + let batch_index = c.resources.batch_index; + let demodulation_initial_phase = divide_round( + (((*batch_index as i64) << (32 - ADC_BATCHES_LOG2)) + - *reference_phase) + << 32, + *reference_frequency, + ) as u32; + + if *batch_index < ADC_BATCHES as u32 - 1 { + *batch_index += 1; + } else { + *batch_index = 0; + *reference_phase -= 1 << 32; + } + + let [dac0, dac1] = dac_samples; + let iir_lockin = c.resources.iir_lockin; + let iir_state_lockin = c.resources.iir_state_lockin; + + dac0.iter_mut().zip(dac1.iter_mut()).enumerate().for_each( + |(i, (d0, d1))| { + let sample_phase = (HARMONIC.wrapping_mul( + (demodulation_frequency.wrapping_mul(i as u32)) + .wrapping_add(demodulation_initial_phase), + )) + .wrapping_add(PHASE_OFFSET); + let (cos, sin) = cossin(sample_phase as i32); + + let mut signal = (0_i32, 0_i32); + + signal.0 = ((adc_samples[0][i] as i16 as i64 * cos as i64) + >> 16) as i32; + signal.1 = ((adc_samples[0][i] as i16 as i64 * sin as i64) + >> 16) as i32; + + signal.0 = + iir_lockin.update(&mut iir_state_lockin[0], signal.0); + signal.1 = + iir_lockin.update(&mut iir_state_lockin[1], signal.0); + + let magnitude = signal.0 * signal.0 + signal.1 * signal.1; + let phase = atan2(signal.0, signal.0); + + *d0 = (magnitude >> 16) as i16 as u16; + *d1 = (phase >> 16) as i16 as u16; + }, + ); + if let Some(dds_output) = c.resources.dds_output { let builder = dds_output.builder().update_channels( &[pounder::Channel::Out0.into()], @@ -994,7 +1085,6 @@ const APP: () = { builder.write_profile(); } - let [dac0, dac1] = dac_samples; c.resources.dacs.0.release_buffer(dac0); c.resources.dacs.1.release_buffer(dac1); } From 4c033c0f3e3068fbfe03fd0e5f794cc02ea61c4e Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Tue, 12 Jan 2021 13:04:40 -0800 Subject: [PATCH 17/34] move timestamp handling into new TimestampHandler struct --- src/main.rs | 137 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 53 deletions(-) diff --git a/src/main.rs b/src/main.rs index 538fb2a..1bc6a11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,6 +68,7 @@ const SAMPLE_BUFFER_SIZE_LOG2: usize = 3; const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; // The number of ADC batches in one timer overflow period. +// TODO almost the same as `calculate_timestamp_timer_period`. pub const ADC_BATCHES_LOG2: usize = 32 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_SAMPLE_TICKS_LOG2 as usize; pub const ADC_BATCHES: usize = 1 << ADC_BATCHES_LOG2; @@ -76,18 +77,11 @@ pub const ADC_BATCHES: usize = 1 << ADC_BATCHES_LOG2; const IIR_CASCADE_LENGTH: usize = 1; // TODO should these be global consts? - // Frequency scaling factor for lock-in harmonic demodulation. const HARMONIC: u32 = 1; - // Phase offset applied to the lock-in demodulation signal. const PHASE_OFFSET: u32 = 0; -// The PLL locks to an external reference signal. See `PLL::update()` -// for a description of shift_frequency and shift_phase. -const PLL_SHIFT_FREQUENCY: u8 = 4; -const PLL_SHIFT_PHASE: u8 = 3; - #[link_section = ".sram3.eth"] static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); @@ -169,6 +163,79 @@ type AFE1 = afe::ProgrammableGainAmplifier< hal::gpio::gpiod::PD15>, >; +/// Locks a PLL to an external reference and computes the initial phase value and frequency of the +/// demodulation signal. +pub struct TimestampHandler { + pll: PLL, + pll_shift_frequency: u8, + pll_shift_phase: u8, + // Index of the current ADC batch. + batch_index: u32, + // Most recent phase and frequency values of the external reference. + reference_phase: i64, + reference_frequency: i64, +} + +impl TimestampHandler { + /// Construct a new `TimestampHandler` instance. + /// + /// # Args + /// * `pll_shift_frequency` - See `PLL::update()`. + /// * `pll_shift_phase` - See `PLL::update()`. + /// + /// # Returns + /// New `TimestampHandler` instance. + pub fn new(pll_shift_frequency: u8, pll_shift_phase: u8) -> Self { + TimestampHandler { + pll: PLL::default(), + pll_shift_frequency, + pll_shift_phase, + batch_index: 0, + reference_phase: 0, + reference_frequency: 0, + } + } + + /// Compute the initial phase value and frequency of the demodulation signal. + /// + /// # Args + /// * `timestamp` - Counter value corresponding to an external reference edge. + /// + /// # Returns + /// Tuple consisting of the initial phase value and frequency of the demodulation signal. + pub fn update(&mut self, timestamp: Option) -> (u32, u32) { + if let Some(t) = timestamp { + let (phase, frequency) = self.pll.update( + t as i32, + self.pll_shift_frequency, + self.pll_shift_phase, + ); + self.reference_phase = phase as u32 as i64; + self.reference_frequency = frequency as u32 as i64; + } + + let demodulation_frequency = divide_round( + 1 << (64 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_BATCHES_LOG2), + self.reference_frequency, + ) as u32; + let demodulation_initial_phase = divide_round( + (((self.batch_index as i64) << (32 - ADC_BATCHES_LOG2)) + - self.reference_phase) + << 32, + self.reference_frequency, + ) as u32; + + if self.batch_index < ADC_BATCHES as u32 - 1 { + self.batch_index += 1; + } else { + self.batch_index = 0; + self.reference_phase -= 1 << 32; + } + + (demodulation_initial_phase, demodulation_frequency) + } +} + macro_rules! route_request { ($request:ident, readable_attributes: [$($read_attribute:tt: $getter:tt),*], @@ -229,10 +296,7 @@ const APP: () = { adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), input_stamper: digital_input_stamper::InputStamper, - pll: PLL, - batch_index: u32, - reference_phase: i64, - reference_frequency: i64, + timestamp_handler: TimestampHandler, iir_lockin: iir_int::IIR, iir_state_lockin: [iir_int::IIRState; 2], @@ -957,10 +1021,7 @@ const APP: () = { #[cfg(not(feature = "pounder_v1_1"))] let pounder_stamper = None; - let pll = PLL::default(); - let batch_index = 0; - let reference_phase = 0; - let reference_frequency = 0; + let timestamp_handler = TimestampHandler::new(4, 3); let iir_lockin = iir_int::IIR { ba: [0; 5] }; let iir_state_lockin = [[0; 5]; 2]; @@ -979,10 +1040,7 @@ const APP: () = { pounder: pounder_devices, pounder_stamper, - pll, - batch_index, - reference_phase, - reference_frequency, + timestamp_handler, iir_lockin, iir_state_lockin, @@ -993,7 +1051,7 @@ const APP: () = { } } - #[task(binds=DMA1_STR4, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper, pll, iir_lockin, iir_state_lockin, batch_index, reference_phase, reference_frequency], priority=2)] + #[task(binds=DMA1_STR4, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper, timestamp_handler, iir_lockin, iir_state_lockin], priority=2)] fn process(c: process::Context) { if let Some(stamper) = c.resources.pounder_stamper { let pounder_timestamps = stamper.acquire_buffer(); @@ -1009,37 +1067,10 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; - let reference_phase = c.resources.reference_phase; - let reference_frequency = c.resources.reference_frequency; - - if let Some(t) = c.resources.input_stamper.latest_timestamp() { - let res = c.resources.pll.update( - t as i32, - PLL_SHIFT_FREQUENCY, - PLL_SHIFT_PHASE, - ); - *reference_phase = res.0 as u32 as i64; - *reference_frequency = res.1 as u32 as i64; - } - - let demodulation_frequency = divide_round( - 1 << (64 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_BATCHES_LOG2), - *reference_frequency, - ) as u32; - let batch_index = c.resources.batch_index; - let demodulation_initial_phase = divide_round( - (((*batch_index as i64) << (32 - ADC_BATCHES_LOG2)) - - *reference_phase) - << 32, - *reference_frequency, - ) as u32; - - if *batch_index < ADC_BATCHES as u32 - 1 { - *batch_index += 1; - } else { - *batch_index = 0; - *reference_phase -= 1 << 32; - } + let (demodulation_initial_phase, demodulation_frequency) = c + .resources + .timestamp_handler + .update(c.resources.input_stamper.latest_timestamp()); let [dac0, dac1] = dac_samples; let iir_lockin = c.resources.iir_lockin; @@ -1064,10 +1095,10 @@ const APP: () = { signal.0 = iir_lockin.update(&mut iir_state_lockin[0], signal.0); signal.1 = - iir_lockin.update(&mut iir_state_lockin[1], signal.0); + iir_lockin.update(&mut iir_state_lockin[1], signal.1); let magnitude = signal.0 * signal.0 + signal.1 * signal.1; - let phase = atan2(signal.0, signal.0); + let phase = atan2(signal.1, signal.0); *d0 = (magnitude >> 16) as i16 as u16; *d1 = (phase >> 16) as i16 as u16; From 41ea2ebed4dfe917dcf845b452143cc9c90139c4 Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Tue, 12 Jan 2021 15:50:26 -0800 Subject: [PATCH 18/34] use round up half integer rounding --- dsp/src/lib.rs | 6 ++++++ src/main.rs | 20 +++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/dsp/src/lib.rs b/dsp/src/lib.rs index 3f090b5..2f02601 100644 --- a/dsp/src/lib.rs +++ b/dsp/src/lib.rs @@ -20,6 +20,12 @@ pub fn shift_round(x: i32, shift: usize) -> i32 { (x + (1 << (shift - 1))) >> shift } +/// TODO consolidate with `shift_round`. +#[inline(always)] +pub fn shift_round64(x: i64, shift: usize) -> i64 { + (x + (1 << (shift - 1))) >> shift +} + /// Integer division, round up half. /// /// # Arguments diff --git a/src/main.rs b/src/main.rs index 1bc6a11..3d484d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,6 +101,7 @@ use dac::{Dac0Output, Dac1Output}; use dsp::{ divide_round, iir, iir_int, pll::PLL, + shift_round, shift_round64, trig::{atan2, cossin}, }; use pounder::DdsOutput; @@ -196,7 +197,7 @@ impl TimestampHandler { } } - /// Compute the initial phase value and frequency of the demodulation signal. + /// Compute the initial phase and frequency of the demodulation signal. /// /// # Args /// * `timestamp` - Counter value corresponding to an external reference edge. @@ -1087,10 +1088,15 @@ const APP: () = { let mut signal = (0_i32, 0_i32); - signal.0 = ((adc_samples[0][i] as i16 as i64 * cos as i64) - >> 16) as i32; - signal.1 = ((adc_samples[0][i] as i16 as i64 * sin as i64) - >> 16) as i32; + // TODO should we shift cos/sin first to avoid i64? + signal.0 = shift_round64( + adc_samples[0][i] as i16 as i64 * cos as i64, + 16, + ) as i32; + signal.1 = shift_round64( + adc_samples[0][i] as i16 as i64 * sin as i64, + 16, + ) as i32; signal.0 = iir_lockin.update(&mut iir_state_lockin[0], signal.0); @@ -1100,8 +1106,8 @@ const APP: () = { let magnitude = signal.0 * signal.0 + signal.1 * signal.1; let phase = atan2(signal.1, signal.0); - *d0 = (magnitude >> 16) as i16 as u16; - *d1 = (phase >> 16) as i16 as u16; + *d0 = shift_round(magnitude, 16) as i16 as u16; + *d1 = shift_round(phase, 16) as i16 as u16; }, ); From 80ed715f5a11411a3396e98fab7906c89884938b Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Tue, 12 Jan 2021 16:07:04 -0800 Subject: [PATCH 19/34] shift sin/cos before demodulation product to avoid i64 --- dsp/src/lib.rs | 6 ------ src/main.rs | 16 ++++++---------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/dsp/src/lib.rs b/dsp/src/lib.rs index 2f02601..3f090b5 100644 --- a/dsp/src/lib.rs +++ b/dsp/src/lib.rs @@ -20,12 +20,6 @@ pub fn shift_round(x: i32, shift: usize) -> i32 { (x + (1 << (shift - 1))) >> shift } -/// TODO consolidate with `shift_round`. -#[inline(always)] -pub fn shift_round64(x: i64, shift: usize) -> i64 { - (x + (1 << (shift - 1))) >> shift -} - /// Integer division, round up half. /// /// # Arguments diff --git a/src/main.rs b/src/main.rs index 3d484d8..22fe534 100644 --- a/src/main.rs +++ b/src/main.rs @@ -101,7 +101,7 @@ use dac::{Dac0Output, Dac1Output}; use dsp::{ divide_round, iir, iir_int, pll::PLL, - shift_round, shift_round64, + shift_round, trig::{atan2, cossin}, }; use pounder::DdsOutput; @@ -1088,15 +1088,11 @@ const APP: () = { let mut signal = (0_i32, 0_i32); - // TODO should we shift cos/sin first to avoid i64? - signal.0 = shift_round64( - adc_samples[0][i] as i16 as i64 * cos as i64, - 16, - ) as i32; - signal.1 = shift_round64( - adc_samples[0][i] as i16 as i64 * sin as i64, - 16, - ) as i32; + // shift cos/sin before multiplying to avoid i64 multiplication + signal.0 = + adc_samples[0][i] as i16 as i32 * shift_round(cos, 16); + signal.0 = + adc_samples[0][i] as i16 as i32 * shift_round(sin, 16); signal.0 = iir_lockin.update(&mut iir_state_lockin[0], signal.0); From f974f4099ca22eddb32269857dea88120bfbf101 Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Tue, 12 Jan 2021 16:17:58 -0800 Subject: [PATCH 20/34] remove TODO note relating ADC_BATCHES and calculate_timestamp_timer_period Having both is not really redundant. --- src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 22fe534..be947f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,7 +68,6 @@ const SAMPLE_BUFFER_SIZE_LOG2: usize = 3; const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; // The number of ADC batches in one timer overflow period. -// TODO almost the same as `calculate_timestamp_timer_period`. pub const ADC_BATCHES_LOG2: usize = 32 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_SAMPLE_TICKS_LOG2 as usize; pub const ADC_BATCHES: usize = 1 << ADC_BATCHES_LOG2; From a0d472b398f2cae1e3b7d5a15407ee6916999ceb Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Tue, 12 Jan 2021 16:35:44 -0800 Subject: [PATCH 21/34] use only integer iir --- src/main.rs | 36 +++++++++++++++++++++--------------- src/server.rs | 12 ++++++------ 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/main.rs b/src/main.rs index be947f4..bc439c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,7 +75,6 @@ pub const ADC_BATCHES: usize = 1 << ADC_BATCHES_LOG2; // The number of cascaded IIR biquads per channel. Select 1 or 2! const IIR_CASCADE_LENGTH: usize = 1; -// TODO should these be global consts? // Frequency scaling factor for lock-in harmonic demodulation. const HARMONIC: u32 = 1; // Phase offset applied to the lock-in demodulation signal. @@ -98,7 +97,8 @@ mod timers; use adc::{Adc0Input, Adc1Input}; use dac::{Dac0Output, Dac1Output}; use dsp::{ - divide_round, iir, iir_int, + divide_round, + iir_int::{IIRState, IIR}, pll::PLL, shift_round, trig::{atan2, cossin}, @@ -146,8 +146,6 @@ static mut NET_STORE: NetStorage = NetStorage { routes_storage: [None; 1], }; -const SCALE: f32 = ((1 << 15) - 1) as f32; - // static ETHERNET_PENDING: AtomicBool = AtomicBool::new(true); const TCP_RX_BUFFER_SIZE: usize = 8192; @@ -163,8 +161,8 @@ type AFE1 = afe::ProgrammableGainAmplifier< hal::gpio::gpiod::PD15>, >; -/// Locks a PLL to an external reference and computes the initial phase value and frequency of the -/// demodulation signal. +/// Processes external timestamps to produce the frequency and initial phase of the demodulation +/// signal. pub struct TimestampHandler { pll: PLL, pll_shift_frequency: u8, @@ -297,8 +295,8 @@ const APP: () = { dacs: (Dac0Output, Dac1Output), input_stamper: digital_input_stamper::InputStamper, timestamp_handler: TimestampHandler, - iir_lockin: iir_int::IIR, - iir_state_lockin: [iir_int::IIRState; 2], + iir_lockin: IIR, + iir_state_lockin: [IIRState; 2], eeprom_i2c: hal::i2c::I2c, @@ -320,10 +318,10 @@ const APP: () = { pounder_stamper: Option, // Format: iir_state[ch][cascade-no][coeff] - #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] - iir_state: [[iir::IIRState; IIR_CASCADE_LENGTH]; 2], - #[init([[iir::IIR { ba: [1., 0., 0., 0., 0.], y_offset: 0., y_min: -SCALE - 1., y_max: SCALE }; IIR_CASCADE_LENGTH]; 2])] - iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], + #[init([[[0; 5]; IIR_CASCADE_LENGTH]; 2])] + iir_state: [[IIRState; IIR_CASCADE_LENGTH]; 2], + #[init([[IIR { ba: [1, 0, 0, 0, 0] }; IIR_CASCADE_LENGTH]; 2])] + iir_ch: [[IIR; IIR_CASCADE_LENGTH]; 2], } #[init] @@ -1022,7 +1020,7 @@ const APP: () = { let pounder_stamper = None; let timestamp_handler = TimestampHandler::new(4, 3); - let iir_lockin = iir_int::IIR { ba: [0; 5] }; + let iir_lockin = IIR { ba: [1, 0, 0, 0, 0] }; let iir_state_lockin = [[0; 5]; 2]; // Start sampling ADCs. @@ -1075,6 +1073,8 @@ const APP: () = { let [dac0, dac1] = dac_samples; let iir_lockin = c.resources.iir_lockin; let iir_state_lockin = c.resources.iir_state_lockin; + let iir_ch = c.resources.iir_ch; + let iir_state = c.resources.iir_state; dac0.iter_mut().zip(dac1.iter_mut()).enumerate().for_each( |(i, (d0, d1))| { @@ -1098,8 +1098,14 @@ const APP: () = { signal.1 = iir_lockin.update(&mut iir_state_lockin[1], signal.1); - let magnitude = signal.0 * signal.0 + signal.1 * signal.1; - let phase = atan2(signal.1, signal.0); + let mut magnitude = signal.0 * signal.0 + signal.1 * signal.1; + let mut phase = atan2(signal.1, signal.0); + + for j in 0..iir_state[0].len() { + magnitude = + iir_ch[0][j].update(&mut iir_state[0][j], magnitude); + phase = iir_ch[1][j].update(&mut iir_state[1][j], phase); + } *d0 = shift_round(magnitude, 16) as i16 as u16; *d1 = shift_round(phase, 16) as i16 as u16; diff --git a/src/server.rs b/src/server.rs index 2803805..72e9102 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; use serde_json_core::{de::from_slice, ser::to_string}; -use super::iir; use super::net; +use super::IIR; #[derive(Deserialize, Serialize, Debug)] pub enum AccessRequest { @@ -25,7 +25,7 @@ pub struct Request<'a> { #[derive(Serialize, Deserialize)] pub struct IirRequest { pub channel: u8, - pub iir: iir::IIR, + pub iir: IIR, } #[derive(Serialize)] @@ -137,10 +137,10 @@ impl Response { #[derive(Serialize)] pub struct Status { pub t: u32, - pub x0: f32, - pub y0: f32, - pub x1: f32, - pub y1: f32, + pub x0: i32, + pub y0: i32, + pub x1: i32, + pub y1: i32, } pub fn json_reply(socket: &mut net::socket::TcpSocket, msg: &T) { From 07b7201b49c79fd16f76328c80696ea45b7e7904 Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Tue, 12 Jan 2021 17:26:42 -0800 Subject: [PATCH 22/34] fix cargo fmt style --- src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index bc439c9..19e0b3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1020,7 +1020,9 @@ const APP: () = { let pounder_stamper = None; let timestamp_handler = TimestampHandler::new(4, 3); - let iir_lockin = IIR { ba: [1, 0, 0, 0, 0] }; + let iir_lockin = IIR { + ba: [1, 0, 0, 0, 0], + }; let iir_state_lockin = [[0; 5]; 2]; // Start sampling ADCs. From 6aad92af433e7a3da44ef2e74930c71cc6540b49 Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Tue, 12 Jan 2021 18:36:18 -0800 Subject: [PATCH 23/34] fix bug in which real signal component is assigned twice --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 19e0b3d..347eebe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1092,7 +1092,7 @@ const APP: () = { // shift cos/sin before multiplying to avoid i64 multiplication signal.0 = adc_samples[0][i] as i16 as i32 * shift_round(cos, 16); - signal.0 = + signal.1 = adc_samples[0][i] as i16 as i32 * shift_round(sin, 16); signal.0 = From 76088efda52bfdcb65c3f7514a0fd261eac1447f Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Wed, 13 Jan 2021 08:37:33 -0800 Subject: [PATCH 24/34] dsp: add reciprocal_pll --- dsp/src/lib.rs | 1 + dsp/src/reciprocal_pll.rs | 92 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 73 ------------------------------- 3 files changed, 93 insertions(+), 73 deletions(-) create mode 100644 dsp/src/reciprocal_pll.rs diff --git a/dsp/src/lib.rs b/dsp/src/lib.rs index 3f090b5..77d8bc1 100644 --- a/dsp/src/lib.rs +++ b/dsp/src/lib.rs @@ -117,6 +117,7 @@ where pub mod iir; pub mod iir_int; pub mod pll; +pub mod reciprocal_pll; pub mod trig; pub mod unwrap; diff --git a/dsp/src/reciprocal_pll.rs b/dsp/src/reciprocal_pll.rs new file mode 100644 index 0000000..49a135b --- /dev/null +++ b/dsp/src/reciprocal_pll.rs @@ -0,0 +1,92 @@ +use super::{divide_round, pll::PLL}; + +/// Processes external timestamps to produce the frequency and initial phase of the demodulation +/// signal. +pub struct TimestampHandler { + pll: PLL, + pll_shift_frequency: u8, + pll_shift_phase: u8, + // Index of the current ADC batch. + batch_index: u32, + // Most recent phase and frequency values of the external reference. + reference_phase: i64, + reference_frequency: i64, + adc_sample_ticks_log2: usize, + sample_buffer_size_log2: usize, +} + +impl TimestampHandler { + /// Construct a new `TimestampHandler` instance. + /// + /// # Args + /// * `pll_shift_frequency` - See `PLL::update()`. + /// * `pll_shift_phase` - See `PLL::update()`. + /// * `adc_sample_ticks_log2` - Number of ticks in one ADC sampling timer period. + /// * `sample_buffer_size_log2` - Number of ADC samples in one processing batch. + /// + /// # Returns + /// New `TimestampHandler` instance. + pub fn new( + pll_shift_frequency: u8, + pll_shift_phase: u8, + adc_sample_ticks_log2: usize, + sample_buffer_size_log2: usize, + ) -> Self { + TimestampHandler { + pll: PLL::default(), + pll_shift_frequency, + pll_shift_phase, + batch_index: 0, + reference_phase: 0, + reference_frequency: 0, + adc_sample_ticks_log2, + sample_buffer_size_log2, + } + } + + /// Compute the initial phase and frequency of the demodulation signal. + /// + /// # Args + /// * `timestamp` - Counter value corresponding to an external reference edge. + /// + /// # Returns + /// Tuple consisting of the initial phase value and frequency of the demodulation signal. + pub fn update(&mut self, timestamp: Option) -> (u32, u32) { + if let Some(t) = timestamp { + let (phase, frequency) = self.pll.update( + t as i32, + self.pll_shift_frequency, + self.pll_shift_phase, + ); + self.reference_phase = phase as u32 as i64; + self.reference_frequency = frequency as u32 as i64; + } + + let demodulation_frequency = divide_round( + 1 << (32 + self.adc_sample_ticks_log2), + self.reference_frequency, + ) as u32; + let demodulation_initial_phase = divide_round( + (((self.batch_index as i64) + << (self.adc_sample_ticks_log2 + + self.sample_buffer_size_log2)) + - self.reference_phase) + << 32, + self.reference_frequency, + ) as u32; + + if self.batch_index + < (1 << (32 + - self.adc_sample_ticks_log2 + - self.sample_buffer_size_log2)) as u32 + - 1 + { + self.batch_index += 1; + } else { + self.batch_index = 0; + self.reference_phase -= 1 << 32; + } + + (demodulation_initial_phase, demodulation_frequency) + } +} diff --git a/src/main.rs b/src/main.rs index 347eebe..6de262c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -161,79 +161,6 @@ type AFE1 = afe::ProgrammableGainAmplifier< hal::gpio::gpiod::PD15>, >; -/// Processes external timestamps to produce the frequency and initial phase of the demodulation -/// signal. -pub struct TimestampHandler { - pll: PLL, - pll_shift_frequency: u8, - pll_shift_phase: u8, - // Index of the current ADC batch. - batch_index: u32, - // Most recent phase and frequency values of the external reference. - reference_phase: i64, - reference_frequency: i64, -} - -impl TimestampHandler { - /// Construct a new `TimestampHandler` instance. - /// - /// # Args - /// * `pll_shift_frequency` - See `PLL::update()`. - /// * `pll_shift_phase` - See `PLL::update()`. - /// - /// # Returns - /// New `TimestampHandler` instance. - pub fn new(pll_shift_frequency: u8, pll_shift_phase: u8) -> Self { - TimestampHandler { - pll: PLL::default(), - pll_shift_frequency, - pll_shift_phase, - batch_index: 0, - reference_phase: 0, - reference_frequency: 0, - } - } - - /// Compute the initial phase and frequency of the demodulation signal. - /// - /// # Args - /// * `timestamp` - Counter value corresponding to an external reference edge. - /// - /// # Returns - /// Tuple consisting of the initial phase value and frequency of the demodulation signal. - pub fn update(&mut self, timestamp: Option) -> (u32, u32) { - if let Some(t) = timestamp { - let (phase, frequency) = self.pll.update( - t as i32, - self.pll_shift_frequency, - self.pll_shift_phase, - ); - self.reference_phase = phase as u32 as i64; - self.reference_frequency = frequency as u32 as i64; - } - - let demodulation_frequency = divide_round( - 1 << (64 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_BATCHES_LOG2), - self.reference_frequency, - ) as u32; - let demodulation_initial_phase = divide_round( - (((self.batch_index as i64) << (32 - ADC_BATCHES_LOG2)) - - self.reference_phase) - << 32, - self.reference_frequency, - ) as u32; - - if self.batch_index < ADC_BATCHES as u32 - 1 { - self.batch_index += 1; - } else { - self.batch_index = 0; - self.reference_phase -= 1 << 32; - } - - (demodulation_initial_phase, demodulation_frequency) - } -} - macro_rules! route_request { ($request:ident, readable_attributes: [$($read_attribute:tt: $getter:tt),*], From e599977983dd097f9854c4f553a67ba3b55c207a Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Wed, 13 Jan 2021 08:53:06 -0800 Subject: [PATCH 25/34] revert changes in main.rs and server.rs --- src/main.rs | 109 ++++++++++++-------------------------------------- src/server.rs | 12 +++--- 2 files changed, 32 insertions(+), 89 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6de262c..c5a9698 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,26 +60,14 @@ use heapless::{consts::*, String}; // The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is // equal to 10ns per tick. // Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz -const ADC_SAMPLE_TICKS_LOG2: u16 = 8; -const ADC_SAMPLE_TICKS: u16 = 1 << ADC_SAMPLE_TICKS_LOG2; +const ADC_SAMPLE_TICKS: u16 = 256; // The desired ADC sample processing buffer size. -const SAMPLE_BUFFER_SIZE_LOG2: usize = 3; -const SAMPLE_BUFFER_SIZE: usize = 1 << SAMPLE_BUFFER_SIZE_LOG2; - -// The number of ADC batches in one timer overflow period. -pub const ADC_BATCHES_LOG2: usize = - 32 - SAMPLE_BUFFER_SIZE_LOG2 - ADC_SAMPLE_TICKS_LOG2 as usize; -pub const ADC_BATCHES: usize = 1 << ADC_BATCHES_LOG2; +const SAMPLE_BUFFER_SIZE: usize = 8; // The number of cascaded IIR biquads per channel. Select 1 or 2! const IIR_CASCADE_LENGTH: usize = 1; -// Frequency scaling factor for lock-in harmonic demodulation. -const HARMONIC: u32 = 1; -// Phase offset applied to the lock-in demodulation signal. -const PHASE_OFFSET: u32 = 0; - #[link_section = ".sram3.eth"] static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); @@ -96,13 +84,7 @@ mod timers; use adc::{Adc0Input, Adc1Input}; use dac::{Dac0Output, Dac1Output}; -use dsp::{ - divide_round, - iir_int::{IIRState, IIR}, - pll::PLL, - shift_round, - trig::{atan2, cossin}, -}; +use dsp::iir; use pounder::DdsOutput; #[cfg(not(feature = "semihosting"))] @@ -146,6 +128,8 @@ static mut NET_STORE: NetStorage = NetStorage { routes_storage: [None; 1], }; +const SCALE: f32 = ((1 << 15) - 1) as f32; + // static ETHERNET_PENDING: AtomicBool = AtomicBool::new(true); const TCP_RX_BUFFER_SIZE: usize = 8192; @@ -221,9 +205,6 @@ const APP: () = { adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), input_stamper: digital_input_stamper::InputStamper, - timestamp_handler: TimestampHandler, - iir_lockin: IIR, - iir_state_lockin: [IIRState; 2], eeprom_i2c: hal::i2c::I2c, @@ -245,10 +226,10 @@ const APP: () = { pounder_stamper: Option, // Format: iir_state[ch][cascade-no][coeff] - #[init([[[0; 5]; IIR_CASCADE_LENGTH]; 2])] - iir_state: [[IIRState; IIR_CASCADE_LENGTH]; 2], - #[init([[IIR { ba: [1, 0, 0, 0, 0] }; IIR_CASCADE_LENGTH]; 2])] - iir_ch: [[IIR; IIR_CASCADE_LENGTH]; 2], + #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] + iir_state: [[iir::IIRState; IIR_CASCADE_LENGTH]; 2], + #[init([[iir::IIR { ba: [1., 0., 0., 0., 0.], y_offset: 0., y_min: -SCALE - 1., y_max: SCALE }; IIR_CASCADE_LENGTH]; 2])] + iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], } #[init] @@ -946,12 +927,6 @@ const APP: () = { #[cfg(not(feature = "pounder_v1_1"))] let pounder_stamper = None; - let timestamp_handler = TimestampHandler::new(4, 3); - let iir_lockin = IIR { - ba: [1, 0, 0, 0, 0], - }; - let iir_state_lockin = [[0; 5]; 2]; - // Start sampling ADCs. sampling_timer.start(); timestamp_timer.start(); @@ -967,10 +942,6 @@ const APP: () = { pounder: pounder_devices, pounder_stamper, - timestamp_handler, - iir_lockin, - iir_state_lockin, - eeprom_i2c, net_interface: network_interface, eth_mac, @@ -978,7 +949,7 @@ const APP: () = { } } - #[task(binds=DMA1_STR4, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper, timestamp_handler, iir_lockin, iir_state_lockin], priority=2)] + #[task(binds=DMA1_STR4, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] fn process(c: process::Context) { if let Some(stamper) = c.resources.pounder_stamper { let pounder_timestamps = stamper.acquire_buffer(); @@ -994,52 +965,23 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; - let (demodulation_initial_phase, demodulation_frequency) = c - .resources - .timestamp_handler - .update(c.resources.input_stamper.latest_timestamp()); + let _timestamp = c.resources.input_stamper.latest_timestamp(); - let [dac0, dac1] = dac_samples; - let iir_lockin = c.resources.iir_lockin; - let iir_state_lockin = c.resources.iir_state_lockin; - let iir_ch = c.resources.iir_ch; - let iir_state = c.resources.iir_state; - - dac0.iter_mut().zip(dac1.iter_mut()).enumerate().for_each( - |(i, (d0, d1))| { - let sample_phase = (HARMONIC.wrapping_mul( - (demodulation_frequency.wrapping_mul(i as u32)) - .wrapping_add(demodulation_initial_phase), - )) - .wrapping_add(PHASE_OFFSET); - let (cos, sin) = cossin(sample_phase as i32); - - let mut signal = (0_i32, 0_i32); - - // shift cos/sin before multiplying to avoid i64 multiplication - signal.0 = - adc_samples[0][i] as i16 as i32 * shift_round(cos, 16); - signal.1 = - adc_samples[0][i] as i16 as i32 * shift_round(sin, 16); - - signal.0 = - iir_lockin.update(&mut iir_state_lockin[0], signal.0); - signal.1 = - iir_lockin.update(&mut iir_state_lockin[1], signal.1); - - let mut magnitude = signal.0 * signal.0 + signal.1 * signal.1; - let mut phase = atan2(signal.1, signal.0); - - for j in 0..iir_state[0].len() { - magnitude = - iir_ch[0][j].update(&mut iir_state[0][j], magnitude); - phase = iir_ch[1][j].update(&mut iir_state[1][j], phase); + for channel in 0..adc_samples.len() { + for sample in 0..adc_samples[0].len() { + let x = f32::from(adc_samples[channel][sample] as i16); + let mut y = x; + for i in 0..c.resources.iir_state[channel].len() { + y = c.resources.iir_ch[channel][i] + .update(&mut c.resources.iir_state[channel][i], y); } - - *d0 = shift_round(magnitude, 16) as i16 as u16; - *d1 = shift_round(phase, 16) as i16 as u16; - }, - ); + // Note(unsafe): The filter limits ensure that the value is in range. + // The truncation introduces 1/2 LSB distortion. + let y = unsafe { y.to_int_unchecked::() }; + // Convert to DAC code + dac_samples[channel][sample] = y as u16 ^ 0x8000; + } + } if let Some(dds_output) = c.resources.dds_output { let builder = dds_output.builder().update_channels( @@ -1052,6 +994,7 @@ const APP: () = { builder.write_profile(); } + let [dac0, dac1] = dac_samples; c.resources.dacs.0.release_buffer(dac0); c.resources.dacs.1.release_buffer(dac1); } diff --git a/src/server.rs b/src/server.rs index 72e9102..2803805 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; use serde_json_core::{de::from_slice, ser::to_string}; +use super::iir; use super::net; -use super::IIR; #[derive(Deserialize, Serialize, Debug)] pub enum AccessRequest { @@ -25,7 +25,7 @@ pub struct Request<'a> { #[derive(Serialize, Deserialize)] pub struct IirRequest { pub channel: u8, - pub iir: IIR, + pub iir: iir::IIR, } #[derive(Serialize)] @@ -137,10 +137,10 @@ impl Response { #[derive(Serialize)] pub struct Status { pub t: u32, - pub x0: i32, - pub y0: i32, - pub x1: i32, - pub y1: i32, + pub x0: f32, + pub y0: f32, + pub x1: f32, + pub y1: f32, } pub fn json_reply(socket: &mut net::socket::TcpSocket, msg: &T) { From 9697560404ad32a3cffdda22db75d2313afd702c Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Wed, 13 Jan 2021 09:08:16 -0800 Subject: [PATCH 26/34] reciprocal_pll: remove unneeded type cast --- dsp/src/reciprocal_pll.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dsp/src/reciprocal_pll.rs b/dsp/src/reciprocal_pll.rs index 49a135b..eaeaac7 100644 --- a/dsp/src/reciprocal_pll.rs +++ b/dsp/src/reciprocal_pll.rs @@ -78,7 +78,7 @@ impl TimestampHandler { if self.batch_index < (1 << (32 - self.adc_sample_ticks_log2 - - self.sample_buffer_size_log2)) as u32 + - self.sample_buffer_size_log2)) - 1 { self.batch_index += 1; From 7a2f950667ef9de53a114958fb0749990dac91e1 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 18 Jan 2021 13:41:23 +0100 Subject: [PATCH 27/34] Updating timer compare offsets --- src/adc.rs | 2 +- src/dac.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adc.rs b/src/adc.rs index 76a42a4..48792d9 100644 --- a/src/adc.rs +++ b/src/adc.rs @@ -246,7 +246,7 @@ macro_rules! adc_input { // Generate DMA events when an output compare of the timer hits the specified // value. trigger_channel.listen_dma(); - trigger_channel.to_output_compare(2); + trigger_channel.to_output_compare(2 + $index); // The trigger stream constantly writes to the SPI CR1 using a static word // (which is a static value to enable the SPI transfer). Thus, neither the diff --git a/src/dac.rs b/src/dac.rs index 971a7de..a22e5da 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -14,7 +14,7 @@ ///! the DAC without requiring the CPU. ///! ///! In order to ensure alignment between the ADC sample batches and DAC output code batches, a DAC -///! output batch is always exactly 2 batches after the ADC batch that generated it. +///! output batch is always exactly 3 batches after the ADC batch that generated it. ///! ///! The DMA transfer for the DAC output codes utilizes a double-buffer mode to avoid losing any ///! transfer events generated by the timer (for example, when 2 update cycles occur before the DMA @@ -134,7 +134,7 @@ macro_rules! dac_output { // 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); + trigger_channel.to_output_compare(4 + $index); // The stream constantly writes to the TX FIFO to write new update codes. let trigger_config = DmaConfig::default() From 20535a721d10968e255ee6f39d103d3f6333a8a9 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 18 Jan 2021 16:47:47 +0100 Subject: [PATCH 28/34] Refactoring to support multiple apps --- .github/workflows/ci.yml | 1 - src/{ => hardware}/adc.rs | 55 +- src/{ => hardware}/afe.rs | 0 src/hardware/configuration.rs | 819 +++++++++++++++++ src/{ => hardware}/dac.rs | 20 +- src/{ => hardware}/design_parameters.rs | 6 +- src/{ => hardware}/digital_input_stamper.rs | 5 +- src/{ => hardware}/eeprom.rs | 1 - src/hardware/mod.rs | 63 ++ src/{ => hardware}/pounder/attenuators.rs | 0 src/{ => hardware}/pounder/dds_output.rs | 6 +- src/{ => hardware/pounder}/hrtimer.rs | 7 +- src/{ => hardware}/pounder/mod.rs | 5 +- src/{ => hardware}/pounder/rf_power.rs | 0 src/{ => hardware}/pounder/timestamp.rs | 12 +- src/{ => hardware}/timers.rs | 2 + src/main.rs | 969 +------------------- src/server.rs | 56 +- 18 files changed, 1045 insertions(+), 982 deletions(-) rename src/{ => hardware}/adc.rs (95%) rename src/{ => hardware}/afe.rs (100%) create mode 100644 src/hardware/configuration.rs rename src/{ => hardware}/dac.rs (96%) rename src/{ => hardware}/design_parameters.rs (92%) rename src/{ => hardware}/digital_input_stamper.rs (97%) rename src/{ => hardware}/eeprom.rs (93%) create mode 100644 src/hardware/mod.rs rename src/{ => hardware}/pounder/attenuators.rs (100%) rename src/{ => hardware}/pounder/dds_output.rs (98%) rename src/{ => hardware/pounder}/hrtimer.rs (97%) rename src/{ => hardware}/pounder/mod.rs (99%) rename src/{ => hardware}/pounder/rf_power.rs (100%) rename src/{ => hardware}/pounder/timestamp.rs (95%) rename src/{ => hardware}/timers.rs (99%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae27f70..f4c6212 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,6 @@ jobs: command: fmt args: --all -- --check - uses: actions-rs/clippy-check@v1 - continue-on-error: true with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/adc.rs b/src/hardware/adc.rs similarity index 95% rename from src/adc.rs rename to src/hardware/adc.rs index 48792d9..e3b8e56 100644 --- a/src/adc.rs +++ b/src/hardware/adc.rs @@ -1,3 +1,10 @@ +use super::timers; +use hal::dma::{ + config::Priority, + dma::{DMAReq, DmaConfig}, + traits::TargetAddress, + MemoryToPeripheral, PeripheralToMemory, Transfer, +}; ///! Stabilizer ADC management interface ///! ///! # Design @@ -72,10 +79,9 @@ ///! be used as a means to both detect and buffer ADC samples during the buffer swap-over. Because ///! of this, double-buffered mode does not offer any advantages over single-buffered mode (unless ///! double-buffered mode offers less overhead due to the DMA disable/enable procedure). -use super::{ - hal, timers, DMAReq, DmaConfig, MemoryToPeripheral, PeripheralToMemory, - Priority, TargetAddress, Transfer, SAMPLE_BUFFER_SIZE, -}; +use stm32h7xx_hal as hal; + +use crate::SAMPLE_BUFFER_SIZE; // The following data is written by the timer ADC sample trigger into the SPI CR1 to start the // transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is @@ -174,13 +180,13 @@ macro_rules! adc_input { PeripheralToMemory, &'static mut [u16; SAMPLE_BUFFER_SIZE], >, - _trigger_transfer: Transfer< + trigger_transfer: Transfer< hal::dma::dma::$trigger_stream, [< $spi CR >], MemoryToPeripheral, &'static mut [u32; 1], >, - _flag_clear_transfer: Transfer< + clear_transfer: Transfer< hal::dma::dma::$clear_stream, [< $spi IFCR >], MemoryToPeripheral, @@ -227,7 +233,7 @@ macro_rules! adc_input { clear_channel.listen_dma(); clear_channel.to_output_compare(0); - let mut clear_transfer: Transfer< + let clear_transfer: Transfer< _, _, MemoryToPeripheral, @@ -264,7 +270,7 @@ macro_rules! adc_input { }; // Construct the trigger stream to write from memory to the peripheral. - let mut trigger_transfer: Transfer< + let trigger_transfer: Transfer< _, _, MemoryToPeripheral, @@ -299,7 +305,7 @@ macro_rules! adc_input { // The data transfer is always a transfer of data from the peripheral to a RAM // buffer. - let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> = + let data_transfer: Transfer<_, _, PeripheralToMemory, _> = Transfer::init( data_stream, spi, @@ -310,29 +316,30 @@ macro_rules! adc_input { data_config, ); - data_transfer.start(|spi| { - // Allow the SPI RX FIFO to generate DMA transfer requests when data is - // available. - spi.enable_dma_rx(); - - // Each transaction is 1 word (16 bytes). - spi.inner().cr2.modify(|_, w| w.tsize().bits(1)); - spi.inner().cr1.modify(|_, w| w.spe().set_bit()); - }); - - clear_transfer.start(|_| {}); - trigger_transfer.start(|_| {}); - Self { // Note(unsafe): The ADC_BUF[$index][1] is "owned" by this peripheral. It // shall not be used anywhere else in the module. next_buffer: unsafe { Some(&mut ADC_BUF[$index][1]) }, transfer: data_transfer, - _trigger_transfer: trigger_transfer, - _flag_clear_transfer: clear_transfer, + trigger_transfer, + clear_transfer, } } + /// Enable the ADC DMA transfer sequence. + pub fn start(&mut self) { + self.transfer.start(|spi| { + spi.enable_dma_rx(); + + spi.inner().cr2.modify(|_, w| w.tsize().bits(1)); + spi.inner().cr1.modify(|_, w| w.spe().set_bit()); + }); + + self.clear_transfer.start(|_| {}); + self.trigger_transfer.start(|_| {}); + + } + /// Obtain a buffer filled with ADC samples. /// /// # Returns diff --git a/src/afe.rs b/src/hardware/afe.rs similarity index 100% rename from src/afe.rs rename to src/hardware/afe.rs diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs new file mode 100644 index 0000000..2c11045 --- /dev/null +++ b/src/hardware/configuration.rs @@ -0,0 +1,819 @@ +///! Stabilizer hardware configuration +///! +///! This file contains all of the hardware-specific configuration of Stabilizer. +use crate::ADC_SAMPLE_TICKS; + +#[cfg(feature = "pounder_v1_1")] +use crate::SAMPLE_BUFFER_SIZE; + +use smoltcp::{iface::Routes, wire::Ipv4Address}; + +use stm32h7xx_hal::{ + self as hal, + ethernet::{self, PHY}, + prelude::*, +}; + +use embedded_hal::digital::v2::{InputPin, OutputPin}; + +use super::{ + adc, afe, dac, design_parameters, digital_input_stamper, eeprom, pounder, + timers, DdsOutput, Ethernet, AFE0, AFE1, +}; + +// Network storage definition for the ethernet interface. +struct NetStorage { + ip_addrs: [smoltcp::wire::IpCidr; 1], + neighbor_cache: + [Option<(smoltcp::wire::IpAddress, smoltcp::iface::Neighbor)>; 8], + routes_storage: [Option<(smoltcp::wire::IpCidr, smoltcp::iface::Route)>; 1], +} + +/// The available networking devices on Stabilizer. +pub struct NetworkDevices { + pub interface: Ethernet, + pub phy: ethernet::phy::LAN8742A, +} + +/// The available hardware interfaces on Stabilizer. +pub struct StabilizerDevices { + pub afes: (AFE0, AFE1), + pub adcs: (adc::Adc0Input, adc::Adc1Input), + pub dacs: (dac::Dac0Output, dac::Dac1Output), + pub timestamper: digital_input_stamper::InputStamper, + pub adc_dac_timer: timers::SamplingTimer, + pub timestamp_timer: timers::TimestampTimer, + pub net: NetworkDevices, +} + +/// The available Pounder-specific hardware interfaces. +pub struct PounderDevices { + pub pounder: pounder::PounderDevices, + pub dds_output: DdsOutput, + + #[cfg(feature = "pounder_v1_1")] + pub timestamper: pounder::timestamp::Timestamper, +} + +#[link_section = ".sram3.eth"] +/// Static storage for the ethernet DMA descriptor ring. +static mut DES_RING: ethernet::DesRing = ethernet::DesRing::new(); + +/// Static, global-scope network storage for the ethernet interface. +/// +/// This is a static singleton so that the network storage can be referenced from all contexts. +static mut NET_STORE: NetStorage = NetStorage { + // Placeholder for the real IP address, which is initialized at runtime. + ip_addrs: [smoltcp::wire::IpCidr::Ipv6( + smoltcp::wire::Ipv6Cidr::SOLICITED_NODE_PREFIX, + )], + neighbor_cache: [None; 8], + routes_storage: [None; 1], +}; + +/// Configure the stabilizer hardware for operation. +/// +/// # Args +/// * `core` - The RTIC core for configuring the cortex-M core of the device. +/// * `device` - The microcontroller peripherals to be configured. +/// +/// # Returns +/// (stabilizer, pounder) where `stabilizer` is a `StabilizerDevices` structure containing all +/// stabilizer hardware interfaces in a disabled state. `pounder` is an `Option` containing +/// `Some(devices)` if pounder is detected, where `devices` is a `PounderDevices` structure +/// containing all of the pounder hardware interfaces in a disabled state. +pub fn setup( + mut core: rtic::export::Peripherals, + device: stm32h7xx_hal::stm32::Peripherals, +) -> (StabilizerDevices, Option) { + let pwr = device.PWR.constrain(); + let vos = pwr.freeze(); + + // Enable SRAM3 for the ethernet descriptor ring. + device.RCC.ahb2enr.modify(|_, w| w.sram3en().set_bit()); + + // Clear reset flags. + device.RCC.rsr.write(|w| w.rmvf().set_bit()); + + // Select the PLLs for SPI. + device + .RCC + .d2ccip1r + .modify(|_, w| w.spi123sel().pll2_p().spi45sel().pll2_q()); + + let rcc = device.RCC.constrain(); + let ccdr = rcc + .use_hse(8.mhz()) + .sysclk(400.mhz()) + .hclk(200.mhz()) + .per_ck(100.mhz()) + .pll2_p_ck(100.mhz()) + .pll2_q_ck(100.mhz()) + .freeze(vos, &device.SYSCFG); + + #[cfg(feature = "semihosting")] + { + use cortex_m_log::log::{init as init_log, Logger}; + use cortex_m_log::printer::semihosting::{hio::HStdout, InterruptOk}; + use log::LevelFilter; + static mut LOGGER: Option>> = None; + let logger = Logger { + inner: InterruptOk::<_>::stdout().unwrap(), + level: LevelFilter::Info, + }; + let logger = unsafe { LOGGER.get_or_insert(logger) }; + + init_log(logger).unwrap(); + } + + let mut delay = hal::delay::Delay::new(core.SYST, ccdr.clocks); + + let gpioa = device.GPIOA.split(ccdr.peripheral.GPIOA); + let gpiob = device.GPIOB.split(ccdr.peripheral.GPIOB); + let gpioc = device.GPIOC.split(ccdr.peripheral.GPIOC); + let gpiod = device.GPIOD.split(ccdr.peripheral.GPIOD); + let gpioe = device.GPIOE.split(ccdr.peripheral.GPIOE); + let gpiof = device.GPIOF.split(ccdr.peripheral.GPIOF); + let mut gpiog = device.GPIOG.split(ccdr.peripheral.GPIOG); + + let dma_streams = + hal::dma::dma::StreamsTuple::new(device.DMA1, ccdr.peripheral.DMA1); + + // Configure timer 2 to trigger conversions for the ADC + let mut sampling_timer = { + // The timer frequency is manually adjusted below, so the 1KHz setting here is a + // dont-care. + let mut timer2 = + device + .TIM2 + .timer(1.khz(), ccdr.peripheral.TIM2, &ccdr.clocks); + + // Configure the timer to count at the designed tick rate. We will manually set the + // period below. + timer2.pause(); + timer2.reset_counter(); + timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY); + + let mut sampling_timer = timers::SamplingTimer::new(timer2); + sampling_timer.set_period_ticks((ADC_SAMPLE_TICKS - 1) as u32); + + // The sampling timer is used as the master timer for the shadow-sampling timer. Thus, + // it generates a trigger whenever it is enabled. + + sampling_timer + }; + + let mut shadow_sampling_timer = { + // The timer frequency is manually adjusted below, so the 1KHz setting here is a + // dont-care. + let mut timer3 = + device + .TIM3 + .timer(1.khz(), ccdr.peripheral.TIM3, &ccdr.clocks); + + // Configure the timer to count at the designed tick rate. We will manually set the + // period below. + timer3.pause(); + timer3.reset_counter(); + timer3.set_tick_freq(design_parameters::TIMER_FREQUENCY); + + let mut shadow_sampling_timer = + timers::ShadowSamplingTimer::new(timer3); + shadow_sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1); + + // The shadow sampling timer is a slave-mode timer to the sampling timer. It should + // always be in-sync - thus, we configure it to operate in slave mode using "Trigger + // mode". + // For TIM3, TIM2 can be made the internal trigger connection using ITR1. Thus, the + // SamplingTimer start now gates the start of the ShadowSamplingTimer. + shadow_sampling_timer.set_slave_mode( + timers::TriggerSource::Trigger1, + timers::SlaveMode::Trigger, + ); + + shadow_sampling_timer + }; + + let sampling_timer_channels = sampling_timer.channels(); + let shadow_sampling_timer_channels = shadow_sampling_timer.channels(); + + let mut timestamp_timer = { + // The timer frequency is manually adjusted below, so the 1KHz setting here is a + // dont-care. + let mut timer5 = + device + .TIM5 + .timer(1.khz(), ccdr.peripheral.TIM5, &ccdr.clocks); + + // Configure the timer to count at the designed tick rate. We will manually set the + // period below. + timer5.pause(); + timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY); + + // The timestamp timer must run at exactly a multiple of the sample timer based on the + // batch size. To accomodate this, we manually set the prescaler identical to the sample + // timer, but use a period that is longer. + let mut timer = timers::TimestampTimer::new(timer5); + + let period = digital_input_stamper::calculate_timestamp_timer_period(); + timer.set_period_ticks(period); + + timer + }; + + let timestamp_timer_channels = timestamp_timer.channels(); + + // Configure the SPI interfaces to the ADCs and DACs. + 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() + .communication_mode(hal::spi::CommunicationMode::Receiver) + .cs_delay(design_parameters::ADC_SETUP_TIME); + + let spi: hal::spi::Spi<_, _, u16> = device.SPI2.spi( + (spi_sck, spi_miso, hal::spi::NoMosi), + config, + design_parameters::ADC_DAC_SCK_MAX, + ccdr.peripheral.SPI2, + &ccdr.clocks, + ); + + adc::Adc0Input::new( + spi, + dma_streams.0, + dma_streams.1, + dma_streams.2, + sampling_timer_channels.ch1, + shadow_sampling_timer_channels.ch1, + ) + }; + + 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); + + 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::Receiver) + .cs_delay(design_parameters::ADC_SETUP_TIME); + + let spi: hal::spi::Spi<_, _, u16> = device.SPI3.spi( + (spi_sck, spi_miso, hal::spi::NoMosi), + config, + design_parameters::ADC_DAC_SCK_MAX, + ccdr.peripheral.SPI3, + &ccdr.clocks, + ); + + adc::Adc1Input::new( + spi, + dma_streams.3, + dma_streams.4, + dma_streams.5, + sampling_timer_channels.ch2, + shadow_sampling_timer_channels.ch2, + ) + }; + + (adc0, adc1) + }; + + 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 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(); + + device.SPI4.spi( + (spi_sck, spi_miso, hal::spi::NoMosi), + config, + design_parameters::ADC_DAC_SCK_MAX, + 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(); + + device.SPI5.spi( + (spi_sck, spi_miso, hal::spi::NoMosi), + config, + design_parameters::ADC_DAC_SCK_MAX, + ccdr.peripheral.SPI5, + &ccdr.clocks, + ) + }; + + let dac0 = dac::Dac0Output::new( + dac0_spi, + dma_streams.6, + sampling_timer_channels.ch3, + ); + let dac1 = dac::Dac1Output::new( + dac1_spi, + dma_streams.7, + sampling_timer_channels.ch4, + ); + (dac0, dac1) + }; + + let afes = { + let afe0 = { + let a0_pin = gpiof.pf2.into_push_pull_output(); + let a1_pin = gpiof.pf5.into_push_pull_output(); + afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin) + }; + + let afe1 = { + let a0_pin = gpiod.pd14.into_push_pull_output(); + let a1_pin = gpiod.pd15.into_push_pull_output(); + afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin) + }; + + (afe0, afe1) + }; + + let input_stamper = { + let trigger = gpioa.pa3.into_alternate_af2(); + digital_input_stamper::InputStamper::new( + trigger, + timestamp_timer_channels.ch4, + ) + }; + + let mut eeprom_i2c = { + let sda = gpiof.pf0.into_alternate_af4().set_open_drain(); + let scl = gpiof.pf1.into_alternate_af4().set_open_drain(); + device.I2C2.i2c( + (scl, sda), + 100.khz(), + ccdr.peripheral.I2C2, + &ccdr.clocks, + ) + }; + + // Configure ethernet pins. + { + // Reset the PHY before configuring pins. + let mut eth_phy_nrst = gpioe.pe3.into_push_pull_output(); + eth_phy_nrst.set_low().unwrap(); + delay.delay_us(200u8); + eth_phy_nrst.set_high().unwrap(); + let _rmii_ref_clk = gpioa + .pa1 + .into_alternate_af11() + .set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_mdio = gpioa + .pa2 + .into_alternate_af11() + .set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_mdc = gpioc + .pc1 + .into_alternate_af11() + .set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_crs_dv = gpioa + .pa7 + .into_alternate_af11() + .set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_rxd0 = gpioc + .pc4 + .into_alternate_af11() + .set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_rxd1 = gpioc + .pc5 + .into_alternate_af11() + .set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_tx_en = gpiob + .pb11 + .into_alternate_af11() + .set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_txd0 = gpiob + .pb12 + .into_alternate_af11() + .set_speed(hal::gpio::Speed::VeryHigh); + let _rmii_txd1 = gpiog + .pg14 + .into_alternate_af11() + .set_speed(hal::gpio::Speed::VeryHigh); + } + + let mac_addr = match eeprom::read_eui48(&mut eeprom_i2c) { + Err(_) => { + info!("Could not read EEPROM, using default MAC address"); + smoltcp::wire::EthernetAddress([0x10, 0xE2, 0xD5, 0x00, 0x03, 0x00]) + } + Ok(raw_mac) => smoltcp::wire::EthernetAddress(raw_mac), + }; + + let network_devices = { + // Configure the ethernet controller + let (eth_dma, eth_mac) = unsafe { + ethernet::new_unchecked( + device.ETHERNET_MAC, + device.ETHERNET_MTL, + device.ETHERNET_DMA, + &mut DES_RING, + mac_addr, + ccdr.peripheral.ETH1MAC, + &ccdr.clocks, + ) + }; + + // Reset and initialize the ethernet phy. + let mut lan8742a = + ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0)); + lan8742a.phy_reset(); + lan8742a.phy_init(); + + unsafe { ethernet::enable_interrupt() }; + + let store = unsafe { &mut NET_STORE }; + + store.ip_addrs[0] = smoltcp::wire::IpCidr::new( + smoltcp::wire::IpAddress::v4(10, 0, 16, 99), + 24, + ); + + let default_v4_gw = Ipv4Address::new(10, 0, 16, 1); + let mut routes = Routes::new(&mut store.routes_storage[..]); + routes.add_default_ipv4_route(default_v4_gw).unwrap(); + + let neighbor_cache = + smoltcp::iface::NeighborCache::new(&mut store.neighbor_cache[..]); + + let interface = smoltcp::iface::EthernetInterfaceBuilder::new(eth_dma) + .ethernet_addr(mac_addr) + .neighbor_cache(neighbor_cache) + .ip_addrs(&mut store.ip_addrs[..]) + .routes(routes) + .finalize(); + + NetworkDevices { + interface, + phy: lan8742a, + } + }; + + let mut fp_led_0 = gpiod.pd5.into_push_pull_output(); + let mut fp_led_1 = gpiod.pd6.into_push_pull_output(); + let mut fp_led_2 = gpiog.pg4.into_push_pull_output(); + let mut fp_led_3 = gpiod.pd12.into_push_pull_output(); + + fp_led_0.set_low().unwrap(); + fp_led_1.set_low().unwrap(); + fp_led_2.set_low().unwrap(); + fp_led_3.set_low().unwrap(); + + let stabilizer = StabilizerDevices { + afes, + adcs, + dacs, + timestamper: input_stamper, + net: network_devices, + adc_dac_timer: sampling_timer, + timestamp_timer, + }; + + // 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 = if pounder_pgood.is_high().unwrap() { + let ad9959 = { + let qspi_interface = { + // Instantiate the QUADSPI pins and peripheral interface. + let qspi_pins = { + let _qspi_ncs = gpioc + .pc11 + .into_alternate_af9() + .set_speed(hal::gpio::Speed::VeryHigh); + + let clk = gpiob + .pb2 + .into_alternate_af9() + .set_speed(hal::gpio::Speed::VeryHigh); + let io0 = gpioe + .pe7 + .into_alternate_af10() + .set_speed(hal::gpio::Speed::VeryHigh); + let io1 = gpioe + .pe8 + .into_alternate_af10() + .set_speed(hal::gpio::Speed::VeryHigh); + let io2 = gpioe + .pe9 + .into_alternate_af10() + .set_speed(hal::gpio::Speed::VeryHigh); + let io3 = gpioe + .pe10 + .into_alternate_af10() + .set_speed(hal::gpio::Speed::VeryHigh); + + (clk, io0, io1, io2, io3) + }; + + let qspi = hal::qspi::Qspi::bank2( + device.QUADSPI, + qspi_pins, + design_parameters::POUNDER_QSPI_FREQUENCY, + &ccdr.clocks, + ccdr.peripheral.QSPI, + ); + + pounder::QspiInterface::new(qspi).unwrap() + }; + + #[cfg(feature = "pounder_v1_1")] + let reset_pin = gpiog.pg6.into_push_pull_output(); + #[cfg(not(feature = "pounder_v1_1"))] + let reset_pin = gpioa.pa0.into_push_pull_output(); + + let mut io_update = gpiog.pg7.into_push_pull_output(); + + let ref_clk: hal::time::Hertz = + design_parameters::DDS_REF_CLK.into(); + + let ad9959 = ad9959::Ad9959::new( + qspi_interface, + reset_pin, + &mut io_update, + &mut delay, + ad9959::Mode::FourBitSerial, + ref_clk.0 as f32, + design_parameters::DDS_MULTIPLIER, + ) + .unwrap(); + + // Return IO_Update + gpiog.pg7 = io_update.into_analog(); + + ad9959 + }; + + let io_expander = { + let sda = gpiob.pb7.into_alternate_af4().set_open_drain(); + let scl = gpiob.pb8.into_alternate_af4().set_open_drain(); + let i2c1 = device.I2C1.i2c( + (scl, sda), + 100.khz(), + ccdr.peripheral.I2C1, + &ccdr.clocks, + ); + mcp23017::MCP23017::default(i2c1).unwrap() + }; + + let spi = { + let spi_mosi = gpiod + .pd7 + .into_alternate_af5() + .set_speed(hal::gpio::Speed::VeryHigh); + let spi_miso = gpioa + .pa6 + .into_alternate_af5() + .set_speed(hal::gpio::Speed::VeryHigh); + let spi_sck = gpiog + .pg11 + .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, + }); + + // The maximum frequency of this SPI must be limited due to capacitance on the MISO + // line causing a long RC decay. + device.SPI1.spi( + (spi_sck, spi_miso, spi_mosi), + config, + 5.mhz(), + ccdr.peripheral.SPI1, + &ccdr.clocks, + ) + }; + + let (adc1, adc2) = { + let (mut adc1, mut adc2) = hal::adc::adc12( + device.ADC1, + device.ADC2, + &mut delay, + ccdr.peripheral.ADC12, + &ccdr.clocks, + ); + + let adc1 = { + adc1.calibrate(); + adc1.enable() + }; + + let adc2 = { + adc2.calibrate(); + adc2.enable() + }; + + (adc1, adc2) + }; + + let adc1_in_p = gpiof.pf11.into_analog(); + let adc2_in_p = gpiof.pf14.into_analog(); + + let pounder_devices = pounder::PounderDevices::new( + io_expander, + 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 = pounder::hrtimer::HighResTimerE::new( + device.HRTIM_TIME, + device.HRTIM_MASTER, + device.HRTIM_COMMON, + ccdr.clocks, + ccdr.peripheral.HRTIM, + ); + + // IO_Update occurs after a fixed delay from the QSPI write. Note that the timer + // is triggered after the QSPI write, which can take approximately 120nS, so + // there is additional margin. + hrtimer.configure_single_shot( + pounder::hrtimer::Channel::Two, + design_parameters::POUNDER_IO_UPDATE_DURATION, + design_parameters::POUNDER_IO_UPDATE_DELAY, + ); + + // Ensure that we have enough time for an IO-update every sample. + let sample_frequency = { + let timer_frequency: hal::time::Hertz = + design_parameters::TIMER_FREQUENCY.into(); + timer_frequency.0 as f32 / ADC_SAMPLE_TICKS as f32 + }; + + let sample_period = 1.0 / sample_frequency; + assert!( + sample_period > design_parameters::POUNDER_IO_UPDATE_DELAY + ); + + hrtimer + }; + + let (qspi, config) = ad9959.freeze(); + DdsOutput::new(qspi, io_update_trigger, config) + }; + + #[cfg(feature = "pounder_v1_1")] + let pounder_stamper = { + let dma2_streams = hal::dma::dma::StreamsTuple::new( + device.DMA2, + ccdr.peripheral.DMA2, + ); + + let etr_pin = gpioa.pa0.into_alternate_af3(); + + // The frequency in the constructor is dont-care, as we will modify the period + clock + // source manually below. + let tim8 = + device + .TIM8 + .timer(1.khz(), ccdr.peripheral.TIM8, &ccdr.clocks); + let mut timestamp_timer = timers::PounderTimestampTimer::new(tim8); + + // Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is + // output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for + // the timestamp timer. 31.25MHz corresponds with a 32ns tick rate. + timestamp_timer.set_external_clock(timers::Prescaler::Div4); + timestamp_timer.start(); + + // We want the pounder timestamp timer to overflow once per batch. + let tick_ratio = { + let sync_clk_mhz: f32 = design_parameters::DDS_SYSTEM_CLK.0 + as f32 + / design_parameters::DDS_SYNC_CLK_DIV as f32; + sync_clk_mhz / design_parameters::TIMER_FREQUENCY.0 as f32 + }; + + let period = (tick_ratio + * ADC_SAMPLE_TICKS as f32 + * SAMPLE_BUFFER_SIZE as f32) as u32 + / 4; + timestamp_timer.set_period_ticks((period - 1).try_into().unwrap()); + let tim8_channels = timestamp_timer.channels(); + + pounder::timestamp::Timestamper::new( + timestamp_timer, + dma2_streams.0, + tim8_channels.ch1, + &mut sampling_timer, + etr_pin, + ) + }; + + Some(PounderDevices { + pounder: pounder_devices, + dds_output, + + #[cfg(feature = "pounder_v1_1")] + timestamper: pounder_stamper, + }) + } else { + None + }; + + // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); + // info!("Built on {}", build_info::BUILT_TIME_UTC); + // info!("{} {}", build_info::RUSTC_VERSION, build_info::TARGET); + + // Enable the instruction cache. + core.SCB.enable_icache(); + + // Utilize the cycle counter for RTIC scheduling. + core.DWT.enable_cycle_counter(); + + (stabilizer, pounder) +} diff --git a/src/dac.rs b/src/hardware/dac.rs similarity index 96% rename from src/dac.rs rename to src/hardware/dac.rs index a22e5da..09d0497 100644 --- a/src/dac.rs +++ b/src/hardware/dac.rs @@ -50,9 +50,15 @@ ///! While double-buffered mode is used for DMA to avoid lost DAC-update events, there is no check ///! for re-use of a previously provided DAC output buffer. It is assumed that the DMA request is ///! served promptly after the transfer completes. -use super::{ - hal, timers, DMAReq, DmaConfig, MemoryToPeripheral, TargetAddress, - Transfer, SAMPLE_BUFFER_SIZE, +use stm32h7xx_hal as hal; + +use crate::SAMPLE_BUFFER_SIZE; + +use super::timers; +use hal::dma::{ + dma::{DMAReq, DmaConfig}, + traits::TargetAddress, + MemoryToPeripheral, Transfer, }; // The following global buffers are used for the DAC code DMA transfers. Two buffers are used for @@ -158,7 +164,7 @@ macro_rules! dac_output { } // Construct the trigger stream to write from memory to the peripheral. - let mut transfer: Transfer<_, _, MemoryToPeripheral, _> = + let transfer: Transfer<_, _, MemoryToPeripheral, _> = Transfer::init( stream, $spi::new(trigger_channel, spi), @@ -169,8 +175,6 @@ macro_rules! dac_output { trigger_config, ); - transfer.start(|spi| spi.start_dma()); - Self { transfer, // Note(unsafe): This buffer is only used once and provided for the next DMA transfer. @@ -178,6 +182,10 @@ macro_rules! dac_output { } } + pub fn start(&mut self) { + self.transfer.start(|spi| spi.start_dma()); + } + /// Acquire the next output buffer to populate it with DAC codes. pub fn acquire_buffer(&mut self) -> &mut [u16; SAMPLE_BUFFER_SIZE] { // Note: If a device hangs up, check that this conditional is passing correctly, as diff --git a/src/design_parameters.rs b/src/hardware/design_parameters.rs similarity index 92% rename from src/design_parameters.rs rename to src/hardware/design_parameters.rs index 3edf04b..3de7c15 100644 --- a/src/design_parameters.rs +++ b/src/hardware/design_parameters.rs @@ -1,4 +1,4 @@ -use super::hal::time::MegaHertz; +use stm32h7xx_hal::time::MegaHertz; /// 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. @@ -17,13 +17,13 @@ pub const POUNDER_QSPI_FREQUENCY: MegaHertz = MegaHertz(40); // Pounder 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. -pub const POUNDER_IO_UPDATE_DELAY: f32 = 900_e-9; +pub const POUNDER_IO_UPDATE_DELAY: f32 = 900e-9; /// The duration to assert IO_Update for the pounder DDS. // 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 500MHz), this corresponds to // 32ns. To accomodate rounding errors, we use 50ns instead. -pub const POUNDER_IO_UPDATE_DURATION: f32 = 50_e-9; +pub const POUNDER_IO_UPDATE_DURATION: f32 = 50e-9; /// The DDS reference clock frequency in MHz. pub const DDS_REF_CLK: MegaHertz = MegaHertz(100); diff --git a/src/digital_input_stamper.rs b/src/hardware/digital_input_stamper.rs similarity index 97% rename from src/digital_input_stamper.rs rename to src/hardware/digital_input_stamper.rs index 648105f..4262800 100644 --- a/src/digital_input_stamper.rs +++ b/src/hardware/digital_input_stamper.rs @@ -24,7 +24,8 @@ ///! ///! This module only supports DI0 for timestamping due to trigger constraints on the DIx pins. If ///! timestamping is desired in DI1, a separate timer + capture channel will be necessary. -use super::{hal, timers, ADC_SAMPLE_TICKS, SAMPLE_BUFFER_SIZE}; +use super::{hal, timers}; +use crate::{ADC_SAMPLE_TICKS, SAMPLE_BUFFER_SIZE}; /// Calculate the period of the digital input timestamp timer. /// @@ -89,6 +90,7 @@ impl InputStamper { } /// Start to capture timestamps on DI0. + #[allow(dead_code)] pub fn start(&mut self) { self.capture_channel.enable(); } @@ -101,6 +103,7 @@ impl InputStamper { /// /// To prevent timestamp loss, the batch size and sampling rate must be adjusted such that at /// most one timestamp will occur in each data processing cycle. + #[allow(dead_code)] pub fn latest_timestamp(&mut self) -> Option { self.capture_channel .latest_capture() diff --git a/src/eeprom.rs b/src/hardware/eeprom.rs similarity index 93% rename from src/eeprom.rs rename to src/hardware/eeprom.rs index aa5caf7..d84dd70 100644 --- a/src/eeprom.rs +++ b/src/hardware/eeprom.rs @@ -2,7 +2,6 @@ use embedded_hal::blocking::i2c::WriteRead; const I2C_ADDR: u8 = 0x50; -#[allow(dead_code)] pub fn read_eui48(i2c: &mut T) -> Result<[u8; 6], T::Error> where T: WriteRead, diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs new file mode 100644 index 0000000..dbe8a9a --- /dev/null +++ b/src/hardware/mod.rs @@ -0,0 +1,63 @@ +///! Module for all hardware-specific setup of Stabilizer +use stm32h7xx_hal as hal; + +mod adc; +mod afe; +mod configuration; +mod dac; +mod design_parameters; +mod digital_input_stamper; +mod eeprom; +mod pounder; +mod timers; + +pub use adc::{Adc0Input, Adc1Input}; +pub use afe::Gain as AfeGain; +pub use dac::{Dac0Output, Dac1Output}; +pub use pounder::DdsOutput; + +// Type alias for the analog front-end (AFE) for ADC0. +pub type AFE0 = afe::ProgrammableGainAmplifier< + hal::gpio::gpiof::PF2>, + hal::gpio::gpiof::PF5>, +>; + +// Type alias for the analog front-end (AFE) for ADC1. +pub type AFE1 = afe::ProgrammableGainAmplifier< + hal::gpio::gpiod::PD14>, + hal::gpio::gpiod::PD15>, +>; + +// Type alias for the ethernet interface on Stabilizer. +pub type Ethernet = smoltcp::iface::EthernetInterface< + 'static, + 'static, + 'static, + hal::ethernet::EthernetDMA<'static>, +>; + +pub use configuration::{setup, PounderDevices, StabilizerDevices}; + +#[inline(never)] +#[panic_handler] +#[cfg(all(feature = "nightly", not(feature = "semihosting")))] +fn panic(_info: &core::panic::PanicInfo) -> ! { + let gpiod = unsafe { &*hal::stm32::GPIOD::ptr() }; + gpiod.odr.modify(|_, w| w.odr6().high().odr12().high()); // FP_LED_1, FP_LED_3 + #[cfg(feature = "nightly")] + core::intrinsics::abort(); + #[cfg(not(feature = "nightly"))] + unsafe { + core::intrinsics::abort(); + } +} + +#[cortex_m_rt::exception] +fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { + panic!("HardFault at {:#?}", ef); +} + +#[cortex_m_rt::exception] +fn DefaultHandler(irqn: i16) { + panic!("Unhandled exception (IRQn = {})", irqn); +} diff --git a/src/pounder/attenuators.rs b/src/hardware/pounder/attenuators.rs similarity index 100% rename from src/pounder/attenuators.rs rename to src/hardware/pounder/attenuators.rs diff --git a/src/pounder/dds_output.rs b/src/hardware/pounder/dds_output.rs similarity index 98% rename from src/pounder/dds_output.rs rename to src/hardware/pounder/dds_output.rs index 36399f2..c7c5e6d 100644 --- a/src/pounder/dds_output.rs +++ b/src/hardware/pounder/dds_output.rs @@ -52,8 +52,7 @@ ///! compile-time-known register update sequence needed for the application, the serialization ///! process can be done once and then register values can be written into a pre-computed serialized ///! buffer to avoid the software overhead of much of the serialization process. -use super::QspiInterface; -use crate::hrtimer::HighResTimerE; +use super::{hrtimer::HighResTimerE, QspiInterface}; use ad9959::{Channel, DdsConfig, ProfileSerializer}; use stm32h7xx_hal as hal; @@ -90,6 +89,7 @@ impl DdsOutput { } /// Get a builder for serializing a Pounder DDS profile. + #[allow(dead_code)] pub fn builder(&mut self) -> ProfileBuilder { let builder = self.config.builder(); ProfileBuilder { @@ -145,6 +145,7 @@ impl<'a> ProfileBuilder<'a> { /// * `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. + #[allow(dead_code)] pub fn update_channels( mut self, channels: &[Channel], @@ -157,6 +158,7 @@ impl<'a> ProfileBuilder<'a> { } /// Write the profile to the DDS asynchronously. + #[allow(dead_code)] pub fn write_profile(mut self) { let profile = self.serializer.finalize(); self.dds_stream.write_profile(profile); diff --git a/src/hrtimer.rs b/src/hardware/pounder/hrtimer.rs similarity index 97% rename from src/hrtimer.rs rename to src/hardware/pounder/hrtimer.rs index 2831bbe..ef7086d 100644 --- a/src/hrtimer.rs +++ b/src/hardware/pounder/hrtimer.rs @@ -1,8 +1,11 @@ ///! 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}; +use stm32h7xx_hal::{ + self as hal, + rcc::{rec, CoreClocks, ResetEnable}, +}; /// A HRTimer output channel. +#[allow(dead_code)] pub enum Channel { One, Two, diff --git a/src/pounder/mod.rs b/src/hardware/pounder/mod.rs similarity index 99% rename from src/pounder/mod.rs rename to src/hardware/pounder/mod.rs index 2811e92..3a85937 100644 --- a/src/pounder/mod.rs +++ b/src/hardware/pounder/mod.rs @@ -2,7 +2,10 @@ use serde::{Deserialize, Serialize}; mod attenuators; mod dds_output; +pub mod hrtimer; mod rf_power; + +#[cfg(feature = "pounder_v1_1")] pub mod timestamp; pub use dds_output::DdsOutput; @@ -27,13 +30,11 @@ const ATT_LE0_PIN: u8 = 8; pub enum Error { Spi, I2c, - Dds, Qspi, Bounds, InvalidAddress, InvalidChannel, Adc, - Access, } #[derive(Debug, Copy, Clone)] diff --git a/src/pounder/rf_power.rs b/src/hardware/pounder/rf_power.rs similarity index 100% rename from src/pounder/rf_power.rs rename to src/hardware/pounder/rf_power.rs diff --git a/src/pounder/timestamp.rs b/src/hardware/pounder/timestamp.rs similarity index 95% rename from src/pounder/timestamp.rs rename to src/hardware/pounder/timestamp.rs index f6c1a14..decbfd6 100644 --- a/src/pounder/timestamp.rs +++ b/src/hardware/pounder/timestamp.rs @@ -26,7 +26,7 @@ use stm32h7xx_hal as hal; use hal::dma::{dma::DmaConfig, PeripheralToMemory, Transfer}; -use crate::{timers, SAMPLE_BUFFER_SIZE}; +use crate::{hardware::timers, SAMPLE_BUFFER_SIZE}; // Three buffers are required for double buffered mode - 2 are owned by the DMA stream and 1 is the // working data provided to the application. These buffers must exist in a DMA-accessible memory @@ -89,7 +89,7 @@ impl Timestamper { input_capture.listen_dma(); // The data transfer is always a transfer of data from the peripheral to a RAM buffer. - let mut data_transfer: Transfer<_, _, PeripheralToMemory, _> = + let data_transfer: Transfer<_, _, PeripheralToMemory, _> = Transfer::init( stream, input_capture, @@ -100,8 +100,6 @@ impl Timestamper { config, ); - data_transfer.start(|capture_channel| capture_channel.enable()); - Self { timer: timestamp_timer, transfer: data_transfer, @@ -112,6 +110,12 @@ impl Timestamper { } } + /// Start the DMA transfer for collecting timestamps. + pub fn start(&mut self) { + self.transfer + .start(|capture_channel| capture_channel.enable()); + } + /// Update the period of the underlying timestamp timer. pub fn update_period(&mut self, period: u16) { self.timer.set_period_ticks(period); diff --git a/src/timers.rs b/src/hardware/timers.rs similarity index 99% rename from src/timers.rs rename to src/hardware/timers.rs index 062eb4b..7199730 100644 --- a/src/timers.rs +++ b/src/hardware/timers.rs @@ -32,6 +32,7 @@ pub enum TriggerSource { } /// Prescalers for externally-supplied reference clocks. +#[allow(dead_code)] pub enum Prescaler { Div1 = 0b00, Div2 = 0b01, @@ -40,6 +41,7 @@ pub enum Prescaler { } /// Optional slave operation modes of a timer. +#[allow(dead_code)] pub enum SlaveMode { Disabled = 0, Trigger = 0b0110, diff --git a/src/main.rs b/src/main.rs index 6d25919..721e1f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,59 +1,20 @@ #![deny(warnings)] -#![allow(clippy::missing_safety_doc)] #![no_std] #![no_main] -#![cfg_attr(feature = "nightly", feature(asm))] -// Enable returning `!` -#![cfg_attr(feature = "nightly", feature(never_type))] #![cfg_attr(feature = "nightly", feature(core_intrinsics))] -#[inline(never)] -#[panic_handler] -#[cfg(all(feature = "nightly", not(feature = "semihosting")))] -fn panic(_info: &core::panic::PanicInfo) -> ! { - let gpiod = unsafe { &*hal::stm32::GPIOD::ptr() }; - gpiod.odr.modify(|_, w| w.odr6().high().odr12().high()); // FP_LED_1, FP_LED_3 - #[cfg(feature = "nightly")] - core::intrinsics::abort(); - #[cfg(not(feature = "nightly"))] - unsafe { - core::intrinsics::abort(); - } -} +use stm32h7xx_hal as hal; #[cfg(feature = "semihosting")] -extern crate panic_semihosting; +use panic_semihosting as _; #[cfg(not(any(feature = "nightly", feature = "semihosting")))] -extern crate panic_halt; +use panic_halt as _; #[macro_use] extern crate log; -#[allow(unused_imports)] -use core::convert::TryInto; - -// use core::sync::atomic::{AtomicU32, AtomicBool, Ordering}; -use cortex_m_rt::exception; use rtic::cyccnt::{Instant, U32Ext}; -use stm32h7xx_hal as hal; -use stm32h7xx_hal::prelude::*; - -use embedded_hal::digital::v2::{InputPin, OutputPin}; - -use hal::{ - dma::{ - config::Priority, - dma::{DMAReq, DmaConfig}, - traits::TargetAddress, - MemoryToPeripheral, PeripheralToMemory, Transfer, - }, - ethernet::{self, PHY}, -}; - -use smoltcp as net; -use smoltcp::iface::Routes; -use smoltcp::wire::Ipv4Address; use heapless::{consts::*, String}; @@ -68,162 +29,24 @@ const SAMPLE_BUFFER_SIZE: usize = 8; // The number of cascaded IIR biquads per channel. Select 1 or 2! const IIR_CASCADE_LENGTH: 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 digital_input_stamper; -mod eeprom; -mod hrtimer; -mod pounder; +#[macro_use] mod server; -mod timers; - -use adc::{Adc0Input, Adc1Input}; -use dac::{Dac0Output, Dac1Output}; +mod hardware; +use hardware::{Adc0Input, Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1}; use dsp::iir; -use pounder::DdsOutput; - -#[cfg(not(feature = "semihosting"))] -fn init_log() {} - -#[cfg(feature = "semihosting")] -fn init_log() { - use cortex_m_log::log::{init as init_log, Logger}; - use cortex_m_log::printer::semihosting::{hio::HStdout, InterruptOk}; - use log::LevelFilter; - static mut LOGGER: Option>> = None; - let logger = Logger { - inner: InterruptOk::<_>::stdout().unwrap(), - level: LevelFilter::Info, - }; - let logger = unsafe { LOGGER.get_or_insert(logger) }; - - init_log(logger).unwrap(); -} - -// Pull in build information (from `built` crate) -mod build_info { - #![allow(dead_code)] - // include!(concat!(env!("OUT_DIR"), "/built.rs")); -} - -pub struct NetStorage { - ip_addrs: [net::wire::IpCidr; 1], - neighbor_cache: [Option<(net::wire::IpAddress, net::iface::Neighbor)>; 8], - routes_storage: [Option<(smoltcp::wire::IpCidr, smoltcp::iface::Route)>; 1], -} - -static mut NET_STORE: NetStorage = NetStorage { - // Placeholder for the real IP address, which is initialized at runtime. - ip_addrs: [net::wire::IpCidr::Ipv6( - net::wire::Ipv6Cidr::SOLICITED_NODE_PREFIX, - )], - - neighbor_cache: [None; 8], - - routes_storage: [None; 1], -}; const SCALE: f32 = ((1 << 15) - 1) as f32; -// static ETHERNET_PENDING: AtomicBool = AtomicBool::new(true); - const TCP_RX_BUFFER_SIZE: usize = 8192; const TCP_TX_BUFFER_SIZE: usize = 8192; -type AFE0 = afe::ProgrammableGainAmplifier< - hal::gpio::gpiof::PF2>, - hal::gpio::gpiof::PF5>, ->; - -type AFE1 = afe::ProgrammableGainAmplifier< - hal::gpio::gpiod::PD14>, - hal::gpio::gpiod::PD15>, ->; - -macro_rules! route_request { - ($request:ident, - readable_attributes: [$($read_attribute:tt: $getter:tt),*], - modifiable_attributes: [$($write_attribute:tt: $TYPE:ty, $setter:tt),*]) => { - match $request.req { - server::AccessRequest::Read => { - match $request.attribute { - $( - $read_attribute => { - #[allow(clippy::redundant_closure_call)] - let value = match $getter() { - Ok(data) => data, - Err(_) => return server::Response::error($request.attribute, - "Failed to read attribute"), - }; - - let encoded_data: String = match serde_json_core::to_string(&value) { - Ok(data) => data, - Err(_) => return server::Response::error($request.attribute, - "Failed to encode attribute value"), - }; - - server::Response::success($request.attribute, &encoded_data) - }, - )* - _ => server::Response::error($request.attribute, "Unknown attribute") - } - }, - server::AccessRequest::Write => { - match $request.attribute { - $( - $write_attribute => { - let new_value = match serde_json_core::from_str::<$TYPE>(&$request.value) { - Ok(data) => data, - Err(_) => return server::Response::error($request.attribute, - "Failed to decode value"), - }; - - #[allow(clippy::redundant_closure_call)] - match $setter(new_value) { - Ok(_) => server::Response::success($request.attribute, &$request.value), - Err(_) => server::Response::error($request.attribute, - "Failed to set attribute"), - } - } - )* - _ => server::Response::error($request.attribute, "Unknown attribute") - } - } - } - } -} - #[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] const APP: () = { struct Resources { afes: (AFE0, AFE1), adcs: (Adc0Input, Adc1Input), dacs: (Dac0Output, Dac1Output), - input_stamper: digital_input_stamper::InputStamper, - - eeprom_i2c: hal::i2c::I2c, - - dds_output: Option, - - // Note: It appears that rustfmt generates a format that GDB cannot recognize, which - // results in GDB breakpoints being set improperly. - #[rustfmt::skip] - net_interface: net::iface::EthernetInterface< - 'static, - 'static, - 'static, - ethernet::EthernetDMA<'static>>, - eth_mac: ethernet::phy::LAN8742A, - mac_addr: net::wire::EthernetAddress, - - pounder: Option, - - pounder_stamper: Option, + net_interface: hardware::Ethernet, // Format: iir_state[ch][cascade-no][coeff] #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] @@ -234,718 +57,23 @@ const APP: () = { #[init] fn init(c: init::Context) -> init::LateResources { - let dp = c.device; - let mut cp = c.core; - - let pwr = dp.PWR.constrain(); - let vos = pwr.freeze(); - - // Enable SRAM3 for the ethernet descriptor ring. - dp.RCC.ahb2enr.modify(|_, w| w.sram3en().set_bit()); - - // Clear reset flags. - dp.RCC.rsr.write(|w| w.rmvf().set_bit()); - - // Select the PLLs for SPI. - dp.RCC - .d2ccip1r - .modify(|_, w| w.spi123sel().pll2_p().spi45sel().pll2_q()); - - let rcc = dp.RCC.constrain(); - let ccdr = rcc - .use_hse(8.mhz()) - .sysclk(400.mhz()) - .hclk(200.mhz()) - .per_ck(100.mhz()) - .pll2_p_ck(100.mhz()) - .pll2_q_ck(100.mhz()) - .freeze(vos, &dp.SYSCFG); - - init_log(); - - let mut delay = hal::delay::Delay::new(cp.SYST, ccdr.clocks); - - let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA); - let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); - let gpioc = dp.GPIOC.split(ccdr.peripheral.GPIOC); - let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD); - let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE); - let gpiof = dp.GPIOF.split(ccdr.peripheral.GPIOF); - let mut gpiog = dp.GPIOG.split(ccdr.peripheral.GPIOG); - - let afe0 = { - let a0_pin = gpiof.pf2.into_push_pull_output(); - let a1_pin = gpiof.pf5.into_push_pull_output(); - afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin) - }; - - let afe1 = { - let a0_pin = gpiod.pd14.into_push_pull_output(); - let a1_pin = gpiod.pd15.into_push_pull_output(); - afe::ProgrammableGainAmplifier::new(a0_pin, a1_pin) - }; - - let dma_streams = - hal::dma::dma::StreamsTuple::new(dp.DMA1, ccdr.peripheral.DMA1); - - // Configure timer 2 to trigger conversions for the ADC - let mut sampling_timer = { - // The timer frequency is manually adjusted below, so the 1KHz setting here is a - // dont-care. - let mut timer2 = - dp.TIM2.timer(1.khz(), ccdr.peripheral.TIM2, &ccdr.clocks); - - // Configure the timer to count at the designed tick rate. We will manually set the - // period below. - timer2.pause(); - timer2.reset_counter(); - timer2.set_tick_freq(design_parameters::TIMER_FREQUENCY); - - let mut sampling_timer = timers::SamplingTimer::new(timer2); - sampling_timer.set_period_ticks((ADC_SAMPLE_TICKS - 1) as u32); - - // The sampling timer is used as the master timer for the shadow-sampling timer. Thus, - // it generates a trigger whenever it is enabled. - - sampling_timer - }; - - let mut shadow_sampling_timer = { - // The timer frequency is manually adjusted below, so the 1KHz setting here is a - // dont-care. - let mut timer3 = - dp.TIM3.timer(1.khz(), ccdr.peripheral.TIM3, &ccdr.clocks); - - // Configure the timer to count at the designed tick rate. We will manually set the - // period below. - timer3.pause(); - timer3.reset_counter(); - timer3.set_tick_freq(design_parameters::TIMER_FREQUENCY); - - let mut shadow_sampling_timer = - timers::ShadowSamplingTimer::new(timer3); - shadow_sampling_timer.set_period_ticks(ADC_SAMPLE_TICKS - 1); - - // The shadow sampling timer is a slave-mode timer to the sampling timer. It should - // always be in-sync - thus, we configure it to operate in slave mode using "Trigger - // mode". - // For TIM3, TIM2 can be made the internal trigger connection using ITR1. Thus, the - // SamplingTimer start now gates the start of the ShadowSamplingTimer. - shadow_sampling_timer.set_slave_mode( - timers::TriggerSource::Trigger1, - timers::SlaveMode::Trigger, - ); - - shadow_sampling_timer - }; - - let sampling_timer_channels = sampling_timer.channels(); - let shadow_sampling_timer_channels = shadow_sampling_timer.channels(); - - let mut timestamp_timer = { - // The timer frequency is manually adjusted below, so the 1KHz setting here is a - // dont-care. - let mut timer5 = - dp.TIM5.timer(1.khz(), ccdr.peripheral.TIM5, &ccdr.clocks); - - // Configure the timer to count at the designed tick rate. We will manually set the - // period below. - timer5.pause(); - timer5.set_tick_freq(design_parameters::TIMER_FREQUENCY); - - // The timestamp timer must run at exactly a multiple of the sample timer based on the - // batch size. To accomodate this, we manually set the prescaler identical to the sample - // timer, but use a period that is longer. - let mut timer = timers::TimestampTimer::new(timer5); - - let period = - digital_input_stamper::calculate_timestamp_timer_period(); - timer.set_period_ticks(period); - - timer - }; - - let timestamp_timer_channels = timestamp_timer.channels(); - - // Configure the SPI interfaces to the ADCs and DACs. - 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() - .communication_mode(hal::spi::CommunicationMode::Receiver) - .cs_delay(design_parameters::ADC_SETUP_TIME); - - let spi: hal::spi::Spi<_, _, u16> = dp.SPI2.spi( - (spi_sck, spi_miso, hal::spi::NoMosi), - config, - design_parameters::ADC_DAC_SCK_MAX, - ccdr.peripheral.SPI2, - &ccdr.clocks, - ); - - Adc0Input::new( - spi, - dma_streams.0, - dma_streams.1, - dma_streams.2, - sampling_timer_channels.ch1, - shadow_sampling_timer_channels.ch1, - ) - }; - - 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); - - 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::Receiver) - .cs_delay(design_parameters::ADC_SETUP_TIME); - - let spi: hal::spi::Spi<_, _, u16> = dp.SPI3.spi( - (spi_sck, spi_miso, hal::spi::NoMosi), - config, - design_parameters::ADC_DAC_SCK_MAX, - ccdr.peripheral.SPI3, - &ccdr.clocks, - ); - - Adc1Input::new( - spi, - dma_streams.3, - dma_streams.4, - dma_streams.5, - sampling_timer_channels.ch2, - shadow_sampling_timer_channels.ch2, - ) - }; - - (adc0, adc1) - }; - - 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 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, - design_parameters::ADC_DAC_SCK_MAX, - 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_MAX, - ccdr.peripheral.SPI5, - &ccdr.clocks, - ) - }; - - let dac0 = Dac0Output::new( - dac0_spi, - dma_streams.6, - sampling_timer_channels.ch3, - ); - let dac1 = Dac1Output::new( - dac1_spi, - dma_streams.7, - sampling_timer_channels.ch4, - ); - (dac0, dac1) - }; - - let mut fp_led_0 = gpiod.pd5.into_push_pull_output(); - let mut fp_led_1 = gpiod.pd6.into_push_pull_output(); - let mut fp_led_2 = gpiog.pg4.into_push_pull_output(); - let mut fp_led_3 = gpiod.pd12.into_push_pull_output(); - - fp_led_0.set_low().unwrap(); - fp_led_1.set_low().unwrap(); - fp_led_2.set_low().unwrap(); - fp_led_3.set_low().unwrap(); - - // 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, dds_output) = if pounder_pgood.is_high().unwrap() - { - let ad9959 = { - let qspi_interface = { - // Instantiate the QUADSPI pins and peripheral interface. - let qspi_pins = { - let _qspi_ncs = gpioc - .pc11 - .into_alternate_af9() - .set_speed(hal::gpio::Speed::VeryHigh); - - let clk = gpiob - .pb2 - .into_alternate_af9() - .set_speed(hal::gpio::Speed::VeryHigh); - let io0 = gpioe - .pe7 - .into_alternate_af10() - .set_speed(hal::gpio::Speed::VeryHigh); - let io1 = gpioe - .pe8 - .into_alternate_af10() - .set_speed(hal::gpio::Speed::VeryHigh); - let io2 = gpioe - .pe9 - .into_alternate_af10() - .set_speed(hal::gpio::Speed::VeryHigh); - let io3 = gpioe - .pe10 - .into_alternate_af10() - .set_speed(hal::gpio::Speed::VeryHigh); - - (clk, io0, io1, io2, io3) - }; - - let qspi = hal::qspi::Qspi::bank2( - dp.QUADSPI, - qspi_pins, - design_parameters::POUNDER_QSPI_FREQUENCY, - &ccdr.clocks, - ccdr.peripheral.QSPI, - ); - - pounder::QspiInterface::new(qspi).unwrap() - }; - - #[cfg(feature = "pounder_v1_1")] - let reset_pin = gpiog.pg6.into_push_pull_output(); - #[cfg(not(feature = "pounder_v1_1"))] - let reset_pin = gpioa.pa0.into_push_pull_output(); - - let mut io_update = gpiog.pg7.into_push_pull_output(); - - let ref_clk: hal::time::Hertz = - design_parameters::DDS_REF_CLK.into(); - - let ad9959 = ad9959::Ad9959::new( - qspi_interface, - reset_pin, - &mut io_update, - &mut delay, - ad9959::Mode::FourBitSerial, - ref_clk.0 as f32, - design_parameters::DDS_MULTIPLIER, - ) - .unwrap(); - - // Return IO_Update - gpiog.pg7 = io_update.into_analog(); - - ad9959 - }; - - let io_expander = { - let sda = gpiob.pb7.into_alternate_af4().set_open_drain(); - let scl = gpiob.pb8.into_alternate_af4().set_open_drain(); - let i2c1 = dp.I2C1.i2c( - (scl, sda), - 100.khz(), - ccdr.peripheral.I2C1, - &ccdr.clocks, - ); - mcp23017::MCP23017::default(i2c1).unwrap() - }; - - let spi = { - let spi_mosi = gpiod - .pd7 - .into_alternate_af5() - .set_speed(hal::gpio::Speed::VeryHigh); - let spi_miso = gpioa - .pa6 - .into_alternate_af5() - .set_speed(hal::gpio::Speed::VeryHigh); - let spi_sck = gpiog - .pg11 - .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, - }); - - // The maximum frequency of this SPI must be limited due to capacitance on the MISO - // line causing a long RC decay. - dp.SPI1.spi( - (spi_sck, spi_miso, spi_mosi), - config, - 5.mhz(), - ccdr.peripheral.SPI1, - &ccdr.clocks, - ) - }; - - let (adc1, adc2) = { - let (mut adc1, mut adc2) = hal::adc::adc12( - dp.ADC1, - dp.ADC2, - &mut delay, - ccdr.peripheral.ADC12, - &ccdr.clocks, - ); - - let adc1 = { - adc1.calibrate(); - adc1.enable() - }; - - let adc2 = { - adc2.calibrate(); - adc2.enable() - }; - - (adc1, adc2) - }; - - let adc1_in_p = gpiof.pf11.into_analog(); - let adc2_in_p = gpiof.pf14.into_analog(); - - let pounder_devices = pounder::PounderDevices::new( - io_expander, - 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 occurs after a fixed delay from the QSPI write. 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, - design_parameters::POUNDER_IO_UPDATE_DURATION, - design_parameters::POUNDER_IO_UPDATE_DELAY, - ); - - // Ensure that we have enough time for an IO-update every sample. - let sample_frequency = { - let timer_frequency: hal::time::Hertz = - design_parameters::TIMER_FREQUENCY.into(); - timer_frequency.0 as f32 / ADC_SAMPLE_TICKS as f32 - }; - - let sample_period = 1.0 / sample_frequency; - assert!( - sample_period - > design_parameters::POUNDER_IO_UPDATE_DELAY - ); - - hrtimer - }; - - let (qspi, config) = ad9959.freeze(); - DdsOutput::new(qspi, io_update_trigger, config) - }; - - (Some(pounder_devices), Some(dds_output)) - } else { - (None, None) - }; - - let mut eeprom_i2c = { - let sda = gpiof.pf0.into_alternate_af4().set_open_drain(); - let scl = gpiof.pf1.into_alternate_af4().set_open_drain(); - dp.I2C2.i2c( - (scl, sda), - 100.khz(), - ccdr.peripheral.I2C2, - &ccdr.clocks, - ) - }; - - // Configure ethernet pins. - { - // Reset the PHY before configuring pins. - let mut eth_phy_nrst = gpioe.pe3.into_push_pull_output(); - eth_phy_nrst.set_low().unwrap(); - delay.delay_us(200u8); - eth_phy_nrst.set_high().unwrap(); - let _rmii_ref_clk = gpioa - .pa1 - .into_alternate_af11() - .set_speed(hal::gpio::Speed::VeryHigh); - let _rmii_mdio = gpioa - .pa2 - .into_alternate_af11() - .set_speed(hal::gpio::Speed::VeryHigh); - let _rmii_mdc = gpioc - .pc1 - .into_alternate_af11() - .set_speed(hal::gpio::Speed::VeryHigh); - let _rmii_crs_dv = gpioa - .pa7 - .into_alternate_af11() - .set_speed(hal::gpio::Speed::VeryHigh); - let _rmii_rxd0 = gpioc - .pc4 - .into_alternate_af11() - .set_speed(hal::gpio::Speed::VeryHigh); - let _rmii_rxd1 = gpioc - .pc5 - .into_alternate_af11() - .set_speed(hal::gpio::Speed::VeryHigh); - let _rmii_tx_en = gpiob - .pb11 - .into_alternate_af11() - .set_speed(hal::gpio::Speed::VeryHigh); - let _rmii_txd0 = gpiob - .pb12 - .into_alternate_af11() - .set_speed(hal::gpio::Speed::VeryHigh); - let _rmii_txd1 = gpiog - .pg14 - .into_alternate_af11() - .set_speed(hal::gpio::Speed::VeryHigh); - } - - let mac_addr = match eeprom::read_eui48(&mut eeprom_i2c) { - Err(_) => { - info!("Could not read EEPROM, using default MAC address"); - net::wire::EthernetAddress([0x10, 0xE2, 0xD5, 0x00, 0x03, 0x00]) - } - Ok(raw_mac) => net::wire::EthernetAddress(raw_mac), - }; - - let (network_interface, eth_mac) = { - // Configure the ethernet controller - let (eth_dma, eth_mac) = unsafe { - ethernet::new_unchecked( - dp.ETHERNET_MAC, - dp.ETHERNET_MTL, - dp.ETHERNET_DMA, - &mut DES_RING, - mac_addr, - ccdr.peripheral.ETH1MAC, - &ccdr.clocks, - ) - }; - - // Reset and initialize the ethernet phy. - let mut lan8742a = - ethernet::phy::LAN8742A::new(eth_mac.set_phy_addr(0)); - lan8742a.phy_reset(); - lan8742a.phy_init(); - - unsafe { ethernet::enable_interrupt() }; - - let store = unsafe { &mut NET_STORE }; - - store.ip_addrs[0] = net::wire::IpCidr::new( - net::wire::IpAddress::v4(10, 0, 16, 99), - 24, - ); - - let default_v4_gw = Ipv4Address::new(10, 0, 16, 1); - let mut routes = Routes::new(&mut store.routes_storage[..]); - routes.add_default_ipv4_route(default_v4_gw).unwrap(); - - let neighbor_cache = - net::iface::NeighborCache::new(&mut store.neighbor_cache[..]); - - let interface = net::iface::EthernetInterfaceBuilder::new(eth_dma) - .ethernet_addr(mac_addr) - .neighbor_cache(neighbor_cache) - .ip_addrs(&mut store.ip_addrs[..]) - .routes(routes) - .finalize(); - - (interface, lan8742a) - }; - - cp.SCB.enable_icache(); - - // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); - // info!("Built on {}", build_info::BUILT_TIME_UTC); - // info!("{} {}", build_info::RUSTC_VERSION, build_info::TARGET); - - // Utilize the cycle counter for RTIC scheduling. - cp.DWT.enable_cycle_counter(); - - let mut input_stamper = { - let trigger = gpioa.pa3.into_alternate_af2(); - digital_input_stamper::InputStamper::new( - trigger, - timestamp_timer_channels.ch4, - ) - }; - - #[cfg(feature = "pounder_v1_1")] - let pounder_stamper = { - let dma2_streams = - hal::dma::dma::StreamsTuple::new(dp.DMA2, ccdr.peripheral.DMA2); - - let etr_pin = gpioa.pa0.into_alternate_af3(); - - // The frequency in the constructor is dont-care, as we will modify the period + clock - // source manually below. - let tim8 = - dp.TIM8.timer(1.khz(), ccdr.peripheral.TIM8, &ccdr.clocks); - let mut timestamp_timer = timers::PounderTimestampTimer::new(tim8); - - // Pounder is configured to generate a 500MHz reference clock, so a 125MHz sync-clock is - // output. As a result, dividing the 125MHz sync-clk provides a 31.25MHz tick rate for - // the timestamp timer. 31.25MHz corresponds with a 32ns tick rate. - timestamp_timer.set_external_clock(timers::Prescaler::Div4); - timestamp_timer.start(); - - // We want the pounder timestamp timer to overflow once per batch. - let tick_ratio = { - let sync_clk_mhz: f32 = design_parameters::DDS_SYSTEM_CLK.0 - as f32 - / design_parameters::DDS_SYNC_CLK_DIV as f32; - sync_clk_mhz / design_parameters::TIMER_FREQUENCY.0 as f32 - }; - - let period = (tick_ratio - * ADC_SAMPLE_TICKS as f32 - * SAMPLE_BUFFER_SIZE as f32) as u32 - / 4; - timestamp_timer.set_period_ticks((period - 1).try_into().unwrap()); - let tim8_channels = timestamp_timer.channels(); - - let stamper = pounder::timestamp::Timestamper::new( - timestamp_timer, - dma2_streams.0, - tim8_channels.ch1, - &mut sampling_timer, - etr_pin, - ); - - Some(stamper) - }; - - #[cfg(not(feature = "pounder_v1_1"))] - let pounder_stamper = None; + // Configure the microcontroller + let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); + + // Enable ADC/DAC events + stabilizer.adcs.0.start(); + stabilizer.adcs.1.start(); + stabilizer.dacs.0.start(); + stabilizer.dacs.1.start(); // Start sampling ADCs. - sampling_timer.start(); - timestamp_timer.start(); - input_stamper.start(); + stabilizer.adc_dac_timer.start(); init::LateResources { - afes: (afe0, afe1), - - adcs, - dacs, - input_stamper, - dds_output, - pounder: pounder_devices, - pounder_stamper, - - eeprom_i2c, - net_interface: network_interface, - eth_mac, - mac_addr, + afes: stabilizer.afes, + adcs: stabilizer.adcs, + dacs: stabilizer.dacs, + net_interface: stabilizer.net.interface, } } @@ -965,13 +93,8 @@ const APP: () = { /// /// Because the ADC and DAC operate at the same rate, these two constraints actually implement /// the same time bounds, meeting one also means the other is also met. - #[task(binds=DMA1_STR4, resources=[pounder_stamper, adcs, dacs, iir_state, iir_ch, dds_output, input_stamper], priority=2)] + #[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch], priority=2)] fn process(c: process::Context) { - if let Some(stamper) = c.resources.pounder_stamper { - let pounder_timestamps = stamper.acquire_buffer(); - info!("{:?}", pounder_timestamps); - } - let adc_samples = [ c.resources.adcs.0.acquire_buffer(), c.resources.adcs.1.acquire_buffer(), @@ -982,8 +105,6 @@ const APP: () = { c.resources.dacs.1.acquire_buffer(), ]; - let _timestamp = c.resources.input_stamper.latest_timestamp(); - for channel in 0..adc_samples.len() { for sample in 0..adc_samples[0].len() { let x = f32::from(adc_samples[channel][sample] as i16); @@ -999,34 +120,23 @@ const APP: () = { dac_samples[channel][sample] = y as u16 ^ 0x8000; } } - - if let Some(dds_output) = c.resources.dds_output { - let builder = dds_output.builder().update_channels( - &[pounder::Channel::Out0.into()], - Some(u32::MAX / 4), - None, - None, - ); - - builder.write_profile(); - } } - #[idle(resources=[net_interface, pounder, mac_addr, eth_mac, iir_state, iir_ch, afes])] + #[idle(resources=[net_interface, iir_state, iir_ch, afes])] fn idle(mut c: idle::Context) -> ! { let mut socket_set_entries: [_; 8] = Default::default(); let mut sockets = - net::socket::SocketSet::new(&mut socket_set_entries[..]); + smoltcp::socket::SocketSet::new(&mut socket_set_entries[..]); let mut rx_storage = [0; TCP_RX_BUFFER_SIZE]; let mut tx_storage = [0; TCP_TX_BUFFER_SIZE]; let tcp_handle = { let tcp_rx_buffer = - net::socket::TcpSocketBuffer::new(&mut rx_storage[..]); + smoltcp::socket::TcpSocketBuffer::new(&mut rx_storage[..]); let tcp_tx_buffer = - net::socket::TcpSocketBuffer::new(&mut tx_storage[..]); + smoltcp::socket::TcpSocketBuffer::new(&mut tx_storage[..]); let tcp_socket = - net::socket::TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + smoltcp::socket::TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); sockets.add(tcp_socket) }; @@ -1048,8 +158,8 @@ const APP: () = { { let socket = - &mut *sockets.get::(tcp_handle); - if socket.state() == net::socket::TcpState::CloseWait { + &mut *sockets.get::(tcp_handle); + if socket.state() == smoltcp::socket::TcpState::CloseWait { socket.close(); } else if !(socket.is_open() || socket.is_listening()) { socket @@ -1073,8 +183,7 @@ const APP: () = { Ok::(state) }), // "_b" means cascades 2nd IIR - "stabilizer/iir_b/state": (|| { - let state = c.resources.iir_state.lock(|iir_state| + "stabilizer/iir_b/state": (|| { let state = c.resources.iir_state.lock(|iir_state| server::Status { t: time, x0: iir_state[0][IIR_CASCADE_LENGTH-1][0], @@ -1134,11 +243,11 @@ const APP: () = { Ok::(req) }) }), - "stabilizer/afe0/gain": afe::Gain, (|gain| { + "stabilizer/afe0/gain": hardware::AfeGain, (|gain| { c.resources.afes.0.set_gain(gain); Ok::<(), ()>(()) }), - "stabilizer/afe1/gain": afe::Gain, (|gain| { + "stabilizer/afe1/gain": hardware::AfeGain, (|gain| { c.resources.afes.1.set_gain(gain); Ok::<(), ()>(()) }) @@ -1150,10 +259,10 @@ const APP: () = { let sleep = match c.resources.net_interface.poll( &mut sockets, - net::time::Instant::from_millis(time as i64), + smoltcp::time::Instant::from_millis(time as i64), ) { Ok(changed) => !changed, - Err(net::Error::Unrecognized) => true, + Err(smoltcp::Error::Unrecognized) => true, Err(e) => { info!("iface poll error: {:?}", e); true @@ -1168,7 +277,7 @@ const APP: () = { #[task(binds = ETH, priority = 1)] fn eth(_: eth::Context) { - unsafe { ethernet::interrupt_handler() } + unsafe { hal::ethernet::interrupt_handler() } } #[task(binds = SPI2, priority = 3)] @@ -1199,13 +308,3 @@ const APP: () = { fn SDMMC(); } }; - -#[exception] -fn HardFault(ef: &cortex_m_rt::ExceptionFrame) -> ! { - panic!("HardFault at {:#?}", ef); -} - -#[exception] -fn DefaultHandler(irqn: i16) { - panic!("Unhandled exception (IRQn = {})", irqn); -} diff --git a/src/server.rs b/src/server.rs index 2803805..2357988 100644 --- a/src/server.rs +++ b/src/server.rs @@ -7,7 +7,61 @@ use serde::{Deserialize, Serialize}; use serde_json_core::{de::from_slice, ser::to_string}; use super::iir; -use super::net; +use smoltcp as net; + +macro_rules! route_request { + ($request:ident, + readable_attributes: [$($read_attribute:tt: $getter:tt),*], + modifiable_attributes: [$($write_attribute:tt: $TYPE:ty, $setter:tt),*]) => { + match $request.req { + server::AccessRequest::Read => { + match $request.attribute { + $( + $read_attribute => { + #[allow(clippy::redundant_closure_call)] + let value = match $getter() { + Ok(data) => data, + Err(_) => return server::Response::error($request.attribute, + "Failed to read attribute"), + }; + + let encoded_data: String = match serde_json_core::to_string(&value) { + Ok(data) => data, + Err(_) => return server::Response::error($request.attribute, + "Failed to encode attribute value"), + }; + + server::Response::success($request.attribute, &encoded_data) + }, + )* + _ => server::Response::error($request.attribute, "Unknown attribute") + } + }, + server::AccessRequest::Write => { + match $request.attribute { + $( + $write_attribute => { + let new_value = match serde_json_core::from_str::<$TYPE>(&$request.value) { + Ok(data) => data, + Err(_) => return server::Response::error($request.attribute, + "Failed to decode value"), + }; + + #[allow(clippy::redundant_closure_call)] + match $setter(new_value) { + Ok(_) => server::Response::success($request.attribute, &$request.value), + Err(_) => server::Response::error($request.attribute, + "Failed to set attribute"), + } + } + )* + _ => server::Response::error($request.attribute, "Unknown attribute") + } + } + } + } +} + #[derive(Deserialize, Serialize, Debug)] pub enum AccessRequest { From 8dd72ae75e915e8c3e8490fb6599bf4452838265 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 18 Jan 2021 16:52:09 +0100 Subject: [PATCH 29/34] Reordering --- src/hardware/adc.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/hardware/adc.rs b/src/hardware/adc.rs index e3b8e56..188e436 100644 --- a/src/hardware/adc.rs +++ b/src/hardware/adc.rs @@ -1,10 +1,3 @@ -use super::timers; -use hal::dma::{ - config::Priority, - dma::{DMAReq, DmaConfig}, - traits::TargetAddress, - MemoryToPeripheral, PeripheralToMemory, Transfer, -}; ///! Stabilizer ADC management interface ///! ///! # Design @@ -83,6 +76,14 @@ use stm32h7xx_hal as hal; use crate::SAMPLE_BUFFER_SIZE; +use super::timers; +use hal::dma::{ + config::Priority, + dma::{DMAReq, DmaConfig}, + traits::TargetAddress, + MemoryToPeripheral, PeripheralToMemory, Transfer, +}; + // The following data is written by the timer ADC sample trigger into the SPI CR1 to start the // transfer. Data in AXI SRAM is not initialized on boot, so the contents are random. This value is // initialized during setup. From 6618e921febc58bb04cc584894de66d6e1fd0716 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 18 Jan 2021 16:55:56 +0100 Subject: [PATCH 30/34] Moving panic configuration --- src/hardware/mod.rs | 6 ++++++ src/main.rs | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hardware/mod.rs b/src/hardware/mod.rs index dbe8a9a..6434dbc 100644 --- a/src/hardware/mod.rs +++ b/src/hardware/mod.rs @@ -1,6 +1,12 @@ ///! Module for all hardware-specific setup of Stabilizer use stm32h7xx_hal as hal; +#[cfg(feature = "semihosting")] +use panic_semihosting as _; + +#[cfg(not(any(feature = "nightly", feature = "semihosting")))] +use panic_halt as _; + mod adc; mod afe; mod configuration; diff --git a/src/main.rs b/src/main.rs index 721e1f0..7ab7361 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,12 +5,6 @@ use stm32h7xx_hal as hal; -#[cfg(feature = "semihosting")] -use panic_semihosting as _; - -#[cfg(not(any(feature = "nightly", feature = "semihosting")))] -use panic_halt as _; - #[macro_use] extern crate log; From 573189bdd96b002d11dc152118d69599cb3dcf2d Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 18 Jan 2021 17:23:21 +0100 Subject: [PATCH 31/34] Fixing build --- src/hardware/configuration.rs | 23 +++++++++++++---------- src/hardware/pounder/timestamp.rs | 3 +++ src/main.rs | 2 +- src/server.rs | 1 - 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/hardware/configuration.rs b/src/hardware/configuration.rs index 2c11045..18ca67f 100644 --- a/src/hardware/configuration.rs +++ b/src/hardware/configuration.rs @@ -6,6 +6,9 @@ use crate::ADC_SAMPLE_TICKS; #[cfg(feature = "pounder_v1_1")] use crate::SAMPLE_BUFFER_SIZE; +#[cfg(feature = "pounder_v1_1")] +use core::convert::TryInto; + use smoltcp::{iface::Routes, wire::Ipv4Address}; use stm32h7xx_hal::{ @@ -541,16 +544,6 @@ pub fn setup( fp_led_2.set_low().unwrap(); fp_led_3.set_low().unwrap(); - let stabilizer = StabilizerDevices { - afes, - adcs, - dacs, - timestamper: input_stamper, - net: network_devices, - adc_dac_timer: sampling_timer, - timestamp_timer, - }; - // 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); @@ -805,6 +798,16 @@ pub fn setup( None }; + let stabilizer = StabilizerDevices { + afes, + adcs, + dacs, + timestamper: input_stamper, + net: network_devices, + adc_dac_timer: sampling_timer, + timestamp_timer, + }; + // info!("Version {} {}", build_info::PKG_VERSION, build_info::GIT_VERSION.unwrap()); // info!("Built on {}", build_info::BUILT_TIME_UTC); // info!("{} {}", build_info::RUSTC_VERSION, build_info::TARGET); diff --git a/src/hardware/pounder/timestamp.rs b/src/hardware/pounder/timestamp.rs index decbfd6..5ccf235 100644 --- a/src/hardware/pounder/timestamp.rs +++ b/src/hardware/pounder/timestamp.rs @@ -111,12 +111,14 @@ impl Timestamper { } /// Start the DMA transfer for collecting timestamps. + #[allow(dead_code)] pub fn start(&mut self) { self.transfer .start(|capture_channel| capture_channel.enable()); } /// Update the period of the underlying timestamp timer. + #[allow(dead_code)] pub fn update_period(&mut self, period: u16) { self.timer.set_period_ticks(period); } @@ -125,6 +127,7 @@ impl Timestamper { /// /// # Returns /// A reference to the underlying buffer that has been filled with timestamps. + #[allow(dead_code)] pub fn acquire_buffer(&mut self) -> &[u16; SAMPLE_BUFFER_SIZE] { // 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 diff --git a/src/main.rs b/src/main.rs index 7ab7361..3b86c37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,8 @@ const IIR_CASCADE_LENGTH: usize = 1; #[macro_use] mod server; mod hardware; -use hardware::{Adc0Input, Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1}; use dsp::iir; +use hardware::{Adc0Input, Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1}; const SCALE: f32 = ((1 << 15) - 1) as f32; diff --git a/src/server.rs b/src/server.rs index 2357988..f434060 100644 --- a/src/server.rs +++ b/src/server.rs @@ -62,7 +62,6 @@ macro_rules! route_request { } } - #[derive(Deserialize, Serialize, Debug)] pub enum AccessRequest { Read, From 26677063eaac28431c52403b929ca2a9739da1b0 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 20 Jan 2021 13:43:34 +0100 Subject: [PATCH 32/34] Adding support for multiple applications --- src/bin/dual-iir.rs | 295 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 16 +++ src/server.rs | 10 +- 3 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 src/bin/dual-iir.rs create mode 100644 src/lib.rs diff --git a/src/bin/dual-iir.rs b/src/bin/dual-iir.rs new file mode 100644 index 0000000..fd8f5e4 --- /dev/null +++ b/src/bin/dual-iir.rs @@ -0,0 +1,295 @@ +#![deny(warnings)] +#![no_std] +#![no_main] +#![cfg_attr(feature = "nightly", feature(core_intrinsics))] + +use stm32h7xx_hal as hal; + +#[macro_use] +extern crate log; + +use rtic::cyccnt::{Instant, U32Ext}; + +use heapless::{consts::*, String}; + +use stabilizer::{hardware, server}; + +use dsp::iir; +use hardware::{Adc0Input, Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1}; + +const SCALE: f32 = ((1 << 15) - 1) as f32; + +const TCP_RX_BUFFER_SIZE: usize = 8192; +const TCP_TX_BUFFER_SIZE: usize = 8192; + +// The number of cascaded IIR biquads per channel. Select 1 or 2! +const IIR_CASCADE_LENGTH: usize = 1; + +#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] +const APP: () = { + struct Resources { + afes: (AFE0, AFE1), + adcs: (Adc0Input, Adc1Input), + dacs: (Dac0Output, Dac1Output), + net_interface: hardware::Ethernet, + + // Format: iir_state[ch][cascade-no][coeff] + #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] + iir_state: [[iir::IIRState; IIR_CASCADE_LENGTH]; 2], + #[init([[iir::IIR { ba: [1., 0., 0., 0., 0.], y_offset: 0., y_min: -SCALE - 1., y_max: SCALE }; IIR_CASCADE_LENGTH]; 2])] + iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], + } + + #[init] + fn init(c: init::Context) -> init::LateResources { + // Configure the microcontroller + let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); + + // Enable ADC/DAC events + stabilizer.adcs.0.start(); + stabilizer.adcs.1.start(); + stabilizer.dacs.0.start(); + stabilizer.dacs.1.start(); + + // Start sampling ADCs. + stabilizer.adc_dac_timer.start(); + + init::LateResources { + afes: stabilizer.afes, + adcs: stabilizer.adcs, + dacs: stabilizer.dacs, + net_interface: stabilizer.net.interface, + } + } + + /// Main DSP processing routine for Stabilizer. + /// + /// # Note + /// Processing time for the DSP application code is bounded by the following constraints: + /// + /// DSP application code starts after the ADC has generated a batch of samples and must be + /// completed by the time the next batch of ADC samples has been acquired (plus the FIFO buffer + /// time). If this constraint is not met, firmware will panic due to an ADC input overrun. + /// + /// The DSP application code must also fill out the next DAC output buffer in time such that the + /// DAC can switch to it when it has completed the current buffer. If this constraint is not met + /// it's possible that old DAC codes will be generated on the output and the output samples will + /// be delayed by 1 batch. + /// + /// Because the ADC and DAC operate at the same rate, these two constraints actually implement + /// the same time bounds, meeting one also means the other is also met. + #[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch], priority=2)] + fn process(c: process::Context) { + let adc_samples = [ + c.resources.adcs.0.acquire_buffer(), + c.resources.adcs.1.acquire_buffer(), + ]; + + let dac_samples = [ + c.resources.dacs.0.acquire_buffer(), + c.resources.dacs.1.acquire_buffer(), + ]; + + for channel in 0..adc_samples.len() { + for sample in 0..adc_samples[0].len() { + let x = f32::from(adc_samples[channel][sample] as i16); + let mut y = x; + for i in 0..c.resources.iir_state[channel].len() { + y = c.resources.iir_ch[channel][i] + .update(&mut c.resources.iir_state[channel][i], y); + } + // Note(unsafe): The filter limits ensure that the value is in range. + // The truncation introduces 1/2 LSB distortion. + let y = unsafe { y.to_int_unchecked::() }; + // Convert to DAC code + dac_samples[channel][sample] = y as u16 ^ 0x8000; + } + } + } + + #[idle(resources=[net_interface, iir_state, iir_ch, afes])] + fn idle(mut c: idle::Context) -> ! { + let mut socket_set_entries: [_; 8] = Default::default(); + let mut sockets = + smoltcp::socket::SocketSet::new(&mut socket_set_entries[..]); + + let mut rx_storage = [0; TCP_RX_BUFFER_SIZE]; + let mut tx_storage = [0; TCP_TX_BUFFER_SIZE]; + let tcp_handle = { + let tcp_rx_buffer = + smoltcp::socket::TcpSocketBuffer::new(&mut rx_storage[..]); + let tcp_tx_buffer = + smoltcp::socket::TcpSocketBuffer::new(&mut tx_storage[..]); + let tcp_socket = + smoltcp::socket::TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + sockets.add(tcp_socket) + }; + + let mut server = server::Server::new(); + + let mut time = 0u32; + let mut next_ms = Instant::now(); + + // TODO: Replace with reference to CPU clock from CCDR. + next_ms += 400_000.cycles(); + + loop { + let tick = Instant::now() > next_ms; + + if tick { + next_ms += 400_000.cycles(); + time += 1; + } + + { + let socket = + &mut *sockets.get::(tcp_handle); + if socket.state() == smoltcp::socket::TcpState::CloseWait { + socket.close(); + } else if !(socket.is_open() || socket.is_listening()) { + socket + .listen(1235) + .unwrap_or_else(|e| warn!("TCP listen error: {:?}", e)); + } else { + server.poll(socket, |req| { + info!("Got request: {:?}", req); + stabilizer::route_request!(req, + readable_attributes: [ + "stabilizer/iir/state": (|| { + let state = c.resources.iir_state.lock(|iir_state| + server::Status { + t: time, + x0: iir_state[0][0][0], + y0: iir_state[0][0][2], + x1: iir_state[1][0][0], + y1: iir_state[1][0][2], + }); + + Ok::(state) + }), + // "_b" means cascades 2nd IIR + "stabilizer/iir_b/state": (|| { let state = c.resources.iir_state.lock(|iir_state| + server::Status { + t: time, + x0: iir_state[0][IIR_CASCADE_LENGTH-1][0], + y0: iir_state[0][IIR_CASCADE_LENGTH-1][2], + x1: iir_state[1][IIR_CASCADE_LENGTH-1][0], + y1: iir_state[1][IIR_CASCADE_LENGTH-1][2], + }); + + Ok::(state) + }), + "stabilizer/afe0/gain": (|| c.resources.afes.0.get_gain()), + "stabilizer/afe1/gain": (|| c.resources.afes.1.get_gain()) + ], + + modifiable_attributes: [ + "stabilizer/iir0/state": server::IirRequest, (|req: server::IirRequest| { + c.resources.iir_ch.lock(|iir_ch| { + if req.channel > 1 { + return Err(()); + } + + iir_ch[req.channel as usize][0] = req.iir; + + Ok::(req) + }) + }), + "stabilizer/iir1/state": server::IirRequest, (|req: server::IirRequest| { + c.resources.iir_ch.lock(|iir_ch| { + if req.channel > 1 { + return Err(()); + } + + iir_ch[req.channel as usize][0] = req.iir; + + Ok::(req) + }) + }), + "stabilizer/iir_b0/state": server::IirRequest, (|req: server::IirRequest| { + c.resources.iir_ch.lock(|iir_ch| { + if req.channel > 1 { + return Err(()); + } + + iir_ch[req.channel as usize][IIR_CASCADE_LENGTH-1] = req.iir; + + Ok::(req) + }) + }), + "stabilizer/iir_b1/state": server::IirRequest,(|req: server::IirRequest| { + c.resources.iir_ch.lock(|iir_ch| { + if req.channel > 1 { + return Err(()); + } + + iir_ch[req.channel as usize][IIR_CASCADE_LENGTH-1] = req.iir; + + Ok::(req) + }) + }), + "stabilizer/afe0/gain": hardware::AfeGain, (|gain| { + c.resources.afes.0.set_gain(gain); + Ok::<(), ()>(()) + }), + "stabilizer/afe1/gain": hardware::AfeGain, (|gain| { + c.resources.afes.1.set_gain(gain); + Ok::<(), ()>(()) + }) + ] + ) + }); + } + } + + let sleep = match c.resources.net_interface.poll( + &mut sockets, + smoltcp::time::Instant::from_millis(time as i64), + ) { + Ok(changed) => !changed, + Err(smoltcp::Error::Unrecognized) => true, + Err(e) => { + info!("iface poll error: {:?}", e); + true + } + }; + + if sleep { + cortex_m::asm::wfi(); + } + } + } + + #[task(binds = ETH, priority = 1)] + fn eth(_: eth::Context) { + unsafe { hal::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 + fn DCMI(); + fn JPEG(); + fn SDMMC(); + } +}; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a51024a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,16 @@ +#![no_std] + +pub mod server; + +#[macro_use] +extern crate log; + +pub mod hardware; + +// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is +// equal to 10ns per tick. +// Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz +const ADC_SAMPLE_TICKS: u16 = 256; + +// The desired ADC sample processing buffer size. +const SAMPLE_BUFFER_SIZE: usize = 8; diff --git a/src/server.rs b/src/server.rs index f434060..5e6950d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,14 +1,12 @@ -use heapless::{consts::*, String, Vec}; - use core::fmt::Write; - +use heapless::{consts::*, String, Vec}; use serde::{Deserialize, Serialize}; - use serde_json_core::{de::from_slice, ser::to_string}; - -use super::iir; use smoltcp as net; +use dsp::iir; + +#[macro_export] macro_rules! route_request { ($request:ident, readable_attributes: [$($read_attribute:tt: $getter:tt),*], From 86355c9c5d4e8c6cac5d786cb61fafba61b829da Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 20 Jan 2021 13:44:16 +0100 Subject: [PATCH 33/34] Removing main.rs --- src/main.rs | 304 ---------------------------------------------------- 1 file changed, 304 deletions(-) delete mode 100644 src/main.rs diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 3b86c37..0000000 --- a/src/main.rs +++ /dev/null @@ -1,304 +0,0 @@ -#![deny(warnings)] -#![no_std] -#![no_main] -#![cfg_attr(feature = "nightly", feature(core_intrinsics))] - -use stm32h7xx_hal as hal; - -#[macro_use] -extern crate log; - -use rtic::cyccnt::{Instant, U32Ext}; - -use heapless::{consts::*, String}; - -// The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is -// equal to 10ns per tick. -// Currently, the sample rate is equal to: Fsample = 100/256 MHz = 390.625 KHz -const ADC_SAMPLE_TICKS: u16 = 256; - -// The desired ADC sample processing buffer size. -const SAMPLE_BUFFER_SIZE: usize = 8; - -// The number of cascaded IIR biquads per channel. Select 1 or 2! -const IIR_CASCADE_LENGTH: usize = 1; - -#[macro_use] -mod server; -mod hardware; -use dsp::iir; -use hardware::{Adc0Input, Adc1Input, Dac0Output, Dac1Output, AFE0, AFE1}; - -const SCALE: f32 = ((1 << 15) - 1) as f32; - -const TCP_RX_BUFFER_SIZE: usize = 8192; -const TCP_TX_BUFFER_SIZE: usize = 8192; - -#[rtic::app(device = stm32h7xx_hal::stm32, peripherals = true, monotonic = rtic::cyccnt::CYCCNT)] -const APP: () = { - struct Resources { - afes: (AFE0, AFE1), - adcs: (Adc0Input, Adc1Input), - dacs: (Dac0Output, Dac1Output), - net_interface: hardware::Ethernet, - - // Format: iir_state[ch][cascade-no][coeff] - #[init([[[0.; 5]; IIR_CASCADE_LENGTH]; 2])] - iir_state: [[iir::IIRState; IIR_CASCADE_LENGTH]; 2], - #[init([[iir::IIR { ba: [1., 0., 0., 0., 0.], y_offset: 0., y_min: -SCALE - 1., y_max: SCALE }; IIR_CASCADE_LENGTH]; 2])] - iir_ch: [[iir::IIR; IIR_CASCADE_LENGTH]; 2], - } - - #[init] - fn init(c: init::Context) -> init::LateResources { - // Configure the microcontroller - let (mut stabilizer, _pounder) = hardware::setup(c.core, c.device); - - // Enable ADC/DAC events - stabilizer.adcs.0.start(); - stabilizer.adcs.1.start(); - stabilizer.dacs.0.start(); - stabilizer.dacs.1.start(); - - // Start sampling ADCs. - stabilizer.adc_dac_timer.start(); - - init::LateResources { - afes: stabilizer.afes, - adcs: stabilizer.adcs, - dacs: stabilizer.dacs, - net_interface: stabilizer.net.interface, - } - } - - /// Main DSP processing routine for Stabilizer. - /// - /// # Note - /// Processing time for the DSP application code is bounded by the following constraints: - /// - /// DSP application code starts after the ADC has generated a batch of samples and must be - /// completed by the time the next batch of ADC samples has been acquired (plus the FIFO buffer - /// time). If this constraint is not met, firmware will panic due to an ADC input overrun. - /// - /// The DSP application code must also fill out the next DAC output buffer in time such that the - /// DAC can switch to it when it has completed the current buffer. If this constraint is not met - /// it's possible that old DAC codes will be generated on the output and the output samples will - /// be delayed by 1 batch. - /// - /// Because the ADC and DAC operate at the same rate, these two constraints actually implement - /// the same time bounds, meeting one also means the other is also met. - #[task(binds=DMA1_STR4, resources=[adcs, dacs, iir_state, iir_ch], priority=2)] - fn process(c: process::Context) { - let adc_samples = [ - c.resources.adcs.0.acquire_buffer(), - c.resources.adcs.1.acquire_buffer(), - ]; - - let dac_samples = [ - c.resources.dacs.0.acquire_buffer(), - c.resources.dacs.1.acquire_buffer(), - ]; - - for channel in 0..adc_samples.len() { - for sample in 0..adc_samples[0].len() { - let x = f32::from(adc_samples[channel][sample] as i16); - let mut y = x; - for i in 0..c.resources.iir_state[channel].len() { - y = c.resources.iir_ch[channel][i] - .update(&mut c.resources.iir_state[channel][i], y); - } - // Note(unsafe): The filter limits ensure that the value is in range. - // The truncation introduces 1/2 LSB distortion. - let y = unsafe { y.to_int_unchecked::() }; - // Convert to DAC code - dac_samples[channel][sample] = y as u16 ^ 0x8000; - } - } - } - - #[idle(resources=[net_interface, iir_state, iir_ch, afes])] - fn idle(mut c: idle::Context) -> ! { - let mut socket_set_entries: [_; 8] = Default::default(); - let mut sockets = - smoltcp::socket::SocketSet::new(&mut socket_set_entries[..]); - - let mut rx_storage = [0; TCP_RX_BUFFER_SIZE]; - let mut tx_storage = [0; TCP_TX_BUFFER_SIZE]; - let tcp_handle = { - let tcp_rx_buffer = - smoltcp::socket::TcpSocketBuffer::new(&mut rx_storage[..]); - let tcp_tx_buffer = - smoltcp::socket::TcpSocketBuffer::new(&mut tx_storage[..]); - let tcp_socket = - smoltcp::socket::TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); - sockets.add(tcp_socket) - }; - - let mut server = server::Server::new(); - - let mut time = 0u32; - let mut next_ms = Instant::now(); - - // TODO: Replace with reference to CPU clock from CCDR. - next_ms += 400_000.cycles(); - - loop { - let tick = Instant::now() > next_ms; - - if tick { - next_ms += 400_000.cycles(); - time += 1; - } - - { - let socket = - &mut *sockets.get::(tcp_handle); - if socket.state() == smoltcp::socket::TcpState::CloseWait { - socket.close(); - } else if !(socket.is_open() || socket.is_listening()) { - socket - .listen(1235) - .unwrap_or_else(|e| warn!("TCP listen error: {:?}", e)); - } else { - server.poll(socket, |req| { - info!("Got request: {:?}", req); - route_request!(req, - readable_attributes: [ - "stabilizer/iir/state": (|| { - let state = c.resources.iir_state.lock(|iir_state| - server::Status { - t: time, - x0: iir_state[0][0][0], - y0: iir_state[0][0][2], - x1: iir_state[1][0][0], - y1: iir_state[1][0][2], - }); - - Ok::(state) - }), - // "_b" means cascades 2nd IIR - "stabilizer/iir_b/state": (|| { let state = c.resources.iir_state.lock(|iir_state| - server::Status { - t: time, - x0: iir_state[0][IIR_CASCADE_LENGTH-1][0], - y0: iir_state[0][IIR_CASCADE_LENGTH-1][2], - x1: iir_state[1][IIR_CASCADE_LENGTH-1][0], - y1: iir_state[1][IIR_CASCADE_LENGTH-1][2], - }); - - Ok::(state) - }), - "stabilizer/afe0/gain": (|| c.resources.afes.0.get_gain()), - "stabilizer/afe1/gain": (|| c.resources.afes.1.get_gain()) - ], - - modifiable_attributes: [ - "stabilizer/iir0/state": server::IirRequest, (|req: server::IirRequest| { - c.resources.iir_ch.lock(|iir_ch| { - if req.channel > 1 { - return Err(()); - } - - iir_ch[req.channel as usize][0] = req.iir; - - Ok::(req) - }) - }), - "stabilizer/iir1/state": server::IirRequest, (|req: server::IirRequest| { - c.resources.iir_ch.lock(|iir_ch| { - if req.channel > 1 { - return Err(()); - } - - iir_ch[req.channel as usize][0] = req.iir; - - Ok::(req) - }) - }), - "stabilizer/iir_b0/state": server::IirRequest, (|req: server::IirRequest| { - c.resources.iir_ch.lock(|iir_ch| { - if req.channel > 1 { - return Err(()); - } - - iir_ch[req.channel as usize][IIR_CASCADE_LENGTH-1] = req.iir; - - Ok::(req) - }) - }), - "stabilizer/iir_b1/state": server::IirRequest,(|req: server::IirRequest| { - c.resources.iir_ch.lock(|iir_ch| { - if req.channel > 1 { - return Err(()); - } - - iir_ch[req.channel as usize][IIR_CASCADE_LENGTH-1] = req.iir; - - Ok::(req) - }) - }), - "stabilizer/afe0/gain": hardware::AfeGain, (|gain| { - c.resources.afes.0.set_gain(gain); - Ok::<(), ()>(()) - }), - "stabilizer/afe1/gain": hardware::AfeGain, (|gain| { - c.resources.afes.1.set_gain(gain); - Ok::<(), ()>(()) - }) - ] - ) - }); - } - } - - let sleep = match c.resources.net_interface.poll( - &mut sockets, - smoltcp::time::Instant::from_millis(time as i64), - ) { - Ok(changed) => !changed, - Err(smoltcp::Error::Unrecognized) => true, - Err(e) => { - info!("iface poll error: {:?}", e); - true - } - }; - - if sleep { - cortex_m::asm::wfi(); - } - } - } - - #[task(binds = ETH, priority = 1)] - fn eth(_: eth::Context) { - unsafe { hal::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 - fn DCMI(); - fn JPEG(); - fn SDMMC(); - } -}; From 4d0b1b55660d3a7614d7a8decb1962dff8a2c669 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 20 Jan 2021 13:44:53 +0100 Subject: [PATCH 34/34] Reordering lib.rs --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a51024a..b685f7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,10 @@ #![no_std] -pub mod server; - #[macro_use] extern crate log; pub mod hardware; +pub mod server; // The number of ticks in the ADC sampling timer. The timer runs at 100MHz, so the step size is // equal to 10ns per tick.