BERGSONNE

Sense.M.3G

Triaxial geomagnetic magnetometer driver for Sense.M.3G (BMM350).

Vibe Settings

What Studio’s Vibe interface lets you change on this tile, in plain language or from the tile inspector. Each one is an argument of a driver function below, so the same setting is available from Blocks, the DSL and C. Basic settings are shown by default; advanced ones sit behind the inspector’s Advanced toggle.

SettingTierValuesDefaultFunction
Output data ratebasic400 Hz (no averaging only), 200 Hz (averaging up to 2x), 100 Hz (averaging up to 4x), 50 Hz, 25 Hz (driver default), 12.5 Hz, 6.25 Hz, 3.125 Hz, 1.5625 Hz25 Hz (driver default)set_odr_averaging(odr)
Averagingadvanced1 sample (low power), 2 samples (regular power), 4 samples (low noise, driver default), 8 samples (ultra low noise, 50 Hz or slower)4 samples (low noise, driver default)set_odr_averaging(averaging)

Examples

Polled example (Cores SDK)

  tile_t mag;
  tile_sense_m_3g_init(core_tiles_pal(&core_i2c1), 0, &mag, NULL);
  tile_sense_m_3g_set_mode(&mag, SENSE_M_3G_MODE_NORMAL);
  while (1) {
      if (tile_sense_m_3g_data_ready(&mag)) {
          tile_sense_m_3g_read(&mag);
          printf("%ld %ld %ld nT\n",
                 tile_sense_m_3g_get_x_nt(&mag),
                 tile_sense_m_3g_get_y_nt(&mag),
                 tile_sense_m_3g_get_z_nt(&mag));
      }
      core_delay_ms(10);
  }

Interrupt-driven example (INT wired to a Core pad)

  sense_m_3g_cfg_t cfg = {
      .odr = SENSE_M_3G_ODR_50HZ,
      .averaging = SENSE_M_3G_AVG_4,
      .int_pin = 9,                       // Core pad carrying INT
      .on_data = on_mag_sample,
  };
  tile_sense_m_3g_init(core_tiles_pal(&core_i2c1), 0, &mag, &cfg);
  tile_sense_m_3g_set_mode(&mag, SENSE_M_3G_MODE_NORMAL);
  while (1) { tile_sense_m_3g_process(&mag); }

API reference

Initialization

tile_sense_m_3g_find

uint8_t tile_sense_m_3g_find(tiles_pal_t *hal, uint8_t instance)

Probe the bus for a BMM350.

hal
Tiles HAL handle (I2C bus)
instance
0 for address 0x14 (Sense.M.3G), 1 for 0x15

Returns 1 if a device ACKs and reports CHIP_ID 0x33, 0 otherwise

tile_sense_m_3g_init

void tile_sense_m_3g_init(tiles_pal_t *hal, uint8_t instance, tile_t *tile, const sense_m_3g_cfg_t *cfg)

Soft-resets the device, verifies CHIP_ID, downloads the 32 OTP trim words and derives the compensation coefficients from them, powers the OTP back down, then runs a full magnetic reset so the transducer starts from a known magnetic state. Leaves the device in suspend — call set_mode() to start measuring. Takes roughly 100 ms, most of it the OTP download and the magnetic reset's bit-reset and flip-gain-reset settling.

hal
Tiles HAL handle (I2C bus)
instance
0 for 0x14, 1 for 0x15
tile
Tile handle to initialize
cfg
Optional config. NULL for defaults.

Lifecycle

tile_sense_m_3g_set_mode

Studio
void tile_sense_m_3g_set_mode(tile_t *tile, sense_m_3g_mode_t mode)

Forced mode is reachable only from suspend, so a request to enter it from normal mode is routed through suspend first. Each transition blocks for the settling time the device needs, which for suspend to forced depends on the averaging setting. Init leaves the device in suspend, so nothing is measured until this (or wake(), or trigger_measurement()) is called.

mode
Target mode (sense_m_3g_mode_t)

tile_sense_m_3g_trigger_measurement

Studio
uint8_t tile_sense_m_3g_trigger_measurement(tile_t *tile)

Uses the fast forced mode when the configured ODR is 25 Hz or above and the slow one otherwise, per datasheet §5.1.4. The device returns to suspend on its own once the conversion completes. Does not read the sample — call read() after.

