iNavFlight之MSP DJI协议分析

Posted lida2003

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iNavFlight之MSP DJI协议分析相关的知识,希望对你有一定的参考价值。

iNavFlight之MSP DJI协议分析

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. 参考资料

【1】iNav 2.4.0 Release Notes – Support for DJI HD FPV in Feb 6, 2020
【2】Multiwii Serial Protocol
【3】BetaFlight模块设计之三十二:MSP协议模块分析
【4】Multiwii Serial Protocol (MSP)

以上是关于iNavFlight之MSP DJI协议分析的主要内容,如果未能解决你的问题,请参考以下文章

iNavFlight之MSP DJI协议天空端请求报文

iNavFlight之RC遥控MSP协议

iNavFlight之MSP v2 Sensor报文格式

iNavFlight之RC遥控CRSF协议

BetaFlight模块设计之三十二:MSP协议模块分析

iNavFlight之电传MAVLink协议