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.
| Setting | Tier | Values | Default | Function |
|---|---|---|---|---|
| Output data rate | basic | 400 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 Hz | 25 Hz (driver default) | set_odr_averaging(odr) |
| Averaging | advanced | 1 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
Studiovoid 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
Studiouint8_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
Studiovoid tile_sense_m_3g_sleep(tile_t *tile)Put the device in suspend mode (lowest power, settings kept).
tile_sense_m_3g_wake
Studiovoid tile_sense_m_3g_wake(tile_t *tile)Return the device to normal (free-running) mode.
tile_sense_m_3g_reset
Studiovoid 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
Studiouint8_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
Studiouint8_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
Studiovoid 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
Studiovoid 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
Studioint32_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
Studioint32_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
Studioint32_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
Studioint32_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
Studiouint32_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
Studiouint8_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
Studiouint32_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
Studiovoid 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
Studiovoid 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
Studiovoid 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
Studiovoid 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
Studiovoid 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
Studiovoid 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
Advanced
tile_sense_m_3g_get_error
Studiouint8_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
Studiouint8_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
Studiouint16_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
Studioint32_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
Studiouint8_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
Studiovoid 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
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_MAJOR | 1 | |
| TILE_SENSE_M_3G_VERSION_MINOR | 0 | |
| TILE_SENSE_M_3G_VERSION_PATCH | 0 | |
| BMM350_I2C_ADDR_LOW | 0x14 | |
| BMM350_I2C_ADDR_HIGH | 0x15 | |
| BMM350_CHIP_ID | 0x33 | |
| BMM350_DUMMY_BYTES | 2 | |
| BMM350_MAG_TEMP_DATA_LEN | 12 | |
| BMM350_PMU_CMD_SUS | 0x00 | Suspend mode |
| BMM350_PMU_CMD_NM | 0x01 | Normal mode |
| BMM350_PMU_CMD_UPD_OAE | 0x02 | Apply a new ODR / averaging setting |
| BMM350_PMU_CMD_FM | 0x03 | Forced mode |
| BMM350_PMU_CMD_FM_FAST | 0x04 | Forced mode, fast (ODR >= 25 Hz) |
| BMM350_PMU_CMD_FGR | 0x05 | Flip-gain reset (magnetic reset) |
| BMM350_PMU_CMD_FGR_FAST | 0x06 | |
| BMM350_PMU_CMD_BR | 0x07 | Bit reset (magnetic reset) |
| BMM350_PMU_CMD_BR_FAST | 0x08 | |
| BMM350_CMD_SOFTRESET | 0xB6 | |
| BMM350_EN_XYZ | 0x07 |

