Skip to content

Practical use of ESP32 codeΒ #3

Description

@brightproject

Hello @michal34512 πŸ˜ƒ
I wrote code based on the library.
The code runs on an ESP32-S3 and reads data from the BNO085 sensor.

#include <Arduino.h>
#include <Wire.h>
#include <esp_log.h>
#include "mems_imu/mems_imu.h"

extern "C" {
    #include "mag_calib_new/geometry.h"
}

#define I2C_FREQ 400000
#define I2C_SDA_IMU GPIO_NUM_1
#define I2C_SCL_IMU GPIO_NUM_2
#define SERIAL_BAUD 115200

#define BNO08X_RATE_HZ 200
#define DISPLAY_RATE_HZ 50

#define MAG_CALIB_POINTS 300
#define MAG_CALIB_MIN_DELTA 15

#define BNO08X_TASK_STACK 6 * 1024
#define DISPLAY_TASK_STACK 4 * 1024
#define BNO08X_TASK_PRIORITY 3
#define DISPLAY_TASK_PRIORITY 2
#define BNO08X_TASK_CORE 0
#define DISPLAY_TASK_CORE 1

#define MAG_LOG_ONLY

bool calib_on = true;
// bool calib_on = false;

typedef struct {
    int16_t mx_raw;
    int16_t my_raw;
    int16_t mz_raw;
    
    int16_t mx_calib;
    int16_t my_calib;
    int16_t mz_calib;
    
    float roll_deg;
    float pitch_deg;
    float heading_deg;
    uint8_t accuracy_mag;
} IMUDataExtended;

// Structure for data collection
typedef struct {
    double* x_data;
    double* y_data;
    double* z_data;
    int count;
    int max_points;
} mag_calib_collection_t;

IMUDataExtended imu_data_extended;
mag_calib_collection_t mag_calib_collection;

TwoWire* imuWire = &Wire;
MemsImu mems_bno(imuWire, I2C_SDA_IMU, I2C_SCL_IMU, I2C_FREQ);

Callibration_t magneto_calib_result;
bool magneto_calib_ready = false;
bool magneto_calib_collecting = false;

double mag_target_radius = 1000.0;


static double x_buffer[MAG_CALIB_POINTS];
static double y_buffer[MAG_CALIB_POINTS];
static double z_buffer[MAG_CALIB_POINTS];
static int point_count = 0;

SemaphoreHandle_t i2cMutex = NULL;
SemaphoreHandle_t imuDataMutex = NULL;

TaskHandle_t bno08xTaskHandle = NULL;
TaskHandle_t displayTaskHandle = NULL;

static bool bno08xInitialized = false;

void init_mag_calibration(int max_points) {
    if (mag_calib_collection.x_data != NULL) {
        free(mag_calib_collection.x_data);
        free(mag_calib_collection.y_data);
        free(mag_calib_collection.z_data);
        mag_calib_collection.x_data = NULL;
        mag_calib_collection.y_data = NULL;
        mag_calib_collection.z_data = NULL;
    }
    
    mag_calib_collection.x_data = (double*)malloc(max_points * sizeof(double));
    mag_calib_collection.y_data = (double*)malloc(max_points * sizeof(double));
    mag_calib_collection.z_data = (double*)malloc(max_points * sizeof(double));
    
    if (!mag_calib_collection.x_data || !mag_calib_collection.y_data || !mag_calib_collection.z_data) {
        Serial.println("ERROR: Failed to allocate memory for calibration collection!");
        return;
    }
    
    mag_calib_collection.count = 0;
    mag_calib_collection.max_points = max_points;
    magneto_calib_collecting = true;
    magneto_calib_ready = false;
    
    Serial.printf("Magnetometer calibration started. Collecting %d samples...\n", max_points);
}

