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

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