Skip to main content
Visitor II
July 11, 2025
Question

LIS2MDL Magnetometer Compass Calculation - Left-Handed Coordinate System Issue

  • July 11, 2025
  • 1 reply
  • 441 views

Hi,

We are implementing a compass application using the LIS2MDL magnetometer sensor and experiencing incorrect direction readings due to the sensor's left-handed coordinate system. The raw sensor data is functioning correctly, but standard compass calculation algorithms designed for right-handed coordinate systems produce inaccurate results.

Issue Summary:

  • Sensor: LIS2MDL 3-axis magnetometer
  • Problem: Compass calculations yield incorrect directions (primarily showing North/East instead of full 360° range)
  • Root Cause: LIS2MDL uses left-handed coordinate system (confirmed from ST Community forum discussion)
  • Impact: Standard compass algorithms fail to provide accurate bearings

Technical Details:

  1. Raw sensor readings are stable and changing correctly
  2. Sensor communication and initialization working properly (WHO_AM_I = 0x40)
  3. Sample raw data shows expected magnetic field variations
  4. Issue occurs during coordinate system conversion for compass heading calculations

Current Environment:

  • Platform: Embedded Linux
  • Interface: I2C
  • Application: Digital compass/heading indicator
  • Sensor Configuration: 100Hz ODR, continuous mode, BDU enabled

Datasheet refering to file:///C:/Users/raj82251/Downloads/LIS2MDL.PDF

I have refered  some of the forum links https://community.st.com/t5/mems-sensors/lis2mdl-axis-directions/td-p/209018

Can you provide official documentation or application notes specifically addressing compass/heading calculations for LIS2MDL's left-handed coordinate system?

Please confirm the exact positive axis directions for LIS2MDL when mounted flat (PCB orientation) for compass applications.

What is the recommended mathematical transformation to convert LIS2MDL left-handed coordinates to standard compass bearings (0°=North, 90°=East, 180°=South, 270°=West)?

Do you have reference code or algorithms specifically for LIS2MDL compass applications that properly handle the left-handed coordinate system?