bool add_mag_sample(int16_t x, int16_t y, int16_t z) {
    if (!magneto_calib_collecting || mag_calib_collection.count >= mag_calib_collection.max_points) {
        return false;
    }
    
    mag_calib_collection.x_data[mag_calib_collection.count] = (double)x;
    mag_calib_collection.y_data[mag_calib_collection.count] = (double)y;
    mag_calib_collection.z_data[mag_calib_collection.count] = (double)z;
    mag_calib_collection.count++;
    
    Serial.printf("Sample %d/%d: X=%d, Y=%d, Z=%d\n", 
                  mag_calib_collection.count, mag_calib_collection.max_points, x, y, z);
    
    if (mag_calib_collection.count >= mag_calib_collection.max_points) {
        magneto_calib_collecting = false;
        
        if (magneto_calib_ready) {
            calib_free(magneto_calib_result);
            magneto_calib_ready = false;
        }
        
        // Create vectors from data ONCE
        Vector vx = vec_from_array(mag_calib_collection.x_data, mag_calib_collection.count);
        Vector vy = vec_from_array(mag_calib_collection.y_data, mag_calib_collection.count);
        Vector vz = vec_from_array(mag_calib_collection.z_data, mag_calib_collection.count);
        
        double variance_before = square_distance_variance(vx, vy, vz);
        printf("Variance of distances before calibration: %f\n", variance_before);
        
        magneto_calib_result = calib_calibrate_sensor(vx, vy, vz);
        
        bool success = calib_calibration_success(magneto_calib_result);
        printf("Calculating calibration data of data points was %s\n", 
               success ? "SUCCESSFUL" : "UNSUCCESSFUL");
        
        if (success) {
            magneto_calib_ready = true;
            
            // Apply calibration to the same vectors (without creating new ones)
            calib_calibrate_multiple_points(magneto_calib_result, vx, vy, vz);
            
            double variance_after = square_distance_variance(vx, vy, vz);
            printf("Variance of distances after calibration: %f\n", variance_after);
            
            // ========== BIAS И SCALE ==========
            printf("\n=== MAGNETOMETER CALIBRATION PARAMETERS ===\n");
            
            // Access the data through the correct structure fields
            Vector offset = magneto_calib_result.offset; // offset vector
            Matrix transform = magneto_calib_result.transform; // 3x3 transformation matrix
            
            // Output bias for each axis
            printf("Bias (offset) values:\n");
            printf("  X bias: %.3f\n", VEC_X(offset));
            printf("  Y bias: %.3f\n", VEC_Y(offset));
            printf("  Z bias: %.3f\n", VEC_Z(offset));
            
            // Output the transformation matrix (scale + cross-axis)
            // Accessing matrix elements: MAT_ELEM(matrix, row, col)
            printf("\nTransform matrix (scale + rotation/cross-axis):\n");
            printf("  [%.6f, %.6f, %.6f]\n", 
                   MAT_ELEM(transform, 0, 0), MAT_ELEM(transform, 0, 1), MAT_ELEM(transform, 0, 2));
            printf("  [%.6f, %.6f, %.6f]\n", 
                   MAT_ELEM(transform, 1, 0), MAT_ELEM(transform, 1, 1), MAT_ELEM(transform, 1, 2));
            printf("  [%.6f, %.6f, %.6f]\n", 
                   MAT_ELEM(transform, 2, 0), MAT_ELEM(transform, 2, 1), MAT_ELEM(transform, 2, 2));
            
            // Scaling factors (diagonal elements)
            printf("\nScale factors (diagonal elements of transform matrix):\n");
            printf("  X scale factor: %.6f\n", MAT_ELEM(transform, 0, 0));
            printf("  Y scale factor: %.6f\n", MAT_ELEM(transform, 1, 1));
            printf("  Z scale factor: %.6f\n", MAT_ELEM(transform, 2, 2));
            
            // Cross-sensitivity (off-diagonal elements)
            printf("\nCross-axis sensitivity (off-diagonal elements):\n");
            printf("  XY: %.6f, XZ: %.6f\n", MAT_ELEM(transform, 0, 1), MAT_ELEM(transform, 0, 2));
            printf("  YX: %.6f, YZ: %.6f\n", MAT_ELEM(transform, 1, 0), MAT_ELEM(transform, 1, 2));
            printf("  ZX: %.6f, ZY: %.6f\n", MAT_ELEM(transform, 2, 0), MAT_ELEM(transform, 2, 1));
            
            double sum_r_norm = 0;
            for (int i = 0; i < mag_calib_collection.count; i++) {
                double dx = VEC_ELEM(vx, i);
                double dy = VEC_ELEM(vy, i);
                double dz = VEC_ELEM(vz, i);
                sum_r_norm += sqrt(dx*dx + dy*dy + dz*dz);
            }
            double avg_norm_radius = sum_r_norm / mag_calib_collection.count;
            printf("\nAverage normalized radius after calibration: %f (should be ~1.0)\n", avg_norm_radius);
            printf("Target radius for output: %f\n", mag_target_radius);
            printf("=============================================\n");
        }
        
        // Free the vectors (only once)
        vec_free(vx);
        vec_free(vy);
        vec_free(vz);
        
        return true;
    }
    return false;
}

void apply_mag_calibration_to_data(int16_t raw_x, int16_t raw_y, int16_t raw_z, 
                                    int16_t* calib_x, int16_t* calib_y, int16_t* calib_z) {
    if (!magneto_calib_ready) {
        *calib_x = raw_x;
        *calib_y = raw_y;
        *calib_z = raw_z;
        return;
    }
    
    Vector dataPoint = vec_new(3);
    VEC_X(dataPoint) = (double)raw_x;
    VEC_Y(dataPoint) = (double)raw_y;
    VEC_Z(dataPoint) = (double)raw_z;
    
    calib_calibrate_point(magneto_calib_result, dataPoint);
    
    *calib_x = (int16_t)round(VEC_X(dataPoint) * mag_target_radius);
    *calib_y = (int16_t)round(VEC_Y(dataPoint) * mag_target_radius);
    *calib_z = (int16_t)round(VEC_Z(dataPoint) * mag_target_radius);
    
    vec_free(dataPoint);
}

void bno08x_task(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xFrequency = pdMS_TO_TICKS(1000 / BNO08X_RATE_HZ);
    
    while (1) {
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
        
        if (bno08xInitialized) {
            IMUData bno_data; 
            
            xSemaphoreTake(i2cMutex, portMAX_DELAY);
            mems_bno.update();
            bool gotEvent = mems_bno.getIMUData(bno_data);
            xSemaphoreGive(i2cMutex);
            
            if (gotEvent) {
                xSemaphoreTake(imuDataMutex, portMAX_DELAY);
                
                imu_data_extended.mx_raw = bno_data.mx_raw;
                imu_data_extended.my_raw = bno_data.my_raw;
                imu_data_extended.mz_raw = bno_data.mz_raw;
                imu_data_extended.roll_deg = bno_data.roll_deg;
                imu_data_extended.pitch_deg = bno_data.pitch_deg;
                imu_data_extended.heading_deg = bno_data.heading_deg;
                imu_data_extended.accuracy_mag = bno_data.accuracy_mag;
                
                apply_mag_calibration_to_data(
                    imu_data_extended.mx_raw, imu_data_extended.my_raw, imu_data_extended.mz_raw,
                    &imu_data_extended.mx_calib, &imu_data_extended.my_calib, &imu_data_extended.mz_calib
                );
                
                xSemaphoreGive(imuDataMutex);
            }
            
            if (calib_on && !magneto_calib_collecting && !magneto_calib_ready) {
                Serial.println("\n=== Starting MAGNETOMETER CALIBRATION ===");
                init_mag_calibration(MAG_CALIB_POINTS);
                
                int valid_samples = 0;
                int32_t last_mx = 0, last_my = 0, last_mz = 0;
                
                while (valid_samples < MAG_CALIB_POINTS && magneto_calib_collecting) {
                    xSemaphoreTake(i2cMutex, portMAX_DELAY);
                    mems_bno.update();
                    mems_bno.getIMUData(bno_data);
                    xSemaphoreGive(i2cMutex);
                    
                    if (abs(bno_data.mx_raw - last_mx) > MAG_CALIB_MIN_DELTA || 
                        abs(bno_data.my_raw - last_my) > MAG_CALIB_MIN_DELTA || 
                        abs(bno_data.mz_raw - last_mz) > MAG_CALIB_MIN_DELTA) {
                        
                        add_mag_sample(bno_data.mx_raw, bno_data.my_raw, bno_data.mz_raw);
                        
                        last_mx = bno_data.mx_raw;
                        last_my = bno_data.my_raw;
                        last_mz = bno_data.mz_raw;
                        valid_samples++;
                    }
                    vTaskDelay(pdMS_TO_TICKS(20));
                }
                
                calib_on = false;
                Serial.println("\n=== Magnetometer calibration COMPLETED ===");
            }
        }
    }
}

