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/>.
22 #if defined(SITL_BUILD)
32 #include "blackbox_io.h"
34 #include "common/axis.h"
35 #include "common/encoding.h"
36 #include "common/maths.h"
37 #include "common/printf.h"
38 #include "common/typeconversion.h"
40 #include "config/parameter_group.h"
41 #include "config/parameter_group_ids.h"
44 #include "drivers/sdcard/sdcard.h"
47 #include "io/asyncfatfs/asyncfatfs.h"
48 #include "io/flashfs.h"
49 #include "io/serial.h"
51 #include "msp/msp_serial.h"
53 #include "sensors/gyro.h"
54 #include "fc/config.h"
56 #define BLACKBOX_SERIAL_PORT_MODE MODE_TX
58 // How many bytes can we transmit per loop iteration when writing headers?
59 static uint8_t blackboxMaxHeaderBytesPerIteration
;
61 // How many bytes can we write *this* iteration without overflowing transmit buffers or overstressing the OpenLog?
62 int32_t blackboxHeaderBudget
;
64 STATIC_UNIT_TESTED serialPort_t
*blackboxPort
= NULL
;
66 static portSharing_e blackboxPortSharing
;
72 afatfsFilePtr_t logFile
;
73 afatfsFilePtr_t logDirectory
;
74 afatfsFinder_t logDirectoryFinder
;
75 uint32_t largestLogFileNumber
;
78 BLACKBOX_SDCARD_INITIAL
,
79 BLACKBOX_SDCARD_WAITING
,
80 BLACKBOX_SDCARD_ENUMERATE_FILES
,
81 BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY
,
82 BLACKBOX_SDCARD_READY_TO_CREATE_LOG
,
83 BLACKBOX_SDCARD_READY_TO_LOG
89 #if defined(SITL_BUILD)
96 void blackboxOpen(void)
98 serialPort_t
*sharedBlackboxAndMspPort
= findSharedSerialPort(FUNCTION_BLACKBOX
, FUNCTION_MSP
);
99 if (sharedBlackboxAndMspPort
) {
100 mspSerialReleasePortIfAllocated(sharedBlackboxAndMspPort
);
105 void blackboxWrite(uint8_t value
)
107 switch (blackboxConfig()->device
) {
109 case BLACKBOX_DEVICE_FLASH
:
110 flashfsWriteByte(value
); // Write byte asynchronously
114 case BLACKBOX_DEVICE_SDCARD
:
115 afatfs_fputc(blackboxSDCard
.logFile
, value
);
118 #if defined(SITL_BUILD)
119 case BLACKBOX_DEVICE_FILE
:
120 fputc(value
, blackboxFile
.file_handler
);
123 case BLACKBOX_DEVICE_SERIAL
:
125 serialWrite(blackboxPort
, value
);
130 // Print the null-terminated string 's' to the blackbox device and return the number of bytes written
131 int blackboxPrint(const char *s
)
136 switch (blackboxConfig()->device
) {
139 case BLACKBOX_DEVICE_FLASH
:
141 flashfsWrite((const uint8_t*) s
, length
, false); // Write asynchronously
146 case BLACKBOX_DEVICE_SDCARD
:
148 afatfs_fwrite(blackboxSDCard
.logFile
, (const uint8_t*) s
, length
); // Ignore failures due to buffers filling up
152 #if defined(SITL_BUILD)
153 case BLACKBOX_DEVICE_FILE
:
155 fputs(s
, blackboxFile
.file_handler
);
159 case BLACKBOX_DEVICE_SERIAL
:
163 serialWrite(blackboxPort
, *pos
);
167 length
= pos
- (uint8_t*) s
;
175 * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
177 * Intended to be called regularly for the blackbox device to perform housekeeping.
179 void blackboxDeviceFlush(void)
181 switch (blackboxConfig()->device
) {
184 * This is our only output device which requires us to call flush() in order for it to write anything. The other
185 * devices will progressively write in the background without Blackbox calling anything.
187 case BLACKBOX_DEVICE_FLASH
:
198 * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
200 * Returns true if all data has been written to the device.
202 bool blackboxDeviceFlushForce(void)
204 switch (blackboxConfig()->device
) {
205 case BLACKBOX_DEVICE_SERIAL
:
206 // Nothing to speed up flushing on serial, as serial is continuously being drained out of its buffer
207 return isSerialTransmitBufferEmpty(blackboxPort
);
210 case BLACKBOX_DEVICE_FLASH
:
211 return flashfsFlushAsync();
215 case BLACKBOX_DEVICE_SDCARD
:
216 /* SD card will flush itself without us calling it, but we need to call flush manually in order to check
217 * if it's done yet or not!
219 return afatfs_flush();
222 #if defined(SITL_BUILD)
223 case BLACKBOX_DEVICE_FILE
:
224 fflush(blackboxFile
.file_handler
);
234 * Attempt to open the logging device. Returns true if successful.
237 bool blackboxDeviceOpen(void)
239 switch (blackboxConfig()->device
) {
240 case BLACKBOX_DEVICE_SERIAL
:
242 serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_BLACKBOX
);
243 baudRate_e baudRateIndex
;
244 portOptions_t portOptions
= SERIAL_PARITY_NO
| SERIAL_NOT_INVERTED
;
250 blackboxPortSharing
= determinePortSharing(portConfig
, FUNCTION_BLACKBOX
);
251 baudRateIndex
= portConfig
->peripheral_baudrateIndex
;
253 if (baudRates
[baudRateIndex
] == 230400) {
255 * OpenLog's 230400 baud rate is very inaccurate, so it requires a larger inter-character gap in
256 * order to maintain synchronization.
258 portOptions
|= SERIAL_STOPBITS_2
;
260 portOptions
|= SERIAL_STOPBITS_1
;
263 blackboxPort
= openSerialPort(portConfig
->identifier
, FUNCTION_BLACKBOX
, NULL
, NULL
, baudRates
[baudRateIndex
],
264 BLACKBOX_SERIAL_PORT_MODE
, portOptions
);
267 * The slowest MicroSD cards have a write latency approaching 150ms. The OpenLog's buffer is about 900
268 * bytes. In order for its buffer to be able to absorb this latency we must write slower than 6000 B/s.
271 * Bytes per loop iteration = floor((looptime_ns / 1000000.0) * 6000)
272 * = floor((looptime_ns * 6000) / 1000000.0)
273 * = floor((looptime_ns * 3) / 500.0)
274 * = (looptime_ns * 3) / 500
276 blackboxMaxHeaderBytesPerIteration
= constrain((getLooptime() * 3) / 500, 1, BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
);
278 return blackboxPort
!= NULL
;
282 case BLACKBOX_DEVICE_FLASH
:
283 if (flashfsGetSize() == 0 || isBlackboxDeviceFull()) {
287 blackboxMaxHeaderBytesPerIteration
= BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
;
293 case BLACKBOX_DEVICE_SDCARD
:
294 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_FATAL
|| afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_UNKNOWN
|| afatfs_isFull()) {
298 blackboxMaxHeaderBytesPerIteration
= BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
;
303 #if defined(SITL_BUILD)
304 case BLACKBOX_DEVICE_FILE
:
306 const time_t now
= time(NULL
);
307 const struct tm
*t
= localtime(&now
);
309 strftime(filename
, sizeof(filename
), "%Y_%m_%d_%H%M%S.TXT", t
);
311 blackboxFile
.file_handler
= fopen(filename
, "wb");
312 if (blackboxFile
.file_handler
== NULL
) {
313 fprintf(stderr
, "[BlackBox] Failed to create log file\n");
316 fprintf(stderr
, "[BlackBox] Created %s\n", filename
);
319 blackboxMaxHeaderBytesPerIteration
= BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
;
330 * Close the Blackbox logging device immediately without attempting to flush any remaining data.
333 void blackboxDeviceClose(void)
335 switch (blackboxConfig()->device
) {
336 case BLACKBOX_DEVICE_SERIAL
:
337 // Since the serial port could be shared with other processes, we have to give it back here
338 closeSerialPort(blackboxPort
);
342 * Normally this would be handled by mw.c, but since we take an unknown amount
343 * of time to shut down asynchronously, we're the only ones that know when to call it.
345 if (blackboxPortSharing
== PORTSHARING_SHARED
) {
346 mspSerialAllocatePorts();
350 case BLACKBOX_DEVICE_FLASH
:
351 // Some flash device, e.g., NAND devices, require explicit close to flush internally buffered data.
355 #if defined(SITL_BUILD)
356 case BLACKBOX_DEVICE_FILE
:
357 fclose(blackboxFile
.file_handler
);
368 static void blackboxLogDirCreated(afatfsFilePtr_t directory
)
371 blackboxSDCard
.logDirectory
= directory
;
373 afatfs_findFirst(blackboxSDCard
.logDirectory
, &blackboxSDCard
.logDirectoryFinder
);
375 blackboxSDCard
.state
= BLACKBOX_SDCARD_ENUMERATE_FILES
;
378 blackboxSDCard
.state
= BLACKBOX_SDCARD_INITIAL
;
382 static void blackboxLogFileCreated(afatfsFilePtr_t file
)
385 blackboxSDCard
.logFile
= file
;
387 blackboxSDCard
.largestLogFileNumber
++;
389 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_LOG
;
392 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_CREATE_LOG
;
396 static void blackboxCreateLogFile(void)
398 uint32_t remainder
= blackboxSDCard
.largestLogFileNumber
+ 1;
406 for (int i
= 7; i
>= 3; i
--) {
407 filename
[i
] = (remainder
% 10) + '0';
417 blackboxSDCard
.state
= BLACKBOX_SDCARD_WAITING
;
419 afatfs_fopen(filename
, "as", blackboxLogFileCreated
);
423 * Begin a new log on the SDCard.
425 * Keep calling until the function returns true (open is complete).
427 static bool blackboxSDCardBeginLog(void)
429 fatDirectoryEntry_t
*directoryEntry
;
432 switch (blackboxSDCard
.state
) {
433 case BLACKBOX_SDCARD_INITIAL
:
434 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY
) {
435 blackboxSDCard
.state
= BLACKBOX_SDCARD_WAITING
;
437 afatfs_mkdir("logs", blackboxLogDirCreated
);
441 case BLACKBOX_SDCARD_WAITING
:
442 // Waiting for directory entry to be created
445 case BLACKBOX_SDCARD_ENUMERATE_FILES
:
446 while (afatfs_findNext(blackboxSDCard
.logDirectory
, &blackboxSDCard
.logDirectoryFinder
, &directoryEntry
) == AFATFS_OPERATION_SUCCESS
) {
447 if (directoryEntry
&& !fat_isDirectoryEntryTerminator(directoryEntry
)) {
448 // If this is a log file, parse the log number from the filename
450 directoryEntry
->filename
[0] == 'L' && directoryEntry
->filename
[1] == 'O' && directoryEntry
->filename
[2] == 'G'
451 && directoryEntry
->filename
[8] == 'T' && directoryEntry
->filename
[9] == 'X' && directoryEntry
->filename
[10] == 'T'
453 char logSequenceNumberString
[6];
455 memcpy(logSequenceNumberString
, directoryEntry
->filename
+ 3, 5);
456 logSequenceNumberString
[5] = '\0';
458 blackboxSDCard
.largestLogFileNumber
= MAX((uint32_t) fastA2I(logSequenceNumberString
), blackboxSDCard
.largestLogFileNumber
);
461 // We're done checking all the files on the card, now we can create a new log file
462 afatfs_findLast(blackboxSDCard
.logDirectory
);
464 blackboxSDCard
.state
= BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY
;
470 case BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY
:
471 // Change into the log directory:
472 if (afatfs_chdir(blackboxSDCard
.logDirectory
)) {
473 // We no longer need our open handle on the log directory
474 afatfs_fclose(blackboxSDCard
.logDirectory
, NULL
);
475 blackboxSDCard
.logDirectory
= NULL
;
477 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_CREATE_LOG
;
482 case BLACKBOX_SDCARD_READY_TO_CREATE_LOG
:
483 blackboxCreateLogFile();
486 case BLACKBOX_SDCARD_READY_TO_LOG
:
487 return true; // Log has been created!
490 // Not finished init yet
497 * Begin a new log (for devices which support separations between the logs of multiple flights).
499 * Keep calling until the function returns true (open is complete).
501 bool blackboxDeviceBeginLog(void)
503 switch (blackboxConfig()->device
) {
505 case BLACKBOX_DEVICE_SDCARD
:
506 return blackboxSDCardBeginLog();
515 * Terminate the current log (for devices which support separations between the logs of multiple flights).
517 * retainLog - Pass true if the log should be kept, or false if the log should be discarded (if supported).
519 * Keep calling until this returns true
521 bool blackboxDeviceEndLog(bool retainLog
)
527 switch (blackboxConfig()->device
) {
529 case BLACKBOX_DEVICE_SDCARD
:
530 // Keep retrying until the close operation queues
532 (retainLog
&& afatfs_fclose(blackboxSDCard
.logFile
, NULL
))
533 || (!retainLog
&& afatfs_funlink(blackboxSDCard
.logFile
, NULL
))
535 // Don't bother waiting the for the close to complete, it's queued now and will complete eventually
536 blackboxSDCard
.logFile
= NULL
;
537 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_CREATE_LOG
;
547 bool isBlackboxDeviceFull(void)
549 switch (blackboxConfig()->device
) {
550 case BLACKBOX_DEVICE_SERIAL
:
554 case BLACKBOX_DEVICE_FLASH
:
555 return flashfsIsEOF();
559 case BLACKBOX_DEVICE_SDCARD
:
560 return afatfs_isFull();
563 #if defined (SITL_BUILD)
564 case BLACKBOX_DEVICE_FILE
:
573 bool isBlackboxDeviceWorking(void)
575 switch (blackboxConfig()->device
) {
576 case BLACKBOX_DEVICE_SERIAL
:
577 return blackboxPort
!= NULL
;
579 case BLACKBOX_DEVICE_SDCARD
:
580 return sdcard_isInserted() && sdcard_isFunctional() && (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY
);
583 case BLACKBOX_DEVICE_FLASH
:
584 return flashfsIsReady();
591 int32_t blackboxGetLogNumber(void)
593 switch (blackboxConfig()->device
) {
595 case BLACKBOX_DEVICE_SDCARD
:
596 return blackboxSDCard
.largestLogFileNumber
;
604 * Call once every loop iteration in order to maintain the global blackboxHeaderBudget with the number of bytes we can
605 * transmit this iteration.
607 void blackboxReplenishHeaderBudget(void)
611 switch (blackboxConfig()->device
) {
612 case BLACKBOX_DEVICE_SERIAL
:
613 freeSpace
= serialTxBytesFree(blackboxPort
);
616 case BLACKBOX_DEVICE_FLASH
:
617 freeSpace
= flashfsGetWriteBufferFreeSpace();
621 case BLACKBOX_DEVICE_SDCARD
:
622 freeSpace
= afatfs_getFreeBufferSpace();
625 #if defined(SITL_BUILD)
626 case BLACKBOX_DEVICE_FILE
:
627 freeSpace
= BLACKBOX_MAX_ACCUMULATED_HEADER_BUDGET
;
634 blackboxHeaderBudget
= MIN(MIN(freeSpace
, blackboxHeaderBudget
+ blackboxMaxHeaderBytesPerIteration
), BLACKBOX_MAX_ACCUMULATED_HEADER_BUDGET
);
638 * You must call this function before attempting to write Blackbox header bytes to ensure that the write will not
639 * cause buffers to overflow. The number of bytes you can write is capped by the blackboxHeaderBudget. Calling this
640 * reservation function doesn't decrease blackboxHeaderBudget, so you must manually decrement that variable by the
641 * number of bytes you actually wrote.
643 * When the Blackbox device is FlashFS, a successful return code guarantees that no data will be lost if you write that
644 * many bytes to the device (i.e. FlashFS's buffers won't overflow).
646 * When the device is a serial port, a successful return code guarantees that Cleanflight's serial Tx buffer will not
647 * overflow, and the outgoing bandwidth is likely to be small enough to give the OpenLog time to absorb MicroSD card
648 * latency. However the OpenLog could still end up silently dropping data.
651 * BLACKBOX_RESERVE_SUCCESS - Upon success
652 * BLACKBOX_RESERVE_TEMPORARY_FAILURE - The buffer is currently too full to service the request, try again later
653 * BLACKBOX_RESERVE_PERMANENT_FAILURE - The buffer is too small to ever service this request
655 blackboxBufferReserveStatus_e
blackboxDeviceReserveBufferSpace(int32_t bytes
)
657 if (bytes
<= blackboxHeaderBudget
) {
658 return BLACKBOX_RESERVE_SUCCESS
;
662 switch (blackboxConfig()->device
) {
663 case BLACKBOX_DEVICE_SERIAL
:
665 * One byte of the tx buffer isn't available for user data (due to its circular list implementation),
666 * hence the -1. Note that the USB VCP implementation doesn't use a buffer and has txBufferSize set to zero.
668 if (blackboxPort
->txBufferSize
&& bytes
> (int32_t) blackboxPort
->txBufferSize
- 1) {
669 return BLACKBOX_RESERVE_PERMANENT_FAILURE
;
672 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
675 case BLACKBOX_DEVICE_FLASH
:
676 if (bytes
> (int32_t) flashfsGetWriteBufferSize()) {
677 return BLACKBOX_RESERVE_PERMANENT_FAILURE
;
680 if (bytes
> (int32_t) flashfsGetWriteBufferFreeSpace()) {
682 * The write doesn't currently fit in the buffer, so try to make room for it. Our flushing here means
683 * that the Blackbox header writing code doesn't have to guess about the best time to ask flashfs to
684 * flush, and doesn't stall waiting for a flush that would otherwise not automatically be called.
689 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
693 case BLACKBOX_DEVICE_SDCARD
:
694 // Assume that all writes will fit in the SDCard's buffers
695 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
698 #if defined(SITL_BUILD)
699 case BLACKBOX_DEVICE_FILE
:
700 // Assume that all writes will fit in the file's buffers
701 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
705 return BLACKBOX_RESERVE_PERMANENT_FAILURE
;