Skip to content

High acceleration axis cross-talk on BMI270 on AtomS3R (C126) #229

@marcindulak

Description

@marcindulak

https://m5stack.lang-ship.com/catalog/products/controller/c126_atoms3r/ links to https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/core/K128%20CoreS3/BMI270.PDF, which contains in the table on page 15 Cross Axis Sensitivity SXA = 1%

Image

See https://www.murata.com/en-us/support/faqs/sensor/accel/char/0008 for definition of "Cross Axis Sensitivity".

I'm seeing some unexpectedly high values of acceleration on the axis not involved in the movement.
This is how I'm testing this.
Please check the procedure and the code included further down make sense, since I'm not sure whether any/what calibration is needed for the accelerometer on AtomS3R (C126).

  1. Place device FLAT on table, display facing up, USB-C port toward you
  2. Position book parallel to USB-C port (as edge guide)
  3. Keep device still against book edge for 2 seconds (BASELINE)
  4. Slide device PARALLEL to USB-C port (forward/backward along book edge).
    Expected: Y-axis activates; X and Z minimal

In the results below M5U is the M5Unified's magnitude-constraint calibration. Simple is the raw sensor data with per-axis offset subtraction.

Baseline (still, device flat), at 06:01:03.829:

M5U_x: [-0.009, 0.000] g
M5U_y: [0.000, 0.065] g
M5U_z: [0.000, 1.021] g

Simple_x: [-0.021, 0.023] g
Simple_y: [-0.005, 0.001] g
Simple_z: [0.000, 1.019] g

End of motion, at 06:01:08.917:

M5U_x: [-0.086, 0.047] g (span 0.133g)
M5U_y: [-0.210, 0.419] g (span 0.629g)
M5U_z: [0.000, 1.024] g

Simple_x: [-0.376, 0.254] g (span 0.630g)
Simple_y: [-0.082, 0.050] g (span 0.132g)
Simple_z: [0.000, 1.022] g

Both M5U and Simple methods show significant activation on the perpendicular axis (0.13g) when motion is applied to the primary axis (0.63g). This crosstalk is present in both methods, indicating it may be a sensor characteristic.

I'm using https://github.com/m5stack/M5Unified/releases/tag/0.2.13.
Note that the M5U calibration is applying a transformation like below, so axis exchange between the M5U and Simple methods is expected:

CAL_X ≈ RAW_Y
CAL_Y ≈ -RAW_X  (with sign inversion!)
CAL_Z ≈ RAW_Z

if (board == m5::board_t::board_M5AtomS3R || board == m5::board_t::board_M5AtomS3RCam || board == m5::board_t::board_M5AtomS3RExt)
{ // AtomS3Rシリーズ では、ジャイロと加速度のY軸とX軸を入れ替え、Y軸を反転するほか、地磁気のX軸とZ軸をそれぞれ反転する
_internal_axisorder_fixed[sensor_index_accel] = (internal_axisorder_t)(axis_order_yxz | axis_invert_y);
_internal_axisorder_fixed[sensor_index_gyro ] = (internal_axisorder_t)(axis_order_yxz | axis_invert_y);
_internal_axisorder_fixed[sensor_index_mag ] = (internal_axisorder_t)(axis_invert_x | axis_invert_z); // X軸,Z軸反転
}

This is the code used for the axis cross-talk test:

/**
 * @file accel_axis_mixing.ino
 * @brief Detect axis mixing in accelerometer readings
 *
 * Test compares two calibration approaches on identical sensor data:
 * 1. M5Unified: Uses "distance from 1g" constraint (continuous, enforces magnitude)
 * 2. Simple offset: Per-axis offset tracking WITHOUT magnitude constraint
 *
 * Test procedure (requires a book as guide for precise linear motion):
 *
 *   1. Place device FLAT on table, display facing up, USB-C port toward you
 *   2. Position book parallel to USB-C port (as edge guide)
 *   3. Keep device still against book edge for 2 seconds (BASELINE)
 *   4. Slide device PARALLEL to USB-C port (forward/backward along book edge)
 *       Expected: Y-axis activates; X and Z minimal
 *
 * Analysis:
 * If axis mixing is present, the test would show unexpected
 * activation in secondary axes during motion. The simple offset method should
 * behave identically to M5Unified since both use same sensor data.
 */

#include <M5Unified.h>

// Simple offset tracking (on raw sensor data, no magnitude constraint)
struct SimpleOffsets {
    int16_t offset_x = 0;
    int16_t offset_y = 0;
    int16_t offset_z = 0;  // Raw offset for Z (accounting for 1g gravity)
    int sample_count = 0;
};
SimpleOffsets simple_offsets = {};
static const float ACCEL_SCALE = 8.0f / 32768.0f;  // Standard ±8g conversion

