Merge pull request #9335 from iNavFlight/MrD_Add-odometer-to-OSD
[inav.git] / src / main / flight / gyroanalyse.c
bloba22be18d700238c87caf44a30100cb9f98972d01
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_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"
48 enum {
49 STEP_ARM_CFFT_F32,
50 STEP_BITREVERSAL_AND_STAGE_RFFT_F32,
51 STEP_MAGNITUDE_AND_FREQUENCY,
52 STEP_UPDATE_FILTERS_AND_HANNING,
53 STEP_COUNT
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
76 ) {
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);
148 if (denom != 0.0f) {
149 //Cap precise bin to prevent off values if parabola is not fitted correctly
150 preciseBin += constrainf((y0 - y2) / denom, -0.5f, 0.5f);
153 return preciseBin;
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);
169 break;
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);
175 break;
177 case STEP_MAGNITUDE_AND_FREQUENCY:
179 // 8us
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;
188 // Find peaks
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
193 if (
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];
208 break;
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;
228 break;
230 case STEP_UPDATE_FILTERS_AND_HANNING:
234 * Update frequencies
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);
243 } else {
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