void display_task(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xFrequency = pdMS_TO_TICKS(1000 / DISPLAY_RATE_HZ);
    
    while (1) {
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
        
        if (bno08xInitialized && !calib_on) {
            xSemaphoreTake(imuDataMutex, portMAX_DELAY);

            #ifdef MAG_LOG_ONLY
            // Serial.printf("RAW Mag: X=%5d Y=%5d Z=%5d | CALIB Mag: X=%5d Y=%5d Z=%5d | Acc: %d\n",
            //                 imu_data_extended.mx_raw, imu_data_extended.my_raw, imu_data_extended.mz_raw,
            //                 imu_data_extended.mx_calib, imu_data_extended.my_calib, imu_data_extended.mz_calib,
            //                 imu_data_extended.accuracy_mag);

            // For data collection and processing through a buffer
            if (point_count < MAG_CALIB_POINTS) {
                x_buffer[point_count] = imu_data_extended.mx_calib * 0.062500f;
                y_buffer[point_count] = imu_data_extended.my_calib * 0.062500f;
                z_buffer[point_count] = imu_data_extended.mz_calib * 0.062500f;
                point_count++;
                
                if (point_count == MAG_CALIB_POINTS) {
                    Serial.printf("double x[%d] = {", MAG_CALIB_POINTS);
                    for (int i = 0; i < MAG_CALIB_POINTS; i++) {
                        Serial.printf("%.6f%s", x_buffer[i], i < MAG_CALIB_POINTS - 1 ? ", " : "");
                    }
                    Serial.println("};");
                    
                    Serial.printf("double y[%d] = {", MAG_CALIB_POINTS);
                    for (int i = 0; i < MAG_CALIB_POINTS; i++) {
                        Serial.printf("%.6f%s", y_buffer[i], i < MAG_CALIB_POINTS - 1 ? ", " : "");
                    }
                    Serial.println("};");
                    
                    Serial.printf("double z[%d] = {", MAG_CALIB_POINTS);
                    for (int i = 0; i < MAG_CALIB_POINTS; i++) {
                        Serial.printf("%.6f%s", z_buffer[i], i < MAG_CALIB_POINTS - 1 ? ", " : "");
                    }
                    Serial.println("};");
                    
                    point_count = 0;
                }
            }

            #else               
            Serial.printf("Roll: %.2f Pitch: %.2f Heading: %.2f | RAW Mag: X=%5d Y=%5d Z=%5d | CALIB Mag: X=%5d Y=%5d Z=%5d | Acc: %d\n",
                            imu_data_extended.roll_deg, imu_data_extended.pitch_deg, imu_data_extended.heading_deg,
                            imu_data_extended.mx_raw, imu_data_extended.my_raw, imu_data_extended.mz_raw,
                            imu_data_extended.mx_calib, imu_data_extended.my_calib, imu_data_extended.mz_calib,
                            imu_data_extended.accuracy_mag);
            #endif
            
            xSemaphoreGive(imuDataMutex);
        }
    }
}

void setup() {
    Serial.begin(SERIAL_BAUD);
    delay(1000);
    
    Serial.println("\n=================================");
    Serial.println("BNO08X sensor with Ellipsoid calibration");
    Serial.println("=================================");
    
    // Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ структуры
    memset(&mag_calib_collection, 0, sizeof(mag_calib_collection_t));
    
    i2cMutex = xSemaphoreCreateMutex();
    imuDataMutex = xSemaphoreCreateMutex();
    
    if (!i2cMutex || !imuDataMutex) {
        Serial.println("ERROR: Failed to create mutexes!");
        while(1) delay(1000);
    }
    
    if (mems_bno.begin()) {
        bno08xInitialized = true;
        Serial.println("BNO08x initialized OK.");
        
        sh2_Quaternion_t sh_orientation = { 0, 0, -sqrt(2) / 2, sqrt(2) / 2};
        mems_bno.setOrientation(sh_orientation);
        
        delay(20);
        
        xTaskCreatePinnedToCore(bno08x_task, "bno08x_task", 
            BNO08X_TASK_STACK, NULL, BNO08X_TASK_PRIORITY, 
            &bno08xTaskHandle, BNO08X_TASK_CORE);
            
        delay(20);
        
        xTaskCreatePinnedToCore(display_task, "display_task", 
            DISPLAY_TASK_STACK, NULL, DISPLAY_TASK_PRIORITY, 
            &displayTaskHandle, DISPLAY_TASK_CORE);
            
        delay(20);
        
        Serial.println("Tasks started");
        if (calib_on) {
            Serial.println("Calibration will start automatically...");
            Serial.println("Move the sensor in figure-8 pattern");
        }
    } else {
        Serial.println("Failed to initialize BNO08x!");
        while (1) { 
            // a crutch for rebooting the MK
            delay(10); 
            ESP.restart();
        }
    }
    
    vTaskDelete(NULL);
}

void loop() {
    vTaskDelay(pdMS_TO_TICKS(1000));
}

My project structure is similar to the original one.