void calibrateSimpleOffsets(int duration_ms) {
    Serial.println("Calibrating simple offsets from raw sensor data (accumulating during stillness)...");

    // Reset accumulators
    int32_t sum_x = 0, sum_y = 0, sum_z = 0;
    int count = 0;
    uint32_t start = millis();

    while (millis() - start < duration_ms) {
        if (M5.Imu.update()) {
            int16_t raw_x = M5.Imu.getRawData(0);
            int16_t raw_y = M5.Imu.getRawData(1);
            int16_t raw_z = M5.Imu.getRawData(2);

            sum_x += raw_x;
            sum_y += raw_y;
            sum_z += raw_z;  // We'll subtract 1g equivalent (32768/8 = 4096 LSBs)
            count++;
        }
        delay(1);
    }

    // Compute average raw offsets
    if (count > 0) {
        simple_offsets.offset_x = sum_x / count;
        simple_offsets.offset_y = sum_y / count;
        simple_offsets.offset_z = (sum_z / count) - 4096;  // Subtract 1g gravity (4096 LSBs at ±8g)
        simple_offsets.sample_count = count;
    }

    Serial.printf("Raw offsets from %d samples: x=%d, y=%d, z=%d (LSBs)\n",
        count, simple_offsets.offset_x, simple_offsets.offset_y, simple_offsets.offset_z);
}

void setup() {
    auto cfg = M5.config();
    cfg.serial_baudrate = 115200;
    M5.begin(cfg);

    Serial.println("\n=== BMI270 AXIS MIXING PROOF OF CONCEPT ===");
    Serial.println("Comparing M5Unified vs Simple Offset calibration");
    Serial.println();

    // Re-initialize I2C
    M5.In_I2C.begin((i2c_port_t)I2C_NUM_0, 45, 0);
    delay(100);
    M5.Imu.update();
    delay(100);

    // M5Unified calibration
    Serial.println("1. M5Unified calibration (with magnitude constraint)...");
    M5.Imu.setCalibration(64, 64, 64);
    delay(2000);
    M5.Imu.setCalibration(0, 0, 0);

    // Simple offset calibration (on raw sensor data)
    Serial.println("2. Computing simple offsets from raw sensor data...");
    calibrateSimpleOffsets(1000);

    Serial.println();
    Serial.println("Ready to test. Use a book as edge guide for precise linear motion.");
    Serial.println("Follow the procedure in file comments.");
    Serial.println();
    Serial.println("Format: time_ms | M5U:(x,y,z)g | Simple:(x,y,z)g | ranges...");
    Serial.println("---");
}

void loop() {
    static uint32_t lastPrint = 0;
    static float m5u_min_x = 0, m5u_max_x = 0;
    static float m5u_min_y = 0, m5u_max_y = 0;
    static float m5u_min_z = 0, m5u_max_z = 0;
    static float sim_min_x = 0, sim_max_x = 0;
    static float sim_min_y = 0, sim_max_y = 0;
    static float sim_min_z = 0, sim_max_z = 0;

    uint32_t now = millis();

    if (M5.Imu.update()) {
        auto data = M5.Imu.getImuData();

        // M5Unified values (with magnitude constraint calibration)
        float m5u_x = data.accel.x;
        float m5u_y = data.accel.y;
        float m5u_z = data.accel.z;

        // Simple offset correction on raw data (no magnitude constraint)
        int16_t raw_x = M5.Imu.getRawData(0);
        int16_t raw_y = M5.Imu.getRawData(1);
        int16_t raw_z = M5.Imu.getRawData(2);

        float sim_x = (raw_x - simple_offsets.offset_x) * ACCEL_SCALE;
        float sim_y = (raw_y - simple_offsets.offset_y) * ACCEL_SCALE;
        float sim_z = (raw_z - simple_offsets.offset_z) * ACCEL_SCALE;

        // Track ranges
        if (m5u_x < m5u_min_x) m5u_min_x = m5u_x;
        if (m5u_x > m5u_max_x) m5u_max_x = m5u_x;
        if (m5u_y < m5u_min_y) m5u_min_y = m5u_y;
        if (m5u_y > m5u_max_y) m5u_max_y = m5u_y;
        if (m5u_z < m5u_min_z) m5u_min_z = m5u_z;
        if (m5u_z > m5u_max_z) m5u_max_z = m5u_z;

        if (sim_x < sim_min_x) sim_min_x = sim_x;
        if (sim_x > sim_max_x) sim_max_x = sim_x;
        if (sim_y < sim_min_y) sim_min_y = sim_y;
        if (sim_y > sim_max_y) sim_max_y = sim_y;
        if (sim_z < sim_min_z) sim_min_z = sim_z;
        if (sim_z > sim_max_z) sim_max_z = sim_z;

        // Print at 5Hz
        if (now - lastPrint >= 200) {
            Serial.printf("%6lu | M5U:(%7.3f,%7.3f,%7.3f) | Sim:(%7.3f,%7.3f,%7.3f) | ",
                now,
                m5u_x, m5u_y, m5u_z,
                sim_x, sim_y, sim_z);

            Serial.printf("M5U_x:[%.3f,%.3f] M5U_y:[%.3f,%.3f] M5U_z:[%.3f,%.3f] | ",
                m5u_min_x, m5u_max_x,
                m5u_min_y, m5u_max_y,
                m5u_min_z, m5u_max_z);

            Serial.printf("Sim_x:[%.3f,%.3f] Sim_y:[%.3f,%.3f] Sim_z:[%.3f,%.3f]",
                sim_min_x, sim_max_x,
                sim_min_y, sim_max_y,
                sim_min_z, sim_max_z);

            Serial.println();
            lastPrint = now;
        }
    }

    delay(1);
}

