iNavFlight之MSP DJI协议分析

2023-05-16

iNavFlight之MSP DJI协议分析

  • 1. iNav串行口通信
    • 1.1 iNav 串口任务
    • 1.2 调用逻辑
  • 2. iNav串行抽象
    • 2.1 框架代码
    • 2.2 MSP(DJI)协议处理
  • 3. DJI协议相关实现
    • 3.1 DJI串口初始化
    • 3.2 DJI命令集
    • 3.3. DJI相关函数
  • 4. 协议格式
  • 5. 参考资料

MSP DJI协议主要是为了解决如何将飞控内部信息传送到DJI数字图传,进而在DJI的数字系统的视频上叠加飞控OSD信息。

注:目前模拟图传的做法是通过MAX7456芯片将模拟VI和飞控

1. iNav串行口通信

1.1 iNav 串口任务

taskHandleSerial是STM32任务之一。

    [TASK_SERIAL] = {
        .taskName = "SERIAL",
        .taskFunc = taskHandleSerial,
        .desiredPeriod = TASK_PERIOD_HZ(100),     // 100 Hz should be enough to flush up to 115 bytes @ 115200 baud
        .staticPriority = TASK_PRIORITY_LOW,
    },

1.2 调用逻辑

taskHandleSerial是专门集中处理串口的任务。其中djiOsdSerialProcess是与DJI天空端通信的实现代码入口。

void taskHandleSerial(timeUs_t currentTimeUs)
{
    UNUSED(currentTimeUs);
    // in cli mode, all serial stuff goes to here. enter cli mode by sending #
    if (cliMode) {
        cliProcess();
    }

    // Allow MSP processing even if in CLI mode
    mspSerialProcess(ARMING_FLAG(ARMED) ? MSP_SKIP_NON_MSP_DATA : MSP_EVALUATE_NON_MSP_DATA, mspFcProcessCommand);

#if defined(USE_DJI_HD_OSD)
    // DJI OSD uses a special flavour of MSP (subset of Betaflight 4.1.1 MSP) - process as part of serial task
    djiOsdSerialProcess();
#endif

#ifdef USE_MSP_OSD
	// Capture MSP Displayport messages to determine if VTX is connected
    mspOsdSerialProcess(mspFcProcessCommand);
#endif
}

mspSerialProcess是正常的MSP控制协议函数实现。

void mspSerialProcess(mspEvaluateNonMspData_e evaluateNonMspData, mspProcessCommandFnPtr mspProcessCommandFn)
{
    for (uint8_t portIndex = 0; portIndex < MAX_MSP_PORT_COUNT; portIndex++) {
        mspPort_t * const mspPort = &mspPorts[portIndex];
        if (mspPort->port) {
            mspSerialProcessOnePort(mspPort, evaluateNonMspData, mspProcessCommandFn);
        }
    }
}

djiOsdSerialProcess是专门用于处理与DJI天空端通信的函数实现。

void djiOsdSerialProcess(void)
{
    // Check if DJI OSD is configured
    if (!djiMspPort.port) {
        return;
    }

    // Piggyback on existing MSP protocol stack, but pass our special command processing function
    mspSerialProcessOnePort(&djiMspPort, MSP_SKIP_NON_MSP_DATA, djiProcessMspCommand);
}

这段代码据说没有使能,详见:how to open MSP_DISPLAYPORT #6415。但是从代码上看是有编译到的,详见:#define USE_MSP_OSD,因此应该是特殊配置的FUNCTION_MSP_OSD端口(且不是DJI的)。

#ifdef USE_MSP_OSD
	// Capture MSP Displayport messages to determine if VTX is connected
    mspOsdSerialProcess(mspFcProcessCommand);
#endif

2. iNav串行抽象

从该代码实现角度看,是典型的C/S模型。

通用框架mspSerialProcessOnePort主要是提供收包,异常处理,解析,调用命令处理流程和报文反馈,其主要差异在于命令处理流程部分。

2.1 框架代码

