2 * This file is part of INAV Project.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
6 * You can obtain one at http://mozilla.org/MPL/2.0/.
8 * Alternatively, the contents of this file may be used under the terms
9 * of the GNU General Public License Version 3, as described below:
11 * This file is free software: you may copy, redistribute and/or modify
12 * it under the terms of the GNU General Public License as published by the
13 * Free Software Foundation, either version 3 of the License, or (at your
14 * option) any later version.
16 * This file is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
19 * Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see http://www.gnu.org/licenses/.
32 #include "build/build_config.h"
34 #include "common/axis.h"
35 #include "common/maths.h"
36 #include "common/utils.h"
37 #include "common/time.h"
39 #include "config/feature.h"
40 #include "config/parameter_group.h"
41 #include "config/parameter_group_ids.h"
43 #include "drivers/io.h"
44 #include "drivers/light_led.h"
45 #include "drivers/time.h"
47 #include "drivers/opflow/opflow.h"
48 #include "drivers/opflow/opflow_fake.h"
49 #include "drivers/opflow/opflow_virtual.h"
51 #include "fc/config.h"
52 #include "fc/runtime_config.h"
53 #include "fc/settings.h"
55 #include "sensors/boardalignment.h"
56 #include "sensors/gyro.h"
57 #include "sensors/sensors.h"
58 #include "sensors/opflow.h"
60 #include "scheduler/scheduler.h"
62 #include "io/opflow.h"
64 #include "build/debug.h"
69 static bool opflowIsCalibrating
= false;
70 static timeMs_t opflowCalibrationStartedAt
;
71 static float opflowCalibrationBodyAcc
;
72 static float opflowCalibrationFlowAcc
;
74 #define OPFLOW_SQUAL_THRESHOLD_HIGH 35 // TBD
75 #define OPFLOW_SQUAL_THRESHOLD_LOW 10 // TBD
76 #define OPFLOW_UPDATE_TIMEOUT_US 200000 // At least 5Hz updates required
77 #define OPFLOW_CALIBRATE_TIME_MS 30000 // 30 second calibration time
79 PG_REGISTER_WITH_RESET_TEMPLATE(opticalFlowConfig_t
, opticalFlowConfig
, PG_OPFLOW_CONFIG
, 2);
81 PG_RESET_TEMPLATE(opticalFlowConfig_t
, opticalFlowConfig
,
82 .opflow_hardware
= SETTING_OPFLOW_HARDWARE_DEFAULT
,
83 .opflow_align
= SETTING_ALIGN_OPFLOW_DEFAULT
,
84 .opflow_scale
= SETTING_OPFLOW_SCALE_DEFAULT
,
87 static bool opflowDetect(opflowDev_t
* dev
, uint8_t opflowHardwareToUse
)
89 opticalFlowSensor_e opflowHardware
= OPFLOW_NONE
;
90 requestedSensors
[SENSOR_INDEX_OPFLOW
] = opflowHardwareToUse
;
92 switch (opflowHardwareToUse
) {
94 #if defined(USE_OPFLOW_FAKE)
95 if (fakeOpflowDetect(dev
)) {
96 opflowHardware
= OPFLOW_FAKE
;
102 #if defined(USE_OPFLOW_CXOF)
103 if (virtualOpflowDetect(dev
, &opflowCxofVtable
)) {
104 opflowHardware
= OPFLOW_CXOF
;
110 #if defined(USE_OPFLOW_MSP)
111 if (virtualOpflowDetect(dev
, &opflowMSPVtable
)) {
112 opflowHardware
= OPFLOW_MSP
;
118 opflowHardware
= OPFLOW_NONE
;
122 if (opflowHardware
== OPFLOW_NONE
) {
123 sensorsClear(SENSOR_OPFLOW
);
127 detectedSensors
[SENSOR_INDEX_OPFLOW
] = opflowHardware
;
128 sensorsSet(SENSOR_OPFLOW
);
132 static void opflowZeroBodyGyroAcc(void)
134 opflow
.gyroBodyRateTimeUs
= 0;
135 opflow
.gyroBodyRateAcc
[X
] = 0;
136 opflow
.gyroBodyRateAcc
[Y
] = 0;
139 bool opflowInit(void)
141 if (!opflowDetect(&opflow
.dev
, opticalFlowConfig()->opflow_hardware
)) {
145 if (!opflow
.dev
.initFn(&opflow
.dev
)) {
146 sensorsClear(SENSOR_OPFLOW
);
150 opflowZeroBodyGyroAcc();
155 void opflowStartCalibration(void)
157 opflowCalibrationStartedAt
= millis();
158 opflowIsCalibrating
= true;
159 opflowCalibrationBodyAcc
= 0;
160 opflowCalibrationFlowAcc
= 0;
164 * This is called periodically by the scheduler
166 void opflowUpdate(timeUs_t currentTimeUs
)
168 if (!opflow
.dev
.updateFn
)
171 if (opflow
.dev
.updateFn(&opflow
.dev
)) {
172 // Indicate valid update
173 opflow
.isHwHealty
= true;
174 opflow
.lastValidUpdate
= currentTimeUs
;
175 opflow
.rawQuality
= opflow
.dev
.rawData
.quality
;
177 // Handle state switching
178 switch (opflow
.flowQuality
) {
179 case OPFLOW_QUALITY_INVALID
:
180 if (opflow
.dev
.rawData
.quality
>= OPFLOW_SQUAL_THRESHOLD_HIGH
) {
181 opflow
.flowQuality
= OPFLOW_QUALITY_VALID
;
185 case OPFLOW_QUALITY_VALID
:
186 if (opflow
.dev
.rawData
.quality
<= OPFLOW_SQUAL_THRESHOLD_LOW
) {
187 opflow
.flowQuality
= OPFLOW_QUALITY_INVALID
;
192 // Opflow updated. Assume zero valus unless further processing sets otherwise
193 opflow
.flowRate
[X
] = 0;
194 opflow
.flowRate
[Y
] = 0;
195 opflow
.bodyRate
[X
] = 0;
196 opflow
.bodyRate
[Y
] = 0;
198 // In the following code we operate deg/s and do conversion to rad/s in the last step
199 // Calculate body rates
200 if (opflow
.gyroBodyRateTimeUs
> 0) {
201 opflow
.bodyRate
[X
] = opflow
.gyroBodyRateAcc
[X
] / opflow
.gyroBodyRateTimeUs
;
202 opflow
.bodyRate
[Y
] = opflow
.gyroBodyRateAcc
[Y
] / opflow
.gyroBodyRateTimeUs
;
205 // If quality of the flow from the sensor is good - process further
206 if (opflow
.flowQuality
== OPFLOW_QUALITY_VALID
) {
207 const float integralToRateScaler
= (opticalFlowConfig()->opflow_scale
> 0.01f
) ? (1.0e6
/ opflow
.dev
.rawData
.deltaTime
) / (float)opticalFlowConfig()->opflow_scale
: 0.0f
;
209 // Apply sensor alignment
210 applySensorAlignment(opflow
.dev
.rawData
.flowRateRaw
, opflow
.dev
.rawData
.flowRateRaw
, opticalFlowConfig()->opflow_align
);
212 // Calculate flow rate and accumulated body rate
213 opflow
.flowRate
[X
] = opflow
.dev
.rawData
.flowRateRaw
[X
] * integralToRateScaler
;
214 opflow
.flowRate
[Y
] = opflow
.dev
.rawData
.flowRateRaw
[Y
] * integralToRateScaler
;
216 // Only update DEBUG_FLOW_RAW if flow is good
217 DEBUG_SET(DEBUG_FLOW_RAW
, 0, (opflow
.flowRate
[X
]));
218 DEBUG_SET(DEBUG_FLOW_RAW
, 1, (opflow
.flowRate
[Y
]));
219 DEBUG_SET(DEBUG_FLOW_RAW
, 2, (opflow
.bodyRate
[X
]));
220 DEBUG_SET(DEBUG_FLOW_RAW
, 3, (opflow
.bodyRate
[Y
]));
223 // Process calibration
224 if (opflowIsCalibrating
) {
228 if ((millis() - opflowCalibrationStartedAt
) > OPFLOW_CALIBRATE_TIME_MS
) {
229 // Finish calibration if we accumulated more than 3600 deg of rotation over 30 seconds
230 if (opflowCalibrationBodyAcc
> 3600.0f
) {
231 opticalFlowConfigMutable()->opflow_scale
= opflowCalibrationFlowAcc
/ opflowCalibrationBodyAcc
;
232 saveConfigAndNotify();
235 opflowIsCalibrating
= 0;
237 else if (opflow
.flowQuality
== OPFLOW_QUALITY_VALID
) {
238 // Ongoing calibration - accumulate body and flow rotation magniture if opflow quality is good enough
239 const float invDt
= 1.0e6
/ opflow
.dev
.rawData
.deltaTime
;
240 opflowCalibrationBodyAcc
+= calc_length_pythagorean_2D(opflow
.bodyRate
[X
], opflow
.bodyRate
[Y
]);
241 opflowCalibrationFlowAcc
+= calc_length_pythagorean_2D(opflow
.dev
.rawData
.flowRateRaw
[X
], opflow
.dev
.rawData
.flowRateRaw
[Y
]) * invDt
;
245 // Convert to radians so NAV doesn't have to do the conversion
246 opflow
.bodyRate
[X
] = DEGREES_TO_RADIANS(opflow
.bodyRate
[X
]);
247 opflow
.bodyRate
[Y
] = DEGREES_TO_RADIANS(opflow
.bodyRate
[Y
]);
248 opflow
.flowRate
[X
] = DEGREES_TO_RADIANS(opflow
.flowRate
[X
]);
249 opflow
.flowRate
[Y
] = DEGREES_TO_RADIANS(opflow
.flowRate
[Y
]);
251 // Zero out gyro accumulators to calculate rotation per flow update
252 opflowZeroBodyGyroAcc();
255 // No new data available
256 if (opflow
.isHwHealty
&& ((currentTimeUs
- opflow
.lastValidUpdate
) > OPFLOW_UPDATE_TIMEOUT_US
)) {
257 opflow
.isHwHealty
= false;
259 opflow
.flowQuality
= OPFLOW_QUALITY_INVALID
;
260 opflow
.rawQuality
= 0;
262 opflow
.flowRate
[X
] = 0;
263 opflow
.flowRate
[Y
] = 0;
264 opflow
.bodyRate
[X
] = 0;
265 opflow
.bodyRate
[Y
] = 0;
267 opflowZeroBodyGyroAcc();
272 /* Run a simple gyro update integrator to estimate average body rate between two optical flow updates */
273 void opflowGyroUpdateCallback(timeUs_t gyroUpdateDeltaUs
)
275 if (!opflow
.isHwHealty
)
278 for (int axis
= 0; axis
< 2; axis
++) {
279 opflow
.gyroBodyRateAcc
[axis
] += gyro
.gyroADCf
[axis
] * gyroUpdateDeltaUs
;
282 opflow
.gyroBodyRateTimeUs
+= gyroUpdateDeltaUs
;
285 bool opflowIsHealthy(void)
287 return opflow
.isHwHealty
;