Protection to ensure only one or the other is initialised in regards to blackbox...
[betaflight.git] / src / main / blackbox / blackbox.c
blob50a5ea1be532d35397703a6e987055cd4973fbd7
1 /*
2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <string.h>
21 #include <math.h>
23 #include "platform.h"
25 #ifdef BLACKBOX
27 #include "build/debug.h"
28 #include "build/version.h"
30 #include "common/axis.h"
31 #include "common/encoding.h"
32 #include "common/utils.h"
34 #include "blackbox.h"
35 #include "blackbox_io.h"
37 #include "drivers/sensor.h"
38 #include "drivers/compass.h"
39 #include "drivers/system.h"
40 #include "drivers/pwm_output.h"
42 #include "fc/config.h"
43 #include "fc/rc_controls.h"
44 #include "fc/runtime_config.h"
46 #include "flight/pid.h"
48 #include "io/beeper.h"
50 #include "sensors/sensors.h"
51 #include "sensors/compass.h"
52 #include "sensors/sonar.h"
54 #include "config/config_profile.h"
55 #include "config/config_master.h"
56 #include "config/feature.h"
58 #define BLACKBOX_I_INTERVAL 32
59 #define BLACKBOX_SHUTDOWN_TIMEOUT_MILLIS 200
60 #define SLOW_FRAME_INTERVAL 4096
62 #define ARRAY_LENGTH(x) (sizeof((x))/sizeof((x)[0]))
64 #define STATIC_ASSERT(condition, name ) \
65 typedef char assert_failed_ ## name [(condition) ? 1 : -1 ]
67 // Some macros to make writing FLIGHT_LOG_FIELD_* constants shorter:
69 #define PREDICT(x) CONCAT(FLIGHT_LOG_FIELD_PREDICTOR_, x)
70 #define ENCODING(x) CONCAT(FLIGHT_LOG_FIELD_ENCODING_, x)
71 #define CONDITION(x) CONCAT(FLIGHT_LOG_FIELD_CONDITION_, x)
72 #define UNSIGNED FLIGHT_LOG_FIELD_UNSIGNED
73 #define SIGNED FLIGHT_LOG_FIELD_SIGNED
75 static const char blackboxHeader[] =
76 "H Product:Blackbox flight data recorder by Nicholas Sherlock\n"
77 "H Data version:2\n"
78 "H I interval:" STR(BLACKBOX_I_INTERVAL) "\n";
80 static const char* const blackboxFieldHeaderNames[] = {
81 "name",
82 "signed",
83 "predictor",
84 "encoding",
85 "predictor",
86 "encoding"
89 /* All field definition structs should look like this (but with longer arrs): */
90 typedef struct blackboxFieldDefinition_s {
91 const char *name;
92 // If the field name has a number to be included in square brackets [1] afterwards, set it here, or -1 for no brackets:
93 int8_t fieldNameIndex;
95 // Each member of this array will be the value to print for this field for the given header index
96 uint8_t arr[1];
97 } blackboxFieldDefinition_t;
99 #define BLACKBOX_DELTA_FIELD_HEADER_COUNT ARRAY_LENGTH(blackboxFieldHeaderNames)
100 #define BLACKBOX_SIMPLE_FIELD_HEADER_COUNT (BLACKBOX_DELTA_FIELD_HEADER_COUNT - 2)
101 #define BLACKBOX_CONDITIONAL_FIELD_HEADER_COUNT (BLACKBOX_DELTA_FIELD_HEADER_COUNT - 2)
103 typedef struct blackboxSimpleFieldDefinition_s {
104 const char *name;
105 int8_t fieldNameIndex;
107 uint8_t isSigned;
108 uint8_t predict;
109 uint8_t encode;
110 } blackboxSimpleFieldDefinition_t;
112 typedef struct blackboxConditionalFieldDefinition_s {
113 const char *name;
114 int8_t fieldNameIndex;
116 uint8_t isSigned;
117 uint8_t predict;
118 uint8_t encode;
119 uint8_t condition; // Decide whether this field should appear in the log
120 } blackboxConditionalFieldDefinition_t;
122 typedef struct blackboxDeltaFieldDefinition_s {
123 const char *name;
124 int8_t fieldNameIndex;
126 uint8_t isSigned;
127 uint8_t Ipredict;
128 uint8_t Iencode;
129 uint8_t Ppredict;
130 uint8_t Pencode;
131 uint8_t condition; // Decide whether this field should appear in the log
132 } blackboxDeltaFieldDefinition_t;
135 * Description of the blackbox fields we are writing in our main intra (I) and inter (P) frames. This description is
136 * written into the flight log header so the log can be properly interpreted (but these definitions don't actually cause
137 * the encoding to happen, we have to encode the flight log ourselves in write{Inter|Intra}frame() in a way that matches
138 * the encoding we've promised here).
140 static const blackboxDeltaFieldDefinition_t blackboxMainFields[] = {
141 /* loopIteration doesn't appear in P frames since it always increments */
142 {"loopIteration",-1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(INC), .Pencode = FLIGHT_LOG_FIELD_ENCODING_NULL, CONDITION(ALWAYS)},
143 /* Time advances pretty steadily so the P-frame prediction is a straight line */
144 {"time", -1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(STRAIGHT_LINE), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
145 {"axisP", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
146 {"axisP", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
147 {"axisP", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
148 /* I terms get special packed encoding in P frames: */
149 {"axisI", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
150 {"axisI", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
151 {"axisI", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
152 {"axisD", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_0)},
153 {"axisD", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_1)},
154 {"axisD", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_2)},
155 /* rcCommands are encoded together as a group in P-frames: */
156 {"rcCommand", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
157 {"rcCommand", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
158 {"rcCommand", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
159 /* Throttle is always in the range [minthrottle..maxthrottle]: */
160 {"rcCommand", 3, UNSIGNED, .Ipredict = PREDICT(MINTHROTTLE), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
162 {"vbatLatest", -1, UNSIGNED, .Ipredict = PREDICT(VBATREF), .Iencode = ENCODING(NEG_14BIT), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_VBAT},
163 {"amperageLatest",-1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_AMPERAGE_ADC},
165 #ifdef MAG
166 {"magADC", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
167 {"magADC", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
168 {"magADC", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
169 #endif
170 #ifdef BARO
171 {"BaroAlt", -1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_BARO},
172 #endif
173 #ifdef SONAR
174 {"sonarRaw", -1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_SONAR},
175 #endif
176 {"rssi", -1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_RSSI},
178 /* Gyros and accelerometers base their P-predictions on the average of the previous 2 frames to reduce noise impact */
179 {"gyroADC", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
180 {"gyroADC", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
181 {"gyroADC", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
182 {"accSmooth", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
183 {"accSmooth", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
184 {"accSmooth", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
185 {"debug", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
186 {"debug", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
187 {"debug", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
188 {"debug", 3, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
189 /* Motors only rarely drops under minthrottle (when stick falls below mincommand), so predict minthrottle for it and use *unsigned* encoding (which is large for negative numbers but more compact for positive ones): */
190 {"motor", 0, UNSIGNED, .Ipredict = PREDICT(MINMOTOR), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_1)},
191 /* Subsequent motors base their I-frame values on the first one, P-frame values on the average of last two frames: */
192 {"motor", 1, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_2)},
193 {"motor", 2, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_3)},
194 {"motor", 3, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_4)},
195 {"motor", 4, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_5)},
196 {"motor", 5, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_6)},
197 {"motor", 6, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_7)},
198 {"motor", 7, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_8)},
200 /* Tricopter tail servo */
201 {"servo", 5, UNSIGNED, .Ipredict = PREDICT(1500), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(TRICOPTER)}
204 #ifdef GPS
205 // GPS position/vel frame
206 static const blackboxConditionalFieldDefinition_t blackboxGpsGFields[] = {
207 {"time", -1, UNSIGNED, PREDICT(LAST_MAIN_FRAME_TIME), ENCODING(UNSIGNED_VB), CONDITION(NOT_LOGGING_EVERY_FRAME)},
208 {"GPS_numSat", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
209 {"GPS_coord", 0, SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
210 {"GPS_coord", 1, SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
211 {"GPS_altitude", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
212 {"GPS_speed", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
213 {"GPS_ground_course", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)}
216 // GPS home frame
217 static const blackboxSimpleFieldDefinition_t blackboxGpsHFields[] = {
218 {"GPS_home", 0, SIGNED, PREDICT(0), ENCODING(SIGNED_VB)},
219 {"GPS_home", 1, SIGNED, PREDICT(0), ENCODING(SIGNED_VB)}
221 #endif
223 // Rarely-updated fields
224 static const blackboxSimpleFieldDefinition_t blackboxSlowFields[] = {
225 {"flightModeFlags", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB)},
226 {"stateFlags", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB)},
228 {"failsafePhase", -1, UNSIGNED, PREDICT(0), ENCODING(TAG2_3S32)},
229 {"rxSignalReceived", -1, UNSIGNED, PREDICT(0), ENCODING(TAG2_3S32)},
230 {"rxFlightChannelsValid", -1, UNSIGNED, PREDICT(0), ENCODING(TAG2_3S32)}
233 typedef enum BlackboxState {
234 BLACKBOX_STATE_DISABLED = 0,
235 BLACKBOX_STATE_STOPPED,
236 BLACKBOX_STATE_PREPARE_LOG_FILE,
237 BLACKBOX_STATE_SEND_HEADER,
238 BLACKBOX_STATE_SEND_MAIN_FIELD_HEADER,
239 BLACKBOX_STATE_SEND_GPS_H_HEADER,
240 BLACKBOX_STATE_SEND_GPS_G_HEADER,
241 BLACKBOX_STATE_SEND_SLOW_HEADER,
242 BLACKBOX_STATE_SEND_SYSINFO,
243 BLACKBOX_STATE_PAUSED,
244 BLACKBOX_STATE_RUNNING,
245 BLACKBOX_STATE_SHUTTING_DOWN
246 } BlackboxState;
248 #define BLACKBOX_FIRST_HEADER_SENDING_STATE BLACKBOX_STATE_SEND_HEADER
249 #define BLACKBOX_LAST_HEADER_SENDING_STATE BLACKBOX_STATE_SEND_SYSINFO
251 typedef struct blackboxMainState_s {
252 uint32_t time;
254 int32_t axisPID_P[XYZ_AXIS_COUNT], axisPID_I[XYZ_AXIS_COUNT], axisPID_D[XYZ_AXIS_COUNT];
256 int16_t rcCommand[4];
257 int16_t gyroADC[XYZ_AXIS_COUNT];
258 int16_t accSmooth[XYZ_AXIS_COUNT];
259 int16_t debug[4];
260 int16_t motor[MAX_SUPPORTED_MOTORS];
261 int16_t servo[MAX_SUPPORTED_SERVOS];
263 uint16_t vbatLatest;
264 uint16_t amperageLatest;
266 #ifdef BARO
267 int32_t BaroAlt;
268 #endif
269 #ifdef MAG
270 int16_t magADC[XYZ_AXIS_COUNT];
271 #endif
272 #ifdef SONAR
273 int32_t sonarRaw;
274 #endif
275 uint16_t rssi;
276 } blackboxMainState_t;
278 typedef struct blackboxGpsState_s {
279 int32_t GPS_home[2], GPS_coord[2];
280 uint8_t GPS_numSat;
281 } blackboxGpsState_t;
283 // This data is updated really infrequently:
284 typedef struct blackboxSlowState_s {
285 uint32_t flightModeFlags; // extend this data size (from uint16_t)
286 uint8_t stateFlags;
287 uint8_t failsafePhase;
288 bool rxSignalReceived;
289 bool rxFlightChannelsValid;
290 } __attribute__((__packed__)) blackboxSlowState_t; // We pack this struct so that padding doesn't interfere with memcmp()
292 //From mixer.c:
293 extern uint16_t motorOutputHigh, motorOutputLow;
295 //From rc_controls.c
296 extern uint32_t rcModeActivationMask;
298 static BlackboxState blackboxState = BLACKBOX_STATE_DISABLED;
300 static uint32_t blackboxLastArmingBeep = 0;
301 static uint32_t blackboxLastFlightModeFlags = 0; // New event tracking of flight modes
304 static struct {
305 uint32_t headerIndex;
307 /* Since these fields are used during different blackbox states (never simultaneously) we can
308 * overlap them to save on RAM
310 union {
311 int fieldIndex;
312 uint32_t startTime;
313 } u;
314 } xmitState;
316 // Cache for FLIGHT_LOG_FIELD_CONDITION_* test results:
317 static uint32_t blackboxConditionCache;
319 STATIC_ASSERT((sizeof(blackboxConditionCache) * 8) >= FLIGHT_LOG_FIELD_CONDITION_NEVER, too_many_flight_log_conditions);
321 static uint32_t blackboxIteration;
322 static uint16_t blackboxPFrameIndex, blackboxIFrameIndex;
323 static uint16_t blackboxSlowFrameIterationTimer;
324 static bool blackboxLoggedAnyFrames;
327 * We store voltages in I-frames relative to this, which was the voltage when the blackbox was activated.
328 * This helps out since the voltage is only expected to fall from that point and we can reduce our diffs
329 * to encode:
331 static uint16_t vbatReference;
333 static blackboxGpsState_t gpsHistory;
334 static blackboxSlowState_t slowHistory;
336 // Keep a history of length 2, plus a buffer for MW to store the new values into
337 static blackboxMainState_t blackboxHistoryRing[3];
339 // These point into blackboxHistoryRing, use them to know where to store history of a given age (0, 1 or 2 generations old)
340 static blackboxMainState_t* blackboxHistory[3];
342 static bool blackboxModeActivationConditionPresent = false;
345 * Return true if it is safe to edit the Blackbox configuration in the emasterConfig.
347 bool blackboxMayEditConfig()
349 return blackboxState <= BLACKBOX_STATE_STOPPED;
352 static bool blackboxIsOnlyLoggingIntraframes() {
353 return blackboxConfig()->rate_num == 1 && blackboxConfig()->rate_denom == 32;
356 static bool testBlackboxConditionUncached(FlightLogFieldCondition condition)
358 switch (condition) {
359 case FLIGHT_LOG_FIELD_CONDITION_ALWAYS:
360 return true;
362 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_1:
363 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_2:
364 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_3:
365 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_4:
366 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_5:
367 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_6:
368 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_7:
369 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_8:
370 return getMotorCount() >= condition - FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_1 + 1;
372 case FLIGHT_LOG_FIELD_CONDITION_TRICOPTER:
373 return mixerConfig()->mixerMode == MIXER_TRI || mixerConfig()->mixerMode == MIXER_CUSTOM_TRI;
375 case FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_0:
376 case FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_1:
377 case FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_2:
378 return currentProfile->pidProfile.D8[condition - FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_0] != 0;
380 case FLIGHT_LOG_FIELD_CONDITION_MAG:
381 #ifdef MAG
382 return sensors(SENSOR_MAG);
383 #else
384 return false;
385 #endif
387 case FLIGHT_LOG_FIELD_CONDITION_BARO:
388 #ifdef BARO
389 return sensors(SENSOR_BARO);
390 #else
391 return false;
392 #endif
394 case FLIGHT_LOG_FIELD_CONDITION_VBAT:
395 return feature(FEATURE_VBAT);
397 case FLIGHT_LOG_FIELD_CONDITION_AMPERAGE_ADC:
398 return feature(FEATURE_CURRENT_METER) && batteryConfig()->currentMeterType == CURRENT_SENSOR_ADC;
400 case FLIGHT_LOG_FIELD_CONDITION_SONAR:
401 #ifdef SONAR
402 return feature(FEATURE_SONAR);
403 #else
404 return false;
405 #endif
407 case FLIGHT_LOG_FIELD_CONDITION_RSSI:
408 return rxConfig()->rssi_channel > 0 || feature(FEATURE_RSSI_ADC);
410 case FLIGHT_LOG_FIELD_CONDITION_NOT_LOGGING_EVERY_FRAME:
411 return blackboxConfig()->rate_num < blackboxConfig()->rate_denom;
413 case FLIGHT_LOG_FIELD_CONDITION_NEVER:
414 return false;
415 default:
416 return false;
420 static void blackboxBuildConditionCache()
422 FlightLogFieldCondition cond;
424 blackboxConditionCache = 0;
426 for (cond = FLIGHT_LOG_FIELD_CONDITION_FIRST; cond <= FLIGHT_LOG_FIELD_CONDITION_LAST; cond++) {
427 if (testBlackboxConditionUncached(cond)) {
428 blackboxConditionCache |= 1 << cond;
433 static bool testBlackboxCondition(FlightLogFieldCondition condition)
435 return (blackboxConditionCache & (1 << condition)) != 0;
438 static void blackboxSetState(BlackboxState newState)
440 //Perform initial setup required for the new state
441 switch (newState) {
442 case BLACKBOX_STATE_PREPARE_LOG_FILE:
443 blackboxLoggedAnyFrames = false;
444 break;
445 case BLACKBOX_STATE_SEND_HEADER:
446 blackboxHeaderBudget = 0;
447 xmitState.headerIndex = 0;
448 xmitState.u.startTime = millis();
449 break;
450 case BLACKBOX_STATE_SEND_MAIN_FIELD_HEADER:
451 case BLACKBOX_STATE_SEND_GPS_G_HEADER:
452 case BLACKBOX_STATE_SEND_GPS_H_HEADER:
453 case BLACKBOX_STATE_SEND_SLOW_HEADER:
454 xmitState.headerIndex = 0;
455 xmitState.u.fieldIndex = -1;
456 break;
457 case BLACKBOX_STATE_SEND_SYSINFO:
458 xmitState.headerIndex = 0;
459 break;
460 case BLACKBOX_STATE_RUNNING:
461 blackboxSlowFrameIterationTimer = SLOW_FRAME_INTERVAL; //Force a slow frame to be written on the first iteration
462 break;
463 case BLACKBOX_STATE_SHUTTING_DOWN:
464 xmitState.u.startTime = millis();
465 break;
466 default:
469 blackboxState = newState;
472 static void writeIntraframe(void)
474 blackboxMainState_t *blackboxCurrent = blackboxHistory[0];
476 blackboxWrite('I');
478 blackboxWriteUnsignedVB(blackboxIteration);
479 blackboxWriteUnsignedVB(blackboxCurrent->time);
481 blackboxWriteSignedVBArray(blackboxCurrent->axisPID_P, XYZ_AXIS_COUNT);
482 blackboxWriteSignedVBArray(blackboxCurrent->axisPID_I, XYZ_AXIS_COUNT);
484 // Don't bother writing the current D term if the corresponding PID setting is zero
485 for (int x = 0; x < XYZ_AXIS_COUNT; x++) {
486 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_0 + x)) {
487 blackboxWriteSignedVB(blackboxCurrent->axisPID_D[x]);
491 // Write roll, pitch and yaw first:
492 blackboxWriteSigned16VBArray(blackboxCurrent->rcCommand, 3);
495 * Write the throttle separately from the rest of the RC data so we can apply a predictor to it.
496 * Throttle lies in range [minthrottle..maxthrottle]:
498 blackboxWriteUnsignedVB(blackboxCurrent->rcCommand[THROTTLE] - motorConfig()->minthrottle);
500 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_VBAT)) {
502 * Our voltage is expected to decrease over the course of the flight, so store our difference from
503 * the reference:
505 * Write 14 bits even if the number is negative (which would otherwise result in 32 bits)
507 blackboxWriteUnsignedVB((vbatReference - blackboxCurrent->vbatLatest) & 0x3FFF);
510 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_AMPERAGE_ADC)) {
511 // 12bit value directly from ADC
512 blackboxWriteUnsignedVB(blackboxCurrent->amperageLatest);
515 #ifdef MAG
516 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_MAG)) {
517 blackboxWriteSigned16VBArray(blackboxCurrent->magADC, XYZ_AXIS_COUNT);
519 #endif
521 #ifdef BARO
522 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_BARO)) {
523 blackboxWriteSignedVB(blackboxCurrent->BaroAlt);
525 #endif
527 #ifdef SONAR
528 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_SONAR)) {
529 blackboxWriteSignedVB(blackboxCurrent->sonarRaw);
531 #endif
533 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_RSSI)) {
534 blackboxWriteUnsignedVB(blackboxCurrent->rssi);
537 blackboxWriteSigned16VBArray(blackboxCurrent->gyroADC, XYZ_AXIS_COUNT);
538 blackboxWriteSigned16VBArray(blackboxCurrent->accSmooth, XYZ_AXIS_COUNT);
539 blackboxWriteSigned16VBArray(blackboxCurrent->debug, 4);
541 //Motors can be below minimum output when disarmed, but that doesn't happen much
542 blackboxWriteUnsignedVB(blackboxCurrent->motor[0] - motorOutputLow);
544 //Motors tend to be similar to each other so use the first motor's value as a predictor of the others
545 const int motorCount = getMotorCount();
546 for (int x = 1; x < motorCount; x++) {
547 blackboxWriteSignedVB(blackboxCurrent->motor[x] - blackboxCurrent->motor[0]);
550 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_TRICOPTER)) {
551 //Assume the tail spends most of its time around the center
552 blackboxWriteSignedVB(blackboxCurrent->servo[5] - 1500);
555 //Rotate our history buffers:
557 //The current state becomes the new "before" state
558 blackboxHistory[1] = blackboxHistory[0];
559 //And since we have no other history, we also use it for the "before, before" state
560 blackboxHistory[2] = blackboxHistory[0];
561 //And advance the current state over to a blank space ready to be filled
562 blackboxHistory[0] = ((blackboxHistory[0] - blackboxHistoryRing + 1) % 3) + blackboxHistoryRing;
564 blackboxLoggedAnyFrames = true;
567 static void blackboxWriteMainStateArrayUsingAveragePredictor(int arrOffsetInHistory, int count)
569 int16_t *curr = (int16_t*) ((char*) (blackboxHistory[0]) + arrOffsetInHistory);
570 int16_t *prev1 = (int16_t*) ((char*) (blackboxHistory[1]) + arrOffsetInHistory);
571 int16_t *prev2 = (int16_t*) ((char*) (blackboxHistory[2]) + arrOffsetInHistory);
573 for (int i = 0; i < count; i++) {
574 // Predictor is the average of the previous two history states
575 int32_t predictor = (prev1[i] + prev2[i]) / 2;
577 blackboxWriteSignedVB(curr[i] - predictor);
581 static void writeInterframe(void)
583 int x;
584 int32_t deltas[8];
586 blackboxMainState_t *blackboxCurrent = blackboxHistory[0];
587 blackboxMainState_t *blackboxLast = blackboxHistory[1];
589 blackboxWrite('P');
591 //No need to store iteration count since its delta is always 1
594 * Since the difference between the difference between successive times will be nearly zero (due to consistent
595 * looptime spacing), use second-order differences.
597 blackboxWriteSignedVB((int32_t) (blackboxHistory[0]->time - 2 * blackboxHistory[1]->time + blackboxHistory[2]->time));
599 arraySubInt32(deltas, blackboxCurrent->axisPID_P, blackboxLast->axisPID_P, XYZ_AXIS_COUNT);
600 blackboxWriteSignedVBArray(deltas, XYZ_AXIS_COUNT);
603 * The PID I field changes very slowly, most of the time +-2, so use an encoding
604 * that can pack all three fields into one byte in that situation.
606 arraySubInt32(deltas, blackboxCurrent->axisPID_I, blackboxLast->axisPID_I, XYZ_AXIS_COUNT);
607 blackboxWriteTag2_3S32(deltas);
610 * The PID D term is frequently set to zero for yaw, which makes the result from the calculation
611 * always zero. So don't bother recording D results when PID D terms are zero.
613 for (x = 0; x < XYZ_AXIS_COUNT; x++) {
614 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_0 + x)) {
615 blackboxWriteSignedVB(blackboxCurrent->axisPID_D[x] - blackboxLast->axisPID_D[x]);
620 * RC tends to stay the same or fairly small for many frames at a time, so use an encoding that
621 * can pack multiple values per byte:
623 for (x = 0; x < 4; x++) {
624 deltas[x] = blackboxCurrent->rcCommand[x] - blackboxLast->rcCommand[x];
627 blackboxWriteTag8_4S16(deltas);
629 //Check for sensors that are updated periodically (so deltas are normally zero)
630 int optionalFieldCount = 0;
632 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_VBAT)) {
633 deltas[optionalFieldCount++] = (int32_t) blackboxCurrent->vbatLatest - blackboxLast->vbatLatest;
636 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_AMPERAGE_ADC)) {
637 deltas[optionalFieldCount++] = (int32_t) blackboxCurrent->amperageLatest - blackboxLast->amperageLatest;
640 #ifdef MAG
641 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_MAG)) {
642 for (x = 0; x < XYZ_AXIS_COUNT; x++) {
643 deltas[optionalFieldCount++] = blackboxCurrent->magADC[x] - blackboxLast->magADC[x];
646 #endif
648 #ifdef BARO
649 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_BARO)) {
650 deltas[optionalFieldCount++] = blackboxCurrent->BaroAlt - blackboxLast->BaroAlt;
652 #endif
654 #ifdef SONAR
655 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_SONAR)) {
656 deltas[optionalFieldCount++] = blackboxCurrent->sonarRaw - blackboxLast->sonarRaw;
658 #endif
660 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_RSSI)) {
661 deltas[optionalFieldCount++] = (int32_t) blackboxCurrent->rssi - blackboxLast->rssi;
664 blackboxWriteTag8_8SVB(deltas, optionalFieldCount);
666 //Since gyros, accs and motors are noisy, base their predictions on the average of the history:
667 blackboxWriteMainStateArrayUsingAveragePredictor(offsetof(blackboxMainState_t, gyroADC), XYZ_AXIS_COUNT);
668 blackboxWriteMainStateArrayUsingAveragePredictor(offsetof(blackboxMainState_t, accSmooth), XYZ_AXIS_COUNT);
669 blackboxWriteMainStateArrayUsingAveragePredictor(offsetof(blackboxMainState_t, debug), 4);
670 blackboxWriteMainStateArrayUsingAveragePredictor(offsetof(blackboxMainState_t, motor), getMotorCount());
672 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_TRICOPTER)) {
673 blackboxWriteSignedVB(blackboxCurrent->servo[5] - blackboxLast->servo[5]);
676 //Rotate our history buffers
677 blackboxHistory[2] = blackboxHistory[1];
678 blackboxHistory[1] = blackboxHistory[0];
679 blackboxHistory[0] = ((blackboxHistory[0] - blackboxHistoryRing + 1) % 3) + blackboxHistoryRing;
681 blackboxLoggedAnyFrames = true;
684 /* Write the contents of the global "slowHistory" to the log as an "S" frame. Because this data is logged so
685 * infrequently, delta updates are not reasonable, so we log independent frames. */
686 static void writeSlowFrame(void)
688 int32_t values[3];
690 blackboxWrite('S');
692 blackboxWriteUnsignedVB(slowHistory.flightModeFlags);
693 blackboxWriteUnsignedVB(slowHistory.stateFlags);
696 * Most of the time these three values will be able to pack into one byte for us:
698 values[0] = slowHistory.failsafePhase;
699 values[1] = slowHistory.rxSignalReceived ? 1 : 0;
700 values[2] = slowHistory.rxFlightChannelsValid ? 1 : 0;
701 blackboxWriteTag2_3S32(values);
703 blackboxSlowFrameIterationTimer = 0;
707 * Load rarely-changing values from the FC into the given structure
709 static void loadSlowState(blackboxSlowState_t *slow)
711 slow->flightModeFlags = rcModeActivationMask; //was flightModeFlags;
712 slow->stateFlags = stateFlags;
713 slow->failsafePhase = failsafePhase();
714 slow->rxSignalReceived = rxIsReceivingSignal();
715 slow->rxFlightChannelsValid = rxAreFlightChannelsValid();
719 * If the data in the slow frame has changed, log a slow frame.
721 * If allowPeriodicWrite is true, the frame is also logged if it has been more than SLOW_FRAME_INTERVAL logging iterations
722 * since the field was last logged.
724 static void writeSlowFrameIfNeeded(bool allowPeriodicWrite)
726 // Write the slow frame peridocially so it can be recovered if we ever lose sync
727 bool shouldWrite = allowPeriodicWrite && blackboxSlowFrameIterationTimer >= SLOW_FRAME_INTERVAL;
729 if (shouldWrite) {
730 loadSlowState(&slowHistory);
731 } else {
732 blackboxSlowState_t newSlowState;
734 loadSlowState(&newSlowState);
736 // Only write a slow frame if it was different from the previous state
737 if (memcmp(&newSlowState, &slowHistory, sizeof(slowHistory)) != 0) {
738 // Use the new state as our new history
739 memcpy(&slowHistory, &newSlowState, sizeof(slowHistory));
740 shouldWrite = true;
744 if (shouldWrite) {
745 writeSlowFrame();
749 static int gcd(int num, int denom)
751 if (denom == 0) {
752 return num;
755 return gcd(denom, num % denom);
758 void validateBlackboxConfig()
760 int div;
762 if (blackboxConfig()->rate_num == 0 || blackboxConfig()->rate_denom == 0
763 || blackboxConfig()->rate_num >= blackboxConfig()->rate_denom) {
764 blackboxConfig()->rate_num = 1;
765 blackboxConfig()->rate_denom = 1;
766 } else {
767 /* Reduce the fraction the user entered as much as possible (makes the recorded/skipped frame pattern repeat
768 * itself more frequently)
770 div = gcd(blackboxConfig()->rate_num, blackboxConfig()->rate_denom);
772 blackboxConfig()->rate_num /= div;
773 blackboxConfig()->rate_denom /= div;
776 // If we've chosen an unsupported device, change the device to serial
777 switch (blackboxConfig()->device) {
778 #ifdef USE_FLASHFS
779 case BLACKBOX_DEVICE_FLASH:
780 #endif
781 #ifdef USE_SDCARD
782 case BLACKBOX_DEVICE_SDCARD:
783 #endif
784 case BLACKBOX_DEVICE_SERIAL:
785 // Device supported, leave the setting alone
786 break;
788 default:
789 blackboxConfig()->device = BLACKBOX_DEVICE_SERIAL;
794 * Start Blackbox logging if it is not already running. Intended to be called upon arming.
796 void startBlackbox(void)
798 if (blackboxState == BLACKBOX_STATE_STOPPED) {
799 validateBlackboxConfig();
801 if (!blackboxDeviceOpen()) {
802 blackboxSetState(BLACKBOX_STATE_DISABLED);
803 return;
806 memset(&gpsHistory, 0, sizeof(gpsHistory));
808 blackboxHistory[0] = &blackboxHistoryRing[0];
809 blackboxHistory[1] = &blackboxHistoryRing[1];
810 blackboxHistory[2] = &blackboxHistoryRing[2];
812 vbatReference = vbatLatest;
814 //No need to clear the content of blackboxHistoryRing since our first frame will be an intra which overwrites it
817 * We use conditional tests to decide whether or not certain fields should be logged. Since our headers
818 * must always agree with the logged data, the results of these tests must not change during logging. So
819 * cache those now.
821 blackboxBuildConditionCache();
823 blackboxModeActivationConditionPresent = isModeActivationConditionPresent(modeActivationProfile()->modeActivationConditions, BOXBLACKBOX);
825 blackboxIteration = 0;
826 blackboxPFrameIndex = 0;
827 blackboxIFrameIndex = 0;
830 * Record the beeper's current idea of the last arming beep time, so that we can detect it changing when
831 * it finally plays the beep for this arming event.
833 blackboxLastArmingBeep = getArmingBeepTimeMicros();
834 blackboxLastFlightModeFlags = rcModeActivationMask; // record startup status
837 blackboxSetState(BLACKBOX_STATE_PREPARE_LOG_FILE);
842 * Begin Blackbox shutdown.
844 void finishBlackbox(void)
846 switch (blackboxState) {
847 case BLACKBOX_STATE_DISABLED:
848 case BLACKBOX_STATE_STOPPED:
849 case BLACKBOX_STATE_SHUTTING_DOWN:
850 // We're already stopped/shutting down
851 break;
853 case BLACKBOX_STATE_RUNNING:
854 case BLACKBOX_STATE_PAUSED:
855 blackboxLogEvent(FLIGHT_LOG_EVENT_LOG_END, NULL);
857 // Fall through
858 default:
859 blackboxSetState(BLACKBOX_STATE_SHUTTING_DOWN);
864 * Test Motors Blackbox Logging
866 bool startedLoggingInTestMode = false;
868 void startInTestMode(void)
870 if(!startedLoggingInTestMode) {
871 if (blackboxConfig()->device == BLACKBOX_DEVICE_SERIAL) {
872 serialPort_t *sharedBlackboxAndMspPort = findSharedSerialPort(FUNCTION_BLACKBOX, FUNCTION_MSP);
873 if (sharedBlackboxAndMspPort) {
874 return; // When in test mode, we cannot share the MSP and serial logger port!
877 startBlackbox();
878 startedLoggingInTestMode = true;
881 void stopInTestMode(void)
883 if(startedLoggingInTestMode) {
884 finishBlackbox();
885 startedLoggingInTestMode = false;
889 * We are going to monitor the MSP_SET_MOTOR target variables motor_disarmed[] for values other than minthrottle
890 * on reading a value (i.e. the user is testing the motors), then we enable test mode logging;
891 * we monitor when the values return to minthrottle and start a delay timer (5 seconds); if
892 * the test motors are left at minimum throttle for this delay timer, then we assume we are done testing and
893 * shutdown the logger.
895 * Of course, after the 5 seconds and shutdown of the logger, the system will be re-enabled to allow the
896 * test mode to trigger again; its just that the data will be in a second, third, fourth etc log file.
898 bool inMotorTestMode(void) {
899 static uint32_t resetTime = 0;
900 uint16_t inactiveMotorCommand;
901 if (feature(FEATURE_3D)) {
902 inactiveMotorCommand = flight3DConfig()->neutral3d;
903 #ifdef USE_DSHOT
904 } else if (isMotorProtocolDshot()) {
905 inactiveMotorCommand = DSHOT_DISARM_COMMAND;
906 #endif
907 } else {
908 inactiveMotorCommand = motorConfig()->mincommand;
911 int i;
912 bool atLeastOneMotorActivated = false;
914 // set disarmed motor values
915 for (i = 0; i < MAX_SUPPORTED_MOTORS; i++)
916 atLeastOneMotorActivated |= (motor_disarmed[i] != inactiveMotorCommand);
918 if(atLeastOneMotorActivated) {
919 resetTime = millis() + 5000; // add 5 seconds
920 return true;
921 } else {
922 // Monitor the duration at minimum
923 return (millis() < resetTime);
925 return false;
928 #ifdef GPS
929 static void writeGPSHomeFrame()
931 blackboxWrite('H');
933 blackboxWriteSignedVB(GPS_home[0]);
934 blackboxWriteSignedVB(GPS_home[1]);
935 //TODO it'd be great if we could grab the GPS current time and write that too
937 gpsHistory.GPS_home[0] = GPS_home[0];
938 gpsHistory.GPS_home[1] = GPS_home[1];
941 static void writeGPSFrame(timeUs_t currentTimeUs)
943 blackboxWrite('G');
946 * If we're logging every frame, then a GPS frame always appears just after a frame with the
947 * currentTime timestamp in the log, so the reader can just use that timestamp for the GPS frame.
949 * If we're not logging every frame, we need to store the time of this GPS frame.
951 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_NOT_LOGGING_EVERY_FRAME)) {
952 // Predict the time of the last frame in the main log
953 blackboxWriteUnsignedVB(currentTimeUs - blackboxHistory[1]->time);
956 blackboxWriteUnsignedVB(GPS_numSat);
957 blackboxWriteSignedVB(GPS_coord[0] - gpsHistory.GPS_home[0]);
958 blackboxWriteSignedVB(GPS_coord[1] - gpsHistory.GPS_home[1]);
959 blackboxWriteUnsignedVB(GPS_altitude);
960 blackboxWriteUnsignedVB(GPS_speed);
961 blackboxWriteUnsignedVB(GPS_ground_course);
963 gpsHistory.GPS_numSat = GPS_numSat;
964 gpsHistory.GPS_coord[0] = GPS_coord[0];
965 gpsHistory.GPS_coord[1] = GPS_coord[1];
967 #endif
970 * Fill the current state of the blackbox using values read from the flight controller
972 static void loadMainState(timeUs_t currentTimeUs)
974 blackboxMainState_t *blackboxCurrent = blackboxHistory[0];
975 int i;
977 blackboxCurrent->time = currentTimeUs;
979 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
980 blackboxCurrent->axisPID_P[i] = axisPID_P[i];
982 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
983 blackboxCurrent->axisPID_I[i] = axisPID_I[i];
985 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
986 blackboxCurrent->axisPID_D[i] = axisPID_D[i];
989 for (i = 0; i < 4; i++) {
990 blackboxCurrent->rcCommand[i] = rcCommand[i];
993 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
994 blackboxCurrent->gyroADC[i] = lrintf(gyro.gyroADCf[i]);
997 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
998 blackboxCurrent->accSmooth[i] = acc.accSmooth[i];
1001 for (i = 0; i < 4; i++) {
1002 blackboxCurrent->debug[i] = debug[i];
1005 const int motorCount = getMotorCount();
1006 for (i = 0; i < motorCount; i++) {
1007 blackboxCurrent->motor[i] = motor[i];
1010 blackboxCurrent->vbatLatest = vbatLatest;
1011 blackboxCurrent->amperageLatest = amperageLatest;
1013 #ifdef MAG
1014 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
1015 blackboxCurrent->magADC[i] = mag.magADC[i];
1017 #endif
1019 #ifdef BARO
1020 blackboxCurrent->BaroAlt = baro.BaroAlt;
1021 #endif
1023 #ifdef SONAR
1024 // Store the raw sonar value without applying tilt correction
1025 blackboxCurrent->sonarRaw = sonarRead();
1026 #endif
1028 blackboxCurrent->rssi = rssi;
1030 #ifdef USE_SERVOS
1031 //Tail servo for tricopters
1032 blackboxCurrent->servo[5] = servo[5];
1033 #endif
1037 * Transmit the header information for the given field definitions. Transmitted header lines look like:
1039 * H Field I name:a,b,c
1040 * H Field I predictor:0,1,2
1042 * For all header types, provide a "mainFrameChar" which is the name for the field and will be used to refer to it in the
1043 * header (e.g. P, I etc). For blackboxDeltaField_t fields, also provide deltaFrameChar, otherwise set this to zero.
1045 * Provide an array 'conditions' of FlightLogFieldCondition enums if you want these conditions to decide whether a field
1046 * should be included or not. Otherwise provide NULL for this parameter and NULL for secondCondition.
1048 * Set xmitState.headerIndex to 0 and xmitState.u.fieldIndex to -1 before calling for the first time.
1050 * secondFieldDefinition and secondCondition element pointers need to be provided in order to compute the stride of the
1051 * fieldDefinition and secondCondition arrays.
1053 * Returns true if there is still header left to transmit (so call again to continue transmission).
1055 static bool sendFieldDefinition(char mainFrameChar, char deltaFrameChar, const void *fieldDefinitions,
1056 const void *secondFieldDefinition, int fieldCount, const uint8_t *conditions, const uint8_t *secondCondition)
1058 const blackboxFieldDefinition_t *def;
1059 unsigned int headerCount;
1060 static bool needComma = false;
1061 size_t definitionStride = (char*) secondFieldDefinition - (char*) fieldDefinitions;
1062 size_t conditionsStride = (char*) secondCondition - (char*) conditions;
1064 if (deltaFrameChar) {
1065 headerCount = BLACKBOX_DELTA_FIELD_HEADER_COUNT;
1066 } else {
1067 headerCount = BLACKBOX_SIMPLE_FIELD_HEADER_COUNT;
1071 * We're chunking up the header data so we don't exceed our datarate. So we'll be called multiple times to transmit
1072 * the whole header.
1075 // On our first call we need to print the name of the header and a colon
1076 if (xmitState.u.fieldIndex == -1) {
1077 if (xmitState.headerIndex >= headerCount) {
1078 return false; //Someone probably called us again after we had already completed transmission
1081 uint32_t charsToBeWritten = strlen("H Field x :") + strlen(blackboxFieldHeaderNames[xmitState.headerIndex]);
1083 if (blackboxDeviceReserveBufferSpace(charsToBeWritten) != BLACKBOX_RESERVE_SUCCESS) {
1084 return true; // Try again later
1087 blackboxHeaderBudget -= blackboxPrintf("H Field %c %s:", xmitState.headerIndex >= BLACKBOX_SIMPLE_FIELD_HEADER_COUNT ? deltaFrameChar : mainFrameChar, blackboxFieldHeaderNames[xmitState.headerIndex]);
1089 xmitState.u.fieldIndex++;
1090 needComma = false;
1093 // The longest we expect an integer to be as a string:
1094 const uint32_t LONGEST_INTEGER_STRLEN = 2;
1096 for (; xmitState.u.fieldIndex < fieldCount; xmitState.u.fieldIndex++) {
1097 def = (const blackboxFieldDefinition_t*) ((const char*)fieldDefinitions + definitionStride * xmitState.u.fieldIndex);
1099 if (!conditions || testBlackboxCondition(conditions[conditionsStride * xmitState.u.fieldIndex])) {
1100 // First (over)estimate the length of the string we want to print
1102 int32_t bytesToWrite = 1; // Leading comma
1104 // The first header is a field name
1105 if (xmitState.headerIndex == 0) {
1106 bytesToWrite += strlen(def->name) + strlen("[]") + LONGEST_INTEGER_STRLEN;
1107 } else {
1108 //The other headers are integers
1109 bytesToWrite += LONGEST_INTEGER_STRLEN;
1112 // Now perform the write if the buffer is large enough
1113 if (blackboxDeviceReserveBufferSpace(bytesToWrite) != BLACKBOX_RESERVE_SUCCESS) {
1114 // Ran out of space!
1115 return true;
1118 blackboxHeaderBudget -= bytesToWrite;
1120 if (needComma) {
1121 blackboxWrite(',');
1122 } else {
1123 needComma = true;
1126 // The first header is a field name
1127 if (xmitState.headerIndex == 0) {
1128 blackboxPrint(def->name);
1130 // Do we need to print an index in brackets after the name?
1131 if (def->fieldNameIndex != -1) {
1132 blackboxPrintf("[%d]", def->fieldNameIndex);
1134 } else {
1135 //The other headers are integers
1136 blackboxPrintf("%d", def->arr[xmitState.headerIndex - 1]);
1141 // Did we complete this line?
1142 if (xmitState.u.fieldIndex == fieldCount && blackboxDeviceReserveBufferSpace(1) == BLACKBOX_RESERVE_SUCCESS) {
1143 blackboxHeaderBudget--;
1144 blackboxWrite('\n');
1145 xmitState.headerIndex++;
1146 xmitState.u.fieldIndex = -1;
1149 return xmitState.headerIndex < headerCount;
1152 #ifndef BLACKBOX_PRINT_HEADER_LINE
1153 #define BLACKBOX_PRINT_HEADER_LINE(x, ...) case __COUNTER__: \
1154 blackboxPrintfHeaderLine(x, __VA_ARGS__); \
1155 break;
1156 #define BLACKBOX_PRINT_HEADER_LINE_CUSTOM(...) case __COUNTER__: \
1157 {__VA_ARGS__}; \
1158 break;
1159 #endif
1162 * Transmit a portion of the system information headers. Call the first time with xmitState.headerIndex == 0. Returns
1163 * true iff transmission is complete, otherwise call again later to continue transmission.
1165 static bool blackboxWriteSysinfo()
1167 // Make sure we have enough room in the buffer for our longest line (as of this writing, the "Firmware date" line)
1168 if (blackboxDeviceReserveBufferSpace(64) != BLACKBOX_RESERVE_SUCCESS) {
1169 return false;
1172 const profile_t *currentProfile = &masterConfig.profile[masterConfig.current_profile_index];
1173 const controlRateConfig_t *currentControlRateProfile = &currentProfile->controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile];
1174 switch (xmitState.headerIndex) {
1175 BLACKBOX_PRINT_HEADER_LINE("Firmware type:%s", "Cleanflight");
1176 BLACKBOX_PRINT_HEADER_LINE("Firmware revision:%s %s (%s) %s", FC_FIRMWARE_NAME, FC_VERSION_STRING, shortGitRevision, targetName);
1177 BLACKBOX_PRINT_HEADER_LINE("Firmware date:%s %s", buildDate, buildTime);
1178 BLACKBOX_PRINT_HEADER_LINE("Craft name:%s", masterConfig.name);
1179 BLACKBOX_PRINT_HEADER_LINE("P interval:%d/%d", blackboxConfig()->rate_num, blackboxConfig()->rate_denom);
1180 BLACKBOX_PRINT_HEADER_LINE("minthrottle:%d", motorConfig()->minthrottle);
1181 BLACKBOX_PRINT_HEADER_LINE("maxthrottle:%d", motorConfig()->maxthrottle);
1182 BLACKBOX_PRINT_HEADER_LINE("gyro_scale:0x%x", castFloatBytesToInt(1.0f));
1183 BLACKBOX_PRINT_HEADER_LINE("motorOutput:%d,%d", motorOutputLow,motorOutputHigh);
1184 BLACKBOX_PRINT_HEADER_LINE("acc_1G:%u", acc.dev.acc_1G);
1186 BLACKBOX_PRINT_HEADER_LINE_CUSTOM(
1187 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_VBAT)) {
1188 blackboxPrintfHeaderLine("vbatscale:%u", batteryConfig()->vbatscale);
1189 } else {
1190 xmitState.headerIndex += 2; // Skip the next two vbat fields too
1194 BLACKBOX_PRINT_HEADER_LINE("vbatcellvoltage:%u,%u,%u", batteryConfig()->vbatmincellvoltage,
1195 batteryConfig()->vbatwarningcellvoltage,
1196 batteryConfig()->vbatmaxcellvoltage);
1197 BLACKBOX_PRINT_HEADER_LINE("vbatref:%u", vbatReference);
1199 BLACKBOX_PRINT_HEADER_LINE_CUSTOM(
1200 //Note: Log even if this is a virtual current meter, since the virtual meter uses these parameters too:
1201 if (feature(FEATURE_CURRENT_METER)) {
1202 blackboxPrintfHeaderLine("currentMeter:%d,%d", batteryConfig()->currentMeterOffset, batteryConfig()->currentMeterScale);
1206 BLACKBOX_PRINT_HEADER_LINE("looptime:%d", gyro.targetLooptime);
1207 BLACKBOX_PRINT_HEADER_LINE("gyro_sync_denom:%d", gyroConfig()->gyro_sync_denom);
1208 BLACKBOX_PRINT_HEADER_LINE("pid_process_denom:%d", pidConfig()->pid_process_denom);
1209 BLACKBOX_PRINT_HEADER_LINE("rcRate:%d", currentControlRateProfile->rcRate8);
1210 BLACKBOX_PRINT_HEADER_LINE("rcExpo:%d", currentControlRateProfile->rcExpo8);
1211 BLACKBOX_PRINT_HEADER_LINE("rcYawRate:%d", currentControlRateProfile->rcYawRate8);
1212 BLACKBOX_PRINT_HEADER_LINE("rcYawExpo:%d", currentControlRateProfile->rcYawExpo8);
1213 BLACKBOX_PRINT_HEADER_LINE("thrMid:%d", currentControlRateProfile->thrMid8);
1214 BLACKBOX_PRINT_HEADER_LINE("thrExpo:%d", currentControlRateProfile->thrExpo8);
1215 BLACKBOX_PRINT_HEADER_LINE("dynThrPID:%d", currentControlRateProfile->dynThrPID);
1216 BLACKBOX_PRINT_HEADER_LINE("tpa_breakpoint:%d", currentControlRateProfile->tpa_breakpoint);
1217 BLACKBOX_PRINT_HEADER_LINE("rates:%d,%d,%d", currentControlRateProfile->rates[ROLL],
1218 currentControlRateProfile->rates[PITCH],
1219 currentControlRateProfile->rates[YAW]);
1220 BLACKBOX_PRINT_HEADER_LINE("rollPID:%d,%d,%d", currentProfile->pidProfile.P8[ROLL],
1221 currentProfile->pidProfile.I8[ROLL],
1222 currentProfile->pidProfile.D8[ROLL]);
1223 BLACKBOX_PRINT_HEADER_LINE("pitchPID:%d,%d,%d", currentProfile->pidProfile.P8[PITCH],
1224 currentProfile->pidProfile.I8[PITCH],
1225 currentProfile->pidProfile.D8[PITCH]);
1226 BLACKBOX_PRINT_HEADER_LINE("yawPID:%d,%d,%d", currentProfile->pidProfile.P8[YAW],
1227 currentProfile->pidProfile.I8[YAW],
1228 currentProfile->pidProfile.D8[YAW]);
1229 BLACKBOX_PRINT_HEADER_LINE("altPID:%d,%d,%d", currentProfile->pidProfile.P8[PIDALT],
1230 currentProfile->pidProfile.I8[PIDALT],
1231 currentProfile->pidProfile.D8[PIDALT]);
1232 BLACKBOX_PRINT_HEADER_LINE("posPID:%d,%d,%d", currentProfile->pidProfile.P8[PIDPOS],
1233 currentProfile->pidProfile.I8[PIDPOS],
1234 currentProfile->pidProfile.D8[PIDPOS]);
1235 BLACKBOX_PRINT_HEADER_LINE("posrPID:%d,%d,%d", currentProfile->pidProfile.P8[PIDPOSR],
1236 currentProfile->pidProfile.I8[PIDPOSR],
1237 currentProfile->pidProfile.D8[PIDPOSR]);
1238 BLACKBOX_PRINT_HEADER_LINE("navrPID:%d,%d,%d", currentProfile->pidProfile.P8[PIDNAVR],
1239 currentProfile->pidProfile.I8[PIDNAVR],
1240 currentProfile->pidProfile.D8[PIDNAVR]);
1241 BLACKBOX_PRINT_HEADER_LINE("levelPID:%d,%d,%d", currentProfile->pidProfile.P8[PIDLEVEL],
1242 currentProfile->pidProfile.I8[PIDLEVEL],
1243 currentProfile->pidProfile.D8[PIDLEVEL]);
1244 BLACKBOX_PRINT_HEADER_LINE("magPID:%d", currentProfile->pidProfile.P8[PIDMAG]);
1245 BLACKBOX_PRINT_HEADER_LINE("velPID:%d,%d,%d", currentProfile->pidProfile.P8[PIDVEL],
1246 currentProfile->pidProfile.I8[PIDVEL],
1247 currentProfile->pidProfile.D8[PIDVEL]);
1248 BLACKBOX_PRINT_HEADER_LINE("dterm_filter_type:%d", currentProfile->pidProfile.dterm_filter_type);
1249 BLACKBOX_PRINT_HEADER_LINE("dterm_lpf_hz:%d", currentProfile->pidProfile.dterm_lpf_hz);
1250 BLACKBOX_PRINT_HEADER_LINE("yaw_lpf_hz:%d", currentProfile->pidProfile.yaw_lpf_hz);
1251 BLACKBOX_PRINT_HEADER_LINE("dterm_notch_hz:%d", currentProfile->pidProfile.dterm_notch_hz);
1252 BLACKBOX_PRINT_HEADER_LINE("dterm_notch_cutoff:%d", currentProfile->pidProfile.dterm_notch_cutoff);
1253 BLACKBOX_PRINT_HEADER_LINE("itermWindupPointPercent:%d", currentProfile->pidProfile.itermWindupPointPercent);
1254 BLACKBOX_PRINT_HEADER_LINE("dterm_average_count:%d", currentProfile->pidProfile.dterm_average_count);
1255 BLACKBOX_PRINT_HEADER_LINE("vbat_pid_compensation:%d", currentProfile->pidProfile.vbatPidCompensation);
1256 BLACKBOX_PRINT_HEADER_LINE("pidAtMinThrottle:%d", currentProfile->pidProfile.pidAtMinThrottle);
1258 // Betaflight PID controller parameters
1259 BLACKBOX_PRINT_HEADER_LINE("anti_gravity_threshold:%d", currentProfile->pidProfile.itermThrottleThreshold);
1260 BLACKBOX_PRINT_HEADER_LINE("anti_gravity_gain:%d", castFloatBytesToInt(currentProfile->pidProfile.itermAcceleratorGain));
1261 BLACKBOX_PRINT_HEADER_LINE("setpointRelaxRatio:%d", currentProfile->pidProfile.setpointRelaxRatio);
1262 BLACKBOX_PRINT_HEADER_LINE("dtermSetpointWeight:%d", currentProfile->pidProfile.dtermSetpointWeight);
1263 BLACKBOX_PRINT_HEADER_LINE("yawRateAccelLimit:%d", castFloatBytesToInt(currentProfile->pidProfile.yawRateAccelLimit));
1264 BLACKBOX_PRINT_HEADER_LINE("rateAccelLimit:%d", castFloatBytesToInt(currentProfile->pidProfile.rateAccelLimit));
1265 BLACKBOX_PRINT_HEADER_LINE("pidSumLimit:%d", castFloatBytesToInt(currentProfile->pidProfile.pidSumLimit));
1266 BLACKBOX_PRINT_HEADER_LINE("pidSumLimitYaw:%d", castFloatBytesToInt(currentProfile->pidProfile.pidSumLimitYaw));
1267 // End of Betaflight controller parameters
1269 BLACKBOX_PRINT_HEADER_LINE("deadband:%d", rcControlsConfig()->deadband);
1270 BLACKBOX_PRINT_HEADER_LINE("yaw_deadband:%d", rcControlsConfig()->yaw_deadband);
1271 BLACKBOX_PRINT_HEADER_LINE("gyro_lpf:%d", gyroConfig()->gyro_lpf);
1272 BLACKBOX_PRINT_HEADER_LINE("gyro_soft_type:%d", gyroConfig()->gyro_soft_lpf_type);
1273 BLACKBOX_PRINT_HEADER_LINE("gyro_lowpass_hz:%d", gyroConfig()->gyro_soft_lpf_hz);
1274 BLACKBOX_PRINT_HEADER_LINE("gyro_notch_hz:%d,%d", gyroConfig()->gyro_soft_notch_hz_1,
1275 gyroConfig()->gyro_soft_notch_hz_2);
1276 BLACKBOX_PRINT_HEADER_LINE("gyro_notch_cutoff:%d,%d", gyroConfig()->gyro_soft_notch_cutoff_1,
1277 gyroConfig()->gyro_soft_notch_cutoff_2);
1278 BLACKBOX_PRINT_HEADER_LINE("acc_lpf_hz:%d", (int)(accelerometerConfig()->acc_lpf_hz * 100.0f));
1279 BLACKBOX_PRINT_HEADER_LINE("acc_hardware:%d", accelerometerConfig()->acc_hardware);
1280 BLACKBOX_PRINT_HEADER_LINE("baro_hardware:%d", barometerConfig()->baro_hardware);
1281 BLACKBOX_PRINT_HEADER_LINE("mag_hardware:%d", compassConfig()->mag_hardware);
1282 BLACKBOX_PRINT_HEADER_LINE("gyro_cal_on_first_arm:%d", armingConfig()->gyro_cal_on_first_arm);
1283 BLACKBOX_PRINT_HEADER_LINE("rc_interpolation:%d", rxConfig()->rcInterpolation);
1284 BLACKBOX_PRINT_HEADER_LINE("rc_interpolation_interval:%d", rxConfig()->rcInterpolationInterval);
1285 BLACKBOX_PRINT_HEADER_LINE("airmode_activate_throttle:%d", rxConfig()->airModeActivateThreshold);
1286 BLACKBOX_PRINT_HEADER_LINE("serialrx_provider:%d", rxConfig()->serialrx_provider);
1287 BLACKBOX_PRINT_HEADER_LINE("unsynced_fast_pwm:%d", motorConfig()->useUnsyncedPwm);
1288 BLACKBOX_PRINT_HEADER_LINE("fast_pwm_protocol:%d", motorConfig()->motorPwmProtocol);
1289 BLACKBOX_PRINT_HEADER_LINE("motor_pwm_rate:%d", motorConfig()->motorPwmRate);
1290 BLACKBOX_PRINT_HEADER_LINE("digitalIdleOffset:%d", (int)(motorConfig()->digitalIdleOffsetPercent * 100.0f));
1291 BLACKBOX_PRINT_HEADER_LINE("debug_mode:%d", masterConfig.debug_mode);
1292 BLACKBOX_PRINT_HEADER_LINE("features:%d", masterConfig.enabledFeatures);
1294 default:
1295 return true;
1298 xmitState.headerIndex++;
1299 return false;
1303 * Write the given event to the log immediately
1305 void blackboxLogEvent(FlightLogEvent event, flightLogEventData_t *data)
1307 // Only allow events to be logged after headers have been written
1308 if (!(blackboxState == BLACKBOX_STATE_RUNNING || blackboxState == BLACKBOX_STATE_PAUSED)) {
1309 return;
1312 //Shared header for event frames
1313 blackboxWrite('E');
1314 blackboxWrite(event);
1316 //Now serialize the data for this specific frame type
1317 switch (event) {
1318 case FLIGHT_LOG_EVENT_SYNC_BEEP:
1319 blackboxWriteUnsignedVB(data->syncBeep.time);
1320 break;
1321 case FLIGHT_LOG_EVENT_FLIGHTMODE: // New flightmode flags write
1322 blackboxWriteUnsignedVB(data->flightMode.flags);
1323 blackboxWriteUnsignedVB(data->flightMode.lastFlags);
1324 break;
1325 case FLIGHT_LOG_EVENT_INFLIGHT_ADJUSTMENT:
1326 if (data->inflightAdjustment.floatFlag) {
1327 blackboxWrite(data->inflightAdjustment.adjustmentFunction + FLIGHT_LOG_EVENT_INFLIGHT_ADJUSTMENT_FUNCTION_FLOAT_VALUE_FLAG);
1328 blackboxWriteFloat(data->inflightAdjustment.newFloatValue);
1329 } else {
1330 blackboxWrite(data->inflightAdjustment.adjustmentFunction);
1331 blackboxWriteSignedVB(data->inflightAdjustment.newValue);
1333 break;
1334 case FLIGHT_LOG_EVENT_LOGGING_RESUME:
1335 blackboxWriteUnsignedVB(data->loggingResume.logIteration);
1336 blackboxWriteUnsignedVB(data->loggingResume.currentTime);
1337 break;
1338 case FLIGHT_LOG_EVENT_LOG_END:
1339 blackboxPrint("End of log");
1340 blackboxWrite(0);
1341 break;
1345 /* If an arming beep has played since it was last logged, write the time of the arming beep to the log as a synchronization point */
1346 static void blackboxCheckAndLogArmingBeep()
1348 flightLogEvent_syncBeep_t eventData;
1350 // Use != so that we can still detect a change if the counter wraps
1351 if (getArmingBeepTimeMicros() != blackboxLastArmingBeep) {
1352 blackboxLastArmingBeep = getArmingBeepTimeMicros();
1354 eventData.time = blackboxLastArmingBeep;
1356 blackboxLogEvent(FLIGHT_LOG_EVENT_SYNC_BEEP, (flightLogEventData_t *) &eventData);
1360 /* monitor the flight mode event status and trigger an event record if the state changes */
1361 static void blackboxCheckAndLogFlightMode()
1363 flightLogEvent_flightMode_t eventData; // Add new data for current flight mode flags
1365 // Use != so that we can still detect a change if the counter wraps
1366 if (rcModeActivationMask != blackboxLastFlightModeFlags) {
1367 eventData.lastFlags = blackboxLastFlightModeFlags;
1368 blackboxLastFlightModeFlags = rcModeActivationMask;
1369 eventData.flags = rcModeActivationMask;
1371 blackboxLogEvent(FLIGHT_LOG_EVENT_FLIGHTMODE, (flightLogEventData_t *) &eventData);
1376 * Use the user's num/denom settings to decide if the P-frame of the given index should be logged, allowing the user to control
1377 * the portion of logged loop iterations.
1379 static bool blackboxShouldLogPFrame(uint32_t pFrameIndex)
1381 /* Adding a magic shift of "blackboxConfig()->rate_num - 1" in here creates a better spread of
1382 * recorded / skipped frames when the I frame's position is considered:
1384 return (pFrameIndex + blackboxConfig()->rate_num - 1) % blackboxConfig()->rate_denom < blackboxConfig()->rate_num;
1387 static bool blackboxShouldLogIFrame() {
1388 return blackboxPFrameIndex == 0;
1391 // Called once every FC loop in order to keep track of how many FC loop iterations have passed
1392 static void blackboxAdvanceIterationTimers()
1394 blackboxSlowFrameIterationTimer++;
1395 blackboxIteration++;
1396 blackboxPFrameIndex++;
1398 if (blackboxPFrameIndex == BLACKBOX_I_INTERVAL) {
1399 blackboxPFrameIndex = 0;
1400 blackboxIFrameIndex++;
1404 // Called once every FC loop in order to log the current state
1405 static void blackboxLogIteration(timeUs_t currentTimeUs)
1407 // Write a keyframe every BLACKBOX_I_INTERVAL frames so we can resynchronise upon missing frames
1408 if (blackboxShouldLogIFrame()) {
1410 * Don't log a slow frame if the slow data didn't change ("I" frames are already large enough without adding
1411 * an additional item to write at the same time). Unless we're *only* logging "I" frames, then we have no choice.
1413 writeSlowFrameIfNeeded(blackboxIsOnlyLoggingIntraframes());
1415 loadMainState(currentTimeUs);
1416 writeIntraframe();
1417 } else {
1418 blackboxCheckAndLogArmingBeep();
1419 blackboxCheckAndLogFlightMode(); // Check for FlightMode status change event
1421 if (blackboxShouldLogPFrame(blackboxPFrameIndex)) {
1423 * We assume that slow frames are only interesting in that they aid the interpretation of the main data stream.
1424 * So only log slow frames during loop iterations where we log a main frame.
1426 writeSlowFrameIfNeeded(true);
1428 loadMainState(currentTimeUs);
1429 writeInterframe();
1431 #ifdef GPS
1432 if (feature(FEATURE_GPS)) {
1434 * If the GPS home point has been updated, or every 128 intraframes (~10 seconds), write the
1435 * GPS home position.
1437 * We write it periodically so that if one Home Frame goes missing, the GPS coordinates can
1438 * still be interpreted correctly.
1440 if (GPS_home[0] != gpsHistory.GPS_home[0] || GPS_home[1] != gpsHistory.GPS_home[1]
1441 || (blackboxPFrameIndex == BLACKBOX_I_INTERVAL / 2 && blackboxIFrameIndex % 128 == 0)) {
1443 writeGPSHomeFrame();
1444 writeGPSFrame(currentTimeUs);
1445 } else if (GPS_numSat != gpsHistory.GPS_numSat || GPS_coord[0] != gpsHistory.GPS_coord[0]
1446 || GPS_coord[1] != gpsHistory.GPS_coord[1]) {
1447 //We could check for velocity changes as well but I doubt it changes independent of position
1448 writeGPSFrame(currentTimeUs);
1451 #endif
1454 //Flush every iteration so that our runtime variance is minimized
1455 blackboxDeviceFlush();
1459 * Call each flight loop iteration to perform blackbox logging.
1461 void handleBlackbox(timeUs_t currentTimeUs)
1463 int i;
1465 if (blackboxState >= BLACKBOX_FIRST_HEADER_SENDING_STATE && blackboxState <= BLACKBOX_LAST_HEADER_SENDING_STATE) {
1466 blackboxReplenishHeaderBudget();
1469 switch (blackboxState) {
1470 case BLACKBOX_STATE_PREPARE_LOG_FILE:
1471 if (blackboxDeviceBeginLog()) {
1472 blackboxSetState(BLACKBOX_STATE_SEND_HEADER);
1474 break;
1475 case BLACKBOX_STATE_SEND_HEADER:
1476 //On entry of this state, xmitState.headerIndex is 0 and startTime is intialised
1479 * Once the UART has had time to init, transmit the header in chunks so we don't overflow its transmit
1480 * buffer, overflow the OpenLog's buffer, or keep the main loop busy for too long.
1482 if (millis() > xmitState.u.startTime + 100) {
1483 if (blackboxDeviceReserveBufferSpace(BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION) == BLACKBOX_RESERVE_SUCCESS) {
1484 for (i = 0; i < BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION && blackboxHeader[xmitState.headerIndex] != '\0'; i++, xmitState.headerIndex++) {
1485 blackboxWrite(blackboxHeader[xmitState.headerIndex]);
1486 blackboxHeaderBudget--;
1489 if (blackboxHeader[xmitState.headerIndex] == '\0') {
1490 blackboxSetState(BLACKBOX_STATE_SEND_MAIN_FIELD_HEADER);
1494 break;
1495 case BLACKBOX_STATE_SEND_MAIN_FIELD_HEADER:
1496 //On entry of this state, xmitState.headerIndex is 0 and xmitState.u.fieldIndex is -1
1497 if (!sendFieldDefinition('I', 'P', blackboxMainFields, blackboxMainFields + 1, ARRAY_LENGTH(blackboxMainFields),
1498 &blackboxMainFields[0].condition, &blackboxMainFields[1].condition)) {
1499 #ifdef GPS
1500 if (feature(FEATURE_GPS)) {
1501 blackboxSetState(BLACKBOX_STATE_SEND_GPS_H_HEADER);
1502 } else
1503 #endif
1504 blackboxSetState(BLACKBOX_STATE_SEND_SLOW_HEADER);
1506 break;
1507 #ifdef GPS
1508 case BLACKBOX_STATE_SEND_GPS_H_HEADER:
1509 //On entry of this state, xmitState.headerIndex is 0 and xmitState.u.fieldIndex is -1
1510 if (!sendFieldDefinition('H', 0, blackboxGpsHFields, blackboxGpsHFields + 1, ARRAY_LENGTH(blackboxGpsHFields),
1511 NULL, NULL)) {
1512 blackboxSetState(BLACKBOX_STATE_SEND_GPS_G_HEADER);
1514 break;
1515 case BLACKBOX_STATE_SEND_GPS_G_HEADER:
1516 //On entry of this state, xmitState.headerIndex is 0 and xmitState.u.fieldIndex is -1
1517 if (!sendFieldDefinition('G', 0, blackboxGpsGFields, blackboxGpsGFields + 1, ARRAY_LENGTH(blackboxGpsGFields),
1518 &blackboxGpsGFields[0].condition, &blackboxGpsGFields[1].condition)) {
1519 blackboxSetState(BLACKBOX_STATE_SEND_SLOW_HEADER);
1521 break;
1522 #endif
1523 case BLACKBOX_STATE_SEND_SLOW_HEADER:
1524 //On entry of this state, xmitState.headerIndex is 0 and xmitState.u.fieldIndex is -1
1525 if (!sendFieldDefinition('S', 0, blackboxSlowFields, blackboxSlowFields + 1, ARRAY_LENGTH(blackboxSlowFields),
1526 NULL, NULL)) {
1527 blackboxSetState(BLACKBOX_STATE_SEND_SYSINFO);
1529 break;
1530 case BLACKBOX_STATE_SEND_SYSINFO:
1531 //On entry of this state, xmitState.headerIndex is 0
1533 //Keep writing chunks of the system info headers until it returns true to signal completion
1534 if (blackboxWriteSysinfo()) {
1537 * Wait for header buffers to drain completely before data logging begins to ensure reliable header delivery
1538 * (overflowing circular buffers causes all data to be discarded, so the first few logged iterations
1539 * could wipe out the end of the header if we weren't careful)
1541 if (blackboxDeviceFlushForce()) {
1542 blackboxSetState(BLACKBOX_STATE_RUNNING);
1545 break;
1546 case BLACKBOX_STATE_PAUSED:
1547 // Only allow resume to occur during an I-frame iteration, so that we have an "I" base to work from
1548 if (IS_RC_MODE_ACTIVE(BOXBLACKBOX) && blackboxShouldLogIFrame()) {
1549 // Write a log entry so the decoder is aware that our large time/iteration skip is intended
1550 flightLogEvent_loggingResume_t resume;
1552 resume.logIteration = blackboxIteration;
1553 resume.currentTime = currentTimeUs;
1555 blackboxLogEvent(FLIGHT_LOG_EVENT_LOGGING_RESUME, (flightLogEventData_t *) &resume);
1556 blackboxSetState(BLACKBOX_STATE_RUNNING);
1558 blackboxLogIteration(currentTimeUs);
1561 // Keep the logging timers ticking so our log iteration continues to advance
1562 blackboxAdvanceIterationTimers();
1563 break;
1564 case BLACKBOX_STATE_RUNNING:
1565 // On entry to this state, blackboxIteration, blackboxPFrameIndex and blackboxIFrameIndex are reset to 0
1566 // Prevent the Pausing of the log on the mode switch if in Motor Test Mode
1567 if (blackboxModeActivationConditionPresent && !IS_RC_MODE_ACTIVE(BOXBLACKBOX) && !startedLoggingInTestMode) {
1568 blackboxSetState(BLACKBOX_STATE_PAUSED);
1569 } else {
1570 blackboxLogIteration(currentTimeUs);
1573 blackboxAdvanceIterationTimers();
1574 break;
1575 case BLACKBOX_STATE_SHUTTING_DOWN:
1576 //On entry of this state, startTime is set
1579 * Wait for the log we've transmitted to make its way to the logger before we release the serial port,
1580 * since releasing the port clears the Tx buffer.
1582 * Don't wait longer than it could possibly take if something funky happens.
1584 if (blackboxDeviceEndLog(blackboxLoggedAnyFrames) && (millis() > xmitState.u.startTime + BLACKBOX_SHUTDOWN_TIMEOUT_MILLIS || blackboxDeviceFlushForce())) {
1585 blackboxDeviceClose();
1586 blackboxSetState(BLACKBOX_STATE_STOPPED);
1588 break;
1589 default:
1590 break;
1593 // Did we run out of room on the device? Stop!
1594 if (isBlackboxDeviceFull()) {
1595 blackboxSetState(BLACKBOX_STATE_STOPPED);
1596 // ensure we reset the test mode flag if we stop due to full memory card
1597 if (startedLoggingInTestMode) startedLoggingInTestMode = false;
1598 } else { // Only log in test mode if there is room!
1600 if(blackboxConfig()->on_motor_test) {
1601 // Handle Motor Test Mode
1602 if(inMotorTestMode()) {
1603 if(blackboxState==BLACKBOX_STATE_STOPPED)
1604 startInTestMode();
1605 } else {
1606 if(blackboxState!=BLACKBOX_STATE_STOPPED)
1607 stopInTestMode();
1613 static bool canUseBlackboxWithCurrentConfiguration(void)
1615 return feature(FEATURE_BLACKBOX) &&
1616 (blackboxConfig()->device != BLACKBOX_SDCARD || feature(FEATURE_SDCARD));
1620 * Call during system startup to initialize the blackbox.
1622 void initBlackbox(void)
1624 if (canUseBlackboxWithCurrentConfiguration()) {
1625 blackboxSetState(BLACKBOX_STATE_STOPPED);
1626 } else {
1627 blackboxSetState(BLACKBOX_STATE_DISABLED);
1630 #endif