main.cpp
β”œβ”€β”€ mag_calib_new
β”‚Β Β  β”œβ”€β”€ eigen.c
β”‚Β Β  β”œβ”€β”€ eigen.h
β”‚Β Β  β”œβ”€β”€ ellipsoid_fit.c
β”‚Β Β  β”œβ”€β”€ ellipsoid_fit.h
β”‚Β Β  β”œβ”€β”€ geometry.h
β”‚Β Β  β”œβ”€β”€ LICENSE
β”‚Β Β  β”œβ”€β”€ main.c0
β”‚Β Β  β”œβ”€β”€ make.bat
β”‚Β Β  β”œβ”€β”€ matrix.c
β”‚Β Β  β”œβ”€β”€ matrix.h
β”‚Β Β  β”œβ”€β”€ qr.c
β”‚Β Β  β”œβ”€β”€ qr.h
β”‚Β Β  β”œβ”€β”€ README.md
β”‚Β Β  β”œβ”€β”€ sensor_calibration.c
β”‚Β Β  β”œβ”€β”€ sensor_calibration.h
β”‚Β Β  β”œβ”€β”€ vector.c
β”‚Β Β  β”œβ”€β”€ vector.h
β”‚Β Β  β”œβ”€β”€ wmm_compensation.c
β”‚Β Β  └── wmm_compensation.h

The code collects 300 samples, or some other value.
I use raw data, not converted to microTesla.

=====================================
BNO08X sensor with Ellipsoid calibration
=======================================
BNO08x initialized OK.

=== Starting MAGNETOMETER CALIBRATION ===
Magnetometer calibration started. Collecting 300 samples...
Tasks started
Calibration will start automatically...
Move the sensor in figure-8 pattern
Sample 1/300: X=138, Y=849, Z=-484
Sample 2/300: X=5, Y=-208, Z=-677
Sample 3/300: X=-451, Y=711, Z=-574
...
Sample 297/300: X=191, Y=295, Z=-721
Sample 298/300: X=-370, Y=11, Z=-760
Sample 299/300: X=-382, Y=387, Z=-740
Variance of distances before calibration: 20184.154681
Sample 300/300: X=-40, Y=700, Z=-637
Calculating calibration data of data points was SUCCESSFUL
Variance of distances after calibration: 0.000714

=== MAGNETOMETER CALIBRATION PARAMETERS ===
Bias (offset) values:
  X bias: -169.266
  Y bias: 217.206
  Z bias: -23.836

Transform matrix (scale + rotation/cross-axis):
  [0.001200, -0.000012, -0.000018]
  [-0.000012, 0.001172, 0.000003]
  [-0.000018, 0.000003, 0.001320]

Scale factors (diagonal elements of transform matrix):
  X scale factor: 0.001200
  Y scale factor: 0.001172
  Z scale factor: 0.001320

Cross-axis sensitivity (off-diagonal elements):
  XY: -0.000012, XZ: -0.000018
  YX: -0.000012, YZ: 0.000003
  ZX: -0.000018, ZY: 0.000003

Average normalized radius after calibration: 0.999646 (should be ~1.0)
Target radius for output: 1000.000000
=============================================

Usually I used the MAGNETO method and got the bias and scale, and then applied them according to this formula.
But here the bias values ​​are somehow huge, which made me doubt the correctness of my adaptation of the source code.

temp_mag[3];

temp_mag[0] = (mx_raW - (float)db[compass::M_BAinv_0_0]);
temp_mag[1] = (my_raW - (float)db[compass::M_BAinv_0_1]);
temp_mag[2] = (mz_raW - (float)db[compass::M_BAinv_0_2]);

Mxyz[0] = (float)db[compass::M_BAinv_1_0] * temp_mag[0] + (float)db[compass::M_BAinv_1_1] * temp_mag[1]
+ (float)db[compass::M_BAinv_1_2] * temp_mag[2];
Mxyz[1] = (float)db[compass::M_BAinv_2_0] * temp_mag[0] + (float)db[compass::M_BAinv_2_1] * temp_mag[1]
+ (float)db[compass::M_BAinv_2_2] * temp_mag[2];
Mxyz[2] = (float)db[compass::M_BAinv_3_0] * temp_mag[0] + (float)db[compass::M_BAinv_3_1] * temp_mag[1]
+ (float)db[compass::M_BAinv_3_2] * temp_mag[2];

In this code I expected something similar, but I didn’t get any scale or matrices right away.

But if I comment out the line in the code,

#define MAG_LOG_ONLY

After calibration, I get both uncalibrated and calibrated values, and the calibrated values ​​magically calibrateβ€”not perfectly, but still.
I became curious about how the code works and whether it correctly fits the ellipsoid using the least-squares method.
I wrote another test code, for which I collected three sets of samples: 20, 100, and 300 points along each axis of the magnetometer.
Here I have already collected samples in microTesla.

#define _USE_MATH_DEFINES
#include <Arduino.h>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

extern "C" {
    #include "mag_calib_new/geometry.h"
}

#define SERIAL_BAUD 115200

#define MIN_DATA_LEN 15

// Select mode: 1 - 20 samples, 2 - 100 samples, 3 - 300 samples
#define DATA_MODE 3

// DEMO OF MAGNETOMETER CALIBRATION USING REAL DATA
// This demo:
// 1. Takes real magnetometer data points
// 2. Calculates calibration data (offset vector and transformation matrix)
// 3. Checks if calibration was successful
// 4. Calibrates the data points

typedef struct {
    Vector vx;
    Vector vy;
    Vector vz;
} mag_data_t;


#if DATA_MODE == 1

// Variance of distances before calibration: 74.736556
// Variance of distances after calibration: 0.013583

    #define MAX_DATA_LEN 20

    double x[20] = {
        0.312500,   6.500000,   18.437500,  30.000000,  34.000000,   35.812500,  32.937500,  30.000000,  19.875000,  9.750000,
        -1.437500,  -10.125000, -16.625000, -23.875000, -28.937500,  -28.562500, -26.375000, -19.875000, -14.812500, -6.875000
    };

    double y[20] = {
        12.250000,  22.062500,  32.187500,  35.812500,  38.687500,   36.875000,  32.187500,  22.437500,  9.000000,    -4.312500,
        -15.875000, -22.750000, -25.312500, -17.687500, -5.062500,   8.687500,   26.750000,  33.250000,  40.187500,  48.875000
    };

    double z[20] = {
        -44.750000, -41.937500, -35.937500, -37.562500, -39.187500,  -42.437500, -44.062500, -46.875000, -47.250000, -46.062500,
        -41.625000, -36.375000, -32.312500, -28.312500, -28.312500,  -24.250000, -23.062500, -20.625000, -22.250000, -26.687500
    };

