Skip to main content

Motion detection

What this example shows​

Letting the BMA400 watch for movement on its own. The chip's generic interrupt 1 compares the acceleration with a reference and raises INT1 when any axis moves more than the threshold. Your board only reacts to the interrupt.

Besides the I2C wiring (GPIO 9 for SCL and GPIO 8 for SDA in the example), wire the breakout's INT1 pin to GPIO 5, or change INT1_PIN to the pin you use.

The example code​

# FILE: bma400-motionDetection.py
# AUTHOR: Soldered Electronics
# BRIEF: Uses the BMA400's generic interrupt feature to detect motion via a
# hardware interrupt pin
# WORKS WITH: BMA400 Accelerometer breakout: www.solde.red/333419
# LAST UPDATED: 2026-09-17

from machine import I2C, Pin
from bma400 import (
BMA400,
BMA400_OK,
BMA400_AXIS_XYZ_EN,
BMA400_DATA_SRC_ACCEL_FILT_2,
BMA400_ACTIVITY_INT,
BMA400_ANY_AXES_INT,
BMA400_UPDATE_EVERY_TIME,
BMA400_HYST_96_MG,
BMA400_INT_CHANNEL_1,
BMA400_INT_PUSH_PULL_ACTIVE_1,
BMA400_GEN1_INT_EN,
BMA400_ASSERTED_GEN1_INT,
)

# Hardware setup: connect the breakout board's INT1 pin to INT1_PIN below
INT1_PIN = 5

mems_event = False


def int1_callback(pin):
global mems_event
mems_event = True


# Change these to match how your board wires I2C
i2c = I2C(0, scl=Pin(9), sda=Pin(8))
sensor = BMA400(i2c)

if sensor.status != BMA400_OK:
raise Exception("Failed to initialize BMA400: " + sensor.status_string())

# Configure the generic interrupt feature. It triggers an interrupt when the
# measured acceleration exceeds a reference acceleration
config = {
"gen_int_thres": 5, # 8mg resolution (e.g. 5 = 40mg)
"gen_int_dur": 5, # 10ms resolution (e.g. 5 = 50ms)
"axes_sel": BMA400_AXIS_XYZ_EN, # Evaluate all axes for interrupts
"data_src": BMA400_DATA_SRC_ACCEL_FILT_2, # Datasheet recommends filter 2 (100Hz)
"criterion_sel": BMA400_ACTIVITY_INT, # Trigger when active
"evaluate_axes": BMA400_ANY_AXES_INT, # Trigger if any axis exceeds threshold
"ref_update": BMA400_UPDATE_EVERY_TIME, # Automatically update reference values
"hysteresis": BMA400_HYST_96_MG, # Hysteresis for noise rejection
"int_thres_ref_x": 0,
"int_thres_ref_y": 0,
"int_thres_ref_z": 512, # 1g at 4g range
"int_chan": BMA400_INT_CHANNEL_1, # Route interrupt to INT1 pin
}
sensor.set_generic1_interrupt(config)

# Configure INT1 pin to push/pull mode, active high
sensor.set_interrupt_pin_mode(BMA400_INT_CHANNEL_1, BMA400_INT_PUSH_PULL_ACTIVE_1)

# Enable the generic 1 interrupt condition
sensor.enable_interrupt(BMA400_GEN1_INT_EN, True)

int1 = Pin(INT1_PIN, Pin.IN)
int1.irq(trigger=Pin.IRQ_RISING, handler=int1_callback)

print("Waiting for motion...")

while True:
if mems_event:
mems_event = False
status = sensor.get_interrupt_status()
if status is not None and (status & BMA400_ASSERTED_GEN1_INT):
print("Motion detected!")

🔗Open the complete example

bma400-motionDetection.py at commit 484a5be

→

This example also uses machine.I2C (MicroPython reference) and machine.Pin with its irq() method (MicroPython reference), which come with the MicroPython firmware and are not part of the bma400 module.

Expected result:

ShellMicroPython
Waiting for motion...
Motion detected!

Waiting for motion... appears once the interrupt is set up. Motion detected! appears each time the BMA400 signals movement on INT1.

Functions used​

BMA400(i2c=None, address=BMA400_I2C_ADDRESS_SDO_LOW)returns BMA400

Constructor. Soft-resets the sensor, checks its chip ID (0x90) and puts it in normal power mode. Raises an exception when the sensor does not answer or another chip answers.

ReturnsThe sensor object.

Parameters

TypeNameDescription
I2Ci2cAn initialized machine.I2C object. Optional only on firmware that reports itself as esp32, esp8266 or one older Soldered board firmware.
intaddressI2C address. BMA400_I2C_ADDRESS_SDO_LOW (0x14, the default) or BMA400_I2C_ADDRESS_SDO_HIGH (0x15).
status_string()returns str

Describes the current sensor.status code, for example Communication failure or Sensor not found.

ReturnsThe description, or an empty string when the status is BMA400_OK.

set_generic1_interrupt(config)returns None

Writes the configuration of the BMA400's generic interrupt 1. The result is stored in sensor.status.

ReturnsNothing; check sensor.status.

Parameters

TypeNameDescription
dictconfigKeys gen_int_thres (8 mg steps), gen_int_dur (10 ms steps), axes_sel, data_src, criterion_sel, evaluate_axes, ref_update, hysteresis, int_thres_ref_x, int_thres_ref_y, int_thres_ref_z and int_chan.
set_interrupt_pin_mode(channel, mode)returns None

Sets an interrupt pin as push-pull or open drain, active high or active low. The result is stored in sensor.status.

ReturnsNothing; check sensor.status.

Parameters

TypeNameDescription
intchannelBMA400_INT_CHANNEL_1 or BMA400_INT_CHANNEL_2.
intmodeBMA400_INT_PUSH_PULL_ACTIVE_0, BMA400_INT_PUSH_PULL_ACTIVE_1 (default), BMA400_INT_OPEN_DRIVE_ACTIVE_0 or BMA400_INT_OPEN_DRIVE_ACTIVE_1.
enable_interrupt(int_type, enable)returns None

Turns one interrupt condition on or off. The result is stored in sensor.status.

ReturnsNothing; check sensor.status.

Parameters

TypeNameDescription
intint_typeOne of the BMA400_*_INT_EN constants, here BMA400_GEN1_INT_EN.
boolenableTrue to enable, False to disable.
get_interrupt_status()returns int or None

Reads which interrupt conditions are asserted. Test the result against flags such as BMA400_ASSERTED_GEN1_INT.

ReturnsA bitmask of BMA400_ASSERTED_* flags, or None on a bus error.

Putting it together​

With gen_int_thres and gen_int_dur both at 5, the interrupt fires when an axis moves more than 40 mg away from its reference for 50 ms, following the example's comments. ref_update set to BMA400_UPDATE_EVERY_TIME lets the chip update the reference on its own, and the Z reference of 512 is 1 g at the ±4 g range. INT1 is push-pull, active high, so the pin interrupt triggers on Pin.IRQ_RISING.

The callback only sets mems_event. The main loop then reads the status over I2C and prints Motion detected! when the generic 1 flag is set.