|
1 |
| -# MicroPython MPU-9250 (MPU-6500 + AK8963) I2C driver |
| 1 | +# MicroPython MPU-9250 (MPU-6500 + AK8963) I2C driver: |
2 | 2 | # https://github.com/tuupola/micropython-mpu9250
|
3 | 3 |
|
4 |
| -# Kalman filter |
5 |
| -# https://github.com/micropython-IMU/micropython-fusion?tab=readme-ov-file |
6 |
| -# Kalman for Arduino |
| 4 | +# Arduino + IMU + VPython: |
| 5 | +# https://toptechboy.com/arduino-based-9-axis-inertial-measurement-unit-imu-based-on-bno055-sensor/ |
| 6 | + |
| 7 | +# Kalman filter: |
7 | 8 | # https://how2electronics.com/measure-pitch-roll-yaw-with-mpu6050-hmc5883l-esp32/
|
| 9 | +# https://github.com/micropython-IMU/micropython-fusion?tab=readme-ov-file |
8 | 10 | # https://github.com/nhatuan84/Arduino-KalmanFilter/tree/main
|
9 |
| -# Kalman for ESP-IDF |
10 | 11 | # https://github.com/JChunX/imu-kalman/tree/main
|
11 | 12 |
|
12 | 13 |
|
13 | 14 | import utime
|
14 | 15 | from machine import SoftI2C, Pin
|
15 | 16 | import math
|
16 |
| - |
17 | 17 | from mpu9250 import MPU9250
|
18 | 18 | # from mpu6500 import MPU6500, SF_G, SF_DEG_S
|
19 | 19 | from ak8963 import AK8963
|
20 | 20 |
|
| 21 | +# Adjust yaw using magnetic declination angle |
| 22 | +# https://www.magnetic-declination.com/ |
| 23 | +# +5°22' for Brno |
| 24 | +magnetic_declination = 0.094 # rad = pi * (d+m/60) / 180 |
| 25 | + |
| 26 | +# --- KALMAN FILTER PARAMETERS --- |
| 27 | +Q_angle = 0.001 # Process noise variance for the accelerometer |
| 28 | +Q_bias = 0.003 # Process noise variance for the gyroscope bias |
| 29 | +R_measure = 0.03 # Measurement noise variance |
| 30 | +angle = 0 # Kalman filter state variables |
| 31 | +bias = 0 |
| 32 | +rate = 0 |
| 33 | +P = [[0, 0], [0, 0]] # Error covariance matrix |
| 34 | +pitch, roll, yaw = 0, 0, 0 |
| 35 | + |
| 36 | + |
| 37 | +# Kalman filter function |
| 38 | +def Kalman_filter(angle, gyroRate, accelAngle): |
| 39 | + global bias, P, dt, Q_angle, Q_bias, R_measure |
| 40 | + |
| 41 | + # Predict |
| 42 | + rate = gyroRate - bias |
| 43 | + angle += dt * rate |
| 44 | + |
| 45 | + # Update the error covariance matrix |
| 46 | + P[0][0] += dt * (dt * P[1][1] - P[0][1] - P[1][0] + Q_angle) |
| 47 | + P[0][1] -= dt * P[1][1] |
| 48 | + P[1][0] -= dt * P[1][1] |
| 49 | + P[1][1] += Q_bias * dt |
| 50 | + |
| 51 | + # Update |
| 52 | + S = P[0][0] + R_measure # Estimate error |
| 53 | + K = [P[0][0] / S, P[1][0] / S] # Kalman gain |
| 54 | + |
| 55 | + y = accelAngle - angle # Angle difference |
| 56 | + angle += K[0] * y |
| 57 | + bias += K[1] * y |
| 58 | + |
| 59 | + # Update the error covariance matrix |
| 60 | + P00_temp = P[0][0] |
| 61 | + P01_temp = P[0][1] |
| 62 | + |
| 63 | + P[0][0] -= K[0] * P00_temp |
| 64 | + P[0][1] -= K[0] * P01_temp |
| 65 | + P[1][0] -= K[1] * P00_temp |
| 66 | + P[1][1] -= K[1] * P01_temp |
| 67 | + |
| 68 | + return angle |
| 69 | + |
| 70 | + |
| 71 | +toRad = 2.0*math.pi / 360 |
| 72 | +toDeg = 1 / toRad |
| 73 | + |
21 | 74 | i2c = SoftI2C(scl=Pin(22), sda=Pin(21))
|
22 | 75 | print(f"I2C configuration: {str(i2c)}")
|
23 | 76 |
|
24 | 77 | # mpu6500 = MPU6500(i2c, accel_sf=SF_G, gyro_sf=SF_DEG_S)
|
25 | 78 | # sensor = MPU9250(i2c, mpu6500=mpu6500)
|
26 | 79 | dummy = MPU9250(i2c) # this opens the bybass to access to the AK8963
|
27 |
| -ak8963 = AK8963( |
28 |
| - i2c, |
29 |
| - offset=(-7.423243, 18.58711, -124.3011), |
30 |
| - scale=(1.113371, 0.9323783, 0.971533)) |
| 80 | +ak8963 = AK8963(i2c, |
| 81 | + offset=(47.79844, 46.37666, 66.57422), |
| 82 | + scale=(1.060139, 0.9904738, 0.9550097)) |
| 83 | +# ak8963 = AK8963(i2c) |
| 84 | +# print("Calibrating! Rotate the sensor in all directions... ", end="") |
31 | 85 | # offset, scale = ak8963.calibrate(count=256, delay=200)
|
32 |
| -# print(offset, scale) |
| 86 | +# print("Done") |
| 87 | +# print(f"offset=({offset}), scale=({scale})") |
33 | 88 |
|
34 | 89 | sensor = MPU9250(i2c, ak8963=ak8963)
|
35 | 90 |
|
36 |
| -# print("MPU9250 id: " + hex(sensor.whoami)) |
| 91 | +# Set the initial time for the loop |
| 92 | +lastTime = utime.ticks_ms() |
| 93 | + |
| 94 | +print("MPU9250 id: " + hex(sensor.whoami)) |
37 | 95 | print("\nPress `Ctrl+C` to stop\n")
|
38 | 96 |
|
39 | 97 | try:
|
|
43 | 101 | mx, my, mz = sensor.magnetic
|
44 | 102 | temp = sensor.temperature
|
45 | 103 |
|
| 104 | + # --- CALCULATE TIME STEP --- |
| 105 | + currentTime = utime.ticks_ms() |
| 106 | + dt = (currentTime - lastTime) / 1000.0 # Calculate time in seconds |
| 107 | + if (dt == 0): |
| 108 | + dt = 0.001 # Prevent division by zero |
| 109 | + lastTime = currentTime |
| 110 | + |
46 | 111 | # Calculate pitch and roll from accelerometer data
|
47 | 112 | # https://engineering.stackexchange.com/questions/3348/calculating-pitch-yaw-and-roll-from-mag-acc-and-gyro-data
|
48 |
| - pitch = math.atan2(-ax, math.sqrt(ay**2 + az**2)) * 180.0 / math.pi |
49 |
| - roll = math.atan2(ay, az) * 180.0 / math.pi |
| 113 | + # pitch = math.atan2(-ax, math.sqrt(ay**2 + az**2)) * toDeg |
| 114 | + # roll = math.atan2(ay, az) * toDeg |
| 115 | + |
| 116 | + # --- KALMAN FILTER FOR PITCH AND ROLL --- |
| 117 | + pitch = Kalman_filter(pitch, gx, math.atan2(-ax, math.sqrt(ay**2 + az**2)) * toDeg) |
| 118 | + roll = Kalman_filter(roll, gy, math.atan2(ay, az) * toDeg) |
50 | 119 |
|
| 120 | + # Calculate and adjust yaw from magnetometer data |
51 | 121 | yaw = math.atan2(my, mx)
|
| 122 | + yaw += magnetic_declination |
| 123 | + # Normalize yaw to 0--360 degrees |
52 | 124 | if yaw < 0:
|
53 | 125 | yaw += 2.0 * math.pi
|
54 | 126 | if yaw > 2.0 * math.pi:
|
55 | 127 | yaw -= 2 * math.pi
|
56 |
| - yaw = yaw * 180.0 / math.pi |
| 128 | + yaw = yaw * toDeg |
57 | 129 |
|
58 |
| - print(f"{pitch:.1f} \t{roll:.1f} \t{yaw:.1f} \t{temp:.1f}") |
59 |
| - utime.sleep_ms(1000) |
| 130 | + # print(f"{roll:.1f},{pitch:.1f},{yaw:.1f},{temp:.1f}") |
| 131 | + print(f"{roll:.1f},{pitch:.1f},{yaw:.1f}") |
| 132 | + utime.sleep_ms(20) # 50 Hz |
60 | 133 |
|
61 | 134 | except KeyboardInterrupt:
|
62 | 135 | # This part runs when Ctrl+C is pressed
|
|
0 commit comments