Dynamic Notch Filter Update
[betaflight.git] / src / main / sensors / gyroanalyse.c
blobfd7fc2180991619c398f2ee8d2e9f42deafe86ec
1 /*
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)
8 * any later version.
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
26 #include <stdint.h>
28 #include "platform.h"
30 #ifdef USE_GYRO_DATA_ANALYSE
31 #include "arm_math.h"
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);
111 initGyroData();
112 initHanning();
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);
125 // used in OSD
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
148 fftAccCount++;
150 // this runs at 1kHz
151 if (fftAccCount == fftSamplingCount) {
152 fftAccCount = 0;
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;
159 if (axis == 0)
160 DEBUG_SET(DEBUG_FFT, 2, lrintf(sample));
161 fftAcc[axis] = 0;
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);
183 typedef enum {
184 STEP_ARM_CFFT_F32,
185 STEP_BITREVERSAL,
186 STEP_STAGE_RFFT_F32,
187 STEP_ARM_CMPLX_MAG_F32,
188 STEP_CALC_FREQUENCIES,
189 STEP_UPDATE_FILTERS,
190 STEP_HANNING,
191 STEP_COUNT
192 } UpdateStep_e;
195 * Analyse last gyro data from the last FFT_WINDOW_SIZE milliseconds
197 void gyroDataAnalyseUpdate(biquadFilter_t *notchFilterDyn)
199 static int axis;
200 static int step;
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);
208 switch (step) {
209 case STEP_ARM_CFFT_F32:
211 switch (FFT_BIN_COUNT) {
212 case 16:
213 // 16us
214 arm_cfft_radix8by2_f32(Sint, fftData);
215 break;
216 case 32:
217 // 35us
218 arm_cfft_radix8by4_f32(Sint, fftData);
219 break;
220 case 64:
221 // 70us
222 arm_radix8_butterfly_f32(fftData, FFT_BIN_COUNT, Sint->pTwiddle, 1);
223 break;
225 DEBUG_SET(DEBUG_FFT_TIME, 1, micros() - startTime);
226 break;
228 case STEP_BITREVERSAL:
230 // 6us
231 arm_bitreversal_32((uint32_t*) fftData, Sint->bitRevLength, Sint->pBitRevTable);
232 DEBUG_SET(DEBUG_FFT_TIME, 1, micros() - startTime);
233 step++;
234 FALLTHROUGH;
236 case STEP_STAGE_RFFT_F32:
238 // 14us
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);
242 break;
244 case STEP_ARM_CMPLX_MAG_F32:
246 // 8us
247 arm_cmplx_mag_f32(rfftData, fftData, FFT_BIN_COUNT);
248 DEBUG_SET(DEBUG_FFT_TIME, 2, micros() - startTime);
249 step++;
250 FALLTHROUGH;
252 case STEP_CALC_FREQUENCIES:
254 // 13us
255 // calculate FFT centreFreq
256 float fftSum = 0;
257 float fftWeightedSum = 0;
258 fftResult[axis].maxVal = 0;
259 bool fftIncreasing = false;
260 float cubedData;
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
265 } else {
266 cubedData = fftData[i] * fftData[i] * fftData[i]; //more weight on higher peaks
267 if (!fftIncreasing){
268 cubedData += fftData[i-1] * fftData[i-1] * fftData[i-1]; //add previous bin before first rise
270 fftSum += cubedData;
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)
276 float centerFreq;
277 float fftMeanIndex;
278 if (fftSum > 0) {
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);
283 } else {
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;
288 if (axis == 0) {
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);
293 break;
295 case STEP_UPDATE_FILTERS:
297 // 7us
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(&notchFilterDyn[axis], fftResult[axis].centerFreq, gyro.targetLooptime, notchQ, FILTER_NOTCH);
302 DEBUG_SET(DEBUG_FFT_TIME, 1, micros() - startTime);
304 axis = (axis + 1) % 3;
305 step++;
306 FALLTHROUGH;
308 case STEP_HANNING:
310 // 5us
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);
315 if (fftIdx > 0)
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