pounder_test/dsp/tests/lockin_low_pass.rs

1140 lines
37 KiB
Rust

use dsp::iir::IIR;
use dsp::lockin::{
decimate, magnitude_phase, Lockin, ADC_SAMPLE_BUFFER_SIZE,
DECIMATED_BUFFER_SIZE, TIMESTAMP_BUFFER_SIZE,
};
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<PureSine>,
timestamp_start: u64,
internal_frequency: f64,
adc_frequency: f64,
) -> [i16; ADC_SAMPLE_BUFFER_SIZE] {
// amplitude of each pure signal
let mut amplitude: Vec<f64> = Vec::<f64>::new();
// initial phase value for each pure signal
let mut initial_phase: Vec<f64> = Vec::<f64>::new();
// phase increment at each ADC sample for each pure signal
let mut phase_increment: Vec<f64> = Vec::<f64>::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,
timestamp_start: u64,
timestamp_stop: u64,
internal_frequency: f64,
) -> (usize, [u16; TIMESTAMP_BUFFER_SIZE]) {
let reference_period = internal_frequency / reference_frequency;
let start_count = timestamp_start as f64 % reference_period;
let mut valid_timestamps: usize = 0;
let mut timestamps: [u16; TIMESTAMP_BUFFER_SIZE] =
[0; TIMESTAMP_BUFFER_SIZE];
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;
}
(valid_timestamps, 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<PureSine>,
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<PureSine>,
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 signal: [i16; ADC_SAMPLE_BUFFER_SIZE] = adc_sampled_signal(
&pure_signals,
timestamp_start,
internal_frequency,
adc_frequency,
);
let (valid_timestamps, timestamps) = adc_batch_timestamps(
reference_frequency,
timestamp_start,
timestamp_start + sample_count - 1,
internal_frequency,
);
let mut in_phase: [f32; ADC_SAMPLE_BUFFER_SIZE];
let mut quadrature: [f32; ADC_SAMPLE_BUFFER_SIZE];
let lockin_demodulate =
lockin.demodulate(signal, timestamps, valid_timestamps as u16);
match lockin_demodulate {
Some(i) => {
in_phase = i.0;
quadrature = i.1;
}
None => {
continue;
}
}
lockin.filter(&mut in_phase, &mut quadrature);
let (in_phase_decimated, quadrature_decimated) =
decimate(in_phase, quadrature);
let mut magnitude_decimated = in_phase_decimated.clone();
let mut phase_decimated = quadrature_decimated.clone();
magnitude_phase(&mut magnitude_decimated, &mut 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;
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,
phase_decimated[k],
total_phase_noise as f32,
tolerance
),
"phase actual: {:.4}, phase computed: {:.4}, tolerance: {:.4}",
effective_phase_offset as f32,
phase_decimated[k],
max_error(
effective_phase_offset as f32,
total_phase_noise as f32,
tolerance
)
);
let in_phase_normalized: f32 =
in_phase_decimated[k] / ADC_MAX_COUNT as f32;
let quadrature_normalized: f32 =
quadrature_decimated[k] / 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,
);
}