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:
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 BMA400Constructor. 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
| Type | Name | Description |
|---|---|---|
I2C | i2c | An initialized machine.I2C object. Optional only on firmware that reports itself as esp32, esp8266 or one older Soldered board firmware. |
int | address | I2C address. BMA400_I2C_ADDRESS_SDO_LOW (0x14, the default) or BMA400_I2C_ADDRESS_SDO_HIGH (0x15). |
status_string()returns strDescribes 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 NoneWrites the configuration of the BMA400's generic interrupt 1. The result is stored in sensor.status.
ReturnsNothing; check sensor.status.
Parameters
| Type | Name | Description |
|---|---|---|
dict | config | Keys 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 NoneSets 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
| Type | Name | Description |
|---|---|---|
int | channel | BMA400_INT_CHANNEL_1 or BMA400_INT_CHANNEL_2. |
int | mode | BMA400_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 NoneTurns one interrupt condition on or off. The result is stored in sensor.status.
ReturnsNothing; check sensor.status.
Parameters
| Type | Name | Description |
|---|---|---|
int | int_type | One of the BMA400_*_INT_EN constants, here BMA400_GEN1_INT_EN. |
bool | enable | True to enable, False to disable. |
get_interrupt_status()returns int or NoneReads 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.