ACwing FC firmware
STM32WB55: attitude IEKF, altitude Kalman filter, flight core, protocol v2
Loading...
Searching...
No Matches
battery.c
Go to the documentation of this file.
1
5#include "battery.h"
6#include "proto_v2.h"
7#include "spsc.h"
8#include <math.h>
9#include <string.h>
10
11#define DETECT_MS 500u /* battery present and stable before counting cells */
12#define R_WIN_MS 100u /* window of the internal resistance estimate */
13#define R_MIN_DI 0.5f /* A: load step needed for an estimate */
14#define REST_I 0.3f /* A: below this the pack is "resting" */
15#define SOC_REST_TAU 2.0f /* s */
16#define SOC_V_TAU 10.0f /* s: voltage-only state of charge */
17#define SOC_CORR_TAU 600.0f /* s: pull of the voltage on the coulomb counter */
18#define I_SLOW_TAU 10.0f /* s: current used for the time left */
19
22static uint32_t s_present_ms, s_absent_ms;
23static uint32_t s_below_ms[4], s_recover_ms;
24static uint8_t s_have_anchor, s_have_f;
26/* internal resistance windows */
27static float s_wv, s_wi;
28static uint32_t s_wn, s_wms;
29static float s_pv, s_pi;
30static uint8_t s_have_pw;
31
32static volatile uint32_t s_seq;
34
35/* LiPo open-circuit voltage per cell, 0…100 % in 5 % steps */
36static const float k_ocv[21] = {
37 3.27f, 3.61f, 3.69f, 3.71f, 3.73f, 3.75f, 3.77f, 3.79f, 3.80f, 3.82f, 3.84f,
38 3.85f, 3.87f, 3.91f, 3.95f, 3.98f, 4.02f, 4.08f, 4.11f, 4.15f, 4.20f
39};
40
41float batt_soc_from_ocv(float v)
42{
43 if (!(v > k_ocv[0])) return 0.0f;
44 if (v >= k_ocv[20]) return 1.0f;
45 for (int k = 1; k <= 20; ++k) {
46 if (v < k_ocv[k]) {
47 const float f = (v - k_ocv[k - 1]) / (k_ocv[k] - k_ocv[k - 1]);
48 return 0.05f * ((float)(k - 1) + f);
49 }
50 }
51 return 1.0f;
52}
53
54static float lpf(float y, float x, float dt, float tau) { return y + (x - y) * (dt / (tau + dt)); }
55static float clamp01(float v) { return v < 0.0f ? 0.0f : (v > 1.0f ? 1.0f : v); }
56
57void batt_init(const batt_cfg_t *cfg)
58{
59 s_cfg = *cfg;
60 if (!(s_cfg.scale_v > 0.0f)) s_cfg.scale_v = 110.0f;
61 if (!(s_cfg.scale_i > 0.0f)) s_cfg.scale_i = 1.0f;
62 if (!(s_cfg.sens_v_per_a > 0.0f)) s_cfg.sens_v_per_a = 0.1f;
63 memset(&s, 0, sizeof s);
65 s.remain_s = -1.0f;
67 memset(s_below_ms, 0, sizeof s_below_ms);
70 s_wv = s_wi = 0.0f; s_wn = s_wms = 0u; s_have_pw = 0u;
71 s_seq = 0u;
72 s_pub = s;
73}
74
75void batt_set_scales(float sv, float si)
76{
77 if (sv > 0.0f && isfinite(sv)) s_cfg.scale_v = sv;
78 if (si > 0.0f && isfinite(si)) s_cfg.scale_i = si;
79}
80
81void batt_get_cfg(batt_cfg_t *out) { *out = s_cfg; }
82const batt_state_t *batt_state(void) { return &s; }
83
85{
86 for (int tries = 0; tries < 8; ++tries) {
87 const uint32_t s0 = s_seq;
88 if (s0 & 1u) continue;
92 if (s_seq == s0) { *out = v; return; }
93 }
94}
95
96static void publish(void)
97{
98 s_seq++;
100 s_pub = s;
101 SPSC_BARRIER();
102 s_seq++;
103}
104
105static void lost_battery(void)
106{
107 s.present = 0u;
108 s.cells = 0u;
110 s.cell_v = s.cell_rest_v = 0.0f;
111 s.soc = 0.0f; s.pct = 0u;
112 s.remain_s = -1.0f;
113 s_present_ms = 0u;
114 memset(s_below_ms, 0, sizeof s_below_ms);
115 s_recover_ms = 0u;
116 s_have_anchor = 0u;
117 s_have_pw = 0u;
118}
119
120/* internal resistance from load steps: two consecutive 100 ms means */
121static void r_estimate(float v, float i, uint32_t dt)
122{
123 s_wv += v; s_wi += i; s_wn++; s_wms += dt;
124 if (s_wms < R_WIN_MS) return;
125 const float mv = s_wv / (float)s_wn, mi = s_wi / (float)s_wn;
126 s_wv = s_wi = 0.0f; s_wn = 0u; s_wms = 0u;
127 if (s_have_pw && s.cells) {
128 const float di = mi - s_pi;
129 if (fabsf(di) > R_MIN_DI) {
130 const float r = -(mv - s_pv) / di / (float)s.cells;
131 if (r > 0.002f && r < 0.5f) s.r_cell = 0.9f * s.r_cell + 0.1f * r;
132 }
133 }
134 s_pv = mv; s_pi = mi; s_have_pw = 1u;
135}
136
137static uint8_t level_of(float cv, const batt_limits_t *L, float hyst)
138{
139 if (cv < L->emerg + hyst) return PV2_BATT_EMERGENCY;
140 if (cv < L->crit + hyst) return PV2_BATT_CRITICAL;
141 if (cv < L->warn + hyst) return PV2_BATT_LOW;
142 return PV2_BATT_OK;
143}
144
145void batt_step(float pin_v, float cur_pin_v, uint32_t dt_ms, uint8_t armed, const batt_limits_t *L)
146{
147 const float dt = (float)dt_ms * 1e-3f;
148 const float v = pin_v * s_cfg.scale_v / 10.0f;
149 const float i = s_cfg.has_current ? cur_pin_v / s_cfg.sens_v_per_a * s_cfg.scale_i : 0.0f;
150 s.vbat_raw = v;
151
152 if (!s_have_f) { s.vbat = v; s.current = i; s_have_f = 1u; }
153 else { s.vbat = lpf(s.vbat, v, dt, 0.1f); s.current = lpf(s.current, i, dt, 0.05f); }
154 if (s.current > 0.0f) s.used_mah += s.current * dt / 3.6f; /* A·s → mAh */
156
157 /* ---- presence ---- */
158 if (!s.present) {
159 s_absent_ms = 0u;
160 if (s.vbat > BATT_PRESENT_V) { s.present = 1u; s_present_ms = 0u; }
161 } else if (s.vbat < BATT_ABSENT_V) {
162 if ((s_absent_ms += dt_ms) > 200u) lost_battery();
163 } else s_absent_ms = 0u;
164 if (!s.present) { s.remain_s = -1.0f; publish(); return; }
165 if (s_present_ms < 0xFFFF0000u) s_present_ms += dt_ms;
166
167 /* ---- cells ---- */
168 if (L->cells >= 1u && L->cells <= 6u) s.cells = L->cells;
169 else if (!s.cells && !armed && s_present_ms >= DETECT_MS) {
170 int n = (int)ceilf(s.vbat / BATT_CELL_DETECT_V);
171 s.cells = (uint8_t)(n < 1 ? 1 : (n > 6 ? 6 : n));
172 }
173 if (!s.cells) { publish(); return; }
174
175 const float n = (float)s.cells;
176 const float cell = s.vbat / n;
177 s.cell_v = (s.cell_v > 0.0f) ? lpf(s.cell_v, cell, dt, 0.5f) : cell;
178 if (s_cfg.has_current) r_estimate(v, i, dt_ms);
179 s.cell_rest_v = cell + s.current * s.r_cell;
180
181 /* ---- warning level: enter after 2 s below, leave only disarmed ---- */
182 const float thr[4] = { 0.0f, L->warn, L->crit, L->emerg };
183 for (uint8_t k = 1u; k <= 3u; ++k) {
184 s_below_ms[k] = (s.cell_v < thr[k]) ? s_below_ms[k] + dt_ms : 0u;
185 if (s_below_ms[k] >= BATT_LEVEL_MS && s.level < k) s.level = k;
186 }
187 if (!armed && s.level > PV2_BATT_OK) {
188 const uint8_t now_lvl = level_of(s.cell_v, L, BATT_RECOVER_HYST);
189 if (now_lvl < s.level) {
190 if ((s_recover_ms += dt_ms) >= BATT_RECOVER_MS) { s.level = now_lvl; s_recover_ms = 0u; }
191 } else s_recover_ms = 0u;
192 } else s_recover_ms = 0u;
193
194 /* ---- state of charge ---- */
195 const float soc_v = batt_soc_from_ocv(s.cell_rest_v);
196 const uint8_t counting = (uint8_t)(s_cfg.has_current && L->capacity_mah > 0.0f);
197 const uint8_t resting = (uint8_t)(!armed && (!s_cfg.has_current || fabsf(s.current) < REST_I));
198 if (!s_have_anchor) { s.soc = soc_v; s_have_anchor = 1u; s_soc_anchor = soc_v; s_used_anchor = s.used_mah; }
199 else if (resting) {
200 s.soc = lpf(s.soc, soc_v, dt, SOC_REST_TAU);
202 } else if (counting) {
203 s.soc = s_soc_anchor - (s.used_mah - s_used_anchor) / L->capacity_mah;
204 s_soc_anchor += (soc_v - s.soc) * (dt / SOC_CORR_TAU);
205 } else {
206 s.soc = lpf(s.soc, soc_v, dt, SOC_V_TAU);
207 }
208 s.soc = clamp01(s.soc);
209 s.pct = (uint8_t)(s.soc * 100.0f + 0.5f);
210
211 /* ---- flight time left ---- */
212 if (armed && counting && s_i_slow > 0.2f) {
213 const float usable = (s.soc - BATT_RESERVE_SOC) * L->capacity_mah; /* mAh */
214 s.remain_s = usable > 0.0f ? usable / (s_i_slow * 1000.0f) * 3600.0f : 0.0f;
215 } else s.remain_s = -1.0f;
216
217 publish();
218}
void batt_init(const batt_cfg_t *cfg)
Definition battery.c:57
#define SOC_REST_TAU
Definition battery.c:15
#define DETECT_MS
Definition battery.c:11
static batt_state_t s
Definition battery.c:21
static const float k_ocv[21]
Definition battery.c:36
static float s_i_slow
Definition battery.c:25
static uint8_t s_have_pw
Definition battery.c:30
static uint8_t level_of(float cv, const batt_limits_t *L, float hyst)
Definition battery.c:137
float batt_soc_from_ocv(float v)
LiPo open-circuit voltage → state of charge 0…1 (exposed for the tests)
Definition battery.c:41
static float s_used_anchor
Definition battery.c:25
static float s_wv
Definition battery.c:27
#define I_SLOW_TAU
Definition battery.c:18
static void publish(void)
Definition battery.c:96
static uint32_t s_below_ms[4]
Definition battery.c:23
static void r_estimate(float v, float i, uint32_t dt)
Definition battery.c:121
static uint32_t s_recover_ms
Definition battery.c:23
#define SOC_V_TAU
Definition battery.c:16
static uint32_t s_absent_ms
Definition battery.c:22
static float s_pv
Definition battery.c:29
static float lpf(float y, float x, float dt, float tau)
Definition battery.c:54
static void lost_battery(void)
Definition battery.c:105
static uint32_t s_wms
Definition battery.c:28
void batt_read(batt_state_t *out)
seqlock snapshot for other tasks; keeps *out on a race
Definition battery.c:84
static float s_wi
Definition battery.c:27
const batt_state_t * batt_state(void)
state as computed by the last step (same context)
Definition battery.c:82
static batt_cfg_t s_cfg
Definition battery.c:20
static float clamp01(float v)
Definition battery.c:55
#define R_WIN_MS
Definition battery.c:12
#define REST_I
Definition battery.c:14
#define SOC_CORR_TAU
Definition battery.c:17
static uint32_t s_wn
Definition battery.c:28
static uint8_t s_have_anchor
Definition battery.c:24
#define R_MIN_DI
Definition battery.c:13
void batt_get_cfg(batt_cfg_t *out)
Definition battery.c:81
static uint32_t s_present_ms
Definition battery.c:22
static float s_soc_anchor
Definition battery.c:25
static volatile uint32_t s_seq
Definition battery.c:32
static float s_pi
Definition battery.c:29
static uint8_t s_have_f
Definition battery.c:24
void batt_set_scales(float sv, float si)
Definition battery.c:75
void batt_step(float pin_v, float cur_pin_v, uint32_t dt_ms, uint8_t armed, const batt_limits_t *L)
one sample: pin voltages, time step, armed flag (flight-control context)
Definition battery.c:145
static batt_state_t s_pub
Definition battery.c:33
Battery monitor: voltage, current, consumed charge, cell count, state of charge, flight time left and...
#define BATT_PRESENT_V
pack above this = battery connected
Definition battery.h:35
#define BATT_CELL_DETECT_V
max cell voltage used to count cells
Definition battery.h:34
#define BATT_RESERVE_SOC
charge kept for landing (remain)
Definition battery.h:40
#define BATT_RECOVER_MS
disarmed, above threshold + hyst
Definition battery.h:38
#define BATT_LEVEL_MS
below a threshold this long -> level
Definition battery.h:37
#define BATT_ABSENT_V
Definition battery.h:36
#define BATT_RECOVER_HYST
V/cell
Definition battery.h:39
#define BATT_R_CELL_DEFAULT
Ω per cell before any estimate
Definition battery.h:41
uint32_t n
Definition calib_mgr.c:35
static struct @8 L
ACwing Pilot app protocol v2: identifiers and frame layouts.
@ PV2_BATT_OK
Definition proto_v2.h:136
@ PV2_BATT_LOW
Definition proto_v2.h:136
@ PV2_BATT_EMERGENCY
Definition proto_v2.h:136
@ PV2_BATT_CRITICAL
Definition proto_v2.h:136
Lock-free single-producer single-consumer queue of fixed-size items.
#define SPSC_BARRIER()
Definition spsc.h:18
float sens_v_per_a
current sensor gain, V/A
Definition battery.h:46
uint8_t has_current
a current sensor is wired
Definition battery.h:47
float scale_i
current multiplier (1 = nominal sensor)
Definition battery.h:45
float scale_v
×10 divider ratio (110 = ×11)
Definition battery.h:44
thresholds and settings (from the parameters)
Definition battery.h:68
published state
Definition battery.h:51
uint8_t level
PV2_BATT_OK / LOW / CRITICAL / EMERGENCY
Definition battery.h:54
uint8_t cells
0 = not known
Definition battery.h:53
uint8_t present
a battery is connected
Definition battery.h:52
float remain_s
s of flight left, < 0 = n/a
Definition battery.h:63
float vbat_raw
V, last sample, unfiltered (calibration)
Definition battery.h:64
uint8_t pct
0…100
Definition battery.h:55
float current
A, filtered (τ 0.05 s); 0 without a sensor
Definition battery.h:57
float vbat
V, pack, filtered (τ 0.1 s)
Definition battery.h:56
float r_cell
Ω per cell, estimate
Definition battery.h:61
float used_mah
since power-on
Definition battery.h:58
float soc
0…1
Definition battery.h:62
float cell_v
V, sagged, filtered (τ 0.5 s): failsafes, CHECKS.
Definition battery.h:59
float cell_rest_v
V, sag compensated: state of charge
Definition battery.h:60