PX4 Firmware
PX4 Autopilot Software http://px4.io
mag_calibration.cpp
Go to the documentation of this file.
1 /****************************************************************************
2  *
3  * Copyright (c) 2013-2017 PX4 Development Team. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in
13  * the documentation and/or other materials provided with the
14  * distribution.
15  * 3. Neither the name PX4 nor the names of its contributors may be
16  * used to endorse or promote products derived from this software
17  * without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
26  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  *
32  ****************************************************************************/
33 
34 /**
35  * @file mag_calibration.cpp
36  *
37  * Magnetometer calibration routine
38  */
39 
40 #include "mag_calibration.h"
41 #include "commander_helper.h"
42 #include "calibration_routines.h"
43 #include "calibration_messages.h"
44 
45 #include <px4_platform_common/defines.h>
46 #include <px4_platform_common/posix.h>
47 #include <px4_platform_common/time.h>
48 #include <stdio.h>
49 #include <unistd.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <poll.h>
53 #include <math.h>
54 #include <fcntl.h>
55 #include <drivers/drv_hrt.h>
56 #include <drivers/drv_accel.h>
57 #include <drivers/drv_gyro.h>
58 #include <drivers/drv_mag.h>
59 #include <drivers/drv_tone_alarm.h>
60 #include <systemlib/mavlink_log.h>
61 #include <parameters/param.h>
62 #include <systemlib/err.h>
64 
65 static const char *sensor_name = "mag";
66 static constexpr unsigned max_mags = 4;
67 static constexpr float mag_sphere_radius = 0.2f;
68 static unsigned int calibration_sides = 6; ///< The total number of sides
69 static constexpr unsigned int calibration_total_points = 240; ///< The total points per magnetometer
70 static constexpr unsigned int calibraton_duration_seconds = 42; ///< The total duration the routine is allowed to take
71 
72 static constexpr float MAG_MAX_OFFSET_LEN =
73  1.3f; ///< The maximum measurement range is ~1.9 Ga, the earth field is ~0.6 Ga, so an offset larger than ~1.3 Ga means the mag will saturate in some directions.
74 
76 bool internal[max_mags];
78 int32_t device_id_primary = 0;
79 static unsigned _last_mag_progress = 0;
80 
82 
83 /// Data passed to calibration worker routine
84 typedef struct {
86  unsigned done_count;
87  int sub_mag[max_mags];
91  unsigned int calibration_counter_total[max_mags];
92  bool side_data_collected[detect_orientation_side_count];
93  float *x[max_mags];
94  float *y[max_mags];
95  float *z[max_mags];
97 
98 
100 {
102 
103  struct mag_calibration_s mscale_null;
104  mscale_null.x_offset = 0.0f;
105  mscale_null.x_scale = 1.0f;
106  mscale_null.y_offset = 0.0f;
107  mscale_null.y_scale = 1.0f;
108  mscale_null.z_offset = 0.0f;
109  mscale_null.z_scale = 1.0f;
110 
111  int result = PX4_OK;
112 
113  // Determine which mags are available and reset each
114 
115  char str[30];
116 
117  // reset the learned EKF mag in-flight bias offsets which have been learned for the previous
118  // sensor calibration and will be invalidated by a new sensor calibration
119  (void)sprintf(str, "EKF2_MAGBIAS_X");
120  result = param_set_no_notification(param_find(str), &mscale_null.x_offset);
121 
122  if (result != PX4_OK) {
123  PX4_ERR("unable to reset %s", str);
124  }
125 
126  (void)sprintf(str, "EKF2_MAGBIAS_Y");
127  result = param_set_no_notification(param_find(str), &mscale_null.y_offset);
128 
129  if (result != PX4_OK) {
130  PX4_ERR("unable to reset %s", str);
131  }
132 
133  (void)sprintf(str, "EKF2_MAGBIAS_Z");
134  result = param_set_no_notification(param_find(str), &mscale_null.z_offset);
135 
136  if (result != PX4_OK) {
137  PX4_ERR("unable to reset %s", str);
138  }
139 
140  for (size_t i = 0; i < max_mags; i++) {
141  device_ids[i] = 0; // signals no mag
142  }
143 
144  _last_mag_progress = 0;
145 
146  for (unsigned cur_mag = 0; cur_mag < max_mags; cur_mag++) {
147 #ifdef __PX4_NUTTX
148  // Reset mag id to mag not available
149  (void)sprintf(str, "CAL_MAG%u_ID", cur_mag);
150  result = param_set_no_notification(param_find(str), &(device_ids[cur_mag]));
151 
152  if (result != PX4_OK) {
153  calibration_log_info(mavlink_log_pub, "[cal] Unable to reset CAL_MAG%u_ID", cur_mag);
154  break;
155  }
156 
157 #else
158  (void)sprintf(str, "CAL_MAG%u_XOFF", cur_mag);
159  result = param_set_no_notification(param_find(str), &mscale_null.x_offset);
160 
161  if (result != PX4_OK) {
162  PX4_ERR("unable to reset %s", str);
163  }
164 
165  (void)sprintf(str, "CAL_MAG%u_YOFF", cur_mag);
166  result = param_set_no_notification(param_find(str), &mscale_null.y_offset);
167 
168  if (result != PX4_OK) {
169  PX4_ERR("unable to reset %s", str);
170  }
171 
172  (void)sprintf(str, "CAL_MAG%u_ZOFF", cur_mag);
173  result = param_set_no_notification(param_find(str), &mscale_null.z_offset);
174 
175  if (result != PX4_OK) {
176  PX4_ERR("unable to reset %s", str);
177  }
178 
179  (void)sprintf(str, "CAL_MAG%u_XSCALE", cur_mag);
180  result = param_set_no_notification(param_find(str), &mscale_null.x_scale);
181 
182  if (result != PX4_OK) {
183  PX4_ERR("unable to reset %s", str);
184  }
185 
186  (void)sprintf(str, "CAL_MAG%u_YSCALE", cur_mag);
187  result = param_set_no_notification(param_find(str), &mscale_null.y_scale);
188 
189  if (result != PX4_OK) {
190  PX4_ERR("unable to reset %s", str);
191  }
192 
193  (void)sprintf(str, "CAL_MAG%u_ZSCALE", cur_mag);
194  result = param_set_no_notification(param_find(str), &mscale_null.z_scale);
195 
196  if (result != PX4_OK) {
197  PX4_ERR("unable to reset %s", str);
198  }
199 
200 #endif
201 
203 
204 #ifdef __PX4_NUTTX
205  // Attempt to open mag
206  (void)sprintf(str, "%s%u", MAG_BASE_DEVICE_PATH, cur_mag);
207  int fd = px4_open(str, O_RDONLY);
208 
209  if (fd < 0) {
210  continue;
211  }
212 
213  // Get device id for this mag
214  device_ids[cur_mag] = px4_ioctl(fd, DEVIOCGDEVICEID, 0);
215  internal[cur_mag] = (px4_ioctl(fd, MAGIOCGEXTERNAL, 0) <= 0);
216 
217  // Reset mag scale
218  result = px4_ioctl(fd, MAGIOCSSCALE, (long unsigned int)&mscale_null);
219 
220  if (result != PX4_OK) {
221  calibration_log_critical(mavlink_log_pub, CAL_ERROR_RESET_CAL_MSG, cur_mag);
222  }
223 
224  /* calibrate range */
225  if (result == PX4_OK) {
226  result = px4_ioctl(fd, MAGIOCCALIBRATE, fd);
227 
228  if (result != PX4_OK) {
229  calibration_log_info(mavlink_log_pub, "[cal] Skipped scale calibration, sensor %u", cur_mag);
230  /* this is non-fatal - mark it accordingly */
231  result = PX4_OK;
232  }
233  }
234 
235  px4_close(fd);
236 #endif
237  }
238 
239  // Calibrate all mags at the same time
240  if (result == PX4_OK) {
241  switch (mag_calibrate_all(mavlink_log_pub)) {
243  // Cancel message already displayed, we're done here
244  result = PX4_ERROR;
245  break;
246 
247  case calibrate_return_ok:
248  /* if there is a any preflight-check system response, let the barrage of messages through */
249  px4_usleep(200000);
250 
251  calibration_log_info(mavlink_log_pub, CAL_QGC_PROGRESS_MSG, 100);
252  px4_usleep(20000);
254  px4_usleep(20000);
255  break;
256 
257  default:
259  px4_usleep(20000);
260  break;
261  }
262  }
263 
264  /* give this message enough time to propagate */
265  px4_usleep(600000);
266 
267  return result;
268 }
269 
270 static bool reject_sample(float sx, float sy, float sz, float x[], float y[], float z[], unsigned count,
271  unsigned max_count)
272 {
273  float min_sample_dist = fabsf(5.4f * mag_sphere_radius / sqrtf(max_count)) / 3.0f;
274 
275  for (size_t i = 0; i < count; i++) {
276  float dx = sx - x[i];
277  float dy = sy - y[i];
278  float dz = sz - z[i];
279  float dist = sqrtf(dx * dx + dy * dy + dz * dz);
280 
281  if (dist < min_sample_dist) {
282  return true;
283  }
284  }
285 
286  return false;
287 }
288 
289 static unsigned progress_percentage(mag_worker_data_t *worker_data)
290 {
291  return 100 * ((float)worker_data->done_count) / calibration_sides;
292 }
293 
294 // Returns calibrate_return_error if any parameter is not finite
295 // Logs if parameters are out of range
296 static calibrate_return check_calibration_result(float offset_x, float offset_y, float offset_z,
297  float sphere_radius,
298  float diag_x, float diag_y, float diag_z,
299  float offdiag_x, float offdiag_y, float offdiag_z,
300  orb_advert_t *mavlink_log_pub, size_t cur_mag)
301 {
302  float must_be_finite[] = {offset_x, offset_y, offset_z,
303  sphere_radius,
304  diag_x, diag_y, diag_z,
305  offdiag_x, offdiag_y, offdiag_z
306  };
307 
308  float should_be_not_huge[] = {offset_x, offset_y, offset_z};
309  float should_be_positive[] = {sphere_radius, diag_x, diag_y, diag_z};
310 
311  // Make sure every parameter is finite
312  const int num_finite = sizeof(must_be_finite) / sizeof(*must_be_finite);
313 
314  for (unsigned i = 0; i < num_finite; ++i) {
315  if (!PX4_ISFINITE(must_be_finite[i])) {
316  calibration_log_emergency(mavlink_log_pub,
317  "ERROR: Retry calibration (sphere NaN, #%zu)", cur_mag);
318  return calibrate_return_error;
319  }
320  }
321 
322  // Notify if offsets are too large
323  const int num_not_huge = sizeof(should_be_not_huge) / sizeof(*should_be_not_huge);
324 
325  for (unsigned i = 0; i < num_not_huge; ++i) {
326  if (fabsf(should_be_not_huge[i]) > MAG_MAX_OFFSET_LEN) {
327  calibration_log_critical(mavlink_log_pub, "Warning: %s mag with large offsets",
328  (internal[cur_mag]) ? "autopilot, internal" : "GPS unit, external");
329  break;
330  }
331  }
332 
333  // Notify if a parameter which should be positive is non-positive
334  const int num_positive = sizeof(should_be_positive) / sizeof(*should_be_positive);
335 
336  for (unsigned i = 0; i < num_positive; ++i) {
337  if (should_be_positive[i] <= 0.0f) {
338  calibration_log_critical(mavlink_log_pub, "Warning: %s mag with non-positive scale",
339  (internal[cur_mag]) ? "autopilot, internal" : "GPS unit, external");
340  break;
341  }
342  }
343 
344  return calibrate_return_ok;
345 }
346 
347 static calibrate_return mag_calibration_worker(detect_orientation_return orientation, int cancel_sub, void *data)
348 {
350 
351  unsigned int calibration_counter_side;
352 
353  mag_worker_data_t *worker_data = (mag_worker_data_t *)(data);
354 
355  // notify user to start rotating
357 
358  calibration_log_info(worker_data->mavlink_log_pub, "[cal] Rotate vehicle around the detected orientation");
359  calibration_log_info(worker_data->mavlink_log_pub, "[cal] Continue rotation for %s %u s",
361 
362  /*
363  * Detect if the system is rotating.
364  *
365  * We're detecting this as a general rotation on any axis, not necessary on the one we
366  * asked the user for. This is because we really just need two roughly orthogonal axes
367  * for a good result, so we're not constraining the user more than we have to.
368  */
369 
370  hrt_abstime detection_deadline = hrt_absolute_time() + worker_data->calibration_interval_perside_useconds * 5;
371  hrt_abstime last_gyro = 0;
372  float gyro_x_integral = 0.0f;
373  float gyro_y_integral = 0.0f;
374  float gyro_z_integral = 0.0f;
375 
376  const float gyro_int_thresh_rad = 0.5f;
377 
378  int sub_gyro = orb_subscribe(ORB_ID(sensor_gyro));
379 
380  while (fabsf(gyro_x_integral) < gyro_int_thresh_rad &&
381  fabsf(gyro_y_integral) < gyro_int_thresh_rad &&
382  fabsf(gyro_z_integral) < gyro_int_thresh_rad) {
383 
384  /* abort on request */
385  if (calibrate_cancel_check(worker_data->mavlink_log_pub, cancel_sub)) {
387  px4_close(sub_gyro);
388  return result;
389  }
390 
391  /* abort with timeout */
392  if (hrt_absolute_time() > detection_deadline) {
393  result = calibrate_return_error;
394  warnx("int: %8.4f, %8.4f, %8.4f", (double)gyro_x_integral, (double)gyro_y_integral, (double)gyro_z_integral);
395  calibration_log_critical(worker_data->mavlink_log_pub, "Failed: This calibration requires rotation.");
396  break;
397  }
398 
399  /* Wait clocking for new data on all gyro */
400  px4_pollfd_struct_t fds[1];
401  fds[0].fd = sub_gyro;
402  fds[0].events = POLLIN;
403  size_t fd_count = 1;
404 
405  int poll_ret = px4_poll(fds, fd_count, 1000);
406 
407  if (poll_ret > 0) {
408  sensor_gyro_s gyro{};
409  orb_copy(ORB_ID(sensor_gyro), sub_gyro, &gyro);
410 
411  /* ensure we have a valid first timestamp */
412  if (last_gyro > 0) {
413 
414  /* integrate */
415  float delta_t = (gyro.timestamp - last_gyro) / 1e6f;
416  gyro_x_integral += gyro.x * delta_t;
417  gyro_y_integral += gyro.y * delta_t;
418  gyro_z_integral += gyro.z * delta_t;
419  }
420 
421  last_gyro = gyro.timestamp;
422  }
423  }
424 
425  px4_close(sub_gyro);
426 
427  uint64_t calibration_deadline = hrt_absolute_time() + worker_data->calibration_interval_perside_useconds;
428  unsigned poll_errcount = 0;
429 
430  calibration_counter_side = 0;
431 
432  while (hrt_absolute_time() < calibration_deadline &&
433  calibration_counter_side < worker_data->calibration_points_perside) {
434 
435  if (calibrate_cancel_check(worker_data->mavlink_log_pub, cancel_sub)) {
437  break;
438  }
439 
440  // Wait clocking for new data on all mags
441  px4_pollfd_struct_t fds[max_mags];
442  size_t fd_count = 0;
443 
444  for (size_t cur_mag = 0; cur_mag < max_mags; cur_mag++) {
445  if (worker_data->sub_mag[cur_mag] >= 0 && device_ids[cur_mag] != 0) {
446  fds[fd_count].fd = worker_data->sub_mag[cur_mag];
447  fds[fd_count].events = POLLIN;
448  fd_count++;
449  }
450  }
451 
452  int poll_ret = px4_poll(fds, fd_count, 1000);
453 
454  if (poll_ret > 0) {
455 
456  int prev_count[max_mags];
457  bool rejected = false;
458 
459  for (size_t cur_mag = 0; cur_mag < max_mags; cur_mag++) {
460 
461  prev_count[cur_mag] = worker_data->calibration_counter_total[cur_mag];
462 
463  if (worker_data->sub_mag[cur_mag] >= 0) {
464  struct mag_report mag;
465 
466  orb_copy(ORB_ID(sensor_mag), worker_data->sub_mag[cur_mag], &mag);
467 
468  // Check if this measurement is good to go in
469  rejected = rejected || reject_sample(mag.x, mag.y, mag.z,
470  worker_data->x[cur_mag], worker_data->y[cur_mag], worker_data->z[cur_mag],
471  worker_data->calibration_counter_total[cur_mag],
473 
474  worker_data->x[cur_mag][worker_data->calibration_counter_total[cur_mag]] = mag.x;
475  worker_data->y[cur_mag][worker_data->calibration_counter_total[cur_mag]] = mag.y;
476  worker_data->z[cur_mag][worker_data->calibration_counter_total[cur_mag]] = mag.z;
477  worker_data->calibration_counter_total[cur_mag]++;
478  }
479  }
480 
481  // Keep calibration of all mags in lockstep
482  if (rejected) {
483  // Reset counts, since one of the mags rejected the measurement
484  for (size_t cur_mag = 0; cur_mag < max_mags; cur_mag++) {
485  worker_data->calibration_counter_total[cur_mag] = prev_count[cur_mag];
486  }
487 
488  } else {
489  calibration_counter_side++;
490 
491  unsigned new_progress = progress_percentage(worker_data) +
492  (unsigned)((100 / calibration_sides) * ((float)calibration_counter_side / (float)
493  worker_data->calibration_points_perside));
494 
495  if (new_progress - _last_mag_progress > 3) {
496  // Progress indicator for side
498  "[cal] %s side calibration: progress <%u>",
499  detect_orientation_str(orientation), new_progress);
500  px4_usleep(20000);
501 
502  _last_mag_progress = new_progress;
503  }
504  }
505 
506  } else {
507  poll_errcount++;
508  }
509 
510  if (poll_errcount > worker_data->calibration_points_perside * 3) {
511  result = calibrate_return_error;
513  break;
514  }
515  }
516 
517  if (result == calibrate_return_ok) {
518  calibration_log_info(worker_data->mavlink_log_pub, "[cal] %s side done, rotate to a different side",
519  detect_orientation_str(orientation));
520 
521  worker_data->done_count++;
522  px4_usleep(20000);
524  }
525 
526  return result;
527 }
528 
530 {
532 
533  mag_worker_data_t worker_data;
534 
535  worker_data.mavlink_log_pub = mavlink_log_pub;
536  worker_data.done_count = 0;
539  worker_data.calibration_interval_perside_useconds = worker_data.calibration_interval_perside_seconds * 1000 * 1000;
540 
541  // Collect: As defined by configuration
542  // start with a full mask, all six bits set
543  int32_t cal_mask = (1 << 6) - 1;
544  param_get(param_find("CAL_MAG_SIDES"), &cal_mask);
545 
546  calibration_sides = 0;
547 
548  for (unsigned i = 0; i < (sizeof(worker_data.side_data_collected) / sizeof(worker_data.side_data_collected[0])); i++) {
549 
550  if ((cal_mask & (1 << i)) > 0) {
551  // mark as missing
552  worker_data.side_data_collected[i] = false;
554 
555  } else {
556  // mark as completed from the beginning
557  worker_data.side_data_collected[i] = true;
558 
559  calibration_log_info(mavlink_log_pub,
560  "[cal] %s side done, rotate to a different side",
561  detect_orientation_str(static_cast<enum detect_orientation_return>(i)));
562  px4_usleep(100000);
563  }
564  }
565 
566  for (size_t cur_mag = 0; cur_mag < max_mags; cur_mag++) {
567  // Initialize to no subscription
568  worker_data.sub_mag[cur_mag] = -1;
569 
570  // Initialize to no memory allocated
571  worker_data.x[cur_mag] = nullptr;
572  worker_data.y[cur_mag] = nullptr;
573  worker_data.z[cur_mag] = nullptr;
574  worker_data.calibration_counter_total[cur_mag] = 0;
575  }
576 
577  const unsigned int calibration_points_maxcount = calibration_sides * worker_data.calibration_points_perside;
578 
579  char str[30];
580 
581  // Get actual mag count and alloate only as much memory as needed
582  const unsigned orb_mag_count = orb_group_count(ORB_ID(sensor_mag));
583 
584  // Warn that we will not calibrate more than max_mags magnetometers
585  if (orb_mag_count > max_mags) {
586  calibration_log_critical(mavlink_log_pub, "Detected %u mags, but will calibrate only %u", orb_mag_count, max_mags);
587  }
588 
589  for (size_t cur_mag = 0; cur_mag < orb_mag_count && cur_mag < max_mags; cur_mag++) {
590  worker_data.x[cur_mag] = static_cast<float *>(malloc(sizeof(float) * calibration_points_maxcount));
591  worker_data.y[cur_mag] = static_cast<float *>(malloc(sizeof(float) * calibration_points_maxcount));
592  worker_data.z[cur_mag] = static_cast<float *>(malloc(sizeof(float) * calibration_points_maxcount));
593 
594  if (worker_data.x[cur_mag] == nullptr || worker_data.y[cur_mag] == nullptr || worker_data.z[cur_mag] == nullptr) {
595  calibration_log_critical(mavlink_log_pub, "ERROR: out of memory");
596  result = calibrate_return_error;
597  }
598  }
599 
600 
601  // Setup subscriptions to mag sensors
602  if (result == calibrate_return_ok) {
603 
604  // We should not try to subscribe if the topic doesn't actually exist and can be counted.
605  for (unsigned cur_mag = 0; cur_mag < orb_mag_count && cur_mag < max_mags; cur_mag++) {
606 
607  // Lock in to correct ORB instance
608  bool found_cur_mag = false;
609 
610  for (unsigned i = 0; i < orb_mag_count && !found_cur_mag; i++) {
611  worker_data.sub_mag[cur_mag] = orb_subscribe_multi(ORB_ID(sensor_mag), i);
612 
613  struct mag_report report;
614  orb_copy(ORB_ID(sensor_mag), worker_data.sub_mag[cur_mag], &report);
615 
616 #ifdef __PX4_NUTTX
617 
618  // For NuttX, we get the UNIQUE device ID from the sensor driver via an IOCTL
619  // and match it up with the one from the uORB subscription, because the
620  // instance ordering of uORB and the order of the FDs may not be the same.
621 
622  if (report.device_id == (uint32_t)device_ids[cur_mag]) {
623  // Device IDs match, correct ORB instance for this mag
624  found_cur_mag = true;
625 
626  } else {
627  orb_unsubscribe(worker_data.sub_mag[cur_mag]);
628  }
629 
630 #else
631 
632  // For the DriverFramework drivers, we fill device ID (this is the first time) by copying one report.
633  device_ids[cur_mag] = report.device_id;
634  found_cur_mag = true;
635 
636 #endif
637  }
638 
639  if (!found_cur_mag) {
640  calibration_log_critical(mavlink_log_pub, "Mag #%u (ID %u) no matching uORB devid", cur_mag, device_ids[cur_mag]);
641  result = calibrate_return_error;
642  break;
643  }
644 
645  if (device_ids[cur_mag] != 0) {
646  // Get priority
647  int32_t prio;
648  orb_priority(worker_data.sub_mag[cur_mag], &prio);
649 
650  if (prio > device_prio_max) {
651  device_prio_max = prio;
652  device_id_primary = device_ids[cur_mag];
653  }
654 
655  } else {
656  calibration_log_critical(mavlink_log_pub, "Mag #%u no device id, abort", cur_mag);
657  result = calibrate_return_error;
658  break;
659  }
660  }
661  }
662 
663  // Limit update rate to get equally spaced measurements over time (in ms)
664  if (result == calibrate_return_ok) {
665  for (unsigned cur_mag = 0; cur_mag < max_mags; cur_mag++) {
666  if (device_ids[cur_mag] != 0) {
667  // Mag in this slot is available
668  unsigned int orb_interval_msecs = (worker_data.calibration_interval_perside_useconds / 1000) /
669  worker_data.calibration_points_perside;
670 
671  //calibration_log_info(mavlink_log_pub, "Orb interval %u msecs", orb_interval_msecs);
672  orb_set_interval(worker_data.sub_mag[cur_mag], orb_interval_msecs);
673  }
674  }
675 
676  }
677 
678  if (result == calibrate_return_ok) {
679  int cancel_sub = calibrate_cancel_subscribe();
680 
681  result = calibrate_from_orientation(mavlink_log_pub, // uORB handle to write output
682  cancel_sub, // Subscription to vehicle_command for cancel support
683  worker_data.side_data_collected, // Sides to calibrate
684  mag_calibration_worker, // Calibration worker
685  &worker_data, // Opaque data for calibration worked
686  true); // true: lenient still detection
687  calibrate_cancel_unsubscribe(cancel_sub);
688  }
689 
690  // Close subscriptions
691  for (unsigned cur_mag = 0; cur_mag < max_mags; cur_mag++) {
692  if (worker_data.sub_mag[cur_mag] >= 0) {
693  px4_close(worker_data.sub_mag[cur_mag]);
694  }
695  }
696 
697  // Calculate calibration values for each mag
698 
699  float sphere_x[max_mags];
700  float sphere_y[max_mags];
701  float sphere_z[max_mags];
702  float sphere_radius[max_mags];
703  float diag_x[max_mags];
704  float diag_y[max_mags];
705  float diag_z[max_mags];
706  float offdiag_x[max_mags];
707  float offdiag_y[max_mags];
708  float offdiag_z[max_mags];
709 
710  for (unsigned cur_mag = 0; cur_mag < max_mags; cur_mag++) {
711  sphere_x[cur_mag] = 0.0f;
712  sphere_y[cur_mag] = 0.0f;
713  sphere_z[cur_mag] = 0.0f;
714  sphere_radius[cur_mag] = 0.2f;
715  diag_x[cur_mag] = 1.0f;
716  diag_y[cur_mag] = 1.0f;
717  diag_z[cur_mag] = 1.0f;
718  offdiag_x[cur_mag] = 0.0f;
719  offdiag_y[cur_mag] = 0.0f;
720  offdiag_z[cur_mag] = 0.0f;
721  }
722 
723  // Sphere fit the data to get calibration values
724  if (result == calibrate_return_ok) {
725  for (unsigned cur_mag = 0; cur_mag < max_mags; cur_mag++) {
726  if (device_ids[cur_mag] != 0) {
727  // Mag in this slot is available and we should have values for it to calibrate
728 
729  ellipsoid_fit_least_squares(worker_data.x[cur_mag], worker_data.y[cur_mag], worker_data.z[cur_mag],
730  worker_data.calibration_counter_total[cur_mag],
731  100, 0.0f,
732  &sphere_x[cur_mag], &sphere_y[cur_mag], &sphere_z[cur_mag],
733  &sphere_radius[cur_mag],
734  &diag_x[cur_mag], &diag_y[cur_mag], &diag_z[cur_mag],
735  &offdiag_x[cur_mag], &offdiag_y[cur_mag], &offdiag_z[cur_mag]);
736 
737  result = check_calibration_result(sphere_x[cur_mag], sphere_y[cur_mag], sphere_z[cur_mag],
738  sphere_radius[cur_mag],
739  diag_x[cur_mag], diag_y[cur_mag], diag_z[cur_mag],
740  offdiag_x[cur_mag], offdiag_y[cur_mag], offdiag_z[cur_mag],
741  mavlink_log_pub, cur_mag);
742 
743  if (result == calibrate_return_error) {
744  break;
745  }
746  }
747  }
748  }
749 
750  // Print uncalibrated data points
751  if (result == calibrate_return_ok) {
752 
753  // DO NOT REMOVE! Critical validation data!
754 
755  // printf("RAW DATA:\n--------------------\n");
756  // for (size_t cur_mag = 0; cur_mag < max_mags; cur_mag++) {
757 
758  // if (worker_data.calibration_counter_total[cur_mag] == 0) {
759  // continue;
760  // }
761 
762  // printf("RAW: MAG %u with %u samples:\n", (unsigned)cur_mag, (unsigned)worker_data.calibration_counter_total[cur_mag]);
763 
764  // for (size_t i = 0; i < worker_data.calibration_counter_total[cur_mag]; i++) {
765  // float x = worker_data.x[cur_mag][i];
766  // float y = worker_data.y[cur_mag][i];
767  // float z = worker_data.z[cur_mag][i];
768  // printf("%8.4f, %8.4f, %8.4f\n", (double)x, (double)y, (double)z);
769  // }
770 
771  // printf(">>>>>>>\n");
772  // }
773 
774  // printf("CALIBRATED DATA:\n--------------------\n");
775  // for (size_t cur_mag = 0; cur_mag < max_mags; cur_mag++) {
776 
777  // if (worker_data.calibration_counter_total[cur_mag] == 0) {
778  // continue;
779  // }
780 
781  // printf("Calibrated: MAG %u with %u samples:\n", (unsigned)cur_mag, (unsigned)worker_data.calibration_counter_total[cur_mag]);
782 
783  // for (size_t i = 0; i < worker_data.calibration_counter_total[cur_mag]; i++) {
784  // float x = worker_data.x[cur_mag][i] - sphere_x[cur_mag];
785  // float y = worker_data.y[cur_mag][i] - sphere_y[cur_mag];
786  // float z = worker_data.z[cur_mag][i] - sphere_z[cur_mag];
787  // printf("%8.4f, %8.4f, %8.4f\n", (double)x, (double)y, (double)z);
788  // }
789 
790  // printf("SPHERE RADIUS: %8.4f\n", (double)sphere_radius[cur_mag]);
791  // printf(">>>>>>>\n");
792  // }
793  }
794 
795  // Data points are no longer needed
796  for (size_t cur_mag = 0; cur_mag < max_mags; cur_mag++) {
797  free(worker_data.x[cur_mag]);
798  free(worker_data.y[cur_mag]);
799  free(worker_data.z[cur_mag]);
800  }
801 
802  if (result == calibrate_return_ok) {
803 
804  for (unsigned cur_mag = 0; cur_mag < max_mags; cur_mag++) {
805  if (device_ids[cur_mag] != 0) {
806  struct mag_calibration_s mscale;
807  mscale.x_scale = 1.0;
808  mscale.y_scale = 1.0;
809  mscale.z_scale = 1.0;
810 
811 #ifdef __PX4_NUTTX
812  int fd_mag = -1;
813 
814  // Set new scale
815  (void)sprintf(str, "%s%u", MAG_BASE_DEVICE_PATH, cur_mag);
816  fd_mag = px4_open(str, 0);
817 
818  if (fd_mag < 0) {
819  calibration_log_critical(mavlink_log_pub, "ERROR: unable to open mag device #%u", cur_mag);
820  result = calibrate_return_error;
821  }
822 
823  if (result == calibrate_return_ok) {
824  if (px4_ioctl(fd_mag, MAGIOCGSCALE, (long unsigned int)&mscale) != PX4_OK) {
825  calibration_log_critical(mavlink_log_pub, "ERROR: failed to get current calibration #%u", cur_mag);
826  result = calibrate_return_error;
827  }
828  }
829 
830 #endif
831 
832  if (result == calibrate_return_ok) {
833  mscale.x_offset = sphere_x[cur_mag];
834  mscale.y_offset = sphere_y[cur_mag];
835  mscale.z_offset = sphere_z[cur_mag];
836  mscale.x_scale = diag_x[cur_mag];
837  mscale.y_scale = diag_y[cur_mag];
838  mscale.z_scale = diag_z[cur_mag];
839 
840 #ifdef __PX4_NUTTX
841 
842  if (px4_ioctl(fd_mag, MAGIOCSSCALE, (long unsigned int)&mscale) != PX4_OK) {
843  calibration_log_critical(mavlink_log_pub, CAL_ERROR_APPLY_CAL_MSG, cur_mag);
844  result = calibrate_return_error;
845  }
846 
847 #endif
848  }
849 
850 #ifdef __PX4_NUTTX
851 
852  // Mag device no longer needed
853  if (fd_mag >= 0) {
854  px4_close(fd_mag);
855  }
856 
857 #endif
858 
859  if (result == calibrate_return_ok) {
860  bool failed = false;
861 
862  /* set parameters */
863 
864  (void)sprintf(str, "CAL_MAG%u_ID", cur_mag);
865  failed |= (PX4_OK != param_set_no_notification(param_find(str), &(device_ids[cur_mag])));
866  (void)sprintf(str, "CAL_MAG%u_XOFF", cur_mag);
867  failed |= (PX4_OK != param_set_no_notification(param_find(str), &(mscale.x_offset)));
868  (void)sprintf(str, "CAL_MAG%u_YOFF", cur_mag);
869  failed |= (PX4_OK != param_set_no_notification(param_find(str), &(mscale.y_offset)));
870  (void)sprintf(str, "CAL_MAG%u_ZOFF", cur_mag);
871  failed |= (PX4_OK != param_set_no_notification(param_find(str), &(mscale.z_offset)));
872 
873  // FIXME: scaling is not used right now on QURT
874 #ifndef __PX4_QURT
875  (void)sprintf(str, "CAL_MAG%u_XSCALE", cur_mag);
876  failed |= (PX4_OK != param_set_no_notification(param_find(str), &(mscale.x_scale)));
877  (void)sprintf(str, "CAL_MAG%u_YSCALE", cur_mag);
878  failed |= (PX4_OK != param_set_no_notification(param_find(str), &(mscale.y_scale)));
879  (void)sprintf(str, "CAL_MAG%u_ZSCALE", cur_mag);
880  failed |= (PX4_OK != param_set_no_notification(param_find(str), &(mscale.z_scale)));
881 #endif
882 
883  if (failed) {
884  calibration_log_critical(mavlink_log_pub, CAL_ERROR_SET_PARAMS_MSG, cur_mag);
885  result = calibrate_return_error;
886 
887  } else {
888  calibration_log_info(mavlink_log_pub, "[cal] mag #%u off: x:%.2f y:%.2f z:%.2f Ga",
889  cur_mag, (double)mscale.x_offset, (double)mscale.y_offset, (double)mscale.z_offset);
890 #ifndef __PX4_QURT
891  calibration_log_info(mavlink_log_pub, "[cal] mag #%u scale: x:%.2f y:%.2f z:%.2f",
892  cur_mag, (double)mscale.x_scale, (double)mscale.y_scale, (double)mscale.z_scale);
893 #endif
894  px4_usleep(200000);
895  }
896  }
897  }
898  }
899 
900  // Trigger a param set on the last step so the whole
901  // system updates
902  (void)param_set(param_find("CAL_MAG_PRIME"), &(device_id_primary));
903  }
904 
905  return result;
906 }
#define CAL_ERROR_SENSOR_MSG
#define MAGIOCGSCALE
copy the mag scaling constants to the structure pointed to by (arg)
Definition: drv_mag.h:76
static orb_advert_t * mavlink_log_pub
static constexpr unsigned int calibraton_duration_seconds
The total duration the routine is allowed to take.
#define MAGIOCGEXTERNAL
determine if mag is external or onboard
Definition: drv_mag.h:88
Accelerometer driver interface.
int orb_copy(const struct orb_metadata *meta, int handle, void *buffer)
Definition: uORB.cpp:90
#define CAL_QGC_DONE_MSG
unsigned int calibration_interval_perside_seconds
Gyroscope driver interface.
static unsigned progress_percentage(mag_worker_data_t *worker_data)
__EXPORT int param_get(param_t param, void *val)
Copy the value of a parameter.
Definition: parameters.cpp:589
__EXPORT int param_set_no_notification(param_t param, const void *val)
Set the value of a parameter, but do not notify the system about the change.
Definition: parameters.cpp:820
Common calibration messages.
detect_orientation_return
Barometer calibration routine.
int orb_priority(int handle, int32_t *priority)
Definition: uORB.cpp:121
int orb_set_interval(int handle, unsigned interval)
Definition: uORB.cpp:126
__EXPORT int param_set(param_t param, const void *val)
Set the value of a parameter.
Definition: parameters.cpp:814
int px4_poll(px4_pollfd_struct_t *fds, nfds_t nfds, int timeout)
calibrate_return calibrate_from_orientation(orb_advert_t *mavlink_log_pub, int cancel_sub, bool side_data_collected[detect_orientation_side_count], calibration_from_orientation_worker_t calibration_worker, void *worker_data, bool lenient_still_position)
Perform calibration sequence which require a rest orientation detection prior to calibration.
mag scaling factors; Vout = (Vin * Vscale) + Voffset
Definition: drv_mag.h:56
#define CAL_ERROR_RESET_CAL_MSG
int ellipsoid_fit_least_squares(const float x[], const float y[], const float z[], unsigned int size, int max_iterations, float delta, float *offset_x, float *offset_y, float *offset_z, float *sphere_radius, float *diag_x, float *diag_y, float *diag_z, float *offdiag_x, float *offdiag_y, float *offdiag_z)
High-resolution timer with callouts and timekeeping.
static unsigned _last_mag_progress
unsigned int calibration_counter_total[max_mags]
Global flash based parameter store.
static calibrate_return mag_calibration_worker(detect_orientation_return orientation, int cancel_sub, void *data)
orb_advert_t * mavlink_log_pub
float x_offset
Definition: drv_mag.h:57
void calibrate_cancel_unsubscribe(int cmd_sub)
Called to cancel the subscription to the cancel command.
int orb_subscribe(const struct orb_metadata *meta)
Definition: uORB.cpp:75
uint64_t calibration_interval_perside_useconds
bool side_data_collected[detect_orientation_side_count]
#define ORB_ID(_name)
Generates a pointer to the uORB metadata structure for a given topic.
Definition: uORB.h:87
const char * detect_orientation_str(enum detect_orientation_return orientation)
Returns the human readable string representation of the orientation.
Commander helper functions definitions.
#define MAGIOCSSCALE
set the mag scaling constants to the structure pointed to by (arg)
Definition: drv_mag.h:73
float * y[max_mags]
#define MAG_BASE_DEVICE_PATH
Definition: drv_mag.h:47
#define MAGIOCCALIBRATE
perform self-calibration, update scale factors to canonical units
Definition: drv_mag.h:82
unsigned int calibration_points_perside
static unsigned int calibration_sides
The total number of sides.
static constexpr unsigned max_mags
calibrate_return mag_calibrate_all(orb_advert_t *mavlink_log_pub)
uint8_t * data
Definition: dataman.cpp:149
#define CAL_ERROR_APPLY_CAL_MSG
int orb_unsubscribe(int handle)
Definition: uORB.cpp:85
Vector< float, 6 > f(float t, const Matrix< float, 6, 1 > &, const Matrix< float, 3, 1 > &)
Definition: integration.cpp:8
calibrate_return
#define CAL_QGC_STARTED_MSG
float y_offset
Definition: drv_mag.h:59
#define warnx(...)
Definition: err.h:95
#define CAL_ERROR_SET_PARAMS_MSG
int do_mag_calibration(orb_advert_t *mavlink_log_pub)
Simple error/warning functions, heavily inspired by the BSD functions of the same names...
float * z[max_mags]
float * x[max_mags]
int32_t device_id_primary
static constexpr float MAG_MAX_OFFSET_LEN
The maximum measurement range is ~1.9 Ga, the earth field is ~0.6 Ga, so an offset larger than ~1...
__BEGIN_DECLS typedef uint64_t hrt_abstime
Absolute time, in microsecond units.
Definition: drv_hrt.h:58
Driver for the PX4 audio alarm port, /dev/tone_alarm.
__BEGIN_DECLS typedef void * orb_advert_t
ORB topic advertiser handle.
Definition: uORB.h:134
int fd
Definition: dataman.cpp:146
int orb_group_count(const struct orb_metadata *meta)
Get the number of published instances of a topic group.
Definition: uORB.cpp:110
float z_offset
Definition: drv_mag.h:61
__EXPORT param_t param_find(const char *name)
Look up a parameter by name.
Definition: parameters.cpp:370
int device_prio_max
int px4_open(const char *path, int flags,...)
__EXPORT void param_notify_changes(void)
Notify the system about parameter changes.
Definition: parameters.cpp:323
bool calibrate_cancel_check(orb_advert_t *mavlink_log_pub, int cancel_sub)
Used to periodically check for a cancel command.
int orb_subscribe_multi(const struct orb_metadata *meta, unsigned instance)
Definition: uORB.cpp:80
int sub_mag[max_mags]
static calibrate_return check_calibration_result(float offset_x, float offset_y, float offset_z, float sphere_radius, float diag_x, float diag_y, float diag_z, float offdiag_x, float offdiag_y, float offdiag_z, orb_advert_t *mavlink_log_pub, size_t cur_mag)
Data passed to calibration worker routine.
#define calibration_log_critical(_pub, _text,...)
static constexpr unsigned int calibration_total_points
The total points per magnetometer.
static bool reject_sample(float sx, float sy, float sz, float x[], float y[], float z[], unsigned count, unsigned max_count)
#define calibration_log_emergency(_pub, _text,...)
static const unsigned detect_orientation_side_count
static const char * sensor_name
#define CAL_QGC_PROGRESS_MSG
#define CAL_QGC_FAILED_MSG
void set_tune(int tune)
int calibrate_cancel_subscribe()
Called at the beginning of calibration in order to subscribe to the cancel command.
__EXPORT hrt_abstime hrt_absolute_time(void)
Get absolute time in [us] (does not wrap).
#define calibration_log_info(_pub, _text,...)
int32_t device_ids[max_mags]
static constexpr float mag_sphere_radius
int px4_close(int fd)
#define mag_report
Definition: drv_mag.h:53
int px4_ioctl(int fd, int cmd, unsigned long arg)