mspSerialProcessOnePort框架代码是MSP协议的通用实现,其主要差异在于命令码处理的mspProcessCommandFn函数。

  1. 当MSP_SKIP_NON_MSP_DATA,用mspProcessCommandFn处理;

a) djiProcessMspCommand: MSP(DJI)协议处理
b) mspFcProcessCommand: MSP(Control)协议处理

  1. 当MSP_EVALUATE_NON_MSP_DATA,mspEvaluateNonMspData;

命令行模式

void mspSerialProcessOnePort(mspPort_t * const mspPort, mspEvaluateNonMspData_e evaluateNonMspData, mspProcessCommandFnPtr mspProcessCommandFn)
{
    mspPostProcessFnPtr mspPostProcessFn = NULL;

    if (serialRxBytesWaiting(mspPort->port)) {
        // There are bytes incoming - abort pending request
        mspPort->lastActivityMs = millis();
        mspPort->pendingRequest = MSP_PENDING_NONE;

        // Process incoming bytes
        while (serialRxBytesWaiting(mspPort->port)) {
            const uint8_t c = serialRead(mspPort->port);
            const bool consumed = mspSerialProcessReceivedData(mspPort, c);

            if (!consumed && evaluateNonMspData == MSP_EVALUATE_NON_MSP_DATA) {
                mspEvaluateNonMspData(mspPort, c);
            }

            if (mspPort->c_state == MSP_COMMAND_RECEIVED) {
                mspPostProcessFn = mspSerialProcessReceivedCommand(mspPort, mspProcessCommandFn);
                break; // process one command at a time so as not to block.
            }
        }

        if (mspPostProcessFn) {
            waitForSerialPortToFinishTransmitting(mspPort->port);
            mspPostProcessFn(mspPort->port);
        }
    }
    else {
        mspProcessPendingRequest(mspPort);
    }
}

2.2 MSP(DJI)协议处理

DJI天空端发出请求,FC接收到请求后反馈请求结果,详见:djiProcessMspCommand。

