magfit_rotation_gyro.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #!/usr/bin/env python
  2. '''
  3. fit best estimate of magnetometer rotation to gyro data
  4. '''
  5. from __future__ import print_function
  6. from builtins import range
  7. from builtins import object
  8. from argparse import ArgumentParser
  9. parser = ArgumentParser(description=__doc__)
  10. parser.add_argument("--no-timestamps", dest="notimestamps", action='store_true', help="Log doesn't have timestamps")
  11. parser.add_argument("--verbose", action='store_true', help="verbose output")
  12. parser.add_argument("--min-rotation", default=5.0, type=float, help="min rotation to add point")
  13. parser.add_argument("logs", metavar="LOG", nargs="+")
  14. args = parser.parse_args()
  15. from pymavlink import mavutil
  16. from pymavlink.rotmat import Vector3, Matrix3
  17. from math import radians
  18. class Rotation(object):
  19. def __init__(self, name, roll, pitch, yaw):
  20. self.name = name
  21. self.roll = roll
  22. self.pitch = pitch
  23. self.yaw = yaw
  24. self.r = Matrix3()
  25. self.r.from_euler(self.roll, self.pitch, self.yaw)
  26. def is_90_degrees(self):
  27. return (self.roll % 90 == 0) and (self.pitch % 90 == 0) and (self.yaw % 90 == 0)
  28. def __str__(self):
  29. return self.name
  30. # the rotations used in APM
  31. rotations = [
  32. Rotation("ROTATION_NONE", 0, 0, 0),
  33. Rotation("ROTATION_YAW_45", 0, 0, 45),
  34. Rotation("ROTATION_YAW_90", 0, 0, 90),
  35. Rotation("ROTATION_YAW_135", 0, 0, 135),
  36. Rotation("ROTATION_YAW_180", 0, 0, 180),
  37. Rotation("ROTATION_YAW_225", 0, 0, 225),
  38. Rotation("ROTATION_YAW_270", 0, 0, 270),
  39. Rotation("ROTATION_YAW_315", 0, 0, 315),
  40. Rotation("ROTATION_ROLL_180", 180, 0, 0),
  41. Rotation("ROTATION_ROLL_180_YAW_45", 180, 0, 45),
  42. Rotation("ROTATION_ROLL_180_YAW_90", 180, 0, 90),
  43. Rotation("ROTATION_ROLL_180_YAW_135", 180, 0, 135),
  44. Rotation("ROTATION_PITCH_180", 0, 180, 0),
  45. Rotation("ROTATION_ROLL_180_YAW_225", 180, 0, 225),
  46. Rotation("ROTATION_ROLL_180_YAW_270", 180, 0, 270),
  47. Rotation("ROTATION_ROLL_180_YAW_315", 180, 0, 315),
  48. Rotation("ROTATION_ROLL_90", 90, 0, 0),
  49. Rotation("ROTATION_ROLL_90_YAW_45", 90, 0, 45),
  50. Rotation("ROTATION_ROLL_90_YAW_90", 90, 0, 90),
  51. Rotation("ROTATION_ROLL_90_YAW_135", 90, 0, 135),
  52. Rotation("ROTATION_ROLL_270", 270, 0, 0),
  53. Rotation("ROTATION_ROLL_270_YAW_45", 270, 0, 45),
  54. Rotation("ROTATION_ROLL_270_YAW_90", 270, 0, 90),
  55. Rotation("ROTATION_ROLL_270_YAW_135", 270, 0, 135),
  56. Rotation("ROTATION_PITCH_90", 0, 90, 0),
  57. Rotation("ROTATION_PITCH_270", 0, 270, 0),
  58. Rotation("ROTATION_PITCH_180_YAW_90", 0, 180, 90),
  59. Rotation("ROTATION_PITCH_180_YAW_270", 0, 180, 270),
  60. Rotation("ROTATION_ROLL_90_PITCH_90", 90, 90, 0),
  61. Rotation("ROTATION_ROLL_180_PITCH_90", 180, 90, 0),
  62. Rotation("ROTATION_ROLL_270_PITCH_90", 270, 90, 0),
  63. Rotation("ROTATION_ROLL_90_PITCH_180", 90, 180, 0),
  64. Rotation("ROTATION_ROLL_270_PITCH_180", 270, 180, 0),
  65. Rotation("ROTATION_ROLL_90_PITCH_270", 90, 270, 0),
  66. Rotation("ROTATION_ROLL_180_PITCH_270", 180, 270, 0),
  67. Rotation("ROTATION_ROLL_270_PITCH_270", 270, 270, 0),
  68. Rotation("ROTATION_ROLL_90_PITCH_180_YAW_90", 90, 180, 90),
  69. Rotation("ROTATION_ROLL_90_YAW_270", 90, 0, 270)
  70. ]
  71. def mag_fixup(mag, AHRS_ORIENTATION, COMPASS_ORIENT, COMPASS_EXTERNAL):
  72. '''fixup a mag vector back to original value using AHRS and Compass orientation parameters'''
  73. if COMPASS_EXTERNAL == 0 and AHRS_ORIENTATION != 0:
  74. # undo any board orientation
  75. mag = rotations[AHRS_ORIENTATION].r.transposed() * mag
  76. # undo any compass orientation
  77. if COMPASS_ORIENT != 0:
  78. mag = rotations[COMPASS_ORIENT].r.transposed() * mag
  79. return mag
  80. def add_errors(mag, gyr, last_mag, deltat, total_error, rotations):
  81. for i in range(len(rotations)):
  82. if not rotations[i].is_90_degrees():
  83. continue
  84. r = rotations[i].r
  85. m = Matrix3()
  86. m.rotate(gyr * deltat)
  87. rmag1 = r * last_mag
  88. rmag2 = r * mag
  89. rmag3 = m.transposed() * rmag1
  90. err = rmag3 - rmag2
  91. total_error[i] += err.length()
  92. def magfit(logfile):
  93. '''find best magnetometer rotation fit to a log file'''
  94. print("Processing log %s" % filename)
  95. mlog = mavutil.mavlink_connection(filename, notimestamps=args.notimestamps)
  96. last_mag = None
  97. last_usec = 0
  98. count = 0
  99. total_error = [0]*len(rotations)
  100. AHRS_ORIENTATION = 0
  101. COMPASS_ORIENT = 0
  102. COMPASS_EXTERNAL = 0
  103. last_gyr = None
  104. # now gather all the data
  105. while True:
  106. m = mlog.recv_match()
  107. if m is None:
  108. break
  109. if m.get_type() in ["PARAM_VALUE", "PARM"]:
  110. if m.get_type() == "PARM":
  111. name = str(m.Name)
  112. value = int(m.Value)
  113. else:
  114. name = str(m.param_id)
  115. value = int(m.param_value)
  116. if name == "AHRS_ORIENTATION":
  117. AHRS_ORIENTATION = value
  118. if name == 'COMPASS_ORIENT':
  119. COMPASS_ORIENT = value
  120. if name == 'COMPASS_EXTERNAL':
  121. COMPASS_EXTERNAL = value
  122. if m.get_type() in ["RAW_IMU", "MAG","IMU"]:
  123. if m.get_type() == "RAW_IMU":
  124. mag = Vector3(m.xmag, m.ymag, m.zmag)
  125. gyr = Vector3(m.xgyro, m.ygyro, m.zgyro) * 0.001
  126. usec = m.time_usec
  127. elif m.get_type() == "IMU":
  128. last_gyr = Vector3(m.GyrX,m.GyrY,m.GyrZ)
  129. continue
  130. elif last_gyr is not None:
  131. mag = Vector3(m.MagX,m.MagY,m.MagZ)
  132. gyr = last_gyr
  133. usec = m.TimeMS * 1000
  134. mag = mag_fixup(mag, AHRS_ORIENTATION, COMPASS_ORIENT, COMPASS_EXTERNAL)
  135. if last_mag is not None and gyr.length() > radians(args.min_rotation):
  136. add_errors(mag, gyr, last_mag, (usec - last_usec)*1.0e-6, total_error, rotations)
  137. count += 1
  138. last_mag = mag
  139. last_usec = usec
  140. best_i = 0
  141. best_err = total_error[0]
  142. for i in range(len(rotations)):
  143. r = rotations[i]
  144. if not r.is_90_degrees():
  145. continue
  146. if args.verbose:
  147. print("%s err=%.2f" % (r, total_error[i]/count))
  148. if total_error[i] < best_err:
  149. best_i = i
  150. best_err = total_error[i]
  151. r = rotations[best_i]
  152. print("Current rotation is AHRS_ORIENTATION=%s COMPASS_ORIENT=%s COMPASS_EXTERNAL=%u" % (
  153. rotations[AHRS_ORIENTATION],
  154. rotations[COMPASS_ORIENT],
  155. COMPASS_EXTERNAL))
  156. print("Best rotation is %s err=%.2f from %u points" % (r, best_err/count, count))
  157. print("Please set AHRS_ORIENTATION=%s COMPASS_ORIENT=%s COMPASS_EXTERNAL=1" % (
  158. rotations[AHRS_ORIENTATION],
  159. r))
  160. for filename in args.logs:
  161. magfit(filename)