#elif DATA_MODE == 2

// Variance of distances before calibration: 99.927141
// Variance of distances after calibration: 0.063912

    #define MAX_DATA_LEN 100

    double x[100] = {
        -11.562500, -17.687500, -22.062500, -24.937500, -25.312500, -23.500000, -19.187500, -14.437500, -10.812500, -4.687500,
        0.312500, 3.250000, 6.500000, 14.062500, 18.437500, 24.250000, 30.000000, 32.937500, 34.000000, 35.062500,
        35.812500, 34.750000, 32.937500, 31.500000, 30.000000, 24.250000, 19.875000, 13.750000, 9.750000, 3.250000,
        -1.437500, -4.687500, -10.125000, -14.812500, -16.625000, -21.000000, -23.875000, -26.750000, -28.937500, -28.937500,
        -28.562500, -27.875000, -26.375000, -22.812500, -19.875000, -15.875000, -14.812500, -10.812500, -6.875000, -1.750000,
        3.250000, 8.687500, 14.437500, 19.187500, 24.562500, 27.875000, 31.812500, 34.750000, 35.812500, 35.437500,
        35.437500, 35.437500, 34.375000, 33.625000, 32.562500, 30.375000, 30.375000, 28.187500, 22.062500, 20.625000,
        12.250000, 11.937500, 5.750000, 0.687500, -3.250000, -8.687500, -14.062500, -16.625000, -20.625000, -25.687500,
        -28.187500, -31.812500, -35.125000, -38.375000, -36.187500, -38.687500, -39.062500, -41.625000, -41.250000, -41.625000
    };

    double y[100] = {
        12.250000, 17.000000, 22.062500, 27.125000, 32.187500, 35.437500, 35.812500, 38.000000, 38.687500, 40.125000,
        36.875000, 34.375000, 32.187500, 25.312500, 22.437500, 14.437500, 9.000000, 2.125000, -4.312500, -10.125000,
        -15.875000, -18.437500, -22.750000, -24.250000, -25.312500, -23.500000, -17.687500, -13.000000, -5.062500, 1.062500,
        8.687500, 18.437500, 26.750000, 30.375000, 33.250000, 36.875000, 40.187500, 45.250000, 48.875000, 49.562500,
        50.312500, 47.375000, 45.937500, 42.687500, 39.062500, 34.750000, 30.750000, 27.125000, 26.750000, 23.500000,
        18.062500, 14.437500, 14.437500, 3.250000, -0.312500, -4.312500, -9.375000, -14.812500, -19.875000, -23.125000,
        -26.750000, -28.937500, -27.875000, -26.062500, -23.500000, -20.250000, -13.375000, -7.562500, -1.062500, 6.500000,
        11.937500, 18.812500, 25.687500, 30.375000, 34.000000, 36.187500, 38.687500, 41.625000, 42.312500, 43.062500,
        44.125000, 44.500000, 43.062500, 39.062500, 34.000000, 28.937500, 27.500000, 23.125000, 17.687500, 13.750000,
        9.750000, 9.750000, 12.250000, 13.750000, 17.375000, 17.000000, 14.062500, 12.625000, 9.375000, 7.187500,
    };

    double z[100] = {
        -44.750000, -43.562500, -41.937500, -37.125000, -35.937500, -33.875000, -37.562500, -37.937500, -39.187500, -40.375000,
        -42.437500, -44.437500, -44.062500, -46.062500, -46.875000, -45.687500, -47.250000, -46.062500, -46.062500, -44.437500,
        -41.625000, -38.812500, -36.375000, -33.937500, -32.312500, -30.312500, -28.312500, -29.125000, -28.312500, -27.875000,
        -24.250000, -25.875000, -23.062500, -20.625000, -20.625000, -21.000000, -22.250000, -24.625000, -25.500000, -26.687500,
        -30.312500, -31.562500, -35.562500, -38.000000, -40.062500, -42.875000, -45.687500, -45.312500, -46.937500, -45.312500,
        -44.875000, -47.312500, -46.562500, -46.125000, -44.500000, -46.125000, -44.125000, -41.250000, -40.062500, -37.250000,
        -35.187500, -29.937500, -31.125000, -29.937500, -30.750000, -28.687500, -29.500000, -29.125000, -27.500000, -27.125000,
        -25.500000, -23.875000, -21.437500, -20.187500, -20.187500, -20.187500, -20.625000, -21.812500, -23.437500, -27.937500,
        -27.937500, -34.000000, -40.875000, -43.312500, -46.937500, -48.187500, -50.187500, -49.375000, -49.812500, -48.562500,
        -48.562500, -46.937500, -43.312500, -42.875000, -42.062500, -42.500000, -41.250000, -39.250000, -40.062500, -40.437500
    };

#elif DATA_MODE == 3

