2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
28 #include "build/debug.h"
30 #include "common/filter.h"
31 #include "common/maths.h"
32 #include "common/utils.h"
34 #include "config/config.h"
35 #include "config/feature.h"
37 #include "drivers/adc.h"
39 #include "fc/runtime_config.h"
40 #include "fc/rc_controls.h"
42 #include "flight/mixer.h"
43 #include "flight/mixer_init.h"
45 #include "io/beeper.h"
48 #include "pg/pg_ids.h"
50 #include "scheduler/scheduler.h"
51 #ifdef USE_BATTERY_CONTINUE
55 #include "sensors/battery.h"
58 * terminology: meter vs sensors
60 * voltage and current sensors are used to collect data.
61 * - e.g. voltage at an MCU ADC input pin, value from an ESC sensor.
62 * sensors require very specific configuration, such as resistor values.
63 * voltage and current meters are used to process and expose data collected from sensors to the rest of the system.
64 * - e.g. a meter exposes normalized, and often filtered, values from a sensor.
65 * meters require different or little configuration.
66 * meters also have different precision concerns, and may use different units to the sensors.
70 #define LVC_AFFECT_TIME 10000000 //10 secs for the LVC to slowly kick in
72 // Battery monitoring stuff
73 static uint8_t batteryCellCount
; // Note: this can be 0 when no battery is detected or when the battery voltage sensor is missing or disabled.
74 static uint16_t batteryWarningVoltage
;
75 static uint16_t batteryCriticalVoltage
;
76 static uint16_t batteryWarningHysteresisVoltage
;
77 static uint16_t batteryCriticalHysteresisVoltage
;
78 static lowVoltageCutoff_t lowVoltageCutoff
;
80 static currentMeter_t currentMeter
;
81 static voltageMeter_t voltageMeter
;
83 static batteryState_e batteryState
;
84 static batteryState_e voltageState
;
85 static batteryState_e consumptionState
;
86 static float wattHoursDrawn
;
88 #ifndef DEFAULT_CURRENT_METER_SOURCE
89 #ifdef USE_VIRTUAL_CURRENT_METER
90 #define DEFAULT_CURRENT_METER_SOURCE CURRENT_METER_VIRTUAL
92 #ifdef USE_MSP_CURRENT_METER
93 #define DEFAULT_CURRENT_METER_SOURCE CURRENT_METER_MSP
95 #define DEFAULT_CURRENT_METER_SOURCE CURRENT_METER_NONE
100 #ifndef DEFAULT_VOLTAGE_METER_SOURCE
101 #define DEFAULT_VOLTAGE_METER_SOURCE VOLTAGE_METER_NONE
104 PG_REGISTER_WITH_RESET_TEMPLATE(batteryConfig_t
, batteryConfig
, PG_BATTERY_CONFIG
, 3);
106 PG_RESET_TEMPLATE(batteryConfig_t
, batteryConfig
,
108 .vbatmaxcellvoltage
= VBAT_CELL_VOLTAGE_DEFAULT_MAX
,
109 .vbatmincellvoltage
= VBAT_CELL_VOLTAGE_DEFAULT_MIN
,
110 .vbatwarningcellvoltage
= 350,
111 .vbatnotpresentcellvoltage
= 300, //A cell below 3 will be ignored
112 .voltageMeterSource
= DEFAULT_VOLTAGE_METER_SOURCE
,
113 .lvcPercentage
= 100, //Off by default at 100%
116 .batteryCapacity
= 0,
117 .currentMeterSource
= DEFAULT_CURRENT_METER_SOURCE
,
120 .forceBatteryCellCount
= 0, //0 will be ignored
123 .useVBatAlerts
= true,
124 .useConsumptionAlerts
= false,
125 .consumptionWarningPercentage
= 10,
126 .vbathysteresis
= 1, // 0.01V
128 .vbatfullcellvoltage
= 410,
130 .vbatDisplayLpfPeriod
= 30,
131 .vbatSagLpfPeriod
= 2,
133 .vbatDurationForWarning
= 0,
134 .vbatDurationForCritical
= 0,
137 void batteryUpdateVoltage(timeUs_t currentTimeUs
)
139 UNUSED(currentTimeUs
);
141 switch (batteryConfig()->voltageMeterSource
) {
142 #ifdef USE_ESC_SENSOR
143 case VOLTAGE_METER_ESC
:
144 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
145 voltageMeterESCRefresh();
146 voltageMeterESCReadCombined(&voltageMeter
);
150 case VOLTAGE_METER_ADC
:
151 voltageMeterADCRefresh();
152 voltageMeterADCRead(VOLTAGE_SENSOR_ADC_VBAT
, &voltageMeter
);
156 case VOLTAGE_METER_NONE
:
157 voltageMeterReset(&voltageMeter
);
161 voltageStableUpdate(&voltageMeter
);
163 DEBUG_SET(DEBUG_BATTERY
, 0, voltageMeter
.unfiltered
);
164 DEBUG_SET(DEBUG_BATTERY
, 1, voltageMeter
.displayFiltered
);
165 DEBUG_SET(DEBUG_BATTERY
, 3, voltageMeter
.voltageStableBits
);
166 DEBUG_SET(DEBUG_BATTERY
, 4, voltageIsStable(&voltageMeter
) ? 1 : 0);
167 DEBUG_SET(DEBUG_BATTERY
, 5, isVoltageFromBattery() ? 1 : 0);
168 DEBUG_SET(DEBUG_BATTERY
, 6, voltageMeter
.voltageStablePrevFiltered
);
169 DEBUG_SET(DEBUG_BATTERY
, 7, voltageState
);
172 static void updateBatteryBeeperAlert(void)
174 switch (getBatteryState()) {
175 case BATTERY_WARNING
:
176 beeper(BEEPER_BAT_LOW
);
179 case BATTERY_CRITICAL
:
180 beeper(BEEPER_BAT_CRIT_LOW
);
184 case BATTERY_NOT_PRESENT
:
190 bool isVoltageFromBattery(void)
192 // We want to disable battery getting detected around USB voltage or 0V
194 return (voltageMeter
.displayFiltered
>= batteryConfig()->vbatnotpresentcellvoltage
// Above ~0V
195 && voltageMeter
.displayFiltered
<= batteryConfig()->vbatmaxcellvoltage
) // 1s max cell voltage check
196 || voltageMeter
.displayFiltered
> batteryConfig()->vbatnotpresentcellvoltage
* 2; // USB voltage - 2s or more check
199 void batteryUpdatePresence(void)
201 if ((voltageState
== BATTERY_NOT_PRESENT
|| voltageState
== BATTERY_INIT
)
202 && isVoltageFromBattery()
203 && voltageIsStable(&voltageMeter
)) {
204 // Battery has just been connected - calculate cells, warning voltages and reset state
206 consumptionState
= voltageState
= BATTERY_OK
;
207 if (batteryConfig()->forceBatteryCellCount
!= 0) {
208 batteryCellCount
= batteryConfig()->forceBatteryCellCount
;
210 unsigned cells
= (voltageMeter
.displayFiltered
/ batteryConfig()->vbatmaxcellvoltage
) + 1;
211 if (cells
> MAX_AUTO_DETECT_CELL_COUNT
) {
212 // something is wrong, we expect MAX_CELL_COUNT cells maximum (and autodetection will be problematic at 6+ cells)
213 cells
= MAX_AUTO_DETECT_CELL_COUNT
;
215 batteryCellCount
= cells
;
217 if (!ARMING_FLAG(ARMED
)) {
218 changePidProfileFromCellCount(batteryCellCount
);
222 mixerResetRpmLimiter();
224 batteryWarningVoltage
= batteryCellCount
* batteryConfig()->vbatwarningcellvoltage
;
225 batteryCriticalVoltage
= batteryCellCount
* batteryConfig()->vbatmincellvoltage
;
226 batteryWarningHysteresisVoltage
= (batteryWarningVoltage
> batteryConfig()->vbathysteresis
) ? batteryWarningVoltage
- batteryConfig()->vbathysteresis
: 0;
227 batteryCriticalHysteresisVoltage
= (batteryCriticalVoltage
> batteryConfig()->vbathysteresis
) ? batteryCriticalVoltage
- batteryConfig()->vbathysteresis
: 0;
228 lowVoltageCutoff
.percentage
= 100;
229 lowVoltageCutoff
.startTime
= 0;
230 } else if (voltageState
!= BATTERY_NOT_PRESENT
231 && voltageIsStable(&voltageMeter
)
232 && !isVoltageFromBattery()) {
233 /* battery has been disconnected - can take a while for filter cap to disharge so we use a threshold of batteryConfig()->vbatnotpresentcellvoltage */
234 consumptionState
= voltageState
= BATTERY_NOT_PRESENT
;
236 batteryCellCount
= 0;
237 batteryWarningVoltage
= 0;
238 batteryCriticalVoltage
= 0;
239 batteryWarningHysteresisVoltage
= 0;
240 batteryCriticalHysteresisVoltage
= 0;
241 wattHoursDrawn
= 0.0;
245 void batteryUpdateWhDrawn(void)
247 static int32_t mAhDrawnPrev
= 0;
248 const int32_t mAhDrawnCurrent
= getMAhDrawn();
249 wattHoursDrawn
+= voltageMeter
.displayFiltered
* (mAhDrawnCurrent
- mAhDrawnPrev
) / 100000.0f
;
250 mAhDrawnPrev
= mAhDrawnCurrent
;
253 static void batteryUpdateVoltageState(void)
255 // alerts are currently used by beeper, osd and other subsystems
256 static uint32_t lastVoltageChangeMs
;
257 switch (voltageState
) {
259 if (voltageMeter
.displayFiltered
<= batteryWarningHysteresisVoltage
) {
260 if (cmp32(millis(), lastVoltageChangeMs
) >= batteryConfig()->vbatDurationForWarning
* 100) {
261 voltageState
= BATTERY_WARNING
;
264 lastVoltageChangeMs
= millis();
268 case BATTERY_WARNING
:
269 if (voltageMeter
.displayFiltered
<= batteryCriticalHysteresisVoltage
) {
270 if (cmp32(millis(), lastVoltageChangeMs
) >= batteryConfig()->vbatDurationForCritical
* 100) {
271 voltageState
= BATTERY_CRITICAL
;
274 if (voltageMeter
.displayFiltered
> batteryWarningVoltage
) {
275 voltageState
= BATTERY_OK
;
277 lastVoltageChangeMs
= millis();
281 case BATTERY_CRITICAL
:
282 if (voltageMeter
.displayFiltered
> batteryCriticalVoltage
) {
283 voltageState
= BATTERY_WARNING
;
284 lastVoltageChangeMs
= millis();
294 static void batteryUpdateLVC(timeUs_t currentTimeUs
)
296 if (batteryConfig()->lvcPercentage
< 100) {
297 if (voltageState
== BATTERY_CRITICAL
&& !lowVoltageCutoff
.enabled
) {
298 lowVoltageCutoff
.enabled
= true;
299 lowVoltageCutoff
.startTime
= currentTimeUs
;
300 lowVoltageCutoff
.percentage
= 100;
302 if (lowVoltageCutoff
.enabled
) {
303 if (cmp32(currentTimeUs
,lowVoltageCutoff
.startTime
) < LVC_AFFECT_TIME
) {
304 lowVoltageCutoff
.percentage
= 100 - (cmp32(currentTimeUs
,lowVoltageCutoff
.startTime
) * (100 - batteryConfig()->lvcPercentage
) / LVC_AFFECT_TIME
);
307 lowVoltageCutoff
.percentage
= batteryConfig()->lvcPercentage
;
314 static void batteryUpdateConsumptionState(void)
316 if (batteryConfig()->useConsumptionAlerts
&& batteryConfig()->batteryCapacity
> 0 && batteryCellCount
> 0) {
317 uint8_t batteryPercentageRemaining
= calculateBatteryPercentageRemaining();
319 if (batteryPercentageRemaining
== 0) {
320 consumptionState
= BATTERY_CRITICAL
;
321 } else if (batteryPercentageRemaining
<= batteryConfig()->consumptionWarningPercentage
) {
322 consumptionState
= BATTERY_WARNING
;
324 consumptionState
= BATTERY_OK
;
329 void batteryUpdateStates(timeUs_t currentTimeUs
)
331 batteryUpdateVoltageState();
332 batteryUpdateConsumptionState();
333 batteryUpdateLVC(currentTimeUs
);
334 batteryState
= MAX(voltageState
, consumptionState
);
335 batteryUpdateWhDrawn();
338 const lowVoltageCutoff_t
*getLowVoltageCutoff(void)
340 return &lowVoltageCutoff
;
343 batteryState_e
getBatteryState(void)
348 batteryState_e
getVoltageState(void)
353 batteryState_e
getConsumptionState(void)
355 return consumptionState
;
358 const char * const batteryStateStrings
[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT", "INIT"};
360 const char * getBatteryStateString(void)
362 return batteryStateStrings
[getBatteryState()];
365 void batteryInit(void)
370 batteryState
= BATTERY_INIT
;
371 batteryCellCount
= 0;
381 voltageState
= BATTERY_INIT
;
382 batteryWarningVoltage
= 0;
383 batteryCriticalVoltage
= 0;
384 batteryWarningHysteresisVoltage
= 0;
385 batteryCriticalHysteresisVoltage
= 0;
386 lowVoltageCutoff
.enabled
= false;
387 lowVoltageCutoff
.percentage
= 100;
388 lowVoltageCutoff
.startTime
= 0;
390 voltageMeterReset(&voltageMeter
);
392 voltageMeterGenericInit();
393 switch (batteryConfig()->voltageMeterSource
) {
394 case VOLTAGE_METER_ESC
:
395 #ifdef USE_ESC_SENSOR
396 voltageMeterESCInit();
400 case VOLTAGE_METER_ADC
:
401 voltageMeterADCInit();
411 consumptionState
= BATTERY_OK
;
412 currentMeterReset(¤tMeter
);
413 switch (batteryConfig()->currentMeterSource
) {
414 case CURRENT_METER_ADC
:
415 currentMeterADCInit();
418 case CURRENT_METER_VIRTUAL
:
419 #ifdef USE_VIRTUAL_CURRENT_METER
420 currentMeterVirtualInit();
424 case CURRENT_METER_ESC
:
426 currentMeterESCInit();
429 case CURRENT_METER_MSP
:
430 #ifdef USE_MSP_CURRENT_METER
431 currentMeterMSPInit();
440 void batteryUpdateCurrentMeter(timeUs_t currentTimeUs
)
442 if (batteryCellCount
== 0) {
443 currentMeterReset(¤tMeter
);
447 static uint32_t ibatLastServiced
= 0;
448 const int32_t lastUpdateAt
= cmp32(currentTimeUs
, ibatLastServiced
);
449 ibatLastServiced
= currentTimeUs
;
451 switch (batteryConfig()->currentMeterSource
) {
452 case CURRENT_METER_ADC
:
453 currentMeterADCRefresh(lastUpdateAt
);
454 currentMeterADCRead(¤tMeter
);
457 case CURRENT_METER_VIRTUAL
: {
458 #ifdef USE_VIRTUAL_CURRENT_METER
459 throttleStatus_e throttleStatus
= calculateThrottleStatus();
460 bool throttleLowAndMotorStop
= (throttleStatus
== THROTTLE_LOW
&& featureIsEnabled(FEATURE_MOTOR_STOP
));
461 const int32_t throttleOffset
= lrintf(mixerGetThrottle() * 1000);
463 currentMeterVirtualRefresh(lastUpdateAt
, ARMING_FLAG(ARMED
), throttleLowAndMotorStop
, throttleOffset
);
464 currentMeterVirtualRead(¤tMeter
);
469 case CURRENT_METER_ESC
:
470 #ifdef USE_ESC_SENSOR
471 if (featureIsEnabled(FEATURE_ESC_SENSOR
)) {
472 currentMeterESCRefresh(lastUpdateAt
);
473 currentMeterESCReadCombined(¤tMeter
);
477 case CURRENT_METER_MSP
:
478 #ifdef USE_MSP_CURRENT_METER
479 currentMeterMSPRefresh(currentTimeUs
);
480 currentMeterMSPRead(¤tMeter
);
485 case CURRENT_METER_NONE
:
486 currentMeterReset(¤tMeter
);
491 uint8_t calculateBatteryPercentageRemaining(void)
493 uint8_t batteryPercentage
= 0;
494 if (batteryCellCount
> 0) {
495 uint16_t batteryCapacity
= batteryConfig()->batteryCapacity
;
497 if (batteryCapacity
> 0) {
498 batteryPercentage
= constrain(((float)batteryCapacity
- currentMeter
.mAhDrawn
) * 100 / batteryCapacity
, 0, 100);
500 batteryPercentage
= constrain((((uint32_t)voltageMeter
.displayFiltered
- (batteryConfig()->vbatmincellvoltage
* batteryCellCount
)) * 100) / ((batteryConfig()->vbatmaxcellvoltage
- batteryConfig()->vbatmincellvoltage
) * batteryCellCount
), 0, 100);
504 return batteryPercentage
;
507 void batteryUpdateAlarms(void)
509 // use the state to trigger beeper alerts
510 if (batteryConfig()->useVBatAlerts
) {
511 updateBatteryBeeperAlert();
515 bool isBatteryVoltageConfigured(void)
517 return batteryConfig()->voltageMeterSource
!= VOLTAGE_METER_NONE
;
520 uint16_t getBatteryVoltage(void)
522 return voltageMeter
.displayFiltered
;
525 uint16_t getLegacyBatteryVoltage(void)
527 return (voltageMeter
.displayFiltered
+ 5) / 10;
530 uint16_t getBatteryVoltageLatest(void)
532 return voltageMeter
.unfiltered
;
535 uint8_t getBatteryCellCount(void)
537 return batteryCellCount
;
540 uint16_t getBatteryAverageCellVoltage(void)
542 return (batteryCellCount
? voltageMeter
.displayFiltered
/ batteryCellCount
: 0);
545 #if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
546 uint16_t getBatterySagCellVoltage(void)
548 return (batteryCellCount
? voltageMeter
.sagFiltered
/ batteryCellCount
: 0);
552 bool isAmperageConfigured(void)
554 return batteryConfig()->currentMeterSource
!= CURRENT_METER_NONE
;
557 int32_t getAmperage(void)
559 return currentMeter
.amperage
;
562 int32_t getAmperageLatest(void)
564 return currentMeter
.amperageLatest
;
567 int32_t getMAhDrawn(void)
569 #ifdef USE_BATTERY_CONTINUE
570 return currentMeter
.mAhDrawn
+ currentMeter
.mAhDrawnOffset
;
572 return currentMeter
.mAhDrawn
;
576 #ifdef USE_BATTERY_CONTINUE
577 bool hasUsedMAh(void)
579 return batteryConfig()->isBatteryContinueEnabled
580 && !(ARMING_FLAG(ARMED
) || ARMING_FLAG(WAS_EVER_ARMED
)) && (getBatteryState() == BATTERY_OK
)
581 && getBatteryAverageCellVoltage() < batteryConfig()->vbatfullcellvoltage
582 && statsConfig()->stats_mah_used
> 0;
585 void setMAhDrawn(uint32_t mAhDrawn
)
587 currentMeter
.mAhDrawnOffset
= mAhDrawn
;
591 float getWhDrawn(void)
593 return wattHoursDrawn
;