mirror of
https://github.com/betaflight/betaflight.git
synced 2025-07-23 08:15:30 +03:00
967 lines
27 KiB
C
967 lines
27 KiB
C
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
|
|
#include "platform.h"
|
|
|
|
#ifdef BLACKBOX
|
|
|
|
#include "blackbox.h"
|
|
#include "blackbox_io.h"
|
|
|
|
#include "build/version.h"
|
|
#include "build/build_config.h"
|
|
|
|
#include "common/encoding.h"
|
|
#include "common/maths.h"
|
|
#include "common/printf.h"
|
|
|
|
#include "config/parameter_group.h"
|
|
#include "config/parameter_group_ids.h"
|
|
|
|
#include "fc/config.h"
|
|
#include "fc/rc_controls.h"
|
|
|
|
#include "flight/pid.h"
|
|
|
|
#include "io/asyncfatfs/asyncfatfs.h"
|
|
#include "io/flashfs.h"
|
|
#include "io/serial.h"
|
|
|
|
#include "msp/msp_serial.h"
|
|
|
|
#define BLACKBOX_SERIAL_PORT_MODE MODE_TX
|
|
|
|
// How many bytes can we transmit per loop iteration when writing headers?
|
|
static uint8_t blackboxMaxHeaderBytesPerIteration;
|
|
|
|
// How many bytes can we write *this* iteration without overflowing transmit buffers or overstressing the OpenLog?
|
|
int32_t blackboxHeaderBudget;
|
|
|
|
STATIC_UNIT_TESTED serialPort_t *blackboxPort = NULL;
|
|
#ifndef UNIT_TEST
|
|
static portSharing_e blackboxPortSharing;
|
|
#endif
|
|
|
|
#ifdef USE_SDCARD
|
|
|
|
static struct {
|
|
afatfsFilePtr_t logFile;
|
|
afatfsFilePtr_t logDirectory;
|
|
afatfsFinder_t logDirectoryFinder;
|
|
uint32_t largestLogFileNumber;
|
|
|
|
enum {
|
|
BLACKBOX_SDCARD_INITIAL,
|
|
BLACKBOX_SDCARD_WAITING,
|
|
BLACKBOX_SDCARD_ENUMERATE_FILES,
|
|
BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY,
|
|
BLACKBOX_SDCARD_READY_TO_CREATE_LOG,
|
|
BLACKBOX_SDCARD_READY_TO_LOG
|
|
} state;
|
|
} blackboxSDCard;
|
|
|
|
#define LOGFILE_PREFIX "LOG"
|
|
#define LOGFILE_SUFFIX "BFL"
|
|
|
|
#endif
|
|
|
|
#ifndef UNIT_TEST
|
|
void blackboxOpen()
|
|
{
|
|
serialPort_t *sharedBlackboxAndMspPort = findSharedSerialPort(FUNCTION_BLACKBOX, FUNCTION_MSP);
|
|
if (sharedBlackboxAndMspPort) {
|
|
mspSerialReleasePortIfAllocated(sharedBlackboxAndMspPort);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void blackboxWrite(uint8_t value)
|
|
{
|
|
switch (blackboxConfig()->device) {
|
|
#ifdef USE_FLASHFS
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
flashfsWriteByte(value); // Write byte asynchronously
|
|
break;
|
|
#endif
|
|
#ifdef USE_SDCARD
|
|
case BLACKBOX_DEVICE_SDCARD:
|
|
afatfs_fputc(blackboxSDCard.logFile, value);
|
|
break;
|
|
#endif
|
|
case BLACKBOX_DEVICE_SERIAL:
|
|
default:
|
|
serialWrite(blackboxPort, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void _putc(void *p, char c)
|
|
{
|
|
(void)p;
|
|
blackboxWrite(c);
|
|
}
|
|
|
|
static int blackboxPrintfv(const char *fmt, va_list va)
|
|
{
|
|
return tfp_format(NULL, _putc, fmt, va);
|
|
}
|
|
|
|
|
|
//printf() to the blackbox serial port with no blocking shenanigans (so it's caller's responsibility to not write too fast!)
|
|
int blackboxPrintf(const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
|
|
int written = blackboxPrintfv(fmt, va);
|
|
|
|
va_end(va);
|
|
|
|
return written;
|
|
}
|
|
|
|
/*
|
|
* printf a Blackbox header line with a leading "H " and trailing "\n" added automatically. blackboxHeaderBudget is
|
|
* decreased to account for the number of bytes written.
|
|
*/
|
|
void blackboxPrintfHeaderLine(const char *name, const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
|
|
blackboxWrite('H');
|
|
blackboxWrite(' ');
|
|
blackboxPrint(name);
|
|
blackboxWrite(':');
|
|
|
|
va_start(va, fmt);
|
|
|
|
int written = blackboxPrintfv(fmt, va);
|
|
|
|
va_end(va);
|
|
|
|
blackboxWrite('\n');
|
|
|
|
blackboxHeaderBudget -= written + 3;
|
|
}
|
|
|
|
// Print the null-terminated string 's' to the blackbox device and return the number of bytes written
|
|
int blackboxPrint(const char *s)
|
|
{
|
|
int length;
|
|
const uint8_t *pos;
|
|
|
|
switch (blackboxConfig()->device) {
|
|
|
|
#ifdef USE_FLASHFS
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
length = strlen(s);
|
|
flashfsWrite((const uint8_t*) s, length, false); // Write asynchronously
|
|
break;
|
|
#endif
|
|
|
|
#ifdef USE_SDCARD
|
|
case BLACKBOX_DEVICE_SDCARD:
|
|
length = strlen(s);
|
|
afatfs_fwrite(blackboxSDCard.logFile, (const uint8_t*) s, length); // Ignore failures due to buffers filling up
|
|
break;
|
|
#endif
|
|
|
|
case BLACKBOX_DEVICE_SERIAL:
|
|
default:
|
|
pos = (uint8_t*) s;
|
|
while (*pos) {
|
|
serialWrite(blackboxPort, *pos);
|
|
pos++;
|
|
}
|
|
|
|
length = pos - (uint8_t*) s;
|
|
break;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
/**
|
|
* Write an unsigned integer to the blackbox serial port using variable byte encoding.
|
|
*/
|
|
void blackboxWriteUnsignedVB(uint32_t value)
|
|
{
|
|
//While this isn't the final byte (we can only write 7 bits at a time)
|
|
while (value > 127) {
|
|
blackboxWrite((uint8_t) (value | 0x80)); // Set the high bit to mean "more bytes follow"
|
|
value >>= 7;
|
|
}
|
|
blackboxWrite(value);
|
|
}
|
|
|
|
/**
|
|
* Write a signed integer to the blackbox serial port using ZigZig and variable byte encoding.
|
|
*/
|
|
void blackboxWriteSignedVB(int32_t value)
|
|
{
|
|
//ZigZag encode to make the value always positive
|
|
blackboxWriteUnsignedVB(zigzagEncode(value));
|
|
}
|
|
|
|
void blackboxWriteSignedVBArray(int32_t *array, int count)
|
|
{
|
|
for (int i = 0; i < count; i++) {
|
|
blackboxWriteSignedVB(array[i]);
|
|
}
|
|
}
|
|
|
|
void blackboxWriteSigned16VBArray(int16_t *array, int count)
|
|
{
|
|
for (int i = 0; i < count; i++) {
|
|
blackboxWriteSignedVB(array[i]);
|
|
}
|
|
}
|
|
|
|
void blackboxWriteS16(int16_t value)
|
|
{
|
|
blackboxWrite(value & 0xFF);
|
|
blackboxWrite((value >> 8) & 0xFF);
|
|
}
|
|
|
|
/**
|
|
* Write a 2 bit tag followed by 3 signed fields of 2, 4, 6 or 32 bits
|
|
*/
|
|
void blackboxWriteTag2_3S32(int32_t *values) {
|
|
static const int NUM_FIELDS = 3;
|
|
|
|
//Need to be enums rather than const ints if we want to switch on them (due to being C)
|
|
enum {
|
|
BITS_2 = 0,
|
|
BITS_4 = 1,
|
|
BITS_6 = 2,
|
|
BITS_32 = 3
|
|
};
|
|
|
|
enum {
|
|
BYTES_1 = 0,
|
|
BYTES_2 = 1,
|
|
BYTES_3 = 2,
|
|
BYTES_4 = 3
|
|
};
|
|
|
|
int x;
|
|
int selector = BITS_2, selector2;
|
|
|
|
/*
|
|
* Find out how many bits the largest value requires to encode, and use it to choose one of the packing schemes
|
|
* below:
|
|
*
|
|
* Selector possibilities
|
|
*
|
|
* 2 bits per field ss11 2233,
|
|
* 4 bits per field ss00 1111 2222 3333
|
|
* 6 bits per field ss11 1111 0022 2222 0033 3333
|
|
* 32 bits per field sstt tttt followed by fields of various byte counts
|
|
*/
|
|
for (x = 0; x < NUM_FIELDS; x++) {
|
|
//Require more than 6 bits?
|
|
if (values[x] >= 32 || values[x] < -32) {
|
|
selector = BITS_32;
|
|
break;
|
|
}
|
|
|
|
//Require more than 4 bits?
|
|
if (values[x] >= 8 || values[x] < -8) {
|
|
if (selector < BITS_6) {
|
|
selector = BITS_6;
|
|
}
|
|
} else if (values[x] >= 2 || values[x] < -2) { //Require more than 2 bits?
|
|
if (selector < BITS_4) {
|
|
selector = BITS_4;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (selector) {
|
|
case BITS_2:
|
|
blackboxWrite((selector << 6) | ((values[0] & 0x03) << 4) | ((values[1] & 0x03) << 2) | (values[2] & 0x03));
|
|
break;
|
|
case BITS_4:
|
|
blackboxWrite((selector << 6) | (values[0] & 0x0F));
|
|
blackboxWrite((values[1] << 4) | (values[2] & 0x0F));
|
|
break;
|
|
case BITS_6:
|
|
blackboxWrite((selector << 6) | (values[0] & 0x3F));
|
|
blackboxWrite((uint8_t)values[1]);
|
|
blackboxWrite((uint8_t)values[2]);
|
|
break;
|
|
case BITS_32:
|
|
/*
|
|
* Do another round to compute a selector for each field, assuming that they are at least 8 bits each
|
|
*
|
|
* Selector2 field possibilities
|
|
* 0 - 8 bits
|
|
* 1 - 16 bits
|
|
* 2 - 24 bits
|
|
* 3 - 32 bits
|
|
*/
|
|
selector2 = 0;
|
|
|
|
//Encode in reverse order so the first field is in the low bits:
|
|
for (x = NUM_FIELDS - 1; x >= 0; x--) {
|
|
selector2 <<= 2;
|
|
|
|
if (values[x] < 128 && values[x] >= -128) {
|
|
selector2 |= BYTES_1;
|
|
} else if (values[x] < 32768 && values[x] >= -32768) {
|
|
selector2 |= BYTES_2;
|
|
} else if (values[x] < 8388608 && values[x] >= -8388608) {
|
|
selector2 |= BYTES_3;
|
|
} else {
|
|
selector2 |= BYTES_4;
|
|
}
|
|
}
|
|
|
|
//Write the selectors
|
|
blackboxWrite((selector << 6) | selector2);
|
|
|
|
//And now the values according to the selectors we picked for them
|
|
for (x = 0; x < NUM_FIELDS; x++, selector2 >>= 2) {
|
|
switch (selector2 & 0x03) {
|
|
case BYTES_1:
|
|
blackboxWrite(values[x]);
|
|
break;
|
|
case BYTES_2:
|
|
blackboxWrite(values[x]);
|
|
blackboxWrite(values[x] >> 8);
|
|
break;
|
|
case BYTES_3:
|
|
blackboxWrite(values[x]);
|
|
blackboxWrite(values[x] >> 8);
|
|
blackboxWrite(values[x] >> 16);
|
|
break;
|
|
case BYTES_4:
|
|
blackboxWrite(values[x]);
|
|
blackboxWrite(values[x] >> 8);
|
|
blackboxWrite(values[x] >> 16);
|
|
blackboxWrite(values[x] >> 24);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write an 8-bit selector followed by four signed fields of size 0, 4, 8 or 16 bits.
|
|
*/
|
|
void blackboxWriteTag8_4S16(int32_t *values) {
|
|
|
|
//Need to be enums rather than const ints if we want to switch on them (due to being C)
|
|
enum {
|
|
FIELD_ZERO = 0,
|
|
FIELD_4BIT = 1,
|
|
FIELD_8BIT = 2,
|
|
FIELD_16BIT = 3
|
|
};
|
|
|
|
uint8_t selector, buffer;
|
|
int nibbleIndex;
|
|
int x;
|
|
|
|
selector = 0;
|
|
//Encode in reverse order so the first field is in the low bits:
|
|
for (x = 3; x >= 0; x--) {
|
|
selector <<= 2;
|
|
|
|
if (values[x] == 0) {
|
|
selector |= FIELD_ZERO;
|
|
} else if (values[x] < 8 && values[x] >= -8) {
|
|
selector |= FIELD_4BIT;
|
|
} else if (values[x] < 128 && values[x] >= -128) {
|
|
selector |= FIELD_8BIT;
|
|
} else {
|
|
selector |= FIELD_16BIT;
|
|
}
|
|
}
|
|
|
|
blackboxWrite(selector);
|
|
|
|
nibbleIndex = 0;
|
|
buffer = 0;
|
|
for (x = 0; x < 4; x++, selector >>= 2) {
|
|
switch (selector & 0x03) {
|
|
case FIELD_ZERO:
|
|
//No-op
|
|
break;
|
|
case FIELD_4BIT:
|
|
if (nibbleIndex == 0) {
|
|
//We fill high-bits first
|
|
buffer = values[x] << 4;
|
|
nibbleIndex = 1;
|
|
} else {
|
|
blackboxWrite(buffer | (values[x] & 0x0F));
|
|
nibbleIndex = 0;
|
|
}
|
|
break;
|
|
case FIELD_8BIT:
|
|
if (nibbleIndex == 0) {
|
|
blackboxWrite(values[x]);
|
|
} else {
|
|
//Write the high bits of the value first (mask to avoid sign extension)
|
|
blackboxWrite(buffer | ((values[x] >> 4) & 0x0F));
|
|
//Now put the leftover low bits into the top of the next buffer entry
|
|
buffer = values[x] << 4;
|
|
}
|
|
break;
|
|
case FIELD_16BIT:
|
|
if (nibbleIndex == 0) {
|
|
//Write high byte first
|
|
blackboxWrite(values[x] >> 8);
|
|
blackboxWrite(values[x]);
|
|
} else {
|
|
//First write the highest 4 bits
|
|
blackboxWrite(buffer | ((values[x] >> 12) & 0x0F));
|
|
// Then the middle 8
|
|
blackboxWrite(values[x] >> 4);
|
|
//Only the smallest 4 bits are still left to write
|
|
buffer = values[x] << 4;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
//Anything left over to write?
|
|
if (nibbleIndex == 1) {
|
|
blackboxWrite(buffer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write `valueCount` fields from `values` to the Blackbox using signed variable byte encoding. A 1-byte header is
|
|
* written first which specifies which fields are non-zero (so this encoding is compact when most fields are zero).
|
|
*
|
|
* valueCount must be 8 or less.
|
|
*/
|
|
void blackboxWriteTag8_8SVB(int32_t *values, int valueCount)
|
|
{
|
|
uint8_t header;
|
|
int i;
|
|
|
|
if (valueCount > 0) {
|
|
//If we're only writing one field then we can skip the header
|
|
if (valueCount == 1) {
|
|
blackboxWriteSignedVB(values[0]);
|
|
} else {
|
|
//First write a one-byte header that marks which fields are non-zero
|
|
header = 0;
|
|
|
|
// First field should be in low bits of header
|
|
for (i = valueCount - 1; i >= 0; i--) {
|
|
header <<= 1;
|
|
|
|
if (values[i] != 0) {
|
|
header |= 0x01;
|
|
}
|
|
}
|
|
|
|
blackboxWrite(header);
|
|
|
|
for (i = 0; i < valueCount; i++) {
|
|
if (values[i] != 0) {
|
|
blackboxWriteSignedVB(values[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Write unsigned integer **/
|
|
void blackboxWriteU32(int32_t value)
|
|
{
|
|
blackboxWrite(value & 0xFF);
|
|
blackboxWrite((value >> 8) & 0xFF);
|
|
blackboxWrite((value >> 16) & 0xFF);
|
|
blackboxWrite((value >> 24) & 0xFF);
|
|
}
|
|
|
|
/** Write float value in the integer form **/
|
|
void blackboxWriteFloat(float value)
|
|
{
|
|
blackboxWriteU32(castFloatBytesToInt(value));
|
|
}
|
|
|
|
/**
|
|
* If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
|
|
*
|
|
* Intended to be called regularly for the blackbox device to perform housekeeping.
|
|
*/
|
|
void blackboxDeviceFlush(void)
|
|
{
|
|
switch (blackboxConfig()->device) {
|
|
#ifdef USE_FLASHFS
|
|
/*
|
|
* This is our only output device which requires us to call flush() in order for it to write anything. The other
|
|
* devices will progressively write in the background without Blackbox calling anything.
|
|
*/
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
flashfsFlushAsync();
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If there is data waiting to be written to the blackbox device, attempt to write (a portion of) that now.
|
|
*
|
|
* Returns true if all data has been written to the device.
|
|
*/
|
|
bool blackboxDeviceFlushForce(void)
|
|
{
|
|
switch (blackboxConfig()->device) {
|
|
case BLACKBOX_DEVICE_SERIAL:
|
|
// Nothing to speed up flushing on serial, as serial is continuously being drained out of its buffer
|
|
return isSerialTransmitBufferEmpty(blackboxPort);
|
|
|
|
#ifdef USE_FLASHFS
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
return flashfsFlushAsync();
|
|
#endif
|
|
|
|
#ifdef USE_SDCARD
|
|
case BLACKBOX_DEVICE_SDCARD:
|
|
/* SD card will flush itself without us calling it, but we need to call flush manually in order to check
|
|
* if it's done yet or not!
|
|
*/
|
|
return afatfs_flush();
|
|
#endif
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempt to open the logging device. Returns true if successful.
|
|
*/
|
|
#ifndef UNIT_TEST
|
|
bool blackboxDeviceOpen(void)
|
|
{
|
|
switch (blackboxConfig()->device) {
|
|
case BLACKBOX_DEVICE_SERIAL:
|
|
{
|
|
serialPortConfig_t *portConfig = findSerialPortConfig(FUNCTION_BLACKBOX);
|
|
baudRate_e baudRateIndex;
|
|
portOptions_t portOptions = SERIAL_PARITY_NO | SERIAL_NOT_INVERTED;
|
|
|
|
if (!portConfig) {
|
|
return false;
|
|
}
|
|
|
|
blackboxPortSharing = determinePortSharing(portConfig, FUNCTION_BLACKBOX);
|
|
baudRateIndex = portConfig->blackbox_baudrateIndex;
|
|
|
|
if (baudRates[baudRateIndex] == 230400) {
|
|
/*
|
|
* OpenLog's 230400 baud rate is very inaccurate, so it requires a larger inter-character gap in
|
|
* order to maintain synchronization.
|
|
*/
|
|
portOptions |= SERIAL_STOPBITS_2;
|
|
} else {
|
|
portOptions |= SERIAL_STOPBITS_1;
|
|
}
|
|
|
|
blackboxPort = openSerialPort(portConfig->identifier, FUNCTION_BLACKBOX, NULL, baudRates[baudRateIndex],
|
|
BLACKBOX_SERIAL_PORT_MODE, portOptions);
|
|
|
|
/*
|
|
* The slowest MicroSD cards have a write latency approaching 150ms. The OpenLog's buffer is about 900
|
|
* bytes. In order for its buffer to be able to absorb this latency we must write slower than 6000 B/s.
|
|
*
|
|
* So:
|
|
* Bytes per loop iteration = floor((looptime_ns / 1000000.0) * 6000)
|
|
* = floor((looptime_ns * 6000) / 1000000.0)
|
|
* = floor((looptime_ns * 3) / 500.0)
|
|
* = (looptime_ns * 3) / 500
|
|
*/
|
|
blackboxMaxHeaderBytesPerIteration = constrain((targetPidLooptime * 3) / 500, 1, BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION);
|
|
|
|
return blackboxPort != NULL;
|
|
}
|
|
break;
|
|
#ifdef USE_FLASHFS
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
if (flashfsGetSize() == 0 || isBlackboxDeviceFull()) {
|
|
return false;
|
|
}
|
|
|
|
blackboxMaxHeaderBytesPerIteration = BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION;
|
|
|
|
return true;
|
|
break;
|
|
#endif
|
|
#ifdef USE_SDCARD
|
|
case BLACKBOX_DEVICE_SDCARD:
|
|
if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_FATAL || afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_UNKNOWN || afatfs_isFull()) {
|
|
return false;
|
|
}
|
|
|
|
blackboxMaxHeaderBytesPerIteration = BLACKBOX_TARGET_HEADER_BUDGET_PER_ITERATION;
|
|
|
|
return true;
|
|
break;
|
|
#endif
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Erase all blackbox logs
|
|
*/
|
|
#ifdef USE_FLASHFS
|
|
void blackboxEraseAll(void)
|
|
{
|
|
switch (blackboxConfig()->device) {
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
flashfsEraseCompletely();
|
|
break;
|
|
default:
|
|
//not supported
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check to see if erasing is done
|
|
*/
|
|
bool isBlackboxErased(void)
|
|
{
|
|
switch (blackboxConfig()->device) {
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
return flashfsIsReady();
|
|
break;
|
|
default:
|
|
//not supported
|
|
return true;
|
|
break;
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Close the Blackbox logging device immediately without attempting to flush any remaining data.
|
|
*/
|
|
#ifndef UNIT_TEST
|
|
void blackboxDeviceClose(void)
|
|
{
|
|
switch (blackboxConfig()->device) {
|
|
case BLACKBOX_DEVICE_SERIAL:
|
|
// Since the serial port could be shared with other processes, we have to give it back here
|
|
closeSerialPort(blackboxPort);
|
|
blackboxPort = NULL;
|
|
|
|
/*
|
|
* Normally this would be handled by mw.c, but since we take an unknown amount
|
|
* of time to shut down asynchronously, we're the only ones that know when to call it.
|
|
*/
|
|
if (blackboxPortSharing == PORTSHARING_SHARED) {
|
|
mspSerialAllocatePorts();
|
|
}
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_SDCARD
|
|
|
|
static void blackboxLogDirCreated(afatfsFilePtr_t directory)
|
|
{
|
|
if (directory) {
|
|
blackboxSDCard.logDirectory = directory;
|
|
|
|
afatfs_findFirst(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder);
|
|
|
|
blackboxSDCard.state = BLACKBOX_SDCARD_ENUMERATE_FILES;
|
|
} else {
|
|
// Retry
|
|
blackboxSDCard.state = BLACKBOX_SDCARD_INITIAL;
|
|
}
|
|
}
|
|
|
|
static void blackboxLogFileCreated(afatfsFilePtr_t file)
|
|
{
|
|
if (file) {
|
|
blackboxSDCard.logFile = file;
|
|
|
|
blackboxSDCard.largestLogFileNumber++;
|
|
|
|
blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_LOG;
|
|
} else {
|
|
// Retry
|
|
blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
|
|
}
|
|
}
|
|
|
|
static void blackboxCreateLogFile()
|
|
{
|
|
uint32_t remainder = blackboxSDCard.largestLogFileNumber + 1;
|
|
|
|
char filename[] = LOGFILE_PREFIX "00000." LOGFILE_SUFFIX;
|
|
|
|
for (int i = 7; i >= 3; i--) {
|
|
filename[i] = (remainder % 10) + '0';
|
|
remainder /= 10;
|
|
}
|
|
|
|
blackboxSDCard.state = BLACKBOX_SDCARD_WAITING;
|
|
|
|
afatfs_fopen(filename, "as", blackboxLogFileCreated);
|
|
}
|
|
|
|
/**
|
|
* Begin a new log on the SDCard.
|
|
*
|
|
* Keep calling until the function returns true (open is complete).
|
|
*/
|
|
static bool blackboxSDCardBeginLog()
|
|
{
|
|
fatDirectoryEntry_t *directoryEntry;
|
|
|
|
doMore:
|
|
switch (blackboxSDCard.state) {
|
|
case BLACKBOX_SDCARD_INITIAL:
|
|
if (afatfs_getFilesystemState() == AFATFS_FILESYSTEM_STATE_READY) {
|
|
blackboxSDCard.state = BLACKBOX_SDCARD_WAITING;
|
|
|
|
afatfs_mkdir("logs", blackboxLogDirCreated);
|
|
}
|
|
break;
|
|
|
|
case BLACKBOX_SDCARD_WAITING:
|
|
// Waiting for directory entry to be created
|
|
break;
|
|
|
|
case BLACKBOX_SDCARD_ENUMERATE_FILES:
|
|
while (afatfs_findNext(blackboxSDCard.logDirectory, &blackboxSDCard.logDirectoryFinder, &directoryEntry) == AFATFS_OPERATION_SUCCESS) {
|
|
if (directoryEntry && !fat_isDirectoryEntryTerminator(directoryEntry)) {
|
|
// If this is a log file, parse the log number from the filename
|
|
if (strncmp(directoryEntry->filename, LOGFILE_PREFIX, strlen(LOGFILE_PREFIX)) == 0
|
|
&& strncmp(directoryEntry->filename + 8, LOGFILE_SUFFIX, strlen(LOGFILE_SUFFIX)) == 0) {
|
|
char logSequenceNumberString[6];
|
|
|
|
memcpy(logSequenceNumberString, directoryEntry->filename + 3, 5);
|
|
logSequenceNumberString[5] = '\0';
|
|
|
|
blackboxSDCard.largestLogFileNumber = MAX((uint32_t) atoi(logSequenceNumberString), blackboxSDCard.largestLogFileNumber);
|
|
}
|
|
} else {
|
|
// We're done checking all the files on the card, now we can create a new log file
|
|
afatfs_findLast(blackboxSDCard.logDirectory);
|
|
|
|
blackboxSDCard.state = BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY;
|
|
goto doMore;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case BLACKBOX_SDCARD_CHANGE_INTO_LOG_DIRECTORY:
|
|
// Change into the log directory:
|
|
if (afatfs_chdir(blackboxSDCard.logDirectory)) {
|
|
// We no longer need our open handle on the log directory
|
|
afatfs_fclose(blackboxSDCard.logDirectory, NULL);
|
|
blackboxSDCard.logDirectory = NULL;
|
|
|
|
blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
|
|
goto doMore;
|
|
}
|
|
break;
|
|
|
|
case BLACKBOX_SDCARD_READY_TO_CREATE_LOG:
|
|
blackboxCreateLogFile();
|
|
break;
|
|
|
|
case BLACKBOX_SDCARD_READY_TO_LOG:
|
|
return true; // Log has been created!
|
|
}
|
|
|
|
// Not finished init yet
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* Begin a new log (for devices which support separations between the logs of multiple flights).
|
|
*
|
|
* Keep calling until the function returns true (open is complete).
|
|
*/
|
|
bool blackboxDeviceBeginLog(void)
|
|
{
|
|
switch (blackboxConfig()->device) {
|
|
#ifdef USE_SDCARD
|
|
case BLACKBOX_DEVICE_SDCARD:
|
|
return blackboxSDCardBeginLog();
|
|
#endif
|
|
default:
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Terminate the current log (for devices which support separations between the logs of multiple flights).
|
|
*
|
|
* retainLog - Pass true if the log should be kept, or false if the log should be discarded (if supported).
|
|
*
|
|
* Keep calling until this returns true
|
|
*/
|
|
bool blackboxDeviceEndLog(bool retainLog)
|
|
{
|
|
#ifndef USE_SDCARD
|
|
(void) retainLog;
|
|
#endif
|
|
|
|
switch (blackboxConfig()->device) {
|
|
#ifdef USE_SDCARD
|
|
case BLACKBOX_DEVICE_SDCARD:
|
|
// Keep retrying until the close operation queues
|
|
if (
|
|
(retainLog && afatfs_fclose(blackboxSDCard.logFile, NULL))
|
|
|| (!retainLog && afatfs_funlink(blackboxSDCard.logFile, NULL))
|
|
) {
|
|
// Don't bother waiting the for the close to complete, it's queued now and will complete eventually
|
|
blackboxSDCard.logFile = NULL;
|
|
blackboxSDCard.state = BLACKBOX_SDCARD_READY_TO_CREATE_LOG;
|
|
return true;
|
|
}
|
|
return false;
|
|
#endif
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool isBlackboxDeviceFull(void)
|
|
{
|
|
switch (blackboxConfig()->device) {
|
|
case BLACKBOX_DEVICE_SERIAL:
|
|
return false;
|
|
|
|
#ifdef USE_FLASHFS
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
return flashfsIsEOF();
|
|
#endif
|
|
|
|
#ifdef USE_SDCARD
|
|
case BLACKBOX_DEVICE_SDCARD:
|
|
return afatfs_isFull();
|
|
#endif
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call once every loop iteration in order to maintain the global blackboxHeaderBudget with the number of bytes we can
|
|
* transmit this iteration.
|
|
*/
|
|
void blackboxReplenishHeaderBudget()
|
|
{
|
|
int32_t freeSpace;
|
|
|
|
switch (blackboxConfig()->device) {
|
|
case BLACKBOX_DEVICE_SERIAL:
|
|
freeSpace = serialTxBytesFree(blackboxPort);
|
|
break;
|
|
#ifdef USE_FLASHFS
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
freeSpace = flashfsGetWriteBufferFreeSpace();
|
|
break;
|
|
#endif
|
|
#ifdef USE_SDCARD
|
|
case BLACKBOX_DEVICE_SDCARD:
|
|
freeSpace = afatfs_getFreeBufferSpace();
|
|
break;
|
|
#endif
|
|
default:
|
|
freeSpace = 0;
|
|
}
|
|
|
|
blackboxHeaderBudget = MIN(MIN(freeSpace, blackboxHeaderBudget + blackboxMaxHeaderBytesPerIteration), BLACKBOX_MAX_ACCUMULATED_HEADER_BUDGET);
|
|
}
|
|
|
|
/**
|
|
* You must call this function before attempting to write Blackbox header bytes to ensure that the write will not
|
|
* cause buffers to overflow. The number of bytes you can write is capped by the blackboxHeaderBudget. Calling this
|
|
* reservation function doesn't decrease blackboxHeaderBudget, so you must manually decrement that variable by the
|
|
* number of bytes you actually wrote.
|
|
*
|
|
* When the Blackbox device is FlashFS, a successful return code guarantees that no data will be lost if you write that
|
|
* many bytes to the device (i.e. FlashFS's buffers won't overflow).
|
|
*
|
|
* When the device is a serial port, a successful return code guarantees that Cleanflight's serial Tx buffer will not
|
|
* overflow, and the outgoing bandwidth is likely to be small enough to give the OpenLog time to absorb MicroSD card
|
|
* latency. However the OpenLog could still end up silently dropping data.
|
|
*
|
|
* Returns:
|
|
* BLACKBOX_RESERVE_SUCCESS - Upon success
|
|
* BLACKBOX_RESERVE_TEMPORARY_FAILURE - The buffer is currently too full to service the request, try again later
|
|
* BLACKBOX_RESERVE_PERMANENT_FAILURE - The buffer is too small to ever service this request
|
|
*/
|
|
blackboxBufferReserveStatus_e blackboxDeviceReserveBufferSpace(int32_t bytes)
|
|
{
|
|
if (bytes <= blackboxHeaderBudget) {
|
|
return BLACKBOX_RESERVE_SUCCESS;
|
|
}
|
|
|
|
// Handle failure:
|
|
switch (blackboxConfig()->device) {
|
|
case BLACKBOX_DEVICE_SERIAL:
|
|
/*
|
|
* One byte of the tx buffer isn't available for user data (due to its circular list implementation),
|
|
* hence the -1. Note that the USB VCP implementation doesn't use a buffer and has txBufferSize set to zero.
|
|
*/
|
|
if (blackboxPort->txBufferSize && bytes > (int32_t) blackboxPort->txBufferSize - 1) {
|
|
return BLACKBOX_RESERVE_PERMANENT_FAILURE;
|
|
}
|
|
|
|
return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
|
|
|
|
#ifdef USE_FLASHFS
|
|
case BLACKBOX_DEVICE_FLASH:
|
|
if (bytes > (int32_t) flashfsGetWriteBufferSize()) {
|
|
return BLACKBOX_RESERVE_PERMANENT_FAILURE;
|
|
}
|
|
|
|
if (bytes > (int32_t) flashfsGetWriteBufferFreeSpace()) {
|
|
/*
|
|
* The write doesn't currently fit in the buffer, so try to make room for it. Our flushing here means
|
|
* that the Blackbox header writing code doesn't have to guess about the best time to ask flashfs to
|
|
* flush, and doesn't stall waiting for a flush that would otherwise not automatically be called.
|
|
*/
|
|
flashfsFlushAsync();
|
|
}
|
|
|
|
return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
|
|
#endif
|
|
|
|
#ifdef USE_SDCARD
|
|
case BLACKBOX_DEVICE_SDCARD:
|
|
// Assume that all writes will fit in the SDCard's buffers
|
|
return BLACKBOX_RESERVE_TEMPORARY_FAILURE;
|
|
#endif
|
|
|
|
default:
|
|
return BLACKBOX_RESERVE_PERMANENT_FAILURE;
|
|
}
|
|
}
|
|
|
|
#endif
|