// Variance of distances before calibration: 49.279169
// Variance of distances after calibration: 0.000708

    #define MAX_DATA_LEN 300

    double x[MAX_DATA_LEN] = {
        9.375000, 11.937500, 13.750000, 14.812500, 17.312500, 16.625000, 19.500000, 18.812500, 19.500000, 18.812500,
        18.437500, 16.625000, 14.812500, 12.625000, 9.750000, 7.937500, 6.125000, 2.500000, 0.000000, -2.875000,
        -4.312500, -8.687500, -11.562500, -14.812500, -17.687500, -20.625000, -22.062500, -23.125000, -24.937500, -24.250000,
        -25.312500, -24.937500, -23.500000, -21.312500, -19.187500, -17.000000, -14.437500, -13.375000, -10.812500, -8.312500,
        -4.687500, -3.250000, 0.312500, 1.062500, 3.250000, 4.312500, 6.500000, 8.687500, 14.062500, 14.812500,
        18.437500, 21.000000, 24.250000, 26.750000, 30.000000, 31.125000, 32.937500, 32.937500, 34.000000, 35.812500,
        35.062500, 35.062500, 35.812500, 34.750000, 34.750000, 33.625000, 32.937500, 32.562500, 31.500000, 30.750000,
        30.000000, 25.687500, 24.250000, 21.687500, 19.875000, 18.062500, 13.750000, 11.562500, 9.750000, 5.750000,
        3.250000, 1.062500, -1.437500, -3.562500, -4.687500, -7.562500, -10.125000, -12.625000, -14.812500, -16.625000,
        -16.625000, -19.500000, -21.000000, -23.125000, -23.875000, -25.687500, -26.750000, -27.500000, -28.937500, -28.937500,
        -28.937500, -28.937500, -28.562500, -28.937500, -27.875000, -27.875000, -26.375000, -24.562500, -22.812500, -22.062500,
        -19.875000, -18.062500, -15.875000, -15.875000, -14.812500, -12.250000, -10.812500, -8.312500, -6.875000, -4.312500,
        -1.750000, 1.062500, 3.250000, 6.500000, 8.687500, 11.187500, 14.437500, 17.000000, 19.187500, 22.062500,
        24.562500, 27.125000, 27.875000, 30.375000, 31.812500, 32.937500, 34.750000, 35.812500, 35.812500, 35.437500,
        35.437500, 35.437500, 37.250000, 35.437500, 34.375000, 34.000000, 33.625000, 32.187500, 32.562500, 30.375000,
        30.375000, 26.750000, 28.187500, 26.062500, 22.062500, 18.437500, 20.625000, 14.812500, 12.250000, 11.937500,
        11.937500, 7.187500, 5.750000, 3.250000, 0.687500, 1.062500, -3.250000, -5.750000, -8.687500, -10.500000,
        -14.062500, -14.437500, -16.625000, -17.375000, -20.625000, -22.437500, -25.687500, -26.750000, -28.187500, -30.750000,
        -31.812500, -33.312500, -35.125000, -36.562500, -38.375000, -38.375000, -36.187500, -38.000000, -38.687500, -38.687500,
        -39.062500, -39.812500, -41.625000, -42.687500, -41.250000, -41.625000, -41.625000, -40.187500, -38.687500, -35.125000,
        -31.500000, -28.562500, -24.937500, -23.125000, -21.000000, -18.437500, -16.250000, -14.437500, -12.625000, -10.812500,
        -10.125000, -9.000000, -8.687500, -7.187500, -5.750000, -5.750000, -5.750000, -5.750000, -5.750000, -5.750000,
        6.500000, 8.687500, 11.187500, 11.937500, 13.000000, 14.812500, 15.187500, 14.812500, 13.000000, 12.625000,
        11.562500, 8.687500, 5.062500, 2.500000, -1.062500, -5.375000, -9.375000, -12.625000, -17.687500, -21.312500,
        -26.062500, -29.312500, -32.562500, -33.312500, -33.625000, -31.812500, -32.562500, -33.312500, -29.312500, -26.062500,
        -25.687500, -20.250000, -17.687500, -15.562500, -14.062500, -13.750000, -12.312500, -10.500000, -7.187500, -6.500000,
        -4.312500, -2.500000, -1.437500, -1.750000, -0.687500, -1.062500, -1.437500, -1.750000, -2.875000, -5.750000,
        -7.562500, -8.687500, -13.750000, -13.375000, -12.312500, -13.000000, -15.187500, -16.625000, -15.562500, -13.750000,
        -15.562500, -14.812500, -11.937500, -11.187500, -9.375000, -9.375000, -9.750000, -9.750000, -7.187500, -8.312500,
        -7.562500, -6.125000, -2.125000, -1.750000, 0.000000, 1.750000, 3.250000, 4.687500, 6.500000, 5.062500
    };

    double y[MAX_DATA_LEN] = {
        12.250000, 14.437500, 17.000000, 19.500000, 22.062500, 25.312500, 27.125000, 29.625000, 32.187500, 32.937500,
        35.437500, 35.437500, 35.812500, 37.250000, 38.000000, 38.375000, 38.687500, 40.875000, 40.125000, 38.375000,
        36.875000, 34.750000, 34.375000, 32.187500, 27.500000, 25.312500, 22.437500, 18.812500, 14.437500, 11.187500,
        9.000000, 6.500000, 2.125000, -0.312500, -4.312500, -7.187500, -10.125000, -13.000000, -15.875000, -17.687500,
        -18.437500, -20.625000, -22.750000, -24.937500, -24.250000, -24.937500, -25.312500, -24.937500, -23.500000, -20.625000,
        -17.687500, -15.187500, -13.000000, -8.687500, -5.062500, -1.437500, 1.062500, 4.312500, 8.687500, 12.625000,
        18.437500, 22.750000, 26.750000, 28.187500, 30.375000, 31.500000, 33.250000, 35.062500, 36.875000, 39.062500,
        40.187500, 43.062500, 45.250000, 46.312500, 48.875000, 48.875000, 49.562500, 51.375000, 50.312500, 50.312500,
        47.375000, 47.750000, 45.937500, 44.500000, 42.687500, 40.500000, 39.062500, 36.875000, 34.750000, 33.312500,
        30.750000, 29.312500, 27.125000, 26.062500, 26.750000, 25.687500, 23.500000, 21.312500, 18.062500, 14.437500,
        14.437500, 14.437500, 5.750000, 3.250000, -0.312500, -2.875000, -4.312500, -7.562500, -9.375000, -11.562500,
        -14.812500, -19.187500, -19.875000, -22.437500, -23.125000, -25.687500, -26.750000, -26.750000, -27.125000, -27.875000,
        -28.937500, -28.187500, -27.875000, -26.375000, -26.062500, -24.937500, -23.500000, -22.437500, -20.250000, -14.812500,
        -13.375000, -9.750000, -7.562500, -5.375000, -1.062500, 2.125000, 6.500000, 9.375000, 11.937500, 15.875000,
        18.812500, 22.437500, 25.687500, 28.187500, 30.375000, 32.187500, 34.000000, 35.125000, 36.187500, 38.687500,
        40.500000, 41.625000, 42.312500, 43.437500, 43.062500, 44.125000, 44.500000, 44.125000, 43.062500, 39.062500,
        36.875000, 34.000000, 31.812500, 28.937500, 27.500000, 26.062500, 23.125000, 20.625000, 17.687500, 16.625000,
        13.750000, 12.625000, 9.750000, 9.750000, 10.125000, 11.562500, 12.250000, 13.750000, 13.750000, 14.062500,
        17.375000, 17.375000, 17.000000, 14.812500, 14.062500, 11.187500, 12.625000, 10.812500, 9.375000, 10.500000,
        7.187500, 6.875000, 6.875000, 6.125000, 7.187500, 7.937500, 7.187500, 8.687500, 7.187500, 7.937500,
        6.125000, 5.375000, 3.250000, 1.062500, -2.125000, -5.375000, -10.125000, -13.000000, -16.625000, -20.625000,
        -24.562500, -24.562500, -26.375000, -28.562500, -32.937500, -32.937500, -32.937500, -32.937500, -32.937500, -32.937500,
        -34.750000, -32.937500, -31.500000, -29.312500, -27.125000, -23.125000, -20.250000, -17.375000, -13.750000, -10.812500,
        -5.750000, 0.312500, 4.312500, 7.937500, 10.812500, 12.625000, 15.875000, 18.437500, 21.000000, 23.500000,
        27.125000, 30.000000, 34.000000, 35.125000, 39.437500, 40.875000, 43.062500, 43.437500, 43.437500, 45.250000,
        44.500000, 45.625000, 45.250000, 44.875000, 43.437500, 42.687500, 40.500000, 38.000000, 35.812500, 33.312500,
        29.687500, 25.312500, 20.625000, 15.562500, 10.500000, 9.375000, 6.500000, 5.750000, 2.125000, -1.750000,
        -3.250000, -4.687500, -7.187500, -10.500000, -15.562500, -18.062500, -21.687500, -23.875000, -26.062500, -27.500000,
        -28.562500, -29.687500, -29.687500, -31.500000, -33.312500, -33.312500, -33.312000, -34.375000, -34.000000, -33.625000,
        -33.625000, -30.375000, -28.937500, -27.500000, -25.312500, -25.312500, -24.625000, -22.812500, -19.500000, -15.562500
    };

    double z[MAX_DATA_LEN] = {
        -44.750000, -44.375000, -43.562500, -41.937500, -41.937500, -39.125000, -37.125000, -36.312500, -35.937500, -36.312500,
        -33.875000, -35.937500, -37.562500, -37.562500, -37.937500, -40.000000, -39.187500, -40.812500, -40.375000, -42.000000,
        -42.437500, -44.437500, -44.437500, -44.062500, -46.062500, -45.625000, -46.875000, -46.875000, -45.687500, -45.687500,
        -47.250000, -45.687500, -46.062500, -45.250000, -46.062500, -44.875000, -44.437500, -43.250000, -41.625000, -39.625000,
        -38.812500, -38.375000, -36.375000, -34.750000, -33.937500, -32.312500, -32.312500, -30.312500, -28.312500, -29.500000,
        -29.125000, -29.937500, -28.312500, -28.687500, -27.875000, -26.687500, -24.250000, -26.312500, -25.875000, -25.062500,
        -23.062500, -21.000000, -20.625000, -21.000000, -20.625000, -21.437500, -21.000000, -20.625000, -22.250000, -21.812500,
        -24.625000, -24.687500, -25.500000, -27.062500, -26.687500, -25.062500, -30.312500, -29.937500, -31.562500, -33.562500,
        -35.562500, -36.000000, -38.000000, -40.062500, -40.062500, -41.250000, -42.875000, -43.687500, -45.687500, -45.687500,
        -45.312500, -46.500000, -46.937500, -45.312500, -46.500000, -46.125000, -44.875000, -43.687500, -47.312500, -46.562500,
        -46.562500, -46.562500, -46.125000, -44.500000, -44.500000, -46.125000, -44.500000, -44.937500, -44.125000, -42.875000,
        -41.250000, -40.062500, -40.062500, -38.437500, -37.250000, -36.375000, -35.187500, -34.000000, -29.937500, -31.937500,
        -31.125000, -30.312500, -29.937500, -29.937500, -30.750000, -29.125000, -28.687500, -27.937500, -29.500000, -29.937500,
        -29.125000, -29.500000, -27.500000, -27.500000, -27.125000, -26.687500, -25.500000, -24.687500, -23.875000, -23.437500,
        -21.437500, -21.000000, -20.187500, -18.187500, -20.187500, -17.812500, -20.187500, -20.625000, -20.187500, -21.812500,
        -21.812500, -23.062500, -23.437500, -24.687500, -27.937500, -29.125000, -27.937500, -32.375000, -34.000000, -37.250000,
        -40.875000, -41.687500, -43.312500, -45.312500, -46.937500, -45.312500, -48.187500, -49.375000, -50.187500, -50.187500,
        -49.375000, -49.000000, -49.812500, -49.000000, -48.562500, -49.000000, -48.562500, -46.562500, -46.562500, -45.750000,
        -46.937500, -43.312500, -43.312500, -42.875000, -41.687500, -42.062500, -42.125000, -42.500000, -42.500000, -41.312500,
        -41.250000, -40.437500, -39.250000, -38.875000, -40.062500, -39.250000, -40.437500, -41.250000, -41.312500, -42.500000,
        -44.125000, -44.937500, -46.562500, -46.562500, -45.750000, -44.500000, -44.500000, -41.687500, -39.250000, -36.812500,
        -35.187500, -33.562500, -31.187500, -29.125000, -23.875000, -23.875000, -23.875000, -23.875000, -23.875000, -23.875000,
        -16.187500, -17.812500, -19.812500, -21.812500, -25.062500, -28.312500, -31.187500, -34.375000, -38.062500, -40.437500,
        -42.062500, -44.937500, -46.125000, -48.187500, -48.562500, -46.937500, -49.000000, -49.375000, -49.000000, -46.937500,
        -45.312500, -41.687500, -40.062500, -39.687500, -37.625000, -36.437500, -34.375000, -35.187500, -36.437500, -34.812500,
        -36.437500, -37.250000, -38.437500, -39.250000, -40.062500, -40.500000, -41.312500, -43.312500, -43.312500, -44.937500,
        -45.312500, -44.937500, -44.500000, -47.375000, -46.125000, -47.375000, -46.937500, -46.937500, -45.750000, -44.937500,
        -44.937500, -45.312500, -42.937500, -42.500000, -40.062500, -38.437500, -35.187500, -32.750000, -32.000000, -27.937500,
        -29.125000, -28.312500, -27.500000, -25.062500, -22.250000, -22.687500, -21.875000, -22.687500, -23.062500, -24.687500,
        -27.125000, -28.750000, -30.750000, -32.375000, -33.187500, -32.375000, -33.187500, -34.812500, -37.625000, -40.062500
    };