Returns 1 if a conversion completed, 0 on timeout

tile_sense_m_3g_sleep

Studio
void tile_sense_m_3g_sleep(tile_t *tile)

Put the device in suspend mode (lowest power, settings kept).

tile_sense_m_3g_wake

Studio
void tile_sense_m_3g_wake(tile_t *tile)

Return the device to normal (free-running) mode.

tile_sense_m_3g_reset

Studio
void tile_sense_m_3g_reset(tile_t *tile)

Re-downloads the OTP trim data and repeats the magnetic reset, so the compensation coefficients survive. Leaves the device in suspend.

tile_sense_m_3g_magnetic_reset

Studio
uint8_t tile_sense_m_3g_magnetic_reset(tile_t *tile)

Restores the transducer's magnetic state after exposure to a strong field. The device does this automatically every ODR tick while measuring and once at boot, so it is only needed after the part has sat in suspend through a field event — the sensor cannot detect one while suspended (datasheet §5.1.5). Worth calling after more than a couple of seconds in suspend, which also recharges the CRST capacitor. Must be run from suspend; a device in normal mode is parked, reset, and restored.

Returns 1 if both reset phases were acknowledged, 0 otherwise

Runtime

tile_sense_m_3g_read

Studio
uint8_t tile_sense_m_3g_read(tile_t *tile)

Burst-reads magnetic and temperature data in a single transaction — mandatory, because the device freezes the data registers for the duration of a burst and single reads would tear across an update (datasheet §5.2).

Returns 1 on success, 0 if the bus read failed

tile_sense_m_3g_process

Studio
void tile_sense_m_3g_process(tile_t *tile)

Call from your main loop. Checks data-ready (or the INT flag when an INT pin is configured), and on a new sample calls read() and fires the callback. Cheap when nothing is ready.

tile_sense_m_3g_on_data

Studio
void tile_sense_m_3g_on_data(tile_t *tile, sense_m_3g_data_cb_t cb, void *ctx)

Register or change the new-sample callback.

cb
Callback (NULL to disable)
ctx
User context passed to the callback

tile_sense_m_3g_get_x_nt

Studio
int32_t tile_sense_m_3g_get_x_nt(tile_t *tile)

Compensated X field from the last read, in nanotesla.

Returns X in nT (1000 nT = 1 uT)

tile_sense_m_3g_get_y_nt

Studio
int32_t tile_sense_m_3g_get_y_nt(tile_t *tile)

Compensated Y field from the last read, in nanotesla.

Returns Y in nT

tile_sense_m_3g_get_z_nt

Studio
int32_t tile_sense_m_3g_get_z_nt(tile_t *tile)

Compensated Z field from the last read, in nanotesla.

Returns Z in nT

tile_sense_m_3g_get_temperature_mc

Studio
int32_t tile_sense_m_3g_get_temperature_mc(tile_t *tile)

This is the compensation chain's own temperature, not an ambient reading — the die sits above ambient by whatever the board is doing.

Returns Temperature in m°C (25000 = 25.000 °C)

tile_sense_m_3g_get_magnitude_nt

Studio
uint32_t tile_sense_m_3g_get_magnitude_nt(tile_t *tile)

sqrt(x² + y² + z²), computed with an integer square root. Useful as a sanity check: a quiet indoor spot should land in the 25000-65000 nT band, and a magnet nearby will swamp it.

Returns Magnitude in nT

tile_sense_m_3g_data_ready

Studio
uint8_t tile_sense_m_3g_data_ready(tile_t *tile)

Reads INT_STATUS, which clears the flag. Init configures the interrupt as PULSED, and in pulsed mode the flag also clears itself ~1.25 ms after the sample (datasheet §5.5), so a loop polling less often than that misses most samples. For polled use, call configure_interrupt(tile, 0, 0, 1, 1) to latch it until read.

Returns 1 if INT_STATUS reports data ready

tile_sense_m_3g_get_sensortime

Studio
uint32_t tile_sense_m_3g_get_sensortime(tile_t *tile)

Ticks at 40 µs resolution and marks when the last sample was generated, not when you read it. Only advances in normal mode unless sensor-time always-on is enabled. Wraps at 24 bits without saturating.

Returns Raw 24-bit counter value

Config

tile_sense_m_3g_set_odr_averaging

