|
| 1 | +# Sensor fusion for the micropython board. 21st May 2015 |
| 2 | +# Ported to Python by Peter Hinch |
| 3 | +# V0.1 Experimental |
| 4 | + |
| 5 | +import pyb |
| 6 | +from math import sqrt, atan2, asin, degrees, radians |
| 7 | +''' |
| 8 | +Supports 6 and 9 degrees of freedom sensors. Tested with InvenSense MPU-9150 9DOF sensor. |
| 9 | +Source https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU.git |
| 10 | +also https://github.com/kriswiner/MPU-9250.git |
| 11 | +Ported to Python. Integrator timing adapted for pyboard. |
| 12 | +User should repeatedly call the appropriate update method and extract the yaw pitch and roll angles as |
| 13 | +reuired using the angles method. |
| 14 | +''' |
| 15 | +class Fusion(object): |
| 16 | + ''' |
| 17 | + Class provides sensor fusion allowing yaw, pitch and roll to be extracted. This uses the Madgwick algorithm. |
| 18 | + The update method must be called peiodically. The calculations take 1.6mS on the Pyboard. Suggested update |
| 19 | + rate 50Hz (20mS) |
| 20 | + ''' |
| 21 | + magbias = [0, 0, 0] # local bias factors: average value of each axis when rotated 2*pi radians |
| 22 | + declination = 0 # Option to add offset for true north |
| 23 | + def __init__(self): |
| 24 | + self.start_time = None # Record time between updates |
| 25 | + self.q = [1.0, 0.0, 0.0, 0.0] # vector to hold quaternion |
| 26 | + GyroMeasError = radians(40) # Original code indicates this leads to a 2 sec response time |
| 27 | + self.beta = sqrt(3.0 / 4.0) * GyroMeasError # compute beta |
| 28 | + |
| 29 | + def angles(self): |
| 30 | + yaw = degrees(atan2(2.0 * (self.q[1] * self.q[2] + self.q[0] * self.q[3]), |
| 31 | + self.q[0] * self.q[0] + self.q[1] * self.q[1] - self.q[2] * self.q[2] - self.q[3] * self.q[3])) |
| 32 | + pitch = degrees(-asin(2.0 * (self.q[1] * self.q[3] - self.q[0] * self.q[2]))) |
| 33 | + roll = degrees(atan2(2.0 * (self.q[0] * self.q[1] + self.q[2] * self.q[3]), |
| 34 | + self.q[0] * self.q[0] - self.q[1] * self.q[1] - self.q[2] * self.q[2] + self.q[3] * self.q[3])) |
| 35 | + return yaw + self.declination, pitch, roll |
| 36 | + |
| 37 | + def update(self, accel, gyro, mag): # 3-tuples (x, y, z) for accel, gyro and mag data |
| 38 | + mx, my, mz = (mag[x] - self.magbias[x] for x in range(3)) # Units uT |
| 39 | + ax, ay, az = accel # Units G (but later normalised) |
| 40 | + gx, gy, gz = (radians(x) for x in gyro) # Units deg/s |
| 41 | + if self.start_time is None: |
| 42 | + self.start_time = pyb.micros() # First run |
| 43 | + q1, q2, q3, q4 = (self.q[x] for x in range(4)) # short name local variable for readability |
| 44 | + # Auxiliary variables to avoid repeated arithmetic |
| 45 | + _2q1 = 2.0 * q1 |
| 46 | + _2q2 = 2.0 * q2 |
| 47 | + _2q3 = 2.0 * q3 |
| 48 | + _2q4 = 2.0 * q4 |
| 49 | + _2q1q3 = 2.0 * q1 * q3 |
| 50 | + _2q3q4 = 2.0 * q3 * q4 |
| 51 | + q1q1 = q1 * q1 |
| 52 | + q1q2 = q1 * q2 |
| 53 | + q1q3 = q1 * q3 |
| 54 | + q1q4 = q1 * q4 |
| 55 | + q2q2 = q2 * q2 |
| 56 | + q2q3 = q2 * q3 |
| 57 | + q2q4 = q2 * q4 |
| 58 | + q3q3 = q3 * q3 |
| 59 | + q3q4 = q3 * q4 |
| 60 | + q4q4 = q4 * q4 |
| 61 | + |
| 62 | + # Normalise accelerometer measurement |
| 63 | + norm = sqrt(ax * ax + ay * ay + az * az) |
| 64 | + if (norm == 0.0): |
| 65 | + return # handle NaN |
| 66 | + norm = 1.0/norm |
| 67 | + ax *= norm |
| 68 | + ay *= norm |
| 69 | + az *= norm |
| 70 | + |
| 71 | + # Normalise magnetometer measurement |
| 72 | + norm = sqrt(mx * mx + my * my + mz * mz) |
| 73 | + if (norm == 0.0): |
| 74 | + return # handle NaN |
| 75 | + norm = 1.0/norm |
| 76 | + mx *= norm |
| 77 | + my *= norm |
| 78 | + mz *= norm |
| 79 | + |
| 80 | + # Reference direction of Earth's magnetic field |
| 81 | + _2q1mx = 2.0 * q1 * mx |
| 82 | + _2q1my = 2.0 * q1 * my |
| 83 | + _2q1mz = 2.0 * q1 * mz |
| 84 | + _2q2mx = 2.0 * q2 * mx |
| 85 | + hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 + _2q2 * mz * q4 - mx * q3q3 - mx * q4q4 |
| 86 | + hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 + my * q3q3 + _2q3 * mz * q4 - my * q4q4 |
| 87 | + _2bx = sqrt(hx * hx + hy * hy) |
| 88 | + _2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 + _2q3 * my * q4 - mz * q3q3 + mz * q4q4 |
| 89 | + _4bx = 2.0 * _2bx |
| 90 | + _4bz = 2.0 * _2bz |
| 91 | + |
| 92 | + # Gradient descent algorithm corrective step |
| 93 | + s1 = (-_2q3 * (2.0 * q2q4 - _2q1q3 - ax) + _2q2 * (2.0 * q1q2 + _2q3q4 - ay) - _2bz * q3 * |
| 94 | + (_2bx * (0.5 - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q4 + _2bz * q2) * |
| 95 | + (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q3 * (_2bx * (q1q3 + q2q4) + |
| 96 | + _2bz * (0.5 - q2q2 - q3q3) - mz)) |
| 97 | + s2 = (_2q4 * (2.0 * q2q4 - _2q1q3 - ax) + _2q1 * (2.0 * q1q2 + _2q3q4 - ay) - 4.0 * q2 * |
| 98 | + (1.0 - 2.0 * q2q2 - 2.0 * q3q3 - az) + _2bz * q4 * (_2bx * (0.5 - q3q3 - q4q4) + _2bz * |
| 99 | + (q2q4 - q1q3) - mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 - q1q4) + _2bz * |
| 100 | + (q1q2 + q3q4) - my) + (_2bx * q4 - _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5 - q2q2 - q3q3) - mz)) |
| 101 | + s3 = (-_2q1 * (2.0 * q2q4 - _2q1q3 - ax) + _2q4 * (2.0 * q1q2 + _2q3q4 - ay) - 4.0 * q3 * |
| 102 | + (1.0 - 2.0 * q2q2 - 2.0 * q3q3 - az) + (-_4bx * q3 - _2bz * q1) * (_2bx * (0.5 - q3q3 - q4q4) + |
| 103 | + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 - q1q4) + |
| 104 | + _2bz * (q1q2 + q3q4) - my) + (_2bx * q1 - _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5 - q2q2 - q3q3) - mz)) |
| 105 | + s4 = (_2q2 * (2.0 * q2q4 - _2q1q3 - ax) + _2q3 * (2.0 * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) * |
| 106 | + (_2bx * (0.5 - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q1 + _2bz * q3) * |
| 107 | + (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q2 * (_2bx * (q1q3 + q2q4) + |
| 108 | + _2bz * (0.5 - q2q2 - q3q3) - mz)) |
| 109 | + norm = sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4) # normalise step magnitude |
| 110 | + norm = 1.0/norm |
| 111 | + s1 *= norm |
| 112 | + s2 *= norm |
| 113 | + s3 *= norm |
| 114 | + s4 *= norm |
| 115 | + |
| 116 | + # Compute rate of change of quaternion |
| 117 | + qDot1 = 0.5 * (-q2 * gx - q3 * gy - q4 * gz) - self.beta * s1 |
| 118 | + qDot2 = 0.5 * (q1 * gx + q3 * gz - q4 * gy) - self.beta * s2 |
| 119 | + qDot3 = 0.5 * (q1 * gy - q2 * gz + q4 * gx) - self.beta * s3 |
| 120 | + qDot4 = 0.5 * (q1 * gz + q2 * gy - q3 * gx) - self.beta * s4 |
| 121 | + |
| 122 | + # Integrate to yield quaternion |
| 123 | + deltat = pyb.elapsed_micros(self.start_time) / 1000000 |
| 124 | + self.start_time = pyb.micros() |
| 125 | + q1 += qDot1 * deltat |
| 126 | + q2 += qDot2 * deltat |
| 127 | + q3 += qDot3 * deltat |
| 128 | + q4 += qDot4 * deltat |
| 129 | + norm = sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4) # normalise quaternion |
| 130 | + norm = 1.0/norm |
| 131 | + self.q = q1 * norm, q2 * norm, q3 * norm, q4 * norm |
| 132 | + |
| 133 | + def update_nomag(self, accel, gyro): # 3-tuples (x, y, z) for accel, gyro |
| 134 | + ax, ay, az = accel # Units G (but later normalised) |
| 135 | + gx, gy, gz = (radians(x) for x in gyro) # Units deg/s |
| 136 | + if self.start_time is None: |
| 137 | + self.start_time = pyb.micros() # First run |
| 138 | + q1, q2, q3, q4 = (self.q[x] for x in range(4)) # short name local variable for readability |
| 139 | + # Auxiliary variables to avoid repeated arithmetic |
| 140 | + _2q1 = 2 * q1 |
| 141 | + _2q2 = 2 * q2 |
| 142 | + _2q3 = 2 * q3 |
| 143 | + _2q4 = 2 * q4 |
| 144 | + _4q1 = 4 * q1 |
| 145 | + _4q2 = 4 * q2 |
| 146 | + _4q3 = 4 * q3 |
| 147 | + _8q2 = 8 * q2 |
| 148 | + _8q3 = 8 * q3 |
| 149 | + q1q1 = q1 * q1 |
| 150 | + q2q2 = q2 * q2 |
| 151 | + q3q3 = q3 * q3 |
| 152 | + q4q4 = q4 * q4 |
| 153 | + |
| 154 | + # Normalise accelerometer measurement |
| 155 | + norm = sqrt(ax * ax + ay * ay + az * az) |
| 156 | + if (norm == 0): |
| 157 | + return # handle NaN |
| 158 | + norm = 1 / norm # use reciprocal for division |
| 159 | + ax *= norm |
| 160 | + ay *= norm |
| 161 | + az *= norm |
| 162 | + |
| 163 | + # Gradient decent algorithm corrective step |
| 164 | + s1 = _4q1 * q3q3 + _2q3 * ax + _4q1 * q2q2 - _2q2 * ay |
| 165 | + s2 = _4q2 * q4q4 - _2q4 * ax + 4 * q1q1 * q2 - _2q1 * ay - _4q2 + _8q2 * q2q2 + _8q2 * q3q3 + _4q2 * az |
| 166 | + s3 = 4 * q1q1 * q3 + _2q1 * ax + _4q3 * q4q4 - _2q4 * ay - _4q3 + _8q3 * q2q2 + _8q3 * q3q3 + _4q3 * az |
| 167 | + s4 = 4 * q2q2 * q4 - _2q2 * ax + 4 * q3q3 * q4 - _2q3 * ay |
| 168 | + norm = 1 / sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4) # normalise step magnitude |
| 169 | + s1 *= norm |
| 170 | + s2 *= norm |
| 171 | + s3 *= norm |
| 172 | + s4 *= norm |
| 173 | + |
| 174 | + # Compute rate of change of quaternion |
| 175 | + qDot1 = 0.5 * (-q2 * gx - q3 * gy - q4 * gz) - self.beta * s1 |
| 176 | + qDot2 = 0.5 * (q1 * gx + q3 * gz - q4 * gy) - self.beta * s2 |
| 177 | + qDot3 = 0.5 * (q1 * gy - q2 * gz + q4 * gx) - self.beta * s3 |
| 178 | + qDot4 = 0.5 * (q1 * gz + q2 * gy - q3 * gx) - self.beta * s4 |
| 179 | + |
| 180 | + # Integrate to yield quaternion |
| 181 | + deltat = pyb.elapsed_micros(self.start_time) / 1000000 |
| 182 | + self.start_time = pyb.micros() |
| 183 | + q1 += qDot1 * deltat |
| 184 | + q2 += qDot2 * deltat |
| 185 | + q3 += qDot3 * deltat |
| 186 | + q4 += qDot4 * deltat |
| 187 | + norm = 1 / sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4) # normalise quaternion |
| 188 | + self.q = q1 * norm, q2 * norm, q3 * norm, q4 * norm |
| 189 | + |
| 190 | + def Update(self, accel, gyro, mag): # 3-tuples (x, y, z) for accel, gyro and mag data |
| 191 | + mx, my, mz = (mag[x] - self.magbias[x] for x in range(3)) # Units uT |
| 192 | + ax, ay, az = accel # Units G (but later normalised) |
| 193 | + gx, gy, gz = (radians(x) for x in gyro) # Units deg/s |
| 194 | + if self.start_time is None: |
| 195 | + self.start_time = pyb.micros() # First run |
| 196 | + q1, q2, q3, q4 = (self.q[x] for x in range(4)) # short name local variable for readability |
| 197 | + # Auxiliary variables to avoid repeated arithmetic |
| 198 | + _2q1 = 2 * q1 |
| 199 | + _2q2 = 2 * q2 |
| 200 | + _2q3 = 2 * q3 |
| 201 | + _2q4 = 2 * q4 |
| 202 | + _2q1q3 = 2 * q1 * q3 |
| 203 | + _2q3q4 = 2 * q3 * q4 |
| 204 | + q1q1 = q1 * q1 |
| 205 | + q1q2 = q1 * q2 |
| 206 | + q1q3 = q1 * q3 |
| 207 | + q1q4 = q1 * q4 |
| 208 | + q2q2 = q2 * q2 |
| 209 | + q2q3 = q2 * q3 |
| 210 | + q2q4 = q2 * q4 |
| 211 | + q3q3 = q3 * q3 |
| 212 | + q3q4 = q3 * q4 |
| 213 | + q4q4 = q4 * q4 |
| 214 | + |
| 215 | + # Normalise accelerometer measurement |
| 216 | + norm = sqrt(ax * ax + ay * ay + az * az) |
| 217 | + if (norm == 0): |
| 218 | + return # handle NaN |
| 219 | + norm = 1 / norm # use reciprocal for division |
| 220 | + ax *= norm |
| 221 | + ay *= norm |
| 222 | + az *= norm |
| 223 | + |
| 224 | + # Normalise magnetometer measurement |
| 225 | + norm = sqrt(mx * mx + my * my + mz * mz) |
| 226 | + if (norm == 0): |
| 227 | + return # handle NaN |
| 228 | + norm = 1 / norm # use reciprocal for division |
| 229 | + mx *= norm |
| 230 | + my *= norm |
| 231 | + mz *= norm |
| 232 | + |
| 233 | + # Reference direction of Earth's magnetic field |
| 234 | + _2q1mx = 2 * q1 * mx |
| 235 | + _2q1my = 2 * q1 * my |
| 236 | + _2q1mz = 2 * q1 * mz |
| 237 | + _2q2mx = 2 * q2 * mx |
| 238 | + hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 + _2q2 * mz * q4 - mx * q3q3 - mx * q4q4 |
| 239 | + hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 + my * q3q3 + _2q3 * mz * q4 - my * q4q4 |
| 240 | + _2bx = sqrt(hx * hx + hy * hy) |
| 241 | + _2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 + _2q3 * my * q4 - mz * q3q3 + mz * q4q4 |
| 242 | + _4bx = 2 * _2bx |
| 243 | + _4bz = 2 * _2bz |
| 244 | + |
| 245 | + # Gradient descent algorithm corrective step |
| 246 | + s1 = (-_2q3 * (2 * q2q4 - _2q1q3 - ax) + _2q2 * (2 * q1q2 + _2q3q4 - ay) - _2bz * q3 * (_2bx * (0.5 - q3q3 - q4q4) |
| 247 | + + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q4 + _2bz * q2) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) |
| 248 | + + _2bx * q3 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5 - q2q2 - q3q3) - mz)) |
| 249 | + |
| 250 | + s2 = (_2q4 * (2 * q2q4 - _2q1q3 - ax) + _2q1 * (2 * q1q2 + _2q3q4 - ay) - 4 * q2 * (1 - 2 * q2q2 - 2 * q3q3 - az) |
| 251 | + + _2bz * q4 * (_2bx * (0.5 - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 - q1q4) |
| 252 | + + _2bz * (q1q2 + q3q4) - my) + (_2bx * q4 - _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5 - q2q2 - q3q3) - mz)) |
| 253 | + |
| 254 | + s3 = (-_2q1 * (2 * q2q4 - _2q1q3 - ax) + _2q4 * (2 * q1q2 + _2q3q4 - ay) - 4 * q3 * (1 - 2 * q2q2 - 2 * q3q3 - az) |
| 255 | + + (-_4bx * q3 - _2bz * q1) * (_2bx * (0.5 - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) |
| 256 | + + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) |
| 257 | + + (_2bx * q1 - _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5 - q2q2 - q3q3) - mz)) |
| 258 | + |
| 259 | + s4 = (_2q2 * (2 * q2q4 - _2q1q3 - ax) + _2q3 * (2 * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) * (_2bx * (0.5 - q3q3 - q4q4) |
| 260 | + + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q1 + _2bz * q3) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) |
| 261 | + + _2bx * q2 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5 - q2q2 - q3q3) - mz)) |
| 262 | + |
| 263 | + norm = 1 / sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4) # normalise step magnitude |
| 264 | + s1 *= norm |
| 265 | + s2 *= norm |
| 266 | + s3 *= norm |
| 267 | + s4 *= norm |
| 268 | + |
| 269 | + # Compute rate of change of quaternion |
| 270 | + qDot1 = 0.5 * (-q2 * gx - q3 * gy - q4 * gz) - self.beta * s1 |
| 271 | + qDot2 = 0.5 * (q1 * gx + q3 * gz - q4 * gy) - self.beta * s2 |
| 272 | + qDot3 = 0.5 * (q1 * gy - q2 * gz + q4 * gx) - self.beta * s3 |
| 273 | + qDot4 = 0.5 * (q1 * gz + q2 * gy - q3 * gx) - self.beta * s4 |
| 274 | + |
| 275 | + # Integrate to yield quaternion |
| 276 | + deltat = pyb.elapsed_micros(self.start_time) / 1000000 |
| 277 | + self.start_time = pyb.micros() |
| 278 | + q1 += qDot1 * deltat |
| 279 | + q2 += qDot2 * deltat |
| 280 | + q3 += qDot3 * deltat |
| 281 | + q4 += qDot4 * deltat |
| 282 | + norm = 1 / sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4) # normalise quaternion |
| 283 | + self.q = q1 * norm, q2 * norm, q3 * norm, q4 * norm |
0 commit comments