#else
    #error "DATA_MODE must be 1, 2, or 3!"
#endif

void setup() {
    Serial.begin(115200);
    
    delay(100);
    
    Serial.println("=== Magnetometer Calibration Demo ===");
    Serial.println("Starting calibration process...");
    
    // Create vectors based on data arrays
    // We allocate memory on the heap, since the library will try to free() the passed pointer when calling vec_free()
    // and static arrays cannot be freed.
    double* x_dyn = (double*)malloc(MAX_DATA_LEN * sizeof(double));
    double* y_dyn = (double*)malloc(MAX_DATA_LEN * sizeof(double));
    double* z_dyn = (double*)malloc(MAX_DATA_LEN * sizeof(double));

    if (x_dyn == NULL || y_dyn == NULL || z_dyn == NULL) {
        Serial.println("ERROR: Failed to allocate memory for arrays");
        free(x_dyn); free(y_dyn); free(z_dyn);
        return;
    }

    memcpy(x_dyn, x, MAX_DATA_LEN * sizeof(double));
    memcpy(y_dyn, y, MAX_DATA_LEN * sizeof(double));
    memcpy(z_dyn, z, MAX_DATA_LEN * sizeof(double));

    Vector vx = vec_from_array(x_dyn, MAX_DATA_LEN);
    Vector vy = vec_from_array(y_dyn, MAX_DATA_LEN);
    Vector vz = vec_from_array(z_dyn, MAX_DATA_LEN);
    
    if (vx == NULL || vy == NULL || vz == NULL) {
        Serial.println("ERROR: Failed to create vectors");
        if (vx != NULL) vec_free(vx);
        if (vy != NULL) vec_free(vy);
        if (vz != NULL) vec_free(vz);
        // Clearing temporary buffers in case of vector creation error
        free(x_dyn); free(y_dyn); free(z_dyn);
        return;
    }
    
    // Calculate calibration data (offset vector and transformation matrix)
    Callibration_t calib = calib_calibrate_sensor(vx, vy, vz);
    
    // Check if calibration was successful
    Serial.print("Calculating calibration data of data points was ");
    if (calib_calibration_success(calib)) {
        Serial.println("SUCCESSFUL");
    } else {
        Serial.println("UNSUCCESSFUL");
        calib_free(calib);
        vec_free(vx);
        vec_free(vy);
        vec_free(vz);
        return;
    }
    
    // Print variance before calibration
    Serial.print("Variance of distances before calibration: ");
    Serial.println(square_distance_variance(vx, vy, vz), 6);
    
    // Calibrate single point
    Vector dataPoint = vec_new(3);
    if (dataPoint != NULL) {
        VEC_X(dataPoint) = VEC_ELEM(vx, 0);
        VEC_Y(dataPoint) = VEC_ELEM(vy, 0);
        VEC_Z(dataPoint) = VEC_ELEM(vz, 0);
        calib_calibrate_point(calib, dataPoint);
        
        Vector gravity = vec_new(3);
        if (gravity != NULL) {
            VEC_X(gravity) = 0;
            VEC_Y(gravity) = 0;
            VEC_Z(gravity) = 1;
            wmm_compensate(dataPoint, gravity, 5.9, 68.2);
            vec_free(gravity);
        }
        vec_free(dataPoint);
    }
    
    // Calibrate all data points
    calib_calibrate_multiple_points(calib, vx, vy, vz);
    
    // Print variance after calibration
    Serial.print("Variance of distances after calibration: ");
    Serial.println(square_distance_variance(vx, vy, vz), 6);

    // Print the result
    vec_print(calib.offset);
    mat_print(calib.transform);
    
    // Free allocated memory
    calib_free(calib);
    vec_free(vx);
    vec_free(vy);
    vec_free(vz);
    
    Serial.println("=== Calibration completed ===");
}

