2 * This file is part of INAV.
4 * INAV 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 * INAV 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 INAV. If not, see <http://www.gnu.org/licenses/>.
23 FILE_COMPILE_FOR_SPEED
25 #if defined(USE_POWER_LIMITS)
27 #include "flight/power_limits.h"
29 #include "build/debug.h"
31 #include "common/filter.h"
32 #include "common/maths.h"
33 #include "common/utils.h"
35 #include "config/parameter_group.h"
36 #include "config/parameter_group_ids.h"
38 #include "drivers/time.h"
40 #include "fc/settings.h"
44 #include "sensors/battery.h"
46 #define LIMITING_THR_FILTER_TCONST 50
48 PG_REGISTER_WITH_RESET_TEMPLATE(powerLimitsConfig_t
, powerLimitsConfig
, PG_POWER_LIMITS_CONFIG
, 1);
50 PG_RESET_TEMPLATE(powerLimitsConfig_t
, powerLimitsConfig
,
51 .piP
= SETTING_LIMIT_PI_P_DEFAULT
,
52 .piI
= SETTING_LIMIT_PI_I_DEFAULT
,
53 .attnFilterCutoff
= SETTING_LIMIT_ATTN_FILTER_CUTOFF_DEFAULT
, // Hz
56 static float burstCurrentReserve
; // cA.µs
57 static float burstCurrentReserveMax
; // cA.µs
58 static float burstCurrentReserveFalldown
; // cA.µs
59 static int32_t activeCurrentLimit
; // cA
60 static float currentThrAttnIntegrator
= 0;
61 static pt1Filter_t currentThrAttnFilter
;
62 static pt1Filter_t currentThrLimitingBaseFilter
;
63 static bool wasLimitingCurrent
= false;
66 static float burstPowerReserve
; // cW.µs
67 static float burstPowerReserveMax
; // cW.µs
68 static float burstPowerReserveFalldown
; // cW.µs
69 static int32_t activePowerLimit
; // cW
70 static float powerThrAttnIntegrator
= 0;
71 static pt1Filter_t powerThrAttnFilter
;
72 static pt1Filter_t powerThrLimitingBaseFilter
;
73 static bool wasLimitingPower
= false;
76 void powerLimiterInit(void) {
77 if (currentBatteryProfile
->powerLimits
.burstCurrent
< currentBatteryProfile
->powerLimits
.continuousCurrent
) {
78 currentBatteryProfileMutable
->powerLimits
.burstCurrent
= currentBatteryProfile
->powerLimits
.continuousCurrent
;
81 activeCurrentLimit
= currentBatteryProfile
->powerLimits
.burstCurrent
;
83 uint16_t currentBurstOverContinuous
= currentBatteryProfile
->powerLimits
.burstCurrent
- currentBatteryProfile
->powerLimits
.continuousCurrent
;
84 burstCurrentReserve
= burstCurrentReserveMax
= currentBurstOverContinuous
* currentBatteryProfile
->powerLimits
.burstCurrentTime
* 1e6
;
85 burstCurrentReserveFalldown
= currentBurstOverContinuous
* currentBatteryProfile
->powerLimits
.burstCurrentFalldownTime
* 1e6
;
87 pt1FilterInit(¤tThrAttnFilter
, powerLimitsConfig()->attnFilterCutoff
, 0);
88 pt1FilterInitRC(¤tThrLimitingBaseFilter
, LIMITING_THR_FILTER_TCONST
, 0);
91 if (currentBatteryProfile
->powerLimits
.burstPower
< currentBatteryProfile
->powerLimits
.continuousPower
) {
92 currentBatteryProfileMutable
->powerLimits
.burstPower
= currentBatteryProfile
->powerLimits
.continuousPower
;
95 activePowerLimit
= currentBatteryProfile
->powerLimits
.burstPower
;
97 uint16_t powerBurstOverContinuous
= currentBatteryProfile
->powerLimits
.burstPower
- currentBatteryProfile
->powerLimits
.continuousPower
;
98 burstPowerReserve
= burstPowerReserveMax
= powerBurstOverContinuous
* currentBatteryProfile
->powerLimits
.burstPowerTime
* 1e6
;
99 burstPowerReserveFalldown
= powerBurstOverContinuous
* currentBatteryProfile
->powerLimits
.burstPowerFalldownTime
* 1e6
;
101 pt1FilterInit(&powerThrAttnFilter
, powerLimitsConfig()->attnFilterCutoff
, 0);
102 pt1FilterInitRC(&powerThrLimitingBaseFilter
, LIMITING_THR_FILTER_TCONST
, 0);
106 static uint32_t calculateActiveLimit(int32_t value
, uint32_t continuousLimit
, uint32_t burstLimit
, float *burstReserve
, float burstReserveFalldown
, float burstReserveMax
, timeDelta_t timeDelta
) {
107 int32_t continuousDiff
= value
- continuousLimit
* 10;
108 float spentReserveChunk
= continuousDiff
* timeDelta
;
109 *burstReserve
= constrainf(*burstReserve
- spentReserveChunk
, 0, burstReserveMax
);
111 if (currentBatteryProfile
->powerLimits
.burstCurrentFalldownTime
) {
112 return scaleRangef(MIN(*burstReserve
, burstReserveFalldown
), 0, burstReserveFalldown
, continuousLimit
, burstLimit
) * 10;
115 return (*burstReserve
? burstLimit
: continuousLimit
) * 10;
118 void currentLimiterUpdate(timeDelta_t timeDelta
) {
119 activeCurrentLimit
= calculateActiveLimit(getAmperage(),
120 currentBatteryProfile
->powerLimits
.continuousCurrent
, currentBatteryProfile
->powerLimits
.burstCurrent
,
121 &burstCurrentReserve
, burstCurrentReserveFalldown
, burstCurrentReserveMax
,
126 void powerLimiterUpdate(timeDelta_t timeDelta
) {
127 activePowerLimit
= calculateActiveLimit(getPower(),
128 currentBatteryProfile
->powerLimits
.continuousPower
, currentBatteryProfile
->powerLimits
.burstPower
,
129 &burstPowerReserve
, burstPowerReserveFalldown
, burstPowerReserveMax
,
134 void powerLimiterApply(int16_t *throttleCommand
) {
137 if (!activeCurrentLimit
&& !activePowerLimit
) {
141 if (!activeCurrentLimit
) {
146 static timeUs_t lastCallTimestamp
= 0;
147 timeUs_t currentTimeUs
= micros();
148 timeDelta_t callTimeDelta
= cmpTimeUs(currentTimeUs
, lastCallTimestamp
);
150 int16_t throttleBase
;
151 int16_t currentThrottleCommand
;
153 int16_t powerThrottleCommand
;
156 int16_t current
= getAmperageSample();
158 uint16_t voltage
= getVBatSample();
159 int32_t power
= (int32_t)voltage
* current
/ 100;
163 int32_t overCurrent
= current
- activeCurrentLimit
;
165 if (lastCallTimestamp
) {
166 currentThrAttnIntegrator
= constrainf(currentThrAttnIntegrator
+ overCurrent
* powerLimitsConfig()->piI
* callTimeDelta
* 2e-7, 0, PWM_RANGE_MAX
- PWM_RANGE_MIN
);
169 float currentThrAttnProportional
= MAX(0, overCurrent
) * powerLimitsConfig()->piP
* 1e-3;
171 uint16_t currentThrAttn
= lrintf(pt1FilterApply3(¤tThrAttnFilter
, currentThrAttnProportional
+ currentThrAttnIntegrator
, callTimeDelta
* 1e-6));
173 throttleBase
= wasLimitingCurrent
? lrintf(pt1FilterApply3(¤tThrLimitingBaseFilter
, *throttleCommand
, callTimeDelta
* 1e-6)) : *throttleCommand
;
174 uint16_t currentThrAttned
= MAX(PWM_RANGE_MIN
, (int16_t)throttleBase
- currentThrAttn
);
176 if (activeCurrentLimit
&& currentThrAttned
< *throttleCommand
) {
177 if (!wasLimitingCurrent
&& getAmperage() >= activeCurrentLimit
) {
178 pt1FilterReset(¤tThrLimitingBaseFilter
, *throttleCommand
);
179 wasLimitingCurrent
= true;
182 currentThrottleCommand
= currentThrAttned
;
184 wasLimitingCurrent
= false;
185 pt1FilterReset(¤tThrAttnFilter
, 0);
187 currentThrottleCommand
= *throttleCommand
;
192 int32_t overPower
= power
- activePowerLimit
;
194 if (lastCallTimestamp
) {
195 powerThrAttnIntegrator
= constrainf(powerThrAttnIntegrator
+ overPower
* powerLimitsConfig()->piI
* callTimeDelta
/ voltage
* 2e-5, 0, PWM_RANGE_MAX
- PWM_RANGE_MIN
);
198 float powerThrAttnProportional
= MAX(0, overPower
) * powerLimitsConfig()->piP
/ voltage
* 1e-1;
200 uint16_t powerThrAttn
= lrintf(pt1FilterApply3(&powerThrAttnFilter
, powerThrAttnProportional
+ powerThrAttnIntegrator
, callTimeDelta
* 1e-6));
202 throttleBase
= wasLimitingPower
? lrintf(pt1FilterApply3(&powerThrLimitingBaseFilter
, *throttleCommand
, callTimeDelta
* 1e-6)) : *throttleCommand
;
203 uint16_t powerThrAttned
= MAX(PWM_RANGE_MIN
, (int16_t)throttleBase
- powerThrAttn
);
205 if (activePowerLimit
&& powerThrAttned
< *throttleCommand
) {
206 if (!wasLimitingPower
&& getPower() >= activePowerLimit
) {
207 pt1FilterReset(&powerThrLimitingBaseFilter
, *throttleCommand
);
208 wasLimitingPower
= true;
211 powerThrottleCommand
= powerThrAttned
;
213 wasLimitingPower
= false;
214 pt1FilterReset(&powerThrAttnFilter
, 0);
216 powerThrottleCommand
= *throttleCommand
;
219 *throttleCommand
= MIN(currentThrottleCommand
, powerThrottleCommand
);
221 *throttleCommand
= currentThrottleCommand
;
224 lastCallTimestamp
= currentTimeUs
;
227 bool powerLimiterIsLimiting(void) {
229 return wasLimitingPower
|| wasLimitingCurrent
;
231 return wasLimitingCurrent
;
235 bool powerLimiterIsLimitingCurrent(void) {
236 return wasLimitingCurrent
;
240 bool powerLimiterIsLimitingPower(void) {
241 return wasLimitingPower
;
246 float powerLimiterGetRemainingBurstTime(void) {
247 uint16_t currentBurstOverContinuous
= currentBatteryProfile
->powerLimits
.burstCurrent
- currentBatteryProfile
->powerLimits
.continuousCurrent
;
248 float remainingCurrentBurstTime
= burstCurrentReserve
/ currentBurstOverContinuous
/ 1e7
;
251 uint16_t powerBurstOverContinuous
= currentBatteryProfile
->powerLimits
.burstPower
- currentBatteryProfile
->powerLimits
.continuousPower
;
252 float remainingPowerBurstTime
= burstPowerReserve
/ powerBurstOverContinuous
/ 1e7
;
254 if (!currentBatteryProfile
->powerLimits
.continuousCurrent
) {
255 return remainingPowerBurstTime
;
258 if (!currentBatteryProfile
->powerLimits
.continuousPower
) {
259 return remainingCurrentBurstTime
;
262 return MIN(remainingCurrentBurstTime
, remainingPowerBurstTime
);
264 return remainingCurrentBurstTime
;
269 uint16_t powerLimiterGetActiveCurrentLimit(void) {
270 return activeCurrentLimit
;
275 uint16_t powerLimiterGetActivePowerLimit(void) {
276 return activePowerLimit
;