2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
26 #include "build/debug.h"
28 #include "common/maths.h"
29 #include "common/filter.h"
31 #include "config/config_reset.h"
32 #include "config/parameter_group.h"
33 #include "config/parameter_group_ids.h"
35 #include "drivers/adc.h"
36 #include "drivers/time.h"
38 #include "fc/config.h"
39 #include "fc/controlrate_profile.h"
40 #include "fc/fc_core.h"
41 #include "fc/runtime_config.h"
43 #include "fc/settings.h"
45 #include "flight/imu.h"
46 #include "flight/mixer.h"
48 #include "navigation/navigation.h"
49 #include "navigation/navigation_private.h"
51 #include "config/feature.h"
53 #include "sensors/battery.h"
54 #include "sensors/esc_sensor.h"
58 #include "fc/rc_controls.h"
60 #include "io/beeper.h"
62 #if defined(USE_FAKE_BATT_SENSOR)
63 #include "sensors/battery_sensor_fake.h"
66 #define ADCVREF 3300 // in mV (3300 = 3.3V)
68 #define VBATT_CELL_FULL_MAX_DIFF 10 // Max difference with cell max voltage for the battery to be considered full (10mV steps)
69 #define VBATT_PRESENT_THRESHOLD 220 // Minimum voltage to consider battery present
70 #define VBATT_STABLE_DELAY 40 // Delay after connecting battery to begin monitoring
71 #define VBATT_HYSTERESIS 10 // Batt Hysteresis of +/-100mV for changing battery state
72 #define VBATT_LPF_FREQ 1 // Battery voltage filtering cutoff
73 #define AMPERAGE_LPF_FREQ 1 // Battery current filtering cutoff
74 #define IMPEDANCE_STABLE_SAMPLE_COUNT_THRESH 10 // Minimum sample count to consider calculated power supply impedance as stable
77 // Battery monitoring stuff
78 static uint8_t batteryCellCount
; // cell count
79 static uint16_t batteryFullVoltage
;
80 static uint16_t batteryWarningVoltage
;
81 static uint16_t batteryCriticalVoltage
;
82 static uint32_t batteryRemainingCapacity
= 0;
83 static bool batteryUseCapacityThresholds
= false;
84 static bool batteryFullWhenPluggedIn
= false;
85 static bool profileAutoswitchDisable
= false;
87 static uint16_t vbat
= 0; // battery voltage in 0.01V steps (filtered)
88 static uint16_t powerSupplyImpedance
= 0; // calculated impedance in milliohm
89 static uint16_t sagCompensatedVBat
= 0; // calculated no load vbat
90 static bool powerSupplyImpedanceIsValid
= false;
92 static int16_t amperage
= 0; // amperage read by current sensor in centiampere (1/100th A)
93 static int32_t power
= 0; // power draw in cW (0.01W resolution)
94 static int32_t mAhDrawn
= 0; // milliampere hours drawn from the battery since start
95 static int32_t mWhDrawn
= 0; // energy (milliWatt hours) drawn from the battery since start
97 batteryState_e batteryState
;
98 const batteryProfile_t
*currentBatteryProfile
;
100 PG_REGISTER_ARRAY_WITH_RESET_FN(batteryProfile_t
, MAX_BATTERY_PROFILE_COUNT
, batteryProfiles
, PG_BATTERY_PROFILES
, 3);
102 void pgResetFn_batteryProfiles(batteryProfile_t
*instance
)
104 for (int i
= 0; i
< MAX_BATTERY_PROFILE_COUNT
; i
++) {
105 RESET_CONFIG(batteryProfile_t
, &instance
[i
],
107 .cells
= SETTING_BAT_CELLS_DEFAULT
,
110 .cellDetect
= SETTING_VBAT_CELL_DETECT_VOLTAGE_DEFAULT
,
111 .cellMax
= SETTING_VBAT_MAX_CELL_VOLTAGE_DEFAULT
,
112 .cellMin
= SETTING_VBAT_MIN_CELL_VOLTAGE_DEFAULT
,
113 .cellWarning
= SETTING_VBAT_WARNING_CELL_VOLTAGE_DEFAULT
118 .value
= SETTING_BATTERY_CAPACITY_DEFAULT
,
119 .warning
= SETTING_BATTERY_CAPACITY_WARNING_DEFAULT
,
120 .critical
= SETTING_BATTERY_CAPACITY_CRITICAL_DEFAULT
,
123 .controlRateProfile
= 0,
126 .throttleIdle
= SETTING_THROTTLE_IDLE_DEFAULT
,
127 .throttleScale
= SETTING_THROTTLE_SCALE_DEFAULT
,
129 .turtleModePowerFactor
= SETTING_TURTLE_MODE_POWER_FACTOR_DEFAULT
,
133 .failsafe_throttle
= SETTING_FAILSAFE_THROTTLE_DEFAULT
, // default throttle off.
137 .hover_throttle
= SETTING_NAV_MC_HOVER_THR_DEFAULT
,
141 .cruise_throttle
= SETTING_NAV_FW_CRUISE_THR_DEFAULT
,
142 .max_throttle
= SETTING_NAV_FW_MAX_THR_DEFAULT
,
143 .min_throttle
= SETTING_NAV_FW_MIN_THR_DEFAULT
,
144 .pitch_to_throttle
= SETTING_NAV_FW_PITCH2THR_DEFAULT
, // pwm units per degree of pitch (10pwm units ~ 1% throttle)
145 .launch_throttle
= SETTING_NAV_FW_LAUNCH_THR_DEFAULT
,
146 .launch_idle_throttle
= SETTING_NAV_FW_LAUNCH_IDLE_THR_DEFAULT
, // Motor idle or MOTOR_STOP
150 #if defined(USE_POWER_LIMITS)
152 .continuousCurrent
= SETTING_LIMIT_CONT_CURRENT_DEFAULT
, // dA
153 .burstCurrent
= SETTING_LIMIT_BURST_CURRENT_DEFAULT
, // dA
154 .burstCurrentTime
= SETTING_LIMIT_BURST_CURRENT_TIME_DEFAULT
, // dS
155 .burstCurrentFalldownTime
= SETTING_LIMIT_BURST_CURRENT_FALLDOWN_TIME_DEFAULT
, // dS
157 .continuousPower
= SETTING_LIMIT_CONT_POWER_DEFAULT
, // dW
158 .burstPower
= SETTING_LIMIT_BURST_POWER_DEFAULT
, // dW
159 .burstPowerTime
= SETTING_LIMIT_BURST_POWER_TIME_DEFAULT
, // dS
160 .burstPowerFalldownTime
= SETTING_LIMIT_BURST_POWER_FALLDOWN_TIME_DEFAULT
, // dS
163 #endif // USE_POWER_LIMITS
169 PG_REGISTER_WITH_RESET_TEMPLATE(batteryMetersConfig_t
, batteryMetersConfig
, PG_BATTERY_METERS_CONFIG
, 2);
171 PG_RESET_TEMPLATE(batteryMetersConfig_t
, batteryMetersConfig
,
175 .type
= SETTING_VBAT_METER_TYPE_DEFAULT
,
176 .scale
= VBAT_SCALE_DEFAULT
,
181 .type
= SETTING_CURRENT_METER_TYPE_DEFAULT
,
182 .scale
= CURRENT_METER_SCALE
,
183 .offset
= CURRENT_METER_OFFSET
186 .voltageSource
= SETTING_BAT_VOLTAGE_SRC_DEFAULT
,
188 .capacity_unit
= SETTING_BATTERY_CAPACITY_UNIT_DEFAULT
,
190 .cruise_power
= SETTING_CRUISE_POWER_DEFAULT
,
191 .idle_power
= SETTING_IDLE_POWER_DEFAULT
,
192 .rth_energy_margin
= SETTING_RTH_ENERGY_MARGIN_DEFAULT
,
194 .throttle_compensation_weight
= SETTING_THR_COMP_WEIGHT_DEFAULT
198 void batteryInit(void)
200 batteryState
= BATTERY_NOT_PRESENT
;
201 batteryCellCount
= 0;
202 batteryFullVoltage
= 0;
203 batteryWarningVoltage
= 0;
204 batteryCriticalVoltage
= 0;
208 // profileDetect() profile sorting compare function
209 static int profile_compare(profile_comp_t
*a
, profile_comp_t
*b
) {
210 if (a
->max_voltage
< b
->max_voltage
)
212 else if (a
->max_voltage
> b
->max_voltage
)
218 // Find profile matching plugged battery for profile_autoselect
219 static int8_t profileDetect(void) {
220 profile_comp_t profile_comp_array
[MAX_BATTERY_PROFILE_COUNT
];
222 // Prepare profile sort
223 for (uint8_t i
= 0; i
< MAX_BATTERY_PROFILE_COUNT
; ++i
) {
224 const batteryProfile_t
*profile
= batteryProfiles(i
);
225 profile_comp_array
[i
].profile_index
= i
;
226 profile_comp_array
[i
].max_voltage
= profile
->cells
* profile
->voltage
.cellDetect
;
229 // Sort profiles by max voltage
230 qsort(profile_comp_array
, MAX_BATTERY_PROFILE_COUNT
, sizeof(*profile_comp_array
), (int (*)(const void *, const void *))profile_compare
);
232 // Return index of the first profile where vbat <= profile_max_voltage
233 for (uint8_t i
= 0; i
< MAX_BATTERY_PROFILE_COUNT
; ++i
)
234 if ((profile_comp_array
[i
].max_voltage
> 0) && (vbat
<= profile_comp_array
[i
].max_voltage
))
235 return profile_comp_array
[i
].profile_index
;
237 // No matching profile found
242 void setBatteryProfile(uint8_t profileIndex
)
244 if (profileIndex
>= MAX_BATTERY_PROFILE_COUNT
) {
247 currentBatteryProfile
= batteryProfiles(profileIndex
);
248 if ((currentBatteryProfile
->controlRateProfile
> 0) && (currentBatteryProfile
->controlRateProfile
< MAX_CONTROL_RATE_PROFILE_COUNT
)) {
249 setConfigProfile(currentBatteryProfile
->controlRateProfile
- 1);
253 void activateBatteryProfile(void)
255 static int8_t previous_battery_profile_index
= -1;
256 // Don't call batteryInit if the battery profile was not changed to prevent batteryCellCount to be reset while adjusting board alignment
257 // causing the beeper to be silent when it is disabled while the board is connected through USB (beeper -ON_USB)
258 if (systemConfig()->current_battery_profile_index
!= previous_battery_profile_index
) {
260 previous_battery_profile_index
= systemConfig()->current_battery_profile_index
;
265 static void updateBatteryVoltage(timeUs_t timeDelta
, bool justConnected
)
267 static pt1Filter_t vbatFilterState
;
269 switch (batteryMetersConfig()->voltage
.type
) {
270 case VOLTAGE_SENSOR_ADC
:
272 vbat
= getVBatSample();
275 #if defined(USE_ESC_SENSOR)
276 case VOLTAGE_SENSOR_ESC
:
278 escSensorData_t
* escSensor
= escSensorGetData();
279 if (escSensor
&& escSensor
->dataAge
<= ESC_DATA_MAX_AGE
) {
280 vbat
= escSensor
->voltage
;
289 #if defined(USE_FAKE_BATT_SENSOR)
290 case VOLTAGE_SENSOR_FAKE
:
291 vbat
= fakeBattSensorGetVBat();
294 case VOLTAGE_SENSOR_NONE
:
301 if (ARMING_FLAG(SIMULATOR_MODE_HITL
) && SIMULATOR_HAS_OPTION(HITL_SIMULATE_BATTERY
)) {
302 vbat
= ((uint16_t)simulatorData
.vbat
)*10;
308 pt1FilterReset(&vbatFilterState
, vbat
);
310 vbat
= pt1FilterApply4(&vbatFilterState
, vbat
, VBATT_LPF_FREQ
, US2S(timeDelta
));
314 batteryState_e
checkBatteryVoltageState(void)
316 uint16_t stateVoltage
= getBatteryVoltage();
317 switch (batteryState
)
320 if (stateVoltage
<= (batteryWarningVoltage
- VBATT_HYSTERESIS
)) {
321 return BATTERY_WARNING
;
324 case BATTERY_WARNING
:
325 if (stateVoltage
<= (batteryCriticalVoltage
- VBATT_HYSTERESIS
)) {
326 return BATTERY_CRITICAL
;
327 } else if (stateVoltage
> (batteryWarningVoltage
+ VBATT_HYSTERESIS
)){
331 case BATTERY_CRITICAL
:
332 if (stateVoltage
> (batteryCriticalVoltage
+ VBATT_HYSTERESIS
)) {
333 return BATTERY_WARNING
;
343 static void checkBatteryCapacityState(void)
345 if (batteryRemainingCapacity
== 0) {
346 batteryState
= BATTERY_CRITICAL
;
347 } else if (batteryRemainingCapacity
<= currentBatteryProfile
->capacity
.warning
- currentBatteryProfile
->capacity
.critical
) {
348 batteryState
= BATTERY_WARNING
;
352 void batteryUpdate(timeUs_t timeDelta
)
354 /* battery has just been connected*/
355 if (batteryState
== BATTERY_NOT_PRESENT
&& vbat
> VBATT_PRESENT_THRESHOLD
) {
357 /* Actual battery state is calculated below, this is really BATTERY_PRESENT */
358 batteryState
= BATTERY_OK
;
359 /* wait for VBatt to stabilise then we can calc number of cells
360 (using the filtered value takes a long time to ramp up)
361 We only do this on the ground so don't care if we do block, not
362 worse than original code anyway*/
363 delay(VBATT_STABLE_DELAY
);
364 updateBatteryVoltage(timeDelta
, true);
366 int8_t detectedProfileIndex
= -1;
367 if (feature(FEATURE_BAT_PROFILE_AUTOSWITCH
) && (!profileAutoswitchDisable
))
368 detectedProfileIndex
= profileDetect();
370 if (detectedProfileIndex
!= -1) {
371 systemConfigMutable()->current_battery_profile_index
= detectedProfileIndex
;
372 setBatteryProfile(detectedProfileIndex
);
373 batteryCellCount
= currentBatteryProfile
->cells
;
374 } else if (currentBatteryProfile
->cells
> 0)
375 batteryCellCount
= currentBatteryProfile
->cells
;
377 batteryCellCount
= (vbat
/ currentBatteryProfile
->voltage
.cellDetect
) + 1;
378 // Assume there are no 7S, 9S and 11S batteries so round up to 8S, 10S and 12S respectively
379 if (batteryCellCount
== 7 || batteryCellCount
== 9 || batteryCellCount
== 11) {
380 batteryCellCount
+= 1;
382 batteryCellCount
= MIN(batteryCellCount
, 12);
385 batteryFullVoltage
= batteryCellCount
* currentBatteryProfile
->voltage
.cellMax
;
386 batteryWarningVoltage
= batteryCellCount
* currentBatteryProfile
->voltage
.cellWarning
;
387 batteryCriticalVoltage
= batteryCellCount
* currentBatteryProfile
->voltage
.cellMin
;
389 batteryFullWhenPluggedIn
= vbat
>= (batteryFullVoltage
- batteryCellCount
* VBATT_CELL_FULL_MAX_DIFF
);
390 batteryUseCapacityThresholds
= isAmperageConfigured() && batteryFullWhenPluggedIn
&& (currentBatteryProfile
->capacity
.value
> 0) &&
391 (currentBatteryProfile
->capacity
.warning
> 0) && (currentBatteryProfile
->capacity
.critical
> 0);
394 updateBatteryVoltage(timeDelta
, false);
396 /* battery has been disconnected - can take a while for filter cap to disharge so we use a threshold of VBATT_PRESENT_THRESHOLD */
397 if (batteryState
!= BATTERY_NOT_PRESENT
&& vbat
<= VBATT_PRESENT_THRESHOLD
) {
398 batteryState
= BATTERY_NOT_PRESENT
;
399 batteryCellCount
= 0;
400 batteryWarningVoltage
= 0;
401 batteryCriticalVoltage
= 0;
405 if (batteryState
!= BATTERY_NOT_PRESENT
) {
407 if ((currentBatteryProfile
->capacity
.value
> 0) && batteryFullWhenPluggedIn
) {
408 uint32_t capacityDiffBetweenFullAndEmpty
= currentBatteryProfile
->capacity
.value
- currentBatteryProfile
->capacity
.critical
;
409 int32_t drawn
= (batteryMetersConfig()->capacity_unit
== BAT_CAPACITY_UNIT_MWH
? mWhDrawn
: mAhDrawn
);
410 batteryRemainingCapacity
= (drawn
> (int32_t)capacityDiffBetweenFullAndEmpty
? 0 : capacityDiffBetweenFullAndEmpty
- drawn
);
413 if (batteryUseCapacityThresholds
) {
414 checkBatteryCapacityState();
416 batteryState
= checkBatteryVoltageState();
420 if (ARMING_FLAG(ARMED
) || !ARMING_FLAG(WAS_EVER_ARMED
) || failsafeIsActive())
421 switch (batteryState
) {
422 case BATTERY_WARNING
:
423 beeper(BEEPER_BAT_LOW
);
425 case BATTERY_CRITICAL
:
426 beeper(BEEPER_BAT_CRIT_LOW
);
435 batteryState_e
getBatteryState(void)
440 bool batteryWasFullWhenPluggedIn(void)
442 return batteryFullWhenPluggedIn
;
445 bool batteryUsesCapacityThresholds(void)
447 return batteryUseCapacityThresholds
;
450 bool isBatteryVoltageConfigured(void)
452 return feature(FEATURE_VBAT
);
456 uint16_t getVBatSample(void) {
457 // calculate battery voltage based on ADC reading
458 // result is Vbatt in 0.01V steps. 3.3V = ADC Vref, 0xFFF = 12bit adc, 1100 = 11:1 voltage divider (10k:1k)
459 return (uint64_t)adcGetChannel(ADC_BATTERY
) * batteryMetersConfig()->voltage
.scale
* ADCVREF
/ (0xFFF * 1000);
463 uint16_t getBatteryVoltage(void)
465 if (batteryMetersConfig()->voltageSource
== BAT_VOLTAGE_SAG_COMP
) {
466 return sagCompensatedVBat
;
472 uint16_t getBatteryRawVoltage(void)
477 uint16_t getBatterySagCompensatedVoltage(void)
479 return sagCompensatedVBat
;
482 float calculateThrottleCompensationFactor(void)
484 return 1.0f
+ ((float)batteryFullVoltage
/ sagCompensatedVBat
- 1.0f
) * batteryMetersConfig()->throttle_compensation_weight
;
487 uint8_t getBatteryCellCount(void)
489 return batteryCellCount
;
492 uint16_t getBatteryAverageCellVoltage(void)
494 if (batteryCellCount
> 0) {
495 return getBatteryVoltage() / batteryCellCount
;
500 uint16_t getBatteryRawAverageCellVoltage(void)
502 if (batteryCellCount
> 0) {
503 return vbat
/ batteryCellCount
;
508 uint16_t getBatterySagCompensatedAverageCellVoltage(void)
510 if (batteryCellCount
> 0) {
511 return sagCompensatedVBat
/ batteryCellCount
;
516 uint32_t getBatteryRemainingCapacity(void)
518 return batteryRemainingCapacity
;
521 bool isAmperageConfigured(void)
523 return feature(FEATURE_CURRENT_METER
) && batteryMetersConfig()->current
.type
!= CURRENT_SENSOR_NONE
;
526 int16_t getAmperage(void)
531 int16_t getAmperageSample(void)
533 int32_t microvolts
= ((uint32_t)adcGetChannel(ADC_CURRENT
) * ADCVREF
* 100) / 0xFFF * 10 - (int32_t)batteryMetersConfig()->current
.offset
* 100;
534 return microvolts
/ batteryMetersConfig()->current
.scale
; // current in 0.01A steps
537 int32_t getPower(void)
542 int32_t getMAhDrawn(void)
547 int32_t getMWhDrawn(void)
552 void currentMeterUpdate(timeUs_t timeDelta
)
554 static pt1Filter_t amperageFilterState
;
555 static int64_t mAhdrawnRaw
= 0;
557 switch (batteryMetersConfig()->current
.type
) {
558 case CURRENT_SENSOR_ADC
:
560 amperage
= pt1FilterApply4(&erageFilterState
, getAmperageSample(), AMPERAGE_LPF_FREQ
, US2S(timeDelta
));
563 case CURRENT_SENSOR_VIRTUAL
:
564 amperage
= batteryMetersConfig()->current
.offset
;
565 if (ARMING_FLAG(ARMED
)) {
566 navigationFSMStateFlags_t stateFlags
= navGetCurrentStateFlags();
567 bool allNav
= navConfig()->general
.flags
.nav_overrides_motor_stop
== NOMS_ALL_NAV
&& posControl
.navState
!= NAV_STATE_IDLE
;
568 bool autoNav
= navConfig()->general
.flags
.nav_overrides_motor_stop
== NOMS_AUTO_ONLY
&& (stateFlags
& (NAV_AUTO_RTH
| NAV_AUTO_WP
));
569 int32_t throttleOffset
;
571 if (allNav
|| autoNav
) { // account for motors running in Nav modes with throttle low + motor stop
572 throttleOffset
= (int32_t)rcCommand
[THROTTLE
] - 1000;
574 throttleOffset
= (throttleStickIsLow() && ifMotorstopFeatureEnabled()) ? 0 : (int32_t)rcCommand
[THROTTLE
] - 1000;
576 int32_t throttleFactor
= throttleOffset
+ (throttleOffset
* throttleOffset
/ 50);
577 amperage
+= throttleFactor
* batteryMetersConfig()->current
.scale
/ 1000;
580 #if defined(USE_ESC_SENSOR)
581 case CURRENT_SENSOR_ESC
:
583 escSensorData_t
* escSensor
= escSensorGetData();
584 if (escSensor
&& escSensor
->dataAge
<= ESC_DATA_MAX_AGE
) {
585 amperage
= pt1FilterApply4(&erageFilterState
, escSensor
->current
, AMPERAGE_LPF_FREQ
, US2S(timeDelta
));
594 #if defined(USE_FAKE_BATT_SENSOR)
595 case CURRENT_SENSOR_FAKE
:
596 amperage
= fakeBattSensorGetAmerperage();
599 case CURRENT_SENSOR_NONE
:
605 // Clamp amperage to positive values
606 amperage
= MAX(0, amperage
);
608 // Work around int64 math compiler bug, don't change it unless the bug has been fixed !
609 // should be: mAhdrawnRaw += (int64_t)amperage * timeDelta / 1000;
610 mAhdrawnRaw
+= (int64_t)((int32_t)amperage
* timeDelta
) / 1000;
612 mAhDrawn
= mAhdrawnRaw
/ (3600 * 100);
615 void powerMeterUpdate(timeUs_t timeDelta
)
617 static int64_t mWhDrawnRaw
= 0;
618 power
= (int32_t)amperage
* vbat
/ 100; // power unit is cW (0.01W resolution)
619 int32_t heatLossesCompensatedPower_mW
= (int32_t)amperage
* vbat
/ 10 + sq((int64_t)amperage
) * powerSupplyImpedance
/ 10000;
621 // Work around int64 math compiler bug, don't change it unless the bug has been fixed !
622 // should be: mWhDrawnRaw += (int64_t)heatLossesCompensatedPower_mW * timeDelta / 10000;
623 mWhDrawnRaw
+= (int64_t)((int64_t)heatLossesCompensatedPower_mW
* timeDelta
) / 10000;
625 mWhDrawn
= mWhDrawnRaw
/ (3600 * 100);
628 // calculate true power including heat losses in power supply (battery + wires)
629 // power is in cW (0.01W)
630 // batteryWarningVoltage is in cV (0.01V)
631 int32_t heatLossesCompensatedPower(int32_t power
)
633 return power
+ sq(power
* 100 / batteryWarningVoltage
) * powerSupplyImpedance
/ 100000;
636 void sagCompensatedVBatUpdate(timeUs_t currentTime
, timeUs_t timeDelta
)
638 static timeUs_t recordTimestamp
= 0;
639 static int16_t amperageRecord
;
640 static uint16_t vbatRecord
;
641 static uint8_t impedanceSampleCount
= 0;
642 static pt1Filter_t impedanceFilterState
;
643 static pt1Filter_t sagCompVBatFilterState
;
644 static batteryState_e last_battery_state
= BATTERY_NOT_PRESENT
;
646 if ((batteryState
!= BATTERY_NOT_PRESENT
) && (last_battery_state
== BATTERY_NOT_PRESENT
)) {
647 pt1FilterReset(&sagCompVBatFilterState
, vbat
);
648 pt1FilterReset(&impedanceFilterState
, 0);
651 if (batteryState
== BATTERY_NOT_PRESENT
) {
654 impedanceSampleCount
= 0;
655 powerSupplyImpedance
= 0;
656 powerSupplyImpedanceIsValid
= false;
657 sagCompensatedVBat
= vbat
;
661 if (cmpTimeUs(currentTime
, recordTimestamp
) > MS2US(500))
664 if (!recordTimestamp
) {
665 amperageRecord
= amperage
;
667 recordTimestamp
= currentTime
;
668 } else if ((amperage
- amperageRecord
>= 200) && ((int16_t)vbatRecord
- vbat
>= 4)) {
670 uint16_t impedanceSample
= (int32_t)(vbatRecord
- vbat
) * 1000 / (amperage
- amperageRecord
);
672 if (impedanceSampleCount
<= IMPEDANCE_STABLE_SAMPLE_COUNT_THRESH
) {
673 impedanceSampleCount
+= 1;
676 if (impedanceFilterState
.state
) {
677 pt1FilterSetTimeConstant(&impedanceFilterState
, impedanceSampleCount
> IMPEDANCE_STABLE_SAMPLE_COUNT_THRESH
? 1.2 : 0.5);
678 pt1FilterApply3(&impedanceFilterState
, impedanceSample
, US2S(timeDelta
));
680 pt1FilterReset(&impedanceFilterState
, impedanceSample
);
683 if (impedanceSampleCount
> IMPEDANCE_STABLE_SAMPLE_COUNT_THRESH
) {
684 powerSupplyImpedance
= lrintf(pt1FilterGetLastOutput(&impedanceFilterState
));
685 powerSupplyImpedanceIsValid
= true;
690 uint16_t sagCompensatedVBatSample
= MIN(batteryFullVoltage
, vbat
+ (int32_t)powerSupplyImpedance
* amperage
/ 1000);
691 pt1FilterSetTimeConstant(&sagCompVBatFilterState
, sagCompensatedVBatSample
< pt1FilterGetLastOutput(&sagCompVBatFilterState
) ? 40 : 500);
692 sagCompensatedVBat
= lrintf(pt1FilterApply3(&sagCompVBatFilterState
, sagCompensatedVBatSample
, US2S(timeDelta
)));
695 DEBUG_SET(DEBUG_SAG_COMP_VOLTAGE
, 0, powerSupplyImpedance
);
696 DEBUG_SET(DEBUG_SAG_COMP_VOLTAGE
, 1, sagCompensatedVBat
);
698 last_battery_state
= batteryState
;
701 uint8_t calculateBatteryPercentage(void)
703 if (batteryState
== BATTERY_NOT_PRESENT
)
706 if (batteryFullWhenPluggedIn
&& isAmperageConfigured() && (currentBatteryProfile
->capacity
.value
> 0) && (currentBatteryProfile
->capacity
.critical
> 0)) {
707 uint32_t capacityDiffBetweenFullAndEmpty
= currentBatteryProfile
->capacity
.value
- currentBatteryProfile
->capacity
.critical
;
708 return constrain(batteryRemainingCapacity
* 100 / capacityDiffBetweenFullAndEmpty
, 0, 100);
710 return constrain((getBatteryVoltage() - batteryCriticalVoltage
) * 100L / (batteryFullVoltage
- batteryCriticalVoltage
), 0, 100);
713 void batteryDisableProfileAutoswitch(void) {
714 profileAutoswitchDisable
= true;
717 bool isPowerSupplyImpedanceValid(void) {
718 return powerSupplyImpedanceIsValid
;
721 uint16_t getPowerSupplyImpedance(void) {
722 return powerSupplyImpedance
;
725 // returns cW (0.01W)
726 int32_t calculateAveragePower(void) {
727 return (int64_t)mWhDrawn
* 360 / getFlightTime();
730 // returns mWh / meter
731 int32_t calculateAverageEfficiency(void) {
732 return getFlyingEnergy() * 100 / getTotalTravelDistance();