Are there any specific calibration procedures or offset corrections needed for compass applications with LIS2MDL?

    This topic has been closed for replies.

    1 reply

    Visitor II
    July 11, 2025

    I have added the shell script i have been using 

    #!/bin/bash

    # LIS2MDL Magnetometer Compass with Calibration
    # Handles left-handed coordinate system and magnetic interference compensation

    DEVICE_PATH="/sys/bus/iio/devices/iio:device3"
    CALIBRATION_FILE="/tmp/lis2mdl_calibration.conf"
    SAMPLE_COUNT=100
    CONTINUOUS=false
    VERBOSE=false
    CALIBRATE=false

    # Function to display usage
    usage() {
    cat << EOF
    Usage: $0 [OPTIONS]

    Options:
    -c, --continuous Continuous reading mode
    -v, --verbose Verbose output
    -h, --help Show this help
    -d, --device PATH Set device path (default: $DEVICE_PATH)
    --calibrate Run calibration procedure
    --show-cal Show current calibration values
    --clear-cal Clear calibration data

    Calibration:
    Before first use, run: $0 --calibrate
    Follow the on-screen instructions to rotate the sensor in all orientations.

    Note: LIS2MDL uses left-handed coordinate system - corrected automatically
    EOF
    exit 1
    }

    # Parse command line arguments
    while [[ $# -gt 0 ]]; do
    case $1 in
    -c|--continuous) CONTINUOUS=true; shift ;;
    -v|--verbose) VERBOSE=true; shift ;;
    -d|--device) DEVICE_PATH="$2"; shift 2 ;;
    --calibrate) CALIBRATE=true; shift ;;
    --show-cal) show_calibration; exit 0 ;;
    --clear-cal) rm -f "$CALIBRATION_FILE"; echo "Calibration cleared"; exit 0 ;;
    -h|--help) usage ;;
    *) echo "Unknown option: $1"; usage ;;
    esac
    done

    # Check if device exists
    if [ ! -d "$DEVICE_PATH" ]; then
    echo "Error: Device path $DEVICE_PATH not found"
    exit 1
    fi

    # Function to read raw magnetometer data
    read_raw_data() {
    local x_raw y_raw z_raw scale
    x_raw=$(cat "$DEVICE_PATH/in_magn_x_raw" 2>/dev/null)
    y_raw=$(cat "$DEVICE_PATH/in_magn_y_raw" 2>/dev/null)
    z_raw=$(cat "$DEVICE_PATH/in_magn_z_raw" 2>/dev/null)
    scale=$(cat "$DEVICE_PATH/in_magn_x_scale" 2>/dev/null)
    if [ -z "$x_raw" ] || [ -z "$y_raw" ] || [ -z "$z_raw" ] || [ -z "$scale" ]; then
    return 1
    fi
    echo "$x_raw $y_raw $z_raw $scale"
    }

    # Function to show current calibration
    show_calibration() {
    if [ -f "$CALIBRATION_FILE" ]; then
    echo "Current calibration values:"
    echo "=========================="
    cat "$CALIBRATION_FILE"
    else
    echo "No calibration data found. Run --calibrate first."
    fi
    }

    # Function to run calibration procedure
    run_calibration() {
    echo "LIS2MDL Magnetometer Calibration"
    echo "================================="
    echo ""
    echo "This process will collect magnetometer data while you rotate the sensor."
    echo "You need to rotate the sensor slowly in ALL orientations to capture"
    echo "the complete magnetic field sphere."
    echo ""
    echo "Instructions:"
    echo "1. Hold the sensor and rotate it slowly in figure-8 patterns"
    echo "2. Tilt it in all directions (up, down, left, right)"
    echo "3. Continue for about 30-60 seconds"
    echo ""
    echo "Press Enter to start calibration..."
    read -r
    echo "Collecting calibration data..."
    echo "Rotate the sensor now!"
    # Initialize min/max values
    local x_min=999999 x_max=-999999
    local y_min=999999 y_max=-999999
    local z_min=999999 z_max=-999999
    # Collect samples
    for i in $(seq 1 $SAMPLE_COUNT); do
    data=$(read_raw_data)
    if [ $? -eq 0 ]; then
    read x y z scale <<< "$data"
    # Update min/max values
    [ $x -lt $x_min ] && x_min=$x
    [ $x -gt $x_max ] && x_max=$x
    [ $y -lt $y_min ] && y_min=$y
    [ $y -gt $y_max ] && y_max=$y
    [ $z -lt $z_min ] && z_min=$z
    [ $z -gt $z_max ] && z_max=$z
    # Show progress
    printf "\rProgress: %d/%d samples" $i $SAMPLE_COUNT
    fi
    sleep 0.1
    done
    echo ""
    echo "Calibration complete!"
    # Calculate offsets (center points)
    local x_offset=$(( (x_min + x_max) / 2 ))
    local y_offset=$(( (y_min + y_max) / 2 ))
    local z_offset=$(( (z_min + z_max) / 2 ))
    # Save calibration data
    cat > "$CALIBRATION_FILE" << EOF
    # LIS2MDL Calibration Data
    # Generated on $(date)
    X_MIN=$x_min
    X_MAX=$x_max
    X_OFFSET=$x_offset
    Y_MIN=$y_min
    Y_MAX=$y_max
    Y_OFFSET=$y_offset
    Z_MIN=$z_min
    Z_MAX=$z_max
    Z_OFFSET=$z_offset
    SCALE=$scale
    EOF
    echo "Calibration data saved to $CALIBRATION_FILE"
    echo ""
    echo "Calibration Results:"
    echo "==================="
    echo "X: min=$x_min, max=$x_max, offset=$x_offset"
    echo "Y: min=$y_min, max=$y_max, offset=$y_offset"
    echo "Z: min=$z_min, max=$z_max, offset=$z_offset"
    echo ""
    echo "You can now use the compass with: $0"
    }

    # Function to load calibration data
    load_calibration() {
    if [ ! -f "$CALIBRATION_FILE" ]; then
    echo "Warning: No calibration data found. Run --calibrate first for accurate readings."
    echo "Using default offsets (0, 0, 0)"
    X_OFFSET=0
    Y_OFFSET=0
    Z_OFFSET=0
    return 1
    fi
    source "$CALIBRATION_FILE"
    return 0
    }

    # Function to calculate calibrated heading
    calculate_heading() {
    local data
    data=$(read_raw_data)
    if [ $? -ne 0 ]; then
    echo "Error reading sensor data"
    return 1
    fi
    read x_raw y_raw z_raw scale <<< "$data"
    # Load calibration data
    load_calibration
    # Apply calibration offsets
    local x_cal=$((x_raw - X_OFFSET))
    local y_cal=$((y_raw - Y_OFFSET))
    local z_cal=$((z_raw - Z_OFFSET))
    # Calculate heading with left-handed coordinate system correction
    awk -v x_raw="$x_raw" -v y_raw="$y_raw" -v z_raw="$z_raw" \
    -v x_cal="$x_cal" -v y_cal="$y_cal" -v z_cal="$z_cal" \
    -v x_offset="$X_OFFSET" -v y_offset="$Y_OFFSET" -v z_offset="$Z_OFFSET" \
    -v scale="$scale" -v verbose="$VERBOSE" '
    BEGIN {
    # Convert to Gauss
    x_gauss = x_cal * scale
    y_gauss = y_cal * scale
    z_gauss = z_cal * scale
    # LEFT-HANDED COORDINATE SYSTEM CORRECTION
    # Negate Y axis to convert to right-handed system
    y_corrected = -y_gauss
    # Calculate heading in radians
    heading_rad = atan2(y_corrected, x_gauss)
    # Convert to degrees
    heading_deg = heading_rad * 180 / 3.14159265359
    # Normalize to 0-360 degrees
    if (heading_deg < 0) {
    heading_deg += 360
    }
    # Determine compass direction
    directions[0] = "N"
    directions[1] = "NNE"
    directions[2] = "NE"
    directions[3] = "ENE"
    directions[4] = "E"
    directions[5] = "ESE"
    directions[6] = "SE"
    directions[7] = "SSE"
    directions[8] = "S"
    directions[9] = "SSW"
    directions[10] = "SW"
    directions[11] = "WSW"
    directions[12] = "W"
    directions[13] = "WNW"
    directions[14] = "NW"
    directions[15] = "NNW"
    # Calculate direction index
    dir_index = int((heading_deg + 11.25) / 22.5) % 16
    direction = directions[dir_index]
    # Calculate magnetic field magnitude
    magnitude = sqrt(x_gauss*x_gauss + y_gauss*y_gauss + z_gauss*z_gauss)
    # Output results
    if (verbose == "true") {
    printf "Raw Data: X=%d, Y=%d, Z=%d\n", x_raw, y_raw, z_raw
    printf "Calibrated: X=%d, Y=%d, Z=%d\n", x_cal, y_cal, z_cal
    printf "Offsets: X=%d, Y=%d, Z=%d\n", x_offset, y_offset, z_offset
    printf "Magnetic Field: X=%.4f, Y=%.4f, Z=%.4f Gauss\n", x_gauss, y_corrected, z_gauss
    printf "Magnitude: %.4f Gauss\n", magnitude
    printf "Heading: %.1f° (%s)\n", heading_deg, direction
    } else {
    printf "%.1f° (%s)\n", heading_deg, direction
    }
    }'
    }

    # Function to display device info
    show_device_info() {
    echo "LIS2MDL Magnetometer Compass"
    echo "============================"
    echo "Device Path: $DEVICE_PATH"
    if [ -f "$DEVICE_PATH/name" ]; then
    echo "Device Name: $(cat "$DEVICE_PATH/name")"
    fi
    if [ -f "$DEVICE_PATH/sampling_frequency" ]; then
    echo "Sampling Frequency: $(cat "$DEVICE_PATH/sampling_frequency") Hz"
    fi
    echo "Coordinate System: Left-handed (auto-corrected)"
    echo "Calibration File: $CALIBRATION_FILE"
    if [ -f "$CALIBRATION_FILE" ]; then
    echo "Calibration Status: ✓ Calibrated"
    else
    echo "Calibration Status: ✗ Not calibrated (run --calibrate)"
    fi
    echo ""
    }

    # Main execution
    if [ "$CALIBRATE" = true ]; then
    run_calibration
    exit 0
    fi

    if [ "$VERBOSE" = true ]; then
    show_device_info
    fi

    if [ "$CONTINUOUS" = true ]; then
    echo "Continuous magnetometer compass (Press Ctrl+C to stop)"
    if [ ! -f "$CALIBRATION_FILE" ]; then
    echo ":warning: Warning: Not calibrated - readings may be inaccurate"
    fi
    echo "Time Heading Direction"
    echo "==================== ========== ========="
    while true; do
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    result=$(calculate_heading)
    if [ $? -eq 0 ]; then
    printf "%-20s %s\n" "$timestamp" "$result"
    fi
    sleep 0.1
    done
    else
    # Single reading
    if [ "$VERBOSE" = true ]; then
    echo "Single compass reading:"
    echo "======================"
    fi
    calculate_heading
    fi