MicroPython Driver for ADXL345 Accelerometer

ADXL345 Driver Code for micro:bit

Download as zip file

'''
ADXL345 3-Axis Accelerometer
MicroPython driver for BBC micro:bit

AUTHOR: fredscave.com
DATE  : 2025/08
VERSION : 1.00
'''

from microbit import *
from micropython import const
from math import atan, sqrt, pi

_REG_ID          = const(0x00)
_REG_DATA_X      = const(0x32)
_REG_DATA_Y      = const(0x34)
_REG_DATA_Z      = const(0x36)
_REG_BW_RATE     = const(0x2c)
_REG_POWER_CTL   = const(0x2D)
_REG_DATA_FORMAT = const(0x31)
_REG_INT_SOURCE  = const(0x30)

# Measure bit is On.
_POWER_CTL_BYTE = const(0x08)

# Ranges (+/-g)
_RANGES = {2:0, 4:1, 8:2, 16:3}
# Sensitivity (LSB/g) for all ranges.
_SENSITIVITY = 0.0039
# Mask out unwanted bits on data MSB.
_MSB_MASK = (0b11, 0b111, 0b1111, 0b11111)

class ADXL345():
    def __init__(self, ADDR=0x53):
        self.ADDR = ADDR
        # Set full resolution mode
        self._writeReg([_REG_DATA_FORMAT, 0b1000])
        # Default range (+/-2g)
        self.SetGRange()
        # Turn acceleration sampling on.
        self._writeReg([_REG_POWER_CTL, _POWER_CTL_BYTE])
        # Set Normal power mode.
        # Default Output Data Rate (ODR=10) is 100Hz
        self.SetODR(10)

    # Sets Output Data Rate (ODR).
    # Must be a value: 0 to 11
    # See Table 7 in Datasheet for frequency equivalents.
    def SetODR(self, ODR=10):
        self.ODR = ODR if ODR in range(12) else 10
        self._writeReg([_REG_BW_RATE, self.ODR])
        dummy = self.Reading

    # Sets the range for acceleration sampling (+/-g).
    # Must be one of: 2, 4, 8, 16
    def SetGRange(self, Range=2):
        if Range not in (2, 4, 8, 16):
            self.Range = 2
        else:
            self.Range = Range
        buf = self._readReg(_REG_DATA_FORMAT, 1)
        r = buf[0]
        r = r & 0b11111100
        r = r | _RANGES[self.Range]
        self._writeReg([_REG_DATA_FORMAT, r])
        sleep(10)

# *******************************************
#              Properties
# *******************************************

    # Returns product ID.
    @property
    def GetID(self):
        buf = self._readReg(_REG_ID, 1)
        return hex(buf[0])

    # 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_DATA_X, 6)
       X_unscaled = (buf[1] & _MSB_MASK[_RANGES[self.Range]]) << 8 | buf[0]
       Y_unscaled = (buf[3] & _MSB_MASK[_RANGES[self.Range]]) << 8 | buf[2]
       Z_unscaled = (buf[5] & _MSB_MASK[_RANGES[self.Range]]) << 8 | buf[4]
       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_DATA_X, 2)
        unscaled = (buf[1] & _MSB_MASK[_RANGES[self.Range]]) << 8 | buf[0]
        return self._Scale(unscaled)

    # Returns Y-axis acceleration in g units.
    @property
    def Y(self):
        buf = self._readReg(_REG_DATA_Y, 2)
        unscaled = (buf[1] & _MSB_MASK[_RANGES[self.Range]]) << 8 | buf[0]
        return self._Scale(unscaled)

    # Returns Z-axis acceleration in g units.
    @property
    def Z(self):
        buf = self._readReg(_REG_DATA_Z, 2)
        unscaled = (buf[1] & _MSB_MASK[_RANGES[self.Range]]) << 8 | buf[0]
        return self._Scale(unscaled)

    @property
    def IsDataReady(self):
        buf = self._readReg(_REG_INT_SOURCE, 1)
        b = buf[0]
        return (b & 0b10000000) != 0

    # Returns Output Data Rate (ODR).
    # Value in range of 0 to 11
    @property
    def GetODR(self):
        return self.ODR

    # Returns the acceleration sampling range (+/-g).
    # One of 2, 4, 8, 16
    @property
    def GetGRange(self):
        return self.Range

    # 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
# *******************************************

    # Scales raw acceleration.
    # Scaling is dependent on
    # current G range set.
    def _Scale(self, Raw):
        raw = Raw
        res = _RANGES[self.Range]
        max = 2 ** (res + 10)
        if raw > max /2 - 1:
            raw -= max
        return raw * _SENSITIVITY

    # 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
          

Comparison of Angle Measurements

This project will measure a tilt angle in the X-axis and compare the results with that obtained from a commercial inclinometer. The inclinometer has a magnetic base which easily mounts on a breadboard.

The image below shows the setup used.


Code:

# This program measures a tilt angle (X-axis)
# using an ADXL345 accelerometer mounted on
# a breadboard.

# A commercial inclinometer can be mounted on
# the breadboard inline with the sensor as a
# check on the accuracy of the ADXL345's
# measurement.

from fc_adxl345 import *

sensor = ADXL345()

# Endless loop:
# Pressing Button A on the micro:bit
# returns the X-axis angle.
while True:
    print('\nPress Button A to calculate the angle')
    while not button_a.was_pressed():
        sleep(100)
    print('Tilt in X-axis:', sensor.Xangle, 'degrees')

Typical Output:
Press Button A to calculate the angle
Tilt in X-axis: 11.24113 degrees

Press Button A to calculate the angle
Traceback (most recent call last):
  File "main.py", line 20, in <module>
KeyboardInterrupt:

Inclinometer and ADXL345 accelerometer measuring X-axis angle.
Inclinometer and ADXL345 accelerometer measuring X-axis tilt angle

The ADXL345 read the angle as 11.24 degrees which agrees quite well with the inclinometer's reading of 11.75 degrees.

It needs to be pointed out that the setup for this test is somewhat crude. However it does indicate that even a quite simple accelerometer as the ADXL345 is most likely able to determine tilt angles with a reasonable degree of accuracy.