BERGSONNE

Sense.M.3G

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

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, uint8_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.

tile
Tile handle
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.

tile
Tile handle

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
Tile handle

tile_sense_m_3g_wake

Studio
void tile_sense_m_3g_wake(tile_t *tile)

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

tile
Tile handle

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
Tile handle

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.

tile
Tile handle

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).

tile
Tile handle

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
Tile handle

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.

tile
Tile handle
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.

tile
Tile handle

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.

tile
Tile handle

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.

tile
Tile handle

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.

tile
Tile handle

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.

tile
Tile handle

Returns Magnitude in nT

tile_sense_m_3g_data_ready

Studio
uint8_t tile_sense_m_3g_data_ready(tile_t *tile)

Check whether the device has a new sample waiting.

tile
Tile handle

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.

tile
Tile handle

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, uint8_t odr, uint8_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.

tile
Tile handle
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.

tile
Tile handle
mask
OR of BMM350_EN_X / _Y / _Z

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)

Configure the data-ready interrupt on the INT pin.

tile
Tile handle
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).

tile
Tile handle
drive
Drive strength, 0-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.

tile
Tile handle
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.

tile
Tile handle
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.

tile
Tile handle

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)

Read the PMU command status register.

tile
Tile handle

Returns PMU_CMD_STATUS_0: [3:0] last command, plus busy / normal-mode flags

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.

tile
Tile handle
word
Word index, 0-31

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.

tile
Tile handle
axis
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.

tile
Tile handle
reg
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.

tile
Tile handle
reg
Register address
value
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_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"
SENSE_M_3G_AVG_8
8 samples — "ultra low noise"

sense_m_3g_odr_t

Output data rate (PMU_CMD_AGGR_SET bits [3:0]).

SENSE_M_3G_ODR_400HZ
SENSE_M_3G_ODR_200HZ
SENSE_M_3G_ODR_100HZ
SENSE_M_3G_ODR_50HZ
SENSE_M_3G_ODR_25HZ
SENSE_M_3G_ODR_12_5HZ
SENSE_M_3G_ODR_6_25HZ
SENSE_M_3G_ODR_3_125HZ
SENSE_M_3G_ODR_1_5625HZ

sense_m_3g_mode_t

Power mode.

Constants

TILE_SENSE_M_3G_VERSION_MAJOR0
TILE_SENSE_M_3G_VERSION_MINOR9
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