Digitaler Compass ungenau

Hallo alle

Ich bin den Grove - 3-axis digital compass v2.0 am Testen. Doch ich stelle fest, dass der ziemlich heftig jittert. Also wenn ich den absolut still halte schwanken die Messwerte bis zu +-20 Grad. Ist das normal?
Hab ihn calibriert und den Code so angepasst, dass er den Mittelwert von 20 Messungen nimmt. Aber damit ist er immer noch nicht wirklich genau und zeigt auch nicht korrekt nach Norden. Zudem speichere ich die Kalibrationswerte ins EEPROM.

Der Code:

#include <Arduino.h>
#include <Wire.h>
#include <EEPROM.h>  // Add EEPROM library
#include "bmm150.h"
#include "bmm150_defs.h"

BMM150 bmm = BMM150();
bmm150_mag_data value_offset;

// EEPROM addresses
#define EEPROM_CALIBRATED_FLAG 0
#define EEPROM_OFFSET_X 1
#define EEPROM_OFFSET_Y 5
#define EEPROM_OFFSET_Z 9

void setup() {
    Serial.begin(9600);

    if (bmm.initialize() == BMM150_E_ID_NOT_CONFORM) {
        Serial.println("Chip ID can not read!");
        while (1);
    } else {
        Serial.println("Initialize done!");
    }
    // Try to load calibration from EEPROM
    if (EEPROM.read(EEPROM_CALIBRATED_FLAG) == 1) {
        readCalibrationFromEEPROM();
        Serial.println("Calibration loaded from EEPROM");
    } else {
        Serial.println("Start figure-8 calibration after 3 seconds.");
        delay(3000);
        calibrate(10000);
        saveCalibrationToEEPROM();
        Serial.println("Calibrate done and saved to EEPROM");
    }
}

void saveCalibrationToEEPROM() {
    // Save flag to indicate calibration exists
    EEPROM.write(EEPROM_CALIBRATED_FLAG, 1);
    
    // Save X offset (int16_t = 2 bytes)
    EEPROM.put(EEPROM_OFFSET_X, value_offset.x);
    EEPROM.put(EEPROM_OFFSET_Y, value_offset.y);
    EEPROM.put(EEPROM_OFFSET_Z, value_offset.z);
}

void readCalibrationFromEEPROM() {
    EEPROM.get(EEPROM_OFFSET_X, value_offset.x);
    EEPROM.get(EEPROM_OFFSET_Y, value_offset.y);
    EEPROM.get(EEPROM_OFFSET_Z, value_offset.z);
}

/**
    @brief Do figure-8 calibration for a limited time to get offset values of x/y/z axis.
    @param timeout - seconds of calibration period.
*/
void calibrate(uint32_t timeout) {
    int16_t value_x_min = 0;
    int16_t value_x_max = 0;
    int16_t value_y_min = 0;
    int16_t value_y_max = 0;
    int16_t value_z_min = 0;
    int16_t value_z_max = 0;
    uint32_t timeStart = 0;

    bmm.read_mag_data();
    value_x_min = bmm.raw_mag_data.raw_datax;
    value_x_max = bmm.raw_mag_data.raw_datax;
    value_y_min = bmm.raw_mag_data.raw_datay;
    value_y_max = bmm.raw_mag_data.raw_datay;
    value_z_min = bmm.raw_mag_data.raw_dataz;
    value_z_max = bmm.raw_mag_data.raw_dataz;
    delay(100);

    timeStart = millis();

    while ((millis() - timeStart) < timeout) {
        bmm.read_mag_data();

        /* Update x-Axis max/min value */
        if (value_x_min > bmm.raw_mag_data.raw_datax) {
            value_x_min = bmm.raw_mag_data.raw_datax;
            // Serial.print("Update value_x_min: ");
            // Serial.println(value_x_min);

        } else if (value_x_max < bmm.raw_mag_data.raw_datax) {
            value_x_max = bmm.raw_mag_data.raw_datax;
            // Serial.print("update value_x_max: ");
            // Serial.println(value_x_max);
        }

        /* Update y-Axis max/min value */
        if (value_y_min > bmm.raw_mag_data.raw_datay) {
            value_y_min = bmm.raw_mag_data.raw_datay;
            // Serial.print("Update value_y_min: ");
            // Serial.println(value_y_min);

        } else if (value_y_max < bmm.raw_mag_data.raw_datay) {
            value_y_max = bmm.raw_mag_data.raw_datay;
            // Serial.print("update value_y_max: ");
            // Serial.println(value_y_max);
        }

        /* Update z-Axis max/min value */
        if (value_z_min > bmm.raw_mag_data.raw_dataz) {
            value_z_min = bmm.raw_mag_data.raw_dataz;
            // Serial.print("Update value_z_min: ");
            // Serial.println(value_z_min);

        } else if (value_z_max < bmm.raw_mag_data.raw_dataz) {
            value_z_max = bmm.raw_mag_data.raw_dataz;
            // Serial.print("update value_z_max: ");
            // Serial.println(value_z_max);
        }

        Serial.print(".");
        delay(100);

    }

    value_offset.x = value_x_min + (value_x_max - value_x_min) / 2;
    value_offset.y = value_y_min + (value_y_max - value_y_min) / 2;
    value_offset.z = value_z_min + (value_z_max - value_z_min) / 2;
}

