ACwing FC firmware
STM32WB55: attitude IEKF, altitude Kalman filter, flight core, protocol v2
Loading...
Searching...
No Matches
calib_mgr.c
Go to the documentation of this file.
1
6#include "calib_mgr.h"
7#include "proto_v2.h"
8#include <math.h>
9#include <string.h>
10
11#define D2R 0.017453292519943295f
12#define R2D 57.29577951308232f
13#define STILL_GYRO (3.0f * D2R) /* rad/s: "still" */
14#define ACCEL_STILL_MS 2000u /* still window per position */
15#define LEVEL_MS 5000u
16#define GYRO_MS 5000u
17#define GYRO_MAX_RESTARTS 3u
18#define COMPASS_MIN_SAMPLES 150u
19#define COMPASS_KEEP 256u /* samples kept for the quality figure */
20
22static cal_set_t *s_active; /* set in effect (caller-owned) */
23static cal_set_t s_prev; /* active set before this calibration */
24static cal_set_t s_res; /* active set with this calibration's result */
25static uint8_t s_applied; /* s_res was applied (DONE) */
26
28static uint8_t s_changed, s_failed_type;
29static uint32_t s_t0; /* start of the current window (ms) */
30static uint32_t s_now;
31
32/* ---- per-type working data ---- */
33static struct { /* ACCEL */
35 float sum[3]; uint32_t n; uint32_t t_still;
36 float cap[6][3]; /* captured raw mean per position */
37 float last_g; /* |gyro| of the last sample */
38 float last_a[3];
40} A;
41static struct { /* LEVEL */
42 double sr, sp, sr2, sp2; uint32_t n;
43 float roll, pitch; uint16_t remaining;
44} L;
45static struct { /* GYRO */
46 double s[3], s2[3]; uint32_t n; uint8_t restarts;
47 float bias[3], noise; uint16_t remaining;
48} G;
49static struct { /* BATTERY */
51} B;
52static struct { /* COMPASS */
53 double N[6][6], b[6]; uint32_t n;
54 uint8_t cov[5];
55 float keep[COMPASS_KEEP][3]; uint16_t nkeep, kidx;
56 uint8_t interf, qual;
57} C;
58
59/* ================================================================== */
60void cal_set_defaults(cal_set_t *s, const float hi[3])
61{
62 memset(s, 0, sizeof *s);
63 for (int i = 0; i < 3; ++i) {
64 s->accel.scale[i] = 1.0f;
65 s->compass.scale[i] = 1.0f;
66 s->compass.hi[i] = hi ? hi[i] : 0.0f;
67 }
68 s->battery.scale_v = 110.0f; /* 100k/10k divider of the ACwing FC */
69 s->battery.scale_i = 1.0f;
70}
71
72void cal_copy_type(cal_set_t *d, const cal_set_t *s, uint8_t type)
73{
74 switch (type) {
75 case PV2_CAL_ACCEL: d->accel = s->accel; break;
76 case PV2_CAL_LEVEL: d->level = s->level; break;
77 case PV2_CAL_GYRO: d->gyro = s->gyro; break;
78 case PV2_CAL_COMPASS: d->compass = s->compass; break;
79 case PV2_CAL_MOTORS: d->motors = s->motors; break;
80 case PV2_CAL_BATTERY: d->battery = s->battery; break;
81 default: return;
82 }
83 const uint32_t bit = 1u << (type - 1u);
84 d->done_mask = (d->done_mask & ~bit) | (s->done_mask & bit);
85}
86
87void cal_set_sensors(uint16_t sensors) { s_cfg.sensors = sensors; }
88
89void cal_init(const cal_config_t *cfg, cal_set_t *active)
90{
91 s_cfg = *cfg;
92 s_active = active;
94 s_changed = 0; s_failed_type = 0; s_applied = 0;
95}
96
97static void set_state(uint8_t st, uint8_t step)
98{
99 if (st != s_state || step != s_step) s_changed = 1;
100 s_state = st; s_step = step;
101}
102
103static void apply(const cal_set_t *set)
104{
107}
108
109static void revert(void)
110{
111 if (s_applied) { apply(&s_prev); s_applied = 0; }
112}
113
114static void fail(uint8_t reason)
115{
116 revert();
117 s_reason = reason;
120}
121
122static void done(uint8_t step)
123{
124 s_progress = 100;
125 set_state(PV2_CS_DONE, step);
126 apply(&s_res);
127 s_applied = 1;
128}
129
130/* ================================================================== */
131uint8_t cal_active(void) { return s_state != PV2_CS_IDLE; }
132uint8_t cal_running(void) { return s_state != PV2_CS_IDLE && s_state != PV2_CS_DONE; }
133uint8_t cal_type(void) { return s_type; }
134const cal_set_t *cal_result(void) { return &s_res; }
135
136uint8_t cal_wants_samples(void)
137{
138 return (uint8_t)(s_state == PV2_CS_RUNNING || s_state == PV2_CS_WAITING_USER);
139}
140
141uint8_t cal_take_changed(void) { const uint8_t c = s_changed; s_changed = 0; return c; }
142
143uint8_t cal_take_failed(void)
144{
145 const uint8_t t = s_failed_type;
146 s_failed_type = 0;
147 return t;
148}
149
150uint8_t cal_start(uint8_t type, uint8_t option, uint8_t armed, uint32_t now, uint8_t *reason)
151{
152 *reason = PV2_R_NONE;
153 if (type < PV2_CAL_ACCEL || type > PV2_CAL_STICKS) return PV2_ACK_INVALID;
154 if (armed) { *reason = PV2_R_ARMED; return PV2_ACK_REJECTED; }
155 if (cal_running()) { *reason = PV2_R_CALIBRATING; return PV2_ACK_BUSY; }
156 switch (type) {
157 case PV2_CAL_FLOW_TOF:
158 if (!(s_cfg.sensors & PV2_S_FLOW)) { *reason = PV2_R_SENSOR_MISSING; return PV2_ACK_REJECTED; }
159 break;
160 case PV2_CAL_BATTERY:
161 if (!(s_cfg.sensors & PV2_S_CURRENT) || !s_cfg.batt) { *reason = PV2_R_SENSOR_MISSING; return PV2_ACK_REJECTED; }
162 break;
163 case PV2_CAL_STICKS:
164 if (!(s_cfg.sensors & PV2_S_ELRS)) { *reason = PV2_R_SENSOR_MISSING; return PV2_ACK_REJECTED; }
165 break;
166 case PV2_CAL_MOTORS:
167 if (!s_cfg.motors_built) { *reason = PV2_R_SENSOR_MISSING; return PV2_ACK_REJECTED; }
168 break;
169 case PV2_CAL_COMPASS:
170 if (!(s_cfg.sensors & PV2_S_MAG_FC) || option == 2u) { *reason = PV2_R_SENSOR_MISSING; return PV2_ACK_REJECTED; }
171 break;
172 default:
173 break;
174 }
175 /* the sensors exist but this firmware has no procedure for them yet */
176 if (type == PV2_CAL_FLOW_TOF || type == PV2_CAL_STICKS ||
177 type == PV2_CAL_MOTORS) return PV2_ACK_UNSUPPORTED;
178
179 /* a previous calibration left in DONE (not saved) is discarded */
180 if (s_state == PV2_CS_DONE) revert();
181
182 s_type = type; s_now = now; s_t0 = now;
183 s_prev = *s_active;
184 s_res = *s_active;
185 s_applied = 0;
186 s_progress = 0; s_quality = 0xFF; s_reason = 0;
188 switch (type) {
189 case PV2_CAL_ACCEL:
190 memset(&A, 0, sizeof A);
192 break;
193 case PV2_CAL_LEVEL:
194 memset(&L, 0, sizeof L);
195 L.remaining = LEVEL_MS;
196 L.roll = s_active->level.roll_deg; /* shown until the first sample */
197 L.pitch = s_active->level.pitch_deg;
199 break;
200 case PV2_CAL_GYRO:
201 memset(&G, 0, sizeof G);
202 G.remaining = GYRO_MS;
204 break;
205 case PV2_CAL_COMPASS:
206 memset(&C, 0, sizeof C);
208 break;
209 case PV2_CAL_BATTERY:
210 memset(&B, 0, sizeof B);
211 s_cfg.batt(&B.vbat, &B.used, &B.scale_i_used);
213 break;
214 default:
215 break;
216 }
217 s_changed = 1;
218 return PV2_ACK_OK;
219}
220
221uint8_t cal_abort(uint8_t *reason)
222{
223 if (!cal_active()) { *reason = PV2_R_NOT_CALIBRATING; return PV2_ACK_REJECTED; }
224 revert();
226 *reason = PV2_R_NONE;
227 return PV2_ACK_OK;
228}
229
230uint8_t cal_save_check(uint8_t *reason)
231{
232 *reason = PV2_R_NONE;
233 if (!cal_active() || (s_state != PV2_CS_DONE && s_type != PV2_CAL_MOTORS)) {
234 *reason = PV2_R_NOT_CALIBRATING;
235 return PV2_ACK_REJECTED;
236 }
237 s_res.done_mask |= 1u << (s_type - 1u);
238 s_active->done_mask |= 1u << (s_type - 1u);
239 return PV2_ACK_OK;
240}
241
242void cal_saved(void)
243{
244 s_applied = 0; /* the new values stay in effect */
245 set_state(PV2_CS_SAVED, s_step); /* one frame in SAVED, then IDLE (cal_frame) */
246}
247
248/* ================================================================== */
249/* ACCEL */
250/* ================================================================== */
251/* positions, body FLU: 0 +Z up, 1 -Z, 2 nose down (-X), 3 nose up (+X), 4 left down (-Y), 5 right down (+Y) */
252static int accel_position(const float a[3])
253{
254 int ax = 0;
255 for (int i = 1; i < 3; ++i) if (fabsf(a[i]) > fabsf(a[ax])) ax = i;
256 if (fabsf(a[ax]) < 0.8f) return -1;
257 const int pos = (a[ax] > 0.0f);
258 switch (ax) {
259 case 2: return pos ? 0 : 1;
260 case 0: return pos ? 3 : 2;
261 default: return pos ? 5 : 4;
262 }
263}
264
265static int first_missing(uint8_t mask)
266{
267 for (int i = 0; i < 6; ++i) if (!(mask & (1u << i))) return i;
268 return 0;
269}
270
271static void accel_fit(void)
272{
273 /* per axis: + and - 1 g positions */
274 static const int pplus[3] = { 3, 5, 0 }, pminus[3] = { 2, 4, 1 };
275 float off[3], sc[3];
276 for (int k = 0; k < 3; ++k) {
277 const float p = A.cap[pplus[k]][k], m = A.cap[pminus[k]][k];
278 off[k] = 0.5f * (p + m);
279 sc[k] = (p - m) > 1e-3f ? 2.0f / (p - m) : 0.0f;
280 }
281 double e2 = 0.0;
282 for (int i = 0; i < 6; ++i) {
283 float n2 = 0.0f;
284 for (int k = 0; k < 3; ++k) { const float c = sc[k] * (A.cap[i][k] - off[k]); n2 += c * c; }
285 const double e = sqrt((double)n2) - 1.0;
286 e2 += e * e;
287 }
288 A.fit_err_pct = (float)(100.0 * sqrt(e2 / 6.0));
289 for (int k = 0; k < 3; ++k) {
290 if (sc[k] < 0.90f || sc[k] > 1.10f || fabsf(off[k]) > 0.25f) { fail(PV2_R_OUT_OF_RANGE); return; }
291 s_res.accel.off[k] = off[k];
292 s_res.accel.scale[k] = sc[k];
293 }
294 const float q = 100.0f - 20.0f * A.fit_err_pct;
295 s_quality = (uint8_t)(q < 0.0f ? 0.0f : (q > 100.0f ? 100.0f : q));
296 done(3);
297}
298
299static void accel_capture_pos(int pos)
300{
301 if (A.n) for (int k = 0; k < 3; ++k) A.cap[pos][k] = A.sum[k] / (float)A.n;
302 else for (int k = 0; k < 3; ++k) A.cap[pos][k] = A.last_a[k];
303 A.done_mask |= (uint8_t)(1u << pos);
304 A.sum[0] = A.sum[1] = A.sum[2] = 0.0f; A.n = 0; A.still_pct = 0;
305 A.pos = 0xFF;
306 s_changed = 1;
307 uint8_t cnt = 0;
308 for (int i = 0; i < 6; ++i) cnt += (A.done_mask >> i) & 1u;
309 s_progress = (uint8_t)(100u * cnt / 6u);
310 if (A.done_mask == 0x3Fu) { accel_fit(); return; }
311 A.current = (uint8_t)first_missing(A.done_mask);
312}
313
314static void accel_feed(const cal_sample_t *s, uint32_t now)
315{
316 const float g = sqrtf(s->gyro[0]*s->gyro[0] + s->gyro[1]*s->gyro[1] + s->gyro[2]*s->gyro[2]);
317 A.last_g = g;
318 memcpy(A.last_a, s->acc, sizeof A.last_a);
319 const int pos = accel_position(s->acc);
320 if (g >= STILL_GYRO || pos < 0 || (A.done_mask & (1u << pos))) {
321 A.sum[0] = A.sum[1] = A.sum[2] = 0.0f; A.n = 0; A.still_pct = 0; A.pos = 0xFF;
322 return;
323 }
324 if (pos != A.pos) { /* a new still position: start its window */
325 A.pos = (uint8_t)pos; A.current = (uint8_t)pos;
326 A.sum[0] = A.sum[1] = A.sum[2] = 0.0f; A.n = 0; A.t_still = now;
327 s_changed = 1;
328 }
329 for (int k = 0; k < 3; ++k) A.sum[k] += s->acc[k];
330 A.n++;
331 const uint32_t el = now - A.t_still;
332 A.still_pct = (uint8_t)(el >= ACCEL_STILL_MS ? 100u : el * 100u / ACCEL_STILL_MS);
334}
335
336/* ================================================================== */
337/* LEVEL / GYRO */
338/* ================================================================== */
339static void level_feed(const cal_sample_t *s, uint32_t now)
340{
341 if (!s->running) return;
342 const float g = sqrtf(s->gyro[0]*s->gyro[0] + s->gyro[1]*s->gyro[1] + s->gyro[2]*s->gyro[2]);
343 if (g >= STILL_GYRO) { fail(PV2_R_NOT_STILL); return; }
344 const float w = s->q[0], x = s->q[1], y = s->q[2], z = s->q[3];
345 const float roll = atan2f(2.0f*(w*x + y*z), 1.0f - 2.0f*(x*x + y*y)) * R2D;
346 float sp = 2.0f*(w*y - z*x);
347 sp = sp > 1.0f ? 1.0f : (sp < -1.0f ? -1.0f : sp);
348 const float pitch = asinf(sp) * R2D;
349 L.sr += roll; L.sp += pitch; L.sr2 += (double)roll*roll; L.sp2 += (double)pitch*pitch; L.n++;
350 L.roll = (float)(L.sr / L.n); L.pitch = (float)(L.sp / L.n);
351 const uint32_t el = now - s_t0;
352 L.remaining = (uint16_t)(el >= LEVEL_MS ? 0u : LEVEL_MS - el);
353 s_progress = (uint8_t)(el >= LEVEL_MS ? 100u : el * 100u / LEVEL_MS);
354 if (el >= LEVEL_MS) {
355 const double vr = L.sr2 / L.n - (double)L.roll * L.roll, vp = L.sp2 / L.n - (double)L.pitch * L.pitch;
356 const float sd = (float)sqrt((vr > 0 ? vr : 0) + (vp > 0 ? vp : 0));
357 const float q = 100.0f - 100.0f * sd; /* 0.05 deg rms -> 95 */
358 s_quality = (uint8_t)(q < 0.0f ? 0.0f : q);
359 s_res.level.roll_deg = -L.roll;
360 s_res.level.pitch_deg = -L.pitch;
361 done(3);
362 }
363}
364
365static void gyro_feed(const cal_sample_t *s, uint32_t now)
366{
367 const float g = sqrtf(s->gyro[0]*s->gyro[0] + s->gyro[1]*s->gyro[1] + s->gyro[2]*s->gyro[2]);
368 if (g >= STILL_GYRO) { /* movement restarts the window */
369 if (G.n) {
370 if (++G.restarts > GYRO_MAX_RESTARTS) { fail(PV2_R_NOT_STILL); return; }
371 memset(G.s, 0, sizeof G.s); memset(G.s2, 0, sizeof G.s2); G.n = 0;
372 }
373 s_t0 = now; G.remaining = GYRO_MS; s_progress = 0;
374 return;
375 }
376 for (int k = 0; k < 3; ++k) { G.s[k] += s->gyro[k]; G.s2[k] += (double)s->gyro[k]*s->gyro[k]; }
377 G.n++;
378 const uint32_t el = now - s_t0;
379 G.remaining = (uint16_t)(el >= GYRO_MS ? 0u : GYRO_MS - el);
380 s_progress = (uint8_t)(el >= GYRO_MS ? 100u : el * 100u / GYRO_MS);
381 if (el >= GYRO_MS) {
382 double var = 0.0;
383 for (int k = 0; k < 3; ++k) {
384 const double m = G.s[k] / G.n;
385 G.bias[k] = (float)m;
386 const double v = G.s2[k] / G.n - m*m;
387 var += v > 0 ? v : 0;
388 s_res.gyro.bias[k] = (float)m;
389 }
390 G.noise = (float)sqrt(var / 3.0);
391 const float q = 100.0f - 100.0f * (G.noise * R2D); /* 0.03 deg/s rms -> 97 */
392 s_quality = (uint8_t)(q < 0.0f ? 0.0f : q);
393 done(2);
394 }
395}
396
397/* ================================================================== */
398/* COMPASS */
399/* ================================================================== */
400static void bin(int ring, float a, float b)
401{
402 if (sqrtf(a*a + b*b) < 0.3f) return; /* direction too close to the ring axis */
403 int k = (int)((atan2f(b, a) + 3.14159265f) * (12.0f / 6.2831853f));
404 if (k < 0) k = 0;
405 if (k > 11) k = 11;
406 const int i = ring * 12 + k;
407 C.cov[i >> 3] |= (uint8_t)(1u << (i & 7));
408}
409
410static uint8_t coverage_count(void)
411{
412 uint8_t n = 0;
413 for (int i = 0; i < 36; ++i) n += (C.cov[i >> 3] >> (i & 7)) & 1u;
414 return n;
415}
416
417/* Gaussian elimination with partial pivoting, 6x6 */
418static int solve6(double A6[6][6], double b6[6], double x[6])
419{
420 double M[6][7];
421 for (int i = 0; i < 6; ++i) { for (int j = 0; j < 6; ++j) M[i][j] = A6[i][j]; M[i][6] = b6[i]; }
422 for (int c = 0; c < 6; ++c) {
423 int p = c;
424 for (int r = c + 1; r < 6; ++r) if (fabs(M[r][c]) > fabs(M[p][c])) p = r;
425 if (fabs(M[p][c]) < 1e-12) return -1;
426 if (p != c) for (int j = 0; j < 7; ++j) { const double t = M[c][j]; M[c][j] = M[p][j]; M[p][j] = t; }
427 for (int r = c + 1; r < 6; ++r) {
428 const double f = M[r][c] / M[c][c];
429 for (int j = c; j < 7; ++j) M[r][j] -= f * M[c][j];
430 }
431 }
432 for (int i = 5; i >= 0; --i) {
433 double s = M[i][6];
434 for (int j = i + 1; j < 6; ++j) s -= M[i][j] * x[j];
435 x[i] = s / M[i][i];
436 }
437 return 0;
438}
439
440static void compass_fit(void)
441{
442 double p[6];
443 if (solve6(C.N, C.b, p) != 0 || p[0] <= 0.0 || p[1] <= 0.0 || p[2] <= 0.0) { fail(PV2_R_OUT_OF_RANGE); return; }
444 /* p0 x^2 + p1 y^2 + p2 z^2 + p3 x + p4 y + p5 z = 1
445 * -> centre c_i = -p_{3+i} / (2 p_i), radius r_i = sqrt(G / p_i), G = 1 + sum p_i c_i^2 */
446 double c[3], r[3], Gs = 1.0;
447 for (int i = 0; i < 3; ++i) { c[i] = -p[3 + i] / (2.0 * p[i]); Gs += p[i] * c[i] * c[i]; }
448 if (Gs <= 0.0) { fail(PV2_R_OUT_OF_RANGE); return; }
449 double rm = 0.0;
450 for (int i = 0; i < 3; ++i) { r[i] = sqrt(Gs / p[i]); rm += r[i] / 3.0; }
451 for (int i = 0; i < 3; ++i) {
452 const double sc = rm / r[i];
453 if (r[i] < 15.0 || r[i] > 150.0 || sc < 0.7 || sc > 1.4) { fail(PV2_R_OUT_OF_RANGE); return; }
454 s_res.compass.hi[i] = (float)c[i];
455 s_res.compass.scale[i] = (float)sc;
456 }
457 /* residual: spread of the corrected field magnitude over the kept samples */
458 double s1 = 0.0, s2 = 0.0;
459 for (uint16_t k = 0; k < C.nkeep; ++k) {
460 double n2 = 0.0;
461 for (int i = 0; i < 3; ++i) {
462 const double v = s_res.compass.scale[i] * (C.keep[k][i] - c[i]);
463 n2 += v * v;
464 }
465 const double n = sqrt(n2);
466 s1 += n; s2 += n * n;
467 }
468 const double mean = s1 / C.nkeep, var = s2 / C.nkeep - mean * mean;
469 const double rel = (var > 0 ? sqrt(var) : 0.0) / (mean > 1e-6 ? mean : 1.0);
470 const double q = 100.0 - 1000.0 * rel; /* 1 % spread -> 90 */
471 C.qual = (uint8_t)(q < 0.0 ? 0.0 : (q > 100.0 ? 100.0 : q));
472 C.interf = (uint8_t)(rel * 100.0 > 100.0 ? 100.0 : rel * 100.0 + 0.5);
473 s_quality = C.qual;
474 done(3);
475}
476
477static void compass_feed(const cal_sample_t *s)
478{
479 if (!s->mag_new) return;
480 const float *m = s->mag;
481 /* coverage: direction of the field corrected with the calibration in effect */
482 float u[3], n = 0.0f;
483 for (int i = 0; i < 3; ++i) { u[i] = s_prev.compass.scale[i] * (m[i] - s_prev.compass.hi[i]); n += u[i]*u[i]; }
484 n = sqrtf(n);
485 if (n < 1e-3f) return;
486 for (int i = 0; i < 3; ++i) u[i] /= n;
487 bin(0, u[0], u[1]); /* ring 0: rotation about the vertical (spin flat) */
488 bin(1, u[0], u[2]); /* ring 1: nose up / down */
489 bin(2, u[1], u[2]); /* ring 2: roll left / right */
490
491 const double phi[6] = { (double)m[0]*m[0], (double)m[1]*m[1], (double)m[2]*m[2], m[0], m[1], m[2] };
492 for (int i = 0; i < 6; ++i) {
493 C.b[i] += phi[i];
494 for (int j = 0; j < 6; ++j) C.N[i][j] += phi[i] * phi[j];
495 }
496 C.n++;
497 memcpy(C.keep[C.kidx], m, sizeof C.keep[0]);
498 C.kidx = (uint16_t)((C.kidx + 1u) % COMPASS_KEEP);
499 if (C.nkeep < COMPASS_KEEP) C.nkeep++;
500
501 const uint8_t cnt = coverage_count();
502 s_progress = (uint8_t)(cnt * 100u / 36u);
503 if (cnt == 36u && C.n >= COMPASS_MIN_SAMPLES) compass_fit();
504}
505
506/* ================================================================== */
507void cal_feed(const cal_sample_t *s, uint32_t now)
508{
509 s_now = now;
510 if (!cal_wants_samples()) return;
511 switch (s_type) {
512 case PV2_CAL_ACCEL: accel_feed(s, now); break;
513 case PV2_CAL_LEVEL: level_feed(s, now); break;
514 case PV2_CAL_GYRO: gyro_feed(s, now); break;
515 case PV2_CAL_COMPASS: compass_feed(s); break;
516 default: break;
517 }
518}
519
520void cal_tick(uint32_t now)
521{
522 s_now = now;
523 if (s_type == PV2_CAL_BATTERY && s_cfg.batt &&
525 s_cfg.batt(&B.vbat, &B.used, &B.scale_i_used); /* vbat live, with the new scale once applied */
526}
527
528uint8_t cal_capture(uint32_t now, uint8_t *reason)
529{
530 (void)now;
531 *reason = PV2_R_NONE;
532 if (!cal_active()) { *reason = PV2_R_NOT_CALIBRATING; return PV2_ACK_REJECTED; }
534 set_state(PV2_CS_DONE, 3); /* skip the current step (ยง10.6) */
535 return PV2_ACK_OK;
536 }
538 const int pos = accel_position(A.last_a);
539 if (pos < 0 || A.last_g >= 2.0f * STILL_GYRO) { *reason = PV2_R_NOT_STILL; return PV2_ACK_REJECTED; }
540 if (A.pos != (uint8_t)pos) { A.sum[0] = A.sum[1] = A.sum[2] = 0.0f; A.n = 0; }
542 return PV2_ACK_OK;
543 }
544 return PV2_ACK_INVALID; /* no capture step in this calibration/state */
545}
546
547uint8_t cal_set_ref(uint8_t type, uint8_t index, float v, uint8_t *reason)
548{
549 *reason = PV2_R_NONE;
550 if (!cal_active() || type != s_type) { *reason = PV2_R_NOT_CALIBRATING; return PV2_ACK_REJECTED; }
551 if (type == PV2_CAL_LEVEL && s_state == PV2_CS_DONE && index <= 1u) {
552 if (!isfinite(v) || fabsf(v) > 5.0f) { *reason = PV2_R_OUT_OF_RANGE; return PV2_ACK_INVALID; }
553 if (index == 0u) s_res.level.roll_deg = v; else s_res.level.pitch_deg = v;
554 apply(&s_res);
555 s_applied = 1;
556 s_changed = 1;
557 return PV2_ACK_OK;
558 }
559 if (type == PV2_CAL_BATTERY && s_state == PV2_CS_WAITING_USER && index == 0u) {
560 /* measured pack voltage: scaleV = scaleV * measured / read */
561 if (!isfinite(v) || v < 2.5f || v > 30.0f) { *reason = PV2_R_OUT_OF_RANGE; return PV2_ACK_INVALID; }
562 if (!(B.vbat > 1.0f)) { *reason = PV2_R_SENSOR_MISSING; return PV2_ACK_REJECTED; } /* no battery */
563 const float k = v / B.vbat;
564 if (k < 0.7f || k > 1.3f) { *reason = PV2_R_OUT_OF_RANGE; return PV2_ACK_INVALID; }
565 s_res.battery.scale_v *= k;
566 apply(&s_res);
567 s_applied = 1;
568 B.vbat = v;
570 return PV2_ACK_OK;
571 }
572 if (type == PV2_CAL_BATTERY && s_state == PV2_CS_WAITING_USER && index == 1u) {
573 /* charge put back by the charger after the last flight:
574 * scaleI = (scale the flight was counted with) * mAh / counted mAh */
575 if (!isfinite(v) || v < 50.0f || v > 20000.0f) { *reason = PV2_R_OUT_OF_RANGE; return PV2_ACK_INVALID; }
576 if (!(B.used >= 20.0f) || !(B.scale_i_used > 0.0f)) { *reason = PV2_R_OUT_OF_RANGE; return PV2_ACK_REJECTED; }
577 const float si = B.scale_i_used * v / B.used;
578 if (si < 0.5f * B.scale_i_used || si > 2.0f * B.scale_i_used) { *reason = PV2_R_OUT_OF_RANGE; return PV2_ACK_INVALID; }
579 s_res.battery.scale_i = si;
580 apply(&s_res);
581 s_applied = 1;
583 return PV2_ACK_OK;
584 }
585 return PV2_ACK_INVALID;
586}
587
588/* ================================================================== */
589static int16_t cl16(float v) { return (int16_t)(v > 32767.f ? 32767 : (v < -32768.f ? -32768 : lrintf(v))); }
590static uint16_t clu16(float v) { return (uint16_t)(v > 65535.f ? 65535 : (v < 0.f ? 0 : lrintf(v))); }
591
592size_t cal_frame(uint8_t *out, size_t cap)
593{
594 if (!cal_active()) return 0;
596 union {
599 } d;
600 uint8_t n = 0;
601 memset(&d, 0, sizeof d);
602 switch (s_type) {
603 case PV2_CAL_ACCEL: {
605 d.a.done_mask = A.done_mask; d.a.current = A.current; d.a.still_pct = A.still_pct;
606 int16_t o[3]; uint16_t sc[3];
607 for (int k = 0; k < 3; ++k) { o[k] = cl16(r->off[k] * 1000.0f); sc[k] = clu16(r->scale[k] * 1e4f); }
608 memcpy(d.a.offset_mg, o, sizeof o);
609 memcpy(d.a.scale_e4, sc, sizeof sc);
610 const uint16_t fe = clu16(A.fit_err_pct * 100.0f);
611 memcpy(&d.a.fit_err_x100, &fe, 2);
612 n = sizeof d.a;
613 break;
614 }
615 case PV2_CAL_LEVEL: {
616 const int16_t v[2] = { cl16(L.roll * 100.0f), cl16(L.pitch * 100.0f) };
617 const int16_t t[2] = { cl16(s_res.level.roll_deg * 100.0f), cl16(s_res.level.pitch_deg * 100.0f) };
618 memcpy(&d.l.roll_cdeg, &v[0], 2); memcpy(&d.l.pitch_cdeg, &v[1], 2);
619 memcpy(&d.l.remaining_ms, &L.remaining, 2);
620 memcpy(&d.l.trim_roll_cdeg, &t[0], 2); memcpy(&d.l.trim_pitch_cdeg, &t[1], 2);
621 n = sizeof d.l;
622 break;
623 }
624 case PV2_CAL_GYRO: {
625 int16_t b[3];
626 for (int k = 0; k < 3; ++k) b[k] = cl16(G.bias[k] * R2D * 1000.0f);
627 const uint16_t nz = clu16(G.noise * R2D * 1000.0f);
628 const int16_t tc = 0; /* IMU temperature not read yet */
629 memcpy(&d.g.remaining_ms, &G.remaining, 2);
630 memcpy(d.g.bias_mdps, b, sizeof b);
631 memcpy(&d.g.noise_mdps, &nz, 2);
632 memcpy(&d.g.temp_cc, &tc, 2);
633 n = sizeof d.g;
634 break;
635 }
636 case PV2_CAL_COMPASS: {
637 memcpy(d.c.coverage, C.cov, 5);
638 const uint8_t fin = (s_state == PV2_CS_DONE || s_state == PV2_CS_SAVED);
639 d.c.interf_fc = fin ? C.interf : 0u;
640 d.c.interf_gps = 0xFFu;
641 d.c.quality_fc = fin ? C.qual : 0xFFu;
642 d.c.quality_gps = 0xFFu;
643 n = sizeof d.c;
644 break;
645 }
646 case PV2_CAL_BATTERY: {
647 const uint16_t b[4] = { clu16(B.vbat * 1000.0f), clu16(s_res.battery.scale_v * 10.0f),
648 clu16(s_res.battery.scale_i * 1e4f), clu16(B.used) };
649 memcpy(&d.b, b, sizeof b);
650 n = sizeof d.b;
651 break;
652 }
653 default:
654 break;
655 }
656 const size_t len = pv2_calib(&h, &d, n, out, cap);
657 if (s_state == PV2_CS_SAVED) { s_state = PV2_CS_IDLE; s_type = 0; } /* SAVED is sent once */
658 return len;
659}
static uint32_t bit(uint8_t type)
Definition app_task.c:93
static uint8_t s_quality
Definition calib_mgr.c:27
static struct @10 B
static void apply(const cal_set_t *set)
Definition calib_mgr.c:103
static int accel_position(const float a[3])
Definition calib_mgr.c:252
uint8_t cal_take_changed(void)
1 if the state/step changed since the last call (send a CALIB frame now)
Definition calib_mgr.c:141
static int solve6(double A6[6][6], double b6[6], double x[6])
Definition calib_mgr.c:418
float keep[COMPASS_KEEP][3]
Definition calib_mgr.c:55
static void compass_fit(void)
Definition calib_mgr.c:440
static void gyro_feed(const cal_sample_t *s, uint32_t now)
Definition calib_mgr.c:365
double sr2
Definition calib_mgr.c:42
double sp2
Definition calib_mgr.c:42
static cal_set_t s_res
Definition calib_mgr.c:24
#define COMPASS_MIN_SAMPLES
Definition calib_mgr.c:18
uint8_t cal_abort(uint8_t *reason)
Definition calib_mgr.c:221
double sp
Definition calib_mgr.c:42
static void done(uint8_t step)
Definition calib_mgr.c:122
static int16_t cl16(float v)
Definition calib_mgr.c:589
float roll
Definition calib_mgr.c:43
float pitch
Definition calib_mgr.c:43
uint8_t pos
Definition calib_mgr.c:34
static void compass_feed(const cal_sample_t *s)
Definition calib_mgr.c:477
double b[6]
Definition calib_mgr.c:53
float last_a[3]
Definition calib_mgr.c:38
static uint8_t s_progress
Definition calib_mgr.c:27
#define R2D
Definition calib_mgr.c:12
float sum[3]
Definition calib_mgr.c:35
uint32_t n
Definition calib_mgr.c:35
uint8_t cal_capture(uint32_t now, uint8_t *reason)
Definition calib_mgr.c:528
uint8_t cal_type(void)
Definition calib_mgr.c:133
static uint8_t s_type
Definition calib_mgr.c:27
#define ACCEL_STILL_MS
Definition calib_mgr.c:14
uint8_t restarts
Definition calib_mgr.c:46
void cal_init(const cal_config_t *cfg, cal_set_t *active)
active: the set in effect (RAM), owned by the caller.
Definition calib_mgr.c:89
static cal_set_t * s_active
Definition calib_mgr.c:22
uint8_t current
Definition calib_mgr.c:34
float bias[3]
Definition calib_mgr.c:47
static void revert(void)
Definition calib_mgr.c:109
static void accel_feed(const cal_sample_t *s, uint32_t now)
Definition calib_mgr.c:314
static uint16_t clu16(float v)
Definition calib_mgr.c:590
uint8_t cal_running(void)
active and not DONE: blocks arming / other starts
Definition calib_mgr.c:132
uint8_t cal_save_check(uint8_t *reason)
CAL_SAVE validation: returns the ACK; on OK the caller writes the result (cal_result()) to flash and ...
Definition calib_mgr.c:230
static struct @7 A
static uint8_t s_changed
Definition calib_mgr.c:28
uint32_t t_still
Definition calib_mgr.c:35
static uint8_t coverage_count(void)
Definition calib_mgr.c:410
static struct @9 G
static uint32_t s_t0
Definition calib_mgr.c:29
void cal_saved(void)
state SAVED (one frame), then IDLE
Definition calib_mgr.c:242
float last_g
Definition calib_mgr.c:37
float used
Definition calib_mgr.c:50
double N[6][6]
Definition calib_mgr.c:53
static void level_feed(const cal_sample_t *s, uint32_t now)
Definition calib_mgr.c:339
size_t cal_frame(uint8_t *out, size_t cap)
CALIB frame for the current calibration; 0 if none.
Definition calib_mgr.c:592
#define STILL_GYRO
Definition calib_mgr.c:13
uint16_t kidx
Definition calib_mgr.c:55
#define COMPASS_KEEP
Definition calib_mgr.c:19
static struct @8 L
uint8_t cal_start(uint8_t type, uint8_t option, uint8_t armed, uint32_t now, uint8_t *reason)
Definition calib_mgr.c:150
float cap[6][3]
Definition calib_mgr.c:36
double sr
Definition calib_mgr.c:42
static int first_missing(uint8_t mask)
Definition calib_mgr.c:265
static uint8_t s_step
Definition calib_mgr.c:27
static void bin(int ring, float a, float b)
Definition calib_mgr.c:400
uint8_t interf
Definition calib_mgr.c:56
#define LEVEL_MS
Definition calib_mgr.c:15
uint8_t cal_set_ref(uint8_t type, uint8_t index, float v, uint8_t *reason)
Definition calib_mgr.c:547
uint8_t cal_take_failed(void)
1 + type once when a calibration failed (EVENT 51), 0 otherwise
Definition calib_mgr.c:143
uint8_t still_pct
Definition calib_mgr.c:34
#define GYRO_MS
Definition calib_mgr.c:16
uint8_t cal_active(void)
a calibration exists (any state but IDLE)
Definition calib_mgr.c:131
#define GYRO_MAX_RESTARTS
Definition calib_mgr.c:17
uint16_t remaining
Definition calib_mgr.c:43
static struct @11 C
const cal_set_t * cal_result(void)
Definition calib_mgr.c:134
static uint32_t s_now
Definition calib_mgr.c:30
float scale_i_used
Definition calib_mgr.c:50
double s2[3]
Definition calib_mgr.c:46
static uint8_t s_state
Definition calib_mgr.c:27
float vbat
Definition calib_mgr.c:50
static cal_config_t s_cfg
Definition calib_mgr.c:21
static uint8_t s_failed_type
Definition calib_mgr.c:28
uint8_t done_mask
Definition calib_mgr.c:34
uint8_t cal_wants_samples(void)
Definition calib_mgr.c:136
uint8_t qual
Definition calib_mgr.c:56
float noise
Definition calib_mgr.c:47
static cal_set_t s_prev
Definition calib_mgr.c:23
void cal_copy_type(cal_set_t *d, const cal_set_t *s, uint8_t type)
copies the part of 'src' that belongs to 'type' into 'dst'
Definition calib_mgr.c:72
void cal_set_defaults(cal_set_t *s, const float hi[3])
Definition calib_mgr.c:60
static void set_state(uint8_t st, uint8_t step)
Definition calib_mgr.c:97
void cal_set_sensors(uint16_t sensors)
expansion board detected later
Definition calib_mgr.c:87
void cal_tick(uint32_t now)
Definition calib_mgr.c:520
float fit_err_pct
Definition calib_mgr.c:39
uint16_t nkeep
Definition calib_mgr.c:55
uint8_t cov[5]
Definition calib_mgr.c:54
static uint8_t s_applied
Definition calib_mgr.c:25
void cal_feed(const cal_sample_t *s, uint32_t now)
Definition calib_mgr.c:507
double s[3]
Definition calib_mgr.c:46
static void accel_fit(void)
Definition calib_mgr.c:271
static void accel_capture_pos(int pos)
Definition calib_mgr.c:299
static uint8_t s_reason
Definition calib_mgr.c:27
static void fail(uint8_t reason)
Definition calib_mgr.c:114
Sensor calibrations of protocol v2 (HANDOVER_FW.md ยง10).
uint32_t off
Definition log_task.c:50
uint8_t set
Definition log_task.c:39
size_t pv2_calib(const pv2_calib_hdr_t *h, const void *data, uint8_t data_len, uint8_t *out, size_t cap)
Definition proto_v2.c:60
ACwing Pilot app protocol v2: identifiers and frame layouts.
#define PV2_S_MAG_FC
Definition proto_v2.h:123
@ PV2_ACK_UNSUPPORTED
Definition proto_v2.h:88
@ PV2_ACK_BUSY
Definition proto_v2.h:87
@ PV2_ACK_OK
Definition proto_v2.h:87
@ PV2_ACK_INVALID
Definition proto_v2.h:87
@ PV2_ACK_REJECTED
Definition proto_v2.h:87
@ PV2_R_NONE
Definition proto_v2.h:91
@ PV2_R_SENSOR_MISSING
Definition proto_v2.h:95
@ PV2_R_ARMED
Definition proto_v2.h:93
@ PV2_R_CALIBRATING
Definition proto_v2.h:93
@ PV2_R_NOT_CALIBRATING
Definition proto_v2.h:94
@ PV2_R_OUT_OF_RANGE
Definition proto_v2.h:94
@ PV2_R_NOT_STILL
Definition proto_v2.h:94
#define PV2_S_FLOW
Definition proto_v2.h:126
@ PV2_CS_WAITING_USER
Definition proto_v2.h:154
@ PV2_CS_RUNNING
Definition proto_v2.h:154
@ PV2_CS_IDLE
Definition proto_v2.h:154
@ PV2_CS_DONE
Definition proto_v2.h:154
@ PV2_CS_SAVED
Definition proto_v2.h:155
@ PV2_CS_FAILED
Definition proto_v2.h:155
#define PV2_S_ELRS
Definition proto_v2.h:130
@ PV2_CAL_COMPASS
Definition proto_v2.h:152
@ PV2_CAL_STICKS
Definition proto_v2.h:153
@ PV2_CAL_BATTERY
Definition proto_v2.h:153
@ PV2_CAL_FLOW_TOF
Definition proto_v2.h:153
@ PV2_CAL_MOTORS
Definition proto_v2.h:153
@ PV2_CAL_ACCEL
Definition proto_v2.h:152
@ PV2_CAL_GYRO
Definition proto_v2.h:152
@ PV2_CAL_LEVEL
Definition proto_v2.h:152
#define PV2_S_CURRENT
Definition proto_v2.h:129
float scale[3]
Definition calib_mgr.h:25
float off[3]
Definition calib_mgr.h:25
float scale_v
Definition calib_mgr.h:30
float scale_i
Definition calib_mgr.h:30
float scale[3]
Definition calib_mgr.h:28
float hi[3]
Definition calib_mgr.h:28
uint8_t motors_built
Definition calib_mgr.h:66
uint16_t sensors
CAPS bits.
Definition calib_mgr.h:65
cal_apply_fn apply
Definition calib_mgr.h:67
cal_batt_fn batt
NULL: BATTERY -> SENSOR_MISSING.
Definition calib_mgr.h:68
float bias[3]
Definition calib_mgr.h:27
float pitch_deg
Definition calib_mgr.h:26
float roll_deg
Definition calib_mgr.h:26
cal_level_t level
Definition calib_mgr.h:34
cal_motors_t motors
Definition calib_mgr.h:37
cal_battery_t battery
Definition calib_mgr.h:38
cal_compass_t compass
Definition calib_mgr.h:36
cal_accel_t accel
Definition calib_mgr.h:33
cal_gyro_t gyro
Definition calib_mgr.h:35
uint32_t done_mask
bit (type-1): this calibration was done and saved
Definition calib_mgr.h:39
static USBD_StatusTypeDef st(HAL_StatusTypeDef h)
Definition usbd_conf.c:86