mirror of
https://github.com/ArduPilot/ardupilot
synced 2025-01-18 06:38:29 -04:00
AP_HAL: add Jain's estimator
notch tuning using FFT averaging allocate scratch space for peak finding return all detected peaks to caller
This commit is contained in:
parent
a7b3a5713d
commit
f57ac4787d
@ -18,6 +18,9 @@
|
|||||||
#include <AP_Math/AP_Math.h>
|
#include <AP_Math/AP_Math.h>
|
||||||
#include "AP_HAL.h"
|
#include "AP_HAL.h"
|
||||||
#include "DSP.h"
|
#include "DSP.h"
|
||||||
|
#ifndef HAL_NO_UARTDRIVER
|
||||||
|
#include <GCS_MAVLink/GCS.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HAL_WITH_DSP
|
#if HAL_WITH_DSP
|
||||||
|
|
||||||
@ -42,7 +45,7 @@ DSP::FFTWindowState::FFTWindowState(uint16_t window_size, uint16_t sample_rate)
|
|||||||
|
|
||||||
if (_freq_bins == nullptr || _hanning_window == nullptr || _rfft_data == nullptr || _derivative_freq_bins == nullptr) {
|
if (_freq_bins == nullptr || _hanning_window == nullptr || _rfft_data == nullptr || _derivative_freq_bins == nullptr) {
|
||||||
hal.util->free_type(_freq_bins, sizeof(float) * (_window_size), DSP_MEM_REGION);
|
hal.util->free_type(_freq_bins, sizeof(float) * (_window_size), DSP_MEM_REGION);
|
||||||
hal.util->free_type(_derivative_freq_bins, sizeof(float) * (_bin_count), DSP_MEM_REGION);
|
hal.util->free_type(_derivative_freq_bins, sizeof(float) * (_bin_count + 1), DSP_MEM_REGION);
|
||||||
hal.util->free_type(_hanning_window, sizeof(float) * (_window_size), DSP_MEM_REGION);
|
hal.util->free_type(_hanning_window, sizeof(float) * (_window_size), DSP_MEM_REGION);
|
||||||
hal.util->free_type(_rfft_data, sizeof(float) * (_window_size + 2), DSP_MEM_REGION);
|
hal.util->free_type(_rfft_data, sizeof(float) * (_window_size + 2), DSP_MEM_REGION);
|
||||||
|
|
||||||
@ -59,7 +62,7 @@ DSP::FFTWindowState::FFTWindowState(uint16_t window_size, uint16_t sample_rate)
|
|||||||
_hanning_window[i] = (0.5f - 0.5f * cosf(2.0f * M_PI * i / ((float)window_size - 1)));
|
_hanning_window[i] = (0.5f - 0.5f * cosf(2.0f * M_PI * i / ((float)window_size - 1)));
|
||||||
_window_scale += _hanning_window[i];
|
_window_scale += _hanning_window[i];
|
||||||
}
|
}
|
||||||
// Calculate the inverse of the Effective Noise Bandwidth
|
// Calculate the inverse of the Effective Noise Bandwidth - equation 24
|
||||||
_window_scale = 2.0f / sq(_window_scale);
|
_window_scale = 2.0f / sq(_window_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,12 +70,14 @@ DSP::FFTWindowState::~FFTWindowState()
|
|||||||
{
|
{
|
||||||
hal.util->free_type(_freq_bins, sizeof(float) * (_window_size), DSP_MEM_REGION);
|
hal.util->free_type(_freq_bins, sizeof(float) * (_window_size), DSP_MEM_REGION);
|
||||||
_freq_bins = nullptr;
|
_freq_bins = nullptr;
|
||||||
hal.util->free_type(_derivative_freq_bins, sizeof(float) * (_bin_count), DSP_MEM_REGION);
|
hal.util->free_type(_derivative_freq_bins, sizeof(float) * (_bin_count + 1), DSP_MEM_REGION);
|
||||||
_derivative_freq_bins = nullptr;
|
_derivative_freq_bins = nullptr;
|
||||||
hal.util->free_type(_hanning_window, sizeof(float) * (_window_size), DSP_MEM_REGION);
|
hal.util->free_type(_hanning_window, sizeof(float) * (_window_size), DSP_MEM_REGION);
|
||||||
_hanning_window = nullptr;
|
_hanning_window = nullptr;
|
||||||
hal.util->free_type(_rfft_data, sizeof(float) * (_window_size + 2), DSP_MEM_REGION);
|
hal.util->free_type(_rfft_data, sizeof(float) * (_window_size + 2), DSP_MEM_REGION);
|
||||||
_rfft_data = nullptr;
|
_rfft_data = nullptr;
|
||||||
|
hal.util->free_type(_avg_freq_bins, sizeof(float) * (_window_size), DSP_MEM_REGION);
|
||||||
|
_avg_freq_bins = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: find the magnitudes of the complex data
|
// step 3: find the magnitudes of the complex data
|
||||||
@ -104,6 +109,12 @@ void DSP::step_cmplx_mag(FFTWindowState* fft, uint16_t start_bin, uint16_t end_b
|
|||||||
|
|
||||||
// scale the power to account for the input window
|
// scale the power to account for the input window
|
||||||
vector_scale_float(fft->_freq_bins, fft->_window_scale, fft->_freq_bins, fft->_bin_count);
|
vector_scale_float(fft->_freq_bins, fft->_window_scale, fft->_freq_bins, fft->_bin_count);
|
||||||
|
|
||||||
|
// average the FFT data
|
||||||
|
if (fft->_averaging) {
|
||||||
|
vector_add_float(fft->_avg_freq_bins, fft->_freq_bins, fft->_avg_freq_bins, fft->_bin_count);
|
||||||
|
fft->_averaging_samples++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate the noise width of a peak based on the input parameters
|
// calculate the noise width of a peak based on the input parameters
|
||||||
@ -209,6 +220,100 @@ float DSP::tau(const float x) const
|
|||||||
return (0.25f * p1 - TAU_FACTOR * p2);
|
return (0.25f * p1 - TAU_FACTOR * p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from https://dspguru.com/dsp/howtos/how-to-interpolate-fft-peak/
|
||||||
|
// Works on magnitudes only, which is useful when using averaged data
|
||||||
|
float DSP::calculate_jains_estimator(const FFTWindowState* fft, const float* real_fft, uint16_t k_max)
|
||||||
|
{
|
||||||
|
if (k_max <= 1 || k_max >= fft->_bin_count) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float y1 = real_fft[k_max-1];
|
||||||
|
float y2 = real_fft[k_max];
|
||||||
|
float y3 = real_fft[k_max+1];
|
||||||
|
float d = 0.0f;
|
||||||
|
|
||||||
|
if (y1 > y3) {
|
||||||
|
float a = y2 / y1;
|
||||||
|
d = a / (1 + a) - 1;
|
||||||
|
} else {
|
||||||
|
float a = y3 / y2;
|
||||||
|
d = a / (1 + a);
|
||||||
|
}
|
||||||
|
return constrain_float(d, -0.5f, 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize averaging FFT windows as they are calculated
|
||||||
|
bool DSP::fft_init_average(FFTWindowState* fft)
|
||||||
|
{
|
||||||
|
if (fft->_avg_freq_bins == nullptr) {
|
||||||
|
fft->_avg_freq_bins = (float*)hal.util->malloc_type(sizeof(float) * (fft->_window_size), DSP_MEM_REGION);
|
||||||
|
if (fft->_avg_freq_bins == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start averaging FFT windows as they are calculated
|
||||||
|
bool DSP::fft_start_average(FFTWindowState* fft)
|
||||||
|
{
|
||||||
|
if (fft->_averaging) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fft_init_average(fft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fft->_averaging_samples = 0;
|
||||||
|
fft->_averaging = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start averaging FFT windows as they are calculated
|
||||||
|
uint16_t DSP::fft_stop_average(FFTWindowState* fft, uint16_t start_bin, uint16_t end_bin, float* freqs)
|
||||||
|
{
|
||||||
|
// ensure the window has been allocated even if we do nothing else
|
||||||
|
if (!fft_init_average(fft)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fft->_averaging) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fft->_averaging = false;
|
||||||
|
|
||||||
|
// scale by the number of samples
|
||||||
|
vector_scale_float(fft->_avg_freq_bins, fft->_averaging_samples, fft->_avg_freq_bins, fft->_bin_count);
|
||||||
|
|
||||||
|
const uint16_t smoothwidth = 1;
|
||||||
|
uint16_t bin_range = (MIN(end_bin + ((smoothwidth + 1) >> 1) + 2, fft->_bin_count) - start_bin) + 1;
|
||||||
|
|
||||||
|
// find the three highest peaks using a zero crossing algorithm
|
||||||
|
// allocate the scratch space locally as we are in a different thread to the regular FFT
|
||||||
|
float* scratch_space = (float*)hal.util->malloc_type(sizeof(float) * (fft->_bin_count + 1), DSP_MEM_REGION);
|
||||||
|
if (scratch_space == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint16_t peaks[MAX_TRACKED_PEAKS] {};
|
||||||
|
uint16_t numpeaks = find_peaks(&fft->_avg_freq_bins[start_bin], bin_range,
|
||||||
|
scratch_space, peaks, MAX_TRACKED_PEAKS, 0.0f, -1.0f, smoothwidth, 2);
|
||||||
|
hal.util->free_type(scratch_space, sizeof(float) * (fft->_bin_count + 1), DSP_MEM_REGION);
|
||||||
|
|
||||||
|
// now try and find the lowest harmonic
|
||||||
|
for (uint16_t i = 0; i < numpeaks; i++) {
|
||||||
|
const uint16_t bin = peaks[i] + start_bin;
|
||||||
|
float d = calculate_jains_estimator(fft, fft->_avg_freq_bins, bin);
|
||||||
|
freqs[i] = (bin + d) * fft->_bin_resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
fft->_averaging_samples = 0;
|
||||||
|
return numpeaks;
|
||||||
|
}
|
||||||
|
|
||||||
// find all the peaks in the fft window using https://terpconnect.umd.edu/~toh/spectrum/PeakFindingandMeasurement.htm
|
// find all the peaks in the fft window using https://terpconnect.umd.edu/~toh/spectrum/PeakFindingandMeasurement.htm
|
||||||
// in general peakgrup > 2 is only good for very broad noisy peaks, <= 2 better for spikey peaks, although 1 will miss
|
// in general peakgrup > 2 is only good for very broad noisy peaks, <= 2 better for spikey peaks, although 1 will miss
|
||||||
// a true spike 50% of the time
|
// a true spike 50% of the time
|
||||||
|
@ -61,12 +61,18 @@ public:
|
|||||||
float* _derivative_freq_bins;
|
float* _derivative_freq_bins;
|
||||||
// intermediate real FFT data
|
// intermediate real FFT data
|
||||||
float* _rfft_data;
|
float* _rfft_data;
|
||||||
|
// averaged frequency data via Welch's method
|
||||||
|
float* _avg_freq_bins;
|
||||||
// three highest peaks
|
// three highest peaks
|
||||||
FrequencyPeakData _peak_data[MAX_TRACKED_PEAKS];
|
FrequencyPeakData _peak_data[MAX_TRACKED_PEAKS];
|
||||||
// Hanning window for incoming samples, see https://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window
|
// Hanning window for incoming samples, see https://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window
|
||||||
float* _hanning_window;
|
float* _hanning_window;
|
||||||
// Use in calculating the PS of the signal [Heinz] equations (20) & (21)
|
// Use in calculating the PS of the signal [Heinz] equations (20) & (21)
|
||||||
float _window_scale;
|
float _window_scale;
|
||||||
|
// averaging is ongoing
|
||||||
|
bool _averaging;
|
||||||
|
// number of samples in the average
|
||||||
|
uint32_t _averaging_samples;
|
||||||
|
|
||||||
virtual ~FFTWindowState();
|
virtual ~FFTWindowState();
|
||||||
FFTWindowState(uint16_t window_size, uint16_t sample_rate);
|
FFTWindowState(uint16_t window_size, uint16_t sample_rate);
|
||||||
@ -77,6 +83,10 @@ public:
|
|||||||
virtual void fft_start(FFTWindowState* state, FloatBuffer& samples, uint16_t advance) = 0;
|
virtual void fft_start(FFTWindowState* state, FloatBuffer& samples, uint16_t advance) = 0;
|
||||||
// perform remaining steps of an FFT analysis
|
// perform remaining steps of an FFT analysis
|
||||||
virtual uint16_t fft_analyse(FFTWindowState* state, uint16_t start_bin, uint16_t end_bin, float noise_att_cutoff) = 0;
|
virtual uint16_t fft_analyse(FFTWindowState* state, uint16_t start_bin, uint16_t end_bin, float noise_att_cutoff) = 0;
|
||||||
|
// start averaging FFT data
|
||||||
|
bool fft_start_average(FFTWindowState* fft);
|
||||||
|
// finish the averaging process
|
||||||
|
uint16_t fft_stop_average(FFTWindowState* fft, uint16_t start_bin, uint16_t end_bin, float* peaks);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// step 3: find the magnitudes of the complex data
|
// step 3: find the magnitudes of the complex data
|
||||||
@ -91,8 +101,10 @@ protected:
|
|||||||
virtual void vector_max_float(const float* vin, uint16_t len, float* max_value, uint16_t* max_index) const = 0;
|
virtual void vector_max_float(const float* vin, uint16_t len, float* max_value, uint16_t* max_index) const = 0;
|
||||||
// find the mean value in an vector of floats
|
// find the mean value in an vector of floats
|
||||||
virtual float vector_mean_float(const float* vin, uint16_t len) const = 0;
|
virtual float vector_mean_float(const float* vin, uint16_t len) const = 0;
|
||||||
// multiple an vector of floats by a scale factor
|
// multiply an vector of floats by a scale factor
|
||||||
virtual void vector_scale_float(const float* vin, float scale, float* vout, uint16_t len) const = 0;
|
virtual void vector_scale_float(const float* vin, float scale, float* vout, uint16_t len) const = 0;
|
||||||
|
// add two vectors together
|
||||||
|
virtual void vector_add_float(const float* vin1, const float* vin2, float* vout, uint16_t len) const = 0;
|
||||||
// algorithm for finding peaks in noisy data as per https://terpconnect.umd.edu/~toh/spectrum/PeakFindingandMeasurement.htm
|
// algorithm for finding peaks in noisy data as per https://terpconnect.umd.edu/~toh/spectrum/PeakFindingandMeasurement.htm
|
||||||
uint16_t find_peaks(const float* input, uint16_t length, float* output, uint16_t* peaks, uint16_t peaklen,
|
uint16_t find_peaks(const float* input, uint16_t length, float* output, uint16_t* peaks, uint16_t peaklen,
|
||||||
float slopeThreshold, float ampThreshold, uint16_t smoothwidth, uint16_t peakgroup) const;
|
float slopeThreshold, float ampThreshold, uint16_t smoothwidth, uint16_t peakgroup) const;
|
||||||
@ -100,8 +112,13 @@ protected:
|
|||||||
void derivative(const float* input, float* output, uint16_t n) const;
|
void derivative(const float* input, float* output, uint16_t n) const;
|
||||||
void fastsmooth(float* input, uint16_t n, uint16_t smoothwidth) const;
|
void fastsmooth(float* input, uint16_t n, uint16_t smoothwidth) const;
|
||||||
|
|
||||||
// quinn's frequency interpolator
|
// Quinn's frequency interpolator
|
||||||
float calculate_quinns_second_estimator(const FFTWindowState* fft, const float* complex_fft, uint16_t k) const;
|
float calculate_quinns_second_estimator(const FFTWindowState* fft, const float* complex_fft, uint16_t k) const;
|
||||||
float tau(const float x) const;
|
float tau(const float x) const;
|
||||||
|
// Jain's estimator
|
||||||
|
float calculate_jains_estimator(const FFTWindowState* fft, const float* real_fft, uint16_t k_max);
|
||||||
|
// init averaging FFT data
|
||||||
|
bool fft_init_average(FFTWindowState* fft);
|
||||||
|
|
||||||
#endif // HAL_WITH_DSP
|
#endif // HAL_WITH_DSP
|
||||||
};
|
};
|
||||||
|
@ -47,6 +47,7 @@ protected:
|
|||||||
virtual void vector_max_float(const float* vin, uint16_t len, float* maxValue, uint16_t* maxIndex) const override {}
|
virtual void vector_max_float(const float* vin, uint16_t len, float* maxValue, uint16_t* maxIndex) const override {}
|
||||||
virtual void vector_scale_float(const float* vin, float scale, float* vout, uint16_t len) const override {}
|
virtual void vector_scale_float(const float* vin, float scale, float* vout, uint16_t len) const override {}
|
||||||
virtual float vector_mean_float(const float* vin, uint16_t len) const override { return 0.0f; };
|
virtual float vector_mean_float(const float* vin, uint16_t len) const override { return 0.0f; };
|
||||||
|
virtual void vector_add_float(const float* vin1, const float* vin2, float* vout, uint16_t len) const override {}
|
||||||
public:
|
public:
|
||||||
void run_tests();
|
void run_tests();
|
||||||
} dsptest;
|
} dsptest;
|
||||||
|
Loading…
Reference in New Issue
Block a user