BMM350 shows significant X/Y/Z changes with almost constant temperature after wake-up from sleep

I am using the Bosch BMM350 magnetometer in a battery-powered parking sensor based on an STM32WL MCU.

During installation, when the parking slot is confirmed to be empty, I collect 5 magnetometer samples and average them to create a baseline calibration (X, Y, Z). Every minute, the device wakes from sleep, reads the compensated magnetometer values, computes the difference from the baseline, and determines whether a vehicle is present.

The problem is that even when the parking slot remains empty and no vehicle has moved into or out of the slot, the measured magnetic values occasionally drift enough to exceed my detection threshold, resulting in a false "occupied" indication.

I have also logged the sensor temperature together with the magnetometer readings. In several instances, I observed significant changes in the X, Y, and Z values while the reported temperature remained nearly constant (or changed only by a very small amount). Therefore, the observed drift does not appear to be explained solely by temperature variation.
Please go through this xl sheet to see the sudden drift. and .c for configurations of Sensor APIs.

Magnetometer_data_wrt_temperature.xlsx
75.27KB
 // Defines
#define T_AVG			3

// Exponential Moving Average (EMA) coefficient used to slowly update the baseline 
// when the parking slot is confidently detected as empty
#define ALPHA			0.05f

#define CALIBRATION_SAMPLES	5

// Private Typedef
typedef struct
{
	float x;
	float y;
	float z;
	float temperature;
} BMM350_data_t;

// Global Variables
bool parked = false;
float THRESHOLD = 4.5f;
BMM350_data_t BMM350_CALIBRATED_BASE;

// Private function prototypes
static int BMM350_Init(void);
static int BMM350_Read(BMM350_data_t *s_data);
static float ComputeMagnitude(BMM350_data_t *reading);
void timer_callback();


int main(void)
{

	int status = 0;
	float mag = 0;
	BMM350_data_t reading;
	BMM350_data_t BMM350_CALIBRATION_DATA[CALIBRATION_SAMPLES];

	// Initialize the BMM350 sensor
	status = BMM350_Init();

	// ------------------------------------------------------------------ 
	// Initial calibration: 
	// Collect 5 readings while the parking slot is known to be empty. 
	// These samples are averaged to create the baseline magnetic field. 
	// ------------------------------------------------------------------
	for (int i = 0 ; i < CALIBRATION_SAMPLES ; i++)
	{
		if (BMM350_Read(&reading) != 0)
    		{
      			APP_LOG(TS_ON, VLEVEL_L, "BMM350 read failed — skipping scan\r\n");
      			return 0;
    		}
		
		BMM350_CALIBRATION_DATA[i] = reading;
	}
	
	// Reset baseline accumulators before averaging
	BMM350_CALIBRATED_BASE.x = 0;
        BMM350_CALIBRATED_BASE.y = 0;
        BMM350_CALIBRATED_BASE.z = 0;
        BMM350_CALIBRATED_BASE.temperature = 0;

	// Average of 5 Samples 
        for (int j = 0; j < CALIBRATION_SAMPLES; j++)
        {
          BMM350_CALIBRATED_BASE.x += BMM350_CALIBRATION_DATA[j].x;
          BMM350_CALIBRATED_BASE.y += BMM350_CALIBRATION_DATA[j].y;
          BMM350_CALIBRATED_BASE.z += BMM350_CALIBRATION_DATA[j].z;
          BMM350_CALIBRATED_BASE.temperature += BMM350_CALIBRATION_DATA[j].temperature;
        }

        BMM350_CALIBRATED_BASE.x /= CALIBRATION_SAMPLES;
        BMM350_CALIBRATED_BASE.y /= CALIBRATION_SAMPLES;
        BMM350_CALIBRATED_BASE.z /= CALIBRATION_SAMPLES;
        BMM350_CALIBRATED_BASE.temperature /= CALIBRATION_SAMPLES;

	// ------------------------------------------------------------------ 
	// Main detection loop 
	// Executed once every minute after the MCU wakes from low-power mode. 
	// ------------------------------------------------------------------
	while(timer_flag)
	{
		if (BMM350_Read(&reading) != 0)
    		{
      			APP_LOG(TS_ON, VLEVEL_L, "BMM350 read failed — skipping scan\r\n");
      			return 0;
    		}
		
		// Compute Euclidean distance between current reading and Base values
		mag = ComputeMagnitude(&reading);
		
		//Detection Logic
		if (!parked)
      		{
        		if (mag >= THRESHOLD)
        		{
          			//Threshold crossed 
				parked = true;
        		}
      		}
      		else
      		{
        		if (mag < THRESHOLD * 0.9f)
        		{
          			// Threshold crossed
				parked = false;
        		}
      		}

		// -------------------------------------------------------------- 
		// Adaptive baseline update 
		// When the slot is confidently empty and the measured magnetic 
		// field is still close to the baseline, slowly update the 
		// baseline using an Exponential Moving Average (EMA) to 
		// compensate for gradual environmental or temperature-related 
		// drift. 
		// --------------------------------------------------------------
		if (!parked)
      		{
          		// Check if the current reading is reasonably close to the baseline
			if (mag < (THRESHOLD * 0.5f))
         		 {
              			// Apply an ultra-slow tracking factor (Alpha)
              			// This allows the baseline to smoothly absorb ambient temperature drift
              			BMM350_CALIBRATED_BASE.x = (BMM350_CALIBRATED_BASE.x * (1 - ALPHA)) + (reading.x * ALPHA);
              			BMM350_CALIBRATED_BASE.y = (BMM350_CALIBRATED_BASE.y * (1 - ALPHA)) + (reading.y * ALPHA);
              			BMM350_CALIBRATED_BASE.z = (BMM350_CALIBRATED_BASE.z * (1 - ALPHA)) + (reading.z * ALPHA);
              			BMM350_CALIBRATED_BASE.temperature = (BMM350_CALIBRATED_BASE.temperature * (1 - ALPHA)) + (reading.temperature * ALPHA);
          		}
      		}

	}
	return 0;
}

