|
| 1 | +--- |
| 2 | +title: MaixCAM MaixPy Read IMU Accelerometer and Gyroscope for Attitude Estimation |
| 3 | +update: |
| 4 | + - date: 2025-07-08 |
| 5 | + author: neucrack |
| 6 | + version: 1.0.0 |
| 7 | + content: Added attitude estimation code support and documentatio |
| 8 | +--- |
| 9 | + |
| 10 | + |
| 11 | +## IMU Introduction |
| 12 | + |
| 13 | +IMU (Inertial Measurement Unit) typically consists of several parts: |
| 14 | + |
| 15 | +* Accelerometer: Measures linear acceleration, including gravitational acceleration. |
| 16 | +* Gyroscope: Measures angular velocity (rotation around an axis). |
| 17 | +* Magnetometer (optional): Measures magnetic field direction, used to assist in yaw calculation. |
| 18 | + |
| 19 | +Using data from these sensors, we can calculate the device's attitude, the angles of rotation along its three axes, also known as Euler angles. |
| 20 | +For example, in the following diagram, the `z` axis is up, the `y` axis is forward, and the `x` axis is right. This is a right-handed coordinate system: |
| 21 | + |
| 22 | +``` |
| 23 | + ^z / y(front) |
| 24 | + | / |
| 25 | + | / |
| 26 | + . ————————> x(right) |
| 27 | +``` |
| 28 | + |
| 29 | +The rotation angle around the `x` axis is called `pitch`. |
| 30 | +The rotation angle around the `y` axis is called `roll`. |
| 31 | +The rotation angle around the `z` axis is called `yaw`. |
| 32 | + |
| 33 | +A system of sensor + reading + attitude estimation + output can be called `AHRS` (Attitude and Heading Reference System). |
| 34 | + |
| 35 | +With these three angles, we can determine the current orientation of our device, which can be used in many scenarios, such as: |
| 36 | + |
| 37 | +* Drones |
| 38 | +* Self-balancing vehicles |
| 39 | +* Robots |
| 40 | +* Motion control |
| 41 | +* Anti-shake |
| 42 | +* Direction and vibration detection |
| 43 | +* Motion judgment, gesture recognition, behavior analysis (can be combined with AI models) |
| 44 | + |
| 45 | +## Hardware Support |
| 46 | + |
| 47 | +Some devices have a built-in IMU, as shown below: |
| 48 | + |
| 49 | +| Device Name | Sensor Model | Accelerometer | Gyroscope | Magnetometer | Interface | Features | |
| 50 | +| ----------- | ------------ | ------------- | --------- | ------------ | -------------------- | ----------------------------------------------- | |
| 51 | +| MaixCAM-Pro | QMI8658 | ✅ | ✅ | ❌ | IIC4<br>Address 0x6B | Low power<br>High stability<br>High sensitivity | |
| 52 | +| MaixCAM | None ❌ | ❌ | ❌ | ❌ | | | |
| 53 | + |
| 54 | +Besides using the built-in IMU, you can also connect an external IMU sensor, such as the classic `MPU6050/MPU9150`. Search for the latest and suitable sensors yourself. |
| 55 | + |
| 56 | +In MaixPy, attitude estimation and IMU drivers are separated, so you can use your own driver for external sensors and still call the attitude estimation algorithm. |
| 57 | + |
| 58 | +## MaixPy Read IMU Data |
| 59 | + |
| 60 | +Using the built-in `QMI8658` of MaixCAM-Pro as an example, use `maix.imu.IMU` to read: |
| 61 | + |
| 62 | +```python |
| 63 | +from maix.ext_dev import imu |
| 64 | + |
| 65 | +# Force calibrate first |
| 66 | +calibrate_first = False |
| 67 | + |
| 68 | +# default config: acc +-2g 1KHz, gyro +-256rad/s 8KHz |
| 69 | +sensor = imu.IMU("qmi8658", mode=imu.Mode.DUAL, |
| 70 | + acc_scale=imu.AccScale.ACC_SCALE_2G, |
| 71 | + acc_odr=imu.AccOdr.ACC_ODR_1000, |
| 72 | + gyro_scale=imu.GyroScale.GYRO_SCALE_256DPS, |
| 73 | + gyro_odr=imu.GyroOdr.GYRO_ODR_8000) |
| 74 | + |
| 75 | +# for gyro have bias, we need to calibrate first |
| 76 | +if calibrate_first or not sensor.calib_gyro_exists(): |
| 77 | + print("\n\nNeed calibrate fisrt") |
| 78 | + print("now calibrate, please !! don't move !! device, wait for 10 seconds") |
| 79 | + # calib_gyro will auto calculate and save bias, |
| 80 | + # the next time use load_calib_gyro load |
| 81 | + sensor.calib_gyro(10000) |
| 82 | +else: |
| 83 | + sensor.load_calib_gyro() |
| 84 | + |
| 85 | +while True: |
| 86 | + data = sensor.read_all(calib_gryo = True, radian = False) |
| 87 | + msg = "acc: {:10.4f}, {:10.4f}, {:10.4f}, gyro: {:10.4f}, {:10.4f}, {:10.4f}, temp: {:4.1f}".format( |
| 88 | + data.acc.x, data.acc.y, data.acc.z, |
| 89 | + data.gyro.x, data.gyro.y, data.gyro.z, |
| 90 | + data.temp |
| 91 | + ) |
| 92 | + print(msg) |
| 93 | +``` |
| 94 | + |
| 95 | +Here, an `IMU` object is constructed, then `read_all` is called to read and print the data. |
| 96 | +Since `qmi8658` has no magnetometer, it's not printed. |
| 97 | + |
| 98 | +However, note that calibration is needed, as explained below. |
| 99 | + |
| 100 | +## Data Calibration |
| 101 | + |
| 102 | +### Gyroscope Calibration |
| 103 | + |
| 104 | +Why gyroscope calibration is needed: |
| 105 | +If not calibrated, we assume the gyroscope reads `x, y, z = 0` when stationary, but in practice, there's an offset—known as "zero drift". |
| 106 | + |
| 107 | +As shown in the code, we need to perform `calib_gyro` once, which calibrates the gyroscope data. |
| 108 | +The principle is simple: collect multiple samples over time and average them. |
| 109 | +Here, `calib_gyro(10000)` samples for 10s and saves the result in `/maixapp/share/misc`. |
| 110 | +Next time, use `load_calib_gyro` to load the file, skipping recalibration. |
| 111 | + |
| 112 | +When reading data, `read_all(calib_gryo = True)` will subtract the bias automatically. You can also set `calib_gyro = False` and handle it manually. |
| 113 | + |
| 114 | +### Accelerometer Calibration |
| 115 | + |
| 116 | +In theory, the accelerometer should also be calibrated, but its effect is less significant than the gyroscope. Not covered here—please refer to other resources. |
| 117 | + |
| 118 | +### Magnetometer Calibration |
| 119 | + |
| 120 | +Similarly, magnetometers need calibration, such as the common ellipsoid calibration. Not covered here—please search and learn. |
| 121 | + |
| 122 | +## Attitude Estimation |
| 123 | + |
| 124 | +Once we have sensor values, we can use an attitude estimation algorithm to obtain Euler angles. |
| 125 | + |
| 126 | +### Basic Principles |
| 127 | + |
| 128 | +* Theoretically, gyroscopes can tell how much rotation occurred since the last moment: `angular velocity * dt = rotation angle`. But due to drift, this is only reliable short-term. |
| 129 | +* Accelerometers measure gravity. Since gravity is constant, when the device is still or moving at constant speed, we can use this to determine absolute orientation relative to the Earth. |
| 130 | + So we trust the accelerometer long-term and gyroscope short-term—they complement each other. |
| 131 | +* However, if the device rotates around an axis perpendicular to gravity, the accelerometer can't detect it. In this case, only the gyroscope helps, but drift may occur over time. |
| 132 | +* The magnetometer helps here since it points to Earth's magnetic north, making up for the accelerometer's blind spot. |
| 133 | + |
| 134 | +### Attitude Estimation Algorithm |
| 135 | + |
| 136 | +There are many algorithms. MaixPy includes the **Mahony complementary filter**—lightweight and fast. |
| 137 | +Full code in [MaixPy/examples/ext\_dev/sensors/imu/imu\_ahrs\_mahony.py](https://github.com/sipeed/MaixPy/tree/main/examples/ext_dev/sensors/imu) |
| 138 | + |
| 139 | +```python |
| 140 | +''' |
| 141 | + Mahony AHRS get device euler angle demo. |
| 142 | + Full Application code see: |
| 143 | + https://github.com/sipeed/MaixPy/tree/main/projects/app_imu_ahrs |
| 144 | +''' |
| 145 | + |
| 146 | +from maix import ahrs, app, time |
| 147 | +from maix.ext_dev import imu |
| 148 | + |
| 149 | +# P of PI controller, a larger P (proportional gain) leads to faster response, |
| 150 | +# but it increases the risk of overshoot and oscillation. |
| 151 | +kp = 2 |
| 152 | + |
| 153 | +# I of PI controller, a larger I (integral gain) helps to eliminate steady-state errors more quickly, |
| 154 | +# but it can accumulate error over time, potentially causing instability or slow drift. |
| 155 | +ki = 0.01 |
| 156 | + |
| 157 | +# Force calibrate first |
| 158 | +calibrate_first = False |
| 159 | + |
| 160 | +# default config: acc +-2g 1KHz, gyro +-256rad/s 8KHz |
| 161 | +sensor = imu.IMU("qmi8658", mode=imu.Mode.DUAL, |
| 162 | + acc_scale=imu.AccScale.ACC_SCALE_2G, |
| 163 | + acc_odr=imu.AccOdr.ACC_ODR_1000, |
| 164 | + gyro_scale=imu.GyroScale.GYRO_SCALE_256DPS, |
| 165 | + gyro_odr=imu.GyroOdr.GYRO_ODR_8000) |
| 166 | +ahrs_filter = ahrs.MahonyAHRS(kp, ki) |
| 167 | + |
| 168 | +# for gyro have bias, we need to calibrate first |
| 169 | +if calibrate_first or not sensor.calib_gyro_exists(): |
| 170 | + print("\n\nNeed calibrate fisrt") |
| 171 | + print("now calibrate, please !! don't move !! device, wait for 10 seconds") |
| 172 | + # calib_gyro will auto calculate and save bias, |
| 173 | + # the next time use load_calib_gyro load |
| 174 | + sensor.calib_gyro(10000) |
| 175 | +else: |
| 176 | + sensor.load_calib_gyro() |
| 177 | + |
| 178 | +# time.sleep(3) |
| 179 | +last_time = time.ticks_s() |
| 180 | +while not app.need_exit(): |
| 181 | + # get imu data |
| 182 | + data = sensor.read_all(calib_gryo = True, radian = True) |
| 183 | + |
| 184 | + # calculate angles |
| 185 | + t = time.ticks_s() |
| 186 | + dt = t - last_time |
| 187 | + last_time = t |
| 188 | + # print(f"{data.mag.x:8.2f}, {data.mag.y:8.2f}, {data.mag.z:8.2f}, {data.gyro.z:8.2f}") |
| 189 | + angle = ahrs_filter.get_angle(data.acc, data.gyro, data.mag, dt, radian = False) |
| 190 | + |
| 191 | + # ^z / y(front) |
| 192 | + # | / |
| 193 | + # | / |
| 194 | + # . ————————> x(right) |
| 195 | + # this demo's axis |
| 196 | + |
| 197 | + # x axis same with camera |
| 198 | + # angle.y -= 90 |
| 199 | + |
| 200 | + print(f"pitch: {angle.x:8.2f}, roll: {angle.y:8.2f}, yaw: {angle.z:8.2f}, dt: {int(dt*1000):3d}ms, temp: {data.temp:.1f}") |
| 201 | + |
| 202 | + # time.sleep_ms(1) |
| 203 | +``` |
| 204 | + |
| 205 | +As shown, use `get_angle` to input raw sensor data and get Euler angles. If there's no magnetometer, set it to all zero. |
| 206 | + |
| 207 | +You may have noticed the use of a `PI` controller to adjust sensitivity. You can experiment with different values (learn about PID tuning): |
| 208 | + |
| 209 | +* `kp`: Proportional gain. Higher values respond faster but risk overshoot. |
| 210 | +* `ki`: Integral gain. Higher values eliminate errors faster but may cause instability or drift. |
| 211 | + |
| 212 | +Other parameters, such as the gyroscope's default range of `[-256degree/s, 256degree/s]`, must be tuned for your scenario. |
| 213 | +Exceeding the range (e.g., rotating 100° but detecting only 60°) causes errors. |
| 214 | +Also, different settings affect sensitivity and noise levels. |
| 215 | + |
| 216 | +## API Documentation |
| 217 | + |
| 218 | +For details on IMU APIs, see [IMU API Documentation](../../../api/maix/ext_dev/imu.md). |
| 219 | +For details on AHRS APIs, see [AHRS Documentation](../../../api/maix/ahrs.md). |
0 commit comments