This is the serial output:

06:01:02.606 -> Format: time_ms | M5U:(x,y,z)g | Simple:(x,y,z)g | ranges...
06:01:02.606 -> ---
06:01:02.606 ->   3611 | M5U:( -0.005,  0.045,  1.005) | Sim:( -0.001, -0.001,  1.003) | M5U_x:[-0.005,0.000] M5U_y:[0.000,0.045] M5U_z:[0.000,1.005] | Sim_x:[-0.001,0.000] Sim_y:[-0.001,0.000] Sim_z:[0.000,1.003]
06:01:02.798 ->   3815 | M5U:( -0.005,  0.043,  1.008) | Sim:(  0.001, -0.001,  1.006) | M5U_x:[-0.009,0.000] M5U_y:[0.000,0.065] M5U_z:[0.000,1.021] | Sim_x:[-0.021,0.023] Sim_y:[-0.005,0.001] Sim_z:[0.000,1.019]
06:01:03.025 ->   4018 | M5U:( -0.007,  0.043,  1.003) | Sim:(  0.000, -0.003,  1.001) | M5U_x:[-0.009,0.000] M5U_y:[0.000,0.065] M5U_z:[0.000,1.021] | Sim_x:[-0.021,0.023] Sim_y:[-0.005,0.001] Sim_z:[0.000,1.019]
06:01:03.218 ->   4222 | M5U:( -0.007,  0.041,  0.999) | Sim:(  0.002, -0.003,  0.997) | M5U_x:[-0.009,0.000] M5U_y:[0.000,0.065] M5U_z:[0.000,1.021] | Sim_x:[-0.021,0.023] Sim_y:[-0.005,0.001] Sim_z:[0.000,1.019]
06:01:03.412 ->   4426 | M5U:( -0.007,  0.043,  1.000) | Sim:(  0.001, -0.003,  0.999) | M5U_x:[-0.009,0.000] M5U_y:[0.000,0.065] M5U_z:[0.000,1.021] | Sim_x:[-0.021,0.023] Sim_y:[-0.005,0.001] Sim_z:[0.000,1.019]
06:01:03.637 ->   4629 | M5U:( -0.007,  0.042,  1.005) | Sim:(  0.001, -0.004,  1.003) | M5U_x:[-0.009,0.000] M5U_y:[0.000,0.065] M5U_z:[0.000,1.021] | Sim_x:[-0.021,0.023] Sim_y:[-0.005,0.001] Sim_z:[0.000,1.019]
06:01:03.829 ->   4833 | M5U:( -0.006,  0.042,  1.002) | Sim:(  0.001, -0.003,  1.000) | M5U_x:[-0.009,0.000] M5U_y:[0.000,0.065] M5U_z:[0.000,1.021] | Sim_x:[-0.021,0.023] Sim_y:[-0.005,0.001] Sim_z:[0.000,1.019]
06:01:04.024 ->   5037 | M5U:( -0.006,  0.042,  1.003) | Sim:(  0.002, -0.003,  1.001) | M5U_x:[-0.010,0.000] M5U_y:[0.000,0.065] M5U_z:[0.000,1.021] | Sim_x:[-0.021,0.023] Sim_y:[-0.006,0.001] Sim_z:[0.000,1.019]
06:01:04.249 ->   5240 | M5U:( -0.010,  0.051,  1.000) | Sim:( -0.007, -0.007,  0.998) | M5U_x:[-0.022,0.000] M5U_y:[0.000,0.122] M5U_z:[0.000,1.021] | Sim_x:[-0.078,0.023] Sim_y:[-0.018,0.001] Sim_z:[0.000,1.019]
06:01:04.442 ->   5444 | M5U:( -0.025,  0.122,  0.997) | Sim:( -0.079, -0.022,  0.995) | M5U_x:[-0.035,0.004] M5U_y:[0.000,0.133] M5U_z:[0.000,1.021] | Sim_x:[-0.089,0.023] Sim_y:[-0.032,0.008] Sim_z:[0.000,1.019]
06:01:04.637 ->   5647 | M5U:( -0.021,  0.070,  1.002) | Sim:( -0.026, -0.017,  1.000) | M5U_x:[-0.086,0.039] M5U_y:[-0.019,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.063] Sim_y:[-0.082,0.043] Sim_z:[0.000,1.019]
06:01:04.829 ->   5851 | M5U:(  0.046, -0.069,  0.985) | Sim:(  0.113,  0.050,  0.983) | M5U_x:[-0.086,0.047] M5U_y:[-0.079,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.123] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.019]
06:01:05.056 ->   6055 | M5U:(  0.006,  0.062,  0.993) | Sim:( -0.018,  0.009,  0.991) | M5U_x:[-0.086,0.047] M5U_y:[-0.079,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.123] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.019]
06:01:05.249 ->   6258 | M5U:( -0.044, -0.085,  0.990) | Sim:(  0.129, -0.041,  0.988) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.019]
06:01:05.442 ->   6462 | M5U:( -0.019,  0.015,  1.005) | Sim:(  0.029, -0.015,  1.003) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.019]
06:01:05.667 ->   6666 | M5U:( -0.017,  0.013,  1.003) | Sim:(  0.031, -0.013,  1.001) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.019]
06:01:05.860 ->   6869 | M5U:( -0.016,  0.013,  1.001) | Sim:(  0.031, -0.013,  1.000) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.019]
06:01:06.054 ->   7073 | M5U:( -0.026, -0.019,  1.001) | Sim:(  0.063, -0.023,  0.999) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.019]
06:01:06.279 ->   7277 | M5U:( -0.024, -0.053,  1.012) | Sim:(  0.097, -0.021,  1.010) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.019]
06:01:06.470 ->   7480 | M5U:( -0.028, -0.059,  1.018) | Sim:(  0.102, -0.024,  1.016) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.210] M5U_z:[0.000,1.021] | Sim_x:[-0.167,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.019]
06:01:06.663 ->   7684 | M5U:(  0.020,  0.218,  1.000) | Sim:( -0.174,  0.023,  0.999) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:06.889 ->   7887 | M5U:( -0.006,  0.053,  1.000) | Sim:( -0.010, -0.003,  0.999) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:07.083 ->   8091 | M5U:( -0.011,  0.042,  1.002) | Sim:(  0.001, -0.008,  1.000) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:07.277 ->   8295 | M5U:( -0.005,  0.041,  1.000) | Sim:(  0.003, -0.001,  0.998) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:07.503 ->   8498 | M5U:( -0.007,  0.040,  1.003) | Sim:(  0.004, -0.004,  1.001) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:07.696 ->   8702 | M5U:( -0.003,  0.041,  1.001) | Sim:(  0.003,  0.001,  0.999) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:07.889 ->   8906 | M5U:( -0.003,  0.041,  0.994) | Sim:(  0.003,  0.000,  0.992) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:08.114 ->   9109 | M5U:( -0.005,  0.042,  0.995) | Sim:(  0.002, -0.001,  0.993) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:08.307 ->   9313 | M5U:( -0.004,  0.042,  1.003) | Sim:(  0.002, -0.000,  1.001) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:08.501 ->   9517 | M5U:( -0.005,  0.042,  1.002) | Sim:(  0.002, -0.002,  1.000) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:08.724 ->   9720 | M5U:( -0.004,  0.042,  1.002) | Sim:(  0.002, -0.001,  1.000) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]
06:01:08.917 ->   9924 | M5U:(  0.001,  0.048,  1.003) | Sim:( -0.004,  0.004,  1.001) | M5U_x:[-0.086,0.047] M5U_y:[-0.210,0.419] M5U_z:[0.000,1.024] | Sim_x:[-0.376,0.254] Sim_y:[-0.082,0.050] Sim_z:[0.000,1.022]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions