MicroPython Driver for LIS3DSH Accelerometer
Contents
Introduction
This a MicroPython driver written specifically for the BBC micro:bit that will work with the LIS3DSH accelerometer sensor.
The LIS3DSH sensor is discussed in some detail here.
FIG 1 - LIS3DSH breakout board: [Left] front view, [Right] rear view
There are online reports of confusion between the LIS3DSH and the LIS3DH - another accelerometer sensor available from STMicroelectronics. Occasionally the buyer will receive the LIS3DH breakout board after ordering the LIS3DSH board online.
This leads to some confusion as driver software is not common between the two sensors. This article describes how to tell the difference between the two sensors.
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 LIS3DSH
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
The driver implements the base functionality (i.e. acceleration measurements) offered by this sensor. Also included is a simple tool for measuring angles from the horizontal.
The temperature can be read from the chip's internal temperature sensor.
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_lis3dsh.py - OR -
- Download as a zip file using the link. Unzip the file and save it as fc_lis3dsh.py into the default directory where the MicroPython editor e.g. Mu Editor saves python code files.
After saving the fc_lis3dsh.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 addressLIS3DSH breakout boards have a default I2C address of 0x1D. This can be changed to 0x1E 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 LIS3DSH class to obtain a sensor object.
Syntax:
LIS3DSH(ADDR=0x1D)
Where:
ADDR is the I2C address.
Example
from fc_lis3dsh import *
# Declare a LIS3DSH sensor object.
# The default I2C address is used.
sensor = LIS3DSH()
This assumes that the file fc_lis3dsh.py has been successfully copied to the micro:bit's filesystem as described above.
Methods and PropertiesThe driver provides methods and properties:
-
Setup sampling parameters:
SetODR(), GetODR
SetGRange(), GetGRange -
Read acceleration:
Reading, X, Y, Z, IsDataReady -
Read temperature:
Temperature -
Angle measurement:
Xangle, Yangle -
Chip ID:
ID
Sampling Options
The user has the option to set the output data rate (ODR) and measurement range.
Syntax:
SetODR(ODR = 6)
Sets the output data rate (ODR).
One of 0 to 9.
This provides a range from 3.125Hz to 1600Hz.
Default is 100Hz.
An ODR = 0 powers down the LIS3DSH.
See Table 55 (page 39) in the datasheet.
GetODR
Returns the ODR in use.
One of 0 to 9.
SetGRange(Range=2)
Sets the measurement range.
One of 2, 4, 6, 8, 16.
GetGRange
Returns the measurement range in use.
One of 2, 4, 6, 8, 16.
Example:
from fc_lis3dsh import *
# Declare a LIS3DSH sensor object
# The I2C address is 0x1D (default).
sensor = LIS3DSH()
# Report the default sampling options.
odr_freq = ('Power down', 3.125, 6.25, 12.5,
25, 50, 100, 400, 800, 1600)
odr = sensor.GetODR
print('ODR:', odr_freq[odr], 'Hz')
print('Range: +/-', sensor.GetGRange, 'g')
# Change ODR and measurement range.
sensor.SetODR(9) # 1600Hz
sensor.SetGRange(4) # +/-4g
print('\nNew settings...')
odr = sensor.GetODR
print('ODR:', odr_freq[odr], 'Hz')
print('Range: +/-', sensor.GetGRange, 'g')
Output:
ODR: 100 Hz
Range: +/- 2 g
New settings...
ODR: 1600 Hz
Range: +/- 4 g
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_lis3dsh import *
# Declare a LIS3DSH sensor object
sensor = LIS3DSH()
# 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.0192, -0.00816, 0.9906599)
X-axis: -0.03671999
Y-axis: -0.01374
Z-axis: 0.98742
Measuring Temperature
Syntax:
Temperature
A property that returns the temperature
in degrees Celsius.
Example:
from fc_lis3dsh import *
sensor = LIS3DSH()
print('Temperature:', sensor.Temperature, 'C')
Typical Output:
Temperature: 23 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.
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.
# While the program is running change
# the X-axis tilt angle to output different
# angle values.
from fc_lis3dsh import *
from microbit import sleep
sensor = LIS3DSH()
# Calculate the X-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.Xangle)
Typical Output:
-2.748142
21.14199
19.29456
34.68194
44.4342
54.04536
66.04021
7.700032
-2.68688
-2.656803
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 LIS3DSH 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_lis3dsh import *
sensor = LIS3DSH()
print('LIS3DSH product ID:', sensor.GetID)
Output:
LIS3DSH product ID: 0x3f
Enjoy!
LIS3DSH Driver Code for micro:bit
Download as zip file
'''LIS3DSH 3-Axis Accelerometer
MicroPython driver for BBC micro:bit
AUTHOR: fredscave.com
DATE : 2025/09
VERSION : 1.00
'''
from microbit import *
from micropython import const
from math import atan, sqrt, pi
_REG_OUT_T = const(0x0C)
_REG_WHO_AM_I = const(0x0F)
_REG_CTRL_REG4 = const(0x20)
_REG_CTRL_REG5 = const(0x24)
_REG_STATUS = const(0x27)
_REG_OUT_X = const(0x28)
_REG_OUT_Y = const(0x2A)
_REG_OUT_Z = const(0x2C)
# Measurement ranges configurations
_RANGES = {2:0, 4:1, 6:2, 8:3, 16:4}
_G_RANGE_MASK = (0x00, 0x08, 0x10, 0x18, 0x20)
_SENSITIVITY = (0.06, 0.12, 0.18, 0.24, 0.73)
class LIS3DSH():
def __init__(self, ADDR=0x1D):
self.ADDR = ADDR
sleep(10)
# Disable sensor output updates while
# reading acceleration registers.
self._blockDataUpdate
self.SetGRange(2)
# Set ODR = 100Hz, turn on sampling.
self.SetODR()
# Sets the acceleration +/- measurement range.
# Valid values are 2, 4, 6, 8, 16.
def SetGRange(self, Range=2):
if Range not in (2, 4, 6, 8, 16):
self.Range = 2
else:
self.Range = Range
buf = self._readReg(_REG_CTRL_REG5, 1)
b = buf[0]
b = b & 0b11000000
b = b | _G_RANGE_MASK[_RANGES[self.Range]]
self._writeReg([_REG_CTRL_REG5, b])
sleep(10)
dummy = self.Reading
# Sets the output data rate (ODR).
# Valid values are 0 to 9.
# A value of 0 powers down the LIS3DSH.
# See Table 55 (page 39) of the datasheet
# for the equivalent sampling frequencies.
def SetODR(self, ODR=6):
self.ODR = ODR if (ODR in range(10)) else 6
buf = self._readReg(_REG_CTRL_REG4, 1)
b = buf[0]
b = b & 0b00001111
b = b | (self.ODR << 4)
self._writeReg([_REG_CTRL_REG4, b])
dummy = self.Reading
# *******************************************
# 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)
X_unscaled = buf[1] << 8 | buf[0]
Y_unscaled = buf[3] << 8 | buf[2]
Z_unscaled = buf[5] << 8 | buf[4]
X = self._Scale(X_unscaled)
Y = self._Scale(Y_unscaled)
Z = self._Scale(Z_unscaled)
return (X, Y, Z)
@property
# Returns X-axis acceleration in g units.
def X(self):
buf = self._readReg(_REG_OUT_X, 2)
unscaled = buf[1] << 8 | buf[0]
return self._Scale(unscaled)
@property
# Returns Y-axis acceleration in g units.
def Y(self):
buf = self._readReg(_REG_OUT_Y, 2)
unscaled = buf[1] << 8 | buf[0]
return self._Scale(unscaled)
@property
# Returns Z-axis acceleration in g units.
def Z(self):
buf = self._readReg(_REG_OUT_Z, 2)
unscaled = buf[1] << 8 | buf[0]
return self._Scale(unscaled)
@property
def GetGRange(self):
return self.Range
@property
def GetODR(self):
return self.ODR
# Returns the chip's ID
@property
def GetID(self):
id = self._readReg(_REG_WHO_AM_I, 1)
return hex(id[0])
# Returns True if there is valid acceleration
# ready for reading.
@property
def IsDataReady(self):
buf = self._readReg(_REG_STATUS, 1)
b = buf[0]
return (b & 0b00001000) != 0
# Returns temperature in degrees Celsius.
@property
def Temperature(self):
buf = self._readReg(_REG_OUT_T, 1)
t = buf[0]
t = t if (t < 128) else t - 256
return t + 25
# 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
# *******************************************
# 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
# Ensures that while acceleration result registers
# are being read they won't be updated by the sensor.
def _blockDataUpdate(self):
buf = self._readReg(_REG_CTRL_REG4, 1)
b = buf[0]
b = b | 0b00001000
self._writeReg([_REG_CTRL_REG4, b])
# Scales raw acceleration.
# Scaling is dependent on
# current G range set.
def _Scale(self, Raw):
raw = Raw
if raw > 32767:
raw -= 65536
scale = _SENSITIVITY[_RANGES[self.Range]]
return raw * scale/1000
Selecting the Correct Range
The LIS3DSH offers five different measurement ranges. It's important to select the appropriate range for the expected acceleration values.
If an inappropriate range is selected then there will either be a loss of precision or the acceleration value will be 'saturated' (out of range).
For example: if acceleration values in the order of 2g to 3g are expected but the measurement range ±16g is being used there can be quite a loss of precision. In this case a more appropriate measurement range would be ±4g.
The following program demonstrates this expected loss of precision if the measurement range is too large. The LIS3DSH development board was set a random angle before running the code in the micro:bit.
Code:
# This program calculates an X-axis angle
# multiple times for each of the measurement
# ranges.
# For each set of angle measurements per
# range, the average and standard deviation
# is calculated.
from fc_lis3dsh 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 = LIS3DSH()
sensor.SetGRange(2)
sensor.SetODR(1) # 6.25Hz
L = [' '] * Samples
gRanges = (2, 4, 6, 8, 16)
for r in range(len(gRanges)):
sensor.SetGRange(gRanges[r])
for a in range(Samples):
while not sensor.IsDataReady:
sleep(5)
L[a] = sensor.Xangle
avg = round(Avg(L), 3)
sd = round(SD(L, avg), 3)
print('Range (+/-g):', sensor.GetGRange,
' Angle:', avg, ' SD', sd)
Typical Output:
Range (+/-g): 2 Angle: 32.321 SD 0.092
Range (+/-g): 4 Angle: 32.476 SD 0.104
Range (+/-g): 6 Angle: 32.363 SD 0.135
Range (+/-g): 8 Angle: 32.511 SD 0.144
Range (+/-g): 16 Angle: 32.392 SD 0.335
As the measurement range is increased so does the standard deviation of the set of acceleration readings taken. This indicates an increasing loss of precision as the measurement range increases.
Interestingly, the angle value also increased with increasing measuring range. The breadboard holding the LIS3DSH was not touched, so the actual angle remained unaltered.
The above code was run several times with consistently the same trend. This would suggest that the sensitivity constants (_SENSITIVITY tuple in the driver code) used in the acceleration calculation need calibration if truly accurate acceleration values are required.