LCOV - code coverage report
Current view: top level - AP_GyroFFT - AP_GyroFFT.h (source / functions) Hit Total Coverage
Test: lcov.info Lines: 34 34 100.0 %
Date: 2026-09-09 11:02:08 Functions: 25 25 100.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             : 
      15             :     Code by Andy Piper
      16             :  */
      17             : #pragma once
      18             : 
      19             : #include <AP_HAL/AP_HAL.h>
      20             : 
      21             : #if HAL_GYROFFT_ENABLED
      22             : 
      23             : #include <AP_Common/AP_Common.h>
      24             : #include <AP_HAL/utility/RingBuffer.h>
      25             : #include <AP_Param/AP_Param.h>
      26             : #include <AP_Math/AP_Math.h>
      27             : #include <AP_InertialSensor/AP_InertialSensor.h>
      28             : #include <Filter/LowPassFilter.h>
      29             : #include <Filter/FilterWithBuffer.h>
      30             : 
      31             : #define DEBUG_FFT   0
      32             : 
      33             : // a library that leverages the HAL DSP support to perform FFT analysis on gyro samples
      34             : class AP_GyroFFT
      35             : {
      36             :     friend class ReplayGyroFFT;
      37             : 
      38             : public:
      39             :     typedef AP_HAL::DSP::FrequencyPeak FrequencyPeak;
      40             : 
      41             :     enum class Options : uint32_t {
      42             :         FFTPostFilter = 1 << 0,
      43             :         ESCNoiseCheck = 1 << 1
      44             :     };
      45             : 
      46             :     AP_GyroFFT();
      47             : 
      48             :     // Do not allow copies
      49             :     CLASS_NO_COPY(AP_GyroFFT);
      50             : 
      51             :     void init(uint16_t loop_rate_hz);
      52             : 
      53             :     // cycle through the FFT steps - runs in the FFT thread
      54             :     uint16_t run_cycle();
      55             :     // capture gyro values at the appropriate update rate - runs at fast loop rate
      56             :     void sample_gyros();
      57             :     // update the engine state - runs at 400Hz
      58             :     void update();
      59             :     // update calculated values of dynamic parameters - runs at 1Hz
      60      149217 :     void update_parameters() { update_parameters(false); }
      61             :     // thread for processing gyro data via FFT
      62             :     void update_thread();
      63             :     // start the update thread
      64             :     bool start_update_thread();
      65             :     // is the subsystem enabled
      66        2088 :     bool enabled() const { return _enable; }
      67             : 
      68             :     // check at startup that standard frequencies can be detected
      69             :     bool pre_arm_check(char *failure_msg, const uint8_t failure_msg_len);
      70             :     // make sure calibration is set done
      71             :     bool prepare_for_arming();
      72             :     // called when hovering to determine the average peak frequency and reference value
      73             :     void update_freq_hover(float dt, float throttle_out);
      74             :     // called to save the average peak frequency and reference value
      75             :     void save_params_on_disarm();
      76             :     // dynamically enable or disable the analysis through the aux switch
      77          20 :     void set_analysis_enabled(bool enabled) { _analysis_enabled = enabled; };
      78             :     // notch tuning
      79             :     void start_notch_tune();
      80             :     void stop_notch_tune();
      81             : 
      82             :     // detected peak frequency filtered at 1/3 the update rate
      83             :     const Vector3f& get_noise_center_freq_hz() const { return get_noise_center_freq_hz(FrequencyPeak::CENTER); }
      84      382526 :     const Vector3f& get_noise_center_freq_hz(FrequencyPeak peak) const { return _global_state._center_freq_hz_filtered[peak]; }
      85             :     // frequency values
      86             :     float get_weighted_freq_hz(FrequencyPeak peak) const;
      87             :     // energy of the background noise at the detected center frequency
      88       68568 :     const Vector3f& get_noise_signal_to_noise_db() const { return get_noise_signal_to_noise_db(FrequencyPeak::CENTER); }
      89      277878 :     const Vector3f& get_noise_signal_to_noise_db(FrequencyPeak peak) const { return _global_state._center_freq_snr[peak];; }
      90             :     // detected peak frequency weighted by energy
      91             :     float get_weighted_noise_center_freq_hz() const;
      92             :     // all detected peak frequencies weighted by energy
      93             :     uint8_t get_weighted_noise_center_frequencies_hz(uint8_t num_freqs, float* freqs) const;
      94             :     // detected peak frequency
      95             :     const Vector3f& get_raw_noise_center_freq_hz() const { return _global_state._center_freq_hz; }
      96             :     // match between first and second harmonics
      97       68568 :     const Vector3f& get_raw_noise_harmonic_fit() const { return _global_state._harmonic_fit; }
      98             :     // energy of the detected peak frequency
      99             :     const Vector3f& get_center_freq_energy() const { return get_center_freq_energy(FrequencyPeak::CENTER); }
     100      454284 :     const Vector3f& get_center_freq_energy(FrequencyPeak peak) const { return _global_state._center_freq_energy_filtered[peak]; }
     101             :     // index of the FFT bin containing the detected peak frequency
     102             :     const Vector3<uint16_t>& get_center_freq_bin() const { return _global_state._center_freq_bin; }
     103             :     // detected peak bandwidth
     104             :     const Vector3f& get_noise_center_bandwidth_hz() const { return get_noise_center_bandwidth_hz(FrequencyPeak::CENTER); }
     105      283696 :     const Vector3f& get_noise_center_bandwidth_hz(FrequencyPeak peak) const { return _global_state._center_bandwidth_hz_filtered[peak]; };
     106             :     // weighted detected peak bandwidth
     107             :     float get_weighted_noise_center_bandwidth_hz() const;
     108             :     // log gyro fft messages
     109             :     void write_log_messages();
     110             :     // post filter mask of IMUs
     111       41208 :     bool using_post_filter_samples() const { return (_options & uint32_t(Options::FFTPostFilter)) != 0; }
     112             :     // post filter mask of IMUs
     113      502380 :     bool check_esc_noise() const { return (_options & uint32_t(Options::ESCNoiseCheck)) != 0; }
     114             :     // look for a frequency in the detected noise
     115             :     float has_noise_at_frequency_hz(float freq) const;
     116             :     static float calculate_notch_frequency(float* freqs, uint16_t numpeaks, float harmonic_fit, uint8_t& harmonics);
     117         174 :     static bool is_harmonic_of(float harmonic, float fundamental, uint8_t mult, float _fit) {
     118         174 :         const float fit = 100.0f * fabsf(harmonic - fundamental * mult) / harmonic;
     119         174 :         return (isfinite(fit) && fit < _fit);
     120             :     }
     121             : 
     122             :     static const struct AP_Param::GroupInfo var_info[];
     123       50265 :     static AP_GyroFFT *get_singleton() { return _singleton; }
     124             : 
     125             : private:
     126             :     // configuration data local to the FFT thread but set from the main thread
     127             :     struct EngineConfig {
     128             :         // whether the analyzer should be run
     129             :         bool _analysis_enabled;
     130             :         // minimum frequency of the detection window
     131             :         uint16_t _fft_min_hz;
     132             :         // maximum frequency of the detection window
     133             :         uint16_t _fft_max_hz;
     134             :         // configured start bin based on min hz
     135             :         uint16_t _fft_start_bin;
     136             :         // configured end bin based on max hz
     137             :         uint16_t _fft_end_bin;
     138             :         // attenuation cutoff for calculation of hover bandwidth
     139             :         float _attenuation_cutoff;
     140             :         // SNR Threshold
     141             :         float _snr_threshold_db;
     142             :     } _config;
     143             : 
     144             :     // smoothing filter that first takes the median from a sliding window and then
     145             :     // applies a low pass filter to the result
     146             :     class MedianLowPassFilter3dFloat {
     147             :     public:
     148      132048 :         MedianLowPassFilter3dFloat() { }
     149             : 
     150             :         float apply(uint8_t axis, float sample);
     151             :         float get(uint8_t axis) const { return _lowpass_filter[axis].get(); }
     152             : 
     153         180 :         void set_cutoff_frequency(float sample_freq, float cutoff_freq) {
     154         720 :             for (uint8_t i = 0; i < XYZ_AXIS_COUNT; i++) {
     155         540 :                 _lowpass_filter[i].set_cutoff_frequency(sample_freq, cutoff_freq);
     156             :             }
     157         180 :         }
     158             : 
     159             :     private:
     160             :         LowPassFilterConstDtFloat _lowpass_filter[XYZ_AXIS_COUNT];
     161             :         FilterWithBuffer<float,3> _median_filter[XYZ_AXIS_COUNT];
     162             :     };
     163             : 
     164             :     // structure for holding noise peak data while calculating swaps
     165             :     class FrequencyData {
     166             :     public:
     167             :         FrequencyData(const AP_GyroFFT& gyrofft, const EngineConfig& config);
     168     2777842 :         float get_weighted_frequency(FrequencyPeak i) const { return frequency[i]; }
     169      839202 :         float get_signal_to_noise(FrequencyPeak i) const { return snr[i]; }
     170     3321355 :         bool is_valid(FrequencyPeak i) const { return valid[i]; }
     171             :     private:
     172             :         float frequency[FrequencyPeak::MAX_TRACKED_PEAKS];
     173             :         float snr[FrequencyPeak::MAX_TRACKED_PEAKS];
     174             :         bool valid[FrequencyPeak::MAX_TRACKED_PEAKS];
     175             :     };
     176             :     // distance matrix between filtered and instantaneous peaks
     177             :     typedef float DistanceMatrix[FrequencyPeak::MAX_TRACKED_PEAKS][FrequencyPeak::MAX_TRACKED_PEAKS];
     178             : 
     179             :     // thread-local accessors of filtered state
     180     2013099 :     float get_tl_noise_center_freq_hz(FrequencyPeak peak, uint8_t axis) const { return _thread_state._center_freq_hz_filtered[peak][axis]; }
     181      358130 :     float get_tl_center_freq_energy(FrequencyPeak peak, uint8_t axis) const { return _thread_state._center_freq_energy_filtered[peak][axis]; }
     182             :     float get_tl_noise_center_bandwidth_hz(FrequencyPeak peak, uint8_t axis) const { return _thread_state._center_bandwidth_hz_filtered[peak][axis]; };
     183             :     // thread-local mutators of filtered state
     184      755054 :     float update_tl_noise_center_freq_hz(FrequencyPeak peak, uint8_t axis, float value) {
     185      755054 :         return (_thread_state._center_freq_hz_filtered[peak][axis] = _center_freq_filter[peak].apply(axis, value));
     186             :     }
     187      755054 :     float update_tl_center_freq_energy(FrequencyPeak peak, uint8_t axis, float value) {
     188      755054 :         return (_thread_state._center_freq_energy_filtered[peak][axis] = _center_freq_energy_filter[peak].apply(axis, value));
     189             :     }
     190      755054 :     float update_tl_noise_center_bandwidth_hz(FrequencyPeak peak, uint8_t axis, float value) {
     191      755054 :         return (_thread_state._center_bandwidth_hz_filtered[peak][axis] = _center_bandwidth_filter[peak].apply(axis, value));
     192             :     }
     193             :     // write single log messages
     194             :     void log_noise_peak(uint8_t id, FrequencyPeak peak) const;
     195             :     // calculate the peak noise frequency
     196             :     void calculate_noise(bool calibrating, const EngineConfig& config);
     197             :     // calculate noise peaks based on energy and history
     198             :     uint8_t calculate_tracking_peaks(float& weighted_peak_freq_hz, bool calibrating, const EngineConfig& config);
     199             :     uint8_t calculate_tracking_peaks(float& weighted_center_freq_hz, const FrequencyData& freqs, const EngineConfig& config);
     200             :     // calculate noise peak frequency characteristics
     201             :     bool calculate_filtered_noise(FrequencyPeak target_peak, FrequencyPeak source_peak, const FrequencyData& freqs, const EngineConfig& config);
     202             :     void update_snr_values(const FrequencyData& freqs);
     203             :     // get the weighted frequency
     204             :     bool get_weighted_frequency(FrequencyPeak peak, float& weighted_peak_freq_hz, float& snr, const EngineConfig& config) const;
     205             :     // return the tracked noise peak
     206             :     FrequencyPeak get_tracked_noise_peak() const;
     207             :     // calculate the distance matrix between the current estimates and the current cycle
     208             :     void find_distance_matrix(DistanceMatrix& distance_matrix, const FrequencyData& freqs, const EngineConfig& config) const;
     209             :     // return the instantaneous peak that is closest to the target estimate peak
     210             :     FrequencyPeak find_closest_peak(const FrequencyPeak target, const DistanceMatrix& distance_matrix, uint8_t ignore = 0) const;
     211             :     // detected peak frequency weighted by energy
     212             :     float calculate_weighted_freq_hz(const Vector3f& energy, const Vector3f& freq) const;
     213             :     // update the estimation of the background noise energy
     214             :     void update_ref_energy(uint16_t max_bin);
     215             :     // test frequency detection for all of the allowable bins
     216             :     float self_test_bin_frequencies();
     217             :     // detect the provided frequency
     218             :     float self_test(float frequency, FloatBuffer& test_window);
     219             :     // whether to run analysis or not
     220   622690387 :     bool analysis_enabled() const { return _initialized && _analysis_enabled && _thread_created; };
     221             :     // whether analysis can be run again or not
     222             :     bool start_analysis();
     223             :     // return samples available in the gyro window
     224   535617685 :     uint16_t get_available_samples(uint8_t axis) {
     225   535617685 :         return _sample_mode == 0 ?_ins->get_raw_gyro_window(axis).available() : _downsampled_gyro_data[axis].available();
     226             :     }
     227             :     void update_parameters(bool force);
     228             :     // semaphore for access to shared FFT data
     229             :     HAL_Semaphore _sem;
     230             : 
     231             :     // data set from the FFT thread but accessible from the main thread protected by the semaphore
     232             :     struct EngineState {
     233             :         // energy of the detected peak frequency in dB
     234             :         Vector3f _center_freq_energy_db;
     235             :         // detected peak frequency
     236             :         Vector3f _center_freq_hz;
     237             :         // fit between first and second harmonics
     238             :         Vector3f _harmonic_fit;
     239             :         // bin of detected peak frequency
     240             :         Vector3ui _center_freq_bin;
     241             :         // fft engine health
     242             :         Vector3<uint8_t> _health;
     243             :         Vector3ul _health_ms;
     244             :         // fft engine output rate
     245             :         uint32_t _output_cycle_ms;
     246             :         // tracked frequency peak for the purposes of notching
     247             :         Vector3<uint8_t> _tracked_peak;
     248             :         // center frequency peak ignoring temporary energy changes / order switching
     249             :         Vector3<uint8_t> _center_peak;
     250             :         // signal to noise ratio of PSD at each of the detected centre frequencies
     251             :         Vector3f _center_freq_snr[FrequencyPeak::MAX_TRACKED_PEAKS];
     252             :         // filtered version of the peak frequency
     253             :         Vector3f _center_freq_hz_filtered[FrequencyPeak::MAX_TRACKED_PEAKS];
     254             :         // when we last calculated a value
     255             :         Vector3ul _last_output_us;
     256             :         // filtered energy of the detected peak frequency
     257             :         Vector3f _center_freq_energy_filtered[FrequencyPeak::MAX_TRACKED_PEAKS];
     258             :         // filtered detected peak width
     259             :         Vector3f _center_bandwidth_hz_filtered[FrequencyPeak::MAX_TRACKED_PEAKS];
     260             :         // axes that still require noise calibration
     261             :         uint8_t _noise_needs_calibration;
     262             :         // whether the analyzer is mid-cycle
     263             :         bool _analysis_started;
     264             :     };
     265             : 
     266             :     // Shared FFT engine state local to the FFT thread
     267             :     EngineState _thread_state;
     268             :     // Shared FFT engine state accessible by the main thread
     269             :     EngineState _global_state;
     270             : 
     271             :     // number of samples needed before a new frame can be processed
     272             :     uint16_t _samples_per_frame;
     273             :     // number of ms that a frame should take to process to sustain output rate
     274             :     uint16_t _frame_time_ms;
     275             :     // last cycle time
     276             :     uint32_t _output_cycle_micros;
     277             :     // downsampled gyro data circular buffer for frequency analysis
     278             :     FloatBuffer _downsampled_gyro_data[XYZ_AXIS_COUNT];
     279             :     // accumulator for sampled gyro data
     280             :     Vector3f _oversampled_gyro_accum;
     281             :     // count of oversamples
     282             :     uint16_t _oversampled_gyro_count;
     283             : 
     284             :     // state of the FFT engine
     285             :     AP_HAL::DSP::FFTWindowState* _state;
     286             :     // update state machine step information
     287             :     uint8_t _update_axis;
     288             :     // noise base of the gyros
     289             :     Vector3f* _ref_energy;
     290             :     // the number of cycles required to have a proper noise reference
     291             :     uint16_t _noise_cycles;
     292             :     // number of cycles over which to generate noise ensemble averages
     293             :     uint16_t _noise_calibration_cycles[XYZ_AXIS_COUNT];
     294             :     // current _sample_mode
     295             :     uint8_t _current_sample_mode;
     296             :     // harmonic multiplier for two highest peaks
     297             :     float _harmonic_multiplier;
     298             :     // number of tracked peaks
     299             :     uint8_t _tracked_peaks;
     300             :     // engine health in tracked peaks per axis
     301             :     Vector3<uint8_t> _health;
     302             :     // engine health on roll/pitch/yaw
     303             :     Vector3<uint8_t> _rpy_health;
     304             :     // averaged throttle output over averaging period
     305             :     float _avg_throttle_out;
     306             : 
     307             :     // smoothing filter on the output
     308             :     MedianLowPassFilter3dFloat _center_freq_filter[FrequencyPeak::MAX_TRACKED_PEAKS];
     309             :     // smoothing filter on the energy
     310             :     MedianLowPassFilter3dFloat _center_freq_energy_filter[FrequencyPeak::MAX_TRACKED_PEAKS];
     311             :     // smoothing filter on the bandwidth
     312             :     MedianLowPassFilter3dFloat _center_bandwidth_filter[FrequencyPeak::MAX_TRACKED_PEAKS];
     313             :     // smoothing filter on the frequency fit
     314             :     LowPassFilterConstDtFloat _harmonic_fit_filter[XYZ_AXIS_COUNT];
     315             : 
     316             :     // configured sampling rate
     317             :     uint16_t _fft_sampling_rate_hz;
     318             :     // number of cycles without a detected signal
     319             :     uint8_t _missed_cycles[XYZ_AXIS_COUNT][FrequencyPeak::MAX_TRACKED_PEAKS];
     320             :     // number of cycles where peaks have swapped places
     321             :     uint8_t _distorted_cycles[XYZ_AXIS_COUNT];
     322             :     // whether the analyzer initialized correctly
     323             :     bool _initialized;
     324             : 
     325             :     // whether the analyzer should be run
     326             :     bool _analysis_enabled ;
     327             :     // whether the update thread was created
     328             :     bool _thread_created ;
     329             :     // whether the pre-arm check has successfully completed
     330             :     bool _calibrated;
     331             :     // minimum frequency of the detection window
     332             :     AP_Int16 _fft_min_hz;
     333             :     // maximum frequency of the detection window
     334             :     AP_Int16 _fft_max_hz;
     335             :     // size of the FFT window
     336             :     AP_Int16 _window_size;
     337             :     // percentage overlap of FFT windows
     338             :     AP_Float _window_overlap;
     339             :     // overall enablement of the feature
     340             :     AP_Int8 _enable;
     341             :     // gyro rate sampling or cycle divider
     342             :     AP_Int8 _sample_mode;
     343             :     // learned throttle reference for the hover frequency
     344             :     AP_Float _throttle_ref;
     345             :     // learned hover filter frequency
     346             :     AP_Float _freq_hover_hz;
     347             :     // SNR Threshold
     348             :     AP_Float _snr_threshold_db;
     349             :     // attenuation to use for calculating the peak bandwidth at hover
     350             :     AP_Float _attenuation_power_db;
     351             :     // learned peak bandwidth at configured attenuation at hover
     352             :     AP_Float _bandwidth_hover_hz;
     353             :     // harmonic fit percentage
     354             :     AP_Int8 _harmonic_fit;
     355             :     // harmonic peak target
     356             :     AP_Int8 _harmonic_peak;
     357             :     // number of output frames to retain for averaging
     358             :     AP_Int8 _num_frames;
     359             :     // mask of IMUs to record gyro frames after the filter bank
     360             :     AP_Int32 _options;
     361             :     AP_InertialSensor* _ins;
     362             : #if DEBUG_FFT
     363             :     uint32_t _last_output_ms;
     364             :     EngineState _debug_state;
     365             :     float _debug_max_bin_freq;
     366             :     float _debug_max_freq_bin;
     367             :     uint16_t _debug_max_bin;
     368             :     float _debug_snr;
     369             : #endif
     370             : 
     371             :     static AP_GyroFFT *_singleton;
     372             : };
     373             : 
     374             : namespace AP {
     375             :     AP_GyroFFT *fft();
     376             : };
     377             : 
     378             : #endif // HAL_GYROFFT_ENABLED

Generated by: LCOV version 1.14