Split initialization from gyro.c for flash savings
[betaflight.git] / src / main / sensors / battery.c
blob7764bcf39178fe6569d39c6a1d74503e81a4a350
1 /*
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)
8 * any later version.
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/>.
21 #include "stdbool.h"
22 #include "stdint.h"
23 #include "math.h"
25 #include "platform.h"
27 #include "build/debug.h"
29 #include "common/filter.h"
30 #include "common/maths.h"
31 #include "common/utils.h"
33 #include "config/config.h"
34 #include "config/feature.h"
36 #include "drivers/adc.h"
38 #include "fc/runtime_config.h"
39 #include "fc/rc_controls.h"
41 #include "flight/mixer.h"
43 #include "io/beeper.h"
45 #include "pg/pg.h"
46 #include "pg/pg_ids.h"
48 #include "sensors/battery.h"
50 /**
51 * terminology: meter vs sensors
53 * voltage and current sensors are used to collect data.
54 * - e.g. voltage at an MCU ADC input pin, value from an ESC sensor.
55 * sensors require very specific configuration, such as resistor values.
56 * voltage and current meters are used to process and expose data collected from sensors to the rest of the system.
57 * - e.g. a meter exposes normalized, and often filtered, values from a sensor.
58 * meters require different or little configuration.
59 * meters also have different precision concerns, and may use different units to the sensors.
63 #define VBAT_STABLE_MAX_DELTA 20
64 #define LVC_AFFECT_TIME 10000000 //10 secs for the LVC to slowly kick in
66 // Battery monitoring stuff
67 uint8_t batteryCellCount; // Note: this can be 0 when no battery is detected or when the battery voltage sensor is missing or disabled.
68 uint16_t batteryWarningVoltage;
69 uint16_t batteryCriticalVoltage;
70 static lowVoltageCutoff_t lowVoltageCutoff;
72 static currentMeter_t currentMeter;
73 static voltageMeter_t voltageMeter;
75 static batteryState_e batteryState;
76 static batteryState_e voltageState;
77 static batteryState_e consumptionState;
79 #ifndef DEFAULT_CURRENT_METER_SOURCE
80 #ifdef USE_VIRTUAL_CURRENT_METER
81 #define DEFAULT_CURRENT_METER_SOURCE CURRENT_METER_VIRTUAL
82 #else
83 #ifdef USE_MSP_CURRENT_METER
84 #define DEFAULT_CURRENT_METER_SOURCE CURRENT_METER_MSP
85 #else
86 #define DEFAULT_CURRENT_METER_SOURCE CURRENT_METER_NONE
87 #endif
88 #endif
89 #endif
91 #ifndef DEFAULT_VOLTAGE_METER_SOURCE
92 #define DEFAULT_VOLTAGE_METER_SOURCE VOLTAGE_METER_NONE
93 #endif
95 PG_REGISTER_WITH_RESET_TEMPLATE(batteryConfig_t, batteryConfig, PG_BATTERY_CONFIG, 3);
97 PG_RESET_TEMPLATE(batteryConfig_t, batteryConfig,
98 // voltage
99 .vbatmaxcellvoltage = 430,
100 .vbatmincellvoltage = 330,
101 .vbatwarningcellvoltage = 350,
102 .vbatnotpresentcellvoltage = 300, //A cell below 3 will be ignored
103 .voltageMeterSource = DEFAULT_VOLTAGE_METER_SOURCE,
104 .lvcPercentage = 100, //Off by default at 100%
106 // current
107 .batteryCapacity = 0,
108 .currentMeterSource = DEFAULT_CURRENT_METER_SOURCE,
110 // cells
111 .forceBatteryCellCount = 0, //0 will be ignored
113 // warnings / alerts
114 .useVBatAlerts = true,
115 .useConsumptionAlerts = false,
116 .consumptionWarningPercentage = 10,
117 .vbathysteresis = 1,
119 .vbatfullcellvoltage = 410,
121 .vbatLpfPeriod = 30,
122 .ibatLpfPeriod = 10,
123 .vbatDurationForWarning = 0,
124 .vbatDurationForCritical = 0,
127 void batteryUpdateVoltage(timeUs_t currentTimeUs)
129 UNUSED(currentTimeUs);
131 switch (batteryConfig()->voltageMeterSource) {
132 #ifdef USE_ESC_SENSOR
133 case VOLTAGE_METER_ESC:
134 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
135 voltageMeterESCRefresh();
136 voltageMeterESCReadCombined(&voltageMeter);
138 break;
139 #endif
140 case VOLTAGE_METER_ADC:
141 voltageMeterADCRefresh();
142 voltageMeterADCRead(VOLTAGE_SENSOR_ADC_VBAT, &voltageMeter);
143 break;
145 default:
146 case VOLTAGE_METER_NONE:
147 voltageMeterReset(&voltageMeter);
148 break;
151 if (debugMode == DEBUG_BATTERY) {
152 debug[0] = voltageMeter.unfiltered;
153 debug[1] = voltageMeter.filtered;
157 static void updateBatteryBeeperAlert(void)
159 switch (getBatteryState()) {
160 case BATTERY_WARNING:
161 beeper(BEEPER_BAT_LOW);
163 break;
164 case BATTERY_CRITICAL:
165 beeper(BEEPER_BAT_CRIT_LOW);
167 break;
168 case BATTERY_OK:
169 case BATTERY_NOT_PRESENT:
170 case BATTERY_INIT:
171 break;
175 static bool isVoltageStable(void)
177 return ABS(voltageMeter.filtered - voltageMeter.unfiltered) <= VBAT_STABLE_MAX_DELTA;
180 static bool isVoltageFromBat(void)
182 // We want to disable battery getting detected around USB voltage or 0V
184 return (voltageMeter.filtered >= batteryConfig()->vbatnotpresentcellvoltage // Above ~0V
185 && voltageMeter.filtered <= batteryConfig()->vbatmaxcellvoltage) // 1s max cell voltage check
186 || voltageMeter.filtered > batteryConfig()->vbatnotpresentcellvoltage * 2; // USB voltage - 2s or more check
189 void batteryUpdatePresence(void)
193 if (
194 (voltageState == BATTERY_NOT_PRESENT || voltageState == BATTERY_INIT) && isVoltageFromBat() && isVoltageStable()
196 // Battery has just been connected - calculate cells, warning voltages and reset state
198 consumptionState = voltageState = BATTERY_OK;
199 if (batteryConfig()->forceBatteryCellCount != 0) {
200 batteryCellCount = batteryConfig()->forceBatteryCellCount;
201 } else {
202 unsigned cells = (voltageMeter.filtered / batteryConfig()->vbatmaxcellvoltage) + 1;
203 if (cells > MAX_AUTO_DETECT_CELL_COUNT) {
204 // something is wrong, we expect MAX_CELL_COUNT cells maximum (and autodetection will be problematic at 6+ cells)
205 cells = MAX_AUTO_DETECT_CELL_COUNT;
207 batteryCellCount = cells;
209 if (!ARMING_FLAG(ARMED)) {
210 changePidProfileFromCellCount(batteryCellCount);
213 batteryWarningVoltage = batteryCellCount * batteryConfig()->vbatwarningcellvoltage;
214 batteryCriticalVoltage = batteryCellCount * batteryConfig()->vbatmincellvoltage;
215 lowVoltageCutoff.percentage = 100;
216 lowVoltageCutoff.startTime = 0;
217 } else if (
218 voltageState != BATTERY_NOT_PRESENT && isVoltageStable() && !isVoltageFromBat()
220 /* battery has been disconnected - can take a while for filter cap to disharge so we use a threshold of batteryConfig()->vbatnotpresentcellvoltage */
222 consumptionState = voltageState = BATTERY_NOT_PRESENT;
224 batteryCellCount = 0;
225 batteryWarningVoltage = 0;
226 batteryCriticalVoltage = 0;
228 if (debugMode == DEBUG_BATTERY) {
229 debug[2] = batteryCellCount;
230 debug[3] = isVoltageStable();
234 static void batteryUpdateVoltageState(void)
236 // alerts are currently used by beeper, osd and other subsystems
237 static uint32_t lastVoltageChangeMs;
238 switch (voltageState) {
239 case BATTERY_OK:
240 if (voltageMeter.filtered <= (batteryWarningVoltage - batteryConfig()->vbathysteresis)) {
241 if (cmp32(millis(), lastVoltageChangeMs) >= batteryConfig()->vbatDurationForWarning * 100) {
242 voltageState = BATTERY_WARNING;
244 } else {
245 lastVoltageChangeMs = millis();
247 break;
249 case BATTERY_WARNING:
250 if (voltageMeter.filtered <= (batteryCriticalVoltage - batteryConfig()->vbathysteresis)) {
251 if (cmp32(millis(), lastVoltageChangeMs) >= batteryConfig()->vbatDurationForCritical * 100) {
252 voltageState = BATTERY_CRITICAL;
254 } else {
255 if (voltageMeter.filtered > batteryWarningVoltage) {
256 voltageState = BATTERY_OK;
258 lastVoltageChangeMs = millis();
260 break;
262 case BATTERY_CRITICAL:
263 if (voltageMeter.filtered > batteryCriticalVoltage) {
264 voltageState = BATTERY_WARNING;
265 lastVoltageChangeMs = millis();
267 break;
269 default:
270 break;
275 static void batteryUpdateLVC(timeUs_t currentTimeUs)
277 if (batteryConfig()->lvcPercentage < 100) {
278 if (voltageState == BATTERY_CRITICAL && !lowVoltageCutoff.enabled) {
279 lowVoltageCutoff.enabled = true;
280 lowVoltageCutoff.startTime = currentTimeUs;
281 lowVoltageCutoff.percentage = 100;
283 if (lowVoltageCutoff.enabled) {
284 if (cmp32(currentTimeUs,lowVoltageCutoff.startTime) < LVC_AFFECT_TIME) {
285 lowVoltageCutoff.percentage = 100 - (cmp32(currentTimeUs,lowVoltageCutoff.startTime) * (100 - batteryConfig()->lvcPercentage) / LVC_AFFECT_TIME);
287 else {
288 lowVoltageCutoff.percentage = batteryConfig()->lvcPercentage;
295 static void batteryUpdateConsumptionState(void)
297 if (batteryConfig()->useConsumptionAlerts && batteryConfig()->batteryCapacity > 0 && batteryCellCount > 0) {
298 uint8_t batteryPercentageRemaining = calculateBatteryPercentageRemaining();
300 if (batteryPercentageRemaining == 0) {
301 consumptionState = BATTERY_CRITICAL;
302 } else if (batteryPercentageRemaining <= batteryConfig()->consumptionWarningPercentage) {
303 consumptionState = BATTERY_WARNING;
304 } else {
305 consumptionState = BATTERY_OK;
310 void batteryUpdateStates(timeUs_t currentTimeUs)
312 batteryUpdateVoltageState();
313 batteryUpdateConsumptionState();
314 batteryUpdateLVC(currentTimeUs);
315 batteryState = MAX(voltageState, consumptionState);
318 const lowVoltageCutoff_t *getLowVoltageCutoff(void)
320 return &lowVoltageCutoff;
323 batteryState_e getBatteryState(void)
325 return batteryState;
328 batteryState_e getVoltageState(void)
330 return voltageState;
333 batteryState_e getConsumptionState(void)
335 return consumptionState;
338 const char * const batteryStateStrings[] = {"OK", "WARNING", "CRITICAL", "NOT PRESENT", "INIT"};
340 const char * getBatteryStateString(void)
342 return batteryStateStrings[getBatteryState()];
345 void batteryInit(void)
348 // presence
350 batteryState = BATTERY_INIT;
351 batteryCellCount = 0;
354 // voltage
356 voltageState = BATTERY_INIT;
357 batteryWarningVoltage = 0;
358 batteryCriticalVoltage = 0;
359 lowVoltageCutoff.enabled = false;
360 lowVoltageCutoff.percentage = 100;
361 lowVoltageCutoff.startTime = 0;
363 voltageMeterReset(&voltageMeter);
364 switch (batteryConfig()->voltageMeterSource) {
365 case VOLTAGE_METER_ESC:
366 #ifdef USE_ESC_SENSOR
367 voltageMeterESCInit();
368 #endif
369 break;
371 case VOLTAGE_METER_ADC:
372 voltageMeterADCInit();
373 break;
375 default:
376 break;
380 // current
382 consumptionState = BATTERY_OK;
383 currentMeterReset(&currentMeter);
384 switch (batteryConfig()->currentMeterSource) {
385 case CURRENT_METER_ADC:
386 currentMeterADCInit();
387 break;
389 case CURRENT_METER_VIRTUAL:
390 #ifdef USE_VIRTUAL_CURRENT_METER
391 currentMeterVirtualInit();
392 #endif
393 break;
395 case CURRENT_METER_ESC:
396 #ifdef ESC_SENSOR
397 currentMeterESCInit();
398 #endif
399 break;
400 case CURRENT_METER_MSP:
401 #ifdef USE_MSP_CURRENT_METER
402 currentMeterMSPInit();
403 #endif
404 break;
406 default:
407 break;
412 void batteryUpdateCurrentMeter(timeUs_t currentTimeUs)
414 UNUSED(currentTimeUs);
415 if (batteryCellCount == 0) {
416 currentMeterReset(&currentMeter);
417 return;
420 static uint32_t ibatLastServiced = 0;
421 const int32_t lastUpdateAt = cmp32(currentTimeUs, ibatLastServiced);
422 ibatLastServiced = currentTimeUs;
424 switch (batteryConfig()->currentMeterSource) {
425 case CURRENT_METER_ADC:
426 currentMeterADCRefresh(lastUpdateAt);
427 currentMeterADCRead(&currentMeter);
428 break;
430 case CURRENT_METER_VIRTUAL: {
431 #ifdef USE_VIRTUAL_CURRENT_METER
432 throttleStatus_e throttleStatus = calculateThrottleStatus();
433 bool throttleLowAndMotorStop = (throttleStatus == THROTTLE_LOW && featureIsEnabled(FEATURE_MOTOR_STOP));
434 const int32_t throttleOffset = lrintf(mixerGetThrottle() * 1000);
436 currentMeterVirtualRefresh(lastUpdateAt, ARMING_FLAG(ARMED), throttleLowAndMotorStop, throttleOffset);
437 currentMeterVirtualRead(&currentMeter);
438 #endif
439 break;
442 case CURRENT_METER_ESC:
443 #ifdef USE_ESC_SENSOR
444 if (featureIsEnabled(FEATURE_ESC_SENSOR)) {
445 currentMeterESCRefresh(lastUpdateAt);
446 currentMeterESCReadCombined(&currentMeter);
448 #endif
449 break;
450 case CURRENT_METER_MSP:
451 #ifdef USE_MSP_CURRENT_METER
452 currentMeterMSPRefresh(currentTimeUs);
453 currentMeterMSPRead(&currentMeter);
454 #endif
455 break;
457 default:
458 case CURRENT_METER_NONE:
459 currentMeterReset(&currentMeter);
460 break;
464 float calculateVbatPidCompensation(void) {
465 float batteryScaler = 1.0f;
466 if (batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE && batteryCellCount > 0) {
467 // Up to 33% PID gain. Should be fine for 4,2to 3,3 difference
468 batteryScaler = constrainf((( (float)batteryConfig()->vbatmaxcellvoltage * batteryCellCount ) / (float) voltageMeter.filtered), 1.0f, 1.33f);
470 return batteryScaler;
473 uint8_t calculateBatteryPercentageRemaining(void)
475 uint8_t batteryPercentage = 0;
476 if (batteryCellCount > 0) {
477 uint16_t batteryCapacity = batteryConfig()->batteryCapacity;
479 if (batteryCapacity > 0) {
480 batteryPercentage = constrain(((float)batteryCapacity - currentMeter.mAhDrawn) * 100 / batteryCapacity, 0, 100);
481 } else {
482 batteryPercentage = constrain((((uint32_t)voltageMeter.filtered - (batteryConfig()->vbatmincellvoltage * batteryCellCount)) * 100) / ((batteryConfig()->vbatmaxcellvoltage - batteryConfig()->vbatmincellvoltage) * batteryCellCount), 0, 100);
486 return batteryPercentage;
489 void batteryUpdateAlarms(void)
491 // use the state to trigger beeper alerts
492 if (batteryConfig()->useVBatAlerts) {
493 updateBatteryBeeperAlert();
497 bool isBatteryVoltageConfigured(void)
499 return batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE;
502 uint16_t getBatteryVoltage(void)
504 return voltageMeter.filtered;
507 uint16_t getLegacyBatteryVoltage(void)
509 return (voltageMeter.filtered + 5) / 10;
512 uint16_t getBatteryVoltageLatest(void)
514 return voltageMeter.unfiltered;
517 uint8_t getBatteryCellCount(void)
519 return batteryCellCount;
522 uint16_t getBatteryAverageCellVoltage(void)
524 return voltageMeter.filtered / batteryCellCount;
527 bool isAmperageConfigured(void)
529 return batteryConfig()->currentMeterSource != CURRENT_METER_NONE;
532 int32_t getAmperage(void) {
533 return currentMeter.amperage;
536 int32_t getAmperageLatest(void)
538 return currentMeter.amperageLatest;
541 int32_t getMAhDrawn(void)
543 return currentMeter.mAhDrawn;