|
| 1 | +# SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD |
| 2 | +# |
| 3 | +# SPDX-License-Identifier: MIT |
| 4 | +import math |
| 5 | +import time |
| 6 | + |
| 7 | + |
| 8 | +class AttitudeEstimator: |
| 9 | + def __init__(self): |
| 10 | + self.gyro_x_offset = 0.0 |
| 11 | + self.gyro_y_offset = 0.0 |
| 12 | + self.gyro_z_offset = 0.0 |
| 13 | + self.angle_gyro_x = 0.0 |
| 14 | + self.angle_gyro_y = 0.0 |
| 15 | + self.angle_gyro_z = 0.0 |
| 16 | + self.angle_x = 0.0 |
| 17 | + self.angle_y = 0.0 |
| 18 | + self.angle_z = 0.0 |
| 19 | + self.pre_interval = 0.0 |
| 20 | + self.gyro_coef = 0.98 |
| 21 | + self.acc_coef = 0.02 |
| 22 | + |
| 23 | + def calibrate_gyro(self, read_gyro_func, sample_count=100, delay=0.01): |
| 24 | + sum_gx = 0.0 |
| 25 | + sum_gy = 0.0 |
| 26 | + sum_gz = 0.0 |
| 27 | + |
| 28 | + print("Starting gyroscope calibration, please keep the sensor stationary...") |
| 29 | + for i in range(sample_count): |
| 30 | + gx, gy, gz = read_gyro_func() |
| 31 | + sum_gx += gx |
| 32 | + sum_gy += gy |
| 33 | + sum_gz += gz |
| 34 | + time.sleep(delay) |
| 35 | + |
| 36 | + self.gyro_x_offset = sum_gx / sample_count |
| 37 | + self.gyro_y_offset = sum_gy / sample_count |
| 38 | + self.gyro_z_offset = sum_gz / sample_count |
| 39 | + |
| 40 | + print("Gyroscope calibration completed:") |
| 41 | + print("gyro_x_offset = {:.2f} °/s".format(self.gyro_x_offset)) |
| 42 | + print("gyro_y_offset = {:.2f} °/s".format(self.gyro_y_offset)) |
| 43 | + print("gyro_z_offset = {:.2f} °/s".format(self.gyro_z_offset)) |
| 44 | + |
| 45 | + def update_attitude(self, gx, gy, gz, ax, ay, az, dt): |
| 46 | + """ |
| 47 | + Update attitude angles (fusing accelerometer and gyroscope data) |
| 48 | +
|
| 49 | + :param gx: Gyroscope X-axis angular velocity (°/s) |
| 50 | + :param gy: Gyroscope Y-axis angular velocity (°/s) |
| 51 | + :param gz: Gyroscope Z-axis angular velocity (°/s) |
| 52 | + :param ax: Accelerometer X-axis acceleration (m/s²) |
| 53 | + :param ay: Accelerometer Y-axis acceleration (m/s²) |
| 54 | + :param az: Accelerometer Z-axis acceleration (m/s²) |
| 55 | + :param dt: Sampling time interval (seconds) |
| 56 | + """ |
| 57 | + angle_acc_x = math.atan2(ay, az + abs(ax)) * 360 / (2 * math.pi) |
| 58 | + angle_acc_y = math.atan2(ax, az + abs(ay)) * 360 / (-2 * math.pi) |
| 59 | + |
| 60 | + gyro_x = gx - self.gyro_x_offset |
| 61 | + gyro_y = gy - self.gyro_y_offset |
| 62 | + gyro_z = gz - self.gyro_z_offset |
| 63 | + |
| 64 | + self.angle_gyro_x += gyro_x * dt |
| 65 | + self.angle_gyro_y += gyro_y * dt |
| 66 | + self.angle_gyro_z += gyro_z * dt |
| 67 | + |
| 68 | + self.angle_x = (self.gyro_coef * (self.angle_x + gyro_x * dt)) + ( |
| 69 | + self.acc_coef * angle_acc_x |
| 70 | + ) |
| 71 | + self.angle_y = (self.gyro_coef * (self.angle_y + gyro_y * dt)) + ( |
| 72 | + self.acc_coef * angle_acc_y |
| 73 | + ) |
| 74 | + self.angle_z = self.angle_gyro_z |
| 75 | + self.angle_z = (self.angle_z + 360) % 360 |
| 76 | + |
| 77 | + self.pre_interval += dt |
| 78 | + |
| 79 | + def get_angles(self): |
| 80 | + return self.angle_x, self.angle_y, self.angle_z |
0 commit comments