[FLYWOOF411] add board documentation
[inav/snaewe.git] / src / main / blackbox / blackbox_io.c
blob1cd0d19f47c26bf42fbfc5f214dd88e2f5c8bafb
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 <stdlib.h>
19 #include <stdarg.h>
20 #include <string.h>
22 #include "platform.h"
24 #ifdef USE_BLACKBOX
26 #include "blackbox.h"
27 #include "blackbox_io.h"
29 #include "common/axis.h"
30 #include "common/encoding.h"
31 #include "common/maths.h"
32 #include "common/printf.h"
33 #include "common/typeconversion.h"
35 #include "config/parameter_group.h"
36 #include "config/parameter_group_ids.h"
38 #include "io/asyncfatfs/asyncfatfs.h"
39 #include "io/flashfs.h"
40 #include "io/serial.h"
42 #include "msp/msp_serial.h"
44 #include "sensors/gyro.h"
46 #define BLACKBOX_SERIAL_PORT_MODE MODE_TX
48 // How many bytes can we transmit per loop iteration when writing headers?
49 static uint8_t blackboxMaxHeaderBytesPerIteration;
51 // How many bytes can we write *this* iteration without overflowing transmit buffers or overstressing the OpenLog?
52 int32_t blackboxHeaderBudget;
54 STATIC_UNIT_TESTED serialPort_t *blackboxPort = NULL;
55 #ifndef UNIT_TEST
56 static portSharing_e blackboxPortSharing;
57 #endif // UNIT_TEST
59 #ifdef USE_SDCARD
61 static struct {
62 afatfsFilePtr_t logFile;
63 afatfsFilePtr_t logDirectory;
64 afatfsFinder_t logDirectoryFinder;
65 uint32_t largestLogFileNumber;
67 enum {
68 BLACKBOX_SDCARD_INITIAL,
69 BLACKBOX_SDCARD_WAITING,
70 BLACKBOX_SDCARD_ENUMERATE_FILES,
71 BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY,
72 BLACKBOX_SDCARD_READY_TO_CREATE_LOG,
73 BLACKBOX_SDCARD_READY_TO_LOG
74 } state;
75 } blackboxSDCard;
77 #endif
79 #ifndef UNIT_TEST
80 void blackboxOpen(void)
82 serialPort_t *sharedBlackboxAndMspPort = findSharedSerialPort(FUNCTION_BLACKBOX, FUNCTION_MSP);
83 if (sharedBlackboxAndMspPort) {
84 mspSerialReleasePortIfAllocated(sharedBlackboxAndMspPort);
87 #endif // UNIT_TEST
89 void blackboxWrite(uint8_t value)
91 switch (blackboxConfig()->device) {
92 #ifdef USE_FLASHFS
93 case BLACKBOX_DEVICE_FLASH:
94 flashfsWriteByte(value); // Write byte asynchronously
95 break;
96 #endif
97 #ifdef USE_SDCARD
98 case BLACKBOX_DEVICE_SDCARD:
99 afatfs_fputc(blackboxSDCard.logFile, value);
100 break;
101 #endif
102 case BLACKBOX_DEVICE_SERIAL:
103 default:
104 serialWrite(blackboxPort, value);
105 break;
109 // Print the null-terminated string 's' to the blackbox device and return the number of bytes written
110 int blackboxPrint(const char *s)
112 int length;
113 const uint8_t *pos;
115 switch (blackboxConfig()->device) {
117 #ifdef USE_FLASHFS
118 case BLACKBOX_DEVICE_FLASH:
119 length = strlen(s);
120 flashfsWrite((const uint8_t*) s, length, false); // Write asynchronously
121 break;
122 #endif
124 #ifdef USE_SDCARD
125 case BLACKBOX_DEVICE_SDCARD:
126 length = strlen(s);
127 afatfs_fwrite(blackboxSDCard.logFile, (const uint8_t*) s, length); // Ignore failures due to buffers filling up
128 break;
129 #endif
131 case BLACKBOX_DEVICE_SERIAL:
132 default:
133 pos = (uint8_t*) s;
134 while (*pos) {
135 serialWrite(blackboxPort, *pos);
136 pos++;
139 length = pos - (uint8_t*) s;
140 break;
143 return length;
147 * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
149 * Intended to be called regularly for the blackbox device to perform housekeeping.
151 void blackboxDeviceFlush(void)
153 switch (blackboxConfig()->device) {
154 #ifdef USE_FLASHFS
156 * This is our only output device which requires us to call flush() in order for it to write anything. The other
157 * devices will progressively write in the background without Blackbox calling anything.
159 case BLACKBOX_DEVICE_FLASH:
160 flashfsFlushAsync();
161 break;
162 #endif
164 default:
170 * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
172 * Returns true if all data has been written to the device.
174 bool blackboxDeviceFlushForce(void)
176 switch (blackboxConfig()->device) {
177 case BLACKBOX_DEVICE_SERIAL:
178 // Nothing to speed up flushing on serial, as serial is continuously being drained out of its buffer
179 return isSerialTransmitBufferEmpty(blackboxPort);
181 #ifdef USE_FLASHFS
182 case BLACKBOX_DEVICE_FLASH:
183 return flashfsFlushAsync();
184 #endif
186 #ifdef USE_SDCARD
187 case BLACKBOX_DEVICE_SDCARD:
188 /* SD card will flush itself without us calling it, but we need to call flush manually in order to check
189 * if it's done yet or not!
191 return afatfs_flush();
192 #endif
194 default:
195 return false;
200 * Attempt to open the logging device. Returns true if successful.
202 #ifndef UNIT_TEST
203 bool blackboxDeviceOpen(void)
205 switch (blackboxConfig()->device) {
206 case BLACKBOX_DEVICE_SERIAL:
208 serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_BLACKBOX);
209 baudRate_e baudRateIndex;
210 portOptions_t portOptions = SERIAL_PARITY_NO | SERIAL_NOT_INVERTED;
212 if (!portConfig) {
213 return false;
216 blackboxPortSharing = determinePortSharing(portConfig, FUNCTION_BLACKBOX);
217 baudRateIndex = portConfig->peripheral_baudrateIndex;
219 if (baudRates[baudRateIndex] == 230400) {
221 * OpenLog's 230400 baud rate is very inaccurate, so it requires a larger inter-character gap in
222 * order to maintain synchronization.
224 portOptions |= SERIAL_STOPBITS_2;
225 } else {
226 portOptions |= SERIAL_STOPBITS_1;
229 blackboxPort = openSerialPort(portConfig->identifier, FUNCTION_BLACKBOX, NULL, NULL, baudRates[baudRateIndex],
230 BLACKBOX_SERIAL_PORT_MODE, portOptions);
233 * The slowest MicroSD cards have a write latency approaching 150ms. The OpenLog's buffer is about 900
234 * bytes. In order for its buffer to be able to absorb this latency we must write slower than 6000 B/s.
236 * So:
237 * Bytes per loop iteration = floor((looptime_ns / 1000000.0) * 6000)
238 * = floor((looptime_ns * 6000) / 1000000.0)
239 * = floor((looptime_ns * 3) / 500.0)
240 * = (looptime_ns * 3) / 500
242 blackboxMaxHeaderBytesPerIteration = constrain((gyro.targetLooptime * 3) / 500, 1, BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION);
244 return blackboxPort != NULL;
246 break;
247 #ifdef USE_FLASHFS
248 case BLACKBOX_DEVICE_FLASH:
249 if (flashfsGetSize() == 0 || isBlackboxDeviceFull()) {
250 return false;
253 blackboxMaxHeaderBytesPerIteration = BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION;
255 return true;
256 break;
257 #endif
258 #ifdef USE_SDCARD
259 case BLACKBOX_DEVICE_SDCARD:
260 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_FATAL || afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_UNKNOWN || afatfs_isFull()) {
261 return false;
264 blackboxMaxHeaderBytesPerIteration = BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION;
266 return true;
267 break;
268 #endif
269 default:
270 return false;
273 #endif // UNIT_TEST
276 * Close the Blackbox logging device immediately without attempting to flush any remaining data.
278 #ifndef UNIT_TEST
279 void blackboxDeviceClose(void)
281 switch (blackboxConfig()->device) {
282 case BLACKBOX_DEVICE_SERIAL:
283 // Since the serial port could be shared with other processes, we have to give it back here
284 closeSerialPort(blackboxPort);
285 blackboxPort = NULL;
288 * Normally this would be handled by mw.c, but since we take an unknown amount
289 * of time to shut down asynchronously, we're the only ones that know when to call it.
291 if (blackboxPortSharing == PORTSHARING_SHARED) {
292 mspSerialAllocatePorts();
294 break;
295 default:
299 #endif // UNIT_TEST
301 #ifdef USE_SDCARD
303 static void blackboxLogDirCreated(afatfsFilePtr_t directory)
305 if (directory) {
306 blackboxSDCard.logDirectory = directory;
308 afatfs_findFirst(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder);
310 blackboxSDCard.state = BLACKBOX_SDCARD_ENUMERATE_FILES;
311 } else {
312 // Retry
313 blackboxSDCard.state = BLACKBOX_SDCARD_INITIAL;
317 static void blackboxLogFileCreated(afatfsFilePtr_t file)
319 if (file) {
320 blackboxSDCard.logFile = file;
322 blackboxSDCard.largestLogFileNumber++;
324 blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_LOG;
325 } else {
326 // Retry
327 blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
331 static void blackboxCreateLogFile(void)
333 uint32_t remainder = blackboxSDCard.largestLogFileNumber + 1;
335 char filename[13];
337 filename[0] = 'L';
338 filename[1] = 'O';
339 filename[2] = 'G';
341 for (int i = 7; i >= 3; i--) {
342 filename[i] = (remainder % 10) + '0';
343 remainder /= 10;
346 filename[8] = '.';
347 filename[9] = 'T';
348 filename[10] = 'X';
349 filename[11] = 'T';
350 filename[12] = 0;
352 blackboxSDCard.state = BLACKBOX_SDCARD_WAITING;
354 afatfs_fopen(filename, "as", blackboxLogFileCreated);
358 * Begin a new log on the SDCard.
360 * Keep calling until the function returns true (open is complete).
362 static bool blackboxSDCardBeginLog(void)
364 fatDirectoryEntry_t *directoryEntry;
366 doMore:
367 switch (blackboxSDCard.state) {
368 case BLACKBOX_SDCARD_INITIAL:
369 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY) {
370 blackboxSDCard.state = BLACKBOX_SDCARD_WAITING;
372 afatfs_mkdir("logs", blackboxLogDirCreated);
374 break;
376 case BLACKBOX_SDCARD_WAITING:
377 // Waiting for directory entry to be created
378 break;
380 case BLACKBOX_SDCARD_ENUMERATE_FILES:
381 while (afatfs_findNext(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder, &directoryEntry) == AFATFS_OPERATION_SUCCESS) {
382 if (directoryEntry && !fat_isDirectoryEntryTerminator(directoryEntry)) {
383 // If this is a log file, parse the log number from the filename
384 if (
385 directoryEntry->filename[0] == 'L' && directoryEntry->filename[1] == 'O' && directoryEntry->filename[2] == 'G'
386 && directoryEntry->filename[8] == 'T' && directoryEntry->filename[9] == 'X' && directoryEntry->filename[10] == 'T'
388 char logSequenceNumberString[6];
390 memcpy(logSequenceNumberString, directoryEntry->filename + 3, 5);
391 logSequenceNumberString[5] = '\0';
393 blackboxSDCard.largestLogFileNumber = MAX((uint32_t) fastA2I(logSequenceNumberString), blackboxSDCard.largestLogFileNumber);
395 } else {
396 // We're done checking all the files on the card, now we can create a new log file
397 afatfs_findLast(blackboxSDCard.logDirectory);
399 blackboxSDCard.state = BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY;
400 goto doMore;
403 break;
405 case BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY:
406 // Change into the log directory:
407 if (afatfs_chdir(blackboxSDCard.logDirectory)) {
408 // We no longer need our open handle on the log directory
409 afatfs_fclose(blackboxSDCard.logDirectory, NULL);
410 blackboxSDCard.logDirectory = NULL;
412 blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
413 goto doMore;
415 break;
417 case BLACKBOX_SDCARD_READY_TO_CREATE_LOG:
418 blackboxCreateLogFile();
419 break;
421 case BLACKBOX_SDCARD_READY_TO_LOG:
422 return true; // Log has been created!
425 // Not finished init yet
426 return false;
429 #endif
432 * Begin a new log (for devices which support separations between the logs of multiple flights).
434 * Keep calling until the function returns true (open is complete).
436 bool blackboxDeviceBeginLog(void)
438 switch (blackboxConfig()->device) {
439 #ifdef USE_SDCARD
440 case BLACKBOX_DEVICE_SDCARD:
441 return blackboxSDCardBeginLog();
442 #endif
443 default:
444 return true;
450 * Terminate the current log (for devices which support separations between the logs of multiple flights).
452 * retainLog - Pass true if the log should be kept, or false if the log should be discarded (if supported).
454 * Keep calling until this returns true
456 bool blackboxDeviceEndLog(bool retainLog)
458 #ifndef USE_SDCARD
459 (void) retainLog;
460 #endif
462 switch (blackboxConfig()->device) {
463 #ifdef USE_SDCARD
464 case BLACKBOX_DEVICE_SDCARD:
465 // Keep retrying until the close operation queues
466 if (
467 (retainLog && afatfs_fclose(blackboxSDCard.logFile, NULL))
468 || (!retainLog && afatfs_funlink(blackboxSDCard.logFile, NULL))
470 // Don't bother waiting the for the close to complete, it's queued now and will complete eventually
471 blackboxSDCard.logFile = NULL;
472 blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
473 return true;
475 return false;
476 #endif
477 default:
478 return true;
482 bool isBlackboxDeviceFull(void)
484 switch (blackboxConfig()->device) {
485 case BLACKBOX_DEVICE_SERIAL:
486 return false;
488 #ifdef USE_FLASHFS
489 case BLACKBOX_DEVICE_FLASH:
490 return flashfsIsEOF();
491 #endif
493 #ifdef USE_SDCARD
494 case BLACKBOX_DEVICE_SDCARD:
495 return afatfs_isFull();
496 #endif
498 default:
499 return false;
504 * Call once every loop iteration in order to maintain the global blackboxHeaderBudget with the number of bytes we can
505 * transmit this iteration.
507 void blackboxReplenishHeaderBudget(void)
509 int32_t freeSpace;
511 switch (blackboxConfig()->device) {
512 case BLACKBOX_DEVICE_SERIAL:
513 freeSpace = serialTxBytesFree(blackboxPort);
514 break;
515 #ifdef USE_FLASHFS
516 case BLACKBOX_DEVICE_FLASH:
517 freeSpace = flashfsGetWriteBufferFreeSpace();
518 break;
519 #endif
520 #ifdef USE_SDCARD
521 case BLACKBOX_DEVICE_SDCARD:
522 freeSpace = afatfs_getFreeBufferSpace();
523 break;
524 #endif
525 default:
526 freeSpace = 0;
529 blackboxHeaderBudget = MIN(MIN(freeSpace, blackboxHeaderBudget + blackboxMaxHeaderBytesPerIteration), BLACKBOX_MAX_ACCUMULATED_HEADER_BUDGET);
533 * You must call this function before attempting to write Blackbox header bytes to ensure that the write will not
534 * cause buffers to overflow. The number of bytes you can write is capped by the blackboxHeaderBudget. Calling this
535 * reservation function doesn't decrease blackboxHeaderBudget, so you must manually decrement that variable by the
536 * number of bytes you actually wrote.
538 * When the Blackbox device is FlashFS, a successful return code guarantees that no data will be lost if you write that
539 * many bytes to the device (i.e. FlashFS's buffers won't overflow).
541 * When the device is a serial port, a successful return code guarantees that Cleanflight's serial Tx buffer will not
542 * overflow, and the outgoing bandwidth is likely to be small enough to give the OpenLog time to absorb MicroSD card
543 * latency. However the OpenLog could still end up silently dropping data.
545 * Returns:
546 * BLACKBOX_RESERVE_SUCCESS - Upon success
547 * BLACKBOX_RESERVE_TEMPORARY_FAILURE - The buffer is currently too full to service the request, try again later
548 * BLACKBOX_RESERVE_PERMANENT_FAILURE - The buffer is too small to ever service this request
550 blackboxBufferReserveStatus_e blackboxDeviceReserveBufferSpace(int32_t bytes)
552 if (bytes <= blackboxHeaderBudget) {
553 return BLACKBOX_RESERVE_SUCCESS;
556 // Handle failure:
557 switch (blackboxConfig()->device) {
558 case BLACKBOX_DEVICE_SERIAL:
560 * One byte of the tx buffer isn't available for user data (due to its circular list implementation),
561 * hence the -1. Note that the USB VCP implementation doesn't use a buffer and has txBufferSize set to zero.
563 if (blackboxPort->txBufferSize && bytes > (int32_t) blackboxPort->txBufferSize - 1) {
564 return BLACKBOX_RESERVE_PERMANENT_FAILURE;
567 return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
569 #ifdef USE_FLASHFS
570 case BLACKBOX_DEVICE_FLASH:
571 if (bytes > (int32_t) flashfsGetWriteBufferSize()) {
572 return BLACKBOX_RESERVE_PERMANENT_FAILURE;
575 if (bytes > (int32_t) flashfsGetWriteBufferFreeSpace()) {
577 * The write doesn't currently fit in the buffer, so try to make room for it. Our flushing here means
578 * that the Blackbox header writing code doesn't have to guess about the best time to ask flashfs to
579 * flush, and doesn't stall waiting for a flush that would otherwise not automatically be called.
581 flashfsFlushAsync();
584 return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
585 #endif
587 #ifdef USE_SDCARD
588 case BLACKBOX_DEVICE_SDCARD:
589 // Assume that all writes will fit in the SDCard's buffers
590 return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
591 #endif
593 default:
594 return BLACKBOX_RESERVE_PERMANENT_FAILURE;
598 #endif