cancel
Showing results for 
Search instead for 
Did you mean: 

LIS2MDL Magnetometer Compass Calculation - Left-Handed Coordinate System Issue

Ranjith_Madara
Visitor

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?

1 REPLY 1
Ranjith_Madara
Visitor

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