static void applyLedWarningLayer(bool updateNow, timeUs_t *timer) { static uint8_t warningFlashCounter = 0; static uint8_t warningFlags = 0; // non-zero during blinks if (updateNow) { // keep counter running, so it stays in sync with blink warningFlashCounter++; warningFlashCounter &= 0xF; if (warningFlashCounter == 0) { // update when old flags was processed warningFlags = 0; if (batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE && getBatteryState() != BATTERY_OK) warningFlags |= 1 << WARNING_LOW_BATTERY; if (failsafeIsActive()) warningFlags |= 1 << WARNING_FAILSAFE; if (!ARMING_FLAG(ARMED) && isArmingDisabled()) warningFlags |= 1 << WARNING_ARMING_DISABLED; } *timer += HZ_TO_US(10); } const hsvColor_t *warningColor = NULL; if (warningFlags) { bool colorOn = (warningFlashCounter % 2) == 0; // w_w_ warningFlags_e warningId = warningFlashCounter / 4; if (warningFlags & (1 << warningId)) { switch (warningId) { case WARNING_ARMING_DISABLED: warningColor = colorOn ? &HSV(GREEN) : &HSV(BLACK); break; case WARNING_LOW_BATTERY: warningColor = colorOn ? &HSV(RED) : &HSV(BLACK); break; case WARNING_FAILSAFE: warningColor = colorOn ? &HSV(YELLOW) : &HSV(BLUE); break; default:; } } } else { if (isBeeperOn()) { warningColor = &HSV(ORANGE); } } if (warningColor) { applyLedHsv(LED_MOV_OVERLAY(LED_FLAG_OVERLAY(LED_OVERLAY_WARNING)), warningColor); } }
void tryArm(void) { if (armingConfig()->gyro_cal_on_first_arm) { gyroStartCalibration(true); } updateArmingStatus(); if (!isArmingDisabled()) { if (ARMING_FLAG(ARMED)) { return; } #ifdef USE_DSHOT if (micros() - getLastDshotBeaconCommandTimeUs() < DSHOT_BEACON_GUARD_DELAY_US) { if (tryingToArm == ARMING_DELAYED_DISARMED) { if (isModeActivationConditionPresent(BOXFLIPOVERAFTERCRASH) && IS_RC_MODE_ACTIVE(BOXFLIPOVERAFTERCRASH)) { tryingToArm = ARMING_DELAYED_CRASHFLIP; } else { tryingToArm = ARMING_DELAYED_NORMAL; } } return; } if (isMotorProtocolDshot() && isModeActivationConditionPresent(BOXFLIPOVERAFTERCRASH)) { if (!(IS_RC_MODE_ACTIVE(BOXFLIPOVERAFTERCRASH) || (tryingToArm == ARMING_DELAYED_CRASHFLIP))) { flipOverAfterCrashMode = false; if (!feature(FEATURE_3D)) { pwmWriteDshotCommand(ALL_MOTORS, getMotorCount(), DSHOT_CMD_SPIN_DIRECTION_NORMAL, false); } } else { flipOverAfterCrashMode = true; #ifdef USE_RUNAWAY_TAKEOFF runawayTakeoffCheckDisabled = false; #endif if (!feature(FEATURE_3D)) { pwmWriteDshotCommand(ALL_MOTORS, getMotorCount(), DSHOT_CMD_SPIN_DIRECTION_REVERSED, false); } } } #endif ENABLE_ARMING_FLAG(ARMED); ENABLE_ARMING_FLAG(WAS_EVER_ARMED); resetTryingToArm(); #ifdef USE_ACRO_TRAINER pidAcroTrainerInit(); #endif // USE_ACRO_TRAINER if (isModeActivationConditionPresent(BOXPREARM)) { ENABLE_ARMING_FLAG(WAS_ARMED_WITH_PREARM); } imuQuaternionHeadfreeOffsetSet(); disarmAt = millis() + armingConfig()->auto_disarm_delay * 1000; // start disarm timeout, will be extended when throttle is nonzero lastArmingDisabledReason = 0; //beep to indicate arming #ifdef USE_GPS if (feature(FEATURE_GPS) && STATE(GPS_FIX) && gpsSol.numSat >= 5) { beeper(BEEPER_ARMING_GPS_FIX); } else { beeper(BEEPER_ARMING); } #else beeper(BEEPER_ARMING); #endif #ifdef USE_RUNAWAY_TAKEOFF runawayTakeoffDeactivateUs = 0; runawayTakeoffAccumulatedUs = 0; runawayTakeoffTriggerUs = 0; #endif } else { resetTryingToArm(); if (!isFirstArmingGyroCalibrationRunning()) { int armingDisabledReason = ffs(getArmingDisableFlags()); if (lastArmingDisabledReason != armingDisabledReason) { lastArmingDisabledReason = armingDisabledReason; beeperWarningBeeps(armingDisabledReason); } } } }
static bool osdDrawSingleElement(uint8_t item) { if (!VISIBLE(osdConfig()->item_pos[item]) || BLINK(item)) { return false; } uint8_t elemPosX = OSD_X(osdConfig()->item_pos[item]); uint8_t elemPosY = OSD_Y(osdConfig()->item_pos[item]); char buff[OSD_ELEMENT_BUFFER_LENGTH] = ""; switch (item) { case OSD_RSSI_VALUE: { uint16_t osdRssi = getRssi() * 100 / 1024; // change range if (osdRssi >= 100) osdRssi = 99; tfp_sprintf(buff, "%c%2d", SYM_RSSI, osdRssi); break; } case OSD_MAIN_BATT_VOLTAGE: buff[0] = osdGetBatterySymbol(osdGetBatteryAverageCellVoltage()); tfp_sprintf(buff + 1, "%2d.%1d%c", getBatteryVoltage() / 10, getBatteryVoltage() % 10, SYM_VOLT); break; case OSD_CURRENT_DRAW: { const int32_t amperage = getAmperage(); tfp_sprintf(buff, "%3d.%02d%c", abs(amperage) / 100, abs(amperage) % 100, SYM_AMP); break; } case OSD_MAH_DRAWN: tfp_sprintf(buff, "%4d%c", getMAhDrawn(), SYM_MAH); break; #ifdef USE_GPS case OSD_GPS_SATS: tfp_sprintf(buff, "%c%c%2d", SYM_SAT_L, SYM_SAT_R, gpsSol.numSat); break; case OSD_GPS_SPEED: // FIXME ideally we want to use SYM_KMH symbol but it's not in the font any more, so we use K (M for MPH) switch (osdConfig()->units) { case OSD_UNIT_IMPERIAL: tfp_sprintf(buff, "%3dM", CM_S_TO_MPH(gpsSol.groundSpeed)); break; default: tfp_sprintf(buff, "%3dK", CM_S_TO_KM_H(gpsSol.groundSpeed)); break; } break; case OSD_GPS_LAT: // The SYM_LAT symbol in the actual font contains only blank, so we use the SYM_ARROW_NORTH osdFormatCoordinate(buff, SYM_ARROW_NORTH, gpsSol.llh.lat); break; case OSD_GPS_LON: // The SYM_LON symbol in the actual font contains only blank, so we use the SYM_ARROW_EAST osdFormatCoordinate(buff, SYM_ARROW_EAST, gpsSol.llh.lon); break; case OSD_HOME_DIR: if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) { if (GPS_distanceToHome > 0) { const int h = GPS_directionToHome - DECIDEGREES_TO_DEGREES(attitude.values.yaw); buff[0] = osdGetDirectionSymbolFromHeading(h); } else { // We don't have a HOME symbol in the font, by now we use this buff[0] = SYM_THR1; } } else { // We use this symbol when we don't have a FIX buff[0] = SYM_COLON; } buff[1] = 0; break; case OSD_HOME_DIST: if (STATE(GPS_FIX) && STATE(GPS_FIX_HOME)) { const int32_t distance = osdGetMetersToSelectedUnit(GPS_distanceToHome); tfp_sprintf(buff, "%d%c", distance, osdGetMetersToSelectedUnitSymbol()); } else { // We use this symbol when we don't have a FIX buff[0] = SYM_COLON; // overwrite any previous distance with blanks memset(buff + 1, SYM_BLANK, 6); buff[7] = '\0'; } break; #endif // GPS case OSD_COMPASS_BAR: memcpy(buff, compassBar + osdGetHeadingIntoDiscreteDirections(DECIDEGREES_TO_DEGREES(attitude.values.yaw), 16), 9); buff[9] = 0; break; case OSD_ALTITUDE: osdFormatAltitudeString(buff, getEstimatedAltitude()); break; case OSD_ITEM_TIMER_1: case OSD_ITEM_TIMER_2: osdFormatTimer(buff, true, true, item - OSD_ITEM_TIMER_1); break; case OSD_REMAINING_TIME_ESTIMATE: { const int mAhDrawn = getMAhDrawn(); const int remaining_time = (int)((osdConfig()->cap_alarm - mAhDrawn) * ((float)flyTime) / mAhDrawn); if (mAhDrawn < 0.1 * osdConfig()->cap_alarm) { tfp_sprintf(buff, "--:--"); } else if (mAhDrawn > osdConfig()->cap_alarm) { tfp_sprintf(buff, "00:00"); } else { osdFormatTime(buff, OSD_TIMER_PREC_SECOND, remaining_time); } break; } case OSD_FLYMODE: { if (FLIGHT_MODE(FAILSAFE_MODE)) { strcpy(buff, "!FS!"); } else if (FLIGHT_MODE(ANGLE_MODE)) { strcpy(buff, "STAB"); } else if (FLIGHT_MODE(HORIZON_MODE)) { strcpy(buff, "HOR "); } else if (FLIGHT_MODE(GPS_RESCUE_MODE)) { strcpy(buff, "RESC"); } else if (isAirmodeActive()) { strcpy(buff, "AIR "); } else { strcpy(buff, "ACRO"); } break; } case OSD_ANTI_GRAVITY: { if (pidItermAccelerator() > 1.0f) { strcpy(buff, "AG"); } break; } case OSD_CRAFT_NAME: // This does not strictly support iterative updating if the craft name changes at run time. But since the craft name is not supposed to be changing this should not matter, and blanking the entire length of the craft name string on update will make it impossible to configure elements to be displayed on the right hand side of the craft name. //TODO: When iterative updating is implemented, change this so the craft name is only printed once whenever the OSD 'flight' screen is entered. if (strlen(pilotConfig()->name) == 0) { strcpy(buff, "CRAFT_NAME"); } else { unsigned i; for (i = 0; i < MAX_NAME_LENGTH; i++) { if (pilotConfig()->name[i]) { buff[i] = toupper((unsigned char)pilotConfig()->name[i]); } else { break; } } buff[i] = '\0'; } break; case OSD_THROTTLE_POS: buff[0] = SYM_THR; buff[1] = SYM_THR1; tfp_sprintf(buff + 2, "%3d", (constrain(rcData[THROTTLE], PWM_RANGE_MIN, PWM_RANGE_MAX) - PWM_RANGE_MIN) * 100 / (PWM_RANGE_MAX - PWM_RANGE_MIN)); break; #if defined(USE_VTX_COMMON) case OSD_VTX_CHANNEL: { const char vtxBandLetter = vtx58BandLetter[vtxSettingsConfig()->band]; const char *vtxChannelName = vtx58ChannelNames[vtxSettingsConfig()->channel]; uint8_t vtxPower = vtxSettingsConfig()->power; const vtxDevice_t *vtxDevice = vtxCommonDevice(); if (vtxDevice && vtxSettingsConfig()->lowPowerDisarm) { vtxCommonGetPowerIndex(vtxDevice, &vtxPower); } tfp_sprintf(buff, "%c:%s:%1d", vtxBandLetter, vtxChannelName, vtxPower); break; } #endif case OSD_CROSSHAIRS: buff[0] = SYM_AH_CENTER_LINE; buff[1] = SYM_AH_CENTER; buff[2] = SYM_AH_CENTER_LINE_RIGHT; buff[3] = 0; break; case OSD_ARTIFICIAL_HORIZON: { // Get pitch and roll limits in tenths of degrees const int maxPitch = osdConfig()->ahMaxPitch * 10; const int maxRoll = osdConfig()->ahMaxRoll * 10; const int rollAngle = constrain(attitude.values.roll, -maxRoll, maxRoll); int pitchAngle = constrain(attitude.values.pitch, -maxPitch, maxPitch); // Convert pitchAngle to y compensation value // (maxPitch / 25) divisor matches previous settings of fixed divisor of 8 and fixed max AHI pitch angle of 20.0 degrees pitchAngle = ((pitchAngle * 25) / maxPitch) - 41; // 41 = 4 * AH_SYMBOL_COUNT + 5 for (int x = -4; x <= 4; x++) { const int y = ((-rollAngle * x) / 64) - pitchAngle; if (y >= 0 && y <= 81) { displayWriteChar(osdDisplayPort, elemPosX + x, elemPosY + (y / AH_SYMBOL_COUNT), (SYM_AH_BAR9_0 + (y % AH_SYMBOL_COUNT))); } } return true; } case OSD_HORIZON_SIDEBARS: { // Draw AH sides const int8_t hudwidth = AH_SIDEBAR_WIDTH_POS; const int8_t hudheight = AH_SIDEBAR_HEIGHT_POS; for (int y = -hudheight; y <= hudheight; y++) { displayWriteChar(osdDisplayPort, elemPosX - hudwidth, elemPosY + y, SYM_AH_DECORATION); displayWriteChar(osdDisplayPort, elemPosX + hudwidth, elemPosY + y, SYM_AH_DECORATION); } // AH level indicators displayWriteChar(osdDisplayPort, elemPosX - hudwidth + 1, elemPosY, SYM_AH_LEFT); displayWriteChar(osdDisplayPort, elemPosX + hudwidth - 1, elemPosY, SYM_AH_RIGHT); return true; } case OSD_ROLL_PIDS: osdFormatPID(buff, "ROL", ¤tPidProfile->pid[PID_ROLL]); break; case OSD_PITCH_PIDS: osdFormatPID(buff, "PIT", ¤tPidProfile->pid[PID_PITCH]); break; case OSD_YAW_PIDS: osdFormatPID(buff, "YAW", ¤tPidProfile->pid[PID_YAW]); break; case OSD_POWER: tfp_sprintf(buff, "%4dW", getAmperage() * getBatteryVoltage() / 1000); break; case OSD_PIDRATE_PROFILE: tfp_sprintf(buff, "%d-%d", getCurrentPidProfileIndex() + 1, getCurrentControlRateProfileIndex() + 1); break; case OSD_WARNINGS: { #define OSD_WARNINGS_MAX_SIZE 11 #define OSD_FORMAT_MESSAGE_BUFFER_SIZE (OSD_WARNINGS_MAX_SIZE + 1) STATIC_ASSERT(OSD_FORMAT_MESSAGE_BUFFER_SIZE <= sizeof(buff), osd_warnings_size_exceeds_buffer_size); const batteryState_e batteryState = getBatteryState(); if (osdWarnGetState(OSD_WARNING_BATTERY_CRITICAL) && batteryState == BATTERY_CRITICAL) { osdFormatMessage(buff, OSD_FORMAT_MESSAGE_BUFFER_SIZE, " LAND NOW"); break; } #ifdef USE_ADC_INTERNAL uint8_t coreTemperature = getCoreTemperatureCelsius(); if (osdWarnGetState(OSD_WARNING_CORE_TEMPERATURE) && coreTemperature >= osdConfig()->core_temp_alarm) { char coreTemperatureWarningMsg[OSD_FORMAT_MESSAGE_BUFFER_SIZE]; tfp_sprintf(coreTemperatureWarningMsg, "CORE: %3d%c", osdConvertTemperatureToSelectedUnit(getCoreTemperatureCelsius() * 10) / 10, osdGetTemperatureSymbolForSelectedUnit()); osdFormatMessage(buff, OSD_FORMAT_MESSAGE_BUFFER_SIZE, coreTemperatureWarningMsg); break; } #endif #ifdef USE_ESC_SENSOR // Show warning if we lose motor output, the ESC is overheating or excessive current draw if (feature(FEATURE_ESC_SENSOR) && osdWarnGetState(OSD_WARNING_ESC_FAIL)) { char escWarningMsg[OSD_FORMAT_MESSAGE_BUFFER_SIZE]; unsigned pos = 0; const char *title = "ESC"; // center justify message while (pos < (OSD_WARNINGS_MAX_SIZE - (strlen(title) + getMotorCount())) / 2) { escWarningMsg[pos++] = ' '; } strcpy(escWarningMsg + pos, title); pos += strlen(title); unsigned i = 0; unsigned escWarningCount = 0; while (i < getMotorCount() && pos < OSD_FORMAT_MESSAGE_BUFFER_SIZE - 1) { escSensorData_t *escData = getEscSensorData(i); const char motorNumber = '1' + i; // if everything is OK just display motor number else R, T or C char warnFlag = motorNumber; if (ARMING_FLAG(ARMED) && osdConfig()->esc_rpm_alarm != ESC_RPM_ALARM_OFF && calcEscRpm(escData->rpm) <= osdConfig()->esc_rpm_alarm) { warnFlag = 'R'; } if (osdConfig()->esc_temp_alarm != ESC_TEMP_ALARM_OFF && escData->temperature >= osdConfig()->esc_temp_alarm) { warnFlag = 'T'; } if (ARMING_FLAG(ARMED) && osdConfig()->esc_current_alarm != ESC_CURRENT_ALARM_OFF && escData->current >= osdConfig()->esc_current_alarm) { warnFlag = 'C'; } escWarningMsg[pos++] = warnFlag; if (warnFlag != motorNumber) { escWarningCount++; } i++; } escWarningMsg[pos] = '\0'; if (escWarningCount > 0) { osdFormatMessage(buff, OSD_FORMAT_MESSAGE_BUFFER_SIZE, escWarningMsg); } break; } #endif // Warn when in flip over after crash mode if (osdWarnGetState(OSD_WARNING_CRASH_FLIP) && isFlipOverAfterCrashMode()) { osdFormatMessage(buff, OSD_FORMAT_MESSAGE_BUFFER_SIZE, "CRASH FLIP"); break; } // Show most severe reason for arming being disabled if (osdWarnGetState(OSD_WARNING_ARMING_DISABLE) && IS_RC_MODE_ACTIVE(BOXARM) && isArmingDisabled()) { const armingDisableFlags_e flags = getArmingDisableFlags(); for (int i = 0; i < ARMING_DISABLE_FLAGS_COUNT; i++) { if (flags & (1 << i)) { osdFormatMessage(buff, OSD_FORMAT_MESSAGE_BUFFER_SIZE, armingDisableFlagNames[i]); break; } } break; } if (osdWarnGetState(OSD_WARNING_BATTERY_WARNING) && batteryState == BATTERY_WARNING) { osdFormatMessage(buff, OSD_FORMAT_MESSAGE_BUFFER_SIZE, "LOW BATTERY"); break; } // Show warning if battery is not fresh if (osdWarnGetState(OSD_WARNING_BATTERY_NOT_FULL) && !ARMING_FLAG(WAS_EVER_ARMED) && (getBatteryState() == BATTERY_OK) && getBatteryAverageCellVoltage() < batteryConfig()->vbatfullcellvoltage) { osdFormatMessage(buff, OSD_FORMAT_MESSAGE_BUFFER_SIZE, "BATT < FULL"); break; } // Visual beeper if (osdWarnGetState(OSD_WARNING_VISUAL_BEEPER) && showVisualBeeper) { osdFormatMessage(buff, OSD_FORMAT_MESSAGE_BUFFER_SIZE, " * * * *"); break; } osdFormatMessage(buff, OSD_FORMAT_MESSAGE_BUFFER_SIZE, NULL); break; } case OSD_AVG_CELL_VOLTAGE: { const int cellV = osdGetBatteryAverageCellVoltage(); buff[0] = osdGetBatterySymbol(cellV); tfp_sprintf(buff + 1, "%d.%02d%c", cellV / 100, cellV % 100, SYM_VOLT); break; } case OSD_DEBUG: tfp_sprintf(buff, "DBG %5d %5d %5d %5d", debug[0], debug[1], debug[2], debug[3]); break; case OSD_PITCH_ANGLE: case OSD_ROLL_ANGLE: { const int angle = (item == OSD_PITCH_ANGLE) ? attitude.values.pitch : attitude.values.roll; tfp_sprintf(buff, "%c%02d.%01d", angle < 0 ? '-' : ' ', abs(angle / 10), abs(angle % 10)); break; } case OSD_MAIN_BATT_USAGE: { // Set length of indicator bar #define MAIN_BATT_USAGE_STEPS 11 // Use an odd number so the bar can be centered. // Calculate constrained value const float value = constrain(batteryConfig()->batteryCapacity - getMAhDrawn(), 0, batteryConfig()->batteryCapacity); // Calculate mAh used progress const uint8_t mAhUsedProgress = ceilf((value / (batteryConfig()->batteryCapacity / MAIN_BATT_USAGE_STEPS))); // Create empty battery indicator bar buff[0] = SYM_PB_START; for (int i = 1; i <= MAIN_BATT_USAGE_STEPS; i++) { buff[i] = i <= mAhUsedProgress ? SYM_PB_FULL : SYM_PB_EMPTY; } buff[MAIN_BATT_USAGE_STEPS + 1] = SYM_PB_CLOSE; if (mAhUsedProgress > 0 && mAhUsedProgress < MAIN_BATT_USAGE_STEPS) { buff[1 + mAhUsedProgress] = SYM_PB_END; } buff[MAIN_BATT_USAGE_STEPS+2] = '\0'; break; } case OSD_DISARMED: if (!ARMING_FLAG(ARMED)) { tfp_sprintf(buff, "DISARMED"); } else { if (!lastArmState) { // previously disarmed - blank out the message one time tfp_sprintf(buff, " "); } } break; case OSD_NUMERICAL_HEADING: { const int heading = DECIDEGREES_TO_DEGREES(attitude.values.yaw); tfp_sprintf(buff, "%c%03d", osdGetDirectionSymbolFromHeading(heading), heading); break; } case OSD_NUMERICAL_VARIO: { const int verticalSpeed = osdGetMetersToSelectedUnit(getEstimatedVario()); const char directionSymbol = verticalSpeed < 0 ? SYM_ARROW_SOUTH : SYM_ARROW_NORTH; tfp_sprintf(buff, "%c%01d.%01d", directionSymbol, abs(verticalSpeed / 100), abs((verticalSpeed % 100) / 10)); break; } #ifdef USE_ESC_SENSOR case OSD_ESC_TMP: if (feature(FEATURE_ESC_SENSOR)) { tfp_sprintf(buff, "%3d%c", osdConvertTemperatureToSelectedUnit(escDataCombined->temperature * 10) / 10, osdGetTemperatureSymbolForSelectedUnit()); } break; case OSD_ESC_RPM: if (feature(FEATURE_ESC_SENSOR)) { tfp_sprintf(buff, "%5d", escDataCombined == NULL ? 0 : calcEscRpm(escDataCombined->rpm)); } break; #endif #ifdef USE_RTC_TIME case OSD_RTC_DATETIME: osdFormatRtcDateTime(&buff[0]); break; #endif #ifdef USE_OSD_ADJUSTMENTS case OSD_ADJUSTMENT_RANGE: tfp_sprintf(buff, "%s: %3d", adjustmentRangeName, adjustmentRangeValue); break; #endif #ifdef USE_ADC_INTERNAL case OSD_CORE_TEMPERATURE: tfp_sprintf(buff, "%3d%c", osdConvertTemperatureToSelectedUnit(getCoreTemperatureCelsius() * 10) / 10, osdGetTemperatureSymbolForSelectedUnit()); break; #endif default: return false; } displayWrite(osdDisplayPort, elemPosX, elemPosY, buff); return true; }
void updateArmingStatus(void) { if (ARMING_FLAG(ARMED)) { LED0_ON; } else { // Check if the power on arming grace time has elapsed if ((getArmingDisableFlags() & ARMING_DISABLED_BOOT_GRACE_TIME) && (millis() >= systemConfig()->powerOnArmingGraceTime * 1000)) { // If so, unset the grace time arming disable flag unsetArmingDisabled(ARMING_DISABLED_BOOT_GRACE_TIME); } // Clear the crash flip active status flipOverAfterCrashMode = false; // If switch is used for arming then check it is not defaulting to on when the RX link recovers from a fault if (!isUsingSticksForArming()) { static bool hadRx = false; const bool haveRx = rxIsReceivingSignal(); const bool justGotRxBack = !hadRx && haveRx; if (justGotRxBack && IS_RC_MODE_ACTIVE(BOXARM)) { // If the RX has just started to receive a signal again and the arm switch is on, apply arming restriction setArmingDisabled(ARMING_DISABLED_BAD_RX_RECOVERY); } else if (haveRx && !IS_RC_MODE_ACTIVE(BOXARM)) { // If RX signal is OK and the arm switch is off, remove arming restriction unsetArmingDisabled(ARMING_DISABLED_BAD_RX_RECOVERY); } hadRx = haveRx; } if (IS_RC_MODE_ACTIVE(BOXFAILSAFE)) { setArmingDisabled(ARMING_DISABLED_BOXFAILSAFE); } else { unsetArmingDisabled(ARMING_DISABLED_BOXFAILSAFE); } if (calculateThrottleStatus() != THROTTLE_LOW) { setArmingDisabled(ARMING_DISABLED_THROTTLE); } else { unsetArmingDisabled(ARMING_DISABLED_THROTTLE); } if (!STATE(SMALL_ANGLE) && !IS_RC_MODE_ACTIVE(BOXFLIPOVERAFTERCRASH)) { setArmingDisabled(ARMING_DISABLED_ANGLE); } else { unsetArmingDisabled(ARMING_DISABLED_ANGLE); } if (averageSystemLoadPercent > 100) { setArmingDisabled(ARMING_DISABLED_LOAD); } else { unsetArmingDisabled(ARMING_DISABLED_LOAD); } if (isCalibrating()) { setArmingDisabled(ARMING_DISABLED_CALIBRATING); } else { unsetArmingDisabled(ARMING_DISABLED_CALIBRATING); } if (isModeActivationConditionPresent(BOXPREARM)) { if (IS_RC_MODE_ACTIVE(BOXPREARM) && !ARMING_FLAG(WAS_ARMED_WITH_PREARM)) { unsetArmingDisabled(ARMING_DISABLED_NOPREARM); } else { setArmingDisabled(ARMING_DISABLED_NOPREARM); } } #ifdef USE_GPS_RESCUE if (isModeActivationConditionPresent(BOXGPSRESCUE)) { if (!gpsRescueConfig()->minSats || STATE(GPS_FIX_HOME) || ARMING_FLAG(WAS_EVER_ARMED)) { unsetArmingDisabled(ARMING_DISABLED_GPS); } else { setArmingDisabled(ARMING_DISABLED_GPS); } } #endif if (IS_RC_MODE_ACTIVE(BOXPARALYZE)) { setArmingDisabled(ARMING_DISABLED_PARALYZE); } if (!isUsingSticksForArming()) { /* Ignore ARMING_DISABLED_CALIBRATING if we are going to calibrate gyro on first arm */ bool ignoreGyro = armingConfig()->gyro_cal_on_first_arm && !(getArmingDisableFlags() & ~(ARMING_DISABLED_ARM_SWITCH | ARMING_DISABLED_CALIBRATING)); /* Ignore ARMING_DISABLED_THROTTLE (once arm switch is on) if we are in 3D mode */ bool ignoreThrottle = feature(FEATURE_3D) && !IS_RC_MODE_ACTIVE(BOX3D) && !flight3DConfig()->switched_mode3d && !(getArmingDisableFlags() & ~(ARMING_DISABLED_ARM_SWITCH | ARMING_DISABLED_THROTTLE)); #ifdef USE_RUNAWAY_TAKEOFF if (!IS_RC_MODE_ACTIVE(BOXARM)) { unsetArmingDisabled(ARMING_DISABLED_RUNAWAY_TAKEOFF); } #endif // If arming is disabled and the ARM switch is on if (isArmingDisabled() && !ignoreGyro && !ignoreThrottle && IS_RC_MODE_ACTIVE(BOXARM)) { setArmingDisabled(ARMING_DISABLED_ARM_SWITCH); } else if (!IS_RC_MODE_ACTIVE(BOXARM)) { unsetArmingDisabled(ARMING_DISABLED_ARM_SWITCH); } } if (isArmingDisabled()) { warningLedFlash(); } else { warningLedDisable(); } warningLedUpdate(); } }