Studio
void tile_sense_m_3g_set_odr_averaging(tile_t *tile, sense_m_3g_odr_t odr, sense_m_3g_avg_t averaging)

The pair is constrained: 400 Hz forces no averaging, 200 Hz caps at 2x, 100 Hz at 4x (datasheet Table 5). An illegal combination is clamped to the highest averaging the requested rate allows rather than rejected. Both live in one register and need an update command, which this issues.

odr
Output data rate (sense_m_3g_odr_t)
averaging
Averaging (sense_m_3g_avg_t)

tile_sense_m_3g_set_axes

Studio
void tile_sense_m_3g_set_axes(tile_t *tile, uint8_t mask)

A disabled axis stops converting, saving current, and reads back 0.

mask
[0..7] OR of BMM350_EN_X (1) / _Y (2) / _Z (4)

tile_sense_m_3g_configure_interrupt

Studio
void tile_sense_m_3g_configure_interrupt(tile_t *tile, uint8_t enable, uint8_t active_high, uint8_t push_pull, uint8_t latched)

Data-ready is always mapped into INT_STATUS. `latched` also decides how long the INT_STATUS flag lives: latched holds it until read, pulsed clears it after ~1.25 ms (datasheet §5.5). Note the datasheet marks INT_CTRL.int_output_en reserved on A-Si silicon.

enable
1 to drive the INT pin, 0 to leave it idle
active_high
1 for active-high, 0 for active-low
push_pull
1 for push-pull, 0 for open-drain (needs a pull-up)
latched
1 to latch until INT_STATUS is read, 0 for a pulse

tile_sense_m_3g_set_pad_drive

Studio
void tile_sense_m_3g_set_pad_drive(tile_t *tile, uint8_t drive)

Set the INT/SDA/SCL pad drive strength (0-7, weakest to strongest).

drive
[0..7] Drive strength (reset value 7)

tile_sense_m_3g_set_i2c_watchdog

Studio
void tile_sense_m_3g_set_i2c_watchdog(tile_t *tile, uint8_t enable, uint8_t long_wdt)

Recovers the bus if the host stalls mid-transaction. Independent of the Core's own watchdog.

enable
1 to enable, 0 to disable
long_wdt
1 for the ~40 ms timeout, 0 for ~1.28 ms

tile_sense_m_3g_set_sensortime_always_on

Studio
void tile_sense_m_3g_set_sensortime_always_on(tile_t *tile, uint8_t enable)

Keep the sensor-time counter running outside normal mode.

enable
1 to keep counting in suspend and forced mode
gapCross-axis coefficient overrideThe cross-axis compensation coefficients are read from OTP and applied automatically. There is no API to override them with values from a system-level calibration; hard-iron and soft-iron correction for a specific enclosure belongs above this driver anyway.

Advanced

tile_sense_m_3g_get_error

Studio
uint8_t tile_sense_m_3g_get_error(tile_t *tile)

Read the device's error register.

Returns ERR_REG contents; bit 0 set means the last PMU command was rejected

tile_sense_m_3g_get_pmu_status

Studio
uint8_t tile_sense_m_3g_get_pmu_status(tile_t *tile)

overwritten, bit 2 averaging overwritten, bit 3 normal mode, bit 4 illegal command. Bits [7:5] are reserved in the datasheet; Bosch's SensorAPI reads the last command's value there.

Returns PMU_CMD_STATUS_0 (datasheet §8.7): bit 0 busy, bit 1 ODR

tile_sense_m_3g_get_otp_word

Studio
uint16_t tile_sense_m_3g_get_otp_word(tile_t *tile, uint8_t word)

The compensation coefficients are derived from these. Exposed for diagnostics — comparing them against a known-good part is the fastest way to tell a compensation bug from a broken OTP download.

word
[0..31] Word index

Returns The cached 16-bit OTP word, or 0 if the index is out of range

tile_sense_m_3g_get_raw

Studio
int32_t tile_sense_m_3g_get_raw(tile_t *tile, uint8_t axis)

Straight from the data registers with no compensation applied — the input to the compensation chain. For debugging only; these are not field values and are not in spec.

axis
[0..3] 0 = X, 1 = Y, 2 = Z, 3 = temperature

Returns Signed raw counts from the last read()

tile_sense_m_3g_read_reg

