Skip to content

Commit 78d5bec

Browse files
committed
SITL: improve SITL::Battery in multiple ways
1 parent 72250f2 commit 78d5bec

5 files changed

Lines changed: 68 additions & 62 deletions

File tree

libraries/SITL/SIM_Battery.cpp

Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818

1919
#include "SIM_Battery.h"
20+
#include <float.h>
2021
#include <AP_Math/AP_Math.h>
2122

2223
using namespace SITL;
@@ -72,9 +73,14 @@ static const struct {
7273
/*
7374
use table to get resting voltage from remaining capacity
7475
*/
75-
float Battery::get_resting_voltage(float charge_pct) const
76+
float Battery::get_resting_voltage(void) const
7677
{
78+
if (capacity_is_unlimited()) {
79+
return max_voltage;
80+
}
81+
float charge_pct = 100 * remaining_Ah / capacity_Ah;
7782
const float max_cell_voltage = soc_table[0].volt_per_cell;
83+
const float min_cell_voltage = soc_table[ARRAY_SIZE(soc_table) - 1].volt_per_cell;
7884
for (uint8_t i=1; i<ARRAY_SIZE(soc_table); i++) {
7985
if (charge_pct >= soc_table[i].soc_pct) {
8086
// linear interpolation between table rows
@@ -86,15 +92,21 @@ float Battery::get_resting_voltage(float charge_pct) const
8692
return (cell_volt / max_cell_voltage) * max_voltage;
8793
}
8894
}
89-
// off the bottom of the table, return a small non-zero to prevent math errors
90-
return 0.001;
95+
// off the bottom of the table
96+
return min_cell_voltage;
9197
}
9298

9399
/*
94-
use table to set initial state of charge from voltage
100+
return remaining Amp-hours (aka "charge", "state of charge") corresponding to a voltage
101+
102+
this is const for readability: it has no "side effects"
95103
*/
96-
void Battery::set_initial_SoC(float voltage)
104+
float Battery::compute_remaining_ah(float voltage) const
97105
{
106+
if (capacity_is_unlimited()) {
107+
return FLT_MAX;
108+
}
109+
98110
const float max_cell_voltage = soc_table[0].volt_per_cell;
99111
float cell_volt = (voltage / max_voltage) * max_cell_voltage;
100112

@@ -106,24 +118,28 @@ void Battery::set_initial_SoC(float voltage)
106118
float soc1 = soc_table[i].soc_pct;
107119
float soc2 = soc_table[i-1].soc_pct;
108120
float soc = soc1 + (dv1 / dv2) * (soc2 - soc1);
109-
remaining_Ah = capacity_Ah * soc * 0.01;
110-
return;
121+
return capacity_Ah * soc * 0.01;
111122
}
112123
}
113-
114124
// off the bottom of the table
115-
remaining_Ah = 0;
125+
return 0.0f;
116126
}
117127

118-
void Battery::setup(float _capacity_Ah, float _resistance, float _max_voltage)
128+
void Battery::set_remaining_ah(void)
129+
{
130+
remaining_Ah = compute_remaining_ah(voltage_set);
131+
}
132+
133+
// Reminder: capacity <= 0 means **unlimited**
134+
void Battery::setup(float _capacity_Ah, float _resistance_ohm, float _max_voltage)
119135
{
120136
capacity_Ah = _capacity_Ah;
121-
resistance = _resistance;
137+
resistance_ohm = _resistance_ohm;
122138
max_voltage = _max_voltage;
123139

124140
voltage_set = max_voltage;
125141
voltage_filter.reset(voltage_set);
126-
set_initial_SoC(voltage_set);
142+
set_remaining_ah();
127143
}
128144

129145
void Battery::maybe_reset(float desired_voltage, float desired_capacity_Ah)
@@ -138,23 +154,10 @@ void Battery::maybe_reset(float desired_voltage, float desired_capacity_Ah)
138154
// a negative desired voltage is unexpected, but not problematic
139155
voltage_set = MIN(desired_voltage, max_voltage);
140156
voltage_filter.reset(voltage_set);
141-
set_initial_SoC(voltage_set);
157+
set_remaining_ah();
142158
}
143159

