2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 /* original work by Rav
22 * 2018_07 updated by ctzsnooze to post filter, wider Q, different peak detection
23 * coding assistance and advice from DieHertz, Rav, eTracer
24 * test pilots icr4sh, UAV Tech, Flint723
30 #ifdef USE_GYRO_DATA_ANALYSE
33 #include "build/debug.h"
35 #include "common/filter.h"
36 #include "common/maths.h"
37 #include "common/time.h"
38 #include "common/utils.h"
40 #include "drivers/accgyro/accgyro.h"
41 #include "drivers/time.h"
43 #include "sensors/gyro.h"
44 #include "sensors/gyroanalyse.h"
46 // The FFT splits the frequency domain into an number of bins
47 // A sampling frequency of 1000 and max frequency of 500 at a window size of 32 gives 16 frequency bins each with a width 31.25Hz
48 // Eg [0,31), [31,62), [62, 93) etc
50 #define FFT_WINDOW_SIZE 32 // max for f3 targets
51 #define FFT_BIN_COUNT (FFT_WINDOW_SIZE / 2)
52 #define FFT_BIN_OFFSET 1 // compare 1 + this offset FFT bins for peak, ie if 1 start 2.5 * 41.655 or about 104Hz
53 #define FFT_SAMPLING_RATE_HZ 1333 // analyse up to 666Hz, 16 bins each 41.655z wide
54 #define FFT_BPF_HZ (FFT_SAMPLING_RATE_HZ / 4) // centre frequency of bandpass that constrains input to FFT
55 #define FFT_BIQUAD_Q 0.05f // bandpass quality factor, 0.1 for steep sided bandpass
56 #define FFT_RESOLUTION ((float)FFT_SAMPLING_RATE_HZ / FFT_WINDOW_SIZE) // hz per bin
57 #define FFT_MIN_BIN_RISE 2 // following bin must be at least 2 times previous to indicate start of peak
58 #define DYN_NOTCH_SMOOTH_FREQ_HZ 60 // lowpass frequency for smoothing notch centre point
59 #define DYN_NOTCH_MIN_CENTRE_HZ 125 // notch centre point will not go below this, must be greater than cutoff, mid of bottom bin
60 #define DYN_NOTCH_MAX_CENTRE_HZ (FFT_SAMPLING_RATE_HZ / 2) // maximum notch centre frequency limited by nyquist
61 #define DYN_NOTCH_WIDTH_PERCENT 25 // maybe adjustable via CLI?
62 #define DYN_NOTCH_CUTOFF ((float)(100 - DYN_NOTCH_WIDTH_PERCENT) / 100)
63 #define DYN_NOTCH_MIN_CUTOFF_HZ 105 // lowest allowed notch cutoff frequency
64 #define DYN_NOTCH_CALC_TICKS (XYZ_AXIS_COUNT * 4) // we need 4 steps for each axis
66 static FAST_RAM_ZERO_INIT
uint16_t fftSamplingCount
;
68 // gyro data used for frequency analysis
69 static float FAST_RAM_ZERO_INIT gyroData
[XYZ_AXIS_COUNT
][FFT_WINDOW_SIZE
];
71 static FAST_RAM_ZERO_INIT arm_rfft_fast_instance_f32 fftInstance
;
72 static FAST_RAM_ZERO_INIT
float fftData
[FFT_WINDOW_SIZE
];
73 static FAST_RAM_ZERO_INIT
float rfftData
[FFT_WINDOW_SIZE
];
74 static FAST_RAM_ZERO_INIT gyroFftData_t fftResult
[XYZ_AXIS_COUNT
];
76 // use a circular buffer for the last FFT_WINDOW_SIZE samples
77 static FAST_RAM_ZERO_INIT
uint16_t fftIdx
;
79 // bandpass filter gyro data
80 static FAST_RAM_ZERO_INIT biquadFilter_t fftGyroFilter
[XYZ_AXIS_COUNT
];
82 // filter for smoothing frequency estimation
83 static FAST_RAM_ZERO_INIT biquadFilter_t fftFreqFilter
[XYZ_AXIS_COUNT
];
85 // Hanning window, see https://en.wikipedia.org/wiki/Window_function#Hann_.28Hanning.29_window
86 static FAST_RAM_ZERO_INIT
float hanningWindow
[FFT_WINDOW_SIZE
];
88 void initHanning(void)
90 for (int i
= 0; i
< FFT_WINDOW_SIZE
; i
++) {
91 hanningWindow
[i
] = (0.5 - 0.5 * cos_approx(2 * M_PIf
* i
/ (FFT_WINDOW_SIZE
- 1)));
95 void initGyroData(void)
97 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
98 for (int i
= 0; i
< FFT_WINDOW_SIZE
; i
++) {
99 gyroData
[axis
][i
] = 0;
104 void gyroDataAnalyseInit(uint32_t targetLooptimeUs
)
106 // initialise even if FEATURE_DYNAMIC_FILTER not set, since it may be set later
107 const uint16_t samplingFrequency
= 1000000 / targetLooptimeUs
;
108 fftSamplingCount
= samplingFrequency
/ FFT_SAMPLING_RATE_HZ
;
109 arm_rfft_fast_init_f32(&fftInstance
, FFT_WINDOW_SIZE
);
114 // recalculation of filters takes 4 calls per axis => each filter gets updated every DYN_NOTCH_CALC_TICKS calls
115 // at 4khz gyro loop rate this means 4khz / 4 / 3 = 333Hz => update every 3ms
116 // for gyro rate > 16kHz, we have update frequency of 1kHz => 1ms
117 const float looptime
= MAX(1000000u / FFT_SAMPLING_RATE_HZ
, targetLooptimeUs
* DYN_NOTCH_CALC_TICKS
);
118 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
119 fftResult
[axis
].centerFreq
= 200; // any init value
120 biquadFilterInitLPF(&fftFreqFilter
[axis
], DYN_NOTCH_SMOOTH_FREQ_HZ
, looptime
);
121 biquadFilterInit(&fftGyroFilter
[axis
], FFT_BPF_HZ
, 1000000 / FFT_SAMPLING_RATE_HZ
, FFT_BIQUAD_Q
, FILTER_BPF
);
126 const gyroFftData_t
*gyroFftData(int axis
)
128 return &fftResult
[axis
];
131 static FAST_RAM_ZERO_INIT
float fftAcc
[XYZ_AXIS_COUNT
];
132 void gyroDataAnalysePush(const int axis
, const float sample
)
134 fftAcc
[axis
] += sample
;
138 * Collect gyro data, to be analysed in gyroDataAnalyseUpdate function
140 void gyroDataAnalyse(biquadFilter_t
*notchFilterDyn
)
142 // accumulator for oversampled data => no aliasing and less noise
143 static FAST_RAM_ZERO_INIT
uint32_t fftAccCount
;
144 static FAST_RAM_ZERO_INIT
uint32_t gyroDataAnalyseUpdateTicks
;
146 // samples should have been pushed by `gyroDataAnalysePush`
147 // if gyro sampling is > 1kHz, accumulate multiple samples
151 if (fftAccCount
== fftSamplingCount
) {
154 //calculate mean value of accumulated samples
155 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
156 float sample
= fftAcc
[axis
] / fftSamplingCount
;
157 sample
= biquadFilterApply(&fftGyroFilter
[axis
], sample
);
158 gyroData
[axis
][fftIdx
] = sample
;
160 DEBUG_SET(DEBUG_FFT
, 2, lrintf(sample
));
164 fftIdx
= (fftIdx
+ 1) % FFT_WINDOW_SIZE
;
166 // We need DYN_NOTCH_CALC_TICKS tick to update all axis with newly sampled value
167 gyroDataAnalyseUpdateTicks
= DYN_NOTCH_CALC_TICKS
;
170 // calculate FFT and update filters
171 if (gyroDataAnalyseUpdateTicks
> 0) {
172 gyroDataAnalyseUpdate(notchFilterDyn
);
173 --gyroDataAnalyseUpdateTicks
;
177 void stage_rfft_f32(arm_rfft_fast_instance_f32
* S
, float32_t
* p
, float32_t
* pOut
);
178 void arm_cfft_radix8by2_f32( arm_cfft_instance_f32
* S
, float32_t
* p1
);
179 void arm_cfft_radix8by4_f32( arm_cfft_instance_f32
* S
, float32_t
* p1
);
180 void arm_radix8_butterfly_f32(float32_t
* pSrc
, uint16_t fftLen
, const float32_t
* pCoef
, uint16_t twidCoefModifier
);
181 void arm_bitreversal_32(uint32_t * pSrc
, const uint16_t bitRevLen
, const uint16_t * pBitRevTable
);
187 STEP_ARM_CMPLX_MAG_F32
,
188 STEP_CALC_FREQUENCIES
,
195 * Analyse last gyro data from the last FFT_WINDOW_SIZE milliseconds
197 void gyroDataAnalyseUpdate(biquadFilter_t
*notchFilterDyn
)
201 arm_cfft_instance_f32
* Sint
= &(fftInstance
.Sint
);
203 uint32_t startTime
= 0;
204 if (debugMode
== (DEBUG_FFT_TIME
))
205 startTime
= micros();
207 DEBUG_SET(DEBUG_FFT_TIME
, 0, step
);
209 case STEP_ARM_CFFT_F32
:
211 switch (FFT_BIN_COUNT
) {
214 arm_cfft_radix8by2_f32(Sint
, fftData
);
218 arm_cfft_radix8by4_f32(Sint
, fftData
);
222 arm_radix8_butterfly_f32(fftData
, FFT_BIN_COUNT
, Sint
->pTwiddle
, 1);
225 DEBUG_SET(DEBUG_FFT_TIME
, 1, micros() - startTime
);
228 case STEP_BITREVERSAL
:
231 arm_bitreversal_32((uint32_t*) fftData
, Sint
->bitRevLength
, Sint
->pBitRevTable
);
232 DEBUG_SET(DEBUG_FFT_TIME
, 1, micros() - startTime
);
236 case STEP_STAGE_RFFT_F32
:
239 // this does not work in place => fftData AND rfftData needed
240 stage_rfft_f32(&fftInstance
, fftData
, rfftData
);
241 DEBUG_SET(DEBUG_FFT_TIME
, 1, micros() - startTime
);
244 case STEP_ARM_CMPLX_MAG_F32
:
247 arm_cmplx_mag_f32(rfftData
, fftData
, FFT_BIN_COUNT
);
248 DEBUG_SET(DEBUG_FFT_TIME
, 2, micros() - startTime
);
252 case STEP_CALC_FREQUENCIES
:
255 // calculate FFT centreFreq
257 float fftWeightedSum
= 0;
258 fftResult
[axis
].maxVal
= 0;
259 bool fftIncreasing
= false;
261 // iterate over fft data and calculate weighted indexes
262 for (int i
= 1 + FFT_BIN_OFFSET
; i
< FFT_BIN_COUNT
; i
++) {
263 if (!fftIncreasing
&& (fftData
[i
] < fftData
[i
-1] * FFT_MIN_BIN_RISE
)) {
264 // do nothing unless has increased at some point
266 cubedData
= fftData
[i
] * fftData
[i
] * fftData
[i
]; //more weight on higher peaks
268 cubedData
+= fftData
[i
-1] * fftData
[i
-1] * fftData
[i
-1]; //add previous bin before first rise
271 fftWeightedSum
+= cubedData
* (i
+ 1); // calculate weighted index starting at 1, not 0
272 fftIncreasing
= true;
275 // get weighted center of relevant frequency range (this way we have a better resolution than 31.25Hz)
279 // idx was shifted by 1 to start at 1, not 0
280 fftMeanIndex
= (fftWeightedSum
/ fftSum
) - 1;
281 // the index points at the center frequency of each bin so index 0 is actually 16.125Hz
282 centerFreq
= constrain(fftMeanIndex
* FFT_RESOLUTION
, DYN_NOTCH_MIN_CENTRE_HZ
, DYN_NOTCH_MAX_CENTRE_HZ
);
284 centerFreq
= DYN_NOTCH_MAX_CENTRE_HZ
; // if no peak, go to highest point to minimise delay
286 centerFreq
= biquadFilterApply(&fftFreqFilter
[axis
], centerFreq
);
287 fftResult
[axis
].centerFreq
= centerFreq
;
289 DEBUG_SET(DEBUG_FFT
, 3, lrintf(fftMeanIndex
* 100));
291 DEBUG_SET(DEBUG_FFT_FREQ
, axis
, fftResult
[axis
].centerFreq
);
292 DEBUG_SET(DEBUG_FFT_TIME
, 1, micros() - startTime
);
295 case STEP_UPDATE_FILTERS
:
298 // calculate cutoffFreq and notch Q, update notch filter
299 float cutoffFreq
= fmax(fftResult
[axis
].centerFreq
* DYN_NOTCH_CUTOFF
, DYN_NOTCH_MIN_CUTOFF_HZ
);
300 float notchQ
= filterGetNotchQ(fftResult
[axis
].centerFreq
, cutoffFreq
);
301 biquadFilterUpdate(¬chFilterDyn
[axis
], fftResult
[axis
].centerFreq
, gyro
.targetLooptime
, notchQ
, FILTER_NOTCH
);
302 DEBUG_SET(DEBUG_FFT_TIME
, 1, micros() - startTime
);
304 axis
= (axis
+ 1) % 3;
311 // apply hanning window to gyro samples and store result in fftData
312 // hanning starts and ends with 0, could be skipped for minor speed improvement
313 uint8_t ringBufIdx
= FFT_WINDOW_SIZE
- fftIdx
;
314 arm_mult_f32(&gyroData
[axis
][fftIdx
], &hanningWindow
[0], &fftData
[0], ringBufIdx
);
316 arm_mult_f32(&gyroData
[axis
][0], &hanningWindow
[ringBufIdx
], &fftData
[ringBufIdx
], fftIdx
);
318 DEBUG_SET(DEBUG_FFT_TIME
, 1, micros() - startTime
);
322 step
= (step
+ 1) % STEP_COUNT
;
325 #endif // USE_GYRO_DATA_ANALYSE