Studio
uint8_t tile_sense_m_3g_read_reg(tile_t *tile, uint8_t reg)

Read a raw 8-bit register, dummy bytes handled.

reg
[0..127] Register address

Returns Register value, or 0 if the tile is not ready

tile_sense_m_3g_write_reg

Studio
void tile_sense_m_3g_write_reg(tile_t *tile, uint8_t reg, uint8_t value)

Write a raw 8-bit register.

reg
[0..127] Register address
value
[0..255] Value to write
gapSelf-testThe BMM350 can inject a ~130 uT internal field to verify the X and Y channels (datasheet §5.1.6). The datasheet describes the concept and the pass criterion but not the register sequence, which lives in the SensorAPI's self_test_config / TMR_SELFTEST_USER handling. Exposing it means porting that sequence and validating it against a known field. Driver-deferred, and the highest-value thing to add next.
gapOTP programmingOTP write commands (DIR_PRGM, DIR_PRGM_1B, EXT_PRGM) are exposed by the chip but are a factory-provisioning facility — programming OTP is irreversible and would destroy the part's trim data. Read access is used internally for compensation and available through get_otp_word.
gapI3C interface and in-band interruptsHardware-gated: the BMM350 supports I3C with in-band interrupts (IBI), but Sense.M.3G is wired for I2C and the Core's I3C support is not routed to this tile. INT_CTRL_IBI and the I3C error register are consequently not exposed.

Enums

sense_m_3g_odr_t

SENSE_M_3G_ODR_400HZ
400 Hz (no averaging only) @studio value=400
SENSE_M_3G_ODR_200HZ
200 Hz (averaging up to 2x) @studio value=200
SENSE_M_3G_ODR_100HZ
100 Hz (averaging up to 4x) @studio value=100
SENSE_M_3G_ODR_50HZ
50 Hz @studio value=50
SENSE_M_3G_ODR_25HZ
25 Hz (driver default) @studio value=25
SENSE_M_3G_ODR_12_5HZ
12.5 Hz @studio value=12.5
SENSE_M_3G_ODR_6_25HZ
6.25 Hz @studio value=6.25
SENSE_M_3G_ODR_3_125HZ
3.125 Hz @studio value=3.125
SENSE_M_3G_ODR_1_5625HZ
1.5625 Hz @studio value=1.5625

sense_m_3g_avg_t

SENSE_M_3G_AVG_NONE
1 sample (low power)
SENSE_M_3G_AVG_2
2 samples (regular power)
SENSE_M_3G_AVG_4
4 samples (low noise, driver default)
SENSE_M_3G_AVG_8
8 samples (ultra low noise, 50 Hz or slower)

sense_m_3g_mode_t

SENSE_M_3G_MODE_SUSPEND
Suspend: no conversions, ~1.8 uA, settings kept
SENSE_M_3G_MODE_NORMAL
Normal: free-running at the configured ODR
SENSE_M_3G_MODE_FORCED
One conversion (full CRST recharge), then suspend
SENSE_M_3G_MODE_FORCED_FAST
One conversion (fast CRST recharge; ODR 25 Hz or faster), then suspend

Constants

TILE_SENSE_M_3G_VERSION_MAJOR1
TILE_SENSE_M_3G_VERSION_MINOR0
TILE_SENSE_M_3G_VERSION_PATCH0
BMM350_I2C_ADDR_LOW0x14
BMM350_I2C_ADDR_HIGH0x15
BMM350_CHIP_ID0x33
BMM350_DUMMY_BYTES2
BMM350_MAG_TEMP_DATA_LEN12
BMM350_PMU_CMD_SUS0x00Suspend mode
BMM350_PMU_CMD_NM0x01Normal mode
BMM350_PMU_CMD_UPD_OAE0x02Apply a new ODR / averaging setting
BMM350_PMU_CMD_FM0x03Forced mode
BMM350_PMU_CMD_FM_FAST0x04Forced mode, fast (ODR >= 25 Hz)
BMM350_PMU_CMD_FGR0x05Flip-gain reset (magnetic reset)
BMM350_PMU_CMD_FGR_FAST0x06
BMM350_PMU_CMD_BR0x07Bit reset (magnetic reset)
BMM350_PMU_CMD_BR_FAST0x08
BMM350_CMD_SOFTRESET0xB6
BMM350_EN_XYZ0x07