Rework Super Expo Rate Implementation // On the fly Rc Expo
[betaflight.git] / src / main / blackbox / blackbox.c
blobbf43dcc35f2a5e51d1b004ecde6e696073d5cdc8
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 <string.h>
21 #include "platform.h"
22 #include "version.h"
23 #include "debug.h"
25 #ifdef BLACKBOX
27 #include "common/maths.h"
28 #include "common/axis.h"
29 #include "common/color.h"
30 #include "common/encoding.h"
31 #include "common/utils.h"
33 #include "drivers/gpio.h"
34 #include "drivers/sensor.h"
35 #include "drivers/system.h"
36 #include "drivers/serial.h"
37 #include "drivers/compass.h"
38 #include "drivers/timer.h"
39 #include "drivers/pwm_rx.h"
40 #include "drivers/accgyro.h"
41 #include "drivers/light_led.h"
43 #include "sensors/sensors.h"
44 #include "sensors/boardalignment.h"
45 #include "sensors/sonar.h"
46 #include "sensors/compass.h"
47 #include "sensors/acceleration.h"
48 #include "sensors/barometer.h"
49 #include "sensors/gyro.h"
50 #include "sensors/battery.h"
52 #include "io/beeper.h"
53 #include "io/display.h"
54 #include "io/escservo.h"
55 #include "io/rc_controls.h"
56 #include "io/gimbal.h"
57 #include "io/gps.h"
58 #include "io/ledstrip.h"
59 #include "io/serial.h"
60 #include "io/serial_cli.h"
61 #include "io/serial_msp.h"
62 #include "io/statusindicator.h"
63 #include "io/vtx.h"
65 #include "rx/rx.h"
66 #include "rx/msp.h"
68 #include "telemetry/telemetry.h"
70 #include "flight/mixer.h"
71 #include "flight/altitudehold.h"
72 #include "flight/failsafe.h"
73 #include "flight/imu.h"
74 #include "flight/navigation.h"
76 #include "config/runtime_config.h"
77 #include "config/config.h"
78 #include "config/config_profile.h"
79 #include "config/config_master.h"
81 #include "blackbox.h"
82 #include "blackbox_io.h"
84 #define BLACKBOX_I_INTERVAL 32
85 #define BLACKBOX_SHUTDOWN_TIMEOUT_MILLIS 200
86 #define SLOW_FRAME_INTERVAL 4096
88 #define ARRAY_LENGTH(x) (sizeof((x))/sizeof((x)[0]))
90 #define STATIC_ASSERT(condition, name ) \
91 typedef char assert_failed_ ## name [(condition) ? 1 : -1 ]
93 // Some macros to make writing FLIGHT_LOG_FIELD_* constants shorter:
95 #define PREDICT(x) CONCAT(FLIGHT_LOG_FIELD_PREDICTOR_, x)
96 #define ENCODING(x) CONCAT(FLIGHT_LOG_FIELD_ENCODING_, x)
97 #define CONDITION(x) CONCAT(FLIGHT_LOG_FIELD_CONDITION_, x)
98 #define UNSIGNED FLIGHT_LOG_FIELD_UNSIGNED
99 #define SIGNED FLIGHT_LOG_FIELD_SIGNED
101 static const char blackboxHeader[] =
102 "H Product:Blackbox flight data recorder by Nicholas Sherlock\n"
103 "H Data version:2\n"
104 "H I interval:" STR(BLACKBOX_I_INTERVAL) "\n";
106 static const char* const blackboxFieldHeaderNames[] = {
107 "name",
108 "signed",
109 "predictor",
110 "encoding",
111 "predictor",
112 "encoding"
115 /* All field definition structs should look like this (but with longer arrs): */
116 typedef struct blackboxFieldDefinition_s {
117 const char *name;
118 // If the field name has a number to be included in square brackets [1] afterwards, set it here, or -1 for no brackets:
119 int8_t fieldNameIndex;
121 // Each member of this array will be the value to print for this field for the given header index
122 uint8_t arr[1];
123 } blackboxFieldDefinition_t;
125 #define BLACKBOX_DELTA_FIELD_HEADER_COUNT ARRAY_LENGTH(blackboxFieldHeaderNames)
126 #define BLACKBOX_SIMPLE_FIELD_HEADER_COUNT (BLACKBOX_DELTA_FIELD_HEADER_COUNT - 2)
127 #define BLACKBOX_CONDITIONAL_FIELD_HEADER_COUNT (BLACKBOX_DELTA_FIELD_HEADER_COUNT - 2)
129 typedef struct blackboxSimpleFieldDefinition_s {
130 const char *name;
131 int8_t fieldNameIndex;
133 uint8_t isSigned;
134 uint8_t predict;
135 uint8_t encode;
136 } blackboxSimpleFieldDefinition_t;
138 typedef struct blackboxConditionalFieldDefinition_s {
139 const char *name;
140 int8_t fieldNameIndex;
142 uint8_t isSigned;
143 uint8_t predict;
144 uint8_t encode;
145 uint8_t condition; // Decide whether this field should appear in the log
146 } blackboxConditionalFieldDefinition_t;
148 typedef struct blackboxDeltaFieldDefinition_s {
149 const char *name;
150 int8_t fieldNameIndex;
152 uint8_t isSigned;
153 uint8_t Ipredict;
154 uint8_t Iencode;
155 uint8_t Ppredict;
156 uint8_t Pencode;
157 uint8_t condition; // Decide whether this field should appear in the log
158 } blackboxDeltaFieldDefinition_t;
161 * Description of the blackbox fields we are writing in our main intra (I) and inter (P) frames. This description is
162 * written into the flight log header so the log can be properly interpreted (but these definitions don't actually cause
163 * the encoding to happen, we have to encode the flight log ourselves in write{Inter|Intra}frame() in a way that matches
164 * the encoding we've promised here).
166 static const blackboxDeltaFieldDefinition_t blackboxMainFields[] = {
167 /* loopIteration doesn't appear in P frames since it always increments */
168 {"loopIteration",-1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(INC), .Pencode = FLIGHT_LOG_FIELD_ENCODING_NULL, CONDITION(ALWAYS)},
169 /* Time advances pretty steadily so the P-frame prediction is a straight line */
170 {"time", -1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(STRAIGHT_LINE), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
171 {"axisP", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
172 {"axisP", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
173 {"axisP", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
174 /* I terms get special packed encoding in P frames: */
175 {"axisI", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
176 {"axisI", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
177 {"axisI", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
178 {"axisD", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_0)},
179 {"axisD", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_1)},
180 {"axisD", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(NONZERO_PID_D_2)},
181 /* rcCommands are encoded together as a group in P-frames: */
182 {"rcCommand", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
183 {"rcCommand", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
184 {"rcCommand", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
185 /* Throttle is always in the range [minthrottle..maxthrottle]: */
186 {"rcCommand", 3, UNSIGNED, .Ipredict = PREDICT(MINTHROTTLE), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
188 {"vbatLatest", -1, UNSIGNED, .Ipredict = PREDICT(VBATREF), .Iencode = ENCODING(NEG_14BIT), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_VBAT},
189 {"amperageLatest",-1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_AMPERAGE_ADC},
191 #ifdef MAG
192 {"magADC", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
193 {"magADC", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
194 {"magADC", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_MAG},
195 #endif
196 #ifdef BARO
197 {"BaroAlt", -1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_BARO},
198 #endif
199 #ifdef SONAR
200 {"sonarRaw", -1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_SONAR},
201 #endif
202 {"rssi", -1, UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_8SVB), FLIGHT_LOG_FIELD_CONDITION_RSSI},
204 /* Gyros and accelerometers base their P-predictions on the average of the previous 2 frames to reduce noise impact */
205 {"gyroADC", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
206 {"gyroADC", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
207 {"gyroADC", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
208 {"accSmooth", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
209 {"accSmooth", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
210 {"accSmooth", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
211 {"debug", 0, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
212 {"debug", 1, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
213 {"debug", 2, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
214 {"debug", 3, SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
215 /* 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): */
216 {"motor", 0, UNSIGNED, .Ipredict = PREDICT(MINTHROTTLE), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_1)},
217 /* Subsequent motors base their I-frame values on the first one, P-frame values on the average of last two frames: */
218 {"motor", 1, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_2)},
219 {"motor", 2, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_3)},
220 {"motor", 3, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_4)},
221 {"motor", 4, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_5)},
222 {"motor", 5, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_6)},
223 {"motor", 6, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_7)},
224 {"motor", 7, UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_8)},
226 /* Tricopter tail servo */
227 {"servo", 5, UNSIGNED, .Ipredict = PREDICT(1500), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(TRICOPTER)}
230 #ifdef GPS
231 // GPS position/vel frame
232 static const blackboxConditionalFieldDefinition_t blackboxGpsGFields[] = {
233 {"time", -1, UNSIGNED, PREDICT(LAST_MAIN_FRAME_TIME), ENCODING(UNSIGNED_VB), CONDITION(NOT_LOGGING_EVERY_FRAME)},
234 {"GPS_numSat", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
235 {"GPS_coord", 0, SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
236 {"GPS_coord", 1, SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
237 {"GPS_altitude", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
238 {"GPS_speed", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)},
239 {"GPS_ground_course", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB), CONDITION(ALWAYS)}
242 // GPS home frame
243 static const blackboxSimpleFieldDefinition_t blackboxGpsHFields[] = {
244 {"GPS_home", 0, SIGNED, PREDICT(0), ENCODING(SIGNED_VB)},
245 {"GPS_home", 1, SIGNED, PREDICT(0), ENCODING(SIGNED_VB)}
247 #endif
249 // Rarely-updated fields
250 static const blackboxSimpleFieldDefinition_t blackboxSlowFields[] = {
251 {"flightModeFlags", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB)},
252 {"stateFlags", -1, UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB)},
254 {"failsafePhase", -1, UNSIGNED, PREDICT(0), ENCODING(TAG2_3S32)},
255 {"rxSignalReceived", -1, UNSIGNED, PREDICT(0), ENCODING(TAG2_3S32)},
256 {"rxFlightChannelsValid", -1, UNSIGNED, PREDICT(0), ENCODING(TAG2_3S32)}
259 typedef enum BlackboxState {
260 BLACKBOX_STATE_DISABLED = 0,
261 BLACKBOX_STATE_STOPPED,
262 BLACKBOX_STATE_PREPARE_LOG_FILE,
263 BLACKBOX_STATE_SEND_HEADER,
264 BLACKBOX_STATE_SEND_MAIN_FIELD_HEADER,
265 BLACKBOX_STATE_SEND_GPS_H_HEADER,
266 BLACKBOX_STATE_SEND_GPS_G_HEADER,
267 BLACKBOX_STATE_SEND_SLOW_HEADER,
268 BLACKBOX_STATE_SEND_SYSINFO,
269 BLACKBOX_STATE_PAUSED,
270 BLACKBOX_STATE_RUNNING,
271 BLACKBOX_STATE_SHUTTING_DOWN
272 } BlackboxState;
274 #define BLACKBOX_FIRST_HEADER_SENDING_STATE BLACKBOX_STATE_SEND_HEADER
275 #define BLACKBOX_LAST_HEADER_SENDING_STATE BLACKBOX_STATE_SEND_SYSINFO
277 typedef struct blackboxMainState_s {
278 uint32_t time;
280 int32_t axisPID_P[XYZ_AXIS_COUNT], axisPID_I[XYZ_AXIS_COUNT], axisPID_D[XYZ_AXIS_COUNT];
282 int16_t rcCommand[4];
283 int16_t gyroADC[XYZ_AXIS_COUNT];
284 int16_t accSmooth[XYZ_AXIS_COUNT];
285 int16_t debug[4];
286 int16_t motor[MAX_SUPPORTED_MOTORS];
287 int16_t servo[MAX_SUPPORTED_SERVOS];
289 uint16_t vbatLatest;
290 uint16_t amperageLatest;
292 #ifdef BARO
293 int32_t BaroAlt;
294 #endif
295 #ifdef MAG
296 int16_t magADC[XYZ_AXIS_COUNT];
297 #endif
298 #ifdef SONAR
299 int32_t sonarRaw;
300 #endif
301 uint16_t rssi;
302 } blackboxMainState_t;
304 typedef struct blackboxGpsState_s {
305 int32_t GPS_home[2], GPS_coord[2];
306 uint8_t GPS_numSat;
307 } blackboxGpsState_t;
309 // This data is updated really infrequently:
310 typedef struct blackboxSlowState_s {
311 uint32_t flightModeFlags; // extend this data size (from uint16_t)
312 uint8_t stateFlags;
313 uint8_t failsafePhase;
314 bool rxSignalReceived;
315 bool rxFlightChannelsValid;
316 } __attribute__((__packed__)) blackboxSlowState_t; // We pack this struct so that padding doesn't interfere with memcmp()
318 //From mixer.c:
319 extern uint8_t motorCount;
321 //From mw.c:
322 extern uint32_t currentTime;
324 //From rx.c:
325 extern uint16_t rssi;
327 //From gyro.c
328 extern uint32_t targetLooptime;
330 //From rc_controls.c
331 extern uint32_t rcModeActivationMask;
333 static BlackboxState blackboxState = BLACKBOX_STATE_DISABLED;
335 static uint32_t blackboxLastArmingBeep = 0;
336 static uint32_t blackboxLastFlightModeFlags = 0; // New event tracking of flight modes
339 static struct {
340 uint32_t headerIndex;
342 /* Since these fields are used during different blackbox states (never simultaneously) we can
343 * overlap them to save on RAM
345 union {
346 int fieldIndex;
347 uint32_t startTime;
348 } u;
349 } xmitState;
351 // Cache for FLIGHT_LOG_FIELD_CONDITION_* test results:
352 static uint32_t blackboxConditionCache;
354 STATIC_ASSERT((sizeof(blackboxConditionCache) * 8) >= FLIGHT_LOG_FIELD_CONDITION_NEVER, too_many_flight_log_conditions);
356 static uint32_t blackboxIteration;
357 static uint16_t blackboxPFrameIndex, blackboxIFrameIndex;
358 static uint16_t blackboxSlowFrameIterationTimer;
359 static bool blackboxLoggedAnyFrames;
362 * We store voltages in I-frames relative to this, which was the voltage when the blackbox was activated.
363 * This helps out since the voltage is only expected to fall from that point and we can reduce our diffs
364 * to encode:
366 static uint16_t vbatReference;
368 static blackboxGpsState_t gpsHistory;
369 static blackboxSlowState_t slowHistory;
371 // Keep a history of length 2, plus a buffer for MW to store the new values into
372 static blackboxMainState_t blackboxHistoryRing[3];
374 // These point into blackboxHistoryRing, use them to know where to store history of a given age (0, 1 or 2 generations old)
375 static blackboxMainState_t* blackboxHistory[3];
377 static bool blackboxModeActivationConditionPresent = false;
380 * Return true if it is safe to edit the Blackbox configuration in the emasterConfig.
382 bool blackboxMayEditConfig()
384 return blackboxState <= BLACKBOX_STATE_STOPPED;
387 static bool blackboxIsOnlyLoggingIntraframes() {
388 return masterConfig.blackbox_rate_num == 1 && masterConfig.blackbox_rate_denom == 32;
391 static bool testBlackboxConditionUncached(FlightLogFieldCondition condition)
393 switch (condition) {
394 case FLIGHT_LOG_FIELD_CONDITION_ALWAYS:
395 return true;
397 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_1:
398 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_2:
399 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_3:
400 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_4:
401 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_5:
402 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_6:
403 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_7:
404 case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_8:
405 return motorCount >= condition - FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_1 + 1;
407 case FLIGHT_LOG_FIELD_CONDITION_TRICOPTER:
408 return masterConfig.mixerMode == MIXER_TRI || masterConfig.mixerMode == MIXER_CUSTOM_TRI;
410 case FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_0:
411 case FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_1:
412 case FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_2:
413 return currentProfile->pidProfile.D8[condition - FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_0] != 0;
415 case FLIGHT_LOG_FIELD_CONDITION_MAG:
416 #ifdef MAG
417 return sensors(SENSOR_MAG);
418 #else
419 return false;
420 #endif
422 case FLIGHT_LOG_FIELD_CONDITION_BARO:
423 #ifdef BARO
424 return sensors(SENSOR_BARO);
425 #else
426 return false;
427 #endif
429 case FLIGHT_LOG_FIELD_CONDITION_VBAT:
430 return feature(FEATURE_VBAT);
432 case FLIGHT_LOG_FIELD_CONDITION_AMPERAGE_ADC:
433 return feature(FEATURE_CURRENT_METER) && masterConfig.batteryConfig.currentMeterType == CURRENT_SENSOR_ADC;
435 case FLIGHT_LOG_FIELD_CONDITION_SONAR:
436 #ifdef SONAR
437 return feature(FEATURE_SONAR);
438 #else
439 return false;
440 #endif
442 case FLIGHT_LOG_FIELD_CONDITION_RSSI:
443 return masterConfig.rxConfig.rssi_channel > 0 || feature(FEATURE_RSSI_ADC);
445 case FLIGHT_LOG_FIELD_CONDITION_NOT_LOGGING_EVERY_FRAME:
446 return masterConfig.blackbox_rate_num < masterConfig.blackbox_rate_denom;
448 case FLIGHT_LOG_FIELD_CONDITION_NEVER:
449 return false;
450 default:
451 return false;
455 static void blackboxBuildConditionCache()
457 FlightLogFieldCondition cond;
459 blackboxConditionCache = 0;
461 for (cond = FLIGHT_LOG_FIELD_CONDITION_FIRST; cond <= FLIGHT_LOG_FIELD_CONDITION_LAST; cond++) {
462 if (testBlackboxConditionUncached(cond)) {
463 blackboxConditionCache |= 1 << cond;
468 static bool testBlackboxCondition(FlightLogFieldCondition condition)
470 return (blackboxConditionCache & (1 << condition)) != 0;
473 static void blackboxSetState(BlackboxState newState)
475 //Perform initial setup required for the new state
476 switch (newState) {
477 case BLACKBOX_STATE_PREPARE_LOG_FILE:
478 blackboxLoggedAnyFrames = false;
479 break;
480 case BLACKBOX_STATE_SEND_HEADER:
481 blackboxHeaderBudget = 0;
482 xmitState.headerIndex = 0;
483 xmitState.u.startTime = millis();
484 break;
485 case BLACKBOX_STATE_SEND_MAIN_FIELD_HEADER:
486 case BLACKBOX_STATE_SEND_GPS_G_HEADER:
487 case BLACKBOX_STATE_SEND_GPS_H_HEADER:
488 case BLACKBOX_STATE_SEND_SLOW_HEADER:
489 xmitState.headerIndex = 0;
490 xmitState.u.fieldIndex = -1;
491 break;
492 case BLACKBOX_STATE_SEND_SYSINFO:
493 xmitState.headerIndex = 0;
494 break;
495 case BLACKBOX_STATE_RUNNING:
496 blackboxSlowFrameIterationTimer = SLOW_FRAME_INTERVAL; //Force a slow frame to be written on the first iteration
497 break;
498 case BLACKBOX_STATE_SHUTTING_DOWN:
499 xmitState.u.startTime = millis();
500 break;
501 default:
504 blackboxState = newState;
507 static void writeIntraframe(void)
509 blackboxMainState_t *blackboxCurrent = blackboxHistory[0];
510 int x;
512 blackboxWrite('I');
514 blackboxWriteUnsignedVB(blackboxIteration);
515 blackboxWriteUnsignedVB(blackboxCurrent->time);
517 blackboxWriteSignedVBArray(blackboxCurrent->axisPID_P, XYZ_AXIS_COUNT);
518 blackboxWriteSignedVBArray(blackboxCurrent->axisPID_I, XYZ_AXIS_COUNT);
520 // Don't bother writing the current D term if the corresponding PID setting is zero
521 for (x = 0; x < XYZ_AXIS_COUNT; x++) {
522 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_0 + x)) {
523 blackboxWriteSignedVB(blackboxCurrent->axisPID_D[x]);
527 // Write roll, pitch and yaw first:
528 blackboxWriteSigned16VBArray(blackboxCurrent->rcCommand, 3);
531 * Write the throttle separately from the rest of the RC data so we can apply a predictor to it.
532 * Throttle lies in range [minthrottle..maxthrottle]:
534 blackboxWriteUnsignedVB(blackboxCurrent->rcCommand[THROTTLE] - masterConfig.escAndServoConfig.minthrottle);
536 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_VBAT)) {
538 * Our voltage is expected to decrease over the course of the flight, so store our difference from
539 * the reference:
541 * Write 14 bits even if the number is negative (which would otherwise result in 32 bits)
543 blackboxWriteUnsignedVB((vbatReference - blackboxCurrent->vbatLatest) & 0x3FFF);
546 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_AMPERAGE_ADC)) {
547 // 12bit value directly from ADC
548 blackboxWriteUnsignedVB(blackboxCurrent->amperageLatest);
551 #ifdef MAG
552 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_MAG)) {
553 blackboxWriteSigned16VBArray(blackboxCurrent->magADC, XYZ_AXIS_COUNT);
555 #endif
557 #ifdef BARO
558 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_BARO)) {
559 blackboxWriteSignedVB(blackboxCurrent->BaroAlt);
561 #endif
563 #ifdef SONAR
564 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_SONAR)) {
565 blackboxWriteSignedVB(blackboxCurrent->sonarRaw);
567 #endif
569 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_RSSI)) {
570 blackboxWriteUnsignedVB(blackboxCurrent->rssi);
573 blackboxWriteSigned16VBArray(blackboxCurrent->gyroADC, XYZ_AXIS_COUNT);
574 blackboxWriteSigned16VBArray(blackboxCurrent->accSmooth, XYZ_AXIS_COUNT);
575 blackboxWriteSigned16VBArray(blackboxCurrent->debug, 4);
577 //Motors can be below minthrottle when disarmed, but that doesn't happen much
578 blackboxWriteUnsignedVB(blackboxCurrent->motor[0] - masterConfig.escAndServoConfig.minthrottle);
580 //Motors tend to be similar to each other so use the first motor's value as a predictor of the others
581 for (x = 1; x < motorCount; x++) {
582 blackboxWriteSignedVB(blackboxCurrent->motor[x] - blackboxCurrent->motor[0]);
585 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_TRICOPTER)) {
586 //Assume the tail spends most of its time around the center
587 blackboxWriteSignedVB(blackboxCurrent->servo[5] - 1500);
590 //Rotate our history buffers:
592 //The current state becomes the new "before" state
593 blackboxHistory[1] = blackboxHistory[0];
594 //And since we have no other history, we also use it for the "before, before" state
595 blackboxHistory[2] = blackboxHistory[0];
596 //And advance the current state over to a blank space ready to be filled
597 blackboxHistory[0] = ((blackboxHistory[0] - blackboxHistoryRing + 1) % 3) + blackboxHistoryRing;
599 blackboxLoggedAnyFrames = true;
602 static void blackboxWriteMainStateArrayUsingAveragePredictor(int arrOffsetInHistory, int count)
604 int16_t *curr = (int16_t*) ((char*) (blackboxHistory[0]) + arrOffsetInHistory);
605 int16_t *prev1 = (int16_t*) ((char*) (blackboxHistory[1]) + arrOffsetInHistory);
606 int16_t *prev2 = (int16_t*) ((char*) (blackboxHistory[2]) + arrOffsetInHistory);
608 for (int i = 0; i < count; i++) {
609 // Predictor is the average of the previous two history states
610 int32_t predictor = (prev1[i] + prev2[i]) / 2;
612 blackboxWriteSignedVB(curr[i] - predictor);
616 static void writeInterframe(void)
618 int x;
619 int32_t deltas[8];
621 blackboxMainState_t *blackboxCurrent = blackboxHistory[0];
622 blackboxMainState_t *blackboxLast = blackboxHistory[1];
624 blackboxWrite('P');
626 //No need to store iteration count since its delta is always 1
629 * Since the difference between the difference between successive times will be nearly zero (due to consistent
630 * looptime spacing), use second-order differences.
632 blackboxWriteSignedVB((int32_t) (blackboxHistory[0]->time - 2 * blackboxHistory[1]->time + blackboxHistory[2]->time));
634 arraySubInt32(deltas, blackboxCurrent->axisPID_P, blackboxLast->axisPID_P, XYZ_AXIS_COUNT);
635 blackboxWriteSignedVBArray(deltas, XYZ_AXIS_COUNT);
638 * The PID I field changes very slowly, most of the time +-2, so use an encoding
639 * that can pack all three fields into one byte in that situation.
641 arraySubInt32(deltas, blackboxCurrent->axisPID_I, blackboxLast->axisPID_I, XYZ_AXIS_COUNT);
642 blackboxWriteTag2_3S32(deltas);
645 * The PID D term is frequently set to zero for yaw, which makes the result from the calculation
646 * always zero. So don't bother recording D results when PID D terms are zero.
648 for (x = 0; x < XYZ_AXIS_COUNT; x++) {
649 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_NONZERO_PID_D_0 + x)) {
650 blackboxWriteSignedVB(blackboxCurrent->axisPID_D[x] - blackboxLast->axisPID_D[x]);
655 * RC tends to stay the same or fairly small for many frames at a time, so use an encoding that
656 * can pack multiple values per byte:
658 for (x = 0; x < 4; x++) {
659 deltas[x] = blackboxCurrent->rcCommand[x] - blackboxLast->rcCommand[x];
662 blackboxWriteTag8_4S16(deltas);
664 //Check for sensors that are updated periodically (so deltas are normally zero)
665 int optionalFieldCount = 0;
667 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_VBAT)) {
668 deltas[optionalFieldCount++] = (int32_t) blackboxCurrent->vbatLatest - blackboxLast->vbatLatest;
671 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_AMPERAGE_ADC)) {
672 deltas[optionalFieldCount++] = (int32_t) blackboxCurrent->amperageLatest - blackboxLast->amperageLatest;
675 #ifdef MAG
676 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_MAG)) {
677 for (x = 0; x < XYZ_AXIS_COUNT; x++) {
678 deltas[optionalFieldCount++] = blackboxCurrent->magADC[x] - blackboxLast->magADC[x];
681 #endif
683 #ifdef BARO
684 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_BARO)) {
685 deltas[optionalFieldCount++] = blackboxCurrent->BaroAlt - blackboxLast->BaroAlt;
687 #endif
689 #ifdef SONAR
690 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_SONAR)) {
691 deltas[optionalFieldCount++] = blackboxCurrent->sonarRaw - blackboxLast->sonarRaw;
693 #endif
695 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_RSSI)) {
696 deltas[optionalFieldCount++] = (int32_t) blackboxCurrent->rssi - blackboxLast->rssi;
699 blackboxWriteTag8_8SVB(deltas, optionalFieldCount);
701 //Since gyros, accs and motors are noisy, base their predictions on the average of the history:
702 blackboxWriteMainStateArrayUsingAveragePredictor(offsetof(blackboxMainState_t, gyroADC), XYZ_AXIS_COUNT);
703 blackboxWriteMainStateArrayUsingAveragePredictor(offsetof(blackboxMainState_t, accSmooth), XYZ_AXIS_COUNT);
704 blackboxWriteMainStateArrayUsingAveragePredictor(offsetof(blackboxMainState_t, debug), 4);
705 blackboxWriteMainStateArrayUsingAveragePredictor(offsetof(blackboxMainState_t, motor), motorCount);
707 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_TRICOPTER)) {
708 blackboxWriteSignedVB(blackboxCurrent->servo[5] - blackboxLast->servo[5]);
711 //Rotate our history buffers
712 blackboxHistory[2] = blackboxHistory[1];
713 blackboxHistory[1] = blackboxHistory[0];
714 blackboxHistory[0] = ((blackboxHistory[0] - blackboxHistoryRing + 1) % 3) + blackboxHistoryRing;
716 blackboxLoggedAnyFrames = true;
719 /* Write the contents of the global "slowHistory" to the log as an "S" frame. Because this data is logged so
720 * infrequently, delta updates are not reasonable, so we log independent frames. */
721 static void writeSlowFrame(void)
723 int32_t values[3];
725 blackboxWrite('S');
727 blackboxWriteUnsignedVB(slowHistory.flightModeFlags);
728 blackboxWriteUnsignedVB(slowHistory.stateFlags);
731 * Most of the time these three values will be able to pack into one byte for us:
733 values[0] = slowHistory.failsafePhase;
734 values[1] = slowHistory.rxSignalReceived ? 1 : 0;
735 values[2] = slowHistory.rxFlightChannelsValid ? 1 : 0;
736 blackboxWriteTag2_3S32(values);
738 blackboxSlowFrameIterationTimer = 0;
742 * Load rarely-changing values from the FC into the given structure
744 static void loadSlowState(blackboxSlowState_t *slow)
746 slow->flightModeFlags = rcModeActivationMask; //was flightModeFlags;
747 slow->stateFlags = stateFlags;
748 slow->failsafePhase = failsafePhase();
749 slow->rxSignalReceived = rxIsReceivingSignal();
750 slow->rxFlightChannelsValid = rxAreFlightChannelsValid();
754 * If the data in the slow frame has changed, log a slow frame.
756 * If allowPeriodicWrite is true, the frame is also logged if it has been more than SLOW_FRAME_INTERVAL logging iterations
757 * since the field was last logged.
759 static void writeSlowFrameIfNeeded(bool allowPeriodicWrite)
761 // Write the slow frame peridocially so it can be recovered if we ever lose sync
762 bool shouldWrite = allowPeriodicWrite && blackboxSlowFrameIterationTimer >= SLOW_FRAME_INTERVAL;
764 if (shouldWrite) {
765 loadSlowState(&slowHistory);
766 } else {
767 blackboxSlowState_t newSlowState;
769 loadSlowState(&newSlowState);
771 // Only write a slow frame if it was different from the previous state
772 if (memcmp(&newSlowState, &slowHistory, sizeof(slowHistory)) != 0) {
773 // Use the new state as our new history
774 memcpy(&slowHistory, &newSlowState, sizeof(slowHistory));
775 shouldWrite = true;
779 if (shouldWrite) {
780 writeSlowFrame();
784 static int gcd(int num, int denom)
786 if (denom == 0) {
787 return num;
790 return gcd(denom, num % denom);
793 static void validateBlackboxConfig()
795 int div;
797 if (masterConfig.blackbox_rate_num == 0 || masterConfig.blackbox_rate_denom == 0
798 || masterConfig.blackbox_rate_num >= masterConfig.blackbox_rate_denom) {
799 masterConfig.blackbox_rate_num = 1;
800 masterConfig.blackbox_rate_denom = 1;
801 } else {
802 /* Reduce the fraction the user entered as much as possible (makes the recorded/skipped frame pattern repeat
803 * itself more frequently)
805 div = gcd(masterConfig.blackbox_rate_num, masterConfig.blackbox_rate_denom);
807 masterConfig.blackbox_rate_num /= div;
808 masterConfig.blackbox_rate_denom /= div;
811 // If we've chosen an unsupported device, change the device to serial
812 switch (masterConfig.blackbox_device) {
813 #ifdef USE_FLASHFS
814 case BLACKBOX_DEVICE_FLASH:
815 #endif
816 #ifdef USE_SDCARD
817 case BLACKBOX_DEVICE_SDCARD:
818 #endif
819 case BLACKBOX_DEVICE_SERIAL:
820 // Device supported, leave the setting alone
821 break;
823 default:
824 masterConfig.blackbox_device = BLACKBOX_DEVICE_SERIAL;
829 * Start Blackbox logging if it is not already running. Intended to be called upon arming.
831 void startBlackbox(void)
833 if (blackboxState == BLACKBOX_STATE_STOPPED) {
834 validateBlackboxConfig();
836 if (!blackboxDeviceOpen()) {
837 blackboxSetState(BLACKBOX_STATE_DISABLED);
838 return;
841 memset(&gpsHistory, 0, sizeof(gpsHistory));
843 blackboxHistory[0] = &blackboxHistoryRing[0];
844 blackboxHistory[1] = &blackboxHistoryRing[1];
845 blackboxHistory[2] = &blackboxHistoryRing[2];
847 vbatReference = vbatLatestADC;
849 //No need to clear the content of blackboxHistoryRing since our first frame will be an intra which overwrites it
852 * We use conditional tests to decide whether or not certain fields should be logged. Since our headers
853 * must always agree with the logged data, the results of these tests must not change during logging. So
854 * cache those now.
856 blackboxBuildConditionCache();
858 blackboxModeActivationConditionPresent = isModeActivationConditionPresent(masterConfig.modeActivationConditions, BOXBLACKBOX);
860 blackboxIteration = 0;
861 blackboxPFrameIndex = 0;
862 blackboxIFrameIndex = 0;
865 * Record the beeper's current idea of the last arming beep time, so that we can detect it changing when
866 * it finally plays the beep for this arming event.
868 blackboxLastArmingBeep = getArmingBeepTimeMicros();
869 blackboxLastFlightModeFlags = rcModeActivationMask; // record startup status
872 blackboxSetState(BLACKBOX_STATE_PREPARE_LOG_FILE);
877 * Begin Blackbox shutdown.
879 void finishBlackbox(void)
881 switch (blackboxState) {
882 case BLACKBOX_STATE_DISABLED:
883 case BLACKBOX_STATE_STOPPED:
884 case BLACKBOX_STATE_SHUTTING_DOWN:
885 // We're already stopped/shutting down
886 break;
888 case BLACKBOX_STATE_RUNNING:
889 case BLACKBOX_STATE_PAUSED:
890 blackboxLogEvent(FLIGHT_LOG_EVENT_LOG_END, NULL);
892 // Fall through
893 default:
894 blackboxSetState(BLACKBOX_STATE_SHUTTING_DOWN);
898 #ifdef GPS
899 static void writeGPSHomeFrame()
901 blackboxWrite('H');
903 blackboxWriteSignedVB(GPS_home[0]);
904 blackboxWriteSignedVB(GPS_home[1]);
905 //TODO it'd be great if we could grab the GPS current time and write that too
907 gpsHistory.GPS_home[0] = GPS_home[0];
908 gpsHistory.GPS_home[1] = GPS_home[1];
911 static void writeGPSFrame()
913 blackboxWrite('G');
916 * If we're logging every frame, then a GPS frame always appears just after a frame with the
917 * currentTime timestamp in the log, so the reader can just use that timestamp for the GPS frame.
919 * If we're not logging every frame, we need to store the time of this GPS frame.
921 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_NOT_LOGGING_EVERY_FRAME)) {
922 // Predict the time of the last frame in the main log
923 blackboxWriteUnsignedVB(currentTime - blackboxHistory[1]->time);
926 blackboxWriteUnsignedVB(GPS_numSat);
927 blackboxWriteSignedVB(GPS_coord[0] - gpsHistory.GPS_home[0]);
928 blackboxWriteSignedVB(GPS_coord[1] - gpsHistory.GPS_home[1]);
929 blackboxWriteUnsignedVB(GPS_altitude);
930 blackboxWriteUnsignedVB(GPS_speed);
931 blackboxWriteUnsignedVB(GPS_ground_course);
933 gpsHistory.GPS_numSat = GPS_numSat;
934 gpsHistory.GPS_coord[0] = GPS_coord[0];
935 gpsHistory.GPS_coord[1] = GPS_coord[1];
937 #endif
940 * Fill the current state of the blackbox using values read from the flight controller
942 static void loadMainState(void)
944 blackboxMainState_t *blackboxCurrent = blackboxHistory[0];
945 int i;
947 blackboxCurrent->time = currentTime;
949 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
950 blackboxCurrent->axisPID_P[i] = axisPID_P[i];
952 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
953 blackboxCurrent->axisPID_I[i] = axisPID_I[i];
955 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
956 blackboxCurrent->axisPID_D[i] = axisPID_D[i];
959 for (i = 0; i < 4; i++) {
960 blackboxCurrent->rcCommand[i] = rcCommand[i];
963 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
964 blackboxCurrent->gyroADC[i] = gyroADC[i];
967 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
968 blackboxCurrent->accSmooth[i] = accSmooth[i];
971 for (i = 0; i < 4; i++) {
972 blackboxCurrent->debug[i] = debug[i];
975 for (i = 0; i < motorCount; i++) {
976 blackboxCurrent->motor[i] = motor[i];
979 blackboxCurrent->vbatLatest = vbatLatestADC;
980 blackboxCurrent->amperageLatest = amperageLatestADC;
982 #ifdef MAG
983 for (i = 0; i < XYZ_AXIS_COUNT; i++) {
984 blackboxCurrent->magADC[i] = magADC[i];
986 #endif
988 #ifdef BARO
989 blackboxCurrent->BaroAlt = BaroAlt;
990 #endif
992 #ifdef SONAR
993 // Store the raw sonar value without applying tilt correction
994 blackboxCurrent->sonarRaw = sonarRead();
995 #endif
997 blackboxCurrent->rssi = rssi;
999 #ifdef USE_SERVOS
1000 //Tail servo for tricopters
1001 blackboxCurrent->servo[5] = servo[5];
1002 #endif
1006 * Transmit the header information for the given field definitions. Transmitted header lines look like:
1008 * H Field I name:a,b,c
1009 * H Field I predictor:0,1,2
1011 * For all header types, provide a "mainFrameChar" which is the name for the field and will be used to refer to it in the
1012 * header (e.g. P, I etc). For blackboxDeltaField_t fields, also provide deltaFrameChar, otherwise set this to zero.
1014 * Provide an array 'conditions' of FlightLogFieldCondition enums if you want these conditions to decide whether a field
1015 * should be included or not. Otherwise provide NULL for this parameter and NULL for secondCondition.
1017 * Set xmitState.headerIndex to 0 and xmitState.u.fieldIndex to -1 before calling for the first time.
1019 * secondFieldDefinition and secondCondition element pointers need to be provided in order to compute the stride of the
1020 * fieldDefinition and secondCondition arrays.
1022 * Returns true if there is still header left to transmit (so call again to continue transmission).
1024 static bool sendFieldDefinition(char mainFrameChar, char deltaFrameChar, const void *fieldDefinitions,
1025 const void *secondFieldDefinition, int fieldCount, const uint8_t *conditions, const uint8_t *secondCondition)
1027 const blackboxFieldDefinition_t *def;
1028 unsigned int headerCount;
1029 static bool needComma = false;
1030 size_t definitionStride = (char*) secondFieldDefinition - (char*) fieldDefinitions;
1031 size_t conditionsStride = (char*) secondCondition - (char*) conditions;
1033 if (deltaFrameChar) {
1034 headerCount = BLACKBOX_DELTA_FIELD_HEADER_COUNT;
1035 } else {
1036 headerCount = BLACKBOX_SIMPLE_FIELD_HEADER_COUNT;
1040 * We're chunking up the header data so we don't exceed our datarate. So we'll be called multiple times to transmit
1041 * the whole header.
1044 // On our first call we need to print the name of the header and a colon
1045 if (xmitState.u.fieldIndex == -1) {
1046 if (xmitState.headerIndex >= headerCount) {
1047 return false; //Someone probably called us again after we had already completed transmission
1050 uint32_t charsToBeWritten = strlen("H Field x :") + strlen(blackboxFieldHeaderNames[xmitState.headerIndex]);
1052 if (blackboxDeviceReserveBufferSpace(charsToBeWritten) != BLACKBOX_RESERVE_SUCCESS) {
1053 return true; // Try again later
1056 blackboxHeaderBudget -= blackboxPrintf("H Field %c %s:", xmitState.headerIndex >= BLACKBOX_SIMPLE_FIELD_HEADER_COUNT ? deltaFrameChar : mainFrameChar, blackboxFieldHeaderNames[xmitState.headerIndex]);
1058 xmitState.u.fieldIndex++;
1059 needComma = false;
1062 // The longest we expect an integer to be as a string:
1063 const uint32_t LONGEST_INTEGER_STRLEN = 2;
1065 for (; xmitState.u.fieldIndex < fieldCount; xmitState.u.fieldIndex++) {
1066 def = (const blackboxFieldDefinition_t*) ((const char*)fieldDefinitions + definitionStride * xmitState.u.fieldIndex);
1068 if (!conditions || testBlackboxCondition(conditions[conditionsStride * xmitState.u.fieldIndex])) {
1069 // First (over)estimate the length of the string we want to print
1071 int32_t bytesToWrite = 1; // Leading comma
1073 // The first header is a field name
1074 if (xmitState.headerIndex == 0) {
1075 bytesToWrite += strlen(def->name) + strlen("[]") + LONGEST_INTEGER_STRLEN;
1076 } else {
1077 //The other headers are integers
1078 bytesToWrite += LONGEST_INTEGER_STRLEN;
1081 // Now perform the write if the buffer is large enough
1082 if (blackboxDeviceReserveBufferSpace(bytesToWrite) != BLACKBOX_RESERVE_SUCCESS) {
1083 // Ran out of space!
1084 return true;
1087 blackboxHeaderBudget -= bytesToWrite;
1089 if (needComma) {
1090 blackboxWrite(',');
1091 } else {
1092 needComma = true;
1095 // The first header is a field name
1096 if (xmitState.headerIndex == 0) {
1097 blackboxPrint(def->name);
1099 // Do we need to print an index in brackets after the name?
1100 if (def->fieldNameIndex != -1) {
1101 blackboxPrintf("[%d]", def->fieldNameIndex);
1103 } else {
1104 //The other headers are integers
1105 blackboxPrintf("%d", def->arr[xmitState.headerIndex - 1]);
1110 // Did we complete this line?
1111 if (xmitState.u.fieldIndex == fieldCount && blackboxDeviceReserveBufferSpace(1) == BLACKBOX_RESERVE_SUCCESS) {
1112 blackboxHeaderBudget--;
1113 blackboxWrite('\n');
1114 xmitState.headerIndex++;
1115 xmitState.u.fieldIndex = -1;
1118 return xmitState.headerIndex < headerCount;
1122 * Transmit a portion of the system information headers. Call the first time with xmitState.headerIndex == 0. Returns
1123 * true iff transmission is complete, otherwise call again later to continue transmission.
1125 static bool blackboxWriteSysinfo()
1127 // Make sure we have enough room in the buffer for our longest line (as of this writing, the "Firmware date" line)
1128 if (blackboxDeviceReserveBufferSpace(64) != BLACKBOX_RESERVE_SUCCESS) {
1129 return false;
1132 switch (xmitState.headerIndex) {
1133 case 0:
1134 blackboxPrintfHeaderLine("Firmware type:Cleanflight");
1135 break;
1136 case 1:
1137 blackboxPrintfHeaderLine("Firmware revision:Betaflight %s (%s) %s", FC_VERSION_STRING, shortGitRevision, targetName);
1138 break;
1139 case 2:
1140 blackboxPrintfHeaderLine("Firmware date:%s %s", buildDate, buildTime);
1141 break;
1142 case 3:
1143 blackboxPrintfHeaderLine("P interval:%d/%d", masterConfig.blackbox_rate_num, masterConfig.blackbox_rate_denom);
1144 break;
1145 case 4:
1146 blackboxPrintfHeaderLine("rcRate:%d", masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].rcRate8);
1147 break;
1148 case 5:
1149 blackboxPrintfHeaderLine("minthrottle:%d", masterConfig.escAndServoConfig.minthrottle);
1150 break;
1151 case 6:
1152 blackboxPrintfHeaderLine("maxthrottle:%d", masterConfig.escAndServoConfig.maxthrottle);
1153 break;
1154 case 7:
1155 blackboxPrintfHeaderLine("gyro.scale:0x%x", castFloatBytesToInt(gyro.scale));
1156 break;
1157 case 8:
1158 blackboxPrintfHeaderLine("acc_1G:%u", acc_1G);
1159 break;
1160 case 9:
1161 if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_VBAT)) {
1162 blackboxPrintfHeaderLine("vbatscale:%u", masterConfig.batteryConfig.vbatscale);
1163 } else {
1164 xmitState.headerIndex += 2; // Skip the next two vbat fields too
1166 break;
1167 case 10:
1168 blackboxPrintfHeaderLine("vbatcellvoltage:%u,%u,%u", masterConfig.batteryConfig.vbatmincellvoltage,
1169 masterConfig.batteryConfig.vbatwarningcellvoltage, masterConfig.batteryConfig.vbatmaxcellvoltage);
1170 break;
1171 case 11:
1172 blackboxPrintfHeaderLine("vbatref:%u", vbatReference);
1173 break;
1174 case 12:
1175 //Note: Log even if this is a virtual current meter, since the virtual meter uses these parameters too:
1176 if (feature(FEATURE_CURRENT_METER)) {
1177 blackboxPrintfHeaderLine("currentMeter:%d,%d", masterConfig.batteryConfig.currentMeterOffset, masterConfig.batteryConfig.currentMeterScale);
1179 break;
1180 case 13:
1181 blackboxPrintfHeaderLine("rcExpo:%d", masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].rcExpo8);
1182 break;
1183 case 14:
1184 blackboxPrintfHeaderLine("rcYawExpo:%d", masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].rcYawExpo8);
1185 break;
1186 case 15:
1187 blackboxPrintfHeaderLine("thrMid:%d", masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].thrMid8);
1188 break;
1189 case 16:
1190 blackboxPrintfHeaderLine("thrExpo:%d", masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].thrExpo8);
1191 break;
1192 case 17:
1193 blackboxPrintfHeaderLine("dynThrPID:%d", masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].dynThrPID);
1194 break;
1195 case 18:
1196 blackboxPrintfHeaderLine("tpa_breakpoint:%d", masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].tpa_breakpoint);
1197 break;
1198 case 21:
1199 blackboxPrintfHeaderLine("rates:%d,%d,%d",
1200 masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].rates[ROLL],
1201 masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].rates[PITCH],
1202 masterConfig.profile[masterConfig.current_profile_index].controlRateProfile[masterConfig.profile[masterConfig.current_profile_index].activeRateProfile].rates[YAW]);
1203 break;
1204 case 22:
1205 blackboxPrintfHeaderLine("looptime:%d", targetLooptime);
1206 break;
1207 case 23:
1208 blackboxPrintfHeaderLine("pidController:%d", masterConfig.profile[masterConfig.current_profile_index].pidProfile.pidController);
1209 break;
1210 case 24:
1211 blackboxPrintfHeaderLine("rollPID:%d,%d,%d",
1212 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[ROLL],
1213 masterConfig.profile[masterConfig.current_profile_index].pidProfile.I8[ROLL],
1214 masterConfig.profile[masterConfig.current_profile_index].pidProfile.D8[ROLL]);
1215 break;
1216 case 25:
1217 blackboxPrintfHeaderLine("pitchPID:%d,%d,%d",
1218 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[PITCH],
1219 masterConfig.profile[masterConfig.current_profile_index].pidProfile.I8[PITCH],
1220 masterConfig.profile[masterConfig.current_profile_index].pidProfile.D8[PITCH]);
1221 break;
1222 case 26:
1223 blackboxPrintfHeaderLine("yawPID:%d,%d,%d",
1224 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[YAW],
1225 masterConfig.profile[masterConfig.current_profile_index].pidProfile.I8[YAW],
1226 masterConfig.profile[masterConfig.current_profile_index].pidProfile.D8[YAW]);
1227 break;
1228 case 27:
1229 blackboxPrintfHeaderLine("altPID:%d,%d,%d",
1230 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[PIDALT],
1231 masterConfig.profile[masterConfig.current_profile_index].pidProfile.I8[PIDALT],
1232 masterConfig.profile[masterConfig.current_profile_index].pidProfile.D8[PIDALT]);
1233 break;
1234 case 28:
1235 blackboxPrintfHeaderLine("posPID:%d,%d,%d",
1236 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[PIDPOS],
1237 masterConfig.profile[masterConfig.current_profile_index].pidProfile.I8[PIDPOS],
1238 masterConfig.profile[masterConfig.current_profile_index].pidProfile.D8[PIDPOS]);
1239 break;
1240 case 29:
1241 blackboxPrintfHeaderLine("posrPID:%d,%d,%d",
1242 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[PIDPOSR],
1243 masterConfig.profile[masterConfig.current_profile_index].pidProfile.I8[PIDPOSR],
1244 masterConfig.profile[masterConfig.current_profile_index].pidProfile.D8[PIDPOSR]);
1245 break;
1246 case 30:
1247 blackboxPrintfHeaderLine("navrPID:%d,%d,%d",
1248 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[PIDNAVR],
1249 masterConfig.profile[masterConfig.current_profile_index].pidProfile.I8[PIDNAVR],
1250 masterConfig.profile[masterConfig.current_profile_index].pidProfile.D8[PIDNAVR]);
1251 break;
1252 case 31:
1253 blackboxPrintfHeaderLine("levelPID:%d,%d,%d",
1254 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[PIDLEVEL],
1255 masterConfig.profile[masterConfig.current_profile_index].pidProfile.I8[PIDLEVEL],
1256 masterConfig.profile[masterConfig.current_profile_index].pidProfile.D8[PIDLEVEL]);
1257 break;
1258 case 32:
1259 blackboxPrintfHeaderLine("magPID:%d",
1260 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[PIDMAG]);
1261 break;
1262 case 33:
1263 blackboxPrintfHeaderLine("velPID:%d,%d,%d",
1264 masterConfig.profile[masterConfig.current_profile_index].pidProfile.P8[PIDVEL],
1265 masterConfig.profile[masterConfig.current_profile_index].pidProfile.I8[PIDVEL],
1266 masterConfig.profile[masterConfig.current_profile_index].pidProfile.D8[PIDVEL]);
1267 break;
1268 case 34:
1269 blackboxPrintfHeaderLine("yaw_p_limit:%d",
1270 masterConfig.profile[masterConfig.current_profile_index].pidProfile.yaw_p_limit);
1271 break;
1272 case 35:
1273 blackboxPrintfHeaderLine("yaw_lpf_hz:%d",
1274 (int)(masterConfig.profile[masterConfig.current_profile_index].pidProfile.yaw_lpf_hz * 100.0f));
1275 break;
1276 case 36:
1277 blackboxPrintfHeaderLine("dterm_average_count:%d",
1278 masterConfig.profile[masterConfig.current_profile_index].pidProfile.dterm_average_count);
1279 break;
1280 case 37:
1281 blackboxPrintfHeaderLine("dynamic_pid:%d",
1282 masterConfig.profile[masterConfig.current_profile_index].pidProfile.dynamic_pid);
1283 break;
1284 case 38:
1285 blackboxPrintfHeaderLine("rollPitchItermResetRate:%d",
1286 masterConfig.profile[masterConfig.current_profile_index].pidProfile.rollPitchItermIgnoreRate);
1287 break;
1288 case 39:
1289 blackboxPrintfHeaderLine("yawItermResetRate:%d",
1290 masterConfig.profile[masterConfig.current_profile_index].pidProfile.yawItermIgnoreRate);
1291 break;
1292 case 40:
1293 blackboxPrintfHeaderLine("dterm_lpf_hz:%d",
1294 (int)(masterConfig.profile[masterConfig.current_profile_index].pidProfile.dterm_lpf_hz * 100.0f));
1295 break;
1296 case 41:
1297 blackboxPrintfHeaderLine("airmode_activate_throttle:%d",
1298 masterConfig.rxConfig.airModeActivateThreshold);
1299 break;
1300 case 42:
1301 blackboxPrintfHeaderLine("deadband:%d", masterConfig.rcControlsConfig.deadband);
1302 break;
1303 case 43:
1304 blackboxPrintfHeaderLine("yaw_deadband:%d", masterConfig.rcControlsConfig.yaw_deadband);
1305 break;
1306 case 44:
1307 blackboxPrintfHeaderLine("gyro_lpf:%d", masterConfig.gyro_lpf);
1308 break;
1309 case 45:
1310 blackboxPrintfHeaderLine("gyro_lowpass_hz:%d", (int)(masterConfig.gyro_soft_lpf_hz * 100.0f));
1311 break;
1312 case 46:
1313 blackboxPrintfHeaderLine("acc_lpf_hz:%d", (int)(masterConfig.acc_lpf_hz * 100.0f));
1314 break;
1315 case 47:
1316 blackboxPrintfHeaderLine("acc_hardware:%d", masterConfig.acc_hardware);
1317 break;
1318 case 48:
1319 blackboxPrintfHeaderLine("baro_hardware:%d", masterConfig.baro_hardware);
1320 break;
1321 case 49:
1322 blackboxPrintfHeaderLine("mag_hardware:%d", masterConfig.mag_hardware);
1323 break;
1324 case 50:
1325 blackboxPrintfHeaderLine("gyro_cal_on_first_arm:%d", masterConfig.gyro_cal_on_first_arm);
1326 break;
1327 case 51:
1328 blackboxPrintfHeaderLine("vbat_pid_compensation:%d", masterConfig.batteryConfig.vbatPidCompensation);
1329 break;
1330 case 52:
1331 blackboxPrintfHeaderLine("rc_smoothing:%d", masterConfig.rxConfig.rcSmoothing);
1332 break;
1333 case 53:
1334 blackboxPrintfHeaderLine("features:%d", masterConfig.enabledFeatures);
1335 break;
1336 default:
1337 return true;
1340 xmitState.headerIndex++;
1341 return false;
1345 * Write the given event to the log immediately
1347 void blackboxLogEvent(FlightLogEvent event, flightLogEventData_t *data)
1349 // Only allow events to be logged after headers have been written
1350 if (!(blackboxState == BLACKBOX_STATE_RUNNING || blackboxState == BLACKBOX_STATE_PAUSED)) {
1351 return;
1354 //Shared header for event frames
1355 blackboxWrite('E');
1356 blackboxWrite(event);
1358 //Now serialize the data for this specific frame type
1359 switch (event) {
1360 case FLIGHT_LOG_EVENT_SYNC_BEEP:
1361 blackboxWriteUnsignedVB(data->syncBeep.time);
1362 break;
1363 case FLIGHT_LOG_EVENT_FLIGHTMODE: // New flightmode flags write
1364 blackboxWriteUnsignedVB(data->flightMode.flags);
1365 blackboxWriteUnsignedVB(data->flightMode.lastFlags);
1366 break;
1367 case FLIGHT_LOG_EVENT_INFLIGHT_ADJUSTMENT:
1368 if (data->inflightAdjustment.floatFlag) {
1369 blackboxWrite(data->inflightAdjustment.adjustmentFunction + FLIGHT_LOG_EVENT_INFLIGHT_ADJUSTMENT_FUNCTION_FLOAT_VALUE_FLAG);
1370 blackboxWriteFloat(data->inflightAdjustment.newFloatValue);
1371 } else {
1372 blackboxWrite(data->inflightAdjustment.adjustmentFunction);
1373 blackboxWriteSignedVB(data->inflightAdjustment.newValue);
1375 break;
1376 case FLIGHT_LOG_EVENT_GTUNE_RESULT:
1377 blackboxWrite(data->gtuneCycleResult.gtuneAxis);
1378 blackboxWriteSignedVB(data->gtuneCycleResult.gtuneGyroAVG);
1379 blackboxWriteS16(data->gtuneCycleResult.gtuneNewP);
1380 break;
1381 case FLIGHT_LOG_EVENT_LOGGING_RESUME:
1382 blackboxWriteUnsignedVB(data->loggingResume.logIteration);
1383 blackboxWriteUnsignedVB(data->loggingResume.currentTime);
1384 break;
1385 case FLIGHT_LOG_EVENT_LOG_END:
1386 blackboxPrint("End of log");
1387 blackboxWrite(0);
1388 break;
1392 /* 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 */
1393 static void blackboxCheckAndLogArmingBeep()
1395 flightLogEvent_syncBeep_t eventData;
1397 // Use != so that we can still detect a change if the counter wraps
1398 if (getArmingBeepTimeMicros() != blackboxLastArmingBeep) {
1399 blackboxLastArmingBeep = getArmingBeepTimeMicros();
1401 eventData.time = blackboxLastArmingBeep;
1403 blackboxLogEvent(FLIGHT_LOG_EVENT_SYNC_BEEP, (flightLogEventData_t *) &eventData);
1407 /* monitor the flight mode event status and trigger an event record if the state changes */
1408 static void blackboxCheckAndLogFlightMode()
1410 flightLogEvent_flightMode_t eventData; // Add new data for current flight mode flags
1412 // Use != so that we can still detect a change if the counter wraps
1413 if (rcModeActivationMask != blackboxLastFlightModeFlags) {
1414 eventData.lastFlags = blackboxLastFlightModeFlags;
1415 blackboxLastFlightModeFlags = rcModeActivationMask;
1416 eventData.flags = rcModeActivationMask;
1418 blackboxLogEvent(FLIGHT_LOG_EVENT_FLIGHTMODE, (flightLogEventData_t *) &eventData);
1423 * 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
1424 * the portion of logged loop iterations.
1426 static bool blackboxShouldLogPFrame(uint32_t pFrameIndex)
1428 /* Adding a magic shift of "masterConfig.blackbox_rate_num - 1" in here creates a better spread of
1429 * recorded / skipped frames when the I frame's position is considered:
1431 return (pFrameIndex + masterConfig.blackbox_rate_num - 1) % masterConfig.blackbox_rate_denom < masterConfig.blackbox_rate_num;
1434 static bool blackboxShouldLogIFrame() {
1435 return blackboxPFrameIndex == 0;
1438 // Called once every FC loop in order to keep track of how many FC loop iterations have passed
1439 static void blackboxAdvanceIterationTimers()
1441 blackboxSlowFrameIterationTimer++;
1442 blackboxIteration++;
1443 blackboxPFrameIndex++;
1445 if (blackboxPFrameIndex == BLACKBOX_I_INTERVAL) {
1446 blackboxPFrameIndex = 0;
1447 blackboxIFrameIndex++;
1451 // Called once every FC loop in order to log the current state
1452 static void blackboxLogIteration()
1454 // Write a keyframe every BLACKBOX_I_INTERVAL frames so we can resynchronise upon missing frames
1455 if (blackboxShouldLogIFrame()) {
1457 * Don't log a slow frame if the slow data didn't change ("I" frames are already large enough without adding
1458 * an additional item to write at the same time). Unless we're *only* logging "I" frames, then we have no choice.
1460 writeSlowFrameIfNeeded(blackboxIsOnlyLoggingIntraframes());
1462 loadMainState();
1463 writeIntraframe();
1464 } else {
1465 blackboxCheckAndLogArmingBeep();
1466 blackboxCheckAndLogFlightMode(); // Check for FlightMode status change event
1468 if (blackboxShouldLogPFrame(blackboxPFrameIndex)) {
1470 * We assume that slow frames are only interesting in that they aid the interpretation of the main data stream.
1471 * So only log slow frames during loop iterations where we log a main frame.
1473 writeSlowFrameIfNeeded(true);
1475 loadMainState();
1476 writeInterframe();
1478 #ifdef GPS
1479 if (feature(FEATURE_GPS)) {
1481 * If the GPS home point has been updated, or every 128 intraframes (~10 seconds), write the
1482 * GPS home position.
1484 * We write it periodically so that if one Home Frame goes missing, the GPS coordinates can
1485 * still be interpreted correctly.
1487 if (GPS_home[0] != gpsHistory.GPS_home[0] || GPS_home[1] != gpsHistory.GPS_home[1]
1488 || (blackboxPFrameIndex == BLACKBOX_I_INTERVAL / 2 && blackboxIFrameIndex % 128 == 0)) {
1490 writeGPSHomeFrame();
1491 writeGPSFrame();
1492 } else if (GPS_numSat != gpsHistory.GPS_numSat || GPS_coord[0] != gpsHistory.GPS_coord[0]
1493 || GPS_coord[1] != gpsHistory.GPS_coord[1]) {
1494 //We could check for velocity changes as well but I doubt it changes independent of position
1495 writeGPSFrame();
1498 #endif
1501 //Flush every iteration so that our runtime variance is minimized
1502 blackboxDeviceFlush();
1506 * Call each flight loop iteration to perform blackbox logging.
1508 void handleBlackbox(void)
1510 int i;
1512 if (blackboxState >= BLACKBOX_FIRST_HEADER_SENDING_STATE && blackboxState <= BLACKBOX_LAST_HEADER_SENDING_STATE) {
1513 blackboxReplenishHeaderBudget();
1516 switch (blackboxState) {
1517 case BLACKBOX_STATE_PREPARE_LOG_FILE:
1518 if (blackboxDeviceBeginLog()) {
1519 blackboxSetState(BLACKBOX_STATE_SEND_HEADER);
1521 break;
1522 case BLACKBOX_STATE_SEND_HEADER:
1523 //On entry of this state, xmitState.headerIndex is 0 and startTime is intialised
1526 * Once the UART has had time to init, transmit the header in chunks so we don't overflow its transmit
1527 * buffer, overflow the OpenLog's buffer, or keep the main loop busy for too long.
1529 if (millis() > xmitState.u.startTime + 100) {
1530 if (blackboxDeviceReserveBufferSpace(BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION) == BLACKBOX_RESERVE_SUCCESS) {
1531 for (i = 0; i < BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION && blackboxHeader[xmitState.headerIndex] != '\0'; i++, xmitState.headerIndex++) {
1532 blackboxWrite(blackboxHeader[xmitState.headerIndex]);
1533 blackboxHeaderBudget--;
1536 if (blackboxHeader[xmitState.headerIndex] == '\0') {
1537 blackboxSetState(BLACKBOX_STATE_SEND_MAIN_FIELD_HEADER);
1541 break;
1542 case BLACKBOX_STATE_SEND_MAIN_FIELD_HEADER:
1543 //On entry of this state, xmitState.headerIndex is 0 and xmitState.u.fieldIndex is -1
1544 if (!sendFieldDefinition('I', 'P', blackboxMainFields, blackboxMainFields + 1, ARRAY_LENGTH(blackboxMainFields),
1545 &blackboxMainFields[0].condition, &blackboxMainFields[1].condition)) {
1546 #ifdef GPS
1547 if (feature(FEATURE_GPS)) {
1548 blackboxSetState(BLACKBOX_STATE_SEND_GPS_H_HEADER);
1549 } else
1550 #endif
1551 blackboxSetState(BLACKBOX_STATE_SEND_SLOW_HEADER);
1553 break;
1554 #ifdef GPS
1555 case BLACKBOX_STATE_SEND_GPS_H_HEADER:
1556 //On entry of this state, xmitState.headerIndex is 0 and xmitState.u.fieldIndex is -1
1557 if (!sendFieldDefinition('H', 0, blackboxGpsHFields, blackboxGpsHFields + 1, ARRAY_LENGTH(blackboxGpsHFields),
1558 NULL, NULL)) {
1559 blackboxSetState(BLACKBOX_STATE_SEND_GPS_G_HEADER);
1561 break;
1562 case BLACKBOX_STATE_SEND_GPS_G_HEADER:
1563 //On entry of this state, xmitState.headerIndex is 0 and xmitState.u.fieldIndex is -1
1564 if (!sendFieldDefinition('G', 0, blackboxGpsGFields, blackboxGpsGFields + 1, ARRAY_LENGTH(blackboxGpsGFields),
1565 &blackboxGpsGFields[0].condition, &blackboxGpsGFields[1].condition)) {
1566 blackboxSetState(BLACKBOX_STATE_SEND_SLOW_HEADER);
1568 break;
1569 #endif
1570 case BLACKBOX_STATE_SEND_SLOW_HEADER:
1571 //On entry of this state, xmitState.headerIndex is 0 and xmitState.u.fieldIndex is -1
1572 if (!sendFieldDefinition('S', 0, blackboxSlowFields, blackboxSlowFields + 1, ARRAY_LENGTH(blackboxSlowFields),
1573 NULL, NULL)) {
1574 blackboxSetState(BLACKBOX_STATE_SEND_SYSINFO);
1576 break;
1577 case BLACKBOX_STATE_SEND_SYSINFO:
1578 //On entry of this state, xmitState.headerIndex is 0
1580 //Keep writing chunks of the system info headers until it returns true to signal completion
1581 if (blackboxWriteSysinfo()) {
1584 * Wait for header buffers to drain completely before data logging begins to ensure reliable header delivery
1585 * (overflowing circular buffers causes all data to be discarded, so the first few logged iterations
1586 * could wipe out the end of the header if we weren't careful)
1588 if (blackboxDeviceFlushForce()) {
1589 blackboxSetState(BLACKBOX_STATE_RUNNING);
1592 break;
1593 case BLACKBOX_STATE_PAUSED:
1594 // Only allow resume to occur during an I-frame iteration, so that we have an "I" base to work from
1595 if (IS_RC_MODE_ACTIVE(BOXBLACKBOX) && blackboxShouldLogIFrame()) {
1596 // Write a log entry so the decoder is aware that our large time/iteration skip is intended
1597 flightLogEvent_loggingResume_t resume;
1599 resume.logIteration = blackboxIteration;
1600 resume.currentTime = currentTime;
1602 blackboxLogEvent(FLIGHT_LOG_EVENT_LOGGING_RESUME, (flightLogEventData_t *) &resume);
1603 blackboxSetState(BLACKBOX_STATE_RUNNING);
1605 blackboxLogIteration();
1608 // Keep the logging timers ticking so our log iteration continues to advance
1609 blackboxAdvanceIterationTimers();
1610 break;
1611 case BLACKBOX_STATE_RUNNING:
1612 // On entry to this state, blackboxIteration, blackboxPFrameIndex and blackboxIFrameIndex are reset to 0
1613 if (blackboxModeActivationConditionPresent && !IS_RC_MODE_ACTIVE(BOXBLACKBOX)) {
1614 blackboxSetState(BLACKBOX_STATE_PAUSED);
1615 } else {
1616 blackboxLogIteration();
1619 blackboxAdvanceIterationTimers();
1620 break;
1621 case BLACKBOX_STATE_SHUTTING_DOWN:
1622 //On entry of this state, startTime is set
1625 * Wait for the log we've transmitted to make its way to the logger before we release the serial port,
1626 * since releasing the port clears the Tx buffer.
1628 * Don't wait longer than it could possibly take if something funky happens.
1630 if (blackboxDeviceEndLog(blackboxLoggedAnyFrames) && (millis() > xmitState.u.startTime + BLACKBOX_SHUTDOWN_TIMEOUT_MILLIS || blackboxDeviceFlushForce())) {
1631 blackboxDeviceClose();
1632 blackboxSetState(BLACKBOX_STATE_STOPPED);
1634 break;
1635 default:
1636 break;
1639 // Did we run out of room on the device? Stop!
1640 if (isBlackboxDeviceFull()) {
1641 blackboxSetState(BLACKBOX_STATE_STOPPED);
1645 static bool canUseBlackboxWithCurrentConfiguration(void)
1647 return feature(FEATURE_BLACKBOX);
1651 * Call during system startup to initialize the blackbox.
1653 void initBlackbox(void)
1655 if (canUseBlackboxWithCurrentConfiguration()) {
1656 blackboxSetState(BLACKBOX_STATE_STOPPED);
1657 } else {
1658 blackboxSetState(BLACKBOX_STATE_DISABLED);
1661 #endif