// ------------------------------------------------------------------ 
// Initialize the BMM350 sensor 
// ------------------------------------------------------------------
static int BMM350_Init(void)
{
	HAL_GPIO_WritePin(MAGNETIC_PWR_GPIO_Port, MAGNETIC_PWR_Pin, GPIO_PIN_SET);
	HAL_Delay(1);
	uint8_t chip_id;
	uint8_t int_ctrl, err_reg_data = 0;
	struct bmm350_pmu_cmd_status_0 pmu_cmd_stat_0;

	rslt = bmm350_interface_init(&dev);

	rslt = bmm350_init(&dev);
	chip_id = dev.chip_id;
	if (chip_id == 0x33)
	{
		  /* Check PMU busy */
		  rslt = bmm350_get_pmu_cmd_status_0(&pmu_cmd_stat_0, &dev);

		  /* Get error data */
		  rslt = bmm350_get_regs(BMM350_REG_ERR_REG, &err_reg_data, 1, &dev);

		  /* Configure interrupt settings */
		  rslt = bmm350_configure_interrupt(BMM350_PULSED,
		                                    BMM350_ACTIVE_HIGH,
		                                    BMM350_INTR_PUSH_PULL,
		                                    BMM350_UNMAP_FROM_PIN,
		                                    &dev);

		  /* Enable data ready interrupt */
		  rslt = bmm350_enable_interrupt(BMM350_ENABLE_INTERRUPT, &dev);

		  /* Set ODR and performance */
		  rslt = bmm350_set_odr_performance(BMM350_DATA_RATE_100HZ, BMM350_LOWPOWER, &dev);

		  /* Enable all axis */
		  rslt = bmm350_enable_axes(BMM350_X_EN, BMM350_Y_EN, BMM350_Z_EN, &dev);

		  rslt = bmm350_set_powermode(BMM350_SUSPEND_MODE, &dev);

		  return 0;
	}
	return 1;
}

// ------------------------------------------------------------------ 
// Read the BMM350 
// For each measurement:
// 1. Switch the sensor to FORCED mode. 
// 2. Read compensated X/Y/Z and temperature. 
// 3. Repeat T_AVG times. 
// 4. Return the average of all samples. 
// ------------------------------------------------------------------
static int BMM350_Read(BMM350_data_t *s_data)
{
    int_status = 0;

    sx = 0;
    sy = 0;
    sz = 0;
    st = 0;

  	  for (int i = 0; i < T_AVG; i++)
  	  {
  		  	rslt = bmm350_set_powermode(BMM350_FORCED_MODE, &dev);
  		  	if (rslt != BMM350_OK)
  		  	{
  		  		return -1;  /* I2C/sensor fault — caller must not act on this read */
  		  	}
            rslt = bmm350_get_compensated_mag_xyz_temp_data(&mag_temp_data, &dev);
            if (rslt != BMM350_OK)
            {
                return -1;
            }
            sx += mag_temp_data.x;
            sy += mag_temp_data.y;
            sz += mag_temp_data.z;
            st += mag_temp_data.temperature;
  	  }

  	  s_data->x = sx/T_AVG;
  	  s_data->y = sy/T_AVG;
  	  s_data->z = sz/T_AVG;
  	  s_data->temperature = st/T_AVG;

    return 0;
}

// Compute Euclidean distance between the current magnetic field vector 
// and the calibrated baseline.
static float ComputeMagnitude(BMM350_data_t *reading)
{
  float dx = fabs(reading->x - BMM350_CALIBRATED_BASE.x);
  float dy = fabs(reading->y - BMM350_CALIBRATED_BASE.y);
  float dz = fabs(reading->z - BMM350_CALIBRATED_BASE.z);
  return sqrtf((dx * dx) + (dy * dy) + (dz * dz));
}

// Timer callback executed every 1 minute to trigger a new measurement cycle.
void timer_callback()
{
	timer_flag = 1;
	return;
}

Questions:
1. How to handle this kind of issue?
2. Are there any recommended register settings or operating modes for low-power periodic wake-up applications?
3. Is this amount of X/Y/Z variation expected for the BMM350 after waking from sleep every minute?
4. Is there any known offset drift or baseline drift that should be compensated separately from temperature?
5. For parking detection applications, is it recommended to continuously update the baseline (slow adaptive filter) while the slot is known to be empty?

Any guidance or recommended best practices for using the BMM350 in this type of low-power parking sensor application would be greatly appreciated.

Sensor
BMM350
Application
Industrial applications
Use case
Parking sensor application
Development Platform
STM32WLE5CCU6 via I2C
Project Phase
DVT
Label
Sensor API
Volume
5000
regional information
India
1
2 replies
BMM350