144-
void Battery::init_voltage(float voltage)
145-
{
146-
voltage_filter.reset(voltage);
147-
voltage_set = voltage;
148-
set_initial_SoC(voltage);
149-
}
150-
151-
void Battery::init_capacity(float capacity)
152-
{
153-
capacity_Ah = capacity;
154-
set_initial_SoC(voltage_set);
155-
}
156-
157-
void Battery::set_current(float current)
160+
void Battery::consume_energy(float current_amps)
158161
{
159162
uint64_t now = AP_HAL::micros64();
160163
float dt = (now - last_us) * 1.0e-6;
@@ -163,31 +166,29 @@ void Battery::set_current(float current)
163166
dt = 0;
164167
}
165168
last_us = now;
166-
float delta_Ah = current * dt / 3600;
169+
float delta_Ah = current_amps * dt / 3600;
167170
remaining_Ah -= delta_Ah;
168171
remaining_Ah = MAX(0, remaining_Ah);
169172

170-
float voltage_delta = current * resistance;
171-
float voltage;
172-
if (!is_positive(capacity_Ah)) {
173-
voltage = voltage_set;
174-
} else {
175-
voltage = get_resting_voltage(100 * remaining_Ah / capacity_Ah) - voltage_delta;
176-
}
177-
178-
voltage_filter.apply(voltage, dt);
173+
float voltage_delta = current_amps * resistance_ohm;
174+
float sagged_voltage = get_resting_voltage() - voltage_delta;
175+
voltage_filter.apply(sagged_voltage, dt);
179176

180-
{
181-
const uint64_t temperature_dt = now - temperature.last_update_micros;
182-
temperature.last_update_micros = now;
183-
// 1 amp*1 second == 0.1 degrees of energy. Did those units hurt?
184-
temperature.kelvin += 0.1 * current * temperature_dt * 0.000001;
185-
// decay temperature at some %second towards ambient
186-
temperature.kelvin -= (temperature.kelvin - 273) * 0.10 * temperature_dt * 0.000001;
187-
}
177+
update_temperature(current_amps, now);
188178
}
189179

190-
float Battery::get_voltage(void) const
180+
void Battery::update_temperature(float current_amps, uint64_t now)
191181
{
192-
return voltage_filter.get();
182+
const float dt = (now - temperature.last_update_micros) * 1.0e-6;
183+
temperature.last_update_micros = now;
184+
185+
// temperature growth model
186+
187+
// thermal_capacity value chosen to match previous steady-state behavior at 28amps
188+
// (reminder: thermal_capacity = mass * specific_heat)
189+
const float inverse_of_thermal_capacity = 0.36f; // use inverse so we can multiply, not divide
190+
temperature.kelvin += (current_amps * current_amps) * resistance_ohm * dt * inverse_of_thermal_capacity;
191+
192+
// temperature decay model: first-order
193+
temperature.kelvin -= (temperature.kelvin - (ambient_temperature + 273.15f)) * 0.10f * dt;
193194
}

libraries/SITL/SIM_Battery.h

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,40 +22,45 @@
2222

2323
namespace SITL {
2424

25+
constexpr float ambient_temperature = 0.0f; // degC
26+
2527
class Battery {
2628
public:
27-
void setup(float _capacity_Ah, float _resistance, float _max_voltage);
29+
// Note: capacity<=0 means **unlimited** capacity.
30+
void setup(float _capacity_Ah, float _resistance_ohm, float _max_voltage);
2831

2932
// Implements the (re)setting behavior described by SIM_BATT_* parameters
3033
// (reminder: if the desired values are different than previous choices, reset)
3134
void maybe_reset(float desired_voltage, float desired_capacity_Ah);
3235

33-
void init_voltage(float voltage);
34-
void init_capacity(float capacity);
36+
// Call this periodically to "step" the battery forward in time
37+
void consume_energy(float current_amps);
3538

36-
void set_current(float current_amps);
37-
float get_voltage(void) const;
39+
float get_voltage(void) const { return voltage_filter.get(); }
3840

3941
// return battery temperature in Kelvin:
4042
float get_temperature(void) const { return temperature.kelvin; }
4143

4244
private:
43-
float capacity_Ah;
44-
float resistance;
45+
float capacity_Ah; // set <= 0 for unlmited capacity
46+
float resistance_ohm;
4547
float max_voltage;
4648
float voltage_set;
47-
float remaining_Ah;
49+
float remaining_Ah; // if capacity is unlimited, set this to FLT_MAX
4850
uint64_t last_us;
4951

5052
struct {
51-
float kelvin = 273;
53+
float kelvin = ambient_temperature + 273.15f;
5254
uint64_t last_update_micros;
5355
} temperature;
56+
void update_temperature(float current_amps, uint64_t now);
5457

5558
// 10Hz filter for battery voltage
5659
LowPassFilterFloat voltage_filter{10};
5760

58-
float get_resting_voltage(float charge_pct) const;
59-
void set_initial_SoC(float voltage);
61+
float get_resting_voltage(void) const;
62+
float compute_remaining_ah(float voltage) const; // const shows this has no side effects
63+
void set_remaining_ah(void);
64+
bool capacity_is_unlimited(void) const { return !(is_positive(capacity_Ah)); } // for readability
6065
};
6166
}

libraries/SITL/SIM_Multicopter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ void MultiCopter::update(const struct sitl_input &input)
7777
battery_voltage = battery.get_voltage();
7878
battery_current = frame->get_current_amp();
7979

80-
battery.set_current(battery_current);
80+
battery.consume_energy(battery_current);
8181
battery_temperature = battery.get_temperature();
8282

8383
update_dynamics(rot_accel);

libraries/SITL/SIM_QuadPlane.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ void QuadPlane::update(const struct sitl_input &input)
142142
battery_voltage = battery.get_voltage();
143143
battery_current = frame->get_current_amp();
144144

145-
battery.set_current(battery_current);
145+
battery.consume_energy(battery_current);
146146
battery_temperature = battery.get_temperature();
147147

148148
float throttle;

libraries/SITL/SITL.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,13 @@ const AP_Param::GroupInfo SIM::var_info[] = {
117117
AP_GROUPINFO("SONAR_ROT", 17, SIM, sonar_rot, Rotation::ROTATION_PITCH_270),
118118
// @Param: BATT_VOLTAGE
119119
// @DisplayName: Simulated battery voltage
120-
// @Description: Simulated battery voltage. Constant voltage when SIM_BATT_CAP_AH is 0, otherwise changing this parameter will re-initialize the state of charge of the battery based on this voltage versus the battery's maximum voltage (default is max voltage).
120+
// @Description: Simulated battery voltage. This parameter's default value is the battery's maximum voltage. A value higher than the max will be treated identically as exactly that max value. When capacity is unlimited (see SIM_BATT_CAP_AH), this is the (constant) resting voltage. Otherwise, changing this value correspondingly (re)sets the battery's charge percentage (by comparing the new value against the max voltage).
121121
// @Units: V
122122
// @User: Advanced
123123
AP_GROUPINFO("BATT_VOLTAGE", 19, SIM, batt_voltage, 12.6f),
124124
// @Param: BATT_CAP_AH
125125
// @DisplayName: Simulated battery capacity
126-
// @Description: Simulated battery capacity. Set to 0 for unlimited capacity. Changing this parameter will re-initialize the state of charge of the battery.
126+
// @Description: Simulated battery capacity. Set to 0 for unlimited capacity. Changing this value (re)sets the battery's charge percentage (based on SIM_BATT_VOLTAGE).
127127
// @Units: Ah
128128
// @User: Advanced
129129
AP_GROUPINFO("BATT_CAP_AH", 20, SIM, batt_capacity_ah, 0),

0 commit comments

Comments
 (0)