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
:
217 flashfsFlushAsync(false);
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(true);
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
325 switch (baudRateIndex
) {
330 // assume OpenLager in use, so do not constrain writes
331 blackboxMaxHeaderBytesPerIteration
= BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
;
334 blackboxMaxHeaderBytesPerIteration
= constrain((targetPidLooptime
* 3) / 500, 1, BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
);
338 return blackboxPort
!= NULL
;
342 case BLACKBOX_DEVICE_FLASH
:
343 if (!flashfsIsSupported() || isBlackboxDeviceFull()) {
347 blackboxMaxHeaderBytesPerIteration
= BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
;
351 #endif // USE_FLASHFS
353 case BLACKBOX_DEVICE_SDCARD
:
354 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_FATAL
|| afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_UNKNOWN
|| afatfs_isFull()) {
358 blackboxMaxHeaderBytesPerIteration
= BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION
;
369 * Erase all blackbox logs
372 void blackboxEraseAll(void)
374 switch (blackboxConfig()->device
) {
375 case BLACKBOX_DEVICE_FLASH
:
376 /* Stop the recorder as if blackbox_mode = ALWAYS it will attempt to resume writing after
377 * the erase and leave a corrupted first log.
378 * Possible enhancement here is to restart logging after erase.
381 flashfsEraseCompletely();
390 * Check to see if erasing is done
392 bool isBlackboxErased(void)
394 switch (blackboxConfig()->device
) {
395 case BLACKBOX_DEVICE_FLASH
:
396 return flashfsIsReady();
407 * Close the Blackbox logging device.
409 void blackboxDeviceClose(void)
411 switch (blackboxConfig()->device
) {
412 case BLACKBOX_DEVICE_SERIAL
:
413 // Can immediately close without attempting to flush any remaining data.
414 // Since the serial port could be shared with other processes, we have to give it back here
415 closeSerialPort(blackboxPort
);
419 * Normally this would be handled by mw.c, but since we take an unknown amount
420 * of time to shut down asynchronously, we're the only ones that know when to call it.
422 if (blackboxPortSharing
== PORTSHARING_SHARED
) {
423 mspSerialAllocatePorts();
427 case BLACKBOX_DEVICE_FLASH
:
428 // Some flash device, e.g., NAND devices, require explicit close to flush internally buffered data.
439 static void blackboxLogDirCreated(afatfsFilePtr_t directory
)
442 blackboxSDCard
.logDirectory
= directory
;
444 afatfs_findFirst(blackboxSDCard
.logDirectory
, &blackboxSDCard
.logDirectoryFinder
);
446 blackboxSDCard
.state
= BLACKBOX_SDCARD_ENUMERATE_FILES
;
449 blackboxSDCard
.state
= BLACKBOX_SDCARD_INITIAL
;
453 static void blackboxLogFileCreated(afatfsFilePtr_t file
)
456 blackboxSDCard
.logFile
= file
;
458 blackboxSDCard
.largestLogFileNumber
++;
460 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_LOG
;
463 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_CREATE_LOG
;
467 static void blackboxCreateLogFile(void)
469 int32_t remainder
= blackboxSDCard
.largestLogFileNumber
+ 1;
471 char filename
[] = LOGFILE_PREFIX
"00000." LOGFILE_SUFFIX
;
473 for (int i
= 7; i
>= 3; i
--) {
474 filename
[i
] = (remainder
% 10) + '0';
478 blackboxSDCard
.state
= BLACKBOX_SDCARD_WAITING
;
480 afatfs_fopen(filename
, "as", blackboxLogFileCreated
);
484 * Begin a new log on the SDCard.
486 * Keep calling until the function returns true (open is complete).
488 static bool blackboxSDCardBeginLog(void)
490 fatDirectoryEntry_t
*directoryEntry
;
493 switch (blackboxSDCard
.state
) {
494 case BLACKBOX_SDCARD_INITIAL
:
495 if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY
) {
496 blackboxSDCard
.state
= BLACKBOX_SDCARD_WAITING
;
498 afatfs_mkdir("logs", blackboxLogDirCreated
);
502 case BLACKBOX_SDCARD_WAITING
:
503 // Waiting for directory entry to be created
506 case BLACKBOX_SDCARD_ENUMERATE_FILES
:
507 while (afatfs_findNext(blackboxSDCard
.logDirectory
, &blackboxSDCard
.logDirectoryFinder
, &directoryEntry
) == AFATFS_OPERATION_SUCCESS
) {
508 if (directoryEntry
&& !fat_isDirectoryEntryTerminator(directoryEntry
)) {
509 // If this is a log file, parse the log number from the filename
510 if (strncmp(directoryEntry
->filename
, LOGFILE_PREFIX
, strlen(LOGFILE_PREFIX
)) == 0
511 && strncmp(directoryEntry
->filename
+ 8, LOGFILE_SUFFIX
, strlen(LOGFILE_SUFFIX
)) == 0) {
512 char logSequenceNumberString
[6];
514 memcpy(logSequenceNumberString
, directoryEntry
->filename
+ 3, 5);
515 logSequenceNumberString
[5] = '\0';
517 blackboxSDCard
.largestLogFileNumber
= MAX((int32_t)atoi(logSequenceNumberString
), blackboxSDCard
.largestLogFileNumber
);
520 // We're done checking all the files on the card, now we can create a new log file
521 afatfs_findLast(blackboxSDCard
.logDirectory
);
523 blackboxSDCard
.state
= BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY
;
529 case BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY
:
530 // Change into the log directory:
531 if (afatfs_chdir(blackboxSDCard
.logDirectory
)) {
532 // We no longer need our open handle on the log directory
533 afatfs_fclose(blackboxSDCard
.logDirectory
, NULL
);
534 blackboxSDCard
.logDirectory
= NULL
;
536 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_CREATE_LOG
;
541 case BLACKBOX_SDCARD_READY_TO_CREATE_LOG
:
542 blackboxCreateLogFile();
545 case BLACKBOX_SDCARD_READY_TO_LOG
:
546 return true; // Log has been created!
549 // Not finished init yet
556 * Begin a new log (for devices which support separations between the logs of multiple flights).
558 * Keep calling until the function returns true (open is complete).
560 bool blackboxDeviceBeginLog(void)
562 switch (blackboxConfig()->device
) {
564 case BLACKBOX_DEVICE_SDCARD
:
565 return blackboxSDCardBeginLog();
574 * Terminate the current log (for devices which support separations between the logs of multiple flights).
576 * retainLog - Pass true if the log should be kept, or false if the log should be discarded (if supported).
578 * Keep calling until this returns true
580 bool blackboxDeviceEndLog(bool retainLog
)
586 switch (blackboxConfig()->device
) {
588 case BLACKBOX_DEVICE_SDCARD
:
589 // Keep retrying until the close operation queues
591 (retainLog
&& afatfs_fclose(blackboxSDCard
.logFile
, NULL
))
592 || (!retainLog
&& afatfs_funlink(blackboxSDCard
.logFile
, NULL
))
594 // Don't bother waiting the for the close to complete, it's queued now and will complete eventually
595 blackboxSDCard
.logFile
= NULL
;
596 blackboxSDCard
.state
= BLACKBOX_SDCARD_READY_TO_CREATE_LOG
;
606 bool isBlackboxDeviceFull(void)
608 switch (blackboxConfig()->device
) {
609 case BLACKBOX_DEVICE_SERIAL
:
613 case BLACKBOX_DEVICE_FLASH
:
614 return flashfsIsEOF();
615 #endif // USE_FLASHFS
618 case BLACKBOX_DEVICE_SDCARD
:
619 return afatfs_isFull();
627 bool isBlackboxDeviceWorking(void)
629 switch (blackboxConfig()->device
) {
630 case BLACKBOX_DEVICE_SERIAL
:
631 return blackboxPort
!= NULL
;
634 case BLACKBOX_DEVICE_SDCARD
:
635 return sdcard_isInserted() && sdcard_isFunctional() && (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY
);
639 case BLACKBOX_DEVICE_FLASH
:
640 return flashfsIsReady();
648 int32_t blackboxGetLogNumber(void)
650 switch (blackboxConfig()->device
) {
652 case BLACKBOX_DEVICE_SDCARD
:
653 return blackboxSDCard
.largestLogFileNumber
;
662 * Call once every loop iteration in order to maintain the global blackboxHeaderBudget with the number of bytes we can
663 * transmit this iteration.
665 void blackboxReplenishHeaderBudget(void)
669 switch (blackboxConfig()->device
) {
670 case BLACKBOX_DEVICE_SERIAL
:
671 freeSpace
= serialTxBytesFree(blackboxPort
);
674 case BLACKBOX_DEVICE_FLASH
:
675 freeSpace
= flashfsGetWriteBufferFreeSpace();
679 case BLACKBOX_DEVICE_SDCARD
:
680 freeSpace
= afatfs_getFreeBufferSpace();
686 blackboxHeaderBudget
= MIN(MIN(freeSpace
, blackboxHeaderBudget
+ blackboxMaxHeaderBytesPerIteration
), BLACKBOX_MAX_ACCUMULATED_HEADER_BUDGET
);
690 * You must call this function before attempting to write Blackbox header bytes to ensure that the write will not
691 * cause buffers to overflow. The number of bytes you can write is capped by the blackboxHeaderBudget. Calling this
692 * reservation function doesn't decrease blackboxHeaderBudget, so you must manually decrement that variable by the
693 * number of bytes you actually wrote.
695 * When the Blackbox device is FlashFS, a successful return code guarantees that no data will be lost if you write that
696 * many bytes to the device (i.e. FlashFS's buffers won't overflow).
698 * When the device is a serial port, a successful return code guarantees that Cleanflight's serial Tx buffer will not
699 * overflow, and the outgoing bandwidth is likely to be small enough to give the OpenLog time to absorb MicroSD card
700 * latency. However the OpenLog could still end up silently dropping data.
703 * BLACKBOX_RESERVE_SUCCESS - Upon success
704 * BLACKBOX_RESERVE_TEMPORARY_FAILURE - The buffer is currently too full to service the request, try again later
705 * BLACKBOX_RESERVE_PERMANENT_FAILURE - The buffer is too small to ever service this request
707 blackboxBufferReserveStatus_e
blackboxDeviceReserveBufferSpace(int32_t bytes
)
709 if (bytes
<= blackboxHeaderBudget
) {
710 return BLACKBOX_RESERVE_SUCCESS
;
714 switch (blackboxConfig()->device
) {
715 case BLACKBOX_DEVICE_SERIAL
:
717 * One byte of the tx buffer isn't available for user data (due to its circular list implementation),
718 * hence the -1. Note that the USB VCP implementation doesn't use a buffer and has txBufferSize set to zero.
720 if (blackboxPort
->txBufferSize
&& bytes
> (int32_t) blackboxPort
->txBufferSize
- 1) {
721 return BLACKBOX_RESERVE_PERMANENT_FAILURE
;
723 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
726 case BLACKBOX_DEVICE_FLASH
:
727 if (bytes
> (int32_t) flashfsGetWriteBufferSize()) {
728 return BLACKBOX_RESERVE_PERMANENT_FAILURE
;
731 if (bytes
> (int32_t) flashfsGetWriteBufferFreeSpace()) {
733 * The write doesn't currently fit in the buffer, so try to make room for it. Our flushing here means
734 * that the Blackbox header writing code doesn't have to guess about the best time to ask flashfs to
735 * flush, and doesn't stall waiting for a flush that would otherwise not automatically be called.
737 flashfsFlushAsync(true);
739 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
740 #endif // USE_FLASHFS
743 case BLACKBOX_DEVICE_SDCARD
:
744 // Assume that all writes will fit in the SDCard's buffers
745 return BLACKBOX_RESERVE_TEMPORARY_FAILURE
;
749 return BLACKBOX_RESERVE_PERMANENT_FAILURE
;
753 int8_t blackboxGetLogFileNo(void)
757 // return current file number or -1
758 if (blackboxSDCard
.state
== BLACKBOX_SDCARD_READY_TO_LOG
) {
759 return blackboxSDCard
.largestLogFileNumber
;
764 // will be implemented later for flash based storage