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_DYNAMIC_FILTERS
32 #include "build/debug.h"
34 #include "common/filter.h"
35 #include "common/maths.h"
36 #include "common/time.h"
37 #include "common/utils.h"
38 #include "config/feature.h"
40 #include "drivers/accgyro/accgyro.h"
41 #include "drivers/time.h"
43 #include "sensors/gyro.h"
44 #include "fc/config.h"
46 #include "gyroanalyse.h"
50 STEP_BITREVERSAL_AND_STAGE_RFFT_F32
,
51 STEP_MAGNITUDE_AND_FREQUENCY
,
52 STEP_UPDATE_FILTERS_AND_HANNING
,
56 // The FFT splits the frequency domain into an number of bins
57 // A sampling frequency of 1000 and max frequency of 500 at a window size of 32 gives 16 frequency bins each 31.25Hz wide
58 // Eg [0,31), [31,62), [62, 93) etc
59 // for gyro loop >= 4KHz, sample rate 2000 defines FFT range to 1000Hz, 16 bins each 62.5 Hz wide
60 // NB FFT_WINDOW_SIZE is set to 32 in gyroanalyse.h
61 #define FFT_BIN_COUNT (FFT_WINDOW_SIZE / 2)
62 // smoothing frequency for FFT centre frequency
63 #define DYN_NOTCH_SMOOTH_FREQ_HZ 25
66 * Slow down gyro sample acquisition. This lowers the max frequency but increases the resolution.
67 * On default 500us looptime and denominator 1, max frequency is 1000Hz with a resolution of 31.25Hz
68 * On default 500us looptime and denominator 2, max frequency is 500Hz with a resolution of 15.6Hz
70 #define FFT_SAMPLING_DENOMINATOR 2
72 void gyroDataAnalyseStateInit(
73 gyroAnalyseState_t
*state
,
74 uint16_t minFrequency
,
75 uint32_t targetLooptimeUs
77 state
->minFrequency
= minFrequency
;
79 state
->fftSamplingRateHz
= 1e6f
/ targetLooptimeUs
/ FFT_SAMPLING_DENOMINATOR
;
80 state
->maxFrequency
= state
->fftSamplingRateHz
/ 2; //max possible frequency is half the sampling rate
81 state
->fftResolution
= (float)state
->maxFrequency
/ FFT_BIN_COUNT
;
83 state
->fftStartBin
= state
->minFrequency
/ lrintf(state
->fftResolution
);
85 for (int i
= 0; i
< FFT_WINDOW_SIZE
; i
++) {
86 state
->hanningWindow
[i
] = (0.5f
- 0.5f
* cos_approx(2 * M_PIf
* i
/ (FFT_WINDOW_SIZE
- 1)));
89 arm_rfft_fast_init_f32(&state
->fftInstance
, FFT_WINDOW_SIZE
);
91 // Frequency filter is executed every 12 cycles. 4 steps per cycle, 3 axises
92 const uint32_t filterUpdateUs
= targetLooptimeUs
* STEP_COUNT
* XYZ_AXIS_COUNT
;
94 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
96 for (int i
= 0; i
< DYN_NOTCH_PEAK_COUNT
; i
++) {
97 state
->centerFrequency
[axis
][i
] = state
->maxFrequency
;
98 pt1FilterInit(&state
->detectedFrequencyFilter
[axis
][i
], DYN_NOTCH_SMOOTH_FREQ_HZ
, US2S(filterUpdateUs
));
104 void gyroDataAnalysePush(gyroAnalyseState_t
*state
, const int axis
, const float sample
)
106 state
->currentSample
[axis
] = sample
;
109 static void gyroDataAnalyseUpdate(gyroAnalyseState_t
*state
);
112 * Collect gyro data, to be analysed in gyroDataAnalyseUpdate function
114 void gyroDataAnalyse(gyroAnalyseState_t
*state
)
116 state
->filterUpdateExecute
= false; //This will be changed to true only if new data is present
118 static uint8_t samplingIndex
= 0;
120 if (samplingIndex
== 0) {
121 // calculate mean value of accumulated samples
122 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
123 state
->downsampledGyroData
[axis
][state
->circularBufferIdx
] = state
->currentSample
[axis
];
126 state
->circularBufferIdx
= (state
->circularBufferIdx
+ 1) % FFT_WINDOW_SIZE
;
129 samplingIndex
= (samplingIndex
+ 1) % FFT_SAMPLING_DENOMINATOR
;
131 gyroDataAnalyseUpdate(state
);
134 void stage_rfft_f32(arm_rfft_fast_instance_f32
*S
, float32_t
*p
, float32_t
*pOut
);
135 void arm_cfft_radix8by4_f32(arm_cfft_instance_f32
*S
, float32_t
*p1
);
136 void arm_bitreversal_32(uint32_t *pSrc
, const uint16_t bitRevLen
, const uint16_t *pBitRevTable
);
138 static float computeParabolaMean(gyroAnalyseState_t
*state
, uint8_t peakBinIndex
) {
139 float preciseBin
= peakBinIndex
;
141 // Height of peak bin (y1) and shoulder bins (y0, y2)
142 const float y0
= state
->fftData
[peakBinIndex
- 1];
143 const float y1
= state
->fftData
[peakBinIndex
];
144 const float y2
= state
->fftData
[peakBinIndex
- 1];
146 // Estimate true peak position aka. preciseBin (fit parabola y(x) over y0, y1 and y2, solve dy/dx=0 for x)
147 const float denom
= 2.0f
* (y0
- 2 * y1
+ y2
);
149 //Cap precise bin to prevent off values if parabola is not fitted correctly
150 preciseBin
+= constrainf((y0
- y2
) / denom
, -0.5f
, 0.5f
);
157 * Analyse last gyro data from the last FFT_WINDOW_SIZE milliseconds
159 static NOINLINE
void gyroDataAnalyseUpdate(gyroAnalyseState_t
*state
)
162 arm_cfft_instance_f32
*Sint
= &(state
->fftInstance
.Sint
);
164 switch (state
->updateStep
) {
165 case STEP_ARM_CFFT_F32
:
167 // Important this works only with FFT windows size of 64 elements!
168 arm_cfft_radix8by4_f32(Sint
, state
->fftData
);
171 case STEP_BITREVERSAL_AND_STAGE_RFFT_F32
:
173 arm_bitreversal_32((uint32_t*) state
->fftData
, Sint
->bitRevLength
, Sint
->pBitRevTable
);
174 stage_rfft_f32(&state
->fftInstance
, state
->fftData
, state
->rfftData
);
177 case STEP_MAGNITUDE_AND_FREQUENCY
:
180 arm_cmplx_mag_f32(state
->rfftData
, state
->fftData
, FFT_BIN_COUNT
);
182 //Zero the data structure
183 for (int i
= 0; i
< DYN_NOTCH_PEAK_COUNT
; i
++) {
184 state
->peaks
[i
].bin
= 0;
185 state
->peaks
[i
].value
= 0.0f
;
189 for (int bin
= (state
->fftStartBin
+ 1); bin
< FFT_BIN_COUNT
- 1; bin
++) {
191 * Peak is defined if the current bin is greater than the previous bin and the next bin
194 state
->fftData
[bin
] > state
->fftData
[bin
- 1] &&
195 state
->fftData
[bin
] > state
->fftData
[bin
+ 1]
198 * We are only interested in N biggest peaks
199 * Check previously found peaks and update the structure if necessary
201 for (int p
= 0; p
< DYN_NOTCH_PEAK_COUNT
; p
++) {
202 if (state
->fftData
[bin
] > state
->peaks
[p
].value
) {
203 for (int k
= DYN_NOTCH_PEAK_COUNT
- 1; k
> p
; k
--) {
204 state
->peaks
[k
] = state
->peaks
[k
- 1];
206 state
->peaks
[p
].bin
= bin
;
207 state
->peaks
[p
].value
= state
->fftData
[bin
];
211 bin
++; // If bin is peak, next bin can't be peak => jump it
215 // Sort N biggest peaks in ascending bin order (example: 3, 8, 25, 0, 0, ..., 0)
216 for (int p
= DYN_NOTCH_PEAK_COUNT
- 1; p
> 0; p
--) {
217 for (int k
= 0; k
< p
; k
++) {
218 // Swap peaks but ignore swapping void peaks (bin = 0). This leaves
219 // void peaks at the end of peaks array without moving them
220 if (state
->peaks
[k
].bin
> state
->peaks
[k
+ 1].bin
&& state
->peaks
[k
+ 1].bin
!= 0) {
221 peak_t temp
= state
->peaks
[k
];
222 state
->peaks
[k
] = state
->peaks
[k
+ 1];
223 state
->peaks
[k
+ 1] = temp
;
230 case STEP_UPDATE_FILTERS_AND_HANNING
:
236 for (int i
= 0; i
< DYN_NOTCH_PEAK_COUNT
; i
++) {
238 if (state
->peaks
[i
].bin
> 0) {
239 const int bin
= constrain(state
->peaks
[i
].bin
, state
->fftStartBin
, FFT_BIN_COUNT
- 1);
240 float frequency
= computeParabolaMean(state
, bin
) * state
->fftResolution
;
242 state
->centerFrequency
[state
->updateAxis
][i
] = pt1FilterApply(&state
->detectedFrequencyFilter
[state
->updateAxis
][i
], frequency
);
244 state
->centerFrequency
[state
->updateAxis
][i
] = 0.0f
;
249 * Filters will be updated inside dynamicGyroNotchFiltersUpdate()
251 state
->filterUpdateExecute
= true;
252 state
->filterUpdateAxis
= state
->updateAxis
;
254 //Switch to the next axis
255 state
->updateAxis
= (state
->updateAxis
+ 1) % XYZ_AXIS_COUNT
;
257 // apply hanning window to gyro samples and store result in fftData
258 // hanning starts and ends with 0, could be skipped for minor speed improvement
259 arm_mult_f32(state
->downsampledGyroData
[state
->updateAxis
], state
->hanningWindow
, state
->fftData
, FFT_WINDOW_SIZE
);
263 state
->updateStep
= (state
->updateStep
+ 1) % STEP_COUNT
;
266 #endif // USE_DYNAMIC_FILTERS