MicroPython Driver for BMA400 Accelerometer
Contents
Introduction
This a MicroPython driver written specifically for the BBC micro:bit that will work with the BMA400 accelerometer sensor.
The BMA400 sensor is discussed in some detail here.
FIG 1 - BMA400 breakout board: [Left] front view, [Right] rear view
The sensor is quite small and for projects using a breadboard its much easier to use a breakout board. The breakout boards are not particularly expensive.
Connecting the BMA400
This sensor communicate via I2C and SPI (3-wire and 4-wire). This driver will only use I2C. Hooking it up to the micro:bit for I2C is easy:
| micro:bit | Sensor board |
|---|---|
| 3.3V | VCC |
| GND | GND |
| Pin 20 | SDA |
| Pin 19 | SCL |
This utilises the standard I2C pins of the micro:bit.
Optionally, a jumper wire can be placed between the VCC pin and the SDO pin on the breakout board. This changes the default I2C address and is discussed below.
Driver Overview
The driver implements many of the sampling acceleration options the sensor offers including the ability to choose the power mode, the measurement ranges, amount of oversampling used and the ODR. Also included is a simple tool for measuring angles from the horizontal.
The FIFO and all interrupts (with the exception of Data Ready) are not available in this driver.
Driver codeThe driver code can be:
- Copied from this webpage onto the clipboard then pasted into the MicroPython editor e.g. the Mu Editor. It should be saved as fc_bma400.py - OR -
- Download as a zip file using the link. Unzip the file and save it as fc_bma400.py into the default directory where the MicroPython editor e.g. Mu Editor saves python code files.
After saving the fc_bma400.py file to the computer it should be copied to the small filesystem on the micro:bit. The examples on this page will not work if this step is omitted. Any MicroPython editor that recognises the micro:bit will have an option to do this.
The Mu Editor is recommended for its simplicity. It provides a Files button on the toolbar for just this purpose.
I2C addressBMA400 breakout boards have a default I2C address of 0x14. This can be changed to 0x15 if a jumper wire is connected between the VCC pin and SDO pin.
Class constructorThe driver is implemented as a class.The first thing to do is call the constructor of the BMA400 class to obtain a sensor object.
Syntax:
BMA400(ADDR=0x14)
Where:
ADDR : The I2C address.
Example
from fc_bma400 import *
# Declare a BMA400 sensor object
# The I2C address is 0x14 (default).
sensor1 = BMA400()
# Declare a BMA400 sensor object
# The I2C address is 0x15.
sensor2 = BMA400(0x15)
This assumes that the file fc_bma400.py has been successfully copied to the micro:bit's filesystem as described above.
Methods and PropertiesThe driver provides methods and properties to:
-
Set and get the power mode and sampling parameters (Mode, ODR and IIR filtering):
SetPowerMode(), GetPowerMode, SetODR(), GetODR SetOSR(), GetOSR, SetGRange(), GetGRange, SetFilter(), GetFilter -
Read acceleration:
Reading, X, Y, Z, IsDataReady -
Angle measurement:
Xangle, Yangle -
Miscellaneous:
Temperature, ID, Time
Power Modes
The BMA400 has three main power modes.
- Sleep mode: Very low current consumption, data conversions are stopped.
- Low power mode: Data conversions occur at a fixed rate of 25Hz. Oversampling is available but if used comes at the cost of increased current consumption.
- Normal mode: Output data rates (ODR) can be configured in seven steps ranging from 12.5Hz up to 800Hz. There is a choice from four oversampling (OSR) levels giving increasing accuracy.
Power Mode
SetPowerMode(Mode=NORMAL)
This method is used to set the power mode.
Mode can have a value of:
SLEEP, LOW_POWER, NORMAL.
GetPowerMode
Property that returns the current mode:
0 = SLEEP, 1 = LOW_POWER, 2 = NORMAL
Example
from fc_bma400 import *
# Declare a BMA400 sensor object
# The I2C address is 0x14 (default).
sensor = BMA400()
# Change from default Normal power mode
# to Low Power mode.
sensor.SetPowerMode(LOW_POWER)
# Check the power mode
mode = sensor.GetPowerMode
print('Mode:', ('Sleep', 'Low Power', 'Normal')[mode])
Output:
Mode: Low Power
Sampling Options
In Normal mode there are a several sampling options available that influence acceleration accuracy and conversely sensor current consumption.
Output data rate (ODR), as the name suggests, determines the rate at which the sensor produces new acceleration measurements.
Oversampling (OSR) uses more internal samples to produce the output sample. Higher the OSR, the higher the acceleration accuracy.
There are four measurement ranges (±2g, ±4g, ±8g, ±16g) available to choose from. Configuring the most appropriate range for the acceleration being measured ensures the best precision.
In Normal power mode their are two filter paths available to produce the acceleration output. The main difference is the ODR and low pass filter options available.
- Filter1 : Allows configurable ODR as discussed above. There is a low pass filter that has a default bandwidth of 0.48 x ODR.
- Filter2 : ODR is fixed at 100Hz. However the low pass filter can be configured to the very small bandwidth of 1Hz. At this setting the signal noise rejection is very high.
Syntax:
SetFilter(Filter=1)
Sets the output filter in Normal power mode.
Allowable values are 1 and 2.
GetFilter
Returns the output filter in use.
One of 1 or 2.
SetODR(ODR = 8)
Sets the output data rate (ODR).
One of 5 (12.5Hz), 6 (25 Hz),7 (50Hz), 8 (100Hz),
9 (200Hz), 10 (400Hz), 11 (800Hz)
GetODR
Returns the ODR in use.
One of 5 to 11.
SetOSR(OSR=3)
Sets the oversampling rate (OSR).
One of 0 to 3
GetOSR
Returns the OSR in use.
One of 0 to 3.
SetGRange(Range=4)
Sets the measurement range.
One of 2, 4, 8, 16.
GetGRange
Returns the measurement range in use.
One of 2, 4, 8, 16.
Example:
from fc_bma400 import *
# Declare a BMA400 sensor object
# The I2C address is 0x14 (default).
sensor = BMA400()
# Report the default sampling options.
odr_freq = (12.5, 25, 50, 100, 200, 400, 800)
odr = sensor.GetODR
print('ODR:', odr_freq[odr-5], 'Hz')
print('OSR:', sensor.GetOSR)
print('Filter:', sensor.GetFilter)
print('Range: +/-', sensor.GetGRange, 'g')
# Change ODR and measurement range.
sensor.SetODR(10) # 400Hz
sensor.SetGRange(2) # +/-2g
print('\nNew settings...')
odr = sensor.GetODR
print('ODR:', odr_freq[odr-5], 'Hz')
print('Range: +/-', sensor.GetGRange, 'g')
Output:
ODR: 100 Hz
OSR: 3
Filter: 1
Range: +/- 4 g
New settings...
ODR: 400 Hz
Range: +/- 2 g
Measuring Acceleration
After optionally changing any sampling settings as described above, it's a very simple process to read acceleration values from the sensor. The acceleration values have units of g (1g = 9.8 m/sec2).
Syntax:
Reading
Property that returns acceleration in all three axis.
Values are returned in a tuple in the follow order:
(X-axis, Y-axis, Z-axis)
X
Property that returns the acceleration in the X-axis.
Y
Property that returns the acceleration in the Y-axis.
Z
Property that returns the acceleration in the Z-axis.
IsDataReady
Property returns True if the sensor
has new data ready for reading.
Example:
from fc_bma400 import *
# Declare a BMA400 sensor object
# The I2C address is 0x14 (default).
sensor = BMA400()
sensor.SetOSR(0)
# Read acceleration of all three axis.
acc = sensor.Reading
print('(X-axis, Y-axis, Z-axis):', acc)
# Read acceleration separately from each axis.
sleep(100)
print('\nX-axis:', sensor.X)
print('Y-axis:', sensor.Y)
print('Z-axis:', sensor.Z)
# Read acceleration on X-axis as fast
# as the output data rate (ODR) allows.
sleep(100)
print('\nX-axis')
for __ in range(6):
while not sensor.IsDataReady:
sleep(2)
print(sensor.X)
Typical Output:
(X-axis, Y-axis, Z-axis): (0.05859375, 0.03125, 1.046875)
X-axis: 0.046875
Y-axis: 0.04101563
Z-axis: 1.039063
X-axis
0.05859375
0.05664063
0.04101563
0.05078125
0.046875
0.04882813
In the above example the BMA400 was mounted on a breadboard that was lying on a flat horizontal surface. In this position the X and Y axis would be expected to return values very close to 0g. The Z axis value should be very close to 1g as the result of Earth's gravity.
Measuring Angles
Accelerometers measures:
- static : The sensor is stationary but is under the influence of the Earth's gravitational field
- dynamic : The component of acceleration resulting from the motion or changes in velocity experienced by the sensor over time.
When the accelerometer is stationary and lying perfectly perpendicular to the direction of the Earths gravity, the X-axis and Y-axis acceleration will both be 0g. The Z-axis acceleration will be 1g.
This fact along with some simple trigonometry can be used to measure angles in the X-Axis or Y-axis.
Syntax:
Xangle
Property returns inclination of X-axis from the
horizontal in degrees.
Yangle
Property returns inclination of Y-axis from the
horizontal in degrees.
Example:
# Demonstrates the calculation of the
# X-axis angle from the horizontal.
# Before executing this program place the sensor flat
# on a table top.
# The tilt angle of the X-axis will be calculated and
# output every 5 seconds till the program is stopped.
# Experiment by changing the angle of sensor in
# the X-axis direction.
from fc_bma400 import *
from microbit import sleep
# Declare a BMA400 sensor object
# The I2C address is 0x14 (default).
sensor = BMA400()
while True:
sleep(5000)
print(sensor.Xangle)
Typical Output:
-0.1125668
5.076378
18.15806
32.91272
49.05308
53.55655
33.30347
14.11156
-0.1130834
-0.112861
Traceback (most recent call last):
File "main.py", line 20, in <module>
Miscellaneous
This section throws together some of the additional features of the BMA400 accelerometer.
- Temperature measurement
- Chip ID
- Sensor time
Syntax:
Temperature
Property returns the temperature in °C.
It only has an 8-bit resolution so can't
be relied on for accuracy.
ID
Most Bosch sensors with a serial interface
have a product ID burnt into nonvolatile memory
at time of manufacture. This property returns
that ID.
Should return: 0x90
Time
Returns the sensor's time.
Please refer to Page 27 of the datasheet.
Example:
from fc_bma400 import *
# Declare a BMA400 sensor object
# The I2C address is 0x14 (default).
sensor = BMA400()
# Return the temperature
print('Temperature:',sensor.Temperature, 'C')
# Return the BMA400 product ID
print('Product ID:', sensor.ID)
# Return the sensor's clock time
print('Sensor time:', sensor.Time)
Typical Output:
Temperature: 22.5 C
Product ID: 0x90
Sensor time: 789830
Enjoy!
BMA400 Driver Code for micro:bit
Download as zip file
'''
BMA400 3-Axis Accelerometer
MicroPython driver for BBC micro:bit
AUTHOR: fredscave.com
DATE : 2025/07
VERSION : 1.00
'''
from microbit import *
from micropython import const
from math import atan, pi, sqrt
_REG_CHIP_ID = const(0x00)
_REG_STATUS = const(0x03)
_REG_ACC_X = const(0x04)
_REG_ACC_Y = const(0x06)
_REG_ACC_Z = const(0x08)
_REG_TIME = const(0x0A)
_REG_INT_STAT0 = const(0x0E)
_REG_INT_STAT1 = const(0x0F)
_REG_TEMP_DATA = const(0x11)
_REG_ACC_CONFIG0 = const(0x19)
_REG_ACC_CONFIG1 = const(0x1A)
_REG_ACC_CONFIG2 = const(0x1B)
_REG_INT_CONFIG0 = const(0x1F)
_REG_INT_CONFIG1 = const(0x20)
_G_RANGE = (2, 4, 8, 16)
_G_RANGE_CONFIG = {2:0x00, 4:0x40,
8:0x80, 16:0xC0}
_G_RANGE_SCALE = {2:1024, 4:512,
8:256, 16:128}
SLEEP = 0
LOW_POWER = 1
NORMAL = 2
_CMD_PWR_MODE = (0, 1, 2)
class BMA400:
def __init__(self, ADDR=0x14):
self.ADDR = ADDR
self.GRange = 4
self.Filter = 1
self.OSR = 3
self.ODR = 0x08
self._latchInt()
self._enableDataReady()
self.SetPowerMode(NORMAL)
# Sets the power mode. One of:
# SLEEP, LOW_POWER or NORMAL
def SetPowerMode(self, Mode=NORMAL):
if Mode not in (SLEEP, LOW_POWER, NORMAL):
self.Mode = NORMAL
else:
self.Mode = Mode
self._writeReg([_REG_ACC_CONFIG0, self.Mode])
self.SetGRange(self.GRange)
if self.Mode == NORMAL:
self.SetFilter(self.Filter)
elif self.Filter == 2:
self.SetFilter(1)
self.SetOSR(self.OSR)
self.SetODR(self.ODR)
sleep(80)
# Sets ODR (output data rate)
# for NORMAL mode.
def SetODR(self, ODR = 0x08):
if ODR not in range(0x05, 0x0c):
self.ODR = 0x08
else:
self.ODR = ODR
buf = self._readReg(_REG_ACC_CONFIG1, 1)
r = buf[0]
r = (r >> 4) << 4
r = r | self.ODR
self._writeReg([_REG_ACC_CONFIG1, r])
# Sets level of oversampling (OSR).
# Value of 0..3
def SetOSR(self, OSR=3):
if OSR not in range(4):
self.OSR = 3
else:
self.OSR = OSR
# Set OSR for NORMAL mode.
buf = self._readReg(_REG_ACC_CONFIG1, 1)
r = buf[0]
r = r & 0b11001111
r = r | (self.OSR << 4)
self._writeReg([_REG_ACC_CONFIG1, r])
# Set OSR for LOW_POWER mode.
buf = self._readReg(_REG_ACC_CONFIG0, 1)
r = buf[0]
r = r & 0b10011111
r =r | (self.OSR << 5)
self._writeReg([_REG_ACC_CONFIG0, r])
# Sets G range.
# One of 2, 4, 8, 16.
def SetGRange(self, Range=4):
if Range not in _G_RANGE:
self.GRange = 4
else:
self.GRange = Range
buf = self._readReg(_REG_ACC_CONFIG1, 1)
config = buf[0]
config = config & 0b00111111
r = self.GRange
config = config | _G_RANGE_CONFIG[r]
self._writeReg([_REG_ACC_CONFIG1, config])
# Sets bandwidth filtering for NORMAL mode.
# 1 : Variable ODR
# 2 : ODR = 100Hz, Low-pass bandwidth filter @ 1Hz
def SetFilter(self, Filter=1):
if Filter not in (1, 2):
self.Filter = 1
else:
self.Filter = Filter
if self.Filter == 1:
self._writeReg([_REG_ACC_CONFIG2, 0])
else:
self._writeReg([_REG_ACC_CONFIG2, 8])
sleep(20)
# *******************************************
# Properties
# *******************************************
# Returns X-axis, Y-axis, Z-axis acceleration
# in that order in a tuple in g units.
@property
def Reading(self):
buf = self._readReg(_REG_ACC_X, 6)
X_unscaled = buf[0] + buf[1] * 256
Y_unscaled = buf[2] + buf[3] * 256
Z_unscaled = buf[4] + buf[5] * 256
X = self._Scale(X_unscaled)
Y = self._Scale(Y_unscaled)
Z = self._Scale(Z_unscaled)
return (X, Y, Z)
# Returns X-axis acceleration in g units.
@property
def X(self):
buf = self._readReg(_REG_ACC_X, 2)
unscaled = buf[0] + buf[1] * 256
return self._Scale(unscaled)
# Returns Y-axis acceleration in g units.
@property
def Y(self):
buf = self._readReg(_REG_ACC_Y, 2)
unscaled = buf[0] + buf[1] * 256
return self._Scale(unscaled)
# Returns Z-axis acceleration in g units.
@property
def Z(self):
buf = self._readReg(_REG_ACC_Z, 2)
unscaled = buf[0] + buf[1] * 256
return self._Scale(unscaled)
# Returns temperature in degrees Celsius.
@property
def Temperature(self):
buf = self._readReg(_REG_TEMP_DATA, 1)
temp = buf[0]
if temp > 127:
temp -= 256
return temp * 0.5 + 23
# Returns Power mode.
# 0 : SLEEP
# 1 : LOW_POWER
# 2 : NORMAL
@property
def GetPowerMode(self):
return self.Mode
# Returns oversampling rate (OSR)
# Value of 0..3
@property
def GetOSR(self):
return self.OSR
# Returns G range.
# One of 2, 4, 8, 16.
@property
def GetGRange(self):
return self.GRange
# Returns ODR (output data rate)
# for NORMAL mode.
@property
def GetODR(self):
return self.ODR
# Returns the NORMAL mode filter setting.
# 0 : low bandwidth filtering
# 1 : high bandwidth filtering.
@property
def GetFilter(self):
return self.Filter
# Returns the chip's ID
@property
def ID(self):
id = self._readReg(_REG_CHIP_ID, 1)
return hex(id[0])
# Returns the system clock value.
# Milliseconds from last power-on or reset.
@property
def Time(self):
buf = self._readReg(_REG_TIME, 3)
ttemp = buf[0] + buf[1]*256 + buf[2]*65536
return int(ttemp * 0.3125)
# Returns True if there is a new
# acceleration data set ready.
@property
def IsDataReady(self):
buf = self._readReg(_REG_INT_STAT0, 1)
return (buf[0] & 0b10000000) == 0b10000000
# Returns inclination of X-axis from the
# horizontal in degrees. Offsets are
# subtracted if a calibration has been done.
@property
def Xangle(self):
t = self.Reading
X, Y, Z = t[0], t[1], t[2]
p = atan(X / sqrt((Y*Y + Z*Z))) * 180 / pi
return p
# Returns inclination of Y-axis from the
# horizontal in degrees. Offsets are
# subtracted if a calibration has been done.
@property
def Yangle(self):
t = self.Reading
X, Y, Z = t[0], t[1], t[2]
p = atan(Y / sqrt((X*X + Z*Z))) * 180 / pi
return p
# *******************************************
# Private Methods
# *******************************************
# Scales raw acceleration.
# Scaling is dependent on
# current G range set.
def _Scale(self, Raw):
raw = Raw
if raw > 2047:
raw -= 4096
scale = _G_RANGE_SCALE[self.GRange]
return raw / scale
# Enable interrupt latching
def _latchInt(self):
buf = self._readReg(_REG_INT_CONFIG1, 1)
r = buf[0]
r = r | 0b10000000
self._writeReg([_REG_INT_CONFIG1, r])
# Enable data ready interrupt
def _enableDataReady(self):
buf = self._readReg(_REG_INT_CONFIG0, 1)
r = buf[0]
r = r | 0b10000000
self._writeReg([_REG_INT_CONFIG0, r])
# Writes one or more bytes to register.
# Bytes is expected to be a list.
# First element is the register address.
def _writeReg(self, Bytes):
i2c.write(self.ADDR, bytes(Bytes))
# Read a given number of bytes from
# a register.
def _readReg(self, Reg, Num):
self._writeReg([Reg])
buf = i2c.read(self.ADDR, Num)
return buf
Comparing OSR Settings
Oversampling (OSR) is a common technique used to increase measurement accuracy. The BMA400 has four levels (0, 1, 2,3) of oversampling builtin and is user selectable.
Accuracy would be expected to increase as the OSR value is increased. The following program cycles through the four levels of OSR, calculating the X-axis angle multiple times at each OSR level. At each OSR level the average X-axis angle and the standard deviation is calculated.
As the OSR rate increases it would be expected that the standard deviation becomes smaller.
The Code:
# This program calculates an X-axis angle
# multiple times for each of the
# oversampling (OSR) settings.
# For each set of angle measurements per
# mode, the average and standard deviation
# is calculated.
from fc_bma400 import *
from math import sqrt
from microbit import sleep
Samples = 15
L = [' '] * Samples
def Avg(L):
# Returns the average (mean) of
# the elements of a list.
n = len(L)
sum = 0.0
for i in range(n):
sum += L[i]
return sum/n
def SD(L, avg):
# Returns the sample standard deviation
# of the elements of a list.
n = len(L)
sum = 0
for i in range(n):
diff = (L[i] - avg) ** 2
sum += diff
return sqrt(sum / (n-1))
# main program
sensor = BMA400()
sensor.SetGRange(2)
sensor.SetODR(8) # 100Hz
sleep(200)
dummy = sensor.Reading # Clear the data registers.
# Cycle through the OSR settings.
# For each OSR collect some samples and
# calculate average and standard deviation.
for osr in range(4):
sensor.SetOSR(osr)
for s in range(Samples):
sleep(200)
L[s] = sensor.Xangle
avg = round(Avg(L), 3)
sd = round(SD(L, avg), 3)
print('OSR =', osr, ' Angle =', avg,
' SD =', sd)
Typical Output:
OSR = 0 Angle = 16.129 SD = 0.19
OSR = 1 Angle = 16.588 SD = 0.167
OSR = 2 Angle = 15.629 SD = 0.106
OSR = 3 Angle = 16.276 SD = 0.079
As expected, the standard deviation does trend smaller as the oversampling rate increases. This indeed does indicate that greater accuracy can be expected at higher oversampling rates.