Repository navigation
IMU Orientation
Axis-orientation reference images for M5Stack IMU units and for the hosts with a built-in IMU. Use them
to set the correct axis remap (ahrs.writeAxisOrder / m5::imu::presets::*).
-
Raw chip axes: what the unit drivers (
UnitBMI270,UnitMPU6886,UnitBMM150, ...) return -- the IMU chip's own axes, as mounted on the board. -
Corrected axes: what
AhrsControlleruses after a preset (m5::imu::presets::<board>()) is applied. The board presets apply the same per-board correction as M5Unified, so for a built-in IMU these are also the axesM5.Imureturns.
Where the two differ, both are shown side by side. Elsewhere one image is shown: no correction is applied and the raw chip axes are used as they are.
- Several classic hosts (Gray, Fire, the original StickC) shipped earlier IMU generations (MPU6050 + MAG3110, MPU9250 + AK8963, SH200Q + BMM150) that the current product pages no longer illustrate. Those revisions are listed below text-only (no orientation image); their axis remap must be confirmed on the original hardware before a preset is committed.
- Some hosts changed their IMU chip across hardware revisions without changing what M5.getBoard()
reports (Core2 v1.1 MPU6886 → v1.3 BMI270; ATOM Matrix → v1.1 BMI270). AhrsDemo probes the
internal bus (
m5::unit::imu::detectBuiltinImu) to pick the accel/gyro driver, while the axis remap is still selected by board (the mounting orientation is a board property). - Images are © M5Stack.
The predecessor of the DoF series: BMI270 + BMM150 + BMP280 directly on the GROVE main bus
(driver: UnitMiniIMUPro composite). Pressure is read directly via UnitMiniIMUPro::baro()
(not part of the attitude AHRS; the UnitBMP280 driver comes from M5Unit-ENV).
The axis mark printed on the label shows the BMI270 axes: with the label facing up and the GROVE connector nearest to you, +X points right, +Y away from the connector, +Z up out of the label face. No preset is needed.
No correction: the raw chip axes are used as they are.
DoF9 and DoF10 share DoF6's PCB layout: the label mark (BMI270 axes) is the same, and the BMI270 needs
no correction. The BMM350 is mounted rotated 180 degrees about X -- its own axes are X right, Y toward
the GROVE connector, Z down (the same mounting as Unit BMM350). m5::imu::presets::unitDoF() corrects
it to the BMI270's axes.
BMI270 (accel/gyro): No correction: the raw chip axes are used as they are.
BMM350 (magnetometer):
| Raw chip axes | Corrected axes (presets::unitDoF()) |
|---|---|
![]() |
![]() |
A standalone magnetometer, typically paired with a UnitMiniIMU for 9-axis. The label mark shows the
BMM350's own axes: X right, Y toward the GROVE connector, Z down -- rotated 180 degrees about X
relative to the unit's frame. m5::imu::presets::unitBMM350() corrects it (body = (mag_x, -mag_y, -mag_z)), and AhrsDemo calls that preset. Orient this unit the same way as its accel/gyro partner
(label up, GROVE connector nearest, +X right); a UnitMiniIMU partner needs no correction of its own.
| Raw chip axes | Corrected axes (presets::unitBMM350()) |
|---|---|
![]() |
![]() |
No correction: the raw chip axes are used as they are.
No correction: the raw chip axes are used as they are.
No correction: the raw chip axes are used as they are.
Note: v1.3 switched the IMU from MPU6886 to a 6-axis BMI270 (still no magnetometer).
No correction: the raw chip axes are used as they are.
6-axis MPU6886 (no magnetometer). M5Unified applies no axis correction (identity remap).
No correction: the raw chip axes are used as they are.
No correction for accel/gyro. presets::coreS3() inverts the AUX BMM150's Y and Z (the
magnetometer axes are not drawn).
Applies to the 2019.8–2021.7 generation. From v2.6 (2021.8) the BMM150 magnetometer was removed; v2.6/v2.7 are 6-axis MPU6886 only (see below).
Same layout as Gray.
Raw chip axes
Corrected axes (presets::m5stackCoreFire(), same as M5Unified)
v2.6 (2021.8) removed the BMM150; v2.6 and v2.7 share the same 6-axis MPU6886 configuration (the v2.6 page has no orientation image, so the v2.7 diagram applies to both).
No correction: the raw chip axes are used as they are.
The diagrams above cover the 2019.8+ MPU6886 generation. Earlier Fire hardware used different chips and the current product page carries no orientation diagram for them:
-
original (≤ 2019.6) — MPU9250 (0x68, WHO_AM_I 0x71) + internal AK8963 (0x0C, reached via the
MPU9250 aux bypass). Driver:
UnitMPU9250composite (itsmag()is the AK8963). Same generation as the 2017.12–2019.6 Gray. -
2019.7 — SH200Q (0x6C) + BMM150 (0x10). Driver:
UnitSH200Q+UnitBMM150.
Axis remap for both is unverified (no image, no M5Unified reference table); confirm on original hardware before committing a preset.
The MPU6886 needs no correction: X right, Y away from the A/B/C buttons, Z up out of the screen. The BMM150 gets X and Z inverted, so its corrected axes match the MPU6886's.
Raw chip axes
Corrected axes (presets::m5stackCoreFire(), same as M5Unified)
The diagram above is the 2019.6+ MPU6886 + BMM150 layout. Per the Gray update history, earlier Gray hardware used different chips, for which the current product page carries no orientation diagram:
-
2017 (original) — MPU6050 (0x68) + MAG3110 (0x0E). Two separate chips on the internal bus
(no aux bypass). Driver:
UnitMPU6050+UnitMAG3110. -
2017.12 – 2019.6 — MPU9250 (0x68, WHO_AM_I 0x71) + internal AK8963 (0x0C via the MPU9250 aux
bypass). Driver:
UnitMPU9250composite. Same generation as the original Fire.
Axis remap for both is unverified; confirm on original hardware before committing a preset.
Note: ATOM Matrix v1.1 switched the IMU from MPU6886 to a 6-axis BMI270 (still no magnetometer, same
0x68 address and same mounting orientation, so the axis remap is unchanged across the swap). Because
M5.getBoard() cannot tell the generations apart, AhrsDemo probes the chip on the internal bus
(m5::unit::imu::detectBuiltinImu) to construct the right driver.
presets::atomMatrix() inverts X and Z, as M5Unified does. Checked on an MPU6886 unit; v1.1
(BMI270) is assumed to be the same.
| Raw chip axes | Corrected axes (presets::atomMatrix(), same as M5Unified) |
|---|---|
![]() |
![]() |
No correction: the raw chip axes are used as they are.
presets::atomS3R() swaps X/Y and inverts Y for accel/gyro, as M5Unified does. (The AUX BMM150
gets its own X/Z inversion; it is not drawn.)
| Raw chip axes | Corrected axes (presets::atomS3R(), same as M5Unified) |
|---|---|
![]() |
![]() |
The original M5StickC changed its IMU across production runs; the current product page shows no per-generation orientation diagram:
-
2019.3 — SH200Q (0x6C). Driver:
UnitSH200Q. -
2019.8+ — MPU6886 (0x68). Driver:
UnitMPU6886.
Axis remap for the SH200Q generation is unverified -- field reports even suggest its accel and gyro axis systems may not agree with each other, so it stays at identity until confirmed on 2019 hardware. (StickC-Plus / StickC-Plus2 below are MPU6886 throughout.)
No correction: the raw chip axes are used as they are.
No correction: the raw chip axes are used as they are.
presets::stickS3() swaps X/Y and inverts Y, as M5Unified does.
| Raw chip axes | Corrected axes (presets::stickS3(), same as M5Unified) |
|---|---|
![]() |
![]() |
No correction: the raw chip axes are used as they are.
6-axis BMI270 (no magnetometer). M5Unified applies no axis correction (identity remap).
No correction: the raw chip axes are used as they are.
No correction: the raw chip axes are used as they are.
ESP32-C6 host; 6-axis BMI270 on the internal system bus (no magnetometer). M5Unified applies no axis correction on the C6, so AhrsDemo uses the identity remap (the BMI270 default path). The product page has no dedicated IMU-orientation diagram.
ESP32-P4 host; 6-axis BMI270 on the internal system bus (no magnetometer). M5Unified applies no axis correction on the P4, so AhrsDemo uses the identity remap. Note that +Z points out of the back, not out of the screen.
No correction: the raw chip axes are used as they are.