float getAverageHeading(int samples) {
    float headingSum = 0;
    float sinSum = 0;
    float cosSum = 0;
    
    // Collect samples
    for (int i = 0; i < samples; i++) {
        // Read raw data and apply calibration
        bmm.read_mag_data();
        
        bmm150_mag_data value;
        value.x = bmm.raw_mag_data.raw_datax - value_offset.x;
        value.y = bmm.raw_mag_data.raw_datay - value_offset.y;
        value.z = bmm.raw_mag_data.raw_dataz - value_offset.z;

        // Calculate heading in radians
        float heading = atan2(value.x, value.y);
        
        // Add sin and cos components (avoids averaging issues at 0/360 degrees)
        sinSum += sin(heading);
        cosSum += cos(heading);
        
        delay(10); // Short delay between readings
    }
    
    // Calculate average heading using arctangent of average sin/cos components
    float avgHeading = atan2(sinSum, cosSum);
    
    // Convert to degrees and normalize to 0-360
    if (avgHeading < 0) {
        avgHeading += 2 * PI;
    }
    
    return avgHeading * 180 / M_PI;
}

 void loop() {
     // Add serial command option for recalibration
     if (Serial.available() > 0) {
         char cmd = Serial.read();
         if (cmd == 'c' || cmd == 'C') {
             Serial.println("Start recalibration...");
             calibrate(10000);
             saveCalibrationToEEPROM();
             Serial.println("Recalibration done and saved!");
         }
     }
    
    // Get average heading from 20 readings
    float avgHeading = getAverageHeading(20);
   
    Serial.print("Avg Heading: ");
    Serial.println(avgHeading);
    
    delay(100);
 }

Hast du störende elektromagnetische Felder in der Nähe?
Wenn nicht, kannst du testhalber feststellen, wie der Sensor auf ein Test-Magnetfeld reagiert?

Wie reagiert ein klassischer Kompass bei einem solchen Test ?
Ab welcher Entfernung ein Test-Magnet das natürliche Erd-Magnetfeld überlagert, sollte ja einigermaßen unabhängig davon sein, welchen Kompass ( klassisch oder BMM150 ) du zum Test verwendest.

Wohl eher nicht.
Ich habe damit keine praktische Erfahrung, aber man so immer noch 8 Himmelsrichtungen sauber unterscheiden und z.B. einigermaßen richtig S oder SW als Richtung angeben :slight_smile:

Genauer geht’s mit GPS.

Und in welche Richtung eine Kompassnadel zeigt, hängt davon ab, wo du bist.
(geographische Deklination)

Welche verwendest Du?

Wohl nicht. GPS hat nur die Koordinaten.

BMM150 ist so wie DHT11, ziemlich ungenau, warum hat Bosch den BMM350 auf den markt gebracht?

Dabei ist sogar günstiger als der von Grove.

Richtig, die Richtung kriegt man per GPS erst mit Bewegung. Das merke ich auch beim Handy: In Ruhe ist die Richtungs-Information bald erheblich schlechter, obwohl da Magnet- und Beschleunigungs- Sensoren vorhanden sind.

Sorry für die späte Antwort Leute! Ich habe in der Tat feststellen müssen, dass der Sensor in meinem "Lab" von irgendwelchen Feldern beeinflusst worden sein muss. Im Wohnzimmer Laptop angehängt detektiert er Norden ziemlich genau. Und der Jitter ist jetzt massiv weniger geworden!

Diese Störungen können aber auch real vorkommen. Daher solltest du trotzdem eine Dämpfung einbauen.

Hallo @wildatheart ,

ich denke hier wäre auch ein Kalmanfilter der richtige Filter um das Signal zu “dämpfen” Das macht man ja auch gern bei den MPU6050. Damit bekommt sehr saubere Signale und ein deutlich stabileres Ergebnis. Muss man aber auch ein bisschen “tunen”.
Einen schönen Filter gibt es >Hier<

Gruß

DerDani