void loop() {
    vTaskDelay(pdMS_TO_TICKS(1000));
}

I also read the description

https://github.com/michal34512/Magnetometer-calibration?tab=readme-ov-file#how-to-use

And added the output to the code:

// Print the result
vec_print(calib.offset);
mat_print(calib.transform);

The results are ambiguous and depend on the number of samples:

For 20
// Variance of distances before calibration: 74.736556
// Variance of distances after calibration: 0.013583
For 100
// Variance of distances before calibration: 99.927141
// Variance of distances after calibration: 0.063912
For 300
// Variance of distances before calibration: 49.279169
// Variance of distances after calibration: 0.000708

Here is the data for 300 samples

=== Magnetometer Calibration Demo ===
Starting calibration process...
Calculating calibration data of data points was SUCCESSFUL
Variance of distances before calibration: 49.279169
Variance of distances after calibration: 0.000708
[-3.9935165864, 12.1129365731, -18.5020502551, ]
Size (3, 3):
0.023, -0.000, -0.003, 
-0.000, 0.022, 0.000, 
-0.003, 0.000, 0.034, 
=== Calibration completed ===

I still agree with the bias, but the matrices, which, as I understand it, are soft iron(scale), are for some reason very small, while they are usually about 1 in diagonal.

Like this:

0.9800, -0.000, -0.003, 
-0.000, 1.100, 0.000, 
-0.003, 0.000, 0.999, 

Basically, I'm confused about whether I've adapted the code correctly, whether I'm collecting the data correctly, whether the algorithm is working correctly, and whether the underlying logic is even correct.
All these questions remain unanswered for me.
I hope you could help me figure this out and perhaps even provide additional details on how to use the library and clear applications for achieving practical results.
Thank you.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions