Skip to content

BMI270 on AtomS3R (C126) sign-flips in accelerometer output around high-acceleration events #230

@marcindulak

Description

@marcindulak

BMI270 sensor (in M5Stack ATOM S3R C126) produces sign-flips in accelerometer output during high-acceleration events.
By sign-flips I mean that consecutive samples show oscillations where acceleration values flip sign within 10 ms.

To test, use the provided code and perform this experiment:

  1. Sensor held in hand, palm facing down
  2. Soft pillow placed on rigid table
  3. Hand dropped from ~30cm onto pillow
  4. At impact, hand pressed hard through pillow until table was felt underneath (reducing bounce)

Example sign-flips, focusing on z-axis (normal to the display) sensor movement trajectory.

output.txt

grep "14827 " -B 2 -A 10 output.txt
 14807 | raw:( -2285,  -997, -6448) | cal:( -0.236,  0.570, -1.576)  (sensor descent)
 14817 | raw:( -3578,   235,-11929) | cal:(  0.065,  0.885, -2.914)  (sensor descent)
 14827 | raw:( -3527,  1539,-23443) | cal:(  0.383,  0.873, -5.725)  (sensor descent)
 14837 | raw:( -1190,  1725,-32768) | cal:(  0.429,  0.302,  7.999)  FLIP (saturated: raw=-32768)
 14847 | raw:(  2501,  1160,-32768) | cal:(  0.291, -0.599,  7.999)  (saturated)
 14857 | raw:(  7932,  -168,-32580) | cal:( -0.033, -1.925, -7.955)  FLIP BACK
 14867 | raw:( 16790,  -344,-27327) | cal:( -0.076, -4.087, -6.673)
 14881 | raw:( 32767, -7830,  9186) | cal:( -1.904, -7.988,  2.241)  GAP (14867+10=14877, but shows 14881)
 14891 | raw:( 31414,-11571, 31595) | cal:( -2.817, -7.658,  7.712)
 14901 | raw:( 12632,  -802, 32767) | cal:( -0.188, -3.072,  7.998)  (saturated again)
 14911 | raw:( -4568, -9861, 24476) | cal:( -2.400,  1.127,  5.974)
 14921 | raw:(  5535, -2038,-16586) | cal:( -0.490, -1.339, -4.051)  ANOTHER FLIP
 14931 | raw:( -5016, 12606,  9124) | cal:(  3.085,  1.236,  2.226)

What could be the reason for this behavior?
I'm not very familiar with IMUs, so below there are some guesses I was able to find online.
Could this be due to the lack of FIFO reading #123, sensor filter ringing, MEMS mechanical resonance, or the prolonged effect of the acceleration range saturation?

Are there any workarounds to avoid such flips?

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 this table on page 30.

Image

The measurement code:

/**
 * @file accel_drop_impact.ino
 * @brief Detect acceleration sign-flips during controlled hand-assisted impacts
 *
 * Test procedure:
 *
 * 1. Sensor held in hand, palm facing down
 * 2. Soft pillow placed on rigid table
 * 3. Hand dropped from ~30cm onto pillow
 * 4. At impact, hand pressed hard through pillow until table was felt underneath (reducing bounce)
 */

#include <M5Unified.h>

static const uint32_t SAMPLING_FREQUENCY_HZ = 100;
static const uint32_t SAMPLING_INTERVAL_MS = 1000 / SAMPLING_FREQUENCY_HZ;  // 10ms

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

    Serial.println("\n=== BMI270 HAND-CONTROLLED IMPACT SIGN-FLIP DETECTION ===");
    Serial.println("Using M5Unified defaults: 100 Hz sampling, ±8g accel range");
    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 default calibration
    Serial.println("Calibrating accelerometer baseline (2 seconds)...");
    M5.Imu.setCalibration(64, 64, 64);
    delay(2000);
    M5.Imu.setCalibration(0, 0, 0);

    Serial.println();
    Serial.println("Output format:");
    Serial.println("  timestamp_ms | raw:(x,y,z) LSBs | cal:(x,y,z) g");
    Serial.println("  (all three axes to detect erratic values and cross-talk)");
    Serial.println();
    Serial.println("Ready. Start hand-assisted drops...");
    Serial.println("---");
}

void loop() {
    static uint32_t lastPrint = 0;
    uint32_t now = millis();

    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);

        auto data = M5.Imu.getImuData();
        float cal_x = data.accel.x;
        float cal_y = data.accel.y;
        float cal_z = data.accel.z;

        // Print all samples at configured sampling frequency to capture erratic sign-flips
        // (even low values following high values are important for detecting oscillations)
        if (now - lastPrint >= SAMPLING_INTERVAL_MS) {
            Serial.printf("%6lu | raw:(%6d,%6d,%6d) | cal:(%7.3f,%7.3f,%7.3f)\n",
                now,
                raw_x, raw_y, raw_z,
                cal_x, cal_y, cal_z);
            lastPrint = now;
        }
    }

    delay(1);
}

The serial output capture code:

#!/usr/bin/env python3
"""
Capture full-frequency serial output from drop impact test to file.

Usage:
    python capture_serial.py /dev/ttyACM0 output.txt

The script will:
1. Connect to serial port at 115200 baud
2. Read all incoming data
3. Save to file in real-time
4. Press Ctrl+C to stop
"""

import sys
import serial
import time

def capture_serial(port: str, output_file: str) -> None:
    try:
        ser = serial.Serial(port, 115200, timeout=1)
        print(f"Connected to {port} at 115200 baud")
        print(f"Saving to {output_file}")
        print("Ctrl+C to stop\n")

        with open(output_file, 'w') as f:
            start_time = time.time()
            line_count = 0

            while True:
                if ser.in_waiting:
                    line = ser.readline().decode('utf-8', errors='ignore')
                    if line:
                        f.write(line)
                        f.flush()  # Flush to disk immediately
                        line_count += 1

                        # Print progress every 50 lines
                        if line_count % 50 == 0:
                            elapsed = time.time() - start_time
                            print(f"[{elapsed:.1f}s] {line_count} lines captured")

    except KeyboardInterrupt:
        elapsed = time.time() - start_time
        print(f"\n\nCapture complete!")
        print(f"Total lines: {line_count}")
        print(f"Elapsed: {elapsed:.1f}s")
        print(f"Saved to: {output_file}")
    except serial.SerialException as e:
        print(f"Serial error: {e}")
        sys.exit(1)
    finally:
        if 'ser' in locals():
            ser.close()

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print("Usage: python capture_serial.py <device> <output_file>")
        sys.exit(1)

    port = sys.argv[1]
    output_file = sys.argv[2]

    capture_serial(port, output_file)

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