static mspResult_e djiProcessMspCommand(mspPacket_t *cmd, mspPacket_t *reply, mspPostProcessFnPtr *mspPostProcessFn)
{
    UNUSED(mspPostProcessFn);

    sbuf_t *dst = &reply->buf;
    sbuf_t *src = &cmd->buf;

    // Start initializing the reply message
    reply->cmd = cmd->cmd;
    reply->result = MSP_RESULT_ACK;

    switch (cmd->cmd) {
        case DJI_MSP_API_VERSION:
            sbufWriteU8(dst, MSP_PROTOCOL_VERSION);
            sbufWriteU8(dst, DJI_API_VERSION_MAJOR);
            sbufWriteU8(dst, DJI_API_VERSION_MINOR);
            break;

        case DJI_MSP_FC_VARIANT:
            {
                const char * const flightControllerIdentifier = INAV_IDENTIFIER;
                sbufWriteData(dst, flightControllerIdentifier, FLIGHT_CONTROLLER_IDENTIFIER_LENGTH);
            }
            break;

        case DJI_MSP_FC_VERSION:
            sbufWriteU8(dst, 4);
            sbufWriteU8(dst, 1);
            sbufWriteU8(dst, 0);
            break;

        case DJI_MSP_NAME:
            {
#if defined(USE_OSD)
                if (djiOsdConfig()->use_name_for_messages)  {
                    djiSerializeCraftNameOverride(dst);
                } else {
#endif
                    sbufWriteData(dst, systemConfig()->name, (int)strlen(systemConfig()->name));
#if defined(USE_OSD)
                }
#endif

                break;
            }
            break;

        case DJI_MSP_STATUS:
        case DJI_MSP_STATUS_EX:
            {
                // DJI OSD relies on a statically defined bit order and doesn't use MSP_BOXIDS
                // to get actual BOX order. We need a special packBoxModeFlags()
                boxBitmask_t flightModeBitmask;
                djiPackBoxModeBitmask(&flightModeBitmask);

                sbufWriteU16(dst, (uint16_t)cycleTime);
                sbufWriteU16(dst, 0);
                sbufWriteU16(dst, packSensorStatus());
                sbufWriteData(dst, &flightModeBitmask, 4);        // unconditional part of flags, first 32 bits
                sbufWriteU8(dst, getConfigProfile());

                sbufWriteU16(dst, constrain(averageSystemLoadPercent, 0, 100));
                if (cmd->cmd == MSP_STATUS_EX) {
                    sbufWriteU8(dst, 3);            // PID_PROFILE_COUNT
                    sbufWriteU8(dst, 1);            // getCurrentControlRateProfileIndex()
                } else {
                    sbufWriteU16(dst, cycleTime);   // gyro cycle time
                }

                // Cap BoxModeFlags to 32 bits
                // write flightModeFlags header. Lowest 4 bits contain number of bytes that follow
                sbufWriteU8(dst, 0);
                // sbufWriteData(dst, ((uint8_t*)&flightModeBitmask) + 4, byteCount);

                // Write arming disable flags
                sbufWriteU8(dst, DJI_ARMING_DISABLE_FLAGS_COUNT);
                sbufWriteU32(dst, djiPackArmingDisabledFlags());

                // Extra flags
                sbufWriteU8(dst, 0);
            }
            break;

        case DJI_MSP_RC:
            // Only send sticks (first 4 channels)
            for (int i = 0; i < STICK_CHANNEL_COUNT; i++) {
                sbufWriteU16(dst, rxGetChannelValue(i));
            }
            break;

        case DJI_MSP_RAW_GPS:
            sbufWriteU8(dst, gpsSol.fixType);
            sbufWriteU8(dst, gpsSol.numSat);
            sbufWriteU32(dst, gpsSol.llh.lat);
            sbufWriteU32(dst, gpsSol.llh.lon);
            sbufWriteU16(dst, gpsSol.llh.alt / 100);
            sbufWriteU16(dst, osdGetSpeedFromSelectedSource());
            sbufWriteU16(dst, gpsSol.groundCourse);
            break;

        case DJI_MSP_COMP_GPS:
            sbufWriteU16(dst, GPS_distanceToHome);
            sbufWriteU16(dst, GPS_directionToHome);
            sbufWriteU8(dst, gpsSol.flags.gpsHeartbeat ? 1 : 0);
            break;

        case DJI_MSP_ATTITUDE:
            sbufWriteU16(dst, attitude.values.roll);
            sbufWriteU16(dst, attitude.values.pitch);
            sbufWriteU16(dst, DECIDEGREES_TO_DEGREES(attitude.values.yaw));
            break;

        case DJI_MSP_ALTITUDE:
            sbufWriteU32(dst, lrintf(getEstimatedActualPosition(Z)));
            sbufWriteU16(dst, lrintf(getEstimatedActualVelocity(Z)));
            break;

        case DJI_MSP_ANALOG:
            sbufWriteU8(dst,  constrain(getBatteryVoltage() / 10, 0, 255));
            sbufWriteU16(dst, constrain(getMAhDrawn(), 0, 0xFFFF)); // milliamp hours drawn from battery
#ifdef USE_SERIALRX_CRSF
            // Range of RSSI field: 0-99: 99 = 150 hz , 0 - 98 50 hz / 4 hz
            if (djiOsdConfig()->rssi_source == DJI_CRSF_LQ) {
                uint16_t scaledLq = 0;
                if (rxLinkStatistics.rfMode >= 2) {
                    scaledLq = RSSI_MAX_VALUE;
                } else {
                    scaledLq = scaleRange(constrain(rxLinkStatistics.uplinkLQ, 0, 100), 0, 100, 0, RSSI_BOUNDARY(98));
                }
                sbufWriteU16(dst, scaledLq);
            } else {
#endif
                sbufWriteU16(dst, getRSSI());
#ifdef USE_SERIALRX_CRSF
            }
#endif
            sbufWriteU16(dst, constrain(getAmperage(), -0x8000, 0x7FFF)); // send amperage in 0.01 A steps, range is -320A to 320A
            sbufWriteU16(dst, getBatteryVoltage());
            break;

        case DJI_MSP_PID:
            for (unsigned i = 0; i < ARRAYLEN(djiPidIndexMap); i++) {
                sbufWriteU8(dst, pidBank()->pid[djiPidIndexMap[i]].P);
                sbufWriteU8(dst, pidBank()->pid[djiPidIndexMap[i]].I);
                sbufWriteU8(dst, pidBank()->pid[djiPidIndexMap[i]].D);
            }
            break;

        case DJI_MSP_BATTERY_STATE:
            // Battery characteristics
            sbufWriteU8(dst, constrain(getBatteryCellCount(), 0, 255));
            sbufWriteU16(dst, currentBatteryProfile->capacity.value);

            // Battery state
            sbufWriteU8(dst, constrain(getBatteryVoltage() / 10, 0, 255)); // in 0.1V steps
            sbufWriteU16(dst, constrain(getMAhDrawn(), 0, 0xFFFF));
            sbufWriteU16(dst, constrain(getAmperage(), -0x8000, 0x7FFF));

            // Battery alerts - used values match Betaflight's/DJI's
            sbufWriteU8(dst,  getBatteryState());

            // Additional battery voltage field (in 0.01V steps)
            sbufWriteU16(dst, getBatteryVoltage());
            break;

        case DJI_MSP_RTC:
            {
                dateTime_t datetime;

                // We don't care about validity here - dt will be always set to a sane value
                // rtcGetDateTime() will call rtcGetDefaultDateTime() internally
                rtcGetDateTime(&datetime);

                sbufWriteU16(dst, datetime.year);
                sbufWriteU8(dst, datetime.month);
                sbufWriteU8(dst, datetime.day);
                sbufWriteU8(dst, datetime.hours);
                sbufWriteU8(dst, datetime.minutes);
                sbufWriteU8(dst, datetime.seconds);
                sbufWriteU16(dst, datetime.millis);
            }
            break;

        case DJI_MSP_ESC_SENSOR_DATA:
            if (djiOsdConfig()->proto_workarounds & DJI_OSD_USE_NON_STANDARD_MSP_ESC_SENSOR_DATA) {
                // Version 1.00.06 of DJI firmware is not using the standard MSP_ESC_SENSOR_DATA
                uint16_t protoRpm = 0;
                int16_t protoTemp = 0;

#if defined(USE_ESC_SENSOR)
                if (STATE(ESC_SENSOR_ENABLED) && getMotorCount() > 0) {
                    uint32_t motorRpmAcc = 0;
                    int32_t motorTempAcc = 0;

                    for (int i = 0; i < getMotorCount(); i++) {
                        const escSensorData_t * escSensor = getEscTelemetry(i);
                        motorRpmAcc += escSensor->rpm;
                        motorTempAcc += escSensor->temperature;
                    }

                    protoRpm = motorRpmAcc / getMotorCount();
                    protoTemp = motorTempAcc / getMotorCount();
                }
#endif

                switch (djiOsdConfig()->esc_temperature_source) {
                    // This is ESC temperature (as intended)
                    case DJI_OSD_TEMP_ESC:
                        // No-op, temperature is already set to ESC
                        break;

                    // Re-purpose the field for core temperature
                    case DJI_OSD_TEMP_CORE:
                        getIMUTemperature(&protoTemp);
                        protoTemp = protoTemp / 10;
                        break;

                    // Re-purpose the field for baro temperature
                    case DJI_OSD_TEMP_BARO:
                        getBaroTemperature(&protoTemp);
                        protoTemp = protoTemp / 10;
                        break;
                }

                // No motor count, just raw temp and RPM data
                sbufWriteU8(dst, protoTemp);
                sbufWriteU16(dst, protoRpm);
            }
            else {
                // Use standard MSP_ESC_SENSOR_DATA message
                sbufWriteU8(dst, getMotorCount());
                for (int i = 0; i < getMotorCount(); i++) {
                    uint16_t motorRpm = 0;
                    int16_t motorTemp = 0;

                    // If ESC_SENSOR is enabled, pull the telemetry data and get motor RPM
#if defined(USE_ESC_SENSOR)
                    if (STATE(ESC_SENSOR_ENABLED)) {
                        const escSensorData_t * escSensor = getEscTelemetry(i);
                        motorRpm = escSensor->rpm;
                        motorTemp = escSensor->temperature;
                    }
#endif

                    // Now populate temperature field (which we may override for different purposes)
                    switch (djiOsdConfig()->esc_temperature_source) {
                        // This is ESC temperature (as intended)
                        case DJI_OSD_TEMP_ESC:
                            // No-op, temperature is already set to ESC
                            break;

                        // Re-purpose the field for core temperature
                        case DJI_OSD_TEMP_CORE:
                            getIMUTemperature(&motorTemp);
                            motorTemp = motorTemp / 10;
                            break;

                        // Re-purpose the field for baro temperature
                        case DJI_OSD_TEMP_BARO:
                            getBaroTemperature(&motorTemp);
                            motorTemp = motorTemp / 10;
                            break;
                    }

                    // Add data for this motor to the packet
                    sbufWriteU8(dst, motorTemp);
                    sbufWriteU16(dst, motorRpm);
                }
            }
            break;

        case DJI_MSP_OSD_CONFIG:
#if defined(USE_OSD)
            // This involved some serious magic, better contain in a separate function for readability
            djiSerializeOSDConfigReply(dst);
#else
            sbufWriteU8(dst, 0);
#endif
            break;

        case DJI_MSP_FILTER_CONFIG:
            sbufWriteU8(dst, gyroConfig()->gyro_main_lpf_hz);           // BF: gyroConfig()->gyro_lowpass_hz
            sbufWriteU16(dst, pidProfile()->dterm_lpf_hz);              // BF: currentPidProfile->dterm_lowpass_hz
            sbufWriteU16(dst, pidProfile()->yaw_lpf_hz);                // BF: currentPidProfile->yaw_lowpass_hz
            sbufWriteU16(dst, 0);                                       // BF: gyroConfig()->gyro_soft_notch_hz_1
            sbufWriteU16(dst, 1);                                       // BF: gyroConfig()->gyro_soft_notch_cutoff_1
            sbufWriteU16(dst, 0);                                       // BF: currentPidProfile->dterm_notch_hz
            sbufWriteU16(dst, 1);                                       // BF: currentPidProfile->dterm_notch_cutoff
            sbufWriteU16(dst, 0);                                       // BF: gyroConfig()->gyro_soft_notch_hz_2
            sbufWriteU16(dst, 1);                                       // BF: gyroConfig()->gyro_soft_notch_cutoff_2
            sbufWriteU8(dst, 0);                                        // BF: currentPidProfile->dterm_filter_type
            sbufWriteU8(dst, gyroConfig()->gyro_lpf);                   // BF: gyroConfig()->gyro_hardware_lpf);
            sbufWriteU8(dst, 0);                                        // BF: DEPRECATED: gyro_32khz_hardware_lpf
            sbufWriteU16(dst, gyroConfig()->gyro_main_lpf_hz);          // BF: gyroConfig()->gyro_lowpass_hz);
            sbufWriteU16(dst, 0);                                       // BF: gyroConfig()->gyro_lowpass2_hz);
            sbufWriteU8(dst, 0);                                        // BF: gyroConfig()->gyro_lowpass_type);
            sbufWriteU8(dst, 0);                                        // BF: gyroConfig()->gyro_lowpass2_type);
            sbufWriteU16(dst, 0);                                       // BF: currentPidProfile->dterm_lowpass2_hz);
            sbufWriteU8(dst, 0);                                        // BF: currentPidProfile->dterm_filter2_type);
            break;

        case DJI_MSP_RC_TUNING:
            sbufWriteU8(dst, 100);                                      // INAV doesn't use rcRate
            sbufWriteU8(dst, currentControlRateProfile->stabilized.rcExpo8);
            for (int i = 0 ; i < 3; i++) {
                // R,P,Y rates see flight_dynamics_index_t
                sbufWriteU8(dst, currentControlRateProfile->stabilized.rates[i]);
            }
            sbufWriteU8(dst, currentControlRateProfile->throttle.dynPID);
            sbufWriteU8(dst, currentControlRateProfile->throttle.rcMid8);
            sbufWriteU8(dst, currentControlRateProfile->throttle.rcExpo8);
            sbufWriteU16(dst, currentControlRateProfile->throttle.pa_breakpoint);
            sbufWriteU8(dst, currentControlRateProfile->stabilized.rcYawExpo8);
            sbufWriteU8(dst, 100);                                      // INAV doesn't use rcRate
            sbufWriteU8(dst, 100);                                      // INAV doesn't use rcRate
            sbufWriteU8(dst, currentControlRateProfile->stabilized.rcExpo8);

            // added in 1.41
            sbufWriteU8(dst, 0);
            sbufWriteU8(dst, currentControlRateProfile->throttle.dynPID);
            break;

        case DJI_MSP_SET_PID:
            // Check if we have enough data for all PID coefficients
            if ((unsigned)sbufBytesRemaining(src) >= ARRAYLEN(djiPidIndexMap) * 3) {
                for (unsigned i = 0; i < ARRAYLEN(djiPidIndexMap); i++) {
                    pidBankMutable()->pid[djiPidIndexMap[i]].P = sbufReadU8(src);
                    pidBankMutable()->pid[djiPidIndexMap[i]].I = sbufReadU8(src);
                    pidBankMutable()->pid[djiPidIndexMap[i]].D = sbufReadU8(src);
                }
                schedulePidGainsUpdate();
            }
            else {
                reply->result = MSP_RESULT_ERROR;
            }
            break;

        case DJI_MSP_PID_ADVANCED:
            // TODO
            reply->result = MSP_RESULT_ERROR;
            break;

        case DJI_MSP_SET_FILTER_CONFIG:
        case DJI_MSP_SET_PID_ADVANCED:
        case DJI_MSP_SET_RC_TUNING:
            // TODO
            reply->result = MSP_RESULT_ERROR;
            break;

        default:
            // debug[1]++;
            // debug[2] = cmd->cmd;
            reply->result = MSP_RESULT_ERROR;
            break;
    }

    // Process DONT_REPLY flag
    if (cmd->flags & MSP_FLAG_DONT_REPLY) {
        reply->result = MSP_RESULT_NO_REPLY;
    }

    return reply->result;
}

3. DJI协议相关实现

3.1 DJI串口初始化

main
 └──> init
     └──> djiOsdSerialInit

3.2 DJI命令集

最新DJI命令集请查阅这里。

#define DJI_MSP_API_VERSION             1       // INAV: Implemented     | DSI: ???             | 
#define DJI_MSP_FC_VARIANT              2       // INAV: Implemented     | DSI: ???             | 
#define DJI_MSP_FC_VERSION              3       // INAV: Implemented     | DSI: ???             | 
#define DJI_MSP_NAME                    10      // INAV: Implemented     | DSI: Implemented     | For OSD 'Craft Name'
#define DJI_MSP_OSD_CONFIG              84      // INAV: Implemented     | DSI: Implemented     | OSD item count + positions
#define DJI_MSP_FILTER_CONFIG           92      // INAV: Not implemented | DSI: Implemented     |
#define DJI_MSP_PID_ADVANCED            94      // INAV: Not implemented | DSI: Implemented     |
#define DJI_MSP_STATUS                  101     // INAV: Implemented     | DSI: Implemented     | For OSD ‘armingTime’, Flight controller arming status
#define DJI_MSP_RC                      105     // INAV: Implemented     | DSI: Implemented     |
#define DJI_MSP_RAW_GPS                 106     // INAV: Implemented     | DSI: Implemented     | For OSD ‘GPS Sats’ + coordinates
#define DJI_MSP_COMP_GPS                107     // INAV: Implemented     | DSI: Not implemented | GPS direction to home & distance to home
#define DJI_MSP_ATTITUDE                108     // INAV: Implemented     | DSI: Implemented     | For OSD ‘Angle: roll & pitch’
#define DJI_MSP_ALTITUDE                109     // INAV: Implemented     | DSI: Implemented     | For OSD ‘Numerical Vario’
#define DJI_MSP_ANALOG                  110     // INAV: Implemented     | DSI: Implemented     | For OSD ‘RSSI Value’, For OSD ‘Battery voltage’ etc
#define DJI_MSP_RC_TUNING               111     // INAV: Not implemented | DSI: Implemented     |
#define DJI_MSP_PID                     112     // INAV: Implemented     | DSI: Implemented     | For OSD ‘PID roll, yaw, pitch'
#define DJI_MSP_BATTERY_STATE           130     // INAV: Implemented     | DSI: Implemented     | For OSD ‘Battery current mAh drawn’ etc
#define DJI_MSP_ESC_SENSOR_DATA         134     // INAV: Implemented     | DSI: Implemented     | For OSD ‘ESC temperature’
#define DJI_MSP_STATUS_EX               150     // INAV: Implemented     | DSI: Implemented     | For OSD ‘Fly mode', For OSD ‘Disarmed’
#define DJI_MSP_RTC                     247     // INAV: Implemented     | DSI: Implemented     | For OSD ‘RTC date time’

3.3. DJI相关函数

static void djiPackBoxModeBitmask(boxBitmask_t * flightModeBitmask)
static uint32_t djiPackArmingDisabledFlags(void)
static uint32_t djiEncodeOSDEnabledWarnings(void)  //TODO
static void djiSerializeOSDConfigReply(sbuf_t *dst)
static char * osdArmingDisabledReasonMessage(void)
static char * osdFailsafePhaseMessage(void)
static char * osdFailsafeInfoMessage(void)
static char * navigationStateMessage(void)
static int32_t osdConvertVelocityToUnit(int32_t vel)
void osdDJIFormatVelocityStr(char* buff)
static void osdDJIFormatThrottlePosition(char *buff, bool autoThr )
static void osdDJIFormatDistanceStr(char *buff, int32_t dist)
static void osdDJIEfficiencyMahPerKM(char *buff)
static void osdDJIAdjustmentMessage(char *buff, uint8_t adjustmentFunction)
static bool osdDJIFormatAdjustments(char *buff)
static bool djiFormatMessages(char *buff)
static void djiSerializeCraftNameOverride(sbuf_t *dst)

4. 协议格式

  +---+---+--------+---------+--------+------+---------+------------------------------+-------------+
  |                            Multiwii Serial Protocol V2                length = 9 + payload size |
  +---+---+--------+---------+--------+------+---------+------------------------------+-------------+
  | $ | X | < ! >  | flag(1) | cmd(2)        | size(2) | payload(16bit len)           | checksum_v2 |
  +---+---+--------+---------+--------+------+---------+------------------------------+-------------+
  • ‘$’:表示SOF(Start Of a frame)
  • ‘X’:表示V2
  • ‘<’: 表示request
  • ‘>’:表示response
  • ‘!’:表示error

5. 参考资料

【1】iNav 2.4.0 Release Notes – Support for DJI HD FPV in Feb 6, 2020
【2】Multiwii Serial Protocol
【3】Multiwii Serial Protocol (MSP)
【4】BetaFlight模块设计之三十二:MSP协议模块分析
【5】iNavFlight之MSP DJI协议天空端请求报文
【6】iNavFlight之MSP DJI协议飞控端请求应答

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iNavFlight之MSP DJI协议分析 的相关文章

随机推荐