Header definitions now provided by a struct instead of lots of strings

Introduce mechanism for disabling log fields at logging-start
Remove a div/mod from handleBlackbox()
Bring code required to be executed upon state transitions into a central
setState() routine.
This commit is contained in:
Nicholas Sherlock 2014-12-20 16:51:38 +13:00
parent 50c81aa00e
commit 9b9474250e
2 changed files with 374 additions and 291 deletions

View File

@ -79,6 +79,8 @@
#define BLACKBOX_INITIAL_PORT_MODE MODE_TX
#define BLACKBOX_I_INTERVAL 32
#define ARRAY_LENGTH(x) (sizeof((x))/sizeof((x)[0]))
// Some macros to make writing FLIGHT_LOG_FIELD_PREDICTOR_* constants shorter:
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
@ -86,8 +88,11 @@
#define CONCAT_HELPER(x,y) x ## y
#define CONCAT(x,y) CONCAT_HELPER(x, y)
#define PREDICT(x) STR(CONCAT(FLIGHT_LOG_FIELD_PREDICTOR_, x))
#define ENCODING(x) STR(CONCAT(FLIGHT_LOG_FIELD_ENCODING_, x))
#define PREDICT(x) CONCAT(FLIGHT_LOG_FIELD_PREDICTOR_, x)
#define ENCODING(x) CONCAT(FLIGHT_LOG_FIELD_ENCODING_, x)
#define CONDITION(x) CONCAT(FLIGHT_LOG_FIELD_CONDITION_, x)
#define UNSIGNED FLIGHT_LOG_FIELD_UNSIGNED
#define SIGNED FLIGHT_LOG_FIELD_SIGNED
static const char blackboxHeader[] =
"H Product:Blackbox flight data recorder by Nicholas Sherlock\n"
@ -95,174 +100,115 @@ static const char blackboxHeader[] =
"H Data version:2\n"
"H I interval:" STR(BLACKBOX_I_INTERVAL) "\n";
/* These headers have info for all 8 motors on them, we'll trim the final fields off to match the number of motors in the mixer: */
static const char * const blackboxHeaderFields[] = {
"H Field I name:"
"loopIteration,time,"
"axisP[0],axisP[1],axisP[2],"
"axisI[0],axisI[1],axisI[2],"
"axisD[0],axisD[1],axisD[2],"
"rcCommand[0],rcCommand[1],rcCommand[2],rcCommand[3],"
"gyroData[0],gyroData[1],gyroData[2],"
"accSmooth[0],accSmooth[1],accSmooth[2],"
"motor[0],motor[1],motor[2],motor[3],"
"motor[4],motor[5],motor[6],motor[7]",
"H Field I signed:"
/* loopIteration, time: */
"0,0,"
/* PIDs: */
"1,1,1,1,1,1,1,1,1,"
/* rcCommand[0..2] */
"1,1,1,"
/* rcCommand[3] (Throttle): */
"0,"
/* gyroData[0..2]: */
"1,1,1,"
/* accSmooth[0..2]: */
"1,1,1,"
/* Motor[0..7]: */
"0,0,0,0,0,0,0,0",
"H Field I predictor:"
/* loopIteration, time: */
PREDICT(0) "," PREDICT(0) ","
/* PIDs: */
PREDICT(0) "," PREDICT(0) "," PREDICT(0) ","
PREDICT(0) "," PREDICT(0) "," PREDICT(0) ","
PREDICT(0) "," PREDICT(0) "," PREDICT(0) ","
/* rcCommand[0..2] */
PREDICT(0) "," PREDICT(0) "," PREDICT(0) ","
/* rcCommand[3] (Throttle): */
PREDICT(MINTHROTTLE) ","
/* gyroData[0..2]: */
PREDICT(0) "," PREDICT(0) "," PREDICT(0) ","
/* accSmooth[0..2]: */
PREDICT(0) "," PREDICT(0) "," PREDICT(0) ","
/* Motor[0]: */
PREDICT(MINTHROTTLE) ","
/* Motor[1..7]: */
PREDICT(MOTOR_0) "," PREDICT(MOTOR_0) "," PREDICT(MOTOR_0) ","
PREDICT(MOTOR_0) "," PREDICT(MOTOR_0) "," PREDICT(MOTOR_0) ","
PREDICT(MOTOR_0),
"H Field I encoding:"
/* loopIteration, time: */
ENCODING(UNSIGNED_VB) "," ENCODING(UNSIGNED_VB) ","
/* PIDs: */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
/* rcCommand[0..2] */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
/* rcCommand[3] (Throttle): */
ENCODING(UNSIGNED_VB) ","
/* gyroData[0..2]: */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
/* accSmooth[0..2]: */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
/* Motor[0]: */
ENCODING(UNSIGNED_VB) ","
/* Motor[1..7]: */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
ENCODING(SIGNED_VB),
//Motors and gyros predict an average of the last two measurements (to reduce the impact of noise):
"H Field P predictor:"
/* loopIteration, time: */
PREDICT(INC) "," PREDICT(STRAIGHT_LINE) ","
/* PIDs: */
PREDICT(PREVIOUS) "," PREDICT(PREVIOUS) "," PREDICT(PREVIOUS) ","
PREDICT(PREVIOUS) "," PREDICT(PREVIOUS) "," PREDICT(PREVIOUS) ","
PREDICT(PREVIOUS) "," PREDICT(PREVIOUS) "," PREDICT(PREVIOUS) ","
/* rcCommand[0..2] */
PREDICT(PREVIOUS) "," PREDICT(PREVIOUS) "," PREDICT(PREVIOUS) ","
/* rcCommand[3] (Throttle): */
PREDICT(PREVIOUS) ","
/* gyroData[0..2]: */
PREDICT(AVERAGE_2) "," PREDICT(AVERAGE_2) "," PREDICT(AVERAGE_2) ","
/* accSmooth[0..2]: */
PREDICT(AVERAGE_2) "," PREDICT(AVERAGE_2) "," PREDICT(AVERAGE_2) ","
/* Motor[0]: */
PREDICT(AVERAGE_2) ","
/* Motor[1..7]: */
PREDICT(AVERAGE_2) "," PREDICT(AVERAGE_2) "," PREDICT(AVERAGE_2) ","
PREDICT(AVERAGE_2) "," PREDICT(AVERAGE_2) "," PREDICT(AVERAGE_2) ","
PREDICT(AVERAGE_2),
/* PID_I terms and RC fields are encoded together as groups, everything else is signed since they're diffs: */
"H Field P encoding:"
/* loopIteration, time: */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
/* PIDs: */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
ENCODING(TAG2_3S32) "," ENCODING(TAG2_3S32) "," ENCODING(TAG2_3S32) ","
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
/* rcCommand[0..3] */
ENCODING(TAG8_4S16) "," ENCODING(TAG8_4S16) "," ENCODING(TAG8_4S16) ","
ENCODING(TAG8_4S16) ","
/* gyroData[0..2]: */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
/* accSmooth[0..2]: */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
/* Motor[0]: */
ENCODING(SIGNED_VB) ","
/* Motor[1..7]: */
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
ENCODING(SIGNED_VB)
static const char* const blackboxMainHeaderNames[] = {
"I name",
"I signed",
"I predictor",
"I encoding",
"P predictor",
"P encoding"
};
/**
* Additional fields to tack on to those above for tricopters (to record tail servo position)
*/
static const char * const blackboxAdditionalFieldsTricopter[] = {
//Field I name
"servo[5]",
//Field I signed
"0",
//Field I predictor
PREDICT(1500),
//Field I encoding:
ENCODING(SIGNED_VB),
//Field P predictor:
PREDICT(PREVIOUS),
//Field P encoding:
ENCODING(SIGNED_VB)
static const char* const blackboxGPSGHeaderNames[] = {
"G name",
"G signed",
"G predictor",
"G encoding"
};
static const char blackboxGpsHeader[] =
"H Field G name:"
"GPS_numSat,GPS_coord[0],GPS_coord[1],GPS_altitude,GPS_speed\n"
"H Field G signed:"
"0,1,1,0,0\n"
"H Field G predictor:"
PREDICT(0) "," PREDICT(HOME_COORD) "," PREDICT(HOME_COORD) "," PREDICT(0) "," PREDICT(0) "\n"
"H Field G encoding:"
ENCODING(UNSIGNED_VB) "," ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) ","
ENCODING(UNSIGNED_VB) "," ENCODING(UNSIGNED_VB) "\n"
static const char* const blackboxGPSHHeaderNames[] = {
"H name",
"H signed",
"H predictor",
"H encoding"
};
"H Field H name:"
"GPS_home[0],GPS_home[1]\n"
"H Field H signed:"
"1,1\n"
"H Field H predictor:"
PREDICT(0) "," PREDICT(0) "\n"
"H Field H encoding:"
ENCODING(SIGNED_VB) "," ENCODING(SIGNED_VB) "\n";
/* All field definition structs should look like this (but with longer arrs): */
typedef struct blackboxFieldDefinition_t {
const char *name;
uint8_t arr[1];
} blackboxFieldDefinition_t;
typedef struct blackboxMainFieldDefinition_t {
const char *name;
uint8_t isSigned;
uint8_t Ipredict;
uint8_t Iencode;
uint8_t Ppredict;
uint8_t Pencode;
uint8_t condition; // Decide whether this field should appear in the log
} blackboxMainFieldDefinition_t;
typedef struct blackboxGPSFieldDefinition_t {
const char *name;
uint8_t isSigned;
uint8_t predict;
uint8_t encode;
} blackboxGPSFieldDefinition_t;
static const blackboxMainFieldDefinition_t blackboxMainFields[] = {
/* loopIteration doesn't appear in P frames since it always increments */
{"loopIteration", UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(INC), .Pencode = FLIGHT_LOG_FIELD_ENCODING_NULL, CONDITION(ALWAYS)},
/* Time advances pretty steadily so the P-frame prediction is a straight line */
{"time", UNSIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(STRAIGHT_LINE), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"axisP[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"axisP[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"axisP[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
/* I terms get special packed encoding in P frames: */
{"axisI[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
{"axisI[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
{"axisI[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG2_3S32), CONDITION(ALWAYS)},
{"axisD[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"axisD[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"axisD[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
/* rcCommands are encoded together as a group in P-frames: */
{"rcCommand[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
{"rcCommand[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
{"rcCommand[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
/* Throttle is always in the range [minthrottle..maxthrottle]: */
{"rcCommand[3]", UNSIGNED, .Ipredict = PREDICT(MINTHROTTLE), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(TAG8_4S16), CONDITION(ALWAYS)},
/* Gyros and accelerometers base their P-predictions on the average of the previous 2 frames to reduce noise impact */
{"gyroData[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"gyroData[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"gyroData[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"accSmooth[0]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"accSmooth[1]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
{"accSmooth[2]", SIGNED, .Ipredict = PREDICT(0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(ALWAYS)},
/* Motors only rarely drops under minthrottle (when stick falls below mincommand), so predict minthrottle for it and use *unsigned* encoding (which is large for negative numbers but more compact for positive ones): */
{"motor[0]", UNSIGNED, .Ipredict = PREDICT(MINTHROTTLE), .Iencode = ENCODING(UNSIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_1)},
/* Subsequent motors base their I-frame values on the first one, P-frame values on the average of last two frames: */
{"motor[1]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_2)},
{"motor[2]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_3)},
{"motor[3]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_4)},
{"motor[4]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_5)},
{"motor[5]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_6)},
{"motor[6]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_7)},
{"motor[7]", UNSIGNED, .Ipredict = PREDICT(MOTOR_0), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(AVERAGE_2), .Pencode = ENCODING(SIGNED_VB), CONDITION(AT_LEAST_MOTORS_8)},
{"servo[5]", UNSIGNED, .Ipredict = PREDICT(1500), .Iencode = ENCODING(SIGNED_VB), .Ppredict = PREDICT(PREVIOUS), .Pencode = ENCODING(SIGNED_VB), CONDITION(TRICOPTER)}
};
// GPS position/vel frame
static const blackboxGPSFieldDefinition_t blackboxGpsGFields[] = {
{"GPS_numSat", UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB)},
{"GPS_coord[0]", SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB)},
{"GPS_coord[1]", SIGNED, PREDICT(HOME_COORD), ENCODING(SIGNED_VB)},
{"GPS_altitude", UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB)},
{"GPS_speed", UNSIGNED, PREDICT(0), ENCODING(UNSIGNED_VB)}
};
// GPS home frame
static const blackboxGPSFieldDefinition_t blackboxGpsHFields[] = {
{"GPS_home[0]", SIGNED, PREDICT(0), ENCODING(SIGNED_VB)},
{"GPS_home[1]", SIGNED, PREDICT(0), ENCODING(SIGNED_VB)}
};
typedef enum BlackboxState {
BLACKBOX_STATE_DISABLED = 0,
BLACKBOX_STATE_STOPPED,
BLACKBOX_STATE_SEND_HEADER,
BLACKBOX_STATE_SEND_FIELDINFO,
BLACKBOX_STATE_SEND_GPS_HEADERS,
BLACKBOX_STATE_SEND_GPS_H_HEADERS,
BLACKBOX_STATE_SEND_GPS_G_HEADERS,
BLACKBOX_STATE_SEND_SYSINFO,
BLACKBOX_STATE_RUNNING
} BlackboxState;
@ -278,10 +224,16 @@ extern uint8_t motorCount;
//From mw.c:
extern uint32_t currentTime;
static const int SERIAL_CHUNK_SIZE = 16;
static BlackboxState blackboxState = BLACKBOX_STATE_DISABLED;
static uint32_t startTime;
static unsigned int headerXmitIndex;
static int fieldXmitIndex;
static uint32_t blackboxIteration;
static uint32_t blackboxPFrameIndex, blackboxIFrameIndex;
static serialPort_t *blackboxPort;
static portMode_t previousPortMode;
@ -295,11 +247,6 @@ static blackboxValues_t blackboxHistoryRing[3];
// These point into blackboxHistoryRing, use them to know where to store history of a given age (0, 1 or 2 generations old)
static blackboxValues_t* blackboxHistory[3];
static int isTricopter()
{
return masterConfig.mixerConfiguration == MULTITYPE_TRI;
}
static void blackboxWrite(uint8_t value)
{
serialWrite(blackboxPort, value);
@ -320,6 +267,19 @@ static void blackboxPrintf(char *fmt, ...)
va_end(va);
}
// Print the null-terminated string 's' to the serial port and return the number of bytes written
static int blackboxPrint(const char *s)
{
const char *pos = s;
while (*pos) {
serialWrite(blackboxPort, *pos);
pos++;
}
return pos - s;
}
/**
* Write an unsigned integer to the blackbox serial port using variable byte encoding.
*/
@ -545,6 +505,64 @@ static void writeTag8_4S16(int32_t *values) {
blackboxWrite(buffer);
}
static bool testBlackboxCondition(FlightLogFieldCondition condition)
{
switch (condition) {
case FLIGHT_LOG_FIELD_CONDITION_ALWAYS:
return true;
case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_1:
return motorCount >= 1;
case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_2:
return motorCount >= 2;
case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_3:
return motorCount >= 3;
case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_4:
return motorCount >= 4;
case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_5:
return motorCount >= 5;
case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_6:
return motorCount >= 6;
case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_7:
return motorCount >= 7;
case FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_8:
return motorCount >= 8;
case FLIGHT_LOG_FIELD_CONDITION_TRICOPTER:
return masterConfig.mixerConfiguration == MULTITYPE_TRI;
case FLIGHT_LOG_FIELD_CONDITION_NEVER:
return false;
default:
return false;
}
}
static void blackboxSetState(BlackboxState newState)
{
//Perform initial setup required for the new state
switch (newState) {
case BLACKBOX_STATE_SEND_HEADER:
startTime = millis();
headerXmitIndex = 0;
break;
case BLACKBOX_STATE_SEND_FIELDINFO:
case BLACKBOX_STATE_SEND_GPS_G_HEADERS:
case BLACKBOX_STATE_SEND_GPS_H_HEADERS:
headerXmitIndex = 0;
fieldXmitIndex = -1;
break;
case BLACKBOX_STATE_SEND_SYSINFO:
headerXmitIndex = 0;
break;
case BLACKBOX_STATE_RUNNING:
blackboxIteration = 0;
blackboxPFrameIndex = 0;
blackboxIFrameIndex = 0;
break;
default:
;
}
blackboxState = newState;
}
static void writeIntraframe(void)
{
blackboxValues_t *blackboxCurrent = blackboxHistory[0];
@ -582,7 +600,7 @@ static void writeIntraframe(void)
for (x = 1; x < motorCount; x++)
writeSignedVB(blackboxCurrent->motor[x] - blackboxCurrent->motor[0]);
if (isTricopter())
if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_TRICOPTER))
writeSignedVB(blackboxHistory[0]->servo[5] - 1500);
//Rotate our history buffers:
@ -647,7 +665,7 @@ static void writeInterframe(void)
for (x = 0; x < motorCount; x++)
writeSignedVB(blackboxHistory[0]->motor[x] - (blackboxHistory[1]->motor[x] + blackboxHistory[2]->motor[x]) / 2);
if (isTricopter())
if (testBlackboxCondition(FLIGHT_LOG_FIELD_CONDITION_TRICOPTER))
writeSignedVB(blackboxCurrent->servo[5] - blackboxLast->servo[5]);
//Rotate our history buffers
@ -715,29 +733,26 @@ void startBlackbox(void)
configureBlackboxPort();
if (!blackboxPort) {
blackboxState = BLACKBOX_STATE_DISABLED;
blackboxSetState(BLACKBOX_STATE_DISABLED);
return;
}
startTime = millis();
headerXmitIndex = 0;
blackboxIteration = 0;
blackboxState = BLACKBOX_STATE_SEND_HEADER;
memset(&gpsHistory, 0, sizeof(gpsHistory));
//No need to clear the content of blackboxHistoryRing since our first frame will be an intra which overwrites it
blackboxHistory[0] = &blackboxHistoryRing[0];
blackboxHistory[1] = &blackboxHistoryRing[1];
blackboxHistory[2] = &blackboxHistoryRing[2];
//No need to clear the content of blackboxHistoryRing since our first frame will be an intra which overwrites it
blackboxSetState(BLACKBOX_STATE_SEND_HEADER);
}
}
void finishBlackbox(void)
{
if (blackboxState != BLACKBOX_STATE_DISABLED && blackboxState != BLACKBOX_STATE_STOPPED) {
blackboxState = BLACKBOX_STATE_STOPPED;
blackboxSetState(BLACKBOX_STATE_STOPPED);
releaseBlackboxPort();
}
@ -803,14 +818,81 @@ static void loadBlackboxState(void)
blackboxCurrent->servo[5] = servo[5];
}
/**
* Transmit the header information for the given field definitions. Transmitted header lines look like:
*
* H Field I name:a,b,c
* H Field I predictor:0,1,2
*
* Provide an array 'conditions' of FlightLogFieldCondition enums if you want these conditions to decide whether a field
* should be included or not. Otherwise provide NULL for this parameter and NULL for secondCondition.
*
* Set headerXmitIndex to 0 and fieldXmitIndex to -1 before calling for the first time.
*
* secondFieldDefinition and secondCondition element pointers need to be provided in order to compute the stride of the
* fieldDefinition and secondCondition arrays.
*
* Returns true if there is still header left to transmit (so call again to continue transmission).
*/
static bool sendFieldDefinition(const char * const *headerNames, unsigned int headerCount, const void *fieldDefinitions,
const void *secondFieldDefinition, int fieldCount, const uint8_t *conditions, const uint8_t *secondCondition)
{
const blackboxFieldDefinition_t *def;
int charsWritten;
static bool needComma = false;
size_t definitionStride = (char*) secondFieldDefinition - (char*) fieldDefinitions;
size_t conditionsStride = (char*) secondCondition - (char*) conditions;
/*
* Send information about the fields we're recording to the log. Once again, we're chunking up the data so we don't exceed our datarate.
*/
if (fieldXmitIndex == -1) {
if (headerXmitIndex >= headerCount)
return false; //Someone probably called us again after we had already completed transmission
charsWritten = blackboxPrint("H Field ");
charsWritten += blackboxPrint(headerNames[headerXmitIndex]);
charsWritten += blackboxPrint(":");
fieldXmitIndex++;
needComma = false;
} else
charsWritten = 0;
for (; fieldXmitIndex < fieldCount && charsWritten < SERIAL_CHUNK_SIZE; fieldXmitIndex++) {
def = (const blackboxFieldDefinition_t*) ((const char*)fieldDefinitions + definitionStride * fieldXmitIndex);
if (!conditions || testBlackboxCondition(conditions[conditionsStride * fieldXmitIndex])) {
if (needComma) {
blackboxWrite(',');
charsWritten++;
} else
needComma = true;
// The first header is a field name
if (headerXmitIndex == 0) {
charsWritten += blackboxPrint(def->name);
} else {
//The other headers are integers (format as single digit for now):
blackboxWrite(def->arr[headerXmitIndex - 1] + '0');
charsWritten++;
}
}
}
// Did we complete this line?
if (fieldXmitIndex == fieldCount) {
blackboxWrite('\n');
headerXmitIndex++;
fieldXmitIndex = -1;
}
return headerXmitIndex < headerCount;
}
void handleBlackbox(void)
{
int i;
const char *additionalHeader;
const int SERIAL_CHUNK_SIZE = 16;
static int charXmitIndex = 0;
int motorsToRemove, endIndex;
int blackboxIntercycleIndex, blackboxIntracycleIndex;
union floatConvert_t {
float f;
@ -819,6 +901,8 @@ void handleBlackbox(void)
switch (blackboxState) {
case BLACKBOX_STATE_SEND_HEADER:
//On entry of this state, headerXmitIndex is 0 and startTime is intialised
/*
* Once the UART has had time to init, transmit the header in chunks so we don't overflow our transmit
* buffer.
@ -828,65 +912,36 @@ void handleBlackbox(void)
blackboxWrite(blackboxHeader[headerXmitIndex]);
if (blackboxHeader[headerXmitIndex] == '\0') {
blackboxState = BLACKBOX_STATE_SEND_FIELDINFO;
headerXmitIndex = 0;
charXmitIndex = 0;
blackboxSetState(BLACKBOX_STATE_SEND_FIELDINFO);
}
}
break;
case BLACKBOX_STATE_SEND_FIELDINFO:
/*
* Send information about the fields we're recording to the log.
*
* Once again, we're chunking up the data so we don't exceed our datarate. This time we're trimming the
* excess field defs off the end of the header for motors we don't have.
*/
motorsToRemove = 8 - motorCount;
if (headerXmitIndex < sizeof(blackboxHeaderFields) / sizeof(blackboxHeaderFields[0])){
endIndex = strlen(blackboxHeaderFields[headerXmitIndex]) - (headerXmitIndex == 0 ? strlen(",motor[x]") : strlen(",x")) * motorsToRemove;
for (i = charXmitIndex; i < charXmitIndex + SERIAL_CHUNK_SIZE && i < endIndex; i++)
blackboxWrite(blackboxHeaderFields[headerXmitIndex][i]);
// Did we complete this line?
if (i == endIndex) {
if (isTricopter()) {
//Add fields to the end for the tail servo
blackboxWrite(',');
for (additionalHeader = blackboxAdditionalFieldsTricopter[headerXmitIndex]; *additionalHeader; additionalHeader++)
blackboxWrite(*additionalHeader);
}
blackboxWrite('\n');
headerXmitIndex++;
charXmitIndex = 0;
} else {
charXmitIndex = i;
}
} else {
//Completed sending field information
if (feature(FEATURE_GPS)) {
blackboxState = BLACKBOX_STATE_SEND_GPS_HEADERS;
} else {
blackboxState = BLACKBOX_STATE_SEND_SYSINFO;
}
headerXmitIndex = 0;
//On entry of this state, headerXmitIndex is 0 and fieldXmitIndex is -1
if (!sendFieldDefinition(blackboxMainHeaderNames, ARRAY_LENGTH(blackboxMainHeaderNames), blackboxMainFields, &blackboxMainFields[1],
ARRAY_LENGTH(blackboxMainFields), &blackboxMainFields[0].condition, &blackboxMainFields[1].condition)) {
if (feature(FEATURE_GPS))
blackboxSetState(BLACKBOX_STATE_SEND_GPS_H_HEADERS);
else
blackboxSetState(BLACKBOX_STATE_SEND_SYSINFO);
}
break;
case BLACKBOX_STATE_SEND_GPS_HEADERS:
for (i = 0; i < SERIAL_CHUNK_SIZE && blackboxGpsHeader[headerXmitIndex] != '\0'; i++, headerXmitIndex++)
blackboxWrite(blackboxGpsHeader[headerXmitIndex]);
if (blackboxGpsHeader[headerXmitIndex] == '\0') {
blackboxState = BLACKBOX_STATE_SEND_SYSINFO;
headerXmitIndex = 0;
case BLACKBOX_STATE_SEND_GPS_H_HEADERS:
//On entry of this state, headerXmitIndex is 0 and fieldXmitIndex is -1
if (!sendFieldDefinition(blackboxGPSHHeaderNames, ARRAY_LENGTH(blackboxGPSHHeaderNames), blackboxGpsHFields, &blackboxGpsHFields[1],
ARRAY_LENGTH(blackboxGpsHFields), NULL, 0)) {
blackboxSetState(BLACKBOX_STATE_SEND_GPS_G_HEADERS);
}
break;
case BLACKBOX_STATE_SEND_GPS_G_HEADERS:
//On entry of this state, headerXmitIndex is 0 and fieldXmitIndex is -1
if (!sendFieldDefinition(blackboxGPSGHeaderNames, ARRAY_LENGTH(blackboxGPSGHeaderNames), blackboxGpsGFields, &blackboxGpsGFields[1],
ARRAY_LENGTH(blackboxGpsGFields), NULL, 0)) {
blackboxSetState(BLACKBOX_STATE_SEND_SYSINFO);
}
break;
case BLACKBOX_STATE_SEND_SYSINFO:
//On entry of this state, headerXmitIndex is 0
switch (headerXmitIndex) {
case 0:
blackboxPrintf("H Firmware type:Cleanflight\n");
@ -926,22 +981,21 @@ void handleBlackbox(void)
// One more pause for good luck
break;
default:
blackboxState = BLACKBOX_STATE_RUNNING;
blackboxSetState(BLACKBOX_STATE_RUNNING);
}
headerXmitIndex++;
break;
case BLACKBOX_STATE_RUNNING:
// Write a keyframe every 32 frames so we can resynchronise upon missing frames
blackboxIntercycleIndex = blackboxIteration % BLACKBOX_I_INTERVAL;
blackboxIntracycleIndex = blackboxIteration / BLACKBOX_I_INTERVAL;
// On entry to this state, blackboxIteration, blackboxPFrameIndex and blackboxIFrameIndex are reset to 0
if (blackboxIntercycleIndex == 0) {
// Write a keyframe every BLACKBOX_I_INTERVAL frames so we can resynchronise upon missing frames
if (blackboxPFrameIndex == 0) {
// Copy current system values into the blackbox
loadBlackboxState();
writeIntraframe();
} else {
if ((blackboxIntercycleIndex + masterConfig.blackbox_rate_num - 1) % masterConfig.blackbox_rate_denom < masterConfig.blackbox_rate_num) {
if ((blackboxPFrameIndex + masterConfig.blackbox_rate_num - 1) % masterConfig.blackbox_rate_denom < masterConfig.blackbox_rate_num) {
loadBlackboxState();
writeInterframe();
}
@ -955,7 +1009,7 @@ void handleBlackbox(void)
* still be interpreted correctly.
*/
if (GPS_home[0] != gpsHistory.GPS_home[0] || GPS_home[1] != gpsHistory.GPS_home[1]
|| (blackboxIntercycleIndex == BLACKBOX_I_INTERVAL / 2 && blackboxIntracycleIndex % 128 == 0)) {
|| (blackboxPFrameIndex == BLACKBOX_I_INTERVAL / 2 && blackboxIFrameIndex % 128 == 0)) {
writeGPSHomeFrame();
writeGPSFrame();
@ -968,6 +1022,12 @@ void handleBlackbox(void)
}
blackboxIteration++;
blackboxPFrameIndex++;
if (blackboxPFrameIndex == BLACKBOX_I_INTERVAL) {
blackboxPFrameIndex = 0;
blackboxIFrameIndex++;
}
break;
default:
break;
@ -985,7 +1045,7 @@ static bool canUseBlackboxWithCurrentConfiguration(void)
void initBlackbox(void)
{
if (canUseBlackboxWithCurrentConfiguration())
blackboxState = BLACKBOX_STATE_STOPPED;
blackboxSetState(BLACKBOX_STATE_STOPPED);
else
blackboxState = BLACKBOX_STATE_DISABLED;
blackboxSetState(BLACKBOX_STATE_DISABLED);
}

View File

@ -1,52 +1,75 @@
/*
* This file is part of Cleanflight.
*
* Cleanflight is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cleanflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
//No prediction:
#define FLIGHT_LOG_FIELD_PREDICTOR_0 0
//Predict that the field is the same as last frame:
#define FLIGHT_LOG_FIELD_PREDICTOR_PREVIOUS 1
//Predict that the slope between this field and the previous item is the same as that between the past two history items:
#define FLIGHT_LOG_FIELD_PREDICTOR_STRAIGHT_LINE 2
//Predict that this field is the same as the average of the last two history items:
#define FLIGHT_LOG_FIELD_PREDICTOR_AVERAGE_2 3
//Predict that this field is minthrottle
#define FLIGHT_LOG_FIELD_PREDICTOR_MINTHROTTLE 4
//Predict that this field is the same as motor 0
#define FLIGHT_LOG_FIELD_PREDICTOR_MOTOR_0 5
//This field always increments
#define FLIGHT_LOG_FIELD_PREDICTOR_INC 6
//Predict this GPS co-ordinate is the GPS home co-ordinate (or no prediction if that coordinate is not set)
#define FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD 7
//Predict 1500
#define FLIGHT_LOG_FIELD_PREDICTOR_1500 8
#define FLIGHT_LOG_FIELD_ENCODING_SIGNED_VB 0
#define FLIGHT_LOG_FIELD_ENCODING_UNSIGNED_VB 1
#define FLIGHT_LOG_FIELD_ENCODING_TAG2_3S32 7
#define FLIGHT_LOG_FIELD_ENCODING_TAG8_4S16 8
#define FLIGHT_LOG_FIELD_ENCODING_NULL 9
/*
* This file is part of Cleanflight.
*
* Cleanflight is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Cleanflight is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
typedef enum FlightLogFieldCondition {
FLIGHT_LOG_FIELD_CONDITION_ALWAYS = 0,
FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_1,
FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_2,
FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_3,
FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_4,
FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_5,
FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_6,
FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_7,
FLIGHT_LOG_FIELD_CONDITION_AT_LEAST_MOTORS_8,
FLIGHT_LOG_FIELD_CONDITION_TRICOPTER,
FLIGHT_LOG_FIELD_CONDITION_NEVER = 255,
} FlightLogFieldCondition;
typedef enum FlightLogFieldPredictor {
//No prediction:
FLIGHT_LOG_FIELD_PREDICTOR_0 = 0,
//Predict that the field is the same as last frame:
FLIGHT_LOG_FIELD_PREDICTOR_PREVIOUS = 1,
//Predict that the slope between this field and the previous item is the same as that between the past two history items:
FLIGHT_LOG_FIELD_PREDICTOR_STRAIGHT_LINE = 2,
//Predict that this field is the same as the average of the last two history items:
FLIGHT_LOG_FIELD_PREDICTOR_AVERAGE_2 = 3,
//Predict that this field is minthrottle
FLIGHT_LOG_FIELD_PREDICTOR_MINTHROTTLE = 4,
//Predict that this field is the same as motor 0
FLIGHT_LOG_FIELD_PREDICTOR_MOTOR_0 = 5,
//This field always increments
FLIGHT_LOG_FIELD_PREDICTOR_INC = 6,
//Predict this GPS co-ordinate is the GPS home co-ordinate (or no prediction if that coordinate is not set)
FLIGHT_LOG_FIELD_PREDICTOR_HOME_COORD = 7,
//Predict 1500
FLIGHT_LOG_FIELD_PREDICTOR_1500 = 8
} FlightLogFieldPredictor;
typedef enum FlightLogFieldEncoding {
FLIGHT_LOG_FIELD_ENCODING_SIGNED_VB = 0,
FLIGHT_LOG_FIELD_ENCODING_UNSIGNED_VB = 1,
FLIGHT_LOG_FIELD_ENCODING_TAG2_3S32 = 7,
FLIGHT_LOG_FIELD_ENCODING_TAG8_4S16 = 8,
FLIGHT_LOG_FIELD_ENCODING_NULL = 9
} FlightLogFieldEncoding;
typedef enum FlightLogFieldSign {
FLIGHT_LOG_FIELD_UNSIGNED = 0,
FLIGHT_LOG_FIELD_SIGNED = 1
} FlightLogFieldSign;