MicroPython Driver for LIS2DW12 Accelerometer
Contents
Introduction
This a MicroPython driver written specifically for the BBC micro:bit that will work with the LIS2DW12 accelerometer sensor.
The LIS2DW12 accelerometer is discussed in some detail here.
FIG 1 - LIS2DW12 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 LIS2DW12
This sensor has both I2C and SPI serial interfaces available. This driver will use I2C. Connecting the breakout board to the micro:bit is simple.
| micro:bit | Sensor board |
|---|---|
| 3.3V | VCC |
| GND | GND |
| Pin 19 | SCL |
| Pin 20 | SDA |
This utilises the standard I2C pins of the micro:bit.
Optionally, a jumper wire can be placed between the GND pin and the SDO pin on the breakout board. This changes the default I2C address and is discussed below.
Driver Overview
Features of this driver include:
- Output data rate (ODR) and measurement range (full scale) are user selectable.
- All power modes are available.
- The low noise filter can be turned on/off in any power mode.
- Acceleration readings in any/all axis with Data Ready property signalling when fresh values are available.
- Temperature reading (8-bit only) is available.
- A simple tool for measuring the angle to the horizontal in both X and Y axis.
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_lis2dw12.py - OR -
- Download as a zip file using the link. Unzip the file and save it as fc_lis2dw12.py into the default directory where the MicroPython editor e.g. Mu Editor saves python code files.
After saving the fc_lis2dw12.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 addressLIS2DW12 breakout boards have a default I2C address of 0x19. This can be changed to 0x18 if a jumper wire is connected between the GND pin and SDO pin.
Class constructorThe driver is implemented as a class.The first thing to do is call the constructor of the LIS2DW12 class to obtain a sensor object.
Syntax:
LIS2DW12(ADDR=0x19)
Where:
ADDR is the I2C address.
Example
from fc_lis2dw12 import *
# Declare a LIS2DW12 sensor object.
# The default I2C address is used.
sensor = LIS2DW12()
This assumes that the file fc_lis2dw12.py has been successfully copied to the micro:bit's filesystem as described above.
Methods and PropertiesThe driver provides methods and properties:
-
Power Mode settings:
SetPowerMode(), GetPowerMode -
Setup sampling parameters:
SetODR(), GetODR
SetGRange(), GetGRange
SetLowNoise(), IsLowNoise -
Read acceleration:
Reading, X, Y, Z, IsDataReady -
Read temperature:
Temperature -
Angle measurement:
Xangle, Yangle -
Chip ID:
GetID
Power Modes
There are six possible power modes and this driver implements all of them:
- Power off : No sampling
- Low power : four modes
- Low-power mode 1 : 12-bit resolution
- Low-power modes 2, 3, 4 : 14-bit resolution
- High-performance : 14-bit precision
The power modes are a tradeoff between power consumption, resolution and signal noise. See Table 7 (page 6) of Application Note AN5038 for the LIS2DW12.
Syntax:
SetPowerMode(Mode = High)
Method that sets the sampling power mode;
LP1, LP2, LP3, LP4, or HIGH
GetPowerMode
Method that returns the sampling power mode.
0 (LP1), 1 (LP2), 2 (LP3), 3 (LP4) or 4 (HIGH)
Example
# Test all power modes of the LIS2DW12.
from fc_lis2dw12 import *
from microbit import sleep
sensor = LIS2DW12()
Modes = {LP1:'LP1', LP2:'LP2', LP3:'LP3',
LP4:'LP4', HIGH:'HIGH'}
# Cycle through all the power modes.
for m in Modes:
sensor.SetPowerMode(m)
sleep(100)
print('Now in Power mode:',
Modes[sensor.GetPowerMode])
Output
Now in Power mode: LP1
Now in Power mode: LP2
Now in Power mode: LP3
Now in Power mode: LP4
Now in Power mode: HIGH
Sampling Options
The user has the option to set the output data rate (ODR), measurement range (full scale) and the low noise filter.
Syntax:
SetODR(ODR = 5)
Sets the output data rate (ODR).
One of 0 to 9.
A value of 0 powers down the accelerometer.
Value 1 to 9 are 1.6Hz to 1600Hz and
depend upon power mode.
Default (ODR=5) is 100Hz (all power modes).
See Table 29 (page 36) in the datasheet.
GetODR
Property that returns the ODR in use.
One of 0 to 9.
SetGRange(Range=2)
Sets the measurement range (+/-g).
One of 2, 4, 8, 16.
GetGRange
Property that returns the measurement range in use (+/-g).
One of 2, 4, 8, 16.
SetLowNoise(On=True)
Turn the low noise filter on or off.
IsLowNoise
Property that returns True if the low noise filter is on.
Example
# Test ODR, measurement range
# and low noise filter settings.
from fc_lis2dw12 import *
sensor = LIS2DW12()
print('DEFAULTS...')
print('Default ODR:', sensor.GetODR)
print('Default measurement range:', sensor.GetGRange)
print('Default low noise filter on:', sensor.IsLowNoise)
# Change the above defaults.
sensor.SetODR(9)
sensor.SetGRange(8)
sensor.SetLowNoise(False)
# Report new settings
print('\nNOW...')
print('ODR:', sensor.GetODR)
print('Measurement range:', sensor.GetGRange)
print('Low noise filter on:', sensor.IsLowNoise)
Output:
DEFAULTS...
Default ODR: 5
Default measurement range: 2
Default low noise filter on: True
NOW...
ODR: 9
Measurement range: 8
Low noise filter on: False
Measuring Acceleration
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_lis2dw12 import *
sensor = LIS2DW12()
# Read acceleration of all three axis.
acc = sensor.Reading
print('(X-axis, Y-axis, Z-axis):', acc)
# Read acceleration separately from each axis.
# Wait till data is ready.
while not sensor.IsDataReady:
sleep(1)
print('\nX-axis:', sensor.X)
print('Y-axis:', sensor.Y)
print('Z-axis:', sensor.Z)
Typical Output:
(X-axis, Y-axis, Z-axis): (0.023912, -0.049288, -0.992836)
X-axis: 0.024644
Y-axis: -0.051728
Z-axis: -0.99796
In the above example the sensor was lying on a flat horizontal surface (mounted in a breadboard). In this orientation the X and Y axis acceleration should be very close to 0g and the Z axis acceleration should be close to 1g (from the Earth's gravity).
Measuring Temperature
This driver reads the temperature sensor using only an 8-bit precision. The temperature reading produced should only be taken as indicative - definitely not for any serious process control!
Syntax:
Temperature
A property that returns the temperature
in degrees Celsius.
Example:
from fc_lis2dw12 import *
sensor = LIS2DW12()
print('Temperature:', sensor.Temperature, 'C')
Typical Output:
Temperature: 21 C
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.
Most breakout boards containing alternative accelerometers to the LIS2DW12 have the X-axis parallel to the row of board pins. Interestingly, it was the Y-axis that was parallel to the row of pins on the LIS2DW12 breakout board we used.
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
# Y-axis angle from the horizontal.
# While the program is running change
# the Y-axis tilt angle to output different
# angle values.
from fc_lis2dw12 import *
from microbit import sleep
sensor = LIS2DW12()
# Calculate the Y-axis angle every two seconds.
# Terminate the program in the REPL with
# Ctrl + C on Windows or Command + C on Mac.
while True:
sleep(2000)
print(sensor.Yangle)
Typical Output:
-3.796349
15.11892
13.08353
38.6883
50.30121
61.32842
8.372408
1.982739
-2.937516
Traceback (most recent call last):
File "main.py", line 17, in <module>
KeyboardInterrupt:
In the above example the board was tilted at various angles between the two second reads.
Product ID
The LIS2DW12 has a product ID burnt into non-volatile memory at time of manufacture. This driver provides a simple property to read this ID.
Syntax:
GetID
Returns the product ID
Example:
from fc_lis2dw12 import *
sensor = LIS2DW12()
print('LIS2DW12 product ID:', sensor.GetID)
Output:
LIS2DW12 product ID: 0x44
Enjoy!
LIS2DW12 Driver Code for micro:bit
Download as zip file
'''
LIS2DW12 3-Axis Accelerometer
MicroPython driver for BBC micro:bit
AUTHOR: fredscave.com
DATE : 2025/10
VERSION : 1.00
'''
from microbit import *
from micropython import const
from math import atan, sqrt, pi
_REG_WHO_AM_I = const(0x0F)
_REG_CTRL1 = const(0x20)
_REG_CTRL2 = const(0x21)
_REG_CTRL6 = const(0x25)
_REG_OUT_T = const(0x26)
_REG_STATUS = const(0x27)
_REG_OUT_X = const(0x28)
_REG_OUT_Y = const(0x2A)
_REG_OUT_Z = const(0x2C)
FS = (2, 4, 8, 16)
LP1, LP2, LP3, LP4, HIGH = 0, 1, 2, 3, 4
SENSITIVITY = (0.244, 0.488, 0.976, 1.952)
class LIS2DW12():
def __init__(self, ADDR = 0x19):
self.ADDR = ADDR
# Measurement not updated while
# data registers are being read.
# Increment register pointer after
# each read or write.
self._writeReg([_REG_CTRL2, 0b00001100])
self.SetGRange()
self.SetLowNoise()
self.SetODR()
self.SetPowerMode()
# Turns low noise mode on or off.
def SetLowNoise(self, On = True):
self.LowNoise = On
buf = self._readReg(_REG_CTRL6, 1)
b = buf[0]
if self.LowNoise == True:
b= b | 0b00000100
else:
b = b & 0b11111000
self._writeReg([_REG_CTRL6, b])
# Sets the measurement range.
# One of 2, 4, 8 or 16.
def SetGRange(self, Range=2):
if Range not in FS:
self.Range = 2
else:
self.Range = Range
buf = self._readReg(_REG_CTRL6, 1)
b = buf[0]
b = b & 0b11001100
b = b | (FS.index(self.Range) << 4)
self._writeReg([_REG_CTRL6, b])
# Sets the output data rate (ODR).
# One of 0 to 9.
# A setting of 0 powers down the LIS2DW12.
# Default ODR = 5 is 100Hz.
# See Table 29 of the datasheet for Hz values.
def SetODR(self, ODR = 5):
if ODR not in range(10):
self.ODR = 5
else:
self.ODR = ODR
buf = self._readReg(_REG_CTRL1, 1)
b = buf[0]
b = b & 0b00001111
b = b | (self.ODR << 4)
self._writeReg([_REG_CTRL1, b])
# Set power mode.
# One of LP1, LP2, LP3, LP4 or HIGH.
# See Table 30 and Table 31
# of the datasheet for meaning.
def SetPowerMode(self, Mode=HIGH):
if Mode not in (LP1, LP2, LP3, LP4, HIGH):
self.Mode = HIGH
else:
self.Mode = Mode
buf = self._readReg(_REG_CTRL1, 1)
b = buf[0]
b = b & 0b11110000
if self.Mode == HIGH:
b = b | 0b00000100
else:
b = b | self.Mode
self._writeReg([_REG_CTRL1, b])
# *******************************************
# 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_OUT_X, 6)
unscaledX = self._rawToInt(buf[1], buf[0])
unscaledY = self._rawToInt(buf[3], buf[2])
unscaledZ = self._rawToInt(buf[5], buf[4])
X = self._scale(unscaledX)
Y = self._scale(unscaledY)
Z = self._scale(unscaledZ)
return (X, Y, Z)
# Return acceleration on the X-axis.
@property
def X(self):
buf = self._readReg(_REG_OUT_X, 2)
unscaled = self._rawToInt(buf[1], buf[0])
return self._scale(unscaled)
# Return acceleration on the Y-axis.
@property
def Y(self):
buf = self._readReg(_REG_OUT_Y, 2)
unscaled = self._rawToInt(buf[1], buf[0])
return self._scale(unscaled)
# Return acceleration on the Z-axis.
@property
def Z(self):
buf = self._readReg(_REG_OUT_Z, 2)
unscaled = self._rawToInt(buf[1], buf[0])
return self._scale(unscaled)
# Returns the temperature.
# This is only 8-bit resolution.
@property
def Temperature(self):
buf = self._readReg(_REG_OUT_T, 1)
b = buf[0]
b = b if (b < 128) else b - 256
return b + 25
# Returns the output data rate (ODR).
# See Table 29 of the datasheet for
# explanation of values.
@property
def GetODR(self):
return self.ODR
# Returns measurement range.
# one of 2, 4, 8 or 16.
@property
def GetGRange(self):
return self.Range
# Returns the LIS2DW12 chip ID.
# Should be 0x44
@property
def GetID(self):
buf = self._readReg(_REG_WHO_AM_I, 1)
return(hex(buf[0]))
# Returns whether low noise mode
# is on or off.
@property
def IsLowNoise(self):
return self.LowNoise
# Returns the power mode.
# One 0f 0=LP1, 1=LP2, 2=LP3, 3=LP4, 4=HIGH.
@property
def GetPowerMode(self):
return self.Mode
# Returns True if there is valid acceleration
# ready for reading.
@property
def IsDataReady(self):
buf = self._readReg(_REG_STATUS, 1)
return (buf[0] & 0b00000001) == 1
# Returns inclination of X-axis from the
# horizontal in degrees.
@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.
@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
# *******************************************
# 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
# Scale raw acceleration value.
# The scale depends upon which power mode
# and measurement range is configured.
def _scale(self, unscaled):
sensitivity = SENSITIVITY[FS.index(self.Range)]
if self.Mode == LP1:
sensitivity *= 4
return unscaled * sensitivity / 1000
# Converts raw sensor binary value
# to signed integer.
def _rawToInt(self, MSB, LSB):
if self.Mode != LP1: # 14-bit resolution
Int = (MSB << 6) | (LSB >> 2)
return Int if (Int < 8192) else Int - 16384
else: # 12-bit resolution
Int = (MSB << 4) | (LSB >> 4)
return Int if (Int < 2048) else Int - 4096
Exploring the Power Modes
The LIS2DW12 offers five different power modes; 4 x Low-power and High-performance. There is a tradeoff with power consumption, precision and signal noise across these modes.
The following program demonstrates the expected increase in accuracy (and power consumption) with increasing precision and reducing signal noise across the power modes. The LIS2DW12 development board was set at a random angle before running the code in the micro:bit.
Code:
# This program calculates an Y-axis angle
# multiple times for each of the power modes.
# For each set of angle measurements per
# mode, the average and standard deviation
# is calculated.
from fc_lis2dw12 import *
from math import sqrt
from microbit import sleep
Samples = 10
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 = LIS2DW12()
sensor.Reading # Clear the data registers.
L = [' '] * Samples
Modes = {LP1:'LP1', LP2:'LP2', LP3:'LP3',
LP4:'LP4', HIGH:'HIGH'}
# Cycle through the modes.
# In each mode collect some samples and
# calculate average and standard deviation.
for m in Modes:
sensor.SetPowerMode(m)
for s in range(Samples):
while not sensor.IsDataReady:
sleep(1)
L[s] = sensor.Yangle
avg = round(Avg(L), 3)
sd = round(SD(L, avg), 3)
print(Modes[m],
' Angle =', avg, ' SD =', sd)
Typical Output:
LP1 Angle = 4.193 SD = 0.515
LP2 Angle = 3.892 SD = 0.371
LP3 Angle = 4.111 SD = 0.285
LP4 Angle = 4.173 SD = 0.248
HIGH Angle = 4.081 SD = 0.105
As the power mode moves up from low-power (LP1) to High-performance the standard deviation dramatically decreases as would be expected.