2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
31 #include "build/debug.h"
33 // Debugging code that become useful when output bandwidth saturation is suspected.
34 // Set debug_mode = BLACKBOX_OUTPUT to see following debug values.
36 // 0: Average output bandwidth in last 100ms
37 // 1: Maximum hold of above.
38 // 2: Bytes dropped due to output buffer full.
40 // Note that bandwidth usage slightly increases when DEBUG_BB_OUTPUT is enabled,
41 // as output will include debug variables themselves.
43 #define DEBUG_BB_OUTPUT
46 #include "blackbox_io.h"
48 #include "common/maths.h"
50 #include "flight/pid.h"
52 #include "io/asyncfatfs/asyncfatfs.h"
53 #include "io/flashfs.h"
54 #include "io/serial.h"
56 #include "msp/msp_serial.h"
59 #include "drivers/sdcard.h"
62 #define BLACKBOX_SERIAL_PORT_MODE MODE_TX
64 // How many bytes can we transmit per loop iteration when writing headers?
65 static uint8_t blackboxMaxHeaderBytesPerIteration
;
67 // How many bytes can we write *this* iteration without overflowing transmit buffers or overstressing the OpenLog?
68 int32_t blackboxHeaderBudget
;
70 static serialPort_t
*blackboxPort
= NULL
;
71 static portSharing_e blackboxPortSharing
;
76 afatfsFilePtr_t logFile
;
77 afatfsFilePtr_t logDirectory
;
78 afatfsFinder_t logDirectoryFinder
;
79 int32_t largestLogFileNumber
;
82 BLACKBOX_SDCARD_INITIAL
,
83 BLACKBOX_SDCARD_WAITING
,
84 BLACKBOX_SDCARD_ENUMERATE_FILES
,
85 BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY
,
86 BLACKBOX_SDCARD_READY_TO_CREATE_LOG
,
87 BLACKBOX_SDCARD_READY_TO_LOG
91 #define LOGFILE_PREFIX "LOG"
92 #define LOGFILE_SUFFIX "BFL"
96 void blackboxOpen(void)
98 serialPort_t
*sharedBlackboxAndMspPort
= findSharedSerialPort(FUNCTION_BLACKBOX
, FUNCTION_MSP
);
99 if (sharedBlackboxAndMspPort
) {
100 mspSerialReleasePortIfAllocated(sharedBlackboxAndMspPort
);
104 #ifdef DEBUG_BB_OUTPUT
105 static uint32_t bbBits
;
106 static timeMs_t bbLastclearMs
;
107 static uint16_t bbRateMax
;
108 static uint32_t bbDrops
;
111 void blackboxWrite(uint8_t value
)
113 #ifdef DEBUG_BB_OUTPUT
117 switch (blackboxConfig()->device
) {
119 case BLACKBOX_DEVICE_FLASH
:
120 flashfsWriteByte(value
); // Write byte asynchronously
124 case BLACKBOX_DEVICE_SDCARD
:
125 afatfs_fputc(blackboxSDCard
.logFile
, value
);
128 case BLACKBOX_DEVICE_SERIAL
:
131 int txBytesFree
= serialTxBytesFree(blackboxPort
);
133 #ifdef DEBUG_BB_OUTPUT
135 DEBUG_SET(DEBUG_BLACKBOX_OUTPUT
, 3, txBytesFree
);
138 if (txBytesFree
== 0) {
139 #ifdef DEBUG_BB_OUTPUT
141 DEBUG_SET(DEBUG_BLACKBOX_OUTPUT
, 2, bbDrops
);
145 serialWrite(blackboxPort
, value
);
150 #ifdef DEBUG_BB_OUTPUT
151 timeMs_t now
= millis();
153 if (now
> bbLastclearMs
+ 100) { // Debug log every 100[msec]
154 uint16_t bbRate
= ((bbBits
* 10 + 5) / (now
- bbLastclearMs
)) / 10; // In unit of [Kbps]
155 DEBUG_SET(DEBUG_BLACKBOX_OUTPUT
, 0, bbRate
);
156 if (bbRate
> bbRateMax
) {
158 DEBUG_SET(DEBUG_BLACKBOX_OUTPUT
, 1, bbRateMax
);
166 // Print the null-terminated string 's' to the blackbox device and return the number of bytes written
167 int blackboxWriteString(const char *s
)
172 switch (blackboxConfig()->device
) {
175 case BLACKBOX_DEVICE_FLASH
:
177 flashfsWrite((const uint8_t*) s
, length
, false); // Write asynchronously
179 #endif // USE_FLASHFS
182 case BLACKBOX_DEVICE_SDCARD
:
184 afatfs_fwrite(blackboxSDCard
.logFile
, (const uint8_t*) s
, length
); // Ignore failures due to buffers filling up
188 case BLACKBOX_DEVICE_SERIAL
:
196 length
= pos
- (uint8_t*) s
;
204 * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
206 * Intended to be called regularly for the blackbox device to perform housekeeping.
208 void blackboxDeviceFlush(void)
210 switch (blackboxConfig()->device
) {
213 * This is our only output device which requires us to call flush() in order for it to write anything. The other
214 * devices will progressively write in the background without Blackbox calling anything.
216 case BLACKBOX_DEVICE_FLASH
:
219 #endif // USE_FLASHFS
227 * If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
229 * Returns true if all data has been written to the device.
231 bool blackboxDeviceFlushForce(void)
233 switch (blackboxConfig()->device
) {
234 case BLACKBOX_DEVICE_SERIAL
:
235 // Nothing to speed up flushing on serial, as serial is continuously being drained out of its buffer
236 return isSerialTransmitBufferEmpty(blackboxPort
);
239 case BLACKBOX_DEVICE_FLASH
:
240 return flashfsFlushAsync();
241 #endif // USE_FLASHFS
244 case BLACKBOX_DEVICE_SDCARD
:
245 // SD card will flush itself without us calling it, but we need to call flush manually in order to check
246 // if it's done yet or not!
247 // However the "flush" only queues one dirty sector each time and the process is asynchronous. So after
248 // the last dirty sector is queued the flush returns true even though the sector may not actually have
249 // been physically written to the SD card yet.
250 return afatfs_flush();
258 // Flush the blackbox device and only return true if sync is actually complete.
259 // Primarily to ensure the async operations of SD card sector writes complete thus freeing the cache entries.
260 bool blackboxDeviceFlushForceComplete(void)
262 switch (blackboxConfig()->device
) {
264 case BLACKBOX_DEVICE_SDCARD
:
265 if (afatfs_sectorCacheInSync()) {
268 blackboxDeviceFlushForce();
274 return blackboxDeviceFlushForce();
279 * Attempt to open the logging device. Returns true if successful.
281 bool blackboxDeviceOpen(void)
283 switch (blackboxConfig()->device
) {
284 case BLACKBOX_DEVICE_SERIAL
:
286 const serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_BLACKBOX
);
287 baudRate_e baudRateIndex
;
288 portOptions_e portOptions
= SERIAL_PARITY_NO
| SERIAL_NOT_INVERTED
;
294 blackboxPortSharing
= determinePortSharing(portConfig
, FUNCTION_BLACKBOX
);
295 baudRateIndex
= portConfig
->blackbox_baudrateIndex
;
297 if (baudRates
[baudRateIndex
] == 230400) {
299 * OpenLog's 230400 baud rate is very inaccurate, so it requires a larger inter-character gap in
300 * order to maintain synchronization.
302 portOptions
|= SERIAL_STOPBITS_2
;
304 portOptions
|= SERIAL_STOPBITS_1
;
307 blackboxPort
= openSerialPort(portConfig
->identifier
, FUNCTION_BLACKBOX
, NULL
, NULL
, baudRates
[baudRateIndex
],
308 BLACKBOX_SERIAL_PORT_MODE
, portOptions
);
311 * The slowest MicroSD cards have a write latency approaching 400ms. The OpenLog's buffer is about 900
312 * bytes. In order for its buffer to be able to absorb this latency we must write slower than 6000 B/s.
314 * The OpenLager has a 125KB buffer for when the the MicroSD card is busy, so when the user configures
315 * high baud rates, assume the OpenLager is in use and so there is no need to constrain the writes.
317 * In all other cases, constrain the writes as follows:
319 * Bytes per loop iteration = floor((looptime_ns / 1000000.0) * 6000)
320 * = floor((looptime_ns * 6000) / 1000000.0)
321 * = floor((looptime_ns * 3) / 500.0)
322 * = (looptime_ns * 3) / 500
326 switch (baudRateIndex
) {
331 // assume OpenLager in use, so do not constrain writes
332 blackboxMaxHeaderBytesPerIteration
= BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
;
335 blackboxMaxHeaderBytesPerIteration
= constrain((targetPidLooptime
* 3) / 500, 1, BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
);
339 return blackboxPort
!= NULL
;
343 case BLACKBOX_DEVICE_FLASH
:
344 if (!flashfsIsSupported() || isBlackboxDeviceFull()) {
348 blackboxMaxHeaderBytesPerIteration
= BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
;
352 #endif // USE_FLASHFS
354 case BLACKBOX_DEVICE_SDCARD
:
355 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_FATAL
|| afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_UNKNOWN
|| afatfs_isFull()) {
359 blackboxMaxHeaderBytesPerIteration
= BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
;
370 * Erase all blackbox logs
373 void blackboxEraseAll(void)
375 switch (blackboxConfig()->device
) {
376 case BLACKBOX_DEVICE_FLASH
:
377 /* Stop the recorder as if blackbox_mode = ALWAYS it will attempt to resume writing after
378 * the erase and leave a corrupted first log.
379 * Possible enhancement here is to restart logging after erase.
382 flashfsEraseCompletely();
391 * Check to see if erasing is done
393 bool isBlackboxErased(void)
395 switch (blackboxConfig()->device
) {
396 case BLACKBOX_DEVICE_FLASH
:
397 return flashfsIsReady();
408 * Close the Blackbox logging device.
410 void blackboxDeviceClose(void)
412 switch (blackboxConfig()->device
) {
413 case BLACKBOX_DEVICE_SERIAL
:
414 // Can immediately close without attempting to flush any remaining data.
415 // Since the serial port could be shared with other processes, we have to give it back here
416 closeSerialPort(blackboxPort
);
420 * Normally this would be handled by mw.c, but since we take an unknown amount
421 * of time to shut down asynchronously, we're the only ones that know when to call it.
423 if (blackboxPortSharing
== PORTSHARING_SHARED
) {
424 mspSerialAllocatePorts();
428 case BLACKBOX_DEVICE_FLASH
:
429 // Some flash device, e.g., NAND devices, require explicit close to flush internally buffered data.
440 static void blackboxLogDirCreated(afatfsFilePtr_t directory
)
443 blackboxSDCard
.logDirectory
= directory
;
445 afatfs_findFirst(blackboxSDCard
.logDirectory
, &blackboxSDCard
.logDirectoryFinder
);
447 blackboxSDCard
.state
= BLACKBOX_SDCARD_ENUMERATE_FILES
;
450 blackboxSDCard
.state
= BLACKBOX_SDCARD_INITIAL
;
454 static void blackboxLogFileCreated(afatfsFilePtr_t file
)
457 blackboxSDCard
.logFile
= file
;
459 blackboxSDCard
.largestLogFileNumber
++;
461 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_LOG
;
464 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_CREATE_LOG
;
468 static void blackboxCreateLogFile(void)
470 int32_t remainder
= blackboxSDCard
.largestLogFileNumber
+ 1;
472 char filename
[] = LOGFILE_PREFIX
"00000." LOGFILE_SUFFIX
;
474 for (int i
= 7; i
>= 3; i
--) {
475 filename
[i
] = (remainder
% 10) + '0';
479 blackboxSDCard
.state
= BLACKBOX_SDCARD_WAITING
;
481 afatfs_fopen(filename
, "as", blackboxLogFileCreated
);
485 * Begin a new log on the SDCard.
487 * Keep calling until the function returns true (open is complete).
489 static bool blackboxSDCardBeginLog(void)
491 fatDirectoryEntry_t
*directoryEntry
;
494 switch (blackboxSDCard
.state
) {
495 case BLACKBOX_SDCARD_INITIAL
:
496 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY
) {
497 blackboxSDCard
.state
= BLACKBOX_SDCARD_WAITING
;
499 afatfs_mkdir("logs", blackboxLogDirCreated
);
503 case BLACKBOX_SDCARD_WAITING
:
504 // Waiting for directory entry to be created
507 case BLACKBOX_SDCARD_ENUMERATE_FILES
:
508 while (afatfs_findNext(blackboxSDCard
.logDirectory
, &blackboxSDCard
.logDirectoryFinder
, &directoryEntry
) == AFATFS_OPERATION_SUCCESS
) {
509 if (directoryEntry
&& !fat_isDirectoryEntryTerminator(directoryEntry
)) {
510 // If this is a log file, parse the log number from the filename
511 if (strncmp(directoryEntry
->filename
, LOGFILE_PREFIX
, strlen(LOGFILE_PREFIX
)) == 0
512 && strncmp(directoryEntry
->filename
+ 8, LOGFILE_SUFFIX
, strlen(LOGFILE_SUFFIX
)) == 0) {
513 char logSequenceNumberString
[6];
515 memcpy(logSequenceNumberString
, directoryEntry
->filename
+ 3, 5);
516 logSequenceNumberString
[5] = '\0';
518 blackboxSDCard
.largestLogFileNumber
= MAX((int32_t)atoi(logSequenceNumberString
), blackboxSDCard
.largestLogFileNumber
);
521 // We're done checking all the files on the card, now we can create a new log file
522 afatfs_findLast(blackboxSDCard
.logDirectory
);
524 blackboxSDCard
.state
= BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY
;
530 case BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY
:
531 // Change into the log directory:
532 if (afatfs_chdir(blackboxSDCard
.logDirectory
)) {
533 // We no longer need our open handle on the log directory
534 afatfs_fclose(blackboxSDCard
.logDirectory
, NULL
);
535 blackboxSDCard
.logDirectory
= NULL
;
537 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_CREATE_LOG
;
542 case BLACKBOX_SDCARD_READY_TO_CREATE_LOG
:
543 blackboxCreateLogFile();
546 case BLACKBOX_SDCARD_READY_TO_LOG
:
547 return true; // Log has been created!
550 // Not finished init yet
557 * Begin a new log (for devices which support separations between the logs of multiple flights).
559 * Keep calling until the function returns true (open is complete).
561 bool blackboxDeviceBeginLog(void)
563 switch (blackboxConfig()->device
) {
565 case BLACKBOX_DEVICE_SDCARD
:
566 return blackboxSDCardBeginLog();
575 * Terminate the current log (for devices which support separations between the logs of multiple flights).
577 * retainLog - Pass true if the log should be kept, or false if the log should be discarded (if supported).
579 * Keep calling until this returns true
581 bool blackboxDeviceEndLog(bool retainLog
)
587 switch (blackboxConfig()->device
) {
589 case BLACKBOX_DEVICE_SDCARD
:
590 // Keep retrying until the close operation queues
592 (retainLog
&& afatfs_fclose(blackboxSDCard
.logFile
, NULL
))
593 || (!retainLog
&& afatfs_funlink(blackboxSDCard
.logFile
, NULL
))
595 // Don't bother waiting the for the close to complete, it's queued now and will complete eventually
596 blackboxSDCard
.logFile
= NULL
;
597 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_CREATE_LOG
;
607 bool isBlackboxDeviceFull(void)
609 switch (blackboxConfig()->device
) {
610 case BLACKBOX_DEVICE_SERIAL
:
614 case BLACKBOX_DEVICE_FLASH
:
615 return flashfsIsEOF();
616 #endif // USE_FLASHFS
619 case BLACKBOX_DEVICE_SDCARD
:
620 return afatfs_isFull();
628 bool isBlackboxDeviceWorking(void)
630 switch (blackboxConfig()->device
) {
631 case BLACKBOX_DEVICE_SERIAL
:
632 return blackboxPort
!= NULL
;
635 case BLACKBOX_DEVICE_SDCARD
:
636 return sdcard_isInserted() && sdcard_isFunctional() && (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY
);
640 case BLACKBOX_DEVICE_FLASH
:
641 return flashfsIsReady();
649 int32_t blackboxGetLogNumber(void)
651 switch (blackboxConfig()->device
) {
653 case BLACKBOX_DEVICE_SDCARD
:
654 return blackboxSDCard
.largestLogFileNumber
;
663 * Call once every loop iteration in order to maintain the global blackboxHeaderBudget with the number of bytes we can
664 * transmit this iteration.
666 void blackboxReplenishHeaderBudget(void)
670 switch (blackboxConfig()->device
) {
671 case BLACKBOX_DEVICE_SERIAL
:
672 freeSpace
= serialTxBytesFree(blackboxPort
);
675 case BLACKBOX_DEVICE_FLASH
:
676 freeSpace
= flashfsGetWriteBufferFreeSpace();
680 case BLACKBOX_DEVICE_SDCARD
:
681 freeSpace
= afatfs_getFreeBufferSpace();
687 blackboxHeaderBudget
= MIN(MIN(freeSpace
, blackboxHeaderBudget
+ blackboxMaxHeaderBytesPerIteration
), BLACKBOX_MAX_ACCUMULATED_HEADER_BUDGET
);
691 * You must call this function before attempting to write Blackbox header bytes to ensure that the write will not
692 * cause buffers to overflow. The number of bytes you can write is capped by the blackboxHeaderBudget. Calling this
693 * reservation function doesn't decrease blackboxHeaderBudget, so you must manually decrement that variable by the
694 * number of bytes you actually wrote.
696 * When the Blackbox device is FlashFS, a successful return code guarantees that no data will be lost if you write that
697 * many bytes to the device (i.e. FlashFS's buffers won't overflow).
699 * When the device is a serial port, a successful return code guarantees that Cleanflight's serial Tx buffer will not
700 * overflow, and the outgoing bandwidth is likely to be small enough to give the OpenLog time to absorb MicroSD card
701 * latency. However the OpenLog could still end up silently dropping data.
704 * BLACKBOX_RESERVE_SUCCESS - Upon success
705 * BLACKBOX_RESERVE_TEMPORARY_FAILURE - The buffer is currently too full to service the request, try again later
706 * BLACKBOX_RESERVE_PERMANENT_FAILURE - The buffer is too small to ever service this request
708 blackboxBufferReserveStatus_e
blackboxDeviceReserveBufferSpace(int32_t bytes
)
710 if (bytes
<= blackboxHeaderBudget
) {
711 return BLACKBOX_RESERVE_SUCCESS
;
715 switch (blackboxConfig()->device
) {
716 case BLACKBOX_DEVICE_SERIAL
:
718 * One byte of the tx buffer isn't available for user data (due to its circular list implementation),
719 * hence the -1. Note that the USB VCP implementation doesn't use a buffer and has txBufferSize set to zero.
721 if (blackboxPort
->txBufferSize
&& bytes
> (int32_t) blackboxPort
->txBufferSize
- 1) {
722 return BLACKBOX_RESERVE_PERMANENT_FAILURE
;
724 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
727 case BLACKBOX_DEVICE_FLASH
:
728 if (bytes
> (int32_t) flashfsGetWriteBufferSize()) {
729 return BLACKBOX_RESERVE_PERMANENT_FAILURE
;
732 if (bytes
> (int32_t) flashfsGetWriteBufferFreeSpace()) {
734 * The write doesn't currently fit in the buffer, so try to make room for it. Our flushing here means
735 * that the Blackbox header writing code doesn't have to guess about the best time to ask flashfs to
736 * flush, and doesn't stall waiting for a flush that would otherwise not automatically be called.
740 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
741 #endif // USE_FLASHFS
744 case BLACKBOX_DEVICE_SDCARD
:
745 // Assume that all writes will fit in the SDCard's buffers
746 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
750 return BLACKBOX_RESERVE_PERMANENT_FAILURE
;
754 int8_t blackboxGetLogFileNo(void)
758 // return current file number or -1
759 if (blackboxSDCard
.state
== BLACKBOX_SDCARD_READY_TO_LOG
) {
760 return blackboxSDCard
.largestLogFileNumber
;
765 // will be implemented later for flash based storage