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);
while (1) {
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(40); // one sample per 25 Hz period
}Low-power example (forced mode, one sample on demand)
sense_m_3g_cfg_t cfg = { .start_suspended = 1 };
tile_sense_m_3g_init(core_tiles_pal(&core_i2c1), 0, &mag, &cfg);
if (tile_sense_m_3g_trigger_measurement(&mag)) {
tile_sense_m_3g_read(&mag);
int32_t x = tile_sense_m_3g_get_x_nt(&mag);
}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);
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, runs a full magnetic reset so the transducer starts from a known magnetic state, and applies the configuration. Then it enters normal mode and waits for the first sample, so the device is measuring and the getters return real data from the first call. With cfg->start_suspended set it stays in suspend instead. Takes roughly 100 ms for the OTP download and the magnetic reset's bit-reset and flip-gain-reset settling, plus up to 70 ms suspend-to- normal start-up (datasheet Table 1) and one ODR period for the first sample (40 ms at the default 25 Hz).
- 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 already leaves the device in normal mode (unless cfg->start_suspended is set), so this is for changing modes later.
- 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)The mode init leaves it in; use after sleep() or a forced measurement.
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, then re-applies the configuration (ODR, averaging, axes, interrupt) and returns to the mode init left the device in: measuring, or suspend with cfg->start_suspended.
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)Not needed for fresh data in normal mode: the getters refresh on their own. Use it after trigger_measurement(), or to take an explicit snapshot, which the getters then return until each channel has been read once. Burst-reads magnetic, temperature and sensor-time 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.
- Polled (no INT pin), it goes by data_ready(), and init sets the interrupt PULSED: the flag clears itself ~1.25 ms after each sample (datasheet §5.5). A loop that waits between calls then seldom fires on_data. Call configure_interrupt(tile, 0, 0, 1, 1) once to latch the flag until read, or give process() an INT pin.
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)In normal mode this returns the latest sample (see "Getters return fresh data" above): one 3-byte sensor-time read per call, plus a 15-byte burst when the chip has a new sample. Otherwise the last sample, no bus traffic.
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)Same sample and refresh rules as get_x_nt().
Returns Y in nT
tile_sense_m_3g_get_z_nt
Studioint32_t tile_sense_m_3g_get_z_nt(tile_t *tile)Same sample and refresh rules as get_x_nt().
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. Same sample and refresh rules as get_x_nt().
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. Same sample and refresh rules as get_x_nt(), for all three axes at once.
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 ~39 µs (the datasheet says 40 µs; its ODR timing works out to 39.0625 µs) 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. The device only accepts the change in suspend (datasheet §5.7), so in normal mode this parks it in suspend, writes, and resumes (~45 ms).
- 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)Read the PMU command status register.
Returns PMU_CMD_STATUS_0 (datasheet §8.7): bit 0 busy, bit 1 ODR 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.
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 of the cached sample (last read() or getter refresh; this call does not refresh)
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 | 1 | |
| TILE_SENSE_M_3G_VERSION_PATCH | 1 | |
| 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 |

