From d1b7efad48e4925b18f3439fe68840b434051413 Mon Sep 17 00:00:00 2001 From: Matt Huszagh Date: Sat, 28 Nov 2020 16:04:22 -0800 Subject: [PATCH] dsp: replace in_phase and quadrature with Complex --- dsp/src/complex.rs | 21 +++ dsp/src/lib.rs | 1 + dsp/src/lockin.rs | 333 +++++++++++++++++------------------ dsp/tests/lockin_low_pass.rs | 35 ++-- 4 files changed, 201 insertions(+), 189 deletions(-) create mode 100644 dsp/src/complex.rs diff --git a/dsp/src/complex.rs b/dsp/src/complex.rs new file mode 100644 index 0000000..3fb998f --- /dev/null +++ b/dsp/src/complex.rs @@ -0,0 +1,21 @@ +use core::cmp::PartialEq; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Complex { + pub re: f32, + pub im: f32, +} + +impl Complex { + pub fn new(re: f32, im: f32) -> Self { + Complex { re: re, im: im } + } + + pub fn arg(&self) -> f32 { + libm::atan2f(self.im, self.re) + } + + pub fn abs(&self) -> f32 { + libm::sqrtf([self.re, self.im].iter().map(|i| i * i).sum()) + } +} diff --git a/dsp/src/lib.rs b/dsp/src/lib.rs index ffb021c..41e8a52 100644 --- a/dsp/src/lib.rs +++ b/dsp/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(test), no_std)] #![cfg_attr(feature = "nightly", feature(asm, core_intrinsics))] +pub mod complex; pub mod iir; pub mod lockin; pub mod pll; diff --git a/dsp/src/lockin.rs b/dsp/src/lockin.rs index 321aceb..4dcfe69 100644 --- a/dsp/src/lockin.rs +++ b/dsp/src/lockin.rs @@ -2,25 +2,15 @@ //! //! Lock-in processing is performed through a combination of the //! following modular processing blocks: demodulation, filtering, -//! decimation and computing the magnitude and phase from the in-phase -//! and quadrature signals. These processing blocks are mutually -//! independent. +//! 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. There are two -//! copies of this signal. The first copy is in-phase with the -//! reference signal (before any optional phase shifting). The second -//! is 90 degrees out of phase (in quadrature) with the first -//! copy. The demodulation signals are used to demodulate the ADC +//! optionally frequency scaled and phase shifted. This is a complex +//! signal. The demodulation signals are used to demodulate the ADC //! sampled signal. -//! * _in-phase_ and _quadrature_ - These terms are used to delineate -//! between the two components of the demodulation signal and the -//! resulting two signals at any step downstream of the demodulation -//! step. The in-phase signal is in-phase with the reference signal -//! prior to any phase shifts. The quadrature signal is 90 degrees out -//! of phase with the in-phase 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 @@ -44,24 +34,25 @@ //! //! * `demodulate` - Computes the phase of the demodulation signal //! corresponding to each ADC sample, uses this phase to compute the -//! in-phase and quadrature demodulation signals, and multiplies these -//! demodulation signals 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 in-phase and -//! quadrature signals. This is commonly performed on the in-phase and -//! quadrature components 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 the in-phase and quadrature signals -//! to reduce the load on the DAC output. It does not require any -//! state information and is therefore a normal function. +//! 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::complex::Complex; use super::iir::{IIRState, IIR}; use core::f32::consts::PI; @@ -139,8 +130,7 @@ impl Lockin { } } - /// Demodulate an input signal with in-phase and quadrature - /// reference signals. + /// Demodulate an input signal with the complex reference signal. /// /// # Arguments /// @@ -151,9 +141,9 @@ impl Lockin { /// /// # Returns /// - /// The demodulated in-phase and quadrature signals as an - /// `Option`. When there are an insufficient number of timestamps - /// to perform processing, `None` is returned. + /// The demodulated complex signal as a `Result`. When there are + /// an insufficient number of timestamps to perform processing, + /// `Err` is returned. /// /// # Assumptions /// @@ -165,10 +155,7 @@ impl Lockin { &mut self, adc_samples: &[i16], timestamps: &[u16], - ) -> Result< - ([f32; ADC_SAMPLE_BUFFER_SIZE], [f32; ADC_SAMPLE_BUFFER_SIZE]), - &str, - > { + ) -> Result<[Complex; ADC_SAMPLE_BUFFER_SIZE], &str> { // update old timestamps for new ADC batch let sample_period = self.sample_period as i32; self.timestamps.iter_mut().for_each(|t| match *t { @@ -200,14 +187,12 @@ impl Lockin { // compute ADC sample phases, sines/cosines and demodulate let reference_period = self.timestamps[0].unwrap() - self.timestamps[1].unwrap(); - let mut in_phase = [0f32; ADC_SAMPLE_BUFFER_SIZE]; - let mut quadrature = [0f32; ADC_SAMPLE_BUFFER_SIZE]; - in_phase + let mut signal = [Complex::new(0., 0.); ADC_SAMPLE_BUFFER_SIZE]; + signal .iter_mut() - .zip(quadrature.iter_mut()) .zip(adc_samples.iter()) .enumerate() - .for_each(|(n, ((i, q), sample))| { + .for_each(|(n, (s, sample))| { let integer_phase: i32 = (n as i32 * self.sample_period as i32 - self.timestamps[0].unwrap()) * self.harmonic as i32; @@ -215,104 +200,90 @@ impl Lockin { + 2. * PI * integer_phase as f32 / reference_period as f32; let (sine, cosine) = libm::sincosf(phase); let sample = *sample as f32; - *i = sine * sample; - *q = cosine * sample; + s.re = sine * sample; + s.im = cosine * sample; }); - Ok((in_phase, quadrature)) + Ok(signal) } - /// Filter the in-phase and quadrature signals using the supplied - /// biquad IIR. The signal arrays are modified in place. + /// Filter the complex signal using the supplied biquad IIR. The + /// signal array is modified in place. /// /// # Arguments /// - /// * `in_phase` - In-phase signal. - /// * `quadrature` - Quadrature signal. - pub fn filter(&mut self, in_phase: &mut [f32], quadrature: &mut [f32]) { - in_phase - .iter_mut() - .zip(quadrature.iter_mut()) - .for_each(|(i, q)| { - *i = self.iir.update(&mut self.iirstate[0], *i); - *q = self.iir.update(&mut self.iirstate[1], *q); - }); + /// * `signal` - Complex signal to filter. + pub fn filter(&mut self, signal: &mut [Complex]) { + signal.iter_mut().for_each(|s| { + s.re = self.iir.update(&mut self.iirstate[0], s.re); + s.im = self.iir.update(&mut self.iirstate[1], s.im); + }); } } -/// Decimate the in-phase and quadrature signals to -/// `DECIMATED_BUFFER_SIZE`. The ratio of `ADC_SAMPLE_BUFFER_SIZE` to -/// `DECIMATED_BUFFER_SIZE` must be a power of 2. +/// 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. /// /// # Arguments /// -/// * `in_phase` - In-phase signal. -/// * `quadrature` - Quadrature signal. +/// * `signal` - Complex signal to decimate. /// /// # Returns /// -/// The decimated in-phase and quadrature signals. +/// The decimated signal. pub fn decimate( - in_phase: [f32; ADC_SAMPLE_BUFFER_SIZE], - quadrature: [f32; ADC_SAMPLE_BUFFER_SIZE], -) -> ([f32; DECIMATED_BUFFER_SIZE], [f32; DECIMATED_BUFFER_SIZE]) { + 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 ); - let mut in_phase_decimated = [0f32; DECIMATED_BUFFER_SIZE]; - let mut quadrature_decimated = [0f32; DECIMATED_BUFFER_SIZE]; + let mut signal_decimated = [Complex::new(0., 0.); DECIMATED_BUFFER_SIZE]; - in_phase_decimated + signal_decimated .iter_mut() - .zip(quadrature_decimated.iter_mut()) - .zip( - in_phase - .iter() - .step_by(n_k) - .zip(quadrature.iter().step_by(n_k)), - ) - .for_each(|((i_d, q_d), (i, q))| { - *i_d = *i; - *q_d = *q; + .zip(signal.iter().step_by(n_k)) + .for_each(|(s_d, s)| { + s_d.re = s.re; + s_d.im = s.im; }); - (in_phase_decimated, quadrature_decimated) + signal_decimated } -/// Compute the magnitude and phase from the in-phase and quadrature -/// signals. The in-phase and quadrature arrays are modified in place. +/// Compute the magnitude and phase from the complex signal. The +/// signal array is modified in place. /// /// # Arguments /// -/// * `in_phase` - In-phase signal. -/// * `quadrature` - Quadrature signal. -pub fn magnitude_phase(in_phase: &mut [f32], quadrature: &mut [f32]) { - in_phase - .iter_mut() - .zip(quadrature.iter_mut()) - .for_each(|(i, q)| { - let new_i = libm::sqrtf([*i, *q].iter().map(|i| i * i).sum()); - let new_q = libm::atan2f(*q, *i); - *i = new_i; - *q = new_q; - }); +/// * `signal` - Complex signal to decimate. +pub fn magnitude_phase(signal: &mut [Complex]) { + signal.iter_mut().for_each(|s| { + let new_i = s.abs(); + let new_q = s.arg(); + s.re = new_i; + s.im = new_q; + }); } #[cfg(test)] mod tests { use super::*; - extern crate std; fn f32_is_close(a: f32, b: f32) -> bool { (a - b).abs() <= a.abs().max(b.abs()) * f32::EPSILON } - fn f32_array_is_close(a: &[f32], b: &[f32]) -> bool { + fn complex_is_close(a: Complex, b: Complex) -> bool { + f32_is_close(a.re, b.re) && f32_is_close(a.im, b.im) + } + + fn complex_array_is_close(a: &[Complex], b: &[Complex]) -> bool { let mut result: bool = true; a.iter().zip(b.iter()).for_each(|(i, j)| { - result &= f32_is_close(*i, *j); + result &= complex_is_close(*i, *j); }); result } @@ -327,16 +298,30 @@ mod tests { <= a.abs().max(b.abs()) * relative_tolerance + fixed_tolerance } - fn array_within_tolerance( - a: &[f32], - b: &[f32], + fn complex_within_tolerance( + a: Complex, + b: Complex, + relative_tolerance: f32, + fixed_tolerance: f32, + ) -> bool { + within_tolerance(a.re, b.re, relative_tolerance, fixed_tolerance) + && within_tolerance(a.im, b.im, relative_tolerance, fixed_tolerance) + } + + fn complex_array_within_tolerance( + a: &[Complex], + b: &[Complex], relative_tolerance: f32, fixed_tolerance: f32, ) -> bool { let mut result: bool = true; a.iter().zip(b.iter()).for_each(|(i, j)| { - result &= - within_tolerance(*i, *j, relative_tolerance, fixed_tolerance); + result &= complex_within_tolerance( + *i, + *j, + relative_tolerance, + fixed_tolerance, + ); }); result } @@ -354,75 +339,93 @@ mod tests { #[test] fn magnitude_phase_length_1_quadrant_1() { - let mut in_phase: [f32; 1] = [1.]; - let mut quadrature: [f32; 1] = [1.]; - magnitude_phase(&mut in_phase, &mut quadrature); - assert!(f32_array_is_close(&in_phase, &[2_f32.sqrt()])); - assert!(f32_array_is_close(&quadrature, &[PI / 4.])); + let mut signal: [Complex; 1] = [Complex::new(1., 1.)]; + magnitude_phase(&mut signal); + assert!(complex_array_is_close( + &signal, + &[Complex::new(2_f32.sqrt(), PI / 4.)] + )); - in_phase = [3_f32.sqrt() / 2.]; - quadrature = [1. / 2.]; - magnitude_phase(&mut in_phase, &mut quadrature); - assert!(f32_array_is_close(&in_phase, &[1_f32])); - assert!(f32_array_is_close(&quadrature, &[PI / 6.])); + signal = [Complex::new(3_f32.sqrt() / 2., 1. / 2.)]; + magnitude_phase(&mut signal); + assert!(complex_array_is_close( + &signal, + &[Complex::new(1., PI / 6.)] + )); } #[test] fn magnitude_phase_length_1_quadrant_2() { - let mut in_phase: [f32; 1] = [-1.]; - let mut quadrature: [f32; 1] = [1.]; - magnitude_phase(&mut in_phase, &mut quadrature); - assert!(f32_array_is_close(&in_phase, &[2_f32.sqrt()])); - assert!(f32_array_is_close(&quadrature, &[3. * PI / 4.])); + let mut signal = [Complex::new(-1., 1.)]; + magnitude_phase(&mut signal); + assert!(complex_array_is_close( + &signal, + &[Complex::new(2_f32.sqrt(), 3. * PI / 4.)] + )); - in_phase = [-1. / 2.]; - quadrature = [3_f32.sqrt() / 2.]; - magnitude_phase(&mut in_phase, &mut quadrature); - assert!(f32_array_is_close(&in_phase, &[1_f32])); - assert!(f32_array_is_close(&quadrature, &[2. * PI / 3.])); + signal = [Complex::new(-1. / 2., 3_f32.sqrt() / 2.)]; + magnitude_phase(&mut signal); + assert!(complex_array_is_close( + &signal, + &[Complex::new(1_f32, 2. * PI / 3.)] + )); } #[test] fn magnitude_phase_length_1_quadrant_3() { - let mut in_phase: [f32; 1] = [-1. / 2_f32.sqrt()]; - let mut quadrature: [f32; 1] = [-1. / 2_f32.sqrt()]; - magnitude_phase(&mut in_phase, &mut quadrature); - assert!(f32_array_is_close(&in_phase, &[1_f32.sqrt()])); - assert!(f32_array_is_close(&quadrature, &[-3. * PI / 4.])); + let mut signal = [Complex::new(-1. / 2_f32.sqrt(), -1. / 2_f32.sqrt())]; + magnitude_phase(&mut signal); + assert!(complex_array_is_close( + &signal, + &[Complex::new(1_f32.sqrt(), -3. * PI / 4.)] + )); - in_phase = [-1. / 2.]; - quadrature = [-2_f32.sqrt()]; - magnitude_phase(&mut in_phase, &mut quadrature); - assert!(f32_array_is_close(&in_phase, &[(3. / 2.) as f32])); - assert!(f32_array_is_close(&quadrature, &[-1.91063323625 as f32])); + signal = [Complex::new(-1. / 2., -2_f32.sqrt())]; + magnitude_phase(&mut signal); + assert!(complex_array_is_close( + &signal, + &[Complex::new((3. / 2.) as f32, -1.91063323625 as f32)] + )); } #[test] fn magnitude_phase_length_1_quadrant_4() { - let mut in_phase: [f32; 1] = [1. / 2_f32.sqrt()]; - let mut quadrature: [f32; 1] = [-1. / 2_f32.sqrt()]; - magnitude_phase(&mut in_phase, &mut quadrature); - assert!(f32_array_is_close(&in_phase, &[1_f32.sqrt()])); - assert!(f32_array_is_close(&quadrature, &[-1. * PI / 4.])); + let mut signal = [Complex::new(1. / 2_f32.sqrt(), -1. / 2_f32.sqrt())]; + magnitude_phase(&mut signal); + assert!(complex_array_is_close( + &signal, + &[Complex::new(1_f32.sqrt(), -1. * PI / 4.)] + )); - in_phase = [3_f32.sqrt() / 2.]; - quadrature = [-1. / 2.]; - magnitude_phase(&mut in_phase, &mut quadrature); - assert!(f32_array_is_close(&in_phase, &[1_f32])); - assert!(f32_array_is_close(&quadrature, &[-PI / 6.])); + signal = [Complex::new(3_f32.sqrt() / 2., -1. / 2.)]; + magnitude_phase(&mut signal); + assert!(complex_array_is_close( + &signal, + &[Complex::new(1_f32, -PI / 6.)] + )); } #[test] fn decimate_sample_16_decimated_1() { - let in_phase: [f32; ADC_SAMPLE_BUFFER_SIZE] = [ - 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, - 1.3, 1.4, 1.5, + let signal: [Complex; ADC_SAMPLE_BUFFER_SIZE] = [ + Complex::new(0.0, 1.6), + Complex::new(0.1, 1.7), + Complex::new(0.2, 1.8), + Complex::new(0.3, 1.9), + Complex::new(0.4, 2.0), + Complex::new(0.5, 2.1), + Complex::new(0.6, 2.2), + Complex::new(0.7, 2.3), + Complex::new(0.8, 2.4), + Complex::new(0.9, 2.5), + Complex::new(1.0, 2.6), + Complex::new(1.1, 2.7), + Complex::new(1.2, 2.8), + Complex::new(1.3, 2.9), + Complex::new(1.4, 3.0), + Complex::new(1.5, 3.1), ]; - let quadrature: [f32; ADC_SAMPLE_BUFFER_SIZE] = [ - 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, - 2.9, 3.0, 3.1, - ]; - assert_eq!(decimate(in_phase, quadrature), ([0.0], [1.6])); + assert_eq!(decimate(signal), [Complex::new(0.0, 1.6)]); } #[test] @@ -439,7 +442,7 @@ mod tests { }, ); assert_eq!( - lockin.demodulate(&[0; ADC_SAMPLE_BUFFER_SIZE], &[],), + lockin.demodulate(&[0; ADC_SAMPLE_BUFFER_SIZE], &[]), Err("insufficient timestamps") ); } @@ -489,32 +492,20 @@ mod tests { -(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 in_phase: [f32; ADC_SAMPLE_BUFFER_SIZE] = - [0.; ADC_SAMPLE_BUFFER_SIZE]; - let mut quadrature: [f32; ADC_SAMPLE_BUFFER_SIZE] = - [0.; ADC_SAMPLE_BUFFER_SIZE]; - for (n, (i, q)) in - in_phase.iter_mut().zip(quadrature.iter_mut()).enumerate() - { + let mut signal = [Complex::new(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(); - *i = sine * adc_samples[n] as f32; - *q = cosine * adc_samples[n] as f32; + s.re = sine * adc_samples[n] as f32; + s.im = cosine * adc_samples[n] as f32; } - let (result_in_phase, result_quadrature) = - lockin.demodulate(&adc_samples, timestamps).unwrap(); + let result = lockin.demodulate(&adc_samples, timestamps).unwrap(); assert!( - array_within_tolerance(&result_in_phase, &in_phase, 0., 1e-5), - "\nin_phase computed: {:?},\nin_phase expected: {:?}", - result_in_phase, - in_phase - ); - assert!( - array_within_tolerance(&result_quadrature, &quadrature, 0., 1e-5), - "\nquadrature computed: {:?},\nquadrature expected: {:?}", - result_quadrature, - quadrature + complex_array_within_tolerance(&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 index af64651..21ace3e 100644 --- a/dsp/tests/lockin_low_pass.rs +++ b/dsp/tests/lockin_low_pass.rs @@ -1,3 +1,4 @@ +use dsp::complex::Complex; use dsp::iir::IIR; use dsp::lockin::{ decimate, magnitude_phase, Lockin, ADC_SAMPLE_BUFFER_SIZE, @@ -582,7 +583,7 @@ fn lowpass_test( pure_signals.push(desired_input); for n in 0..(samples + extra_samples) { - let signal: [i16; ADC_SAMPLE_BUFFER_SIZE] = adc_sampled_signal( + let adc_signal: [i16; ADC_SAMPLE_BUFFER_SIZE] = adc_sampled_signal( &pure_signals, timestamp_start, internal_frequency, @@ -597,33 +598,31 @@ fn lowpass_test( internal_frequency, ); - let mut in_phase: [f32; ADC_SAMPLE_BUFFER_SIZE]; - let mut quadrature: [f32; ADC_SAMPLE_BUFFER_SIZE]; - match lockin.demodulate(&signal, timestamps) { - Ok((i, q)) => { - in_phase = i; - quadrature = q; + let mut signal: [Complex; ADC_SAMPLE_BUFFER_SIZE]; + match lockin.demodulate(&adc_signal, timestamps) { + Ok(s) => { + signal = s; } Err(_) => { continue; } } - lockin.filter(&mut in_phase, &mut quadrature); - let (in_phase_decimated, quadrature_decimated) = - decimate(in_phase, quadrature); + lockin.filter(&mut signal); + let signal_decimated = decimate(signal); - let mut magnitude_decimated = in_phase_decimated.clone(); - let mut phase_decimated = quadrature_decimated.clone(); + 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_decimated, &mut phase_decimated); + 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_decimated[k] / ADC_MAX_COUNT as f32; + magnitude_phase_decimated[k].re / 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}", @@ -636,13 +635,13 @@ fn lowpass_test( assert!( tolerance_check( effective_phase_offset as f32, - phase_decimated[k], + magnitude_phase_decimated[k].im, total_phase_noise as f32, tolerance ), "phase actual: {:.4}, phase computed: {:.4}, tolerance: {:.4}", effective_phase_offset as f32, - phase_decimated[k], + magnitude_phase_decimated[k].im, max_error( effective_phase_offset as f32, total_phase_noise as f32, @@ -651,9 +650,9 @@ fn lowpass_test( ); let in_phase_normalized: f32 = - in_phase_decimated[k] / ADC_MAX_COUNT as f32; + signal_decimated[k].re / ADC_MAX_COUNT as f32; let quadrature_normalized: f32 = - quadrature_decimated[k] / ADC_MAX_COUNT as f32; + signal_decimated[k].im / ADC_MAX_COUNT as f32; assert!( tolerance_check( in_phase_actual as f32,